adspecs 0.1.29 → 0.1.31
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.qoder-plugin/plugin.json +1 -1
- package/.workbuddy-plugin/plugin.json +24 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +14 -8
- package/INSTALL.md +57 -4
- package/README.md +5 -4
- package/bin/adspecs.js +9 -8
- package/hooks/hooks.json +2 -2
- package/hooks/platform.js +22 -11
- package/package.json +4 -2
- package/scripts/postinstall.js +95 -36
- package/scripts/sync-version.js +11 -1
- package/skills/adspecs-adversarial-review/SKILL.md +1 -0
- package/skills/adspecs-analyze/SKILL.md +1 -0
- package/skills/adspecs-architecture-planning/SKILL.md +1 -0
- package/skills/adspecs-biz-blueprint/SKILL.md +555 -554
- package/skills/adspecs-clarify/SKILL.md +1 -0
- package/skills/adspecs-constitution/SKILL.md +1 -0
- package/skills/adspecs-export-word/SKILL.md +499 -498
- package/skills/adspecs-front-prototype/SKILL.md +1 -0
- package/skills/adspecs-front-spec/SKILL.md +1 -0
- package/skills/adspecs-front-tasks/SKILL.md +1 -0
- package/skills/adspecs-git-commit/SKILL.md +1 -0
- package/skills/adspecs-git-initialize/SKILL.md +1 -0
- package/skills/adspecs-new-requirement/SKILL.md +1 -0
- package/skills/adspecs-plan/SKILL.md +1 -0
- package/skills/adspecs-prd/SKILL.md +1 -0
- package/skills/adspecs-prd-to-demo/SKILL.md +1 -0
- package/skills/adspecs-save-session/SKILL.md +179 -178
- package/skills/adspecs-tasks/SKILL.md +1 -0
- package/skills/adspecs-update-status/SKILL.md +1 -0
- package/skills/adspecs-utest/SKILL.md +1 -0
- package/skills/adspecs-write-planning-report/SKILL.md +1 -0
- package/skills/adspecs-write-roadmap/SKILL.md +1 -0
- package/skills/adspecs-write-sow/SKILL.md +1 -0
- package/skills/grill-me/SKILL.md +1 -0
- package/skills/playwright-cli/SKILL.md +1 -0
- package/skills/playwright-trace/SKILL.md +1 -0
- package/skills/project-init/SKILL.md +1 -0
- package/skills/sie-front-code-review/SKILL.md +1 -0
- package/skills/wiki-update/SKILL.md +233 -232
- package/src/commands/plugin.js +170 -24
- package/src/commands/update.js +5 -1
- package/src/utils.js +116 -0
package/src/commands/plugin.js
CHANGED
|
@@ -30,7 +30,14 @@ function checkClaudeCli() {
|
|
|
30
30
|
* @param {string} platform - 'claude' | 'qoder'
|
|
31
31
|
*/
|
|
32
32
|
function getPluginName(platform) {
|
|
33
|
-
var manifestDir
|
|
33
|
+
var manifestDir;
|
|
34
|
+
if (platform === 'qoder') {
|
|
35
|
+
manifestDir = '.qoder-plugin';
|
|
36
|
+
} else if (platform === 'workbuddy') {
|
|
37
|
+
manifestDir = '.workbuddy-plugin';
|
|
38
|
+
} else {
|
|
39
|
+
manifestDir = '.claude-plugin';
|
|
40
|
+
}
|
|
34
41
|
const pluginJsonPath = path.join(PLUGIN_DIR, manifestDir, 'plugin.json');
|
|
35
42
|
if (!fs.existsSync(pluginJsonPath)) {
|
|
36
43
|
// 回退到 .claude-plugin
|
|
@@ -47,18 +54,22 @@ function getPluginName(platform) {
|
|
|
47
54
|
|
|
48
55
|
/**
|
|
49
56
|
* 检测当前可用的 AI 平台
|
|
50
|
-
* @returns {'claude'|'qoder'}
|
|
57
|
+
* @returns {'claude'|'qoder'|'workbuddy'}
|
|
51
58
|
*/
|
|
52
59
|
function detectInstalledPlatform() {
|
|
53
60
|
// 显式环境变量优先
|
|
54
61
|
var explicit = (process.env.ADSPECS_PLATFORM || '').toLowerCase();
|
|
55
|
-
if (
|
|
62
|
+
if (['qoder', 'claude', 'workbuddy'].includes(explicit)) {
|
|
56
63
|
return explicit;
|
|
57
64
|
}
|
|
58
65
|
// 检测 claude CLI
|
|
59
66
|
if (checkClaudeCli()) {
|
|
60
67
|
return 'claude';
|
|
61
68
|
}
|
|
69
|
+
// WorkBuddy 运行环境检测(HOME 下有 ~/.workbuddy)
|
|
70
|
+
if (fs.existsSync(path.join(os.homedir(), '.workbuddy'))) {
|
|
71
|
+
return 'workbuddy';
|
|
72
|
+
}
|
|
62
73
|
// 默认回退 qoder(当前运行环境)
|
|
63
74
|
return 'qoder';
|
|
64
75
|
}
|
|
@@ -68,7 +79,7 @@ function detectInstalledPlatform() {
|
|
|
68
79
|
*
|
|
69
80
|
* @param {object} options
|
|
70
81
|
* @param {string} options.scope - 安装范围: user|project
|
|
71
|
-
* @param {string} options.platform - 目标平台: claude|qoder(可选,自动检测)
|
|
82
|
+
* @param {string} options.platform - 目标平台: claude|qoder|workbuddy(可选,自动检测)
|
|
72
83
|
*/
|
|
73
84
|
exports.install = async function install(options) {
|
|
74
85
|
const scope = options.scope || 'user';
|
|
@@ -83,13 +94,80 @@ exports.install = async function install(options) {
|
|
|
83
94
|
console.log(` 目标平台: ${platform}`);
|
|
84
95
|
console.log('');
|
|
85
96
|
|
|
86
|
-
if (platform === '
|
|
97
|
+
if (platform === 'workbuddy') {
|
|
98
|
+
await installForWorkBuddy(pluginName, scope);
|
|
99
|
+
} else if (platform === 'qoder') {
|
|
87
100
|
await installForQoder(pluginName, scope);
|
|
88
101
|
} else {
|
|
89
102
|
await installForClaude(pluginName, scope);
|
|
90
103
|
}
|
|
91
104
|
};
|
|
92
105
|
|
|
106
|
+
/**
|
|
107
|
+
* 检查 codebuddy CLI 是否可用(WorkBuddy 共享 CodeBuddy CLI)
|
|
108
|
+
*/
|
|
109
|
+
function checkCodebuddyCli() {
|
|
110
|
+
try {
|
|
111
|
+
execSync('codebuddy --version', { stdio: 'ignore' });
|
|
112
|
+
return true;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* WorkBuddy 平台安装
|
|
120
|
+
* 通过 CodeBuddy 插件系统安装,自动处理 skills/hooks 加载和 enabledPlugins
|
|
121
|
+
* 若 CLI 不可用则回退为手工配置 enabledPlugins
|
|
122
|
+
*/
|
|
123
|
+
async function installForWorkBuddy(pluginName, scope) {
|
|
124
|
+
const workbuddyManifest = path.join(PLUGIN_DIR, '.workbuddy-plugin', 'plugin.json');
|
|
125
|
+
if (!fs.existsSync(workbuddyManifest)) {
|
|
126
|
+
throw new Error(`未找到 WorkBuddy 插件清单: ${workbuddyManifest}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (checkCodebuddyCli()) {
|
|
130
|
+
// 方式 A:通过 CodeBuddy CLI 安装(自动处理一切)
|
|
131
|
+
const scopeFlag = scope === 'project' ? '--scope project' : '--scope user';
|
|
132
|
+
console.log(chalk.gray(' 通过 CodeBuddy CLI 安装 adspecs 插件...'));
|
|
133
|
+
try {
|
|
134
|
+
execSync(`codebuddy plugin install "${PLUGIN_DIR}" ${scopeFlag}`, {
|
|
135
|
+
encoding: 'utf8',
|
|
136
|
+
stdio: 'pipe'
|
|
137
|
+
});
|
|
138
|
+
console.log(chalk.green(' ✅ adspecs 插件安装成功'));
|
|
139
|
+
console.log(chalk.gray(' 请重启 WorkBuddy 使 skills 与 hooks 生效。'));
|
|
140
|
+
} catch (err) {
|
|
141
|
+
const stderr = err.stderr ? err.stderr.toString() : '';
|
|
142
|
+
const combined = stderr + (err.stdout ? err.stdout.toString() : '');
|
|
143
|
+
if (combined.includes('already exists') || combined.includes('already installed')) {
|
|
144
|
+
console.log(chalk.yellow(' ⏭ 插件已安装,跳过'));
|
|
145
|
+
} else {
|
|
146
|
+
throw new Error(`WorkBuddy 插件安装失败: ${combined.trim() || err.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
// 方式 B:回退为手工配置(无 CLI 时)
|
|
151
|
+
// 完整注册:创建 marketplace 目录 → junction 链接 → known_marketplaces.json → enabledPlugins
|
|
152
|
+
console.log(chalk.yellow(' ⚠ 未检测到 codebuddy CLI,使用手工配置方式'));
|
|
153
|
+
console.log('');
|
|
154
|
+
|
|
155
|
+
const { registerWorkBuddyPlugin } = require('../utils');
|
|
156
|
+
const result = registerWorkBuddyPlugin(PLUGIN_DIR, pluginName);
|
|
157
|
+
|
|
158
|
+
if (result.success) {
|
|
159
|
+
console.log(chalk.green(' ✅ 已完成 WorkBuddy 插件注册'));
|
|
160
|
+
console.log(chalk.gray(' 📁 插件目录:'));
|
|
161
|
+
console.log(chalk.gray(` ${PLUGIN_DIR}`));
|
|
162
|
+
console.log(chalk.gray(' 📁 marketplace:'));
|
|
163
|
+
console.log(chalk.gray(` ~/.workbuddy/plugins/marketplaces/local/`));
|
|
164
|
+
} else {
|
|
165
|
+
throw new Error(`WorkBuddy 插件注册失败: ${result.reason}`);
|
|
166
|
+
}
|
|
167
|
+
console.log(chalk.gray(' 请重启 WorkBuddy 使 skills 与 hooks 生效。'));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
93
171
|
/**
|
|
94
172
|
* Qoder 平台安装
|
|
95
173
|
* 自动注册到 Qoder 插件系统:
|
|
@@ -256,23 +334,7 @@ async function installQoderProject(pluginName, qoderManifest) {
|
|
|
256
334
|
}
|
|
257
335
|
}
|
|
258
336
|
|
|
259
|
-
|
|
260
|
-
* 递归复制目录(排除指定目录名)
|
|
261
|
-
*/
|
|
262
|
-
function copyDirRecursive(src, dest, excludeDirs) {
|
|
263
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
264
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
265
|
-
for (const entry of entries) {
|
|
266
|
-
if (excludeDirs && excludeDirs.includes(entry.name)) continue;
|
|
267
|
-
const srcPath = path.join(src, entry.name);
|
|
268
|
-
const destPath = path.join(dest, entry.name);
|
|
269
|
-
if (entry.isDirectory()) {
|
|
270
|
-
copyDirRecursive(srcPath, destPath, excludeDirs);
|
|
271
|
-
} else {
|
|
272
|
-
fs.copyFileSync(srcPath, destPath);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
337
|
+
const { copyDirRecursive } = require('../utils');
|
|
276
338
|
|
|
277
339
|
/**
|
|
278
340
|
* Qoder 用户级卸载 — 移除链接 + 注册表条目 + 启用配置
|
|
@@ -420,12 +482,94 @@ function printSkillList() {
|
|
|
420
482
|
console.log('');
|
|
421
483
|
}
|
|
422
484
|
|
|
485
|
+
/**
|
|
486
|
+
* WorkBuddy 平台卸载
|
|
487
|
+
* 通过 CodeBuddy CLI 卸载,或手工清除 settings.json 中的 enabledPlugins 条目
|
|
488
|
+
*/
|
|
489
|
+
async function uninstallWorkBuddy(pluginName, scope) {
|
|
490
|
+
if (checkCodebuddyCli()) {
|
|
491
|
+
const scopeFlag = scope === 'project' ? '--scope project' : '--scope user';
|
|
492
|
+
try {
|
|
493
|
+
execSync(`codebuddy plugin uninstall ${pluginName} ${scopeFlag}`, {
|
|
494
|
+
stdio: 'pipe',
|
|
495
|
+
});
|
|
496
|
+
console.log(chalk.green(` ✅ ${pluginName} 插件已卸载`));
|
|
497
|
+
} catch (err) {
|
|
498
|
+
const stderr = err.stderr ? err.stderr.toString() : '';
|
|
499
|
+
if (stderr.includes('not installed') || stderr.includes('not found')) {
|
|
500
|
+
console.log(chalk.yellow(` ⏭ ${pluginName} 未安装,无需卸载`));
|
|
501
|
+
} else {
|
|
502
|
+
throw new Error(`WorkBuddy 插件卸载失败: ${stderr || err.message}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
// 回退:手工清除 marketplace 注册 + junction 链接 + enabledPlugins 条目
|
|
507
|
+
const workbuddyHome = path.join(os.homedir(), '.workbuddy');
|
|
508
|
+
const localMarketplaceDir = path.join(workbuddyHome, 'plugins', 'marketplaces', 'local');
|
|
509
|
+
const pluginLinkPath = path.join(localMarketplaceDir, pluginName);
|
|
510
|
+
const settingsPath = path.join(workbuddyHome, 'settings.json');
|
|
511
|
+
const knownMarketplacesPath = path.join(workbuddyHome, 'plugins', 'known_marketplaces.json');
|
|
512
|
+
|
|
513
|
+
let cleaned = false;
|
|
514
|
+
|
|
515
|
+
// 1. 移除 junction / 目录
|
|
516
|
+
if (fs.existsSync(pluginLinkPath)) {
|
|
517
|
+
try {
|
|
518
|
+
fs.rmSync(pluginLinkPath, { recursive: true, force: true });
|
|
519
|
+
console.log(chalk.green(' ✅ 已移除插件链接'));
|
|
520
|
+
cleaned = true;
|
|
521
|
+
} catch (err) {
|
|
522
|
+
console.log(chalk.yellow(` ⚠ 移除插件链接失败: ${err.message}`));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// 2. 从 settings.json 移除
|
|
527
|
+
if (fs.existsSync(settingsPath)) {
|
|
528
|
+
let settings = {};
|
|
529
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_e) { /* use default */ }
|
|
530
|
+
if (settings.enabledPlugins) {
|
|
531
|
+
const keysBefore = Object.keys(settings.enabledPlugins).length;
|
|
532
|
+
for (var key in settings.enabledPlugins) {
|
|
533
|
+
if (key.startsWith(pluginName + '@')) {
|
|
534
|
+
delete settings.enabledPlugins[key];
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const keysAfter = Object.keys(settings.enabledPlugins).length;
|
|
538
|
+
if (keysBefore > keysAfter) {
|
|
539
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
540
|
+
console.log(chalk.green(' ✅ 已从 settings.json 移除插件配置'));
|
|
541
|
+
cleaned = true;
|
|
542
|
+
} else {
|
|
543
|
+
console.log(chalk.yellow(' ⏭ 未在 settings.json 中找到插件配置'));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 3. 从 known_marketplaces.json 移除 local marketplace
|
|
549
|
+
if (fs.existsSync(knownMarketplacesPath)) {
|
|
550
|
+
let knownMarketplaces = {};
|
|
551
|
+
try { knownMarketplaces = JSON.parse(fs.readFileSync(knownMarketplacesPath, 'utf8')); } catch (_e) { /* use default */ }
|
|
552
|
+
if (knownMarketplaces.local) {
|
|
553
|
+
delete knownMarketplaces.local;
|
|
554
|
+
fs.writeFileSync(knownMarketplacesPath, JSON.stringify(knownMarketplaces, null, 4) + '\n', 'utf8');
|
|
555
|
+
console.log(chalk.green(' ✅ 已从 known_marketplaces.json 移除 local marketplace'));
|
|
556
|
+
cleaned = true;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (!cleaned) {
|
|
561
|
+
console.log(chalk.yellow(' ⏭ 未找到需要清理的插件配置'));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
console.log(chalk.gray(' 请重启 WorkBuddy 使变更生效。'));
|
|
565
|
+
}
|
|
566
|
+
|
|
423
567
|
/**
|
|
424
568
|
* 卸载插件
|
|
425
569
|
*
|
|
426
570
|
* @param {object} options
|
|
427
571
|
* @param {string} options.scope - 卸载范围: user|project
|
|
428
|
-
* @param {string} options.platform - 目标平台: claude|qoder(可选,自动检测)
|
|
572
|
+
* @param {string} options.platform - 目标平台: claude|qoder|workbuddy(可选,自动检测)
|
|
429
573
|
*/
|
|
430
574
|
exports.uninstall = async function uninstall(options) {
|
|
431
575
|
const scope = options.scope || 'user';
|
|
@@ -437,7 +581,9 @@ exports.uninstall = async function uninstall(options) {
|
|
|
437
581
|
console.log(` 目标平台: ${platform}`);
|
|
438
582
|
console.log('');
|
|
439
583
|
|
|
440
|
-
if (platform === '
|
|
584
|
+
if (platform === 'workbuddy') {
|
|
585
|
+
await uninstallWorkBuddy(pluginName, scope);
|
|
586
|
+
} else if (platform === 'qoder') {
|
|
441
587
|
if (scope === 'project') {
|
|
442
588
|
// Qoder 项目级卸载:移除项目链接
|
|
443
589
|
const targetDir = path.join(process.cwd(), '.qoder-plugin');
|
package/src/commands/update.js
CHANGED
|
@@ -81,7 +81,11 @@ module.exports = async function update(options) {
|
|
|
81
81
|
console.log(chalk.green.bold(`✅ 升级完成: ${oldVersion} → ${newVersion}`));
|
|
82
82
|
}
|
|
83
83
|
console.log('');
|
|
84
|
-
console.log('
|
|
84
|
+
console.log(' 🔄 插件注册刷新:');
|
|
85
|
+
console.log(' Claude Code — postinstall 钩子已自动刷新命令行插件注册');
|
|
86
|
+
console.log(' Qoder / CodeBuddy / WorkBuddy — postinstall 钩子已自动刷新插件注册');
|
|
87
|
+
console.log(' 如未自动刷新,请执行 adspecs plugin install --platform <platform>');
|
|
88
|
+
console.log('');
|
|
85
89
|
console.log(` 验证: ${chalk.gray('adspecs --version')}`);
|
|
86
90
|
console.log('');
|
|
87
91
|
};
|
package/src/utils.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* 获取插件根目录(即 adspecs/ 目录)
|
|
@@ -70,6 +71,119 @@ function safeWriteJson(filePath, data) {
|
|
|
70
71
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
/**
|
|
75
|
+
* 递归复制目录(排除指定目录名)
|
|
76
|
+
* @param {string} src
|
|
77
|
+
* @param {string} dest
|
|
78
|
+
* @param {string[]} excludeDirs
|
|
79
|
+
*/
|
|
80
|
+
function copyDirRecursive(src, dest, excludeDirs) {
|
|
81
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
82
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
if (excludeDirs && excludeDirs.includes(entry.name)) continue;
|
|
85
|
+
const srcPath = path.join(src, entry.name);
|
|
86
|
+
const destPath = path.join(dest, entry.name);
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
copyDirRecursive(srcPath, destPath, excludeDirs);
|
|
89
|
+
} else {
|
|
90
|
+
fs.copyFileSync(srcPath, destPath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 注册 WorkBuddy 本地插件 marketplace
|
|
97
|
+
*
|
|
98
|
+
* 手工方式完成完整注册,无需 codebuddy CLI:
|
|
99
|
+
* 1. 创建 ~/.workbuddy/plugins/marketplaces/local/ 目录
|
|
100
|
+
* 2. 创建 junction 链接(或回退复制)指向插件目录
|
|
101
|
+
* 3. 在 known_marketplaces.json 中注册 local marketplace
|
|
102
|
+
* 4. 在 settings.json 的 enabledPlugins 中启用插件
|
|
103
|
+
*
|
|
104
|
+
* @param {string} pluginDir - adspecs 插件根目录
|
|
105
|
+
* @param {string} [pluginName='adspecs'] - 插件名称
|
|
106
|
+
* @returns {{success: boolean, reason?: string}}
|
|
107
|
+
*/
|
|
108
|
+
function registerWorkBuddyPlugin(pluginDir, pluginName) {
|
|
109
|
+
pluginName = pluginName || 'adspecs';
|
|
110
|
+
|
|
111
|
+
const workbuddyHome = path.join(os.homedir(), '.workbuddy');
|
|
112
|
+
|
|
113
|
+
// 检查 WorkBuddy 配置目录是否存在
|
|
114
|
+
if (!fs.existsSync(workbuddyHome)) {
|
|
115
|
+
return { success: false, reason: '未检测到 WorkBuddy 配置目录 (~/.workbuddy)' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const localMarketplaceDir = path.join(workbuddyHome, 'plugins', 'marketplaces', 'local');
|
|
119
|
+
const pluginLinkPath = path.join(localMarketplaceDir, pluginName);
|
|
120
|
+
const settingsPath = path.join(workbuddyHome, 'settings.json');
|
|
121
|
+
const knownMarketplacesPath = path.join(workbuddyHome, 'plugins', 'known_marketplaces.json');
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// 1. 创建 local marketplace 目录
|
|
125
|
+
fs.mkdirSync(localMarketplaceDir, { recursive: true });
|
|
126
|
+
|
|
127
|
+
// 2. 创建 junction / symlink(已存在则先移除重建)
|
|
128
|
+
if (fs.existsSync(pluginLinkPath)) {
|
|
129
|
+
try { fs.rmSync(pluginLinkPath, { recursive: true, force: true }); } catch (_err) {
|
|
130
|
+
try { fs.unlinkSync(pluginLinkPath); } catch (_e) { /* ignore */ }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
fs.symlinkSync(pluginDir, pluginLinkPath, 'junction');
|
|
135
|
+
} catch (_symlinkErr) {
|
|
136
|
+
// junction 创建失败 → 回退为复制关键目录
|
|
137
|
+
fs.mkdirSync(pluginLinkPath, { recursive: true });
|
|
138
|
+
const entriesToCopy = ['.workbuddy-plugin', 'skills', 'hooks', 'references', 'commands'];
|
|
139
|
+
for (const entry of entriesToCopy) {
|
|
140
|
+
const srcPath = path.join(pluginDir, entry);
|
|
141
|
+
if (!fs.existsSync(srcPath)) continue;
|
|
142
|
+
const destPath = path.join(pluginLinkPath, entry);
|
|
143
|
+
if (fs.lstatSync(srcPath).isDirectory()) {
|
|
144
|
+
copyDirRecursive(srcPath, destPath, ['node_modules']);
|
|
145
|
+
} else {
|
|
146
|
+
fs.copyFileSync(srcPath, destPath);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// 同步复制根目录关键文件
|
|
150
|
+
for (const file of ['package.json', 'CLAUDE.md', 'README.md']) {
|
|
151
|
+
const srcFile = path.join(pluginDir, file);
|
|
152
|
+
if (fs.existsSync(srcFile)) {
|
|
153
|
+
fs.copyFileSync(srcFile, path.join(pluginLinkPath, file));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 3. 注册 local marketplace
|
|
159
|
+
let knownMarketplaces = safeReadJson(knownMarketplacesPath, {});
|
|
160
|
+
const now = new Date().toISOString();
|
|
161
|
+
|
|
162
|
+
knownMarketplaces.local = {
|
|
163
|
+
type: 'directory',
|
|
164
|
+
source: {
|
|
165
|
+
source: 'directory',
|
|
166
|
+
path: localMarketplaceDir
|
|
167
|
+
},
|
|
168
|
+
installLocation: localMarketplaceDir,
|
|
169
|
+
autoUpdate: false,
|
|
170
|
+
description: 'Local plugins',
|
|
171
|
+
lastUpdated: now
|
|
172
|
+
};
|
|
173
|
+
safeWriteJson(knownMarketplacesPath, knownMarketplaces);
|
|
174
|
+
|
|
175
|
+
// 4. 启用插件
|
|
176
|
+
let settings = safeReadJson(settingsPath, {});
|
|
177
|
+
if (!settings.enabledPlugins) settings.enabledPlugins = {};
|
|
178
|
+
settings.enabledPlugins[`${pluginName}@local`] = true;
|
|
179
|
+
safeWriteJson(settingsPath, settings);
|
|
180
|
+
|
|
181
|
+
return { success: true };
|
|
182
|
+
} catch (err) {
|
|
183
|
+
return { success: false, reason: err.message };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
73
187
|
module.exports = {
|
|
74
188
|
getPluginDir,
|
|
75
189
|
getPkg,
|
|
@@ -77,4 +191,6 @@ module.exports = {
|
|
|
77
191
|
isInsidePlugin,
|
|
78
192
|
safeReadJson,
|
|
79
193
|
safeWriteJson,
|
|
194
|
+
copyDirRecursive,
|
|
195
|
+
registerWorkBuddyPlugin,
|
|
80
196
|
};
|