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.
@@ -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({
@@ -11,7 +11,6 @@
11
11
  * @module services/hooks-service
12
12
  */
13
13
 
14
- import { execSync } from 'node:child_process'
15
14
  import fs from 'node:fs'
16
15
  import path from 'node:path'
17
16
  import chalk from 'chalk'
@@ -209,7 +208,7 @@ ${sectionName}:
209
208
  )
210
209
  } else {
211
210
  // Append new section
212
- content = content.trimEnd() + '\n' + hookBlock
211
+ content = `${content.trimEnd()}\n${hookBlock}`
213
212
  }
214
213
  }
215
214
 
@@ -262,7 +261,7 @@ function installDirect(projectPath: string, hooks: HookName[]): boolean {
262
261
  continue // Already installed
263
262
  }
264
263
  // Append to existing hook
265
- fs.appendFileSync(hookPath, '\n# prjct auto-sync\n' + script.split('\n').slice(1).join('\n'))
264
+ fs.appendFileSync(hookPath, `\n# prjct auto-sync\n${script.split('\n').slice(1).join('\n')}`)
266
265
  } else {
267
266
  fs.writeFileSync(hookPath, script, { mode: 0o755 })
268
267
  }
@@ -291,7 +290,7 @@ function uninstallLefthook(projectPath: string): boolean {
291
290
  // Clean up empty sections
292
291
  content = content.replace(/^(post-commit|post-checkout):\s*commands:\s*$/gm, '')
293
292
 
294
- fs.writeFileSync(configPath, content.trimEnd() + '\n', 'utf-8')
293
+ fs.writeFileSync(configPath, `${content.trimEnd()}\n`, 'utf-8')
295
294
  return true
296
295
  }
297
296
 
@@ -7,17 +7,10 @@
7
7
  * @module utils/help
8
8
  */
9
9
 
10
+ import chalk from 'chalk'
10
11
  import { CATEGORIES, COMMANDS } from '../commands/command-data'
11
12
  import { VERSION } from './version'
12
13
 
13
- // ANSI colors
14
- const CYAN = '\x1b[36m'
15
- const DIM = '\x1b[2m'
16
- const BOLD = '\x1b[1m'
17
- const RESET = '\x1b[0m'
18
- const GREEN = '\x1b[32m'
19
- const YELLOW = '\x1b[33m'
20
-
21
14
  /**
22
15
  * Terminal commands that run directly in the shell
23
16
  */
@@ -96,22 +89,24 @@ export function formatMainHelp(): string {
96
89
 
97
90
  // Header
98
91
  lines.push('')
99
- lines.push(`${CYAN}${BOLD}prjct${RESET} v${VERSION} - Context layer for AI coding agents`)
100
- lines.push(`${DIM}Works with Claude Code, Gemini CLI, Cursor, Windsurf, and more.${RESET}`)
92
+ lines.push(`${chalk.cyan.bold('prjct')} v${VERSION} - Context layer for AI coding agents`)
93
+ lines.push(chalk.dim('Works with Claude Code, Gemini CLI, Cursor, Windsurf, and more.'))
101
94
  lines.push('')
102
95
 
103
96
  // Quick Start
104
- lines.push(`${BOLD}QUICK START${RESET}`)
105
- lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
106
- lines.push(` ${GREEN}1.${RESET} prjct start ${DIM}# Configure AI providers${RESET}`)
107
- lines.push(` ${GREEN}2.${RESET} cd my-project && prjct init`)
108
- lines.push(` ${GREEN}3.${RESET} Open in Claude Code / Gemini CLI / Cursor`)
109
- lines.push(` ${GREEN}4.${RESET} p. sync ${DIM}# Analyze project${RESET}`)
97
+ lines.push(chalk.bold('QUICK START'))
98
+ lines.push(chalk.dim('─'.repeat(60)))
99
+ lines.push(
100
+ ` ${chalk.green('1.')} prjct start ${chalk.dim('# Configure AI providers')}`
101
+ )
102
+ lines.push(` ${chalk.green('2.')} cd my-project && prjct init`)
103
+ lines.push(` ${chalk.green('3.')} Open in Claude Code / Gemini CLI / Cursor`)
104
+ lines.push(` ${chalk.green('4.')} p. sync ${chalk.dim('# Analyze project')}`)
110
105
  lines.push('')
111
106
 
112
107
  // Terminal Commands
113
- lines.push(`${BOLD}TERMINAL COMMANDS${RESET}`)
114
- lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
108
+ lines.push(chalk.bold('TERMINAL COMMANDS'))
109
+ lines.push(chalk.dim('─'.repeat(60)))
115
110
  for (const cmd of TERMINAL_COMMANDS) {
116
111
  const name = `prjct ${cmd.name}`.padEnd(22)
117
112
  lines.push(` ${name} ${cmd.description}`)
@@ -119,10 +114,10 @@ export function formatMainHelp(): string {
119
114
  lines.push('')
120
115
 
121
116
  // AI Agent Commands
122
- lines.push(`${BOLD}AI AGENT COMMANDS${RESET} ${DIM}(inside Claude/Gemini/Cursor)${RESET}`)
123
- lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
117
+ lines.push(`${chalk.bold('AI AGENT COMMANDS')} ${chalk.dim('(inside Claude/Gemini/Cursor)')}`)
118
+ lines.push(chalk.dim('─'.repeat(60)))
124
119
  lines.push(` ${'Command'.padEnd(22)} Description`)
125
- lines.push(` ${DIM}${'─'.repeat(56)}${RESET}`)
120
+ lines.push(` ${chalk.dim('─'.repeat(56))}`)
126
121
 
127
122
  // Core commands
128
123
  const coreCommands = COMMANDS.filter((c) => c.group === 'core' && c.usage?.claude)
@@ -130,22 +125,24 @@ export function formatMainHelp(): string {
130
125
  const usage = `p. ${cmd.name}`.padEnd(22)
131
126
  lines.push(` ${usage} ${cmd.description}`)
132
127
  }
133
- lines.push(` ${DIM}... and ${coreCommands.length - 10} more (run 'prjct help commands')${RESET}`)
128
+ lines.push(
129
+ ` ${chalk.dim(`... and ${coreCommands.length - 10} more (run 'prjct help commands')`)}`
130
+ )
134
131
  lines.push('')
135
132
 
136
133
  // Global Flags
137
- lines.push(`${BOLD}FLAGS${RESET}`)
138
- lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
134
+ lines.push(chalk.bold('FLAGS'))
135
+ lines.push(chalk.dim('─'.repeat(60)))
139
136
  for (const flag of GLOBAL_FLAGS) {
140
137
  lines.push(` ${flag.flag.padEnd(22)} ${flag.description}`)
141
138
  }
142
139
  lines.push('')
143
140
 
144
141
  // More Info
145
- lines.push(`${BOLD}MORE INFO${RESET}`)
146
- lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
147
- lines.push(` Documentation: ${CYAN}https://prjct.app${RESET}`)
148
- lines.push(` GitHub: ${CYAN}https://github.com/jlopezlira/prjct-cli${RESET}`)
142
+ lines.push(chalk.bold('MORE INFO'))
143
+ lines.push(chalk.dim('─'.repeat(60)))
144
+ lines.push(` Documentation: ${chalk.cyan('https://prjct.app')}`)
145
+ lines.push(` GitHub: ${chalk.cyan('https://github.com/jlopezlira/prjct-cli')}`)
149
146
  lines.push(` Per-command: prjct help <command>`)
150
147
  lines.push('')
151
148
 
@@ -162,15 +159,15 @@ export function formatTerminalCommandHelp(commandName: string): string | null {
162
159
  const lines: string[] = []
163
160
 
164
161
  lines.push('')
165
- lines.push(`${CYAN}${BOLD}prjct ${cmd.name}${RESET} - ${cmd.description}`)
162
+ lines.push(`${chalk.cyan.bold(`prjct ${cmd.name}`)} - ${cmd.description}`)
166
163
  lines.push('')
167
164
 
168
- lines.push(`${BOLD}USAGE${RESET}`)
165
+ lines.push(chalk.bold('USAGE'))
169
166
  lines.push(` ${cmd.example}`)
170
167
  lines.push('')
171
168
 
172
169
  if (cmd.options) {
173
- lines.push(`${BOLD}OPTIONS${RESET}`)
170
+ lines.push(chalk.bold('OPTIONS'))
174
171
  for (const opt of cmd.options) {
175
172
  lines.push(` ${opt}`)
176
173
  }
@@ -178,7 +175,7 @@ export function formatTerminalCommandHelp(commandName: string): string | null {
178
175
  }
179
176
 
180
177
  if (cmd.subcommands) {
181
- lines.push(`${BOLD}SUBCOMMANDS${RESET}`)
178
+ lines.push(chalk.bold('SUBCOMMANDS'))
182
179
  for (const sub of cmd.subcommands) {
183
180
  lines.push(` ${sub}`)
184
181
  }
@@ -198,10 +195,10 @@ export function formatAgentCommandHelp(commandName: string): string | null {
198
195
  const lines: string[] = []
199
196
 
200
197
  lines.push('')
201
- lines.push(`${CYAN}${BOLD}p. ${cmd.name}${RESET} - ${cmd.description}`)
198
+ lines.push(`${chalk.cyan.bold(`p. ${cmd.name}`)} - ${cmd.description}`)
202
199
  lines.push('')
203
200
 
204
- lines.push(`${BOLD}USAGE${RESET}`)
201
+ lines.push(chalk.bold('USAGE'))
205
202
  if (cmd.usage?.claude) {
206
203
  lines.push(` Claude/Gemini: ${cmd.usage.claude.replace('/p:', 'p. ')}`)
207
204
  }
@@ -211,13 +208,13 @@ export function formatAgentCommandHelp(commandName: string): string | null {
211
208
  lines.push('')
212
209
 
213
210
  if (cmd.params) {
214
- lines.push(`${BOLD}PARAMETERS${RESET}`)
211
+ lines.push(chalk.bold('PARAMETERS'))
215
212
  lines.push(` ${cmd.params}`)
216
213
  lines.push('')
217
214
  }
218
215
 
219
216
  if (cmd.features && cmd.features.length > 0) {
220
- lines.push(`${BOLD}FEATURES${RESET}`)
217
+ lines.push(chalk.bold('FEATURES'))
221
218
  for (const feature of cmd.features) {
222
219
  lines.push(` • ${feature}`)
223
220
  }
@@ -225,17 +222,17 @@ export function formatAgentCommandHelp(commandName: string): string | null {
225
222
  }
226
223
 
227
224
  if (cmd.blockingRules) {
228
- lines.push(`${BOLD}REQUIREMENTS${RESET}`)
229
- lines.push(` ${YELLOW}⚠${RESET} ${cmd.blockingRules.check}`)
225
+ lines.push(chalk.bold('REQUIREMENTS'))
226
+ lines.push(` ${chalk.yellow('⚠')} ${cmd.blockingRules.check}`)
230
227
  lines.push('')
231
228
  }
232
229
 
233
230
  // Category info
234
231
  const category = CATEGORIES[cmd.group]
235
232
  if (category) {
236
- lines.push(`${DIM}Category: ${category.title}${RESET}`)
233
+ lines.push(chalk.dim(`Category: ${category.title}`))
237
234
  if (cmd.isOptional) {
238
- lines.push(`${DIM}This is an optional command.${RESET}`)
235
+ lines.push(chalk.dim('This is an optional command.'))
239
236
  }
240
237
  lines.push('')
241
238
  }
@@ -257,7 +254,7 @@ export function formatCommandHelp(commandName: string): string {
257
254
 
258
255
  // Command not found
259
256
  return `
260
- ${YELLOW}Command '${commandName}' not found.${RESET}
257
+ ${chalk.yellow(`Command '${commandName}' not found.`)}
261
258
 
262
259
  Run 'prjct help' to see all available commands.
263
260
  `
@@ -270,7 +267,7 @@ export function formatCommandList(): string {
270
267
  const lines: string[] = []
271
268
 
272
269
  lines.push('')
273
- lines.push(`${CYAN}${BOLD}All Commands${RESET}`)
270
+ lines.push(chalk.cyan.bold('All Commands'))
274
271
  lines.push('')
275
272
 
276
273
  // Group by category
@@ -281,9 +278,9 @@ export function formatCommandList(): string {
281
278
  if (categoryCommands.length === 0) continue
282
279
 
283
280
  lines.push(
284
- `${BOLD}${category.title}${RESET} ${DIM}(${categoryCommands.length} commands)${RESET}`
281
+ `${chalk.bold(category.title)} ${chalk.dim(`(${categoryCommands.length} commands)`)}`
285
282
  )
286
- lines.push(`${DIM}${category.description}${RESET}`)
283
+ lines.push(chalk.dim(category.description))
287
284
  lines.push('')
288
285
 
289
286
  for (const cmd of categoryCommands) {
@@ -295,7 +292,7 @@ export function formatCommandList(): string {
295
292
  lines.push('')
296
293
  }
297
294
 
298
- lines.push(`${DIM}Run 'prjct help <command>' for detailed help on a specific command.${RESET}`)
295
+ lines.push(chalk.dim("Run 'prjct help <command>' for detailed help on a specific command."))
299
296
  lines.push('')
300
297
 
301
298
  return lines.join('\n')
@@ -8,32 +8,25 @@
8
8
  * @module utils/subtask-table
9
9
  */
10
10
 
11
- // ANSI codes
12
- const RESET = '\x1b[0m'
13
- const BOLD = '\x1b[1m'
14
- const DIM = '\x1b[2m'
15
- const GREEN = '\x1b[32m'
16
- const YELLOW = '\x1b[33m'
17
- const WHITE = '\x1b[37m'
18
- const GRAY = '\x1b[90m'
11
+ import chalk from 'chalk'
19
12
 
20
13
  // Color palette for domains (cycle through these)
21
14
  const DOMAIN_COLOR_PALETTE = [
22
- '\x1b[36m', // Cyan
23
- '\x1b[35m', // Magenta
24
- '\x1b[33m', // Yellow
25
- '\x1b[34m', // Blue
26
- '\x1b[32m', // Green
27
- '\x1b[91m', // Light Red
28
- '\x1b[95m', // Light Magenta
29
- '\x1b[96m', // Light Cyan
15
+ chalk.cyan,
16
+ chalk.magenta,
17
+ chalk.yellow,
18
+ chalk.blue,
19
+ chalk.green,
20
+ chalk.redBright,
21
+ chalk.magentaBright,
22
+ chalk.cyanBright,
30
23
  ]
31
24
 
32
25
  /**
33
26
  * Get consistent color for a domain name using hash
34
27
  * Same domain name always returns same color
35
28
  */
36
- function getDomainColor(domain: string): string {
29
+ function getDomainColor(domain: string): (text: string) => string {
37
30
  let hash = 0
38
31
  for (const char of domain) {
39
32
  hash = (hash << 5) - hash + char.charCodeAt(0)
@@ -43,7 +36,7 @@ function getDomainColor(domain: string): string {
43
36
  return DOMAIN_COLOR_PALETTE[index]
44
37
  }
45
38
 
46
- // Hide/show cursor
39
+ // Terminal control sequences (not colors - chalk doesn't handle these)
47
40
  const HIDE_CURSOR = '\x1b[?25l'
48
41
  const SHOW_CURSOR = '\x1b[?25h'
49
42
 
@@ -67,9 +60,9 @@ function formatSubtaskLine(
67
60
  subtask: SubtaskDisplay,
68
61
  spinnerFrame: string = '▶'
69
62
  ): string {
70
- const num = `${DIM}${String(index + 1).padStart(2)}${RESET}`
71
- const domainColor = getDomainColor(subtask.domain)
72
- const domain = `${domainColor}${subtask.domain.padEnd(10)}${RESET}`
63
+ const num = chalk.dim(String(index + 1).padStart(2))
64
+ const domainColorFn = getDomainColor(subtask.domain)
65
+ const domain = domainColorFn(subtask.domain.padEnd(10))
73
66
  const desc =
74
67
  subtask.description.length > 32
75
68
  ? `${subtask.description.slice(0, 29)}...`
@@ -78,22 +71,22 @@ function formatSubtaskLine(
78
71
  let status: string
79
72
  switch (subtask.status) {
80
73
  case 'completed':
81
- status = `${GREEN}✓ Complete${RESET}`
74
+ status = chalk.green('✓ Complete')
82
75
  break
83
76
  case 'in_progress':
84
- status = `${YELLOW}${spinnerFrame} Working...${RESET}`
77
+ status = chalk.yellow(`${spinnerFrame} Working...`)
85
78
  break
86
79
  case 'pending':
87
- status = `${GRAY}○ Pending${RESET}`
80
+ status = chalk.gray('○ Pending')
88
81
  break
89
82
  case 'failed':
90
- status = `\x1b[31m✗ Failed${RESET}`
83
+ status = chalk.red('✗ Failed')
91
84
  break
92
85
  case 'blocked':
93
- status = `${GRAY}⊘ Blocked${RESET}`
86
+ status = chalk.gray('⊘ Blocked')
94
87
  break
95
88
  default:
96
- status = `${GRAY}○ ${subtask.status}${RESET}`
89
+ status = chalk.gray(`○ ${subtask.status}`)
97
90
  }
98
91
 
99
92
  return ` ${num} ${domain} ${desc} ${status}`
@@ -108,8 +101,8 @@ export function renderSubtaskProgress(subtasks: SubtaskDisplay[]): string {
108
101
  const lines: string[] = []
109
102
 
110
103
  lines.push('')
111
- lines.push(` ${BOLD}${WHITE}SUBTASK PROGRESS${RESET}`)
112
- lines.push(` ${DIM}${'─'.repeat(58)}${RESET}`)
104
+ lines.push(` ${chalk.bold.white('SUBTASK PROGRESS')}`)
105
+ lines.push(` ${chalk.dim('─'.repeat(58))}`)
113
106
 
114
107
  for (let i = 0; i < subtasks.length; i++) {
115
108
  lines.push(formatSubtaskLine(i, subtasks[i]))
@@ -141,8 +134,8 @@ export function createSubtaskAnimation(subtasks: SubtaskDisplay[]) {
141
134
  const lines: string[] = []
142
135
 
143
136
  lines.push('')
144
- lines.push(` ${BOLD}${WHITE}SUBTASK PROGRESS${RESET}`)
145
- lines.push(` ${DIM}${'─'.repeat(58)}${RESET}`)
137
+ lines.push(` ${chalk.bold.white('SUBTASK PROGRESS')}`)
138
+ lines.push(` ${chalk.dim('─'.repeat(58))}`)
146
139
 
147
140
  for (let i = 0; i < subtasks.length; i++) {
148
141
  lines.push(formatSubtaskLine(i, subtasks[i], spinnerFrame))
@@ -204,8 +197,8 @@ export function createSubtaskAnimation(subtasks: SubtaskDisplay[]) {
204
197
  // Print final state with static icons
205
198
  const finalLines: string[] = []
206
199
  finalLines.push('')
207
- finalLines.push(` ${BOLD}${WHITE}SUBTASK PROGRESS${RESET}`)
208
- finalLines.push(` ${DIM}${'─'.repeat(58)}${RESET}`)
200
+ finalLines.push(` ${chalk.bold.white('SUBTASK PROGRESS')}`)
201
+ finalLines.push(` ${chalk.dim('─'.repeat(58))}`)
209
202
  for (let i = 0; i < subtasks.length; i++) {
210
203
  finalLines.push(formatSubtaskLine(i, subtasks[i], '▶'))
211
204
  }
@@ -226,7 +219,7 @@ export function createSubtaskAnimation(subtasks: SubtaskDisplay[]) {
226
219
  * Output: "Progress: 2/4 subtasks complete"
227
220
  */
228
221
  export function renderProgressLine(completed: number, total: number): string {
229
- return ` ${DIM}Progress:${RESET} ${completed}/${total} subtasks complete`
222
+ return ` ${chalk.dim('Progress:')} ${completed}/${total} subtasks complete`
230
223
  }
231
224
 
232
225
  // Legacy exports for backwards compatibility
@@ -15,17 +15,11 @@
15
15
 
16
16
  import { exec } from 'node:child_process'
17
17
  import { promisify } from 'node:util'
18
+ import chalk from 'chalk'
18
19
  import memorySystem from '../agentic/memory-system'
19
20
 
20
21
  const execAsync = promisify(exec)
21
22
 
22
- // ANSI colors
23
- const DIM = '\x1b[2m'
24
- const GREEN = '\x1b[32m'
25
- const RED = '\x1b[31m'
26
- const YELLOW = '\x1b[33m'
27
- const RESET = '\x1b[0m'
28
-
29
23
  export type PreferenceScope = 'permanent' | 'session' | 'once'
30
24
  export type HookPhase = 'before' | 'after' | 'skip'
31
25
  export type HookCommand = 'task' | 'done' | 'ship' | 'sync'
@@ -169,7 +163,7 @@ export async function runWorkflowHooks(
169
163
  oncePreferences.delete(key)
170
164
  }
171
165
 
172
- console.log(`\n${DIM}Running ${phase}-${command}: ${action}${RESET}`)
166
+ console.log(`\n${chalk.dim(`Running ${phase}-${command}: ${action}`)}`)
173
167
 
174
168
  try {
175
169
  const startTime = Date.now()
@@ -180,12 +174,12 @@ export async function runWorkflowHooks(
180
174
  })
181
175
  const elapsed = Date.now() - startTime
182
176
  const timeStr = elapsed > 1000 ? `${(elapsed / 1000).toFixed(1)}s` : `${elapsed}ms`
183
- console.log(`${GREEN}✓${RESET} ${DIM}(${timeStr})${RESET}`)
177
+ console.log(`${chalk.green('✓')} ${chalk.dim(`(${timeStr})`)}`)
184
178
  return { success: true }
185
179
  } catch (error) {
186
- console.log(`${RED}✗ failed${RESET}`)
180
+ console.log(chalk.red('✗ failed'))
187
181
  const errorMessage = (error as Error).message || 'Unknown error'
188
- console.log(`${DIM}${errorMessage.split('\n')[0]}${RESET}`)
182
+ console.log(chalk.dim(errorMessage.split('\n')[0]))
189
183
  return { success: false, failed: action, output: errorMessage }
190
184
  }
191
185
  }
@@ -270,7 +264,7 @@ export function formatWorkflowPreferences(
270
264
  }>
271
265
  ): string {
272
266
  if (preferences.length === 0) {
273
- return `${DIM}No workflow preferences configured.${RESET}\n\nSet one: "p. workflow antes de ship corre los tests"`
267
+ return `${chalk.dim('No workflow preferences configured.')}\n\nSet one: "p. workflow antes de ship corre los tests"`
274
268
  }
275
269
 
276
270
  const lines: string[] = ['', 'WORKFLOW PREFERENCES', '────────────────────────────']
@@ -278,17 +272,17 @@ export function formatWorkflowPreferences(
278
272
  for (const pref of preferences) {
279
273
  const scopeBadge =
280
274
  pref.scope === 'permanent'
281
- ? `${GREEN}permanent${RESET}`
275
+ ? chalk.green('permanent')
282
276
  : pref.scope === 'session'
283
- ? `${YELLOW}session${RESET}`
284
- : `${DIM}once${RESET}`
277
+ ? chalk.yellow('session')
278
+ : chalk.dim('once')
285
279
 
286
280
  lines.push(` [${scopeBadge}] ${pref.key.padEnd(15)} → ${pref.action}`)
287
281
  }
288
282
 
289
283
  lines.push('')
290
- lines.push(`${DIM}Modify: "p. workflow antes de ship corre npm test"${RESET}`)
291
- lines.push(`${DIM}Remove: "p. workflow quita el hook de ship"${RESET}`)
284
+ lines.push(chalk.dim('Modify: "p. workflow antes de ship corre npm test"'))
285
+ lines.push(chalk.dim('Remove: "p. workflow quita el hook de ship"'))
292
286
 
293
287
  return lines.join('\n')
294
288
  }