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
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
4
|
+
import { dirname as __pathDirname } from 'path';
|
|
5
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __pathDirname(__filename);
|
|
7
|
+
var ae=Object.defineProperty;var o=(r,e)=>ae(r,"name",{value:e,configurable:!0});var ce=(r,e)=>()=>(r&&(e=r(r=0)),e);var je=(r,e)=>{for(var t in e)ae(r,t,{get:e[t],enumerable:!0})};function Oe(r){return r instanceof Error&&"code"in r}function Z(r){return Oe(r)&&r.code==="ENOENT"}function y(r){return r instanceof Error?r.message:typeof r=="string"?r:"Unknown error"}var x=ce(()=>{"use strict";o(Oe,"isNodeError");o(Z,"isNotFoundError");o(y,"getErrorMessage")});var ue={};je(ue,{deleteCredential:()=>$e,getCredential:()=>N,getCredentialWithSource:()=>_e,hasCredential:()=>De,setCredential:()=>Le});import{exec as Ne}from"node:child_process";import{promisify as Re}from"node:util";async function Le(r,e){if(process.platform!=="darwin")return console.warn("[keychain] macOS Keychain only available on macOS"),!1;try{return await D(`security delete-generic-password -s "${_}" -a "${r}" 2>/dev/null || true`),await D(`security add-generic-password -s "${_}" -a "${r}" -w "${e}"`),!0}catch(t){return console.error("[keychain] Failed to store credential:",y(t)),!1}}async function N(r){if(process.platform!=="darwin")return Q(r);try{let{stdout:e}=await D(`security find-generic-password -s "${_}" -a "${r}" -w 2>/dev/null`);return e.trim()||null}catch{return Q(r)}}async function $e(r){if(process.platform!=="darwin")return!1;try{return await D(`security delete-generic-password -s "${_}" -a "${r}" 2>/dev/null`),!0}catch{return!1}}async function De(r){let e=await N(r);return e!==null&&e.length>0}function Q(r){let t={"linear-api-key":"LINEAR_API_KEY","jira-api-token":"JIRA_API_TOKEN"}[r];return process.env[t]||null}async function _e(r){if(process.platform==="darwin")try{let{stdout:t}=await D(`security find-generic-password -s "${_}" -a "${r}" -w 2>/dev/null`),s=t.trim();if(s)return{value:s,source:"keychain"}}catch{}let e=Q(r);return e?{value:e,source:"env"}:{value:null,source:"none"}}var D,_,z=ce(()=>{"use strict";x();D=Re(Ne),_="prjct-cli";o(Le,"setCredential");o(N,"getCredential");o($e,"deleteCredential");o(De,"hasCredential");o(Q,"getEnvFallback");o(_e,"getCredentialWithSource")});var b=class{static{o(this,"TTLCache")}cache=new Map;ttl;maxSize;constructor(e={}){this.ttl=e.ttl??5e3,this.maxSize=e.maxSize??50}isValid(e){let t=this.cache.get(e);return t?Date.now()-t.timestamp<this.ttl:!1}get(e){let t=this.cache.get(e);return t?this.isValid(e)?t.data:(this.cache.delete(e),null):null}set(e,t){this.cache.set(e,{data:t,timestamp:Date.now()}),this.evictOldEntries()}delete(e){this.cache.delete(e)}clear(){this.cache.clear()}has(e){return this.cache.has(e)}get size(){return this.cache.size}evictOldEntries(){if(this.cache.size<=this.maxSize)return;let t=Array.from(this.cache.entries()).sort((s,i)=>s[1].timestamp-i[1].timestamp).slice(0,this.cache.size-this.maxSize);for(let[s]of t)this.cache.delete(s)}stats(){return{size:this.cache.size,maxSize:this.maxSize,ttl:this.ttl}}prune(){let e=0;for(let t of this.cache.keys())this.isValid(t)||(this.cache.delete(t),e++);return e}};var G=300*1e3,f=new b({ttl:G,maxSize:100}),w=new b({ttl:G,maxSize:10}),j=new b({ttl:G,maxSize:5}),O=new b({ttl:G,maxSize:5});function B(){f.clear(),w.clear(),j.clear(),O.clear()}o(B,"clearLinearCache");function X(){return{issues:f.stats(),assignedIssues:w.stats(),teams:j.stats(),projects:O.stats()}}o(X,"getLinearCacheStats");x();z();var Me={backlog:"backlog",unstarted:"todo",started:"in_progress",completed:"done",canceled:"cancelled",cancelled:"cancelled"},Fe={0:"none",1:"urgent",2:"high",3:"medium",4:"low"},le={none:0,urgent:1,high:2,medium:3,low:4},K=class{static{o(this,"LinearProvider")}name="linear";displayName="Linear";sdk=null;config=null;isConfigured(){return this.sdk!==null&&this.config?.enabled===!0}async initialize(e){this.config=e;let t=e.apiKey||await N("linear-api-key");if(!t)throw new Error("LINEAR_API_KEY not configured. Run `p. linear setup` to configure.");let{LinearClient:s}=await import("@linear/sdk");this.sdk=new s({apiKey:t});try{await this.sdk.viewer}catch(i){throw this.sdk=null,new Error(`Linear connection failed: ${y(i)}`)}}async fetchAssignedIssues(e){if(!this.sdk)throw new Error("Linear not initialized");let t=await this.sdk.viewer,s={};e?.includeCompleted||(s.state={type:{nin:["completed","canceled"]}}),this.config?.defaultTeamId&&(s.team={id:{eq:this.config.defaultTeamId}});let i=await t.assignedIssues({first:e?.limit||50,filter:Object.keys(s).length>0?s:void 0});return Promise.all(i.nodes.map(n=>this.mapIssue(n)))}async fetchTeamIssues(e,t){if(!this.sdk)throw new Error("Linear not initialized");let i=await(await this.sdk.team(e)).issues({first:t?.limit||50,filter:t?.includeCompleted?void 0:{state:{type:{nin:["completed","canceled"]}}}});return Promise.all(i.nodes.map(n=>this.mapIssue(n)))}async fetchIssue(e){if(!this.sdk)throw new Error("Linear not initialized");try{if(e.includes("-")&&/^[A-Z]+-\d+$/.test(e)){let s=e.match(/^([A-Z]+)-(\d+)$/);if(!s)return null;let[,i,n]=s,c=parseInt(n,10),$=(await this.sdk.teams({first:50})).nodes.find(Y=>Y.key===i);if(!$)return null;let A=await $.issues({first:1,filter:{number:{eq:c}}});return A.nodes.length>0?this.mapIssue(A.nodes[0]):null}let t=await this.sdk.issue(e);return this.mapIssue(t)}catch{return null}}async createIssue(e){if(!this.sdk)throw new Error("Linear not initialized");let t=e.teamId||this.config?.defaultTeamId;if(!t)throw new Error("Team ID required for creating issues");let i=await(await this.sdk.createIssue({teamId:t,title:e.title,description:e.description,priority:e.priority?le[e.priority]:void 0,projectId:e.projectId||this.config?.defaultProjectId,assigneeId:e.assigneeId,labelIds:e.labels?await this.resolveLabelIds(t,e.labels):void 0})).issue;if(!i)throw new Error("Failed to create issue");return this.mapIssue(i)}async updateIssue(e,t){if(!this.sdk)throw new Error("Linear not initialized");let s=await this.fetchIssue(e);if(!s)throw new Error(`Issue ${e} not found`);let i={};t.title!==void 0&&(i.title=t.title),t.description!==void 0&&(i.description=t.description),t.priority!==void 0&&(i.priority=le[t.priority]),t.assigneeId!==void 0&&(i.assigneeId=t.assigneeId),t.stateId!==void 0&&(i.stateId=t.stateId),t.projectId!==void 0&&(i.projectId=t.projectId),t.labels!==void 0&&s.team&&(i.labelIds=await this.resolveLabelIds(s.team.id,t.labels)),await this.sdk.updateIssue(s.id,i);let n=await this.fetchIssue(s.id);if(!n)throw new Error("Failed to fetch updated issue");return n}async markInProgress(e){if(!this.sdk)throw new Error("Linear not initialized");let t=await this.fetchIssue(e);if(!t)throw new Error(`Issue ${e} not found`);let i=await(await this.sdk.issue(t.id)).team;if(!i)throw new Error("Issue has no team");let c=(await i.states()).nodes.find(d=>d.type==="started");c&&await this.sdk.updateIssue(t.id,{stateId:c.id})}async markDone(e){if(!this.sdk)throw new Error("Linear not initialized");let t=await this.fetchIssue(e);if(!t)throw new Error(`Issue ${e} not found`);let i=await(await this.sdk.issue(t.id)).team;if(!i)throw new Error("Issue has no team");let c=(await i.states()).nodes.find(d=>d.type==="completed");c&&await this.sdk.updateIssue(t.id,{stateId:c.id})}async addComment(e,t){if(!this.sdk)throw new Error("Linear not initialized");let s=await this.fetchIssue(e);if(!s)throw new Error(`Issue ${e} not found`);await this.sdk.createComment({issueId:s.id,body:t})}async getTeams(){if(!this.sdk)throw new Error("Linear not initialized");return(await this.sdk.teams({first:50})).nodes.map(t=>({id:t.id,name:t.name,key:t.key}))}async getProjects(){if(!this.sdk)throw new Error("Linear not initialized");return(await this.sdk.projects({first:50})).nodes.map(t=>({id:t.id,name:t.name}))}async mapIssue(e){let t=await e.state,s=await e.assignee,i=await e.team,n=await e.project,c=await e.labels();return{id:e.id,externalId:e.identifier,provider:"linear",title:e.title,description:e.description||void 0,status:Me[t?.type||"backlog"]||"backlog",priority:Fe[e.priority]||"none",type:this.inferType(e.title,c.nodes.map(d=>d.name)),assignee:s?{id:s.id,name:s.name,email:s.email}:void 0,labels:c.nodes.map(d=>d.name),team:i?{id:i.id,name:i.name,key:i.key}:void 0,project:n?{id:n.id,name:n.name}:void 0,url:e.url,createdAt:e.createdAt.toISOString(),updatedAt:e.updatedAt.toISOString(),raw:e}}inferType(e,t){let s=e.toLowerCase(),i=t.map(n=>n.toLowerCase());return i.includes("bug")||s.includes("fix")||s.includes("bug")?"bug":i.includes("feature")||s.includes("add")||s.includes("implement")?"feature":i.includes("improvement")||s.includes("improve")||s.includes("enhance")?"improvement":i.includes("chore")||s.includes("chore")||s.includes("deps")?"chore":"task"}async resolveLabelIds(e,t){return this.sdk?(await(await this.sdk.team(e)).labels()).nodes.filter(n=>t.includes(n.name)).map(n=>n.id):[]}},h=new K;var J=class{static{o(this,"LinearService")}initialized=!1;userId=null;isReady(){return this.initialized&&h.isConfigured()}async initialize(e){this.initialized||(await h.initialize(e),this.initialized=!0)}async initializeFromApiKey(e,t){let s={enabled:!0,provider:"linear",apiKey:e,defaultTeamId:t,syncOn:{task:!0,done:!0,ship:!0},enrichment:{enabled:!0,updateProvider:!0}};await this.initialize(s)}async fetchAssignedIssues(e){this.ensureInitialized();let t=`assigned:${this.userId||"me"}`,s=w.get(t);if(s)return s;let i=await h.fetchAssignedIssues(e);w.set(t,i);for(let n of i)f.set(`issue:${n.id}`,n),f.set(`issue:${n.externalId}`,n);return i}async fetchTeamIssues(e,t){this.ensureInitialized();let s=`team:${e}`,i=w.get(s);if(i)return i;let n=await h.fetchTeamIssues(e,t);w.set(s,n);for(let c of n)f.set(`issue:${c.id}`,c),f.set(`issue:${c.externalId}`,c);return n}async fetchIssue(e){this.ensureInitialized();let t=`issue:${e}`,s=f.get(t);if(s)return s;let i=await h.fetchIssue(e);return i&&(f.set(`issue:${i.id}`,i),f.set(`issue:${i.externalId}`,i)),i}async createIssue(e){this.ensureInitialized();let t=await h.createIssue(e);return f.set(`issue:${t.id}`,t),f.set(`issue:${t.externalId}`,t),w.clear(),t}async updateIssue(e,t){this.ensureInitialized();let s=await h.updateIssue(e,t);return f.set(`issue:${s.id}`,s),f.set(`issue:${s.externalId}`,s),s}async markInProgress(e){this.ensureInitialized(),await h.markInProgress(e),f.delete(`issue:${e}`),w.clear()}async markDone(e){this.ensureInitialized(),await h.markDone(e),f.delete(`issue:${e}`),w.clear()}async addComment(e,t){this.ensureInitialized(),await h.addComment(e,t)}async getTeams(){this.ensureInitialized();let e=j.get("teams");if(e)return e;let t=await h.getTeams();return j.set("teams",t),t}async getProjects(){this.ensureInitialized();let e=O.get("projects");if(e)return e;let t=await h.getProjects();return O.set("projects",t),t}clearCache(){B()}getCacheStats(){return X()}ensureInitialized(){if(!this.initialized)throw new Error("Linear service not initialized. Call linearService.initialize() first or run `p. linear setup`.")}},l=new J;import{mkdir as ge,readFile as qe,writeFile as he}from"node:fs/promises";import{join as M}from"node:path";import{z as a}from"zod";var de=a.enum(["linear","jira","github","monday","asana","none"]),Ue=a.enum(["backlog","todo","in_progress","in_review","done","cancelled"]),Ge=a.enum(["none","urgent","high","medium","low"]),ze=a.enum(["feature","bug","improvement","task","chore","epic"]),Ke=a.object({id:a.string(),identifier:a.string(),title:a.string(),description:a.string().optional(),status:Ue,priority:Ge,type:ze.optional(),assignee:a.object({id:a.string(),name:a.string(),email:a.string().optional()}).optional(),labels:a.array(a.string()).default([]),team:a.object({id:a.string(),name:a.string(),key:a.string().optional()}).optional(),project:a.object({id:a.string(),name:a.string()}).optional(),url:a.string(),createdAt:a.string(),updatedAt:a.string(),fetchedAt:a.string()}),Je=a.object({provider:de,lastSync:a.string(),staleAfter:a.number().default(18e5),issues:a.record(a.string(),Ke)}),bt=a.object({provider:de,fetched:a.number(),updated:a.number(),errors:a.array(a.object({issueId:a.string(),error:a.string()})),timestamp:a.string()}),pe=o(r=>Je.parse(r),"parseIssues");function me(r){return{provider:r,lastSync:"",staleAfter:18e5,issues:{}}}o(me,"createEmptyIssues");import{homedir as We}from"node:os";import{join as fe}from"node:path";var Ve=fe(We(),".prjct-cli","projects");function W(r){return fe(Ve,r)}o(W,"getProjectPath");x();import He from"node:fs/promises";x();x();async function E(r){try{return await He.access(r),!0}catch(e){if(Z(e))return!1;throw e}}o(E,"fileExists");var ye=1800*1e3,V=class{static{o(this,"LinearSync")}async pullAll(e){let t=M(W(e),"storage"),s=M(t,"issues.json");await E(t)||await ge(t,{recursive:!0});let i=new Date().toISOString(),n=[];try{let c=await l.fetchAssignedIssues({limit:100}),d={};for(let A of c)try{d[A.externalId]=this.toCachedIssue(A,i)}catch(Y){n.push({issueId:A.externalId||A.id,error:y(Y)})}return await he(s,JSON.stringify({provider:"linear",lastSync:i,staleAfter:ye,issues:d},null,2)),{provider:"linear",fetched:c.length,updated:Object.keys(d).length,errors:n,timestamp:i}}catch(c){return n.push({issueId:"all",error:y(c)}),{provider:"linear",fetched:0,updated:0,errors:n,timestamp:i}}}async getIssue(e,t){let s=await this.loadIssues(e);if(s?.issues[t]){let i=s.issues[t],n=new Date(i.fetchedAt).getTime(),c=Date.now(),d=600*1e3;if(c-n<d)return i}try{let i=await l.fetchIssue(t);if(!i)return null;let n=new Date().toISOString(),c=this.toCachedIssue(i,n);return await this.updateIssueInCache(e,t,c),c}catch{return s?.issues[t]?s.issues[t]:null}}async getIssueLocal(e,t){return(await this.loadIssues(e))?.issues[t]||null}async pushStatus(e,t,s){s==="in_progress"?await l.markInProgress(t):s==="done"&&await l.markDone(t);let i=await this.loadIssues(e);if(i?.issues[t]){let n=s==="done"?"done":"in_progress";i.issues[t].status=n,i.issues[t].fetchedAt=new Date().toISOString(),await this.saveIssues(e,i)}}async isStale(e){let t=await this.loadIssues(e);if(!t||!t.lastSync)return!0;let s=new Date(t.lastSync).getTime(),i=Date.now(),n=t.staleAfter||ye;return i-s>n}async getSyncStatus(e){let t=await this.loadIssues(e);return t?{hasCache:!0,lastSync:t.lastSync||null,issueCount:Object.keys(t.issues).length,isStale:await this.isStale(e)}:{hasCache:!1,lastSync:null,issueCount:0,isStale:!0}}async listCachedIssues(e){let t=await this.loadIssues(e);return t?Object.values(t.issues):[]}async loadIssues(e){let t=M(W(e),"storage","issues.json");if(!await E(t))return null;try{let s=await qe(t,"utf-8");return pe(JSON.parse(s))}catch{return null}}async saveIssues(e,t){let s=M(W(e),"storage"),i=M(s,"issues.json");await E(s)||await ge(s,{recursive:!0}),await he(i,JSON.stringify(t,null,2))}async updateIssueInCache(e,t,s){let i=await this.loadIssues(e);i||(i=me("linear")),i.issues[t]=s,await this.saveIssues(e,i)}toCachedIssue(e,t){return{id:e.id,identifier:e.externalId,title:e.title,description:e.description,status:e.status,priority:e.priority,type:e.type,assignee:e.assignee,labels:e.labels,team:e.team,project:e.project,url:e.url,createdAt:e.createdAt,updatedAt:e.updatedAt,fetchedAt:t}}},F=new V;x();import I from"chalk";import k from"chalk";import{exec as Ze}from"node:child_process";import R from"node:os";import L from"node:path";import{promisify as Qe}from"node:util";import{z as S}from"zod";var nr=S.enum(["opus","sonnet","haiku"]),or=S.enum(["2.5-pro","2.5-flash","2.0-flash"]),ar=S.string().min(1);var Ye=S.object({provider:S.string(),model:S.string(),cliVersion:S.string().optional(),recordedAt:S.string()}),cr=S.object({preferredModel:S.string().optional(),lastAnalysisModel:Ye.optional()});import Be from"node:os";import Ie from"node:path";var Xe=Ie.join(Be.homedir(),".prjct-cli","cache"),mr=Ie.join(Xe,"providers.json"),fr=600*1e3;var Ar=Qe(Ze);var br={name:"claude",displayName:"Claude Code",cliCommand:"claude",configDir:L.join(R.homedir(),".claude"),contextFile:"CLAUDE.md",skillsDir:L.join(R.homedir(),".claude","skills"),commandsDir:".claude/commands",commandFormat:"md",settingsFile:"settings.json",projectSettingsFile:"settings.local.json",ignoreFile:".claudeignore",websiteUrl:"https://www.anthropic.com/claude",docsUrl:"https://docs.anthropic.com/claude-code",defaultModel:"sonnet",supportedModels:["opus","sonnet","haiku"],minCliVersion:"1.0.0"},kr={name:"gemini",displayName:"Gemini CLI",cliCommand:"gemini",configDir:L.join(R.homedir(),".gemini"),contextFile:"GEMINI.md",skillsDir:L.join(R.homedir(),".gemini","skills"),commandsDir:".gemini/commands",commandFormat:"toml",settingsFile:"settings.json",projectSettingsFile:"settings.json",ignoreFile:".geminiignore",websiteUrl:"https://geminicli.com",docsUrl:"https://geminicli.com/docs",defaultModel:"2.5-flash",supportedModels:["2.5-pro","2.5-flash","2.0-flash"],minCliVersion:"1.0.0"},jr={name:"antigravity",displayName:"Google Antigravity",cliCommand:null,configDir:L.join(R.homedir(),".gemini","antigravity"),contextFile:"ANTIGRAVITY.md",skillsDir:L.join(R.homedir(),".gemini","antigravity","global_skills"),commandsDir:".agent/skills",commandFormat:"md",settingsFile:"mcp_config.json",projectSettingsFile:null,ignoreFile:".agentignore",websiteUrl:"https://gemini.google.com/app/antigravity",docsUrl:"https://gemini.google.com/app/antigravity",defaultModel:null,supportedModels:[],minCliVersion:null};function ee(r){return{commitFooter:"Generated with [p/](https://www.prjct.app/)",signature:{claude:"\u26A1 prjct + Claude",gemini:"\u26A1 prjct + Gemini",cursor:"\u26A1 prjct + Cursor",antigravity:"\u26A1 prjct + Antigravity",windsurf:"\u26A1 prjct + Windsurf"}[r]||"\u26A1 prjct"}}o(ee,"getProviderBranding");var we=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],et=80,tt={name:"prjct",icon:"\u26A1",signature:"\u26A1 prjct",spinner:{frames:we,speed:et},cli:{header:o(()=>`${k.cyan.bold("\u26A1")} ${k.cyan("prjct")}`,"header"),footer:o(()=>k.dim("\u26A1 prjct"),"footer"),spin:o((r,e)=>`${k.cyan("\u26A1")} ${k.cyan("prjct")} ${k.cyan(we[r%10])} ${k.dim(e||"")}`,"spin")},template:{header:"\u26A1 prjct",footer:"\u26A1 prjct"},commitFooter:"Generated with [p/](https://www.prjct.app/)",urls:{website:"https://prjct.app",docs:"https://prjct.app/docs"},getCommitFooter:o((r="claude")=>ee(r).commitFooter,"getCommitFooter"),getSignature:o((r="claude")=>ee(r).signature,"getSignature")},te=tt;var re={SPINNER_MSG:45,DONE_MSG:50,FAIL_MSG:65,WARN_MSG:65,STEP_MSG:35,PROGRESS_TEXT:25,ISSUE_TITLE:50,FALLBACK_TRUNCATE:50,CLEAR_WIDTH:80};var Yr=te.spinner.frames,Br=te.spinner.speed,st={silent:{maxLines:0,maxCharsPerLine:0,showMetrics:!1},minimal:{maxLines:1,maxCharsPerLine:65,showMetrics:!1},compact:{maxLines:4,maxCharsPerLine:80,showMetrics:!0},verbose:{maxLines:1/0,maxCharsPerLine:1/0,showMetrics:!0}},U="compact";function Se(r){U=r}o(Se,"setOutputTier");function ie(){return st[U]}o(ie,"getTierConfig");var Xr={success:I.green("\u2713"),fail:I.red("\u2717"),warn:I.yellow("\u26A0"),info:I.blue("\u2139"),debug:I.dim("\u{1F527}"),bullet:I.dim("\u2022"),arrow:I.dim("\u2192"),check:I.green("\u2713"),cross:I.red("\u2717"),spinner:I.cyan("\u25D0")};var H=o((r,e)=>{let t=e??(ie().maxCharsPerLine||re.FALLBACK_TRUNCATE);return r&&r.length>t?`${r.slice(0,t-1)}\u2026`:r||""},"truncate");function se(r,e){let t=e??ie().maxLines;if(t===1/0||t===0)return r;let s=r.split(`
|
|
8
|
+
`);if(s.length<=t)return r;let i=s.slice(0,t),n=s.length-t;return`${i.join(`
|
|
9
|
+
`)}
|
|
10
|
+
${I.dim(`...${n} more lines`)}`}o(se,"limitLines");function ve(r){let e=ie();if(U==="silent")return"";if(U==="verbose")return JSON.stringify(r,null,2);if(typeof r!="object"||r===null)return H(String(r),e.maxCharsPerLine);let t=r;if("identifier"in t&&"title"in t){let n=[];return n.push(`${t.identifier}: ${H(String(t.title),e.maxCharsPerLine-10)}`),t.status&&n.push(`Status: ${t.status}`),t.priority&&t.priority!=="none"&&n.push(`Priority: ${t.priority}`),t.url&&U==="compact"&&n.push(I.dim(String(t.url))),se(n.join(`
|
|
11
|
+
`),e.maxLines)}if("issues"in t&&Array.isArray(t.issues)){let n=t.issues,c=n.slice(0,e.maxLines).map(d=>{let $=d.priority&&d.priority!=="none"?` [${d.priority}]`:"";return`${d.identifier} ${H(String(d.title),re.ISSUE_TITLE)}${$}`});return n.length>e.maxLines&&c.push(I.dim(`...${n.length-e.maxLines} more`)),c.join(`
|
|
12
|
+
`)}let i=["id","name","title","status","message","success","error"].filter(n=>n in t);return i.length>0?se(i.map(n=>`${n}: ${H(String(t[n]),e.maxCharsPerLine-n.length-2)}`).join(`
|
|
13
|
+
`),e.maxLines):se(JSON.stringify(r,null,2),e.maxLines)}o(ve,"formatForHuman");x();import ne from"node:fs/promises";import it from"node:os";import xe from"node:path";z();function Ee(r){return xe.join(it.homedir(),".prjct-cli","projects",r,"config","credentials.json")}o(Ee,"getCredentialsPath");async function C(r){let e=Ee(r);if(!await E(e))return{};try{return JSON.parse(await ne.readFile(e,"utf-8"))}catch(t){return console.error("[project-credentials] Failed to read credentials:",y(t)),{}}}o(C,"getProjectCredentials");async function Ce(r,e){let t=Ee(r),s=xe.dirname(t);await E(s)||await ne.mkdir(s,{recursive:!0});let i=await C(r);i.linear=e,await ne.writeFile(t,JSON.stringify(i,null,2))}o(Ce,"setLinearCredentials");async function oe(r){let e=await C(r);return e.linear?.apiKey?e.linear.apiKey:N("linear-api-key")}o(oe,"getLinearApiKey");async function Te(r){if((await C(r)).linear?.apiKey)return"project";let{getCredentialWithSource:t}=await Promise.resolve().then(()=>(z(),ue)),s=await t("linear-api-key");return s.value?s.source==="keychain"?"keychain":"env":"none"}o(Te,"getCredentialSource");var v=process.argv.slice(2),q=v.indexOf("--project"),m=null;q!==-1&&v[q+1]&&(m=v[q+1],v.splice(q,2));var be=v.indexOf("--json"),T=be!==-1;T&&v.splice(be,1);var ke=v.indexOf("--verbose"),nt=ke!==-1;nt&&(v.splice(ke,1),Se("verbose"));var[Ae,...g]=v;function p(r){console.log(T?JSON.stringify(r,null,2):ve(r))}o(p,"output");function u(r,e=1){console.error(T?JSON.stringify({error:r}):`Error: ${r}`),process.exit(e)}o(u,"error");async function P(){m||u("No --project specified. Usage: linear.ts --project <projectId> <command>");let r=await oe(m);r||u("Linear not configured. Run: p. linear setup");let e=await C(m);await l.initializeFromApiKey(r,e.linear?.teamId)}o(P,"initFromProject");async function ot(){try{switch(Ae){case"setup":{m||u("--project required for setup");let r=g[0],e=g[1];r||u("API key required. Usage: setup <apiKey> [teamId]"),await l.initializeFromApiKey(r,e);let t=await l.getTeams();t.length===0&&u("No teams found. Check your API key permissions.");let s=e,i;if(!s&&t.length===1)s=t[0].id,i=t[0].key;else if(s){let n=t.find(c=>c.id===s||c.key===s);n&&(s=n.id,i=n.key)}await Ce(m,{apiKey:r,teamId:s,teamKey:i,setupAt:new Date().toISOString()}),p({success:!0,teams:t,defaultTeam:s?{id:s,key:i}:null});break}case"list":{await P();let r=g[0]?parseInt(g[0],10):20,e=await C(m),t;e.linear?.teamId?t=await l.fetchTeamIssues(e.linear.teamId,{limit:r}):t=await l.fetchAssignedIssues({limit:r});let s=t.map(i=>({id:i.id,identifier:i.externalId,title:i.title,status:i.status,priority:i.priority,url:i.url}));if(T)p({count:t.length,issues:s});else{console.log(`Your issues (${t.length}):`);for(let i of s.slice(0,10)){let n=i.priority&&i.priority!=="none"?` [${i.priority}]`:"";console.log(` ${i.identifier} ${i.title.slice(0,50)}${n}`)}t.length>10&&console.log(` ...${t.length-10} more`)}break}case"list-team":{await P();let r=g[0],e=g[1]?parseInt(g[1],10):20;r||u("Team ID required. Usage: list-team <teamId> [limit]");let t=await l.fetchTeamIssues(r,{limit:e});p({count:t.length,issues:t.map(s=>({id:s.id,identifier:s.externalId,title:s.title,status:s.status,priority:s.priority,url:s.url}))});break}case"get":{await P();let r=g[0];r||u("Issue ID required. Usage: get <id>");let e=await l.fetchIssue(r);if(e||u(`Issue not found: ${r}`),T)p(e);else{if(console.log(`${e.externalId}: ${e.title}`),console.log(`Status: ${e.status} | Priority: ${e.priority||"none"}`),e.description){let t=e.description.slice(0,200);console.log(`
|
|
14
|
+
${t}${e.description.length>200?"...":""}`)}console.log(`
|
|
15
|
+
${e.url}`)}break}case"get-local":{m||u("--project required for get-local");let r=g[0];r||u("Issue ID required. Usage: get-local <id>");let e=await F.getIssueLocal(m,r);e||u(`Issue not in local cache: ${r}. Run 'sync' first.`),p(e);break}case"sync":{m||u("--project required for sync"),await P();let r=await F.pullAll(m);p({success:r.errors.length===0,...r});break}case"sync-status":{m||u("--project required for sync-status");let r=await F.getSyncStatus(m);p(r);break}case"create":{await P();let r=g[0];r||u(`JSON input required. Usage: create '{"title":"...", "teamId":"..."}'`);let e;try{e=JSON.parse(r)}catch{u(`Invalid JSON: ${r}`)}if(e.title||u("title is required"),!e.teamId){let s=await C(m);s.linear?.teamId?e.teamId=s.linear.teamId:u("teamId is required (no default team configured)")}let t=await l.createIssue(e);p(t);break}case"update":{await P();let r=g[0],e=g[1];r||u(`Issue ID required. Usage: update <id> '{"description":"..."}'`),e||u(`JSON input required. Usage: update <id> '{"description":"..."}'`);let t;try{t=JSON.parse(e)}catch{u(`Invalid JSON: ${e}`)}let s=await l.updateIssue(r,t);p(s);break}case"start":{await P();let r=g[0];r||u("Issue ID required. Usage: start <id>"),await l.markInProgress(r),p({success:!0,id:r,status:"in_progress"});break}case"done":{await P();let r=g[0];r||u("Issue ID required. Usage: done <id>"),await l.markDone(r),p({success:!0,id:r,status:"done"});break}case"comment":{await P();let r=g[0],e=g.slice(1).join(" ");r||u("Issue ID required. Usage: comment <id> <text>"),e||u("Comment text required. Usage: comment <id> <text>"),await l.addComment(r,e),p({success:!0,id:r});break}case"teams":{await P();let r=await l.getTeams();p({count:r.length,teams:r});break}case"projects":{await P();let r=await l.getProjects();p({count:r.length,projects:r});break}case"status":{m||u("--project required for status");let r=await Te(m),e=await oe(m),t=await C(m);if(!e){T?p({configured:!1,source:"none",message:"Linear not configured"}):(console.log("Linear: Not configured"),console.log("Run: p. linear setup"));break}try{await l.initializeFromApiKey(e,t.linear?.teamId);let s=await l.getTeams();T?p({configured:!0,source:r,teamId:t.linear?.teamId,teamKey:t.linear?.teamKey,teamsAvailable:s.length}):(console.log("Linear: Connected"),t.linear?.teamKey&&console.log(`Team: ${t.linear.teamKey}`),console.log(`Teams: ${s.length} available`))}catch(s){T?p({configured:!0,source:r,connectionError:y(s)}):(console.log("Linear: Connection error"),console.log(`Error: ${y(s)}`))}break}case"help":case"--help":case"-h":case void 0:{p({usage:"linear.ts --project <projectId> <command> [args...]",commands:{setup:"setup <apiKey> [teamId] - Store API key",list:"list [limit] - List my assigned issues","list-team":"list-team <teamId> [limit] - List team issues",get:"get <id> - Get issue by ID or identifier","get-local":"get-local <id> - Get from local cache (no API)",sync:"sync - Pull all assigned issues to local storage","sync-status":"sync-status - Check local cache status",create:"create <json> - Create issue",update:"update <id> <json> - Update issue",start:"start <id> - Mark as in progress",done:"done <id> - Mark as done",comment:"comment <id> <text> - Add comment",teams:"teams - List available teams",projects:"projects - List available projects",status:"status - Check connection"}});break}default:u(`Unknown command: ${Ae}. Use --help to see available commands.`)}}catch(r){u(y(r))}}o(ot,"main");ot();
|
|
16
|
+
//# sourceMappingURL=linear.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../core/types/fs.ts", "../../core/utils/keychain.ts", "../../core/utils/cache.ts", "../../core/integrations/linear/cache.ts", "../../core/integrations/linear/client.ts", "../../core/integrations/linear/service.ts", "../../core/integrations/linear/sync.ts", "../../core/schemas/issues.ts", "../../core/schemas/schemas.ts", "../../core/utils/file-helper.ts", "../../core/storage/safe-reader.ts", "../../core/cli/linear.ts", "../../core/utils/output.ts", "../../core/utils/branding.ts", "../../core/infrastructure/ai-provider.ts", "../../core/schemas/model.ts", "../../core/utils/provider-cache.ts", "../../core/utils/constants.ts", "../../core/utils/project-credentials.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * File System & Error Types - Centralized type definitions\n *\n * Eliminates duplicate NodeError definitions across:\n * - file-helper.ts\n * - jsonl-helper.ts\n * - config-manager.ts\n * - patterns.ts, semantic-memories.ts, history.ts\n */\n\n/**\n * Node.js error with optional code property\n * Used for ENOENT and other fs error handling\n */\nexport interface NodeError extends Error {\n code?: string\n errno?: number\n syscall?: string\n path?: string\n}\n\n/**\n * Type guard for NodeError\n */\nexport function isNodeError(error: unknown): error is NodeError {\n return error instanceof Error && 'code' in error\n}\n\n/**\n * Check if error is an ENOENT (file not found) error\n */\nexport function isNotFoundError(error: unknown): boolean {\n return isNodeError(error) && error.code === 'ENOENT'\n}\n\n/**\n * Check if error is a permission error\n */\nexport function isPermissionError(error: unknown): boolean {\n return isNodeError(error) && (error.code === 'EACCES' || error.code === 'EPERM')\n}\n\n/**\n * Check if error is a directory not empty error\n */\nexport function isDirNotEmptyError(error: unknown): boolean {\n return isNodeError(error) && error.code === 'ENOTEMPTY'\n}\n\n/**\n * Check if error is a file exists error\n */\nexport function isFileExistsError(error: unknown): boolean {\n return isNodeError(error) && error.code === 'EEXIST'\n}\n\n/**\n * Safely extract error message from unknown catch value\n */\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message\n if (typeof error === 'string') return error\n return 'Unknown error'\n}\n\n/**\n * Safely extract error stack from unknown catch value\n */\nexport function getErrorStack(error: unknown): string | undefined {\n if (error instanceof Error) return error.stack\n return undefined\n}\n", "/**\n * Keychain Helper - Secure credential storage\n *\n * Uses macOS Keychain for secure storage of API keys and tokens.\n * Falls back to environment variables if keychain is not available.\n */\n\nimport { exec } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { getErrorMessage } from '../types/fs'\n\nconst execAsync = promisify(exec)\n\nconst SERVICE_NAME = 'prjct-cli'\n\nexport type CredentialKey = 'linear-api-key' | 'jira-api-token'\n\n/**\n * Store a credential in the keychain\n */\nexport async function setCredential(key: CredentialKey, value: string): Promise<boolean> {\n if (process.platform !== 'darwin') {\n console.warn('[keychain] macOS Keychain only available on macOS')\n return false\n }\n\n try {\n // Delete existing entry first (ignore errors if it doesn't exist)\n await execAsync(\n `security delete-generic-password -s \"${SERVICE_NAME}\" -a \"${key}\" 2>/dev/null || true`\n )\n\n // Add new entry\n await execAsync(`security add-generic-password -s \"${SERVICE_NAME}\" -a \"${key}\" -w \"${value}\"`)\n\n return true\n } catch (error) {\n console.error('[keychain] Failed to store credential:', getErrorMessage(error))\n return false\n }\n}\n\n/**\n * Get a credential from the keychain\n */\nexport async function getCredential(key: CredentialKey): Promise<string | null> {\n if (process.platform !== 'darwin') {\n // Fallback to environment variables on non-macOS\n return getEnvFallback(key)\n }\n\n try {\n const { stdout } = await execAsync(\n `security find-generic-password -s \"${SERVICE_NAME}\" -a \"${key}\" -w 2>/dev/null`\n )\n return stdout.trim() || null\n } catch (_error) {\n // Not found in keychain - expected, try environment variable\n return getEnvFallback(key)\n }\n}\n\n/**\n * Delete a credential from the keychain\n */\nexport async function deleteCredential(key: CredentialKey): Promise<boolean> {\n if (process.platform !== 'darwin') {\n return false\n }\n\n try {\n await execAsync(`security delete-generic-password -s \"${SERVICE_NAME}\" -a \"${key}\" 2>/dev/null`)\n return true\n } catch (_error) {\n // Not found in keychain - expected\n return false\n }\n}\n\n/**\n * Check if a credential exists\n */\nexport async function hasCredential(key: CredentialKey): Promise<boolean> {\n const value = await getCredential(key)\n return value !== null && value.length > 0\n}\n\n/**\n * Get environment variable fallback for a credential key\n */\nfunction getEnvFallback(key: CredentialKey): string | null {\n const envMap: Record<CredentialKey, string> = {\n 'linear-api-key': 'LINEAR_API_KEY',\n 'jira-api-token': 'JIRA_API_TOKEN',\n }\n\n const envVar = envMap[key]\n return process.env[envVar] || null\n}\n\n/**\n * Get credential with source information\n */\nexport async function getCredentialWithSource(\n key: CredentialKey\n): Promise<{ value: string | null; source: 'keychain' | 'env' | 'none' }> {\n if (process.platform === 'darwin') {\n try {\n const { stdout } = await execAsync(\n `security find-generic-password -s \"${SERVICE_NAME}\" -a \"${key}\" -w 2>/dev/null`\n )\n const value = stdout.trim()\n if (value) {\n return { value, source: 'keychain' }\n }\n } catch (_error) {\n // Not in keychain - expected, check env\n }\n }\n\n const envValue = getEnvFallback(key)\n if (envValue) {\n return { value: envValue, source: 'env' }\n }\n\n return { value: null, source: 'none' }\n}\n", "/**\n * Cache Utilities - Shared caching primitives for storage layers\n *\n * Provides two cache implementations:\n * - TTLCache: Time-based cache with LRU eviction (for StorageManager)\n * - LazyCache: Single-project lazy loading cache (for memory system)\n */\n\nimport type { CacheEntry, CacheOptions, CacheStats } from '../types'\n\n/**\n * TTL Cache with LRU eviction\n *\n * Used by StorageManager for project-keyed caching.\n * Automatically evicts expired entries and oldest entries when over capacity.\n */\nexport class TTLCache<T> {\n private cache = new Map<string, CacheEntry<T>>()\n private readonly ttl: number\n private readonly maxSize: number\n\n constructor(options: CacheOptions = {}) {\n this.ttl = options.ttl ?? 5000\n this.maxSize = options.maxSize ?? 50\n }\n\n /**\n * Check if entry exists and is not expired\n */\n isValid(key: string): boolean {\n const entry = this.cache.get(key)\n if (!entry) return false\n return Date.now() - entry.timestamp < this.ttl\n }\n\n /**\n * Get entry if valid, otherwise null\n */\n get(key: string): T | null {\n const entry = this.cache.get(key)\n\n if (!entry) return null\n\n if (!this.isValid(key)) {\n this.cache.delete(key)\n return null\n }\n\n return entry.data\n }\n\n /**\n * Set entry with current timestamp\n */\n set(key: string, data: T): void {\n this.cache.set(key, { data, timestamp: Date.now() })\n this.evictOldEntries()\n }\n\n /**\n * Delete entry\n */\n delete(key: string): void {\n this.cache.delete(key)\n }\n\n /**\n * Clear all entries\n */\n clear(): void {\n this.cache.clear()\n }\n\n /**\n * Check if key exists (may be expired)\n */\n has(key: string): boolean {\n return this.cache.has(key)\n }\n\n /**\n * Get number of entries (including expired)\n */\n get size(): number {\n return this.cache.size\n }\n\n /**\n * Evict oldest entries if over max size\n */\n private evictOldEntries(): void {\n if (this.cache.size <= this.maxSize) return\n\n const entries = Array.from(this.cache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp)\n\n const toRemove = entries.slice(0, this.cache.size - this.maxSize)\n for (const [key] of toRemove) {\n this.cache.delete(key)\n }\n }\n\n /**\n * Get cache statistics\n */\n stats(): CacheStats {\n return {\n size: this.cache.size,\n maxSize: this.maxSize,\n ttl: this.ttl,\n }\n }\n\n /**\n * Remove all expired entries\n */\n prune(): number {\n let removed = 0\n for (const key of this.cache.keys()) {\n if (!this.isValid(key)) {\n this.cache.delete(key)\n removed++\n }\n }\n return removed\n }\n}\n\n/**\n * Single-project lazy cache\n *\n * Used by CachedStore for memory system (one project at a time).\n * Simpler than TTLCache - just tracks if data is loaded for current project.\n */\nexport class LazyCache<T> {\n private data: T | null = null\n private loaded = false\n private projectId: string | null = null\n\n /**\n * Check if loaded for a specific project\n */\n isLoaded(projectId?: string): boolean {\n if (projectId) {\n return this.loaded && this.projectId === projectId\n }\n return this.loaded\n }\n\n /**\n * Get cached data (may be null if not loaded)\n */\n get(): T | null {\n return this.data\n }\n\n /**\n * Set data for a project\n */\n set(projectId: string, data: T): void {\n this.data = data\n this.loaded = true\n this.projectId = projectId\n }\n\n /**\n * Reset cache\n */\n reset(): void {\n this.data = null\n this.loaded = false\n this.projectId = null\n }\n\n /**\n * Get current project ID\n */\n getProjectId(): string | null {\n return this.projectId\n }\n\n /**\n * Check if cache is empty\n */\n isEmpty(): boolean {\n return !this.loaded || this.data === null\n }\n}\n", "/**\n * Linear Cache Module\n * 5-minute TTL cache for Linear API responses to reduce API calls\n */\n\nimport { TTLCache } from '../../utils/cache'\nimport type { Issue } from '../issue-tracker/types'\n\n// 5-minute TTL for Linear API responses\nconst LINEAR_CACHE_TTL = 5 * 60 * 1000 // 300000ms\n\n/**\n * Cache for individual issues (by ID or identifier)\n * Key format: \"issue:{id}\" or \"issue:{identifier}\"\n */\nexport const issueCache = new TTLCache<Issue>({\n ttl: LINEAR_CACHE_TTL,\n maxSize: 100,\n})\n\n/**\n * Cache for assigned issues list\n * Key format: \"assigned:{userId}\" or \"assigned:me\"\n */\nexport const assignedIssuesCache = new TTLCache<Issue[]>({\n ttl: LINEAR_CACHE_TTL,\n maxSize: 10,\n})\n\n/**\n * Cache for teams list\n * Key format: \"teams\"\n */\nexport const teamsCache = new TTLCache<Array<{ id: string; name: string; key?: string }>>({\n ttl: LINEAR_CACHE_TTL,\n maxSize: 5,\n})\n\n/**\n * Cache for projects list\n * Key format: \"projects\"\n */\nexport const projectsCache = new TTLCache<Array<{ id: string; name: string }>>({\n ttl: LINEAR_CACHE_TTL,\n maxSize: 5,\n})\n\n/**\n * Clear all Linear caches\n */\nexport function clearLinearCache(): void {\n issueCache.clear()\n assignedIssuesCache.clear()\n teamsCache.clear()\n projectsCache.clear()\n}\n\n/**\n * Get cache statistics for debugging\n */\nexport function getLinearCacheStats() {\n return {\n issues: issueCache.stats(),\n assignedIssues: assignedIssuesCache.stats(),\n teams: teamsCache.stats(),\n projects: projectsCache.stats(),\n }\n}\n", "/**\n * Linear Client\n * Implements IssueTrackerProvider for Linear using @linear/sdk\n */\n\nimport type { LinearClient as LinearSDK } from '@linear/sdk'\nimport { getErrorMessage } from '../../types/fs'\nimport { getCredential } from '../../utils/keychain'\nimport type {\n CreateIssueInput,\n FetchOptions,\n Issue,\n IssuePriority,\n IssueStatus,\n IssueTrackerProvider,\n IssueType,\n LinearConfig,\n UpdateIssueInput,\n} from '../issue-tracker/types'\n\n// =============================================================================\n// Status/Priority Mapping\n// =============================================================================\n\nconst LINEAR_STATUS_MAP: Record<string, IssueStatus> = {\n backlog: 'backlog',\n unstarted: 'todo',\n started: 'in_progress',\n completed: 'done',\n canceled: 'cancelled',\n cancelled: 'cancelled',\n}\n\nconst LINEAR_PRIORITY_MAP: Record<number, IssuePriority> = {\n 0: 'none',\n 1: 'urgent',\n 2: 'high',\n 3: 'medium',\n 4: 'low',\n}\n\nconst PRIORITY_TO_LINEAR: Record<IssuePriority, number> = {\n none: 0,\n urgent: 1,\n high: 2,\n medium: 3,\n low: 4,\n}\n\n// =============================================================================\n// Linear Provider Implementation\n// =============================================================================\n\nexport class LinearProvider implements IssueTrackerProvider {\n readonly name = 'linear' as const\n readonly displayName = 'Linear'\n\n private sdk: LinearSDK | null = null\n private config: LinearConfig | null = null\n\n /**\n * Check if provider is configured\n */\n isConfigured(): boolean {\n return this.sdk !== null && this.config?.enabled === true\n }\n\n /**\n * Initialize with config\n * Looks for API key in: 1) config.apiKey, 2) macOS Keychain, 3) LINEAR_API_KEY env var\n */\n async initialize(config: LinearConfig): Promise<void> {\n this.config = config\n\n // Try config first, then keychain (which falls back to env var)\n const apiKey = config.apiKey || (await getCredential('linear-api-key'))\n if (!apiKey) {\n throw new Error('LINEAR_API_KEY not configured. Run `p. linear setup` to configure.')\n }\n\n const { LinearClient } = await import('@linear/sdk')\n this.sdk = new LinearClient({ apiKey })\n\n // Verify connection silently (no output noise)\n try {\n await this.sdk.viewer\n } catch (error) {\n this.sdk = null\n throw new Error(`Linear connection failed: ${getErrorMessage(error)}`)\n }\n }\n\n /**\n * Get issues assigned to current user\n * Filters by configured team if defaultTeamId is set\n */\n async fetchAssignedIssues(options?: FetchOptions): Promise<Issue[]> {\n if (!this.sdk) throw new Error('Linear not initialized')\n\n const viewer = await this.sdk.viewer\n\n // Build filter - always filter by team if configured\n const filter: Record<string, unknown> = {}\n\n if (!options?.includeCompleted) {\n filter.state = { type: { nin: ['completed', 'canceled'] } }\n }\n\n // Filter by configured team to only show relevant issues\n if (this.config?.defaultTeamId) {\n filter.team = { id: { eq: this.config.defaultTeamId } }\n }\n\n const assignedIssues = await viewer.assignedIssues({\n first: options?.limit || 50,\n filter: Object.keys(filter).length > 0 ? filter : undefined,\n })\n\n return Promise.all(assignedIssues.nodes.map((issue) => this.mapIssue(issue)))\n }\n\n /**\n * Get issues from a team\n */\n async fetchTeamIssues(teamId: string, options?: FetchOptions): Promise<Issue[]> {\n if (!this.sdk) throw new Error('Linear not initialized')\n\n const team = await this.sdk.team(teamId)\n const issues = await team.issues({\n first: options?.limit || 50,\n filter: options?.includeCompleted\n ? undefined\n : { state: { type: { nin: ['completed', 'canceled'] } } },\n })\n\n return Promise.all(issues.nodes.map((issue) => this.mapIssue(issue)))\n }\n\n /**\n * Get a single issue by ID or identifier (e.g., \"PRJ-123\")\n */\n async fetchIssue(id: string): Promise<Issue | null> {\n if (!this.sdk) throw new Error('Linear not initialized')\n\n try {\n // Check if it looks like an identifier (e.g., \"PRJ-123\")\n if (id.includes('-') && /^[A-Z]+-\\d+$/.test(id)) {\n // Parse identifier into team key and issue number\n const match = id.match(/^([A-Z]+)-(\\d+)$/)\n if (!match) return null\n\n const [, teamKey, numberStr] = match\n const issueNumber = parseInt(numberStr, 10)\n\n // Find team by key\n const teams = await this.sdk.teams({ first: 50 })\n const team = teams.nodes.find((t) => t.key === teamKey)\n if (!team) return null\n\n // Query issue by team and number\n const issues = await team.issues({\n first: 1,\n filter: { number: { eq: issueNumber } },\n })\n\n if (issues.nodes.length > 0) {\n return this.mapIssue(issues.nodes[0])\n }\n return null\n }\n\n // Try by UUID directly\n const issue = await this.sdk.issue(id)\n return this.mapIssue(issue)\n } catch (_error) {\n return null\n }\n }\n\n /**\n * Create a new issue\n */\n async createIssue(input: CreateIssueInput): Promise<Issue> {\n if (!this.sdk) throw new Error('Linear not initialized')\n\n const teamId = input.teamId || this.config?.defaultTeamId\n if (!teamId) {\n throw new Error('Team ID required for creating issues')\n }\n\n const payload = await this.sdk.createIssue({\n teamId,\n title: input.title,\n description: input.description,\n priority: input.priority ? PRIORITY_TO_LINEAR[input.priority] : undefined,\n projectId: input.projectId || this.config?.defaultProjectId,\n assigneeId: input.assigneeId,\n labelIds: input.labels ? await this.resolveLabelIds(teamId, input.labels) : undefined,\n })\n\n const createdIssue = await payload.issue\n if (!createdIssue) {\n throw new Error('Failed to create issue')\n }\n\n return this.mapIssue(createdIssue)\n }\n\n /**\n * Update an issue\n */\n async updateIssue(id: string, input: UpdateIssueInput): Promise<Issue> {\n if (!this.sdk) throw new Error('Linear not initialized')\n\n // Get the issue first to get UUID (if identifier like PRJ-123 was passed)\n const issue = await this.fetchIssue(id)\n if (!issue) {\n throw new Error(`Issue ${id} not found`)\n }\n\n // Build update payload with all supported fields\n const updatePayload: Record<string, unknown> = {}\n\n if (input.title !== undefined) updatePayload.title = input.title\n if (input.description !== undefined) updatePayload.description = input.description\n if (input.priority !== undefined) updatePayload.priority = PRIORITY_TO_LINEAR[input.priority]\n if (input.assigneeId !== undefined) updatePayload.assigneeId = input.assigneeId\n if (input.stateId !== undefined) updatePayload.stateId = input.stateId\n if (input.projectId !== undefined) updatePayload.projectId = input.projectId\n\n // Handle labels - need to resolve names to IDs\n if (input.labels !== undefined && issue.team) {\n updatePayload.labelIds = await this.resolveLabelIds(issue.team.id, input.labels)\n }\n\n await this.sdk.updateIssue(issue.id, updatePayload)\n\n // Fetch updated issue\n const updated = await this.fetchIssue(issue.id)\n if (!updated) {\n throw new Error('Failed to fetch updated issue')\n }\n\n return updated\n }\n\n /**\n * Mark issue as in progress\n */\n async markInProgress(id: string): Promise<void> {\n if (!this.sdk) throw new Error('Linear not initialized')\n\n const issue = await this.fetchIssue(id)\n if (!issue) throw new Error(`Issue ${id} not found`)\n\n // Find \"started\" state for the team\n const linearIssue = await this.sdk.issue(issue.id)\n const team = await linearIssue.team\n if (!team) throw new Error('Issue has no team')\n\n const states = await team.states()\n const startedState = states.nodes.find((s) => s.type === 'started')\n\n if (startedState) {\n await this.sdk.updateIssue(issue.id, { stateId: startedState.id })\n }\n }\n\n /**\n * Mark issue as done\n */\n async markDone(id: string): Promise<void> {\n if (!this.sdk) throw new Error('Linear not initialized')\n\n const issue = await this.fetchIssue(id)\n if (!issue) throw new Error(`Issue ${id} not found`)\n\n // Find \"completed\" state for the team\n const linearIssue = await this.sdk.issue(issue.id)\n const team = await linearIssue.team\n if (!team) throw new Error('Issue has no team')\n\n const states = await team.states()\n const doneState = states.nodes.find((s) => s.type === 'completed')\n\n if (doneState) {\n await this.sdk.updateIssue(issue.id, { stateId: doneState.id })\n }\n }\n\n /**\n * Add a comment to an issue\n */\n async addComment(id: string, body: string): Promise<void> {\n if (!this.sdk) throw new Error('Linear not initialized')\n\n const issue = await this.fetchIssue(id)\n if (!issue) throw new Error(`Issue ${id} not found`)\n\n await this.sdk.createComment({\n issueId: issue.id,\n body,\n })\n }\n\n /**\n * Get available teams\n */\n async getTeams(): Promise<Array<{ id: string; name: string; key?: string }>> {\n if (!this.sdk) throw new Error('Linear not initialized')\n\n const teams = await this.sdk.teams({ first: 50 })\n return teams.nodes.map((team) => ({\n id: team.id,\n name: team.name,\n key: team.key,\n }))\n }\n\n /**\n * Get available projects\n */\n async getProjects(): Promise<Array<{ id: string; name: string }>> {\n if (!this.sdk) throw new Error('Linear not initialized')\n\n const projects = await this.sdk.projects({ first: 50 })\n return projects.nodes.map((project) => ({\n id: project.id,\n name: project.name,\n }))\n }\n\n // =============================================================================\n // Private Helpers\n // =============================================================================\n\n /**\n * Map Linear issue to normalized Issue\n */\n private async mapIssue(linearIssue: Awaited<ReturnType<LinearSDK['issue']>>): Promise<Issue> {\n const state = await linearIssue.state\n const assignee = await linearIssue.assignee\n const team = await linearIssue.team\n const project = await linearIssue.project\n const labels = await linearIssue.labels()\n\n return {\n id: linearIssue.id,\n externalId: linearIssue.identifier,\n provider: 'linear',\n title: linearIssue.title,\n description: linearIssue.description || undefined,\n status: LINEAR_STATUS_MAP[state?.type || 'backlog'] || 'backlog',\n priority: LINEAR_PRIORITY_MAP[linearIssue.priority] || 'none',\n type: this.inferType(\n linearIssue.title,\n labels.nodes.map((l) => l.name)\n ),\n assignee: assignee\n ? {\n id: assignee.id,\n name: assignee.name,\n email: assignee.email,\n }\n : undefined,\n labels: labels.nodes.map((l) => l.name),\n team: team\n ? {\n id: team.id,\n name: team.name,\n key: team.key,\n }\n : undefined,\n project: project\n ? {\n id: project.id,\n name: project.name,\n }\n : undefined,\n url: linearIssue.url,\n createdAt: linearIssue.createdAt.toISOString(),\n updatedAt: linearIssue.updatedAt.toISOString(),\n raw: linearIssue,\n }\n }\n\n /**\n * Infer issue type from title and labels\n */\n private inferType(title: string, labels: string[]): IssueType {\n const titleLower = title.toLowerCase()\n const labelsLower = labels.map((l) => l.toLowerCase())\n\n if (labelsLower.includes('bug') || titleLower.includes('fix') || titleLower.includes('bug')) {\n return 'bug'\n }\n if (\n labelsLower.includes('feature') ||\n titleLower.includes('add') ||\n titleLower.includes('implement')\n ) {\n return 'feature'\n }\n if (\n labelsLower.includes('improvement') ||\n titleLower.includes('improve') ||\n titleLower.includes('enhance')\n ) {\n return 'improvement'\n }\n if (\n labelsLower.includes('chore') ||\n titleLower.includes('chore') ||\n titleLower.includes('deps')\n ) {\n return 'chore'\n }\n\n return 'task'\n }\n\n /**\n * Resolve label names to IDs\n */\n private async resolveLabelIds(teamId: string, labelNames: string[]): Promise<string[]> {\n if (!this.sdk) return []\n\n const team = await this.sdk.team(teamId)\n const labels = await team.labels()\n\n return labels.nodes.filter((label) => labelNames.includes(label.name)).map((label) => label.id)\n }\n}\n\n// Singleton instance\nexport const linearProvider = new LinearProvider()\n", "/**\n * Linear Service Layer\n * Wraps LinearProvider with caching for improved performance.\n * All operations are cached with 5-minute TTL.\n */\n\nimport type {\n CreateIssueInput,\n FetchOptions,\n Issue,\n LinearConfig,\n UpdateIssueInput,\n} from '../issue-tracker/types'\nimport {\n assignedIssuesCache,\n clearLinearCache,\n getLinearCacheStats,\n issueCache,\n projectsCache,\n teamsCache,\n} from './cache'\nimport { linearProvider } from './client'\n\nexport class LinearService {\n private initialized = false\n private userId: string | null = null\n\n /**\n * Check if service is ready\n */\n isReady(): boolean {\n return this.initialized && linearProvider.isConfigured()\n }\n\n /**\n * Initialize the service with config\n * Must be called before any operations\n */\n async initialize(config: LinearConfig): Promise<void> {\n if (this.initialized) return\n\n await linearProvider.initialize(config)\n this.initialized = true\n }\n\n /**\n * Initialize from API key directly\n * Convenience method for simple setup\n */\n async initializeFromApiKey(apiKey: string, teamId?: string): Promise<void> {\n const config: LinearConfig = {\n enabled: true,\n provider: 'linear',\n apiKey,\n defaultTeamId: teamId,\n syncOn: { task: true, done: true, ship: true },\n enrichment: { enabled: true, updateProvider: true },\n }\n await this.initialize(config)\n }\n\n /**\n * Get issues assigned to current user (cached)\n */\n async fetchAssignedIssues(options?: FetchOptions): Promise<Issue[]> {\n this.ensureInitialized()\n\n const cacheKey = `assigned:${this.userId || 'me'}`\n const cached = assignedIssuesCache.get(cacheKey)\n if (cached) {\n return cached\n }\n\n const issues = await linearProvider.fetchAssignedIssues(options)\n assignedIssuesCache.set(cacheKey, issues)\n\n // Also cache individual issues\n for (const issue of issues) {\n issueCache.set(`issue:${issue.id}`, issue)\n issueCache.set(`issue:${issue.externalId}`, issue)\n }\n\n return issues\n }\n\n /**\n * Get issues from a team (cached)\n */\n async fetchTeamIssues(teamId: string, options?: FetchOptions): Promise<Issue[]> {\n this.ensureInitialized()\n\n const cacheKey = `team:${teamId}`\n const cached = assignedIssuesCache.get(cacheKey)\n if (cached) {\n return cached\n }\n\n const issues = await linearProvider.fetchTeamIssues(teamId, options)\n assignedIssuesCache.set(cacheKey, issues)\n\n // Also cache individual issues\n for (const issue of issues) {\n issueCache.set(`issue:${issue.id}`, issue)\n issueCache.set(`issue:${issue.externalId}`, issue)\n }\n\n return issues\n }\n\n /**\n * Get a single issue by ID or identifier (cached)\n * Accepts UUID or identifier like \"PRJ-123\"\n */\n async fetchIssue(id: string): Promise<Issue | null> {\n this.ensureInitialized()\n\n // Check cache first\n const cacheKey = `issue:${id}`\n const cached = issueCache.get(cacheKey)\n if (cached) {\n return cached\n }\n\n const issue = await linearProvider.fetchIssue(id)\n if (issue) {\n // Cache by both ID and externalId\n issueCache.set(`issue:${issue.id}`, issue)\n issueCache.set(`issue:${issue.externalId}`, issue)\n }\n\n return issue\n }\n\n /**\n * Create a new issue (invalidates assigned cache)\n */\n async createIssue(input: CreateIssueInput): Promise<Issue> {\n this.ensureInitialized()\n\n const issue = await linearProvider.createIssue(input)\n\n // Cache the new issue\n issueCache.set(`issue:${issue.id}`, issue)\n issueCache.set(`issue:${issue.externalId}`, issue)\n\n // Invalidate assigned issues cache (new issue may be assigned)\n assignedIssuesCache.clear()\n\n return issue\n }\n\n /**\n * Update an issue (invalidates cache for that issue)\n */\n async updateIssue(id: string, input: UpdateIssueInput): Promise<Issue> {\n this.ensureInitialized()\n\n const issue = await linearProvider.updateIssue(id, input)\n\n // Update cache\n issueCache.set(`issue:${issue.id}`, issue)\n issueCache.set(`issue:${issue.externalId}`, issue)\n\n return issue\n }\n\n /**\n * Mark issue as in progress (invalidates cache)\n */\n async markInProgress(id: string): Promise<void> {\n this.ensureInitialized()\n\n await linearProvider.markInProgress(id)\n\n // Invalidate caches\n issueCache.delete(`issue:${id}`)\n assignedIssuesCache.clear()\n }\n\n /**\n * Mark issue as done (invalidates cache)\n */\n async markDone(id: string): Promise<void> {\n this.ensureInitialized()\n\n await linearProvider.markDone(id)\n\n // Invalidate caches\n issueCache.delete(`issue:${id}`)\n assignedIssuesCache.clear()\n }\n\n /**\n * Add a comment to an issue\n */\n async addComment(id: string, body: string): Promise<void> {\n this.ensureInitialized()\n await linearProvider.addComment(id, body)\n }\n\n /**\n * Get available teams (cached)\n */\n async getTeams(): Promise<Array<{ id: string; name: string; key?: string }>> {\n this.ensureInitialized()\n\n const cached = teamsCache.get('teams')\n if (cached) {\n return cached\n }\n\n const teams = await linearProvider.getTeams()\n teamsCache.set('teams', teams)\n return teams\n }\n\n /**\n * Get available projects (cached)\n */\n async getProjects(): Promise<Array<{ id: string; name: string }>> {\n this.ensureInitialized()\n\n const cached = projectsCache.get('projects')\n if (cached) {\n return cached\n }\n\n const projects = await linearProvider.getProjects()\n projectsCache.set('projects', projects)\n return projects\n }\n\n /**\n * Clear all caches\n */\n clearCache(): void {\n clearLinearCache()\n }\n\n /**\n * Get cache statistics for debugging\n */\n getCacheStats() {\n return getLinearCacheStats()\n }\n\n /**\n * Ensure service is initialized\n */\n private ensureInitialized(): void {\n if (!this.initialized) {\n throw new Error(\n 'Linear service not initialized. Call linearService.initialize() first or run `p. linear setup`.'\n )\n }\n }\n}\n\n// Singleton instance\nexport const linearService = new LinearService()\n", "/**\n * Linear Sync Layer\n *\n * Bidirectional sync between Linear and local prjct storage.\n * Uses issues.json as local cache with 30-minute staleness.\n *\n * Architecture:\n * Linear (source of truth)\n * \u2195\n * Sync Layer (this file)\n * \u2195\n * storage/issues.json \u2190 FULL COPY of Linear issues\n * \u2195\n * state.json.currentTask.linearId \u2190 DIRECT LINK\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport {\n type CachedIssue,\n createEmptyIssues,\n type IssuesJson,\n parseIssues,\n type SyncResult,\n} from '../../schemas/issues'\nimport { getProjectPath } from '../../schemas/schemas'\nimport { getErrorMessage } from '../../types/fs'\nimport { fileExists } from '../../utils/file-helper'\nimport type { Issue } from '../issue-tracker/types'\nimport { linearService } from './service'\n\n// Default staleness threshold: 30 minutes\nconst DEFAULT_STALE_AFTER = 30 * 60 * 1000\n\nexport class LinearSync {\n /**\n * Pull all assigned issues from Linear and store in issues.json\n * This is the main sync operation - call on `p. sync`\n */\n async pullAll(projectId: string): Promise<SyncResult> {\n const storagePath = join(getProjectPath(projectId), 'storage')\n const issuesPath = join(storagePath, 'issues.json')\n\n // Ensure storage directory exists\n if (!(await fileExists(storagePath))) {\n await mkdir(storagePath, { recursive: true })\n }\n\n const timestamp = new Date().toISOString()\n const errors: Array<{ issueId: string; error: string }> = []\n\n try {\n // Fetch all assigned issues from Linear\n const issues = await linearService.fetchAssignedIssues({ limit: 100 })\n\n // Convert to cached format\n const issuesMap: Record<string, CachedIssue> = {}\n for (const issue of issues) {\n try {\n issuesMap[issue.externalId] = this.toCachedIssue(issue, timestamp)\n } catch (err) {\n errors.push({\n issueId: issue.externalId || issue.id,\n error: getErrorMessage(err),\n })\n }\n }\n\n // Write to issues.json\n const issuesJson: IssuesJson = {\n provider: 'linear',\n lastSync: timestamp,\n staleAfter: DEFAULT_STALE_AFTER,\n issues: issuesMap,\n }\n\n await writeFile(issuesPath, JSON.stringify(issuesJson, null, 2))\n\n return {\n provider: 'linear',\n fetched: issues.length,\n updated: Object.keys(issuesMap).length,\n errors,\n timestamp,\n }\n } catch (err) {\n errors.push({\n issueId: 'all',\n error: getErrorMessage(err),\n })\n return {\n provider: 'linear',\n fetched: 0,\n updated: 0,\n errors,\n timestamp,\n }\n }\n }\n\n /**\n * Get issue from local cache, fetch from API if not found or stale\n * Local-first approach for performance\n */\n async getIssue(projectId: string, identifier: string): Promise<CachedIssue | null> {\n const issuesJson = await this.loadIssues(projectId)\n\n // Check local cache first\n if (issuesJson?.issues[identifier]) {\n const cachedIssue = issuesJson.issues[identifier]\n\n // Check if cached issue is still fresh (within fetchedAt + some grace period)\n const fetchedAt = new Date(cachedIssue.fetchedAt).getTime()\n const now = Date.now()\n const issueStaleness = 10 * 60 * 1000 // 10 minutes for individual issues\n\n if (now - fetchedAt < issueStaleness) {\n return cachedIssue\n }\n }\n\n // Not in cache or stale - fetch from API and update cache\n try {\n const issue = await linearService.fetchIssue(identifier)\n if (!issue) return null\n\n const timestamp = new Date().toISOString()\n const cachedIssue = this.toCachedIssue(issue, timestamp)\n\n // Update cache with this single issue\n await this.updateIssueInCache(projectId, identifier, cachedIssue)\n\n return cachedIssue\n } catch {\n // API failed, return cached version if available (even if stale)\n if (issuesJson?.issues[identifier]) {\n return issuesJson.issues[identifier]\n }\n return null\n }\n }\n\n /**\n * Get issue from local cache ONLY (no API call)\n * Use for fast lookups when you know the issue should be cached\n */\n async getIssueLocal(projectId: string, identifier: string): Promise<CachedIssue | null> {\n const issuesJson = await this.loadIssues(projectId)\n return issuesJson?.issues[identifier] || null\n }\n\n /**\n * Push local status change to Linear\n * Called when task status changes (in_progress, done)\n */\n async pushStatus(\n projectId: string,\n identifier: string,\n status: 'in_progress' | 'done'\n ): Promise<void> {\n // Update Linear\n if (status === 'in_progress') {\n await linearService.markInProgress(identifier)\n } else if (status === 'done') {\n await linearService.markDone(identifier)\n }\n\n // Update local cache to reflect the change\n const issuesJson = await this.loadIssues(projectId)\n if (issuesJson?.issues[identifier]) {\n const cachedStatus = status === 'done' ? 'done' : 'in_progress'\n issuesJson.issues[identifier].status = cachedStatus\n issuesJson.issues[identifier].fetchedAt = new Date().toISOString()\n\n await this.saveIssues(projectId, issuesJson)\n }\n }\n\n /**\n * Check if the local issues cache is stale\n * Staleness = lastSync is older than staleAfter threshold\n */\n async isStale(projectId: string): Promise<boolean> {\n const issuesJson = await this.loadIssues(projectId)\n\n if (!issuesJson || !issuesJson.lastSync) {\n return true // No cache = stale\n }\n\n const lastSyncTime = new Date(issuesJson.lastSync).getTime()\n const now = Date.now()\n const staleAfter = issuesJson.staleAfter || DEFAULT_STALE_AFTER\n\n return now - lastSyncTime > staleAfter\n }\n\n /**\n * Get sync status for display\n */\n async getSyncStatus(projectId: string): Promise<{\n hasCache: boolean\n lastSync: string | null\n issueCount: number\n isStale: boolean\n }> {\n const issuesJson = await this.loadIssues(projectId)\n\n if (!issuesJson) {\n return {\n hasCache: false,\n lastSync: null,\n issueCount: 0,\n isStale: true,\n }\n }\n\n return {\n hasCache: true,\n lastSync: issuesJson.lastSync || null,\n issueCount: Object.keys(issuesJson.issues).length,\n isStale: await this.isStale(projectId),\n }\n }\n\n /**\n * List all cached issues\n */\n async listCachedIssues(projectId: string): Promise<CachedIssue[]> {\n const issuesJson = await this.loadIssues(projectId)\n if (!issuesJson) return []\n\n return Object.values(issuesJson.issues)\n }\n\n // =============================================================================\n // Private Helpers\n // =============================================================================\n\n /**\n * Load issues.json from disk\n */\n private async loadIssues(projectId: string): Promise<IssuesJson | null> {\n const issuesPath = join(getProjectPath(projectId), 'storage', 'issues.json')\n\n if (!(await fileExists(issuesPath))) {\n return null\n }\n\n try {\n const content = await readFile(issuesPath, 'utf-8')\n return parseIssues(JSON.parse(content))\n } catch {\n return null\n }\n }\n\n /**\n * Save issues.json to disk\n */\n private async saveIssues(projectId: string, issuesJson: IssuesJson): Promise<void> {\n const storagePath = join(getProjectPath(projectId), 'storage')\n const issuesPath = join(storagePath, 'issues.json')\n\n if (!(await fileExists(storagePath))) {\n await mkdir(storagePath, { recursive: true })\n }\n\n await writeFile(issuesPath, JSON.stringify(issuesJson, null, 2))\n }\n\n /**\n * Update a single issue in the cache\n */\n private async updateIssueInCache(\n projectId: string,\n identifier: string,\n issue: CachedIssue\n ): Promise<void> {\n let issuesJson = await this.loadIssues(projectId)\n\n if (!issuesJson) {\n issuesJson = createEmptyIssues('linear')\n }\n\n issuesJson.issues[identifier] = issue\n await this.saveIssues(projectId, issuesJson)\n }\n\n /**\n * Convert API Issue to CachedIssue format\n */\n private toCachedIssue(issue: Issue, timestamp: string): CachedIssue {\n return {\n id: issue.id,\n identifier: issue.externalId,\n title: issue.title,\n description: issue.description,\n status: issue.status,\n priority: issue.priority,\n type: issue.type,\n assignee: issue.assignee,\n labels: issue.labels,\n team: issue.team,\n project: issue.project,\n url: issue.url,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n fetchedAt: timestamp,\n }\n }\n}\n\n// Singleton instance\nexport const linearSync = new LinearSync()\n", "/**\n * Issues Schema\n *\n * Defines the structure for issues.json - local cache of issue tracker issues.\n * Used for bidirectional sync with Linear/JIRA/etc.\n *\n * Location: ~/.prjct-cli/projects/{projectId}/storage/issues.json\n */\n\nimport { z } from 'zod'\n\n// =============================================================================\n// Issue Provider Types\n// =============================================================================\n\nexport const IssueProviderSchema = z.enum(['linear', 'jira', 'github', 'monday', 'asana', 'none'])\nexport const IssueStatusSchema = z.enum([\n 'backlog',\n 'todo',\n 'in_progress',\n 'in_review',\n 'done',\n 'cancelled',\n])\nexport const IssuePrioritySchema = z.enum(['none', 'urgent', 'high', 'medium', 'low'])\nexport const IssueTypeSchema = z.enum(['feature', 'bug', 'improvement', 'task', 'chore', 'epic'])\n\n// =============================================================================\n// Cached Issue Schema\n// =============================================================================\n\n/**\n * Single cached issue from provider\n */\nexport const CachedIssueSchema = z.object({\n // Core identifiers\n id: z.string(), // Provider UUID\n identifier: z.string(), // Human-readable ID (e.g., \"PRJ-123\")\n\n // Issue content\n title: z.string(),\n description: z.string().optional(),\n\n // State\n status: IssueStatusSchema,\n priority: IssuePrioritySchema,\n type: IssueTypeSchema.optional(),\n\n // Metadata\n assignee: z\n .object({\n id: z.string(),\n name: z.string(),\n email: z.string().optional(),\n })\n .optional(),\n labels: z.array(z.string()).default([]),\n team: z\n .object({\n id: z.string(),\n name: z.string(),\n key: z.string().optional(),\n })\n .optional(),\n project: z\n .object({\n id: z.string(),\n name: z.string(),\n })\n .optional(),\n\n // URLs and timestamps\n url: z.string(),\n createdAt: z.string(), // ISO8601 from provider\n updatedAt: z.string(), // ISO8601 from provider\n fetchedAt: z.string(), // ISO8601 when we cached it\n})\n\n// =============================================================================\n// Issues JSON Schema (Root)\n// =============================================================================\n\n/**\n * Root schema for issues.json\n * Maps identifier -> CachedIssue for quick lookup\n */\nexport const IssuesJsonSchema = z.object({\n // Provider info\n provider: IssueProviderSchema,\n\n // Sync metadata\n lastSync: z.string(), // ISO8601 of last full sync\n staleAfter: z.number().default(1800000), // 30 minutes in ms\n\n // Issues map: identifier -> issue\n issues: z.record(z.string(), CachedIssueSchema),\n})\n\n// =============================================================================\n// Sync Result Schema\n// =============================================================================\n\nexport const SyncResultSchema = z.object({\n provider: IssueProviderSchema,\n fetched: z.number(),\n updated: z.number(),\n errors: z.array(\n z.object({\n issueId: z.string(),\n error: z.string(),\n })\n ),\n timestamp: z.string(),\n})\n\n// =============================================================================\n// Inferred Types\n// =============================================================================\n\nexport type IssueProvider = z.infer<typeof IssueProviderSchema>\nexport type IssueStatus = z.infer<typeof IssueStatusSchema>\nexport type IssuePriority = z.infer<typeof IssuePrioritySchema>\nexport type IssueType = z.infer<typeof IssueTypeSchema>\nexport type CachedIssue = z.infer<typeof CachedIssueSchema>\nexport type IssuesJson = z.infer<typeof IssuesJsonSchema>\nexport type SyncResult = z.infer<typeof SyncResultSchema>\n\n// =============================================================================\n// Validation Helpers\n// =============================================================================\n\n/** Parse and validate issues.json content */\nexport const parseIssues = (data: unknown): IssuesJson => IssuesJsonSchema.parse(data)\n\n/** Safe parse with error result */\nexport const safeParseIssues = (data: unknown) => IssuesJsonSchema.safeParse(data)\n\n// =============================================================================\n// Defaults\n// =============================================================================\n\nexport const DEFAULT_ISSUES: IssuesJson = {\n provider: 'none',\n lastSync: '',\n staleAfter: 1800000, // 30 minutes\n issues: {},\n}\n\n/**\n * Create empty issues.json for a provider\n */\nexport function createEmptyIssues(provider: IssueProvider): IssuesJson {\n return {\n provider,\n lastSync: '',\n staleAfter: 1800000,\n issues: {},\n }\n}\n", "/**\n * Schemas Utilities\n *\n * ID generators and path helpers for project data.\n */\n\nimport crypto from 'node:crypto'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\n// ============================================\n// ID GENERATORS - UUID ONLY\n// ============================================\n\n/**\n * Generate a standard UUID.\n * All IDs in the system use this format for PostgreSQL consistency.\n */\nexport function generateUUID(): string {\n return crypto.randomUUID()\n}\n\n// All use the same UUID generator\nexport const generateTaskId = generateUUID\nexport const generateFeatureId = generateUUID\nexport const generateIdeaId = generateUUID\nexport const generateShipId = generateUUID\nexport const generateSessionId = generateUUID\n\n// ============================================\n// PATH HELPERS\n// ============================================\n\nexport const GLOBAL_STORAGE = join(homedir(), '.prjct-cli', 'projects')\n\nexport function getProjectPath(projectId: string): string {\n return join(GLOBAL_STORAGE, projectId)\n}\n", "import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { safeRead, type ValidationSchema } from '../storage/safe-reader'\nimport { isNotFoundError } from '../types/fs'\n\n/**\n * File Helper - Centralized file operations with error handling\n *\n * Eliminates duplicated fs operations across:\n * - 101 fs.readFile/writeFile calls in 18 files\n * - Consistent error handling\n * - JSON read/write patterns\n */\n\ninterface ListFilesOptions {\n filesOnly?: boolean\n dirsOnly?: boolean\n extension?: string\n}\n\n/**\n * Read JSON file and parse.\n * When a Zod schema is provided, validates the data and creates a .backup on corruption.\n */\nexport async function readJson<T = unknown>(\n filePath: string,\n defaultValue: T | null = null,\n schema?: ValidationSchema\n): Promise<T | null> {\n if (schema) {\n const data = await safeRead<T>(filePath, schema)\n return data ?? defaultValue\n }\n\n try {\n const content = await fs.readFile(filePath, 'utf-8')\n return JSON.parse(content) as T\n } catch (error) {\n if (isNotFoundError(error)) {\n return defaultValue\n }\n throw error\n }\n}\n\n/**\n * Write object to JSON file (pretty-printed)\n */\nexport async function writeJson(filePath: string, data: unknown, indent = 2): Promise<void> {\n const content = JSON.stringify(data, null, indent)\n await fs.writeFile(filePath, content, 'utf-8')\n}\n\n/**\n * Read text file\n */\nexport async function readFile(filePath: string, defaultValue = ''): Promise<string> {\n try {\n return await fs.readFile(filePath, 'utf-8')\n } catch (error) {\n if (isNotFoundError(error)) {\n return defaultValue\n }\n throw error\n }\n}\n\n/**\n * Write text file\n */\nexport async function writeFile(filePath: string, content: string): Promise<void> {\n const dir = path.dirname(filePath)\n await fs.mkdir(dir, { recursive: true })\n await fs.writeFile(filePath, content, 'utf-8')\n}\n\n/**\n * Atomic write - writes to temp file then renames (prevents partial writes)\n */\nexport async function atomicWrite(filePath: string, content: string): Promise<void> {\n const dir = path.dirname(filePath)\n await fs.mkdir(dir, { recursive: true })\n const tempPath = `${filePath}.${Date.now()}.tmp`\n await fs.writeFile(tempPath, content, 'utf-8')\n await fs.rename(tempPath, filePath)\n}\n\n/**\n * Append to text file\n */\nexport async function appendToFile(filePath: string, content: string): Promise<void> {\n await fs.appendFile(filePath, content, 'utf-8')\n}\n\n/**\n * Append a single line to file (with newline and directory creation)\n */\nexport async function appendLine(filePath: string, line: string): Promise<void> {\n const dir = path.dirname(filePath)\n await fs.mkdir(dir, { recursive: true })\n await fs.appendFile(filePath, `${line}\\n`, 'utf-8')\n}\n\n/**\n * Prepend to text file (adds content at beginning)\n */\nexport async function prependToFile(filePath: string, content: string): Promise<void> {\n try {\n const existing = await fs.readFile(filePath, 'utf-8')\n await fs.writeFile(filePath, content + existing, 'utf-8')\n } catch (error) {\n if (isNotFoundError(error)) {\n await fs.writeFile(filePath, content, 'utf-8')\n } else {\n throw error\n }\n }\n}\n\n/**\n * Check if file exists\n */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath)\n return true\n } catch (error) {\n if (isNotFoundError(error)) {\n return false\n }\n throw error\n }\n}\n\n/**\n * Check if directory exists\n */\nexport async function dirExists(dirPath: string): Promise<boolean> {\n try {\n const stats = await fs.stat(dirPath)\n return stats.isDirectory()\n } catch (error) {\n if (isNotFoundError(error)) {\n return false\n }\n throw error\n }\n}\n\n/**\n * Ensure directory exists (create if not)\n */\nexport async function ensureDir(dirPath: string): Promise<void> {\n await fs.mkdir(dirPath, { recursive: true })\n}\n\n/**\n * Delete file if it exists\n */\nexport async function deleteFile(filePath: string): Promise<boolean> {\n try {\n await fs.unlink(filePath)\n return true\n } catch (error) {\n if (isNotFoundError(error)) {\n return false // File didn't exist\n }\n throw error\n }\n}\n\n/**\n * Delete directory and all contents\n */\nexport async function deleteDir(dirPath: string): Promise<boolean> {\n try {\n await fs.rm(dirPath, { recursive: true, force: true })\n return true\n } catch (error) {\n if (isNotFoundError(error)) {\n return false\n }\n throw error\n }\n}\n\n/**\n * List files in directory\n */\nexport async function listFiles(\n dirPath: string,\n options: ListFilesOptions = {}\n): Promise<string[]> {\n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true })\n let files = entries\n\n if (options.filesOnly) {\n files = files.filter((entry) => entry.isFile())\n }\n\n if (options.dirsOnly) {\n files = files.filter((entry) => entry.isDirectory())\n }\n\n if (options.extension) {\n files = files.filter((entry) => entry.name.endsWith(options.extension!))\n }\n\n return files.map((entry) => entry.name)\n } catch (error) {\n if (isNotFoundError(error)) {\n return []\n }\n throw error\n }\n}\n\n/**\n * Get file size in bytes\n */\nexport async function getFileSize(filePath: string): Promise<number> {\n const stats = await fs.stat(filePath)\n return stats.size\n}\n\n/**\n * Get file modification time\n */\nexport async function getFileModifiedTime(filePath: string): Promise<Date> {\n const stats = await fs.stat(filePath)\n return stats.mtime\n}\n\n/**\n * Copy file\n */\nexport async function copyFile(sourcePath: string, destPath: string): Promise<void> {\n await fs.copyFile(sourcePath, destPath)\n}\n\n/**\n * Move/rename file\n */\nexport async function moveFile(oldPath: string, newPath: string): Promise<void> {\n await fs.rename(oldPath, newPath)\n}\n\n/**\n * Read file and split into lines\n */\nexport async function readLines(filePath: string): Promise<string[]> {\n const content = await readFile(filePath, '')\n return content.split('\\n')\n}\n\n/**\n * Write lines to file\n */\nexport async function writeLines(filePath: string, lines: string[]): Promise<void> {\n const content = lines.join('\\n')\n await writeFile(filePath, content)\n}\n\n/**\n * Get file extension\n */\nexport function getFileExtension(filePath: string): string {\n return path.extname(filePath)\n}\n\n/**\n * Get filename without extension\n */\nexport function getFileNameWithoutExtension(filePath: string): string {\n return path.basename(filePath, path.extname(filePath))\n}\n", "/**\n * Safe Reader\n *\n * Wraps JSON.parse + Zod schema.safeParse() for validated storage reads.\n * On corruption: logs warning, creates .backup, returns null.\n *\n * Uses schema for structural validation only \u2014 returns the raw parsed\n * data (not Zod-transformed) to preserve extra fields that may exist\n * in storage files but aren't in the schema yet (forward compatibility).\n *\n * @version 1.0.0\n */\n\nimport fs from 'node:fs/promises'\nimport type { ZodError } from 'zod'\nimport { isNotFoundError } from '../types/fs'\n\n/**\n * Minimal interface for Zod-like validation.\n * Decoupled from Zod generics to avoid strict type parameter matching.\n */\nexport interface ValidationSchema {\n safeParse(data: unknown): { success: boolean; error?: ZodError }\n}\n\n/**\n * Read and validate a JSON file against a Zod schema.\n *\n * Flow:\n * 1. Read file \u2192 if missing, return null\n * 2. JSON.parse \u2192 if malformed JSON, backup + return null\n * 3. schema.safeParse \u2192 if valid, return raw parsed data as T\n * 4. If invalid but parseable JSON, backup + return null\n *\n * Returns raw parsed JSON (not Zod-transformed) to preserve extra fields.\n *\n * @returns Validated data or null if file is missing/corrupted\n */\nexport async function safeRead<T>(filePath: string, schema: ValidationSchema): Promise<T | null> {\n let content: string\n\n // Step 1: Read file\n try {\n content = await fs.readFile(filePath, 'utf-8')\n } catch (error) {\n if (isNotFoundError(error)) {\n return null\n }\n throw error\n }\n\n // Step 2: JSON.parse\n let raw: unknown\n try {\n raw = JSON.parse(content)\n } catch {\n // Malformed JSON \u2014 backup and return null\n await createBackup(filePath, content)\n logCorruption(filePath, 'Malformed JSON')\n return null\n }\n\n // Step 3: Validate against schema\n const result = schema.safeParse(raw)\n if (result.success) {\n // Return raw data to preserve extra fields not in schema\n return raw as T\n }\n\n // Step 4: Validation failed \u2014 backup and return null\n await createBackup(filePath, content)\n logCorruption(filePath, formatZodError(result.error!))\n return null\n}\n\n/**\n * Create a .backup of a corrupted file\n */\nasync function createBackup(filePath: string, content: string): Promise<void> {\n const backupPath = `${filePath}.backup`\n try {\n await fs.writeFile(backupPath, content, 'utf-8')\n } catch {\n // Best-effort backup \u2014 don't throw if it fails\n }\n}\n\n/**\n * Log corruption warning to stderr\n */\nfunction logCorruption(filePath: string, reason: string): void {\n console.error(`[prjct] Warning: Corrupted storage file: ${filePath}`)\n console.error(`[prjct] Reason: ${reason}`)\n console.error(`[prjct] A .backup file has been created. Returning defaults.`)\n}\n\n/**\n * Format Zod error into a readable string\n */\nfunction formatZodError(error: ZodError): string {\n return error.issues\n .slice(0, 3)\n .map((issue) => `${issue.path.join('.')}: ${issue.message}`)\n .join('; ')\n}\n", "#!/usr/bin/env bun\n\n/**\n * Linear CLI - Bridge between templates and SDK\n *\n * Usage: bun core/cli/linear.ts --project <projectId> <command> [args...]\n *\n * Flags:\n * --project <id> - Project ID (required)\n * --json - Output raw JSON (default: human-readable)\n * --verbose - Show all details (no truncation)\n *\n * Commands:\n * setup <apiKey> [teamId] - Store API key in project credentials\n * list - List my assigned issues\n * list-team <teamId> - List issues from a team\n * get <id> - Get issue by ID or identifier (PRJ-123)\n * get-local <id> - Get issue from local cache (no API call)\n * sync - Pull all assigned issues to local storage\n * sync-status - Check local cache status\n * create <json> - Create issue from JSON input\n * update <id> <json> - Update issue\n * start <id> - Mark issue as in progress\n * done <id> - Mark issue as done\n * comment <id> <text> - Add comment to issue\n * teams - List available teams\n * projects - List available projects\n * status - Check connection status\n *\n * Default output is human-readable. Use --json for machine parsing.\n */\n\nimport type { CreateIssueInput, Issue } from '../integrations/issue-tracker/types'\nimport { linearService, linearSync } from '../integrations/linear'\nimport { getErrorMessage } from '../types/fs'\nimport { formatForHuman, setOutputTier } from '../utils/output'\nimport {\n getCredentialSource,\n getLinearApiKey,\n getProjectCredentials,\n setLinearCredentials,\n} from '../utils/project-credentials'\n\n// Parse arguments\nconst args = process.argv.slice(2)\n\n// Extract --project flag\nconst projectIdx = args.indexOf('--project')\nlet projectId: string | null = null\nif (projectIdx !== -1 && args[projectIdx + 1]) {\n projectId = args[projectIdx + 1]\n args.splice(projectIdx, 2)\n}\n\n// Extract --json flag (raw JSON output)\nconst jsonIdx = args.indexOf('--json')\nconst jsonMode = jsonIdx !== -1\nif (jsonMode) args.splice(jsonIdx, 1)\n\n// Extract --verbose flag\nconst verboseIdx = args.indexOf('--verbose')\nconst verboseMode = verboseIdx !== -1\nif (verboseMode) {\n args.splice(verboseIdx, 1)\n setOutputTier('verbose')\n}\n\nconst [command, ...commandArgs] = args\n\n/**\n * Output result - human-readable by default, JSON with --json flag\n */\nfunction output(data: unknown): void {\n if (jsonMode) {\n console.log(JSON.stringify(data, null, 2))\n } else {\n console.log(formatForHuman(data))\n }\n}\n\n/**\n * Output error and exit\n */\nfunction error(message: string, code = 1): never {\n if (jsonMode) {\n console.error(JSON.stringify({ error: message }))\n } else {\n console.error(`Error: ${message}`)\n }\n process.exit(code)\n}\n\n/**\n * Initialize Linear service from project credentials\n */\nasync function initFromProject(): Promise<void> {\n if (!projectId) {\n error('No --project specified. Usage: linear.ts --project <projectId> <command>')\n }\n\n const apiKey = await getLinearApiKey(projectId)\n if (!apiKey) {\n error('Linear not configured. Run: p. linear setup')\n }\n\n const creds = await getProjectCredentials(projectId)\n await linearService.initializeFromApiKey(apiKey, creds.linear?.teamId)\n}\n\n/**\n * Main CLI handler\n */\nasync function main(): Promise<void> {\n try {\n switch (command) {\n case 'setup': {\n if (!projectId) {\n error('--project required for setup')\n }\n\n const apiKey = commandArgs[0]\n const teamId = commandArgs[1] // optional\n\n if (!apiKey) {\n error('API key required. Usage: setup <apiKey> [teamId]')\n }\n\n // Test connection first\n await linearService.initializeFromApiKey(apiKey, teamId)\n const teams = await linearService.getTeams()\n\n if (teams.length === 0) {\n error('No teams found. Check your API key permissions.')\n }\n\n // Determine default team\n let selectedTeamId = teamId\n let selectedTeamKey: string | undefined\n\n if (!selectedTeamId && teams.length === 1) {\n selectedTeamId = teams[0].id\n selectedTeamKey = teams[0].key\n } else if (selectedTeamId) {\n const team = teams.find((t) => t.id === selectedTeamId || t.key === selectedTeamId)\n if (team) {\n selectedTeamId = team.id\n selectedTeamKey = team.key\n }\n }\n\n // Store in project credentials\n await setLinearCredentials(projectId, {\n apiKey,\n teamId: selectedTeamId,\n teamKey: selectedTeamKey,\n setupAt: new Date().toISOString(),\n })\n\n output({\n success: true,\n teams,\n defaultTeam: selectedTeamId ? { id: selectedTeamId, key: selectedTeamKey } : null,\n })\n break\n }\n\n case 'list': {\n await initFromProject()\n const limit = commandArgs[0] ? parseInt(commandArgs[0], 10) : 20\n\n // Use team issues if teamId is configured, otherwise assigned issues\n const creds = await getProjectCredentials(projectId!)\n let issues: Issue[]\n if (creds.linear?.teamId) {\n issues = await linearService.fetchTeamIssues(creds.linear.teamId, { limit })\n } else {\n issues = await linearService.fetchAssignedIssues({ limit })\n }\n\n const issueList = issues.map((issue) => ({\n id: issue.id,\n identifier: issue.externalId,\n title: issue.title,\n status: issue.status,\n priority: issue.priority,\n url: issue.url,\n }))\n\n if (jsonMode) {\n output({ count: issues.length, issues: issueList })\n } else {\n // Human-friendly table output\n console.log(`Your issues (${issues.length}):`)\n for (const issue of issueList.slice(0, 10)) {\n const p = issue.priority && issue.priority !== 'none' ? ` [${issue.priority}]` : ''\n console.log(` ${issue.identifier} ${issue.title.slice(0, 50)}${p}`)\n }\n if (issues.length > 10) {\n console.log(` ...${issues.length - 10} more`)\n }\n }\n break\n }\n\n case 'list-team': {\n await initFromProject()\n const teamId = commandArgs[0]\n const limit = commandArgs[1] ? parseInt(commandArgs[1], 10) : 20\n\n if (!teamId) {\n error('Team ID required. Usage: list-team <teamId> [limit]')\n }\n\n const issues = await linearService.fetchTeamIssues(teamId, { limit })\n output({\n count: issues.length,\n issues: issues.map((issue) => ({\n id: issue.id,\n identifier: issue.externalId,\n title: issue.title,\n status: issue.status,\n priority: issue.priority,\n url: issue.url,\n })),\n })\n break\n }\n\n case 'get': {\n await initFromProject()\n const id = commandArgs[0]\n\n if (!id) {\n error('Issue ID required. Usage: get <id>')\n }\n\n const issue = await linearService.fetchIssue(id)\n if (!issue) {\n error(`Issue not found: ${id}`)\n }\n\n if (jsonMode) {\n output(issue)\n } else {\n // Human-friendly issue display\n console.log(`${issue.externalId}: ${issue.title}`)\n console.log(`Status: ${issue.status} | Priority: ${issue.priority || 'none'}`)\n if (issue.description) {\n const desc = issue.description.slice(0, 200)\n console.log(`\\n${desc}${issue.description.length > 200 ? '...' : ''}`)\n }\n console.log(`\\n${issue.url}`)\n }\n break\n }\n\n case 'get-local': {\n if (!projectId) {\n error('--project required for get-local')\n }\n\n const id = commandArgs[0]\n if (!id) {\n error('Issue ID required. Usage: get-local <id>')\n }\n\n const cachedIssue = await linearSync.getIssueLocal(projectId, id)\n if (!cachedIssue) {\n error(`Issue not in local cache: ${id}. Run 'sync' first.`)\n }\n\n output(cachedIssue)\n break\n }\n\n case 'sync': {\n if (!projectId) {\n error('--project required for sync')\n }\n\n await initFromProject()\n const result = await linearSync.pullAll(projectId)\n\n output({\n success: result.errors.length === 0,\n ...result,\n })\n break\n }\n\n case 'sync-status': {\n if (!projectId) {\n error('--project required for sync-status')\n }\n\n const status = await linearSync.getSyncStatus(projectId)\n output(status)\n break\n }\n\n case 'create': {\n await initFromProject()\n const inputJson = commandArgs[0]\n\n if (!inputJson) {\n error('JSON input required. Usage: create \\'{\"title\":\"...\", \"teamId\":\"...\"}\\'')\n }\n\n let input: Record<string, unknown>\n try {\n input = JSON.parse(inputJson)\n } catch {\n error(`Invalid JSON: ${inputJson}`)\n }\n\n if (!input.title) {\n error('title is required')\n }\n if (!input.teamId) {\n // Try to use default team from credentials\n const creds = await getProjectCredentials(projectId!)\n if (creds.linear?.teamId) {\n input.teamId = creds.linear.teamId\n } else {\n error('teamId is required (no default team configured)')\n }\n }\n\n const issue = await linearService.createIssue(input as unknown as CreateIssueInput)\n output(issue)\n break\n }\n\n case 'update': {\n await initFromProject()\n const id = commandArgs[0]\n const inputJson = commandArgs[1]\n\n if (!id) {\n error('Issue ID required. Usage: update <id> \\'{\"description\":\"...\"}\\'')\n }\n if (!inputJson) {\n error('JSON input required. Usage: update <id> \\'{\"description\":\"...\"}\\'')\n }\n\n let input: Record<string, unknown>\n try {\n input = JSON.parse(inputJson)\n } catch {\n error(`Invalid JSON: ${inputJson}`)\n }\n\n const issue = await linearService.updateIssue(id, input)\n output(issue)\n break\n }\n\n case 'start': {\n await initFromProject()\n const id = commandArgs[0]\n\n if (!id) {\n error('Issue ID required. Usage: start <id>')\n }\n\n await linearService.markInProgress(id)\n output({ success: true, id, status: 'in_progress' })\n break\n }\n\n case 'done': {\n await initFromProject()\n const id = commandArgs[0]\n\n if (!id) {\n error('Issue ID required. Usage: done <id>')\n }\n\n await linearService.markDone(id)\n output({ success: true, id, status: 'done' })\n break\n }\n\n case 'comment': {\n await initFromProject()\n const id = commandArgs[0]\n const body = commandArgs.slice(1).join(' ')\n\n if (!id) {\n error('Issue ID required. Usage: comment <id> <text>')\n }\n if (!body) {\n error('Comment text required. Usage: comment <id> <text>')\n }\n\n await linearService.addComment(id, body)\n output({ success: true, id })\n break\n }\n\n case 'teams': {\n await initFromProject()\n const teams = await linearService.getTeams()\n output({ count: teams.length, teams })\n break\n }\n\n case 'projects': {\n await initFromProject()\n const projects = await linearService.getProjects()\n output({ count: projects.length, projects })\n break\n }\n\n case 'status': {\n if (!projectId) {\n error('--project required for status')\n }\n\n const source = await getCredentialSource(projectId)\n const apiKey = await getLinearApiKey(projectId)\n const creds = await getProjectCredentials(projectId)\n\n if (!apiKey) {\n if (jsonMode) {\n output({ configured: false, source: 'none', message: 'Linear not configured' })\n } else {\n console.log('Linear: Not configured')\n console.log('Run: p. linear setup')\n }\n break\n }\n\n // Test connection\n try {\n await linearService.initializeFromApiKey(apiKey, creds.linear?.teamId)\n const teams = await linearService.getTeams()\n\n if (jsonMode) {\n output({\n configured: true,\n source,\n teamId: creds.linear?.teamId,\n teamKey: creds.linear?.teamKey,\n teamsAvailable: teams.length,\n })\n } else {\n console.log(`Linear: Connected`)\n if (creds.linear?.teamKey) {\n console.log(`Team: ${creds.linear.teamKey}`)\n }\n console.log(`Teams: ${teams.length} available`)\n }\n } catch (err) {\n if (jsonMode) {\n output({ configured: true, source, connectionError: getErrorMessage(err) })\n } else {\n console.log(`Linear: Connection error`)\n console.log(`Error: ${getErrorMessage(err)}`)\n }\n }\n break\n }\n\n case 'help':\n case '--help':\n case '-h':\n case undefined: {\n output({\n usage: 'linear.ts --project <projectId> <command> [args...]',\n commands: {\n setup: 'setup <apiKey> [teamId] - Store API key',\n list: 'list [limit] - List my assigned issues',\n 'list-team': 'list-team <teamId> [limit] - List team issues',\n get: 'get <id> - Get issue by ID or identifier',\n 'get-local': 'get-local <id> - Get from local cache (no API)',\n sync: 'sync - Pull all assigned issues to local storage',\n 'sync-status': 'sync-status - Check local cache status',\n create: 'create <json> - Create issue',\n update: 'update <id> <json> - Update issue',\n start: 'start <id> - Mark as in progress',\n done: 'done <id> - Mark as done',\n comment: 'comment <id> <text> - Add comment',\n teams: 'teams - List available teams',\n projects: 'projects - List available projects',\n status: 'status - Check connection',\n },\n })\n break\n }\n\n default:\n error(`Unknown command: ${command}. Use --help to see available commands.`)\n }\n } catch (err) {\n error(getErrorMessage(err))\n }\n}\n\nmain()\n", "/**\n * Unified Output System for prjct-cli\n * Spinner while working \u2192 Single line result\n * With prjct branding\n *\n * Supports --quiet mode for CI/CD and scripting\n * Supports output tiers: silent, minimal, compact, verbose\n *\n * @see PRJ-105, PRJ-130\n */\n\nimport chalk from 'chalk'\nimport type { ErrorCode, ErrorWithHint } from '../types/errors'\nimport type { Output, OutputMetrics, OutputTier, TierConfig } from '../types/output'\nimport branding from './branding'\nimport { OUTPUT_LIMITS } from './constants'\nimport { getError } from './error-messages'\n\nconst _FRAMES = branding.spinner.frames\nconst SPEED = branding.spinner.speed\n\nexport type { Output, OutputMetrics, OutputTier, TierConfig } from '../types/output'\n\nexport const OUTPUT_TIERS: Record<OutputTier, TierConfig> = {\n silent: { maxLines: 0, maxCharsPerLine: 0, showMetrics: false },\n minimal: { maxLines: 1, maxCharsPerLine: 65, showMetrics: false },\n compact: { maxLines: 4, maxCharsPerLine: 80, showMetrics: true },\n verbose: { maxLines: Infinity, maxCharsPerLine: Infinity, showMetrics: true },\n} as const\n\n// Current output tier (default: compact for human-readable output)\nlet currentTier: OutputTier = 'compact'\n\n/**\n * Set the output tier\n */\nexport function setOutputTier(tier: OutputTier): void {\n currentTier = tier\n}\n\n/**\n * Get current output tier\n */\nexport function getOutputTier(): OutputTier {\n return currentTier\n}\n\n/**\n * Get current tier config\n */\nexport function getTierConfig(): TierConfig {\n return OUTPUT_TIERS[currentTier]\n}\n\n/**\n * Centralized icons for consistent output\n */\nexport const ICONS = {\n success: chalk.green('\u2713'),\n fail: chalk.red('\u2717'),\n warn: chalk.yellow('\u26A0'),\n info: chalk.blue('\u2139'),\n debug: chalk.dim('\uD83D\uDD27'),\n bullet: chalk.dim('\u2022'),\n arrow: chalk.dim('\u2192'),\n check: chalk.green('\u2713'),\n cross: chalk.red('\u2717'),\n spinner: chalk.cyan('\u25D0'),\n} as const\n\nlet interval: ReturnType<typeof setInterval> | null = null\nlet frame = 0\n\n// Quiet mode - suppress all stdout except errors\nlet quietMode = false\n\n/**\n * Enable quiet mode (no stdout, only stderr for errors)\n */\nexport function setQuietMode(enabled: boolean): void {\n quietMode = enabled\n}\n\n/**\n * Check if quiet mode is enabled\n */\nexport function isQuietMode(): boolean {\n return quietMode\n}\n\n/**\n * Truncate string to max chars (uses tier config if no max specified)\n */\nconst truncate = (s: string | undefined | null, max?: number): string => {\n const limit = max ?? (getTierConfig().maxCharsPerLine || OUTPUT_LIMITS.FALLBACK_TRUNCATE)\n return s && s.length > limit ? `${s.slice(0, limit - 1)}\u2026` : s || ''\n}\n\n/**\n * Limit output to maxLines (respects tier config)\n * Returns truncated content with \"...N more lines\" indicator\n */\n/**\n * Limit output to maxLines (respects tier config)\n * Returns truncated content with \"...N more lines\" indicator\n */\nexport function limitLines(content: string, maxLines?: number): string {\n const limit = maxLines ?? getTierConfig().maxLines\n if (limit === Infinity || limit === 0) return content\n\n const lines = content.split('\\n')\n if (lines.length <= limit) return content\n\n const shown = lines.slice(0, limit)\n const remaining = lines.length - limit\n return `${shown.join('\\n')}\\n${chalk.dim(`...${remaining} more lines`)}`\n}\n\n/**\n * Format data for human-readable output (respects tier)\n * Use this instead of JSON.stringify for CLI output\n */\nexport function formatForHuman(data: unknown): string {\n const tier = getTierConfig()\n\n if (currentTier === 'silent') return ''\n if (currentTier === 'verbose') return JSON.stringify(data, null, 2)\n\n // For minimal/compact: extract key info\n if (typeof data !== 'object' || data === null) {\n return truncate(String(data), tier.maxCharsPerLine)\n }\n\n const obj = data as Record<string, unknown>\n\n // Linear issue format\n if ('identifier' in obj && 'title' in obj) {\n const lines: string[] = []\n lines.push(`${obj.identifier}: ${truncate(String(obj.title), tier.maxCharsPerLine - 10)}`)\n if (obj.status) lines.push(`Status: ${obj.status}`)\n if (obj.priority && obj.priority !== 'none') lines.push(`Priority: ${obj.priority}`)\n if (obj.url && currentTier === 'compact') lines.push(chalk.dim(String(obj.url)))\n return limitLines(lines.join('\\n'), tier.maxLines)\n }\n\n // Issue list format\n if ('issues' in obj && Array.isArray(obj.issues)) {\n const issues = obj.issues as Array<Record<string, unknown>>\n const lines = issues.slice(0, tier.maxLines).map((i) => {\n const priority = i.priority && i.priority !== 'none' ? ` [${i.priority}]` : ''\n return `${i.identifier} ${truncate(String(i.title), OUTPUT_LIMITS.ISSUE_TITLE)}${priority}`\n })\n if (issues.length > tier.maxLines) {\n lines.push(chalk.dim(`...${issues.length - tier.maxLines} more`))\n }\n return lines.join('\\n')\n }\n\n // Generic object: show key fields only\n const keyFields = ['id', 'name', 'title', 'status', 'message', 'success', 'error']\n const relevant = keyFields.filter((k) => k in obj)\n if (relevant.length > 0) {\n return limitLines(\n relevant\n .map((k) => `${k}: ${truncate(String(obj[k]), tier.maxCharsPerLine - k.length - 2)}`)\n .join('\\n'),\n tier.maxLines\n )\n }\n\n // Fallback: compact JSON\n return limitLines(JSON.stringify(data, null, 2), tier.maxLines)\n}\n\nconst clear = (): boolean =>\n process.stdout.isTTY ? process.stdout.write(`\\r${' '.repeat(OUTPUT_LIMITS.CLEAR_WIDTH)}\\r`) : true\n\nconst out: Output = {\n // Branding: Show header at start\n start() {\n if (!quietMode) console.log(branding.cli.header())\n return this\n },\n\n // Branding: Show footer at end\n end() {\n if (!quietMode) console.log(branding.cli.footer())\n return this\n },\n\n // Branded spinner: prjct message...\n // In non-TTY (CI, Claude Code), prints a static line instead of animating\n spin(msg: string) {\n if (quietMode) return this\n this.stop()\n if (!process.stdout.isTTY) {\n process.stdout.write(`${branding.cli.spin(0, truncate(msg, OUTPUT_LIMITS.SPINNER_MSG))}\\n`)\n return this\n }\n interval = setInterval(() => {\n process.stdout.write(\n `\\r${branding.cli.spin(frame++, truncate(msg, OUTPUT_LIMITS.SPINNER_MSG))}`\n )\n }, SPEED)\n return this\n },\n\n done(msg: string, metrics?: OutputMetrics) {\n this.stop()\n if (!quietMode) {\n // Build metrics suffix if provided: [2a | 97% | 45K]\n let suffix = ''\n if (metrics) {\n const parts: string[] = []\n if (metrics.agents !== undefined) parts.push(`${metrics.agents}a`)\n if (metrics.reduction !== undefined) parts.push(`${metrics.reduction}%`)\n if (metrics.tokens !== undefined) parts.push(`${Math.round(metrics.tokens)}K`)\n if (parts.length > 0) {\n suffix = chalk.dim(` [${parts.join(' | ')}]`)\n }\n }\n console.log(`${ICONS.success} ${truncate(msg, OUTPUT_LIMITS.DONE_MSG)}${suffix}`)\n }\n return this\n },\n\n // Errors go to stderr even in quiet mode\n fail(msg: string) {\n this.stop()\n console.error(`${ICONS.fail} ${truncate(msg, OUTPUT_LIMITS.FAIL_MSG)}`)\n return this\n },\n\n // Rich error with context and recovery hint\n failWithHint(error: ErrorWithHint | ErrorCode) {\n this.stop()\n const err = typeof error === 'string' ? getError(error as ErrorCode) : error\n console.error()\n console.error(`${ICONS.fail} ${err.message}`)\n if (err.file) {\n console.error(chalk.dim(` File: ${err.file}`))\n }\n if (err.hint) {\n console.error(chalk.yellow(` \uD83D\uDCA1 ${err.hint}`))\n }\n if (err.docs) {\n console.error(chalk.dim(` Docs: ${err.docs}`))\n }\n console.error()\n return this\n },\n\n warn(msg: string) {\n this.stop()\n if (!quietMode) console.log(`${ICONS.warn} ${truncate(msg, OUTPUT_LIMITS.WARN_MSG)}`)\n return this\n },\n\n // Informational message\n info(msg: string) {\n this.stop()\n if (!quietMode) console.log(`${ICONS.info} ${msg}`)\n return this\n },\n\n // Debug message (only if DEBUG=1 or DEBUG=true)\n debug(msg: string) {\n this.stop()\n // DEBUG: Enable debug output (values: '1' or 'true')\n const debugEnabled = process.env.DEBUG === '1' || process.env.DEBUG === 'true'\n if (!quietMode && debugEnabled) {\n console.log(`${ICONS.debug} ${chalk.dim(msg)}`)\n }\n return this\n },\n\n // Alias for done - explicit success indicator\n success(msg: string, metrics?: OutputMetrics) {\n return this.done(msg, metrics)\n },\n\n // Bulleted list\n list(items: string[], options: { bullet?: string; indent?: number } = {}) {\n this.stop()\n if (quietMode) return this\n const bullet = options.bullet || ICONS.bullet\n const indent = ' '.repeat(options.indent || 0)\n for (const item of items) {\n console.log(`${indent}${bullet} ${item}`)\n }\n return this\n },\n\n // Simple table output\n table(rows: Array<Record<string, string | number>>, options: { header?: boolean } = {}) {\n this.stop()\n if (quietMode || rows.length === 0) return this\n\n const keys = Object.keys(rows[0])\n const colWidths: Record<string, number> = {}\n\n // Calculate column widths\n for (const key of keys) {\n colWidths[key] = key.length\n for (const row of rows) {\n const val = String(row[key] ?? '')\n if (val.length > colWidths[key]) colWidths[key] = val.length\n }\n }\n\n // Print header if requested\n if (options.header !== false) {\n const headerLine = keys.map((k) => k.padEnd(colWidths[k])).join(' ')\n console.log(chalk.dim(headerLine))\n console.log(chalk.dim('\u2500'.repeat(headerLine.length)))\n }\n\n // Print rows\n for (const row of rows) {\n const line = keys.map((k) => String(row[k] ?? '').padEnd(colWidths[k])).join(' ')\n console.log(line)\n }\n return this\n },\n\n // Boxed content\n box(title: string, content: string) {\n this.stop()\n if (quietMode) return this\n const lines = content.split('\\n')\n const maxLen = Math.max(title.length, ...lines.map((l) => l.length))\n const border = '\u2500'.repeat(maxLen + 2)\n\n console.log(chalk.dim(`\u250C${border}\u2510`))\n console.log(`${chalk.dim('\u2502')} ${chalk.bold(title.padEnd(maxLen))} ${chalk.dim('\u2502')}`)\n console.log(chalk.dim(`\u251C${border}\u2524`))\n for (const line of lines) {\n console.log(`${chalk.dim('\u2502')} ${line.padEnd(maxLen)} ${chalk.dim('\u2502')}`)\n }\n console.log(chalk.dim(`\u2514${border}\u2518`))\n return this\n },\n\n // Section header: bold title + underline\n section(title: string) {\n this.stop()\n if (quietMode) return this\n console.log(`\\n${chalk.bold(title)}`)\n console.log(chalk.dim('\u2500'.repeat(title.length)))\n return this\n },\n\n stop() {\n if (interval) {\n clearInterval(interval)\n interval = null\n clear()\n }\n return this\n },\n\n // Step counter: [3/7] Running tests...\n step(current: number, total: number, msg: string) {\n if (quietMode) return this\n this.stop()\n const counter = chalk.dim(`[${current}/${total}]`)\n if (!process.stdout.isTTY) {\n process.stdout.write(\n `${branding.cli.spin(0, `${counter} ${truncate(msg, OUTPUT_LIMITS.STEP_MSG)}`)}\\n`\n )\n return this\n }\n interval = setInterval(() => {\n process.stdout.write(\n `\\r${branding.cli.spin(frame++, `${counter} ${truncate(msg, OUTPUT_LIMITS.STEP_MSG)}`)}`\n )\n }, SPEED)\n return this\n },\n\n // Progress bar: [\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591] 50% Analyzing...\n progress(current: number, total: number, msg?: string) {\n if (quietMode) return this\n this.stop()\n const percent = Math.round((current / total) * 100)\n const filled = Math.round(percent / 10)\n const empty = 10 - filled\n const bar = chalk.cyan('\u2588'.repeat(filled)) + chalk.dim('\u2591'.repeat(empty))\n const text = msg ? ` ${truncate(msg, OUTPUT_LIMITS.PROGRESS_TEXT)}` : ''\n if (!process.stdout.isTTY) {\n process.stdout.write(`${branding.cli.spin(0, `[${bar}] ${percent}%${text}`)}\\n`)\n return this\n }\n interval = setInterval(() => {\n process.stdout.write(`\\r${branding.cli.spin(frame++, `[${bar}] ${percent}%${text}`)}`)\n }, SPEED)\n return this\n },\n}\n\nexport type { ErrorCode, ErrorWithHint } from './error-messages'\nexport { createError, ERRORS, getError } from './error-messages'\nexport default out\n", "/**\n * Branding Configuration for prjct-cli\n * Single source of truth for all branding across CLI and AI agents\n *\n * Supports multiple AI providers (Claude Code, Gemini CLI)\n */\n\nimport chalk from 'chalk'\nimport { getProviderBranding } from '../infrastructure/ai-provider'\nimport type { AIProviderName } from '../types/provider'\n\nconst SPINNER_FRAMES = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F']\nconst SPINNER_SPEED = 80\n\ninterface Branding {\n name: string\n icon: string\n signature: string\n spinner: {\n frames: string[]\n speed: number\n }\n cli: {\n header: () => string\n footer: () => string\n spin: (frame: number, msg?: string) => string\n }\n template: {\n header: string\n footer: string\n }\n commitFooter: string\n urls: {\n website: string\n docs: string\n }\n // Provider-aware methods\n getCommitFooter: (provider?: AIProviderName) => string\n getSignature: (provider?: AIProviderName) => string\n}\n\nconst branding: Branding = {\n // Core identity\n name: 'prjct',\n icon: '\u26A1',\n signature: '\u26A1 prjct',\n\n // Spinner config\n spinner: {\n frames: SPINNER_FRAMES,\n speed: SPINNER_SPEED,\n },\n\n // CLI output (with chalk colors)\n cli: {\n header: () => `${chalk.cyan.bold('\u26A1')} ${chalk.cyan('prjct')}`,\n footer: () => chalk.dim('\u26A1 prjct'),\n spin: (frame: number, msg?: string) =>\n `${chalk.cyan('\u26A1')} ${chalk.cyan('prjct')} ${chalk.cyan(SPINNER_FRAMES[frame % 10])} ${chalk.dim(msg || '')}`,\n },\n\n // Template (plain text)\n template: {\n header: '\u26A1 prjct',\n footer: '\u26A1 prjct',\n },\n\n // Default Git commit footer (generic)\n commitFooter: `Generated with [p/](https://www.prjct.app/)`,\n\n // URLs\n urls: {\n website: 'https://prjct.app',\n docs: 'https://prjct.app/docs',\n },\n\n // Provider-aware commit footer\n getCommitFooter: (provider: AIProviderName = 'claude') => {\n return getProviderBranding(provider).commitFooter\n },\n\n // Provider-aware signature\n getSignature: (provider: AIProviderName = 'claude') => {\n return getProviderBranding(provider).signature\n },\n}\n\nexport default branding\n", "/**\n * AI Provider - Multi-agent support for prjct-cli\n *\n * Supports multiple AI coding agents with a unified abstraction layer:\n * - Claude Code (CLI): ~/.claude/, CLAUDE.md, .md commands\n * - Gemini CLI (CLI): ~/.gemini/, GEMINI.md, .toml commands\n * - Cursor IDE (GUI): .cursor/ (project-level), .mdc rules\n * - Windsurf IDE (GUI): .windsurf/ (project-level), .md rules with YAML frontmatter\n *\n * Key differences:\n * - CLI providers (Claude/Gemini) have global config directories\n * - Cursor/Windsurf have project-level config only (no ~/.cursor/ or ~/.windsurf/)\n *\n * @see https://geminicli.com/docs/cli/gemini-md/\n * @see https://geminicli.com/docs/cli/skills/\n * @see https://cursor.com/docs/context/rules\n * @see https://docs.windsurf.com/windsurf/cascade/memories\n */\n\nimport { exec } from 'node:child_process'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { promisify } from 'node:util'\nimport { compareSemver } from '../schemas/model'\nimport { fileExists } from '../utils/file-helper'\nimport { readProviderCache, writeProviderCache } from '../utils/provider-cache'\n\nconst execAsync = promisify(exec)\nconst SPAWN_TIMEOUT_MS = 2000\n\nimport type {\n AIProviderConfig,\n AIProviderName,\n CursorProjectDetection,\n ProviderBranding,\n ProviderDetectionResult,\n ProviderSelectionResult,\n WindsurfProjectDetection,\n} from '../types/provider'\n\n// =============================================================================\n// Provider Configurations\n// =============================================================================\n\n/**\n * Claude Code provider configuration\n */\nexport const ClaudeProvider: AIProviderConfig = {\n name: 'claude',\n displayName: 'Claude Code',\n cliCommand: 'claude',\n configDir: path.join(os.homedir(), '.claude'),\n contextFile: 'CLAUDE.md',\n skillsDir: path.join(os.homedir(), '.claude', 'skills'),\n commandsDir: '.claude/commands',\n commandFormat: 'md',\n settingsFile: 'settings.json',\n projectSettingsFile: 'settings.local.json',\n ignoreFile: '.claudeignore',\n websiteUrl: 'https://www.anthropic.com/claude',\n docsUrl: 'https://docs.anthropic.com/claude-code',\n defaultModel: 'sonnet',\n supportedModels: ['opus', 'sonnet', 'haiku'],\n minCliVersion: '1.0.0',\n}\n\n/**\n * Gemini CLI provider configuration\n */\nexport const GeminiProvider: AIProviderConfig = {\n name: 'gemini',\n displayName: 'Gemini CLI',\n cliCommand: 'gemini',\n configDir: path.join(os.homedir(), '.gemini'),\n contextFile: 'GEMINI.md',\n skillsDir: path.join(os.homedir(), '.gemini', 'skills'),\n commandsDir: '.gemini/commands',\n commandFormat: 'toml',\n settingsFile: 'settings.json',\n projectSettingsFile: 'settings.json',\n ignoreFile: '.geminiignore',\n websiteUrl: 'https://geminicli.com',\n docsUrl: 'https://geminicli.com/docs',\n defaultModel: '2.5-flash',\n supportedModels: ['2.5-pro', '2.5-flash', '2.0-flash'],\n minCliVersion: '1.0.0',\n}\n\n/**\n * Google Antigravity provider configuration\n *\n * An \"agent-first\" platform that manages multiple agents.\n * Config is located in ~/.gemini/antigravity/\n * Uses SKILL.md for skills and mcp_config.json for tools.\n */\nexport const AntigravityProvider: AIProviderConfig = {\n name: 'antigravity',\n displayName: 'Google Antigravity',\n cliCommand: null, // Not a CLI command, but a platform/app\n configDir: path.join(os.homedir(), '.gemini', 'antigravity'),\n contextFile: 'ANTIGRAVITY.md',\n skillsDir: path.join(os.homedir(), '.gemini', 'antigravity', 'global_skills'),\n commandsDir: '.agent/skills', // Antigravity uses .agent/skills in projects\n commandFormat: 'md', // Uses SKILL.md\n settingsFile: 'mcp_config.json', // Uses MCP config\n projectSettingsFile: null,\n ignoreFile: '.agentignore', // Assumed\n websiteUrl: 'https://gemini.google.com/app/antigravity',\n docsUrl: 'https://gemini.google.com/app/antigravity',\n defaultModel: null, // Platform-managed\n supportedModels: [],\n minCliVersion: null,\n}\n\n/**\n * Cursor IDE provider configuration\n *\n * Key differences from Claude/Gemini:\n * - NOT a CLI (GUI app, VS Code fork)\n * - No global config directory (~/.cursor/ doesn't exist)\n * - Project-level config only (.cursor/rules/, .cursor/commands/)\n * - User can select any model (GPT, Claude, Gemini, DeepSeek, etc.)\n *\n * @see https://cursor.com/docs/context/rules\n */\nexport const CursorProvider: AIProviderConfig = {\n name: 'cursor',\n displayName: 'Cursor IDE',\n cliCommand: null, // Not a CLI - GUI app\n configDir: null, // No global config directory\n contextFile: 'prjct.mdc', // Uses .mdc format with frontmatter\n skillsDir: null, // No skills directory\n commandsDir: '.cursor/commands',\n rulesDir: '.cursor/rules', // Cursor-specific: rules directory\n commandFormat: 'md',\n settingsFile: null,\n projectSettingsFile: null,\n ignoreFile: '.cursorignore',\n isProjectLevel: true, // Config is project-level only\n websiteUrl: 'https://cursor.com',\n docsUrl: 'https://cursor.com/docs',\n defaultModel: null, // Multi-model IDE, user selects\n supportedModels: [],\n minCliVersion: null,\n}\n\n/**\n * Windsurf IDE provider configuration\n *\n * Key differences from Cursor:\n * - Uses .md files (not .mdc) with YAML frontmatter\n * - Uses \"workflows\" instead of \"commands\"\n * - Frontmatter uses `trigger: always_on` instead of `alwaysApply: true`\n * - Character limits: 6000 per file, 12000 total\n *\n * @see https://docs.windsurf.com/windsurf/cascade/memories\n * @see https://docs.windsurf.com/windsurf/cascade/workflows\n */\nexport const WindsurfProvider: AIProviderConfig = {\n name: 'windsurf',\n displayName: 'Windsurf IDE',\n cliCommand: null, // Not a CLI - GUI app\n configDir: null, // No global config directory\n contextFile: 'prjct.md', // Uses .md format (not .mdc)\n skillsDir: null, // No skills directory\n commandsDir: '.windsurf/workflows', // Windsurf uses \"workflows\" not \"commands\"\n rulesDir: '.windsurf/rules',\n commandFormat: 'md',\n settingsFile: null,\n projectSettingsFile: null,\n ignoreFile: '.windsurfignore',\n isProjectLevel: true, // Config is project-level only\n websiteUrl: 'https://windsurf.com',\n docsUrl: 'https://docs.windsurf.com',\n defaultModel: null, // Multi-model IDE, user selects\n supportedModels: [],\n minCliVersion: null,\n}\n\n/**\n * All available providers\n */\nexport const Providers: Record<AIProviderName, AIProviderConfig> = {\n claude: ClaudeProvider,\n gemini: GeminiProvider,\n cursor: CursorProvider,\n antigravity: AntigravityProvider,\n windsurf: WindsurfProvider,\n}\n\n// =============================================================================\n// Provider Detection\n// =============================================================================\n\n/**\n * Check if a CLI command is available\n */\nasync function whichCommand(command: string): Promise<string | null> {\n try {\n const { stdout } = await execAsync(`which ${command}`, { timeout: SPAWN_TIMEOUT_MS })\n return stdout.trim()\n } catch {\n return null\n }\n}\n\n/**\n * Get CLI version\n */\nasync function getCliVersion(command: string): Promise<string | null> {\n try {\n const { stdout } = await execAsync(`${command} --version`, { timeout: SPAWN_TIMEOUT_MS })\n // Extract version number from output (e.g., \"claude 1.0.0\" -> \"1.0.0\")\n const match = stdout.match(/\\d+\\.\\d+\\.\\d+/)\n return match ? match[0] : stdout.trim()\n } catch {\n return null\n }\n}\n\n/**\n * Detect if a specific CLI-based provider is installed\n * Note: Cursor is NOT a CLI, use detectCursorProject() instead\n */\nexport async function detectProvider(provider: AIProviderName): Promise<ProviderDetectionResult> {\n const config = Providers[provider]\n\n // Cursor is not a CLI - return not installed for CLI detection\n if (!config.cliCommand) {\n return { installed: false }\n }\n\n const cliPath = await whichCommand(config.cliCommand)\n\n if (!cliPath) {\n return { installed: false }\n }\n\n const version = await getCliVersion(config.cliCommand)\n const versionWarning = validateCliVersion(provider, version || undefined)\n\n return {\n installed: true,\n version: version || undefined,\n path: cliPath,\n versionWarning: versionWarning || undefined,\n }\n}\n\n/**\n * Validate that a detected CLI version meets the provider's minimum requirement.\n * Returns a warning message if the version is below minimum, or null if OK.\n */\nexport function validateCliVersion(\n provider: AIProviderName,\n version: string | undefined\n): string | null {\n const config = Providers[provider]\n if (!config.minCliVersion || !version) return null\n\n if (compareSemver(version, config.minCliVersion) < 0) {\n return `\u26A0\uFE0F ${config.displayName} v${version} is below minimum v${config.minCliVersion}. Some features may not work correctly.`\n }\n return null\n}\n\n/**\n * Detect all available CLI-based providers\n * Results are cached to disk with a 10-minute TTL to avoid redundant shell spawns.\n * Pass refresh=true to force re-detection.\n */\nexport async function detectAllProviders(refresh = false): Promise<{\n claude: ProviderDetectionResult\n gemini: ProviderDetectionResult\n}> {\n if (!refresh) {\n const cached = await readProviderCache()\n if (cached) return cached\n }\n\n const [claude, gemini] = await Promise.all([detectProvider('claude'), detectProvider('gemini')])\n const detection = { claude, gemini }\n\n await writeProviderCache(detection).catch(() => {})\n\n return detection\n}\n\n/**\n * Get the active provider based on detection or configuration\n *\n * Priority:\n * 1. Check project config for saved provider preference\n * 2. Auto-detect single installed provider\n * 3. Default to Claude if both installed (backward compatibility)\n */\nexport async function getActiveProvider(\n projectProvider?: AIProviderName\n): Promise<AIProviderConfig> {\n // If project has a saved preference, use it\n if (projectProvider && Providers[projectProvider]) {\n return Providers[projectProvider]\n }\n\n // Auto-detect\n const detection = await detectAllProviders()\n\n // If only one is installed, use it\n if (detection.claude.installed && !detection.gemini.installed) {\n return ClaudeProvider\n }\n if (detection.gemini.installed && !detection.claude.installed) {\n return GeminiProvider\n }\n\n // Default to Claude for backward compatibility\n return ClaudeProvider\n}\n\n/**\n * Check if config directory exists for a provider\n * Returns false for project-level providers (Cursor)\n */\nexport async function hasProviderConfig(provider: AIProviderName): Promise<boolean> {\n const config = Providers[provider]\n if (!config.configDir) {\n return false // Cursor has no global config directory\n }\n return fileExists(config.configDir)\n}\n\n// =============================================================================\n// Provider Branding\n// =============================================================================\n\n/**\n * Get provider-specific branding\n */\nexport function getProviderBranding(provider: AIProviderName): ProviderBranding {\n // Generic commit footer for all providers\n const commitFooter = `Generated with [p/](https://www.prjct.app/)`\n\n const signatures: Record<AIProviderName, string> = {\n claude: '\u26A1 prjct + Claude',\n gemini: '\u26A1 prjct + Gemini',\n cursor: '\u26A1 prjct + Cursor',\n antigravity: '\u26A1 prjct + Antigravity',\n windsurf: '\u26A1 prjct + Windsurf',\n }\n\n return {\n commitFooter,\n signature: signatures[provider] || '\u26A1 prjct',\n }\n}\n\n// =============================================================================\n// Cursor Project Detection\n// =============================================================================\n\n/**\n * Detect if a project is configured for Cursor IDE\n *\n * Cursor has NO global config (~/.cursor/ doesn't exist).\n * Detection is based on project-level .cursor/ directory.\n */\nexport async function detectCursorProject(projectRoot: string): Promise<CursorProjectDetection> {\n const cursorDir = path.join(projectRoot, '.cursor')\n const rulesDir = path.join(cursorDir, 'rules')\n const routerPath = path.join(rulesDir, 'prjct.mdc')\n\n const [detected, routerInstalled] = await Promise.all([\n fileExists(cursorDir),\n fileExists(routerPath),\n ])\n\n return {\n detected,\n routerInstalled,\n projectRoot: detected ? projectRoot : undefined,\n }\n}\n\n/**\n * Check if Cursor routers need to be regenerated\n */\nexport async function needsCursorRouterRegeneration(projectRoot: string): Promise<boolean> {\n const detection = await detectCursorProject(projectRoot)\n\n // Only check if .cursor/ exists (project uses Cursor)\n // and prjct router is missing\n return detection.detected && !detection.routerInstalled\n}\n\n// =============================================================================\n// Windsurf Project Detection\n// =============================================================================\n\n/**\n * Detect if a project is configured for Windsurf IDE\n *\n * Windsurf has NO global config (~/.windsurf/ doesn't exist).\n * Detection is based on project-level .windsurf/ directory.\n */\nexport async function detectWindsurfProject(\n projectRoot: string\n): Promise<WindsurfProjectDetection> {\n const windsurfDir = path.join(projectRoot, '.windsurf')\n const rulesDir = path.join(windsurfDir, 'rules')\n const routerPath = path.join(rulesDir, 'prjct.md')\n\n const [detected, routerInstalled] = await Promise.all([\n fileExists(windsurfDir),\n fileExists(routerPath),\n ])\n\n return {\n detected,\n routerInstalled,\n projectRoot: detected ? projectRoot : undefined,\n }\n}\n\n/**\n * Check if Windsurf routers need to be regenerated\n */\nexport async function needsWindsurfRouterRegeneration(projectRoot: string): Promise<boolean> {\n const detection = await detectWindsurfProject(projectRoot)\n\n // Only check if .windsurf/ exists (project uses Windsurf)\n // and prjct router is missing\n return detection.detected && !detection.routerInstalled\n}\n\n// =============================================================================\n// Antigravity Detection\n// =============================================================================\n\n/**\n * Result of Antigravity detection\n */\nexport interface AntigravityDetection {\n /** Whether ~/.gemini/antigravity/ exists */\n installed: boolean\n\n /** Whether prjct skill is installed */\n skillInstalled: boolean\n\n /** Path to config directory */\n configPath?: string\n}\n\n/**\n * Detect if Google Antigravity is installed\n *\n * Antigravity is NOT a CLI command - it's a GUI platform.\n * Detection is based on ~/.gemini/antigravity/ directory.\n */\nexport async function detectAntigravity(): Promise<AntigravityDetection> {\n const configPath = AntigravityProvider.configDir\n if (!configPath) {\n return { installed: false, skillInstalled: false }\n }\n\n const skillPath = path.join(configPath, 'skills', 'prjct', 'SKILL.md')\n const [installed, skillInstalled] = await Promise.all([\n fileExists(configPath),\n fileExists(skillPath),\n ])\n\n return {\n installed,\n skillInstalled,\n configPath: installed ? configPath : undefined,\n }\n}\n\n// =============================================================================\n// Provider Paths\n// =============================================================================\n\n/**\n * Get full path to global context file\n * Returns null for project-level providers (Cursor)\n */\nexport function getGlobalContextPath(provider: AIProviderName): string | null {\n const config = Providers[provider]\n if (!config.configDir) {\n return null // Cursor has no global config\n }\n return path.join(config.configDir, config.contextFile)\n}\n\n/**\n * Get full path to global settings file\n * Returns null for project-level providers (Cursor)\n */\nexport function getGlobalSettingsPath(provider: AIProviderName): string | null {\n const config = Providers[provider]\n if (!config.configDir || !config.settingsFile) {\n return null // Cursor has no global settings\n }\n return path.join(config.configDir, config.settingsFile)\n}\n\n/**\n * Get full path to skills directory\n * Returns null for providers without skill support (Cursor)\n */\nexport function getSkillsPath(provider: AIProviderName): string | null {\n return Providers[provider].skillsDir\n}\n\n/**\n * Get commands directory relative to project root\n */\nexport function getCommandsDir(provider: AIProviderName): string {\n return Providers[provider].commandsDir\n}\n\n/**\n * Get full path to commands directory in a project\n */\nexport function getProjectCommandsPath(provider: AIProviderName, projectRoot: string): string {\n const config = Providers[provider]\n return path.join(projectRoot, config.commandsDir)\n}\n\n// =============================================================================\n// Provider Selection (for setup)\n// =============================================================================\n\n/**\n * Determine which provider to use during setup\n * Returns selection result with detection details\n */\nexport async function selectProvider(): Promise<ProviderSelectionResult> {\n const detection = await detectAllProviders()\n\n const claudeInstalled = detection.claude.installed\n const geminiInstalled = detection.gemini.installed\n\n // Neither installed\n if (!claudeInstalled && !geminiInstalled) {\n // Default to Claude, setup will prompt to install\n return {\n provider: 'claude',\n userSelected: false,\n detection,\n }\n }\n\n // Only Claude installed\n if (claudeInstalled && !geminiInstalled) {\n return {\n provider: 'claude',\n userSelected: false,\n detection,\n }\n }\n\n // Only Gemini installed\n if (geminiInstalled && !claudeInstalled) {\n return {\n provider: 'gemini',\n userSelected: false,\n detection,\n }\n }\n\n // Both installed - will need user selection\n // For now, default to Claude (caller should prompt user)\n return {\n provider: 'claude',\n userSelected: true, // Indicates user should be prompted\n detection,\n }\n}\n", "/**\n * Model Schema\n *\n * Defines model specification types for AI providers.\n * Records which model was used for each analysis and task,\n * enabling consistency tracking and mismatch warnings.\n *\n * @see PRJ-265\n */\n\nimport { z } from 'zod'\n\n// =============================================================================\n// Provider-Specific Model Identifiers\n// =============================================================================\n\n/** Claude model identifiers (short names matching agent frontmatter convention) */\nexport const ClaudeModelSchema = z.enum(['opus', 'sonnet', 'haiku'])\n\n/** Gemini model identifiers */\nexport const GeminiModelSchema = z.enum(['2.5-pro', '2.5-flash', '2.0-flash'])\n\n/** Generic model identifier - allows any string for future providers */\nexport const AIModelSchema = z.string().min(1)\n\n// =============================================================================\n// Supported Models Per Provider\n// =============================================================================\n\nexport const SUPPORTED_MODELS: Record<string, readonly string[]> = {\n claude: ['opus', 'sonnet', 'haiku'],\n gemini: ['2.5-pro', '2.5-flash', '2.0-flash'],\n cursor: [], // Multi-model IDE, user selects model\n windsurf: [], // Multi-model IDE, user selects model\n antigravity: [], // Platform-managed\n} as const\n\nexport const DEFAULT_MODELS: Record<string, string> = {\n claude: 'sonnet',\n gemini: '2.5-flash',\n} as const\n\n// =============================================================================\n// Minimum CLI Versions\n// =============================================================================\n\nexport const MIN_CLI_VERSIONS: Record<string, string> = {\n claude: '1.0.0',\n gemini: '1.0.0',\n} as const\n\n// =============================================================================\n// Model Metadata - Recorded Per Operation\n// =============================================================================\n\n/** Model metadata recorded with each analysis or task */\nexport const ModelMetadataSchema = z.object({\n /** Provider name (e.g., 'claude', 'gemini') */\n provider: z.string(),\n /** Model identifier (e.g., 'opus', 'sonnet', '2.5-pro') */\n model: z.string(),\n /** CLI version used */\n cliVersion: z.string().optional(),\n /** When this was recorded */\n recordedAt: z.string(),\n})\n\n// =============================================================================\n// Model Configuration - Per Project\n// =============================================================================\n\n/** Per-project model preference */\nexport const ModelPreferenceSchema = z.object({\n /** Preferred model for this project */\n preferredModel: z.string().optional(),\n /** Model used for last analysis (for mismatch detection) */\n lastAnalysisModel: ModelMetadataSchema.optional(),\n})\n\n// =============================================================================\n// Inferred Types\n// =============================================================================\n\nexport type ClaudeModel = z.infer<typeof ClaudeModelSchema>\nexport type GeminiModel = z.infer<typeof GeminiModelSchema>\nexport type ModelMetadata = z.infer<typeof ModelMetadataSchema>\nexport type ModelPreference = z.infer<typeof ModelPreferenceSchema>\n\n// =============================================================================\n// Validation Helpers\n// =============================================================================\n\n/** Check if a model is valid for a given provider */\nexport function isValidModelForProvider(provider: string, model: string): boolean {\n const supported = SUPPORTED_MODELS[provider]\n if (!supported || supported.length === 0) return true // No restriction for multi-model IDEs\n return supported.includes(model)\n}\n\n/** Get the default model for a provider */\nexport function getDefaultModel(provider: string): string | null {\n return DEFAULT_MODELS[provider] ?? null\n}\n\n/** Get supported models for a provider */\nexport function getSupportedModels(provider: string): readonly string[] {\n return SUPPORTED_MODELS[provider] ?? []\n}\n\n/** Get minimum CLI version for a provider */\nexport function getMinCliVersion(provider: string): string | null {\n return MIN_CLI_VERSIONS[provider] ?? null\n}\n\n/**\n * Compare semver versions. Returns:\n * -1 if a < b\n * 0 if a == b\n * 1 if a > b\n */\nexport function compareSemver(a: string, b: string): -1 | 0 | 1 {\n const pa = a.split('.').map(Number)\n const pb = b.split('.').map(Number)\n for (let i = 0; i < 3; i++) {\n const va = pa[i] ?? 0\n const vb = pb[i] ?? 0\n if (va < vb) return -1\n if (va > vb) return 1\n }\n return 0\n}\n\n/** Check if a CLI version meets minimum requirements */\nexport function meetsMinVersion(provider: string, version: string): boolean {\n const min = MIN_CLI_VERSIONS[provider]\n if (!min) return true // No minimum defined\n return compareSemver(version, min) >= 0\n}\n\n/**\n * Check for model mismatch between analysis and current task.\n * Returns a warning message if the models differ, or null if they match.\n */\nexport function checkModelMismatch(\n analysisModel: ModelMetadata | undefined,\n taskModel: ModelMetadata | undefined\n): string | null {\n if (!analysisModel || !taskModel) return null\n if (analysisModel.provider !== taskModel.provider || analysisModel.model !== taskModel.model) {\n return `\u26A0\uFE0F Model mismatch: analysis used ${analysisModel.provider}/${analysisModel.model}, but task is using ${taskModel.provider}/${taskModel.model}. Results may differ.`\n }\n return null\n}\n", "import fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport type { ProviderDetectionResult } from '../types/provider'\n\nconst CACHE_DIR = path.join(os.homedir(), '.prjct-cli', 'cache')\nconst CACHE_FILE = path.join(CACHE_DIR, 'providers.json')\nconst TTL_MS = 10 * 60 * 1000 // 10 minutes\n\ninterface ProviderCache {\n timestamp: string\n detection: {\n claude: ProviderDetectionResult\n gemini: ProviderDetectionResult\n }\n}\n\nexport async function readProviderCache(): Promise<ProviderCache['detection'] | null> {\n try {\n const raw = await fs.readFile(CACHE_FILE, 'utf-8')\n const cache: ProviderCache = JSON.parse(raw)\n\n if (!cache.timestamp || !cache.detection) return null\n\n const age = Date.now() - new Date(cache.timestamp).getTime()\n if (age > TTL_MS) return null\n\n return cache.detection\n } catch {\n return null\n }\n}\n\nexport async function writeProviderCache(detection: ProviderCache['detection']): Promise<void> {\n const cache: ProviderCache = {\n timestamp: new Date().toISOString(),\n detection,\n }\n await fs.mkdir(CACHE_DIR, { recursive: true })\n await fs.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2))\n}\n\nexport async function invalidateProviderCache(): Promise<void> {\n try {\n await fs.unlink(CACHE_FILE)\n } catch {\n // File doesn't exist \u2014 fine\n }\n}\n", "/**\n * Constants\n * Single source of truth for all constants in prjct-cli.\n */\n\n// =============================================================================\n// File Format Constants\n// =============================================================================\n\n/**\n * NOW file format patterns.\n */\nexport const NOW = {\n /** Header marker for NOW file */\n HEADER: '# NOW',\n\n /** Pattern to extract task from NOW content */\n TASK_PATTERN: /\\*\\*(.+?)\\*\\*/,\n\n /** Generate NOW file content */\n content: (task: string, startedAt: string, agent?: string, confidence?: number): string => {\n const lines = ['# NOW', '', `**${task}**`, '', `Started: ${startedAt}`]\n if (agent) {\n lines.push(\n `Agent: ${agent}${confidence ? ` (${Math.round(confidence * 100)}% confidence)` : ''}`\n )\n }\n return `${lines.join('\\n')}\\n`\n },\n\n /** Extract task from NOW content */\n extractTask: (content: string): string | null => {\n const match = content.match(NOW.TASK_PATTERN)\n return match ? match[1] : null\n },\n} as const\n\n/**\n * SHIPPED file format patterns.\n */\nexport const SHIPPED = {\n /** Header marker for SHIPPED file */\n HEADER: '# SHIPPED',\n\n /** Generate ship entry */\n entry: (feature: string, date: string, duration?: string): string => {\n const lines = [`## ${feature}`, '', `Shipped: ${date}`]\n if (duration) {\n lines.push(`Duration: ${duration}`)\n }\n return `${lines.join('\\n')}\\n\\n`\n },\n} as const\n\n/**\n * NEXT file format patterns.\n */\nexport const NEXT = {\n /** Header marker for NEXT file */\n HEADER: '# NEXT',\n\n /** Pattern for task entries */\n TASK_PATTERN: /^[-*]\\s+\\[([x ])\\]\\s+(.+)$/gm,\n\n /** Generate task entry */\n entry: (task: string, completed: boolean = false): string => {\n return `- [${completed ? 'x' : ' '}] ${task}\\n`\n },\n} as const\n\n/**\n * IDEAS file format patterns.\n */\nexport const IDEAS = {\n /** Header marker for IDEAS file */\n HEADER: '# IDEAS',\n\n /** Generate idea entry */\n entry: (idea: string, date: string): string => {\n return `- ${idea} _(${date})_\\n`\n },\n} as const\n\n/**\n * Roadmap status markers.\n */\nexport const ROADMAP_STATUS = {\n PLANNED: 'Planned',\n IN_PROGRESS: 'In Progress',\n COMPLETED: 'Completed',\n BLOCKED: 'Blocked',\n} as const\n\nexport type RoadmapStatusKey = keyof typeof ROADMAP_STATUS\n\n/**\n * ROADMAP file format patterns.\n */\nexport const ROADMAP = {\n /** Header marker for ROADMAP file */\n HEADER: '# ROADMAP',\n\n /** Status markers (re-exported for convenience) */\n STATUS: ROADMAP_STATUS,\n\n /** Generate feature entry */\n entry: (feature: string, status: RoadmapStatusKey, tasks?: string[]): string => {\n const lines = [`## ${feature}`, '', `Status: ${ROADMAP_STATUS[status]}`]\n if (tasks && tasks.length > 0) {\n lines.push('', '### Tasks', '')\n for (const task of tasks) {\n lines.push(`- [ ] ${task}`)\n }\n }\n return `${lines.join('\\n')}\\n\\n`\n },\n} as const\n\n/**\n * Session file paths.\n */\nexport const SESSION = {\n /** Date format for session directories */\n DATE_FORMAT: 'YYYY-MM-DD',\n\n /** Generate session path */\n path: (year: string, month: string, day: string): string => {\n return `sessions/${year}-${month}/${year}-${month}-${day}`\n },\n\n /** Session metadata filename */\n METADATA_FILE: 'session-meta.json',\n\n /** Context log filename */\n CONTEXT_FILE: 'context.jsonl',\n} as const\n\n// =============================================================================\n// Status & Priority Constants\n// =============================================================================\n\n/**\n * Status values used throughout prjct.\n */\nexport const STATUS = {\n PENDING: 'pending',\n IN_PROGRESS: 'in_progress',\n COMPLETED: 'completed',\n BLOCKED: 'blocked',\n PAUSED: 'paused',\n} as const\n\nexport type Status = (typeof STATUS)[keyof typeof STATUS]\n\n/**\n * Priority levels.\n */\nexport const PRIORITY = {\n LOW: 'low',\n MEDIUM: 'medium',\n HIGH: 'high',\n CRITICAL: 'critical',\n} as const\n\nexport type Priority = (typeof PRIORITY)[keyof typeof PRIORITY]\n\n// =============================================================================\n// Plan Mode Constants\n// =============================================================================\n\n/**\n * Plan status enum - values must match PlanStatus type in types.ts\n */\nexport const PLAN_STATUS = {\n GATHERING: 'gathering',\n ANALYZING: 'analyzing',\n PROPOSING: 'proposing',\n PENDING_APPROVAL: 'awaiting_approval',\n APPROVED: 'approved',\n REJECTED: 'rejected',\n EXECUTING: 'executing',\n COMPLETED: 'completed',\n ABORTED: 'aborted',\n} as const\n\nexport type PlanStatusValue = (typeof PLAN_STATUS)[keyof typeof PLAN_STATUS]\n\n/**\n * Commands that require planning mode\n */\nexport const PLAN_REQUIRED_COMMANDS = [\n 'feature', // New features need planning\n 'spec', // Specs are planning by definition\n 'design', // Architecture needs planning\n 'refactor', // Refactoring needs impact analysis\n 'migrate', // Migrations are high-risk\n] as const\n\n/**\n * Commands that are destructive and need approval\n */\nexport const DESTRUCTIVE_COMMANDS = [\n 'ship', // Commits and pushes\n 'cleanup', // Deletes files/code\n 'git', // Git operations\n 'migrate', // Database/schema changes\n] as const\n\n/**\n * Read-only tools allowed in planning mode\n */\nexport const PLANNING_TOOLS = [\n 'Read',\n 'Glob',\n 'Grep',\n 'GetTimestamp',\n 'GetDate',\n 'GetDateTime',\n] as const\n\n// =============================================================================\n// Timeout Constants (PRJ-111)\n// =============================================================================\n\n/**\n * Timeout values in milliseconds for various operations.\n * Can be overridden via PRJCT_TIMEOUT_* environment variables.\n */\nexport const TIMEOUTS = {\n /** Tool availability checks (git --version, npm --version) */\n TOOL_CHECK: 5_000,\n\n /** Standard git operations (status, add, commit) */\n GIT_OPERATION: 10_000,\n\n /** Git clone with --depth 1 */\n GIT_CLONE: 60_000,\n\n /** HTTP fetch/API requests */\n API_REQUEST: 30_000,\n\n /** npm install -g (CLI installation) - 2 minutes */\n NPM_INSTALL: 120_000,\n\n /** User-defined workflow hooks */\n WORKFLOW_HOOK: 60_000,\n} as const\n\nexport type TimeoutKey = keyof typeof TIMEOUTS\n\n/**\n * Get timeout value with optional environment variable override.\n * Environment variables: PRJCT_TIMEOUT_TOOL_CHECK, PRJCT_TIMEOUT_GIT_OPERATION, etc.\n */\nexport function getTimeout(key: TimeoutKey): number {\n const envVar = `PRJCT_TIMEOUT_${key}`\n const envValue = process.env[envVar]\n if (envValue) {\n const parsed = Number.parseInt(envValue, 10)\n if (!Number.isNaN(parsed) && parsed > 0) {\n return parsed\n }\n }\n return TIMEOUTS[key]\n}\n\n// =============================================================================\n// Output Limits (PRJ-71)\n// =============================================================================\n\n/**\n * Truncation lengths for CLI output messages.\n * Centralizes magic numbers from output.ts.\n */\nexport const OUTPUT_LIMITS = {\n /** Spinner message truncation */\n SPINNER_MSG: 45,\n /** Done/success message truncation */\n DONE_MSG: 50,\n /** Fail message truncation */\n FAIL_MSG: 65,\n /** Warn message truncation */\n WARN_MSG: 65,\n /** Step counter message truncation */\n STEP_MSG: 35,\n /** Progress bar text truncation */\n PROGRESS_TEXT: 25,\n /** Issue title truncation in lists */\n ISSUE_TITLE: 50,\n /** Fallback truncation when tier config is 0 */\n FALLBACK_TRUNCATE: 50,\n /** Terminal clear width */\n CLEAR_WIDTH: 80,\n} as const\n\n// =============================================================================\n// Storage Limits (PRJ-71)\n// =============================================================================\n\n/**\n * File size and line limits for JSONL/storage operations.\n * Centralizes magic numbers from jsonl-helper.ts.\n */\nexport const STORAGE_LIMITS = {\n /** Default max lines for streaming JSONL reads */\n JSONL_MAX_LINES: 1000,\n /** File rotation threshold in MB */\n ROTATION_SIZE_MB: 10,\n /** Warning threshold for large files in MB */\n LARGE_FILE_WARN_MB: 50,\n} as const\n\n// =============================================================================\n// Event Bus Limits (PRJ-71)\n// =============================================================================\n\n/**\n * Event bus configuration limits.\n * Centralizes magic numbers from bus.ts.\n */\nexport const EVENT_LIMITS = {\n /** Max events kept in history */\n HISTORY_MAX: 100,\n} as const\n\n// =============================================================================\n// Workflow Help Strings (user-facing, no magic strings)\n// =============================================================================\n\n/**\n * Example commands shown in workflow preferences help.\n * Keeps UI copy in English and centralized.\n */\nexport const WORKFLOW_HELP = {\n /** Message when no workflow preferences are configured */\n NO_PREFERENCES: 'No workflow preferences configured.',\n /** Example: set a before-ship hook to run tests */\n SET_EXAMPLE: 'p. workflow before ship run the tests',\n /** Example: modify workflow to run npm test */\n MODIFY_EXAMPLE: 'p. workflow before ship run npm test',\n /** Example: remove the ship hook */\n REMOVE_EXAMPLE: 'p. workflow remove the ship hook',\n} as const\n\n// =============================================================================\n// Combined Exports\n// =============================================================================\n\n/**\n * Combined file format exports for easy import.\n */\nexport const FORMATS = {\n NOW,\n SHIPPED,\n NEXT,\n IDEAS,\n ROADMAP,\n SESSION,\n STATUS,\n PRIORITY,\n} as const\n\n/**\n * Combined plan mode exports for easy import.\n */\nexport const PLAN = {\n STATUS: PLAN_STATUS,\n REQUIRED_COMMANDS: PLAN_REQUIRED_COMMANDS,\n DESTRUCTIVE_COMMANDS,\n TOOLS: PLANNING_TOOLS,\n} as const\n\n/**\n * Combined timeout exports for easy import.\n */\nexport const TIMEOUT = {\n VALUES: TIMEOUTS,\n get: getTimeout,\n} as const\n", "/**\n * Per-project credential storage\n *\n * Stores credentials in: ~/.prjct-cli/projects/{projectId}/config/credentials.json\n *\n * Fallback chain for Linear API key:\n * 1. Project credentials (per-project)\n * 2. Global keychain (macOS)\n * 3. Environment variables\n *\n * This allows different projects to use different Linear workspaces.\n */\n\nimport fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { getErrorMessage } from '../types/fs'\nimport { fileExists } from './file-helper'\nimport { type CredentialKey, getCredential } from './keychain'\n\ninterface LinearCredentials {\n apiKey: string\n teamId?: string\n teamKey?: string\n setupAt: string\n}\n\nexport interface ProjectCredentials {\n linear?: LinearCredentials\n}\n\n/**\n * Get path to project credentials file\n */\nfunction getCredentialsPath(projectId: string): string {\n return path.join(os.homedir(), '.prjct-cli', 'projects', projectId, 'config', 'credentials.json')\n}\n\n/**\n * Get all credentials for a project\n */\nexport async function getProjectCredentials(projectId: string): Promise<ProjectCredentials> {\n const credPath = getCredentialsPath(projectId)\n if (!(await fileExists(credPath))) {\n return {}\n }\n\n try {\n return JSON.parse(await fs.readFile(credPath, 'utf-8'))\n } catch (error) {\n console.error('[project-credentials] Failed to read credentials:', getErrorMessage(error))\n return {}\n }\n}\n\n/**\n * Set Linear credentials for a project\n */\nexport async function setLinearCredentials(\n projectId: string,\n credentials: LinearCredentials\n): Promise<void> {\n const credPath = getCredentialsPath(projectId)\n const dir = path.dirname(credPath)\n\n // Ensure directory exists\n if (!(await fileExists(dir))) {\n await fs.mkdir(dir, { recursive: true })\n }\n\n // Read existing, merge, write\n const current = await getProjectCredentials(projectId)\n current.linear = credentials\n await fs.writeFile(credPath, JSON.stringify(current, null, 2))\n}\n\n/**\n * Delete Linear credentials for a project\n */\nexport async function deleteLinearCredentials(projectId: string): Promise<void> {\n const current = await getProjectCredentials(projectId)\n\n if (current.linear) {\n delete current.linear\n const credPath = getCredentialsPath(projectId)\n await fs.writeFile(credPath, JSON.stringify(current, null, 2))\n }\n}\n\n/**\n * Get Linear API key with fallback chain:\n * 1. Project credentials\n * 2. Global keychain\n * 3. Environment variable\n */\nexport async function getLinearApiKey(projectId: string): Promise<string | null> {\n // 1. Project credentials\n const projectCreds = await getProjectCredentials(projectId)\n if (projectCreds.linear?.apiKey) {\n return projectCreds.linear.apiKey\n }\n\n // 2. Global keychain (falls back to env var internally)\n return getCredential('linear-api-key' as CredentialKey)\n}\n\n/**\n * Get Linear team ID from project credentials\n */\nexport async function getLinearTeamId(projectId: string): Promise<string | undefined> {\n const projectCreds = await getProjectCredentials(projectId)\n return projectCreds.linear?.teamId\n}\n\n/**\n * Check if Linear is configured for a project\n */\nexport async function isLinearConfigured(projectId: string): Promise<boolean> {\n const apiKey = await getLinearApiKey(projectId)\n return apiKey !== null && apiKey.length > 0\n}\n\n/**\n * Get credential source for debugging\n */\nexport async function getCredentialSource(\n projectId: string\n): Promise<'project' | 'keychain' | 'env' | 'none'> {\n // Check project credentials\n const projectCreds = await getProjectCredentials(projectId)\n if (projectCreds.linear?.apiKey) {\n return 'project'\n }\n\n // Check keychain/env\n const { getCredentialWithSource } = await import('./keychain')\n const result = await getCredentialWithSource('linear-api-key' as CredentialKey)\n if (result.value) {\n return result.source === 'keychain' ? 'keychain' : 'env'\n }\n\n return 'none'\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["isNodeError", "error", "isNotFoundError", "getErrorMessage", "init_fs", "__esmMin", "__name", "keychain_exports", "__export", "deleteCredential", "getCredential", "getCredentialWithSource", "hasCredential", "setCredential", "exec", "promisify", "key", "value", "execAsync", "SERVICE_NAME", "error", "getErrorMessage", "getEnvFallback", "stdout", "envVar", "envValue", "init_keychain", "__esmMin", "init_fs", "__name", "TTLCache", "__name", "options", "key", "entry", "data", "toRemove", "a", "b", "removed", "LINEAR_CACHE_TTL", "issueCache", "TTLCache", "assignedIssuesCache", "teamsCache", "projectsCache", "clearLinearCache", "__name", "getLinearCacheStats", "init_fs", "init_keychain", "LINEAR_STATUS_MAP", "LINEAR_PRIORITY_MAP", "PRIORITY_TO_LINEAR", "LinearProvider", "__name", "config", "apiKey", "getCredential", "LinearClient", "error", "getErrorMessage", "options", "viewer", "filter", "assignedIssues", "issue", "teamId", "issues", "id", "match", "teamKey", "numberStr", "issueNumber", "team", "t", "input", "createdIssue", "updatePayload", "updated", "startedState", "s", "doneState", "body", "project", "linearIssue", "state", "assignee", "labels", "l", "title", "titleLower", "labelsLower", "labelNames", "label", "linearProvider", "LinearService", "__name", "linearProvider", "config", "apiKey", "teamId", "options", "cacheKey", "cached", "assignedIssuesCache", "issues", "issue", "issueCache", "id", "input", "body", "teamsCache", "teams", "projectsCache", "projects", "clearLinearCache", "getLinearCacheStats", "linearService", "mkdir", "readFile", "writeFile", "join", "z", "IssueProviderSchema", "z", "IssueStatusSchema", "IssuePrioritySchema", "IssueTypeSchema", "CachedIssueSchema", "IssuesJsonSchema", "SyncResultSchema", "parseIssues", "__name", "data", "createEmptyIssues", "provider", "__name", "homedir", "join", "GLOBAL_STORAGE", "join", "homedir", "getProjectPath", "projectId", "__name", "init_fs", "fs", "init_fs", "init_fs", "fileExists", "filePath", "fs", "error", "isNotFoundError", "__name", "DEFAULT_STALE_AFTER", "LinearSync", "__name", "projectId", "storagePath", "join", "getProjectPath", "issuesPath", "fileExists", "mkdir", "timestamp", "errors", "issues", "linearService", "issuesMap", "issue", "err", "getErrorMessage", "writeFile", "identifier", "issuesJson", "cachedIssue", "fetchedAt", "now", "issueStaleness", "status", "cachedStatus", "lastSyncTime", "staleAfter", "content", "readFile", "parseIssues", "createEmptyIssues", "linearSync", "init_fs", "chalk", "chalk", "exec", "os", "path", "promisify", "z", "ClaudeModelSchema", "z", "GeminiModelSchema", "AIModelSchema", "ModelMetadataSchema", "z", "ModelPreferenceSchema", "os", "path", "CACHE_DIR", "path", "os", "CACHE_FILE", "TTL_MS", "execAsync", "promisify", "exec", "ClaudeProvider", "path", "os", "GeminiProvider", "AntigravityProvider", "getProviderBranding", "provider", "__name", "SPINNER_FRAMES", "SPINNER_SPEED", "branding", "__name", "chalk", "frame", "msg", "provider", "getProviderBranding", "branding_default", "OUTPUT_LIMITS", "_FRAMES", "branding_default", "SPEED", "OUTPUT_TIERS", "currentTier", "setOutputTier", "tier", "__name", "getTierConfig", "OUTPUT_TIERS", "currentTier", "__name", "ICONS", "chalk", "truncate", "__name", "s", "max", "limit", "getTierConfig", "OUTPUT_LIMITS", "limitLines", "content", "maxLines", "lines", "shown", "remaining", "chalk", "formatForHuman", "data", "tier", "currentTier", "obj", "issues", "i", "priority", "relevant", "k", "init_fs", "fs", "os", "path", "init_keychain", "getCredentialsPath", "projectId", "path", "os", "__name", "getProjectCredentials", "credPath", "fileExists", "fs", "error", "getErrorMessage", "setLinearCredentials", "credentials", "dir", "current", "getLinearApiKey", "projectId", "projectCreds", "getProjectCredentials", "getCredential", "__name", "getCredentialSource", "projectId", "getProjectCredentials", "getCredentialWithSource", "result", "__name", "args", "projectIdx", "projectId", "jsonIdx", "jsonMode", "verboseIdx", "verboseMode", "setOutputTier", "command", "commandArgs", "output", "data", "formatForHuman", "__name", "error", "message", "code", "initFromProject", "apiKey", "getLinearApiKey", "creds", "getProjectCredentials", "linearService", "main", "teamId", "teams", "selectedTeamId", "selectedTeamKey", "team", "t", "setLinearCredentials", "limit", "issues", "issueList", "issue", "p", "id", "desc", "cachedIssue", "linearSync", "result", "status", "inputJson", "input", "body", "projects", "source", "getCredentialSource", "err", "getErrorMessage"]
|
|
7
|
+
}
|