prjct-cli 1.2.0 → 1.2.2

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,46 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.2] - 2026-02-06
4
+
5
+ ### Performance
6
+
7
+ - convert execSync to async in ground-truth.ts (PRJ-92) (#113)
8
+ - convert execSync to async in ground-truth.ts (PRJ-92)
9
+
10
+
11
+ ## [1.2.1] - 2026-02-06
12
+
13
+ ### Performance
14
+
15
+ - **Convert execSync to async in ground-truth.ts (PRJ-92)**: Replaced blocking execSync with promisify(exec) in verifyShip
16
+
17
+ ### Bug Fixes
18
+
19
+ - replace raw ANSI codes with chalk library (PRJ-132) (#111)
20
+
21
+ ### Implementation Details
22
+
23
+ Replaced the single `execSync('git status --porcelain')` call in `verifyShip()` with `await execAsync()` using `promisify(exec)` from `node:util`. The rest of `ground-truth.ts` already used async `fs.promises` — this was the last synchronous call blocking the event loop.
24
+
25
+ ### Learnings
26
+
27
+ - `exec` returns `{stdout, stderr}` object vs `execSync` returning a string directly — must destructure
28
+ - `promisify(exec)` is simpler than `spawn` for short-lived commands that return stdout
29
+ - Terminal control sequences (cursor movement) are separate from color/formatting — chalk doesn't handle them
30
+
31
+ ### Test Plan
32
+
33
+ #### For QA
34
+ 1. Run `bun run build && bun run typecheck` — zero errors
35
+ 2. Trigger `verifyShip` path — verify async git status check works
36
+ 3. Test with uncommitted changes — verify warning still appears
37
+ 4. Test in non-git directory — verify graceful fallback (`gitAvailable = false`)
38
+
39
+ #### For Users
40
+ **What changed:** Internal performance improvement — git status check in ground-truth verifier is now async
41
+ **How to use:** No change needed — improvement is internal
42
+ **Breaking changes:** None
43
+
3
44
  ## [1.2.0] - 2026-02-06
4
45
 
5
46
  ### Features
@@ -56,6 +97,46 @@ Hook configuration saved to `project.json` for persistence across sessions.
56
97
  **How to use:** Run `prjct hooks install` in any prjct project
57
98
  **Breaking changes:** None
58
99
 
100
+ ## [1.1.2] - 2026-02-05
101
+
102
+ ### Fixed
103
+
104
+ - **Replace raw ANSI codes with chalk library (PRJ-132)**: Replaced ~60 raw ANSI escape codes across 7 files with chalk library calls
105
+
106
+ ### Implementation Details
107
+
108
+ Replaced all raw ANSI color/formatting constants (`\x1b[32m`, `\x1b[1m`, etc.) with chalk equivalents (`chalk.green()`, `chalk.bold()`, etc.) in:
109
+ - `bin/prjct.ts` — version display, provider status, welcome message
110
+ - `core/index.ts` — version display, provider status
111
+ - `core/cli/start.ts` — gradient banner using `chalk.rgb()`, setup UI
112
+ - `core/utils/subtask-table.ts` — color palette as chalk functions, progress display
113
+ - `core/utils/help.ts` — all help formatting
114
+ - `core/workflow/workflow-preferences.ts` — hook status display
115
+ - `core/infrastructure/setup.ts` — installation messages
116
+
117
+ Terminal control sequences (cursor movement, hide/show) kept as raw ANSI since chalk only handles colors/formatting.
118
+
119
+ ### Learnings
120
+
121
+ - `chalk.rgb(R,G,B)` replaces `\x1b[38;2;R;G;Bm` for true-color support
122
+ - Chalk functions can be stored as array values and called dynamically (useful for color palettes)
123
+ - `chalk.bold.white()` chains work for compound styling
124
+ - Terminal control sequences (`\x1b[?25l`, cursor movement) must stay raw — chalk doesn't handle them
125
+
126
+ ### Test Plan
127
+
128
+ #### For QA
129
+ 1. Run `prjct --version` — verify colored output with provider status
130
+ 2. Run `prjct help` — verify formatted help with colors
131
+ 3. Run `prjct start --force` — verify gradient banner and colored UI
132
+ 4. Set `NO_COLOR=1` and repeat above — verify all color is suppressed
133
+ 5. Run `bun run build && bun run typecheck` — verify zero errors
134
+
135
+ #### For Users
136
+ **What changed:** Terminal colors now use the chalk library instead of raw ANSI codes
137
+ **How to use:** No change — colors appear the same but now respect `NO_COLOR` env variable
138
+ **Breaking changes:** None
139
+
59
140
  ## [1.1.1] - 2026-02-06
60
141
 
61
142
  ### 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()
@@ -11,14 +11,17 @@
11
11
  * Source: Devin, Cursor, Augment Code patterns
12
12
  */
13
13
 
14
- import { execSync } from 'node:child_process'
14
+ import { exec } from 'node:child_process'
15
15
  import fs from 'node:fs/promises'
16
16
  import os from 'node:os'
17
17
  import path from 'node:path'
18
+ import { promisify } from 'node:util'
18
19
 
19
20
  import type { GroundTruthContext, VerificationResult, Verifier } from '../types'
20
21
  import { isNotFoundError } from '../types/fs'
21
22
 
23
+ const execAsync = promisify(exec)
24
+
22
25
  // =============================================================================
23
26
  // Utilities
24
27
  // =============================================================================
@@ -163,9 +166,8 @@ export async function verifyShip(context: GroundTruthContext): Promise<Verificat
163
166
 
164
167
  // 1. Check for uncommitted changes
165
168
  try {
166
- const gitStatus = execSync('git status --porcelain', {
169
+ const { stdout: gitStatus } = await execAsync('git status --porcelain', {
167
170
  cwd: context.projectPath,
168
- encoding: 'utf-8',
169
171
  })
170
172
  actual.hasUncommittedChanges = gitStatus.trim().length > 0
171
173
  actual.uncommittedFiles = gitStatus.trim().split('\n').filter(Boolean).length
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