@loompad/cli 0.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +96 -11
- package/CHANGELOG.md +1054 -0
- package/README.md +1473 -44
- package/THIRD_PARTY_NOTICES.md +275 -0
- package/dist/adapters/antigravity-cli.d.ts +62 -0
- package/dist/adapters/antigravity-cli.js +258 -0
- package/dist/adapters/antigravity-cli.js.map +1 -0
- package/dist/adapters/base.d.ts +61 -9
- package/dist/adapters/base.js +266 -15
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/bridges/antigravity.d.ts +9 -12
- package/dist/adapters/bridges/antigravity.js +9 -12
- package/dist/adapters/bridges/antigravity.js.map +1 -1
- package/dist/adapters/echo.d.ts +1 -0
- package/dist/adapters/echo.js +59 -3
- package/dist/adapters/echo.js.map +1 -1
- package/dist/adapters/grok.d.ts +7 -0
- package/dist/adapters/grok.js +20 -10
- package/dist/adapters/grok.js.map +1 -1
- package/dist/adapters/index.d.ts +5 -1
- package/dist/adapters/index.js +53 -7
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/model.d.ts +137 -0
- package/dist/adapters/model.js +527 -0
- package/dist/adapters/model.js.map +1 -0
- package/dist/adapters/opencode.d.ts +101 -1
- package/dist/adapters/opencode.js +874 -98
- package/dist/adapters/opencode.js.map +1 -1
- package/dist/cli/doctor.d.ts +32 -0
- package/dist/cli/doctor.js +89 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +2651 -87
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/tui-model.js +3 -1
- package/dist/cli/tui-model.js.map +1 -1
- package/dist/cli/tui.js +44 -1
- package/dist/cli/tui.js.map +1 -1
- package/dist/cli/ui.d.ts +2 -0
- package/dist/cli/ui.js +13 -0
- package/dist/cli/ui.js.map +1 -1
- package/dist/core/ades.d.ts +32 -16
- package/dist/core/ades.js +57 -20
- package/dist/core/ades.js.map +1 -1
- package/dist/core/agent-state.d.ts +14 -0
- package/dist/core/agent-state.js +32 -0
- package/dist/core/agent-state.js.map +1 -0
- package/dist/core/approvals.d.ts +33 -0
- package/dist/core/approvals.js +29 -0
- package/dist/core/approvals.js.map +1 -0
- package/dist/core/backup.d.ts +19 -0
- package/dist/core/backup.js +86 -0
- package/dist/core/backup.js.map +1 -0
- package/dist/core/baton.d.ts +2 -2
- package/dist/core/baton.js.map +1 -1
- package/dist/core/brain-extract.d.ts +17 -0
- package/dist/core/brain-extract.js +28 -2
- package/dist/core/brain-extract.js.map +1 -1
- package/dist/core/brain-index.d.ts +71 -0
- package/dist/core/brain-index.js +204 -5
- package/dist/core/brain-index.js.map +1 -1
- package/dist/core/brain.d.ts +58 -2
- package/dist/core/brain.js +79 -1
- package/dist/core/brain.js.map +1 -1
- package/dist/core/chat-import.d.ts +71 -0
- package/dist/core/chat-import.js +419 -0
- package/dist/core/chat-import.js.map +1 -0
- package/dist/core/checkpoint.d.ts +107 -0
- package/dist/core/checkpoint.js +259 -0
- package/dist/core/checkpoint.js.map +1 -0
- package/dist/core/claude-cli.js +1 -1
- package/dist/core/claude-cli.js.map +1 -1
- package/dist/core/continuity/artifacts.d.ts +14 -0
- package/dist/core/continuity/artifacts.js +118 -0
- package/dist/core/continuity/artifacts.js.map +1 -0
- package/dist/core/continuity/capabilities.d.ts +65 -0
- package/dist/core/continuity/capabilities.js +129 -0
- package/dist/core/continuity/capabilities.js.map +1 -0
- package/dist/core/continuity/contracts.d.ts +352 -0
- package/dist/core/continuity/contracts.js +117 -0
- package/dist/core/continuity/contracts.js.map +1 -0
- package/dist/core/continuity/engine.d.ts +55 -0
- package/dist/core/continuity/engine.js +504 -0
- package/dist/core/continuity/engine.js.map +1 -0
- package/dist/core/continuity/store.d.ts +73 -0
- package/dist/core/continuity/store.js +438 -0
- package/dist/core/continuity/store.js.map +1 -0
- package/dist/core/conversations.d.ts +19 -0
- package/dist/core/conversations.js +87 -0
- package/dist/core/conversations.js.map +1 -0
- package/dist/core/crew-coordination.d.ts +6 -0
- package/dist/core/crew-coordination.js +24 -0
- package/dist/core/crew-coordination.js.map +1 -0
- package/dist/core/crew-policy.d.ts +18 -0
- package/dist/core/crew-policy.js +40 -0
- package/dist/core/crew-policy.js.map +1 -0
- package/dist/core/crew-protocol.d.ts +64 -0
- package/dist/core/crew-protocol.js +146 -0
- package/dist/core/crew-protocol.js.map +1 -0
- package/dist/core/crew-requests.d.ts +14 -0
- package/dist/core/crew-requests.js +22 -0
- package/dist/core/crew-requests.js.map +1 -0
- package/dist/core/crew.d.ts +395 -0
- package/dist/core/crew.js +1857 -0
- package/dist/core/crew.js.map +1 -0
- package/dist/core/digest.d.ts +40 -0
- package/dist/core/digest.js +94 -0
- package/dist/core/digest.js.map +1 -0
- package/dist/core/distill.js +3 -5
- package/dist/core/distill.js.map +1 -1
- package/dist/core/eventlog.d.ts +27 -25
- package/dist/core/eventlog.js +97 -150
- package/dist/core/eventlog.js.map +1 -1
- package/dist/core/events/contracts.d.ts +51 -0
- package/dist/core/events/contracts.js +2 -0
- package/dist/core/events/contracts.js.map +1 -0
- package/dist/core/events/jsonl-store.d.ts +23 -0
- package/dist/core/events/jsonl-store.js +90 -0
- package/dist/core/events/jsonl-store.js.map +1 -0
- package/dist/core/events/sqlite-store.d.ts +37 -0
- package/dist/core/events/sqlite-store.js +215 -0
- package/dist/core/events/sqlite-store.js.map +1 -0
- package/dist/core/forecast.d.ts +44 -0
- package/dist/core/forecast.js +46 -0
- package/dist/core/forecast.js.map +1 -0
- package/dist/core/free-pool.d.ts +65 -0
- package/dist/core/free-pool.js +171 -0
- package/dist/core/free-pool.js.map +1 -0
- package/dist/core/git.d.ts +30 -0
- package/dist/core/git.js +50 -2
- package/dist/core/git.js.map +1 -1
- package/dist/core/github-events.d.ts +53 -0
- package/dist/core/github-events.js +227 -0
- package/dist/core/github-events.js.map +1 -0
- package/dist/core/goal-lanes.d.ts +53 -0
- package/dist/core/goal-lanes.js +99 -0
- package/dist/core/goal-lanes.js.map +1 -0
- package/dist/core/hosted.d.ts +27 -0
- package/dist/core/hosted.js +50 -0
- package/dist/core/hosted.js.map +1 -0
- package/dist/core/http-server.d.ts +5 -0
- package/dist/core/http-server.js +8 -0
- package/dist/core/http-server.js.map +1 -0
- package/dist/core/invite-link.d.ts +31 -0
- package/dist/core/invite-link.js +45 -0
- package/dist/core/invite-link.js.map +1 -0
- package/dist/core/loops.d.ts +37 -0
- package/dist/core/loops.js +81 -0
- package/dist/core/loops.js.map +1 -0
- package/dist/core/mcp-catalog.d.ts +182 -0
- package/dist/core/mcp-catalog.js +431 -0
- package/dist/core/mcp-catalog.js.map +1 -0
- package/dist/core/mcp.d.ts +87 -0
- package/dist/core/mcp.js +181 -0
- package/dist/core/mcp.js.map +1 -0
- package/dist/core/memory.d.ts +1 -0
- package/dist/core/memory.js +5 -2
- package/dist/core/memory.js.map +1 -1
- package/dist/core/model-tools.d.ts +97 -0
- package/dist/core/model-tools.js +388 -0
- package/dist/core/model-tools.js.map +1 -0
- package/dist/core/notify.js +19 -1
- package/dist/core/notify.js.map +1 -1
- package/dist/core/orchestra.d.ts +635 -0
- package/dist/core/orchestra.js +2120 -0
- package/dist/core/orchestra.js.map +1 -0
- package/dist/core/permissions.d.ts +56 -0
- package/dist/core/permissions.js +122 -0
- package/dist/core/permissions.js.map +1 -0
- package/dist/core/preview-proxy.d.ts +70 -0
- package/dist/core/preview-proxy.js +563 -0
- package/dist/core/preview-proxy.js.map +1 -0
- package/dist/core/preview-shot.d.ts +46 -0
- package/dist/core/preview-shot.js +109 -0
- package/dist/core/preview-shot.js.map +1 -0
- package/dist/core/process.d.ts +36 -0
- package/dist/core/process.js +168 -0
- package/dist/core/process.js.map +1 -0
- package/dist/core/prompt-queue.d.ts +136 -0
- package/dist/core/prompt-queue.js +269 -0
- package/dist/core/prompt-queue.js.map +1 -0
- package/dist/core/prompts.d.ts +53 -0
- package/dist/core/prompts.js +127 -0
- package/dist/core/prompts.js.map +1 -0
- package/dist/core/proof.d.ts +82 -0
- package/dist/core/proof.js +216 -0
- package/dist/core/proof.js.map +1 -0
- package/dist/core/provider-text.d.ts +23 -0
- package/dist/core/provider-text.js +66 -0
- package/dist/core/provider-text.js.map +1 -0
- package/dist/core/providers.d.ts +152 -0
- package/dist/core/providers.js +343 -0
- package/dist/core/providers.js.map +1 -0
- package/dist/core/quota.d.ts +33 -0
- package/dist/core/quota.js +74 -0
- package/dist/core/quota.js.map +1 -0
- package/dist/core/recipes.d.ts +50 -0
- package/dist/core/recipes.js +99 -0
- package/dist/core/recipes.js.map +1 -0
- package/dist/core/registry.d.ts +54 -0
- package/dist/core/registry.js +62 -5
- package/dist/core/registry.js.map +1 -1
- package/dist/core/relay-client.d.ts +67 -0
- package/dist/core/relay-client.js +184 -0
- package/dist/core/relay-client.js.map +1 -0
- package/dist/core/relay-protocol.d.ts +140 -0
- package/dist/core/relay-protocol.js +189 -0
- package/dist/core/relay-protocol.js.map +1 -0
- package/dist/core/replay.d.ts +18 -0
- package/dist/core/replay.js +191 -0
- package/dist/core/replay.js.map +1 -0
- package/dist/core/review-triage.d.ts +41 -0
- package/dist/core/review-triage.js +124 -0
- package/dist/core/review-triage.js.map +1 -0
- package/dist/core/router.js +2 -2
- package/dist/core/router.js.map +1 -1
- package/dist/core/routes.d.ts +39 -3
- package/dist/core/routes.js +137 -4
- package/dist/core/routes.js.map +1 -1
- package/dist/core/run-command.d.ts +14 -0
- package/dist/core/run-command.js +45 -0
- package/dist/core/run-command.js.map +1 -0
- package/dist/core/runner-setup.d.ts +36 -0
- package/dist/core/runner-setup.js +114 -0
- package/dist/core/runner-setup.js.map +1 -0
- package/dist/core/search.js +0 -0
- package/dist/core/search.js.map +1 -1
- package/dist/core/semantic.d.ts +92 -0
- package/dist/core/semantic.js +227 -0
- package/dist/core/semantic.js.map +1 -0
- package/dist/core/servers.d.ts +115 -0
- package/dist/core/servers.js +305 -0
- package/dist/core/servers.js.map +1 -0
- package/dist/core/setup.js +64 -6
- package/dist/core/setup.js.map +1 -1
- package/dist/core/shield.d.ts +17 -0
- package/dist/core/shield.js +81 -0
- package/dist/core/shield.js.map +1 -0
- package/dist/core/shift.d.ts +69 -0
- package/dist/core/shift.js +120 -0
- package/dist/core/shift.js.map +1 -0
- package/dist/core/skill-install.d.ts +125 -0
- package/dist/core/skill-install.js +328 -0
- package/dist/core/skill-install.js.map +1 -0
- package/dist/core/skills.d.ts +116 -0
- package/dist/core/skills.js +223 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/step-conditions.d.ts +65 -0
- package/dist/core/step-conditions.js +102 -0
- package/dist/core/step-conditions.js.map +1 -0
- package/dist/core/subagents.d.ts +45 -0
- package/dist/core/subagents.js +90 -0
- package/dist/core/subagents.js.map +1 -0
- package/dist/core/team-canon.d.ts +42 -0
- package/dist/core/team-canon.js +121 -0
- package/dist/core/team-canon.js.map +1 -0
- package/dist/core/team-crypto.d.ts +97 -0
- package/dist/core/team-crypto.js +175 -0
- package/dist/core/team-crypto.js.map +1 -0
- package/dist/core/team-hub.d.ts +514 -0
- package/dist/core/team-hub.js +964 -0
- package/dist/core/team-hub.js.map +1 -0
- package/dist/core/team-landing.d.ts +239 -0
- package/dist/core/team-landing.js +504 -0
- package/dist/core/team-landing.js.map +1 -0
- package/dist/core/team-leases.d.ts +44 -0
- package/dist/core/team-leases.js +145 -0
- package/dist/core/team-leases.js.map +1 -0
- package/dist/core/team-memory.d.ts +42 -0
- package/dist/core/team-memory.js +84 -0
- package/dist/core/team-memory.js.map +1 -0
- package/dist/core/team-policy.d.ts +85 -0
- package/dist/core/team-policy.js +191 -0
- package/dist/core/team-policy.js.map +1 -0
- package/dist/core/thread-md.d.ts +6 -0
- package/dist/core/thread-md.js +42 -0
- package/dist/core/thread-md.js.map +1 -0
- package/dist/core/turn-stats.d.ts +61 -0
- package/dist/core/turn-stats.js +124 -0
- package/dist/core/turn-stats.js.map +1 -0
- package/dist/core/updater.d.ts +73 -0
- package/dist/core/updater.js +153 -0
- package/dist/core/updater.js.map +1 -0
- package/dist/core/usage.d.ts +75 -0
- package/dist/core/usage.js +163 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/core/windows-jobs.d.ts +15 -0
- package/dist/core/windows-jobs.js +170 -0
- package/dist/core/windows-jobs.js.map +1 -0
- package/dist/core/worktree-merge.d.ts +57 -0
- package/dist/core/worktree-merge.js +123 -0
- package/dist/core/worktree-merge.js.map +1 -0
- package/dist/core/worktree.d.ts +5 -0
- package/dist/core/worktree.js +45 -5
- package/dist/core/worktree.js.map +1 -1
- package/dist/core/worktrees.d.ts +6 -0
- package/dist/core/worktrees.js +21 -0
- package/dist/core/worktrees.js.map +1 -0
- package/dist/daemon/app-page.d.ts +0 -17
- package/dist/daemon/app-page.js +10 -6346
- package/dist/daemon/app-page.js.map +1 -1
- package/dist/daemon/auth.d.ts +24 -3
- package/dist/daemon/auth.js +66 -7
- package/dist/daemon/auth.js.map +1 -1
- package/dist/daemon/board.d.ts +7 -0
- package/dist/daemon/board.js +12 -1
- package/dist/daemon/board.js.map +1 -1
- package/dist/daemon/brand-icons.d.ts +6 -1
- package/dist/daemon/brand-icons.js +11 -3
- package/dist/daemon/brand-icons.js.map +1 -1
- package/dist/daemon/client.d.ts +665 -1
- package/dist/daemon/client.js +381 -3
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/delivery.d.ts +34 -0
- package/dist/daemon/delivery.js +63 -0
- package/dist/daemon/delivery.js.map +1 -0
- package/dist/daemon/deploys.d.ts +62 -0
- package/dist/daemon/deploys.js +159 -0
- package/dist/daemon/deploys.js.map +1 -0
- package/dist/daemon/landing.d.ts +192 -0
- package/dist/daemon/landing.js +992 -0
- package/dist/daemon/landing.js.map +1 -0
- package/dist/daemon/onboard.d.ts +150 -0
- package/dist/daemon/onboard.js +390 -0
- package/dist/daemon/onboard.js.map +1 -0
- package/dist/daemon/proof.d.ts +70 -0
- package/dist/daemon/proof.js +172 -0
- package/dist/daemon/proof.js.map +1 -0
- package/dist/daemon/push.d.ts +19 -1
- package/dist/daemon/push.js +90 -3
- package/dist/daemon/push.js.map +1 -1
- package/dist/daemon/relay.d.ts +82 -0
- package/dist/daemon/relay.js +351 -0
- package/dist/daemon/relay.js.map +1 -0
- package/dist/daemon/routes/agent-discovery.d.ts +4 -0
- package/dist/daemon/routes/agent-discovery.js +29 -0
- package/dist/daemon/routes/agent-discovery.js.map +1 -0
- package/dist/daemon/routes/agent-settings.d.ts +4 -0
- package/dist/daemon/routes/agent-settings.js +94 -0
- package/dist/daemon/routes/agent-settings.js.map +1 -0
- package/dist/daemon/routes/agents.d.ts +4 -0
- package/dist/daemon/routes/agents.js +105 -0
- package/dist/daemon/routes/agents.js.map +1 -0
- package/dist/daemon/routes/alerts.d.ts +4 -0
- package/dist/daemon/routes/alerts.js +137 -0
- package/dist/daemon/routes/alerts.js.map +1 -0
- package/dist/daemon/routes/approvals.d.ts +4 -0
- package/dist/daemon/routes/approvals.js +40 -0
- package/dist/daemon/routes/approvals.js.map +1 -0
- package/dist/daemon/routes/assets.d.ts +3 -0
- package/dist/daemon/routes/assets.js +64 -0
- package/dist/daemon/routes/assets.js.map +1 -0
- package/dist/daemon/routes/board.d.ts +4 -0
- package/dist/daemon/routes/board.js +147 -0
- package/dist/daemon/routes/board.js.map +1 -0
- package/dist/daemon/routes/brain.d.ts +4 -0
- package/dist/daemon/routes/brain.js +270 -0
- package/dist/daemon/routes/brain.js.map +1 -0
- package/dist/daemon/routes/chats.d.ts +4 -0
- package/dist/daemon/routes/chats.js +249 -0
- package/dist/daemon/routes/chats.js.map +1 -0
- package/dist/daemon/routes/clients.d.ts +4 -0
- package/dist/daemon/routes/clients.js +49 -0
- package/dist/daemon/routes/clients.js.map +1 -0
- package/dist/daemon/routes/cloud.d.ts +4 -0
- package/dist/daemon/routes/cloud.js +46 -0
- package/dist/daemon/routes/cloud.js.map +1 -0
- package/dist/daemon/routes/config.d.ts +4 -0
- package/dist/daemon/routes/config.js +35 -0
- package/dist/daemon/routes/config.js.map +1 -0
- package/dist/daemon/routes/context.d.ts +51 -0
- package/dist/daemon/routes/context.js +2 -0
- package/dist/daemon/routes/context.js.map +1 -0
- package/dist/daemon/routes/crews.d.ts +4 -0
- package/dist/daemon/routes/crews.js +149 -0
- package/dist/daemon/routes/crews.js.map +1 -0
- package/dist/daemon/routes/files.d.ts +3 -0
- package/dist/daemon/routes/files.js +253 -0
- package/dist/daemon/routes/files.js.map +1 -0
- package/dist/daemon/routes/git.d.ts +4 -0
- package/dist/daemon/routes/git.js +96 -0
- package/dist/daemon/routes/git.js.map +1 -0
- package/dist/daemon/routes/history.d.ts +4 -0
- package/dist/daemon/routes/history.js +101 -0
- package/dist/daemon/routes/history.js.map +1 -0
- package/dist/daemon/routes/integrations-project.d.ts +3 -0
- package/dist/daemon/routes/integrations-project.js +103 -0
- package/dist/daemon/routes/integrations-project.js.map +1 -0
- package/dist/daemon/routes/integrations.d.ts +3 -0
- package/dist/daemon/routes/integrations.js +111 -0
- package/dist/daemon/routes/integrations.js.map +1 -0
- package/dist/daemon/routes/logs.d.ts +3 -0
- package/dist/daemon/routes/logs.js +40 -0
- package/dist/daemon/routes/logs.js.map +1 -0
- package/dist/daemon/routes/messages.d.ts +4 -0
- package/dist/daemon/routes/messages.js +60 -0
- package/dist/daemon/routes/messages.js.map +1 -0
- package/dist/daemon/routes/observability.d.ts +4 -0
- package/dist/daemon/routes/observability.js +247 -0
- package/dist/daemon/routes/observability.js.map +1 -0
- package/dist/daemon/routes/onboard.d.ts +12 -0
- package/dist/daemon/routes/onboard.js +67 -0
- package/dist/daemon/routes/onboard.js.map +1 -0
- package/dist/daemon/routes/orchestra.d.ts +4 -0
- package/dist/daemon/routes/orchestra.js +110 -0
- package/dist/daemon/routes/orchestra.js.map +1 -0
- package/dist/daemon/routes/pairing.d.ts +4 -0
- package/dist/daemon/routes/pairing.js +155 -0
- package/dist/daemon/routes/pairing.js.map +1 -0
- package/dist/daemon/routes/preview.d.ts +24 -0
- package/dist/daemon/routes/preview.js +122 -0
- package/dist/daemon/routes/preview.js.map +1 -0
- package/dist/daemon/routes/project-actions.d.ts +4 -0
- package/dist/daemon/routes/project-actions.js +53 -0
- package/dist/daemon/routes/project-actions.js.map +1 -0
- package/dist/daemon/routes/project-team.d.ts +4 -0
- package/dist/daemon/routes/project-team.js +201 -0
- package/dist/daemon/routes/project-team.js.map +1 -0
- package/dist/daemon/routes/projects.d.ts +14 -0
- package/dist/daemon/routes/projects.js +138 -0
- package/dist/daemon/routes/projects.js.map +1 -0
- package/dist/daemon/routes/prompts.d.ts +4 -0
- package/dist/daemon/routes/prompts.js +44 -0
- package/dist/daemon/routes/prompts.js.map +1 -0
- package/dist/daemon/routes/proof.d.ts +4 -0
- package/dist/daemon/routes/proof.js +22 -0
- package/dist/daemon/routes/proof.js.map +1 -0
- package/dist/daemon/routes/providers.d.ts +3 -0
- package/dist/daemon/routes/providers.js +60 -0
- package/dist/daemon/routes/providers.js.map +1 -0
- package/dist/daemon/routes/public.d.ts +4 -0
- package/dist/daemon/routes/public.js +64 -0
- package/dist/daemon/routes/public.js.map +1 -0
- package/dist/daemon/routes/queue.d.ts +4 -0
- package/dist/daemon/routes/queue.js +140 -0
- package/dist/daemon/routes/queue.js.map +1 -0
- package/dist/daemon/routes/routing.d.ts +4 -0
- package/dist/daemon/routes/routing.js +61 -0
- package/dist/daemon/routes/routing.js.map +1 -0
- package/dist/daemon/routes/runtime-handler.d.ts +2 -0
- package/dist/daemon/routes/runtime-handler.js +63 -0
- package/dist/daemon/routes/runtime-handler.js.map +1 -0
- package/dist/daemon/routes/search.d.ts +4 -0
- package/dist/daemon/routes/search.js +17 -0
- package/dist/daemon/routes/search.js.map +1 -0
- package/dist/daemon/routes/servers.d.ts +4 -0
- package/dist/daemon/routes/servers.js +133 -0
- package/dist/daemon/routes/servers.js.map +1 -0
- package/dist/daemon/routes/setup.d.ts +3 -0
- package/dist/daemon/routes/setup.js +50 -0
- package/dist/daemon/routes/setup.js.map +1 -0
- package/dist/daemon/routes/shift.d.ts +4 -0
- package/dist/daemon/routes/shift.js +30 -0
- package/dist/daemon/routes/shift.js.map +1 -0
- package/dist/daemon/routes/specs.d.ts +4 -0
- package/dist/daemon/routes/specs.js +57 -0
- package/dist/daemon/routes/specs.js.map +1 -0
- package/dist/daemon/routes/task-delivery.d.ts +4 -0
- package/dist/daemon/routes/task-delivery.js +38 -0
- package/dist/daemon/routes/task-delivery.js.map +1 -0
- package/dist/daemon/routes/team.d.ts +4 -0
- package/dist/daemon/routes/team.js +193 -0
- package/dist/daemon/routes/team.js.map +1 -0
- package/dist/daemon/routes/terminals.d.ts +4 -0
- package/dist/daemon/routes/terminals.js +87 -0
- package/dist/daemon/routes/terminals.js.map +1 -0
- package/dist/daemon/routes/tools.d.ts +4 -0
- package/dist/daemon/routes/tools.js +180 -0
- package/dist/daemon/routes/tools.js.map +1 -0
- package/dist/daemon/routes/updates.d.ts +4 -0
- package/dist/daemon/routes/updates.js +89 -0
- package/dist/daemon/routes/updates.js.map +1 -0
- package/dist/daemon/routes/voice.d.ts +4 -0
- package/dist/daemon/routes/voice.js +53 -0
- package/dist/daemon/routes/voice.js.map +1 -0
- package/dist/daemon/runner.d.ts +186 -0
- package/dist/daemon/runner.js +612 -0
- package/dist/daemon/runner.js.map +1 -0
- package/dist/daemon/runtime/accounting.d.ts +130 -0
- package/dist/daemon/runtime/accounting.js +303 -0
- package/dist/daemon/runtime/accounting.js.map +1 -0
- package/dist/daemon/runtime/agents.d.ts +16 -0
- package/dist/daemon/runtime/agents.js +71 -0
- package/dist/daemon/runtime/agents.js.map +1 -0
- package/dist/daemon/runtime/briefings.d.ts +134 -0
- package/dist/daemon/runtime/briefings.js +329 -0
- package/dist/daemon/runtime/briefings.js.map +1 -0
- package/dist/daemon/runtime/native-usage.d.ts +21 -0
- package/dist/daemon/runtime/native-usage.js +71 -0
- package/dist/daemon/runtime/native-usage.js.map +1 -0
- package/dist/daemon/runtime/queue.d.ts +94 -0
- package/dist/daemon/runtime/queue.js +222 -0
- package/dist/daemon/runtime/queue.js.map +1 -0
- package/dist/daemon/runtime/relay.d.ts +93 -0
- package/dist/daemon/runtime/relay.js +240 -0
- package/dist/daemon/runtime/relay.js.map +1 -0
- package/dist/daemon/runtime/turns.d.ts +202 -0
- package/dist/daemon/runtime/turns.js +663 -0
- package/dist/daemon/runtime/turns.js.map +1 -0
- package/dist/daemon/runtime-support.d.ts +76 -0
- package/dist/daemon/runtime-support.js +134 -0
- package/dist/daemon/runtime-support.js.map +1 -0
- package/dist/daemon/runtime.d.ts +739 -70
- package/dist/daemon/runtime.js +2751 -470
- package/dist/daemon/runtime.js.map +1 -1
- package/dist/daemon/server.d.ts +82 -55
- package/dist/daemon/server.js +674 -1435
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/shift.d.ts +70 -0
- package/dist/daemon/shift.js +170 -0
- package/dist/daemon/shift.js.map +1 -0
- package/dist/daemon/specs.d.ts +92 -0
- package/dist/daemon/specs.js +285 -0
- package/dist/daemon/specs.js.map +1 -0
- package/dist/daemon/system.d.ts +208 -0
- package/dist/daemon/system.js +557 -0
- package/dist/daemon/system.js.map +1 -0
- package/dist/daemon/tasks.js +1 -1
- package/dist/daemon/team-brain.d.ts +136 -0
- package/dist/daemon/team-brain.js +659 -0
- package/dist/daemon/team-brain.js.map +1 -0
- package/dist/daemon/team-coordinator.d.ts +78 -0
- package/dist/daemon/team-coordinator.js +408 -0
- package/dist/daemon/team-coordinator.js.map +1 -0
- package/dist/daemon/team.d.ts +339 -0
- package/dist/daemon/team.js +1433 -0
- package/dist/daemon/team.js.map +1 -0
- package/dist/daemon/terminals.d.ts +22 -3
- package/dist/daemon/terminals.js +236 -16
- package/dist/daemon/terminals.js.map +1 -1
- package/dist/hub/client.d.ts +120 -0
- package/dist/hub/client.js +228 -0
- package/dist/hub/client.js.map +1 -0
- package/dist/hub/server.d.ts +32 -0
- package/dist/hub/server.js +180 -0
- package/dist/hub/server.js.map +1 -0
- package/dist/hub/supabase-client.d.ts +220 -0
- package/dist/hub/supabase-client.js +843 -0
- package/dist/hub/supabase-client.js.map +1 -0
- package/dist/observability/ask.d.ts +91 -0
- package/dist/observability/ask.js +179 -0
- package/dist/observability/ask.js.map +1 -0
- package/dist/observability/clickhouse.d.ts +33 -0
- package/dist/observability/clickhouse.js +48 -0
- package/dist/observability/clickhouse.js.map +1 -0
- package/dist/observability/decisions.d.ts +176 -0
- package/dist/observability/decisions.js +377 -0
- package/dist/observability/decisions.js.map +1 -0
- package/dist/observability/index.d.ts +89 -0
- package/dist/observability/index.js +348 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/insights.d.ts +151 -0
- package/dist/observability/insights.js +319 -0
- package/dist/observability/insights.js.map +1 -0
- package/dist/observability/logs-query.d.ts +82 -0
- package/dist/observability/logs-query.js +102 -0
- package/dist/observability/logs-query.js.map +1 -0
- package/dist/observability/logs.d.ts +72 -0
- package/dist/observability/logs.js +295 -0
- package/dist/observability/logs.js.map +1 -0
- package/dist/observability/metrics.d.ts +86 -0
- package/dist/observability/metrics.js +303 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/observability/otlp.d.ts +127 -0
- package/dist/observability/otlp.js +242 -0
- package/dist/observability/otlp.js.map +1 -0
- package/dist/observability/snapshots.d.ts +43 -0
- package/dist/observability/snapshots.js +129 -0
- package/dist/observability/snapshots.js.map +1 -0
- package/dist/observability/triage.d.ts +49 -0
- package/dist/observability/triage.js +284 -0
- package/dist/observability/triage.js.map +1 -0
- package/dist/providers/adapter.d.ts +50 -0
- package/dist/providers/adapter.js +38 -0
- package/dist/providers/adapter.js.map +1 -0
- package/dist/providers/agent.d.ts +110 -0
- package/dist/providers/agent.js +487 -0
- package/dist/providers/agent.js.map +1 -0
- package/dist/providers/approvals.d.ts +24 -0
- package/dist/providers/approvals.js +78 -0
- package/dist/providers/approvals.js.map +1 -0
- package/dist/providers/attachments.d.ts +20 -0
- package/dist/providers/attachments.js +51 -0
- package/dist/providers/attachments.js.map +1 -0
- package/dist/providers/claude/adapter.d.ts +99 -0
- package/dist/providers/claude/adapter.js +790 -0
- package/dist/providers/claude/adapter.js.map +1 -0
- package/dist/providers/codex/adapter.d.ts +113 -0
- package/dist/providers/codex/adapter.js +881 -0
- package/dist/providers/codex/adapter.js.map +1 -0
- package/dist/providers/codex/instructions.d.ts +11 -0
- package/dist/providers/codex/instructions.js +154 -0
- package/dist/providers/codex/instructions.js.map +1 -0
- package/dist/providers/codex/rpc.d.ts +36 -0
- package/dist/providers/codex/rpc.js +104 -0
- package/dist/providers/codex/rpc.js.map +1 -0
- package/dist/providers/contracts.d.ts +339 -0
- package/dist/providers/contracts.js +19 -0
- package/dist/providers/contracts.js.map +1 -0
- package/dist/providers/directory.d.ts +70 -0
- package/dist/providers/directory.js +95 -0
- package/dist/providers/directory.js.map +1 -0
- package/dist/providers/errors.d.ts +47 -0
- package/dist/providers/errors.js +24 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/index.d.ts +18 -0
- package/dist/providers/index.js +19 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/ingestion.d.ts +80 -0
- package/dist/providers/ingestion.js +330 -0
- package/dist/providers/ingestion.js.map +1 -0
- package/dist/providers/live.d.ts +22 -0
- package/dist/providers/live.js +56 -0
- package/dist/providers/live.js.map +1 -0
- package/dist/providers/process.d.ts +29 -0
- package/dist/providers/process.js +71 -0
- package/dist/providers/process.js.map +1 -0
- package/dist/providers/reaper.d.ts +33 -0
- package/dist/providers/reaper.js +65 -0
- package/dist/providers/reaper.js.map +1 -0
- package/dist/providers/service.d.ts +87 -0
- package/dist/providers/service.js +256 -0
- package/dist/providers/service.js.map +1 -0
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +1 -1
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +385 -6
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +7 -0
- package/dist/version.js +16 -0
- package/dist/version.js.map +1 -0
- package/dist/web/app.css +4646 -0
- package/dist/web/app.js +22640 -0
- package/dist/web/app.js.map +7 -0
- package/dist/web/shell.html +39 -0
- package/docs/README.md +21 -0
- package/docs/SETUP.md +10 -0
- package/docs/adapters.md +21 -8
- package/docs/brain-continuity.md +186 -0
- package/docs/cloud.md +108 -0
- package/docs/crews.md +62 -0
- package/docs/design-system.md +31 -2
- package/docs/integration-notes.md +128 -20
- package/docs/loompad.md +105 -0
- package/docs/observability.md +220 -0
- package/docs/play-store.md +123 -0
- package/docs/proposals/agent-teams.md +249 -0
- package/docs/proposals/brain-continuity-implementation.md +468 -0
- package/docs/proposals/brain-session-continuity.md +623 -0
- package/docs/proposals/t3code-port.md +81 -0
- package/docs/proposals/t3code-reference.md +95 -0
- package/docs/refactoring/AGENT-TEAMS-PROGRESS.md +88 -0
- package/docs/refactoring/BOUNDARIES.md +120 -0
- package/docs/refactoring/BRAIN-AUDIT.md +176 -0
- package/docs/refactoring/BRAIN-NOTES.md +390 -0
- package/docs/refactoring/BRAIN-TODO.md +214 -0
- package/docs/refactoring/NOTES.md +129 -0
- package/docs/refactoring/T3-PORT-NOTES.md +165 -0
- package/docs/refactoring/T3-PORT-TODO.md +123 -0
- package/docs/refactoring/TODO.md +46 -0
- package/docs/refactoring/brain-benchmark-2026-09-28.json +48 -0
- package/docs/refactoring/brain-benchmark-audit-2026-09-28.json +48 -0
- package/docs/release-notes/v0.2.0.md +35 -0
- package/docs/release-notes/v0.2.1.md +59 -0
- package/docs/release-notes/v0.2.3.md +77 -0
- package/docs/release-notes/v0.2.4.md +128 -0
- package/docs/release-notes/v0.2.5.md +153 -0
- package/docs/release-notes/v0.2.6.md +62 -0
- package/docs/release-notes/v1.0.0.md +120 -0
- package/docs/release-notes/v1.0.1.md +77 -0
- package/docs/release-notes/v1.1.0.md +71 -0
- package/docs/release-notes/v1.1.1.md +38 -0
- package/docs/release-notes/v1.1.2.md +21 -0
- package/docs/teams-architecture.md +697 -0
- package/docs/teams.md +171 -0
- package/docs/windows.md +85 -0
- package/package.json +22 -9
- package/dist/adapters/claude-code.d.ts +0 -26
- package/dist/adapters/claude-code.js +0 -209
- package/dist/adapters/claude-code.js.map +0 -1
- package/dist/adapters/codex.d.ts +0 -51
- package/dist/adapters/codex.js +0 -291
- package/dist/adapters/codex.js.map +0 -1
- package/docs/img/board-linear.png +0 -0
- package/docs/img/board-projects.png +0 -0
- package/docs/img/brain.png +0 -0
- package/docs/img/settings.png +0 -0
- package/docs/img/workspace.png +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Night Shift: line up work, go to sleep, wake up to a report you can trust.
|
|
3
|
+
*
|
|
4
|
+
* Each task runs in its own chat, one after another, through the queue. While
|
|
5
|
+
* the shift runs, Proof checks every "done" against what ran and a clean
|
|
6
|
+
* re-run, the loop watch nudges an agent going in circles, and Quota Relay
|
|
7
|
+
* hands work on when an agent runs out. This file is the part that reads a
|
|
8
|
+
* task's chat afterwards and says, plainly, how it went. Pure.
|
|
9
|
+
*/
|
|
10
|
+
import type { LoomEvent } from "../types.js";
|
|
11
|
+
export type ShiftTaskStatus = "verified" | "done" | "didnt_hold" | "failed" | "needs_you" | "not_started" | "running";
|
|
12
|
+
export interface ShiftTask {
|
|
13
|
+
text: string;
|
|
14
|
+
chat: string;
|
|
15
|
+
/** The queue item it went out as, until it's dispatched. */
|
|
16
|
+
queueId?: string;
|
|
17
|
+
/** Set once the shift knows it won't run (stopped by the clock or the budget). */
|
|
18
|
+
skipped?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface ShiftConfig {
|
|
21
|
+
id: string;
|
|
22
|
+
startedAt: number;
|
|
23
|
+
/** Who takes the tasks; absent → whoever the queue would pick (auto). */
|
|
24
|
+
to?: string;
|
|
25
|
+
/** Stop starting new tasks after this moment (epoch ms). */
|
|
26
|
+
until?: number;
|
|
27
|
+
/** Stop starting new tasks once the shift has spent this much. */
|
|
28
|
+
maxUsd?: number;
|
|
29
|
+
tasks: ShiftTask[];
|
|
30
|
+
/** Set when it stops early, with why. */
|
|
31
|
+
stopped?: string;
|
|
32
|
+
/** Set once the report has gone out. */
|
|
33
|
+
reportedAt?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface TaskOutcome {
|
|
36
|
+
text: string;
|
|
37
|
+
chat: string;
|
|
38
|
+
status: ShiftTaskStatus;
|
|
39
|
+
agent?: string;
|
|
40
|
+
/** Proof's one-line verdict, when it checked. */
|
|
41
|
+
headline?: string;
|
|
42
|
+
/** Times the loop watch caught it going in circles. */
|
|
43
|
+
loops: number;
|
|
44
|
+
files: string[];
|
|
45
|
+
costUsd: number;
|
|
46
|
+
ms: number;
|
|
47
|
+
error?: string;
|
|
48
|
+
question?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface ShiftReport {
|
|
51
|
+
shiftId: string;
|
|
52
|
+
startedAt: number;
|
|
53
|
+
endedAt: number;
|
|
54
|
+
tasks: TaskOutcome[];
|
|
55
|
+
counts: Record<ShiftTaskStatus, number>;
|
|
56
|
+
costUsd: number;
|
|
57
|
+
stopped?: string;
|
|
58
|
+
/** "4 tasks: 3 verified, 1 needs you" */
|
|
59
|
+
summary: string;
|
|
60
|
+
}
|
|
61
|
+
/** How one task went, from its chat's events. */
|
|
62
|
+
export declare function outcomeOf(task: ShiftTask, events: LoomEvent[], opts?: {
|
|
63
|
+
queued?: boolean;
|
|
64
|
+
}): TaskOutcome;
|
|
65
|
+
export declare function report(shift: ShiftConfig, outcomes: TaskOutcome[], endedAt?: number): ShiftReport;
|
|
66
|
+
/** "7:00", "07:30", "7am", "6:45 pm", or an ISO time → the next such moment after `now`. */
|
|
67
|
+
export declare function parseUntil(raw: string | number | undefined, now?: number): number | undefined;
|
|
68
|
+
/** One task per line; blank lines and "- " / "1. " list markers dropped. */
|
|
69
|
+
export declare function parseTasks(raw: string | string[]): string[];
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Night Shift: line up work, go to sleep, wake up to a report you can trust.
|
|
3
|
+
*
|
|
4
|
+
* Each task runs in its own chat, one after another, through the queue. While
|
|
5
|
+
* the shift runs, Proof checks every "done" against what ran and a clean
|
|
6
|
+
* re-run, the loop watch nudges an agent going in circles, and Quota Relay
|
|
7
|
+
* hands work on when an agent runs out. This file is the part that reads a
|
|
8
|
+
* task's chat afterwards and says, plainly, how it went. Pure.
|
|
9
|
+
*/
|
|
10
|
+
/** How one task went, from its chat's events. */
|
|
11
|
+
export function outcomeOf(task, events, opts = {}) {
|
|
12
|
+
const base = { text: task.text, chat: task.chat, status: "not_started", loops: 0, files: [], costUsd: 0, ms: 0 };
|
|
13
|
+
if (opts.queued || task.skipped)
|
|
14
|
+
return base;
|
|
15
|
+
const files = new Set();
|
|
16
|
+
let lastDone, lastProof, lastAsk, lastSay, lastAnswer, lastError;
|
|
17
|
+
for (const e of events) {
|
|
18
|
+
const p = e.payload ?? {};
|
|
19
|
+
if (e.kind === "run_complete" && e.agentId) {
|
|
20
|
+
lastDone = e;
|
|
21
|
+
base.ms += Number(p.durationMs ?? 0) || 0;
|
|
22
|
+
base.agent = e.agentId;
|
|
23
|
+
}
|
|
24
|
+
else if (e.kind === "proof")
|
|
25
|
+
lastProof = e;
|
|
26
|
+
else if (e.kind === "needs_input")
|
|
27
|
+
lastAsk = e;
|
|
28
|
+
else if (e.kind === "message" && !e.agentId)
|
|
29
|
+
lastSay = e;
|
|
30
|
+
else if (e.kind === "error" && e.agentId)
|
|
31
|
+
lastError = e;
|
|
32
|
+
else if (e.kind === "turn_diff")
|
|
33
|
+
for (const f of p.files ?? [])
|
|
34
|
+
files.add(f.path);
|
|
35
|
+
else if (e.kind === "status" && p.state === "question_answered")
|
|
36
|
+
lastAnswer = e;
|
|
37
|
+
else if (e.kind === "status" && p.state === "loop_detected")
|
|
38
|
+
base.loops++;
|
|
39
|
+
else if (e.kind === "status" && p.state === "turn_cost")
|
|
40
|
+
base.costUsd += Number(p.costUsd ?? 0) || 0;
|
|
41
|
+
}
|
|
42
|
+
base.files = [...files];
|
|
43
|
+
base.costUsd = Math.round(base.costUsd * 10_000) / 10_000;
|
|
44
|
+
if (lastDone?.payload.error)
|
|
45
|
+
return { ...base, status: "failed", error: String(lastDone.payload.error).slice(0, 300) };
|
|
46
|
+
// a turn that died with an error (no clean end after it, nothing said since)
|
|
47
|
+
if (lastError && (!lastDone || lastError.id > lastDone.id) && (!lastSay || lastError.id > lastSay.id)) {
|
|
48
|
+
return { ...base, status: "failed", agent: lastError.agentId ?? base.agent, error: String(lastError.payload.message ?? "it stopped with an error").slice(0, 300) };
|
|
49
|
+
}
|
|
50
|
+
// an open question: asked after the last thing you (or the queue) said, and not answered — whether the
|
|
51
|
+
// agent's turn is parked on it or ended on it, it's waiting for you
|
|
52
|
+
if (lastAsk && (!lastSay || lastAsk.id > lastSay.id) && (!lastAnswer || lastAsk.id > lastAnswer.id)) {
|
|
53
|
+
return { ...base, status: "needs_you", agent: lastAsk.agentId ?? base.agent, question: String(lastAsk.payload.question ?? "") };
|
|
54
|
+
}
|
|
55
|
+
if (!lastDone)
|
|
56
|
+
return { ...base, status: lastSay ? "running" : "not_started" };
|
|
57
|
+
// Proof only counts when it checked the last turn (a nudge can come after it)
|
|
58
|
+
if (lastProof && lastProof.id > lastDone.id) {
|
|
59
|
+
const v = String(lastProof.payload.verdict ?? "");
|
|
60
|
+
const headline = String(lastProof.payload.headline ?? "");
|
|
61
|
+
if (v === "contradicted")
|
|
62
|
+
return { ...base, status: "didnt_hold", headline };
|
|
63
|
+
if (v === "verified")
|
|
64
|
+
return { ...base, status: "verified", headline };
|
|
65
|
+
return { ...base, status: "done", headline };
|
|
66
|
+
}
|
|
67
|
+
return { ...base, status: "done" };
|
|
68
|
+
}
|
|
69
|
+
const WORDS = {
|
|
70
|
+
verified: "verified", done: "done (not checked)", didnt_hold: "didn't hold", failed: "failed",
|
|
71
|
+
needs_you: "need you", not_started: "not started", running: "still running",
|
|
72
|
+
};
|
|
73
|
+
const ORDER = ["verified", "done", "didnt_hold", "failed", "needs_you", "running", "not_started"];
|
|
74
|
+
export function report(shift, outcomes, endedAt = Date.now()) {
|
|
75
|
+
const counts = Object.fromEntries(ORDER.map((s) => [s, 0]));
|
|
76
|
+
for (const o of outcomes)
|
|
77
|
+
counts[o.status]++;
|
|
78
|
+
const parts = ORDER.filter((s) => counts[s]).map((s) => `${counts[s]} ${s === "needs_you" && counts[s] === 1 ? "needs you" : WORDS[s]}`);
|
|
79
|
+
const n = outcomes.length;
|
|
80
|
+
return {
|
|
81
|
+
shiftId: shift.id, startedAt: shift.startedAt, endedAt, tasks: outcomes, counts,
|
|
82
|
+
costUsd: Math.round(outcomes.reduce((x, o) => x + o.costUsd, 0) * 10_000) / 10_000,
|
|
83
|
+
...(shift.stopped ? { stopped: shift.stopped } : {}),
|
|
84
|
+
summary: `${n} task${n === 1 ? "" : "s"}: ${parts.join(", ")}`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** "7:00", "07:30", "7am", "6:45 pm", or an ISO time → the next such moment after `now`. */
|
|
88
|
+
export function parseUntil(raw, now = Date.now()) {
|
|
89
|
+
if (raw === undefined || raw === "")
|
|
90
|
+
return undefined;
|
|
91
|
+
if (typeof raw === "number")
|
|
92
|
+
return Number.isFinite(raw) ? raw : undefined;
|
|
93
|
+
const m = /^\s*(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*$/i.exec(raw);
|
|
94
|
+
if (m) {
|
|
95
|
+
let h = Number(m[1]);
|
|
96
|
+
const min = Number(m[2] ?? 0);
|
|
97
|
+
const ap = m[3]?.toLowerCase();
|
|
98
|
+
if (ap === "pm" && h < 12)
|
|
99
|
+
h += 12;
|
|
100
|
+
if (ap === "am" && h === 12)
|
|
101
|
+
h = 0;
|
|
102
|
+
if (h > 23 || min > 59)
|
|
103
|
+
throw new Error(`"${raw}" isn't a time of day`);
|
|
104
|
+
const d = new Date(now);
|
|
105
|
+
d.setHours(h, min, 0, 0);
|
|
106
|
+
if (d.getTime() <= now)
|
|
107
|
+
d.setDate(d.getDate() + 1);
|
|
108
|
+
return d.getTime();
|
|
109
|
+
}
|
|
110
|
+
const t = Date.parse(raw);
|
|
111
|
+
if (!Number.isFinite(t))
|
|
112
|
+
throw new Error(`"${raw}" isn't a time I can read — try 7:00, 6:30am or an ISO time`);
|
|
113
|
+
return t;
|
|
114
|
+
}
|
|
115
|
+
/** One task per line; blank lines and "- " / "1. " list markers dropped. */
|
|
116
|
+
export function parseTasks(raw) {
|
|
117
|
+
const lines = Array.isArray(raw) ? raw : raw.split("\n");
|
|
118
|
+
return lines.map((l) => String(l).replace(/^\s*(?:[-*•]|\d+[.)])\s+/, "").trim()).filter(Boolean).slice(0, 50);
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=shift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shift.js","sourceRoot":"","sources":["../../src/core/shift.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installing a skill — copying somebody else's SKILL.md into this project.
|
|
3
|
+
*
|
|
4
|
+
* Discovery (core/skills.ts) answers "what do I already have". This answers the
|
|
5
|
+
* other half: a skill you found in a gist, a repo, or a folder on disk becomes
|
|
6
|
+
* `<projectDir>/skills/<id>/`, which is the first root the loader scans and the
|
|
7
|
+
* only one Loom is allowed to write to.
|
|
8
|
+
*
|
|
9
|
+
* ## Why so much of this file is refusal
|
|
10
|
+
*
|
|
11
|
+
* Both entry points take a string from an HTTP body and turn it into filesystem
|
|
12
|
+
* writes, so the interesting cases are all the ones that must not happen:
|
|
13
|
+
*
|
|
14
|
+
* - **The id is a path.** `id` is joined onto `<projectDir>/skills/`, so an id
|
|
15
|
+
* of `../../.ssh` writes outside the project. It is validated against a
|
|
16
|
+
* character class rather than sanitised, because silently rewriting a
|
|
17
|
+
* malicious id into a benign one installs a skill under a name the caller
|
|
18
|
+
* didn't ask for, and then everything downstream is about the wrong skill.
|
|
19
|
+
*
|
|
20
|
+
* - **The URL is a command.** `installSkillFromGit` runs git. Every argument
|
|
21
|
+
* goes through `execFile` with an array — never a shell string — so a URL
|
|
22
|
+
* containing `;` or `$(…)` is a URL git will fail to clone, not a command.
|
|
23
|
+
* The URL is also checked to be http(s) or ssh git first: git happily
|
|
24
|
+
* accepts `ext::sh -c …` and `--upload-pack=…` as "remotes", and those are
|
|
25
|
+
* code execution with extra steps.
|
|
26
|
+
*
|
|
27
|
+
* - **An overwrite is silent.** Installing over an existing skill destroys
|
|
28
|
+
* whatever the user wrote there. It needs `force`, and the error says which
|
|
29
|
+
* id collided so the UI can offer the choice instead of guessing.
|
|
30
|
+
*
|
|
31
|
+
* A clone is bounded (90s) and runs with `GIT_TERMINAL_PROMPT=0`: a private repo
|
|
32
|
+
* must fail as a private repo, not hang a request forever on a credential prompt
|
|
33
|
+
* that nobody is at a terminal to answer.
|
|
34
|
+
*/
|
|
35
|
+
/** A refusal with a reason the UI can show verbatim. Distinct so routes can 400 it. */
|
|
36
|
+
export declare class SkillInstallError extends Error {
|
|
37
|
+
constructor(message: string);
|
|
38
|
+
}
|
|
39
|
+
/** Where an installed skill came from — recorded so the caller can say so. */
|
|
40
|
+
export type SkillInstallOrigin = "dir" | "git";
|
|
41
|
+
export interface SkillInstallResult {
|
|
42
|
+
/** The directory name under `<projectDir>/skills/`, and the skill's id. */
|
|
43
|
+
id: string;
|
|
44
|
+
/** The `name:` from the frontmatter. */
|
|
45
|
+
name: string;
|
|
46
|
+
description: string;
|
|
47
|
+
/** Absolute path to the installed directory. */
|
|
48
|
+
dir: string;
|
|
49
|
+
/** Absolute path to the installed SKILL.md. */
|
|
50
|
+
path: string;
|
|
51
|
+
/** How it arrived: a local directory, or a clone. */
|
|
52
|
+
from: SkillInstallOrigin;
|
|
53
|
+
/** The exact source string the caller gave — the src dir, or the git URL. */
|
|
54
|
+
source: string;
|
|
55
|
+
/** True when an existing skill of the same id was replaced (needs `force`). */
|
|
56
|
+
replaced: boolean;
|
|
57
|
+
/** With #all: the other skills installed from the same repo. */
|
|
58
|
+
also?: string[];
|
|
59
|
+
}
|
|
60
|
+
export interface InstallOptions {
|
|
61
|
+
/** Replace an existing skill of the same id instead of refusing. */
|
|
62
|
+
force?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Install under this id instead of the source directory's name. Validated
|
|
65
|
+
* exactly like a derived id — the caller doesn't get a shortcut past the
|
|
66
|
+
* traversal check by supplying the id itself.
|
|
67
|
+
*/
|
|
68
|
+
id?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Copy a skill directory into the project.
|
|
72
|
+
*
|
|
73
|
+
* The whole directory comes across, not just SKILL.md: skills reference scripts
|
|
74
|
+
* and `references/` files next to them, and a skill installed without its
|
|
75
|
+
* attachments is a skill whose instructions point at nothing.
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* Author a skill in place: scaffold `skills/<id>/SKILL.md`, validated.
|
|
79
|
+
*
|
|
80
|
+
* Skills could be browsed and installed but never written — authoring one
|
|
81
|
+
* meant knowing the frontmatter convention by heart and hand-building the
|
|
82
|
+
* directory. The validation is the same reader the roster uses (parseSkillMd),
|
|
83
|
+
* so a skill this accepts is a skill the loader will actually offer: refusing
|
|
84
|
+
* to write anything the roster would silently drop is the whole point.
|
|
85
|
+
*/
|
|
86
|
+
export declare function authorSkill(projectDir: string, input: {
|
|
87
|
+
id: string;
|
|
88
|
+
name: string;
|
|
89
|
+
description: string;
|
|
90
|
+
body?: string;
|
|
91
|
+
}): {
|
|
92
|
+
dir: string;
|
|
93
|
+
file: string;
|
|
94
|
+
};
|
|
95
|
+
export declare function installSkillFromDir(srcDir: string, projectDir: string, opts?: InstallOptions): SkillInstallResult;
|
|
96
|
+
/**
|
|
97
|
+
* Clone a repository and install the skill inside it.
|
|
98
|
+
*
|
|
99
|
+
* `--depth 1` because history is irrelevant to a directory of markdown, and a
|
|
100
|
+
* shallow clone of a large skills repo is the difference between a second and a
|
|
101
|
+
* minute. The SKILL.md is looked for at the repo root first and then one level
|
|
102
|
+
* down — the two layouts people actually publish ("this repo IS a skill" and
|
|
103
|
+
* "this repo is a folder of skills"). Deeper than that is ambiguous: a repo with
|
|
104
|
+
* `a/b/SKILL.md` and `c/d/SKILL.md` has no obvious answer, and picking one is
|
|
105
|
+
* how you install something the user didn't choose.
|
|
106
|
+
*
|
|
107
|
+
* The temp clone is removed on every path, including the failures.
|
|
108
|
+
*/
|
|
109
|
+
export declare function installSkillFromGit(url: string, projectDir: string, opts?: InstallOptions & {
|
|
110
|
+
timeoutMs?: number;
|
|
111
|
+
}): Promise<SkillInstallResult>;
|
|
112
|
+
/**
|
|
113
|
+
* The repo root if it is itself a skill, else the first child directory that is.
|
|
114
|
+
*
|
|
115
|
+
* Exported for the tests: the two layouts this handles are the whole of the
|
|
116
|
+
* git installer's judgement, and a clone is the one part of that path a test
|
|
117
|
+
* can't reach offline.
|
|
118
|
+
*/
|
|
119
|
+
export declare function findSkillDir(root: string): string | null;
|
|
120
|
+
/**
|
|
121
|
+
* Every skill in a checkout: the root if it is one; else each child directory
|
|
122
|
+
* that is; else each one under skills/ (and .claude/skills/) — the layouts
|
|
123
|
+
* people publish.
|
|
124
|
+
*/
|
|
125
|
+
export declare function findSkillDirs(root: string): string[];
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installing a skill — copying somebody else's SKILL.md into this project.
|
|
3
|
+
*
|
|
4
|
+
* Discovery (core/skills.ts) answers "what do I already have". This answers the
|
|
5
|
+
* other half: a skill you found in a gist, a repo, or a folder on disk becomes
|
|
6
|
+
* `<projectDir>/skills/<id>/`, which is the first root the loader scans and the
|
|
7
|
+
* only one Loom is allowed to write to.
|
|
8
|
+
*
|
|
9
|
+
* ## Why so much of this file is refusal
|
|
10
|
+
*
|
|
11
|
+
* Both entry points take a string from an HTTP body and turn it into filesystem
|
|
12
|
+
* writes, so the interesting cases are all the ones that must not happen:
|
|
13
|
+
*
|
|
14
|
+
* - **The id is a path.** `id` is joined onto `<projectDir>/skills/`, so an id
|
|
15
|
+
* of `../../.ssh` writes outside the project. It is validated against a
|
|
16
|
+
* character class rather than sanitised, because silently rewriting a
|
|
17
|
+
* malicious id into a benign one installs a skill under a name the caller
|
|
18
|
+
* didn't ask for, and then everything downstream is about the wrong skill.
|
|
19
|
+
*
|
|
20
|
+
* - **The URL is a command.** `installSkillFromGit` runs git. Every argument
|
|
21
|
+
* goes through `execFile` with an array — never a shell string — so a URL
|
|
22
|
+
* containing `;` or `$(…)` is a URL git will fail to clone, not a command.
|
|
23
|
+
* The URL is also checked to be http(s) or ssh git first: git happily
|
|
24
|
+
* accepts `ext::sh -c …` and `--upload-pack=…` as "remotes", and those are
|
|
25
|
+
* code execution with extra steps.
|
|
26
|
+
*
|
|
27
|
+
* - **An overwrite is silent.** Installing over an existing skill destroys
|
|
28
|
+
* whatever the user wrote there. It needs `force`, and the error says which
|
|
29
|
+
* id collided so the UI can offer the choice instead of guessing.
|
|
30
|
+
*
|
|
31
|
+
* A clone is bounded (90s) and runs with `GIT_TERMINAL_PROMPT=0`: a private repo
|
|
32
|
+
* must fail as a private repo, not hang a request forever on a credential prompt
|
|
33
|
+
* that nobody is at a terminal to answer.
|
|
34
|
+
*/
|
|
35
|
+
import { execFile } from "./process.js";
|
|
36
|
+
import fs from "node:fs";
|
|
37
|
+
import os from "node:os";
|
|
38
|
+
import path from "node:path";
|
|
39
|
+
import { parseSkillMd } from "./skills.js";
|
|
40
|
+
/** A refusal with a reason the UI can show verbatim. Distinct so routes can 400 it. */
|
|
41
|
+
export class SkillInstallError extends Error {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "SkillInstallError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A skill id is a single directory name and nothing else.
|
|
49
|
+
*
|
|
50
|
+
* Letters, digits, dot, dash, underscore. No separators (so it cannot escape),
|
|
51
|
+
* no leading dot (so it cannot be `.` , `..`, or a hidden directory that the
|
|
52
|
+
* loader would then never list), and a length cap so a 4KB id can't be used to
|
|
53
|
+
* probe path limits.
|
|
54
|
+
*/
|
|
55
|
+
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
56
|
+
function assertSafeId(id) {
|
|
57
|
+
const clean = String(id ?? "").trim();
|
|
58
|
+
if (!clean)
|
|
59
|
+
throw new SkillInstallError("skill id is empty — cannot tell where to install it");
|
|
60
|
+
if (!ID_RE.test(clean) || clean.includes("..")) {
|
|
61
|
+
throw new SkillInstallError(`"${clean}" is not a usable skill id — it must be a single directory name of letters, digits, dot, dash or underscore`);
|
|
62
|
+
}
|
|
63
|
+
return clean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Read and validate the SKILL.md that makes a directory a skill.
|
|
67
|
+
*
|
|
68
|
+
* A directory with no SKILL.md is not a skill, and one whose frontmatter has no
|
|
69
|
+
* `name:` is a skill nothing can refer to — the loader would fall back to the
|
|
70
|
+
* directory name, which means installing it produces a manifest whose name was
|
|
71
|
+
* invented here. Both are refused with the path, because "it didn't work" is
|
|
72
|
+
* useless when you cloned a repo with four candidate folders in it.
|
|
73
|
+
*/
|
|
74
|
+
function readManifest(dir) {
|
|
75
|
+
const file = path.join(dir, "SKILL.md");
|
|
76
|
+
let raw;
|
|
77
|
+
try {
|
|
78
|
+
raw = fs.readFileSync(file, "utf8");
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
throw new SkillInstallError(`no readable SKILL.md in ${dir} — a skill is a directory containing SKILL.md`);
|
|
82
|
+
}
|
|
83
|
+
const { name, description } = parseSkillMd(raw);
|
|
84
|
+
if (!name.trim()) {
|
|
85
|
+
throw new SkillInstallError(`${file} has no \`name:\` in its frontmatter — a skill needs one to be referred to`);
|
|
86
|
+
}
|
|
87
|
+
return { name: name.trim(), description, file };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Copy a skill directory into the project.
|
|
91
|
+
*
|
|
92
|
+
* The whole directory comes across, not just SKILL.md: skills reference scripts
|
|
93
|
+
* and `references/` files next to them, and a skill installed without its
|
|
94
|
+
* attachments is a skill whose instructions point at nothing.
|
|
95
|
+
*/
|
|
96
|
+
/**
|
|
97
|
+
* Author a skill in place: scaffold `skills/<id>/SKILL.md`, validated.
|
|
98
|
+
*
|
|
99
|
+
* Skills could be browsed and installed but never written — authoring one
|
|
100
|
+
* meant knowing the frontmatter convention by heart and hand-building the
|
|
101
|
+
* directory. The validation is the same reader the roster uses (parseSkillMd),
|
|
102
|
+
* so a skill this accepts is a skill the loader will actually offer: refusing
|
|
103
|
+
* to write anything the roster would silently drop is the whole point.
|
|
104
|
+
*/
|
|
105
|
+
export function authorSkill(projectDir, input) {
|
|
106
|
+
const id = input.id.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
107
|
+
if (!id)
|
|
108
|
+
throw new SkillInstallError("a skill needs an id (letters, digits, dashes)");
|
|
109
|
+
const name = input.name.trim();
|
|
110
|
+
if (!name)
|
|
111
|
+
throw new SkillInstallError("a skill needs a name");
|
|
112
|
+
const description = input.description.trim();
|
|
113
|
+
if (!description) {
|
|
114
|
+
throw new SkillInstallError("a skill needs a description — it's how agents decide when to use it");
|
|
115
|
+
}
|
|
116
|
+
const skillsDir = path.join(projectDir, "skills");
|
|
117
|
+
const dest = path.join(skillsDir, id);
|
|
118
|
+
if (path.dirname(dest) !== skillsDir) {
|
|
119
|
+
throw new SkillInstallError(`refusing "${id}" — it resolves outside ${skillsDir}`);
|
|
120
|
+
}
|
|
121
|
+
const file = path.join(dest, "SKILL.md");
|
|
122
|
+
if (fs.existsSync(file)) {
|
|
123
|
+
throw new SkillInstallError(`"${id}" already exists — edit ${file} instead`);
|
|
124
|
+
}
|
|
125
|
+
const md = `---\n` +
|
|
126
|
+
`name: ${name}\n` +
|
|
127
|
+
`description: ${description}\n` +
|
|
128
|
+
`---\n\n` +
|
|
129
|
+
(input.body?.trim()
|
|
130
|
+
? `${input.body.trim()}\n`
|
|
131
|
+
: `# ${name}\n\nWrite the instructions an agent should follow here.\n\n` +
|
|
132
|
+
`## When to use\n\n${description}\n\n## Steps\n\n1. …\n`);
|
|
133
|
+
// Round-trip through the real parser before anything touches disk.
|
|
134
|
+
const parsed = parseSkillMd(md);
|
|
135
|
+
if (parsed.name !== name || parsed.description !== description) {
|
|
136
|
+
throw new SkillInstallError("the generated frontmatter didn't survive the parser — check for YAML-breaking characters");
|
|
137
|
+
}
|
|
138
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
139
|
+
fs.writeFileSync(file, md);
|
|
140
|
+
return { dir: dest, file };
|
|
141
|
+
}
|
|
142
|
+
export function installSkillFromDir(srcDir, projectDir, opts = {}) {
|
|
143
|
+
const src = path.resolve(String(srcDir ?? "").trim());
|
|
144
|
+
if (!src || src === path.parse(src).root) {
|
|
145
|
+
throw new SkillInstallError("no source directory given");
|
|
146
|
+
}
|
|
147
|
+
let stat;
|
|
148
|
+
try {
|
|
149
|
+
stat = fs.statSync(src);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
throw new SkillInstallError(`${src} does not exist`);
|
|
153
|
+
}
|
|
154
|
+
if (!stat.isDirectory())
|
|
155
|
+
throw new SkillInstallError(`${src} is not a directory`);
|
|
156
|
+
const id = assertSafeId(opts.id ?? path.basename(src));
|
|
157
|
+
const { name, description } = readManifest(src);
|
|
158
|
+
const skillsDir = path.join(projectDir, "skills");
|
|
159
|
+
const dest = path.join(skillsDir, id);
|
|
160
|
+
// Belt and braces: assertSafeId already forbids separators, so this can only
|
|
161
|
+
// fire if that regex is ever loosened. It is cheap and it is the check that
|
|
162
|
+
// actually protects the filesystem, so it stays.
|
|
163
|
+
if (path.dirname(dest) !== skillsDir) {
|
|
164
|
+
throw new SkillInstallError(`refusing to install "${id}" — it resolves outside ${skillsDir}`);
|
|
165
|
+
}
|
|
166
|
+
if (path.resolve(dest) === src) {
|
|
167
|
+
throw new SkillInstallError(`${src} is already this project's "${id}" skill`);
|
|
168
|
+
}
|
|
169
|
+
const existed = fs.existsSync(dest);
|
|
170
|
+
if (existed && !opts.force) {
|
|
171
|
+
throw new SkillInstallError(`a skill named "${id}" is already installed — pass force to replace it`);
|
|
172
|
+
}
|
|
173
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
174
|
+
if (existed)
|
|
175
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
176
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
177
|
+
return {
|
|
178
|
+
id,
|
|
179
|
+
name,
|
|
180
|
+
description,
|
|
181
|
+
dir: dest,
|
|
182
|
+
path: path.join(dest, "SKILL.md"),
|
|
183
|
+
from: "dir",
|
|
184
|
+
source: src,
|
|
185
|
+
replaced: existed,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// git
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
/**
|
|
192
|
+
* The remote forms git can be handed safely.
|
|
193
|
+
*
|
|
194
|
+
* https/http and ssh (both `ssh://host/path` and the scp-ish `git@host:path`).
|
|
195
|
+
* Deliberately NOT `file://`, `ext::`, `--upload-pack=`, or a bare local path:
|
|
196
|
+
* the transport helpers behind those run programs of the remote's choosing, and
|
|
197
|
+
* a URL field in a web form is not where that decision should be made.
|
|
198
|
+
*/
|
|
199
|
+
const GIT_URL_RE = /^(?:https?:\/\/[^\s]+|ssh:\/\/[^\s]+|[A-Za-z0-9_.-]+@[A-Za-z0-9_.-]+:[^\s]+)$/;
|
|
200
|
+
function assertGitUrl(url) {
|
|
201
|
+
const clean = String(url ?? "").trim();
|
|
202
|
+
if (!clean)
|
|
203
|
+
throw new SkillInstallError("no git URL given");
|
|
204
|
+
if (clean.startsWith("-")) {
|
|
205
|
+
// `git clone -- <url>` already stops this, but a URL that looks like a flag
|
|
206
|
+
// is never a real repository and the error is clearer here.
|
|
207
|
+
throw new SkillInstallError(`"${clean}" is not a git URL`);
|
|
208
|
+
}
|
|
209
|
+
if (!GIT_URL_RE.test(clean)) {
|
|
210
|
+
throw new SkillInstallError(`"${clean}" is not a git URL — expected https://…, ssh://…, or git@host:owner/repo`);
|
|
211
|
+
}
|
|
212
|
+
return clean;
|
|
213
|
+
}
|
|
214
|
+
/** The repository's own name, used as the skill id when SKILL.md sits at the root. */
|
|
215
|
+
function repoName(url) {
|
|
216
|
+
const tail = url.replace(/\/+$/, "").split(/[/:]/).pop() ?? "";
|
|
217
|
+
return tail.replace(/\.git$/i, "");
|
|
218
|
+
}
|
|
219
|
+
function runGit(args, cwd, timeoutMs) {
|
|
220
|
+
return new Promise((resolve, reject) => {
|
|
221
|
+
execFile("git", args, {
|
|
222
|
+
cwd,
|
|
223
|
+
timeout: timeoutMs,
|
|
224
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
225
|
+
// No credential prompt, no interactive host-key question: this runs
|
|
226
|
+
// inside an HTTP handler where there is nobody to answer either.
|
|
227
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo", GCM_INTERACTIVE: "never" },
|
|
228
|
+
}, (err, _stdout, stderr) => {
|
|
229
|
+
if (err) {
|
|
230
|
+
const detail = String(stderr || err.message).split("\n").find((l) => l.trim())?.trim() ?? "";
|
|
231
|
+
reject(new SkillInstallError(detail || `git ${args[0]} failed`));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
resolve();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Clone a repository and install the skill inside it.
|
|
240
|
+
*
|
|
241
|
+
* `--depth 1` because history is irrelevant to a directory of markdown, and a
|
|
242
|
+
* shallow clone of a large skills repo is the difference between a second and a
|
|
243
|
+
* minute. The SKILL.md is looked for at the repo root first and then one level
|
|
244
|
+
* down — the two layouts people actually publish ("this repo IS a skill" and
|
|
245
|
+
* "this repo is a folder of skills"). Deeper than that is ambiguous: a repo with
|
|
246
|
+
* `a/b/SKILL.md` and `c/d/SKILL.md` has no obvious answer, and picking one is
|
|
247
|
+
* how you install something the user didn't choose.
|
|
248
|
+
*
|
|
249
|
+
* The temp clone is removed on every path, including the failures.
|
|
250
|
+
*/
|
|
251
|
+
export async function installSkillFromGit(url, projectDir, opts = {}) {
|
|
252
|
+
// "#name" picks one skill out of a repo that holds several; "#all" takes every one.
|
|
253
|
+
const hash = url.indexOf("#");
|
|
254
|
+
const pick = hash >= 0 ? url.slice(hash + 1).trim() : "";
|
|
255
|
+
const remote = assertGitUrl(hash >= 0 ? url.slice(0, hash) : url);
|
|
256
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "loom-skill-"));
|
|
257
|
+
const checkout = path.join(tmp, "repo");
|
|
258
|
+
try {
|
|
259
|
+
await runGit(["clone", "--depth", "1", "--quiet", "--", remote, checkout], tmp, opts.timeoutMs ?? 90_000);
|
|
260
|
+
const dirs = findSkillDirs(checkout);
|
|
261
|
+
if (!dirs.length) {
|
|
262
|
+
throw new SkillInstallError(`no SKILL.md found in ${remote} — looked in the repository root, one level below it, and under skills/`);
|
|
263
|
+
}
|
|
264
|
+
const idOf = (d) => (d === checkout ? repoName(remote) : path.basename(d));
|
|
265
|
+
let chosen;
|
|
266
|
+
if (pick === "all")
|
|
267
|
+
chosen = dirs;
|
|
268
|
+
else if (pick) {
|
|
269
|
+
const hit = dirs.find((d) => path.basename(d) === pick);
|
|
270
|
+
if (!hit)
|
|
271
|
+
throw new SkillInstallError(`no skill "${pick}" in ${remote} — it has: ${dirs.map(idOf).join(", ")}`);
|
|
272
|
+
chosen = [hit];
|
|
273
|
+
}
|
|
274
|
+
else if (dirs.length === 1)
|
|
275
|
+
chosen = dirs;
|
|
276
|
+
else {
|
|
277
|
+
// Several skills and no choice: say so, rather than install whichever sorts first.
|
|
278
|
+
throw new SkillInstallError(`${remote} holds ${dirs.length} skills: ${dirs.map(idOf).slice(0, 12).join(", ")}${dirs.length > 12 ? ", …" : ""} — add #<name> to the URL to pick one, or #all for every one`);
|
|
279
|
+
}
|
|
280
|
+
const results = chosen.map((d) => installSkillFromDir(d, projectDir, { ...opts, id: chosen.length === 1 ? (opts.id ?? idOf(d)) : idOf(d) }));
|
|
281
|
+
return { ...results[0], from: "git", source: remote, ...(results.length > 1 ? { also: results.slice(1).map((r) => r.id) } : {}) };
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
try {
|
|
285
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// A leftover clone in the OS temp dir is not worth failing an install over.
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* The repo root if it is itself a skill, else the first child directory that is.
|
|
294
|
+
*
|
|
295
|
+
* Exported for the tests: the two layouts this handles are the whole of the
|
|
296
|
+
* git installer's judgement, and a clone is the one part of that path a test
|
|
297
|
+
* can't reach offline.
|
|
298
|
+
*/
|
|
299
|
+
export function findSkillDir(root) {
|
|
300
|
+
return findSkillDirs(root)[0] ?? null;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Every skill in a checkout: the root if it is one; else each child directory
|
|
304
|
+
* that is; else each one under skills/ (and .claude/skills/) — the layouts
|
|
305
|
+
* people publish.
|
|
306
|
+
*/
|
|
307
|
+
export function findSkillDirs(root) {
|
|
308
|
+
if (fs.existsSync(path.join(root, "SKILL.md")))
|
|
309
|
+
return [root];
|
|
310
|
+
const kids = (dir) => {
|
|
311
|
+
try {
|
|
312
|
+
return fs
|
|
313
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
314
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith("."))
|
|
315
|
+
.map((d) => path.join(dir, d.name))
|
|
316
|
+
.filter((d) => fs.existsSync(path.join(d, "SKILL.md")))
|
|
317
|
+
.sort();
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
const top = kids(root);
|
|
324
|
+
if (top.length)
|
|
325
|
+
return top;
|
|
326
|
+
return [...kids(path.join(root, "skills")), ...kids(path.join(root, ".claude", "skills"))];
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=skill-install.js.map
|