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 +81 -0
- package/bin/prjct.ts +25 -31
- package/core/agentic/ground-truth.ts +5 -3
- package/core/cli/start.ts +45 -59
- package/core/index.ts +20 -24
- package/core/infrastructure/setup.ts +29 -32
- package/core/schemas/ideas.ts +1 -1
- package/core/services/hooks-service.ts +3 -4
- package/core/utils/help.ts +42 -45
- package/core/utils/subtask-table.ts +27 -34
- package/core/workflow/workflow-preferences.ts +11 -17
- package/dist/bin/prjct.mjs +408 -439
- package/dist/core/infrastructure/setup.js +29 -30
- package/package.json +1 -1
|
@@ -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(`${
|
|
75
|
+
console.log(`${chalk.yellow('⚠️ npm is not available')}`)
|
|
81
76
|
console.log('')
|
|
82
|
-
console.log(`${
|
|
83
|
-
console.log(
|
|
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
|
-
|
|
80
|
+
chalk.dim(
|
|
81
|
+
` • Use Homebrew: brew install ${provider.name === 'claude' ? 'claude' : 'gemini'}`
|
|
82
|
+
)
|
|
86
83
|
)
|
|
87
|
-
console.log(
|
|
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(
|
|
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(`${
|
|
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(
|
|
106
|
+
console.log(chalk.yellow(`⚠️ Installation timed out for ${provider.displayName}`))
|
|
110
107
|
console.log('')
|
|
111
|
-
console.log(
|
|
112
|
-
console.log(
|
|
113
|
-
console.log(
|
|
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(
|
|
112
|
+
console.log(chalk.yellow(`⚠️ Failed to install ${provider.displayName}: ${err.message}`))
|
|
116
113
|
}
|
|
117
114
|
console.log('')
|
|
118
|
-
console.log(
|
|
119
|
-
console.log(
|
|
120
|
-
console.log(
|
|
121
|
-
console.log(
|
|
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
|
-
|
|
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(` ${
|
|
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(` ${
|
|
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(` ${
|
|
1029
|
+
console.log(` ${chalk.green('✓')} ${provider.displayName} CLI installed`)
|
|
1033
1030
|
} else {
|
|
1034
|
-
console.log(` ${
|
|
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(` ${
|
|
1039
|
+
console.log(` ${chalk.green('✓')} Commands synced (${parts.join(', ')})`)
|
|
1043
1040
|
} else {
|
|
1044
|
-
console.log(` ${
|
|
1041
|
+
console.log(` ${chalk.green('✓')} Commands up to date`)
|
|
1045
1042
|
}
|
|
1046
1043
|
|
|
1047
1044
|
if (results.configAction === 'created') {
|
|
1048
|
-
console.log(` ${
|
|
1045
|
+
console.log(` ${chalk.green('✓')} Global config created (${provider.contextFile})`)
|
|
1049
1046
|
} else if (results.configAction === 'updated') {
|
|
1050
|
-
console.log(` ${
|
|
1047
|
+
console.log(` ${chalk.green('✓')} Global config updated (${provider.contextFile})`)
|
|
1051
1048
|
} else if (results.configAction === 'appended') {
|
|
1052
|
-
console.log(` ${
|
|
1049
|
+
console.log(` ${chalk.green('✓')} Global config merged (${provider.contextFile})`)
|
|
1053
1050
|
}
|
|
1054
1051
|
|
|
1055
1052
|
console.log('')
|
package/core/schemas/ideas.ts
CHANGED
|
@@ -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(), // "
|
|
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()
|
|
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,
|
|
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()
|
|
293
|
+
fs.writeFileSync(configPath, `${content.trimEnd()}\n`, 'utf-8')
|
|
295
294
|
return true
|
|
296
295
|
}
|
|
297
296
|
|
package/core/utils/help.ts
CHANGED
|
@@ -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(`${
|
|
100
|
-
lines.push(
|
|
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(
|
|
105
|
-
lines.push(
|
|
106
|
-
lines.push(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
lines.push(` ${
|
|
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(
|
|
114
|
-
lines.push(
|
|
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(`${
|
|
123
|
-
lines.push(
|
|
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(` ${
|
|
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(
|
|
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(
|
|
138
|
-
lines.push(
|
|
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(
|
|
146
|
-
lines.push(
|
|
147
|
-
lines.push(` Documentation: ${
|
|
148
|
-
lines.push(` GitHub: ${
|
|
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(`${
|
|
162
|
+
lines.push(`${chalk.cyan.bold(`prjct ${cmd.name}`)} - ${cmd.description}`)
|
|
166
163
|
lines.push('')
|
|
167
164
|
|
|
168
|
-
lines.push(
|
|
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(
|
|
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(
|
|
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(`${
|
|
198
|
+
lines.push(`${chalk.cyan.bold(`p. ${cmd.name}`)} - ${cmd.description}`)
|
|
202
199
|
lines.push('')
|
|
203
200
|
|
|
204
|
-
lines.push(
|
|
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(
|
|
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(
|
|
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(
|
|
229
|
-
lines.push(` ${
|
|
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(
|
|
233
|
+
lines.push(chalk.dim(`Category: ${category.title}`))
|
|
237
234
|
if (cmd.isOptional) {
|
|
238
|
-
lines.push(
|
|
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
|
-
${
|
|
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(
|
|
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
|
-
`${
|
|
281
|
+
`${chalk.bold(category.title)} ${chalk.dim(`(${categoryCommands.length} commands)`)}`
|
|
285
282
|
)
|
|
286
|
-
lines.push(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
//
|
|
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 =
|
|
71
|
-
const
|
|
72
|
-
const domain =
|
|
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 =
|
|
74
|
+
status = chalk.green('✓ Complete')
|
|
82
75
|
break
|
|
83
76
|
case 'in_progress':
|
|
84
|
-
status = `${
|
|
77
|
+
status = chalk.yellow(`${spinnerFrame} Working...`)
|
|
85
78
|
break
|
|
86
79
|
case 'pending':
|
|
87
|
-
status =
|
|
80
|
+
status = chalk.gray('○ Pending')
|
|
88
81
|
break
|
|
89
82
|
case 'failed':
|
|
90
|
-
status =
|
|
83
|
+
status = chalk.red('✗ Failed')
|
|
91
84
|
break
|
|
92
85
|
case 'blocked':
|
|
93
|
-
status =
|
|
86
|
+
status = chalk.gray('⊘ Blocked')
|
|
94
87
|
break
|
|
95
88
|
default:
|
|
96
|
-
status =
|
|
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(` ${
|
|
112
|
-
lines.push(` ${
|
|
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(` ${
|
|
145
|
-
lines.push(` ${
|
|
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(` ${
|
|
208
|
-
finalLines.push(` ${
|
|
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 ` ${
|
|
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${
|
|
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(`${
|
|
177
|
+
console.log(`${chalk.green('✓')} ${chalk.dim(`(${timeStr})`)}`)
|
|
184
178
|
return { success: true }
|
|
185
179
|
} catch (error) {
|
|
186
|
-
console.log(
|
|
180
|
+
console.log(chalk.red('✗ failed'))
|
|
187
181
|
const errorMessage = (error as Error).message || 'Unknown error'
|
|
188
|
-
console.log(
|
|
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 `${
|
|
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
|
-
?
|
|
275
|
+
? chalk.green('permanent')
|
|
282
276
|
: pref.scope === 'session'
|
|
283
|
-
?
|
|
284
|
-
:
|
|
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(
|
|
291
|
-
lines.push(
|
|
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
|
}
|