prjct-cli 1.22.0 → 1.23.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/CHANGELOG.md +147 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct.mjs +917 -35845
- package/dist/bin/prjct.mjs.map +7 -0
- package/dist/cli/linear.mjs +16 -0
- package/dist/cli/linear.mjs.map +7 -0
- package/dist/templates.json +1 -0
- package/package.json +4 -5
- package/bin/prjct.ts +0 -342
- package/core/__tests__/agentic/analysis-injection.test.ts +0 -377
- package/core/__tests__/agentic/cache-eviction.test.ts +0 -294
- package/core/__tests__/agentic/command-context.test.ts +0 -281
- package/core/__tests__/agentic/command-executor.test.ts +0 -659
- package/core/__tests__/agentic/domain-classifier.test.ts +0 -330
- package/core/__tests__/agentic/injection-validator.test.ts +0 -255
- package/core/__tests__/agentic/memory-system.test.ts +0 -281
- package/core/__tests__/agentic/plan-mode.test.ts +0 -386
- package/core/__tests__/agentic/prompt-assembly.test.ts +0 -298
- package/core/__tests__/agentic/prompt-builder.test.ts +0 -243
- package/core/__tests__/agentic/response-validator.test.ts +0 -263
- package/core/__tests__/agentic/semantic-matching.test.ts +0 -131
- package/core/__tests__/agentic/smart-context.test.ts +0 -372
- package/core/__tests__/agentic/tech-normalizer.test.ts +0 -136
- package/core/__tests__/agentic/token-budget.test.ts +0 -294
- package/core/__tests__/ai-tools/formatters.test.ts +0 -476
- package/core/__tests__/domain/bm25.test.ts +0 -225
- package/core/__tests__/domain/change-propagator.test.ts +0 -100
- package/core/__tests__/domain/fibonacci.test.ts +0 -113
- package/core/__tests__/domain/file-hasher.test.ts +0 -146
- package/core/__tests__/domain/file-ranker.test.ts +0 -169
- package/core/__tests__/domain/git-cochange.test.ts +0 -121
- package/core/__tests__/domain/import-graph.test.ts +0 -156
- package/core/__tests__/domain/velocity.test.ts +0 -623
- package/core/__tests__/infrastructure/performance-tracker.test.ts +0 -328
- package/core/__tests__/schemas/model.test.ts +0 -272
- package/core/__tests__/services/dependency-validator.test.ts +0 -175
- package/core/__tests__/services/hierarchical-agent-resolver.test.ts +0 -359
- package/core/__tests__/services/nested-context-resolver.test.ts +0 -443
- package/core/__tests__/services/project-index.test.ts +0 -355
- package/core/__tests__/services/staleness-checker.test.ts +0 -204
- package/core/__tests__/storage/analysis-storage.test.ts +0 -641
- package/core/__tests__/storage/archive-storage.test.ts +0 -455
- package/core/__tests__/storage/safe-reader.test.ts +0 -262
- package/core/__tests__/storage/sqlite-migration.test.ts +0 -1016
- package/core/__tests__/storage/state-storage-feedback.test.ts +0 -463
- package/core/__tests__/storage/state-storage-history.test.ts +0 -469
- package/core/__tests__/storage/storage-manager.test.ts +0 -383
- package/core/__tests__/storage/subtask-handoff.test.ts +0 -237
- package/core/__tests__/types/fs.test.ts +0 -125
- package/core/__tests__/utils/date-helper.test.ts +0 -449
- package/core/__tests__/utils/output.test.ts +0 -278
- package/core/__tests__/utils/preserve-sections.test.ts +0 -216
- package/core/__tests__/utils/project-commands.test.ts +0 -71
- package/core/__tests__/utils/retry.test.ts +0 -381
- package/core/__tests__/workflow/state-machine.test.ts +0 -216
- package/core/agentic/agent-router.ts +0 -150
- package/core/agentic/anti-hallucination.ts +0 -141
- package/core/agentic/chain-of-thought.ts +0 -234
- package/core/agentic/command-classifier.ts +0 -141
- package/core/agentic/command-context.ts +0 -168
- package/core/agentic/command-executor.ts +0 -471
- package/core/agentic/context-builder.ts +0 -285
- package/core/agentic/domain-classifier.ts +0 -525
- package/core/agentic/environment-block.ts +0 -102
- package/core/agentic/ground-truth.ts +0 -706
- package/core/agentic/index.ts +0 -193
- package/core/agentic/injection-validator.ts +0 -208
- package/core/agentic/loop-detector.ts +0 -451
- package/core/agentic/memory-system.ts +0 -1547
- package/core/agentic/orchestrator-executor.ts +0 -579
- package/core/agentic/plan-mode.ts +0 -525
- package/core/agentic/prompt-builder.ts +0 -1069
- package/core/agentic/response-validator.ts +0 -98
- package/core/agentic/services.ts +0 -167
- package/core/agentic/skill-loader.ts +0 -106
- package/core/agentic/smart-context.ts +0 -393
- package/core/agentic/tech-normalizer.ts +0 -167
- package/core/agentic/template-executor.ts +0 -272
- package/core/agentic/template-loader.ts +0 -109
- package/core/agentic/token-budget.ts +0 -226
- package/core/agentic/tool-registry.ts +0 -146
- package/core/agents/index.ts +0 -28
- package/core/agents/performance.ts +0 -429
- package/core/ai-tools/formatters.ts +0 -341
- package/core/ai-tools/generator.ts +0 -144
- package/core/ai-tools/index.ts +0 -15
- package/core/ai-tools/registry.ts +0 -201
- package/core/bus/bus.ts +0 -314
- package/core/bus/index.ts +0 -8
- package/core/cli/linear.ts +0 -500
- package/core/cli/lint-meta-commentary.ts +0 -177
- package/core/cli/start.ts +0 -386
- package/core/commands/analysis.ts +0 -1274
- package/core/commands/analytics.ts +0 -342
- package/core/commands/base.ts +0 -118
- package/core/commands/cleanup.ts +0 -157
- package/core/commands/command-data.ts +0 -463
- package/core/commands/commands.ts +0 -306
- package/core/commands/context.ts +0 -238
- package/core/commands/design.ts +0 -77
- package/core/commands/index.ts +0 -19
- package/core/commands/maintenance.ts +0 -77
- package/core/commands/performance.ts +0 -114
- package/core/commands/planning.ts +0 -662
- package/core/commands/register.ts +0 -127
- package/core/commands/registry.ts +0 -444
- package/core/commands/setup.ts +0 -280
- package/core/commands/shipping.ts +0 -267
- package/core/commands/snapshots.ts +0 -297
- package/core/commands/uninstall.ts +0 -542
- package/core/commands/velocity.ts +0 -149
- package/core/commands/workflow.ts +0 -505
- package/core/config/command-context.config.json +0 -66
- package/core/constants/index.ts +0 -379
- package/core/context/generator.ts +0 -368
- package/core/context-tools/files-tool.ts +0 -577
- package/core/context-tools/imports-tool.ts +0 -400
- package/core/context-tools/index.ts +0 -434
- package/core/context-tools/recent-tool.ts +0 -301
- package/core/context-tools/signatures-tool.ts +0 -495
- package/core/context-tools/summary-tool.ts +0 -301
- package/core/context-tools/token-counter.ts +0 -273
- package/core/context-tools/types.ts +0 -253
- package/core/domain/agent-generator.ts +0 -186
- package/core/domain/agent-loader.ts +0 -419
- package/core/domain/analyzer.ts +0 -387
- package/core/domain/architecture-generator.ts +0 -108
- package/core/domain/bm25.ts +0 -525
- package/core/domain/change-propagator.ts +0 -162
- package/core/domain/context-estimator.ts +0 -175
- package/core/domain/fibonacci.ts +0 -128
- package/core/domain/file-hasher.ts +0 -296
- package/core/domain/file-ranker.ts +0 -151
- package/core/domain/git-cochange.ts +0 -250
- package/core/domain/import-graph.ts +0 -315
- package/core/domain/snapshot-manager.ts +0 -415
- package/core/domain/task-stack.ts +0 -578
- package/core/domain/velocity.ts +0 -470
- package/core/errors.ts +0 -335
- package/core/events/events.ts +0 -85
- package/core/events/index.ts +0 -8
- package/core/index.ts +0 -481
- package/core/infrastructure/agent-detector.ts +0 -135
- package/core/infrastructure/ai-provider.ts +0 -578
- package/core/infrastructure/author-detector.ts +0 -133
- package/core/infrastructure/capability-installer.ts +0 -76
- package/core/infrastructure/claude-agent.ts +0 -297
- package/core/infrastructure/command-installer.ts +0 -752
- package/core/infrastructure/config-manager.ts +0 -364
- package/core/infrastructure/editors-config.ts +0 -172
- package/core/infrastructure/path-manager.ts +0 -571
- package/core/infrastructure/performance-tracker.ts +0 -326
- package/core/infrastructure/permission-manager.ts +0 -289
- package/core/infrastructure/setup.ts +0 -1061
- package/core/infrastructure/update-checker.ts +0 -246
- package/core/integrations/issue-tracker/enricher.ts +0 -271
- package/core/integrations/issue-tracker/index.ts +0 -8
- package/core/integrations/issue-tracker/manager.ts +0 -286
- package/core/integrations/issue-tracker/types.ts +0 -310
- package/core/integrations/jira/cache.ts +0 -57
- package/core/integrations/jira/client.ts +0 -688
- package/core/integrations/jira/index.ts +0 -23
- package/core/integrations/jira/service.ts +0 -244
- package/core/integrations/linear/cache.ts +0 -68
- package/core/integrations/linear/client.ts +0 -436
- package/core/integrations/linear/index.ts +0 -20
- package/core/integrations/linear/service.ts +0 -260
- package/core/integrations/linear/sync.ts +0 -314
- package/core/outcomes/analyzer.ts +0 -286
- package/core/outcomes/index.ts +0 -34
- package/core/outcomes/recorder.ts +0 -195
- package/core/plugin/builtin/webhook.ts +0 -148
- package/core/plugin/hooks.ts +0 -315
- package/core/plugin/index.ts +0 -50
- package/core/plugin/loader.ts +0 -354
- package/core/plugin/registry.ts +0 -326
- package/core/schemas/agents.ts +0 -27
- package/core/schemas/analysis.ts +0 -530
- package/core/schemas/classification.ts +0 -91
- package/core/schemas/command-context.ts +0 -29
- package/core/schemas/enriched-task.ts +0 -291
- package/core/schemas/ideas.ts +0 -114
- package/core/schemas/index.ts +0 -53
- package/core/schemas/issues.ts +0 -159
- package/core/schemas/llm-output.ts +0 -170
- package/core/schemas/metrics.ts +0 -143
- package/core/schemas/model.ts +0 -153
- package/core/schemas/outcomes.ts +0 -487
- package/core/schemas/performance.ts +0 -128
- package/core/schemas/permissions.ts +0 -180
- package/core/schemas/prd.ts +0 -450
- package/core/schemas/project.ts +0 -57
- package/core/schemas/roadmap.ts +0 -322
- package/core/schemas/schemas.ts +0 -38
- package/core/schemas/shipped.ts +0 -109
- package/core/schemas/state.ts +0 -284
- package/core/schemas/velocity.ts +0 -103
- package/core/server/index.ts +0 -21
- package/core/server/routes-extended.ts +0 -566
- package/core/server/routes.ts +0 -176
- package/core/server/server.ts +0 -149
- package/core/server/sse.ts +0 -192
- package/core/services/agent-generator.ts +0 -385
- package/core/services/agent-service.ts +0 -168
- package/core/services/breakdown-service.ts +0 -124
- package/core/services/context-generator.ts +0 -445
- package/core/services/context-selector.ts +0 -429
- package/core/services/dependency-validator.ts +0 -318
- package/core/services/diff-generator.ts +0 -313
- package/core/services/doctor-service.ts +0 -423
- package/core/services/file-categorizer.ts +0 -448
- package/core/services/file-scorer.ts +0 -270
- package/core/services/git-analyzer.ts +0 -293
- package/core/services/hierarchical-agent-resolver.ts +0 -236
- package/core/services/hooks-service.ts +0 -685
- package/core/services/index.ts +0 -46
- package/core/services/local-state-generator.ts +0 -158
- package/core/services/memory-service.ts +0 -181
- package/core/services/nested-context-resolver.ts +0 -842
- package/core/services/project-index.ts +0 -911
- package/core/services/project-service.ts +0 -155
- package/core/services/session-tracker.ts +0 -287
- package/core/services/skill-installer.ts +0 -447
- package/core/services/skill-lock.ts +0 -132
- package/core/services/skill-service.ts +0 -306
- package/core/services/stack-detector.ts +0 -229
- package/core/services/staleness-checker.ts +0 -327
- package/core/services/sync-service.ts +0 -1515
- package/core/services/sync-verifier.ts +0 -253
- package/core/services/watch-service.ts +0 -312
- package/core/session/compaction.ts +0 -248
- package/core/session/index.ts +0 -35
- package/core/session/log-migration.ts +0 -88
- package/core/session/metrics.ts +0 -323
- package/core/session/session-log-manager.ts +0 -307
- package/core/session/task-session-manager.ts +0 -404
- package/core/session/utils.ts +0 -51
- package/core/storage/analysis-storage.ts +0 -373
- package/core/storage/archive-storage.ts +0 -205
- package/core/storage/database.ts +0 -575
- package/core/storage/ideas-storage.ts +0 -298
- package/core/storage/index-storage.ts +0 -523
- package/core/storage/index.ts +0 -79
- package/core/storage/metrics-storage.ts +0 -321
- package/core/storage/migrate-json.ts +0 -720
- package/core/storage/queue-storage.ts +0 -336
- package/core/storage/safe-reader.ts +0 -105
- package/core/storage/shipped-storage.ts +0 -253
- package/core/storage/state-storage.ts +0 -1035
- package/core/storage/storage-manager.ts +0 -205
- package/core/storage/storage.ts +0 -177
- package/core/storage/velocity-storage.ts +0 -149
- package/core/sync/auth-config.ts +0 -138
- package/core/sync/index.ts +0 -31
- package/core/sync/oauth-handler.ts +0 -143
- package/core/sync/sync-client.ts +0 -251
- package/core/sync/sync-manager.ts +0 -327
- package/core/tsconfig.json +0 -22
- package/core/types/agentic.ts +0 -760
- package/core/types/agents.ts +0 -150
- package/core/types/bus.ts +0 -193
- package/core/types/citations.ts +0 -22
- package/core/types/commands.ts +0 -399
- package/core/types/config.ts +0 -92
- package/core/types/core.ts +0 -96
- package/core/types/diff.ts +0 -41
- package/core/types/domain.ts +0 -71
- package/core/types/errors.ts +0 -111
- package/core/types/events.ts +0 -42
- package/core/types/fs.ts +0 -72
- package/core/types/index.ts +0 -510
- package/core/types/infrastructure.ts +0 -210
- package/core/types/integrations.ts +0 -31
- package/core/types/jira.ts +0 -51
- package/core/types/logger.ts +0 -17
- package/core/types/memory.ts +0 -313
- package/core/types/outcomes.ts +0 -190
- package/core/types/output.ts +0 -47
- package/core/types/plugin.ts +0 -25
- package/core/types/project-sync.ts +0 -129
- package/core/types/provider.ts +0 -163
- package/core/types/server.ts +0 -71
- package/core/types/services.ts +0 -84
- package/core/types/session.ts +0 -135
- package/core/types/stack.ts +0 -19
- package/core/types/storage.ts +0 -318
- package/core/types/sync-verifier.ts +0 -33
- package/core/types/sync.ts +0 -121
- package/core/types/task.ts +0 -72
- package/core/types/template.ts +0 -24
- package/core/types/utils.ts +0 -92
- package/core/types/workflow.ts +0 -23
- package/core/utils/agent-stream.ts +0 -140
- package/core/utils/animations.ts +0 -251
- package/core/utils/branding.ts +0 -88
- package/core/utils/cache.ts +0 -187
- package/core/utils/citations.ts +0 -39
- package/core/utils/collection-filters.ts +0 -209
- package/core/utils/date-helper.ts +0 -176
- package/core/utils/error-messages.ts +0 -38
- package/core/utils/file-helper.ts +0 -277
- package/core/utils/fs-helpers.ts +0 -14
- package/core/utils/help.ts +0 -314
- package/core/utils/jsonl-helper.ts +0 -290
- package/core/utils/keychain.ts +0 -127
- package/core/utils/logger.ts +0 -77
- package/core/utils/markdown-builder.ts +0 -280
- package/core/utils/next-steps.ts +0 -95
- package/core/utils/output.ts +0 -403
- package/core/utils/preserve-sections.ts +0 -218
- package/core/utils/project-commands.ts +0 -126
- package/core/utils/project-credentials.ts +0 -143
- package/core/utils/provider-cache.ts +0 -49
- package/core/utils/retry.ts +0 -318
- package/core/utils/runtime.ts +0 -108
- package/core/utils/session-helper.ts +0 -278
- package/core/utils/subtask-table.ts +0 -227
- package/core/utils/version.ts +0 -128
- package/core/wizard/index.ts +0 -13
- package/core/wizard/onboarding.ts +0 -633
- package/core/workflow/index.ts +0 -7
- package/core/workflow/state-machine.ts +0 -198
- package/core/workflow/workflow-preferences.ts +0 -294
- package/dist/core/infrastructure/command-installer.js +0 -1141
- package/dist/core/infrastructure/editors-config.js +0 -177
- package/dist/core/infrastructure/setup.js +0 -2244
- package/dist/core/utils/version.js +0 -141
- package/templates/agentic/agent-routing.md +0 -45
- package/templates/agentic/agents/uxui.md +0 -63
- package/templates/agentic/checklist-routing.md +0 -98
- package/templates/agentic/orchestrator.md +0 -68
- package/templates/agentic/task-fragmentation.md +0 -89
- package/templates/agents/AGENTS.md +0 -68
- package/templates/analysis/analyze.md +0 -84
- package/templates/analysis/patterns.md +0 -60
- package/templates/antigravity/SKILL.md +0 -39
- package/templates/architect/discovery.md +0 -67
- package/templates/architect/phases.md +0 -59
- package/templates/checklists/architecture.md +0 -28
- package/templates/checklists/code-quality.md +0 -28
- package/templates/checklists/data.md +0 -33
- package/templates/checklists/documentation.md +0 -33
- package/templates/checklists/infrastructure.md +0 -33
- package/templates/checklists/performance.md +0 -33
- package/templates/checklists/security.md +0 -33
- package/templates/checklists/testing.md +0 -33
- package/templates/checklists/ux-ui.md +0 -37
- package/templates/commands/analyze.md +0 -56
- package/templates/commands/auth.md +0 -234
- package/templates/commands/bug.md +0 -163
- package/templates/commands/cleanup.md +0 -19
- package/templates/commands/dash.md +0 -99
- package/templates/commands/design.md +0 -15
- package/templates/commands/done.md +0 -291
- package/templates/commands/enrich.md +0 -174
- package/templates/commands/git.md +0 -295
- package/templates/commands/history.md +0 -389
- package/templates/commands/idea.md +0 -88
- package/templates/commands/impact.md +0 -864
- package/templates/commands/init.md +0 -54
- package/templates/commands/jira.md +0 -278
- package/templates/commands/linear.md +0 -288
- package/templates/commands/merge.md +0 -206
- package/templates/commands/next.md +0 -80
- package/templates/commands/p.md +0 -67
- package/templates/commands/p.toml +0 -37
- package/templates/commands/pause.md +0 -136
- package/templates/commands/plan.md +0 -696
- package/templates/commands/prd.md +0 -356
- package/templates/commands/resume.md +0 -171
- package/templates/commands/review.md +0 -276
- package/templates/commands/serve.md +0 -118
- package/templates/commands/setup.md +0 -91
- package/templates/commands/ship.md +0 -475
- package/templates/commands/skill.md +0 -259
- package/templates/commands/spec.md +0 -218
- package/templates/commands/status.md +0 -207
- package/templates/commands/sync.md +0 -104
- package/templates/commands/task.md +0 -312
- package/templates/commands/test.md +0 -93
- package/templates/commands/update.md +0 -63
- package/templates/commands/verify.md +0 -204
- package/templates/commands/workflow.md +0 -150
- package/templates/config/skill-mappings.json +0 -82
- package/templates/context/dashboard.md +0 -256
- package/templates/context/roadmap.md +0 -221
- package/templates/cursor/commands/bug.md +0 -8
- package/templates/cursor/commands/done.md +0 -4
- package/templates/cursor/commands/pause.md +0 -6
- package/templates/cursor/commands/resume.md +0 -4
- package/templates/cursor/commands/ship.md +0 -8
- package/templates/cursor/commands/sync.md +0 -4
- package/templates/cursor/commands/task.md +0 -8
- package/templates/cursor/p.md +0 -29
- package/templates/cursor/router.mdc +0 -28
- package/templates/design/api.md +0 -95
- package/templates/design/architecture.md +0 -77
- package/templates/design/component.md +0 -89
- package/templates/design/database.md +0 -78
- package/templates/design/flow.md +0 -94
- package/templates/global/ANTIGRAVITY.md +0 -254
- package/templates/global/CLAUDE.md +0 -497
- package/templates/global/CURSOR.mdc +0 -266
- package/templates/global/GEMINI.md +0 -293
- package/templates/global/STORAGE-SPEC.md +0 -391
- package/templates/global/WINDSURF.md +0 -266
- package/templates/global/modules/CLAUDE-commands.md +0 -70
- package/templates/global/modules/CLAUDE-core.md +0 -105
- package/templates/global/modules/CLAUDE-git.md +0 -50
- package/templates/global/modules/CLAUDE-intelligence.md +0 -92
- package/templates/global/modules/CLAUDE-storage.md +0 -50
- package/templates/global/modules/module-config.json +0 -36
- package/templates/mcp-config.json +0 -19
- package/templates/permissions/default.jsonc +0 -60
- package/templates/permissions/permissive.jsonc +0 -49
- package/templates/permissions/strict.jsonc +0 -58
- package/templates/planning-methodology.md +0 -195
- package/templates/skills/code-review.md +0 -47
- package/templates/skills/debug.md +0 -61
- package/templates/skills/refactor.md +0 -47
- package/templates/subagents/agent-base.md +0 -20
- package/templates/subagents/domain/backend.md +0 -109
- package/templates/subagents/domain/database.md +0 -121
- package/templates/subagents/domain/devops.md +0 -152
- package/templates/subagents/domain/frontend.md +0 -103
- package/templates/subagents/domain/testing.md +0 -169
- package/templates/subagents/pm-expert.md +0 -366
- package/templates/subagents/workflow/chief-architect.md +0 -657
- package/templates/subagents/workflow/prjct-planner.md +0 -159
- package/templates/subagents/workflow/prjct-shipper.md +0 -188
- package/templates/subagents/workflow/prjct-workflow.md +0 -98
- package/templates/tools/bash.txt +0 -22
- package/templates/tools/edit.txt +0 -18
- package/templates/tools/glob.txt +0 -19
- package/templates/tools/grep.txt +0 -21
- package/templates/tools/read.txt +0 -14
- package/templates/tools/task.txt +0 -20
- package/templates/tools/webfetch.txt +0 -16
- package/templates/tools/websearch.txt +0 -18
- package/templates/tools/write.txt +0 -17
- package/templates/windsurf/router.md +0 -28
- package/templates/windsurf/workflows/bug.md +0 -8
- package/templates/windsurf/workflows/done.md +0 -4
- package/templates/windsurf/workflows/pause.md +0 -4
- package/templates/windsurf/workflows/resume.md +0 -4
- package/templates/windsurf/workflows/ship.md +0 -8
- package/templates/windsurf/workflows/sync.md +0 -4
- package/templates/windsurf/workflows/task.md +0 -8
|
@@ -1,1061 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Setup Module - Core installation logic
|
|
3
|
-
*
|
|
4
|
-
* Executes ALL setup needed for prjct-cli:
|
|
5
|
-
* 1. Detect AI provider (Claude Code or Gemini CLI)
|
|
6
|
-
* 2. Install CLI if missing
|
|
7
|
-
* 3. Sync commands to provider's commands directory
|
|
8
|
-
* 4. Install global config (CLAUDE.md or GEMINI.md)
|
|
9
|
-
* 5. Save version in editors-config
|
|
10
|
-
*
|
|
11
|
-
* Supports multiple AI CLI agents:
|
|
12
|
-
* - Claude Code: ~/.claude/commands/p/, CLAUDE.md
|
|
13
|
-
* - Gemini CLI: ~/.gemini/commands/p/, GEMINI.md
|
|
14
|
-
*
|
|
15
|
-
* This module is called from:
|
|
16
|
-
* - core/index.js (on first CLI use)
|
|
17
|
-
* - scripts/postinstall.js (if npm scripts are enabled)
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { execSync } from 'node:child_process'
|
|
21
|
-
import fs from 'node:fs/promises'
|
|
22
|
-
import os from 'node:os'
|
|
23
|
-
import path from 'node:path'
|
|
24
|
-
import chalk from 'chalk'
|
|
25
|
-
import { getTimeout } from '../constants'
|
|
26
|
-
import { dependencyValidator } from '../services/dependency-validator'
|
|
27
|
-
import { getErrorMessage, isNotFoundError } from '../types/fs'
|
|
28
|
-
import type { AIProviderConfig, AIProviderName } from '../types/provider'
|
|
29
|
-
import { fileExists } from '../utils/fs-helpers'
|
|
30
|
-
import log from '../utils/logger'
|
|
31
|
-
import { PACKAGE_ROOT, VERSION } from '../utils/version'
|
|
32
|
-
import {
|
|
33
|
-
detectAllProviders,
|
|
34
|
-
detectAntigravity,
|
|
35
|
-
detectProvider,
|
|
36
|
-
Providers,
|
|
37
|
-
selectProvider,
|
|
38
|
-
} from './ai-provider'
|
|
39
|
-
import installer from './command-installer'
|
|
40
|
-
import editorsConfig from './editors-config'
|
|
41
|
-
|
|
42
|
-
interface ProviderSetupResult {
|
|
43
|
-
provider: AIProviderName
|
|
44
|
-
cliInstalled: boolean
|
|
45
|
-
commandsAdded: number
|
|
46
|
-
commandsUpdated: number
|
|
47
|
-
configAction: string | null
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface SetupResults {
|
|
51
|
-
provider: AIProviderName // Primary provider (for backward compat)
|
|
52
|
-
providers: ProviderSetupResult[] // All installed providers
|
|
53
|
-
cliInstalled: boolean
|
|
54
|
-
commandsAdded: number
|
|
55
|
-
commandsUpdated: number
|
|
56
|
-
configAction: string | null
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Check if an AI CLI is installed
|
|
61
|
-
*/
|
|
62
|
-
async function _hasAICLI(provider: AIProviderConfig): Promise<boolean> {
|
|
63
|
-
const detection = await detectProvider(provider.name)
|
|
64
|
-
return detection.installed
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Install AI CLI for the specified provider
|
|
69
|
-
* PRJ-114: Enhanced with graceful degradation and alternative install suggestions
|
|
70
|
-
*/
|
|
71
|
-
async function installAICLI(provider: AIProviderConfig): Promise<boolean> {
|
|
72
|
-
const packageName =
|
|
73
|
-
provider.name === 'claude' ? '@anthropic-ai/claude-code' : '@google/gemini-cli'
|
|
74
|
-
|
|
75
|
-
// PRJ-114: Check npm availability first
|
|
76
|
-
if (!dependencyValidator.isAvailable('npm')) {
|
|
77
|
-
console.log(`${chalk.yellow('⚠️ npm is not available')}`)
|
|
78
|
-
console.log('')
|
|
79
|
-
console.log(`${chalk.dim(`Install ${provider.displayName} using one of:`)}`)
|
|
80
|
-
console.log(chalk.dim(' • Install Node.js: https://nodejs.org'))
|
|
81
|
-
console.log(
|
|
82
|
-
chalk.dim(
|
|
83
|
-
` • Use Homebrew: brew install ${provider.name === 'claude' ? 'claude' : 'gemini'}`
|
|
84
|
-
)
|
|
85
|
-
)
|
|
86
|
-
console.log(chalk.dim(` • Use npx directly: npx ${packageName}`))
|
|
87
|
-
console.log('')
|
|
88
|
-
return false
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
console.log(chalk.yellow(`📦 ${provider.displayName} not found. Installing...`))
|
|
93
|
-
console.log('')
|
|
94
|
-
// PRJ-111: Add timeout to npm install (default: 2 minutes, configurable via PRJCT_TIMEOUT_NPM_INSTALL)
|
|
95
|
-
execSync(`npm install -g ${packageName}`, {
|
|
96
|
-
stdio: 'inherit',
|
|
97
|
-
timeout: getTimeout('NPM_INSTALL'),
|
|
98
|
-
})
|
|
99
|
-
console.log('')
|
|
100
|
-
console.log(`${chalk.green('✓')} ${provider.displayName} installed successfully`)
|
|
101
|
-
console.log('')
|
|
102
|
-
return true
|
|
103
|
-
} catch (error) {
|
|
104
|
-
const err = error as Error & { killed?: boolean; signal?: string }
|
|
105
|
-
const isTimeout = err.killed && err.signal === 'SIGTERM'
|
|
106
|
-
|
|
107
|
-
if (isTimeout) {
|
|
108
|
-
console.log(chalk.yellow(`⚠️ Installation timed out for ${provider.displayName}`))
|
|
109
|
-
console.log('')
|
|
110
|
-
console.log(chalk.dim('The npm install took too long. Try:'))
|
|
111
|
-
console.log(chalk.dim(' • Set PRJCT_TIMEOUT_NPM_INSTALL=300000 for 5 minutes'))
|
|
112
|
-
console.log(chalk.dim(` • Run manually: npm install -g ${packageName}`))
|
|
113
|
-
} else {
|
|
114
|
-
console.log(chalk.yellow(`⚠️ Failed to install ${provider.displayName}: ${err.message}`))
|
|
115
|
-
}
|
|
116
|
-
console.log('')
|
|
117
|
-
console.log(chalk.dim('Alternative installation methods:'))
|
|
118
|
-
console.log(chalk.dim(` • npm: npm install -g ${packageName}`))
|
|
119
|
-
console.log(chalk.dim(` • yarn: yarn global add ${packageName}`))
|
|
120
|
-
console.log(chalk.dim(` • pnpm: pnpm add -g ${packageName}`))
|
|
121
|
-
console.log(
|
|
122
|
-
chalk.dim(` • brew: brew install ${provider.name === 'claude' ? 'claude' : 'gemini'}`)
|
|
123
|
-
)
|
|
124
|
-
console.log('')
|
|
125
|
-
return false
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Main setup function - installs for ALL detected providers
|
|
131
|
-
*/
|
|
132
|
-
export async function run(): Promise<SetupResults> {
|
|
133
|
-
// Step 0: Detect all available providers
|
|
134
|
-
const detection = await detectAllProviders()
|
|
135
|
-
const selection = await selectProvider()
|
|
136
|
-
const _primaryProvider = Providers[selection.provider]
|
|
137
|
-
|
|
138
|
-
const results: SetupResults = {
|
|
139
|
-
provider: selection.provider,
|
|
140
|
-
providers: [],
|
|
141
|
-
cliInstalled: false,
|
|
142
|
-
commandsAdded: 0,
|
|
143
|
-
commandsUpdated: 0,
|
|
144
|
-
configAction: null,
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Step 1: Install for each CLI-based provider (Claude, Gemini)
|
|
148
|
-
// Note: Cursor is project-level and handled separately via installCursorProject()
|
|
149
|
-
const cliProviderNames: ('claude' | 'gemini')[] = ['claude', 'gemini']
|
|
150
|
-
|
|
151
|
-
for (const providerName of cliProviderNames) {
|
|
152
|
-
const providerConfig = Providers[providerName]
|
|
153
|
-
const providerDetection = detection[providerName]
|
|
154
|
-
|
|
155
|
-
const providerResult: ProviderSetupResult = {
|
|
156
|
-
provider: providerName,
|
|
157
|
-
cliInstalled: false,
|
|
158
|
-
commandsAdded: 0,
|
|
159
|
-
commandsUpdated: 0,
|
|
160
|
-
configAction: null,
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Check if CLI is installed
|
|
164
|
-
if (!providerDetection.installed) {
|
|
165
|
-
// Only prompt to install the primary (selected) provider
|
|
166
|
-
if (providerName === selection.provider) {
|
|
167
|
-
const installed = await installAICLI(providerConfig)
|
|
168
|
-
if (installed) {
|
|
169
|
-
providerResult.cliInstalled = true
|
|
170
|
-
results.cliInstalled = true
|
|
171
|
-
} else {
|
|
172
|
-
throw new Error(`${providerConfig.displayName} installation failed`)
|
|
173
|
-
}
|
|
174
|
-
} else {
|
|
175
|
-
// Skip non-primary providers that aren't installed
|
|
176
|
-
continue
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Step 2: Install commands and config for this provider
|
|
181
|
-
if (providerName === 'claude') {
|
|
182
|
-
const claudeDetected = await installer.detectClaude()
|
|
183
|
-
|
|
184
|
-
if (claudeDetected) {
|
|
185
|
-
// Sync commands
|
|
186
|
-
const syncResult = await installer.syncCommands()
|
|
187
|
-
if (syncResult.success) {
|
|
188
|
-
providerResult.commandsAdded = syncResult.added
|
|
189
|
-
providerResult.commandsUpdated = syncResult.updated
|
|
190
|
-
results.commandsAdded += syncResult.added
|
|
191
|
-
results.commandsUpdated += syncResult.updated
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Install global configuration
|
|
195
|
-
const configResult = await installer.installGlobalConfig()
|
|
196
|
-
if (configResult.success) {
|
|
197
|
-
providerResult.configAction = configResult.action
|
|
198
|
-
if (!results.configAction) {
|
|
199
|
-
results.configAction = configResult.action
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Install documentation files
|
|
204
|
-
await installer.installDocs()
|
|
205
|
-
|
|
206
|
-
// Install status line (Claude only)
|
|
207
|
-
await installStatusLine()
|
|
208
|
-
|
|
209
|
-
// Install Context7 MCP (only MCP server prjct uses)
|
|
210
|
-
await installContext7MCP()
|
|
211
|
-
}
|
|
212
|
-
} else if (providerName === 'gemini') {
|
|
213
|
-
// Gemini provider - install router and global config
|
|
214
|
-
const geminiInstalled = await installGeminiRouter()
|
|
215
|
-
if (geminiInstalled) {
|
|
216
|
-
providerResult.commandsAdded = 1
|
|
217
|
-
results.commandsAdded += 1
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const geminiConfigResult = await installGeminiGlobalConfig()
|
|
221
|
-
if (geminiConfigResult.success) {
|
|
222
|
-
providerResult.configAction = geminiConfigResult.action
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
results.providers.push(providerResult)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Step 2b: Install for Antigravity if detected (separate from CLI providers)
|
|
230
|
-
const antigravityDetection = await detectAntigravity()
|
|
231
|
-
if (antigravityDetection.installed) {
|
|
232
|
-
const antigravityResult = await installAntigravitySkill()
|
|
233
|
-
if (antigravityResult.success) {
|
|
234
|
-
console.log(` ${chalk.green('✓')} Antigravity skill installed`)
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Step 3: Save version in editors-config
|
|
239
|
-
await editorsConfig.saveConfig(VERSION, await installer.getInstallPath(), selection.provider)
|
|
240
|
-
|
|
241
|
-
// Step 4: Migrate existing projects to add cliVersion
|
|
242
|
-
await migrateProjectsCliVersion()
|
|
243
|
-
|
|
244
|
-
// Show results for all providers
|
|
245
|
-
for (const providerResult of results.providers) {
|
|
246
|
-
showResults(providerResult, Providers[providerResult.provider])
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return results
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Install the p.toml router for Gemini CLI
|
|
254
|
-
*/
|
|
255
|
-
async function installGeminiRouter(): Promise<boolean> {
|
|
256
|
-
try {
|
|
257
|
-
const geminiCommandsDir = path.join(os.homedir(), '.gemini', 'commands')
|
|
258
|
-
const routerSource = path.join(PACKAGE_ROOT, 'templates', 'commands', 'p.toml')
|
|
259
|
-
const routerDest = path.join(geminiCommandsDir, 'p.toml')
|
|
260
|
-
|
|
261
|
-
// Ensure commands directory exists
|
|
262
|
-
await fs.mkdir(geminiCommandsDir, { recursive: true })
|
|
263
|
-
|
|
264
|
-
// Copy router
|
|
265
|
-
if (await fileExists(routerSource)) {
|
|
266
|
-
await fs.copyFile(routerSource, routerDest)
|
|
267
|
-
return true
|
|
268
|
-
}
|
|
269
|
-
return false
|
|
270
|
-
} catch (error) {
|
|
271
|
-
log.warn(`Gemini router warning: ${getErrorMessage(error)}`)
|
|
272
|
-
return false
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Install or update global GEMINI.md configuration
|
|
278
|
-
*/
|
|
279
|
-
async function installGeminiGlobalConfig(): Promise<{ success: boolean; action: string | null }> {
|
|
280
|
-
try {
|
|
281
|
-
const geminiDir = path.join(os.homedir(), '.gemini')
|
|
282
|
-
const globalConfigPath = path.join(geminiDir, 'GEMINI.md')
|
|
283
|
-
const templatePath = path.join(PACKAGE_ROOT, 'templates', 'global', 'GEMINI.md')
|
|
284
|
-
|
|
285
|
-
// Ensure ~/.gemini directory exists
|
|
286
|
-
await fs.mkdir(geminiDir, { recursive: true })
|
|
287
|
-
|
|
288
|
-
// Read template content
|
|
289
|
-
const templateContent = await fs.readFile(templatePath, 'utf-8')
|
|
290
|
-
|
|
291
|
-
// Check if global config already exists
|
|
292
|
-
let existingContent = ''
|
|
293
|
-
let configExists = false
|
|
294
|
-
|
|
295
|
-
try {
|
|
296
|
-
existingContent = await fs.readFile(globalConfigPath, 'utf-8')
|
|
297
|
-
configExists = true
|
|
298
|
-
} catch (error) {
|
|
299
|
-
if (isNotFoundError(error)) {
|
|
300
|
-
configExists = false
|
|
301
|
-
} else {
|
|
302
|
-
throw error
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (!configExists) {
|
|
307
|
-
// Create new file with full template
|
|
308
|
-
await fs.writeFile(globalConfigPath, templateContent, 'utf-8')
|
|
309
|
-
return { success: true, action: 'created' }
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// File exists - perform intelligent merge
|
|
313
|
-
const startMarker = '<!-- prjct:start - DO NOT REMOVE THIS MARKER -->'
|
|
314
|
-
const endMarker = '<!-- prjct:end - DO NOT REMOVE THIS MARKER -->'
|
|
315
|
-
|
|
316
|
-
const hasMarkers = existingContent.includes(startMarker) && existingContent.includes(endMarker)
|
|
317
|
-
|
|
318
|
-
if (!hasMarkers) {
|
|
319
|
-
// No markers - append prjct section at the end
|
|
320
|
-
const updatedContent = `${existingContent}\n\n${templateContent}`
|
|
321
|
-
await fs.writeFile(globalConfigPath, updatedContent, 'utf-8')
|
|
322
|
-
return { success: true, action: 'appended' }
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Markers exist - replace content between markers
|
|
326
|
-
const beforeMarker = existingContent.substring(0, existingContent.indexOf(startMarker))
|
|
327
|
-
const afterMarker = existingContent.substring(
|
|
328
|
-
existingContent.indexOf(endMarker) + endMarker.length
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
// Extract prjct section from template
|
|
332
|
-
const prjctSection = templateContent.substring(
|
|
333
|
-
templateContent.indexOf(startMarker),
|
|
334
|
-
templateContent.indexOf(endMarker) + endMarker.length
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
const updatedContent = beforeMarker + prjctSection + afterMarker
|
|
338
|
-
await fs.writeFile(globalConfigPath, updatedContent, 'utf-8')
|
|
339
|
-
return { success: true, action: 'updated' }
|
|
340
|
-
} catch (error) {
|
|
341
|
-
log.warn(`Gemini config warning: ${getErrorMessage(error)}`)
|
|
342
|
-
return { success: false, action: null }
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// =============================================================================
|
|
347
|
-
// Antigravity Installation (Skills-based)
|
|
348
|
-
// =============================================================================
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Install prjct as a skill for Google Antigravity
|
|
352
|
-
*
|
|
353
|
-
* Antigravity uses SKILL.md files in ~/.gemini/antigravity/skills/
|
|
354
|
-
* This is the recommended integration method (not MCP).
|
|
355
|
-
*/
|
|
356
|
-
export async function installAntigravitySkill(): Promise<{
|
|
357
|
-
success: boolean
|
|
358
|
-
action: string | null
|
|
359
|
-
}> {
|
|
360
|
-
try {
|
|
361
|
-
const antigravitySkillsDir = path.join(os.homedir(), '.gemini', 'antigravity', 'skills')
|
|
362
|
-
const prjctSkillDir = path.join(antigravitySkillsDir, 'prjct')
|
|
363
|
-
const skillMdPath = path.join(prjctSkillDir, 'SKILL.md')
|
|
364
|
-
const templatePath = path.join(PACKAGE_ROOT, 'templates', 'antigravity', 'SKILL.md')
|
|
365
|
-
|
|
366
|
-
// Ensure skills directory exists
|
|
367
|
-
await fs.mkdir(prjctSkillDir, { recursive: true })
|
|
368
|
-
|
|
369
|
-
// Check if SKILL.md already exists
|
|
370
|
-
const skillExists = await fileExists(skillMdPath)
|
|
371
|
-
|
|
372
|
-
// Read template content
|
|
373
|
-
if (!(await fileExists(templatePath))) {
|
|
374
|
-
log.warn('Antigravity SKILL.md template not found')
|
|
375
|
-
return { success: false, action: null }
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const templateContent = await fs.readFile(templatePath, 'utf-8')
|
|
379
|
-
|
|
380
|
-
// Write SKILL.md
|
|
381
|
-
await fs.writeFile(skillMdPath, templateContent, 'utf-8')
|
|
382
|
-
|
|
383
|
-
return { success: true, action: skillExists ? 'updated' : 'created' }
|
|
384
|
-
} catch (error) {
|
|
385
|
-
log.warn(`Antigravity skill warning: ${getErrorMessage(error)}`)
|
|
386
|
-
return { success: false, action: null }
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Check if Antigravity skill needs installation or update
|
|
392
|
-
*/
|
|
393
|
-
export async function needsAntigravityInstallation(): Promise<boolean> {
|
|
394
|
-
const detection = await detectAntigravity()
|
|
395
|
-
return detection.installed && !detection.skillInstalled
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// =============================================================================
|
|
399
|
-
// Cursor IDE Installation (Project-Level)
|
|
400
|
-
// =============================================================================
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Install prjct routers for Cursor IDE in a project
|
|
404
|
-
*
|
|
405
|
-
* Unlike Claude/Gemini which have global config, Cursor uses project-level
|
|
406
|
-
* configuration in .cursor/rules/ and .cursor/commands/.
|
|
407
|
-
*
|
|
408
|
-
* Creates minimal routers that point to the npm package for real instructions.
|
|
409
|
-
* Installs individual command files for better Cursor UX (/sync, /task, etc.)
|
|
410
|
-
*
|
|
411
|
-
* @param projectRoot - The project root directory
|
|
412
|
-
* @returns Object with success status and files created
|
|
413
|
-
*/
|
|
414
|
-
export async function installCursorProject(projectRoot: string): Promise<{
|
|
415
|
-
success: boolean
|
|
416
|
-
rulesCreated: boolean
|
|
417
|
-
commandsCreated: boolean
|
|
418
|
-
gitignoreUpdated: boolean
|
|
419
|
-
}> {
|
|
420
|
-
const result = {
|
|
421
|
-
success: false,
|
|
422
|
-
rulesCreated: false,
|
|
423
|
-
commandsCreated: false,
|
|
424
|
-
gitignoreUpdated: false,
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
try {
|
|
428
|
-
const cursorDir = path.join(projectRoot, '.cursor')
|
|
429
|
-
const rulesDir = path.join(cursorDir, 'rules')
|
|
430
|
-
const commandsDir = path.join(cursorDir, 'commands')
|
|
431
|
-
|
|
432
|
-
const routerMdcDest = path.join(rulesDir, 'prjct.mdc')
|
|
433
|
-
|
|
434
|
-
const routerMdcSource = path.join(PACKAGE_ROOT, 'templates', 'cursor', 'router.mdc')
|
|
435
|
-
const cursorCommandsSource = path.join(PACKAGE_ROOT, 'templates', 'cursor', 'commands')
|
|
436
|
-
|
|
437
|
-
// Ensure directories exist
|
|
438
|
-
await fs.mkdir(rulesDir, { recursive: true })
|
|
439
|
-
await fs.mkdir(commandsDir, { recursive: true })
|
|
440
|
-
|
|
441
|
-
// Copy router.mdc → .cursor/rules/prjct.mdc
|
|
442
|
-
if (await fileExists(routerMdcSource)) {
|
|
443
|
-
await fs.copyFile(routerMdcSource, routerMdcDest)
|
|
444
|
-
result.rulesCreated = true
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Copy individual command files → .cursor/commands/
|
|
448
|
-
// This enables /sync, /task, /done, /ship, etc. syntax in Cursor
|
|
449
|
-
if (await fileExists(cursorCommandsSource)) {
|
|
450
|
-
const commandFiles = (await fs.readdir(cursorCommandsSource)).filter((f) => f.endsWith('.md'))
|
|
451
|
-
|
|
452
|
-
for (const file of commandFiles) {
|
|
453
|
-
const src = path.join(cursorCommandsSource, file)
|
|
454
|
-
const dest = path.join(commandsDir, file)
|
|
455
|
-
await fs.copyFile(src, dest)
|
|
456
|
-
}
|
|
457
|
-
result.commandsCreated = commandFiles.length > 0
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Update .gitignore to exclude prjct Cursor routers
|
|
461
|
-
result.gitignoreUpdated = await addCursorToGitignore(projectRoot)
|
|
462
|
-
|
|
463
|
-
result.success = result.rulesCreated || result.commandsCreated
|
|
464
|
-
return result
|
|
465
|
-
} catch (error) {
|
|
466
|
-
log.warn(`Cursor installation warning: ${getErrorMessage(error)}`)
|
|
467
|
-
return result
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Add Cursor prjct routers to .gitignore
|
|
473
|
-
*
|
|
474
|
-
* These files are per-developer and regenerated automatically.
|
|
475
|
-
*/
|
|
476
|
-
async function addCursorToGitignore(projectRoot: string): Promise<boolean> {
|
|
477
|
-
try {
|
|
478
|
-
const gitignorePath = path.join(projectRoot, '.gitignore')
|
|
479
|
-
const entriesToAdd = [
|
|
480
|
-
'# prjct Cursor routers (regenerated per-developer)',
|
|
481
|
-
'.cursor/rules/prjct.mdc',
|
|
482
|
-
'.cursor/commands/sync.md',
|
|
483
|
-
'.cursor/commands/task.md',
|
|
484
|
-
'.cursor/commands/done.md',
|
|
485
|
-
'.cursor/commands/ship.md',
|
|
486
|
-
'.cursor/commands/bug.md',
|
|
487
|
-
'.cursor/commands/pause.md',
|
|
488
|
-
'.cursor/commands/resume.md',
|
|
489
|
-
]
|
|
490
|
-
|
|
491
|
-
let content = ''
|
|
492
|
-
let configExists = false
|
|
493
|
-
|
|
494
|
-
try {
|
|
495
|
-
content = await fs.readFile(gitignorePath, 'utf-8')
|
|
496
|
-
configExists = true
|
|
497
|
-
} catch (error) {
|
|
498
|
-
if (!isNotFoundError(error)) {
|
|
499
|
-
throw error
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Check if already added
|
|
504
|
-
if (content.includes('.cursor/rules/prjct.mdc')) {
|
|
505
|
-
return false // Already added
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Append to .gitignore
|
|
509
|
-
const newContent = configExists
|
|
510
|
-
? `${content.trimEnd()}\n\n${entriesToAdd.join('\n')}\n`
|
|
511
|
-
: `${entriesToAdd.join('\n')}\n`
|
|
512
|
-
|
|
513
|
-
await fs.writeFile(gitignorePath, newContent, 'utf-8')
|
|
514
|
-
return true
|
|
515
|
-
} catch (error) {
|
|
516
|
-
log.warn(`Gitignore update warning: ${getErrorMessage(error)}`)
|
|
517
|
-
return false
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Check if a project has Cursor configured (has .cursor/ directory)
|
|
523
|
-
*/
|
|
524
|
-
export async function hasCursorProject(projectRoot: string): Promise<boolean> {
|
|
525
|
-
return await fileExists(path.join(projectRoot, '.cursor'))
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Check if Cursor routers need regeneration
|
|
530
|
-
*/
|
|
531
|
-
export async function needsCursorRegeneration(projectRoot: string): Promise<boolean> {
|
|
532
|
-
const cursorDir = path.join(projectRoot, '.cursor')
|
|
533
|
-
const routerPath = path.join(cursorDir, 'rules', 'prjct.mdc')
|
|
534
|
-
|
|
535
|
-
// Only check if .cursor/ exists (project uses Cursor)
|
|
536
|
-
return (await fileExists(cursorDir)) && !(await fileExists(routerPath))
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// =============================================================================
|
|
540
|
-
// Windsurf IDE Installation (Project-Level)
|
|
541
|
-
// =============================================================================
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* Install prjct routers for Windsurf IDE in a project
|
|
545
|
-
*
|
|
546
|
-
* Unlike Claude/Gemini which have global config, Windsurf uses project-level
|
|
547
|
-
* configuration in .windsurf/rules/ and .windsurf/workflows/.
|
|
548
|
-
*
|
|
549
|
-
* Key differences from Cursor:
|
|
550
|
-
* - Uses .md files (not .mdc) with YAML frontmatter
|
|
551
|
-
* - Uses "workflows" directory instead of "commands"
|
|
552
|
-
* - Frontmatter uses `trigger: always_on` instead of `alwaysApply: true`
|
|
553
|
-
*
|
|
554
|
-
* @param projectRoot - The project root directory
|
|
555
|
-
* @returns Object with success status and files created
|
|
556
|
-
*/
|
|
557
|
-
export async function installWindsurfProject(projectRoot: string): Promise<{
|
|
558
|
-
success: boolean
|
|
559
|
-
rulesCreated: boolean
|
|
560
|
-
workflowsCreated: boolean
|
|
561
|
-
gitignoreUpdated: boolean
|
|
562
|
-
}> {
|
|
563
|
-
const result = {
|
|
564
|
-
success: false,
|
|
565
|
-
rulesCreated: false,
|
|
566
|
-
workflowsCreated: false,
|
|
567
|
-
gitignoreUpdated: false,
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
try {
|
|
571
|
-
const windsurfDir = path.join(projectRoot, '.windsurf')
|
|
572
|
-
const rulesDir = path.join(windsurfDir, 'rules')
|
|
573
|
-
const workflowsDir = path.join(windsurfDir, 'workflows')
|
|
574
|
-
|
|
575
|
-
const routerDest = path.join(rulesDir, 'prjct.md')
|
|
576
|
-
|
|
577
|
-
const routerSource = path.join(PACKAGE_ROOT, 'templates', 'windsurf', 'router.md')
|
|
578
|
-
const windsurfWorkflowsSource = path.join(PACKAGE_ROOT, 'templates', 'windsurf', 'workflows')
|
|
579
|
-
|
|
580
|
-
// Ensure directories exist
|
|
581
|
-
await fs.mkdir(rulesDir, { recursive: true })
|
|
582
|
-
await fs.mkdir(workflowsDir, { recursive: true })
|
|
583
|
-
|
|
584
|
-
// Copy router.md → .windsurf/rules/prjct.md
|
|
585
|
-
if (await fileExists(routerSource)) {
|
|
586
|
-
await fs.copyFile(routerSource, routerDest)
|
|
587
|
-
result.rulesCreated = true
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Copy individual workflow files → .windsurf/workflows/
|
|
591
|
-
// This enables /sync, /task, /done, /ship, etc. syntax in Windsurf
|
|
592
|
-
if (await fileExists(windsurfWorkflowsSource)) {
|
|
593
|
-
const workflowFiles = (await fs.readdir(windsurfWorkflowsSource)).filter((f) =>
|
|
594
|
-
f.endsWith('.md')
|
|
595
|
-
)
|
|
596
|
-
|
|
597
|
-
for (const file of workflowFiles) {
|
|
598
|
-
const src = path.join(windsurfWorkflowsSource, file)
|
|
599
|
-
const dest = path.join(workflowsDir, file)
|
|
600
|
-
await fs.copyFile(src, dest)
|
|
601
|
-
}
|
|
602
|
-
result.workflowsCreated = workflowFiles.length > 0
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// Update .gitignore to exclude prjct Windsurf routers
|
|
606
|
-
result.gitignoreUpdated = await addWindsurfToGitignore(projectRoot)
|
|
607
|
-
|
|
608
|
-
result.success = result.rulesCreated || result.workflowsCreated
|
|
609
|
-
return result
|
|
610
|
-
} catch (error) {
|
|
611
|
-
log.warn(`Windsurf installation warning: ${getErrorMessage(error)}`)
|
|
612
|
-
return result
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* Add Windsurf prjct routers to .gitignore
|
|
618
|
-
*
|
|
619
|
-
* These files are per-developer and regenerated automatically.
|
|
620
|
-
*/
|
|
621
|
-
async function addWindsurfToGitignore(projectRoot: string): Promise<boolean> {
|
|
622
|
-
try {
|
|
623
|
-
const gitignorePath = path.join(projectRoot, '.gitignore')
|
|
624
|
-
const entriesToAdd = [
|
|
625
|
-
'# prjct Windsurf routers (regenerated per-developer)',
|
|
626
|
-
'.windsurf/rules/prjct.md',
|
|
627
|
-
'.windsurf/workflows/sync.md',
|
|
628
|
-
'.windsurf/workflows/task.md',
|
|
629
|
-
'.windsurf/workflows/done.md',
|
|
630
|
-
'.windsurf/workflows/ship.md',
|
|
631
|
-
'.windsurf/workflows/bug.md',
|
|
632
|
-
'.windsurf/workflows/pause.md',
|
|
633
|
-
'.windsurf/workflows/resume.md',
|
|
634
|
-
]
|
|
635
|
-
|
|
636
|
-
let content = ''
|
|
637
|
-
let configExists = false
|
|
638
|
-
|
|
639
|
-
try {
|
|
640
|
-
content = await fs.readFile(gitignorePath, 'utf-8')
|
|
641
|
-
configExists = true
|
|
642
|
-
} catch (error) {
|
|
643
|
-
if (!isNotFoundError(error)) {
|
|
644
|
-
throw error
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// Check if already added
|
|
649
|
-
if (content.includes('.windsurf/rules/prjct.md')) {
|
|
650
|
-
return false // Already added
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// Append to .gitignore
|
|
654
|
-
const newContent = configExists
|
|
655
|
-
? `${content.trimEnd()}\n\n${entriesToAdd.join('\n')}\n`
|
|
656
|
-
: `${entriesToAdd.join('\n')}\n`
|
|
657
|
-
|
|
658
|
-
await fs.writeFile(gitignorePath, newContent, 'utf-8')
|
|
659
|
-
return true
|
|
660
|
-
} catch (error) {
|
|
661
|
-
log.warn(`Gitignore update warning: ${getErrorMessage(error)}`)
|
|
662
|
-
return false
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/**
|
|
667
|
-
* Check if a project has Windsurf configured (has .windsurf/ directory)
|
|
668
|
-
*/
|
|
669
|
-
export async function hasWindsurfProject(projectRoot: string): Promise<boolean> {
|
|
670
|
-
return await fileExists(path.join(projectRoot, '.windsurf'))
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* Check if Windsurf routers need regeneration
|
|
675
|
-
*/
|
|
676
|
-
export async function needsWindsurfRegeneration(projectRoot: string): Promise<boolean> {
|
|
677
|
-
const windsurfDir = path.join(projectRoot, '.windsurf')
|
|
678
|
-
const routerPath = path.join(windsurfDir, 'rules', 'prjct.md')
|
|
679
|
-
|
|
680
|
-
// Only check if .windsurf/ exists (project uses Windsurf)
|
|
681
|
-
return (await fileExists(windsurfDir)) && !(await fileExists(routerPath))
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Migrate existing projects to add cliVersion field
|
|
686
|
-
* This clears the status line warning after npm update
|
|
687
|
-
*/
|
|
688
|
-
async function migrateProjectsCliVersion(): Promise<void> {
|
|
689
|
-
try {
|
|
690
|
-
const projectsDir = path.join(os.homedir(), '.prjct-cli', 'projects')
|
|
691
|
-
|
|
692
|
-
if (!(await fileExists(projectsDir))) {
|
|
693
|
-
return
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
const projectDirs = (await fs.readdir(projectsDir, { withFileTypes: true }))
|
|
697
|
-
.filter((dirent) => dirent.isDirectory())
|
|
698
|
-
.map((dirent) => dirent.name)
|
|
699
|
-
|
|
700
|
-
let migrated = 0
|
|
701
|
-
|
|
702
|
-
for (const projectId of projectDirs) {
|
|
703
|
-
const projectJsonPath = path.join(projectsDir, projectId, 'project.json')
|
|
704
|
-
|
|
705
|
-
if (!(await fileExists(projectJsonPath))) {
|
|
706
|
-
continue
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
try {
|
|
710
|
-
const content = await fs.readFile(projectJsonPath, 'utf8')
|
|
711
|
-
const project = JSON.parse(content)
|
|
712
|
-
|
|
713
|
-
// Only update if cliVersion is missing or different
|
|
714
|
-
if (project.cliVersion !== VERSION) {
|
|
715
|
-
project.cliVersion = VERSION
|
|
716
|
-
await fs.writeFile(projectJsonPath, JSON.stringify(project, null, 2))
|
|
717
|
-
migrated++
|
|
718
|
-
}
|
|
719
|
-
} catch (error) {
|
|
720
|
-
// Skip invalid project.json files (missing or malformed JSON)
|
|
721
|
-
if (!isNotFoundError(error) && !(error instanceof SyntaxError)) {
|
|
722
|
-
throw error
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
if (migrated > 0) {
|
|
728
|
-
console.log(` ${chalk.green('✓')} Updated ${migrated} project(s) to v${VERSION}`)
|
|
729
|
-
}
|
|
730
|
-
} catch (error) {
|
|
731
|
-
// Silently fail if projects directory doesn't exist
|
|
732
|
-
if (!isNotFoundError(error)) {
|
|
733
|
-
// Log unexpected errors but don't crash - migration is optional
|
|
734
|
-
log.warn(`Migration warning: ${getErrorMessage(error)}`)
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
* Ensure settings.json has statusLine configured
|
|
741
|
-
*/
|
|
742
|
-
async function ensureStatusLineSettings(
|
|
743
|
-
settingsPath: string,
|
|
744
|
-
statusLinePath: string
|
|
745
|
-
): Promise<void> {
|
|
746
|
-
let settings: Record<string, unknown> = {}
|
|
747
|
-
if (await fileExists(settingsPath)) {
|
|
748
|
-
try {
|
|
749
|
-
settings = JSON.parse(await fs.readFile(settingsPath, 'utf8'))
|
|
750
|
-
} catch (error) {
|
|
751
|
-
// Invalid JSON, start fresh - but propagate unexpected errors
|
|
752
|
-
if (!(error instanceof SyntaxError)) {
|
|
753
|
-
throw error
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
settings.statusLine = { type: 'command', command: statusLinePath }
|
|
758
|
-
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2))
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* Install status line script with version check
|
|
763
|
-
* Copies modular statusline from assets/ to ~/.prjct-cli/statusline/
|
|
764
|
-
* Includes: statusline.sh, lib/, components/, themes/, config.json
|
|
765
|
-
* Creates symlink at ~/.claude/prjct-statusline.sh
|
|
766
|
-
* Updates CLI_VERSION in the script
|
|
767
|
-
*/
|
|
768
|
-
async function installStatusLine(): Promise<void> {
|
|
769
|
-
try {
|
|
770
|
-
const claudeDir = path.join(os.homedir(), '.claude')
|
|
771
|
-
const settingsPath = path.join(claudeDir, 'settings.json')
|
|
772
|
-
const claudeStatusLinePath = path.join(claudeDir, 'prjct-statusline.sh')
|
|
773
|
-
|
|
774
|
-
// Target location for the actual script
|
|
775
|
-
const prjctStatusLineDir = path.join(os.homedir(), '.prjct-cli', 'statusline')
|
|
776
|
-
const prjctStatusLinePath = path.join(prjctStatusLineDir, 'statusline.sh')
|
|
777
|
-
const prjctThemesDir = path.join(prjctStatusLineDir, 'themes')
|
|
778
|
-
const prjctLibDir = path.join(prjctStatusLineDir, 'lib')
|
|
779
|
-
const prjctComponentsDir = path.join(prjctStatusLineDir, 'components')
|
|
780
|
-
const prjctConfigPath = path.join(prjctStatusLineDir, 'config.json')
|
|
781
|
-
|
|
782
|
-
// Source assets (from the package)
|
|
783
|
-
const assetsDir = path.join(PACKAGE_ROOT, 'assets', 'statusline')
|
|
784
|
-
const sourceScript = path.join(assetsDir, 'statusline.sh')
|
|
785
|
-
const sourceThemeDir = path.join(assetsDir, 'themes')
|
|
786
|
-
const sourceLibDir = path.join(assetsDir, 'lib')
|
|
787
|
-
const sourceComponentsDir = path.join(assetsDir, 'components')
|
|
788
|
-
const sourceConfigPath = path.join(assetsDir, 'default-config.json')
|
|
789
|
-
|
|
790
|
-
// Ensure directories exist
|
|
791
|
-
if (!(await fileExists(claudeDir))) {
|
|
792
|
-
await fs.mkdir(claudeDir, { recursive: true })
|
|
793
|
-
}
|
|
794
|
-
if (!(await fileExists(prjctStatusLineDir))) {
|
|
795
|
-
await fs.mkdir(prjctStatusLineDir, { recursive: true })
|
|
796
|
-
}
|
|
797
|
-
if (!(await fileExists(prjctThemesDir))) {
|
|
798
|
-
await fs.mkdir(prjctThemesDir, { recursive: true })
|
|
799
|
-
}
|
|
800
|
-
if (!(await fileExists(prjctLibDir))) {
|
|
801
|
-
await fs.mkdir(prjctLibDir, { recursive: true })
|
|
802
|
-
}
|
|
803
|
-
if (!(await fileExists(prjctComponentsDir))) {
|
|
804
|
-
await fs.mkdir(prjctComponentsDir, { recursive: true })
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// Check if statusline already exists
|
|
808
|
-
if (await fileExists(prjctStatusLinePath)) {
|
|
809
|
-
const existingContent = await fs.readFile(prjctStatusLinePath, 'utf8')
|
|
810
|
-
|
|
811
|
-
if (existingContent.includes('CLI_VERSION=')) {
|
|
812
|
-
// Has CLI_VERSION - update if needed
|
|
813
|
-
const versionMatch = existingContent.match(/CLI_VERSION="([^"]*)"/)
|
|
814
|
-
|
|
815
|
-
if (versionMatch && versionMatch[1] !== VERSION) {
|
|
816
|
-
// Update CLI_VERSION in-place
|
|
817
|
-
const updatedContent = existingContent.replace(
|
|
818
|
-
/CLI_VERSION="[^"]*"/,
|
|
819
|
-
`CLI_VERSION="${VERSION}"`
|
|
820
|
-
)
|
|
821
|
-
await fs.writeFile(prjctStatusLinePath, updatedContent, { mode: 0o755 })
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// Ensure modular structure is installed (upgrade path)
|
|
825
|
-
await installStatusLineModules(sourceLibDir, prjctLibDir)
|
|
826
|
-
await installStatusLineModules(sourceComponentsDir, prjctComponentsDir)
|
|
827
|
-
|
|
828
|
-
// Ensure symlink and settings
|
|
829
|
-
await ensureStatusLineSymlink(claudeStatusLinePath, prjctStatusLinePath)
|
|
830
|
-
await ensureStatusLineSettings(settingsPath, claudeStatusLinePath)
|
|
831
|
-
return
|
|
832
|
-
}
|
|
833
|
-
// else: Script exists WITHOUT CLI_VERSION - fall through to replace with new version
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
// Install fresh from assets if source exists
|
|
837
|
-
if (await fileExists(sourceScript)) {
|
|
838
|
-
// Copy script and update version
|
|
839
|
-
let scriptContent = await fs.readFile(sourceScript, 'utf8')
|
|
840
|
-
scriptContent = scriptContent.replace(/CLI_VERSION="[^"]*"/, `CLI_VERSION="${VERSION}"`)
|
|
841
|
-
await fs.writeFile(prjctStatusLinePath, scriptContent, { mode: 0o755 })
|
|
842
|
-
|
|
843
|
-
// Copy lib/ modules
|
|
844
|
-
await installStatusLineModules(sourceLibDir, prjctLibDir)
|
|
845
|
-
|
|
846
|
-
// Copy components/
|
|
847
|
-
await installStatusLineModules(sourceComponentsDir, prjctComponentsDir)
|
|
848
|
-
|
|
849
|
-
// Copy themes
|
|
850
|
-
if (await fileExists(sourceThemeDir)) {
|
|
851
|
-
const themes = await fs.readdir(sourceThemeDir)
|
|
852
|
-
for (const theme of themes) {
|
|
853
|
-
const src = path.join(sourceThemeDir, theme)
|
|
854
|
-
const dest = path.join(prjctThemesDir, theme)
|
|
855
|
-
// Always update themes to get new icons/colors
|
|
856
|
-
await fs.copyFile(src, dest)
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// Copy default config (only if not exists - preserve user customizations)
|
|
861
|
-
if (!(await fileExists(prjctConfigPath)) && (await fileExists(sourceConfigPath))) {
|
|
862
|
-
await fs.copyFile(sourceConfigPath, prjctConfigPath)
|
|
863
|
-
}
|
|
864
|
-
} else {
|
|
865
|
-
// Fallback: create simple script inline
|
|
866
|
-
const scriptContent = `#!/bin/bash
|
|
867
|
-
# prjct Status Line for Claude Code
|
|
868
|
-
CLI_VERSION="${VERSION}"
|
|
869
|
-
input=$(cat)
|
|
870
|
-
CWD=$(echo "$input" | jq -r '.workspace.current_dir // "~"' 2>/dev/null)
|
|
871
|
-
CONFIG="$CWD/.prjct/prjct.config.json"
|
|
872
|
-
if [ -f "$CONFIG" ]; then
|
|
873
|
-
PROJECT_ID=$(jq -r '.projectId // ""' "$CONFIG" 2>/dev/null)
|
|
874
|
-
if [ -n "$PROJECT_ID" ]; then
|
|
875
|
-
PROJECT_JSON="$HOME/.prjct-cli/projects/$PROJECT_ID/project.json"
|
|
876
|
-
if [ -f "$PROJECT_JSON" ]; then
|
|
877
|
-
PROJECT_VERSION=$(jq -r '.cliVersion // ""' "$PROJECT_JSON" 2>/dev/null)
|
|
878
|
-
if [ -z "$PROJECT_VERSION" ] || [ "$PROJECT_VERSION" != "$CLI_VERSION" ]; then
|
|
879
|
-
echo "prjct v$CLI_VERSION - run p. sync"
|
|
880
|
-
exit 0
|
|
881
|
-
fi
|
|
882
|
-
else
|
|
883
|
-
echo "prjct v$CLI_VERSION - run p. sync"
|
|
884
|
-
exit 0
|
|
885
|
-
fi
|
|
886
|
-
STATE="$HOME/.prjct-cli/projects/$PROJECT_ID/storage/state.json"
|
|
887
|
-
if [ -f "$STATE" ]; then
|
|
888
|
-
TASK=$(jq -r '.currentTask.description // ""' "$STATE" 2>/dev/null)
|
|
889
|
-
if [ -n "$TASK" ]; then
|
|
890
|
-
echo "$TASK"
|
|
891
|
-
exit 0
|
|
892
|
-
fi
|
|
893
|
-
fi
|
|
894
|
-
fi
|
|
895
|
-
fi
|
|
896
|
-
echo "prjct"
|
|
897
|
-
`
|
|
898
|
-
await fs.writeFile(prjctStatusLinePath, scriptContent, { mode: 0o755 })
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// Create symlink and configure settings
|
|
902
|
-
await ensureStatusLineSymlink(claudeStatusLinePath, prjctStatusLinePath)
|
|
903
|
-
await ensureStatusLineSettings(settingsPath, claudeStatusLinePath)
|
|
904
|
-
} catch (error) {
|
|
905
|
-
// Silently fail if directories don't exist
|
|
906
|
-
if (!isNotFoundError(error)) {
|
|
907
|
-
// Log unexpected errors but don't crash - status line is optional
|
|
908
|
-
log.warn(`Status line warning: ${getErrorMessage(error)}`)
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
/**
|
|
914
|
-
* Install Context7 MCP server configuration
|
|
915
|
-
*
|
|
916
|
-
* Context7 is the ONLY MCP server prjct uses - for library documentation lookup.
|
|
917
|
-
* All issue tracker integrations (Linear, JIRA) use SDK/REST API directly.
|
|
918
|
-
*/
|
|
919
|
-
async function installContext7MCP(): Promise<void> {
|
|
920
|
-
try {
|
|
921
|
-
const claudeDir = path.join(os.homedir(), '.claude')
|
|
922
|
-
const mcpConfigPath = path.join(claudeDir, 'mcp.json')
|
|
923
|
-
|
|
924
|
-
// Ensure ~/.claude directory exists
|
|
925
|
-
if (!(await fileExists(claudeDir))) {
|
|
926
|
-
await fs.mkdir(claudeDir, { recursive: true })
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
// Context7 MCP configuration
|
|
930
|
-
const context7Config = {
|
|
931
|
-
mcpServers: {
|
|
932
|
-
context7: {
|
|
933
|
-
command: 'npx',
|
|
934
|
-
args: ['-y', '@upstash/context7-mcp@latest'],
|
|
935
|
-
},
|
|
936
|
-
},
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
// Check if mcp.json exists
|
|
940
|
-
if (await fileExists(mcpConfigPath)) {
|
|
941
|
-
// Read existing config
|
|
942
|
-
const existingContent = await fs.readFile(mcpConfigPath, 'utf-8')
|
|
943
|
-
const existingConfig = JSON.parse(existingContent)
|
|
944
|
-
|
|
945
|
-
// Check if context7 is already configured
|
|
946
|
-
if (existingConfig.mcpServers?.context7) {
|
|
947
|
-
// Already configured, skip
|
|
948
|
-
return
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
// Add context7 to existing config
|
|
952
|
-
existingConfig.mcpServers = existingConfig.mcpServers || {}
|
|
953
|
-
existingConfig.mcpServers.context7 = context7Config.mcpServers.context7
|
|
954
|
-
await fs.writeFile(mcpConfigPath, JSON.stringify(existingConfig, null, 2), 'utf-8')
|
|
955
|
-
} else {
|
|
956
|
-
// Create new mcp.json with context7
|
|
957
|
-
await fs.writeFile(mcpConfigPath, JSON.stringify(context7Config, null, 2), 'utf-8')
|
|
958
|
-
}
|
|
959
|
-
} catch (error) {
|
|
960
|
-
// Non-fatal error, just log
|
|
961
|
-
log.warn(`Context7 MCP setup warning: ${getErrorMessage(error)}`)
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
/**
|
|
966
|
-
* Install statusline modules (lib/ or components/)
|
|
967
|
-
* Copies .sh files from source to destination, always overwriting for updates
|
|
968
|
-
*/
|
|
969
|
-
async function installStatusLineModules(sourceDir: string, destDir: string): Promise<void> {
|
|
970
|
-
if (!(await fileExists(sourceDir))) {
|
|
971
|
-
return
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
const files = await fs.readdir(sourceDir)
|
|
975
|
-
for (const file of files) {
|
|
976
|
-
if (file.endsWith('.sh')) {
|
|
977
|
-
const src = path.join(sourceDir, file)
|
|
978
|
-
const dest = path.join(destDir, file)
|
|
979
|
-
await fs.copyFile(src, dest)
|
|
980
|
-
await fs.chmod(dest, 0o755)
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
/**
|
|
986
|
-
* Ensure symlink from Claude config to prjct statusline
|
|
987
|
-
*/
|
|
988
|
-
async function ensureStatusLineSymlink(linkPath: string, targetPath: string): Promise<void> {
|
|
989
|
-
try {
|
|
990
|
-
// Check if link already points to correct target
|
|
991
|
-
if (await fileExists(linkPath)) {
|
|
992
|
-
const stats = await fs.lstat(linkPath)
|
|
993
|
-
if (stats.isSymbolicLink()) {
|
|
994
|
-
const existingTarget = await fs.readlink(linkPath)
|
|
995
|
-
if (existingTarget === targetPath) {
|
|
996
|
-
return // Already correct
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
// Remove existing file/symlink
|
|
1000
|
-
await fs.unlink(linkPath)
|
|
1001
|
-
}
|
|
1002
|
-
// Create symlink
|
|
1003
|
-
await fs.symlink(targetPath, linkPath)
|
|
1004
|
-
} catch (_error) {
|
|
1005
|
-
// If symlink fails (e.g., Windows, permission issues), try copy instead
|
|
1006
|
-
try {
|
|
1007
|
-
if (await fileExists(targetPath)) {
|
|
1008
|
-
await fs.copyFile(targetPath, linkPath)
|
|
1009
|
-
await fs.chmod(linkPath, 0o755)
|
|
1010
|
-
}
|
|
1011
|
-
} catch (copyError) {
|
|
1012
|
-
// Both symlink and copy failed - log if unexpected error
|
|
1013
|
-
if (!isNotFoundError(copyError)) {
|
|
1014
|
-
log.warn(`Symlink fallback warning: ${(copyError as Error).message}`)
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
/**
|
|
1021
|
-
* Show setup results for a single provider
|
|
1022
|
-
*/
|
|
1023
|
-
function showResults(results: ProviderSetupResult, provider: AIProviderConfig): void {
|
|
1024
|
-
console.log('')
|
|
1025
|
-
|
|
1026
|
-
if (results.cliInstalled) {
|
|
1027
|
-
console.log(` ${chalk.green('✓')} ${provider.displayName} CLI installed`)
|
|
1028
|
-
} else {
|
|
1029
|
-
console.log(` ${chalk.green('✓')} ${provider.displayName} CLI found`)
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
const totalCommands = results.commandsAdded + results.commandsUpdated
|
|
1033
|
-
if (totalCommands > 0) {
|
|
1034
|
-
const parts: string[] = []
|
|
1035
|
-
if (results.commandsAdded > 0) parts.push(`${results.commandsAdded} new`)
|
|
1036
|
-
if (results.commandsUpdated > 0) parts.push(`${results.commandsUpdated} updated`)
|
|
1037
|
-
console.log(` ${chalk.green('✓')} Commands synced (${parts.join(', ')})`)
|
|
1038
|
-
} else {
|
|
1039
|
-
console.log(` ${chalk.green('✓')} Commands up to date`)
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
if (results.configAction === 'created') {
|
|
1043
|
-
console.log(` ${chalk.green('✓')} Global config created (${provider.contextFile})`)
|
|
1044
|
-
} else if (results.configAction === 'updated') {
|
|
1045
|
-
console.log(` ${chalk.green('✓')} Global config updated (${provider.contextFile})`)
|
|
1046
|
-
} else if (results.configAction === 'appended') {
|
|
1047
|
-
console.log(` ${chalk.green('✓')} Global config merged (${provider.contextFile})`)
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
console.log('')
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
// Auto-execute when run directly (for bun/node CLI usage)
|
|
1054
|
-
// This enables: bun core/infrastructure/setup.ts
|
|
1055
|
-
const isDirectRun = process.argv[1]?.includes('setup.ts') || process.argv[1]?.includes('setup.js')
|
|
1056
|
-
if (isDirectRun) {
|
|
1057
|
-
run().catch((error) => {
|
|
1058
|
-
console.error('Setup error:', error.message)
|
|
1059
|
-
process.exit(1)
|
|
1060
|
-
})
|
|
1061
|
-
}
|