personas: sharing v1 — export/import bundles, version + consent (OPE-7)

Bundle zip export + import (zip-slip guarded) through the picker's Import door; version+provenance with a replaces-note, re-consent only when capabilities grow.
Consent screen: trust warning first, capability summary with collapsed tool list, recommended connectors.
This commit is contained in:
Rohit C Prasad
2026-08-11 12:12:24 -07:00
committed by Rohit P
parent b5b000eb76
commit 110a8ae8ce
14 changed files with 600 additions and 34 deletions
+19 -1
View File
@@ -450,6 +450,16 @@ def create_app(manager: SessionManager) -> FastAPI:
summaries = reg.install_from_git(str(body["git_url"]))
elif body.get("dir"):
summaries = reg.install_from_dir(str(body["dir"]))
elif body.get("zip_b64"):
# Sharing v1 (OPE-7): a bundle zip — the export format — round-trips
# through the same dir installer + consent path.
try:
data = base64.b64decode(str(body["zip_b64"]), validate=True)
except (ValueError, binascii.Error):
return {"ok": False, "error": "Invalid archive encoding."}
summaries = reg.install_from_zip(
data, str(body.get("filename", ""))
)
elif body.get("gallery_slug"):
# Gallery install = fetch the manifest markdown from the cloud
# (sign-in required), verify its hash, then reuse the exact
@@ -483,12 +493,20 @@ def create_app(manager: SessionManager) -> FastAPI:
else:
return {
"ok": False,
"error": "provide a `dir`, `git_url`, or `gallery_slug`",
"error": "provide a `dir`, `git_url`, `zip_b64`, or `gallery_slug`",
}
except Exception as e: # surface manifest/clone errors to the caller
return {"ok": False, "error": str(e)}
return {"ok": True, "consent": summaries, "personas": reg.list_all()}
@app.post("/v1/personas/{persona_id}/export")
def export_persona(persona_id: str, body: dict) -> dict[str, Any]:
# Sharing v1 (OPE-7): zip the persona's bundle into the chosen folder. The zip
# is the import format — send it to a teammate, they import it from the picker.
return manager.personas.export_persona(
persona_id, str((body or {}).get("dir", ""))
)
@app.get("/v1/cloud/gallery/{slug}")
def cloud_gallery_detail(slug: str) -> dict[str, Any]:
"""Solo page for one gallery coworker: publisher pitch + capabilities