@specforge/cli 0.1.0
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 +781 -0
- package/bin/specforge +21 -0
- package/dist/channel.d.ts +27 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +22 -0
- package/dist/channel.js.map +1 -0
- package/dist/cli/commands/configure.d.ts +16 -0
- package/dist/cli/commands/configure.d.ts.map +1 -0
- package/dist/cli/commands/configure.js +186 -0
- package/dist/cli/commands/configure.js.map +1 -0
- package/dist/cli/commands/configure.types.d.ts +110 -0
- package/dist/cli/commands/configure.types.d.ts.map +1 -0
- package/dist/cli/commands/configure.types.js +82 -0
- package/dist/cli/commands/configure.types.js.map +1 -0
- package/dist/cli/commands/debug/call.d.ts +14 -0
- package/dist/cli/commands/debug/call.d.ts.map +1 -0
- package/dist/cli/commands/debug/call.js +96 -0
- package/dist/cli/commands/debug/call.js.map +1 -0
- package/dist/cli/commands/debug/index.d.ts +12 -0
- package/dist/cli/commands/debug/index.d.ts.map +1 -0
- package/dist/cli/commands/debug/index.js +14 -0
- package/dist/cli/commands/debug/index.js.map +1 -0
- package/dist/cli/commands/debug/register.d.ts +11 -0
- package/dist/cli/commands/debug/register.d.ts.map +1 -0
- package/dist/cli/commands/debug/register.js +78 -0
- package/dist/cli/commands/debug/register.js.map +1 -0
- package/dist/cli/commands/debug/test.d.ts +13 -0
- package/dist/cli/commands/debug/test.d.ts.map +1 -0
- package/dist/cli/commands/debug/test.js +91 -0
- package/dist/cli/commands/debug/test.js.map +1 -0
- package/dist/cli/commands/debug/tools.d.ts +13 -0
- package/dist/cli/commands/debug/tools.d.ts.map +1 -0
- package/dist/cli/commands/debug/tools.js +86 -0
- package/dist/cli/commands/debug/tools.js.map +1 -0
- package/dist/cli/commands/debug/types.d.ts +119 -0
- package/dist/cli/commands/debug/types.d.ts.map +1 -0
- package/dist/cli/commands/debug/types.js +36 -0
- package/dist/cli/commands/debug/types.js.map +1 -0
- package/dist/cli/commands/debug/whoami.d.ts +13 -0
- package/dist/cli/commands/debug/whoami.d.ts.map +1 -0
- package/dist/cli/commands/debug/whoami.js +131 -0
- package/dist/cli/commands/debug/whoami.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +16 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +413 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/doctor.types.d.ts +180 -0
- package/dist/cli/commands/doctor.types.d.ts.map +1 -0
- package/dist/cli/commands/doctor.types.js +140 -0
- package/dist/cli/commands/doctor.types.js.map +1 -0
- package/dist/cli/commands/feedback.d.ts +27 -0
- package/dist/cli/commands/feedback.d.ts.map +1 -0
- package/dist/cli/commands/feedback.js +262 -0
- package/dist/cli/commands/feedback.js.map +1 -0
- package/dist/cli/commands/feedback.types.d.ts +116 -0
- package/dist/cli/commands/feedback.types.d.ts.map +1 -0
- package/dist/cli/commands/feedback.types.js +65 -0
- package/dist/cli/commands/feedback.types.js.map +1 -0
- package/dist/cli/commands/index.d.ts +15 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +21 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +17 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +773 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/init.types.d.ts +193 -0
- package/dist/cli/commands/init.types.d.ts.map +1 -0
- package/dist/cli/commands/init.types.js +29 -0
- package/dist/cli/commands/init.types.js.map +1 -0
- package/dist/cli/commands/invitations.d.ts +31 -0
- package/dist/cli/commands/invitations.d.ts.map +1 -0
- package/dist/cli/commands/invitations.js +250 -0
- package/dist/cli/commands/invitations.js.map +1 -0
- package/dist/cli/commands/login.d.ts +40 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +139 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/members.d.ts +55 -0
- package/dist/cli/commands/members.d.ts.map +1 -0
- package/dist/cli/commands/members.js +296 -0
- package/dist/cli/commands/members.js.map +1 -0
- package/dist/cli/commands/scaffold/agent-types.d.ts +54 -0
- package/dist/cli/commands/scaffold/agent-types.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/agent-types.js +22 -0
- package/dist/cli/commands/scaffold/agent-types.js.map +1 -0
- package/dist/cli/commands/scaffold/display.d.ts +56 -0
- package/dist/cli/commands/scaffold/display.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/display.js +217 -0
- package/dist/cli/commands/scaffold/display.js.map +1 -0
- package/dist/cli/commands/scaffold/files.d.ts +99 -0
- package/dist/cli/commands/scaffold/files.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/files.js +122 -0
- package/dist/cli/commands/scaffold/files.js.map +1 -0
- package/dist/cli/commands/scaffold/index.d.ts +13 -0
- package/dist/cli/commands/scaffold/index.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/index.js +12 -0
- package/dist/cli/commands/scaffold/index.js.map +1 -0
- package/dist/cli/commands/scaffold/register.d.ts +11 -0
- package/dist/cli/commands/scaffold/register.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/register.js +11 -0
- package/dist/cli/commands/scaffold/register.js.map +1 -0
- package/dist/cli/commands/scaffold/scaffold.d.ts +11 -0
- package/dist/cli/commands/scaffold/scaffold.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/scaffold.js +728 -0
- package/dist/cli/commands/scaffold/scaffold.js.map +1 -0
- package/dist/cli/commands/scaffold/targets.d.ts +60 -0
- package/dist/cli/commands/scaffold/targets.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/targets.js +251 -0
- package/dist/cli/commands/scaffold/targets.js.map +1 -0
- package/dist/cli/commands/scaffold/types.d.ts +170 -0
- package/dist/cli/commands/scaffold/types.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/types.js +12 -0
- package/dist/cli/commands/scaffold/types.js.map +1 -0
- package/dist/cli/commands/scaffold/utils.d.ts +89 -0
- package/dist/cli/commands/scaffold/utils.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/utils.js +401 -0
- package/dist/cli/commands/scaffold/utils.js.map +1 -0
- package/dist/cli/commands/status.d.ts +19 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +124 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/status.types.d.ts +37 -0
- package/dist/cli/commands/status.types.d.ts.map +1 -0
- package/dist/cli/commands/status.types.js +7 -0
- package/dist/cli/commands/status.types.js.map +1 -0
- package/dist/cli/commands/switch.d.ts +18 -0
- package/dist/cli/commands/switch.d.ts.map +1 -0
- package/dist/cli/commands/switch.js +134 -0
- package/dist/cli/commands/switch.js.map +1 -0
- package/dist/cli/commands/switch.types.d.ts +123 -0
- package/dist/cli/commands/switch.types.d.ts.map +1 -0
- package/dist/cli/commands/switch.types.js +46 -0
- package/dist/cli/commands/switch.types.js.map +1 -0
- package/dist/cli/config/agent-teams.types.d.ts +120 -0
- package/dist/cli/config/agent-teams.types.d.ts.map +1 -0
- package/dist/cli/config/agent-teams.types.js +23 -0
- package/dist/cli/config/agent-teams.types.js.map +1 -0
- package/dist/cli/config/index.d.ts +13 -0
- package/dist/cli/config/index.d.ts.map +1 -0
- package/dist/cli/config/index.js +7 -0
- package/dist/cli/config/index.js.map +1 -0
- package/dist/cli/config/loader.d.ts +114 -0
- package/dist/cli/config/loader.d.ts.map +1 -0
- package/dist/cli/config/loader.js +198 -0
- package/dist/cli/config/loader.js.map +1 -0
- package/dist/cli/config/paths.d.ts +107 -0
- package/dist/cli/config/paths.d.ts.map +1 -0
- package/dist/cli/config/paths.js +79 -0
- package/dist/cli/config/paths.js.map +1 -0
- package/dist/cli/config/planning-sessions-registry.d.ts +52 -0
- package/dist/cli/config/planning-sessions-registry.d.ts.map +1 -0
- package/dist/cli/config/planning-sessions-registry.js +46 -0
- package/dist/cli/config/planning-sessions-registry.js.map +1 -0
- package/dist/cli/config/types.d.ts +186 -0
- package/dist/cli/config/types.d.ts.map +1 -0
- package/dist/cli/config/types.js +27 -0
- package/dist/cli/config/types.js.map +1 -0
- package/dist/cli/config/validation.d.ts +62 -0
- package/dist/cli/config/validation.d.ts.map +1 -0
- package/dist/cli/config/validation.js +133 -0
- package/dist/cli/config/validation.js.map +1 -0
- package/dist/cli/config/writer.d.ts +153 -0
- package/dist/cli/config/writer.d.ts.map +1 -0
- package/dist/cli/config/writer.js +168 -0
- package/dist/cli/config/writer.js.map +1 -0
- package/dist/cli/index.d.ts +30 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +142 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/middleware/auth-guard.d.ts +107 -0
- package/dist/cli/middleware/auth-guard.d.ts.map +1 -0
- package/dist/cli/middleware/auth-guard.js +97 -0
- package/dist/cli/middleware/auth-guard.js.map +1 -0
- package/dist/cli/middleware/error-handler.d.ts +133 -0
- package/dist/cli/middleware/error-handler.d.ts.map +1 -0
- package/dist/cli/middleware/error-handler.js +139 -0
- package/dist/cli/middleware/error-handler.js.map +1 -0
- package/dist/cli/middleware/index.d.ts +6 -0
- package/dist/cli/middleware/index.d.ts.map +1 -0
- package/dist/cli/middleware/index.js +3 -0
- package/dist/cli/middleware/index.js.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.d.ts +9 -0
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.d.ts.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.js +160 -0
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.js.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-spec-creator.d.ts +9 -0
- package/dist/cli/templates/agents/content/core/sfag-spec-creator.d.ts.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-spec-creator.js +343 -0
- package/dist/cli/templates/agents/content/core/sfag-spec-creator.js.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.d.ts +9 -0
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.d.ts.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.js +254 -0
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.js.map +1 -0
- package/dist/cli/templates/agents/content/research/sfag-package-researcher.d.ts +8 -0
- package/dist/cli/templates/agents/content/research/sfag-package-researcher.d.ts.map +1 -0
- package/dist/cli/templates/agents/content/research/sfag-package-researcher.js +128 -0
- package/dist/cli/templates/agents/content/research/sfag-package-researcher.js.map +1 -0
- package/dist/cli/templates/agents/index.d.ts +23 -0
- package/dist/cli/templates/agents/index.d.ts.map +1 -0
- package/dist/cli/templates/agents/index.js +29 -0
- package/dist/cli/templates/agents/index.js.map +1 -0
- package/dist/cli/templates/commands.d.ts +17 -0
- package/dist/cli/templates/commands.d.ts.map +1 -0
- package/dist/cli/templates/commands.js +78 -0
- package/dist/cli/templates/commands.js.map +1 -0
- package/dist/cli/templates/content/sf-blockers.d.ts +7 -0
- package/dist/cli/templates/content/sf-blockers.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-blockers.js +69 -0
- package/dist/cli/templates/content/sf-blockers.js.map +1 -0
- package/dist/cli/templates/content/sf-commit.d.ts +7 -0
- package/dist/cli/templates/content/sf-commit.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-commit.js +79 -0
- package/dist/cli/templates/content/sf-commit.js.map +1 -0
- package/dist/cli/templates/content/sf-context.d.ts +7 -0
- package/dist/cli/templates/content/sf-context.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-context.js +63 -0
- package/dist/cli/templates/content/sf-context.js.map +1 -0
- package/dist/cli/templates/content/sf-help.d.ts +7 -0
- package/dist/cli/templates/content/sf-help.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-help.js +42 -0
- package/dist/cli/templates/content/sf-help.js.map +1 -0
- package/dist/cli/templates/content/sf-init.d.ts +7 -0
- package/dist/cli/templates/content/sf-init.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-init.js +62 -0
- package/dist/cli/templates/content/sf-init.js.map +1 -0
- package/dist/cli/templates/content/sf-reset.d.ts +7 -0
- package/dist/cli/templates/content/sf-reset.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-reset.js +67 -0
- package/dist/cli/templates/content/sf-reset.js.map +1 -0
- package/dist/cli/templates/content/sf-search.d.ts +7 -0
- package/dist/cli/templates/content/sf-search.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-search.js +63 -0
- package/dist/cli/templates/content/sf-search.js.map +1 -0
- package/dist/cli/templates/content/sf-status.d.ts +7 -0
- package/dist/cli/templates/content/sf-status.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-status.js +62 -0
- package/dist/cli/templates/content/sf-status.js.map +1 -0
- package/dist/cli/templates/index.d.ts +8 -0
- package/dist/cli/templates/index.d.ts.map +1 -0
- package/dist/cli/templates/index.js +17 -0
- package/dist/cli/templates/index.js.map +1 -0
- package/dist/cli/ui/banner.d.ts +38 -0
- package/dist/cli/ui/banner.d.ts.map +1 -0
- package/dist/cli/ui/banner.js +89 -0
- package/dist/cli/ui/banner.js.map +1 -0
- package/dist/cli/ui/colors.d.ts +76 -0
- package/dist/cli/ui/colors.d.ts.map +1 -0
- package/dist/cli/ui/colors.js +67 -0
- package/dist/cli/ui/colors.js.map +1 -0
- package/dist/cli/ui/index.d.ts +7 -0
- package/dist/cli/ui/index.d.ts.map +1 -0
- package/dist/cli/ui/index.js +4 -0
- package/dist/cli/ui/index.js.map +1 -0
- package/dist/cli/ui/output.d.ts +117 -0
- package/dist/cli/ui/output.d.ts.map +1 -0
- package/dist/cli/ui/output.js +83 -0
- package/dist/cli/ui/output.js.map +1 -0
- package/dist/cli/ui/spinner.d.ts +120 -0
- package/dist/cli/ui/spinner.d.ts.map +1 -0
- package/dist/cli/ui/spinner.js +158 -0
- package/dist/cli/ui/spinner.js.map +1 -0
- package/dist/cli/ui/time.d.ts +54 -0
- package/dist/cli/ui/time.d.ts.map +1 -0
- package/dist/cli/ui/time.js +119 -0
- package/dist/cli/ui/time.js.map +1 -0
- package/dist/client/api-client.d.ts +123 -0
- package/dist/client/api-client.d.ts.map +1 -0
- package/dist/client/api-client.js +105 -0
- package/dist/client/api-client.js.map +1 -0
- package/dist/client/cli-client.d.ts +26 -0
- package/dist/client/cli-client.d.ts.map +1 -0
- package/dist/client/cli-client.js +20 -0
- package/dist/client/cli-client.js.map +1 -0
- package/dist/config/index.d.ts +78 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +79 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/format.d.ts +126 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +71 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/index.d.ts +11 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +45 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/monorepo-detector.d.ts +31 -0
- package/dist/lib/monorepo-detector.d.ts.map +1 -0
- package/dist/lib/monorepo-detector.js +188 -0
- package/dist/lib/monorepo-detector.js.map +1 -0
- package/dist/lib/prompt-generator.d.ts +65 -0
- package/dist/lib/prompt-generator.d.ts.map +1 -0
- package/dist/lib/prompt-generator.js +162 -0
- package/dist/lib/prompt-generator.js.map +1 -0
- package/dist/lib/strategy-analyzer.d.ts +59 -0
- package/dist/lib/strategy-analyzer.d.ts.map +1 -0
- package/dist/lib/strategy-analyzer.js +91 -0
- package/dist/lib/strategy-analyzer.js.map +1 -0
- package/dist/lib/workflow-definitions.d.ts +41 -0
- package/dist/lib/workflow-definitions.d.ts.map +1 -0
- package/dist/lib/workflow-definitions.js +497 -0
- package/dist/lib/workflow-definitions.js.map +1 -0
- package/dist/patterns/index.d.ts +9 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +21 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/patterns/inheritance.d.ts +193 -0
- package/dist/patterns/inheritance.d.ts.map +1 -0
- package/dist/patterns/inheritance.js +125 -0
- package/dist/patterns/inheritance.js.map +1 -0
- package/dist/server.d.ts +44 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +91 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/core/context-helper.d.ts +50 -0
- package/dist/tools/core/context-helper.d.ts.map +1 -0
- package/dist/tools/core/context-helper.js +49 -0
- package/dist/tools/core/context-helper.js.map +1 -0
- package/dist/tools/core/index.d.ts +2 -0
- package/dist/tools/core/index.d.ts.map +1 -0
- package/dist/tools/core/index.js +2 -0
- package/dist/tools/core/index.js.map +1 -0
- package/dist/tools/index.d.ts +58 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +1499 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/transport/__tests__/resolve-endpoint.test.d.ts +6 -0
- package/dist/transport/__tests__/resolve-endpoint.test.d.ts.map +1 -0
- package/dist/transport/index.d.ts +6 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +5 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/interface.d.ts +25 -0
- package/dist/transport/interface.d.ts.map +1 -0
- package/dist/transport/interface.js +1 -0
- package/dist/transport/interface.js.map +1 -0
- package/dist/transport/remote.d.ts +38 -0
- package/dist/transport/remote.d.ts.map +1 -0
- package/dist/transport/remote.js +107 -0
- package/dist/transport/remote.js.map +1 -0
- package/dist/transport/resolve-endpoint.d.ts +58 -0
- package/dist/transport/resolve-endpoint.d.ts.map +1 -0
- package/dist/transport/resolve-endpoint.js +146 -0
- package/dist/transport/resolve-endpoint.js.map +1 -0
- package/dist/types/index.d.ts +212 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/validation/index.d.ts +86 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +392 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/ticket-validation.d.ts +162 -0
- package/dist/validation/ticket-validation.d.ts.map +1 -0
- package/dist/validation/ticket-validation.js +188 -0
- package/dist/validation/ticket-validation.js.map +1 -0
- package/package.json +69 -0
- package/src/cli/templates/agents/content/core/sfag-orchestrator.ts +165 -0
- package/src/cli/templates/agents/content/core/sfag-spec-creator.ts +348 -0
- package/src/cli/templates/agents/content/core/sfag-ticket-implementer.ts +259 -0
- package/src/cli/templates/agents/content/research/sfag-package-researcher.ts +132 -0
- package/src/cli/templates/agents/index.ts +47 -0
- package/src/cli/templates/commands.ts +90 -0
- package/src/cli/templates/content/sf-blockers.ts +71 -0
- package/src/cli/templates/content/sf-commit.ts +81 -0
- package/src/cli/templates/content/sf-context.ts +65 -0
- package/src/cli/templates/content/sf-help.ts +44 -0
- package/src/cli/templates/content/sf-init.ts +64 -0
- package/src/cli/templates/content/sf-reset.ts +69 -0
- package/src/cli/templates/content/sf-search.ts +65 -0
- package/src/cli/templates/content/sf-status.ts +64 -0
- package/src/cli/templates/index.ts +16 -0
- package/src/cli/templates/skills/specforge-conventions.md +114 -0
- package/src/cli/templates/skills/specforge-orchestrator.md +409 -0
- package/src/cli/templates/skills/specforge-validator.md +128 -0
- package/src/cli/templates/skills/specforge-worker.md +412 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/config/validation.ts"],"sourcesContent":["/**\n * Config Schema Validation\n *\n * Validates the extended .specforge/config.json configuration schema.\n * Each section is validated independently — absent sections are\n * considered valid (all sections are optional).\n *\n * Uses manual validation (no external libs) to match existing patterns.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A single validation error with field path and expected values.\n */\nexport interface ValidationError {\n /** Dot-separated field path (e.g., \"agentTeams.strategy\") */\n field: string;\n /** Human-readable error message including expected values */\n message: string;\n /** The actual value that failed validation */\n value: unknown;\n}\n\n/**\n * Result of a validation operation.\n */\nexport interface ValidationResult {\n /** Whether all validations passed */\n valid: boolean;\n /** List of validation errors (empty when valid) */\n errors: ValidationError[];\n}\n\n// ---------------------------------------------------------------------------\n// Allowed values\n// ---------------------------------------------------------------------------\n\nconst ALLOWED_STRATEGIES = ['auto', 'single', 'parallel', 'phased'] as const;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction err(field: string, message: string, value: unknown): ValidationError {\n return { field, message, value };\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nfunction oneOf(field: string, value: unknown, allowed: readonly string[]): ValidationError | null {\n if (typeof value !== 'string' || !allowed.includes(value)) {\n return err(field, `Must be one of: ${allowed.join(', ')}`, value);\n }\n return null;\n}\n\nfunction numberInRange(field: string, value: unknown, min: number, max: number): ValidationError | null {\n if (typeof value !== 'number' || !Number.isFinite(value) || value < min || value > max) {\n return err(field, `Must be a number between ${min} and ${max}`, value);\n }\n return null;\n}\n\nfunction mustBeBoolean(field: string, value: unknown): ValidationError | null {\n if (typeof value !== 'boolean') {\n return err(field, 'Must be a boolean', value);\n }\n return null;\n}\n\nfunction mustBeString(field: string, value: unknown): ValidationError | null {\n if (typeof value !== 'string') {\n return err(field, 'Must be a string', value);\n }\n return null;\n}\n\nfunction mustBeNonEmptyString(field: string, value: unknown): ValidationError | null {\n if (typeof value !== 'string' || value.trim().length === 0) {\n return err(field, 'Must be a non-empty string', value);\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Section validators\n// ---------------------------------------------------------------------------\n\n/**\n * Validate the `agentTeams` config section (v2.0.0).\n *\n * Returns valid:true if the section is absent/undefined.\n * Only validates v2.0.0 fields. Deprecated fields are silently ignored.\n */\nexport function validateAgentTeamsConfig(config: unknown): ValidationResult {\n if (config === undefined || config === null) {\n return { valid: true, errors: [] };\n }\n\n const errors: ValidationError[] = [];\n const prefix = 'agentTeams';\n\n if (!isObject(config)) {\n return { valid: false, errors: [err(prefix, 'Must be an object', config)] };\n }\n\n if ('enabled' in config && config.enabled !== undefined) {\n const e = mustBeBoolean(`${prefix}.enabled`, config.enabled);\n if (e) errors.push(e);\n }\n\n if ('strategy' in config && config.strategy !== undefined) {\n const e = oneOf(`${prefix}.strategy`, config.strategy, ALLOWED_STRATEGIES);\n if (e) errors.push(e);\n }\n\n if ('maxTicketsPerTeam' in config && config.maxTicketsPerTeam !== undefined) {\n const e = numberInRange(`${prefix}.maxTicketsPerTeam`, config.maxTicketsPerTeam, 1, 20);\n if (e) errors.push(e);\n }\n\n if ('maxParallelEpics' in config && config.maxParallelEpics !== undefined) {\n const e = numberInRange(`${prefix}.maxParallelEpics`, config.maxParallelEpics, 1, 10);\n if (e) errors.push(e);\n }\n\n if ('branchPrefix' in config && config.branchPrefix !== undefined) {\n const e = mustBeString(`${prefix}.branchPrefix`, config.branchPrefix);\n if (e) errors.push(e);\n }\n\n if ('timeoutMinutes' in config && config.timeoutMinutes !== undefined) {\n const e = numberInRange(`${prefix}.timeoutMinutes`, config.timeoutMinutes, 5, 60);\n if (e) errors.push(e);\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n/**\n * Validate the `activeSpecification` config section.\n *\n * Returns valid:true if the section is absent/undefined.\n * When present, `id`, `title`, and `activatedAt` are required.\n */\nexport function validateActiveSpecification(config: unknown): ValidationResult {\n if (config === undefined || config === null) {\n return { valid: true, errors: [] };\n }\n\n const errors: ValidationError[] = [];\n const prefix = 'activeSpecification';\n\n if (!isObject(config)) {\n return { valid: false, errors: [err(prefix, 'Must be an object', config)] };\n }\n\n // id — required, non-empty string\n if (!('id' in config) || config.id === undefined) {\n errors.push(err(`${prefix}.id`, 'Required field is missing', undefined));\n } else {\n const e = mustBeNonEmptyString(`${prefix}.id`, config.id);\n if (e) errors.push(e);\n }\n\n // title — required, non-empty string, max 200 chars\n if (!('title' in config) || config.title === undefined) {\n errors.push(err(`${prefix}.title`, 'Required field is missing', undefined));\n } else {\n const e = mustBeNonEmptyString(`${prefix}.title`, config.title);\n if (e) {\n errors.push(e);\n } else if (typeof config.title === 'string' && config.title.length > 200) {\n errors.push(err(`${prefix}.title`, 'Must be at most 200 characters', config.title));\n }\n }\n\n // activatedAt — required, valid ISO 8601\n if (!('activatedAt' in config) || config.activatedAt === undefined) {\n errors.push(err(`${prefix}.activatedAt`, 'Required field is missing', undefined));\n } else if (typeof config.activatedAt !== 'string') {\n errors.push(err(`${prefix}.activatedAt`, 'Must be a string', config.activatedAt));\n } else if (isNaN(Date.parse(config.activatedAt))) {\n errors.push(err(`${prefix}.activatedAt`, 'Must be a valid ISO 8601 date string', config.activatedAt));\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n// ---------------------------------------------------------------------------\n// Top-level validator\n// ---------------------------------------------------------------------------\n\n/**\n * Validate the entire .specforge/config.json for Agent Teams sections.\n *\n * Orchestrates sub-validators and aggregates all errors.\n * Only validates the Agent Teams sections — existing ProjectConfig\n * fields are validated elsewhere.\n *\n * @param config - Raw parsed JSON object from .specforge/config.json\n * @returns Combined validation result from all sections\n */\nexport function validateSpecforgeJson(config: unknown): ValidationResult {\n if (config === undefined || config === null) {\n return { valid: true, errors: [] };\n }\n\n if (!isObject(config)) {\n return { valid: false, errors: [err('config', 'Must be an object', config)] };\n }\n\n const results = [\n validateAgentTeamsConfig(config.agentTeams),\n validateActiveSpecification(config.activeSpecification),\n ];\n\n const errors = results.flatMap(r => r.errors);\n return { valid: errors.length === 0, errors };\n}\n\n/**\n * Format validation errors into human-readable lines.\n *\n * @param errors - Array of validation errors\n * @returns Formatted error strings, one per line\n */\nexport function formatValidationErrors(errors: ValidationError[]): string[] {\n return errors.map(e => {\n const valueStr = e.value === undefined ? 'undefined' : JSON.stringify(e.value);\n return ` ${e.field}: ${e.message} (got ${valueStr})`;\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Writer
|
|
3
|
+
*
|
|
4
|
+
* Provides safe config file writing with atomic operations and proper permissions.
|
|
5
|
+
*
|
|
6
|
+
* Security features:
|
|
7
|
+
* - Global config (contains API key) written with mode 0600 (owner read/write only)
|
|
8
|
+
* - Global directory created with mode 0700 (owner access only)
|
|
9
|
+
* - Project config written with standard permissions (0644)
|
|
10
|
+
* - Path validation to prevent writes outside allowed directories
|
|
11
|
+
*/
|
|
12
|
+
import { GlobalConfig, ProjectConfig } from './types.js';
|
|
13
|
+
import type { ActiveSpecification, AgentTeamsConfig } from './agent-teams.types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Ensure global config directory exists with secure permissions
|
|
16
|
+
*
|
|
17
|
+
* Creates ~/.specforge/ if it doesn't exist, with mode 0700 (owner-only access).
|
|
18
|
+
* Idempotent - safe to call multiple times.
|
|
19
|
+
*
|
|
20
|
+
* @throws Error if directory creation fails
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* ensureGlobalDir();
|
|
25
|
+
* // ~/.specforge/ now exists with drwx------ permissions
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function ensureGlobalDir(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Save global configuration
|
|
31
|
+
*
|
|
32
|
+
* Merges the provided config with existing global config and writes to
|
|
33
|
+
* ~/.specforge/config.json with secure permissions (0600).
|
|
34
|
+
*
|
|
35
|
+
* Undefined values are removed from the final config.
|
|
36
|
+
*
|
|
37
|
+
* @param config - Partial global config to save
|
|
38
|
+
* @throws Error if write fails
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* // Save API key
|
|
43
|
+
* saveGlobalConfig({ apiKey: 'sf_live_...' });
|
|
44
|
+
*
|
|
45
|
+
* // Update MCP output format, keeping existing apiKey
|
|
46
|
+
* saveGlobalConfig({ mcpOutputFormat: 'json' });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function saveGlobalConfig(config: Partial<GlobalConfig>): void;
|
|
50
|
+
/**
|
|
51
|
+
* Delete a field from global config
|
|
52
|
+
*
|
|
53
|
+
* Loads existing config, removes the specified field, and saves.
|
|
54
|
+
* No-op if the field doesn't exist.
|
|
55
|
+
*
|
|
56
|
+
* @param field - Field name to remove
|
|
57
|
+
* @throws Error if read/write fails
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* deleteGlobalConfigField('apiKey');
|
|
62
|
+
* deleteGlobalConfigField('defaultProjectId');
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function deleteGlobalConfigField(field: keyof GlobalConfig): void;
|
|
66
|
+
/**
|
|
67
|
+
* Save project configuration
|
|
68
|
+
*
|
|
69
|
+
* Merges the provided config with existing project config and writes to
|
|
70
|
+
* `.specforge/config.json` in the target directory.
|
|
71
|
+
*
|
|
72
|
+
* Undefined values are removed from the final config.
|
|
73
|
+
*
|
|
74
|
+
* @param config - Partial project config to save
|
|
75
|
+
* @param targetDir - Directory to write the config under (defaults to cwd)
|
|
76
|
+
* @throws Error if write fails
|
|
77
|
+
* @throws ConfigError if a legacy `.specforge.json` is detected
|
|
78
|
+
*/
|
|
79
|
+
export declare function saveProjectConfig(config: Partial<ProjectConfig>, targetDir?: string): void;
|
|
80
|
+
/**
|
|
81
|
+
* Set the active specification in .specforge.json
|
|
82
|
+
*
|
|
83
|
+
* Updates only the `activeSpecification` section, preserving all other fields.
|
|
84
|
+
* Pass `null` to remove the active specification entirely.
|
|
85
|
+
*
|
|
86
|
+
* @param spec - ActiveSpecification to set, or null to clear
|
|
87
|
+
* @param targetDir - Directory containing .specforge.json (defaults to cwd)
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* // Set active specification
|
|
92
|
+
* setActiveSpecification({ id: 'spec_123', title: 'My Spec', activatedAt: new Date().toISOString() });
|
|
93
|
+
*
|
|
94
|
+
* // Clear active specification
|
|
95
|
+
* setActiveSpecification(null);
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export declare function setActiveSpecification(spec: ActiveSpecification | null, targetDir?: string): void;
|
|
99
|
+
/**
|
|
100
|
+
* Update the Agent Teams configuration in .specforge/config.json (v2.0.0)
|
|
101
|
+
*
|
|
102
|
+
* Claude Code Agent Teams only.
|
|
103
|
+
*
|
|
104
|
+
* Merges the provided partial config with the existing `agentTeams` section,
|
|
105
|
+
* preserving all other fields. Only the keys present in `config` are overwritten.
|
|
106
|
+
* Deprecated pre-v2.0.0 fields are stripped on write.
|
|
107
|
+
*
|
|
108
|
+
* @param config - Partial AgentTeamsConfig to merge
|
|
109
|
+
* @param targetDir - Directory containing config (defaults to cwd)
|
|
110
|
+
*/
|
|
111
|
+
export declare function setAgentTeamsConfig(config: Partial<AgentTeamsConfig>, targetDir?: string): void;
|
|
112
|
+
/**
|
|
113
|
+
* Add an entry to .gitignore if it exists
|
|
114
|
+
*
|
|
115
|
+
* Does not create .gitignore if it doesn't exist.
|
|
116
|
+
* Does not duplicate existing entries.
|
|
117
|
+
*
|
|
118
|
+
* @param entry - Line to add to .gitignore
|
|
119
|
+
* @param targetDir - Directory containing .gitignore (defaults to cwd)
|
|
120
|
+
* @returns true if entry was added, false if already exists or no .gitignore
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* const added = addToGitignore('.specforge.json');
|
|
125
|
+
* if (added) {
|
|
126
|
+
* console.log('Added .specforge.json to .gitignore');
|
|
127
|
+
* }
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export declare function addToGitignore(entry: string, targetDir?: string): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Remove API key from global config (logout)
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* clearApiKey();
|
|
137
|
+
* console.log('Logged out successfully');
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare function clearApiKey(): void;
|
|
141
|
+
/**
|
|
142
|
+
* Check if global config file exists
|
|
143
|
+
*
|
|
144
|
+
* @returns true if ~/.specforge/config.json exists
|
|
145
|
+
*/
|
|
146
|
+
export declare function hasGlobalConfig(): boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Check if project config file exists in cwd
|
|
149
|
+
*
|
|
150
|
+
* @returns true if .specforge.json exists
|
|
151
|
+
*/
|
|
152
|
+
export declare function hasProjectConfig(): boolean;
|
|
153
|
+
//# sourceMappingURL=writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../../src/cli/config/writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAEL,YAAY,EACZ,aAAa,EACd,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAEjB,MAAM,wBAAwB,CAAC;AAsBhC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAetC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAgCpE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,YAAY,GAAG,IAAI,CAuBvE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,EAC9B,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI,CAgCN;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,mBAAmB,GAAG,IAAI,EAChC,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI,CAaN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,EACjC,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI,CAiBN;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAiCzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
CONFIG_FILES
|
|
5
|
+
} from "./types.js";
|
|
6
|
+
import { DEPRECATED_AGENT_TEAMS_FIELDS } from "./agent-teams.types.js";
|
|
7
|
+
import {
|
|
8
|
+
getGlobalConfigDir,
|
|
9
|
+
getGlobalConfigPath,
|
|
10
|
+
getProjectConfigPath
|
|
11
|
+
} from "./paths.js";
|
|
12
|
+
import { loadGlobalConfig, tryLoadProjectConfig } from "./loader.js";
|
|
13
|
+
const PERMISSIONS = {
|
|
14
|
+
/** Directory accessible only by owner (rwx------) */
|
|
15
|
+
SECURE_DIR: 448,
|
|
16
|
+
/** File readable/writable only by owner (rw-------) */
|
|
17
|
+
SECRET_FILE: 384,
|
|
18
|
+
/** Regular file permissions (rw-r--r--) */
|
|
19
|
+
REGULAR_FILE: 420
|
|
20
|
+
};
|
|
21
|
+
function ensureGlobalDir() {
|
|
22
|
+
const globalDir = getGlobalConfigDir();
|
|
23
|
+
if (fs.existsSync(globalDir)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
fs.mkdirSync(globalDir, { mode: PERMISSIONS.SECURE_DIR, recursive: true });
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Failed to create global config directory: ${globalDir}
|
|
31
|
+
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function saveGlobalConfig(config) {
|
|
36
|
+
ensureGlobalDir();
|
|
37
|
+
const configPath = getGlobalConfigPath();
|
|
38
|
+
const existing = loadGlobalConfig();
|
|
39
|
+
const merged = {
|
|
40
|
+
...existing,
|
|
41
|
+
...config
|
|
42
|
+
};
|
|
43
|
+
Object.keys(merged).forEach((key) => {
|
|
44
|
+
if (merged[key] === void 0) {
|
|
45
|
+
delete merged[key];
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
const content = JSON.stringify(merged, null, 2) + "\n";
|
|
49
|
+
try {
|
|
50
|
+
fs.writeFileSync(configPath, content, { mode: PERMISSIONS.SECRET_FILE });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Failed to write global config: ${configPath}
|
|
54
|
+
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function deleteGlobalConfigField(field) {
|
|
59
|
+
const configPath = getGlobalConfigPath();
|
|
60
|
+
if (!fs.existsSync(configPath)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const existing = loadGlobalConfig();
|
|
64
|
+
delete existing[field];
|
|
65
|
+
const content = JSON.stringify(existing, null, 2) + "\n";
|
|
66
|
+
try {
|
|
67
|
+
fs.writeFileSync(configPath, content, { mode: PERMISSIONS.SECRET_FILE });
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Failed to update global config: ${configPath}
|
|
71
|
+
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function saveProjectConfig(config, targetDir) {
|
|
76
|
+
const resolvedDir = targetDir ? path.resolve(targetDir) : process.cwd();
|
|
77
|
+
const configPath = path.join(resolvedDir, CONFIG_FILES.PROJECT_DIR, "config.json");
|
|
78
|
+
const existing = tryLoadProjectConfig() || {};
|
|
79
|
+
const merged = {
|
|
80
|
+
...existing,
|
|
81
|
+
...config
|
|
82
|
+
};
|
|
83
|
+
Object.keys(merged).forEach((key) => {
|
|
84
|
+
if (merged[key] === void 0) {
|
|
85
|
+
delete merged[key];
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
const specforgeDir = path.dirname(configPath);
|
|
89
|
+
if (!fs.existsSync(specforgeDir)) {
|
|
90
|
+
fs.mkdirSync(specforgeDir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
const content = JSON.stringify(merged, null, 2) + "\n";
|
|
93
|
+
try {
|
|
94
|
+
fs.writeFileSync(configPath, content, { mode: PERMISSIONS.REGULAR_FILE });
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Failed to write project config: ${configPath}
|
|
98
|
+
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function setActiveSpecification(spec, targetDir) {
|
|
103
|
+
const existing = tryLoadProjectConfig() || {};
|
|
104
|
+
const updated = structuredClone(existing);
|
|
105
|
+
if (spec === null) {
|
|
106
|
+
updated.activeSpecification = void 0;
|
|
107
|
+
} else {
|
|
108
|
+
updated.activeSpecification = spec;
|
|
109
|
+
}
|
|
110
|
+
saveProjectConfig(updated, targetDir);
|
|
111
|
+
}
|
|
112
|
+
function setAgentTeamsConfig(config, targetDir) {
|
|
113
|
+
const existing = tryLoadProjectConfig() || {};
|
|
114
|
+
const updated = structuredClone(existing);
|
|
115
|
+
const merged = {
|
|
116
|
+
...updated.agentTeams,
|
|
117
|
+
...config
|
|
118
|
+
};
|
|
119
|
+
for (const field of DEPRECATED_AGENT_TEAMS_FIELDS) {
|
|
120
|
+
delete merged[field];
|
|
121
|
+
}
|
|
122
|
+
updated.agentTeams = merged;
|
|
123
|
+
saveProjectConfig(updated, targetDir);
|
|
124
|
+
}
|
|
125
|
+
function addToGitignore(entry, targetDir) {
|
|
126
|
+
const gitignorePath = targetDir ? path.join(path.resolve(targetDir), ".gitignore") : path.resolve(".gitignore");
|
|
127
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
132
|
+
const lines = content.split("\n");
|
|
133
|
+
const normalizedEntry = entry.trim();
|
|
134
|
+
if (lines.some((line) => line.trim() === normalizedEntry)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
const newContent = content.endsWith("\n") ? content + normalizedEntry + "\n" : content + "\n" + normalizedEntry + "\n";
|
|
138
|
+
fs.writeFileSync(gitignorePath, newContent);
|
|
139
|
+
return true;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.warn(
|
|
142
|
+
`Warning: Could not update .gitignore: ${error instanceof Error ? error.message : String(error)}`
|
|
143
|
+
);
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function clearApiKey() {
|
|
148
|
+
deleteGlobalConfigField("apiKey");
|
|
149
|
+
}
|
|
150
|
+
function hasGlobalConfig() {
|
|
151
|
+
return fs.existsSync(getGlobalConfigPath());
|
|
152
|
+
}
|
|
153
|
+
function hasProjectConfig() {
|
|
154
|
+
return fs.existsSync(getProjectConfigPath());
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
addToGitignore,
|
|
158
|
+
clearApiKey,
|
|
159
|
+
deleteGlobalConfigField,
|
|
160
|
+
ensureGlobalDir,
|
|
161
|
+
hasGlobalConfig,
|
|
162
|
+
hasProjectConfig,
|
|
163
|
+
saveGlobalConfig,
|
|
164
|
+
saveProjectConfig,
|
|
165
|
+
setActiveSpecification,
|
|
166
|
+
setAgentTeamsConfig
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/config/writer.ts"],"sourcesContent":["/**\n * Config Writer\n *\n * Provides safe config file writing with atomic operations and proper permissions.\n *\n * Security features:\n * - Global config (contains API key) written with mode 0600 (owner read/write only)\n * - Global directory created with mode 0700 (owner access only)\n * - Project config written with standard permissions (0644)\n * - Path validation to prevent writes outside allowed directories\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport {\n CONFIG_FILES,\n GlobalConfig,\n ProjectConfig,\n} from './types.js';\nimport type {\n ActiveSpecification,\n AgentTeamsConfig,\n SpecforgeJsonConfig,\n} from './agent-teams.types.js';\nimport { DEPRECATED_AGENT_TEAMS_FIELDS } from './agent-teams.types.js';\nimport {\n getGlobalConfigDir,\n getGlobalConfigPath,\n getProjectConfigPath,\n isWithinGlobalDir,\n} from './paths.js';\nimport { loadGlobalConfig, tryLoadProjectConfig } from './loader.js';\n\n/**\n * File permission constants\n */\nconst PERMISSIONS = {\n /** Directory accessible only by owner (rwx------) */\n SECURE_DIR: 0o700,\n /** File readable/writable only by owner (rw-------) */\n SECRET_FILE: 0o600,\n /** Regular file permissions (rw-r--r--) */\n REGULAR_FILE: 0o644,\n} as const;\n\n/**\n * Ensure global config directory exists with secure permissions\n *\n * Creates ~/.specforge/ if it doesn't exist, with mode 0700 (owner-only access).\n * Idempotent - safe to call multiple times.\n *\n * @throws Error if directory creation fails\n *\n * @example\n * ```typescript\n * ensureGlobalDir();\n * // ~/.specforge/ now exists with drwx------ permissions\n * ```\n */\nexport function ensureGlobalDir(): void {\n const globalDir = getGlobalConfigDir();\n\n if (fs.existsSync(globalDir)) {\n return; // Already exists\n }\n\n try {\n fs.mkdirSync(globalDir, { mode: PERMISSIONS.SECURE_DIR, recursive: true });\n } catch (error) {\n throw new Error(\n `Failed to create global config directory: ${globalDir}\\n` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\n/**\n * Save global configuration\n *\n * Merges the provided config with existing global config and writes to\n * ~/.specforge/config.json with secure permissions (0600).\n *\n * Undefined values are removed from the final config.\n *\n * @param config - Partial global config to save\n * @throws Error if write fails\n *\n * @example\n * ```typescript\n * // Save API key\n * saveGlobalConfig({ apiKey: 'sf_live_...' });\n *\n * // Update MCP output format, keeping existing apiKey\n * saveGlobalConfig({ mcpOutputFormat: 'json' });\n * ```\n */\nexport function saveGlobalConfig(config: Partial<GlobalConfig>): void {\n ensureGlobalDir();\n\n const configPath = getGlobalConfigPath();\n\n // Load existing config\n const existing = loadGlobalConfig();\n\n // Merge configs\n const merged: GlobalConfig = {\n ...existing,\n ...config,\n };\n\n // Remove undefined values\n Object.keys(merged).forEach(key => {\n if (merged[key as keyof GlobalConfig] === undefined) {\n delete merged[key as keyof GlobalConfig];\n }\n });\n\n // Write with secure permissions\n const content = JSON.stringify(merged, null, 2) + '\\n';\n\n try {\n fs.writeFileSync(configPath, content, { mode: PERMISSIONS.SECRET_FILE });\n } catch (error) {\n throw new Error(\n `Failed to write global config: ${configPath}\\n` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\n/**\n * Delete a field from global config\n *\n * Loads existing config, removes the specified field, and saves.\n * No-op if the field doesn't exist.\n *\n * @param field - Field name to remove\n * @throws Error if read/write fails\n *\n * @example\n * ```typescript\n * deleteGlobalConfigField('apiKey');\n * deleteGlobalConfigField('defaultProjectId');\n * ```\n */\nexport function deleteGlobalConfigField(field: keyof GlobalConfig): void {\n const configPath = getGlobalConfigPath();\n\n if (!fs.existsSync(configPath)) {\n return; // No config to modify\n }\n\n const existing = loadGlobalConfig();\n\n // Remove field\n delete existing[field];\n\n // Save updated config\n const content = JSON.stringify(existing, null, 2) + '\\n';\n\n try {\n fs.writeFileSync(configPath, content, { mode: PERMISSIONS.SECRET_FILE });\n } catch (error) {\n throw new Error(\n `Failed to update global config: ${configPath}\\n` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\n/**\n * Save project configuration\n *\n * Merges the provided config with existing project config and writes to\n * `.specforge/config.json` in the target directory.\n *\n * Undefined values are removed from the final config.\n *\n * @param config - Partial project config to save\n * @param targetDir - Directory to write the config under (defaults to cwd)\n * @throws Error if write fails\n * @throws ConfigError if a legacy `.specforge.json` is detected\n */\nexport function saveProjectConfig(\n config: Partial<ProjectConfig>,\n targetDir?: string\n): void {\n const resolvedDir = targetDir ? path.resolve(targetDir) : process.cwd();\n const configPath = path.join(resolvedDir, CONFIG_FILES.PROJECT_DIR, 'config.json');\n\n const existing = tryLoadProjectConfig() || {};\n\n const merged: ProjectConfig = {\n ...existing,\n ...config,\n };\n\n Object.keys(merged).forEach(key => {\n if (merged[key as keyof ProjectConfig] === undefined) {\n delete merged[key as keyof ProjectConfig];\n }\n });\n\n const specforgeDir = path.dirname(configPath);\n if (!fs.existsSync(specforgeDir)) {\n fs.mkdirSync(specforgeDir, { recursive: true });\n }\n\n const content = JSON.stringify(merged, null, 2) + '\\n';\n\n try {\n fs.writeFileSync(configPath, content, { mode: PERMISSIONS.REGULAR_FILE });\n } catch (error) {\n throw new Error(\n `Failed to write project config: ${configPath}\\n` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\n/**\n * Set the active specification in .specforge.json\n *\n * Updates only the `activeSpecification` section, preserving all other fields.\n * Pass `null` to remove the active specification entirely.\n *\n * @param spec - ActiveSpecification to set, or null to clear\n * @param targetDir - Directory containing .specforge.json (defaults to cwd)\n *\n * @example\n * ```typescript\n * // Set active specification\n * setActiveSpecification({ id: 'spec_123', title: 'My Spec', activatedAt: new Date().toISOString() });\n *\n * // Clear active specification\n * setActiveSpecification(null);\n * ```\n */\nexport function setActiveSpecification(\n spec: ActiveSpecification | null,\n targetDir?: string\n): void {\n const existing: SpecforgeJsonConfig = tryLoadProjectConfig() || {};\n const updated = structuredClone(existing);\n\n if (spec === null) {\n // Set to undefined (not delete) so the spread in saveProjectConfig\n // overrides the existing value, and the remove-undefined loop cleans it up.\n updated.activeSpecification = undefined;\n } else {\n updated.activeSpecification = spec;\n }\n\n saveProjectConfig(updated as Partial<ProjectConfig>, targetDir);\n}\n\n/**\n * Update the Agent Teams configuration in .specforge/config.json (v2.0.0)\n *\n * Claude Code Agent Teams only.\n *\n * Merges the provided partial config with the existing `agentTeams` section,\n * preserving all other fields. Only the keys present in `config` are overwritten.\n * Deprecated pre-v2.0.0 fields are stripped on write.\n *\n * @param config - Partial AgentTeamsConfig to merge\n * @param targetDir - Directory containing config (defaults to cwd)\n */\nexport function setAgentTeamsConfig(\n config: Partial<AgentTeamsConfig>,\n targetDir?: string\n): void {\n const existing: SpecforgeJsonConfig = tryLoadProjectConfig() || {};\n const updated = structuredClone(existing);\n\n const merged: Record<string, unknown> = {\n ...updated.agentTeams,\n ...config,\n };\n\n // Strip deprecated pre-v2.0.0 fields\n for (const field of DEPRECATED_AGENT_TEAMS_FIELDS) {\n delete merged[field];\n }\n\n updated.agentTeams = merged as AgentTeamsConfig;\n\n saveProjectConfig(updated, targetDir);\n}\n\n/**\n * Add an entry to .gitignore if it exists\n *\n * Does not create .gitignore if it doesn't exist.\n * Does not duplicate existing entries.\n *\n * @param entry - Line to add to .gitignore\n * @param targetDir - Directory containing .gitignore (defaults to cwd)\n * @returns true if entry was added, false if already exists or no .gitignore\n *\n * @example\n * ```typescript\n * const added = addToGitignore('.specforge.json');\n * if (added) {\n * console.log('Added .specforge.json to .gitignore');\n * }\n * ```\n */\nexport function addToGitignore(entry: string, targetDir?: string): boolean {\n const gitignorePath = targetDir\n ? path.join(path.resolve(targetDir), '.gitignore')\n : path.resolve('.gitignore');\n\n if (!fs.existsSync(gitignorePath)) {\n return false;\n }\n\n try {\n const content = fs.readFileSync(gitignorePath, 'utf-8');\n const lines = content.split('\\n');\n\n // Check if entry already exists (exact match, trimmed)\n const normalizedEntry = entry.trim();\n if (lines.some(line => line.trim() === normalizedEntry)) {\n return false; // Already exists\n }\n\n // Add entry with proper newline handling\n const newContent = content.endsWith('\\n')\n ? content + normalizedEntry + '\\n'\n : content + '\\n' + normalizedEntry + '\\n';\n\n fs.writeFileSync(gitignorePath, newContent);\n return true;\n } catch (error) {\n // Log warning but don't fail - this is a nice-to-have\n console.warn(\n `Warning: Could not update .gitignore: ${error instanceof Error ? error.message : String(error)}`\n );\n return false;\n }\n}\n\n/**\n * Remove API key from global config (logout)\n *\n * @example\n * ```typescript\n * clearApiKey();\n * console.log('Logged out successfully');\n * ```\n */\nexport function clearApiKey(): void {\n deleteGlobalConfigField('apiKey');\n}\n\n/**\n * Check if global config file exists\n *\n * @returns true if ~/.specforge/config.json exists\n */\nexport function hasGlobalConfig(): boolean {\n return fs.existsSync(getGlobalConfigPath());\n}\n\n/**\n * Check if project config file exists in cwd\n *\n * @returns true if .specforge.json exists\n */\nexport function hasProjectConfig(): boolean {\n return fs.existsSync(getProjectConfigPath());\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecForge MCP CLI Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Main CLI application using Commander.js.
|
|
5
|
+
* Sets up command routing, middleware, and global options.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
/**
|
|
9
|
+
* CLI version - read from package.json
|
|
10
|
+
*/
|
|
11
|
+
declare const CLI_VERSION: string;
|
|
12
|
+
/**
|
|
13
|
+
* The main CLI program instance
|
|
14
|
+
*/
|
|
15
|
+
declare const program: Command;
|
|
16
|
+
/**
|
|
17
|
+
* Run the CLI with given arguments
|
|
18
|
+
*
|
|
19
|
+
* @param argv - Command line arguments (defaults to process.argv)
|
|
20
|
+
*/
|
|
21
|
+
export declare function runCLI(argv?: string[]): void;
|
|
22
|
+
/**
|
|
23
|
+
* Export program for testing and extension
|
|
24
|
+
*/
|
|
25
|
+
export { program };
|
|
26
|
+
/**
|
|
27
|
+
* Export version for external use
|
|
28
|
+
*/
|
|
29
|
+
export { CLI_VERSION };
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0BpC;;GAEG;AACH,QAAA,MAAM,WAAW,EAAE,MAAoB,CAAC;AAqKxC;;GAEG;AACH,QAAA,MAAM,OAAO,SAAkB,CAAC;AAShC;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,IAAI,CAa1D;AAED;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB;;GAEG;AACH,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { authGuardHook } from "./middleware/auth-guard.js";
|
|
4
|
+
import { withErrorHandler, debugLog, handleError } from "./middleware/error-handler.js";
|
|
5
|
+
import { resolveConfig } from "./config/loader.js";
|
|
6
|
+
import { colors } from "./ui/index.js";
|
|
7
|
+
import { printBanner } from "./ui/banner.js";
|
|
8
|
+
import { CHANNEL } from "../channel.js";
|
|
9
|
+
const BANNER_SKIP_COMMANDS = /* @__PURE__ */ new Set(["init", "login", "serve"]);
|
|
10
|
+
const require2 = createRequire(import.meta.url);
|
|
11
|
+
const pkg = require2("../../package.json");
|
|
12
|
+
import {
|
|
13
|
+
registerLoginCommand,
|
|
14
|
+
registerInitCommand,
|
|
15
|
+
registerConfigureCommand,
|
|
16
|
+
registerDoctorCommand,
|
|
17
|
+
registerStatusCommand,
|
|
18
|
+
registerSwitchCommand,
|
|
19
|
+
registerFeedbackCommand,
|
|
20
|
+
registerMembersCommand,
|
|
21
|
+
registerInvitationsCommand
|
|
22
|
+
} from "./commands/index.js";
|
|
23
|
+
import { registerDebugCommands } from "./commands/debug/index.js";
|
|
24
|
+
import { registerScaffoldCommand } from "./commands/scaffold/index.js";
|
|
25
|
+
const CLI_VERSION = pkg.version;
|
|
26
|
+
const CLI_NAME = CHANNEL.name;
|
|
27
|
+
function createProgram() {
|
|
28
|
+
const program2 = new Command();
|
|
29
|
+
program2.name(CLI_NAME).description("SpecForge MCP CLI - Configure and interact with SpecForge").version(CLI_VERSION, "-v, --version", "Display version number");
|
|
30
|
+
program2.option("--debug", "Enable debug output").option("--no-color", "Disable colored output").option("--json", "Output in JSON format");
|
|
31
|
+
program2.hook("preAction", (thisCommand, actionCommand) => {
|
|
32
|
+
const commandName = actionCommand.name();
|
|
33
|
+
if (thisCommand.opts().debug) {
|
|
34
|
+
process.env.SPECFORGE_DEBUG = "true";
|
|
35
|
+
}
|
|
36
|
+
if (thisCommand.opts().noColor) {
|
|
37
|
+
process.env.NO_COLOR = "1";
|
|
38
|
+
}
|
|
39
|
+
const jsonOutput = !!thisCommand.opts().json;
|
|
40
|
+
const programState = thisCommand;
|
|
41
|
+
if (!programState._bannerPrinted && process.stdout.isTTY && !jsonOutput && !BANNER_SKIP_COMMANDS.has(commandName)) {
|
|
42
|
+
printBanner("compact");
|
|
43
|
+
programState._bannerPrinted = true;
|
|
44
|
+
}
|
|
45
|
+
debugLog(`Running command: ${commandName}`);
|
|
46
|
+
debugLog("Resolved config:", resolveConfig());
|
|
47
|
+
authGuardHook({ name: () => commandName });
|
|
48
|
+
});
|
|
49
|
+
return program2;
|
|
50
|
+
}
|
|
51
|
+
function registerServeCommand(program2) {
|
|
52
|
+
program2.command("serve", { isDefault: true, hidden: true }).description("Start MCP server (default)").action(withErrorHandler(async () => {
|
|
53
|
+
debugLog("Starting MCP server");
|
|
54
|
+
const { loadConfig, validateConfig } = await import("../config/index.js");
|
|
55
|
+
const { createServer, startServer } = await import("../server.js");
|
|
56
|
+
const config = await loadConfig();
|
|
57
|
+
validateConfig(config);
|
|
58
|
+
config.mcpOutputFormat = resolveConfig().mcpOutputFormat;
|
|
59
|
+
const server = await createServer(config);
|
|
60
|
+
await startServer(server);
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
function registerCommands(program2) {
|
|
64
|
+
registerLoginCommand(program2);
|
|
65
|
+
registerInitCommand(program2);
|
|
66
|
+
registerConfigureCommand(program2);
|
|
67
|
+
registerDoctorCommand(program2);
|
|
68
|
+
registerStatusCommand(program2);
|
|
69
|
+
registerSwitchCommand(program2);
|
|
70
|
+
registerDebugCommands(program2);
|
|
71
|
+
registerScaffoldCommand(program2);
|
|
72
|
+
registerFeedbackCommand(program2);
|
|
73
|
+
registerMembersCommand(program2);
|
|
74
|
+
registerInvitationsCommand(program2);
|
|
75
|
+
}
|
|
76
|
+
function configureHelp(program2) {
|
|
77
|
+
program2.configureHelp({
|
|
78
|
+
sortSubcommands: false,
|
|
79
|
+
subcommandTerm: (cmd) => cmd.name()
|
|
80
|
+
});
|
|
81
|
+
program2.addHelpText("after", `
|
|
82
|
+
${colors.muted("Command Groups:")}
|
|
83
|
+
|
|
84
|
+
${colors.bold("Authentication")}
|
|
85
|
+
login Authenticate with SpecForge API
|
|
86
|
+
configure Read/write configuration
|
|
87
|
+
|
|
88
|
+
${colors.bold("Setup")}
|
|
89
|
+
init Initialize SpecForge in a project
|
|
90
|
+
scaffold Generate skills, agents, and commands
|
|
91
|
+
|
|
92
|
+
${colors.bold("Diagnostics")}
|
|
93
|
+
doctor Health check
|
|
94
|
+
status Project status and session info
|
|
95
|
+
|
|
96
|
+
${colors.bold("Context")}
|
|
97
|
+
switch Switch active project or specification
|
|
98
|
+
|
|
99
|
+
${colors.bold("Collaboration")}
|
|
100
|
+
members Manage project members
|
|
101
|
+
invitations Manage invitations
|
|
102
|
+
|
|
103
|
+
${colors.bold("Debug")}
|
|
104
|
+
debug call Direct MCP tool dispatch
|
|
105
|
+
debug tools List available MCP tools
|
|
106
|
+
debug test Connection test
|
|
107
|
+
debug whoami Identity and config info
|
|
108
|
+
|
|
109
|
+
${colors.bold("Info")}
|
|
110
|
+
feedback List and submit feedback
|
|
111
|
+
|
|
112
|
+
${colors.muted("Examples:")}
|
|
113
|
+
$ ${CLI_NAME} login # Authenticate with API key
|
|
114
|
+
$ ${CLI_NAME} init # Initialize in current directory
|
|
115
|
+
$ ${CLI_NAME} status # Show current status
|
|
116
|
+
$ ${CLI_NAME} switch <id> # Switch active project/spec
|
|
117
|
+
|
|
118
|
+
${colors.muted("Documentation:")}
|
|
119
|
+
${colors.primary("https://docs.specforge.com/cli")}
|
|
120
|
+
|
|
121
|
+
${colors.muted("Report issues:")}
|
|
122
|
+
${colors.primary("https://github.com/specforge/mcp/issues")}
|
|
123
|
+
`);
|
|
124
|
+
}
|
|
125
|
+
const program = createProgram();
|
|
126
|
+
registerServeCommand(program);
|
|
127
|
+
registerCommands(program);
|
|
128
|
+
configureHelp(program);
|
|
129
|
+
function runCLI(argv = process.argv) {
|
|
130
|
+
try {
|
|
131
|
+
program.parse(argv);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
debugLog("CLI parse error:", error);
|
|
134
|
+
handleError(error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
CLI_VERSION,
|
|
139
|
+
program,
|
|
140
|
+
runCLI
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["/**\n * SpecForge MCP CLI Entry Point\n *\n * Main CLI application using Commander.js.\n * Sets up command routing, middleware, and global options.\n */\n\nimport { createRequire } from 'node:module';\nimport { Command } from 'commander';\nimport { authGuardHook } from './middleware/auth-guard.js';\nimport { withErrorHandler, debugLog, handleError } from './middleware/error-handler.js';\nimport { resolveConfig } from './config/loader.js';\nimport { colors } from './ui/index.js';\nimport { printBanner } from './ui/banner.js';\nimport { CHANNEL } from '../channel.js';\n\nconst BANNER_SKIP_COMMANDS = new Set(['init', 'login', 'serve']);\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../../package.json');\nimport {\n registerLoginCommand,\n registerInitCommand,\n registerConfigureCommand,\n registerDoctorCommand,\n registerStatusCommand,\n registerSwitchCommand,\n registerFeedbackCommand,\n registerMembersCommand,\n registerInvitationsCommand,\n} from './commands/index.js';\nimport { registerDebugCommands } from './commands/debug/index.js';\nimport { registerScaffoldCommand } from './commands/scaffold/index.js';\n\n/**\n * CLI version - read from package.json\n */\nconst CLI_VERSION: string = pkg.version;\n\n/**\n * CLI name for display — varies per build channel (`specforge` vs.\n * `specforge-canary`). The `SpecForge` brand text in banners stays fixed.\n */\nconst CLI_NAME = CHANNEL.name;\n\n/**\n * Create and configure the CLI program\n */\nfunction createProgram(): Command {\n const program = new Command();\n\n // Basic program info\n program\n .name(CLI_NAME)\n .description('SpecForge MCP CLI - Configure and interact with SpecForge')\n .version(CLI_VERSION, '-v, --version', 'Display version number');\n\n // Global options\n program\n .option('--debug', 'Enable debug output')\n .option('--no-color', 'Disable colored output')\n .option('--json', 'Output in JSON format');\n\n // Pre-action hook for auth guard\n program.hook('preAction', (thisCommand, actionCommand) => {\n const commandName = actionCommand.name();\n\n // Set debug mode from flag\n if (thisCommand.opts().debug) {\n process.env.SPECFORGE_DEBUG = 'true';\n }\n\n // Disable colors if requested\n if (thisCommand.opts().noColor) {\n // chalk.level = 0 is handled in colors module\n process.env.NO_COLOR = '1';\n }\n\n // Compact brand header for human-facing commands\n const jsonOutput = !!thisCommand.opts().json;\n const programState = thisCommand as Command & { _bannerPrinted?: boolean };\n if (\n !programState._bannerPrinted &&\n process.stdout.isTTY &&\n !jsonOutput &&\n !BANNER_SKIP_COMMANDS.has(commandName)\n ) {\n printBanner('compact');\n programState._bannerPrinted = true;\n }\n\n debugLog(`Running command: ${commandName}`);\n debugLog('Resolved config:', resolveConfig());\n\n // Run auth guard\n authGuardHook({ name: () => commandName });\n });\n\n return program;\n}\n\n/**\n * Register the default serve command (MCP server mode)\n *\n * This is the hidden default when no subcommand is provided.\n * Maintains backward compatibility with direct MCP server usage.\n */\nfunction registerServeCommand(program: Command): void {\n program\n .command('serve', { isDefault: true, hidden: true })\n .description('Start MCP server (default)')\n .action(withErrorHandler(async () => {\n debugLog('Starting MCP server');\n\n // Dynamic import to avoid loading server code for CLI commands\n const { loadConfig, validateConfig } = await import('../config/index.js');\n const { createServer, startServer } = await import('../server.js');\n\n // Load and validate config\n const config = await loadConfig();\n validateConfig(config);\n // Honor the resolved MCP output format (env > project > global > default).\n config.mcpOutputFormat = resolveConfig().mcpOutputFormat;\n\n // Create and start server\n const server = await createServer(config);\n await startServer(server);\n }));\n}\n\n/**\n * Register all CLI commands\n */\nfunction registerCommands(program: Command): void {\n registerLoginCommand(program);\n registerInitCommand(program);\n registerConfigureCommand(program);\n registerDoctorCommand(program);\n registerStatusCommand(program);\n registerSwitchCommand(program);\n registerDebugCommands(program);\n registerScaffoldCommand(program);\n registerFeedbackCommand(program);\n registerMembersCommand(program);\n registerInvitationsCommand(program);\n}\n\n/**\n * Configure help output styling\n */\nfunction configureHelp(program: Command): void {\n program.configureHelp({\n sortSubcommands: false,\n subcommandTerm: (cmd) => cmd.name(),\n });\n\n // Custom help with organized command groups\n program.addHelpText('after', `\n${colors.muted('Command Groups:')}\n\n ${colors.bold('Authentication')}\n login Authenticate with SpecForge API\n configure Read/write configuration\n\n ${colors.bold('Setup')}\n init Initialize SpecForge in a project\n scaffold Generate skills, agents, and commands\n\n ${colors.bold('Diagnostics')}\n doctor Health check\n status Project status and session info\n\n ${colors.bold('Context')}\n switch Switch active project or specification\n\n ${colors.bold('Collaboration')}\n members Manage project members\n invitations Manage invitations\n\n ${colors.bold('Debug')}\n debug call Direct MCP tool dispatch\n debug tools List available MCP tools\n debug test Connection test\n debug whoami Identity and config info\n\n ${colors.bold('Info')}\n feedback List and submit feedback\n\n${colors.muted('Examples:')}\n $ ${CLI_NAME} login # Authenticate with API key\n $ ${CLI_NAME} init # Initialize in current directory\n $ ${CLI_NAME} status # Show current status\n $ ${CLI_NAME} switch <id> # Switch active project/spec\n\n${colors.muted('Documentation:')}\n ${colors.primary('https://docs.specforge.com/cli')}\n\n${colors.muted('Report issues:')}\n ${colors.primary('https://github.com/specforge/mcp/issues')}\n`);\n}\n\n/**\n * The main CLI program instance\n */\nconst program = createProgram();\n\n// Register commands\nregisterServeCommand(program);\nregisterCommands(program);\n\n// Configure help\nconfigureHelp(program);\n\n/**\n * Run the CLI with given arguments\n *\n * @param argv - Command line arguments (defaults to process.argv)\n */\nexport function runCLI(argv: string[] = process.argv): void {\n try {\n program.parse(argv);\n } catch (error) {\n // Commander handles action-handler errors via `withErrorHandler`, but\n // anything thrown from a `preAction` hook (e.g. `resolveConfig()`\n // surfacing a `LEGACY_CONFIG_DETECTED` ConfigError for a project dir\n // with a legacy `.specforge.json`) reaches here. Use the shared\n // formatter so the user sees the message + hint instead of a silent\n // exit. Keep `debugLog` for stack-trace-under-debug ergonomics.\n debugLog('CLI parse error:', error);\n handleError(error);\n }\n}\n\n/**\n * Export program for testing and extension\n */\nexport { program };\n\n/**\n * Export version for external use\n */\nexport { CLI_VERSION };\n"],"mappings":"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","names":["require","program"]}
|