@okclaw-build/cli 1.0.0-beta.5 → 1.0.0-beta.50

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 (85) hide show
  1. package/dist/commands/backup.d.ts +16 -0
  2. package/dist/commands/backup.js +72 -0
  3. package/dist/commands/backup.js.map +1 -0
  4. package/dist/commands/check.js +22 -6
  5. package/dist/commands/check.js.map +1 -1
  6. package/dist/commands/edit.d.ts +2 -7
  7. package/dist/commands/edit.js +27 -15
  8. package/dist/commands/edit.js.map +1 -1
  9. package/dist/commands/feed.js +63 -19
  10. package/dist/commands/feed.js.map +1 -1
  11. package/dist/commands/install.js +57 -9
  12. package/dist/commands/install.js.map +1 -1
  13. package/dist/commands/migration.d.ts +1 -0
  14. package/dist/commands/migration.js +205 -0
  15. package/dist/commands/migration.js.map +1 -0
  16. package/dist/commands/restore.d.ts +16 -0
  17. package/dist/commands/restore.js +67 -0
  18. package/dist/commands/restore.js.map +1 -0
  19. package/dist/commands/service.js +56 -13
  20. package/dist/commands/service.js.map +1 -1
  21. package/dist/commands/show.d.ts +13 -0
  22. package/dist/commands/show.js +70 -0
  23. package/dist/commands/show.js.map +1 -0
  24. package/dist/commands/skill.d.ts +1 -0
  25. package/dist/commands/skill.js +114 -0
  26. package/dist/commands/skill.js.map +1 -0
  27. package/dist/commands/uninstall.d.ts +1 -0
  28. package/dist/commands/uninstall.js +46 -0
  29. package/dist/commands/uninstall.js.map +1 -0
  30. package/dist/index.js +100 -7
  31. package/dist/index.js.map +1 -1
  32. package/dist/installers/base.d.ts +1 -0
  33. package/dist/installers/channel.d.ts +16 -1
  34. package/dist/installers/channel.js +143 -9
  35. package/dist/installers/channel.js.map +1 -1
  36. package/dist/installers/openclaw.d.ts +10 -2
  37. package/dist/installers/openclaw.js +140 -51
  38. package/dist/installers/openclaw.js.map +1 -1
  39. package/dist/installers/openviking-purge.d.ts +52 -0
  40. package/dist/installers/openviking-purge.js +380 -0
  41. package/dist/installers/openviking-purge.js.map +1 -0
  42. package/dist/installers/openviking.js +1 -2
  43. package/dist/installers/openviking.js.map +1 -1
  44. package/dist/installers/skill.d.ts +2 -0
  45. package/dist/installers/skill.js +35 -5
  46. package/dist/installers/skill.js.map +1 -1
  47. package/dist/migration/archive.d.ts +14 -0
  48. package/dist/migration/archive.js +84 -0
  49. package/dist/migration/archive.js.map +1 -0
  50. package/dist/migration/crypto.d.ts +16 -0
  51. package/dist/migration/crypto.js +56 -0
  52. package/dist/migration/crypto.js.map +1 -0
  53. package/dist/migration/manifest.d.ts +39 -0
  54. package/dist/migration/manifest.js +140 -0
  55. package/dist/migration/manifest.js.map +1 -0
  56. package/dist/openclaw-user-data.d.ts +66 -0
  57. package/dist/openclaw-user-data.js +283 -0
  58. package/dist/openclaw-user-data.js.map +1 -0
  59. package/dist/output/mode.d.ts +11 -0
  60. package/dist/output/mode.js +27 -0
  61. package/dist/output/mode.js.map +1 -0
  62. package/dist/output/ndjson.d.ts +57 -0
  63. package/dist/output/ndjson.js +136 -0
  64. package/dist/output/ndjson.js.map +1 -0
  65. package/dist/utils/constants.d.ts +8 -5
  66. package/dist/utils/constants.js +32 -7
  67. package/dist/utils/constants.js.map +1 -1
  68. package/dist/utils/deps.d.ts +34 -3
  69. package/dist/utils/deps.js +62 -100
  70. package/dist/utils/deps.js.map +1 -1
  71. package/dist/utils/logger.d.ts +10 -0
  72. package/dist/utils/logger.js +31 -4
  73. package/dist/utils/logger.js.map +1 -1
  74. package/dist/utils/mirror.js +14 -0
  75. package/dist/utils/mirror.js.map +1 -1
  76. package/dist/utils/openclaw-config-cli.d.ts +19 -0
  77. package/dist/utils/openclaw-config-cli.js +81 -0
  78. package/dist/utils/openclaw-config-cli.js.map +1 -0
  79. package/dist/utils/openclaw-daemon.d.ts +58 -0
  80. package/dist/utils/openclaw-daemon.js +154 -0
  81. package/dist/utils/openclaw-daemon.js.map +1 -0
  82. package/dist/utils/shell.d.ts +12 -2
  83. package/dist/utils/shell.js +80 -5
  84. package/dist/utils/shell.js.map +1 -1
  85. package/package.json +3 -2
@@ -1,5 +1,32 @@
1
- export function info(msg) { console.log(`[okclaw] ${msg}`); }
2
- export function warn(msg) { console.log(`[okclaw] WARN: ${msg}`); }
3
- export function error(msg) { console.error(`[okclaw] ERROR: ${msg}`); }
4
- export function success(msg) { console.log(`[okclaw] ✓ ${msg}`); }
1
+ import { isNdjson } from '../output/mode.js';
2
+ /**
3
+ * 人类可读 log。
4
+ *
5
+ * ndjson 模式下:所有业务级 log(info / warn / success / error)静默。
6
+ * - info/warn/success 本就无关紧要,静默避免污染 stdout 结构化流
7
+ * - error 也静默(bug#5 修):业务错误必须通过 emitter.end(false, code, { error: msg }) 承载,
8
+ * 与 design 契约对齐(stderr 仅给真正的崩溃栈,由 registerExitHandlers 的 uncaughtException 打印)
9
+ *
10
+ * 调用方注意:ndjson 模式下调 error(msg) 不会让用户看见消息,务必把 msg 塞进 emitter.end 的 fields。
11
+ */
12
+ export function info(msg) {
13
+ if (isNdjson())
14
+ return;
15
+ console.log(`[okclaw] ${msg}`);
16
+ }
17
+ export function warn(msg) {
18
+ if (isNdjson())
19
+ return;
20
+ console.log(`[okclaw] WARN: ${msg}`);
21
+ }
22
+ export function error(msg) {
23
+ if (isNdjson())
24
+ return;
25
+ console.error(`[okclaw] ERROR: ${msg}`);
26
+ }
27
+ export function success(msg) {
28
+ if (isNdjson())
29
+ return;
30
+ console.log(`[okclaw] ✓ ${msg}`);
31
+ }
5
32
  //# sourceMappingURL=logger.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,IAAI,CAAC,GAAW,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AACrE,MAAM,UAAU,IAAI,CAAC,GAAW,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAC3E,MAAM,UAAU,KAAK,CAAC,GAAW,IAAI,OAAO,CAAC,KAAK,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAC/E,MAAM,UAAU,OAAO,CAAC,GAAW,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C;;;;;;;;;GASG;AAEH,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,IAAI,QAAQ,EAAE;QAAE,OAAO;IACvB,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,EAAE,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,IAAI,QAAQ,EAAE;QAAE,OAAO;IACvB,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,EAAE,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAW;IAC/B,IAAI,QAAQ,EAAE;QAAE,OAAO;IACvB,OAAO,CAAC,KAAK,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,IAAI,QAAQ,EAAE;QAAE,OAAO;IACvB,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,EAAE,CAAC,CAAC;AACnC,CAAC"}
@@ -4,6 +4,20 @@ import { NPM_REGISTRY, PIP_INDEX_URL, PIP_TRUSTED_HOST } from './constants.js';
4
4
  export async function configureMirrors() {
5
5
  info('Configuring China mirrors...');
6
6
  await shell(`npm config set registry ${NPM_REGISTRY}`).catch(() => { });
7
+ // 第三方插件(如 @soimy/dingtalk)的 devDependencies 偶发会因为上游版本漂移
8
+ // 出现 peer dep 冲突(实例:oxlint@1.63.0 要求 oxlint-tsgolint>=0.22.1,但插件
9
+ // 钉死 ^0.18.0),npm 10 默认严格 peer 解析会报 ERESOLVE 直接挂掉
10
+ // openclaw plugins install 内部的 npm install。openclaw 没暴露 install flag 给我们传,
11
+ // 设成全局 npm config 是唯一能影响它的方式。lint/format devDep 的版本拉新对运行时
12
+ // 无影响,让 npm 用旧 peer 解析逻辑通过即可。
13
+ await shell(`npm config set legacy-peer-deps true`).catch(() => { });
14
+ // 部分 npm 包(如 openclaw 依赖的 libsignal-node)用 git+ssh://git@github.com/...
15
+ // 引用 GitHub 仓库。云主机 root 账户默认没有 GitHub SSH key 会拉取失败。
16
+ // 这里把 ssh://git@github.com/ 和 git@github.com: 都重写成 https://github.com/(匿名可读)。
17
+ // 注意: 同一个 URL name 下要写多个 insteadOf 必须用 --add,否则第二次 set 会覆盖第一次。
18
+ await shell(`git config --global --unset-all url."https://github.com/".insteadOf 2>/dev/null || true`).catch(() => { });
19
+ await shell(`git config --global --add url."https://github.com/".insteadOf ssh://git@github.com/`).catch(() => { });
20
+ await shell(`git config --global --add url."https://github.com/".insteadOf git@github.com:`).catch(() => { });
7
21
  // Use /root/.pip — not ~/
8
22
  await shell(`mkdir -p /root/.pip && cat > /root/.pip/pip.conf << 'EOF'
9
23
  [global]
@@ -1 +1 @@
1
- {"version":3,"file":"mirror.js","sourceRoot":"","sources":["../../src/utils/mirror.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE/E,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAErC,MAAM,KAAK,CAAC,2BAA2B,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAEvE,0BAA0B;IAC1B,MAAM,KAAK,CAAC;;cAEA,aAAa;iBACV,gBAAgB;IAC7B,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAEpB,IAAI,CAAC,qBAAqB,CAAC,CAAC;AAC9B,CAAC"}
1
+ {"version":3,"file":"mirror.js","sourceRoot":"","sources":["../../src/utils/mirror.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE/E,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAErC,MAAM,KAAK,CAAC,2BAA2B,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAEvE,wDAAwD;IACxD,iEAAiE;IACjE,kDAAkD;IAClD,2EAA2E;IAC3E,0DAA0D;IAC1D,8BAA8B;IAC9B,MAAM,KAAK,CAAC,sCAAsC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAEpE,wEAAwE;IACxE,qDAAqD;IACrD,8EAA8E;IAC9E,+DAA+D;IAC/D,MAAM,KAAK,CACT,yFAAyF,CAC1F,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAClB,MAAM,KAAK,CACT,qFAAqF,CACtF,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAClB,MAAM,KAAK,CACT,+EAA+E,CAChF,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAElB,0BAA0B;IAC1B,MAAM,KAAK,CAAC;;cAEA,aAAa;iBACV,gBAAgB;IAC7B,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAEpB,IAAI,CAAC,qBAAqB,CAAC,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,19 @@
1
+ export declare class OpenclawConfigCliError extends Error {
2
+ readonly cmd: string;
3
+ readonly exitCode: number;
4
+ readonly stdout: string;
5
+ readonly stderr: string;
6
+ constructor(cmd: string, exitCode: number, stdout: string, stderr: string);
7
+ }
8
+ export interface CliRunner {
9
+ run(args: string[]): {
10
+ exitCode: number;
11
+ stdout: string;
12
+ stderr: string;
13
+ };
14
+ }
15
+ export declare const defaultCliRunner: CliRunner;
16
+ export declare function configGet(path: string, runner?: CliRunner): unknown | undefined;
17
+ export declare function configSet(path: string, valueJson: string, runner?: CliRunner): void;
18
+ export declare function configUnset(path: string, runner?: CliRunner): void;
19
+ export declare function configValidate(runner?: CliRunner): boolean;
@@ -0,0 +1,81 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ export class OpenclawConfigCliError extends Error {
3
+ cmd;
4
+ exitCode;
5
+ stdout;
6
+ stderr;
7
+ constructor(cmd, exitCode, stdout, stderr) {
8
+ super(`openclaw ${cmd} failed (exit ${exitCode}): ${stderr || stdout}`);
9
+ this.cmd = cmd;
10
+ this.exitCode = exitCode;
11
+ this.stdout = stdout;
12
+ this.stderr = stderr;
13
+ }
14
+ }
15
+ function stringifyOutput(value) {
16
+ if (typeof value === 'string')
17
+ return value;
18
+ if (Buffer.isBuffer(value))
19
+ return value.toString('utf8');
20
+ return '';
21
+ }
22
+ export const defaultCliRunner = {
23
+ run(args) {
24
+ try {
25
+ const stdout = execFileSync('openclaw', args, {
26
+ encoding: 'utf8',
27
+ stdio: ['ignore', 'pipe', 'pipe'],
28
+ });
29
+ return { exitCode: 0, stdout, stderr: '' };
30
+ }
31
+ catch (err) {
32
+ const error = err;
33
+ return {
34
+ exitCode: typeof error.status === 'number' ? error.status : 127,
35
+ stdout: stringifyOutput(error.stdout),
36
+ stderr: stringifyOutput(error.stderr || error.message),
37
+ };
38
+ }
39
+ },
40
+ };
41
+ function runOrThrow(args, runner = defaultCliRunner) {
42
+ const result = runner.run(args);
43
+ if (result.exitCode !== 0) {
44
+ throw new OpenclawConfigCliError(args.join(' '), result.exitCode, result.stdout, result.stderr);
45
+ }
46
+ return result;
47
+ }
48
+ function isNotFoundOutput(output) {
49
+ return /Config\s+path\s+not\s+found/i.test(output);
50
+ }
51
+ export function configGet(path, runner = defaultCliRunner) {
52
+ const args = ['config', 'get', path];
53
+ const result = runner.run(args);
54
+ if (result.exitCode !== 0) {
55
+ if (isNotFoundOutput(`${result.stderr}\n${result.stdout}`))
56
+ return undefined;
57
+ throw new OpenclawConfigCliError(args.join(' '), result.exitCode, result.stdout, result.stderr);
58
+ }
59
+ const stdout = result.stdout.trim();
60
+ if (!stdout)
61
+ return undefined;
62
+ try {
63
+ return JSON.parse(stdout);
64
+ }
65
+ catch {
66
+ // openclaw config get prints scalar string values as raw text without JSON
67
+ // quoting (e.g. `openviking` rather than `"openviking"`). number / bool / null
68
+ // are valid JSON literals so they parse above; only unquoted strings reach here.
69
+ return stdout;
70
+ }
71
+ }
72
+ export function configSet(path, valueJson, runner = defaultCliRunner) {
73
+ runOrThrow(['config', 'set', path, valueJson, '--strict-json'], runner);
74
+ }
75
+ export function configUnset(path, runner = defaultCliRunner) {
76
+ runOrThrow(['config', 'unset', path], runner);
77
+ }
78
+ export function configValidate(runner = defaultCliRunner) {
79
+ return runner.run(['config', 'validate']).exitCode === 0;
80
+ }
81
+ //# sourceMappingURL=openclaw-config-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openclaw-config-cli.js","sourceRoot":"","sources":["../../src/utils/openclaw-config-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAE7B;IACA;IACA;IACA;IAJlB,YACkB,GAAW,EACX,QAAgB,EAChB,MAAc,EACd,MAAc;QAE9B,KAAK,CAAC,YAAY,GAAG,iBAAiB,QAAQ,MAAM,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC;QALxD,QAAG,GAAH,GAAG,CAAQ;QACX,aAAQ,GAAR,QAAQ,CAAQ;QAChB,WAAM,GAAN,MAAM,CAAQ;QACd,WAAM,GAAN,MAAM,CAAQ;IAGhC,CAAC;CACF;AAMD,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAc;IACzC,GAAG,CAAC,IAAc;QAChB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,YAAY,CAAC,UAAU,EAAE,IAAI,EAAE;gBAC5C,QAAQ,EAAE,MAAM;gBAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;aAClC,CAAC,CAAC;YACH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,GAIb,CAAC;YACF,OAAO;gBACL,QAAQ,EAAE,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG;gBAC/D,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC;gBACrC,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC;aACvD,CAAC;QACJ,CAAC;IACH,CAAC;CACF,CAAC;AAEF,SAAS,UAAU,CAAC,IAAc,EAAE,SAAoB,gBAAgB;IACtE,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAClG,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAc;IACtC,OAAO,8BAA8B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,SAAoB,gBAAgB;IAC1E,MAAM,IAAI,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,IAAI,gBAAgB,CAAC,GAAG,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;YAAE,OAAO,SAAS,CAAC;QAC7E,MAAM,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAClG,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,+EAA+E;QAC/E,iFAAiF;QACjF,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,SAAiB,EAAE,SAAoB,gBAAgB;IAC7F,UAAU,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,SAAoB,gBAAgB;IAC5E,UAAU,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,SAAoB,gBAAgB;IACjE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 确保 gateway 的 systemd 服务已安装且能常驻。
3
+ * - `loginctl enable-linger`:systemd user 服务默认随最后一个登录会话退出而停,
4
+ * 开 linger 后才能在部署 SSH 断开后 / 主机重启后继续运行。
5
+ * - `openclaw gateway install --force`:幂等(重装覆盖),顺带在 openclaw 升级后
6
+ * 刷新 unit 的 ExecStart。
7
+ */
8
+ export declare function installGatewayService(): Promise<void>;
9
+ /** 启动 gateway(先确保 systemd 服务已装)。 */
10
+ export declare function startGateway(): Promise<void>;
11
+ /** 重启 gateway(先确保 systemd 服务已装)。 */
12
+ export declare function restartGateway(): Promise<void>;
13
+ /**
14
+ * 停掉 gateway:先停掉所有已知的 systemd unit(新 + 旧 unit 名),再按监听
15
+ * 端口兜底杀掉非 systemd 托管的残留进程。顺序见 {@link STOP_GATEWAY_UNITS}。
16
+ */
17
+ export declare function stopGateway(): Promise<void>;
18
+ /** gateway 运行状态。健康的唯一标准是端口已监听。 */
19
+ export type GatewayState = 'listening' | 'starting' | 'down';
20
+ /**
21
+ * 探测 gateway 状态。**健康的唯一标准是 18789 端口已监听** —— 进程 active
22
+ * 不等于对外可服务。
23
+ *
24
+ * - `listening`:端口已监听,健康。
25
+ * - `starting` :systemd 服务 active,但端口在 bounded wait 内始终没监听 ——
26
+ * 配置异常 / 启动卡住 / 仍在初始化,**不算健康**。
27
+ * - `down` :systemd 服务非 active 且端口未监听。
28
+ *
29
+ * `openclaw gateway restart` 返回后服务立刻是 active,但 gateway 进程还要几秒
30
+ * 才 bind 端口。所以端口没起来、服务却 active 时给一段 bounded wait 覆盖这个
31
+ * 启动窗口;服务都不 active 则直接 `down`,不等。
32
+ */
33
+ export declare function probeGatewayState(): Promise<GatewayState>;
34
+ /**
35
+ * 清掉 systemd user 目录下的 gateway unit 残留。`openclaw gateway uninstall`
36
+ * 只删主 `.service` 文件,残留的 `.service.bak` 备份和 `.service.d/` drop-in
37
+ * 会在下次 install 时被 systemd 自动 merge 进来(注入过时的 env 等),必须一并清。
38
+ */
39
+ export declare function cleanupGatewayServiceFiles(): Promise<void>;
40
+ /**
41
+ * Best-effort stop of the openclaw gateway so cli operations have exclusive
42
+ * write access to openclaw.json + extensions/.
43
+ *
44
+ * Why this is needed: the gateway has a config file watcher that writes its
45
+ * own metadata back into openclaw.json on every config change. Concurrent
46
+ * writes during cli's cleanup / `openclaw plugins install` / `openclaw config
47
+ * set` race with that watcher and produce:
48
+ * - ENOTEMPTY: rmdir '.../node_modules/.cache/jiti' (jiti cache write race)
49
+ * - ConfigMutationConflictError: config changed since last load (hash check
50
+ * in openclaw plugins install commit / openclaw config set atomic write)
51
+ *
52
+ * Bootstrap orchestrator (commands/install.ts) is responsible for restarting
53
+ * the gateway after the full component install pipeline finishes.
54
+ *
55
+ * @param context A short label for logs, e.g. "dingtalk install" or
56
+ * "openclaw deployment defaults".
57
+ */
58
+ export declare function stopDaemonForExclusiveAccess(context: string): Promise<void>;
@@ -0,0 +1,154 @@
1
+ import { shell, shellCapture } from './shell.js';
2
+ import { info } from './logger.js';
3
+ import { OPENCLAW_GATEWAY_PORT } from './constants.js';
4
+ /**
5
+ * openclaw gateway 生命周期管理。
6
+ *
7
+ * 关键决策:gateway 交给 openclaw 自带的 systemd 服务管理,cli 不再自己用
8
+ * nohup + ps + pkill 管进程。
9
+ *
10
+ * 为什么:openclaw 2026.5 的 gateway 有自己的单实例 lock、restart-handoff、
11
+ * 进程改名(`openclaw` / `node`)机制。cli 旧实现按进程名 `openclaw-gateway`
12
+ * 识别 + nohup 拉起 + 手动 kill,实测都顶不住:
13
+ * - 进程名匹配 0 个 → restart 存活检测恒失败,报 E_RESTART_FAILED
14
+ * (即使 gateway 其实已经起来了)
15
+ * - 改用 `openclaw gateway run --force` + nohup,会和 openclaw 自身的
16
+ * lock / handoff 互相赛跑 → 双进程、端口悬空、健康检查 abnormal closure
17
+ * 而 `openclaw gateway install/start/stop/restart`(systemd user 服务)实测
18
+ * 连续重启始终单进程、端口干净、状态准确。所以一律走 openclaw 的 service 命令。
19
+ */
20
+ // gateway / systemctl 命令的环境前缀。
21
+ //
22
+ // unset SUDO_*:cli 在非 root 登录的主机(如 AWS Ubuntu AMI 的 ubuntu 用户)
23
+ // 上由 os-api 用 `sudo -H` 提权到 root 跑。`sudo -H` 把 HOME 设成 /root,
24
+ // 于是 `openclaw gateway install` 把 unit 文件写进 /root/.config/systemd/user/;
25
+ // 但 openclaw 又据 SUDO_USER 判定"当前用户是 ubuntu",enable 时跑的是
26
+ // `systemctl --machine ubuntu@ --user ...` —— 去 ubuntu 的 systemd 里找 unit,
27
+ // 找不到 → "Unit file does not exist" → install 失败。unset 掉 SUDO_* 让
28
+ // openclaw 当成纯 root 调用,写 unit 与操作 systemd 都对着 root,一致。
29
+ // XDG_RUNTIME_DIR:systemctl --user 需要它;非交互 SSH exec 下通常已由
30
+ // pam_systemd 注入,这里再兜底一次,防个别主机 sshd PAM 配置不注入。
31
+ const SYSTEMD_ENV = 'unset SUDO_USER SUDO_UID SUDO_GID; ' +
32
+ 'export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"; ';
33
+ // gateway 没被 systemd 接管时(老主机残留 / 异常)按监听端口兜底杀进程。
34
+ // 覆盖 `openclaw` / `node` 两种进程名;且绝不会误杀正在执行本 CLI 的进程。
35
+ // 用 awk 解析 `pid=NNN` 而不是 `grep -oP` —— grep 的 -P(PCRE,含 \K)是 GNU
36
+ // 扩展,非 GNU 环境(BSD grep 等)会整条命令报错、兜底路径静默退化成 no-op。
37
+ // awk -F'pid=' 把每个 `pid=` 后的字段数值化($i+0 取前导数字),POSIX 通用。
38
+ const KILL_GATEWAY_BY_PORT = `ss -ltnHp 'sport = :${OPENCLAW_GATEWAY_PORT}' 2>/dev/null` +
39
+ ` | awk -F'pid=' '{for(i=2;i<=NF;i++){p=$i+0;if(p>0)print p}}'` +
40
+ ` | sort -u | xargs -r kill 2>/dev/null || true`;
41
+ /**
42
+ * 确保 gateway 的 systemd 服务已安装且能常驻。
43
+ * - `loginctl enable-linger`:systemd user 服务默认随最后一个登录会话退出而停,
44
+ * 开 linger 后才能在部署 SSH 断开后 / 主机重启后继续运行。
45
+ * - `openclaw gateway install --force`:幂等(重装覆盖),顺带在 openclaw 升级后
46
+ * 刷新 unit 的 ExecStart。
47
+ */
48
+ export async function installGatewayService() {
49
+ await shell(`loginctl enable-linger root 2>/dev/null || true`).catch(() => { });
50
+ await shell(`${SYSTEMD_ENV}openclaw gateway install --force --port ${OPENCLAW_GATEWAY_PORT}`);
51
+ }
52
+ /** 启动 gateway(先确保 systemd 服务已装)。 */
53
+ export async function startGateway() {
54
+ await installGatewayService();
55
+ await shell(`${SYSTEMD_ENV}openclaw gateway start`);
56
+ }
57
+ /** 重启 gateway(先确保 systemd 服务已装)。 */
58
+ export async function restartGateway() {
59
+ await installGatewayService();
60
+ await shell(`${SYSTEMD_ENV}openclaw gateway restart`);
61
+ }
62
+ // 停掉所有已知的 gateway systemd unit。新版 openclaw 的 unit 名是
63
+ // openclaw-gateway.service;老主机可能是 openclaw.service(早期 cli 用过的
64
+ // unit 名,--user / system 两种 scope 都覆盖)。
65
+ //
66
+ // **必须在按端口 kill 之前先停 unit**:先停 unit,systemd 视为"主动停止"、
67
+ // 不会触发 Restart=;反过来先 kill 进程,systemd 当成异常退出,会按
68
+ // Restart=always/on-failure 立刻把 gateway 拉回来 —— stopDaemonForExclusiveAccess
69
+ // 就拿不到真正的独占窗口,config watcher 写配置的竞争(ConfigMutationConflict)复现。
70
+ // 按端口 kill 只作为非 systemd 托管残留进程的兜底,不能替代停 supervisor。
71
+ const STOP_GATEWAY_UNITS = `${SYSTEMD_ENV}` +
72
+ `openclaw gateway stop 2>/dev/null || true; ` +
73
+ `systemctl --user stop openclaw-gateway.service 2>/dev/null || true; ` +
74
+ `systemctl --user stop openclaw.service 2>/dev/null || true; ` +
75
+ `systemctl stop openclaw.service 2>/dev/null || true`;
76
+ /**
77
+ * 停掉 gateway:先停掉所有已知的 systemd unit(新 + 旧 unit 名),再按监听
78
+ * 端口兜底杀掉非 systemd 托管的残留进程。顺序见 {@link STOP_GATEWAY_UNITS}。
79
+ */
80
+ export async function stopGateway() {
81
+ await shell(STOP_GATEWAY_UNITS).catch(() => { });
82
+ await shell(KILL_GATEWAY_BY_PORT).catch(() => { });
83
+ }
84
+ // `active 但端口未监听` 的最长等待:覆盖 restart 后进程已 active、端口还没
85
+ // bind 的启动窗口。等满仍未监听就判 starting(配置异常 / 卡住),不再傻等。
86
+ const STATUS_PORT_WAIT_ATTEMPTS = 20;
87
+ async function isPortListening() {
88
+ const r = await shellCapture(`ss -ltnH 'sport = :${OPENCLAW_GATEWAY_PORT}' 2>/dev/null || true`);
89
+ return r.stdout.trim().length > 0;
90
+ }
91
+ async function isServiceActive() {
92
+ const r = await shellCapture(`${SYSTEMD_ENV}systemctl --user is-active openclaw-gateway.service 2>/dev/null || true`);
93
+ return r.stdout.trim() === 'active';
94
+ }
95
+ /**
96
+ * 探测 gateway 状态。**健康的唯一标准是 18789 端口已监听** —— 进程 active
97
+ * 不等于对外可服务。
98
+ *
99
+ * - `listening`:端口已监听,健康。
100
+ * - `starting` :systemd 服务 active,但端口在 bounded wait 内始终没监听 ——
101
+ * 配置异常 / 启动卡住 / 仍在初始化,**不算健康**。
102
+ * - `down` :systemd 服务非 active 且端口未监听。
103
+ *
104
+ * `openclaw gateway restart` 返回后服务立刻是 active,但 gateway 进程还要几秒
105
+ * 才 bind 端口。所以端口没起来、服务却 active 时给一段 bounded wait 覆盖这个
106
+ * 启动窗口;服务都不 active 则直接 `down`,不等。
107
+ */
108
+ export async function probeGatewayState() {
109
+ if (await isPortListening())
110
+ return 'listening';
111
+ if (!(await isServiceActive()))
112
+ return 'down';
113
+ for (let i = 0; i < STATUS_PORT_WAIT_ATTEMPTS; i++) {
114
+ await new Promise((resolve) => setTimeout(resolve, 1000));
115
+ if (await isPortListening())
116
+ return 'listening';
117
+ }
118
+ return 'starting';
119
+ }
120
+ /**
121
+ * 清掉 systemd user 目录下的 gateway unit 残留。`openclaw gateway uninstall`
122
+ * 只删主 `.service` 文件,残留的 `.service.bak` 备份和 `.service.d/` drop-in
123
+ * 会在下次 install 时被 systemd 自动 merge 进来(注入过时的 env 等),必须一并清。
124
+ */
125
+ export async function cleanupGatewayServiceFiles() {
126
+ await shell(`${SYSTEMD_ENV}rm -rf /root/.config/systemd/user/openclaw-gateway.service.d` +
127
+ ` /root/.config/systemd/user/openclaw-gateway.service.bak;` +
128
+ ` systemctl --user daemon-reload 2>/dev/null || true`).catch(() => { });
129
+ }
130
+ /**
131
+ * Best-effort stop of the openclaw gateway so cli operations have exclusive
132
+ * write access to openclaw.json + extensions/.
133
+ *
134
+ * Why this is needed: the gateway has a config file watcher that writes its
135
+ * own metadata back into openclaw.json on every config change. Concurrent
136
+ * writes during cli's cleanup / `openclaw plugins install` / `openclaw config
137
+ * set` race with that watcher and produce:
138
+ * - ENOTEMPTY: rmdir '.../node_modules/.cache/jiti' (jiti cache write race)
139
+ * - ConfigMutationConflictError: config changed since last load (hash check
140
+ * in openclaw plugins install commit / openclaw config set atomic write)
141
+ *
142
+ * Bootstrap orchestrator (commands/install.ts) is responsible for restarting
143
+ * the gateway after the full component install pipeline finishes.
144
+ *
145
+ * @param context A short label for logs, e.g. "dingtalk install" or
146
+ * "openclaw deployment defaults".
147
+ */
148
+ export async function stopDaemonForExclusiveAccess(context) {
149
+ info(`Stopping openclaw gateway for exclusive access during ${context}...`);
150
+ await stopGateway();
151
+ // `systemctl stop` 是显式停,Restart=always 不会把它拉回来;留 1.5s 兜底观察。
152
+ await new Promise((resolve) => setTimeout(resolve, 1500));
153
+ }
154
+ //# sourceMappingURL=openclaw-daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openclaw-daemon.js","sourceRoot":"","sources":["../../src/utils/openclaw-daemon.ts"],"names":[],"mappings":"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"}
@@ -8,8 +8,18 @@ export interface ShellResult {
8
8
  */
9
9
  export declare function ensureRoot(): void;
10
10
  /**
11
- * Run a shell command with inherited stdio (visible output).
12
- * Automatically sources nvm so node/npm/openclaw are available.
11
+ * Run a shell command and wait for exit.
12
+ *
13
+ * text 模式:stdio:inherit,子进程输出直通父进程 TTY(本地交互体验不变)。
14
+ *
15
+ * ndjson 模式:捕获 stdout/stderr 并按行合成 phase="log" 事件(stdout→level=info,
16
+ * stderr→level=warn),通过 {@link getActiveEmitter} 拿到当前 op 的 emitter 发出。
17
+ * 这样像 `npm install` 这种几分钟才回一行 ndjson 的长操作,其实时进度也能通过
18
+ * 事件流推给 os-api / 前端,用户不会看到 UI 卡死 10 分钟。
19
+ *
20
+ * 透传是"尽力而为":emitter 缺失(理论上只有本地脚本直接 import shell() 且没走
21
+ * 正常 command 入口才会发生)时静默吞,不影响命令本身执行。
22
+ *
13
23
  * Throws on non-zero exit.
14
24
  */
15
25
  export declare function shell(cmd: string): Promise<void>;
@@ -1,5 +1,7 @@
1
1
  import { spawn, execFileSync } from 'node:child_process';
2
2
  import { error as logError, info } from './logger.js';
3
+ import { isNdjson } from '../output/mode.js';
4
+ import { getActiveEmitter } from '../output/ndjson.js';
3
5
  /**
4
6
  * Ensure running as root. If not, re-exec with sudo and exit.
5
7
  */
@@ -20,19 +22,46 @@ export function ensureRoot() {
20
22
  process.exit(code);
21
23
  }
22
24
  }
23
- // Prefix to source nvm before every shell command so node/npm are on PATH
24
- const NVM_INIT = 'export HOME=/root; export NVM_DIR=/root/.nvm; [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh"; ';
25
+ // Prefix to source nvm before every shell command so node/npm are on PATH.
26
+ //
27
+ // 关键:source nvm.sh 之后必须显式 `nvm use default`。nvm.sh 被 source 时的
28
+ // auto-use 只会激活"当前 PATH 上已有的那个 node"(nvm_ls_current 命中即
29
+ // `nvm use $NVM_CURRENT`),而不是 default。bootstrap 脚本只 source 一次 nvm、
30
+ // 把旧 node(如 22.16)钉进并 export 进 PATH,于是这里每个子进程继承到的都是
31
+ // 旧 node —— 即便 `okclaw check` 已经把 default 升到 24,`npm install openclaw`/
32
+ // `openclaw onboard`/`openclaw gateway` 仍跑在旧 node 上,触发 openclaw 的
33
+ // "Node.js vX is required" 而部署失败。强制 `nvm use default` 抵消这个行为,
34
+ // 让所有命令都跑在升级后的 default(24)。
35
+ // `command -v nvm` 守卫:全新主机只有 ensureNodeScript 写的极简 nvm.sh(无 nvm
36
+ // 函数,升级前),此时跳过 use default,不影响首个 node 的安装与版本判定。
37
+ const NVM_INIT = 'export HOME=/root; export NVM_DIR=/root/.nvm; '
38
+ + '[ -s "$NVM_DIR/nvm.sh" ] && { source "$NVM_DIR/nvm.sh"; command -v nvm >/dev/null 2>&1 && nvm use default --silent >/dev/null 2>&1; }; ';
25
39
  /**
26
- * Run a shell command with inherited stdio (visible output).
27
- * Automatically sources nvm so node/npm/openclaw are available.
40
+ * Run a shell command and wait for exit.
41
+ *
42
+ * text 模式:stdio:inherit,子进程输出直通父进程 TTY(本地交互体验不变)。
43
+ *
44
+ * ndjson 模式:捕获 stdout/stderr 并按行合成 phase="log" 事件(stdout→level=info,
45
+ * stderr→level=warn),通过 {@link getActiveEmitter} 拿到当前 op 的 emitter 发出。
46
+ * 这样像 `npm install` 这种几分钟才回一行 ndjson 的长操作,其实时进度也能通过
47
+ * 事件流推给 os-api / 前端,用户不会看到 UI 卡死 10 分钟。
48
+ *
49
+ * 透传是"尽力而为":emitter 缺失(理论上只有本地脚本直接 import shell() 且没走
50
+ * 正常 command 入口才会发生)时静默吞,不影响命令本身执行。
51
+ *
28
52
  * Throws on non-zero exit.
29
53
  */
30
54
  export function shell(cmd) {
31
55
  return new Promise((resolve, reject) => {
56
+ const ndjson = isNdjson();
32
57
  const child = spawn('bash', ['-c', NVM_INIT + cmd], {
33
- stdio: 'inherit',
58
+ stdio: ndjson ? ['ignore', 'pipe', 'pipe'] : 'inherit',
34
59
  env: { ...process.env, HOME: '/root' },
35
60
  });
61
+ if (ndjson) {
62
+ forwardStreamAsLogEvents(child, 'stdout', 'info');
63
+ forwardStreamAsLogEvents(child, 'stderr', 'warn');
64
+ }
36
65
  child.on('error', reject);
37
66
  child.on('close', (code) => {
38
67
  if (code === 0)
@@ -42,6 +71,52 @@ export function shell(cmd) {
42
71
  });
43
72
  });
44
73
  }
74
+ /**
75
+ * 把子进程的一个输出流按换行切分,每行合成一条 phase="log" ndjson 事件。
76
+ *
77
+ * 实现细节:
78
+ * - 跨 chunk 续行:`buf` 累积未看到 \n 的尾巴,下次 data 再拼
79
+ * - 空行丢弃(避免 npm 输出里的空白行污染事件流)
80
+ * - \r 尾字符剥掉(Windows / 终端控制字符常见)
81
+ * - close 时 flush 尾 buf,防止最后一行没 \n 漏掉
82
+ * - emitter 为空时整段 no-op,绝不抛异常——shell() 的主路径不能因日志透传失败而失败
83
+ */
84
+ function forwardStreamAsLogEvents(child, streamName, level) {
85
+ const stream = child[streamName];
86
+ if (!stream)
87
+ return;
88
+ let buf = '';
89
+ stream.setEncoding('utf8');
90
+ stream.on('data', (chunk) => {
91
+ buf += chunk;
92
+ let idx = buf.indexOf('\n');
93
+ while (idx >= 0) {
94
+ emitLogLine(buf.slice(0, idx), level);
95
+ buf = buf.slice(idx + 1);
96
+ idx = buf.indexOf('\n');
97
+ }
98
+ });
99
+ stream.on('close', () => {
100
+ if (buf.length > 0) {
101
+ emitLogLine(buf, level);
102
+ buf = '';
103
+ }
104
+ });
105
+ }
106
+ function emitLogLine(line, level) {
107
+ const cleaned = line.endsWith('\r') ? line.slice(0, -1) : line;
108
+ if (cleaned.length === 0)
109
+ return;
110
+ const emitter = getActiveEmitter();
111
+ if (!emitter)
112
+ return;
113
+ try {
114
+ emitter.log(level, cleaned);
115
+ }
116
+ catch {
117
+ // log 事件透传失败不能影响命令本身执行;这里静默
118
+ }
119
+ }
45
120
  /**
46
121
  * Run a shell command with retries.
47
122
  */
@@ -1 +1 @@
1
- {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/utils/shell.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/utils/shell.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okclaw-build/cli",
3
- "version": "1.0.0-beta.5",
3
+ "version": "1.0.0-beta.50",
4
4
  "description": "OKClaw deployment CLI - install and manage OpenClaw, OpenViking, and channel plugins",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,7 +10,8 @@
10
10
  "scripts": {
11
11
  "build": "tsc",
12
12
  "dev": "tsc --watch",
13
- "prepublishOnly": "npm run build"
13
+ "prepublishOnly": "npm run build",
14
+ "test": "tsc -p tsconfig.test.json && node --test dist-test/tests/*.test.js dist-test/tests/*/*.test.js"
14
15
  },
15
16
  "keywords": ["openclaw", "openviking", "deploy", "cli"],
16
17
  "license": "MIT",