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:
98
scripts/check_record_counts.py
Normal file
98
scripts/check_record_counts.py
Normal 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)
|
||||
27
scripts/check_rmm_status.cmd
Normal file
27
scripts/check_rmm_status.cmd
Normal 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
|
||||
28
scripts/create_jwt_token.py
Normal file
28
scripts/create_jwt_token.py
Normal 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}")
|
||||
158
scripts/example_credential_import.py
Normal file
158
scripts/example_credential_import.py
Normal 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()
|
||||
Reference in New Issue
Block a user