prjct-cli 0.4.9 → 0.5.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 +316 -0
- package/CLAUDE.md +109 -3
- package/README.md +228 -93
- package/core/agent-detector.js +55 -122
- package/core/agent-generator.js +516 -0
- package/core/command-installer.js +104 -890
- package/core/commands.js +3 -18
- package/core/editors-config.js +9 -57
- package/core/git-integration.js +401 -0
- package/package.json +10 -7
- package/scripts/install.sh +0 -1
- package/templates/agents/be.template.md +42 -0
- package/templates/agents/data.template.md +41 -0
- package/templates/agents/devops.template.md +41 -0
- package/templates/agents/fe.template.md +42 -0
- package/templates/agents/mobile.template.md +41 -0
- package/templates/agents/pm.template.md +84 -0
- package/templates/agents/qa.template.md +54 -0
- package/templates/agents/scribe.template.md +95 -0
- package/templates/agents/security.template.md +41 -0
- package/templates/agents/ux.template.md +49 -0
- package/templates/commands/analyze.md +137 -3
- package/templates/commands/done.md +154 -5
- package/templates/commands/init.md +61 -3
- package/templates/commands/ship.md +146 -6
- package/templates/commands/sync.md +220 -0
- package/templates/examples/natural-language-examples.md +234 -22
- package/core/agents/codex-agent.js +0 -256
- package/core/agents/terminal-agent.js +0 -465
- package/templates/workflows/analyze.md +0 -159
- package/templates/workflows/cleanup.md +0 -73
- package/templates/workflows/context.md +0 -72
- package/templates/workflows/design.md +0 -88
- package/templates/workflows/done.md +0 -20
- package/templates/workflows/fix.md +0 -201
- package/templates/workflows/git.md +0 -192
- package/templates/workflows/help.md +0 -13
- package/templates/workflows/idea.md +0 -22
- package/templates/workflows/init.md +0 -80
- package/templates/workflows/natural-language-handler.md +0 -183
- package/templates/workflows/next.md +0 -44
- package/templates/workflows/now.md +0 -19
- package/templates/workflows/progress.md +0 -113
- package/templates/workflows/recap.md +0 -66
- package/templates/workflows/roadmap.md +0 -95
- package/templates/workflows/ship.md +0 -18
- package/templates/workflows/stuck.md +0 -25
- package/templates/workflows/task.md +0 -109
- package/templates/workflows/test.md +0 -243
|
@@ -3,95 +3,33 @@ const path = require('path')
|
|
|
3
3
|
const os = require('os')
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* CommandInstaller - Installs prjct commands to
|
|
6
|
+
* CommandInstaller - Installs prjct commands to Claude (Code + Desktop)
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* 100% Claude-focused architecture
|
|
9
|
+
* Handles installation and synchronization of /p:* commands
|
|
10
|
+
* to Claude's native slash command system
|
|
10
11
|
*
|
|
11
|
-
* @version 0.
|
|
12
|
+
* @version 0.5.0
|
|
12
13
|
*/
|
|
13
14
|
class CommandInstaller {
|
|
14
15
|
constructor() {
|
|
15
16
|
this.homeDir = os.homedir()
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
this.editors = {
|
|
19
|
-
claude: {
|
|
20
|
-
name: 'Claude Code',
|
|
21
|
-
commandsPath: path.join(this.homeDir, '.claude', 'commands', 'p'),
|
|
22
|
-
configPath: path.join(this.homeDir, '.claude'),
|
|
23
|
-
format: 'slash-commands', // *.md with frontmatter
|
|
24
|
-
detected: false,
|
|
25
|
-
},
|
|
26
|
-
cursor: {
|
|
27
|
-
name: 'Cursor AI',
|
|
28
|
-
commandsPath: path.join(this.homeDir, '.cursor', 'commands', 'p'),
|
|
29
|
-
configPath: path.join(this.homeDir, '.cursor'),
|
|
30
|
-
format: 'slash-commands', // *.md with frontmatter
|
|
31
|
-
detected: false,
|
|
32
|
-
},
|
|
33
|
-
codex: {
|
|
34
|
-
name: 'OpenAI Codex',
|
|
35
|
-
commandsPath: path.join(this.homeDir, '.codex', 'instructions.md'),
|
|
36
|
-
configPath: path.join(this.homeDir, '.codex'),
|
|
37
|
-
format: 'agents-md', // Single instructions.md file
|
|
38
|
-
detected: false,
|
|
39
|
-
projectBased: false,
|
|
40
|
-
},
|
|
41
|
-
windsurf: {
|
|
42
|
-
name: 'Windsurf/Codeium',
|
|
43
|
-
commandsPath: path.join(this.homeDir, '.windsurf', 'workflows'),
|
|
44
|
-
configPath: path.join(this.homeDir, '.windsurf'),
|
|
45
|
-
format: 'workflows', // *.md workflows
|
|
46
|
-
detected: false,
|
|
47
|
-
projectBased: false,
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
|
|
17
|
+
this.claudeCommandsPath = path.join(this.homeDir, '.claude', 'commands', 'p')
|
|
18
|
+
this.claudeConfigPath = path.join(this.homeDir, '.claude')
|
|
51
19
|
this.templatesDir = path.join(__dirname, '..', 'templates', 'commands')
|
|
52
|
-
this.agentsTemplateDir = path.join(__dirname, '..', 'templates', 'agents')
|
|
53
|
-
this.workflowsTemplateDir = path.join(__dirname, '..', 'templates', 'workflows')
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Set project path for project-based editors
|
|
58
|
-
* @param {string} projectPath - Path to the project
|
|
59
|
-
*/
|
|
60
|
-
setProjectPath(projectPath) {
|
|
61
|
-
this.projectPath = projectPath
|
|
62
|
-
// codex and windsurf use global paths defined in constructor
|
|
63
20
|
}
|
|
64
21
|
|
|
65
22
|
/**
|
|
66
|
-
* Detect
|
|
67
|
-
* @
|
|
68
|
-
* @returns {Promise<Object>} Object with editor detection results
|
|
23
|
+
* Detect if Claude is installed
|
|
24
|
+
* @returns {Promise<boolean>} True if Claude directory exists
|
|
69
25
|
*/
|
|
70
|
-
async
|
|
71
|
-
|
|
72
|
-
this.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
for (const [key, editor] of Object.entries(this.editors)) {
|
|
78
|
-
try {
|
|
79
|
-
await fs.access(editor.configPath)
|
|
80
|
-
editor.detected = true
|
|
81
|
-
|
|
82
|
-
let commandPath = editor.commandsPath
|
|
83
|
-
if (!commandPath && editor.projectBased) {
|
|
84
|
-
commandPath = path.join(this.projectPath, 'AGENTS.md')
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
results[key] = { detected: true, path: commandPath, format: editor.format }
|
|
88
|
-
} catch {
|
|
89
|
-
editor.detected = false
|
|
90
|
-
results[key] = { detected: false, path: null, format: editor.format }
|
|
91
|
-
}
|
|
26
|
+
async detectClaude() {
|
|
27
|
+
try {
|
|
28
|
+
await fs.access(this.claudeConfigPath)
|
|
29
|
+
return true
|
|
30
|
+
} catch {
|
|
31
|
+
return false
|
|
92
32
|
}
|
|
93
|
-
|
|
94
|
-
return results
|
|
95
33
|
}
|
|
96
34
|
|
|
97
35
|
/**
|
|
@@ -103,6 +41,7 @@ class CommandInstaller {
|
|
|
103
41
|
const files = await fs.readdir(this.templatesDir)
|
|
104
42
|
return files.filter(f => f.endsWith('.md'))
|
|
105
43
|
} catch (error) {
|
|
44
|
+
// Fallback to core commands if template directory not accessible
|
|
106
45
|
return [
|
|
107
46
|
'init.md',
|
|
108
47
|
'now.md',
|
|
@@ -115,898 +54,173 @@ class CommandInstaller {
|
|
|
115
54
|
'stuck.md',
|
|
116
55
|
'context.md',
|
|
117
56
|
'analyze.md',
|
|
57
|
+
'sync.md',
|
|
118
58
|
'roadmap.md',
|
|
119
59
|
'task.md',
|
|
120
60
|
'git.md',
|
|
121
61
|
'fix.md',
|
|
122
62
|
'test.md',
|
|
63
|
+
'cleanup.md',
|
|
64
|
+
'design.md',
|
|
123
65
|
]
|
|
124
66
|
}
|
|
125
67
|
}
|
|
126
68
|
|
|
127
69
|
/**
|
|
128
|
-
*
|
|
129
|
-
* @returns {Promise<
|
|
130
|
-
*/
|
|
131
|
-
async generateAgentsMd() {
|
|
132
|
-
const templatePath = path.join(this.agentsTemplateDir, 'AGENTS.md')
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
return await fs.readFile(templatePath, 'utf-8')
|
|
136
|
-
} catch {
|
|
137
|
-
const existingPath = path.join(this.projectPath, 'AGENTS.md')
|
|
138
|
-
try {
|
|
139
|
-
return await fs.readFile(existingPath, 'utf-8')
|
|
140
|
-
} catch {
|
|
141
|
-
return `# AGENTS.md - OpenAI Codex Configuration
|
|
142
|
-
|
|
143
|
-
This file provides guidance to OpenAI Codex when working with this repository.
|
|
144
|
-
|
|
145
|
-
## prjct Commands
|
|
146
|
-
|
|
147
|
-
The project uses prjct-cli for project management. All commands access global data in \`~/.prjct-cli/projects/{id}/\`.
|
|
148
|
-
|
|
149
|
-
### /p:init
|
|
150
|
-
Initialize prjct in current project. Creates global structure and local config.
|
|
151
|
-
|
|
152
|
-
### /p:now [task]
|
|
153
|
-
Set or show current task.
|
|
154
|
-
- Read: Show current task from global storage
|
|
155
|
-
- Write: Update task in \`~/.prjct-cli/projects/{id}/core/now.md\`
|
|
156
|
-
|
|
157
|
-
### /p:done
|
|
158
|
-
Complete current task and clear focus.
|
|
159
|
-
|
|
160
|
-
### /p:ship <feature>
|
|
161
|
-
Ship and celebrate a completed feature.
|
|
162
|
-
|
|
163
|
-
### /p:next
|
|
164
|
-
Show priority queue of upcoming tasks.
|
|
165
|
-
|
|
166
|
-
### /p:idea <text>
|
|
167
|
-
Capture an idea quickly to the backlog.
|
|
168
|
-
|
|
169
|
-
### /p:recap
|
|
170
|
-
Show project overview with progress metrics.
|
|
171
|
-
|
|
172
|
-
See complete command documentation in the prjct-cli repository.
|
|
173
|
-
`
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Generate Windsurf workflow content
|
|
180
|
-
* @param {string} commandName - Command name (e.g., 'now', 'done')
|
|
181
|
-
* @returns {Promise<string>} Workflow content
|
|
182
|
-
*/
|
|
183
|
-
async generateWorkflow(commandName) {
|
|
184
|
-
const templatePath = path.join(this.workflowsTemplateDir, `${commandName}.md`)
|
|
185
|
-
|
|
186
|
-
try {
|
|
187
|
-
return await fs.readFile(templatePath, 'utf-8')
|
|
188
|
-
} catch {
|
|
189
|
-
const invocableName = `p:${commandName}`
|
|
190
|
-
return `---
|
|
191
|
-
title: prjct ${commandName}
|
|
192
|
-
invocable_name: ${invocableName}
|
|
193
|
-
description: Execute prjct ${commandName} command
|
|
194
|
-
---
|
|
195
|
-
|
|
196
|
-
# Steps
|
|
197
|
-
|
|
198
|
-
1. Read project config from \`.prjct/prjct.config.json\`
|
|
199
|
-
2. Get project ID from config
|
|
200
|
-
3. Execute ${commandName} operation on global data in \`~/.prjct-cli/projects/{id}/\`
|
|
201
|
-
4. Update relevant files in appropriate layers (core, progress, planning, memory)
|
|
202
|
-
5. Log action to memory with timestamp
|
|
203
|
-
6. Display confirmation with next suggested actions
|
|
204
|
-
|
|
205
|
-
For detailed implementation, see prjct-cli documentation.
|
|
206
|
-
`
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Install commands to a specific editor
|
|
212
|
-
* @param {string} editorKey - Editor identifier (claude, cursor, codex, windsurf)
|
|
213
|
-
* @param {boolean} forceUpdate - Force update existing commands
|
|
214
|
-
* @returns {Promise<Object>} Installation result
|
|
70
|
+
* Install commands to Claude
|
|
71
|
+
* @returns {Promise<Object>} Installation results
|
|
215
72
|
*/
|
|
216
|
-
async
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
if (!editor) {
|
|
220
|
-
return { success: false, message: `Unknown editor: ${editorKey}` }
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (!editor.detected) {
|
|
224
|
-
return { success: false, message: `${editor.name} not detected` }
|
|
225
|
-
}
|
|
73
|
+
async installCommands() {
|
|
74
|
+
const claudeDetected = await this.detectClaude()
|
|
226
75
|
|
|
227
|
-
|
|
228
|
-
switch (editor.format) {
|
|
229
|
-
case 'slash-commands':
|
|
230
|
-
return await this.installSlashCommands(editorKey, forceUpdate)
|
|
231
|
-
case 'agents-md':
|
|
232
|
-
return await this.installAgentsMd(editorKey, forceUpdate)
|
|
233
|
-
case 'workflows':
|
|
234
|
-
return await this.installWorkflows(editorKey, forceUpdate)
|
|
235
|
-
default:
|
|
236
|
-
return {
|
|
237
|
-
success: false,
|
|
238
|
-
message: `Unknown format: ${editor.format}`,
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
} catch (error) {
|
|
76
|
+
if (!claudeDetected) {
|
|
242
77
|
return {
|
|
243
78
|
success: false,
|
|
244
|
-
|
|
79
|
+
error: 'Claude not detected. Please install Claude Code or Claude Desktop first.',
|
|
245
80
|
}
|
|
246
81
|
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Install slash commands format (Claude, Cursor)
|
|
251
|
-
*/
|
|
252
|
-
async installSlashCommands(editorKey, forceUpdate) {
|
|
253
|
-
const editor = this.editors[editorKey]
|
|
254
|
-
|
|
255
|
-
await fs.mkdir(editor.commandsPath, { recursive: true })
|
|
256
82
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const updated = []
|
|
261
|
-
|
|
262
|
-
for (const filename of commandFiles) {
|
|
263
|
-
const targetPath = path.join(editor.commandsPath, filename)
|
|
264
|
-
const templatePath = path.join(this.templatesDir, filename)
|
|
83
|
+
try {
|
|
84
|
+
// Ensure commands directory exists
|
|
85
|
+
await fs.mkdir(this.claudeCommandsPath, { recursive: true })
|
|
265
86
|
|
|
266
|
-
const
|
|
87
|
+
const commandFiles = await this.getCommandFiles()
|
|
88
|
+
const installed = []
|
|
89
|
+
const errors = []
|
|
267
90
|
|
|
268
|
-
|
|
269
|
-
skipped.push(filename)
|
|
270
|
-
continue
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
let content
|
|
274
|
-
try {
|
|
275
|
-
content = await fs.readFile(templatePath, 'utf-8')
|
|
276
|
-
} catch {
|
|
277
|
-
const claudePath = path.join(this.editors.claude.commandsPath, filename)
|
|
91
|
+
for (const file of commandFiles) {
|
|
278
92
|
try {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
} catch {
|
|
282
|
-
skipped.push(filename)
|
|
283
|
-
continue
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
await fs.writeFile(targetPath, content, 'utf-8')
|
|
93
|
+
const sourcePath = path.join(this.templatesDir, file)
|
|
94
|
+
const destPath = path.join(this.claudeCommandsPath, file)
|
|
288
95
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
} else {
|
|
292
|
-
installed.push(filename)
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return {
|
|
297
|
-
success: true,
|
|
298
|
-
editor: editor.name,
|
|
299
|
-
format: 'slash-commands',
|
|
300
|
-
installed: installed.length,
|
|
301
|
-
updated: updated.length,
|
|
302
|
-
skipped: skipped.length,
|
|
303
|
-
details: { installed, updated, skipped },
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Install AGENTS.md format (Codex)
|
|
309
|
-
*/
|
|
310
|
-
async installAgentsMd(editorKey, forceUpdate) {
|
|
311
|
-
const editor = this.editors[editorKey]
|
|
312
|
-
const targetPath = editor.commandsPath
|
|
313
|
-
|
|
314
|
-
const exists = await this.fileExists(targetPath)
|
|
315
|
-
|
|
316
|
-
if (exists && !forceUpdate) {
|
|
317
|
-
return {
|
|
318
|
-
success: true,
|
|
319
|
-
editor: editor.name,
|
|
320
|
-
format: 'agents-md',
|
|
321
|
-
installed: 0,
|
|
322
|
-
updated: 0,
|
|
323
|
-
skipped: 1,
|
|
324
|
-
details: { installed: [], updated: [], skipped: ['AGENTS.md'] },
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const content = await this.generateAgentsMd()
|
|
96
|
+
const content = await fs.readFile(sourcePath, 'utf-8')
|
|
97
|
+
await fs.writeFile(destPath, content, 'utf-8')
|
|
329
98
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
success: true,
|
|
334
|
-
editor: editor.name,
|
|
335
|
-
format: 'agents-md',
|
|
336
|
-
installed: exists ? 0 : 1,
|
|
337
|
-
updated: exists ? 1 : 0,
|
|
338
|
-
skipped: 0,
|
|
339
|
-
details: {
|
|
340
|
-
installed: exists ? [] : ['AGENTS.md'],
|
|
341
|
-
updated: exists ? ['AGENTS.md'] : [],
|
|
342
|
-
skipped: [],
|
|
343
|
-
},
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Install workflows format (Windsurf)
|
|
349
|
-
*/
|
|
350
|
-
async installWorkflows(editorKey, forceUpdate) {
|
|
351
|
-
const editor = this.editors[editorKey]
|
|
352
|
-
|
|
353
|
-
await fs.mkdir(editor.commandsPath, { recursive: true })
|
|
354
|
-
|
|
355
|
-
const commandFiles = await this.getCommandFiles()
|
|
356
|
-
const commandNames = commandFiles.map(f => f.replace('.md', ''))
|
|
357
|
-
|
|
358
|
-
const installed = []
|
|
359
|
-
const skipped = []
|
|
360
|
-
const updated = []
|
|
361
|
-
|
|
362
|
-
for (const commandName of commandNames) {
|
|
363
|
-
const filename = `p_${commandName}.md` // e.g., p_now.md
|
|
364
|
-
const targetPath = path.join(editor.commandsPath, filename)
|
|
365
|
-
|
|
366
|
-
const exists = await this.fileExists(targetPath)
|
|
367
|
-
|
|
368
|
-
if (exists && !forceUpdate) {
|
|
369
|
-
skipped.push(filename)
|
|
370
|
-
continue
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const content = await this.generateWorkflow(commandName)
|
|
374
|
-
|
|
375
|
-
await fs.writeFile(targetPath, content, 'utf-8')
|
|
376
|
-
|
|
377
|
-
if (exists) {
|
|
378
|
-
updated.push(filename)
|
|
379
|
-
} else {
|
|
380
|
-
installed.push(filename)
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return {
|
|
385
|
-
success: true,
|
|
386
|
-
editor: editor.name,
|
|
387
|
-
format: 'workflows',
|
|
388
|
-
installed: installed.length,
|
|
389
|
-
updated: updated.length,
|
|
390
|
-
skipped: skipped.length,
|
|
391
|
-
details: { installed, updated, skipped },
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Install commands to selected editors
|
|
397
|
-
* @param {string[]} selectedEditors - Array of editor keys to install to
|
|
398
|
-
* @param {boolean} forceUpdate - Force update existing commands
|
|
399
|
-
* @returns {Promise<Object>} Installation results for selected editors
|
|
400
|
-
*/
|
|
401
|
-
async installToSelected(selectedEditors, forceUpdate = false) {
|
|
402
|
-
await this.detectEditors(this.projectPath)
|
|
403
|
-
|
|
404
|
-
const results = {}
|
|
405
|
-
const installedTo = []
|
|
406
|
-
const successfulEditors = []
|
|
407
|
-
|
|
408
|
-
for (const editorKey of selectedEditors) {
|
|
409
|
-
const editor = this.editors[editorKey]
|
|
410
|
-
|
|
411
|
-
if (!editor) {
|
|
412
|
-
results[editorKey] = {
|
|
413
|
-
success: false,
|
|
414
|
-
message: `Unknown editor: ${editorKey}`,
|
|
415
|
-
}
|
|
416
|
-
continue
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (!editor.detected) {
|
|
420
|
-
results[editorKey] = {
|
|
421
|
-
success: false,
|
|
422
|
-
message: `${editor.name} not detected on this system`,
|
|
99
|
+
installed.push(file.replace('.md', ''))
|
|
100
|
+
} catch (error) {
|
|
101
|
+
errors.push({ file, error: error.message })
|
|
423
102
|
}
|
|
424
|
-
continue
|
|
425
103
|
}
|
|
426
104
|
|
|
427
|
-
results[editorKey] = await this.installToEditor(editorKey, forceUpdate)
|
|
428
|
-
if (results[editorKey].success) {
|
|
429
|
-
installedTo.push(editor.name)
|
|
430
|
-
successfulEditors.push(editorKey)
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (installedTo.length === 0) {
|
|
435
105
|
return {
|
|
436
|
-
success:
|
|
437
|
-
|
|
438
|
-
|
|
106
|
+
success: true,
|
|
107
|
+
installed,
|
|
108
|
+
errors,
|
|
109
|
+
path: this.claudeCommandsPath,
|
|
439
110
|
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
const totalInstalled = Object.values(results)
|
|
443
|
-
.reduce((sum, r) => sum + (r.installed || 0), 0)
|
|
444
|
-
const totalUpdated = Object.values(results)
|
|
445
|
-
.reduce((sum, r) => sum + (r.updated || 0), 0)
|
|
446
|
-
|
|
447
|
-
// Always save editor selections to config for tracking updates
|
|
448
|
-
if (successfulEditors.length > 0) {
|
|
449
|
-
await this.saveEditorConfig(successfulEditors)
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
return {
|
|
453
|
-
success: true,
|
|
454
|
-
editors: installedTo,
|
|
455
|
-
totalInstalled,
|
|
456
|
-
totalUpdated,
|
|
457
|
-
results,
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Install commands to all detected editors
|
|
463
|
-
* @param {boolean} forceUpdate - Force update existing commands
|
|
464
|
-
* @returns {Promise<Object>} Installation results for all editors
|
|
465
|
-
*/
|
|
466
|
-
async installToAll(forceUpdate = false) {
|
|
467
|
-
const detection = await this.detectEditors(this.projectPath)
|
|
468
|
-
const detectedEditors = Object.entries(detection)
|
|
469
|
-
.filter(([_, info]) => info.detected)
|
|
470
|
-
.map(([key, _]) => key)
|
|
471
|
-
|
|
472
|
-
if (detectedEditors.length === 0) {
|
|
111
|
+
} catch (error) {
|
|
473
112
|
return {
|
|
474
113
|
success: false,
|
|
475
|
-
|
|
476
|
-
results: {},
|
|
114
|
+
error: error.message,
|
|
477
115
|
}
|
|
478
116
|
}
|
|
479
|
-
|
|
480
|
-
return await this.installToSelected(detectedEditors, forceUpdate)
|
|
481
117
|
}
|
|
482
118
|
|
|
483
119
|
/**
|
|
484
|
-
* Uninstall commands from
|
|
485
|
-
* @
|
|
486
|
-
* @returns {Promise<Object>} Uninstall result
|
|
120
|
+
* Uninstall commands from Claude
|
|
121
|
+
* @returns {Promise<Object>} Uninstallation results
|
|
487
122
|
*/
|
|
488
|
-
async
|
|
489
|
-
const chalk = require('chalk')
|
|
490
|
-
const editorsConfig = require('./editors-config')
|
|
491
|
-
|
|
492
|
-
const editor = this.editors[editorKey]
|
|
493
|
-
if (!editor) {
|
|
494
|
-
return {
|
|
495
|
-
success: false,
|
|
496
|
-
message: `Unknown editor: ${editorKey}`,
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
123
|
+
async uninstallCommands() {
|
|
500
124
|
try {
|
|
501
|
-
|
|
502
|
-
const
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
// Get list of prjct commands
|
|
506
|
-
const commands = ['init', 'now', 'done', 'ship', 'next', 'idea', 'recap', 'progress', 'stuck', 'context',
|
|
507
|
-
'cleanup', 'design', 'task', 'git', 'test', 'roadmap', 'fix', 'analyze']
|
|
125
|
+
const commandFiles = await this.getCommandFiles()
|
|
126
|
+
const uninstalled = []
|
|
127
|
+
const errors = []
|
|
508
128
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
for (const command of commands) {
|
|
512
|
-
const commandFile = path.join(commandsPath, `p:${command}.md`)
|
|
129
|
+
for (const file of commandFiles) {
|
|
513
130
|
try {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
131
|
+
const filePath = path.join(this.claudeCommandsPath, file)
|
|
132
|
+
await fs.unlink(filePath)
|
|
133
|
+
uninstalled.push(file.replace('.md', ''))
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (error.code !== 'ENOENT') {
|
|
136
|
+
errors.push({ file, error: error.message })
|
|
137
|
+
}
|
|
518
138
|
}
|
|
519
139
|
}
|
|
520
140
|
|
|
521
|
-
//
|
|
522
|
-
await editorsConfig.removeTrackedEditor(editorKey)
|
|
523
|
-
|
|
524
|
-
console.log(chalk.yellow(`🗑️ Removed ${deletedCount} commands from ${editor.name}`))
|
|
525
|
-
|
|
526
|
-
return {
|
|
527
|
-
success: true,
|
|
528
|
-
message: `Removed commands from ${editor.name}`,
|
|
529
|
-
deleted: deletedCount,
|
|
530
|
-
}
|
|
531
|
-
} catch (error) {
|
|
532
|
-
console.error(chalk.red(`❌ Error removing commands from ${editor.name}:`), error.message)
|
|
533
|
-
return {
|
|
534
|
-
success: false,
|
|
535
|
-
message: `Failed to remove commands: ${error.message}`,
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* Interactive installation with user selection using prompts
|
|
542
|
-
* @param {boolean} allowUninstall - Allow uninstalling editors
|
|
543
|
-
* @returns {Promise<Object>} Installation results
|
|
544
|
-
*/
|
|
545
|
-
async interactiveInstall(allowUninstall = false) {
|
|
546
|
-
const prompts = require('prompts')
|
|
547
|
-
const editorsConfig = require('./editors-config')
|
|
548
|
-
|
|
549
|
-
// Detect all editors
|
|
550
|
-
const detected = await this.detectEditors(this.projectPath)
|
|
551
|
-
|
|
552
|
-
// Get currently installed editors if allowUninstall
|
|
553
|
-
let installedEditors = []
|
|
554
|
-
if (allowUninstall) {
|
|
141
|
+
// Try to remove the /p directory if empty
|
|
555
142
|
try {
|
|
556
|
-
|
|
143
|
+
await fs.rmdir(this.claudeCommandsPath)
|
|
557
144
|
} catch {
|
|
558
|
-
//
|
|
145
|
+
// Directory not empty or doesn't exist - that's fine
|
|
559
146
|
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// Create choices for prompts
|
|
563
|
-
const availableEditors = Object.entries(detected)
|
|
564
|
-
.filter(([_, info]) => info.detected)
|
|
565
|
-
.map(([key, info]) => ({
|
|
566
|
-
title: `${this.editors[key].name} (${info.path})`,
|
|
567
|
-
value: key,
|
|
568
|
-
// Pre-select if allowUninstall and already installed, otherwise select all
|
|
569
|
-
selected: allowUninstall ? installedEditors.includes(key) : true,
|
|
570
|
-
}))
|
|
571
147
|
|
|
572
|
-
if (availableEditors.length === 0) {
|
|
573
148
|
return {
|
|
574
|
-
success:
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
results: {},
|
|
149
|
+
success: true,
|
|
150
|
+
uninstalled,
|
|
151
|
+
errors,
|
|
578
152
|
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// Show interactive selection prompt
|
|
582
|
-
const response = await prompts({
|
|
583
|
-
type: 'multiselect',
|
|
584
|
-
name: 'selectedEditors',
|
|
585
|
-
message: allowUninstall ? 'Select AI editors (uncheck to remove):' : 'Select AI editors to install commands to:',
|
|
586
|
-
choices: availableEditors,
|
|
587
|
-
min: allowUninstall ? 0 : 1, // Allow 0 selections if uninstalling
|
|
588
|
-
hint: '- Space to select. Return to submit',
|
|
589
|
-
instructions: false,
|
|
590
|
-
})
|
|
591
|
-
|
|
592
|
-
// Check if user cancelled
|
|
593
|
-
if (response.selectedEditors === undefined) {
|
|
153
|
+
} catch (error) {
|
|
594
154
|
return {
|
|
595
155
|
success: false,
|
|
596
|
-
|
|
597
|
-
editors: [],
|
|
598
|
-
results: {},
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const selectedEditors = response.selectedEditors || []
|
|
603
|
-
|
|
604
|
-
// If allowUninstall, handle editors that were deselected
|
|
605
|
-
if (allowUninstall) {
|
|
606
|
-
const deselectedEditors = installedEditors.filter(e => !selectedEditors.includes(e))
|
|
607
|
-
|
|
608
|
-
for (const editorKey of deselectedEditors) {
|
|
609
|
-
await this.uninstallFromEditor(editorKey)
|
|
156
|
+
error: error.message,
|
|
610
157
|
}
|
|
611
158
|
}
|
|
612
|
-
|
|
613
|
-
// Install to selected editors (or return success if none selected)
|
|
614
|
-
if (selectedEditors.length === 0) {
|
|
615
|
-
return {
|
|
616
|
-
success: true,
|
|
617
|
-
message: 'All editors removed',
|
|
618
|
-
editors: [],
|
|
619
|
-
results: {},
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
return await this.installToSelected(selectedEditors, true) // Force update
|
|
624
159
|
}
|
|
625
160
|
|
|
626
161
|
/**
|
|
627
|
-
*
|
|
628
|
-
* @
|
|
629
|
-
* @returns {string} Updated content
|
|
162
|
+
* Check if commands are already installed
|
|
163
|
+
* @returns {Promise<Object>} Installation status
|
|
630
164
|
*/
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
/\.prjct\//g,
|
|
634
|
-
'~/.prjct-cli/projects/{id}/',
|
|
635
|
-
)
|
|
636
|
-
|
|
637
|
-
if (!content.includes('Global Architecture')) {
|
|
638
|
-
const frontmatter = content.match(/^---[\s\S]*?---/m)
|
|
639
|
-
if (frontmatter) {
|
|
640
|
-
const note = `
|
|
165
|
+
async checkInstallation() {
|
|
166
|
+
const claudeDetected = await this.detectClaude()
|
|
641
167
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
- Commands synchronized across all editors
|
|
647
|
-
|
|
648
|
-
`
|
|
649
|
-
updated = content.replace(frontmatter[0], frontmatter[0] + note)
|
|
168
|
+
if (!claudeDetected) {
|
|
169
|
+
return {
|
|
170
|
+
installed: false,
|
|
171
|
+
claudeDetected: false,
|
|
650
172
|
}
|
|
651
173
|
}
|
|
652
174
|
|
|
653
|
-
return updated
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
/**
|
|
657
|
-
* Check if a file exists
|
|
658
|
-
* @param {string} filePath - Path to check
|
|
659
|
-
* @returns {Promise<boolean>} True if file exists
|
|
660
|
-
*/
|
|
661
|
-
async fileExists(filePath) {
|
|
662
175
|
try {
|
|
663
|
-
await fs.access(
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return false
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* Create command templates directory and copy existing commands
|
|
672
|
-
* @returns {Promise<Object>} Template creation result
|
|
673
|
-
*/
|
|
674
|
-
async createTemplates() {
|
|
675
|
-
try {
|
|
676
|
-
await fs.mkdir(this.templatesDir, { recursive: true })
|
|
677
|
-
|
|
678
|
-
const claudeCommandsPath = this.editors.claude.commandsPath
|
|
679
|
-
const hasClaudeCommands = await this.fileExists(claudeCommandsPath)
|
|
680
|
-
|
|
681
|
-
if (!hasClaudeCommands) {
|
|
682
|
-
return {
|
|
683
|
-
success: false,
|
|
684
|
-
message: 'No source commands found. Claude Code commands directory not detected.',
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const files = await fs.readdir(claudeCommandsPath)
|
|
689
|
-
const mdFiles = files.filter(f => f.endsWith('.md'))
|
|
690
|
-
|
|
691
|
-
let copied = 0
|
|
692
|
-
for (const filename of mdFiles) {
|
|
693
|
-
const sourcePath = path.join(claudeCommandsPath, filename)
|
|
694
|
-
const targetPath = path.join(this.templatesDir, filename)
|
|
695
|
-
|
|
696
|
-
const content = await fs.readFile(sourcePath, 'utf-8')
|
|
697
|
-
const updated = this.updateCommandForGlobalArchitecture(content)
|
|
698
|
-
|
|
699
|
-
await fs.writeFile(targetPath, updated, 'utf-8')
|
|
700
|
-
copied++
|
|
701
|
-
}
|
|
176
|
+
await fs.access(this.claudeCommandsPath)
|
|
177
|
+
const files = await fs.readdir(this.claudeCommandsPath)
|
|
178
|
+
const installedCommands = files.filter(f => f.endsWith('.md')).map(f => f.replace('.md', ''))
|
|
702
179
|
|
|
703
180
|
return {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
181
|
+
installed: installedCommands.length > 0,
|
|
182
|
+
claudeDetected: true,
|
|
183
|
+
commands: installedCommands,
|
|
184
|
+
path: this.claudeCommandsPath,
|
|
707
185
|
}
|
|
708
|
-
} catch
|
|
186
|
+
} catch {
|
|
709
187
|
return {
|
|
710
|
-
|
|
711
|
-
|
|
188
|
+
installed: false,
|
|
189
|
+
claudeDetected: true,
|
|
190
|
+
commands: [],
|
|
712
191
|
}
|
|
713
192
|
}
|
|
714
193
|
}
|
|
715
194
|
|
|
716
195
|
/**
|
|
717
|
-
*
|
|
718
|
-
* @
|
|
719
|
-
* @returns {string} Formatted report
|
|
196
|
+
* Update commands (reinstall with latest templates)
|
|
197
|
+
* @returns {Promise<Object>} Update results
|
|
720
198
|
*/
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// Handle single editor installation (installToEditor returns different format)
|
|
727
|
-
if (results.editor && !results.editors) {
|
|
728
|
-
const lines = [
|
|
729
|
-
'✅ Command Installation Complete!',
|
|
730
|
-
'',
|
|
731
|
-
`📦 Editor: ${results.editor}`,
|
|
732
|
-
`📝 Commands installed: ${results.installed}`,
|
|
733
|
-
`🔄 Commands updated: ${results.updated}`,
|
|
734
|
-
`⊘ Commands skipped: ${results.skipped}`,
|
|
735
|
-
'',
|
|
736
|
-
'💡 Commands are now available in your editor!',
|
|
737
|
-
]
|
|
738
|
-
return lines.join('\n')
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// Handle multiple editors installation (installToSelected/installToAll)
|
|
742
|
-
const lines = [
|
|
743
|
-
'✅ Command Installation Complete!',
|
|
744
|
-
'',
|
|
745
|
-
`📦 Editors: ${results.editors.join(', ')}`,
|
|
746
|
-
`📝 Commands installed: ${results.totalInstalled}`,
|
|
747
|
-
`🔄 Commands updated: ${results.totalUpdated}`,
|
|
748
|
-
'',
|
|
749
|
-
]
|
|
750
|
-
|
|
751
|
-
for (const [key, result] of Object.entries(results.results)) {
|
|
752
|
-
if (result.success) {
|
|
753
|
-
lines.push(`${this.editors[key].name}:`)
|
|
754
|
-
lines.push(` ✓ Installed: ${result.installed}`)
|
|
755
|
-
lines.push(` ↻ Updated: ${result.updated}`)
|
|
756
|
-
lines.push(` ⊘ Skipped: ${result.skipped}`)
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
lines.push('')
|
|
761
|
-
lines.push('💡 Commands are now available in all detected editors!')
|
|
762
|
-
|
|
763
|
-
return lines.join('\n')
|
|
199
|
+
async updateCommands() {
|
|
200
|
+
// Simply reinstall - will overwrite with latest templates
|
|
201
|
+
return await this.installCommands()
|
|
764
202
|
}
|
|
765
203
|
|
|
766
204
|
/**
|
|
767
|
-
*
|
|
768
|
-
* @
|
|
769
|
-
* @returns {Promise<void>}
|
|
205
|
+
* Get installation path for Claude commands
|
|
206
|
+
* @returns {string} Path to Claude commands directory
|
|
770
207
|
*/
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
const editorsConfig = require('./editors-config')
|
|
774
|
-
const packageJson = require('../package.json')
|
|
775
|
-
|
|
776
|
-
// Build paths object
|
|
777
|
-
const paths = {}
|
|
778
|
-
for (const editorKey of editors) {
|
|
779
|
-
const editor = this.editors[editorKey]
|
|
780
|
-
if (editor) {
|
|
781
|
-
paths[editorKey] = editor.commandsPath
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
await editorsConfig.saveConfig(
|
|
786
|
-
editors,
|
|
787
|
-
paths,
|
|
788
|
-
packageJson.version,
|
|
789
|
-
)
|
|
790
|
-
} catch (error) {
|
|
791
|
-
// Don't fail installation if config save fails
|
|
792
|
-
console.error('[command-installer] Error saving editor config:', error.message)
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
/**
|
|
797
|
-
* Install Context7 MCP configuration for all detected editors
|
|
798
|
-
* @returns {Promise<Object>} Installation results
|
|
799
|
-
*/
|
|
800
|
-
async installContext7MCP() {
|
|
801
|
-
const results = {
|
|
802
|
-
success: true,
|
|
803
|
-
editors: [],
|
|
804
|
-
details: {},
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
const mcpConfigTemplate = path.join(__dirname, '..', 'templates', 'mcp-config.json')
|
|
808
|
-
const mcpConfig = JSON.parse(await fs.readFile(mcpConfigTemplate, 'utf-8'))
|
|
809
|
-
|
|
810
|
-
try {
|
|
811
|
-
// 1. Claude Code: ~/.config/claude/claude_desktop_config.json
|
|
812
|
-
if (this.editors.claude.detected) {
|
|
813
|
-
const claudeConfigDir = path.join(this.homeDir, '.config', 'claude')
|
|
814
|
-
const claudeConfigFile = path.join(claudeConfigDir, 'claude_desktop_config.json')
|
|
815
|
-
|
|
816
|
-
await fs.mkdir(claudeConfigDir, { recursive: true })
|
|
817
|
-
|
|
818
|
-
let config = {}
|
|
819
|
-
if (await this.fileExists(claudeConfigFile)) {
|
|
820
|
-
const content = await fs.readFile(claudeConfigFile, 'utf-8')
|
|
821
|
-
config = JSON.parse(content)
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// Merge Context7 into existing config
|
|
825
|
-
config.mcpServers = config.mcpServers || {}
|
|
826
|
-
config.mcpServers.context7 = mcpConfig.mcpServers.context7
|
|
827
|
-
|
|
828
|
-
await fs.writeFile(claudeConfigFile, JSON.stringify(config, null, 2), 'utf-8')
|
|
829
|
-
results.editors.push('Claude Code')
|
|
830
|
-
results.details.claude = { success: true, path: claudeConfigFile }
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
// 2. Cursor: ~/.cursor/mcp.json
|
|
834
|
-
if (this.editors.cursor.detected) {
|
|
835
|
-
const cursorMcpFile = path.join(this.homeDir, '.cursor', 'mcp.json')
|
|
836
|
-
await fs.mkdir(path.dirname(cursorMcpFile), { recursive: true })
|
|
837
|
-
|
|
838
|
-
let config = {}
|
|
839
|
-
if (await this.fileExists(cursorMcpFile)) {
|
|
840
|
-
const content = await fs.readFile(cursorMcpFile, 'utf-8')
|
|
841
|
-
config = JSON.parse(content)
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
config.mcpServers = config.mcpServers || {}
|
|
845
|
-
config.mcpServers.context7 = mcpConfig.mcpServers.context7
|
|
846
|
-
|
|
847
|
-
await fs.writeFile(cursorMcpFile, JSON.stringify(config, null, 2), 'utf-8')
|
|
848
|
-
results.editors.push('Cursor')
|
|
849
|
-
results.details.cursor = { success: true, path: cursorMcpFile }
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
// 3. Windsurf: ~/.windsurf/mcp.json
|
|
853
|
-
if (this.editors.windsurf.detected) {
|
|
854
|
-
const windsurfMcpFile = path.join(this.homeDir, '.windsurf', 'mcp.json')
|
|
855
|
-
await fs.mkdir(path.dirname(windsurfMcpFile), { recursive: true })
|
|
856
|
-
|
|
857
|
-
let config = {}
|
|
858
|
-
if (await this.fileExists(windsurfMcpFile)) {
|
|
859
|
-
const content = await fs.readFile(windsurfMcpFile, 'utf-8')
|
|
860
|
-
config = JSON.parse(content)
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
config.mcpServers = config.mcpServers || {}
|
|
864
|
-
config.mcpServers.context7 = mcpConfig.mcpServers.context7
|
|
865
|
-
|
|
866
|
-
await fs.writeFile(windsurfMcpFile, JSON.stringify(config, null, 2), 'utf-8')
|
|
867
|
-
results.editors.push('Windsurf')
|
|
868
|
-
results.details.windsurf = { success: true, path: windsurfMcpFile }
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
// 4. Codex: Add MCP instructions to ~/.codex/instructions.md
|
|
872
|
-
if (this.editors.codex.detected) {
|
|
873
|
-
const codexInstructions = this.editors.codex.commandsPath
|
|
874
|
-
|
|
875
|
-
let content = ''
|
|
876
|
-
if (await this.fileExists(codexInstructions)) {
|
|
877
|
-
content = await fs.readFile(codexInstructions, 'utf-8')
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
// Add MCP section if not present
|
|
881
|
-
if (!content.includes('## MCP Integration')) {
|
|
882
|
-
const mcpSection = '\n\n## MCP Integration\n\nThe system integrates with MCP servers:\n\n- **Context7**: Library documentation lookup\n- **Filesystem**: Direct file manipulation\n- **Memory**: Persistent decision storage\n- **Sequential**: Deep reasoning for complex problems\n\n### Using Context7\n\nFor any library or framework questions, use Context7 MCP to lookup official documentation:\n\n```\n# Example: Get React hooks documentation\nUse Context7 to lookup React hooks patterns before implementing\n```\n'
|
|
883
|
-
content += mcpSection
|
|
884
|
-
await fs.writeFile(codexInstructions, content, 'utf-8')
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
results.editors.push('Codex')
|
|
888
|
-
results.details.codex = { success: true, path: codexInstructions }
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
results.message = `Context7 MCP installed for: ${results.editors.join(', ')}`
|
|
892
|
-
} catch (error) {
|
|
893
|
-
results.success = false
|
|
894
|
-
results.message = `Context7 installation failed: ${error.message}`
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
return results
|
|
208
|
+
getInstallPath() {
|
|
209
|
+
return this.claudeCommandsPath
|
|
898
210
|
}
|
|
899
211
|
|
|
900
212
|
/**
|
|
901
|
-
*
|
|
902
|
-
* @param {string}
|
|
903
|
-
* @returns {Promise<
|
|
213
|
+
* Verify command template exists
|
|
214
|
+
* @param {string} commandName - Command name (without .md extension)
|
|
215
|
+
* @returns {Promise<boolean>} True if template exists
|
|
904
216
|
*/
|
|
905
|
-
async
|
|
906
|
-
const editor = this.editors[editorKey]
|
|
907
|
-
|
|
908
|
-
if (!editor) {
|
|
909
|
-
return { success: false, editor: editorKey, message: `Unknown editor: ${editorKey}` }
|
|
910
|
-
}
|
|
911
|
-
|
|
217
|
+
async verifyTemplate(commandName) {
|
|
912
218
|
try {
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
success: true,
|
|
919
|
-
editor: editor.name,
|
|
920
|
-
path: editor.commandsPath,
|
|
921
|
-
message: `Removed slash commands from ${editor.name}`,
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
case 'agents-md':
|
|
925
|
-
// Remove AGENTS.md if it exists
|
|
926
|
-
const exists = await this.fileExists(editor.commandsPath)
|
|
927
|
-
if (exists) {
|
|
928
|
-
await fs.unlink(editor.commandsPath)
|
|
929
|
-
}
|
|
930
|
-
return {
|
|
931
|
-
success: true,
|
|
932
|
-
editor: editor.name,
|
|
933
|
-
path: editor.commandsPath,
|
|
934
|
-
message: `Removed AGENTS.md from ${editor.name}`,
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
case 'workflows':
|
|
938
|
-
// Remove all p_*.md workflow files
|
|
939
|
-
try {
|
|
940
|
-
const files = await fs.readdir(editor.commandsPath)
|
|
941
|
-
const prjctWorkflows = files.filter(f => f.startsWith('p_') && f.endsWith('.md'))
|
|
942
|
-
|
|
943
|
-
for (const file of prjctWorkflows) {
|
|
944
|
-
await fs.unlink(path.join(editor.commandsPath, file))
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
return {
|
|
948
|
-
success: true,
|
|
949
|
-
editor: editor.name,
|
|
950
|
-
path: editor.commandsPath,
|
|
951
|
-
removed: prjctWorkflows.length,
|
|
952
|
-
message: `Removed ${prjctWorkflows.length} workflows from ${editor.name}`,
|
|
953
|
-
}
|
|
954
|
-
} catch (error) {
|
|
955
|
-
// Directory might not exist, that's ok
|
|
956
|
-
return {
|
|
957
|
-
success: true,
|
|
958
|
-
editor: editor.name,
|
|
959
|
-
message: `No workflows found in ${editor.name}`,
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
default:
|
|
964
|
-
return {
|
|
965
|
-
success: false,
|
|
966
|
-
editor: editor.name,
|
|
967
|
-
message: `Unknown format: ${editor.format}`,
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
} catch (error) {
|
|
971
|
-
return {
|
|
972
|
-
success: false,
|
|
973
|
-
editor: editor.name,
|
|
974
|
-
message: `Uninstall failed: ${error.message}`,
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
/**
|
|
980
|
-
* Uninstall commands from all tracked editors
|
|
981
|
-
* @returns {Promise<Object>} Uninstallation results
|
|
982
|
-
*/
|
|
983
|
-
async uninstallFromAll() {
|
|
984
|
-
const editorsConfig = require('./editors-config')
|
|
985
|
-
const trackedEditors = await editorsConfig.getTrackedEditors()
|
|
986
|
-
|
|
987
|
-
if (trackedEditors.length === 0) {
|
|
988
|
-
return {
|
|
989
|
-
success: true,
|
|
990
|
-
message: 'No editors tracked, nothing to uninstall',
|
|
991
|
-
results: {},
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
const results = {}
|
|
996
|
-
const successfulEditors = []
|
|
997
|
-
|
|
998
|
-
for (const editorKey of trackedEditors) {
|
|
999
|
-
results[editorKey] = await this.uninstallFromEditor(editorKey)
|
|
1000
|
-
if (results[editorKey].success) {
|
|
1001
|
-
successfulEditors.push(this.editors[editorKey]?.name || editorKey)
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
return {
|
|
1006
|
-
success: true,
|
|
1007
|
-
editors: successfulEditors,
|
|
1008
|
-
totalRemoved: successfulEditors.length,
|
|
1009
|
-
results,
|
|
219
|
+
const templatePath = path.join(this.templatesDir, `${commandName}.md`)
|
|
220
|
+
await fs.access(templatePath)
|
|
221
|
+
return true
|
|
222
|
+
} catch {
|
|
223
|
+
return false
|
|
1010
224
|
}
|
|
1011
225
|
}
|
|
1012
226
|
}
|