specsmd 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -0
- package/bin/cli.js +21 -0
- package/flows/aidlc/README.md +372 -0
- package/flows/aidlc/agents/construction-agent.md +81 -0
- package/flows/aidlc/agents/inception-agent.md +95 -0
- package/flows/aidlc/agents/master-agent.md +61 -0
- package/flows/aidlc/agents/operations-agent.md +89 -0
- package/flows/aidlc/commands/construction-agent.md +63 -0
- package/flows/aidlc/commands/inception-agent.md +55 -0
- package/flows/aidlc/commands/master-agent.md +47 -0
- package/flows/aidlc/commands/operations-agent.md +77 -0
- package/flows/aidlc/context-config.yaml +41 -0
- package/flows/aidlc/memory-bank.yaml +104 -0
- package/flows/aidlc/quick-start.md +315 -0
- package/flows/aidlc/skills/construction/bolt-list.md +163 -0
- package/flows/aidlc/skills/construction/bolt-replan.md +343 -0
- package/flows/aidlc/skills/construction/bolt-start.md +289 -0
- package/flows/aidlc/skills/construction/bolt-status.md +185 -0
- package/flows/aidlc/skills/construction/navigator.md +196 -0
- package/flows/aidlc/skills/inception/bolt-plan.md +338 -0
- package/flows/aidlc/skills/inception/context.md +171 -0
- package/flows/aidlc/skills/inception/intent-create.md +211 -0
- package/flows/aidlc/skills/inception/intent-list.md +124 -0
- package/flows/aidlc/skills/inception/navigator.md +207 -0
- package/flows/aidlc/skills/inception/requirements.md +227 -0
- package/flows/aidlc/skills/inception/review.md +248 -0
- package/flows/aidlc/skills/inception/story-create.md +304 -0
- package/flows/aidlc/skills/inception/units.md +271 -0
- package/flows/aidlc/skills/master/analyze-context.md +132 -0
- package/flows/aidlc/skills/master/answer-question.md +141 -0
- package/flows/aidlc/skills/master/explain-flow.md +146 -0
- package/flows/aidlc/skills/master/project-init.md +281 -0
- package/flows/aidlc/skills/master/route-request.md +126 -0
- package/flows/aidlc/skills/operations/build.md +237 -0
- package/flows/aidlc/skills/operations/deploy.md +259 -0
- package/flows/aidlc/skills/operations/monitor.md +265 -0
- package/flows/aidlc/skills/operations/navigator.md +209 -0
- package/flows/aidlc/skills/operations/verify.md +224 -0
- package/flows/aidlc/templates/construction/bolt-template.md +193 -0
- package/flows/aidlc/templates/construction/bolt-types/bdd-construction-bolt.md +250 -0
- package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +49 -0
- package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +55 -0
- package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +67 -0
- package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +62 -0
- package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt.md +528 -0
- package/flows/aidlc/templates/construction/bolt-types/simple-construction-bolt.md +273 -0
- package/flows/aidlc/templates/construction/bolt-types/spike-bolt.md +240 -0
- package/flows/aidlc/templates/construction/bolt-types/tdd-construction-bolt.md +259 -0
- package/flows/aidlc/templates/construction/construction-log-template.md +129 -0
- package/flows/aidlc/templates/construction/standards/coding-standards.md +29 -0
- package/flows/aidlc/templates/construction/standards/system-architecture.md +22 -0
- package/flows/aidlc/templates/construction/standards/tech-stack.md +19 -0
- package/flows/aidlc/templates/inception/inception-log-template.md +134 -0
- package/flows/aidlc/templates/inception/project/README.md +55 -0
- package/flows/aidlc/templates/inception/requirements-template.md +144 -0
- package/flows/aidlc/templates/inception/stories-template.md +38 -0
- package/flows/aidlc/templates/inception/story-template.md +147 -0
- package/flows/aidlc/templates/inception/system-context-template.md +29 -0
- package/flows/aidlc/templates/inception/unit-brief-template.md +177 -0
- package/flows/aidlc/templates/inception/units-template.md +52 -0
- package/flows/aidlc/templates/standards/catalog.yaml +345 -0
- package/flows/aidlc/templates/standards/coding-standards.guide.md +553 -0
- package/flows/aidlc/templates/standards/data-stack.guide.md +162 -0
- package/flows/aidlc/templates/standards/tech-stack.guide.md +280 -0
- package/lib/InstallerFactory.js +36 -0
- package/lib/cli-utils.js +372 -0
- package/lib/constants.js +31 -0
- package/lib/installer.js +314 -0
- package/lib/installers/AntigravityInstaller.js +22 -0
- package/lib/installers/ClaudeInstaller.js +85 -0
- package/lib/installers/ClineInstaller.js +21 -0
- package/lib/installers/CodexInstaller.js +21 -0
- package/lib/installers/CopilotInstaller.js +113 -0
- package/lib/installers/CursorInstaller.js +63 -0
- package/lib/installers/GeminiInstaller.js +75 -0
- package/lib/installers/KiroInstaller.js +22 -0
- package/lib/installers/OpenCodeInstaller.js +22 -0
- package/lib/installers/RooInstaller.js +22 -0
- package/lib/installers/ToolInstaller.js +73 -0
- package/lib/installers/WindsurfInstaller.js +76 -0
- package/lib/markdown-validator.ts +175 -0
- package/lib/yaml-validator.ts +99 -0
- package/package.json +65 -0
package/lib/constants.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
// Theme Colors (Terracotta/Orange inspired by Claude Code)
|
|
3
|
+
const THEME_COLORS = {
|
|
4
|
+
primary: '#D97757', // Terracotta
|
|
5
|
+
secondary: '#ff9966', // Light orange
|
|
6
|
+
success: '#22c55e', // Green
|
|
7
|
+
error: '#ef4444', // Red
|
|
8
|
+
warning: '#f59e0b', // Amber
|
|
9
|
+
info: '#3b82f6', // Blue
|
|
10
|
+
dim: '#6b7280' // Gray
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const FLOWS = {
|
|
14
|
+
aidlc: {
|
|
15
|
+
name: 'AI-DLC',
|
|
16
|
+
description: 'AI-Driven Development Life Cycle - Best for greenfield projects with AI-native development',
|
|
17
|
+
path: 'aidlc'
|
|
18
|
+
},
|
|
19
|
+
agile: {
|
|
20
|
+
name: 'Agile',
|
|
21
|
+
description: 'Sprint-based Agile development - Best for iterative development with changing requirements',
|
|
22
|
+
path: 'agile',
|
|
23
|
+
disabled: true, // Not implemented yet
|
|
24
|
+
message: '(Coming soon)'
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
THEME_COLORS,
|
|
30
|
+
FLOWS
|
|
31
|
+
};
|
package/lib/installer.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const prompts = require('prompts');
|
|
4
|
+
const yaml = require('js-yaml');
|
|
5
|
+
const CLIUtils = require('./cli-utils');
|
|
6
|
+
const InstallerFactory = require('./InstallerFactory');
|
|
7
|
+
const { FLOWS } = require('./constants');
|
|
8
|
+
|
|
9
|
+
// Use theme from CLIUtils for consistent styling
|
|
10
|
+
const { theme } = CLIUtils;
|
|
11
|
+
|
|
12
|
+
async function detectTools() {
|
|
13
|
+
const detected = [];
|
|
14
|
+
const installers = InstallerFactory.getInstallers();
|
|
15
|
+
|
|
16
|
+
for (const installer of installers) {
|
|
17
|
+
if (await installer.detect()) {
|
|
18
|
+
detected.push(installer.key);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return detected;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function install() {
|
|
25
|
+
await CLIUtils.displayLogo();
|
|
26
|
+
CLIUtils.displayHeader('Installation', '');
|
|
27
|
+
|
|
28
|
+
// Step 1: Detect agentic coding tools
|
|
29
|
+
const detectedToolKeys = await detectTools();
|
|
30
|
+
const installers = InstallerFactory.getInstallers();
|
|
31
|
+
const detectedNames = installers
|
|
32
|
+
.filter(i => detectedToolKeys.includes(i.key))
|
|
33
|
+
.map(i => i.name);
|
|
34
|
+
|
|
35
|
+
CLIUtils.displayStep(1, 4, 'Detecting agentic coding tools...');
|
|
36
|
+
if (detectedNames.length > 0) {
|
|
37
|
+
CLIUtils.displayStatus('', `Detected: ${detectedNames.join(', ')}`, 'success');
|
|
38
|
+
} else {
|
|
39
|
+
CLIUtils.displayStatus('', 'No agentic coding tools detected', 'warning');
|
|
40
|
+
}
|
|
41
|
+
console.log('');
|
|
42
|
+
|
|
43
|
+
// Step 2: Select tools
|
|
44
|
+
CLIUtils.displayStep(2, 4, 'Select target tools');
|
|
45
|
+
|
|
46
|
+
// Build choices with descriptive formatting
|
|
47
|
+
const toolChoices = installers.map(installer => ({
|
|
48
|
+
title: installer.name + (detectedToolKeys.includes(installer.key) ? theme.dim(' (detected)') : ''),
|
|
49
|
+
value: installer.key,
|
|
50
|
+
selected: detectedToolKeys.includes(installer.key)
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
console.log(theme.dim(' [Space] toggle [Enter] confirm [a] toggle all'));
|
|
54
|
+
console.log(theme.dim(` ${theme.success('[x]')} = selected ${theme.dim('[ ]')} = not selected\n`));
|
|
55
|
+
|
|
56
|
+
const { selectedToolKeys } = await prompts({
|
|
57
|
+
type: 'multiselect',
|
|
58
|
+
name: 'selectedToolKeys',
|
|
59
|
+
message: 'Choose tools:',
|
|
60
|
+
choices: toolChoices,
|
|
61
|
+
min: 1,
|
|
62
|
+
instructions: false
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!selectedToolKeys || selectedToolKeys.length === 0) {
|
|
66
|
+
CLIUtils.displayError('Installation cancelled - no tools selected');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Step 3: Select Flow
|
|
71
|
+
console.log('');
|
|
72
|
+
CLIUtils.displayStep(3, 4, 'Select SDLC flow');
|
|
73
|
+
const flowChoices = Object.entries(FLOWS).map(([key, flow]) => ({
|
|
74
|
+
title: `${flow.name} - ${flow.description}${flow.message || ''}`,
|
|
75
|
+
value: key,
|
|
76
|
+
disabled: flow.disabled
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
const { selectedFlow } = await prompts({
|
|
80
|
+
type: 'select',
|
|
81
|
+
name: 'selectedFlow',
|
|
82
|
+
message: 'Which SDLC flow would you like to install?',
|
|
83
|
+
choices: flowChoices
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!selectedFlow) {
|
|
87
|
+
CLIUtils.displayError('Installation cancelled');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Step 4: Install flow files
|
|
92
|
+
console.log('');
|
|
93
|
+
CLIUtils.displayStep(4, 4, `Installing ${FLOWS[selectedFlow].name} flow...`);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
await installFlow(selectedFlow, selectedToolKeys);
|
|
97
|
+
|
|
98
|
+
CLIUtils.displaySuccess(`${FLOWS[selectedFlow].name} flow installed successfully!`, 'Installation Complete');
|
|
99
|
+
|
|
100
|
+
// Get selected tool names for next steps message
|
|
101
|
+
const selectedToolNames = installers
|
|
102
|
+
.filter(i => selectedToolKeys.includes(i.key))
|
|
103
|
+
.map(i => i.name);
|
|
104
|
+
|
|
105
|
+
const nextSteps = [
|
|
106
|
+
`Read .specsmd/${selectedFlow}/quick-start.md for getting started`,
|
|
107
|
+
`Open ${selectedToolNames.join(' or ')} and run /specsmd-master-agent`
|
|
108
|
+
];
|
|
109
|
+
CLIUtils.displayNextSteps(nextSteps);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
CLIUtils.displayError(`Installation failed: ${error.message}`);
|
|
112
|
+
console.log(theme.dim('\nRolling back changes...'));
|
|
113
|
+
await rollback(selectedFlow, selectedToolKeys);
|
|
114
|
+
CLIUtils.displayStatus('', 'Installation rolled back', 'warning');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function installFlow(flowKey, toolKeys) {
|
|
120
|
+
const flowPath = path.join(__dirname, '..', 'flows', FLOWS[flowKey].path);
|
|
121
|
+
|
|
122
|
+
// Step 1: Install commands for each tool
|
|
123
|
+
// Pass empty config since config.yaml is removed
|
|
124
|
+
const dummyConfig = {};
|
|
125
|
+
for (const toolKey of toolKeys) {
|
|
126
|
+
const installer = InstallerFactory.getInstaller(toolKey);
|
|
127
|
+
if (installer) {
|
|
128
|
+
await installer.installCommands(flowPath, dummyConfig);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Step 2: Install shared flow config
|
|
133
|
+
const specsmdDir = '.specsmd';
|
|
134
|
+
const targetFlowDir = path.join(specsmdDir, flowKey);
|
|
135
|
+
|
|
136
|
+
console.log(theme.dim(` Installing flow resources to ${targetFlowDir}/...`));
|
|
137
|
+
await fs.ensureDir(targetFlowDir);
|
|
138
|
+
|
|
139
|
+
// Copy agents
|
|
140
|
+
await fs.copy(path.join(flowPath, 'agents'), path.join(targetFlowDir, 'agents'));
|
|
141
|
+
|
|
142
|
+
// Copy internal agent capabilities (legacy check)
|
|
143
|
+
if (await fs.pathExists(path.join(flowPath, 'agent-capabilities'))) {
|
|
144
|
+
await fs.copy(path.join(flowPath, 'agent-capabilities'), path.join(targetFlowDir, 'agent-capabilities'));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Copy bolt-types if they exist (legacy check)
|
|
148
|
+
if (await fs.pathExists(path.join(flowPath, 'bolt-types'))) {
|
|
149
|
+
await fs.copy(path.join(flowPath, 'bolt-types'), path.join(targetFlowDir, 'bolt-types'));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Copy skills, templates, shared (now at flow root level, not nested in .specsmd)
|
|
153
|
+
if (await fs.pathExists(path.join(flowPath, 'skills'))) {
|
|
154
|
+
await fs.copy(path.join(flowPath, 'skills'), path.join(targetFlowDir, 'skills'));
|
|
155
|
+
}
|
|
156
|
+
if (await fs.pathExists(path.join(flowPath, 'templates'))) {
|
|
157
|
+
await fs.copy(path.join(flowPath, 'templates'), path.join(targetFlowDir, 'templates'));
|
|
158
|
+
}
|
|
159
|
+
if (await fs.pathExists(path.join(flowPath, 'shared'))) {
|
|
160
|
+
await fs.copy(path.join(flowPath, 'shared'), path.join(targetFlowDir, 'shared'));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Copy config files
|
|
164
|
+
if (await fs.pathExists(path.join(flowPath, 'memory-bank.yaml'))) {
|
|
165
|
+
await fs.copy(path.join(flowPath, 'memory-bank.yaml'), path.join(targetFlowDir, 'memory-bank.yaml'));
|
|
166
|
+
}
|
|
167
|
+
if (await fs.pathExists(path.join(flowPath, 'context-config.yaml'))) {
|
|
168
|
+
await fs.copy(path.join(flowPath, 'context-config.yaml'), path.join(targetFlowDir, 'context-config.yaml'));
|
|
169
|
+
}
|
|
170
|
+
if (await fs.pathExists(path.join(flowPath, 'quick-start.md'))) {
|
|
171
|
+
await fs.copy(path.join(flowPath, 'quick-start.md'), path.join(targetFlowDir, 'quick-start.md'));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Copy docs
|
|
175
|
+
await fs.copy(path.join(flowPath, 'README.md'), path.join(targetFlowDir, 'README.md'));
|
|
176
|
+
|
|
177
|
+
if (await fs.pathExists(path.join(flowPath, 'constitution.md'))) {
|
|
178
|
+
await fs.copy(path.join(flowPath, 'constitution.md'), path.join(targetFlowDir, 'constitution.md'));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
CLIUtils.displayStatus('', 'Installed flow resources', 'success');
|
|
182
|
+
|
|
183
|
+
// NOTE: memory-bank/ is NOT created during installation
|
|
184
|
+
// It will be created when user runs project-init
|
|
185
|
+
// This allows us to detect if project is initialized by checking for memory-bank/standards/
|
|
186
|
+
|
|
187
|
+
// Step 3: Create manifest
|
|
188
|
+
const manifest = {
|
|
189
|
+
flow: flowKey,
|
|
190
|
+
version: require('../package.json').version,
|
|
191
|
+
installed_at: new Date().toISOString(),
|
|
192
|
+
tools: toolKeys
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
await fs.writeFile(
|
|
196
|
+
path.join(specsmdDir, 'manifest.yaml'),
|
|
197
|
+
yaml.dump(manifest),
|
|
198
|
+
'utf8'
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
CLIUtils.displayStatus('', 'Created installation manifest', 'success');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function rollback(flowKey, toolKeys) {
|
|
205
|
+
// Remove tool command files
|
|
206
|
+
for (const toolKey of toolKeys) {
|
|
207
|
+
const installer = InstallerFactory.getInstaller(toolKey);
|
|
208
|
+
if (installer) {
|
|
209
|
+
const commandsDir = installer.commandsDir;
|
|
210
|
+
if (await fs.pathExists(commandsDir)) {
|
|
211
|
+
const files = await fs.readdir(commandsDir);
|
|
212
|
+
for (const file of files) {
|
|
213
|
+
if (file.startsWith('specsmd-')) {
|
|
214
|
+
await fs.remove(path.join(commandsDir, file));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Remove .specsmd directory
|
|
222
|
+
if (await fs.pathExists('.specsmd')) {
|
|
223
|
+
await fs.remove('.specsmd');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function uninstall() {
|
|
228
|
+
CLIUtils.displayHeader('Uninstall', '');
|
|
229
|
+
|
|
230
|
+
// Check if specsmd is installed
|
|
231
|
+
if (!await fs.pathExists('.specsmd/manifest.yaml')) {
|
|
232
|
+
CLIUtils.displayWarning('specsmd is not installed in this project');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Read manifest
|
|
237
|
+
const manifestContent = await fs.readFile('.specsmd/manifest.yaml', 'utf8');
|
|
238
|
+
const manifest = yaml.load(manifestContent);
|
|
239
|
+
|
|
240
|
+
const installers = InstallerFactory.getInstallers();
|
|
241
|
+
// Support both old 'ides' key and new 'tools' key for backward compatibility
|
|
242
|
+
const installedToolKeys = manifest.tools || manifest.ides || [];
|
|
243
|
+
const installedNames = installers
|
|
244
|
+
.filter(i => installedToolKeys.includes(i.key))
|
|
245
|
+
.map(i => i.name);
|
|
246
|
+
|
|
247
|
+
console.log(theme.dim(`Found installation: ${FLOWS[manifest.flow].name} flow`));
|
|
248
|
+
console.log(theme.dim(`Installed for: ${installedNames.join(', ')}\n`));
|
|
249
|
+
|
|
250
|
+
const { confirm } = await prompts({
|
|
251
|
+
type: 'confirm',
|
|
252
|
+
name: 'confirm',
|
|
253
|
+
message: 'Are you sure you want to uninstall specsmd?',
|
|
254
|
+
initial: false
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (!confirm) {
|
|
258
|
+
CLIUtils.displayStatus('', 'Uninstall cancelled', 'warning');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Ask about memory bank
|
|
263
|
+
const { keepMemoryBank } = await prompts({
|
|
264
|
+
type: 'confirm',
|
|
265
|
+
name: 'keepMemoryBank',
|
|
266
|
+
message: 'Keep memory-bank folder? (Contains your project artifacts)',
|
|
267
|
+
initial: true
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
console.log(theme.primary('\nUninstalling...\n'));
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
// Remove command files
|
|
274
|
+
for (const toolKey of installedToolKeys) {
|
|
275
|
+
const installer = InstallerFactory.getInstaller(toolKey);
|
|
276
|
+
if (installer) {
|
|
277
|
+
const commandsDir = installer.commandsDir;
|
|
278
|
+
if (await fs.pathExists(commandsDir)) {
|
|
279
|
+
console.log(theme.dim(` Removing commands from ${commandsDir}/...`));
|
|
280
|
+
const files = await fs.readdir(commandsDir);
|
|
281
|
+
for (const file of files) {
|
|
282
|
+
if (file.startsWith('specsmd-')) {
|
|
283
|
+
await fs.remove(path.join(commandsDir, file));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Remove .specsmd directory
|
|
291
|
+
console.log(theme.dim(' Removing .specsmd/...'));
|
|
292
|
+
await fs.remove('.specsmd');
|
|
293
|
+
|
|
294
|
+
// Optionally remove memory-bank
|
|
295
|
+
if (!keepMemoryBank && await fs.pathExists('memory-bank')) {
|
|
296
|
+
console.log(theme.dim(' Removing memory-bank/...'));
|
|
297
|
+
await fs.remove('memory-bank');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
CLIUtils.displaySuccess('Uninstall complete!', 'Complete');
|
|
301
|
+
if (keepMemoryBank) {
|
|
302
|
+
console.log(theme.dim('memory-bank/ was preserved\n'));
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
CLIUtils.displayError(`Uninstall failed: ${error.message}`);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
module.exports = {
|
|
311
|
+
install,
|
|
312
|
+
uninstall
|
|
313
|
+
};
|
|
314
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const ToolInstaller = require('./ToolInstaller');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class AntigravityInstaller extends ToolInstaller {
|
|
5
|
+
get key() {
|
|
6
|
+
return 'antigravity';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get name() {
|
|
10
|
+
return 'Google Antigravity';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get commandsDir() {
|
|
14
|
+
return path.join('.agent', 'agents');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get detectPath() {
|
|
18
|
+
return '.agent';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = AntigravityInstaller;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const ToolInstaller = require('./ToolInstaller');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const CLIUtils = require('../cli-utils');
|
|
5
|
+
const { theme } = CLIUtils;
|
|
6
|
+
|
|
7
|
+
class ClaudeInstaller extends ToolInstaller {
|
|
8
|
+
get key() {
|
|
9
|
+
return 'claude';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get name() {
|
|
13
|
+
return 'Claude Code';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get commandsDir() {
|
|
17
|
+
return path.join('.claude', 'commands');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get agentsDir() {
|
|
21
|
+
return path.join('.claude', 'agents');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get detectPath() {
|
|
25
|
+
return '.claude';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Override to install both commands and agents
|
|
30
|
+
*/
|
|
31
|
+
async installCommands(flowPath, config) {
|
|
32
|
+
// Install commands (default behavior)
|
|
33
|
+
const installedCommands = await super.installCommands(flowPath, config);
|
|
34
|
+
|
|
35
|
+
// Install agents
|
|
36
|
+
const installedAgents = await this.installAgents(flowPath, config);
|
|
37
|
+
|
|
38
|
+
return [...installedCommands, ...installedAgents];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Install agents to .claude/agents/
|
|
43
|
+
* Uses the commands folder as source (same files serve as both commands and agents)
|
|
44
|
+
*/
|
|
45
|
+
async installAgents(flowPath, config) {
|
|
46
|
+
const targetAgentsDir = this.agentsDir;
|
|
47
|
+
console.log(theme.dim(` Installing agents to ${targetAgentsDir}/...`));
|
|
48
|
+
await fs.ensureDir(targetAgentsDir);
|
|
49
|
+
|
|
50
|
+
const commandsSourceDir = path.join(flowPath, 'commands');
|
|
51
|
+
|
|
52
|
+
if (!await fs.pathExists(commandsSourceDir)) {
|
|
53
|
+
console.log(theme.dim(` No commands folder found at ${commandsSourceDir}`));
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const agentFiles = await fs.readdir(commandsSourceDir);
|
|
58
|
+
const installedFiles = [];
|
|
59
|
+
|
|
60
|
+
for (const agentFile of agentFiles) {
|
|
61
|
+
if (agentFile.endsWith('.md')) {
|
|
62
|
+
try {
|
|
63
|
+
const sourcePath = path.join(commandsSourceDir, agentFile);
|
|
64
|
+
const prefix = (config && config.command && config.command.prefix) ? `${config.command.prefix}-` : '';
|
|
65
|
+
const targetFileName = `specsmd-${prefix}${agentFile}`;
|
|
66
|
+
const targetPath = path.join(targetAgentsDir, targetFileName);
|
|
67
|
+
|
|
68
|
+
const content = await fs.readFile(sourcePath, 'utf8');
|
|
69
|
+
await fs.outputFile(targetPath, content, 'utf8');
|
|
70
|
+
installedFiles.push(targetFileName);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.log(theme.warning(` Failed to install agent ${agentFile}: ${err.message}`));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (installedFiles.length > 0) {
|
|
78
|
+
CLIUtils.displayStatus('', `Installed ${installedFiles.length} agents for ${this.name}`, 'success');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return installedFiles;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = ClaudeInstaller;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const ToolInstaller = require('./ToolInstaller');
|
|
2
|
+
|
|
3
|
+
class ClineInstaller extends ToolInstaller {
|
|
4
|
+
get key() {
|
|
5
|
+
return 'cline';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get name() {
|
|
9
|
+
return 'Cline';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get commandsDir() {
|
|
13
|
+
return '.clinerules';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get detectPath() {
|
|
17
|
+
return '.clinerules';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = ClineInstaller;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const ToolInstaller = require('./ToolInstaller');
|
|
2
|
+
|
|
3
|
+
class CodexInstaller extends ToolInstaller {
|
|
4
|
+
get key() {
|
|
5
|
+
return 'codex';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get name() {
|
|
9
|
+
return 'Codex';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get commandsDir() {
|
|
13
|
+
return '.codex';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get detectPath() {
|
|
17
|
+
return '.codex';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = CodexInstaller;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const ToolInstaller = require('./ToolInstaller');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const CLIUtils = require('../cli-utils');
|
|
5
|
+
const { theme } = CLIUtils;
|
|
6
|
+
|
|
7
|
+
class CopilotInstaller extends ToolInstaller {
|
|
8
|
+
get key() {
|
|
9
|
+
return 'copilot';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get name() {
|
|
13
|
+
return 'GitHub Copilot';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get commandsDir() {
|
|
17
|
+
return path.join('.github', 'prompts');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get agentsDir() {
|
|
21
|
+
return path.join('.github', 'agents');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get detectPath() {
|
|
25
|
+
return '.github';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Override to install both commands and agents
|
|
30
|
+
*/
|
|
31
|
+
async installCommands(flowPath, config) {
|
|
32
|
+
const installedCommands = await this.installCommandFiles(flowPath, config);
|
|
33
|
+
const installedAgents = await this.installAgentFiles(flowPath, config);
|
|
34
|
+
return [...installedCommands, ...installedAgents];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Install prompts to .github/prompts/ with .prompt.md extension
|
|
39
|
+
*/
|
|
40
|
+
async installCommandFiles(flowPath, config) {
|
|
41
|
+
const targetDir = this.commandsDir;
|
|
42
|
+
console.log(theme.dim(` Installing prompts to ${targetDir}/...`));
|
|
43
|
+
await fs.ensureDir(targetDir);
|
|
44
|
+
|
|
45
|
+
const sourceDir = path.join(flowPath, 'commands');
|
|
46
|
+
|
|
47
|
+
if (!await fs.pathExists(sourceDir)) {
|
|
48
|
+
console.log(theme.dim(` No commands folder found at ${sourceDir}`));
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const files = await fs.readdir(sourceDir);
|
|
53
|
+
const installedFiles = [];
|
|
54
|
+
|
|
55
|
+
for (const file of files) {
|
|
56
|
+
if (file.endsWith('.md')) {
|
|
57
|
+
const sourcePath = path.join(sourceDir, file);
|
|
58
|
+
const prefix = (config && config.command && config.command.prefix) ? `${config.command.prefix}-` : '';
|
|
59
|
+
// Transform .md to .prompt.md for Copilot prompts
|
|
60
|
+
const targetFileName = `specsmd-${prefix}${file}`.replace(/\.md$/, '.prompt.md');
|
|
61
|
+
const targetPath = path.join(targetDir, targetFileName);
|
|
62
|
+
|
|
63
|
+
await fs.copy(sourcePath, targetPath);
|
|
64
|
+
installedFiles.push(targetFileName);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (installedFiles.length > 0) {
|
|
69
|
+
CLIUtils.displayStatus('', `Installed ${installedFiles.length} prompts for ${this.name}`, 'success');
|
|
70
|
+
}
|
|
71
|
+
return installedFiles;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Install agents to .github/agents/ with .agent.md extension
|
|
76
|
+
* Uses the commands folder as source (same files serve as both commands and agents)
|
|
77
|
+
*/
|
|
78
|
+
async installAgentFiles(flowPath, config) {
|
|
79
|
+
const targetDir = this.agentsDir;
|
|
80
|
+
console.log(theme.dim(` Installing agents to ${targetDir}/...`));
|
|
81
|
+
await fs.ensureDir(targetDir);
|
|
82
|
+
|
|
83
|
+
const sourceDir = path.join(flowPath, 'commands');
|
|
84
|
+
|
|
85
|
+
if (!await fs.pathExists(sourceDir)) {
|
|
86
|
+
console.log(theme.dim(` No commands folder found at ${sourceDir}`));
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const files = await fs.readdir(sourceDir);
|
|
91
|
+
const installedFiles = [];
|
|
92
|
+
|
|
93
|
+
for (const file of files) {
|
|
94
|
+
if (file.endsWith('.md')) {
|
|
95
|
+
const sourcePath = path.join(sourceDir, file);
|
|
96
|
+
const prefix = (config && config.command && config.command.prefix) ? `${config.command.prefix}-` : '';
|
|
97
|
+
// Transform .md to .agent.md for Copilot agents
|
|
98
|
+
const targetFileName = `specsmd-${prefix}${file}`.replace(/\.md$/, '.agent.md');
|
|
99
|
+
const targetPath = path.join(targetDir, targetFileName);
|
|
100
|
+
|
|
101
|
+
await fs.copy(sourcePath, targetPath);
|
|
102
|
+
installedFiles.push(targetFileName);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (installedFiles.length > 0) {
|
|
107
|
+
CLIUtils.displayStatus('', `Installed ${installedFiles.length} agents for ${this.name}`, 'success');
|
|
108
|
+
}
|
|
109
|
+
return installedFiles;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = CopilotInstaller;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const ToolInstaller = require('./ToolInstaller');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const CLIUtils = require('../cli-utils');
|
|
5
|
+
const { theme } = CLIUtils;
|
|
6
|
+
|
|
7
|
+
class CursorInstaller extends ToolInstaller {
|
|
8
|
+
get key() {
|
|
9
|
+
return 'cursor';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get name() {
|
|
13
|
+
return 'Cursor';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get commandsDir() {
|
|
17
|
+
return path.join('.cursor', 'commands');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get detectPath() {
|
|
21
|
+
return '.cursor';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Override to install commands as .md files to .cursor/commands
|
|
26
|
+
*/
|
|
27
|
+
async installCommands(flowPath, config) {
|
|
28
|
+
const targetCommandsDir = this.commandsDir;
|
|
29
|
+
console.log(theme.dim(` Installing commands to ${targetCommandsDir}/...`));
|
|
30
|
+
await fs.ensureDir(targetCommandsDir);
|
|
31
|
+
|
|
32
|
+
const commandsSourceDir = path.join(flowPath, 'commands');
|
|
33
|
+
|
|
34
|
+
if (!await fs.pathExists(commandsSourceDir)) {
|
|
35
|
+
console.log(theme.warning(` No commands folder found at ${commandsSourceDir}`));
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const commandFiles = await fs.readdir(commandsSourceDir);
|
|
40
|
+
const installedFiles = [];
|
|
41
|
+
|
|
42
|
+
for (const cmdFile of commandFiles) {
|
|
43
|
+
if (cmdFile.endsWith('.md')) {
|
|
44
|
+
const sourcePath = path.join(commandsSourceDir, cmdFile);
|
|
45
|
+
const prefix = (config && config.command && config.command.prefix) ? `${config.command.prefix}-` : '';
|
|
46
|
+
|
|
47
|
+
// Keep .md extension for Cursor commands
|
|
48
|
+
const targetFileName = `specsmd-${prefix}${cmdFile}`;
|
|
49
|
+
const targetPath = path.join(targetCommandsDir, targetFileName);
|
|
50
|
+
|
|
51
|
+
// Copy content directly without adding frontmatter
|
|
52
|
+
const content = await fs.readFile(sourcePath, 'utf8');
|
|
53
|
+
await fs.writeFile(targetPath, content);
|
|
54
|
+
installedFiles.push(targetFileName);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
CLIUtils.displayStatus('', `Installed ${installedFiles.length} commands for ${this.name}`, 'success');
|
|
59
|
+
return installedFiles;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = CursorInstaller;
|