main
py 81 lines 3.07 KB
Raw
1 from __future__ import annotations
2
3 import os
4 import time
5 import uuid
6 from pathlib import Path
7
8 from helpers.api import ApiHandler, Request, Response
9 from helpers import files
10 from helpers.skills_import import import_skills
11 from werkzeug.datastructures import FileStorage
12 from werkzeug.utils import secure_filename
13
14
15 class SkillsImport(ApiHandler):
16 """
17 Import an external skills pack (.zip) into usr/skills/<namespace>/...
18 Performs the actual import (not dry-run).
19 """
20
21 async def process(self, input: dict, request: Request) -> dict | Response:
22 if "skills_file" not in request.files:
23 return {"success": False, "error": "No skills file provided"}
24
25 skills_file: FileStorage = request.files["skills_file"]
26 if not skills_file.filename:
27 return {"success": False, "error": "No file selected"}
28
29 ctxid = request.form.get("ctxid", "")
30 if not ctxid:
31 return {"success": False, "error": "No context id provided"}
32 _context = self.use_context(ctxid)
33
34 conflict = (request.form.get("conflict", "skip") or "skip").strip().lower()
35 if conflict not in ("skip", "overwrite", "rename"):
36 conflict = "skip"
37
38 namespace = (request.form.get("namespace", "") or "").strip() or None
39 project_name = (request.form.get("project_name", "") or "").strip() or None
40 agent_profile = (request.form.get("agent_profile", "") or "").strip() or None
41
42 # Save upload to a temp file so we can pass a filesystem path to the importer
43 tmp_dir = Path(files.get_abs_path("tmp", "uploads"))
44 tmp_dir.mkdir(parents=True, exist_ok=True)
45 base = secure_filename(skills_file.filename) # type: ignore[arg-type]
46 if not base.lower().endswith(".zip"):
47 base = f"{base}.zip"
48 unique = uuid.uuid4().hex[:8]
49 stamp = time.strftime("%Y%m%d_%H%M%S")
50 tmp_path = tmp_dir / f"skills_import_{stamp}_{unique}_{base}"
51 skills_file.save(str(tmp_path))
52
53 try:
54 result = import_skills(
55 str(tmp_path),
56 namespace=namespace,
57 conflict=conflict, # type: ignore[arg-type]
58 dry_run=False, # Actual import, not preview
59 project_name=project_name,
60 agent_profile=agent_profile,
61 )
62
63 imported = [files.deabsolute_path(str(p)) for p in result.imported]
64 skipped = [files.deabsolute_path(str(p)) for p in result.skipped]
65 dest_root = files.deabsolute_path(str(result.destination_root / result.namespace))
66
67 return {
68 "success": True,
69 "namespace": result.namespace,
70 "destination": dest_root,
71 "imported": imported,
72 "skipped": skipped,
73 "imported_count": len(imported),
74 "skipped_count": len(skipped),
75 "conflict_policy": conflict,
76 }
77 finally:
78 try:
79 tmp_path.unlink(missing_ok=True) # type: ignore[arg-type]
80 except Exception:
81 pass