prizmkit 1.0.17 → 1.0.19
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/bundled/VERSION.json +3 -3
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
- package/src/external-skills.js +104 -45
- package/src/index.js +38 -3
- package/src/scaffold.js +4 -2
package/bundled/VERSION.json
CHANGED
package/package.json
CHANGED
package/src/external-skills.js
CHANGED
|
@@ -1,36 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External Skills Installer
|
|
3
3
|
*
|
|
4
|
-
* Delegates to `npx skills add <repo> --skill <name> --yes`
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* .
|
|
4
|
+
* Delegates to `npx skills add <repo> --skill <name> --yes` to fetch the skill.
|
|
5
|
+
* When run non-interactively (stdio: 'pipe'), npx skills only writes the canonical
|
|
6
|
+
* .agents/skills/<name>/SKILL.md without creating any platform symlinks.
|
|
7
|
+
* We read that file, copy it to the selected platform dir(s), then clean up
|
|
8
|
+
* .agents/ and skills-lock.json.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { execSync } from 'node:child_process';
|
|
12
12
|
import path from 'path';
|
|
13
13
|
import fs from 'fs-extra';
|
|
14
14
|
|
|
15
|
-
// All platform symlink dirs that `npx skills` creates (besides .agents/ canonical store)
|
|
16
|
-
const ALL_PLATFORM_DIRS = [
|
|
17
|
-
'.claude/skills',
|
|
18
|
-
'.codebuddy/skills',
|
|
19
|
-
'.trae/skills',
|
|
20
|
-
'.agent/skills',
|
|
21
|
-
'skills',
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
// Which dirs to KEEP per platform selection
|
|
25
|
-
const KEEP_DIRS = {
|
|
26
|
-
claude: ['.claude/skills'],
|
|
27
|
-
codebuddy: ['.codebuddy/skills'],
|
|
28
|
-
both: ['.claude/skills', '.codebuddy/skills'],
|
|
29
|
-
};
|
|
30
|
-
|
|
31
15
|
/**
|
|
32
|
-
* Install an external skill via `npx skills add`, then prune unwanted platform symlinks.
|
|
33
|
-
*
|
|
34
16
|
* @param {Object} skill - Skill definition from external_skills.known
|
|
35
17
|
* skill.repo {string} - GitHub URL
|
|
36
18
|
* skill.name {string} - Skill name as listed in the repo
|
|
@@ -46,35 +28,112 @@ export async function installExternalSkill(skill, platform, projectRoot, dryRun)
|
|
|
46
28
|
const cmd = `npx skills add ${skill.repo} --skill ${skill.name} --yes`;
|
|
47
29
|
|
|
48
30
|
if (dryRun) {
|
|
49
|
-
const
|
|
31
|
+
const targets = getTargetDirs(platform, projectRoot)
|
|
32
|
+
.map(d => path.relative(projectRoot, path.join(d, skill.name, 'SKILL.md')));
|
|
50
33
|
console.log(` [dry-run] ${cmd}`);
|
|
51
|
-
console.log(` [dry-run]
|
|
34
|
+
for (const t of targets) console.log(` [dry-run] → ${t}`);
|
|
52
35
|
return;
|
|
53
36
|
}
|
|
54
37
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
stdio: 'pipe',
|
|
58
|
-
timeout: 60000,
|
|
59
|
-
});
|
|
38
|
+
// stdio: 'pipe' → npx skills runs non-interactively, writes only .agents/skills/<name>/SKILL.md
|
|
39
|
+
execSync(cmd, { cwd: projectRoot, stdio: 'pipe', timeout: 60000 });
|
|
60
40
|
|
|
61
|
-
|
|
62
|
-
|
|
41
|
+
const canonicalSkillMd = path.join(projectRoot, '.agents', 'skills', skill.name, 'SKILL.md');
|
|
42
|
+
if (!await fs.pathExists(canonicalSkillMd)) {
|
|
43
|
+
throw new Error(`npx skills did not produce .agents/skills/${skill.name}/SKILL.md`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const content = await fs.readFile(canonicalSkillMd, 'utf8');
|
|
47
|
+
|
|
48
|
+
// Copy to selected platform dir(s)
|
|
49
|
+
// npx skills may have created symlinks (e.g. .claude/skills/<name> -> ../../.agents/skills/<name>)
|
|
50
|
+
// We must remove those symlinks first so we can write a real directory with the actual content.
|
|
51
|
+
for (const targetDir of getTargetDirs(platform, projectRoot)) {
|
|
52
|
+
const skillDir = path.join(targetDir, skill.name);
|
|
53
|
+
// Remove any existing symlink or directory at this path before creating a real dir
|
|
54
|
+
await fs.remove(skillDir).catch(() => {});
|
|
55
|
+
await fs.ensureDir(skillDir);
|
|
56
|
+
await fs.writeFile(path.join(skillDir, 'SKILL.md'), content);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Clean up npx skills artifacts after copying
|
|
60
|
+
await fs.remove(path.join(projectRoot, '.agents')).catch(() => {});
|
|
61
|
+
await fs.remove(path.join(projectRoot, 'skills-lock.json')).catch(() => {});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Remove all artifacts created by `npx skills` that are not needed by PrizmKit.
|
|
66
|
+
* Call this once after all external skills have been installed.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} projectRoot
|
|
69
|
+
* @param {string} platform - 'claude' | 'codebuddy' | 'both'
|
|
70
|
+
*/
|
|
71
|
+
export async function cleanExternalSkillArtifacts(projectRoot, platform) {
|
|
72
|
+
const keepDirs = new Set(
|
|
73
|
+
getTargetDirs(platform, projectRoot).map(d => path.resolve(d))
|
|
74
|
+
);
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
const dirsToCheck = [
|
|
77
|
+
'.agents',
|
|
78
|
+
'.agent',
|
|
79
|
+
'.trae',
|
|
80
|
+
'skills',
|
|
81
|
+
'.claude/skills',
|
|
82
|
+
'.codebuddy/skills',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const dir of dirsToCheck) {
|
|
86
|
+
const abs = path.join(projectRoot, dir);
|
|
87
|
+
if (keepDirs.has(path.resolve(abs))) continue;
|
|
88
|
+
await fs.remove(abs).catch(() => {});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Remove any dangling symlinks inside kept platform skill dirs
|
|
92
|
+
// (npx skills creates symlinks like .claude/skills/<name> -> ../../.agents/skills/<name>)
|
|
93
|
+
for (const keepDir of keepDirs) {
|
|
94
|
+
if (!await fs.pathExists(keepDir)) continue;
|
|
95
|
+
let entries;
|
|
96
|
+
try { entries = await fs.readdir(keepDir); } catch { continue; }
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
const entryPath = path.join(keepDir, entry);
|
|
99
|
+
try {
|
|
100
|
+
const stat = await fs.lstat(entryPath);
|
|
101
|
+
if (stat.isSymbolicLink()) {
|
|
102
|
+
await fs.remove(entryPath);
|
|
103
|
+
}
|
|
104
|
+
} catch { /* ignore */ }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Remove empty parent dirs left behind (e.g. .codebuddy/ after removing .codebuddy/skills)
|
|
109
|
+
const parentDirs = ['.claude', '.codebuddy', '.trae', '.agent'];
|
|
110
|
+
for (const dir of parentDirs) {
|
|
111
|
+
const abs = path.join(projectRoot, dir);
|
|
112
|
+
if (!await fs.pathExists(abs)) continue;
|
|
113
|
+
// Only remove if this platform dir is entirely unneeded
|
|
114
|
+
const isKept = getTargetDirs(platform, projectRoot)
|
|
115
|
+
.some(d => path.resolve(d).startsWith(path.resolve(abs) + path.sep));
|
|
116
|
+
if (isKept) continue;
|
|
67
117
|
try {
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
70
|
-
await fs.remove(
|
|
71
|
-
// Remove parent dir if now empty
|
|
72
|
-
const parent = path.join(projectRoot, dir);
|
|
73
|
-
const remaining = await fs.readdir(parent).catch(() => ['_']);
|
|
74
|
-
if (remaining.length === 0) await fs.remove(parent);
|
|
118
|
+
const entries = await fs.readdir(abs);
|
|
119
|
+
if (entries.length === 0) {
|
|
120
|
+
await fs.remove(abs);
|
|
75
121
|
}
|
|
76
|
-
} catch {
|
|
77
|
-
|
|
78
|
-
|
|
122
|
+
} catch { /* ignore */ }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await fs.remove(path.join(projectRoot, 'skills-lock.json')).catch(() => {});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getTargetDirs(platform, projectRoot) {
|
|
129
|
+
if (platform === 'both') {
|
|
130
|
+
return [
|
|
131
|
+
path.join(projectRoot, '.claude', 'skills'),
|
|
132
|
+
path.join(projectRoot, '.codebuddy', 'skills'),
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
if (platform === 'codebuddy') {
|
|
136
|
+
return [path.join(projectRoot, '.codebuddy', 'skills')];
|
|
79
137
|
}
|
|
138
|
+
return [path.join(projectRoot, '.claude', 'skills')];
|
|
80
139
|
}
|
package/src/index.js
CHANGED
|
@@ -86,11 +86,46 @@ export async function runScaffold(directory, options) {
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
// 1.5. AI CLI 命令配置(独立于平台,决定实际运行的可执行命令)
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
const cliChoices = [
|
|
90
|
+
{
|
|
91
|
+
name: `cbc — CodeBuddy CLI${detected.cbc ? chalk.green(' ✓ 已安装') : chalk.gray(' (未检测到)')}`,
|
|
92
|
+
value: 'cbc',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: `claude — Claude Code${detected.claude ? chalk.green(' ✓ 已安装') : chalk.gray(' (未检测到)')}`,
|
|
96
|
+
value: 'claude',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: '自定义 — 输入其他命令',
|
|
100
|
+
value: '__custom__',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: '跳过 — 稍后手动配置',
|
|
104
|
+
value: '',
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
// 如果检测到的推荐命令不在固定列表里,默认选"自定义"
|
|
109
|
+
const knownCliValues = ['cbc', 'claude'];
|
|
110
|
+
const cliDefault = knownCliValues.includes(detected.suggestedCli)
|
|
111
|
+
? detected.suggestedCli
|
|
112
|
+
: (detected.suggestedCli ? '__custom__' : '');
|
|
113
|
+
|
|
114
|
+
const cliSelection = await select({
|
|
115
|
+
message: '选择底层 AI CLI 可执行命令:',
|
|
116
|
+
choices: cliChoices,
|
|
117
|
+
default: cliDefault,
|
|
92
118
|
});
|
|
93
119
|
|
|
120
|
+
let aiCli = cliSelection;
|
|
121
|
+
if (cliSelection === '__custom__') {
|
|
122
|
+
aiCli = await input({
|
|
123
|
+
message: '输入自定义 AI CLI 命令:',
|
|
124
|
+
default: knownCliValues.includes(detected.suggestedCli) ? '' : detected.suggestedCli,
|
|
125
|
+
validate: (v) => v.trim() ? true : '请输入命令',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
94
129
|
// 2. 选择技能套件
|
|
95
130
|
const skillSuiteChoice = await select({
|
|
96
131
|
message: '选择技能套件:',
|
package/src/scaffold.js
CHANGED
|
@@ -697,7 +697,7 @@ export async function scaffold(config) {
|
|
|
697
697
|
|
|
698
698
|
// 9. 安装外部 Skills(如果选择了)
|
|
699
699
|
if (externalSkills?.length) {
|
|
700
|
-
const { installExternalSkill } = await import('./external-skills.js');
|
|
700
|
+
const { installExternalSkill, cleanExternalSkillArtifacts } = await import('./external-skills.js');
|
|
701
701
|
const metadata = await loadMetadata();
|
|
702
702
|
const knownSkills = metadata.external_skills?.known || [];
|
|
703
703
|
console.log(chalk.blue('\n 外部技能:'));
|
|
@@ -705,7 +705,6 @@ export async function scaffold(config) {
|
|
|
705
705
|
const skillDef = knownSkills.find(s => s.name === name);
|
|
706
706
|
if (skillDef) {
|
|
707
707
|
try {
|
|
708
|
-
// npx skills add handles installation; we pass platform to prune unwanted symlinks
|
|
709
708
|
await installExternalSkill(skillDef, platform, projectRoot, dryRun);
|
|
710
709
|
console.log(chalk.green(` ✓ ${name} (external)`));
|
|
711
710
|
} catch (e) {
|
|
@@ -713,6 +712,9 @@ export async function scaffold(config) {
|
|
|
713
712
|
}
|
|
714
713
|
}
|
|
715
714
|
}
|
|
715
|
+
if (!dryRun) {
|
|
716
|
+
await cleanExternalSkillArtifacts(projectRoot, platform);
|
|
717
|
+
}
|
|
716
718
|
}
|
|
717
719
|
|
|
718
720
|
// 10. Git pre-commit hook
|