prjct-cli 0.46.0 → 0.48.0
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 +49 -3787
- package/bin/prjct.ts +3 -47
- package/core/agentic/command-executor.ts +8 -1
- package/core/agentic/context-builder.ts +4 -4
- package/core/agentic/memory-system.ts +3 -1
- package/core/agentic/prompt-builder.ts +6 -2
- package/core/agentic/smart-context.ts +2 -2
- package/core/ai-tools/generator.ts +11 -1
- package/core/cli/linear.ts +5 -4
- package/core/commands/analytics.ts +3 -1
- package/core/commands/command-data.ts +16 -0
- package/core/commands/commands.ts +8 -1
- package/core/commands/register.ts +1 -0
- package/core/commands/registry.ts +3 -2
- package/core/commands/setup.ts +4 -4
- package/core/commands/shipping.ts +26 -3
- package/core/commands/workflow.ts +105 -2
- package/core/constants/index.ts +3 -1
- package/core/context-tools/imports-tool.ts +1 -1
- package/core/context-tools/signatures-tool.ts +1 -1
- package/core/plugin/hooks.ts +1 -1
- package/core/services/agent-generator.ts +58 -6
- package/core/services/context-generator.ts +35 -15
- package/core/utils/help.ts +320 -0
- package/core/utils/markdown-builder.ts +9 -3
- package/core/utils/preserve-sections.ts +1 -1
- package/core/utils/project-commands.ts +0 -6
- package/core/utils/subtask-table.ts +234 -0
- package/core/workflow/index.ts +1 -0
- package/core/workflow/workflow-preferences.ts +312 -0
- package/dist/bin/prjct.mjs +4540 -3902
- package/package.json +1 -1
- package/templates/commands/workflow.md +150 -0
- package/templates/global/CLAUDE.md +27 -0
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
import fs from 'node:fs/promises'
|
|
11
11
|
import path from 'node:path'
|
|
12
|
+
import {
|
|
13
|
+
hasPreservedSections,
|
|
14
|
+
mergePreservedSections,
|
|
15
|
+
validatePreserveBlocks,
|
|
16
|
+
} from '../utils/preserve-sections'
|
|
12
17
|
import type { StackDetection } from './stack-detector'
|
|
13
18
|
|
|
14
19
|
// ============================================================================
|
|
@@ -65,21 +70,68 @@ export class AgentGenerator {
|
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
/**
|
|
68
|
-
*
|
|
73
|
+
* Cache of existing agent content (for preserving user sections)
|
|
74
|
+
*/
|
|
75
|
+
private existingAgents: Map<string, string> = new Map()
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Read existing agents and cache their content for preservation
|
|
79
|
+
* Then remove the files (they'll be regenerated with preserved sections)
|
|
69
80
|
*/
|
|
70
81
|
private async purgeOldAgents(): Promise<void> {
|
|
82
|
+
this.existingAgents.clear()
|
|
83
|
+
|
|
71
84
|
try {
|
|
72
85
|
const files = await fs.readdir(this.agentsPath)
|
|
86
|
+
const mdFiles = files.filter((file) => file.endsWith('.md'))
|
|
87
|
+
|
|
88
|
+
// Read all existing agent files BEFORE deleting
|
|
73
89
|
await Promise.all(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
mdFiles.map(async (file) => {
|
|
91
|
+
const filePath = path.join(this.agentsPath, file)
|
|
92
|
+
try {
|
|
93
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
94
|
+
// Only cache if it has user-preserved sections
|
|
95
|
+
if (hasPreservedSections(content)) {
|
|
96
|
+
this.existingAgents.set(file, content)
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// File read failed, skip
|
|
100
|
+
}
|
|
101
|
+
})
|
|
77
102
|
)
|
|
103
|
+
|
|
104
|
+
// Now delete the files
|
|
105
|
+
await Promise.all(mdFiles.map((file) => fs.unlink(path.join(this.agentsPath, file))))
|
|
78
106
|
} catch {
|
|
79
107
|
// Directory might not exist yet
|
|
80
108
|
}
|
|
81
109
|
}
|
|
82
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Write agent file, preserving user sections from previous version
|
|
113
|
+
*/
|
|
114
|
+
private async writeAgentWithPreservation(filename: string, content: string): Promise<void> {
|
|
115
|
+
const existingContent = this.existingAgents.get(filename)
|
|
116
|
+
|
|
117
|
+
let finalContent = content
|
|
118
|
+
if (existingContent) {
|
|
119
|
+
// Validate existing preserved blocks
|
|
120
|
+
const validation = validatePreserveBlocks(existingContent)
|
|
121
|
+
if (!validation.valid) {
|
|
122
|
+
console.warn(`⚠️ Agent ${filename} has invalid preserve blocks:`)
|
|
123
|
+
for (const error of validation.errors) {
|
|
124
|
+
console.warn(` ${error}`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Merge preserved sections from old content
|
|
129
|
+
finalContent = mergePreservedSections(content, existingContent)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await fs.writeFile(path.join(this.agentsPath, filename), finalContent, 'utf-8')
|
|
133
|
+
}
|
|
134
|
+
|
|
83
135
|
/**
|
|
84
136
|
* Generate workflow agents (always included)
|
|
85
137
|
*/
|
|
@@ -143,7 +195,7 @@ export class AgentGenerator {
|
|
|
143
195
|
content = this.generateMinimalWorkflowAgent(name)
|
|
144
196
|
}
|
|
145
197
|
|
|
146
|
-
await
|
|
198
|
+
await this.writeAgentWithPreservation(`${name}.md`, content)
|
|
147
199
|
}
|
|
148
200
|
|
|
149
201
|
/**
|
|
@@ -169,7 +221,7 @@ export class AgentGenerator {
|
|
|
169
221
|
content = this.generateMinimalDomainAgent(name, stats, stack)
|
|
170
222
|
}
|
|
171
223
|
|
|
172
|
-
await
|
|
224
|
+
await this.writeAgentWithPreservation(`${name}.md`, content)
|
|
173
225
|
}
|
|
174
226
|
|
|
175
227
|
/**
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import fs from 'node:fs/promises'
|
|
13
13
|
import path from 'node:path'
|
|
14
14
|
import dateHelper from '../utils/date-helper'
|
|
15
|
-
import { mergePreservedSections } from '../utils/preserve-sections'
|
|
15
|
+
import { mergePreservedSections, validatePreserveBlocks } from '../utils/preserve-sections'
|
|
16
16
|
|
|
17
17
|
// ============================================================================
|
|
18
18
|
// TYPES
|
|
@@ -65,6 +65,35 @@ export class ContextFileGenerator {
|
|
|
65
65
|
this.config = config
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Write file with preserved sections from existing content
|
|
70
|
+
* This ensures user customizations survive regeneration
|
|
71
|
+
*/
|
|
72
|
+
private async writeWithPreservation(filePath: string, content: string): Promise<void> {
|
|
73
|
+
let finalContent = content
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const existingContent = await fs.readFile(filePath, 'utf-8')
|
|
77
|
+
|
|
78
|
+
// Validate existing preserved blocks
|
|
79
|
+
const validation = validatePreserveBlocks(existingContent)
|
|
80
|
+
if (!validation.valid) {
|
|
81
|
+
const filename = path.basename(filePath)
|
|
82
|
+
console.warn(`⚠️ ${filename} has invalid preserve blocks:`)
|
|
83
|
+
for (const error of validation.errors) {
|
|
84
|
+
console.warn(` ${error}`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Merge preserved sections from existing content
|
|
89
|
+
finalContent = mergePreservedSections(content, existingContent)
|
|
90
|
+
} catch {
|
|
91
|
+
// File doesn't exist yet - use generated content as-is
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await fs.writeFile(filePath, finalContent, 'utf-8')
|
|
95
|
+
}
|
|
96
|
+
|
|
68
97
|
/**
|
|
69
98
|
* Generate all context files in parallel
|
|
70
99
|
*/
|
|
@@ -181,17 +210,8 @@ Load from \`~/.prjct-cli/projects/${this.config.projectId}/agents/\`:
|
|
|
181
210
|
**Domain**: ${domainAgents.join(', ') || 'none'}
|
|
182
211
|
`
|
|
183
212
|
|
|
184
|
-
// Preserve user customizations from existing file
|
|
185
213
|
const claudePath = path.join(contextPath, 'CLAUDE.md')
|
|
186
|
-
|
|
187
|
-
try {
|
|
188
|
-
const existingContent = await fs.readFile(claudePath, 'utf-8')
|
|
189
|
-
finalContent = mergePreservedSections(content, existingContent)
|
|
190
|
-
} catch {
|
|
191
|
-
// File doesn't exist yet - use generated content as-is
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
await fs.writeFile(claudePath, finalContent, 'utf-8')
|
|
214
|
+
await this.writeWithPreservation(claudePath, content)
|
|
195
215
|
}
|
|
196
216
|
|
|
197
217
|
/**
|
|
@@ -222,7 +242,7 @@ _No active task_
|
|
|
222
242
|
Use \`p. task "description"\` to start working.
|
|
223
243
|
`
|
|
224
244
|
|
|
225
|
-
await
|
|
245
|
+
await this.writeWithPreservation(path.join(contextPath, 'now.md'), content)
|
|
226
246
|
}
|
|
227
247
|
|
|
228
248
|
/**
|
|
@@ -248,7 +268,7 @@ ${
|
|
|
248
268
|
}
|
|
249
269
|
`
|
|
250
270
|
|
|
251
|
-
await
|
|
271
|
+
await this.writeWithPreservation(path.join(contextPath, 'next.md'), content)
|
|
252
272
|
}
|
|
253
273
|
|
|
254
274
|
/**
|
|
@@ -272,7 +292,7 @@ ${
|
|
|
272
292
|
}
|
|
273
293
|
`
|
|
274
294
|
|
|
275
|
-
await
|
|
295
|
+
await this.writeWithPreservation(path.join(contextPath, 'ideas.md'), content)
|
|
276
296
|
}
|
|
277
297
|
|
|
278
298
|
/**
|
|
@@ -303,7 +323,7 @@ ${
|
|
|
303
323
|
**Total shipped:** ${shipped.shipped.length}
|
|
304
324
|
`
|
|
305
325
|
|
|
306
|
-
await
|
|
326
|
+
await this.writeWithPreservation(path.join(contextPath, 'shipped.md'), content)
|
|
307
327
|
}
|
|
308
328
|
}
|
|
309
329
|
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Help System - Structured help output for prjct CLI
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent, well-formatted help text for all commands.
|
|
5
|
+
*
|
|
6
|
+
* @see PRJ-133
|
|
7
|
+
* @module utils/help
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { CATEGORIES, COMMANDS } from '../commands/command-data'
|
|
11
|
+
import { VERSION } from './version'
|
|
12
|
+
|
|
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
|
+
/**
|
|
22
|
+
* Terminal commands that run directly in the shell
|
|
23
|
+
*/
|
|
24
|
+
const TERMINAL_COMMANDS = [
|
|
25
|
+
{
|
|
26
|
+
name: 'start',
|
|
27
|
+
description: 'First-time setup wizard',
|
|
28
|
+
example: 'prjct start',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'init',
|
|
32
|
+
description: 'Initialize project in current directory',
|
|
33
|
+
example: 'prjct init',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'sync',
|
|
37
|
+
description: 'Sync project state and update context files',
|
|
38
|
+
example: 'prjct sync',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'watch',
|
|
42
|
+
description: 'Auto-sync on file changes',
|
|
43
|
+
example: 'prjct watch',
|
|
44
|
+
options: ['--verbose', '--debounce=<ms>', '--interval=<sec>'],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'doctor',
|
|
48
|
+
description: 'Check system health and dependencies',
|
|
49
|
+
example: 'prjct doctor',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'serve',
|
|
53
|
+
description: 'Start web dashboard server',
|
|
54
|
+
example: 'prjct serve [port]',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'context',
|
|
58
|
+
description: 'Smart context filtering tools for AI',
|
|
59
|
+
example: 'prjct context files "add auth"',
|
|
60
|
+
subcommands: ['files', 'signatures', 'imports', 'recent', 'summary'],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'linear',
|
|
64
|
+
description: 'Linear issue tracker CLI',
|
|
65
|
+
example: 'prjct linear list',
|
|
66
|
+
subcommands: ['list', 'get', 'create', 'update'],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'uninstall',
|
|
70
|
+
description: 'Complete system removal of prjct',
|
|
71
|
+
example: 'prjct uninstall --backup',
|
|
72
|
+
options: ['--force', '--backup', '--dry-run', '--keep-package'],
|
|
73
|
+
},
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Global CLI flags
|
|
78
|
+
*/
|
|
79
|
+
const GLOBAL_FLAGS = [
|
|
80
|
+
{ flag: '-q, --quiet', description: 'Suppress all output (errors to stderr only)' },
|
|
81
|
+
{ flag: '-v, --version', description: 'Show version and provider status' },
|
|
82
|
+
{ flag: '-h, --help', description: 'Show this help message' },
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Format the main help output
|
|
87
|
+
*/
|
|
88
|
+
export function formatMainHelp(): string {
|
|
89
|
+
const lines: string[] = []
|
|
90
|
+
|
|
91
|
+
// Header
|
|
92
|
+
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}`)
|
|
95
|
+
lines.push('')
|
|
96
|
+
|
|
97
|
+
// 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}`)
|
|
104
|
+
lines.push('')
|
|
105
|
+
|
|
106
|
+
// Terminal Commands
|
|
107
|
+
lines.push(`${BOLD}TERMINAL COMMANDS${RESET}`)
|
|
108
|
+
lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
|
|
109
|
+
for (const cmd of TERMINAL_COMMANDS) {
|
|
110
|
+
const name = `prjct ${cmd.name}`.padEnd(22)
|
|
111
|
+
lines.push(` ${name} ${cmd.description}`)
|
|
112
|
+
}
|
|
113
|
+
lines.push('')
|
|
114
|
+
|
|
115
|
+
// AI Agent Commands
|
|
116
|
+
lines.push(`${BOLD}AI AGENT COMMANDS${RESET} ${DIM}(inside Claude/Gemini/Cursor)${RESET}`)
|
|
117
|
+
lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
|
|
118
|
+
lines.push(` ${'Command'.padEnd(22)} Description`)
|
|
119
|
+
lines.push(` ${DIM}${'─'.repeat(56)}${RESET}`)
|
|
120
|
+
|
|
121
|
+
// Core commands
|
|
122
|
+
const coreCommands = COMMANDS.filter((c) => c.group === 'core' && c.usage?.claude)
|
|
123
|
+
for (const cmd of coreCommands.slice(0, 10)) {
|
|
124
|
+
const usage = `p. ${cmd.name}`.padEnd(22)
|
|
125
|
+
lines.push(` ${usage} ${cmd.description}`)
|
|
126
|
+
}
|
|
127
|
+
lines.push(` ${DIM}... and ${coreCommands.length - 10} more (run 'prjct help commands')${RESET}`)
|
|
128
|
+
lines.push('')
|
|
129
|
+
|
|
130
|
+
// Global Flags
|
|
131
|
+
lines.push(`${BOLD}FLAGS${RESET}`)
|
|
132
|
+
lines.push(`${DIM}${'─'.repeat(60)}${RESET}`)
|
|
133
|
+
for (const flag of GLOBAL_FLAGS) {
|
|
134
|
+
lines.push(` ${flag.flag.padEnd(22)} ${flag.description}`)
|
|
135
|
+
}
|
|
136
|
+
lines.push('')
|
|
137
|
+
|
|
138
|
+
// 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}`)
|
|
143
|
+
lines.push(` Per-command: prjct help <command>`)
|
|
144
|
+
lines.push('')
|
|
145
|
+
|
|
146
|
+
return lines.join('\n')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Format help for a specific terminal command
|
|
151
|
+
*/
|
|
152
|
+
export function formatTerminalCommandHelp(commandName: string): string | null {
|
|
153
|
+
const cmd = TERMINAL_COMMANDS.find((c) => c.name === commandName)
|
|
154
|
+
if (!cmd) return null
|
|
155
|
+
|
|
156
|
+
const lines: string[] = []
|
|
157
|
+
|
|
158
|
+
lines.push('')
|
|
159
|
+
lines.push(`${CYAN}${BOLD}prjct ${cmd.name}${RESET} - ${cmd.description}`)
|
|
160
|
+
lines.push('')
|
|
161
|
+
|
|
162
|
+
lines.push(`${BOLD}USAGE${RESET}`)
|
|
163
|
+
lines.push(` ${cmd.example}`)
|
|
164
|
+
lines.push('')
|
|
165
|
+
|
|
166
|
+
if (cmd.options) {
|
|
167
|
+
lines.push(`${BOLD}OPTIONS${RESET}`)
|
|
168
|
+
for (const opt of cmd.options) {
|
|
169
|
+
lines.push(` ${opt}`)
|
|
170
|
+
}
|
|
171
|
+
lines.push('')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (cmd.subcommands) {
|
|
175
|
+
lines.push(`${BOLD}SUBCOMMANDS${RESET}`)
|
|
176
|
+
for (const sub of cmd.subcommands) {
|
|
177
|
+
lines.push(` ${sub}`)
|
|
178
|
+
}
|
|
179
|
+
lines.push('')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return lines.join('\n')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Format help for an AI agent command
|
|
187
|
+
*/
|
|
188
|
+
export function formatAgentCommandHelp(commandName: string): string | null {
|
|
189
|
+
const cmd = COMMANDS.find((c) => c.name === commandName)
|
|
190
|
+
if (!cmd) return null
|
|
191
|
+
|
|
192
|
+
const lines: string[] = []
|
|
193
|
+
|
|
194
|
+
lines.push('')
|
|
195
|
+
lines.push(`${CYAN}${BOLD}p. ${cmd.name}${RESET} - ${cmd.description}`)
|
|
196
|
+
lines.push('')
|
|
197
|
+
|
|
198
|
+
lines.push(`${BOLD}USAGE${RESET}`)
|
|
199
|
+
if (cmd.usage?.claude) {
|
|
200
|
+
lines.push(` Claude/Gemini: ${cmd.usage.claude.replace('/p:', 'p. ')}`)
|
|
201
|
+
}
|
|
202
|
+
if (cmd.usage?.terminal) {
|
|
203
|
+
lines.push(` Terminal: ${cmd.usage.terminal}`)
|
|
204
|
+
}
|
|
205
|
+
lines.push('')
|
|
206
|
+
|
|
207
|
+
if (cmd.params) {
|
|
208
|
+
lines.push(`${BOLD}PARAMETERS${RESET}`)
|
|
209
|
+
lines.push(` ${cmd.params}`)
|
|
210
|
+
lines.push('')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (cmd.features && cmd.features.length > 0) {
|
|
214
|
+
lines.push(`${BOLD}FEATURES${RESET}`)
|
|
215
|
+
for (const feature of cmd.features) {
|
|
216
|
+
lines.push(` • ${feature}`)
|
|
217
|
+
}
|
|
218
|
+
lines.push('')
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (cmd.blockingRules) {
|
|
222
|
+
lines.push(`${BOLD}REQUIREMENTS${RESET}`)
|
|
223
|
+
lines.push(` ${YELLOW}⚠${RESET} ${cmd.blockingRules.check}`)
|
|
224
|
+
lines.push('')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Category info
|
|
228
|
+
const category = CATEGORIES[cmd.group]
|
|
229
|
+
if (category) {
|
|
230
|
+
lines.push(`${DIM}Category: ${category.title}${RESET}`)
|
|
231
|
+
if (cmd.isOptional) {
|
|
232
|
+
lines.push(`${DIM}This is an optional command.${RESET}`)
|
|
233
|
+
}
|
|
234
|
+
lines.push('')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return lines.join('\n')
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Format help for a specific command (auto-detect type)
|
|
242
|
+
*/
|
|
243
|
+
export function formatCommandHelp(commandName: string): string {
|
|
244
|
+
// Try terminal command first
|
|
245
|
+
const terminalHelp = formatTerminalCommandHelp(commandName)
|
|
246
|
+
if (terminalHelp) return terminalHelp
|
|
247
|
+
|
|
248
|
+
// Try agent command
|
|
249
|
+
const agentHelp = formatAgentCommandHelp(commandName)
|
|
250
|
+
if (agentHelp) return agentHelp
|
|
251
|
+
|
|
252
|
+
// Command not found
|
|
253
|
+
return `
|
|
254
|
+
${YELLOW}Command '${commandName}' not found.${RESET}
|
|
255
|
+
|
|
256
|
+
Run 'prjct help' to see all available commands.
|
|
257
|
+
`
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Format list of all commands grouped by category
|
|
262
|
+
*/
|
|
263
|
+
export function formatCommandList(): string {
|
|
264
|
+
const lines: string[] = []
|
|
265
|
+
|
|
266
|
+
lines.push('')
|
|
267
|
+
lines.push(`${CYAN}${BOLD}All Commands${RESET}`)
|
|
268
|
+
lines.push('')
|
|
269
|
+
|
|
270
|
+
// Group by category
|
|
271
|
+
const categories = Object.entries(CATEGORIES).sort((a, b) => a[1].order - b[1].order)
|
|
272
|
+
|
|
273
|
+
for (const [categoryKey, category] of categories) {
|
|
274
|
+
const categoryCommands = COMMANDS.filter((c) => c.group === categoryKey)
|
|
275
|
+
if (categoryCommands.length === 0) continue
|
|
276
|
+
|
|
277
|
+
lines.push(
|
|
278
|
+
`${BOLD}${category.title}${RESET} ${DIM}(${categoryCommands.length} commands)${RESET}`
|
|
279
|
+
)
|
|
280
|
+
lines.push(`${DIM}${category.description}${RESET}`)
|
|
281
|
+
lines.push('')
|
|
282
|
+
|
|
283
|
+
for (const cmd of categoryCommands) {
|
|
284
|
+
const name = `p. ${cmd.name}`.padEnd(18)
|
|
285
|
+
const desc =
|
|
286
|
+
cmd.description.length > 45 ? `${cmd.description.slice(0, 42)}...` : cmd.description
|
|
287
|
+
lines.push(` ${name} ${desc}`)
|
|
288
|
+
}
|
|
289
|
+
lines.push('')
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
lines.push(`${DIM}Run 'prjct help <command>' for detailed help on a specific command.${RESET}`)
|
|
293
|
+
lines.push('')
|
|
294
|
+
|
|
295
|
+
return lines.join('\n')
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get help output based on topic
|
|
300
|
+
*/
|
|
301
|
+
export function getHelp(topic?: string): string {
|
|
302
|
+
if (!topic) {
|
|
303
|
+
return formatMainHelp()
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (topic === 'commands' || topic === 'all') {
|
|
307
|
+
return formatCommandList()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return formatCommandHelp(topic)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export default {
|
|
314
|
+
formatMainHelp,
|
|
315
|
+
formatCommandHelp,
|
|
316
|
+
formatCommandList,
|
|
317
|
+
formatTerminalCommandHelp,
|
|
318
|
+
formatAgentCommandHelp,
|
|
319
|
+
getHelp,
|
|
320
|
+
}
|
|
@@ -117,7 +117,9 @@ export class MarkdownBuilder {
|
|
|
117
117
|
* Add multiple list items
|
|
118
118
|
*/
|
|
119
119
|
list(items: string[], options?: { checked?: boolean }): this {
|
|
120
|
-
|
|
120
|
+
for (const item of items) {
|
|
121
|
+
this.li(item, options)
|
|
122
|
+
}
|
|
121
123
|
return this
|
|
122
124
|
}
|
|
123
125
|
|
|
@@ -125,7 +127,9 @@ export class MarkdownBuilder {
|
|
|
125
127
|
* Add multiple numbered list items
|
|
126
128
|
*/
|
|
127
129
|
orderedList(items: string[]): this {
|
|
128
|
-
|
|
130
|
+
for (let i = 0; i < items.length; i++) {
|
|
131
|
+
this.oli(items[i], i + 1)
|
|
132
|
+
}
|
|
129
133
|
return this
|
|
130
134
|
}
|
|
131
135
|
|
|
@@ -225,7 +229,9 @@ export class MarkdownBuilder {
|
|
|
225
229
|
* Iterate and build for each item
|
|
226
230
|
*/
|
|
227
231
|
each<T>(items: T[], builder: (md: MarkdownBuilder, item: T, index: number) => void): this {
|
|
228
|
-
|
|
232
|
+
for (let i = 0; i < items.length; i++) {
|
|
233
|
+
builder(this, items[i], i)
|
|
234
|
+
}
|
|
229
235
|
return this
|
|
230
236
|
}
|
|
231
237
|
|
|
@@ -3,12 +3,6 @@ import type { DetectedProjectCommands } from '../types'
|
|
|
3
3
|
import * as fileHelper from './file-helper'
|
|
4
4
|
|
|
5
5
|
type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'
|
|
6
|
-
type DetectedStack = 'js' | 'python' | 'go' | 'rust' | 'dotnet' | 'java' | 'unknown'
|
|
7
|
-
|
|
8
|
-
interface DetectedCommand {
|
|
9
|
-
command: string
|
|
10
|
-
tool: string
|
|
11
|
-
}
|
|
12
6
|
|
|
13
7
|
interface PackageJson {
|
|
14
8
|
scripts?: Record<string, string>
|