# retoor from devplacepy.database import get_table from devplacepy.cli._shared import _audit_cli def _remove_zip_artifacts(job): import shutil from pathlib import Path from devplacepy.config import ZIP_STAGING_DIR local_path = (job.get("result") or {}).get("local_path") if local_path: Path(local_path).unlink(missing_ok=True) shutil.rmtree(ZIP_STAGING_DIR / job["uid"], ignore_errors=True) def cmd_zips_prune(args): from datetime import datetime, timezone from devplacepy.services.jobs import queue now = datetime.now(timezone.utc) removed = 0 for job in queue.list_jobs(kind="zip", status=queue.DONE): expires_at = job.get("expires_at") if not expires_at: continue try: expiry = datetime.fromisoformat(expires_at) except (ValueError, TypeError): continue if expiry < now: _remove_zip_artifacts(job) get_table("jobs").delete(uid=job["uid"]) removed += 1 _audit_cli("cli.zips.prune", f"CLI pruned {removed} expired zip jobs", metadata={"count": removed}) print(f"Pruned {removed} expired zip job(s)") def cmd_zips_clear(args): from devplacepy.services.jobs import queue jobs = queue.list_jobs(kind="zip") for job in jobs: _remove_zip_artifacts(job) get_table("jobs").delete(uid=job["uid"]) _audit_cli("cli.zips.clear", f"CLI cleared all zip jobs ({len(jobs)})", metadata={"count": len(jobs)}) print(f"Cleared {len(jobs)} zip job(s) and their archives") def cmd_forks_prune(args): from datetime import datetime, timezone from devplacepy.services.jobs import queue now = datetime.now(timezone.utc) removed = 0 for job in queue.list_jobs(kind="fork", status=queue.DONE): expires_at = job.get("expires_at") if not expires_at: continue try: expiry = datetime.fromisoformat(expires_at) except (ValueError, TypeError): continue if expiry < now: get_table("jobs").delete(uid=job["uid"]) removed += 1 _audit_cli("cli.forks.prune", f"CLI pruned {removed} expired fork jobs", metadata={"count": removed}) print(f"Pruned {removed} expired fork job(s)") def cmd_forks_clear(args): from devplacepy.services.jobs import queue jobs = queue.list_jobs(kind="fork") for job in jobs: get_table("jobs").delete(uid=job["uid"]) _audit_cli("cli.forks.clear", f"CLI cleared all fork jobs ({len(jobs)})", metadata={"count": len(jobs)}) print(f"Cleared {len(jobs)} fork job(s)") def _remove_seo_artifacts(job): import shutil from devplacepy.config import SEO_REPORTS_DIR shutil.rmtree(SEO_REPORTS_DIR / job["uid"], ignore_errors=True) def cmd_seo_prune(args): from datetime import datetime, timezone from devplacepy.services.jobs import queue now = datetime.now(timezone.utc) removed = 0 for job in queue.list_jobs(kind="seo", status=queue.DONE): expires_at = job.get("expires_at") if not expires_at: continue try: expiry = datetime.fromisoformat(expires_at) except (ValueError, TypeError): continue if expiry < now: _remove_seo_artifacts(job) get_table("jobs").delete(uid=job["uid"]) removed += 1 _audit_cli("cli.seo.prune", f"CLI pruned {removed} expired SEO jobs", metadata={"count": removed}) print(f"Pruned {removed} expired SEO audit(s)") def cmd_seo_clear(args): from devplacepy.services.jobs import queue jobs = queue.list_jobs(kind="seo") for job in jobs: _remove_seo_artifacts(job) get_table("jobs").delete(uid=job["uid"]) _audit_cli("cli.seo.clear", f"CLI cleared all SEO jobs ({len(jobs)})", metadata={"count": len(jobs)}) print(f"Cleared {len(jobs)} SEO audit(s) and their reports") def cmd_seo_meta_prune(args): from datetime import datetime, timezone from devplacepy.services.jobs import queue now = datetime.now(timezone.utc) removed = 0 for job in queue.list_jobs(kind="seo_meta", status=queue.DONE): expires_at = job.get("expires_at") if not expires_at: continue try: expiry = datetime.fromisoformat(expires_at) except (ValueError, TypeError): continue if expiry < now: get_table("jobs").delete(uid=job["uid"]) removed += 1 _audit_cli( "cli.seo_meta.prune", f"CLI pruned {removed} expired SEO metadata jobs", metadata={"count": removed}, ) print(f"Pruned {removed} expired SEO metadata job(s)") def cmd_seo_meta_clear(args): from devplacepy.services.jobs import queue jobs = queue.list_jobs(kind="seo_meta") for job in jobs: get_table("jobs").delete(uid=job["uid"]) _audit_cli( "cli.seo_meta.clear", f"CLI cleared all SEO metadata jobs ({len(jobs)})", metadata={"count": len(jobs)}, ) print(f"Cleared {len(jobs)} SEO metadata job(s); generated metadata persists") def _remove_deepsearch_artifacts(job): import shutil from devplacepy.config import DEEPSEARCH_DIR from devplacepy.services.deepsearch.store import VectorStore uid = job["uid"] collection = f"ds_{uid.replace('-', '')}" VectorStore(collection).drop() shutil.rmtree(DEEPSEARCH_DIR / uid, ignore_errors=True) def cmd_deepsearch_prune(args): from datetime import datetime, timezone from devplacepy.services.jobs import queue now = datetime.now(timezone.utc) removed = 0 for job in queue.list_jobs(kind="deepsearch", status=queue.DONE): expires_at = job.get("expires_at") if not expires_at: continue try: expiry = datetime.fromisoformat(expires_at) except (ValueError, TypeError): continue if expiry < now: _remove_deepsearch_artifacts(job) get_table("jobs").delete(uid=job["uid"]) removed += 1 _audit_cli( "cli.deepsearch.prune", f"CLI pruned {removed} expired DeepSearch jobs", metadata={"count": removed}, ) print(f"Pruned {removed} expired DeepSearch job(s)") def cmd_deepsearch_clear(args): from devplacepy.services.jobs import queue jobs = queue.list_jobs(kind="deepsearch") for job in jobs: _remove_deepsearch_artifacts(job) get_table("jobs").delete(uid=job["uid"]) _audit_cli( "cli.deepsearch.clear", f"CLI cleared all DeepSearch jobs ({len(jobs)})", metadata={"count": len(jobs)}, ) print(f"Cleared {len(jobs)} DeepSearch job(s) and their collections") def cmd_isslop_prune(args): from datetime import datetime, timezone from devplacepy.services.jobs import queue now = datetime.now(timezone.utc) removed = 0 for job in queue.list_jobs(kind="isslop", status=queue.DONE): expires_at = job.get("expires_at") if not expires_at: continue try: expiry = datetime.fromisoformat(expires_at) except (ValueError, TypeError): continue if expiry < now: get_table("jobs").delete(uid=job["uid"]) removed += 1 _audit_cli("cli.isslop.prune", f"CLI pruned {removed} expired AI usage analysis jobs", metadata={"count": removed}) print(f"Pruned {removed} expired AI usage analysis job(s) (reports persist)") def cmd_isslop_clear(args): from devplacepy.services.jobs import queue from devplacepy.services.jobs.isslop import store jobs = queue.list_jobs(kind="isslop") for job in jobs: get_table("jobs").delete(uid=job["uid"]) analyses = list(get_table(store.TABLE_ANALYSES).find()) for analysis in analyses: store.purge_analysis(analysis["uid"]) _audit_cli( "cli.isslop.clear", f"CLI cleared {len(analyses)} AI usage analyses and {len(jobs)} job rows", metadata={"analyses": len(analyses), "jobs": len(jobs)}, ) print(f"Cleared {len(analyses)} AI usage analysis(es), their reports and {len(jobs)} job row(s)") def cmd_isslop_analyze(args): import asyncio from devplacepy.config import ISSLOP_WORKSPACES_DIR, ensure_data_dirs from devplacepy.database import INTERNAL_GATEWAY_URL, internal_gateway_key from devplacepy.models import IsslopRunForm from devplacepy.services.jobs.isslop import store from devplacepy.services.jobs.isslop.acquisition.workspace import remove_workspace, workspace_for from devplacepy.services.jobs.isslop.config import settings_from_payload from devplacepy.services.jobs.isslop.events import KIND_DONE, KIND_ERROR from devplacepy.services.jobs.isslop.persistence import EventPersister from devplacepy.services.jobs.isslop.pipeline import run_pipeline from devplacepy.utils import generate_uid url = IsslopRunForm(url=args.url).url ensure_data_dirs() uid = generate_uid() settings = settings_from_payload( { "url": url, "llm_endpoint": INTERNAL_GATEWAY_URL, "api_key": internal_gateway_key(), "allow_private": bool(args.allow_private), "media_dir": str(store.media_dir_for(uid)), } ) store.create_analysis(uid, url, "system", "cli") persister = EventPersister(uid) store.update_analysis(uid, status="running") workspace = workspace_for(ISSLOP_WORKSPACES_DIR, url, uid) async def run() -> int: failed = False try: async for event in run_pipeline(url, workspace, settings): persister.apply(event) if args.json: print(event.to_json(), flush=True) else: print(f"[{event.kind}] {event.message}", flush=True) if event.kind == KIND_ERROR: failed = True if event.kind == KIND_DONE and not args.json: print(f"Report: /tools/isslop/{uid}/report") print(f"Badge: /tools/isslop/{uid}/badge.svg") finally: remove_workspace(workspace) return 1 if failed else 0 exit_code = asyncio.run(run()) _audit_cli( "cli.isslop.analyze", f"CLI AI usage analysis of {url}", metadata={"uid": uid, "failed": bool(exit_code)}, ) raise SystemExit(exit_code) def register_jobs(subparsers): zips = subparsers.add_parser("zips", help="Zip archive job management") zips_sub = zips.add_subparsers(title="action", dest="action") zips_prune = zips_sub.add_parser( "prune", help="Delete expired zip archives and their job rows" ) zips_prune.set_defaults(func=cmd_zips_prune) zips_clear = zips_sub.add_parser( "clear", help="Delete every zip archive and job row" ) zips_clear.set_defaults(func=cmd_zips_clear) forks = subparsers.add_parser("forks", help="Fork job management") forks_sub = forks.add_subparsers(title="action", dest="action") forks_prune = forks_sub.add_parser( "prune", help="Delete expired completed fork job rows (forked projects persist)" ) forks_prune.set_defaults(func=cmd_forks_prune) forks_clear = forks_sub.add_parser( "clear", help="Delete every fork job row (forked projects persist)" ) forks_clear.set_defaults(func=cmd_forks_clear) seo = subparsers.add_parser("seo", help="SEO Diagnostics job management") seo_sub = seo.add_subparsers(title="action", dest="action") seo_prune = seo_sub.add_parser( "prune", help="Delete expired SEO audit reports and their job rows" ) seo_prune.set_defaults(func=cmd_seo_prune) seo_clear = seo_sub.add_parser( "clear", help="Delete every SEO audit report and job row" ) seo_clear.set_defaults(func=cmd_seo_clear) seo_meta = subparsers.add_parser("seo-meta", help="SEO metadata job management") seo_meta_sub = seo_meta.add_subparsers(title="action", dest="action") seo_meta_prune = seo_meta_sub.add_parser( "prune", help="Delete expired SEO metadata job rows (generated metadata persists)" ) seo_meta_prune.set_defaults(func=cmd_seo_meta_prune) seo_meta_clear = seo_meta_sub.add_parser( "clear", help="Delete every SEO metadata job row (generated metadata persists)" ) seo_meta_clear.set_defaults(func=cmd_seo_meta_clear) deepsearch = subparsers.add_parser("deepsearch", help="DeepSearch job management") deepsearch_sub = deepsearch.add_subparsers(title="action", dest="action") deepsearch_prune = deepsearch_sub.add_parser( "prune", help="Delete expired DeepSearch sessions and their job rows" ) deepsearch_prune.set_defaults(func=cmd_deepsearch_prune) deepsearch_clear = deepsearch_sub.add_parser( "clear", help="Delete every DeepSearch session and job row" ) deepsearch_clear.set_defaults(func=cmd_deepsearch_clear) isslop = subparsers.add_parser("isslop", help="AI Usage Analyzer job management") isslop_sub = isslop.add_subparsers(title="action", dest="action") isslop_prune = isslop_sub.add_parser( "prune", help="Delete expired AI usage analysis job rows (analyses and reports persist)" ) isslop_prune.set_defaults(func=cmd_isslop_prune) isslop_clear = isslop_sub.add_parser( "clear", help="Delete every AI usage analysis, its report and job rows" ) isslop_clear.set_defaults(func=cmd_isslop_clear) isslop_analyze = isslop_sub.add_parser( "analyze", help="Run a AI usage analysis from the terminal and persist its report" ) isslop_analyze.add_argument("url", help="Repository or website URL to classify") isslop_analyze.add_argument("--json", action="store_true", help="Emit raw JSON events") isslop_analyze.add_argument("--allow-private", action="store_true", dest="allow_private", help="Permit private and loopback hosts") isslop_analyze.set_defaults(func=cmd_isslop_analyze)