@wang121ye/skillmanager 0.0.1 → 0.0.2
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/LICENSE +21 -21
- package/README.md +252 -219
- package/manifests/sources.json +38 -38
- package/package.json +50 -50
- package/src/cli.js +178 -178
- package/src/commands/bootstrap.js +153 -153
- package/src/commands/config.js +152 -96
- package/src/commands/select.js +79 -79
- package/src/commands/source.js +103 -103
- package/src/commands/uninstall.js +63 -63
- package/src/commands/update.js +110 -110
- package/src/commands/webui.js +150 -150
- package/src/commands/where.js +23 -23
- package/src/index.js +2 -2
- package/src/lib/concurrency.js +22 -22
- package/src/lib/config.js +75 -75
- package/src/lib/fs.js +33 -33
- package/src/lib/git.js +57 -57
- package/src/lib/http.js +42 -42
- package/src/lib/installed.js +35 -35
- package/src/lib/local-install.js +35 -35
- package/src/lib/manifest.js +84 -84
- package/src/lib/openskills.js +50 -50
- package/src/lib/paths.js +44 -44
- package/src/lib/profiles.js +31 -31
- package/src/lib/scan.js +50 -50
- package/src/lib/source-utils.js +62 -62
- package/src/ui/server.js +321 -321
package/src/commands/update.js
CHANGED
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
const { getAppPaths } = require('../lib/paths');
|
|
2
|
-
const { ensureDir } = require('../lib/fs');
|
|
3
|
-
const { loadSourcesManifest } = require('../lib/manifest');
|
|
4
|
-
const { ensureRepo } = require('../lib/git');
|
|
5
|
-
const { scanSkillsInRepo } = require('../lib/scan');
|
|
6
|
-
const { loadProfile } = require('../lib/profiles');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const os = require('os');
|
|
9
|
-
const { syncAgents, runOpenSkills } = require('../lib/openskills');
|
|
10
|
-
const { installFromLocalSkillDir } = require('../lib/local-install');
|
|
11
|
-
const { mapWithConcurrency } = require('../lib/concurrency');
|
|
12
|
-
const { getEffectiveDefaultProfile } = require('../lib/config');
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
function uniq(arr) {
|
|
16
|
-
return Array.from(new Set(arr));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function update(opts) {
|
|
20
|
-
const globalInstall = !!opts?.global;
|
|
21
|
-
const universal = !!opts?.universal;
|
|
22
|
-
|
|
23
|
-
const profileName = opts?.profile;
|
|
24
|
-
const wantsSelection = !!profileName;
|
|
25
|
-
|
|
26
|
-
if (!wantsSelection) {
|
|
27
|
-
// Default: ask openskills to update everything it has tracked.
|
|
28
|
-
// eslint-disable-next-line no-console
|
|
29
|
-
console.log('正在执行 openskills update(更新所有已记录来源)…');
|
|
30
|
-
await runOpenSkills(['update']);
|
|
31
|
-
if (opts?.sync !== false) {
|
|
32
|
-
await syncAgents({ output: opts?.output, cwd: process.cwd() });
|
|
33
|
-
}
|
|
34
|
-
// eslint-disable-next-line no-console
|
|
35
|
-
console.log('\n完成。');
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Profile-based update: refresh repos cache and re-install selected skill dirs.
|
|
40
|
-
const paths = getAppPaths();
|
|
41
|
-
await ensureDir(paths.reposDir);
|
|
42
|
-
await ensureDir(paths.profilesDir);
|
|
43
|
-
|
|
44
|
-
const { sources } = await loadSourcesManifest();
|
|
45
|
-
const enabledSources = sources.filter((s) => s && s.enabled !== false);
|
|
46
|
-
|
|
47
|
-
const concurrency = Number(opts?.concurrency || process.env.SKILLMANAGER_CONCURRENCY || 3);
|
|
48
|
-
// eslint-disable-next-line no-console
|
|
49
|
-
console.log(`并发扫描:${Math.max(1, concurrency)}(可用 --concurrency 或环境变量 SKILLMANAGER_CONCURRENCY 调整)`);
|
|
50
|
-
|
|
51
|
-
const skillsById = new Map();
|
|
52
|
-
const perSource = await mapWithConcurrency(enabledSources, concurrency, async (s) => {
|
|
53
|
-
try {
|
|
54
|
-
const repoDir = await ensureRepo({ reposDir: paths.reposDir, source: s });
|
|
55
|
-
const skills = await scanSkillsInRepo({
|
|
56
|
-
sourceId: s.id,
|
|
57
|
-
sourceName: s.name || s.id,
|
|
58
|
-
repoDir
|
|
59
|
-
});
|
|
60
|
-
return { source: s, skills };
|
|
61
|
-
} catch (err) {
|
|
62
|
-
// eslint-disable-next-line no-console
|
|
63
|
-
console.warn(`警告:拉取/扫描来源失败,将跳过:${s.name || s.id}`);
|
|
64
|
-
// eslint-disable-next-line no-console
|
|
65
|
-
console.warn(err?.message || String(err));
|
|
66
|
-
return { source: s, skills: [] };
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
for (const { skills } of perSource) {
|
|
71
|
-
for (const sk of skills) skillsById.set(sk.id, sk);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const allSkills = Array.from(skillsById.values());
|
|
75
|
-
|
|
76
|
-
const effectiveProfileName = profileName || (await getEffectiveDefaultProfile());
|
|
77
|
-
const existing = await loadProfile({ profilesDir: paths.profilesDir, profileName: effectiveProfileName });
|
|
78
|
-
let selectedIds =
|
|
79
|
-
existing?.selectedSkillIds && Array.isArray(existing.selectedSkillIds)
|
|
80
|
-
? existing.selectedSkillIds
|
|
81
|
-
: allSkills.map((s) => s.id);
|
|
82
|
-
|
|
83
|
-
// 交互式选择已迁移到:skillmanager webui(mode=install),先保存 profile 再 update --profile
|
|
84
|
-
|
|
85
|
-
selectedIds = uniq(selectedIds).filter((id) => skillsById.has(id));
|
|
86
|
-
|
|
87
|
-
// eslint-disable-next-line no-console
|
|
88
|
-
console.log(`将更新/重装 ${selectedIds.length} 个 skills(global=${globalInstall}, universal=${universal})…`);
|
|
89
|
-
|
|
90
|
-
for (const id of selectedIds) {
|
|
91
|
-
const skill = skillsById.get(id);
|
|
92
|
-
// eslint-disable-next-line no-console
|
|
93
|
-
console.log(`\n==> Re-installing: ${skill.name} (${skill.sourceId})`);
|
|
94
|
-
const folder = universal ? '.agent/skills' : '.claude/skills';
|
|
95
|
-
const targetDir = globalInstall ? path.join(os.homedir(), folder) : path.join(process.cwd(), folder);
|
|
96
|
-
const { targetPath } = await installFromLocalSkillDir({ skillDir: skill.skillDir, targetDir });
|
|
97
|
-
// eslint-disable-next-line no-console
|
|
98
|
-
console.log(`✅ Re-installed (local copy): ${targetPath}`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (opts?.sync !== false) {
|
|
102
|
-
await syncAgents({ output: opts?.output, cwd: process.cwd() });
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// eslint-disable-next-line no-console
|
|
106
|
-
console.log('\n完成。');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
module.exports = { update };
|
|
110
|
-
|
|
1
|
+
const { getAppPaths } = require('../lib/paths');
|
|
2
|
+
const { ensureDir } = require('../lib/fs');
|
|
3
|
+
const { loadSourcesManifest } = require('../lib/manifest');
|
|
4
|
+
const { ensureRepo } = require('../lib/git');
|
|
5
|
+
const { scanSkillsInRepo } = require('../lib/scan');
|
|
6
|
+
const { loadProfile } = require('../lib/profiles');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const { syncAgents, runOpenSkills } = require('../lib/openskills');
|
|
10
|
+
const { installFromLocalSkillDir } = require('../lib/local-install');
|
|
11
|
+
const { mapWithConcurrency } = require('../lib/concurrency');
|
|
12
|
+
const { getEffectiveDefaultProfile } = require('../lib/config');
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
function uniq(arr) {
|
|
16
|
+
return Array.from(new Set(arr));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function update(opts) {
|
|
20
|
+
const globalInstall = !!opts?.global;
|
|
21
|
+
const universal = !!opts?.universal;
|
|
22
|
+
|
|
23
|
+
const profileName = opts?.profile;
|
|
24
|
+
const wantsSelection = !!profileName;
|
|
25
|
+
|
|
26
|
+
if (!wantsSelection) {
|
|
27
|
+
// Default: ask openskills to update everything it has tracked.
|
|
28
|
+
// eslint-disable-next-line no-console
|
|
29
|
+
console.log('正在执行 openskills update(更新所有已记录来源)…');
|
|
30
|
+
await runOpenSkills(['update']);
|
|
31
|
+
if (opts?.sync !== false) {
|
|
32
|
+
await syncAgents({ output: opts?.output, cwd: process.cwd() });
|
|
33
|
+
}
|
|
34
|
+
// eslint-disable-next-line no-console
|
|
35
|
+
console.log('\n完成。');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Profile-based update: refresh repos cache and re-install selected skill dirs.
|
|
40
|
+
const paths = getAppPaths();
|
|
41
|
+
await ensureDir(paths.reposDir);
|
|
42
|
+
await ensureDir(paths.profilesDir);
|
|
43
|
+
|
|
44
|
+
const { sources } = await loadSourcesManifest();
|
|
45
|
+
const enabledSources = sources.filter((s) => s && s.enabled !== false);
|
|
46
|
+
|
|
47
|
+
const concurrency = Number(opts?.concurrency || process.env.SKILLMANAGER_CONCURRENCY || 3);
|
|
48
|
+
// eslint-disable-next-line no-console
|
|
49
|
+
console.log(`并发扫描:${Math.max(1, concurrency)}(可用 --concurrency 或环境变量 SKILLMANAGER_CONCURRENCY 调整)`);
|
|
50
|
+
|
|
51
|
+
const skillsById = new Map();
|
|
52
|
+
const perSource = await mapWithConcurrency(enabledSources, concurrency, async (s) => {
|
|
53
|
+
try {
|
|
54
|
+
const repoDir = await ensureRepo({ reposDir: paths.reposDir, source: s });
|
|
55
|
+
const skills = await scanSkillsInRepo({
|
|
56
|
+
sourceId: s.id,
|
|
57
|
+
sourceName: s.name || s.id,
|
|
58
|
+
repoDir
|
|
59
|
+
});
|
|
60
|
+
return { source: s, skills };
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// eslint-disable-next-line no-console
|
|
63
|
+
console.warn(`警告:拉取/扫描来源失败,将跳过:${s.name || s.id}`);
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
console.warn(err?.message || String(err));
|
|
66
|
+
return { source: s, skills: [] };
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
for (const { skills } of perSource) {
|
|
71
|
+
for (const sk of skills) skillsById.set(sk.id, sk);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const allSkills = Array.from(skillsById.values());
|
|
75
|
+
|
|
76
|
+
const effectiveProfileName = profileName || (await getEffectiveDefaultProfile());
|
|
77
|
+
const existing = await loadProfile({ profilesDir: paths.profilesDir, profileName: effectiveProfileName });
|
|
78
|
+
let selectedIds =
|
|
79
|
+
existing?.selectedSkillIds && Array.isArray(existing.selectedSkillIds)
|
|
80
|
+
? existing.selectedSkillIds
|
|
81
|
+
: allSkills.map((s) => s.id);
|
|
82
|
+
|
|
83
|
+
// 交互式选择已迁移到:skillmanager webui(mode=install),先保存 profile 再 update --profile
|
|
84
|
+
|
|
85
|
+
selectedIds = uniq(selectedIds).filter((id) => skillsById.has(id));
|
|
86
|
+
|
|
87
|
+
// eslint-disable-next-line no-console
|
|
88
|
+
console.log(`将更新/重装 ${selectedIds.length} 个 skills(global=${globalInstall}, universal=${universal})…`);
|
|
89
|
+
|
|
90
|
+
for (const id of selectedIds) {
|
|
91
|
+
const skill = skillsById.get(id);
|
|
92
|
+
// eslint-disable-next-line no-console
|
|
93
|
+
console.log(`\n==> Re-installing: ${skill.name} (${skill.sourceId})`);
|
|
94
|
+
const folder = universal ? '.agent/skills' : '.claude/skills';
|
|
95
|
+
const targetDir = globalInstall ? path.join(os.homedir(), folder) : path.join(process.cwd(), folder);
|
|
96
|
+
const { targetPath } = await installFromLocalSkillDir({ skillDir: skill.skillDir, targetDir });
|
|
97
|
+
// eslint-disable-next-line no-console
|
|
98
|
+
console.log(`✅ Re-installed (local copy): ${targetPath}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (opts?.sync !== false) {
|
|
102
|
+
await syncAgents({ output: opts?.output, cwd: process.cwd() });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// eslint-disable-next-line no-console
|
|
106
|
+
console.log('\n完成。');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { update };
|
|
110
|
+
|
package/src/commands/webui.js
CHANGED
|
@@ -1,150 +1,150 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const os = require('os');
|
|
3
|
-
const fsp = require('fs/promises');
|
|
4
|
-
|
|
5
|
-
const { getAppPaths } = require('../lib/paths');
|
|
6
|
-
const { ensureDir } = require('../lib/fs');
|
|
7
|
-
const { loadSourcesManifest } = require('../lib/manifest');
|
|
8
|
-
const { ensureRepo } = require('../lib/git');
|
|
9
|
-
const { scanSkillsInRepo } = require('../lib/scan');
|
|
10
|
-
const { loadProfile, saveProfile } = require('../lib/profiles');
|
|
11
|
-
const { getEffectiveDefaultProfile } = require('../lib/config');
|
|
12
|
-
const { mapWithConcurrency } = require('../lib/concurrency');
|
|
13
|
-
const { installFromLocalSkillDir } = require('../lib/local-install');
|
|
14
|
-
const { listInstalledSkills } = require('../lib/installed');
|
|
15
|
-
const { syncAgents } = require('../lib/openskills');
|
|
16
|
-
const { launchSelectionUi } = require('../ui/server');
|
|
17
|
-
|
|
18
|
-
function resolveTargetDir({ globalInstall, universal }) {
|
|
19
|
-
const folder = universal ? '.agent/skills' : '.claude/skills';
|
|
20
|
-
return globalInstall ? path.join(os.homedir(), folder) : path.join(process.cwd(), folder);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function webui(opts) {
|
|
24
|
-
const modeRaw = String(opts?.mode || 'install').toLowerCase();
|
|
25
|
-
const mode = modeRaw === 'uninstall' ? 'uninstall' : 'install';
|
|
26
|
-
|
|
27
|
-
const globalInstall = !!opts?.global;
|
|
28
|
-
const universal = !!opts?.universal;
|
|
29
|
-
const targetDir = resolveTargetDir({ globalInstall, universal });
|
|
30
|
-
|
|
31
|
-
if (mode === 'uninstall') {
|
|
32
|
-
const installed = await listInstalledSkills(targetDir);
|
|
33
|
-
const installedByName = new Map(installed.map((s) => [s.name, s]));
|
|
34
|
-
|
|
35
|
-
const chosen = await launchSelectionUi({
|
|
36
|
-
title: `skillmanager webui · uninstall · ${globalInstall ? 'global' : 'project'} · ${universal ? '.agent' : '.claude'}`,
|
|
37
|
-
skills: installed.map((s) => ({
|
|
38
|
-
id: s.name,
|
|
39
|
-
sourceId: 'installed',
|
|
40
|
-
sourceName: 'Installed',
|
|
41
|
-
name: s.name,
|
|
42
|
-
description: s.description
|
|
43
|
-
})),
|
|
44
|
-
selectedSkillIds: installed.map((s) => s.name)
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const toRemove = Array.from(new Set(chosen)).filter((n) => installedByName.has(n));
|
|
48
|
-
|
|
49
|
-
if (toRemove.length === 0) {
|
|
50
|
-
// eslint-disable-next-line no-console
|
|
51
|
-
console.log(`未选择任何可卸载的 skill。目标目录:${targetDir}`);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// eslint-disable-next-line no-console
|
|
56
|
-
console.log(`将卸载 ${toRemove.length} 个 skills(目标:${targetDir})…`);
|
|
57
|
-
for (const name of toRemove) {
|
|
58
|
-
const entry = installedByName.get(name);
|
|
59
|
-
if (!entry) continue;
|
|
60
|
-
// eslint-disable-next-line no-console
|
|
61
|
-
console.log(`- remove ${name}`);
|
|
62
|
-
await fsp.rm(entry.skillDir, { recursive: true, force: true });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (opts?.sync !== false) {
|
|
66
|
-
await syncAgents({ output: opts?.output, cwd: process.cwd() });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// eslint-disable-next-line no-console
|
|
70
|
-
console.log('完成。');
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// install mode
|
|
75
|
-
const paths = getAppPaths();
|
|
76
|
-
await ensureDir(paths.reposDir);
|
|
77
|
-
await ensureDir(paths.profilesDir);
|
|
78
|
-
|
|
79
|
-
const profileName = opts?.profile || (await getEffectiveDefaultProfile());
|
|
80
|
-
const existing = await loadProfile({ profilesDir: paths.profilesDir, profileName });
|
|
81
|
-
|
|
82
|
-
const { sources } = await loadSourcesManifest();
|
|
83
|
-
const enabledSources = sources.filter((s) => s && s.enabled !== false);
|
|
84
|
-
|
|
85
|
-
const concurrency = Number(opts?.concurrency || process.env.SKILLMANAGER_CONCURRENCY || 3);
|
|
86
|
-
// eslint-disable-next-line no-console
|
|
87
|
-
console.log(`并发扫描:${Math.max(1, concurrency)}(可用 --concurrency 或环境变量 SKILLMANAGER_CONCURRENCY 调整)`);
|
|
88
|
-
|
|
89
|
-
const skillsById = new Map();
|
|
90
|
-
const perSource = await mapWithConcurrency(enabledSources, concurrency, async (s) => {
|
|
91
|
-
try {
|
|
92
|
-
const repoDir = await ensureRepo({ reposDir: paths.reposDir, source: s });
|
|
93
|
-
const skills = await scanSkillsInRepo({
|
|
94
|
-
sourceId: s.id,
|
|
95
|
-
sourceName: s.name || s.id,
|
|
96
|
-
repoDir
|
|
97
|
-
});
|
|
98
|
-
return { source: s, skills };
|
|
99
|
-
} catch (err) {
|
|
100
|
-
// eslint-disable-next-line no-console
|
|
101
|
-
console.warn(`警告:拉取/扫描来源失败,将跳过:${s.name || s.id}`);
|
|
102
|
-
// eslint-disable-next-line no-console
|
|
103
|
-
console.warn(err?.message || String(err));
|
|
104
|
-
return { source: s, skills: [] };
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
for (const { skills } of perSource) for (const sk of skills) skillsById.set(sk.id, sk);
|
|
109
|
-
|
|
110
|
-
const allSkills = Array.from(skillsById.values());
|
|
111
|
-
const initialSelected =
|
|
112
|
-
existing?.selectedSkillIds && Array.isArray(existing.selectedSkillIds) ? existing.selectedSkillIds : allSkills.map((s) => s.id);
|
|
113
|
-
|
|
114
|
-
const chosen = await launchSelectionUi({
|
|
115
|
-
title: `skillmanager webui · install · profile=${profileName}`,
|
|
116
|
-
skills: allSkills.map((s) => ({
|
|
117
|
-
id: s.id,
|
|
118
|
-
sourceId: s.sourceId,
|
|
119
|
-
sourceName: s.sourceName,
|
|
120
|
-
name: s.name,
|
|
121
|
-
description: s.description
|
|
122
|
-
})),
|
|
123
|
-
selectedSkillIds: initialSelected
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const selectedIds = Array.from(new Set(chosen)).filter((id) => skillsById.has(id));
|
|
127
|
-
await saveProfile({ profilesDir: paths.profilesDir, profileName, selectedSkillIds: selectedIds });
|
|
128
|
-
|
|
129
|
-
// eslint-disable-next-line no-console
|
|
130
|
-
console.log(`将安装 ${selectedIds.length} 个 skills(目标:${targetDir},profile=${profileName})…`);
|
|
131
|
-
|
|
132
|
-
for (const id of selectedIds) {
|
|
133
|
-
const skill = skillsById.get(id);
|
|
134
|
-
// eslint-disable-next-line no-console
|
|
135
|
-
console.log(`- install ${skill.name} (${skill.sourceId})`);
|
|
136
|
-
const { targetPath } = await installFromLocalSkillDir({ skillDir: skill.skillDir, targetDir });
|
|
137
|
-
// eslint-disable-next-line no-console
|
|
138
|
-
console.log(` ✅ ${targetPath}`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (opts?.sync !== false) {
|
|
142
|
-
await syncAgents({ output: opts?.output, cwd: process.cwd() });
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// eslint-disable-next-line no-console
|
|
146
|
-
console.log('完成。');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
module.exports = { webui };
|
|
150
|
-
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const fsp = require('fs/promises');
|
|
4
|
+
|
|
5
|
+
const { getAppPaths } = require('../lib/paths');
|
|
6
|
+
const { ensureDir } = require('../lib/fs');
|
|
7
|
+
const { loadSourcesManifest } = require('../lib/manifest');
|
|
8
|
+
const { ensureRepo } = require('../lib/git');
|
|
9
|
+
const { scanSkillsInRepo } = require('../lib/scan');
|
|
10
|
+
const { loadProfile, saveProfile } = require('../lib/profiles');
|
|
11
|
+
const { getEffectiveDefaultProfile } = require('../lib/config');
|
|
12
|
+
const { mapWithConcurrency } = require('../lib/concurrency');
|
|
13
|
+
const { installFromLocalSkillDir } = require('../lib/local-install');
|
|
14
|
+
const { listInstalledSkills } = require('../lib/installed');
|
|
15
|
+
const { syncAgents } = require('../lib/openskills');
|
|
16
|
+
const { launchSelectionUi } = require('../ui/server');
|
|
17
|
+
|
|
18
|
+
function resolveTargetDir({ globalInstall, universal }) {
|
|
19
|
+
const folder = universal ? '.agent/skills' : '.claude/skills';
|
|
20
|
+
return globalInstall ? path.join(os.homedir(), folder) : path.join(process.cwd(), folder);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function webui(opts) {
|
|
24
|
+
const modeRaw = String(opts?.mode || 'install').toLowerCase();
|
|
25
|
+
const mode = modeRaw === 'uninstall' ? 'uninstall' : 'install';
|
|
26
|
+
|
|
27
|
+
const globalInstall = !!opts?.global;
|
|
28
|
+
const universal = !!opts?.universal;
|
|
29
|
+
const targetDir = resolveTargetDir({ globalInstall, universal });
|
|
30
|
+
|
|
31
|
+
if (mode === 'uninstall') {
|
|
32
|
+
const installed = await listInstalledSkills(targetDir);
|
|
33
|
+
const installedByName = new Map(installed.map((s) => [s.name, s]));
|
|
34
|
+
|
|
35
|
+
const chosen = await launchSelectionUi({
|
|
36
|
+
title: `skillmanager webui · uninstall · ${globalInstall ? 'global' : 'project'} · ${universal ? '.agent' : '.claude'}`,
|
|
37
|
+
skills: installed.map((s) => ({
|
|
38
|
+
id: s.name,
|
|
39
|
+
sourceId: 'installed',
|
|
40
|
+
sourceName: 'Installed',
|
|
41
|
+
name: s.name,
|
|
42
|
+
description: s.description
|
|
43
|
+
})),
|
|
44
|
+
selectedSkillIds: installed.map((s) => s.name)
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const toRemove = Array.from(new Set(chosen)).filter((n) => installedByName.has(n));
|
|
48
|
+
|
|
49
|
+
if (toRemove.length === 0) {
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
console.log(`未选择任何可卸载的 skill。目标目录:${targetDir}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.log(`将卸载 ${toRemove.length} 个 skills(目标:${targetDir})…`);
|
|
57
|
+
for (const name of toRemove) {
|
|
58
|
+
const entry = installedByName.get(name);
|
|
59
|
+
if (!entry) continue;
|
|
60
|
+
// eslint-disable-next-line no-console
|
|
61
|
+
console.log(`- remove ${name}`);
|
|
62
|
+
await fsp.rm(entry.skillDir, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (opts?.sync !== false) {
|
|
66
|
+
await syncAgents({ output: opts?.output, cwd: process.cwd() });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// eslint-disable-next-line no-console
|
|
70
|
+
console.log('完成。');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// install mode
|
|
75
|
+
const paths = getAppPaths();
|
|
76
|
+
await ensureDir(paths.reposDir);
|
|
77
|
+
await ensureDir(paths.profilesDir);
|
|
78
|
+
|
|
79
|
+
const profileName = opts?.profile || (await getEffectiveDefaultProfile());
|
|
80
|
+
const existing = await loadProfile({ profilesDir: paths.profilesDir, profileName });
|
|
81
|
+
|
|
82
|
+
const { sources } = await loadSourcesManifest();
|
|
83
|
+
const enabledSources = sources.filter((s) => s && s.enabled !== false);
|
|
84
|
+
|
|
85
|
+
const concurrency = Number(opts?.concurrency || process.env.SKILLMANAGER_CONCURRENCY || 3);
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.log(`并发扫描:${Math.max(1, concurrency)}(可用 --concurrency 或环境变量 SKILLMANAGER_CONCURRENCY 调整)`);
|
|
88
|
+
|
|
89
|
+
const skillsById = new Map();
|
|
90
|
+
const perSource = await mapWithConcurrency(enabledSources, concurrency, async (s) => {
|
|
91
|
+
try {
|
|
92
|
+
const repoDir = await ensureRepo({ reposDir: paths.reposDir, source: s });
|
|
93
|
+
const skills = await scanSkillsInRepo({
|
|
94
|
+
sourceId: s.id,
|
|
95
|
+
sourceName: s.name || s.id,
|
|
96
|
+
repoDir
|
|
97
|
+
});
|
|
98
|
+
return { source: s, skills };
|
|
99
|
+
} catch (err) {
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.warn(`警告:拉取/扫描来源失败,将跳过:${s.name || s.id}`);
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.warn(err?.message || String(err));
|
|
104
|
+
return { source: s, skills: [] };
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
for (const { skills } of perSource) for (const sk of skills) skillsById.set(sk.id, sk);
|
|
109
|
+
|
|
110
|
+
const allSkills = Array.from(skillsById.values());
|
|
111
|
+
const initialSelected =
|
|
112
|
+
existing?.selectedSkillIds && Array.isArray(existing.selectedSkillIds) ? existing.selectedSkillIds : allSkills.map((s) => s.id);
|
|
113
|
+
|
|
114
|
+
const chosen = await launchSelectionUi({
|
|
115
|
+
title: `skillmanager webui · install · profile=${profileName}`,
|
|
116
|
+
skills: allSkills.map((s) => ({
|
|
117
|
+
id: s.id,
|
|
118
|
+
sourceId: s.sourceId,
|
|
119
|
+
sourceName: s.sourceName,
|
|
120
|
+
name: s.name,
|
|
121
|
+
description: s.description
|
|
122
|
+
})),
|
|
123
|
+
selectedSkillIds: initialSelected
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const selectedIds = Array.from(new Set(chosen)).filter((id) => skillsById.has(id));
|
|
127
|
+
await saveProfile({ profilesDir: paths.profilesDir, profileName, selectedSkillIds: selectedIds });
|
|
128
|
+
|
|
129
|
+
// eslint-disable-next-line no-console
|
|
130
|
+
console.log(`将安装 ${selectedIds.length} 个 skills(目标:${targetDir},profile=${profileName})…`);
|
|
131
|
+
|
|
132
|
+
for (const id of selectedIds) {
|
|
133
|
+
const skill = skillsById.get(id);
|
|
134
|
+
// eslint-disable-next-line no-console
|
|
135
|
+
console.log(`- install ${skill.name} (${skill.sourceId})`);
|
|
136
|
+
const { targetPath } = await installFromLocalSkillDir({ skillDir: skill.skillDir, targetDir });
|
|
137
|
+
// eslint-disable-next-line no-console
|
|
138
|
+
console.log(` ✅ ${targetPath}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (opts?.sync !== false) {
|
|
142
|
+
await syncAgents({ output: opts?.output, cwd: process.cwd() });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// eslint-disable-next-line no-console
|
|
146
|
+
console.log('完成。');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = { webui };
|
|
150
|
+
|
package/src/commands/where.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
const { getAppPaths } = require('../lib/paths');
|
|
2
|
-
const { loadSourcesManifest } = require('../lib/manifest');
|
|
3
|
-
|
|
4
|
-
async function where() {
|
|
5
|
-
const p = getAppPaths();
|
|
6
|
-
const m = await loadSourcesManifest();
|
|
7
|
-
|
|
8
|
-
// eslint-disable-next-line no-console
|
|
9
|
-
console.log('skillmanager paths:');
|
|
10
|
-
// eslint-disable-next-line no-console
|
|
11
|
-
console.log(`- configDir: ${p.configDir}`);
|
|
12
|
-
// eslint-disable-next-line no-console
|
|
13
|
-
console.log(`- profilesDir:${p.profilesDir}`);
|
|
14
|
-
// eslint-disable-next-line no-console
|
|
15
|
-
console.log(`- cacheDir: ${p.cacheDir}`);
|
|
16
|
-
// eslint-disable-next-line no-console
|
|
17
|
-
console.log(`- reposDir: ${p.reposDir}`);
|
|
18
|
-
// eslint-disable-next-line no-console
|
|
19
|
-
console.log(`- manifest: ${m.manifestPath}`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
module.exports = { where };
|
|
23
|
-
|
|
1
|
+
const { getAppPaths } = require('../lib/paths');
|
|
2
|
+
const { loadSourcesManifest } = require('../lib/manifest');
|
|
3
|
+
|
|
4
|
+
async function where() {
|
|
5
|
+
const p = getAppPaths();
|
|
6
|
+
const m = await loadSourcesManifest();
|
|
7
|
+
|
|
8
|
+
// eslint-disable-next-line no-console
|
|
9
|
+
console.log('skillmanager paths:');
|
|
10
|
+
// eslint-disable-next-line no-console
|
|
11
|
+
console.log(`- configDir: ${p.configDir}`);
|
|
12
|
+
// eslint-disable-next-line no-console
|
|
13
|
+
console.log(`- profilesDir:${p.profilesDir}`);
|
|
14
|
+
// eslint-disable-next-line no-console
|
|
15
|
+
console.log(`- cacheDir: ${p.cacheDir}`);
|
|
16
|
+
// eslint-disable-next-line no-console
|
|
17
|
+
console.log(`- reposDir: ${p.reposDir}`);
|
|
18
|
+
// eslint-disable-next-line no-console
|
|
19
|
+
console.log(`- manifest: ${m.manifestPath}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { where };
|
|
23
|
+
|
package/src/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
module.exports = {};
|
|
2
|
-
|
|
1
|
+
module.exports = {};
|
|
2
|
+
|
package/src/lib/concurrency.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
async function mapWithConcurrency(items, limit, mapper) {
|
|
2
|
-
const arr = Array.from(items || []);
|
|
3
|
-
const n = Math.max(1, Number(limit || 1));
|
|
4
|
-
const results = new Array(arr.length);
|
|
5
|
-
|
|
6
|
-
let nextIndex = 0;
|
|
7
|
-
async function worker() {
|
|
8
|
-
while (true) {
|
|
9
|
-
const i = nextIndex++;
|
|
10
|
-
if (i >= arr.length) return;
|
|
11
|
-
results[i] = await mapper(arr[i], i);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const workers = [];
|
|
16
|
-
for (let i = 0; i < Math.min(n, arr.length); i++) workers.push(worker());
|
|
17
|
-
await Promise.all(workers);
|
|
18
|
-
return results;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
module.exports = { mapWithConcurrency };
|
|
22
|
-
|
|
1
|
+
async function mapWithConcurrency(items, limit, mapper) {
|
|
2
|
+
const arr = Array.from(items || []);
|
|
3
|
+
const n = Math.max(1, Number(limit || 1));
|
|
4
|
+
const results = new Array(arr.length);
|
|
5
|
+
|
|
6
|
+
let nextIndex = 0;
|
|
7
|
+
async function worker() {
|
|
8
|
+
while (true) {
|
|
9
|
+
const i = nextIndex++;
|
|
10
|
+
if (i >= arr.length) return;
|
|
11
|
+
results[i] = await mapper(arr[i], i);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const workers = [];
|
|
16
|
+
for (let i = 0; i < Math.min(n, arr.length); i++) workers.push(worker());
|
|
17
|
+
await Promise.all(workers);
|
|
18
|
+
return results;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { mapWithConcurrency };
|
|
22
|
+
|