prjct-cli 1.1.1 → 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,12 +1,115 @@
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
+
11
+ ## [1.2.0] - 2026-02-06
12
+
13
+ ### Features
14
+
15
+ - git hooks integration for auto-sync (PRJ-128) (#112)
16
+
17
+
18
+ ## [1.2.0] - 2026-02-05
19
+
20
+ ### Added
21
+
22
+ - **Git hooks integration (PRJ-128)**: New `prjct hooks` command for auto-syncing context on commit and branch checkout
23
+
24
+ ### Implementation Details
25
+
26
+ New `prjct hooks` CLI subcommand with three operations:
27
+ - `prjct hooks install` — auto-detects hook manager (lefthook > husky > direct `.git/hooks/`) and installs post-commit + post-checkout hooks
28
+ - `prjct hooks uninstall` — cleanly removes only prjct hooks, preserving existing hooks
29
+ - `prjct hooks status` — shows active hooks, strategy, and available managers
30
+
31
+ Hook scripts feature:
32
+ - **Rate limiting** — 30-second lockfile prevents over-syncing on rapid commits
33
+ - **Background execution** — hooks run `prjct sync` in background, never blocking git
34
+ - **Branch-only checkout** — post-checkout only fires on branch switch, not file checkout
35
+ - **Cross-platform** — handles macOS/Linux differences in `stat` and `md5` commands
36
+
37
+ Supports three installation strategies:
38
+ - **Lefthook** — adds `prjct-sync-*` commands to existing `lefthook.yml`
39
+ - **Husky** — appends to existing `.husky/` hook scripts
40
+ - **Direct** — writes to `.git/hooks/` as fallback
41
+
42
+ Hook configuration saved to `project.json` for persistence across sessions.
43
+
44
+ ### Learnings
45
+
46
+ - Strategy pattern works well for hook manager abstraction (detect → select → install)
47
+ - `stat -f%m` (macOS) vs `stat -c%Y` (Linux) for file modification time
48
+ - Lefthook section merging needs careful regex to avoid duplicates
49
+ - `$3` parameter in post-checkout distinguishes branch checkout (1) from file checkout (0)
50
+
51
+ ### Test Plan
52
+
53
+ #### For QA
54
+ 1. Run `prjct hooks status` — verify shows "Not installed" with available managers
55
+ 2. Run `prjct hooks install` — verify detects manager and installs hooks
56
+ 3. Run `prjct hooks status` — verify shows "Active"
57
+ 4. Make a git commit — verify sync runs in background
58
+ 5. Switch branches — verify post-checkout triggers sync
59
+ 6. Run `prjct hooks uninstall` — verify clean removal
60
+ 7. Run `bun run build && bun run typecheck` — zero errors
61
+
62
+ #### For Users
63
+ **What changed:** New `prjct hooks` command for automatic context syncing
64
+ **How to use:** Run `prjct hooks install` in any prjct project
65
+ **Breaking changes:** None
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
+
3
107
  ## [1.1.1] - 2026-02-06
4
108
 
5
109
  ### Bug Fixes
6
110
 
7
111
  - visual grouping with boxes and tables for structured output (PRJ-134) (#110)
8
112
 
9
-
10
113
  ## [1.1.1] - 2026-02-05
11
114
 
12
115
  ### Improved
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
@@ -108,6 +104,12 @@ if (args[0] === 'start' || args[0] === 'setup') {
108
104
  console.log(JSON.stringify(result, null, 2))
109
105
  process.exitCode = result.tool === 'error' ? 1 : 0
110
106
  }
107
+ } else if (args[0] === 'hooks') {
108
+ // Git hooks management
109
+ const { hooksService } = await import('../core/services/hooks-service')
110
+ const subcommand = args[1] || 'status'
111
+ const exitCode = await hooksService.run(process.cwd(), subcommand)
112
+ process.exitCode = exitCode
111
113
  } else if (args[0] === 'doctor') {
112
114
  // Health check command
113
115
  const { doctorService } = await import('../core/services/doctor-service')
@@ -200,52 +202,50 @@ if (args[0] === 'start' || args[0] === 'setup') {
200
202
  const windsurfDetected = fs.existsSync(path.join(cwd, '.windsurf'))
201
203
  const windsurfConfigured = fs.existsSync(path.join(cwd, '.windsurf', 'rules', 'prjct.md'))
202
204
 
203
- const GREEN = '\x1b[32m'
204
-
205
205
  console.log(`
206
- ${CYAN}p/${RESET} prjct v${VERSION}
207
- ${DIM}Context layer for AI coding agents${RESET}
206
+ ${chalk.cyan('p/')} prjct v${VERSION}
207
+ ${chalk.dim('Context layer for AI coding agents')}
208
208
 
209
- ${DIM}Providers:${RESET}`)
209
+ ${chalk.dim('Providers:')}`)
210
210
 
211
211
  // Claude status
212
212
  if (detection.claude.installed) {
213
- const status = claudeConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
213
+ const status = claudeConfigured ? chalk.green('✓ ready') : chalk.yellow('● installed')
214
214
  const ver = detection.claude.version ? ` (v${detection.claude.version})` : ''
215
- console.log(` Claude Code ${status}${DIM}${ver}${RESET}`)
215
+ console.log(` Claude Code ${status}${chalk.dim(ver)}`)
216
216
  } else {
217
- console.log(` Claude Code ${DIM}○ not installed${RESET}`)
217
+ console.log(` Claude Code ${chalk.dim('○ not installed')}`)
218
218
  }
219
219
 
220
220
  // Gemini status
221
221
  if (detection.gemini.installed) {
222
- const status = geminiConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
222
+ const status = geminiConfigured ? chalk.green('✓ ready') : chalk.yellow('● installed')
223
223
  const ver = detection.gemini.version ? ` (v${detection.gemini.version})` : ''
224
- console.log(` Gemini CLI ${status}${DIM}${ver}${RESET}`)
224
+ console.log(` Gemini CLI ${status}${chalk.dim(ver)}`)
225
225
  } else {
226
- console.log(` Gemini CLI ${DIM}○ not installed${RESET}`)
226
+ console.log(` Gemini CLI ${chalk.dim('○ not installed')}`)
227
227
  }
228
228
 
229
229
  // Cursor status (project-level)
230
230
  if (cursorDetected) {
231
- const status = cursorConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● detected${RESET}`
232
- 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)')}`)
233
233
  } else {
234
- console.log(` Cursor IDE ${DIM}○ not detected${RESET}`)
234
+ console.log(` Cursor IDE ${chalk.dim('○ not detected')}`)
235
235
  }
236
236
 
237
237
  // Windsurf status (project-level)
238
238
  if (windsurfDetected) {
239
- const status = windsurfConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● detected${RESET}`
240
- 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)')}`)
241
241
  } else {
242
- console.log(` Windsurf IDE ${DIM}○ not detected${RESET}`)
242
+ console.log(` Windsurf IDE ${chalk.dim('○ not detected')}`)
243
243
  }
244
244
 
245
245
  console.log(`
246
- ${DIM}Run 'prjct start' to configure (CLI providers)${RESET}
247
- ${DIM}Run 'prjct init' to configure (Cursor/Windsurf IDE)${RESET}
248
- ${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')}
249
249
  `)
250
250
  } else {
251
251
  // Check if setup has been done
@@ -255,12 +255,12 @@ ${CYAN}https://prjct.app${RESET}
255
255
  if (!fs.existsSync(configPath) || !routersInstalled) {
256
256
  // First time - prompt to run start
257
257
  console.log(`
258
- ${CYAN}${BOLD} Welcome to prjct!${RESET}
258
+ ${chalk.cyan.bold(' Welcome to prjct!')}
259
259
 
260
- Run ${BOLD}prjct start${RESET} to configure your AI providers.
260
+ Run ${chalk.bold('prjct start')} to configure your AI providers.
261
261
 
262
- ${DIM}This is a one-time setup that lets you choose between
263
- 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.`)}
264
264
  `)
265
265
  process.exitCode = 0
266
266
  } else {
@@ -269,7 +269,7 @@ ${CYAN}${BOLD} Welcome to prjct!${RESET}
269
269
  const lastVersion = await editorsConfig.getLastVersion()
270
270
 
271
271
  if (lastVersion && lastVersion !== VERSION) {
272
- 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`)
273
273
 
274
274
  const { default: setup } = await import('../core/infrastructure/setup')
275
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
 
@@ -207,6 +207,7 @@ export class PlanningCommands extends PrjctCommandsBase {
207
207
  console.log(' Quick start:')
208
208
  console.log(' prjct sync Update context after changes')
209
209
  console.log(' prjct task Start working on a task')
210
+ console.log(' prjct hooks Auto-sync on commit/checkout')
210
211
  console.log('')
211
212
 
212
213
  if (wizardResult) {
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
 
@@ -306,6 +302,7 @@ TERMINAL COMMANDS (this CLI)
306
302
  prjct setup Reconfigure installations
307
303
  prjct sync Sync project state
308
304
  prjct watch Auto-sync on file changes (Ctrl+C to stop)
305
+ prjct hooks Manage git hooks for auto-sync
309
306
  prjct doctor Check system health and dependencies
310
307
 
311
308
  EXAMPLES