@wang121ye/skillmanager 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.
@@ -0,0 +1,96 @@
1
+ const { loadConfig, setDefaultProfile, setRemoteProfileUrl, getEffectiveRemoteProfileUrl } = require('../lib/config');
2
+ const { getAppPaths } = require('../lib/paths');
3
+ const { loadProfile, saveProfile } = require('../lib/profiles');
4
+ const { httpFetch } = require('../lib/http');
5
+
6
+ async function showConfig() {
7
+ const { configPath, config } = await loadConfig();
8
+ // eslint-disable-next-line no-console
9
+ console.log(`config: ${configPath}`);
10
+ // eslint-disable-next-line no-console
11
+ console.log(JSON.stringify(config, null, 2));
12
+ }
13
+
14
+ async function setDefaultProfileCmd(name) {
15
+ const { configPath, config } = await setDefaultProfile(name);
16
+ // eslint-disable-next-line no-console
17
+ console.log(`已设置 defaultProfile=${config.defaultProfile}`);
18
+ // eslint-disable-next-line no-console
19
+ console.log(`写入:${configPath}`);
20
+ // eslint-disable-next-line no-console
21
+ console.log(`你也可以用环境变量临时覆盖:SKILLMANAGER_PROFILE=${config.defaultProfile}`);
22
+ }
23
+
24
+ async function setRemoteProfileUrlCmd(url) {
25
+ const { configPath, config } = await setRemoteProfileUrl(url);
26
+ // eslint-disable-next-line no-console
27
+ console.log(`已设置 remoteProfileUrl=${config.remoteProfileUrl}`);
28
+ // eslint-disable-next-line no-console
29
+ console.log(`写入:${configPath}`);
30
+ // eslint-disable-next-line no-console
31
+ console.log('提示:把远端设置为“公共写”非常危险,建议后续改成签名 URL 或私有桶。');
32
+ }
33
+
34
+ function normalizeUrl(url) {
35
+ const u = String(url || '').trim();
36
+ if (!u) return null;
37
+ return u;
38
+ }
39
+
40
+ async function pushProfileCmd(opts) {
41
+ const profileName = String(opts?.profile || 'default');
42
+ const url = normalizeUrl(opts?.url) || (await getEffectiveRemoteProfileUrl());
43
+ if (!url) throw new Error('缺少 --url,或未设置 config.remoteProfileUrl / SKILLMANAGER_PROFILE_URL');
44
+
45
+ const appPaths = getAppPaths();
46
+ const profilesDir = appPaths.profilesDir;
47
+ const profile = await loadProfile({ profilesDir, profileName });
48
+ if (!profile) throw new Error(`本地 profile 不存在:${profileName}`);
49
+
50
+ // eslint-disable-next-line no-console
51
+ console.log(`上传 profile=${profileName} -> ${url}`);
52
+ // eslint-disable-next-line no-console
53
+ console.log('警告:如果该 URL 允许公共写入,任何人都可以篡改你的配置。');
54
+
55
+ const res = await httpFetch(url, {
56
+ method: 'PUT',
57
+ headers: { 'content-type': 'application/json; charset=utf-8' },
58
+ body: JSON.stringify(profile, null, 2)
59
+ });
60
+ if (!res.ok) {
61
+ const text = await res.text().catch(() => '');
62
+ throw new Error(`push 失败:HTTP ${res.status} ${res.statusText} ${text ? `\n${text}` : ''}`);
63
+ }
64
+
65
+ // eslint-disable-next-line no-console
66
+ console.log('push 成功。');
67
+ }
68
+
69
+ async function pullProfileCmd(opts) {
70
+ const profileName = String(opts?.profile || 'default');
71
+ const url = normalizeUrl(opts?.url) || (await getEffectiveRemoteProfileUrl());
72
+ if (!url) throw new Error('缺少 --url,或未设置 config.remoteProfileUrl / SKILLMANAGER_PROFILE_URL');
73
+
74
+ // eslint-disable-next-line no-console
75
+ console.log(`下载 profile=${profileName} <- ${url}`);
76
+ const res = await httpFetch(url, { method: 'GET' });
77
+ if (!res.ok) {
78
+ const text = await res.text().catch(() => '');
79
+ throw new Error(`pull 失败:HTTP ${res.status} ${res.statusText} ${text ? `\n${text}` : ''}`);
80
+ }
81
+ const json = await res.json();
82
+
83
+ const appPaths = getAppPaths();
84
+ const profilesDir = appPaths.profilesDir;
85
+ await saveProfile({
86
+ profilesDir,
87
+ profileName,
88
+ selectedSkillIds: Array.isArray(json?.selectedSkillIds) ? json.selectedSkillIds : []
89
+ });
90
+
91
+ // eslint-disable-next-line no-console
92
+ console.log('pull 成功。');
93
+ }
94
+
95
+ module.exports = { showConfig, setDefaultProfileCmd, setRemoteProfileUrlCmd, pushProfileCmd, pullProfileCmd };
96
+
@@ -0,0 +1,79 @@
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, saveProfile } = require('../lib/profiles');
7
+ const { mapWithConcurrency } = require('../lib/concurrency');
8
+ const { getEffectiveDefaultProfile } = require('../lib/config');
9
+ const { launchSelectionUi } = require('../ui/server');
10
+
11
+ async function selectUi(opts) {
12
+ const paths = getAppPaths();
13
+ await ensureDir(paths.reposDir);
14
+ await ensureDir(paths.profilesDir);
15
+
16
+ const { sources } = await loadSourcesManifest();
17
+ const enabledSources = sources.filter((s) => s && s.enabled !== false);
18
+
19
+ const concurrency = Number(opts?.concurrency || process.env.SKILLMANAGER_CONCURRENCY || 3);
20
+ // eslint-disable-next-line no-console
21
+ console.log(`并发扫描:${Math.max(1, concurrency)}(可用 --concurrency 或环境变量 SKILLMANAGER_CONCURRENCY 调整)`);
22
+
23
+ const perSource = await mapWithConcurrency(enabledSources, concurrency, async (s) => {
24
+ try {
25
+ // eslint-disable-next-line no-console
26
+ console.log(`\n==> 扫描来源:${s.name || s.id}`);
27
+ const repoDir = await ensureRepo({ reposDir: paths.reposDir, source: s });
28
+ const skills = await scanSkillsInRepo({
29
+ sourceId: s.id,
30
+ sourceName: s.name || s.id,
31
+ repoDir
32
+ });
33
+ // eslint-disable-next-line no-console
34
+ console.log(` 找到 ${skills.length} 个 skills`);
35
+ return skills;
36
+ } catch (err) {
37
+ // eslint-disable-next-line no-console
38
+ console.warn(`警告:拉取/扫描来源失败,将跳过:${s.name || s.id}`);
39
+ // eslint-disable-next-line no-console
40
+ console.warn(err?.message || String(err));
41
+ return [];
42
+ }
43
+ });
44
+
45
+ const allSkills = perSource.flat();
46
+
47
+ const profileName = opts?.profile || (await getEffectiveDefaultProfile());
48
+ const existing = await loadProfile({ profilesDir: paths.profilesDir, profileName });
49
+ const initialSelected =
50
+ existing?.selectedSkillIds && Array.isArray(existing.selectedSkillIds)
51
+ ? existing.selectedSkillIds
52
+ : allSkills.map((s) => s.id);
53
+
54
+ const chosen = await launchSelectionUi({
55
+ title: `skillmanager · profile=${profileName}`,
56
+ skills: allSkills.map((s) => ({
57
+ id: s.id,
58
+ sourceId: s.sourceId,
59
+ sourceName: s.sourceName,
60
+ name: s.name,
61
+ description: s.description
62
+ })),
63
+ selectedSkillIds: initialSelected
64
+ });
65
+
66
+ const savedPath = await saveProfile({
67
+ profilesDir: paths.profilesDir,
68
+ profileName,
69
+ selectedSkillIds: chosen
70
+ });
71
+
72
+ // eslint-disable-next-line no-console
73
+ console.log(`已保存 profile:${profileName}`);
74
+ // eslint-disable-next-line no-console
75
+ console.log(`路径:${savedPath}`);
76
+ }
77
+
78
+ module.exports = { selectUi };
79
+
@@ -0,0 +1,103 @@
1
+ const { readUserSourcesManifest, writeUserSourcesManifest, loadSourcesManifest, getUserManifestPath } = require('../lib/manifest');
2
+ const { defaultSourceIdFromInput, parseGitHubRef } = require('../lib/source-utils');
3
+
4
+ function uniqueId(desired, existingIds) {
5
+ if (!existingIds.has(desired)) return desired;
6
+ for (let i = 2; i < 1000; i++) {
7
+ const next = `${desired}-${i}`;
8
+ if (!existingIds.has(next)) return next;
9
+ }
10
+ throw new Error(`无法生成唯一 id:${desired}`);
11
+ }
12
+
13
+ async function listSources() {
14
+ const { sources, manifestPath } = await loadSourcesManifest();
15
+ // eslint-disable-next-line no-console
16
+ console.log(`sources(来自:${manifestPath})`);
17
+ for (const s of sources) {
18
+ // eslint-disable-next-line no-console
19
+ console.log(
20
+ `- ${s.id} [${s.enabled === false ? 'disabled' : 'enabled'}] ${s.name || ''}\n repo=${s.repo || ''}\n openskillsRef=${s.openskillsRef || ''}`
21
+ );
22
+ }
23
+ // eslint-disable-next-line no-console
24
+ console.log(`\n用户配置文件路径:${getUserManifestPath()}`);
25
+ }
26
+
27
+ async function addSource(repoOrRef, opts) {
28
+ const { manifest, sources, userPath } = await readUserSourcesManifest();
29
+ const existingIds = new Set(sources.map((s) => s && s.id).filter(Boolean));
30
+
31
+ const gh = parseGitHubRef(repoOrRef);
32
+ const desiredId = opts?.id ? String(opts.id) : defaultSourceIdFromInput(repoOrRef);
33
+ const id = uniqueId(desiredId, existingIds);
34
+
35
+ const newSource = {
36
+ id,
37
+ name: opts?.name ? String(opts.name) : gh ? `${gh.owner}/${gh.repo}` : String(repoOrRef),
38
+ kind: 'git',
39
+ enabled: opts?.disabled ? false : true,
40
+ repo: gh?.httpsRepo || String(repoOrRef),
41
+ openskillsRef: opts?.ref ? String(opts.ref) : gh?.openskillsRef || undefined
42
+ };
43
+
44
+ const next = {
45
+ ...(manifest || {}),
46
+ version: Number(manifest?.version || 1),
47
+ sources: [...sources, newSource]
48
+ };
49
+
50
+ await writeUserSourcesManifest(next);
51
+
52
+ // eslint-disable-next-line no-console
53
+ console.log(`已添加来源:${id}`);
54
+ // eslint-disable-next-line no-console
55
+ console.log(`写入:${userPath}`);
56
+ // eslint-disable-next-line no-console
57
+ console.log(`repo=${newSource.repo}`);
58
+ // eslint-disable-next-line no-console
59
+ console.log(`openskillsRef=${newSource.openskillsRef || '(none)'}`);
60
+ }
61
+
62
+ async function removeSource(id) {
63
+ const { manifest, sources, userPath } = await readUserSourcesManifest();
64
+ const before = sources.length;
65
+ const nextSources = sources.filter((s) => s && s.id !== id);
66
+ if (nextSources.length === before) {
67
+ // eslint-disable-next-line no-console
68
+ console.log(`未找到来源:${id}`);
69
+ return;
70
+ }
71
+ const next = { ...(manifest || {}), sources: nextSources };
72
+ await writeUserSourcesManifest(next);
73
+ // eslint-disable-next-line no-console
74
+ console.log(`已移除来源:${id}`);
75
+ // eslint-disable-next-line no-console
76
+ console.log(`写入:${userPath}`);
77
+ }
78
+
79
+ async function setEnabled(id, enabled) {
80
+ const { manifest, sources, userPath } = await readUserSourcesManifest();
81
+ let found = false;
82
+ const nextSources = sources.map((s) => {
83
+ if (s && s.id === id) {
84
+ found = true;
85
+ return { ...s, enabled };
86
+ }
87
+ return s;
88
+ });
89
+ if (!found) {
90
+ // eslint-disable-next-line no-console
91
+ console.log(`未找到来源:${id}`);
92
+ return;
93
+ }
94
+ const next = { ...(manifest || {}), sources: nextSources };
95
+ await writeUserSourcesManifest(next);
96
+ // eslint-disable-next-line no-console
97
+ console.log(`已${enabled ? '启用' : '禁用'}来源:${id}`);
98
+ // eslint-disable-next-line no-console
99
+ console.log(`写入:${userPath}`);
100
+ }
101
+
102
+ module.exports = { listSources, addSource, removeSource, setEnabled };
103
+
@@ -0,0 +1,63 @@
1
+ const path = require('path');
2
+ const os = require('os');
3
+ const fsp = require('fs/promises');
4
+
5
+ const { listInstalledSkills } = require('../lib/installed');
6
+ const { syncAgents } = require('../lib/openskills');
7
+
8
+ function resolveTargetDir({ globalInstall, universal }) {
9
+ const folder = universal ? '.agent/skills' : '.claude/skills';
10
+ return globalInstall ? path.join(os.homedir(), folder) : path.join(process.cwd(), folder);
11
+ }
12
+
13
+ async function uninstall(opts, skillNames) {
14
+ const globalInstall = !!opts?.global;
15
+ const universal = !!opts?.universal;
16
+ const targetDir = resolveTargetDir({ globalInstall, universal });
17
+
18
+ const installed = await listInstalledSkills(targetDir);
19
+ const installedByName = new Map(installed.map((s) => [s.name, s]));
20
+
21
+ let toRemove = Array.isArray(skillNames) ? skillNames.filter(Boolean) : [];
22
+
23
+ if (opts?.all) {
24
+ toRemove = installed.map((s) => s.name);
25
+ }
26
+
27
+ toRemove = Array.from(new Set(toRemove)).filter((n) => installedByName.has(n));
28
+
29
+ if (toRemove.length === 0) {
30
+ // eslint-disable-next-line no-console
31
+ console.log(`未选择任何可卸载的 skill。目标目录:${targetDir}`);
32
+ // eslint-disable-next-line no-console
33
+ console.log('用法:');
34
+ // eslint-disable-next-line no-console
35
+ console.log(' - skillmanager webui --mode uninstall');
36
+ // eslint-disable-next-line no-console
37
+ console.log(' - skillmanager uninstall <skill1> <skill2>');
38
+ // eslint-disable-next-line no-console
39
+ console.log(' - skillmanager uninstall --all');
40
+ return;
41
+ }
42
+
43
+ // eslint-disable-next-line no-console
44
+ console.log(`将卸载 ${toRemove.length} 个 skills(目标:${targetDir})…`);
45
+
46
+ for (const name of toRemove) {
47
+ const entry = installedByName.get(name);
48
+ if (!entry) continue;
49
+ // eslint-disable-next-line no-console
50
+ console.log(`- remove ${name}`);
51
+ await fsp.rm(entry.skillDir, { recursive: true, force: true });
52
+ }
53
+
54
+ if (opts?.sync !== false) {
55
+ await syncAgents({ output: opts?.output, cwd: process.cwd() });
56
+ }
57
+
58
+ // eslint-disable-next-line no-console
59
+ console.log('完成。');
60
+ }
61
+
62
+ module.exports = { uninstall };
63
+
@@ -0,0 +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
+
@@ -0,0 +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
+
@@ -0,0 +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
+
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ module.exports = {};
2
+
@@ -0,0 +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
+