prjct-cli 1.2.0 → 1.2.1

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.1] - 2026-02-06
4
+
5
+ ### Bug Fixes
6
+
7
+ - replace raw ANSI codes with chalk library (PRJ-132) (#111)
8
+ - replace raw ANSI codes with chalk library (PRJ-132)
9
+
10
+
3
11
  ## [1.2.0] - 2026-02-06
4
12
 
5
13
  ### Features
@@ -56,6 +64,46 @@ Hook configuration saved to `project.json` for persistence across sessions.
56
64
  **How to use:** Run `prjct hooks install` in any prjct project
57
65
  **Breaking changes:** None
58
66
 
67
+ ## [1.1.2] - 2026-02-05
68
+
69
+ ### Fixed
70
+
71
+ - **Replace raw ANSI codes with chalk library (PRJ-132)**: Replaced ~60 raw ANSI escape codes across 7 files with chalk library calls
72
+
73
+ ### Implementation Details
74
+
75
+ Replaced all raw ANSI color/formatting constants (`\x1b[32m`, `\x1b[1m`, etc.) with chalk equivalents (`chalk.green()`, `chalk.bold()`, etc.) in:
76
+ - `bin/prjct.ts` — version display, provider status, welcome message
77
+ - `core/index.ts` — version display, provider status
78
+ - `core/cli/start.ts` — gradient banner using `chalk.rgb()`, setup UI
79
+ - `core/utils/subtask-table.ts` — color palette as chalk functions, progress display
80
+ - `core/utils/help.ts` — all help formatting
81
+ - `core/workflow/workflow-preferences.ts` — hook status display
82
+ - `core/infrastructure/setup.ts` — installation messages
83
+
84
+ Terminal control sequences (cursor movement, hide/show) kept as raw ANSI since chalk only handles colors/formatting.
85
+
86
+ ### Learnings
87
+
88
+ - `chalk.rgb(R,G,B)` replaces `\x1b[38;2;R;G;Bm` for true-color support
89
+ - Chalk functions can be stored as array values and called dynamically (useful for color palettes)
90
+ - `chalk.bold.white()` chains work for compound styling
91
+ - Terminal control sequences (`\x1b[?25l`, cursor movement) must stay raw — chalk doesn't handle them
92
+
93
+ ### Test Plan
94
+
95
+ #### For QA
96
+ 1. Run `prjct --version` — verify colored output with provider status
97
+ 2. Run `prjct help` — verify formatted help with colors
98
+ 3. Run `prjct start --force` — verify gradient banner and colored UI
99
+ 4. Set `NO_COLOR=1` and repeat above — verify all color is suppressed
100
+ 5. Run `bun run build && bun run typecheck` — verify zero errors
101
+
102
+ #### For Users
103
+ **What changed:** Terminal colors now use the chalk library instead of raw ANSI codes
104
+ **How to use:** No change — colors appear the same but now respect `NO_COLOR` env variable
105
+ **Breaking changes:** None
106
+
59
107
  ## [1.1.1] - 2026-02-06
60
108
 
61
109
  ### Bug Fixes
package/bin/prjct.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  import fs from 'node:fs'
12
12
  import os from 'node:os'
13
13
  import path from 'node:path'
14
+ import chalk from 'chalk'
14
15
  import { detectAllProviders } from '../core/infrastructure/ai-provider'
15
16
  import configManager from '../core/infrastructure/config-manager'
16
17
  import editorsConfig from '../core/infrastructure/editors-config'
@@ -61,12 +62,7 @@ if (isQuietMode) {
61
62
  setQuietMode(true)
62
63
  }
63
64
 
64
- // Colors for output
65
- const CYAN = '\x1b[36m'
66
- const YELLOW = '\x1b[33m'
67
- const DIM = '\x1b[2m'
68
- const BOLD = '\x1b[1m'
69
- const RESET = '\x1b[0m'
65
+ // Colors for output (chalk respects NO_COLOR env)
70
66
 
71
67
  if (args[0] === 'start' || args[0] === 'setup') {
72
68
  // Interactive setup with beautiful UI
@@ -206,52 +202,50 @@ if (args[0] === 'start' || args[0] === 'setup') {
206
202
  const windsurfDetected = fs.existsSync(path.join(cwd, '.windsurf'))
207
203
  const windsurfConfigured = fs.existsSync(path.join(cwd, '.windsurf', 'rules', 'prjct.md'))
208
204
 
209
- const GREEN = '\x1b[32m'
210
-
211
205
  console.log(`
212
- ${CYAN}p/${RESET} prjct v${VERSION}
213
- ${DIM}Context layer for AI coding agents${RESET}
206
+ ${chalk.cyan('p/')} prjct v${VERSION}
207
+ ${chalk.dim('Context layer for AI coding agents')}
214
208
 
215
- ${DIM}Providers:${RESET}`)
209
+ ${chalk.dim('Providers:')}`)
216
210
 
217
211
  // Claude status
218
212
  if (detection.claude.installed) {
219
- const status = claudeConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
213
+ const status = claudeConfigured ? chalk.green('✓ ready') : chalk.yellow('● installed')
220
214
  const ver = detection.claude.version ? ` (v${detection.claude.version})` : ''
221
- console.log(` Claude Code ${status}${DIM}${ver}${RESET}`)
215
+ console.log(` Claude Code ${status}${chalk.dim(ver)}`)
222
216
  } else {
223
- console.log(` Claude Code ${DIM}○ not installed${RESET}`)
217
+ console.log(` Claude Code ${chalk.dim('○ not installed')}`)
224
218
  }
225
219
 
226
220
  // Gemini status
227
221
  if (detection.gemini.installed) {
228
- const status = geminiConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
222
+ const status = geminiConfigured ? chalk.green('✓ ready') : chalk.yellow('● installed')
229
223
  const ver = detection.gemini.version ? ` (v${detection.gemini.version})` : ''
230
- console.log(` Gemini CLI ${status}${DIM}${ver}${RESET}`)
224
+ console.log(` Gemini CLI ${status}${chalk.dim(ver)}`)
231
225
  } else {
232
- console.log(` Gemini CLI ${DIM}○ not installed${RESET}`)
226
+ console.log(` Gemini CLI ${chalk.dim('○ not installed')}`)
233
227
  }
234
228
 
235
229
  // Cursor status (project-level)
236
230
  if (cursorDetected) {
237
- const status = cursorConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● detected${RESET}`
238
- console.log(` Cursor IDE ${status}${DIM} (project)${RESET}`)
231
+ const status = cursorConfigured ? chalk.green('✓ ready') : chalk.yellow('● detected')
232
+ console.log(` Cursor IDE ${status}${chalk.dim(' (project)')}`)
239
233
  } else {
240
- console.log(` Cursor IDE ${DIM}○ not detected${RESET}`)
234
+ console.log(` Cursor IDE ${chalk.dim('○ not detected')}`)
241
235
  }
242
236
 
243
237
  // Windsurf status (project-level)
244
238
  if (windsurfDetected) {
245
- const status = windsurfConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● detected${RESET}`
246
- console.log(` Windsurf IDE ${status}${DIM} (project)${RESET}`)
239
+ const status = windsurfConfigured ? chalk.green('✓ ready') : chalk.yellow('● detected')
240
+ console.log(` Windsurf IDE ${status}${chalk.dim(' (project)')}`)
247
241
  } else {
248
- console.log(` Windsurf IDE ${DIM}○ not detected${RESET}`)
242
+ console.log(` Windsurf IDE ${chalk.dim('○ not detected')}`)
249
243
  }
250
244
 
251
245
  console.log(`
252
- ${DIM}Run 'prjct start' to configure (CLI providers)${RESET}
253
- ${DIM}Run 'prjct init' to configure (Cursor/Windsurf IDE)${RESET}
254
- ${CYAN}https://prjct.app${RESET}
246
+ ${chalk.dim("Run 'prjct start' to configure (CLI providers)")}
247
+ ${chalk.dim("Run 'prjct init' to configure (Cursor/Windsurf IDE)")}
248
+ ${chalk.cyan('https://prjct.app')}
255
249
  `)
256
250
  } else {
257
251
  // Check if setup has been done
@@ -261,12 +255,12 @@ ${CYAN}https://prjct.app${RESET}
261
255
  if (!fs.existsSync(configPath) || !routersInstalled) {
262
256
  // First time - prompt to run start
263
257
  console.log(`
264
- ${CYAN}${BOLD} Welcome to prjct!${RESET}
258
+ ${chalk.cyan.bold(' Welcome to prjct!')}
265
259
 
266
- Run ${BOLD}prjct start${RESET} to configure your AI providers.
260
+ Run ${chalk.bold('prjct start')} to configure your AI providers.
267
261
 
268
- ${DIM}This is a one-time setup that lets you choose between
269
- Claude Code, Gemini CLI, or both.${RESET}
262
+ ${chalk.dim(`This is a one-time setup that lets you choose between
263
+ Claude Code, Gemini CLI, or both.`)}
270
264
  `)
271
265
  process.exitCode = 0
272
266
  } else {
@@ -275,7 +269,7 @@ ${CYAN}${BOLD} Welcome to prjct!${RESET}
275
269
  const lastVersion = await editorsConfig.getLastVersion()
276
270
 
277
271
  if (lastVersion && lastVersion !== VERSION) {
278
- console.log(`\n${YELLOW}ℹ${RESET} Updating prjct v${lastVersion} → v${VERSION}...\n`)
272
+ console.log(`\n${chalk.yellow('ℹ')} Updating prjct v${lastVersion} → v${VERSION}...\n`)
279
273
 
280
274
  const { default: setup } = await import('../core/infrastructure/setup')
281
275
  await setup.run()
package/core/cli/start.ts CHANGED
@@ -12,46 +12,35 @@ import fs from 'node:fs'
12
12
  import os from 'node:os'
13
13
  import path from 'node:path'
14
14
  import readline from 'node:readline'
15
+ import chalk from 'chalk'
15
16
  import { detectAllProviders, Providers } from '../infrastructure/ai-provider'
16
17
  import type { AIProviderName } from '../types/provider'
17
18
  import { VERSION } from '../utils/version'
18
19
 
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
20
  // 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
21
+ const G1 = chalk.rgb(0, 255, 255)
22
+ const G2 = chalk.rgb(80, 180, 255)
23
+ const G3 = chalk.rgb(140, 120, 255)
24
+ const G4 = chalk.rgb(200, 80, 220)
25
+ const G5 = chalk.rgb(255, 80, 180)
37
26
 
38
27
  // Large block letters - PRJCT (7 lines tall)
39
28
  const BANNER = `
40
29
 
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}
30
+ ${G1(' ██████╗ ')}${G2(' ██████╗ ')}${G3(' ██╗')}${G4(' ██████╗')}${G5('████████╗')}
31
+ ${G1(' ██╔══██╗')}${G2(' ██╔══██╗')}${G3(' ██║')}${G4('██╔════╝')}${G5('╚══██╔══╝')}
32
+ ${G1(' ██████╔╝')}${G2(' ██████╔╝')}${G3(' ██║')}${G4('██║ ')}${G5(' ██║ ')}
33
+ ${G1(' ██╔═══╝ ')}${G2(' ██╔══██╗')}${G3('██ ██║')}${G4('██║ ')}${G5(' ██║ ')}
34
+ ${G1(' ██║ ')}${G2(' ██║ ██║')}${G3('╚█████╔╝')}${G4('╚██████╗')}${G5(' ██║ ')}
35
+ ${G1(' ╚═╝ ')}${G2(' ╚═╝ ╚═╝')}${G3(' ╚════╝ ')}${G4(' ╚═════╝')}${G5(' ╚═╝ ')}
47
36
 
48
37
  `
49
38
 
50
- const WELCOME_BOX = ` ${WHITE}Context Layer for AI Agents${RESET} ${DIM}v${VERSION}${RESET}
39
+ const WELCOME_BOX = ` ${chalk.white('Context Layer for AI Agents')} ${chalk.dim(`v${VERSION}`)}
51
40
 
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}
41
+ ${chalk.dim(`Project context layer for AI coding agents.
42
+ Works with Claude Code, Gemini CLI, and more.`)}
43
+ ${chalk.cyan('https://prjct.app')}
55
44
  `
56
45
 
57
46
  interface ProviderOption {
@@ -85,17 +74,14 @@ function showBanner(): void {
85
74
  * Show provider selection UI
86
75
  */
87
76
  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`)
77
+ console.log(`\n${chalk.bold(' Select AI providers to configure:')}\n`)
78
+ console.log(` ${chalk.dim('(Use arrow keys to navigate, space to toggle, enter to confirm)')}\n`)
90
79
 
91
80
  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 =
98
- index === currentIndex ? `${BOLD}${option.displayName}${RESET}` : option.displayName
81
+ const cursor = index === currentIndex ? chalk.cyan('❯') : ' '
82
+ const checkbox = option.selected ? chalk.green('[✓]') : chalk.dim('[ ]')
83
+ const status = option.installed ? chalk.green('(installed)') : chalk.yellow('(will install)')
84
+ const name = index === currentIndex ? chalk.bold(option.displayName) : option.displayName
99
85
 
100
86
  console.log(` ${cursor} ${checkbox} ${name} ${status}`)
101
87
  })
@@ -131,10 +117,10 @@ async function selectProviders(): Promise<AIProviderName[]> {
131
117
 
132
118
  // Non-interactive mode: auto-select detected providers
133
119
  if (!process.stdin.isTTY) {
134
- console.log(`\n${BOLD} Detected providers:${RESET}\n`)
120
+ console.log(`\n${chalk.bold(' Detected providers:')}\n`)
135
121
  options.forEach((option) => {
136
122
  if (option.installed) {
137
- console.log(` ${GREEN}✓${RESET} ${option.displayName}`)
123
+ console.log(` ${chalk.green('✓')} ${option.displayName}`)
138
124
  }
139
125
  })
140
126
  console.log('')
@@ -230,7 +216,7 @@ async function installRouter(provider: AIProviderName): Promise<boolean> {
230
216
  return false
231
217
  } catch (error) {
232
218
  console.error(
233
- ` ${YELLOW}⚠${RESET} Failed to install ${provider} router: ${(error as Error).message}`
219
+ ` ${chalk.yellow('⚠')} Failed to install ${provider} router: ${(error as Error).message}`
234
220
  )
235
221
  return false
236
222
  }
@@ -294,7 +280,7 @@ async function installGlobalConfig(provider: AIProviderName): Promise<boolean> {
294
280
  return false
295
281
  } catch (error) {
296
282
  console.error(
297
- ` ${YELLOW}⚠${RESET} Failed to install ${provider} config: ${(error as Error).message}`
283
+ ` ${chalk.yellow('⚠')} Failed to install ${provider} config: ${(error as Error).message}`
298
284
  )
299
285
  return false
300
286
  }
@@ -324,27 +310,27 @@ async function saveSetupConfig(providers: AIProviderName[]): Promise<void> {
324
310
  * Show completion message
325
311
  */
326
312
  function showCompletion(providers: AIProviderName[]): void {
327
- console.log(`\n${GREEN}${BOLD} ✓ Setup complete!${RESET}\n`)
313
+ console.log(`\n${chalk.green.bold(' ✓ Setup complete!')}\n`)
328
314
 
329
- console.log(` ${DIM}Configured providers:${RESET}`)
315
+ console.log(` ${chalk.dim('Configured providers:')}`)
330
316
  providers.forEach((p) => {
331
317
  const config = Providers[p]
332
- console.log(` ${GREEN}✓${RESET} ${config.displayName}`)
318
+ console.log(` ${chalk.green('✓')} ${config.displayName}`)
333
319
  })
334
320
 
335
321
  console.log(`
336
- ${BOLD}Next steps:${RESET}
322
+ ${chalk.bold('Next steps:')}
337
323
 
338
- ${CYAN}1.${RESET} Navigate to your project directory
339
- ${CYAN}2.${RESET} Run ${BOLD}p. init${RESET} to initialize prjct for that project
340
- ${CYAN}3.${RESET} Start tracking with ${BOLD}p. task "your task"${RESET}
324
+ ${chalk.cyan('1.')} Navigate to your project directory
325
+ ${chalk.cyan('2.')} Run ${chalk.bold('p. init')} to initialize prjct for that project
326
+ ${chalk.cyan('3.')} Start tracking with ${chalk.bold('p. task "your task"')}
341
327
 
342
- ${DIM}Tips:${RESET}
343
- ${DIM}•${RESET} Use ${BOLD}p. sync${RESET} to analyze your codebase
344
- ${DIM}•${RESET} Use ${BOLD}p. done${RESET} to complete tasks
345
- ${DIM}•${RESET} Use ${BOLD}p. ship${RESET} to create PRs
328
+ ${chalk.dim('Tips:')}
329
+ ${chalk.dim('•')} Use ${chalk.bold('p. sync')} to analyze your codebase
330
+ ${chalk.dim('•')} Use ${chalk.bold('p. done')} to complete tasks
331
+ ${chalk.dim('•')} Use ${chalk.bold('p. ship')} to create PRs
346
332
 
347
- ${DIM}Learn more: https://prjct.app/docs${RESET}
333
+ ${chalk.dim('Learn more: https://prjct.app/docs')}
348
334
  `)
349
335
  }
350
336
 
@@ -359,8 +345,8 @@ export async function runStart(): Promise<void> {
359
345
  if (fs.existsSync(configPath)) {
360
346
  const existing = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
361
347
  if (existing.version === VERSION) {
362
- console.log(` ${YELLOW}ℹ${RESET} Already configured for v${VERSION}`)
363
- console.log(` ${DIM}Run with --force to reconfigure${RESET}\n`)
348
+ console.log(` ${chalk.yellow('ℹ')} Already configured for v${VERSION}`)
349
+ console.log(` ${chalk.dim('Run with --force to reconfigure')}\n`)
364
350
 
365
351
  if (!process.argv.includes('--force')) {
366
352
  return
@@ -371,22 +357,22 @@ export async function runStart(): Promise<void> {
371
357
  // Select providers
372
358
  const selectedProviders = await selectProviders()
373
359
 
374
- console.log(`\n ${CYAN}Setting up...${RESET}\n`)
360
+ console.log(`\n ${chalk.cyan('Setting up...')}\n`)
375
361
 
376
362
  // Install for each selected provider
377
363
  for (const provider of selectedProviders) {
378
364
  const config = Providers[provider]
379
- process.stdout.write(` ${DIM}•${RESET} ${config.displayName}... `)
365
+ process.stdout.write(` ${chalk.dim('•')} ${config.displayName}... `)
380
366
 
381
367
  const routerOk = await installRouter(provider)
382
368
  const configOk = await installGlobalConfig(provider)
383
369
 
384
370
  if (routerOk && configOk) {
385
- console.log(`${GREEN}✓${RESET}`)
371
+ console.log(chalk.green('✓'))
386
372
  } else if (routerOk || configOk) {
387
- console.log(`${YELLOW}partial${RESET}`)
373
+ console.log(chalk.yellow('partial'))
388
374
  } else {
389
- console.log(`${YELLOW}skipped${RESET}`)
375
+ console.log(chalk.yellow('skipped'))
390
376
  }
391
377
  }
392
378
 
package/core/index.ts CHANGED
@@ -10,6 +10,7 @@ import './commands/register' // Ensure commands are registered
10
10
  import fs from 'node:fs'
11
11
  import os from 'node:os'
12
12
  import path from 'node:path'
13
+ import chalk from 'chalk'
13
14
  import type { CommandMeta } from './commands/registry'
14
15
  import { detectAllProviders, detectAntigravity } from './infrastructure/ai-provider'
15
16
  import out from './utils/output'
@@ -195,12 +196,7 @@ function parseCommandArgs(_cmd: CommandMeta, rawArgs: string[]): ParsedCommandAr
195
196
  return { parsedArgs, options }
196
197
  }
197
198
 
198
- // Colors for version display
199
- const CYAN = '\x1b[36m'
200
- const GREEN = '\x1b[32m'
201
- const YELLOW = '\x1b[33m'
202
- const DIM = '\x1b[2m'
203
- const RESET = '\x1b[0m'
199
+ // Colors via chalk (respects NO_COLOR env)
204
200
 
205
201
  /**
206
202
  * Display version with provider status
@@ -219,53 +215,53 @@ function displayVersion(version: string): void {
219
215
  const cursorExists = fs.existsSync(path.join(process.cwd(), '.cursor'))
220
216
 
221
217
  console.log(`
222
- ${CYAN}p/${RESET} prjct v${version}
223
- ${DIM}Context layer for AI coding agents${RESET}
218
+ ${chalk.cyan('p/')} prjct v${version}
219
+ ${chalk.dim('Context layer for AI coding agents')}
224
220
 
225
- ${DIM}Providers:${RESET}`)
221
+ ${chalk.dim('Providers:')}`)
226
222
 
227
223
  // Claude status
228
224
  if (detection.claude.installed) {
229
- const status = claudeConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
225
+ const status = claudeConfigured ? chalk.green('✓ ready') : chalk.yellow('● installed')
230
226
  const ver = detection.claude.version ? ` (v${detection.claude.version})` : ''
231
- console.log(` Claude Code ${status}${DIM}${ver}${RESET}`)
227
+ console.log(` Claude Code ${status}${chalk.dim(ver)}`)
232
228
  } else {
233
- console.log(` Claude Code ${DIM}○ not installed${RESET}`)
229
+ console.log(` Claude Code ${chalk.dim('○ not installed')}`)
234
230
  }
235
231
 
236
232
  // Gemini status
237
233
  if (detection.gemini.installed) {
238
- const status = geminiConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
234
+ const status = geminiConfigured ? chalk.green('✓ ready') : chalk.yellow('● installed')
239
235
  const ver = detection.gemini.version ? ` (v${detection.gemini.version})` : ''
240
- console.log(` Gemini CLI ${status}${DIM}${ver}${RESET}`)
236
+ console.log(` Gemini CLI ${status}${chalk.dim(ver)}`)
241
237
  } else {
242
- console.log(` Gemini CLI ${DIM}○ not installed${RESET}`)
238
+ console.log(` Gemini CLI ${chalk.dim('○ not installed')}`)
243
239
  }
244
240
 
245
241
  // Antigravity status (global, skills-based)
246
242
  const antigravityDetection = detectAntigravity()
247
243
  if (antigravityDetection.installed) {
248
244
  const status = antigravityDetection.skillInstalled
249
- ? `${GREEN}✓ ready${RESET}`
250
- : `${YELLOW}● detected${RESET}`
251
- const hint = antigravityDetection.skillInstalled ? '' : ` ${DIM}(run prjct start)${RESET}`
245
+ ? chalk.green('✓ ready')
246
+ : chalk.yellow('● detected')
247
+ const hint = antigravityDetection.skillInstalled ? '' : ` ${chalk.dim('(run prjct start)')}`
252
248
  console.log(` Antigravity ${status}${hint}`)
253
249
  } else {
254
- console.log(` Antigravity ${DIM}○ not installed${RESET}`)
250
+ console.log(` Antigravity ${chalk.dim('○ not installed')}`)
255
251
  }
256
252
 
257
253
  // Cursor status (project-level, but shown in same format)
258
254
  if (cursorConfigured) {
259
- console.log(` Cursor IDE ${GREEN}✓ ready${RESET} ${DIM}(use /sync, /task)${RESET}`)
255
+ console.log(` Cursor IDE ${chalk.green('✓ ready')} ${chalk.dim('(use /sync, /task)')}`)
260
256
  } else if (cursorExists) {
261
- console.log(` Cursor IDE ${YELLOW}● detected${RESET} ${DIM}(run prjct init)${RESET}`)
257
+ console.log(` Cursor IDE ${chalk.yellow('● detected')} ${chalk.dim('(run prjct init)')}`)
262
258
  } else {
263
- console.log(` Cursor IDE ${DIM}○ no .cursor/ folder${RESET}`)
259
+ console.log(` Cursor IDE ${chalk.dim('○ no .cursor/ folder')}`)
264
260
  }
265
261
 
266
262
  console.log(`
267
- ${DIM}Run 'prjct start' for Claude/Gemini, 'prjct init' for Cursor${RESET}
268
- ${CYAN}https://prjct.app${RESET}
263
+ ${chalk.dim("Run 'prjct start' for Claude/Gemini, 'prjct init' for Cursor")}
264
+ ${chalk.cyan('https://prjct.app')}
269
265
  `)
270
266
  }
271
267
 
@@ -21,6 +21,7 @@ import { execSync } from 'node:child_process'
21
21
  import fs from 'node:fs'
22
22
  import os from 'node:os'
23
23
  import path from 'node:path'
24
+ import chalk from 'chalk'
24
25
  import { getTimeout } from '../constants'
25
26
  import { dependencyValidator } from '../services/dependency-validator'
26
27
  import { isNotFoundError } from '../types/fs'
@@ -36,12 +37,6 @@ import {
36
37
  import installer from './command-installer'
37
38
  import editorsConfig from './editors-config'
38
39
 
39
- // Colors
40
- const GREEN = '\x1b[32m'
41
- const YELLOW = '\x1b[33m'
42
- const DIM = '\x1b[2m'
43
- const NC = '\x1b[0m'
44
-
45
40
  interface ProviderSetupResult {
46
41
  provider: AIProviderName
47
42
  cliInstalled: boolean
@@ -77,20 +72,22 @@ async function installAICLI(provider: AIProviderConfig): Promise<boolean> {
77
72
 
78
73
  // PRJ-114: Check npm availability first
79
74
  if (!dependencyValidator.isAvailable('npm')) {
80
- console.log(`${YELLOW}⚠️ npm is not available${NC}`)
75
+ console.log(`${chalk.yellow('⚠️ npm is not available')}`)
81
76
  console.log('')
82
- console.log(`${DIM}Install ${provider.displayName} using one of:${NC}`)
83
- console.log(`${DIM} • Install Node.js: https://nodejs.org${NC}`)
77
+ console.log(`${chalk.dim(`Install ${provider.displayName} using one of:`)}`)
78
+ console.log(chalk.dim(' • Install Node.js: https://nodejs.org'))
84
79
  console.log(
85
- `${DIM} • Use Homebrew: brew install ${provider.name === 'claude' ? 'claude' : 'gemini'}${NC}`
80
+ chalk.dim(
81
+ ` • Use Homebrew: brew install ${provider.name === 'claude' ? 'claude' : 'gemini'}`
82
+ )
86
83
  )
87
- console.log(`${DIM} • Use npx directly: npx ${packageName}${NC}`)
84
+ console.log(chalk.dim(` • Use npx directly: npx ${packageName}`))
88
85
  console.log('')
89
86
  return false
90
87
  }
91
88
 
92
89
  try {
93
- console.log(`${YELLOW}📦 ${provider.displayName} not found. Installing...${NC}`)
90
+ console.log(chalk.yellow(`📦 ${provider.displayName} not found. Installing...`))
94
91
  console.log('')
95
92
  // PRJ-111: Add timeout to npm install (default: 2 minutes, configurable via PRJCT_TIMEOUT_NPM_INSTALL)
96
93
  execSync(`npm install -g ${packageName}`, {
@@ -98,7 +95,7 @@ async function installAICLI(provider: AIProviderConfig): Promise<boolean> {
98
95
  timeout: getTimeout('NPM_INSTALL'),
99
96
  })
100
97
  console.log('')
101
- console.log(`${GREEN}✓${NC} ${provider.displayName} installed successfully`)
98
+ console.log(`${chalk.green('✓')} ${provider.displayName} installed successfully`)
102
99
  console.log('')
103
100
  return true
104
101
  } catch (error) {
@@ -106,21 +103,21 @@ async function installAICLI(provider: AIProviderConfig): Promise<boolean> {
106
103
  const isTimeout = err.killed && err.signal === 'SIGTERM'
107
104
 
108
105
  if (isTimeout) {
109
- console.log(`${YELLOW}⚠️ Installation timed out for ${provider.displayName}${NC}`)
106
+ console.log(chalk.yellow(`⚠️ Installation timed out for ${provider.displayName}`))
110
107
  console.log('')
111
- console.log(`${DIM}The npm install took too long. Try:${NC}`)
112
- console.log(`${DIM} • Set PRJCT_TIMEOUT_NPM_INSTALL=300000 for 5 minutes${NC}`)
113
- console.log(`${DIM} • Run manually: npm install -g ${packageName}${NC}`)
108
+ console.log(chalk.dim('The npm install took too long. Try:'))
109
+ console.log(chalk.dim(' • Set PRJCT_TIMEOUT_NPM_INSTALL=300000 for 5 minutes'))
110
+ console.log(chalk.dim(` • Run manually: npm install -g ${packageName}`))
114
111
  } else {
115
- console.log(`${YELLOW}⚠️ Failed to install ${provider.displayName}: ${err.message}${NC}`)
112
+ console.log(chalk.yellow(`⚠️ Failed to install ${provider.displayName}: ${err.message}`))
116
113
  }
117
114
  console.log('')
118
- console.log(`${DIM}Alternative installation methods:${NC}`)
119
- console.log(`${DIM} • npm: npm install -g ${packageName}${NC}`)
120
- console.log(`${DIM} • yarn: yarn global add ${packageName}${NC}`)
121
- console.log(`${DIM} • pnpm: pnpm add -g ${packageName}${NC}`)
115
+ console.log(chalk.dim('Alternative installation methods:'))
116
+ console.log(chalk.dim(` • npm: npm install -g ${packageName}`))
117
+ console.log(chalk.dim(` • yarn: yarn global add ${packageName}`))
118
+ console.log(chalk.dim(` • pnpm: pnpm add -g ${packageName}`))
122
119
  console.log(
123
- `${DIM} • brew: brew install ${provider.name === 'claude' ? 'claude' : 'gemini'}${NC}`
120
+ chalk.dim(` • brew: brew install ${provider.name === 'claude' ? 'claude' : 'gemini'}`)
124
121
  )
125
122
  console.log('')
126
123
  return false
@@ -232,7 +229,7 @@ export async function run(): Promise<SetupResults> {
232
229
  if (antigravityDetection.installed) {
233
230
  const antigravityResult = await installAntigravitySkill()
234
231
  if (antigravityResult.success) {
235
- console.log(` ${GREEN}✓${NC} Antigravity skill installed`)
232
+ console.log(` ${chalk.green('✓')} Antigravity skill installed`)
236
233
  }
237
234
  }
238
235
 
@@ -733,7 +730,7 @@ async function migrateProjectsCliVersion(): Promise<void> {
733
730
  }
734
731
 
735
732
  if (migrated > 0) {
736
- console.log(` ${GREEN}✓${NC} Updated ${migrated} project(s) to v${VERSION}`)
733
+ console.log(` ${chalk.green('✓')} Updated ${migrated} project(s) to v${VERSION}`)
737
734
  }
738
735
  } catch (error) {
739
736
  // Silently fail if projects directory doesn't exist
@@ -1029,9 +1026,9 @@ function showResults(results: ProviderSetupResult, provider: AIProviderConfig):
1029
1026
  console.log('')
1030
1027
 
1031
1028
  if (results.cliInstalled) {
1032
- console.log(` ${GREEN}✓${NC} ${provider.displayName} CLI installed`)
1029
+ console.log(` ${chalk.green('✓')} ${provider.displayName} CLI installed`)
1033
1030
  } else {
1034
- console.log(` ${GREEN}✓${NC} ${provider.displayName} CLI found`)
1031
+ console.log(` ${chalk.green('✓')} ${provider.displayName} CLI found`)
1035
1032
  }
1036
1033
 
1037
1034
  const totalCommands = results.commandsAdded + results.commandsUpdated
@@ -1039,17 +1036,17 @@ function showResults(results: ProviderSetupResult, provider: AIProviderConfig):
1039
1036
  const parts: string[] = []
1040
1037
  if (results.commandsAdded > 0) parts.push(`${results.commandsAdded} new`)
1041
1038
  if (results.commandsUpdated > 0) parts.push(`${results.commandsUpdated} updated`)
1042
- console.log(` ${GREEN}✓${NC} Commands synced (${parts.join(', ')})`)
1039
+ console.log(` ${chalk.green('✓')} Commands synced (${parts.join(', ')})`)
1043
1040
  } else {
1044
- console.log(` ${GREEN}✓${NC} Commands up to date`)
1041
+ console.log(` ${chalk.green('✓')} Commands up to date`)
1045
1042
  }
1046
1043
 
1047
1044
  if (results.configAction === 'created') {
1048
- console.log(` ${GREEN}✓${NC} Global config created (${provider.contextFile})`)
1045
+ console.log(` ${chalk.green('✓')} Global config created (${provider.contextFile})`)
1049
1046
  } else if (results.configAction === 'updated') {
1050
- console.log(` ${GREEN}✓${NC} Global config updated (${provider.contextFile})`)
1047
+ console.log(` ${chalk.green('✓')} Global config updated (${provider.contextFile})`)
1051
1048
  } else if (results.configAction === 'appended') {
1052
- console.log(` ${GREEN}✓${NC} Global config merged (${provider.contextFile})`)
1049
+ console.log(` ${chalk.green('✓')} Global config merged (${provider.contextFile})`)
1053
1050
  }
1054
1051
 
1055
1052
  console.log('')
@@ -33,7 +33,7 @@ export const TechStackSchema = z.object({
33
33
 
34
34
  export const IdeaModuleSchema = z.object({
35
35
  name: z.string(), // "Multi-tenant"
36
- description: z.string(), // "Empresas con RLS estricto"
36
+ description: z.string(), // "Strict RLS for organizations"
37
37
  })
38
38
 
39
39
  export const IdeaRoleSchema = z.object({