|
# retoor <retoor@molodetz.nl>
|
|
|
|
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)
|