prjct-cli 0.35.4 → 0.37.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +64 -618
  3. package/bin/prjct.ts +128 -17
  4. package/core/agentic/template-executor.ts +14 -4
  5. package/core/cli/start.ts +397 -0
  6. package/core/commands/analysis.ts +6 -3
  7. package/core/commands/setup.ts +27 -17
  8. package/core/index.ts +101 -35
  9. package/core/infrastructure/ai-provider.ts +414 -0
  10. package/core/infrastructure/command-installer.ts +119 -42
  11. package/core/infrastructure/editors-config.ts +20 -6
  12. package/core/infrastructure/path-manager.ts +18 -9
  13. package/core/infrastructure/setup.ts +356 -62
  14. package/core/services/skill-service.ts +52 -16
  15. package/core/types/index.ts +12 -0
  16. package/core/types/provider.ts +134 -0
  17. package/core/utils/branding.ts +20 -3
  18. package/dist/bin/prjct.mjs +1482 -2763
  19. package/dist/core/infrastructure/command-installer.js +33 -2
  20. package/dist/core/infrastructure/editors-config.js +13 -3
  21. package/dist/core/infrastructure/setup.js +293 -73
  22. package/package.json +9 -9
  23. package/scripts/postinstall.js +17 -119
  24. package/templates/_bases/tracker-base.md +7 -5
  25. package/templates/agents/AGENTS.md +9 -1
  26. package/templates/commands/github.md +7 -5
  27. package/templates/commands/init.md +16 -0
  28. package/templates/commands/jira.md +8 -6
  29. package/templates/commands/linear.md +8 -6
  30. package/templates/commands/monday.md +8 -6
  31. package/templates/commands/p.md +1 -1
  32. package/templates/commands/p.toml +37 -0
  33. package/templates/commands/sync.md +11 -1
  34. package/templates/cursor/p.md +29 -0
  35. package/templates/cursor/router.mdc +28 -0
  36. package/templates/global/CLAUDE.md +33 -1
  37. package/templates/global/CURSOR.mdc +233 -0
  38. package/templates/global/GEMINI.md +265 -0
  39. package/templates/global/STORAGE-SPEC.md +256 -0
  40. package/templates/global/docs/agents.md +88 -0
  41. package/templates/global/docs/architecture.md +103 -0
  42. package/templates/global/docs/commands.md +96 -0
  43. package/templates/global/docs/validation.md +95 -0
package/bin/prjct.ts CHANGED
@@ -3,17 +3,67 @@
3
3
  *
4
4
  * Auto-setup on first use (like Astro, Vite, etc.)
5
5
  * Supports both Bun and Node.js runtimes.
6
+ *
7
+ * IMPORTANT: postinstall.js often doesn't run, so we detect and
8
+ * auto-install on first CLI use. This is the reliable path.
6
9
  */
7
10
 
11
+ import fs from 'fs'
12
+ import path from 'path'
13
+ import os from 'os'
8
14
  import { VERSION } from '../core/utils/version'
9
15
  import editorsConfig from '../core/infrastructure/editors-config'
10
16
  import { startServer, DEFAULT_PORT } from '../core/server/server'
11
17
  import configManager from '../core/infrastructure/config-manager'
18
+ import { detectAllProviders } from '../core/infrastructure/ai-provider'
19
+
20
+ /**
21
+ * Check if routers are installed for detected providers
22
+ * Returns true if at least one provider has its router installed
23
+ */
24
+ function checkRoutersInstalled(): boolean {
25
+ const home = os.homedir()
26
+ const detection = detectAllProviders()
27
+
28
+ // Check Claude router
29
+ if (detection.claude.installed) {
30
+ const claudeRouter = path.join(home, '.claude', 'commands', 'p.md')
31
+ if (!fs.existsSync(claudeRouter)) {
32
+ return false
33
+ }
34
+ }
35
+
36
+ // Check Gemini router
37
+ if (detection.gemini.installed) {
38
+ const geminiRouter = path.join(home, '.gemini', 'commands', 'p.toml')
39
+ if (!fs.existsSync(geminiRouter)) {
40
+ return false
41
+ }
42
+ }
43
+
44
+ // If no providers detected, consider it "installed" (setup will handle)
45
+ if (!detection.claude.installed && !detection.gemini.installed) {
46
+ return true
47
+ }
48
+
49
+ return true
50
+ }
12
51
 
13
52
  // Check for special subcommands that bypass normal CLI
14
53
  const args = process.argv.slice(2)
15
54
 
16
- if (args[0] === 'dev') {
55
+ // Colors for output
56
+ const CYAN = '\x1b[36m'
57
+ const YELLOW = '\x1b[33m'
58
+ const DIM = '\x1b[2m'
59
+ const BOLD = '\x1b[1m'
60
+ const RESET = '\x1b[0m'
61
+
62
+ if (args[0] === 'start' || args[0] === 'setup') {
63
+ // Interactive setup with beautiful UI
64
+ const { runStart } = await import('../core/cli/start')
65
+ await runStart()
66
+ } else if (args[0] === 'dev') {
17
67
  // Dev mode - placeholder for future development server
18
68
  console.log('Dev mode is not yet implemented.')
19
69
  console.log('Use "prjct serve" to start the web server.')
@@ -35,26 +85,87 @@ if (args[0] === 'dev') {
35
85
  console.error('Server error:', (error as Error).message)
36
86
  process.exitCode = 1
37
87
  }
88
+ } else if (args[0] === 'version' || args[0] === '-v' || args[0] === '--version') {
89
+ // Show version with provider status
90
+ const detection = detectAllProviders()
91
+ const home = os.homedir()
92
+ const cwd = process.cwd()
93
+ const claudeConfigured = fs.existsSync(path.join(home, '.claude', 'commands', 'p.md'))
94
+ const geminiConfigured = fs.existsSync(path.join(home, '.gemini', 'commands', 'p.toml'))
95
+ const cursorDetected = fs.existsSync(path.join(cwd, '.cursor'))
96
+ const cursorConfigured = fs.existsSync(path.join(cwd, '.cursor', 'rules', 'prjct.mdc'))
97
+
98
+ const GREEN = '\x1b[32m'
99
+
100
+ console.log(`
101
+ ${CYAN}p/${RESET} prjct v${VERSION}
102
+ ${DIM}Context layer for AI coding agents${RESET}
103
+
104
+ ${DIM}Providers:${RESET}`)
105
+
106
+ // Claude status
107
+ if (detection.claude.installed) {
108
+ const status = claudeConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
109
+ const ver = detection.claude.version ? ` (v${detection.claude.version})` : ''
110
+ console.log(` Claude Code ${status}${DIM}${ver}${RESET}`)
111
+ } else {
112
+ console.log(` Claude Code ${DIM}○ not installed${RESET}`)
113
+ }
114
+
115
+ // Gemini status
116
+ if (detection.gemini.installed) {
117
+ const status = geminiConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
118
+ const ver = detection.gemini.version ? ` (v${detection.gemini.version})` : ''
119
+ console.log(` Gemini CLI ${status}${DIM}${ver}${RESET}`)
120
+ } else {
121
+ console.log(` Gemini CLI ${DIM}○ not installed${RESET}`)
122
+ }
123
+
124
+ // Cursor status (project-level)
125
+ if (cursorDetected) {
126
+ const status = cursorConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● detected${RESET}`
127
+ console.log(` Cursor IDE ${status}${DIM} (project)${RESET}`)
128
+ } else {
129
+ console.log(` Cursor IDE ${DIM}○ not detected${RESET}`)
130
+ }
131
+
132
+ console.log(`
133
+ ${DIM}Run 'prjct start' to configure (CLI providers)${RESET}
134
+ ${DIM}Run 'prjct init' to configure (Cursor IDE)${RESET}
135
+ ${CYAN}https://prjct.app${RESET}
136
+ `)
38
137
  } else {
39
- // Ensure setup has run for this version
40
- try {
41
- const lastVersion = await editorsConfig.getLastVersion()
138
+ // Check if setup has been done
139
+ const configPath = path.join(os.homedir(), '.prjct-cli', 'config', 'installed-editors.json')
140
+ const routersInstalled = checkRoutersInstalled()
141
+
142
+ if (!fs.existsSync(configPath) || !routersInstalled) {
143
+ // First time - prompt to run start
144
+ console.log(`
145
+ ${CYAN}${BOLD} Welcome to prjct!${RESET}
146
+
147
+ Run ${BOLD}prjct start${RESET} to configure your AI providers.
42
148
 
43
- if (!lastVersion || lastVersion !== VERSION) {
44
- console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
45
- console.log('🔧 One-time setup (v' + VERSION + ')...')
46
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')
149
+ ${DIM}This is a one-time setup that lets you choose between
150
+ Claude Code, Gemini CLI, or both.${RESET}
151
+ `)
152
+ process.exitCode = 0
153
+ } else {
154
+ // Check version and auto-update if needed
155
+ try {
156
+ const lastVersion = await editorsConfig.getLastVersion()
47
157
 
48
- const { default: setup } = await import('../core/infrastructure/setup')
49
- await setup.run()
158
+ if (lastVersion && lastVersion !== VERSION) {
159
+ console.log(`\n${YELLOW}ℹ${RESET} Updating prjct v${lastVersion} → v${VERSION}...\n`)
50
160
 
51
- console.log('✓ Setup complete!\n')
161
+ const { default: setup } = await import('../core/infrastructure/setup')
162
+ await setup.run()
163
+ }
164
+ } catch (error) {
165
+ // Silent fail on version check
52
166
  }
53
- } catch (error) {
54
- console.error('\n⚠️ Setup warning:', (error as Error).message)
55
- console.error('You can run setup manually: prjct setup\n')
56
- }
57
167
 
58
- // Continue to main CLI logic
59
- await import('../core/index')
168
+ // Continue to main CLI logic
169
+ await import('../core/index')
170
+ }
60
171
  }
@@ -27,7 +27,11 @@ export interface TemplateExecutionContext {
27
27
  command: string
28
28
  args: string
29
29
 
30
- // Paths for Claude (not content - Claude reads what it needs)
30
+ // Agent information
31
+ agentName: string
32
+ agentSettingsPath: string
33
+
34
+ // Paths for agent (not content)
31
35
  paths: {
32
36
  orchestrator: string
33
37
  agentRouting: string
@@ -85,6 +89,8 @@ export class TemplateExecutor {
85
89
  ): Promise<TemplateExecutionContext> {
86
90
  const projectId = await this.getProjectId(projectPath)
87
91
  const globalPath = pathManager.getGlobalProjectPath(projectId)
92
+ const aiProvider = require('../infrastructure/ai-provider')
93
+ const activeProvider = aiProvider.getActiveProvider()
88
94
 
89
95
  // Get templates directory - use local path during development
90
96
  let templatesDir: string
@@ -102,6 +108,8 @@ export class TemplateExecutor {
102
108
  globalPath,
103
109
  command,
104
110
  args,
111
+ agentName: activeProvider.displayName,
112
+ agentSettingsPath: pathManager.getAgentSettingsPath(),
105
113
  paths: {
106
114
  orchestrator: path.join(templatesDir, 'agentic', 'orchestrator.md'),
107
115
  agentRouting: path.join(templatesDir, 'agentic', 'agent-routing.md'),
@@ -109,7 +117,7 @@ export class TemplateExecutor {
109
117
  commandTemplate: path.join(templatesDir, 'commands', `${command}.md`),
110
118
  repoAnalysis: path.join(globalPath, 'analysis', 'repo-analysis.json'),
111
119
  agentsDir: path.join(globalPath, 'agents'),
112
- skillsDir: path.join(os.homedir(), '.claude', 'skills'),
120
+ skillsDir: activeProvider.skillsDir,
113
121
  stateJson: path.join(globalPath, 'storage', 'state.json'),
114
122
  }
115
123
  }
@@ -157,7 +165,7 @@ export class TemplateExecutor {
157
165
  }
158
166
 
159
167
  /**
160
- * Build prompt that tells Claude to execute templates agentically
168
+ * Build prompt that tells agent to execute templates agentically
161
169
  */
162
170
  buildAgenticPrompt(context: TemplateExecutionContext): AgenticPromptInfo {
163
171
  const requiresOrchestration = this.requiresOrchestration(context.command)
@@ -165,9 +173,11 @@ export class TemplateExecutor {
165
173
  const prompt = `
166
174
  ## Agentic Execution Mode
167
175
 
168
- You are executing a prjct command. Follow the template-first approach.
176
+ You are executing a prjct command as ${context.agentName}. Follow the template-first approach.
169
177
 
170
178
  ### Context
179
+ - Agent: ${context.agentName}
180
+ - Settings: ${context.agentSettingsPath}
171
181
  - Command: ${context.command}
172
182
  - Args: ${context.args}
173
183
  - Project: ${context.projectPath}
@@ -0,0 +1,397 @@
1
+ /**
2
+ * prjct start - Global initialization with beautiful UI
3
+ *
4
+ * First-time setup command that:
5
+ * 1. Shows beautiful ASCII banner
6
+ * 2. Detects available AI providers
7
+ * 3. Lets user select which to configure
8
+ * 4. Installs routers and global config
9
+ */
10
+
11
+ import fs from 'fs'
12
+ import path from 'path'
13
+ import os from 'os'
14
+ import readline from 'readline'
15
+ import { VERSION } from '../utils/version'
16
+ import { detectAllProviders, Providers } from '../infrastructure/ai-provider'
17
+ import type { AIProviderName } from '../types/provider'
18
+
19
+ // Colors
20
+ const RESET = '\x1b[0m'
21
+ const BOLD = '\x1b[1m'
22
+ const DIM = '\x1b[2m'
23
+ const GREEN = '\x1b[32m'
24
+ const YELLOW = '\x1b[33m'
25
+ const BLUE = '\x1b[34m'
26
+ const MAGENTA = '\x1b[35m'
27
+ const CYAN = '\x1b[36m'
28
+ const WHITE = '\x1b[37m'
29
+ const BG_BLUE = '\x1b[44m'
30
+
31
+ // True color gradient (cyan -> blue -> purple -> pink)
32
+ const G1 = '\x1b[38;2;0;255;255m' // Cyan
33
+ const G2 = '\x1b[38;2;80;180;255m' // Sky blue
34
+ const G3 = '\x1b[38;2;140;120;255m' // Blue-purple
35
+ const G4 = '\x1b[38;2;200;80;220m' // Purple
36
+ const G5 = '\x1b[38;2;255;80;180m' // Pink
37
+
38
+ // Large block letters - PRJCT (7 lines tall)
39
+ const BANNER = `
40
+
41
+ ${G1} ██████╗ ${G2} ██████╗ ${G3} ██╗${G4} ██████╗${G5}████████╗${RESET}
42
+ ${G1} ██╔══██╗${G2} ██╔══██╗${G3} ██║${G4}██╔════╝${G5}╚══██╔══╝${RESET}
43
+ ${G1} ██████╔╝${G2} ██████╔╝${G3} ██║${G4}██║ ${G5} ██║ ${RESET}
44
+ ${G1} ██╔═══╝ ${G2} ██╔══██╗${G3}██ ██║${G4}██║ ${G5} ██║ ${RESET}
45
+ ${G1} ██║ ${G2} ██║ ██║${G3}╚█████╔╝${G4}╚██████╗${G5} ██║ ${RESET}
46
+ ${G1} ╚═╝ ${G2} ╚═╝ ╚═╝${G3} ╚════╝ ${G4} ╚═════╝${G5} ╚═╝ ${RESET}
47
+
48
+ `
49
+
50
+ const WELCOME_BOX = ` ${WHITE}Context Layer for AI Agents${RESET} ${DIM}v${VERSION}${RESET}
51
+
52
+ ${DIM}Project context layer for AI coding agents.
53
+ Works with Claude Code, Gemini CLI, and more.${RESET}
54
+ ${CYAN}https://prjct.app${RESET}
55
+ `
56
+
57
+ interface ProviderOption {
58
+ name: AIProviderName
59
+ displayName: string
60
+ installed: boolean
61
+ selected: boolean
62
+ }
63
+
64
+ /**
65
+ * Create readline interface for user input
66
+ */
67
+ function createReadline(): readline.Interface {
68
+ return readline.createInterface({
69
+ input: process.stdin,
70
+ output: process.stdout,
71
+ })
72
+ }
73
+
74
+ /**
75
+ * Clear screen and show banner
76
+ */
77
+ function showBanner(): void {
78
+ // Clear screen
79
+ console.clear()
80
+ console.log(BANNER)
81
+ console.log(WELCOME_BOX)
82
+ }
83
+
84
+ /**
85
+ * Show provider selection UI
86
+ */
87
+ function showProviderSelection(options: ProviderOption[], currentIndex: number): void {
88
+ console.log(`\n${BOLD} Select AI providers to configure:${RESET}\n`)
89
+ console.log(` ${DIM}(Use arrow keys to navigate, space to toggle, enter to confirm)${RESET}\n`)
90
+
91
+ options.forEach((option, index) => {
92
+ const cursor = index === currentIndex ? `${CYAN}❯${RESET}` : ' '
93
+ const checkbox = option.selected ? `${GREEN}[✓]${RESET}` : `${DIM}[ ]${RESET}`
94
+ const status = option.installed
95
+ ? `${GREEN}(installed)${RESET}`
96
+ : `${YELLOW}(will install)${RESET}`
97
+ const name = index === currentIndex
98
+ ? `${BOLD}${option.displayName}${RESET}`
99
+ : option.displayName
100
+
101
+ console.log(` ${cursor} ${checkbox} ${name} ${status}`)
102
+ })
103
+
104
+ console.log('')
105
+ }
106
+
107
+ /**
108
+ * Interactive provider selection (with fallback for non-TTY)
109
+ */
110
+ async function selectProviders(): Promise<AIProviderName[]> {
111
+ const detection = detectAllProviders()
112
+
113
+ const options: ProviderOption[] = [
114
+ {
115
+ name: 'claude',
116
+ displayName: 'Claude Code',
117
+ installed: detection.claude.installed,
118
+ selected: detection.claude.installed,
119
+ },
120
+ {
121
+ name: 'gemini',
122
+ displayName: 'Gemini CLI',
123
+ installed: detection.gemini.installed,
124
+ selected: detection.gemini.installed,
125
+ },
126
+ ]
127
+
128
+ // If neither installed, select Claude by default
129
+ if (!options.some(o => o.selected)) {
130
+ options[0].selected = true
131
+ }
132
+
133
+ // Non-interactive mode: auto-select detected providers
134
+ if (!process.stdin.isTTY) {
135
+ console.log(`\n${BOLD} Detected providers:${RESET}\n`)
136
+ options.forEach(option => {
137
+ if (option.installed) {
138
+ console.log(` ${GREEN}✓${RESET} ${option.displayName}`)
139
+ }
140
+ })
141
+ console.log('')
142
+ return options.filter(o => o.selected).map(o => o.name)
143
+ }
144
+
145
+ return new Promise((resolve) => {
146
+ let currentIndex = 0
147
+
148
+ const render = () => {
149
+ process.stdout.write('\x1b[8A')
150
+ process.stdout.write('\x1b[0J')
151
+ showProviderSelection(options, currentIndex)
152
+ }
153
+
154
+ showProviderSelection(options, currentIndex)
155
+
156
+ process.stdin.setRawMode(true)
157
+ process.stdin.resume()
158
+ process.stdin.setEncoding('utf8')
159
+
160
+ const cleanup = () => {
161
+ process.stdin.setRawMode(false)
162
+ process.stdin.removeListener('data', handleKey)
163
+ process.stdin.pause()
164
+ }
165
+
166
+ const handleKey = (key: string) => {
167
+ if (key === '\u0003') {
168
+ cleanup()
169
+ console.log('\n Cancelled.\n')
170
+ process.exit(0)
171
+ }
172
+
173
+ if (key === '\r' || key === '\n') {
174
+ cleanup()
175
+ const selected = options.filter(o => o.selected).map(o => o.name)
176
+ resolve(selected.length > 0 ? selected : ['claude'])
177
+ return
178
+ }
179
+
180
+ if (key === '\x1b[A') {
181
+ currentIndex = Math.max(0, currentIndex - 1)
182
+ render()
183
+ }
184
+
185
+ if (key === '\x1b[B') {
186
+ currentIndex = Math.min(options.length - 1, currentIndex + 1)
187
+ render()
188
+ }
189
+
190
+ if (key === ' ') {
191
+ options[currentIndex].selected = !options[currentIndex].selected
192
+ render()
193
+ }
194
+ }
195
+
196
+ process.stdin.on('data', handleKey)
197
+ })
198
+ }
199
+
200
+ /**
201
+ * Install router for a CLI-based provider (Claude/Gemini)
202
+ * Note: Cursor uses project-level config, not global
203
+ */
204
+ async function installRouter(provider: AIProviderName): Promise<boolean> {
205
+ const config = Providers[provider]
206
+
207
+ // Skip project-level providers (Cursor)
208
+ if (!config.configDir) {
209
+ return false
210
+ }
211
+
212
+ try {
213
+ // Create commands directory
214
+ const commandsDir = path.join(config.configDir, 'commands')
215
+ fs.mkdirSync(commandsDir, { recursive: true })
216
+
217
+ // Find package root (where templates are)
218
+ const { getPackageRoot } = await import('../utils/version')
219
+ const packageRoot = getPackageRoot()
220
+
221
+ // Copy router file
222
+ const routerFile = provider === 'claude' ? 'p.md' : 'p.toml'
223
+ const src = path.join(packageRoot, 'templates', 'commands', routerFile)
224
+ const dest = path.join(commandsDir, routerFile)
225
+
226
+ if (fs.existsSync(src)) {
227
+ fs.copyFileSync(src, dest)
228
+ return true
229
+ }
230
+
231
+ return false
232
+ } catch (error) {
233
+ console.error(` ${YELLOW}⚠${RESET} Failed to install ${provider} router: ${(error as Error).message}`)
234
+ return false
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Install global config for a CLI-based provider (Claude/Gemini)
240
+ * Note: Cursor uses project-level config, not global
241
+ */
242
+ async function installGlobalConfig(provider: AIProviderName): Promise<boolean> {
243
+ const config = Providers[provider]
244
+
245
+ // Skip project-level providers (Cursor)
246
+ if (!config.configDir) {
247
+ return false
248
+ }
249
+
250
+ try {
251
+ // Ensure config directory exists
252
+ fs.mkdirSync(config.configDir, { recursive: true })
253
+
254
+ // Find package root
255
+ const { getPackageRoot } = await import('../utils/version')
256
+ const packageRoot = getPackageRoot()
257
+
258
+ // Copy global config
259
+ const configFile = provider === 'claude' ? 'CLAUDE.md' : 'GEMINI.md'
260
+ const src = path.join(packageRoot, 'templates', 'global', configFile)
261
+ const dest = path.join(config.configDir, configFile)
262
+
263
+ if (fs.existsSync(src)) {
264
+ const content = fs.readFileSync(src, 'utf-8')
265
+
266
+ // Check if file exists and has our markers
267
+ if (fs.existsSync(dest)) {
268
+ const existing = fs.readFileSync(dest, 'utf-8')
269
+ const startMarker = '<!-- prjct:start - DO NOT REMOVE THIS MARKER -->'
270
+ const endMarker = '<!-- prjct:end - DO NOT REMOVE THIS MARKER -->'
271
+
272
+ if (existing.includes(startMarker) && existing.includes(endMarker)) {
273
+ // Replace between markers
274
+ const before = existing.substring(0, existing.indexOf(startMarker))
275
+ const after = existing.substring(existing.indexOf(endMarker) + endMarker.length)
276
+ const prjctSection = content.substring(
277
+ content.indexOf(startMarker),
278
+ content.indexOf(endMarker) + endMarker.length
279
+ )
280
+ fs.writeFileSync(dest, before + prjctSection + after)
281
+ } else {
282
+ // Append
283
+ fs.writeFileSync(dest, existing + '\n\n' + content)
284
+ }
285
+ } else {
286
+ // Create new
287
+ fs.writeFileSync(dest, content)
288
+ }
289
+
290
+ return true
291
+ }
292
+
293
+ return false
294
+ } catch (error) {
295
+ console.error(` ${YELLOW}⚠${RESET} Failed to install ${provider} config: ${(error as Error).message}`)
296
+ return false
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Save setup configuration
302
+ */
303
+ async function saveSetupConfig(providers: AIProviderName[]): Promise<void> {
304
+ const configDir = path.join(os.homedir(), '.prjct-cli', 'config')
305
+ fs.mkdirSync(configDir, { recursive: true })
306
+
307
+ const configPath = path.join(configDir, 'installed-editors.json')
308
+ const config = {
309
+ version: VERSION,
310
+ providers,
311
+ editor: providers[0], // deprecated, for backward compat
312
+ provider: providers[0],
313
+ lastInstall: new Date().toISOString(),
314
+ path: path.join(os.homedir(), `.${providers[0]}`, 'commands'),
315
+ }
316
+
317
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
318
+ }
319
+
320
+ /**
321
+ * Show completion message
322
+ */
323
+ function showCompletion(providers: AIProviderName[]): void {
324
+ console.log(`\n${GREEN}${BOLD} ✓ Setup complete!${RESET}\n`)
325
+
326
+ console.log(` ${DIM}Configured providers:${RESET}`)
327
+ providers.forEach(p => {
328
+ const config = Providers[p]
329
+ console.log(` ${GREEN}✓${RESET} ${config.displayName}`)
330
+ })
331
+
332
+ console.log(`
333
+ ${BOLD}Next steps:${RESET}
334
+
335
+ ${CYAN}1.${RESET} Navigate to your project directory
336
+ ${CYAN}2.${RESET} Run ${BOLD}p. init${RESET} to initialize prjct for that project
337
+ ${CYAN}3.${RESET} Start tracking with ${BOLD}p. task "your task"${RESET}
338
+
339
+ ${DIM}Tips:${RESET}
340
+ ${DIM}•${RESET} Use ${BOLD}p. sync${RESET} to analyze your codebase
341
+ ${DIM}•${RESET} Use ${BOLD}p. done${RESET} to complete tasks
342
+ ${DIM}•${RESET} Use ${BOLD}p. ship${RESET} to create PRs
343
+
344
+ ${DIM}Learn more: https://prjct.app/docs${RESET}
345
+ `)
346
+ }
347
+
348
+ /**
349
+ * Main start command
350
+ */
351
+ export async function runStart(): Promise<void> {
352
+ showBanner()
353
+
354
+ // Check if already configured
355
+ const configPath = path.join(os.homedir(), '.prjct-cli', 'config', 'installed-editors.json')
356
+ if (fs.existsSync(configPath)) {
357
+ const existing = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
358
+ if (existing.version === VERSION) {
359
+ console.log(` ${YELLOW}ℹ${RESET} Already configured for v${VERSION}`)
360
+ console.log(` ${DIM}Run with --force to reconfigure${RESET}\n`)
361
+
362
+ if (!process.argv.includes('--force')) {
363
+ return
364
+ }
365
+ }
366
+ }
367
+
368
+ // Select providers
369
+ const selectedProviders = await selectProviders()
370
+
371
+ console.log(`\n ${CYAN}Setting up...${RESET}\n`)
372
+
373
+ // Install for each selected provider
374
+ for (const provider of selectedProviders) {
375
+ const config = Providers[provider]
376
+ process.stdout.write(` ${DIM}•${RESET} ${config.displayName}... `)
377
+
378
+ const routerOk = await installRouter(provider)
379
+ const configOk = await installGlobalConfig(provider)
380
+
381
+ if (routerOk && configOk) {
382
+ console.log(`${GREEN}✓${RESET}`)
383
+ } else if (routerOk || configOk) {
384
+ console.log(`${YELLOW}partial${RESET}`)
385
+ } else {
386
+ console.log(`${YELLOW}skipped${RESET}`)
387
+ }
388
+ }
389
+
390
+ // Save configuration
391
+ await saveSetupConfig(selectedProviders)
392
+
393
+ // Show completion
394
+ showCompletion(selectedProviders)
395
+ }
396
+
397
+ export default { runStart }
@@ -74,14 +74,17 @@ export class AnalysisCommands extends PrjctCommandsBase {
74
74
 
75
75
  await generateContext(projectId!, projectPath)
76
76
 
77
+ const aiProvider = require('../infrastructure/ai-provider')
78
+ const activeProvider = aiProvider.getActiveProvider()
79
+
77
80
  const globalConfigResult = await commandInstaller.installGlobalConfig()
78
81
  if (globalConfigResult.success) {
79
- console.log('📝 Updated ~/.claude/CLAUDE.md')
82
+ console.log(`📝 Updated ${pathManager.getDisplayPath(globalConfigResult.path!)}`)
80
83
  }
81
84
 
82
85
  console.log('✅ Analysis complete!\n')
83
86
  console.log('📄 Full report: analysis/repo-summary.md')
84
- console.log('📝 Context: ~/.prjct-cli/projects/' + projectId + '/CLAUDE.md\n')
87
+ console.log(`📝 Context: ~/.prjct-cli/projects/${projectId}/${activeProvider.contextFile}\n`)
85
88
  console.log('Next steps:')
86
89
  console.log('• /p:sync → Generate agents based on stack')
87
90
  console.log('• /p:feature → Add a new feature')
@@ -220,7 +223,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
220
223
  // Update global config
221
224
  const globalConfigResult = await commandInstaller.installGlobalConfig()
222
225
  if (globalConfigResult.success) {
223
- console.log('📝 Updated ~/.claude/CLAUDE.md')
226
+ console.log(`📝 Updated ${pathManager.getDisplayPath(globalConfigResult.path!)}`)
224
227
  }
225
228
 
226
229
  // Format output