@wang121ye/skillmanager 0.0.2 → 0.0.4

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.md CHANGED
@@ -4,6 +4,32 @@
4
4
 
5
5
  本项目 **基于 `openskills`** 实现安装与 `AGENTS.md` 同步。
6
6
 
7
+ ## 环境要求与兼容性(重要)
8
+
9
+ `skillmanager` 会调用系统里的 `git` 拉取/更新 skills 来源仓库,并通过 `openskills` 执行安装与 `AGENTS.md` 同步。因此你的环境版本过低时,可能出现“看起来配置没问题但某些机器上失败”的情况。
10
+
11
+ - **Node.js(用于运行 openskills)**:建议 **>= 20.6.0**
12
+ - 低于该版本可能出现语法错误(例如依赖使用了 RegExp `/v` flag)。
13
+ - **openskills**:建议 **>= 1.5.0**(本项目依赖与运行时行为以该版本为基准)
14
+ - **git**:建议 **>= 2.34.0**
15
+ - 低版本在 GitHub HTTPS + partial clone(如 `--filter=blob:none`)场景下,可能更容易遇到 TLS/gnutls 相关中断(如 `gnutls_handshake()`)。
16
+
17
+ 常见规避方案:
18
+
19
+ - **升级 git / Node / openskills**(推荐根治)
20
+ - **改用 SSH 拉取 GitHub 仓库**(绕开 HTTPS/TLS):
21
+
22
+ ```bash
23
+ export SKILLMANAGER_GIT_PROTOCOL=ssh
24
+ skillmanager webui
25
+ ```
26
+
27
+ - **降低并发**(网络/中间设备对并发连接敏感时):
28
+
29
+ ```bash
30
+ skillmanager webui --concurrency 1
31
+ ```
32
+
7
33
  ## 安装与使用
8
34
 
9
35
  ### 全局安装(推荐)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wang121ye/skillmanager",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Cross-platform agent skill manager (bootstrap + optional Web UI selection) built on top of openskills.",
5
5
  "keywords": [
6
6
  "skillmanager",
@@ -45,6 +45,6 @@
45
45
  "gray-matter": "^4.0.3",
46
46
  "openskills": "^1.5.0",
47
47
  "simple-git": "^3.27.0",
48
- "undici": "^7.4.0"
48
+ "undici": "^6.23.0"
49
49
  }
50
50
  }
package/src/cli.js CHANGED
@@ -94,7 +94,7 @@ async function main() {
94
94
  config
95
95
  .command('set-remote-profile-url')
96
96
  .description('设置远端 profile URL(用于 config push/pull)。')
97
- .argument('<url>', '例如 https://<bucket>.<region>.aliyuncs.com/skillmanager_profile.json')
97
+ .argument('<url>', '例如 https://<bucket>.<region>.aliyuncs.com/skillmanager/')
98
98
  .action(async (url) => {
99
99
  await setRemoteProfileUrlCmd(url);
100
100
  });
@@ -10,6 +10,7 @@ const path = require('path');
10
10
  const os = require('os');
11
11
  const { installSourceRef, syncAgents } = require('../lib/openskills');
12
12
  const { installFromLocalSkillDir } = require('../lib/local-install');
13
+ const { warnPrereqs } = require('../lib/prereqs');
13
14
 
14
15
 
15
16
  function uniq(arr) {
@@ -17,6 +18,7 @@ function uniq(arr) {
17
18
  }
18
19
 
19
20
  async function bootstrap(opts) {
21
+ await warnPrereqs({ needGit: true, needOpenSkills: true });
20
22
  const paths = getAppPaths();
21
23
  await ensureDir(paths.reposDir);
22
24
  await ensureDir(paths.profilesDir);
@@ -10,6 +10,7 @@ const { syncAgents, runOpenSkills } = require('../lib/openskills');
10
10
  const { installFromLocalSkillDir } = require('../lib/local-install');
11
11
  const { mapWithConcurrency } = require('../lib/concurrency');
12
12
  const { getEffectiveDefaultProfile } = require('../lib/config');
13
+ const { warnPrereqs } = require('../lib/prereqs');
13
14
 
14
15
 
15
16
  function uniq(arr) {
@@ -17,6 +18,7 @@ function uniq(arr) {
17
18
  }
18
19
 
19
20
  async function update(opts) {
21
+ await warnPrereqs({ needGit: true, needOpenSkills: true });
20
22
  const globalInstall = !!opts?.global;
21
23
  const universal = !!opts?.universal;
22
24
 
@@ -14,6 +14,7 @@ const { installFromLocalSkillDir } = require('../lib/local-install');
14
14
  const { listInstalledSkills } = require('../lib/installed');
15
15
  const { syncAgents } = require('../lib/openskills');
16
16
  const { launchSelectionUi } = require('../ui/server');
17
+ const { warnPrereqs } = require('../lib/prereqs');
17
18
 
18
19
  function resolveTargetDir({ globalInstall, universal }) {
19
20
  const folder = universal ? '.agent/skills' : '.claude/skills';
@@ -21,6 +22,7 @@ function resolveTargetDir({ globalInstall, universal }) {
21
22
  }
22
23
 
23
24
  async function webui(opts) {
25
+ await warnPrereqs({ needGit: true, needOpenSkills: true });
24
26
  const modeRaw = String(opts?.mode || 'install').toLowerCase();
25
27
  const mode = modeRaw === 'uninstall' ? 'uninstall' : 'install';
26
28
 
package/src/lib/http.js CHANGED
@@ -1,4 +1,6 @@
1
1
  let proxyInitialized = false;
2
+ let fetchImpl = globalThis.fetch;
3
+ let undiciModule = null;
2
4
 
3
5
  function getProxyUrlFromEnv() {
4
6
  return (
@@ -11,18 +13,35 @@ function getProxyUrlFromEnv() {
11
13
  );
12
14
  }
13
15
 
16
+ function getUndici() {
17
+ if (undiciModule) return undiciModule;
18
+ try {
19
+ // eslint-disable-next-line global-require
20
+ undiciModule = require('undici');
21
+ return undiciModule;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
14
27
  function ensureProxyInitialized() {
15
28
  if (proxyInitialized) return;
16
29
  proxyInitialized = true;
17
30
 
18
31
  const proxyUrl = getProxyUrlFromEnv();
32
+ const undici = getUndici();
33
+
34
+ // 如果有 undici,就优先用它的 fetch(这样 setGlobalDispatcher 才能生效)
35
+ if (undici?.fetch) fetchImpl = undici.fetch.bind(undici);
36
+
19
37
  if (!proxyUrl) return;
20
38
 
21
39
  try {
22
- // Node 20+ ships undici. Use ProxyAgent so global fetch respects proxy.
23
- // eslint-disable-next-line global-require
24
- const { ProxyAgent, setGlobalDispatcher } = require('undici');
25
- setGlobalDispatcher(new ProxyAgent(proxyUrl));
40
+ if (!undici?.ProxyAgent || !undici?.setGlobalDispatcher) {
41
+ throw new Error('未找到 undici ProxyAgent/setGlobalDispatcher');
42
+ }
43
+
44
+ undici.setGlobalDispatcher(new undici.ProxyAgent(proxyUrl));
26
45
  // eslint-disable-next-line no-console
27
46
  console.log(`使用代理:${proxyUrl}`);
28
47
  } catch (e) {
@@ -35,7 +54,10 @@ function ensureProxyInitialized() {
35
54
 
36
55
  async function httpFetch(url, options) {
37
56
  ensureProxyInitialized();
38
- return await fetch(url, options);
57
+ if (typeof fetchImpl !== 'function') {
58
+ throw new Error('当前 Node 环境不支持 fetch(需要 Node 18+ 或提供 undici.fetch)');
59
+ }
60
+ return await fetchImpl(url, options);
39
61
  }
40
62
 
41
63
  module.exports = { httpFetch };
@@ -0,0 +1,102 @@
1
+ const { execFile } = require('child_process');
2
+ const fsp = require('fs/promises');
3
+
4
+ const MIN_GIT = { major: 2, minor: 34, patch: 0 };
5
+ const MIN_NODE_FOR_OPENSKILLS = { major: 20, minor: 6, patch: 0 };
6
+ const MIN_OPENSKILLS = { major: 1, minor: 5, patch: 0 };
7
+
8
+ let warnedOnce = false;
9
+
10
+ function parseSemverLike(input) {
11
+ const s = String(input || '');
12
+ const m = s.match(/(\d+)\.(\d+)\.(\d+)/);
13
+ if (!m) return null;
14
+ return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
15
+ }
16
+
17
+ function cmpVersion(a, b) {
18
+ if (!a || !b) return 0;
19
+ if (a.major !== b.major) return a.major - b.major;
20
+ if (a.minor !== b.minor) return a.minor - b.minor;
21
+ return a.patch - b.patch;
22
+ }
23
+
24
+ function fmt(v) {
25
+ if (!v) return '(unknown)';
26
+ return `${v.major}.${v.minor}.${v.patch}`;
27
+ }
28
+
29
+ async function execFileText(cmd, args) {
30
+ return await new Promise((resolve, reject) => {
31
+ execFile(cmd, args, { windowsHide: true }, (err, stdout, stderr) => {
32
+ if (err) return reject(err);
33
+ resolve(String(stdout || stderr || '').trim());
34
+ });
35
+ });
36
+ }
37
+
38
+ async function getGitVersion() {
39
+ const out = await execFileText('git', ['--version']).catch(() => null);
40
+ if (!out) return null;
41
+ // e.g. "git version 2.25.1" / "git version 2.44.0.windows.1"
42
+ return parseSemverLike(out);
43
+ }
44
+
45
+ async function getOpenSkillsVersion() {
46
+ try {
47
+ const pkgPath = require.resolve('openskills/package.json');
48
+ const txt = await fsp.readFile(pkgPath, 'utf8');
49
+ const json = JSON.parse(txt);
50
+ return parseSemverLike(json?.version);
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ function getNodeVersion() {
57
+ return parseSemverLike(process.versions.node);
58
+ }
59
+
60
+ async function warnPrereqs({ needGit = false, needOpenSkills = false } = {}) {
61
+ // 只在实际运行(非 help)且需要相关能力时提示一次,避免刷屏
62
+ if (warnedOnce) return;
63
+ warnedOnce = true;
64
+
65
+ const lines = [];
66
+
67
+ if (needGit) {
68
+ const gitV = await getGitVersion();
69
+ if (!gitV) {
70
+ lines.push('- git:未检测到 `git`(需要安装 git 才能拉取来源仓库)。');
71
+ } else if (cmpVersion(gitV, MIN_GIT) < 0) {
72
+ lines.push(
73
+ `- git:检测到 ${fmt(gitV)},建议至少 ${fmt(MIN_GIT)}。低版本在 GitHub HTTPS/partial clone 场景下可能出现 TLS/gnutls 握手中断(例如 gnutls_handshake)。`
74
+ );
75
+ lines.push(' - 建议:升级 git(或设置 `SKILLMANAGER_GIT_PROTOCOL=ssh` 使用 SSH 拉取;并避免开启 partial clone filter)。');
76
+ }
77
+ }
78
+
79
+ if (needOpenSkills) {
80
+ const nodeV = getNodeVersion();
81
+ if (nodeV && cmpVersion(nodeV, MIN_NODE_FOR_OPENSKILLS) < 0) {
82
+ lines.push(
83
+ `- Node.js:检测到 ${fmt(nodeV)},openskills 需要至少 ${fmt(MIN_NODE_FOR_OPENSKILLS)}(否则可能出现语法错误,例如 RegExp /v flag)。`
84
+ );
85
+ }
86
+
87
+ const osV = await getOpenSkillsVersion();
88
+ if (!osV) {
89
+ lines.push('- openskills:未检测到依赖(或无法读取版本)。如需 sync/安装来源,请确保已安装 openskills。');
90
+ } else if (cmpVersion(osV, MIN_OPENSKILLS) < 0) {
91
+ lines.push(`- openskills:检测到 ${fmt(osV)},建议至少 ${fmt(MIN_OPENSKILLS)}。`);
92
+ }
93
+ }
94
+
95
+ if (lines.length) {
96
+ // eslint-disable-next-line no-console
97
+ console.warn(['\n⚠️ 环境兼容性提示(skillmanager)', ...lines, ''].join('\n'));
98
+ }
99
+ }
100
+
101
+ module.exports = { warnPrereqs };
102
+