feat: Major directory reorganization and cleanup

Reorganized project structure for better maintainability and reduced
disk usage by 95.9% (11 GB -> 451 MB).

Directory Reorganization (85% reduction in root files):
- Created docs/ with subdirectories (deployment, testing, database, etc.)
- Created infrastructure/vpn-configs/ for VPN scripts
- Moved 90+ files from root to organized locations
- Archived obsolete documentation (context system, offline mode, zombie debugging)
- Moved all test files to tests/ directory
- Root directory: 119 files -> 18 files

Disk Cleanup (10.55 GB recovered):
- Deleted Rust build artifacts: 9.6 GB (target/ directories)
- Deleted Python virtual environments: 161 MB (venv/ directories)
- Deleted Python cache: 50 KB (__pycache__/)

New Structure:
- docs/ - All documentation organized by category
- docs/archives/ - Obsolete but preserved documentation
- infrastructure/ - VPN configs and SSH setup
- tests/ - All test files consolidated
- logs/ - Ready for future logs

Benefits:
- Cleaner root directory (18 vs 119 files)
- Logical organization of documentation
- 95.9% disk space reduction
- Faster navigation and discovery
- Better portability (build artifacts excluded)

Build artifacts can be regenerated:
- Rust: cargo build --release (5-15 min per project)
- Python: pip install -r requirements.txt (2-3 min)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-18 20:42:28 -07:00
parent 89e5118306
commit 06f7617718
96 changed files with 54 additions and 2639 deletions

View File

@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""
Check record counts in all ClaudeTools database tables
"""
import sys
from sqlalchemy import create_engine, text, inspect
# Database connection
DATABASE_URL = "mysql+pymysql://claudetools:CT_e8fcd5a3952030a79ed6debae6c954ed@172.16.3.30:3306/claudetools?charset=utf8mb4"
def get_table_counts():
"""Get row counts for all tables"""
engine = create_engine(DATABASE_URL)
with engine.connect() as conn:
# Get all table names
inspector = inspect(engine)
tables = inspector.get_table_names()
print("=" * 70)
print("ClaudeTools Database Record Counts")
print("=" * 70)
print(f"Database: claudetools @ 172.16.3.30:3306")
print(f"Total Tables: {len(tables)}")
print("=" * 70)
print()
# Count rows in each table
counts = {}
total_records = 0
for table in sorted(tables):
result = conn.execute(text(f"SELECT COUNT(*) FROM `{table}`"))
count = result.scalar()
counts[table] = count
total_records += count
# Group by category
categories = {
'Core': ['machines', 'clients', 'projects', 'sessions', 'tags'],
'MSP Work': ['work_items', 'tasks', 'billable_time', 'work_item_files'],
'Infrastructure': ['sites', 'infrastructure', 'services', 'networks', 'firewall_rules', 'm365_tenants', 'm365_licenses'],
'Credentials': ['credentials', 'credential_audit_logs', 'security_incidents'],
'Context Recall': ['conversation_contexts', 'context_snippets', 'project_states', 'decision_logs'],
'Learning': ['command_runs', 'file_changes', 'problem_solutions', 'failure_patterns', 'environmental_insights'],
'Integrations': ['msp_integrations', 'backup_jobs', 'backup_reports'],
'Junction': ['session_tags', 'session_work_items', 'client_contacts', 'project_repositories']
}
# Print by category
for category, table_list in categories.items():
category_tables = [t for t in table_list if t in counts]
if not category_tables:
continue
print(f"{category}:")
print("-" * 70)
category_total = 0
for table in category_tables:
count = counts[table]
category_total += count
status = "[OK]" if count > 0 else " "
print(f" {status} {table:.<50} {count:>10,}")
print(f" {'Subtotal':.<50} {category_total:>10,}")
print()
# Print any uncategorized tables
all_categorized = set()
for table_list in categories.values():
all_categorized.update(table_list)
uncategorized = [t for t in counts.keys() if t not in all_categorized]
if uncategorized:
print("Other Tables:")
print("-" * 70)
for table in uncategorized:
count = counts[table]
status = "[OK]" if count > 0 else " "
print(f" {status} {table:.<50} {count:>10,}")
print()
# Print summary
print("=" * 70)
print(f"TOTAL RECORDS: {total_records:,}")
print(f"Tables with data: {sum(1 for c in counts.values() if c > 0)}/{len(tables)}")
print("=" * 70)
return counts, total_records
if __name__ == "__main__":
try:
counts, total = get_table_counts()
sys.exit(0)
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
import traceback
traceback.print_exc()
sys.exit(1)

View File

@@ -0,0 +1,27 @@
@echo off
REM Check current status of ClaudeTools API on RMM server
echo ============================================================
echo ClaudeTools API Status Check
echo ============================================================
echo.
echo [1] API Service Status:
plink guru@172.16.3.30 "sudo systemctl status claudetools-api --no-pager | head -15"
echo.
echo [2] Current Code Version (checking for search_term parameter):
plink guru@172.16.3.30 "grep -c 'search_term.*Query' /opt/claudetools/api/routers/conversation_contexts.py"
echo (0 = OLD CODE, 1+ = NEW CODE)
echo.
echo [3] File Last Modified:
plink guru@172.16.3.30 "ls -lh /opt/claudetools/api/routers/conversation_contexts.py"
echo.
echo [4] API Response Format:
python -c "import requests; jwt='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJpbXBvcnQtc2NyaXB0Iiwic2NvcGVzIjpbImFkbWluIiwiaW1wb3J0Il0sImV4cCI6MTc3MTI3NTEyOX0.-DJF50tq0MaNwVQBdO7cGYNuO5pQuXte-tTj5DpHi2U'; r=requests.get('http://172.16.3.30:8001/api/conversation-contexts/recall', headers={'Authorization': f'Bearer {jwt}'}, params={'limit': 1}); print(f'Response keys: {list(r.json().keys())}'); print('Format: NEW' if 'contexts' in r.json() else 'Format: OLD')"
echo.
echo ============================================================
pause

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""
Create a JWT token for ClaudeTools API access
"""
import jwt
from datetime import datetime, timedelta, timezone
# Get the JWT secret from the RMM server's .env file
# This should match what's in /opt/claudetools/.env on 172.16.3.30
JWT_SECRET = "NdwgH6jsGR1WfPdUwR3u9i1NwNx3QthhLHBsRCfFxcg="
# Create token data
data = {
"sub": "import-script",
"scopes": ["admin", "import"],
"exp": datetime.now(timezone.utc) + timedelta(days=30)
}
# Create token
token = jwt.encode(data, JWT_SECRET, algorithm="HS256")
print(f"New JWT Token:")
print(token)
print()
print(f"Expires: {data['exp']}")
print()
print("Add this to .claude/context-recall-config.env:")
print(f"JWT_TOKEN={token}")

View File

@@ -0,0 +1,158 @@
"""
Example: Import credentials from a client project directory.
This script demonstrates a real-world use case for the credential scanner:
importing credentials from a client's project directory into the ClaudeTools
credential vault with automatic encryption.
Usage:
python example_credential_import.py /path/to/client/project [--client-id UUID]
"""
import argparse
import logging
import sys
from pathlib import Path
from api.database import SessionLocal
from api.utils.credential_scanner import (
scan_for_credential_files,
parse_credential_file,
import_credentials_to_db,
scan_and_import_credentials,
)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def preview_credentials(base_path: str):
"""Preview credentials that would be imported without actually importing."""
logger.info(f"Scanning for credentials in: {base_path}")
# Find credential files
files = scan_for_credential_files(base_path)
if not files:
logger.warning("No credential files found")
return []
logger.info(f"\nFound {len(files)} credential file(s):")
for file_path in files:
logger.info(f" - {file_path}")
# Parse and preview
all_credentials = []
for file_path in files:
credentials = parse_credential_file(file_path)
all_credentials.extend(credentials)
logger.info(f"\n{Path(file_path).name}:")
for cred in credentials:
logger.info(f" Service: {cred.get('service_name')}")
logger.info(f" Type: {cred.get('credential_type')}")
if cred.get('username'):
logger.info(f" Username: {cred.get('username')}")
logger.info("")
logger.info(f"Total credentials found: {len(all_credentials)}")
return all_credentials
def import_with_confirmation(base_path: str, client_id: str = None):
"""Import credentials with user confirmation."""
# Preview first
credentials = preview_credentials(base_path)
if not credentials:
logger.info("No credentials to import")
return
# Ask for confirmation
logger.info("\n" + "=" * 60)
response = input(f"Import {len(credentials)} credential(s) to database? (yes/no): ")
if response.lower() not in ['yes', 'y']:
logger.info("Import cancelled")
return
# Import to database
db = SessionLocal()
try:
logger.info("\nImporting credentials...")
results = scan_and_import_credentials(
base_path=base_path,
db=db,
client_id=client_id,
user_id="manual_import",
ip_address=None
)
logger.info("\n" + "=" * 60)
logger.info("IMPORT COMPLETE")
logger.info("=" * 60)
logger.info(f"Files scanned: {results['files_found']}")
logger.info(f"Credentials parsed: {results['credentials_parsed']}")
logger.info(f"Credentials imported: {results['credentials_imported']}")
if results['credentials_imported'] < results['credentials_parsed']:
logger.warning(
f"Warning: {results['credentials_parsed'] - results['credentials_imported']} "
"credential(s) failed to import. Check logs for details."
)
except Exception as e:
logger.error(f"Import failed: {str(e)}", exc_info=True)
raise
finally:
db.close()
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Import credentials from a directory into ClaudeTools credential vault"
)
parser.add_argument(
'path',
type=str,
help='Path to directory containing credential files'
)
parser.add_argument(
'--client-id',
type=str,
help='UUID of client to associate credentials with (optional)',
default=None
)
parser.add_argument(
'--preview',
action='store_true',
help='Preview credentials without importing'
)
args = parser.parse_args()
# Validate path
path = Path(args.path)
if not path.exists():
logger.error(f"Path does not exist: {args.path}")
sys.exit(1)
if not path.is_dir():
logger.error(f"Path is not a directory: {args.path}")
sys.exit(1)
# Preview or import
if args.preview:
preview_credentials(str(path))
else:
import_with_confirmation(str(path), args.client_id)
if __name__ == "__main__":
main()