speccrew 0.1.1 → 0.1.3
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.ar.md +98 -91
- package/README.bn.md +122 -0
- package/README.bs.md +321 -0
- package/README.da.md +321 -0
- package/README.de.md +321 -0
- package/README.el.md +122 -0
- package/README.en.md +92 -85
- package/README.es.md +96 -89
- package/README.fr.md +321 -0
- package/README.it.md +321 -0
- package/README.ja.md +321 -0
- package/README.ko.md +321 -0
- package/README.md +92 -109
- package/README.no.md +321 -0
- package/README.pl.md +321 -0
- package/README.pt-BR.md +321 -0
- package/README.ru.md +321 -0
- package/README.th.md +239 -0
- package/README.tr.md +239 -0
- package/README.uk.md +239 -0
- package/README.vi.md +122 -0
- package/README.zh-TW.md +321 -0
- package/bin/cli.js +5 -1
- package/docs/GETTING-STARTED.ar.md +452 -0
- package/docs/GETTING-STARTED.bn.md +449 -0
- package/docs/GETTING-STARTED.bs.md +449 -0
- package/docs/GETTING-STARTED.da.md +448 -0
- package/docs/GETTING-STARTED.de.md +448 -0
- package/docs/GETTING-STARTED.el.md +449 -0
- package/docs/GETTING-STARTED.en.md +448 -0
- package/docs/GETTING-STARTED.es.md +448 -0
- package/docs/GETTING-STARTED.fr.md +448 -0
- package/docs/GETTING-STARTED.it.md +448 -0
- package/docs/GETTING-STARTED.ja.md +448 -0
- package/docs/GETTING-STARTED.ko.md +448 -0
- package/docs/GETTING-STARTED.md +448 -0
- package/docs/GETTING-STARTED.no.md +449 -0
- package/docs/GETTING-STARTED.pl.md +449 -0
- package/docs/GETTING-STARTED.pt-BR.md +449 -0
- package/docs/GETTING-STARTED.ru.md +449 -0
- package/docs/GETTING-STARTED.th.md +449 -0
- package/docs/GETTING-STARTED.tr.md +449 -0
- package/docs/GETTING-STARTED.uk.md +449 -0
- package/docs/GETTING-STARTED.vi.md +449 -0
- package/docs/GETTING-STARTED.zh-TW.md +448 -0
- package/lib/commands/init.js +238 -41
- package/lib/commands/uninstall.js +150 -32
- package/lib/commands/update.js +159 -24
- package/lib/ide-adapters.js +257 -3
- package/lib/utils.js +23 -7
- package/package.json +5 -2
package/lib/commands/init.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const readline = require('readline');
|
|
3
4
|
const {
|
|
4
5
|
copyDirRecursive,
|
|
5
6
|
isSpeccrewFile,
|
|
@@ -9,7 +10,7 @@ const {
|
|
|
9
10
|
getWorkspaceTemplatePath,
|
|
10
11
|
ensureDirectories,
|
|
11
12
|
} = require('../utils');
|
|
12
|
-
const { resolveIDE } = require('../ide-adapters');
|
|
13
|
+
const { resolveIDE, transformAgentForIDE, transformSkillForIDE } = require('../ide-adapters');
|
|
13
14
|
|
|
14
15
|
// 解析命令行参数
|
|
15
16
|
function parseArgs() {
|
|
@@ -35,8 +36,22 @@ function checkNodeVersion() {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
// 进度显示辅助函数
|
|
40
|
+
function printProgress(step, total, message) {
|
|
41
|
+
process.stdout.write(`[${step}/${total}] ${message}... `);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function printDone() {
|
|
45
|
+
console.log('done');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 获取 npm 包根目录
|
|
49
|
+
function getPackageRoot() {
|
|
50
|
+
return path.resolve(__dirname, '..', '..');
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
// 复制 agents(speccrew-* 前缀文件,总是覆盖)
|
|
39
|
-
function copyAgents(sourceDir, destDir) {
|
|
54
|
+
function copyAgents(sourceDir, destDir, ideConfig) {
|
|
40
55
|
if (!fs.existsSync(sourceDir)) return { copied: 0, skipped: 0 };
|
|
41
56
|
|
|
42
57
|
fs.mkdirSync(destDir, { recursive: true });
|
|
@@ -52,7 +67,14 @@ function copyAgents(sourceDir, destDir) {
|
|
|
52
67
|
const srcPath = path.join(sourceDir, entry.name);
|
|
53
68
|
const destPath = path.join(destDir, entry.name);
|
|
54
69
|
|
|
55
|
-
|
|
70
|
+
// 如果 IDE 需要转换 frontmatter 且是 .md 文件
|
|
71
|
+
if (ideConfig && ideConfig.transformFrontmatter && entry.name.endsWith('.md')) {
|
|
72
|
+
const originalContent = fs.readFileSync(srcPath, 'utf8');
|
|
73
|
+
const transformedContent = transformAgentForIDE(originalContent, ideConfig);
|
|
74
|
+
fs.writeFileSync(destPath, transformedContent, 'utf8');
|
|
75
|
+
} else {
|
|
76
|
+
fs.copyFileSync(srcPath, destPath);
|
|
77
|
+
}
|
|
56
78
|
copied++;
|
|
57
79
|
}
|
|
58
80
|
|
|
@@ -60,7 +82,7 @@ function copyAgents(sourceDir, destDir) {
|
|
|
60
82
|
}
|
|
61
83
|
|
|
62
84
|
// 复制 skills(speccrew-* 前缀目录,递归复制,总是覆盖)
|
|
63
|
-
function copySkills(sourceDir, destDir) {
|
|
85
|
+
function copySkills(sourceDir, destDir, ideConfig) {
|
|
64
86
|
if (!fs.existsSync(sourceDir)) return { copied: 0, skipped: 0 };
|
|
65
87
|
|
|
66
88
|
let copied = 0, skipped = 0;
|
|
@@ -76,7 +98,18 @@ function copySkills(sourceDir, destDir) {
|
|
|
76
98
|
const destPath = path.join(destDir, entry.name);
|
|
77
99
|
|
|
78
100
|
if (entry.isDirectory()) {
|
|
79
|
-
|
|
101
|
+
// 构建 contentTransform 回调:只对 SKILL.md 文件转化
|
|
102
|
+
let contentTransform = null;
|
|
103
|
+
if (ideConfig && ideConfig.transformFrontmatter) {
|
|
104
|
+
contentTransform = (content, fileName, filePath) => {
|
|
105
|
+
if (fileName === 'SKILL.md') {
|
|
106
|
+
return transformSkillForIDE(content, ideConfig);
|
|
107
|
+
}
|
|
108
|
+
// 其他文件返回 null,表示按原方式复制
|
|
109
|
+
return null;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const result = copyDirRecursive(srcPath, destPath, null, contentTransform);
|
|
80
113
|
copied += result.copied;
|
|
81
114
|
skipped += result.skipped;
|
|
82
115
|
} else {
|
|
@@ -143,23 +176,148 @@ function copyWorkspaceTemplate(templateDir, workspaceDir) {
|
|
|
143
176
|
return { copied, skipped };
|
|
144
177
|
}
|
|
145
178
|
|
|
146
|
-
//
|
|
147
|
-
function
|
|
179
|
+
// 复制文档文件(仅复制不存在的文件)
|
|
180
|
+
function copyDocs(packageRoot, workspaceDir) {
|
|
181
|
+
let copied = 0;
|
|
182
|
+
|
|
183
|
+
// 复制 GETTING-STARTED*.md 到 workspace/docs/
|
|
184
|
+
const docsSourceDir = path.join(packageRoot, 'docs');
|
|
185
|
+
const docsDestDir = path.join(workspaceDir, 'docs');
|
|
186
|
+
|
|
187
|
+
if (fs.existsSync(docsSourceDir)) {
|
|
188
|
+
const entries = fs.readdirSync(docsSourceDir, { withFileTypes: true });
|
|
189
|
+
for (const entry of entries) {
|
|
190
|
+
if (entry.name.startsWith('GETTING-STARTED') && entry.name.endsWith('.md')) {
|
|
191
|
+
const srcPath = path.join(docsSourceDir, entry.name);
|
|
192
|
+
const destPath = path.join(docsDestDir, entry.name);
|
|
193
|
+
|
|
194
|
+
if (!fs.existsSync(destPath)) {
|
|
195
|
+
fs.copyFileSync(srcPath, destPath);
|
|
196
|
+
copied++;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 复制 README*.md 到 workspace/
|
|
203
|
+
const entries = fs.readdirSync(packageRoot, { withFileTypes: true });
|
|
204
|
+
for (const entry of entries) {
|
|
205
|
+
if (entry.name.startsWith('README') && entry.name.endsWith('.md')) {
|
|
206
|
+
const srcPath = path.join(packageRoot, entry.name);
|
|
207
|
+
const destPath = path.join(workspaceDir, entry.name);
|
|
208
|
+
|
|
209
|
+
if (!fs.existsSync(destPath)) {
|
|
210
|
+
fs.copyFileSync(srcPath, destPath);
|
|
211
|
+
copied++;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { copied };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 迁移旧的 .speccrewrc 到 workspace 目录
|
|
220
|
+
function migrateOldSpeccrewRC(projectRoot, workspaceDir) {
|
|
221
|
+
const oldRcPath = path.join(projectRoot, '.speccrewrc');
|
|
222
|
+
const newRcPath = path.join(workspaceDir, '.speccrewrc');
|
|
223
|
+
|
|
224
|
+
if (fs.existsSync(oldRcPath) && !fs.existsSync(newRcPath)) {
|
|
225
|
+
fs.renameSync(oldRcPath, newRcPath);
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 统计 agents 和 skills 数量
|
|
232
|
+
function countAgentsAndSkills(sourceRoot) {
|
|
233
|
+
let agentCount = 0;
|
|
234
|
+
let skillCount = 0;
|
|
235
|
+
|
|
236
|
+
const agentsSourceDir = path.join(sourceRoot, 'agents');
|
|
237
|
+
const skillsSourceDir = path.join(sourceRoot, 'skills');
|
|
238
|
+
|
|
239
|
+
if (fs.existsSync(agentsSourceDir)) {
|
|
240
|
+
const entries = fs.readdirSync(agentsSourceDir, { withFileTypes: true });
|
|
241
|
+
agentCount = entries.filter(e => isSpeccrewFile(e.name)).length;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (fs.existsSync(skillsSourceDir)) {
|
|
245
|
+
const entries = fs.readdirSync(skillsSourceDir, { withFileTypes: true });
|
|
246
|
+
skillCount = entries.filter(e => isSpeccrewFile(e.name)).length;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return { agentCount, skillCount };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 询问用户确认
|
|
253
|
+
function askConfirm(message) {
|
|
254
|
+
return new Promise((resolve) => {
|
|
255
|
+
const rl = readline.createInterface({
|
|
256
|
+
input: process.stdin,
|
|
257
|
+
output: process.stdout,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
rl.question(message, (answer) => {
|
|
261
|
+
rl.close();
|
|
262
|
+
const normalized = answer.trim().toLowerCase();
|
|
263
|
+
resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 核心安装逻辑
|
|
269
|
+
async function runInit(options = {}) {
|
|
270
|
+
const {
|
|
271
|
+
projectRoot = process.cwd(),
|
|
272
|
+
ideArg = null,
|
|
273
|
+
silent = false,
|
|
274
|
+
skipConfirm = false,
|
|
275
|
+
} = options;
|
|
276
|
+
|
|
277
|
+
const log = silent ? () => {} : console.log;
|
|
278
|
+
|
|
148
279
|
try {
|
|
149
|
-
// 1.
|
|
150
|
-
|
|
280
|
+
// 1. 检查 Node.js 版本
|
|
281
|
+
checkNodeVersion();
|
|
282
|
+
|
|
283
|
+
// 2. 确定源文件路径
|
|
284
|
+
const sourceRoot = getSourceRoot();
|
|
285
|
+
const packageRoot = getPackageRoot();
|
|
151
286
|
|
|
152
|
-
//
|
|
153
|
-
|
|
287
|
+
// 3. 解析 IDE
|
|
288
|
+
if (!silent) printProgress(1, 5, 'Detecting IDE environment');
|
|
289
|
+
const ideConfigs = resolveIDE(projectRoot, ideArg);
|
|
290
|
+
if (!silent) printDone();
|
|
154
291
|
|
|
155
|
-
//
|
|
156
|
-
|
|
292
|
+
// 4. 统计 agents 和 skills 数量
|
|
293
|
+
const { agentCount, skillCount } = countAgentsAndSkills(sourceRoot);
|
|
157
294
|
|
|
158
|
-
//
|
|
159
|
-
const
|
|
295
|
+
// 5. 显示安装摘要并确认
|
|
296
|
+
const version = getPackageVersion();
|
|
297
|
+
const workspaceDir = path.join(projectRoot, 'speccrew-workspace');
|
|
160
298
|
|
|
161
|
-
|
|
162
|
-
|
|
299
|
+
if (!skipConfirm && !silent) {
|
|
300
|
+
log(`\nSpecCrew v${version}\n`);
|
|
301
|
+
log('Installation Summary:');
|
|
302
|
+
if (ideConfigs.length === 1) {
|
|
303
|
+
log(` IDE: ${ideConfigs[0].name} (${ideConfigs[0].baseDir}/)`);
|
|
304
|
+
} else {
|
|
305
|
+
log(` IDE: ${ideConfigs.map(c => c.name).join(', ')}`);
|
|
306
|
+
}
|
|
307
|
+
log(` Agents: ${agentCount} agents`);
|
|
308
|
+
log(` Skills: ${skillCount} skills`);
|
|
309
|
+
log(` Workspace: speccrew-workspace/\n`);
|
|
310
|
+
|
|
311
|
+
const confirmed = await askConfirm('Proceed with installation? (Y/n) ');
|
|
312
|
+
if (!confirmed) {
|
|
313
|
+
log('Installation cancelled.');
|
|
314
|
+
return { cancelled: true };
|
|
315
|
+
}
|
|
316
|
+
log('');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// 6. 迁移旧的 .speccrewrc(如果存在)
|
|
320
|
+
migrateOldSpeccrewRC(projectRoot, workspaceDir);
|
|
163
321
|
|
|
164
322
|
// 统计信息
|
|
165
323
|
const stats = {
|
|
@@ -167,17 +325,19 @@ function run() {
|
|
|
167
325
|
totalAgents: 0,
|
|
168
326
|
totalSkills: 0,
|
|
169
327
|
workspaceCreated: false,
|
|
328
|
+
docsInstalled: 0,
|
|
170
329
|
};
|
|
171
330
|
|
|
172
|
-
//
|
|
331
|
+
// 7. 复制 agents 和 skills
|
|
332
|
+
if (!silent) printProgress(2, 5, `Installing agents (${agentCount})`);
|
|
173
333
|
for (const ideConfig of ideConfigs) {
|
|
174
334
|
const agentsSourceDir = path.join(sourceRoot, 'agents');
|
|
175
335
|
const skillsSourceDir = path.join(sourceRoot, 'skills');
|
|
176
336
|
const agentsDestDir = path.join(projectRoot, ideConfig.agentsDir);
|
|
177
337
|
const skillsDestDir = path.join(projectRoot, ideConfig.skillsDir);
|
|
178
338
|
|
|
179
|
-
const agentsResult = copyAgents(agentsSourceDir, agentsDestDir);
|
|
180
|
-
const skillsResult = copySkills(skillsSourceDir, skillsDestDir);
|
|
339
|
+
const agentsResult = copyAgents(agentsSourceDir, agentsDestDir, ideConfig);
|
|
340
|
+
const skillsResult = copySkills(skillsSourceDir, skillsDestDir, ideConfig);
|
|
181
341
|
|
|
182
342
|
stats.ides.push({
|
|
183
343
|
name: ideConfig.name,
|
|
@@ -188,44 +348,81 @@ function run() {
|
|
|
188
348
|
stats.totalAgents += agentsResult.copied;
|
|
189
349
|
stats.totalSkills += skillsResult.copied;
|
|
190
350
|
}
|
|
351
|
+
if (!silent) printDone();
|
|
191
352
|
|
|
192
|
-
//
|
|
193
|
-
|
|
353
|
+
// 8. 复制 skills(显示进度)
|
|
354
|
+
if (!silent) printProgress(3, 5, `Installing skills (${skillCount})`);
|
|
355
|
+
// skills 已经在上面复制完成,这里只是显示进度
|
|
356
|
+
if (!silent) printDone();
|
|
357
|
+
|
|
358
|
+
// 9. 创建 speccrew-workspace 目录结构
|
|
359
|
+
if (!silent) printProgress(4, 5, 'Creating workspace structure');
|
|
194
360
|
createWorkspaceStructure(workspaceDir);
|
|
195
361
|
stats.workspaceCreated = true;
|
|
362
|
+
if (!silent) printDone();
|
|
196
363
|
|
|
197
|
-
//
|
|
364
|
+
// 10. 复制 workspace 模板
|
|
198
365
|
const templateDir = getWorkspaceTemplatePath();
|
|
199
366
|
copyWorkspaceTemplate(templateDir, workspaceDir);
|
|
200
367
|
|
|
201
|
-
//
|
|
202
|
-
|
|
368
|
+
// 11. 复制文档
|
|
369
|
+
if (!silent) printProgress(5, 5, 'Installing documentation');
|
|
370
|
+
const docsResult = copyDocs(packageRoot, workspaceDir);
|
|
371
|
+
stats.docsInstalled = docsResult.copied;
|
|
372
|
+
if (!silent) printDone();
|
|
373
|
+
|
|
374
|
+
// 12. 写入 .speccrewrc 到 workspace 目录
|
|
203
375
|
const rcConfig = {
|
|
204
376
|
ide: ideConfigs.length === 1 ? ideConfigs[0].id : ideConfigs.map(c => c.id),
|
|
205
377
|
version: version,
|
|
206
378
|
installedAt: new Date().toISOString(),
|
|
207
379
|
};
|
|
208
|
-
writeSpeccrewRC(
|
|
380
|
+
writeSpeccrewRC(workspaceDir, rcConfig);
|
|
209
381
|
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
382
|
+
// 13. 输出安装摘要
|
|
383
|
+
if (!silent) {
|
|
384
|
+
log(`\nSpecCrew v${version} installed successfully!\n`);
|
|
385
|
+
|
|
386
|
+
if (ideConfigs.length === 1) {
|
|
387
|
+
log(` IDE: ${ideConfigs[0].name} (${ideConfigs[0].baseDir}/)`);
|
|
388
|
+
} else {
|
|
389
|
+
log(` IDE: ${ideConfigs.map(c => c.name).join(', ')}`);
|
|
390
|
+
}
|
|
391
|
+
log(` Agents: ${stats.totalAgents} installed`);
|
|
392
|
+
log(` Skills: ${stats.totalSkills} installed`);
|
|
393
|
+
log(` Workspace: speccrew-workspace/ created`);
|
|
394
|
+
log(` Docs: ${stats.docsInstalled} files installed`);
|
|
395
|
+
|
|
396
|
+
log(`\nRun 'speccrew doctor' to verify your installation.`);
|
|
221
397
|
}
|
|
222
398
|
|
|
223
|
-
|
|
399
|
+
return {
|
|
400
|
+
success: true,
|
|
401
|
+
version,
|
|
402
|
+
stats,
|
|
403
|
+
ideConfigs,
|
|
404
|
+
};
|
|
224
405
|
|
|
225
406
|
} catch (error) {
|
|
226
|
-
|
|
227
|
-
|
|
407
|
+
if (!silent) {
|
|
408
|
+
console.error(`Error: ${error.message}`);
|
|
409
|
+
}
|
|
410
|
+
throw error;
|
|
228
411
|
}
|
|
229
412
|
}
|
|
230
413
|
|
|
231
|
-
|
|
414
|
+
// CLI 入口
|
|
415
|
+
function run() {
|
|
416
|
+
const { ide: cliIdeArg } = parseArgs();
|
|
417
|
+
|
|
418
|
+
runInit({
|
|
419
|
+
projectRoot: process.cwd(),
|
|
420
|
+
ideArg: cliIdeArg,
|
|
421
|
+
silent: false,
|
|
422
|
+
skipConfirm: false,
|
|
423
|
+
}).catch(() => {
|
|
424
|
+
process.exit(1);
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
module.exports = { runInit, run };
|
|
@@ -1,32 +1,141 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const readline = require('readline');
|
|
3
4
|
const { readSpeccrewRC, isSpeccrewFile, removeDirRecursive } = require('../utils');
|
|
4
5
|
|
|
5
6
|
function run(projectRoot, args) {
|
|
6
|
-
//
|
|
7
|
-
|
|
7
|
+
// 检查是否已初始化(兼容新旧两个路径)
|
|
8
|
+
let rc = readSpeccrewRC(projectRoot);
|
|
9
|
+
const newRcPath = path.join(projectRoot, 'speccrew-workspace', '.speccrewrc');
|
|
10
|
+
|
|
11
|
+
// 如果旧路径没有配置文件,检查新路径
|
|
12
|
+
if (!rc && fs.existsSync(newRcPath)) {
|
|
13
|
+
try {
|
|
14
|
+
rc = JSON.parse(fs.readFileSync(newRcPath, 'utf8'));
|
|
15
|
+
} catch (e) {
|
|
16
|
+
// 忽略解析错误
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
if (!rc) {
|
|
9
21
|
console.log('SpecCrew is not initialized in this project.');
|
|
10
22
|
console.log('Run "speccrew init" to initialize.');
|
|
11
23
|
return false;
|
|
12
24
|
}
|
|
13
25
|
|
|
14
|
-
console.log('SpecCrew Uninstall\n');
|
|
15
|
-
|
|
16
26
|
const isAll = args.includes('--all');
|
|
17
|
-
let removedAgents = 0;
|
|
18
|
-
let removedSkills = 0;
|
|
19
|
-
const removedItems = [];
|
|
20
27
|
|
|
21
28
|
// 获取 IDE 配置
|
|
22
29
|
const ides = rc.ide ? (Array.isArray(rc.ide) ? rc.ide : [rc.ide]) : [];
|
|
23
30
|
|
|
24
|
-
//
|
|
31
|
+
// 扫描将要删除的内容(不实际删除)
|
|
32
|
+
let totalAgents = 0;
|
|
33
|
+
let totalSkills = 0;
|
|
34
|
+
const idePaths = [];
|
|
35
|
+
|
|
25
36
|
for (const ideId of ides) {
|
|
26
37
|
const ideConfig = getIDEConfig(ideId);
|
|
27
38
|
if (!ideConfig) continue;
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
let agentCount = 0;
|
|
41
|
+
let skillCount = 0;
|
|
42
|
+
|
|
43
|
+
// 统计 agents
|
|
44
|
+
const agentsDir = path.join(projectRoot, ideConfig.agentsDir);
|
|
45
|
+
if (fs.existsSync(agentsDir)) {
|
|
46
|
+
const entries = fs.readdirSync(agentsDir, { withFileTypes: true });
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
if (isSpeccrewFile(entry.name)) {
|
|
49
|
+
agentCount++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 统计 skills
|
|
55
|
+
const skillsDir = path.join(projectRoot, ideConfig.skillsDir);
|
|
56
|
+
if (fs.existsSync(skillsDir)) {
|
|
57
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (isSpeccrewFile(entry.name)) {
|
|
60
|
+
skillCount++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
totalAgents += agentCount;
|
|
66
|
+
totalSkills += skillCount;
|
|
67
|
+
idePaths.push({ ideConfig, agentCount, skillCount });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 检查配置文件是否存在(新路径和旧路径)
|
|
71
|
+
const oldRcPath = path.join(projectRoot, '.speccrewrc');
|
|
72
|
+
const hasNewRc = fs.existsSync(newRcPath);
|
|
73
|
+
const hasOldRc = fs.existsSync(oldRcPath);
|
|
74
|
+
const hasRcFile = hasNewRc || hasOldRc;
|
|
75
|
+
|
|
76
|
+
// 检查 workspace 是否存在
|
|
77
|
+
const workspaceDir = path.join(projectRoot, 'speccrew-workspace');
|
|
78
|
+
const hasWorkspace = fs.existsSync(workspaceDir);
|
|
79
|
+
|
|
80
|
+
// 显示将要删除的内容
|
|
81
|
+
console.log('SpecCrew Uninstall\n');
|
|
82
|
+
console.log('The following will be removed:');
|
|
83
|
+
|
|
84
|
+
for (const { ideConfig, agentCount, skillCount } of idePaths) {
|
|
85
|
+
if (agentCount > 0) {
|
|
86
|
+
console.log(` - ${agentCount} agent(s) from ${ideConfig.baseDir}/agents/`);
|
|
87
|
+
}
|
|
88
|
+
if (skillCount > 0) {
|
|
89
|
+
console.log(` - ${skillCount} skill(s) from ${ideConfig.baseDir}/skills/`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (hasRcFile) {
|
|
94
|
+
console.log(' - SpecCrew configuration (speccrew-workspace/.speccrewrc)');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (isAll && hasWorkspace) {
|
|
98
|
+
console.log(' - speccrew-workspace/ (including all iterations, docs and knowledge base)');
|
|
99
|
+
console.log('\nWARNING: This will permanently delete your workspace data!');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 询问用户确认
|
|
103
|
+
const rl = readline.createInterface({
|
|
104
|
+
input: process.stdin,
|
|
105
|
+
output: process.stdout
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return new Promise((resolve) => {
|
|
109
|
+
rl.question('\nProceed with uninstall? (Y/n) ', (answer) => {
|
|
110
|
+
rl.close();
|
|
111
|
+
|
|
112
|
+
const confirmed = answer.toLowerCase() === 'y' || answer === '' || answer.toLowerCase() === 'yes';
|
|
113
|
+
|
|
114
|
+
if (!confirmed) {
|
|
115
|
+
console.log('Uninstall cancelled.');
|
|
116
|
+
resolve(false);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 执行卸载
|
|
121
|
+
const result = performUninstall(projectRoot, idePaths, isAll, hasOldRc);
|
|
122
|
+
resolve(result);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function performUninstall(projectRoot, idePaths, isAll, hasOldRc) {
|
|
128
|
+
let removedAgents = 0;
|
|
129
|
+
let removedSkills = 0;
|
|
130
|
+
|
|
131
|
+
// 计算总步骤数
|
|
132
|
+
const totalSteps = isAll ? 4 : 3;
|
|
133
|
+
let currentStep = 0;
|
|
134
|
+
|
|
135
|
+
// 删除 agents
|
|
136
|
+
currentStep++;
|
|
137
|
+
process.stdout.write(`[${currentStep}/${totalSteps}] Removing agents... `);
|
|
138
|
+
for (const { ideConfig } of idePaths) {
|
|
30
139
|
const agentsDir = path.join(projectRoot, ideConfig.agentsDir);
|
|
31
140
|
if (fs.existsSync(agentsDir)) {
|
|
32
141
|
const entries = fs.readdirSync(agentsDir, { withFileTypes: true });
|
|
@@ -39,12 +148,16 @@ function run(projectRoot, args) {
|
|
|
39
148
|
fs.unlinkSync(fullPath);
|
|
40
149
|
}
|
|
41
150
|
removedAgents++;
|
|
42
|
-
removedItems.push(`${ideConfig.baseDir}/agents/${entry.name}`);
|
|
43
151
|
}
|
|
44
152
|
}
|
|
45
153
|
}
|
|
154
|
+
}
|
|
155
|
+
console.log(`done (${removedAgents} removed)`);
|
|
46
156
|
|
|
47
|
-
|
|
157
|
+
// 删除 skills
|
|
158
|
+
currentStep++;
|
|
159
|
+
process.stdout.write(`[${currentStep}/${totalSteps}] Removing skills... `);
|
|
160
|
+
for (const { ideConfig } of idePaths) {
|
|
48
161
|
const skillsDir = path.join(projectRoot, ideConfig.skillsDir);
|
|
49
162
|
if (fs.existsSync(skillsDir)) {
|
|
50
163
|
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
@@ -57,39 +170,44 @@ function run(projectRoot, args) {
|
|
|
57
170
|
fs.unlinkSync(fullPath);
|
|
58
171
|
}
|
|
59
172
|
removedSkills++;
|
|
60
|
-
removedItems.push(`${ideConfig.baseDir}/skills/${entry.name}`);
|
|
61
173
|
}
|
|
62
174
|
}
|
|
63
175
|
}
|
|
64
176
|
}
|
|
177
|
+
console.log(`done (${removedSkills} removed)`);
|
|
178
|
+
|
|
179
|
+
// 删除配置文件
|
|
180
|
+
currentStep++;
|
|
181
|
+
process.stdout.write(`[${currentStep}/${totalSteps}] Removing configuration... `);
|
|
182
|
+
|
|
183
|
+
// 删除新路径的配置文件
|
|
184
|
+
const newRcPath = path.join(projectRoot, 'speccrew-workspace', '.speccrewrc');
|
|
185
|
+
if (fs.existsSync(newRcPath)) {
|
|
186
|
+
fs.unlinkSync(newRcPath);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 兼容处理:删除旧路径的配置文件
|
|
190
|
+
const oldRcPath = path.join(projectRoot, '.speccrewrc');
|
|
191
|
+
if (fs.existsSync(oldRcPath)) {
|
|
192
|
+
fs.unlinkSync(oldRcPath);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log('done');
|
|
65
196
|
|
|
66
197
|
// 如果 --all,删除 workspace
|
|
67
|
-
let workspaceRemoved = false;
|
|
68
198
|
if (isAll) {
|
|
199
|
+
currentStep++;
|
|
200
|
+
process.stdout.write(`[${currentStep}/${totalSteps}] Removing workspace... `);
|
|
69
201
|
const workspaceDir = path.join(projectRoot, 'speccrew-workspace');
|
|
70
202
|
if (fs.existsSync(workspaceDir)) {
|
|
71
203
|
removeDirRecursive(workspaceDir);
|
|
72
|
-
workspaceRemoved = true;
|
|
73
|
-
removedItems.push('speccrew-workspace/');
|
|
74
204
|
}
|
|
75
|
-
|
|
205
|
+
console.log('done');
|
|
76
206
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
removedItems.push('.speccrewrc');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 输出摘要
|
|
85
|
-
console.log(`Removed ${removedAgents} agent(s) and ${removedSkills} skill(s)`);
|
|
86
|
-
if (workspaceRemoved) {
|
|
87
|
-
console.log('Removed speccrew-workspace/');
|
|
88
|
-
}
|
|
89
|
-
console.log('Removed .speccrewrc');
|
|
90
|
-
console.log('\nSpecCrew has been uninstalled.');
|
|
91
|
-
if (!isAll) {
|
|
92
|
-
console.log('Workspace data preserved. Use --all to remove everything.');
|
|
207
|
+
console.log('\nSpecCrew has been completely uninstalled.');
|
|
208
|
+
} else {
|
|
209
|
+
console.log('\nSpecCrew has been uninstalled.');
|
|
210
|
+
console.log('Workspace data preserved in speccrew-workspace/');
|
|
93
211
|
}
|
|
94
212
|
|
|
95
213
|
return true;
|