gsd-pi 2.76.0-dev.fe143342a → 2.77.0-dev.1d17f366c
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/README.md +17 -35
- package/dist/claude-cli-check.js +32 -3
- package/dist/cli-web-branch.d.ts +1 -0
- package/dist/cli-web-branch.js +3 -0
- package/dist/cli.js +38 -2
- package/dist/extension-discovery.d.ts +6 -0
- package/dist/extension-discovery.js +37 -0
- package/dist/extension-registry.d.ts +3 -0
- package/dist/extension-sort.d.ts +18 -0
- package/dist/extension-sort.js +114 -0
- package/dist/extension-validator.d.ts +47 -0
- package/dist/extension-validator.js +127 -0
- package/dist/loader.js +35 -7
- package/dist/onboarding.js +45 -0
- package/dist/provider-migrations.d.ts +18 -0
- package/dist/provider-migrations.js +14 -0
- package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +78 -59
- package/dist/resources/extensions/cmux/index.js +20 -0
- package/dist/resources/extensions/github-sync/templates.js +103 -0
- package/dist/resources/extensions/google-search/extension-manifest.json +5 -4
- package/dist/resources/extensions/google-search/index.js +3 -375
- package/dist/resources/extensions/gsd/abandon-detect.js +44 -0
- package/dist/resources/extensions/gsd/auto/loop.js +90 -2
- package/dist/resources/extensions/gsd/auto/phases.js +95 -21
- package/dist/resources/extensions/gsd/auto/resolve.js +24 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +48 -4
- package/dist/resources/extensions/gsd/auto/session.js +18 -1
- package/dist/resources/extensions/gsd/auto/turn-epoch.js +95 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +115 -17
- package/dist/resources/extensions/gsd/auto-loop.js +1 -1
- package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +90 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +46 -1
- package/dist/resources/extensions/gsd/auto-start.js +45 -39
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +11 -5
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +11 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +109 -61
- package/dist/resources/extensions/gsd/auto.js +97 -31
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +27 -1
- package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +4 -2
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +11 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -6
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -6
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
- package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +31 -4
- package/dist/resources/extensions/gsd/commands-cmux.js +9 -6
- package/dist/resources/extensions/gsd/commands-extensions.js +634 -43
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
- package/dist/resources/extensions/gsd/dispatch-guard.js +29 -3
- package/dist/resources/extensions/gsd/file-lock.js +49 -9
- package/dist/resources/extensions/gsd/git-service.js +1 -0
- package/dist/resources/extensions/gsd/gitignore.js +2 -0
- package/dist/resources/extensions/gsd/gsd-db.js +90 -30
- package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -1
- package/dist/resources/extensions/gsd/guided-flow.js +212 -9
- package/dist/resources/extensions/gsd/health-widget.js +4 -1
- package/dist/resources/extensions/gsd/journal.js +17 -2
- package/dist/resources/extensions/gsd/key-manager.js +22 -0
- package/dist/resources/extensions/gsd/milestone-actions.js +15 -0
- package/dist/resources/extensions/gsd/milestone-summary-classifier.js +37 -0
- package/dist/resources/extensions/gsd/model-router.js +36 -3
- package/dist/resources/extensions/gsd/notifications.js +30 -16
- package/dist/resources/extensions/gsd/pre-execution-checks.js +31 -6
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +29 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -0
- package/dist/resources/extensions/gsd/reports.js +5 -4
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +12 -4
- package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
- package/dist/resources/extensions/gsd/state-transition-matrix.js +118 -0
- package/dist/resources/extensions/gsd/state.js +25 -25
- package/dist/resources/extensions/gsd/token-counter.js +22 -5
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +16 -10
- package/dist/resources/extensions/gsd/tools/complete-slice.js +21 -0
- package/dist/resources/extensions/gsd/tools/complete-task.js +31 -0
- package/dist/resources/extensions/gsd/uok/audit.js +18 -2
- package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +33 -0
- package/dist/resources/extensions/gsd/uok/execution-graph.js +10 -0
- package/dist/resources/extensions/gsd/uok/gitops.js +2 -1
- package/dist/resources/extensions/gsd/uok/loop-adapter.js +37 -10
- package/dist/resources/extensions/gsd/uok/parity-report.js +58 -0
- package/dist/resources/extensions/gsd/uok/plan-v2.js +30 -7
- package/dist/resources/extensions/gsd/uok/writer.js +82 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +10 -2
- package/dist/resources/extensions/gsd/worktree-manager.js +1 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +50 -10
- package/dist/resources/extensions/mcp-client/auth.js +10 -1
- package/dist/resources/extensions/mcp-client/index.js +118 -9
- package/dist/resources/extensions/shared/cmux-events.js +12 -0
- package/dist/resources/extensions/shared/rtk-session-stats.js +1 -2
- package/dist/resources/skills/create-skill/SKILL.md +2 -2
- package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
- package/dist/resources/skills/create-skill/workflows/audit-skill.md +4 -4
- package/dist/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
- package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
- package/dist/resources/skills/write-docs/SKILL.md +2 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
- package/dist/web/standalone/.next/server/chunks/1926.js +1 -0
- package/dist/web/standalone/.next/server/chunks/6897.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +11 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-5fc74f13a25fa1bb.js → webpack-2e68521d7c82f7c2.js} +1 -1
- package/dist/welcome-screen.js +6 -1
- package/dist/wizard.js +2 -0
- package/package.json +16 -14
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/README.md +3 -3
- package/packages/mcp-server/dist/env-writer.d.ts +1 -0
- package/packages/mcp-server/dist/env-writer.d.ts.map +1 -1
- package/packages/mcp-server/dist/env-writer.js +74 -6
- package/packages/mcp-server/dist/env-writer.js.map +1 -1
- package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
- package/packages/mcp-server/dist/remote-questions.js +732 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts +7 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +95 -10
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/session-manager.d.ts +14 -0
- package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
- package/packages/mcp-server/dist/session-manager.js +49 -1
- package/packages/mcp-server/dist/session-manager.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +15 -6
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +9 -3
- package/packages/mcp-server/src/env-writer.test.ts +79 -1
- package/packages/mcp-server/src/env-writer.ts +76 -6
- package/packages/mcp-server/src/mcp-server.test.ts +67 -0
- package/packages/mcp-server/src/readers/readers.test.ts +5 -1
- package/packages/mcp-server/src/remote-questions.test.ts +294 -0
- package/packages/mcp-server/src/remote-questions.ts +916 -0
- package/packages/mcp-server/src/server.ts +118 -16
- package/packages/mcp-server/src/session-manager.ts +43 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +44 -0
- package/packages/mcp-server/src/workflow-tools.ts +19 -6
- package/packages/mcp-server/tsconfig.test.json +19 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +6 -1
- package/packages/native/src/__tests__/clipboard.test.mjs +69 -23
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/package.json +6 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +220 -15
- package/packages/pi-ai/dist/models/custom.d.ts +38 -0
- package/packages/pi-ai/dist/models/custom.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/custom.js +41 -0
- package/packages/pi-ai/dist/models/custom.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +8 -3
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +16 -1
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/package.json +6 -1
- package/packages/pi-ai/src/models/custom.ts +42 -0
- package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
- package/packages/pi-ai/src/providers/anthropic.ts +9 -3
- package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
- package/packages/pi-ai/src/providers/simple-options.ts +17 -1
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +3 -2
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +7 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +25 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +105 -6
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js +230 -28
- package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +30 -2
- package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.js +113 -12
- package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +29 -18
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js +130 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-utils.test.js +56 -1
- package/packages/pi-coding-agent/dist/core/compaction-utils.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +8 -15
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.d.ts +25 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.js +109 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-registry.d.ts +67 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-registry.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-registry.js +167 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-registry.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +3 -2
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +24 -8
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +14 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +11 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +2 -2
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +14 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +4 -1
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.test.js +19 -1
- package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +21 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +3 -3
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js +2 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +15 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +7 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +31 -9
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +14 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +6 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +3 -2
- package/packages/pi-coding-agent/src/core/agent-session.ts +11 -0
- package/packages/pi-coding-agent/src/core/compaction/compaction.test.ts +368 -28
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +122 -6
- package/packages/pi-coding-agent/src/core/compaction/utils.ts +111 -13
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.test.ts +154 -0
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +32 -18
- package/packages/pi-coding-agent/src/core/compaction-utils.test.ts +68 -1
- package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +9 -18
- package/packages/pi-coding-agent/src/core/extensions/extension-discovery.ts +119 -0
- package/packages/pi-coding-agent/src/core/extensions/extension-registry.ts +222 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +24 -11
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +15 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +13 -0
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +2 -2
- package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +16 -0
- package/packages/pi-coding-agent/src/core/resource-loader.ts +1 -1
- package/packages/pi-coding-agent/src/core/sdk.test.ts +25 -1
- package/packages/pi-coding-agent/src/core/sdk.ts +10 -3
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +30 -1
- package/packages/pi-coding-agent/src/core/session-manager.ts +1 -1
- package/packages/pi-coding-agent/src/core/system-prompt.ts +3 -3
- package/packages/pi-coding-agent/src/core/tools/path-utils.test.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +17 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +14 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +45 -11
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +14 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +12 -5
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +21 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.d.ts +7 -0
- package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.js +20 -0
- package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
- package/packages/pi-tui/package.json +6 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +20 -5
- package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +27 -0
- package/packages/pi-tui/src/stdin-buffer.ts +26 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/package.json +6 -1
- package/pkg/package.json +1 -1
- package/scripts/install.js +512 -0
- package/scripts/lib/workspace-manifest.cjs +86 -0
- package/scripts/link-workspace-packages.cjs +5 -17
- package/scripts/postinstall.js +9 -178
- package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +91 -63
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +114 -12
- package/src/resources/extensions/cmux/index.ts +35 -10
- package/src/resources/extensions/github-sync/templates.ts +151 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +59 -0
- package/src/resources/extensions/google-search/extension-manifest.json +5 -4
- package/src/resources/extensions/google-search/index.ts +9 -470
- package/src/resources/extensions/gsd/abandon-detect.ts +62 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +14 -1
- package/src/resources/extensions/gsd/auto/loop.ts +104 -2
- package/src/resources/extensions/gsd/auto/phases.ts +123 -21
- package/src/resources/extensions/gsd/auto/resolve.ts +29 -0
- package/src/resources/extensions/gsd/auto/run-unit.ts +56 -4
- package/src/resources/extensions/gsd/auto/session.ts +28 -1
- package/src/resources/extensions/gsd/auto/turn-epoch.ts +108 -0
- package/src/resources/extensions/gsd/auto/types.ts +1 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +117 -16
- package/src/resources/extensions/gsd/auto-loop.ts +1 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +92 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +40 -1
- package/src/resources/extensions/gsd/auto-start.ts +48 -52
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -5
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +14 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +122 -68
- package/src/resources/extensions/gsd/auto.ts +105 -35
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -1
- package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -2
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +11 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +18 -6
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +13 -9
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
- package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +27 -8
- package/src/resources/extensions/gsd/commands-cmux.ts +10 -6
- package/src/resources/extensions/gsd/commands-extensions.ts +747 -41
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
- package/src/resources/extensions/gsd/dispatch-guard.ts +26 -2
- package/src/resources/extensions/gsd/file-lock.ts +84 -11
- package/src/resources/extensions/gsd/git-service.ts +1 -0
- package/src/resources/extensions/gsd/gitignore.ts +2 -1
- package/src/resources/extensions/gsd/gsd-db.ts +92 -32
- package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -1
- package/src/resources/extensions/gsd/guided-flow.ts +259 -10
- package/src/resources/extensions/gsd/health-widget.ts +3 -1
- package/src/resources/extensions/gsd/journal.ts +29 -3
- package/src/resources/extensions/gsd/key-manager.ts +22 -0
- package/src/resources/extensions/gsd/milestone-actions.ts +18 -0
- package/src/resources/extensions/gsd/milestone-summary-classifier.ts +42 -0
- package/src/resources/extensions/gsd/model-router.ts +42 -1
- package/src/resources/extensions/gsd/notifications.ts +27 -15
- package/src/resources/extensions/gsd/pre-execution-checks.ts +33 -7
- package/src/resources/extensions/gsd/preferences-types.ts +8 -0
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +29 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -0
- package/src/resources/extensions/gsd/reports.ts +5 -4
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +16 -3
- package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
- package/src/resources/extensions/gsd/state-transition-matrix.ts +152 -0
- package/src/resources/extensions/gsd/state.ts +35 -30
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +238 -4
- package/src/resources/extensions/gsd/tests/auto-mode-guards.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/cmux.test.ts +5 -9
- package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +61 -1
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +161 -0
- package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +1 -2
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +14 -9
- package/src/resources/extensions/gsd/tests/dispatch-guard-summary-db-mismatch.test.ts +77 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/execution-entry-missing-context-4671.test.ts +173 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/file-lock.test.ts +86 -12
- package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +131 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +296 -1
- package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/integration/gitignore-tracked-gsd.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/integration/worktree-e2e.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/milestone-status-authoritative.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/milestone-summary-classifier.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/parallel-commit-scope.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +150 -0
- package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +337 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +39 -25
- package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
- package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
- package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/rewrite-docs-abandon-detect.test.ts +195 -0
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +413 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
- package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
- package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +50 -2
- package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/uok-parity-report.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +42 -2
- package/src/resources/extensions/gsd/tests/uok-writer.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/validate-extension-package.test.ts +168 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +138 -5
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +25 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -5
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
- package/src/resources/extensions/gsd/token-counter.ts +22 -5
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +15 -9
- package/src/resources/extensions/gsd/tools/complete-slice.ts +38 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +49 -0
- package/src/resources/extensions/gsd/uok/audit.ts +20 -2
- package/src/resources/extensions/gsd/uok/contracts.ts +65 -0
- package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +56 -0
- package/src/resources/extensions/gsd/uok/execution-graph.ts +22 -0
- package/src/resources/extensions/gsd/uok/gitops.ts +6 -1
- package/src/resources/extensions/gsd/uok/loop-adapter.ts +45 -10
- package/src/resources/extensions/gsd/uok/parity-report.ts +84 -0
- package/src/resources/extensions/gsd/uok/plan-v2.ts +39 -8
- package/src/resources/extensions/gsd/uok/writer.ts +113 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +23 -3
- package/src/resources/extensions/gsd/worktree-manager.ts +1 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +54 -9
- package/src/resources/extensions/mcp-client/auth.ts +12 -1
- package/src/resources/extensions/mcp-client/index.ts +129 -10
- package/src/resources/extensions/shared/cmux-events.ts +59 -0
- package/src/resources/extensions/shared/rtk-session-stats.ts +1 -2
- package/src/resources/skills/create-skill/SKILL.md +2 -2
- package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
- package/src/resources/skills/create-skill/workflows/audit-skill.md +4 -4
- package/src/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
- package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
- package/src/resources/skills/write-docs/SKILL.md +2 -1
- package/dist/web/standalone/.next/server/chunks/7461.js +0 -1
- package/dist/web/standalone/.next/static/chunks/2826.e59e8578e2e28639.js +0 -9
- /package/dist/web/standalone/.next/static/{n21VtX2hZlkpdEUO_nU4z → vidAVJkURvTJ0_V2-64ro}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{n21VtX2hZlkpdEUO_nU4z → vidAVJkURvTJ0_V2-64ro}/_ssgManifest.js +0 -0
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GSD Extensions Command — /gsd extensions
|
|
3
3
|
*
|
|
4
|
-
* Manage the extension registry: list, enable, disable, info.
|
|
4
|
+
* Manage the extension registry: list, enable, disable, info, install.
|
|
5
5
|
* Self-contained — no imports outside the extensions tree (extensions are loaded
|
|
6
6
|
* via jiti at runtime from ~/.gsd/agent/, not compiled by tsc).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { dirname, join } from "node:path";
|
|
12
|
-
import { homedir } from "node:os";
|
|
10
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
|
+
import { homedir, tmpdir } from "node:os";
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
14
|
+
import { lockSync, unlockSync } from "proper-lockfile";
|
|
15
|
+
import semver from "semver";
|
|
13
16
|
|
|
14
17
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
15
18
|
|
|
@@ -40,6 +43,9 @@ interface ExtensionRegistryEntry {
|
|
|
40
43
|
source: "bundled" | "user" | "project";
|
|
41
44
|
disabledAt?: string;
|
|
42
45
|
disabledReason?: string;
|
|
46
|
+
version?: string;
|
|
47
|
+
installedFrom?: string;
|
|
48
|
+
installType?: "npm" | "git" | "local";
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
interface ExtensionRegistry {
|
|
@@ -82,6 +88,32 @@ function saveRegistry(registry: ExtensionRegistry): void {
|
|
|
82
88
|
} catch { /* non-fatal */ }
|
|
83
89
|
}
|
|
84
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Run a registry load → mutate → save transaction under a cross-process lock.
|
|
93
|
+
* Prevents two concurrent `gsd extensions install/uninstall/update` invocations
|
|
94
|
+
* from trampling each other's registry mutations.
|
|
95
|
+
*
|
|
96
|
+
* Uses proper-lockfile.lockSync against the registry path. Directory is created
|
|
97
|
+
* first so locking works on fresh installs. Lock is always released via finally.
|
|
98
|
+
*/
|
|
99
|
+
function withRegistryLock<T>(mutate: (registry: ExtensionRegistry) => T): T {
|
|
100
|
+
const filePath = getRegistryPath();
|
|
101
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
102
|
+
// lockSync requires the file to exist — ensure it does before acquiring.
|
|
103
|
+
if (!existsSync(filePath)) {
|
|
104
|
+
writeFileSync(filePath, JSON.stringify({ version: 1, entries: {} }, null, 2), "utf-8");
|
|
105
|
+
}
|
|
106
|
+
lockSync(filePath, { retries: { retries: 5, minTimeout: 50, maxTimeout: 500 } });
|
|
107
|
+
try {
|
|
108
|
+
const registry = loadRegistry();
|
|
109
|
+
const result = mutate(registry);
|
|
110
|
+
saveRegistry(registry);
|
|
111
|
+
return result;
|
|
112
|
+
} finally {
|
|
113
|
+
try { unlockSync(filePath); } catch { /* lock may already be gone */ }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
85
117
|
function isEnabled(registry: ExtensionRegistry, id: string): boolean {
|
|
86
118
|
const entry = registry.entries[id];
|
|
87
119
|
if (!entry) return true;
|
|
@@ -100,18 +132,625 @@ function readManifest(dir: string): ExtensionManifest | null {
|
|
|
100
132
|
}
|
|
101
133
|
}
|
|
102
134
|
|
|
135
|
+
// ─── Package Validation (mirrored — D-14, no src/ imports) ────────────────
|
|
136
|
+
|
|
137
|
+
export interface ValidationResult {
|
|
138
|
+
valid: boolean;
|
|
139
|
+
errors: string[];
|
|
140
|
+
warnings: string[];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function validateExtensionPackage(packageDir: string): ValidationResult {
|
|
144
|
+
const errors: string[] = [];
|
|
145
|
+
const warnings: string[] = [];
|
|
146
|
+
|
|
147
|
+
// Check package.json exists
|
|
148
|
+
const pkgPath = join(packageDir, "package.json");
|
|
149
|
+
if (!existsSync(pkgPath)) {
|
|
150
|
+
return { valid: false, errors: ["package.json not found"], warnings };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let pkg: Record<string, unknown>;
|
|
154
|
+
try {
|
|
155
|
+
pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
156
|
+
} catch {
|
|
157
|
+
return { valid: false, errors: ["package.json is invalid JSON"], warnings };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// (a) gsd.extension: true marker (D-12a)
|
|
161
|
+
const gsdField = pkg.gsd as Record<string, unknown> | undefined;
|
|
162
|
+
if (gsdField?.extension !== true) {
|
|
163
|
+
errors.push('package.json missing "gsd": { "extension": true }');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// (b) pi.extensions entry paths exist and are resolvable (D-12b)
|
|
167
|
+
const piField = pkg.pi as Record<string, unknown> | undefined;
|
|
168
|
+
const piExtensions = piField?.extensions;
|
|
169
|
+
if (!Array.isArray(piExtensions) || piExtensions.length === 0) {
|
|
170
|
+
errors.push('package.json missing "pi": { "extensions": [...] }');
|
|
171
|
+
} else {
|
|
172
|
+
for (const entry of piExtensions) {
|
|
173
|
+
if (typeof entry === "string") {
|
|
174
|
+
const resolved = join(packageDir, entry);
|
|
175
|
+
if (!existsSync(resolved)) {
|
|
176
|
+
errors.push(`pi.extensions entry not found: ${entry}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// (c) @gsd/* packages must be in peerDependencies, not dependencies/devDependencies (D-12c)
|
|
183
|
+
// Mirrors validateExtensionManifest below and extension-validator.ts:checkDependencyPlacement.
|
|
184
|
+
for (const field of ["dependencies", "devDependencies"] as const) {
|
|
185
|
+
const deps = (pkg[field] as Record<string, unknown> | undefined) ?? {};
|
|
186
|
+
for (const dep of Object.keys(deps)) {
|
|
187
|
+
if (dep.startsWith("@gsd/")) {
|
|
188
|
+
errors.push(`"${dep}" must be in peerDependencies, not ${field}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
194
|
+
}
|
|
195
|
+
|
|
103
196
|
function discoverManifests(): Map<string, ExtensionManifest> {
|
|
104
|
-
const extDir = getAgentExtensionsDir();
|
|
105
197
|
const manifests = new Map<string, ExtensionManifest>();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
198
|
+
// Scan both bundled/agent dir and user-installed dir so CLI (list/info/
|
|
199
|
+
// enable/disable) sees the same set the loader will merge at runtime.
|
|
200
|
+
// Bundled entries are scanned first so user-installed IDs override on collision.
|
|
201
|
+
const dirs = [getAgentExtensionsDir(), getInstalledExtDir()];
|
|
202
|
+
for (const extDir of dirs) {
|
|
203
|
+
if (!existsSync(extDir)) continue;
|
|
204
|
+
for (const entry of readdirSync(extDir, { withFileTypes: true })) {
|
|
205
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
206
|
+
const m = readManifest(join(extDir, entry.name));
|
|
207
|
+
if (m) manifests.set(m.id, m);
|
|
208
|
+
}
|
|
111
209
|
}
|
|
112
210
|
return manifests;
|
|
113
211
|
}
|
|
114
212
|
|
|
213
|
+
function getInstalledExtDir(): string {
|
|
214
|
+
return join(gsdHome, "extensions");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Source: derived from npm/git URL conventions (from RESEARCH.md)
|
|
218
|
+
function detectInstallType(specifier: string): "npm" | "git" | "local" {
|
|
219
|
+
if (
|
|
220
|
+
specifier.startsWith("/") ||
|
|
221
|
+
specifier.startsWith("./") ||
|
|
222
|
+
specifier.startsWith("../") ||
|
|
223
|
+
specifier.startsWith("~/")
|
|
224
|
+
) return "local";
|
|
225
|
+
if (
|
|
226
|
+
specifier.startsWith("git+") ||
|
|
227
|
+
specifier.startsWith("git://") ||
|
|
228
|
+
specifier.startsWith("github:") ||
|
|
229
|
+
specifier.startsWith("gitlab:") ||
|
|
230
|
+
specifier.startsWith("bitbucket:") ||
|
|
231
|
+
(specifier.startsWith("https://") && specifier.endsWith(".git")) ||
|
|
232
|
+
(specifier.startsWith("http://") && specifier.endsWith(".git"))
|
|
233
|
+
) return "git";
|
|
234
|
+
return "npm";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─── Manifest Validation (mirrored from extension-validator.ts) ─────────────
|
|
238
|
+
// Note: distinct from validateExtensionPackage above (which validates a package
|
|
239
|
+
// directory on disk and returns string errors). This one validates an already-
|
|
240
|
+
// parsed package.json object and returns structured errors, used by install.
|
|
241
|
+
|
|
242
|
+
interface ManifestValidationError {
|
|
243
|
+
code: string;
|
|
244
|
+
message: string;
|
|
245
|
+
field?: string;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
interface ManifestValidationResult {
|
|
249
|
+
valid: boolean;
|
|
250
|
+
errors: ManifestValidationError[];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function validateExtensionManifest(pkg: unknown, opts: { extensionId?: string; allowGsdNamespace?: boolean } = {}): ManifestValidationResult {
|
|
254
|
+
const errors: ManifestValidationError[] = [];
|
|
255
|
+
|
|
256
|
+
// Check gsd.extension === true (strict)
|
|
257
|
+
if (typeof pkg !== "object" || pkg === null) {
|
|
258
|
+
errors.push({ code: "MISSING_GSD_MARKER", message: 'package.json must declare "gsd": { "extension": true } to be recognized as a GSD extension.', field: "gsd.extension" });
|
|
259
|
+
} else {
|
|
260
|
+
const obj = pkg as Record<string, unknown>;
|
|
261
|
+
const gsd = obj.gsd;
|
|
262
|
+
if (typeof gsd !== "object" || gsd === null || (gsd as Record<string, unknown>).extension !== true) {
|
|
263
|
+
errors.push({ code: "MISSING_GSD_MARKER", message: 'package.json must declare "gsd": { "extension": true } to be recognized as a GSD extension.', field: "gsd.extension" });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Check namespace reservation
|
|
268
|
+
if (opts.extensionId && opts.extensionId.startsWith("gsd.") && opts.allowGsdNamespace !== true) {
|
|
269
|
+
errors.push({ code: "RESERVED_NAMESPACE", message: `Extension ID "${opts.extensionId}" is reserved for GSD core extensions. Use a different namespace for community extensions.`, field: "extensionId" });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Check dependency placement
|
|
273
|
+
if (typeof pkg === "object" && pkg !== null) {
|
|
274
|
+
const obj = pkg as Record<string, unknown>;
|
|
275
|
+
for (const field of ["dependencies", "devDependencies"] as const) {
|
|
276
|
+
const deps = obj[field];
|
|
277
|
+
if (typeof deps === "object" && deps !== null) {
|
|
278
|
+
for (const pkgName of Object.keys(deps as Record<string, unknown>)) {
|
|
279
|
+
if (pkgName.startsWith("@gsd/")) {
|
|
280
|
+
errors.push({ code: "WRONG_DEP_FIELD", message: `"${pkgName}" must not appear in "${field}". Move it to "peerDependencies".`, field });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return { valid: errors.length === 0, errors };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ─── Post-install convergence ────────────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Allowed characters for an extension id when used as a path segment.
|
|
294
|
+
* Rejects anything that could enable traversal or escape (slashes, "..", backslashes).
|
|
295
|
+
*/
|
|
296
|
+
const SAFE_EXTENSION_ID_RE = /^[A-Za-z0-9._-]+$/;
|
|
297
|
+
|
|
298
|
+
function isSafeExtensionId(id: string): boolean {
|
|
299
|
+
if (!id || id === "." || id === "..") return false;
|
|
300
|
+
if (id.includes("/") || id.includes("\\") || id.includes("..")) return false;
|
|
301
|
+
return SAFE_EXTENSION_ID_RE.test(id);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Post-install convergence: validate package and read manifest.
|
|
306
|
+
* Returns the (validated) extension ID and manifest on success, or null on failure.
|
|
307
|
+
* Caller is responsible for writing the registry entry *after* the final commit
|
|
308
|
+
* rename succeeds so a failed move doesn't leave a dangling registry entry.
|
|
309
|
+
*/
|
|
310
|
+
function postInstallValidate(
|
|
311
|
+
destPath: string,
|
|
312
|
+
specifier: string,
|
|
313
|
+
ctx: ExtensionCommandContext,
|
|
314
|
+
): { id: string; manifest: ExtensionManifest } | null {
|
|
315
|
+
// Read package.json
|
|
316
|
+
const pkgJsonPath = join(destPath, "package.json");
|
|
317
|
+
if (!existsSync(pkgJsonPath)) {
|
|
318
|
+
ctx.ui.notify(`Cannot install "${specifier}": no package.json found.`, "error");
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
let pkgJson: Record<string, unknown>;
|
|
322
|
+
try {
|
|
323
|
+
pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
324
|
+
} catch {
|
|
325
|
+
ctx.ui.notify(`Cannot install "${specifier}": malformed package.json.`, "error");
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Read extension-manifest.json for the ID
|
|
330
|
+
const manifest = readManifest(destPath);
|
|
331
|
+
const extensionId = manifest?.id;
|
|
332
|
+
|
|
333
|
+
// Validate
|
|
334
|
+
const validation = validateExtensionManifest(pkgJson, { extensionId });
|
|
335
|
+
if (!validation.valid) {
|
|
336
|
+
const msgs = validation.errors.map(e => e.message).join("\n");
|
|
337
|
+
ctx.ui.notify(`Cannot install "${specifier}": ${msgs}`, "error");
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (!manifest || !extensionId) {
|
|
342
|
+
ctx.ui.notify(`Cannot install "${specifier}": no extension-manifest.json with valid id found.`, "error");
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// The id from the manifest is used as a path segment under installedExtDir.
|
|
347
|
+
// Reject unsafe ids before the caller performs any path joins.
|
|
348
|
+
if (!isSafeExtensionId(extensionId)) {
|
|
349
|
+
ctx.ui.notify(
|
|
350
|
+
`Cannot install "${specifier}": extension id "${extensionId}" contains unsafe characters (allowed: alphanumerics, ".", "-", "_").`,
|
|
351
|
+
"error",
|
|
352
|
+
);
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return { id: extensionId, manifest };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Write the registry entry for a freshly-installed extension. Called after the
|
|
361
|
+
* final destination commit succeeds so a failed rename can't leave a stale entry.
|
|
362
|
+
*/
|
|
363
|
+
function writeInstalledRegistryEntry(
|
|
364
|
+
id: string,
|
|
365
|
+
manifest: ExtensionManifest,
|
|
366
|
+
specifier: string,
|
|
367
|
+
installType: "npm" | "git" | "local",
|
|
368
|
+
): void {
|
|
369
|
+
withRegistryLock((registry) => {
|
|
370
|
+
registry.entries[id] = {
|
|
371
|
+
id,
|
|
372
|
+
enabled: true,
|
|
373
|
+
source: "user",
|
|
374
|
+
version: manifest.version,
|
|
375
|
+
installedFrom: specifier,
|
|
376
|
+
installType,
|
|
377
|
+
};
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ─── Uninstall helpers ───────────────────────────────────────────────────────
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Scan installed extensions to find which ones depend on the target ID.
|
|
385
|
+
* Used for dependency warning on uninstall (D-06).
|
|
386
|
+
*/
|
|
387
|
+
function findDependents(targetId: string, installedExtDir: string): string[] {
|
|
388
|
+
const dependents: string[] = [];
|
|
389
|
+
if (!existsSync(installedExtDir)) return dependents;
|
|
390
|
+
for (const entry of readdirSync(installedExtDir, { withFileTypes: true })) {
|
|
391
|
+
if (!entry.isDirectory()) continue;
|
|
392
|
+
const manifest = readManifest(join(installedExtDir, entry.name));
|
|
393
|
+
if (!manifest) continue;
|
|
394
|
+
if (manifest.dependencies?.extensions?.includes(targetId)) {
|
|
395
|
+
dependents.push(manifest.id);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return dependents;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function handleUninstall(id: string | undefined, ctx: ExtensionCommandContext): void {
|
|
402
|
+
if (!id) {
|
|
403
|
+
ctx.ui.notify("Usage: /gsd extensions uninstall <id>", "warning");
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Hold the registry lock for the entire uninstall transaction so a concurrent
|
|
408
|
+
// install can't add or re-enable `id` while we're in the middle of removing it.
|
|
409
|
+
const result = withRegistryLock((registry) => {
|
|
410
|
+
const entry = registry.entries[id];
|
|
411
|
+
|
|
412
|
+
// Check if extension exists and is user-installed
|
|
413
|
+
if (!entry || entry.source !== "user") {
|
|
414
|
+
return { ok: false as const, reason: "not-found" as const };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const installedExtDir = getInstalledExtDir();
|
|
418
|
+
const extDir = join(installedExtDir, id);
|
|
419
|
+
|
|
420
|
+
// Check for dependents and warn (D-06: warn-then-proceed)
|
|
421
|
+
const dependents = findDependents(id, installedExtDir);
|
|
422
|
+
|
|
423
|
+
// Remove directory first, then registry entry (Pitfall 4 from RESEARCH.md)
|
|
424
|
+
// If rm fails, do NOT remove registry entry — leaves a recoverable state
|
|
425
|
+
try {
|
|
426
|
+
if (existsSync(extDir)) {
|
|
427
|
+
rmSync(extDir, { recursive: true, force: true });
|
|
428
|
+
}
|
|
429
|
+
} catch (err) {
|
|
430
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
431
|
+
return { ok: false as const, reason: "rm-failed" as const, msg };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Remove registry entry (D-07)
|
|
435
|
+
delete registry.entries[id];
|
|
436
|
+
return { ok: true as const, dependents };
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
if (!result.ok) {
|
|
440
|
+
if (result.reason === "not-found") {
|
|
441
|
+
ctx.ui.notify(
|
|
442
|
+
`Extension "${id}" not found in registry. Run /gsd extensions list to see installed extensions.`,
|
|
443
|
+
"warning",
|
|
444
|
+
);
|
|
445
|
+
} else if (result.reason === "rm-failed") {
|
|
446
|
+
ctx.ui.notify(`Failed to remove extension directory for "${id}": ${result.msg}`, "error");
|
|
447
|
+
}
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (result.dependents.length > 0) {
|
|
452
|
+
ctx.ui.notify(
|
|
453
|
+
`Warning: the following installed extensions depend on "${id}": ${result.dependents.join(", ")}. Removed anyway.`,
|
|
454
|
+
"warning",
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
ctx.ui.notify(`Uninstalled "${id}". Restart GSD to deactivate.`, "info");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ─── Update subcommand ───────────────────────────────────────────────────────
|
|
461
|
+
|
|
462
|
+
async function getLatestNpmVersion(packageName: string): Promise<string | null> {
|
|
463
|
+
try {
|
|
464
|
+
const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
465
|
+
signal: AbortSignal.timeout(5000),
|
|
466
|
+
});
|
|
467
|
+
if (!res.ok) return null;
|
|
468
|
+
const data = await res.json() as { version?: string };
|
|
469
|
+
return data.version ?? null;
|
|
470
|
+
} catch {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function handleUpdate(id: string | undefined, ctx: ExtensionCommandContext): Promise<void> {
|
|
476
|
+
const registry = loadRegistry();
|
|
477
|
+
|
|
478
|
+
if (id) {
|
|
479
|
+
// Update single extension (D-12)
|
|
480
|
+
await updateSingleExtension(id, registry, ctx);
|
|
481
|
+
} else {
|
|
482
|
+
// Update all installed extensions (D-11)
|
|
483
|
+
await updateAllExtensions(registry, ctx);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function updateSingleExtension(
|
|
488
|
+
id: string,
|
|
489
|
+
registry: ExtensionRegistry,
|
|
490
|
+
ctx: ExtensionCommandContext,
|
|
491
|
+
): Promise<void> {
|
|
492
|
+
const entry = registry.entries[id];
|
|
493
|
+
|
|
494
|
+
if (!entry || entry.source !== "user") {
|
|
495
|
+
ctx.ui.notify(
|
|
496
|
+
`Extension "${id}" not found in registry. Run /gsd extensions list to see installed extensions.`,
|
|
497
|
+
"warning",
|
|
498
|
+
);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Git and local installs: "reinstall to update" hint (D-10, D-12)
|
|
503
|
+
if (entry.installType !== "npm") {
|
|
504
|
+
const source = entry.installType ?? "unknown";
|
|
505
|
+
const hint = entry.installedFrom ? `gsd extensions install ${entry.installedFrom}` : `gsd extensions install <specifier>`;
|
|
506
|
+
ctx.ui.notify(
|
|
507
|
+
`"${id}" was installed from ${source}. Reinstall to update: ${hint}`,
|
|
508
|
+
"warning",
|
|
509
|
+
);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// npm extension: check for newer version (D-09)
|
|
514
|
+
const current = entry.version ?? "0.0.0";
|
|
515
|
+
const specifier = entry.installedFrom;
|
|
516
|
+
if (!specifier) {
|
|
517
|
+
ctx.ui.notify(`"${id}" has no recorded install source. Reinstall manually.`, "warning");
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Split npm specifier into name + optional pin.
|
|
522
|
+
// Scoped (`@scope/name[@version]`) vs unscoped (`name[@version]`).
|
|
523
|
+
const { name: packageName, pin } = parseNpmSpecifier(specifier);
|
|
524
|
+
|
|
525
|
+
// Pinned installs: the user explicitly requested a specific version. Don't
|
|
526
|
+
// silently upgrade past the pin — tell them to re-install with a new pin.
|
|
527
|
+
if (pin) {
|
|
528
|
+
ctx.ui.notify(
|
|
529
|
+
`"${id}" was installed with a pinned version (${pin}). To update, run: gsd extensions install ${packageName}@<new-version>`,
|
|
530
|
+
"info",
|
|
531
|
+
);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const latest = await getLatestNpmVersion(packageName);
|
|
536
|
+
if (!latest) {
|
|
537
|
+
ctx.ui.notify(`Could not fetch latest version for "${id}".`, "warning");
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (semver.gt(latest, current)) {
|
|
542
|
+
ctx.ui.notify(`Updating "${id}": v${current} → v${latest}...`, "info");
|
|
543
|
+
await handleInstall(packageName, ctx);
|
|
544
|
+
} else {
|
|
545
|
+
ctx.ui.notify(`"${id}" is already at the latest version (v${current}).`, "info");
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Parse an npm specifier into its package name and optional version pin.
|
|
551
|
+
* Handles scoped (`@scope/name[@version]`) and unscoped (`name[@version]`).
|
|
552
|
+
*/
|
|
553
|
+
function parseNpmSpecifier(specifier: string): { name: string; pin: string | null } {
|
|
554
|
+
const isScoped = specifier.startsWith("@");
|
|
555
|
+
const searchFrom = isScoped ? specifier.indexOf("/") + 1 : 0;
|
|
556
|
+
const atIdx = specifier.indexOf("@", searchFrom);
|
|
557
|
+
if (atIdx === -1) return { name: specifier, pin: null };
|
|
558
|
+
return { name: specifier.slice(0, atIdx), pin: specifier.slice(atIdx + 1) };
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
async function updateAllExtensions(
|
|
562
|
+
registry: ExtensionRegistry,
|
|
563
|
+
ctx: ExtensionCommandContext,
|
|
564
|
+
): Promise<void> {
|
|
565
|
+
// Find all user-installed extensions
|
|
566
|
+
const userEntries = Object.values(registry.entries).filter(e => e.source === "user");
|
|
567
|
+
|
|
568
|
+
if (userEntries.length === 0) {
|
|
569
|
+
ctx.ui.notify("No user-installed extensions found. Use: gsd extensions install <package> to add one.", "warning");
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
ctx.ui.notify(`Checking ${userEntries.length} installed extension(s) for updates...`, "info");
|
|
574
|
+
|
|
575
|
+
let updated = 0;
|
|
576
|
+
let skipped = 0;
|
|
577
|
+
|
|
578
|
+
for (const entry of userEntries) {
|
|
579
|
+
// Skip non-npm installs (D-11)
|
|
580
|
+
if (entry.installType !== "npm") {
|
|
581
|
+
const source = entry.installType ?? "unknown";
|
|
582
|
+
ctx.ui.notify(` ${entry.id}: installed from ${source} — reinstall to update`, "info");
|
|
583
|
+
skipped++;
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const current = entry.version ?? "0.0.0";
|
|
588
|
+
const packageName = entry.installedFrom;
|
|
589
|
+
if (!packageName) {
|
|
590
|
+
ctx.ui.notify(` ${entry.id}: no recorded install source — skip`, "info");
|
|
591
|
+
skipped++;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const latest = await getLatestNpmVersion(packageName);
|
|
596
|
+
if (!latest) {
|
|
597
|
+
ctx.ui.notify(` ${entry.id}: could not fetch latest version — skip`, "info");
|
|
598
|
+
skipped++;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (semver.gt(latest, current)) {
|
|
603
|
+
ctx.ui.notify(` ${entry.id}: v${current} → v${latest} (updating)`, "info");
|
|
604
|
+
await handleInstall(packageName, ctx);
|
|
605
|
+
updated++;
|
|
606
|
+
} else {
|
|
607
|
+
ctx.ui.notify(` ${entry.id}: v${current} (already up to date)`, "info");
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
ctx.ui.notify(`Updated ${updated} extension(s). ${skipped} skipped (git/local — reinstall to update).`, "info");
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// ─── Install subcommand ──────────────────────────────────────────────────────
|
|
615
|
+
|
|
616
|
+
async function handleInstall(specifier: string | undefined, ctx: ExtensionCommandContext): Promise<void> {
|
|
617
|
+
if (!specifier) {
|
|
618
|
+
ctx.ui.notify("Usage: /gsd extensions install <npm-package|git-url|local-path>", "warning");
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const installType = detectInstallType(specifier);
|
|
623
|
+
const installedExtDir = getInstalledExtDir();
|
|
624
|
+
mkdirSync(installedExtDir, { recursive: true });
|
|
625
|
+
|
|
626
|
+
process.stderr.write(`Installing ${specifier}...\n`);
|
|
627
|
+
|
|
628
|
+
if (installType === "npm") {
|
|
629
|
+
installFromNpm(specifier, installedExtDir, ctx);
|
|
630
|
+
} else if (installType === "git") {
|
|
631
|
+
installFromGit(specifier, installedExtDir, ctx);
|
|
632
|
+
} else {
|
|
633
|
+
installFromLocal(specifier, installedExtDir, ctx);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function installFromNpm(specifier: string, installedExtDir: string, ctx: ExtensionCommandContext): void {
|
|
638
|
+
// packDir holds the tarball in tmpdir(). The *extractDir* is staged inside
|
|
639
|
+
// installedExtDir so the final renameSync to destPath stays on a single
|
|
640
|
+
// filesystem (avoids EXDEV when tmpdir() and ~/.gsd live on different mounts).
|
|
641
|
+
const packDir = mkdtempSync(join(tmpdir(), "gsd-install-"));
|
|
642
|
+
let extractDir: string | null = null;
|
|
643
|
+
try {
|
|
644
|
+
// Step 1: npm pack to tmpdir (D-01, D-05)
|
|
645
|
+
execFileSync("npm", ["pack", specifier, "--pack-destination", packDir, "--ignore-scripts"], {
|
|
646
|
+
stdio: "pipe",
|
|
647
|
+
encoding: "utf-8",
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// Step 2: Find the tarball
|
|
651
|
+
const tgzFile = readdirSync(packDir).find(f => f.endsWith(".tgz"));
|
|
652
|
+
if (!tgzFile) throw new Error("npm pack produced no tarball");
|
|
653
|
+
|
|
654
|
+
// Step 3: Extract via tar into a staging dir *inside* installedExtDir
|
|
655
|
+
extractDir = mkdtempSync(join(installedExtDir, "tmp-npm-"));
|
|
656
|
+
execFileSync("tar", ["xzf", join(packDir, tgzFile), "-C", extractDir, "--strip-components=1"], { stdio: "pipe" });
|
|
657
|
+
|
|
658
|
+
// Step 4: Validate and get extension ID
|
|
659
|
+
const validated = postInstallValidate(extractDir, specifier, ctx);
|
|
660
|
+
if (!validated) {
|
|
661
|
+
return; // Error already notified
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Step 5: Move to final destination — same filesystem as extractDir
|
|
665
|
+
const destPath = join(installedExtDir, validated.id);
|
|
666
|
+
if (existsSync(destPath)) {
|
|
667
|
+
rmSync(destPath, { recursive: true, force: true });
|
|
668
|
+
}
|
|
669
|
+
renameSync(extractDir, destPath);
|
|
670
|
+
extractDir = null; // Successfully moved; skip cleanup
|
|
671
|
+
|
|
672
|
+
// Step 6: Commit the registry entry only after the rename succeeds.
|
|
673
|
+
writeInstalledRegistryEntry(validated.id, validated.manifest, specifier, "npm");
|
|
674
|
+
ctx.ui.notify(`Installed "${validated.id}" v${validated.manifest.version ?? "unknown"}. Restart GSD to activate.`, "info");
|
|
675
|
+
} catch (err) {
|
|
676
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
677
|
+
ctx.ui.notify(`Failed to install "${specifier}": ${msg}`, "error");
|
|
678
|
+
} finally {
|
|
679
|
+
if (extractDir && existsSync(extractDir)) {
|
|
680
|
+
try { rmSync(extractDir, { recursive: true, force: true }); } catch { /* best-effort */ }
|
|
681
|
+
}
|
|
682
|
+
rmSync(packDir, { recursive: true, force: true });
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function installFromGit(gitUrl: string, installedExtDir: string, ctx: ExtensionCommandContext): void {
|
|
687
|
+
// Clone into temp dir, validate, then rename to real ID (D-02)
|
|
688
|
+
const tmpDir = join(installedExtDir, `__installing-${Date.now()}`);
|
|
689
|
+
try {
|
|
690
|
+
execFileSync("git", ["clone", "--depth=1", gitUrl, tmpDir], { stdio: "pipe" });
|
|
691
|
+
|
|
692
|
+
// Remove .git directory — not needed after clone
|
|
693
|
+
const dotGit = join(tmpDir, ".git");
|
|
694
|
+
if (existsSync(dotGit)) {
|
|
695
|
+
rmSync(dotGit, { recursive: true, force: true });
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const validated = postInstallValidate(tmpDir, gitUrl, ctx);
|
|
699
|
+
if (!validated) {
|
|
700
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const destPath = join(installedExtDir, validated.id);
|
|
705
|
+
if (existsSync(destPath)) {
|
|
706
|
+
rmSync(destPath, { recursive: true, force: true });
|
|
707
|
+
}
|
|
708
|
+
renameSync(tmpDir, destPath);
|
|
709
|
+
|
|
710
|
+
writeInstalledRegistryEntry(validated.id, validated.manifest, gitUrl, "git");
|
|
711
|
+
ctx.ui.notify(`Installed "${validated.id}" v${validated.manifest.version ?? "unknown"}. Restart GSD to activate.`, "info");
|
|
712
|
+
} catch (err) {
|
|
713
|
+
if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true, force: true });
|
|
714
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
715
|
+
ctx.ui.notify(`Failed to install "${gitUrl}": ${msg}`, "error");
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function installFromLocal(localPath: string, installedExtDir: string, ctx: ExtensionCommandContext): void {
|
|
720
|
+
// Resolve path and copy (not symlink) per D-03
|
|
721
|
+
const sourcePath = resolve(localPath.startsWith("~/") ? join(homedir(), localPath.slice(2)) : localPath);
|
|
722
|
+
|
|
723
|
+
if (!existsSync(sourcePath)) {
|
|
724
|
+
ctx.ui.notify(`Cannot install "${localPath}": path does not exist.`, "error");
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Copy to temp dir first, validate, then rename
|
|
729
|
+
const tmpDir = join(installedExtDir, `__installing-${Date.now()}`);
|
|
730
|
+
try {
|
|
731
|
+
cpSync(sourcePath, tmpDir, { recursive: true });
|
|
732
|
+
|
|
733
|
+
const validated = postInstallValidate(tmpDir, localPath, ctx);
|
|
734
|
+
if (!validated) {
|
|
735
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const destPath = join(installedExtDir, validated.id);
|
|
740
|
+
if (existsSync(destPath)) {
|
|
741
|
+
rmSync(destPath, { recursive: true, force: true });
|
|
742
|
+
}
|
|
743
|
+
renameSync(tmpDir, destPath);
|
|
744
|
+
|
|
745
|
+
writeInstalledRegistryEntry(validated.id, validated.manifest, localPath, "local");
|
|
746
|
+
ctx.ui.notify(`Installed "${validated.id}" v${validated.manifest.version ?? "unknown"}. Restart GSD to activate.`, "info");
|
|
747
|
+
} catch (err) {
|
|
748
|
+
if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true, force: true });
|
|
749
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
750
|
+
ctx.ui.notify(`Failed to install "${localPath}": ${msg}`, "error");
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
115
754
|
// ─── Command Handler ────────────────────────────────────────────────────────
|
|
116
755
|
|
|
117
756
|
export async function handleExtensions(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
@@ -138,8 +777,28 @@ export async function handleExtensions(args: string, ctx: ExtensionCommandContex
|
|
|
138
777
|
return;
|
|
139
778
|
}
|
|
140
779
|
|
|
780
|
+
if (subCmd === "install") {
|
|
781
|
+
await handleInstall(parts[1], ctx);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (subCmd === "uninstall") {
|
|
786
|
+
handleUninstall(parts[1], ctx);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (subCmd === "update") {
|
|
791
|
+
await handleUpdate(parts[1], ctx);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (subCmd === "validate") {
|
|
796
|
+
handleValidate(parts[1], ctx);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
|
|
141
800
|
ctx.ui.notify(
|
|
142
|
-
`Unknown: /gsd extensions ${subCmd}. Usage: /gsd extensions [list|enable|disable|info]`,
|
|
801
|
+
`Unknown: /gsd extensions ${subCmd}. Usage: /gsd extensions [list|enable|disable|info|install|uninstall|update|validate]`,
|
|
143
802
|
"warning",
|
|
144
803
|
);
|
|
145
804
|
}
|
|
@@ -180,6 +839,19 @@ function handleList(ctx: ExtensionCommandContext): void {
|
|
|
180
839
|
String(cmdCount),
|
|
181
840
|
);
|
|
182
841
|
|
|
842
|
+
// Show source indicator and install info for user-installed extensions
|
|
843
|
+
const regEntry = registry.entries[m.id];
|
|
844
|
+
if (regEntry?.source === "user") {
|
|
845
|
+
// Append [user] tag to the last line
|
|
846
|
+
const lastLine = lines[lines.length - 1];
|
|
847
|
+
lines[lines.length - 1] = lastLine + " [user]";
|
|
848
|
+
if (regEntry.installedFrom) {
|
|
849
|
+
const typePrefix = regEntry.installType ? `${regEntry.installType}:` : "";
|
|
850
|
+
const versionSuffix = regEntry.version ? `@${regEntry.version}` : "";
|
|
851
|
+
lines.push(` installed from: ${typePrefix}${regEntry.installedFrom}${versionSuffix}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
183
855
|
if (!enabled) {
|
|
184
856
|
lines.push(` ↳ gsd extensions enable ${m.id}`);
|
|
185
857
|
}
|
|
@@ -200,21 +872,22 @@ function handleEnable(id: string | undefined, ctx: ExtensionCommandContext): voi
|
|
|
200
872
|
return;
|
|
201
873
|
}
|
|
202
874
|
|
|
203
|
-
const
|
|
204
|
-
|
|
875
|
+
const alreadyEnabled = withRegistryLock((registry) => {
|
|
876
|
+
if (isEnabled(registry, id)) return true;
|
|
877
|
+
const entry = registry.entries[id];
|
|
878
|
+
if (entry) {
|
|
879
|
+
entry.enabled = true;
|
|
880
|
+
delete entry.disabledAt;
|
|
881
|
+
delete entry.disabledReason;
|
|
882
|
+
} else {
|
|
883
|
+
registry.entries[id] = { id, enabled: true, source: "bundled" };
|
|
884
|
+
}
|
|
885
|
+
return false;
|
|
886
|
+
});
|
|
887
|
+
if (alreadyEnabled) {
|
|
205
888
|
ctx.ui.notify(`Extension "${id}" is already enabled.`, "info");
|
|
206
889
|
return;
|
|
207
890
|
}
|
|
208
|
-
|
|
209
|
-
const entry = registry.entries[id];
|
|
210
|
-
if (entry) {
|
|
211
|
-
entry.enabled = true;
|
|
212
|
-
delete entry.disabledAt;
|
|
213
|
-
delete entry.disabledReason;
|
|
214
|
-
} else {
|
|
215
|
-
registry.entries[id] = { id, enabled: true, source: "bundled" };
|
|
216
|
-
}
|
|
217
|
-
saveRegistry(registry);
|
|
218
891
|
ctx.ui.notify(`Enabled "${id}". Restart GSD to activate.`, "info");
|
|
219
892
|
}
|
|
220
893
|
|
|
@@ -237,27 +910,28 @@ function handleDisable(id: string | undefined, reason: string, ctx: ExtensionCom
|
|
|
237
910
|
return;
|
|
238
911
|
}
|
|
239
912
|
|
|
240
|
-
const
|
|
241
|
-
|
|
913
|
+
const alreadyDisabled = withRegistryLock((registry) => {
|
|
914
|
+
if (!isEnabled(registry, id)) return true;
|
|
915
|
+
const entry = registry.entries[id];
|
|
916
|
+
if (entry) {
|
|
917
|
+
entry.enabled = false;
|
|
918
|
+
entry.disabledAt = new Date().toISOString();
|
|
919
|
+
entry.disabledReason = reason || undefined;
|
|
920
|
+
} else {
|
|
921
|
+
registry.entries[id] = {
|
|
922
|
+
id,
|
|
923
|
+
enabled: false,
|
|
924
|
+
source: "bundled",
|
|
925
|
+
disabledAt: new Date().toISOString(),
|
|
926
|
+
disabledReason: reason || undefined,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
return false;
|
|
930
|
+
});
|
|
931
|
+
if (alreadyDisabled) {
|
|
242
932
|
ctx.ui.notify(`Extension "${id}" is already disabled.`, "info");
|
|
243
933
|
return;
|
|
244
934
|
}
|
|
245
|
-
|
|
246
|
-
const entry = registry.entries[id];
|
|
247
|
-
if (entry) {
|
|
248
|
-
entry.enabled = false;
|
|
249
|
-
entry.disabledAt = new Date().toISOString();
|
|
250
|
-
entry.disabledReason = reason || undefined;
|
|
251
|
-
} else {
|
|
252
|
-
registry.entries[id] = {
|
|
253
|
-
id,
|
|
254
|
-
enabled: false,
|
|
255
|
-
source: "bundled",
|
|
256
|
-
disabledAt: new Date().toISOString(),
|
|
257
|
-
disabledReason: reason || undefined,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
saveRegistry(registry);
|
|
261
935
|
ctx.ui.notify(`Disabled "${id}". Restart GSD to deactivate.`, "info");
|
|
262
936
|
}
|
|
263
937
|
|
|
@@ -294,6 +968,16 @@ function handleInfo(id: string | undefined, ctx: ExtensionCommandContext): void
|
|
|
294
968
|
lines.push(` Reason: ${entry.disabledReason}`);
|
|
295
969
|
}
|
|
296
970
|
|
|
971
|
+
// Phase 8 fields for user-installed extensions (per UI-SPEC)
|
|
972
|
+
if (entry?.source === "user") {
|
|
973
|
+
if (entry.installedFrom) {
|
|
974
|
+
lines.push(` Installed from: ${entry.installedFrom}`);
|
|
975
|
+
}
|
|
976
|
+
if (entry.installType) {
|
|
977
|
+
lines.push(` Install type: ${entry.installType}`);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
297
981
|
if (manifest.provides) {
|
|
298
982
|
lines.push("");
|
|
299
983
|
lines.push(" Provides:");
|
|
@@ -325,6 +1009,28 @@ function handleInfo(id: string | undefined, ctx: ExtensionCommandContext): void
|
|
|
325
1009
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
326
1010
|
}
|
|
327
1011
|
|
|
1012
|
+
function handleValidate(path: string | undefined, ctx: ExtensionCommandContext): void {
|
|
1013
|
+
if (!path) {
|
|
1014
|
+
ctx.ui.notify("Usage: /gsd extensions validate <path>", "warning");
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const resolved = resolve(path);
|
|
1018
|
+
if (!existsSync(resolved)) {
|
|
1019
|
+
ctx.ui.notify(`Path not found: ${resolved}`, "warning");
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const result = validateExtensionPackage(resolved);
|
|
1023
|
+
if (result.valid) {
|
|
1024
|
+
ctx.ui.notify(`Valid extension package: ${resolved}`, "info");
|
|
1025
|
+
} else {
|
|
1026
|
+
ctx.ui.notify(
|
|
1027
|
+
`Invalid extension package: ${resolved}\n` +
|
|
1028
|
+
result.errors.map(e => ` - ${e}`).join("\n"),
|
|
1029
|
+
"warning",
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
328
1034
|
function padRight(str: string, len: number): string {
|
|
329
1035
|
return str.length >= len ? str + " " : str + " ".repeat(len - str.length);
|
|
330
1036
|
}
|