From 6a14823c26b1f44310d8eb96aa77c84640cce382 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 15:06:22 +0000 Subject: [PATCH] Add install-from-prompt to the Skills tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skill marketplaces (SkillsMP and friends) publish an install prompt meant to be pasted into an interactive claude, which fetches the skill's files and places them under a skills directory. Handler has no interactive claude and its skills are DB rows, so the Skills tab gains an "Install from a marketplace prompt" card wired to a new skill_install command: the worker runs the pasted prompt through a one-off headless claude in a throwaway staging directory (sandboxed by a generated settings.json allowing fetch/clone tooling with acceptEdits), then imports whatever /SKILL.md landed as managed rows — reinstalling a skill updates it in place. Headless means nobody can answer questions mid-install, so the wrapper prompt front-loads the answers a human would give: install into the staging dir, always user scope (Handler distributes skills to workers itself), pick the instructions' defaults, never stop to ask, and end with a report of the choices made — surfaced in the command result for after-the-fact review, with the imported skill editable/disableable in the UI. Multi-file skills survive the import: a new claude_skill_files table (migration 0011, alongside the command-type constraint change) captures auxiliary files (references/, scripts/, ...), the launch-time sync rebuilds each managed skill dir from them, and skill cards list what a skill ships with. The one-off run's timeout defaults under worker_stale_after so a slow install can't get the worker's live runs falsely reaped. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019f42XmjtVsc3zQ9Dhn6DqZ --- README.md | 8 +- .../components/sections/ClaudeSection.tsx | 54 +++- frontend/components/store.tsx | 52 ++++ frontend/lib/api.ts | 3 + src/handler/api/routes/claude.py | 29 ++- src/handler/api/schemas.py | 13 + src/handler/api/static/404.html | 2 +- src/handler/api/static/404/index.html | 2 +- .../_buildManifest.js | 0 .../_ssgManifest.js | 0 ...67bb7636c62.js => 171-0a6dd93b551f1ca6.js} | 2 +- .../app/claude/page-47f8d9b94703c22a.js | 1 - .../app/claude/page-6e97b9d68ff8eca2.js | 1 + src/handler/api/static/activity/index.html | 2 +- src/handler/api/static/activity/index.txt | 6 +- src/handler/api/static/agents/index.html | 2 +- src/handler/api/static/agents/index.txt | 6 +- src/handler/api/static/approvals/index.html | 2 +- src/handler/api/static/approvals/index.txt | 6 +- src/handler/api/static/claude/index.html | 2 +- src/handler/api/static/claude/index.txt | 6 +- src/handler/api/static/index.html | 2 +- src/handler/api/static/index.txt | 6 +- src/handler/api/static/login/index.html | 2 +- src/handler/api/static/login/index.txt | 4 +- .../api/static/repositories/index.html | 2 +- src/handler/api/static/repositories/index.txt | 6 +- src/handler/api/static/schedules/index.html | 2 +- src/handler/api/static/schedules/index.txt | 6 +- src/handler/api/static/servers/index.html | 2 +- src/handler/api/static/servers/index.txt | 6 +- src/handler/api/static/shared/index.html | 2 +- src/handler/api/static/shared/index.txt | 6 +- src/handler/config.py | 6 + src/handler/control/claude_gen.py | 43 +++- src/handler/control/skill_install.py | 230 ++++++++++++++++++ src/handler/control/worker.py | 15 +- src/handler/db/repository.py | 22 ++ src/handler/db/tables.py | 21 ++ .../migrations/versions/0011_skill_install.py | 60 +++++ tests/test_claude_management.py | 144 ++++++++++- 41 files changed, 731 insertions(+), 55 deletions(-) rename src/handler/api/static/_next/static/{9ucF5j1fWuiDF55MzWzbS => 1cIeZLB5pMn-RK3Yuxqs0}/_buildManifest.js (100%) rename src/handler/api/static/_next/static/{9ucF5j1fWuiDF55MzWzbS => 1cIeZLB5pMn-RK3Yuxqs0}/_ssgManifest.js (100%) rename src/handler/api/static/_next/static/chunks/{171-58fbf67bb7636c62.js => 171-0a6dd93b551f1ca6.js} (52%) delete mode 100644 src/handler/api/static/_next/static/chunks/app/claude/page-47f8d9b94703c22a.js create mode 100644 src/handler/api/static/_next/static/chunks/app/claude/page-6e97b9d68ff8eca2.js create mode 100644 src/handler/control/skill_install.py create mode 100644 src/handler/migrations/versions/0011_skill_install.py diff --git a/README.md b/README.md index 079740a..660b453 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,13 @@ What the dashboard can now do (all state-changing actions require `ADMIN_TOKEN`) (marker-file managed, so hand-installed skills survive), enabled connectors become the run's `--mcp-config` file (nothing lands in the repo tree), and plugins/permissions fold into the generated per-agent `settings.json` — so a change in the UI reaches the next - launch of every agent, no redeploy. + launch of every agent, no redeploy. Skills can also be **installed from a marketplace + prompt** (SkillsMP and friends): paste the page's install prompt and a `skill_install` + command runs it through a one-off headless claude in a staging dir on the worker, then + imports whatever `/SKILL.md` (+ auxiliary files) landed as managed rows. + Headless means nobody can answer questions mid-install, so the wrapped prompt makes the + choices a human would be asked — always user scope, the instructions' defaults — and + reports them in the command result for after-the-fact review. The command queue is exposed over HTTP as `POST …/agents/spawn`, `POST …/agents/{n}/kill`, `POST …/approvals`, `POST …/forge-init`, `POST …/poll-ci`, `POST …/sync`, diff --git a/frontend/components/sections/ClaudeSection.tsx b/frontend/components/sections/ClaudeSection.tsx index ea4c94a..c5d60e2 100644 --- a/frontend/components/sections/ClaudeSection.tsx +++ b/frontend/components/sections/ClaudeSection.tsx @@ -43,6 +43,51 @@ function parseLines(text: string): string[] { const emptySkill = { name: "", description: "", content: "", enabled: true }; +/* Install-from-prompt: paste the "install prompt" a marketplace page (SkillsMP etc.) + * shows, and the worker runs it through a one-off headless claude, importing whatever + * it fetches as managed skills. Headless = nobody to answer questions, so the run makes + * the choices a human would be asked (scope, options) itself — always user scope, + * sensible defaults — and reports them; the import lands below for review/editing. */ +function InstallCard() { + const s = useDashboard(); + const [prompt, setPrompt] = useState(""); + + const run = async () => { + const ok = await s.installClaudeSkill(prompt.trim()); + if (ok) setPrompt(""); + }; + + return ( + +
+ + Install from a marketplace prompt + +
+