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.
@@ -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
  */
@@ -43,6 +36,12 @@ const TERMINAL_COMMANDS = [
43
36
  example: 'prjct watch',
44
37
  options: ['--verbose', '--debounce=<ms>', '--interval=<sec>'],
45
38
  },
39
+ {
40
+ name: 'hooks',
41
+ description: 'Manage git hooks for auto-sync',
42
+ example: 'prjct hooks install',
43
+ subcommands: ['install', 'uninstall', 'status'],
44
+ },
46
45
  {
47
46
  name: 'doctor',
48
47
  description: 'Check system health and dependencies',
@@ -90,22 +89,24 @@ export function formatMainHelp(): string {
90
89
 
91
90
  // Header
92
91
  lines.push('')
93
- lines.push(`${CYAN}${BOLD}prjct${RESET} v${VERSION} - Context layer for AI coding agents`)
94
- 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.'))
95
94
  lines.push('')
96
95
 
97
96
  // Quick Start
98
- lines.push(`${BOLD}QUICK START${RESET}`)
99
- lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
100
- lines.push(` ${GREEN}1.${RESET} prjct start ${DIM}# Configure AI providers${RESET}`)
101
- lines.push(` ${GREEN}2.${RESET} cd my-project && prjct init`)
102
- lines.push(` ${GREEN}3.${RESET} Open in Claude Code / Gemini CLI / Cursor`)
103
- 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')}`)
104
105
  lines.push('')
105
106
 
106
107
  // Terminal Commands
107
- lines.push(`${BOLD}TERMINAL COMMANDS${RESET}`)
108
- lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
108
+ lines.push(chalk.bold('TERMINAL COMMANDS'))
109
+ lines.push(chalk.dim('─'.repeat(60)))
109
110
  for (const cmd of TERMINAL_COMMANDS) {
110
111
  const name = `prjct ${cmd.name}`.padEnd(22)
111
112
  lines.push(` ${name} ${cmd.description}`)
@@ -113,10 +114,10 @@ export function formatMainHelp(): string {
113
114
  lines.push('')
114
115
 
115
116
  // AI Agent Commands
116
- lines.push(`${BOLD}AI AGENT COMMANDS${RESET} ${DIM}(inside Claude/Gemini/Cursor)${RESET}`)
117
- 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)))
118
119
  lines.push(` ${'Command'.padEnd(22)} Description`)
119
- lines.push(` ${DIM}${'─'.repeat(56)}${RESET}`)
120
+ lines.push(` ${chalk.dim('─'.repeat(56))}`)
120
121
 
121
122
  // Core commands
122
123
  const coreCommands = COMMANDS.filter((c) => c.group === 'core' && c.usage?.claude)
@@ -124,22 +125,24 @@ export function formatMainHelp(): string {
124
125
  const usage = `p. ${cmd.name}`.padEnd(22)
125
126
  lines.push(` ${usage} ${cmd.description}`)
126
127
  }
127
- 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
+ )
128
131
  lines.push('')
129
132
 
130
133
  // Global Flags
131
- lines.push(`${BOLD}FLAGS${RESET}`)
132
- lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
134
+ lines.push(chalk.bold('FLAGS'))
135
+ lines.push(chalk.dim('─'.repeat(60)))
133
136
  for (const flag of GLOBAL_FLAGS) {
134
137
  lines.push(` ${flag.flag.padEnd(22)} ${flag.description}`)
135
138
  }
136
139
  lines.push('')
137
140
 
138
141
  // More Info
139
- lines.push(`${BOLD}MORE INFO${RESET}`)
140
- lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
141
- lines.push(` Documentation: ${CYAN}https://prjct.app${RESET}`)
142
- 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')}`)
143
146
  lines.push(` Per-command: prjct help <command>`)
144
147
  lines.push('')
145
148
 
@@ -156,15 +159,15 @@ export function formatTerminalCommandHelp(commandName: string): string | null {
156
159
  const lines: string[] = []
157
160
 
158
161
  lines.push('')
159
- lines.push(`${CYAN}${BOLD}prjct ${cmd.name}${RESET} - ${cmd.description}`)
162
+ lines.push(`${chalk.cyan.bold(`prjct ${cmd.name}`)} - ${cmd.description}`)
160
163
  lines.push('')
161
164
 
162
- lines.push(`${BOLD}USAGE${RESET}`)
165
+ lines.push(chalk.bold('USAGE'))
163
166
  lines.push(` ${cmd.example}`)
164
167
  lines.push('')
165
168
 
166
169
  if (cmd.options) {
167
- lines.push(`${BOLD}OPTIONS${RESET}`)
170
+ lines.push(chalk.bold('OPTIONS'))
168
171
  for (const opt of cmd.options) {
169
172
  lines.push(` ${opt}`)
170
173
  }
@@ -172,7 +175,7 @@ export function formatTerminalCommandHelp(commandName: string): string | null {
172
175
  }
173
176
 
174
177
  if (cmd.subcommands) {
175
- lines.push(`${BOLD}SUBCOMMANDS${RESET}`)
178
+ lines.push(chalk.bold('SUBCOMMANDS'))
176
179
  for (const sub of cmd.subcommands) {
177
180
  lines.push(` ${sub}`)
178
181
  }
@@ -192,10 +195,10 @@ export function formatAgentCommandHelp(commandName: string): string | null {
192
195
  const lines: string[] = []
193
196
 
194
197
  lines.push('')
195
- lines.push(`${CYAN}${BOLD}p. ${cmd.name}${RESET} - ${cmd.description}`)
198
+ lines.push(`${chalk.cyan.bold(`p. ${cmd.name}`)} - ${cmd.description}`)
196
199
  lines.push('')
197
200
 
198
- lines.push(`${BOLD}USAGE${RESET}`)
201
+ lines.push(chalk.bold('USAGE'))
199
202
  if (cmd.usage?.claude) {
200
203
  lines.push(` Claude/Gemini: ${cmd.usage.claude.replace('/p:', 'p. ')}`)
201
204
  }
@@ -205,13 +208,13 @@ export function formatAgentCommandHelp(commandName: string): string | null {
205
208
  lines.push('')
206
209
 
207
210
  if (cmd.params) {
208
- lines.push(`${BOLD}PARAMETERS${RESET}`)
211
+ lines.push(chalk.bold('PARAMETERS'))
209
212
  lines.push(` ${cmd.params}`)
210
213
  lines.push('')
211
214
  }
212
215
 
213
216
  if (cmd.features && cmd.features.length > 0) {
214
- lines.push(`${BOLD}FEATURES${RESET}`)
217
+ lines.push(chalk.bold('FEATURES'))
215
218
  for (const feature of cmd.features) {
216
219
  lines.push(` • ${feature}`)
217
220
  }
@@ -219,17 +222,17 @@ export function formatAgentCommandHelp(commandName: string): string | null {
219
222
  }
220
223
 
221
224
  if (cmd.blockingRules) {
222
- lines.push(`${BOLD}REQUIREMENTS${RESET}`)
223
- lines.push(` ${YELLOW}⚠${RESET} ${cmd.blockingRules.check}`)
225
+ lines.push(chalk.bold('REQUIREMENTS'))
226
+ lines.push(` ${chalk.yellow('⚠')} ${cmd.blockingRules.check}`)
224
227
  lines.push('')
225
228
  }
226
229
 
227
230
  // Category info
228
231
  const category = CATEGORIES[cmd.group]
229
232
  if (category) {
230
- lines.push(`${DIM}Category: ${category.title}${RESET}`)
233
+ lines.push(chalk.dim(`Category: ${category.title}`))
231
234
  if (cmd.isOptional) {
232
- lines.push(`${DIM}This is an optional command.${RESET}`)
235
+ lines.push(chalk.dim('This is an optional command.'))
233
236
  }
234
237
  lines.push('')
235
238
  }
@@ -251,7 +254,7 @@ export function formatCommandHelp(commandName: string): string {
251
254
 
252
255
  // Command not found
253
256
  return `
254
- ${YELLOW}Command '${commandName}' not found.${RESET}
257
+ ${chalk.yellow(`Command '${commandName}' not found.`)}
255
258
 
256
259
  Run 'prjct help' to see all available commands.
257
260
  `
@@ -264,7 +267,7 @@ export function formatCommandList(): string {
264
267
  const lines: string[] = []
265
268
 
266
269
  lines.push('')
267
- lines.push(`${CYAN}${BOLD}All Commands${RESET}`)
270
+ lines.push(chalk.cyan.bold('All Commands'))
268
271
  lines.push('')
269
272
 
270
273
  // Group by category
@@ -275,9 +278,9 @@ export function formatCommandList(): string {
275
278
  if (categoryCommands.length === 0) continue
276
279
 
277
280
  lines.push(
278
- `${BOLD}${category.title}${RESET} ${DIM}(${categoryCommands.length} commands)${RESET}`
281
+ `${chalk.bold(category.title)} ${chalk.dim(`(${categoryCommands.length} commands)`)}`
279
282
  )
280
- lines.push(`${DIM}${category.description}${RESET}`)
283
+ lines.push(chalk.dim(category.description))
281
284
  lines.push('')
282
285
 
283
286
  for (const cmd of categoryCommands) {
@@ -289,7 +292,7 @@ export function formatCommandList(): string {
289
292
  lines.push('')
290
293
  }
291
294
 
292
- 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."))
293
296
  lines.push('')
294
297
 
295
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
  }