wangchuan 1.0.0

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.
Files changed (73) hide show
  1. package/.wangchuan/config.example.json +43 -0
  2. package/README.md +210 -0
  3. package/dist/bin/wangchuan.d.ts +9 -0
  4. package/dist/bin/wangchuan.d.ts.map +1 -0
  5. package/dist/bin/wangchuan.js +93 -0
  6. package/dist/bin/wangchuan.js.map +1 -0
  7. package/dist/src/commands/diff.d.ts +9 -0
  8. package/dist/src/commands/diff.d.ts.map +1 -0
  9. package/dist/src/commands/diff.js +96 -0
  10. package/dist/src/commands/diff.js.map +1 -0
  11. package/dist/src/commands/init.d.ts +6 -0
  12. package/dist/src/commands/init.d.ts.map +1 -0
  13. package/dist/src/commands/init.js +49 -0
  14. package/dist/src/commands/init.js.map +1 -0
  15. package/dist/src/commands/list.d.ts +9 -0
  16. package/dist/src/commands/list.d.ts.map +1 -0
  17. package/dist/src/commands/list.js +45 -0
  18. package/dist/src/commands/list.js.map +1 -0
  19. package/dist/src/commands/pull.d.ts +6 -0
  20. package/dist/src/commands/pull.d.ts.map +1 -0
  21. package/dist/src/commands/pull.js +53 -0
  22. package/dist/src/commands/pull.js.map +1 -0
  23. package/dist/src/commands/push.d.ts +9 -0
  24. package/dist/src/commands/push.d.ts.map +1 -0
  25. package/dist/src/commands/push.js +72 -0
  26. package/dist/src/commands/push.js.map +1 -0
  27. package/dist/src/commands/status.d.ts +6 -0
  28. package/dist/src/commands/status.d.ts.map +1 -0
  29. package/dist/src/commands/status.js +86 -0
  30. package/dist/src/commands/status.js.map +1 -0
  31. package/dist/src/core/config.d.ts +27 -0
  32. package/dist/src/core/config.d.ts.map +1 -0
  33. package/dist/src/core/config.js +110 -0
  34. package/dist/src/core/config.js.map +1 -0
  35. package/dist/src/core/crypto.d.ts +24 -0
  36. package/dist/src/core/crypto.d.ts.map +1 -0
  37. package/dist/src/core/crypto.js +89 -0
  38. package/dist/src/core/crypto.js.map +1 -0
  39. package/dist/src/core/git.d.ts +18 -0
  40. package/dist/src/core/git.d.ts.map +1 -0
  41. package/dist/src/core/git.js +79 -0
  42. package/dist/src/core/git.js.map +1 -0
  43. package/dist/src/core/sync.d.ts +27 -0
  44. package/dist/src/core/sync.d.ts.map +1 -0
  45. package/dist/src/core/sync.js +253 -0
  46. package/dist/src/core/sync.js.map +1 -0
  47. package/dist/src/types.d.ts +95 -0
  48. package/dist/src/types.d.ts.map +1 -0
  49. package/dist/src/types.js +5 -0
  50. package/dist/src/types.js.map +1 -0
  51. package/dist/src/utils/linediff.d.ts +22 -0
  52. package/dist/src/utils/linediff.d.ts.map +1 -0
  53. package/dist/src/utils/linediff.js +80 -0
  54. package/dist/src/utils/linediff.js.map +1 -0
  55. package/dist/src/utils/logger.d.ts +13 -0
  56. package/dist/src/utils/logger.d.ts.map +1 -0
  57. package/dist/src/utils/logger.js +34 -0
  58. package/dist/src/utils/logger.js.map +1 -0
  59. package/dist/src/utils/prompt.d.ts +14 -0
  60. package/dist/src/utils/prompt.d.ts.map +1 -0
  61. package/dist/src/utils/prompt.js +37 -0
  62. package/dist/src/utils/prompt.js.map +1 -0
  63. package/dist/src/utils/validator.d.ts +17 -0
  64. package/dist/src/utils/validator.d.ts.map +1 -0
  65. package/dist/src/utils/validator.js +33 -0
  66. package/dist/src/utils/validator.js.map +1 -0
  67. package/dist/test/crypto.test.d.ts +5 -0
  68. package/dist/test/crypto.test.d.ts.map +1 -0
  69. package/dist/test/crypto.test.js +93 -0
  70. package/dist/test/crypto.test.js.map +1 -0
  71. package/package.json +56 -0
  72. package/skill/SKILL.md +67 -0
  73. package/skill/wangchuan-skill.sh +54 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * prompt.ts — 交互式用户确认工具
3
+ *
4
+ * 提供单文件冲突提示和批量冲突策略选择,
5
+ * 支持 TTY 交互和 CI 非交互模式(环境变量 WANGCHUAN_NONINTERACTIVE=1)。
6
+ */
7
+ import readline from 'readline';
8
+ /** 非交互模式的默认策略(CI 环境) */
9
+ const NON_INTERACTIVE_DEFAULT = 'skip';
10
+ /**
11
+ * 询问单个冲突文件的处理方式。
12
+ * 返回 overwrite_all / skip_all 时,调用方应将该策略用于后续所有冲突。
13
+ */
14
+ export async function askConflict(repoRel) {
15
+ // 非交互模式(CI / pipe)
16
+ if (process.env['WANGCHUAN_NONINTERACTIVE'] === '1' || !process.stdin.isTTY) {
17
+ return NON_INTERACTIVE_DEFAULT;
18
+ }
19
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
20
+ return new Promise(resolve => {
21
+ process.stdout.write(`\n ⚡ 冲突: ${repoRel}\n` +
22
+ ` 本地文件已存在且内容不同。\n` +
23
+ ` [o] 覆盖本地 [s] 跳过(保留本地) [A] 全部覆盖 [S] 全部跳过\n` +
24
+ ` 请选择 [o/s/A/S]: `);
25
+ rl.once('line', (ans) => {
26
+ rl.close();
27
+ switch (ans.trim()) {
28
+ case 'o': return resolve('overwrite');
29
+ case 'A': return resolve('overwrite_all');
30
+ case 'S': return resolve('skip_all');
31
+ case 's':
32
+ default: return resolve('skip');
33
+ }
34
+ });
35
+ });
36
+ }
37
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../../src/utils/prompt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,QAAQ,MAAM,UAAU,CAAC;AAShC,wBAAwB;AACxB,MAAM,uBAAuB,GAAqB,MAAM,CAAC;AAEzD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAe;IAC/C,mBAAmB;IACnB,IAAI,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QAC5E,OAAO,uBAAuB,CAAC;IACjC,CAAC;IAED,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEtF,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,aAAa,OAAO,IAAI;YACxB,sBAAsB;YACtB,mDAAmD;YACnD,mBAAmB,CACpB,CAAC;QAEF,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAW,EAAE,EAAE;YAC9B,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,QAAQ,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;gBACnB,KAAK,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC,WAAW,CAAC,CAAC;gBACtC,KAAK,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC,eAAe,CAAC,CAAC;gBAC1C,KAAK,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;gBACrC,KAAK,GAAG,CAAC;gBACT,OAAO,CAAC,CAAE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;YACnC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * validator.ts — 通用校验函数
3
+ *
4
+ * requireInit 使用 TypeScript asserts 类型谓词,调用后编译器收窄 cfg 为非 null。
5
+ * 必须声明显式接口(TS2775:assertion 函数不能通过推断的 const 对象调用)。
6
+ */
7
+ import type { WangchuanConfig } from '../types.js';
8
+ /** 显式接口确保 asserts 签名可被编译器识别 */
9
+ interface Validator {
10
+ pathExists(p: string): boolean;
11
+ isGitUrl(url: string): boolean;
12
+ containsSensitiveData(content: string): boolean;
13
+ requireInit(cfg: WangchuanConfig | null): asserts cfg is WangchuanConfig;
14
+ }
15
+ export declare const validator: Validator;
16
+ export {};
17
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/utils/validator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAUnD,+BAA+B;AAC/B,UAAU,SAAS;IACjB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAChD,WAAW,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI,eAAe,CAAC;CAC1E;AAED,eAAO,MAAM,SAAS,EAAE,SAmBvB,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * validator.ts — 通用校验函数
3
+ *
4
+ * requireInit 使用 TypeScript asserts 类型谓词,调用后编译器收窄 cfg 为非 null。
5
+ * 必须声明显式接口(TS2775:assertion 函数不能通过推断的 const 对象调用)。
6
+ */
7
+ import fs from 'fs';
8
+ const SENSITIVE_PATTERNS = [
9
+ /sk-[A-Za-z0-9]{20,}/,
10
+ /api[_-]?key\s*[:=]\s*["']?\S+/i,
11
+ /token\s*[:=]\s*["']?\S{16,}/i,
12
+ /password\s*[:=]\s*["']?\S+/i,
13
+ /secret\s*[:=]\s*["']?\S+/i,
14
+ ];
15
+ export const validator = {
16
+ pathExists(p) {
17
+ return fs.existsSync(p);
18
+ },
19
+ isGitUrl(url) {
20
+ if (!url)
21
+ return false;
22
+ return /^(git@|https?:\/\/).+\.(git)?/.test(url.trim());
23
+ },
24
+ containsSensitiveData(content) {
25
+ return SENSITIVE_PATTERNS.some(re => re.test(content));
26
+ },
27
+ requireInit(cfg) {
28
+ if (cfg === null) {
29
+ throw new Error('忘川尚未初始化,请先运行: wangchuan init --repo <仓库地址>');
30
+ }
31
+ },
32
+ };
33
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../../src/utils/validator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpB,MAAM,kBAAkB,GAAsB;IAC5C,qBAAqB;IACrB,gCAAgC;IAChC,8BAA8B;IAC9B,6BAA6B;IAC7B,2BAA2B;CAC5B,CAAC;AAUF,MAAM,CAAC,MAAM,SAAS,GAAc;IAClC,UAAU,CAAC,CAAS;QAClB,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IAED,QAAQ,CAAC,GAAW;QAClB,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC;QACvB,OAAO,+BAA+B,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,qBAAqB,CAAC,OAAe;QACnC,OAAO,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,WAAW,CAAC,GAA2B;QACrC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * crypto.test.ts — AES-256-GCM 加解密模块单元测试
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=crypto.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.test.d.ts","sourceRoot":"","sources":["../../test/crypto.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * crypto.test.ts — AES-256-GCM 加解密模块单元测试
3
+ */
4
+ import { describe, it, before, after } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import fs from 'fs';
7
+ import os from 'os';
8
+ import path from 'path';
9
+ import { cryptoEngine } from '../src/core/crypto.js';
10
+ const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'wc-test-'));
11
+ const KEY = path.join(TMP, 'master.key');
12
+ after(() => {
13
+ fs.rmSync(TMP, { recursive: true, force: true });
14
+ });
15
+ describe('cryptoEngine.generateKey', () => {
16
+ it('生成 64 字符十六进制密钥文件', () => {
17
+ cryptoEngine.generateKey(KEY);
18
+ const hex = fs.readFileSync(KEY, 'utf-8').trim();
19
+ assert.equal(hex.length, 64);
20
+ assert.match(hex, /^[0-9a-f]+$/);
21
+ });
22
+ it('文件权限为 0o600', () => {
23
+ if (process.platform !== 'win32') {
24
+ const stat = fs.statSync(KEY);
25
+ assert.equal((stat.mode & 0o777).toString(8), '600');
26
+ }
27
+ });
28
+ });
29
+ describe('cryptoEngine.hasKey', () => {
30
+ it('密钥存在时返回 true', () => assert.equal(cryptoEngine.hasKey(KEY), true));
31
+ it('密钥不存在时返回 false', () => assert.equal(cryptoEngine.hasKey(`${KEY}.nope`), false));
32
+ });
33
+ describe('cryptoEngine.encryptString / decryptString', () => {
34
+ const cases = [
35
+ '普通 ASCII text',
36
+ '中文内容:忘川永不遗忘',
37
+ '{"api_key":"sk-test-1234","model":"claude-3"}',
38
+ 'A'.repeat(10_000),
39
+ '',
40
+ ];
41
+ for (const plain of cases) {
42
+ it(`往返一致: "${plain.slice(0, 40)}"`, () => {
43
+ const enc = cryptoEngine.encryptString(plain, KEY);
44
+ assert.ok(typeof enc === 'string' && enc.length > 0);
45
+ const dec = cryptoEngine.decryptString(enc, KEY);
46
+ assert.equal(dec, plain);
47
+ });
48
+ }
49
+ it('相同明文两次加密结果不同(IV 随机)', () => {
50
+ const a = cryptoEngine.encryptString('hello', KEY);
51
+ const b = cryptoEngine.encryptString('hello', KEY);
52
+ assert.notEqual(a, b);
53
+ });
54
+ it('篡改密文后解密抛出错误(GCM AuthTag 校验)', () => {
55
+ const enc = cryptoEngine.encryptString('sensitive', KEY);
56
+ const buf = Buffer.from(enc, 'base64');
57
+ const last = buf[buf.length - 1];
58
+ assert.ok(last !== undefined, '密文不应为空');
59
+ buf[buf.length - 1] = last ^ 0xff;
60
+ const tampered = buf.toString('base64');
61
+ assert.throws(() => cryptoEngine.decryptString(tampered, KEY));
62
+ });
63
+ });
64
+ describe('cryptoEngine.encryptFile / decryptFile', () => {
65
+ const PLAIN_FILE = path.join(TMP, 'plain.txt');
66
+ const ENC_FILE = path.join(TMP, 'plain.txt.enc');
67
+ const DEC_FILE = path.join(TMP, 'plain.dec.txt');
68
+ const CONTENT = '敏感配置\nAPI_KEY=sk-xxxxx\n用户信息';
69
+ before(() => {
70
+ fs.writeFileSync(PLAIN_FILE, CONTENT, 'utf-8');
71
+ });
72
+ it('加密后文件不含明文内容', () => {
73
+ cryptoEngine.encryptFile(PLAIN_FILE, ENC_FILE, KEY);
74
+ const raw = fs.readFileSync(ENC_FILE, 'utf-8');
75
+ assert.ok(!raw.includes('敏感配置'));
76
+ assert.ok(!raw.includes('sk-xxxxx'));
77
+ });
78
+ it('解密后内容与原文一致', () => {
79
+ cryptoEngine.decryptFile(ENC_FILE, DEC_FILE, KEY);
80
+ assert.equal(fs.readFileSync(DEC_FILE, 'utf-8'), CONTENT);
81
+ });
82
+ });
83
+ describe('cryptoEngine 错误路径', () => {
84
+ it('密钥文件不存在时抛出友好错误', () => {
85
+ assert.throws(() => cryptoEngine.encryptString('x', '/tmp/nonexistent.key'), /密钥文件不存在/);
86
+ });
87
+ it('无效密钥格式时抛出错误', () => {
88
+ const badKey = path.join(TMP, 'bad.key');
89
+ fs.writeFileSync(badKey, 'tooshort', 'utf-8');
90
+ assert.throws(() => cryptoEngine.encryptString('x', badKey), /密钥文件格式无效/);
91
+ });
92
+ });
93
+ //# sourceMappingURL=crypto.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.test.js","sourceRoot":"","sources":["../../test/crypto.test.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "wangchuan",
3
+ "version": "1.0.0",
4
+ "description": "忘川 · AI 记忆同步系统 — 智能体记忆永不遗失",
5
+ "bin": {
6
+ "wangchuan": "./dist/bin/wangchuan.js"
7
+ },
8
+ "files": [
9
+ "dist/",
10
+ "skill/",
11
+ ".wangchuan/config.example.json",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "postbuild": "chmod +x dist/bin/wangchuan.js",
18
+ "prepublishOnly": "npm run build",
19
+ "dev": "tsx bin/wangchuan.ts",
20
+ "test": "node --import tsx/esm --test test/crypto.test.ts",
21
+ "typecheck": "tsc --noEmit"
22
+ },
23
+ "keywords": [
24
+ "ai",
25
+ "memory",
26
+ "sync",
27
+ "agent",
28
+ "claude",
29
+ "gemini",
30
+ "openclaw",
31
+ "encryption"
32
+ ],
33
+ "author": "SUpermin6u",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/SUpermin6u/wangchuan.git"
38
+ },
39
+ "homepage": "https://github.com/SUpermin6u/wangchuan",
40
+ "dependencies": {
41
+ "chalk": "^5.3.0",
42
+ "commander": "^12.1.0",
43
+ "dotenv": "^16.4.5",
44
+ "ora": "^8.1.1",
45
+ "simple-git": "^3.27.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.19.15",
49
+ "tsx": "^4.21.0",
50
+ "typescript": "^5.9.3"
51
+ },
52
+ "engines": {
53
+ "node": ">=18.19.0"
54
+ },
55
+ "type": "module"
56
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,67 @@
1
+ # wangchuan — 智能体记忆同步技能
2
+
3
+ ## 技能简介
4
+
5
+ 忘川(Wangchuan)AI 记忆同步系统的 OpenClaw Skill 封装。
6
+ 在对话中直接调用以同步 AI 智能体的配置文件,跨环境永不遗失记忆。
7
+
8
+ ## 命令速查
9
+
10
+ ```
11
+ wangchuan list [--agent openclaw|claude|gemini] 列出所有受管配置项
12
+ wangchuan status [--agent openclaw|claude|gemini] 查看同步状态和差异摘要
13
+ wangchuan diff [--agent openclaw|claude|gemini] 显示行级文件差异
14
+ wangchuan pull [--agent openclaw|claude|gemini] 拉取远端配置,还原到本地
15
+ wangchuan push [--agent <name>] [-m "<描述>"] 加密推送本地配置到远端
16
+ wangchuan init --repo <git地址> 首次初始化
17
+ ```
18
+
19
+ ## 调用示例
20
+
21
+ > 帮我列出忘川管理的所有配置文件
22
+
23
+ > 查看一下忘川的同步状态
24
+
25
+ > 只看 openclaw 的配置差异
26
+
27
+ > 帮我把最新的 AI 记忆同步到本地
28
+
29
+ > 只拉取 openclaw 的配置
30
+
31
+ > 我修改了 MEMORY.md,帮我推送一下,描述是"更新项目记忆"
32
+
33
+ > 只推送 claude 的配置
34
+
35
+ ## 输出说明
36
+
37
+ ### list
38
+ - `✔ 本地 ✔ 仓库` — 两侧均存在,已同步
39
+ - `✔ 本地 · 仓库` — 本地有但未推送
40
+ - `✖ 本地 ✔ 仓库` — 仓库有但本地缺失,执行 pull 还原
41
+ - `[enc]` — 该文件加密存储(AES-256-GCM)
42
+
43
+ ### diff
44
+ - `+` 绿色行 — 本地新增内容
45
+ - `-` 红色行 — 仓库有但本地已删除
46
+ - 灰色行 — 上下文(未变化)
47
+ - `[enc]` — 加密文件已自动解密后再对比
48
+
49
+ ### push / pull
50
+ - `[已加密]` / `[已解密]` — 经过 AES-256-GCM 处理
51
+
52
+ ## --agent 说明
53
+
54
+ 所有命令均支持 `--agent` 过滤,只操作指定智能体的配置:
55
+
56
+ | 值 | 说明 |
57
+ |----|------|
58
+ | `openclaw` | ~/.openclaw/workspace/ 下的所有配置 |
59
+ | `claude` | ~/.claude/.claude.json(需在 config.json 中 enabled:true)|
60
+ | `gemini` | ~/.gemini/ 下的配置(需在 config.json 中 enabled:true)|
61
+
62
+ ## 前置条件
63
+
64
+ 1. Node.js ≥ 18
65
+ 2. 已执行过 `wangchuan init`(~/.wangchuan/config.json 存在)
66
+ 3. 本地 SSH 密钥可访问目标 git 仓库
67
+ 4. 跨机器迁移时需手动复制 `~/.wangchuan/master.key`
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ # wangchuan-skill.sh — OpenClaw Skill 脚本
3
+ #
4
+ # 用法(由 OpenClaw / Claude 调用):
5
+ # wangchuan-skill.sh pull [--agent openclaw|claude|gemini]
6
+ # wangchuan-skill.sh push [--agent <name>] [--message "<msg>"]
7
+ # wangchuan-skill.sh status [--agent <name>]
8
+ # wangchuan-skill.sh diff [--agent <name>]
9
+ # wangchuan-skill.sh list [--agent <name>]
10
+ # wangchuan-skill.sh init --repo <url>
11
+ #
12
+ # 环境变量:
13
+ # WANGCHUAN_DIR 可覆盖 wangchuan 安装路径(默认 ~/wangchuan)
14
+ # WANGCHUAN_LOG_LEVEL 日志级别 debug|info|warn|error
15
+
16
+ set -euo pipefail
17
+
18
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
+ WC_DIR="${WANGCHUAN_DIR:-"$(dirname "$SCRIPT_DIR")"}"
20
+ BIN="$WC_DIR/dist/bin/wangchuan.js"
21
+
22
+ if [[ ! -f "$BIN" ]]; then
23
+ echo "✖ 找不到 wangchuan 编译产物: $BIN" >&2
24
+ echo " 请先在 $WC_DIR 目录执行: npm run build" >&2
25
+ exit 1
26
+ fi
27
+
28
+ if ! command -v node &>/dev/null; then
29
+ echo "✖ 未找到 Node.js,请先安装 (https://nodejs.org)" >&2
30
+ exit 1
31
+ fi
32
+
33
+ NODE_MAJOR="$(node --version | sed 's/v//' | cut -d. -f1)"
34
+ if (( NODE_MAJOR < 18 )); then
35
+ echo "✖ Node.js 版本过低,需要 >= 18(当前: $(node --version))" >&2
36
+ exit 1
37
+ fi
38
+
39
+ CMD="${1:-status}"
40
+ shift || true # 移除第一个参数,剩余参数透传
41
+
42
+ case "$CMD" in
43
+ pull|push|status|diff|list)
44
+ node "$BIN" "$CMD" "$@"
45
+ ;;
46
+ init)
47
+ node "$BIN" init "$@"
48
+ ;;
49
+ *)
50
+ echo "✖ 未知命令: $CMD" >&2
51
+ echo " 可用命令: pull | push | status | diff | list | init" >&2
52
+ exit 1
53
+ ;;
54
+ esac