prjct-cli 0.4.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 +312 -0
- package/CLAUDE.md +300 -0
- package/LICENSE +21 -0
- package/README.md +424 -0
- package/bin/prjct +214 -0
- package/core/agent-detector.js +249 -0
- package/core/agents/claude-agent.js +250 -0
- package/core/agents/codex-agent.js +256 -0
- package/core/agents/terminal-agent.js +465 -0
- package/core/analyzer.js +596 -0
- package/core/animations-simple.js +240 -0
- package/core/animations.js +277 -0
- package/core/author-detector.js +218 -0
- package/core/capability-installer.js +190 -0
- package/core/command-installer.js +775 -0
- package/core/commands.js +2050 -0
- package/core/config-manager.js +335 -0
- package/core/migrator.js +784 -0
- package/core/path-manager.js +324 -0
- package/core/project-capabilities.js +144 -0
- package/core/session-manager.js +439 -0
- package/core/version.js +107 -0
- package/core/workflow-engine.js +213 -0
- package/core/workflow-prompts.js +192 -0
- package/core/workflow-rules.js +147 -0
- package/package.json +80 -0
- package/scripts/install.sh +433 -0
- package/scripts/verify-installation.sh +158 -0
- package/templates/agents/AGENTS.md +164 -0
- package/templates/commands/analyze.md +125 -0
- package/templates/commands/cleanup.md +102 -0
- package/templates/commands/context.md +105 -0
- package/templates/commands/design.md +113 -0
- package/templates/commands/done.md +44 -0
- package/templates/commands/fix.md +87 -0
- package/templates/commands/git.md +79 -0
- package/templates/commands/help.md +72 -0
- package/templates/commands/idea.md +50 -0
- package/templates/commands/init.md +237 -0
- package/templates/commands/next.md +74 -0
- package/templates/commands/now.md +35 -0
- package/templates/commands/progress.md +92 -0
- package/templates/commands/recap.md +86 -0
- package/templates/commands/roadmap.md +107 -0
- package/templates/commands/ship.md +41 -0
- package/templates/commands/stuck.md +48 -0
- package/templates/commands/task.md +97 -0
- package/templates/commands/test.md +94 -0
- package/templates/commands/workflow.md +224 -0
- package/templates/examples/natural-language-examples.md +320 -0
- package/templates/mcp-config.json +8 -0
- package/templates/workflows/analyze.md +159 -0
- package/templates/workflows/cleanup.md +73 -0
- package/templates/workflows/context.md +72 -0
- package/templates/workflows/design.md +88 -0
- package/templates/workflows/done.md +20 -0
- package/templates/workflows/fix.md +201 -0
- package/templates/workflows/git.md +192 -0
- package/templates/workflows/help.md +13 -0
- package/templates/workflows/idea.md +22 -0
- package/templates/workflows/init.md +80 -0
- package/templates/workflows/natural-language-handler.md +183 -0
- package/templates/workflows/next.md +44 -0
- package/templates/workflows/now.md +19 -0
- package/templates/workflows/progress.md +113 -0
- package/templates/workflows/recap.md +66 -0
- package/templates/workflows/roadmap.md +95 -0
- package/templates/workflows/ship.md +18 -0
- package/templates/workflows/stuck.md +25 -0
- package/templates/workflows/task.md +109 -0
- package/templates/workflows/test.md +243 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Codex Agent Adapter
|
|
3
|
+
* Implements prjct commands for OpenAI Codex environment
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs').promises
|
|
7
|
+
|
|
8
|
+
class CodexAgent {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.name = 'OpenAI Codex'
|
|
11
|
+
this.type = 'codex'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format response for Codex with structured output
|
|
16
|
+
* Codex runs in sandboxed environments, so we use clear text formatting
|
|
17
|
+
*/
|
|
18
|
+
formatResponse(message, type = 'info') {
|
|
19
|
+
const prefixes = {
|
|
20
|
+
success: '[SUCCESS]',
|
|
21
|
+
error: '[ERROR]',
|
|
22
|
+
warning: '[WARNING]',
|
|
23
|
+
info: '[INFO]',
|
|
24
|
+
celebrate: '[SHIPPED]',
|
|
25
|
+
ship: '[FEATURE]',
|
|
26
|
+
focus: '[FOCUS]',
|
|
27
|
+
idea: '[IDEA]',
|
|
28
|
+
progress: '[PROGRESS]',
|
|
29
|
+
task: '[TASK]',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const prefix = prefixes[type] || prefixes.info
|
|
33
|
+
|
|
34
|
+
return `${prefix} ${message}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Read file using native fs (Codex doesn't have MCP)
|
|
39
|
+
*/
|
|
40
|
+
async readFile(filePath) {
|
|
41
|
+
try {
|
|
42
|
+
return await fs.readFile(filePath, 'utf8')
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new Error(`Failed to read file ${filePath}: ${error.message}`)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Write file using native fs
|
|
50
|
+
*/
|
|
51
|
+
async writeFile(filePath, content) {
|
|
52
|
+
try {
|
|
53
|
+
await fs.writeFile(filePath, content, 'utf8')
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new Error(`Failed to write file ${filePath}: ${error.message}`)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* List directory contents
|
|
61
|
+
*/
|
|
62
|
+
async listDirectory(dirPath) {
|
|
63
|
+
try {
|
|
64
|
+
return await fs.readdir(dirPath)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new Error(`Failed to list directory ${dirPath}: ${error.message}`)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if file exists
|
|
72
|
+
*/
|
|
73
|
+
async fileExists(filePath) {
|
|
74
|
+
try {
|
|
75
|
+
await fs.access(filePath)
|
|
76
|
+
return true
|
|
77
|
+
} catch {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create directory
|
|
84
|
+
*/
|
|
85
|
+
async createDirectory(dirPath) {
|
|
86
|
+
try {
|
|
87
|
+
await fs.mkdir(dirPath, { recursive: true })
|
|
88
|
+
} catch (error) {
|
|
89
|
+
throw new Error(`Failed to create directory ${dirPath}: ${error.message}`)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get current timestamp in ISO format
|
|
95
|
+
*/
|
|
96
|
+
getTimestamp() {
|
|
97
|
+
return new Date().toISOString()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Format task list with structured output
|
|
102
|
+
*/
|
|
103
|
+
formatTaskList(tasks) {
|
|
104
|
+
if (!tasks || tasks.length === 0) {
|
|
105
|
+
return this.formatResponse('No tasks in queue', 'info')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let output = 'TASK QUEUE:\n'
|
|
109
|
+
output += '===========\n'
|
|
110
|
+
tasks.forEach((task, index) => {
|
|
111
|
+
output += ` ${index + 1}. ${task}\n`
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return output
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Format recap with structured output
|
|
119
|
+
*/
|
|
120
|
+
formatRecap(data) {
|
|
121
|
+
const output = `
|
|
122
|
+
PROJECT RECAP
|
|
123
|
+
=============
|
|
124
|
+
|
|
125
|
+
Current Task: ${data.currentTask || 'None'}
|
|
126
|
+
Shipped Features: ${data.shippedCount} total
|
|
127
|
+
Queued Tasks: ${data.queuedCount}
|
|
128
|
+
Ideas Captured: ${data.ideasCount}
|
|
129
|
+
|
|
130
|
+
${data.recentActivity ? 'Recent Activity:\n' + data.recentActivity : ''}
|
|
131
|
+
|
|
132
|
+
Status: ${data.shippedCount > 0 ? 'Productive' : 'Getting Started'}
|
|
133
|
+
`.trim()
|
|
134
|
+
|
|
135
|
+
return output
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Format progress report
|
|
140
|
+
*/
|
|
141
|
+
formatProgress(data) {
|
|
142
|
+
const trend =
|
|
143
|
+
data.velocity > data.previousVelocity
|
|
144
|
+
? 'UP'
|
|
145
|
+
: data.velocity < data.previousVelocity
|
|
146
|
+
? 'DOWN'
|
|
147
|
+
: 'STEADY'
|
|
148
|
+
|
|
149
|
+
const output = `
|
|
150
|
+
PROGRESS REPORT (${data.period.toUpperCase()})
|
|
151
|
+
=====================================
|
|
152
|
+
|
|
153
|
+
Features Shipped: ${data.count}
|
|
154
|
+
Velocity: ${data.velocity.toFixed(1)} features/day
|
|
155
|
+
Trend: ${trend}
|
|
156
|
+
|
|
157
|
+
${data.recentFeatures ? 'Recent Features:\n' + data.recentFeatures : ''}
|
|
158
|
+
|
|
159
|
+
${data.motivationalMessage}
|
|
160
|
+
`.trim()
|
|
161
|
+
|
|
162
|
+
return output
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get help content based on issue type
|
|
167
|
+
* Structured format for Codex readability
|
|
168
|
+
*/
|
|
169
|
+
getHelpContent(issue) {
|
|
170
|
+
const helps = {
|
|
171
|
+
debugging: `
|
|
172
|
+
DEBUGGING STRATEGY:
|
|
173
|
+
===================
|
|
174
|
+
1. ISOLATE - Comment out code until error disappears
|
|
175
|
+
2. LOG - Add console.log at key points
|
|
176
|
+
3. SIMPLIFY - Create minimal reproduction
|
|
177
|
+
4. RESEARCH - Search for exact error message
|
|
178
|
+
5. BREAK - Take a walk, fresh perspective helps
|
|
179
|
+
`,
|
|
180
|
+
design: `
|
|
181
|
+
DESIGN APPROACH:
|
|
182
|
+
================
|
|
183
|
+
1. START SIMPLE - Basic version first
|
|
184
|
+
2. USER FIRST - What problem does this solve?
|
|
185
|
+
3. ITERATE - Ship v1, improve based on feedback
|
|
186
|
+
4. PATTERNS - Look for similar solutions
|
|
187
|
+
5. VALIDATE - Show mockup before building
|
|
188
|
+
`,
|
|
189
|
+
performance: `
|
|
190
|
+
PERFORMANCE STRATEGY:
|
|
191
|
+
====================
|
|
192
|
+
1. MEASURE FIRST - Profile before optimizing
|
|
193
|
+
2. BIGGEST WINS - Focus on slowest parts
|
|
194
|
+
3. CACHE - Store expensive computations
|
|
195
|
+
4. LAZY LOAD - Defer non-critical work
|
|
196
|
+
5. MONITOR - Track improvements
|
|
197
|
+
`,
|
|
198
|
+
default: `
|
|
199
|
+
GENERAL STRATEGY:
|
|
200
|
+
================
|
|
201
|
+
1. BREAK IT DOWN - Divide into smaller tasks
|
|
202
|
+
2. START SMALL - Implement simplest part first
|
|
203
|
+
3. TEST OFTEN - Verify each step works
|
|
204
|
+
4. DOCUMENT - Write down what you learn
|
|
205
|
+
5. SHIP IT - Perfect is the enemy of done
|
|
206
|
+
`,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const helpType =
|
|
210
|
+
Object.keys(helps).find((key) => issue.toLowerCase().includes(key)) || 'default'
|
|
211
|
+
|
|
212
|
+
return helps[helpType]
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Suggest next action based on context
|
|
217
|
+
* Clear, actionable suggestions for Codex
|
|
218
|
+
*/
|
|
219
|
+
suggestNextAction(context) {
|
|
220
|
+
const suggestions = {
|
|
221
|
+
taskCompleted: 'NEXT: Check task queue with /p:next',
|
|
222
|
+
featureShipped: 'NEXT: Set new focus with /p:now [task]',
|
|
223
|
+
ideaCaptured: 'NEXT: Start working with /p:now [task]',
|
|
224
|
+
initialized: 'NEXT: Set first task with /p:now "your first task"',
|
|
225
|
+
stuck: 'NEXT: Break down problem with /p:idea for each step',
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return suggestions[context] || 'NEXT: What would you like to work on?'
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Handle sandboxed environment limitations
|
|
233
|
+
*/
|
|
234
|
+
async ensureSandboxCompatibility() {
|
|
235
|
+
const isSandboxed =
|
|
236
|
+
process.cwd().includes('/sandbox/') ||
|
|
237
|
+
process.cwd().includes('/tmp/') ||
|
|
238
|
+
process.env.CODEX_SANDBOX
|
|
239
|
+
|
|
240
|
+
if (isSandboxed) {
|
|
241
|
+
return {
|
|
242
|
+
sandboxed: true,
|
|
243
|
+
basePath: process.cwd(),
|
|
244
|
+
restrictions: ['no-network', 'limited-filesystem'],
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
sandboxed: false,
|
|
250
|
+
basePath: process.cwd(),
|
|
251
|
+
restrictions: [],
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = CodexAgent
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal Agent Adapter
|
|
3
|
+
* Implements prjct commands for terminal/CLI environment
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs').promises
|
|
7
|
+
|
|
8
|
+
let chalk
|
|
9
|
+
let animations
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
chalk = require('chalk')
|
|
13
|
+
|
|
14
|
+
if (chalk.supportsColor && chalk.supportsColor.has16m) {
|
|
15
|
+
animations = require('../animations')
|
|
16
|
+
} else {
|
|
17
|
+
animations = require('../animations-simple')
|
|
18
|
+
}
|
|
19
|
+
} catch (e) {
|
|
20
|
+
chalk = {
|
|
21
|
+
green: (str) => str,
|
|
22
|
+
blue: (str) => str,
|
|
23
|
+
yellow: (str) => str,
|
|
24
|
+
red: (str) => str,
|
|
25
|
+
cyan: (str) => str,
|
|
26
|
+
magenta: (str) => str,
|
|
27
|
+
bold: (str) => str,
|
|
28
|
+
dim: (str) => str,
|
|
29
|
+
}
|
|
30
|
+
animations = null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class TerminalAgent {
|
|
34
|
+
constructor() {
|
|
35
|
+
this.name = 'Terminal/CLI'
|
|
36
|
+
this.type = 'terminal'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Format response for terminal with ANSI colors and emojis
|
|
41
|
+
*/
|
|
42
|
+
formatResponse(message, type = 'info') {
|
|
43
|
+
if (animations) {
|
|
44
|
+
if (type === 'success' && message.includes('Cleanup complete')) {
|
|
45
|
+
const filesMatch = message.match(/Files removed: (\d+)/)
|
|
46
|
+
const tasksMatch = message.match(/Tasks archived: (\d+)/)
|
|
47
|
+
const spaceMatch = message.match(/Space freed: ([\d.]+)/)
|
|
48
|
+
|
|
49
|
+
if (filesMatch && tasksMatch && spaceMatch) {
|
|
50
|
+
return animations.formatCleanup(
|
|
51
|
+
parseInt(filesMatch[1]),
|
|
52
|
+
parseInt(tasksMatch[1]),
|
|
53
|
+
parseFloat(spaceMatch[1]),
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
switch (type) {
|
|
59
|
+
case 'success':
|
|
60
|
+
return animations.formatSuccess(message)
|
|
61
|
+
case 'error':
|
|
62
|
+
return animations.formatError(message)
|
|
63
|
+
case 'ship':
|
|
64
|
+
return animations.formatShip(message, 1)
|
|
65
|
+
case 'focus':
|
|
66
|
+
return animations.formatFocus(message, new Date().toISOString())
|
|
67
|
+
case 'idea':
|
|
68
|
+
return animations.formatIdea(message)
|
|
69
|
+
default: {
|
|
70
|
+
const emojis = {
|
|
71
|
+
warning: '⚠️',
|
|
72
|
+
info: 'ℹ️',
|
|
73
|
+
celebrate: '🎉',
|
|
74
|
+
progress: '📊',
|
|
75
|
+
task: '📝',
|
|
76
|
+
}
|
|
77
|
+
const emoji = emojis[type] || '💬'
|
|
78
|
+
return `${emoji} ${animations.colors.text(message)}`
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const emojis = {
|
|
84
|
+
success: '✅',
|
|
85
|
+
error: '❌',
|
|
86
|
+
warning: '⚠️',
|
|
87
|
+
info: 'ℹ️',
|
|
88
|
+
celebrate: '🎉',
|
|
89
|
+
ship: '🚀',
|
|
90
|
+
focus: '🎯',
|
|
91
|
+
idea: '💡',
|
|
92
|
+
progress: '📊',
|
|
93
|
+
task: '📝',
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const colors = {
|
|
97
|
+
success: chalk.green,
|
|
98
|
+
error: chalk.red,
|
|
99
|
+
warning: chalk.yellow,
|
|
100
|
+
info: chalk.blue,
|
|
101
|
+
celebrate: chalk.magenta,
|
|
102
|
+
ship: chalk.cyan,
|
|
103
|
+
focus: chalk.green,
|
|
104
|
+
idea: chalk.yellow,
|
|
105
|
+
progress: chalk.blue,
|
|
106
|
+
task: chalk.cyan,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const emoji = emojis[type] || emojis.info
|
|
110
|
+
const color = colors[type] || colors.info
|
|
111
|
+
|
|
112
|
+
return `${emoji} ${color(message)}`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Read file using native fs
|
|
117
|
+
*/
|
|
118
|
+
async readFile(filePath) {
|
|
119
|
+
try {
|
|
120
|
+
return await fs.readFile(filePath, 'utf8')
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw new Error(`Failed to read file ${filePath}: ${error.message}`)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Write file using native fs
|
|
128
|
+
*/
|
|
129
|
+
async writeFile(filePath, content) {
|
|
130
|
+
try {
|
|
131
|
+
await fs.writeFile(filePath, content, 'utf8')
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw new Error(`Failed to write file ${filePath}: ${error.message}`)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* List directory contents
|
|
139
|
+
*/
|
|
140
|
+
async listDirectory(dirPath) {
|
|
141
|
+
try {
|
|
142
|
+
return await fs.readdir(dirPath)
|
|
143
|
+
} catch (error) {
|
|
144
|
+
throw new Error(`Failed to list directory ${dirPath}: ${error.message}`)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if file exists
|
|
150
|
+
*/
|
|
151
|
+
async fileExists(filePath) {
|
|
152
|
+
try {
|
|
153
|
+
await fs.access(filePath)
|
|
154
|
+
return true
|
|
155
|
+
} catch {
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Create directory
|
|
162
|
+
*/
|
|
163
|
+
async createDirectory(dirPath) {
|
|
164
|
+
try {
|
|
165
|
+
await fs.mkdir(dirPath, { recursive: true })
|
|
166
|
+
} catch (error) {
|
|
167
|
+
throw new Error(`Failed to create directory ${dirPath}: ${error.message}`)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get current timestamp in ISO format
|
|
173
|
+
*/
|
|
174
|
+
getTimestamp() {
|
|
175
|
+
return new Date().toISOString()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Format task list with colors
|
|
180
|
+
*/
|
|
181
|
+
formatTaskList(tasks) {
|
|
182
|
+
if (!tasks || tasks.length === 0) {
|
|
183
|
+
return this.formatResponse('No tasks in queue', 'info')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (animations) {
|
|
187
|
+
let output = animations.colors.primary.bold('\n📋 Task Queue\n')
|
|
188
|
+
output += animations.colors.dim('─'.repeat(40)) + '\n'
|
|
189
|
+
|
|
190
|
+
tasks.forEach((task, index) => {
|
|
191
|
+
output += animations.colors.task(` ${index + 1}.`) + ` ${animations.colors.text(task)}\n`
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
return output
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let output = chalk.bold('\n📋 Task Queue\n')
|
|
198
|
+
output += chalk.dim('─'.repeat(40)) + '\n'
|
|
199
|
+
|
|
200
|
+
tasks.forEach((task, index) => {
|
|
201
|
+
output += chalk.cyan(` ${index + 1}.`) + ` ${task}\n`
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
return output
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Format recap with colored output
|
|
209
|
+
*/
|
|
210
|
+
formatRecap(data) {
|
|
211
|
+
if (animations) {
|
|
212
|
+
return animations.formatRecap(data)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let output = '\n' + chalk.bold('📊 Project Recap\n')
|
|
216
|
+
output += chalk.dim('─'.repeat(40)) + '\n\n'
|
|
217
|
+
|
|
218
|
+
output += `${chalk.green('🎯 Current Task:')} ${data.currentTask || chalk.dim('None')}\n`
|
|
219
|
+
output += `${chalk.cyan('🚀 Shipped Features:')} ${chalk.bold(data.shippedCount)} total\n`
|
|
220
|
+
output += `${chalk.blue('📝 Queued Tasks:')} ${data.queuedCount}\n`
|
|
221
|
+
output += `${chalk.yellow('💡 Ideas Captured:')} ${data.ideasCount}\n`
|
|
222
|
+
|
|
223
|
+
if (data.recentActivity) {
|
|
224
|
+
output += '\n' + chalk.bold('Recent Activity:\n')
|
|
225
|
+
output += data.recentActivity
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
output += '\n' + chalk.dim('─'.repeat(40))
|
|
229
|
+
output += '\n' + chalk.green('💪 Keep shipping! Every feature counts!')
|
|
230
|
+
|
|
231
|
+
return output
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Format progress report
|
|
236
|
+
*/
|
|
237
|
+
formatProgress(data) {
|
|
238
|
+
if (animations) {
|
|
239
|
+
const trend =
|
|
240
|
+
data.velocity > data.previousVelocity
|
|
241
|
+
? '📈'
|
|
242
|
+
: data.velocity < data.previousVelocity
|
|
243
|
+
? '📉'
|
|
244
|
+
: '➡️'
|
|
245
|
+
|
|
246
|
+
const divider = animations.colors.primary('━'.repeat(40))
|
|
247
|
+
|
|
248
|
+
let output = '\n' + divider + '\n'
|
|
249
|
+
output += animations.colors.primary.bold(`📊 PROGRESS REPORT - ${data.period.toUpperCase()}\n`)
|
|
250
|
+
output += divider + '\n\n'
|
|
251
|
+
|
|
252
|
+
output += animations.colors.text('Features Shipped: ') + animations.colors.success.bold(data.count) + '\n'
|
|
253
|
+
output += animations.colors.text('Velocity: ') + animations.colors.info.bold(data.velocity.toFixed(1) + ' features/day') + ' ' + trend + '\n'
|
|
254
|
+
|
|
255
|
+
if (data.recentFeatures) {
|
|
256
|
+
output += '\n' + animations.colors.primary.bold('Recent Wins:\n')
|
|
257
|
+
output += data.recentFeatures
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
output += '\n' + divider
|
|
261
|
+
output += '\n' + animations.colors.celebrate(data.motivationalMessage || '🚀 Keep shipping!')
|
|
262
|
+
|
|
263
|
+
return output
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const trend =
|
|
267
|
+
data.velocity > data.previousVelocity
|
|
268
|
+
? '📈'
|
|
269
|
+
: data.velocity < data.previousVelocity
|
|
270
|
+
? '📉'
|
|
271
|
+
: '➡️'
|
|
272
|
+
|
|
273
|
+
let output = '\n' + chalk.bold(`📊 Progress Report (${data.period})\n`)
|
|
274
|
+
output += chalk.dim('─'.repeat(40)) + '\n\n'
|
|
275
|
+
|
|
276
|
+
output += `${chalk.green('Features Shipped:')} ${chalk.bold(data.count)}\n`
|
|
277
|
+
output += `${chalk.blue('Velocity:')} ${data.velocity.toFixed(1)} features/day ${trend}\n`
|
|
278
|
+
|
|
279
|
+
if (data.recentFeatures) {
|
|
280
|
+
output += '\n' + chalk.bold('Recent Wins:\n')
|
|
281
|
+
output += data.recentFeatures
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
output += '\n' + chalk.dim('─'.repeat(40))
|
|
285
|
+
output += '\n' + chalk.green(data.motivationalMessage)
|
|
286
|
+
|
|
287
|
+
return output
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get help content based on issue type
|
|
292
|
+
*/
|
|
293
|
+
getHelpContent(issue) {
|
|
294
|
+
if (animations) {
|
|
295
|
+
const c = animations.colors
|
|
296
|
+
const helps = {
|
|
297
|
+
debugging: `
|
|
298
|
+
${c.error.bold('🔍 Debugging Strategy:')}
|
|
299
|
+
${c.secondary('1.')} ${c.text.bold('Isolate')} - Comment out code until error disappears
|
|
300
|
+
${c.secondary('2.')} ${c.text.bold('Log')} - Add console.log at key points
|
|
301
|
+
${c.secondary('3.')} ${c.text.bold('Simplify')} - Create minimal reproduction
|
|
302
|
+
${c.secondary('4.')} ${c.text.bold('Research')} - Search for exact error message
|
|
303
|
+
${c.secondary('5.')} ${c.text.bold('Break')} - Take a walk, fresh perspective helps!
|
|
304
|
+
`,
|
|
305
|
+
design: `
|
|
306
|
+
${c.primary.bold('🎨 Design Approach:')}
|
|
307
|
+
${c.secondary('1.')} ${c.text.bold('Start Simple')} - Basic version first
|
|
308
|
+
${c.secondary('2.')} ${c.text.bold('User First')} - What problem does this solve?
|
|
309
|
+
${c.secondary('3.')} ${c.text.bold('Iterate')} - Ship v1, improve based on feedback
|
|
310
|
+
${c.secondary('4.')} ${c.text.bold('Patterns')} - Look for similar solutions
|
|
311
|
+
${c.secondary('5.')} ${c.text.bold('Validate')} - Show mockup before building
|
|
312
|
+
`,
|
|
313
|
+
performance: `
|
|
314
|
+
${c.warning.bold('⚡ Performance Strategy:')}
|
|
315
|
+
${c.secondary('1.')} ${c.text.bold('Measure First')} - Profile before optimizing
|
|
316
|
+
${c.secondary('2.')} ${c.text.bold('Biggest Wins')} - Focus on slowest parts
|
|
317
|
+
${c.secondary('3.')} ${c.text.bold('Cache')} - Store expensive computations
|
|
318
|
+
${c.secondary('4.')} ${c.text.bold('Lazy Load')} - Defer non-critical work
|
|
319
|
+
${c.secondary('5.')} ${c.text.bold('Monitor')} - Track improvements
|
|
320
|
+
`,
|
|
321
|
+
default: `
|
|
322
|
+
${c.idea.bold('💡 General Strategy:')}
|
|
323
|
+
${c.secondary('1.')} ${c.text.bold('Break It Down')} - Divide into smaller tasks
|
|
324
|
+
${c.secondary('2.')} ${c.text.bold('Start Small')} - Implement simplest part first
|
|
325
|
+
${c.secondary('3.')} ${c.text.bold('Test Often')} - Verify each step works
|
|
326
|
+
${c.secondary('4.')} ${c.text.bold('Document')} - Write down what you learn
|
|
327
|
+
${c.secondary('5.')} ${c.text.bold('Ship It')} - Perfect is the enemy of done
|
|
328
|
+
`,
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const helpType =
|
|
332
|
+
Object.keys(helps).find((key) => issue.toLowerCase().includes(key)) || 'default'
|
|
333
|
+
|
|
334
|
+
return helps[helpType]
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const helps = {
|
|
338
|
+
debugging: `
|
|
339
|
+
${chalk.bold('🔍 Debugging Strategy:')}
|
|
340
|
+
${chalk.cyan('1.')} ${chalk.bold('Isolate')} - Comment out code until error disappears
|
|
341
|
+
${chalk.cyan('2.')} ${chalk.bold('Log')} - Add console.log at key points
|
|
342
|
+
${chalk.cyan('3.')} ${chalk.bold('Simplify')} - Create minimal reproduction
|
|
343
|
+
${chalk.cyan('4.')} ${chalk.bold('Research')} - Search for exact error message
|
|
344
|
+
${chalk.cyan('5.')} ${chalk.bold('Break')} - Take a walk, fresh perspective helps!
|
|
345
|
+
`,
|
|
346
|
+
design: `
|
|
347
|
+
${chalk.bold('🎨 Design Approach:')}
|
|
348
|
+
${chalk.cyan('1.')} ${chalk.bold('Start Simple')} - Basic version first
|
|
349
|
+
${chalk.cyan('2.')} ${chalk.bold('User First')} - What problem does this solve?
|
|
350
|
+
${chalk.cyan('3.')} ${chalk.bold('Iterate')} - Ship v1, improve based on feedback
|
|
351
|
+
${chalk.cyan('4.')} ${chalk.bold('Patterns')} - Look for similar solutions
|
|
352
|
+
${chalk.cyan('5.')} ${chalk.bold('Validate')} - Show mockup before building
|
|
353
|
+
`,
|
|
354
|
+
performance: `
|
|
355
|
+
${chalk.bold('⚡ Performance Strategy:')}
|
|
356
|
+
${chalk.cyan('1.')} ${chalk.bold('Measure First')} - Profile before optimizing
|
|
357
|
+
${chalk.cyan('2.')} ${chalk.bold('Biggest Wins')} - Focus on slowest parts
|
|
358
|
+
${chalk.cyan('3.')} ${chalk.bold('Cache')} - Store expensive computations
|
|
359
|
+
${chalk.cyan('4.')} ${chalk.bold('Lazy Load')} - Defer non-critical work
|
|
360
|
+
${chalk.cyan('5.')} ${chalk.bold('Monitor')} - Track improvements
|
|
361
|
+
`,
|
|
362
|
+
default: `
|
|
363
|
+
${chalk.bold('💡 General Strategy:')}
|
|
364
|
+
${chalk.cyan('1.')} ${chalk.bold('Break It Down')} - Divide into smaller tasks
|
|
365
|
+
${chalk.cyan('2.')} ${chalk.bold('Start Small')} - Implement simplest part first
|
|
366
|
+
${chalk.cyan('3.')} ${chalk.bold('Test Often')} - Verify each step works
|
|
367
|
+
${chalk.cyan('4.')} ${chalk.bold('Document')} - Write down what you learn
|
|
368
|
+
${chalk.cyan('5.')} ${chalk.bold('Ship It')} - Perfect is the enemy of done
|
|
369
|
+
`,
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const helpType =
|
|
373
|
+
Object.keys(helps).find((key) => issue.toLowerCase().includes(key)) || 'default'
|
|
374
|
+
|
|
375
|
+
return helps[helpType]
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Suggest next action based on context
|
|
380
|
+
*/
|
|
381
|
+
suggestNextAction(context) {
|
|
382
|
+
if (animations) {
|
|
383
|
+
const c = animations.colors
|
|
384
|
+
const suggestions = {
|
|
385
|
+
taskCompleted:
|
|
386
|
+
c.dim('→ Ready for the next challenge? Use ') +
|
|
387
|
+
c.success('prjct next') +
|
|
388
|
+
c.dim(' to see your queue!'),
|
|
389
|
+
featureShipped:
|
|
390
|
+
c.dim('→ Awesome! Take a moment to celebrate, then ') +
|
|
391
|
+
c.ship('prjct now') +
|
|
392
|
+
c.dim(' for next focus!'),
|
|
393
|
+
ideaCaptured:
|
|
394
|
+
c.dim('→ Great idea! Use ') +
|
|
395
|
+
c.idea('prjct now') +
|
|
396
|
+
c.dim(' to start working on it!'),
|
|
397
|
+
initialized: c.dim('→ All set! Start with ') + c.primary('prjct now "your first task"'),
|
|
398
|
+
stuck:
|
|
399
|
+
c.dim('→ You got this! Break it down with ') +
|
|
400
|
+
c.focus('prjct idea') +
|
|
401
|
+
c.dim(' for each step'),
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return suggestions[context] || c.dim('→ What would you like to work on next?')
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const suggestions = {
|
|
408
|
+
taskCompleted:
|
|
409
|
+
chalk.dim('→ Ready for the next challenge? Use ') +
|
|
410
|
+
chalk.green('prjct next') +
|
|
411
|
+
chalk.dim(' to see your queue!'),
|
|
412
|
+
featureShipped:
|
|
413
|
+
chalk.dim('→ Awesome! Take a moment to celebrate, then ') +
|
|
414
|
+
chalk.green('prjct now') +
|
|
415
|
+
chalk.dim(' for next focus!'),
|
|
416
|
+
ideaCaptured:
|
|
417
|
+
chalk.dim('→ Great idea! Use ') +
|
|
418
|
+
chalk.green('prjct now') +
|
|
419
|
+
chalk.dim(' to start working on it!'),
|
|
420
|
+
initialized: chalk.dim('→ All set! Start with ') + chalk.green('prjct now "your first task"'),
|
|
421
|
+
stuck:
|
|
422
|
+
chalk.dim('→ You got this! Break it down with ') +
|
|
423
|
+
chalk.green('prjct idea') +
|
|
424
|
+
chalk.dim(' for each step'),
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return suggestions[context] || chalk.dim('→ What would you like to work on next?')
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Clear terminal screen (for better UX)
|
|
432
|
+
*/
|
|
433
|
+
clearScreen() {
|
|
434
|
+
if (process.stdout.isTTY) {
|
|
435
|
+
process.stdout.write('\x1Bc')
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Show progress spinner for long operations
|
|
441
|
+
*/
|
|
442
|
+
showSpinner(message) {
|
|
443
|
+
if (!process.stdout.isTTY) return
|
|
444
|
+
|
|
445
|
+
const frames = animations?.frames?.loading || ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
446
|
+
let i = 0
|
|
447
|
+
|
|
448
|
+
const formattedMessage = animations
|
|
449
|
+
? animations.colors.text(message)
|
|
450
|
+
: message
|
|
451
|
+
|
|
452
|
+
const interval = setInterval(() => {
|
|
453
|
+
const frame = animations?.colors?.primary(frames[i]) || frames[i]
|
|
454
|
+
process.stdout.write(`\r${frame} ${formattedMessage}`)
|
|
455
|
+
i = (i + 1) % frames.length
|
|
456
|
+
}, 80)
|
|
457
|
+
|
|
458
|
+
return () => {
|
|
459
|
+
clearInterval(interval)
|
|
460
|
+
process.stdout.write('\r' + ' '.repeat(message.length + 2) + '\r')
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
module.exports = TerminalAgent
|