@skylandnpm/octopus-proxy 0.1.38 → 0.1.40

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
@@ -33,6 +33,31 @@ octopus-proxy doctor
33
33
  octopus-proxy restart
34
34
  ```
35
35
 
36
+ Claw 新建终端时若出现 `posix_spawnp failed.`,错误来自 Proxy 主机上的 PTY 启动。`node-pty 1.1.0` 的 macOS 预编译 `spawn-helper` 存在安装后缺少执行权限的[上游问题](https://github.com/microsoft/node-pty/issues/850);同一 macOS 版本的两台电脑也可能因安装方式、CPU 架构和安装目录不同而只有一台失败。
37
+
38
+ Proxy 在新建终端前检查实际加载的辅助程序,并自动补齐缺失的执行权限。`octopus-proxy doctor` 会显示辅助程序路径及当前 Node 架构,`doctor --fix` 可修复权限;检查独立于配置是否有效。若安装目录不允许当前账号修改,诊断会提示由安装账号修复或重新安装,不会自动提权。辅助程序缺失需重新安装;权限正常仍失败时,终端错误会显示 shell、工作目录、辅助程序路径和 Node 版本,供进一步核查。诊断应在发生错误的 Proxy 主机、同一 Node 安装环境下运行,之后执行 `octopus-proxy restart` 并在 Claw 新建终端。
39
+
40
+ 尚未升级到含此修复的 Proxy 时,可在出错的 Mac 上运行以下临时修复。它从实际 `octopus-proxy` 命令解析其 `node-pty` 依赖,只修复正在使用的辅助程序。运行时的 Node 应与 Proxy 服务一致;权限正常仍失败时,请保留输出继续排查。
41
+
42
+ ```sh
43
+ node - "$(command -v octopus-proxy)" <<'NODE'
44
+ const fs = require('node:fs');
45
+ const path = require('node:path');
46
+ const requireProxy = require('node:module').createRequire(fs.realpathSync(process.argv[2]));
47
+ const utils = requireProxy.resolve('node-pty/lib/utils.js');
48
+ const native = requireProxy(utils).loadNativeModule('pty');
49
+ const helper = path.resolve(path.dirname(utils), native.dir, 'spawn-helper');
50
+ const info = fs.statSync(helper);
51
+ if (!info.isFile()) throw new Error('spawn-helper 不是普通文件');
52
+ console.log({ helper, mode: (info.mode & 0o777).toString(8), arch: process.arch, node: process.version });
53
+ try { fs.accessSync(helper, fs.constants.X_OK); }
54
+ catch { fs.chmodSync(helper, (info.mode & 0o777) | 0o111); }
55
+ fs.accessSync(helper, fs.constants.X_OK);
56
+ console.log('spawn-helper 执行权限正常');
57
+ NODE
58
+ octopus-proxy restart
59
+ ```
60
+
36
61
  Claw 完成主机信任和账号认证后,通过端到端会话向 Proxy 发送只读 `runtime.snapshot` 命令。响应包含当前运行状态、Hub 和工具设置、授权项目路径,以及工具可提供的模型信息;机器人 Secret、工具密码和提供商凭据不会返回。工作区的“Proxy 信息”按钮可查看这些内容。
37
62
 
38
63
  Codex 和 ZCode 由 Proxy 按授权项目启动各自的 app-server,默认随 Proxy 自动启动;服务未就绪时也可在 Claw 点击“启动服务”。若命令不可执行或某个项目启动失败,工作区会显示失败项目及原因。修改命令路径或授权项目后,运行 `octopus-proxy restart` 使配置生效。
@@ -65,6 +90,18 @@ octopus-proxy onboard --tool pi
65
90
 
66
91
  协议和验证详情见 [Pi 接入说明](../../docs/pi-protocol-notes.md)。
67
92
 
93
+ ## 更新
94
+
95
+ 在交互式终端中运行:
96
+
97
+ ```sh
98
+ octopus-proxy update
99
+ ```
100
+
101
+ 命令通过 `npm view @skylandnpm/octopus-proxy@latest version` 查询最新版本。存在新版本时,输入 `y` 或 `yes` 确认覆盖安装;直接回车或输入其他内容会取消。确认后依次停止已安装的服务、使用 npm 全局安装查到的版本、重启服务,并校验安装版本和运行状态后反馈结果。配置、主机身份和已有服务定义会保留;未安装服务时仅更新 npm 包。
102
+
103
+ 更新失败时会尝试恢复原先运行的服务,并报告安装及恢复结果;包安装失败不保证旧版本文件完整保留。已安装新版本但重启失败时会单独提示,可运行 `octopus-proxy doctor` 和 `octopus-proxy logs` 排查。此命令用于 npm 全局安装,停止服务前会检查 npm 安装目录是否与当前命令一致;源码、本地安装或 Node.js 环境不一致时会中止并给出提示。
104
+
68
105
  ## 发布
69
106
 
70
107
  在 `apps/octopus-proxy` 目录执行 `npm run publish`:先递增 `package.json` 的补丁版本,再编译,最后发布。即使编译或发布失败,版本号也会保留递增结果,重试时继续递增。版本更新不会自动创建 Git 提交或标签。先登录有权发布 `@skylandnpm` 包的 npm 账号:
@@ -402,11 +402,11 @@ var require_codegen = __commonJS({
402
402
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
403
403
  return `${varKind} ${this.name}${rhs};` + _n;
404
404
  }
405
- optimizeNames(names, constants5) {
405
+ optimizeNames(names, constants6) {
406
406
  if (!names[this.name.str])
407
407
  return;
408
408
  if (this.rhs)
409
- this.rhs = optimizeExpr(this.rhs, names, constants5);
409
+ this.rhs = optimizeExpr(this.rhs, names, constants6);
410
410
  return this;
411
411
  }
412
412
  get names() {
@@ -423,10 +423,10 @@ var require_codegen = __commonJS({
423
423
  render({ _n }) {
424
424
  return `${this.lhs} = ${this.rhs};` + _n;
425
425
  }
426
- optimizeNames(names, constants5) {
426
+ optimizeNames(names, constants6) {
427
427
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
428
428
  return;
429
- this.rhs = optimizeExpr(this.rhs, names, constants5);
429
+ this.rhs = optimizeExpr(this.rhs, names, constants6);
430
430
  return this;
431
431
  }
432
432
  get names() {
@@ -487,8 +487,8 @@ var require_codegen = __commonJS({
487
487
  optimizeNodes() {
488
488
  return `${this.code}` ? this : void 0;
489
489
  }
490
- optimizeNames(names, constants5) {
491
- this.code = optimizeExpr(this.code, names, constants5);
490
+ optimizeNames(names, constants6) {
491
+ this.code = optimizeExpr(this.code, names, constants6);
492
492
  return this;
493
493
  }
494
494
  get names() {
@@ -517,12 +517,12 @@ var require_codegen = __commonJS({
517
517
  }
518
518
  return nodes.length > 0 ? this : void 0;
519
519
  }
520
- optimizeNames(names, constants5) {
520
+ optimizeNames(names, constants6) {
521
521
  const { nodes } = this;
522
522
  let i = nodes.length;
523
523
  while (i--) {
524
524
  const n = nodes[i];
525
- if (n.optimizeNames(names, constants5))
525
+ if (n.optimizeNames(names, constants6))
526
526
  continue;
527
527
  subtractNames(names, n.names);
528
528
  nodes.splice(i, 1);
@@ -575,12 +575,12 @@ var require_codegen = __commonJS({
575
575
  return void 0;
576
576
  return this;
577
577
  }
578
- optimizeNames(names, constants5) {
578
+ optimizeNames(names, constants6) {
579
579
  var _a;
580
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants5);
581
- if (!(super.optimizeNames(names, constants5) || this.else))
580
+ this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants6);
581
+ if (!(super.optimizeNames(names, constants6) || this.else))
582
582
  return;
583
- this.condition = optimizeExpr(this.condition, names, constants5);
583
+ this.condition = optimizeExpr(this.condition, names, constants6);
584
584
  return this;
585
585
  }
586
586
  get names() {
@@ -603,10 +603,10 @@ var require_codegen = __commonJS({
603
603
  render(opts) {
604
604
  return `for(${this.iteration})` + super.render(opts);
605
605
  }
606
- optimizeNames(names, constants5) {
607
- if (!super.optimizeNames(names, constants5))
606
+ optimizeNames(names, constants6) {
607
+ if (!super.optimizeNames(names, constants6))
608
608
  return;
609
- this.iteration = optimizeExpr(this.iteration, names, constants5);
609
+ this.iteration = optimizeExpr(this.iteration, names, constants6);
610
610
  return this;
611
611
  }
612
612
  get names() {
@@ -642,10 +642,10 @@ var require_codegen = __commonJS({
642
642
  render(opts) {
643
643
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
644
644
  }
645
- optimizeNames(names, constants5) {
646
- if (!super.optimizeNames(names, constants5))
645
+ optimizeNames(names, constants6) {
646
+ if (!super.optimizeNames(names, constants6))
647
647
  return;
648
- this.iterable = optimizeExpr(this.iterable, names, constants5);
648
+ this.iterable = optimizeExpr(this.iterable, names, constants6);
649
649
  return this;
650
650
  }
651
651
  get names() {
@@ -687,11 +687,11 @@ var require_codegen = __commonJS({
687
687
  (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
688
688
  return this;
689
689
  }
690
- optimizeNames(names, constants5) {
690
+ optimizeNames(names, constants6) {
691
691
  var _a, _b;
692
- super.optimizeNames(names, constants5);
693
- (_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants5);
694
- (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants5);
692
+ super.optimizeNames(names, constants6);
693
+ (_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants6);
694
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants6);
695
695
  return this;
696
696
  }
697
697
  get names() {
@@ -992,7 +992,7 @@ var require_codegen = __commonJS({
992
992
  function addExprNames(names, from) {
993
993
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
994
994
  }
995
- function optimizeExpr(expr, names, constants5) {
995
+ function optimizeExpr(expr, names, constants6) {
996
996
  if (expr instanceof code_1.Name)
997
997
  return replaceName(expr);
998
998
  if (!canOptimize(expr))
@@ -1007,14 +1007,14 @@ var require_codegen = __commonJS({
1007
1007
  return items;
1008
1008
  }, []));
1009
1009
  function replaceName(n) {
1010
- const c = constants5[n.str];
1010
+ const c = constants6[n.str];
1011
1011
  if (c === void 0 || names[n.str] !== 1)
1012
1012
  return n;
1013
1013
  delete names[n.str];
1014
1014
  return c;
1015
1015
  }
1016
1016
  function canOptimize(e) {
1017
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants5[c.str] !== void 0);
1017
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants6[c.str] !== void 0);
1018
1018
  }
1019
1019
  }
1020
1020
  function subtractNames(names, from) {
@@ -2976,7 +2976,7 @@ var require_compile = __commonJS({
2976
2976
  const schOrFunc = root.refs[ref];
2977
2977
  if (schOrFunc)
2978
2978
  return schOrFunc;
2979
- let _sch = resolve4.call(this, root, ref);
2979
+ let _sch = resolve5.call(this, root, ref);
2980
2980
  if (_sch === void 0) {
2981
2981
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2982
2982
  const { schemaId } = this.opts;
@@ -3003,7 +3003,7 @@ var require_compile = __commonJS({
3003
3003
  function sameSchemaEnv(s1, s2) {
3004
3004
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3005
3005
  }
3006
- function resolve4(root, ref) {
3006
+ function resolve5(root, ref) {
3007
3007
  let sch;
3008
3008
  while (typeof (sch = this.refs[ref]) == "string")
3009
3009
  ref = sch;
@@ -3833,7 +3833,7 @@ var require_fast_uri = __commonJS({
3833
3833
  }
3834
3834
  return uri;
3835
3835
  }
3836
- function resolve4(baseURI, relativeURI, options) {
3836
+ function resolve5(baseURI, relativeURI, options) {
3837
3837
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3838
3838
  const {
3839
3839
  parsed: baseParsed,
@@ -4202,7 +4202,7 @@ var require_fast_uri = __commonJS({
4202
4202
  var fastUri = {
4203
4203
  SCHEMES,
4204
4204
  normalize,
4205
- resolve: resolve4,
4205
+ resolve: resolve5,
4206
4206
  resolveComponent,
4207
4207
  equal,
4208
4208
  serialize,
@@ -12310,9 +12310,9 @@ async function ensureHostAuth(stateDirectory) {
12310
12310
  compiler.stderr.on("data", (chunk) => {
12311
12311
  diagnostics += String(chunk).slice(0, 4096);
12312
12312
  });
12313
- const exit = await new Promise((resolve4, reject) => {
12313
+ const exit = await new Promise((resolve5, reject) => {
12314
12314
  compiler.on("error", reject);
12315
- compiler.on("exit", (code) => resolve4(code ?? 1));
12315
+ compiler.on("exit", (code) => resolve5(code ?? 1));
12316
12316
  });
12317
12317
  if (exit !== 0) {
12318
12318
  await unlink4(temporary).catch(() => {
@@ -12343,9 +12343,9 @@ async function verifyHostPassword(stateDirectory, username, password) {
12343
12343
  });
12344
12344
  const timer = setTimeout(() => child.kill("SIGKILL"), 1e4);
12345
12345
  try {
12346
- const code = await new Promise((resolve4, reject) => {
12346
+ const code = await new Promise((resolve5, reject) => {
12347
12347
  child.on("error", reject);
12348
- child.on("exit", (value) => resolve4(value ?? 1));
12348
+ child.on("exit", (value) => resolve5(value ?? 1));
12349
12349
  });
12350
12350
  return code === 0 && output.trim() === "OK";
12351
12351
  } finally {
@@ -12359,12 +12359,12 @@ var ScreenLockError = class extends Error {
12359
12359
  var nativeErrors = {
12360
12360
  permission: "\u8BF7\u5728 Proxy \u4E3B\u673A\u201C\u7CFB\u7EDF\u8BBE\u7F6E \u2192 \u9690\u79C1\u4E0E\u5B89\u5168\u6027 \u2192 \u8F85\u52A9\u529F\u80FD\u201D\u4E2D\u6388\u6743\u5B9E\u9645\u8FD0\u884C Proxy \u7684\u7A0B\u5E8F\uFF0C\u5E76\u91CD\u542F Proxy\u3002",
12361
12361
  session: "\u65E0\u6CD5\u8BBF\u95EE Proxy \u7528\u6237\u7684\u6D3B\u52A8\u56FE\u5F62\u4F1A\u8BDD\uFF1B\u4E0D\u652F\u6301\u767B\u5F55\u524D\u3001FileVault \u542F\u52A8\u89E3\u9501\u6216\u5207\u6362\u5230\u5176\u4ED6\u7528\u6237\u7684\u5C4F\u5E55\u3002",
12362
- focus: "\u672A\u627E\u5230 loginwindow \u7684\u5BC6\u7801\u8F93\u5165\u6846\uFF0C\u8BF7\u5728\u4E3B\u673A\u663E\u793A\u5F53\u524D\u7528\u6237\u7684\u5BC6\u7801\u89E3\u9501\u754C\u9762\u540E\u91CD\u8BD5\u3002",
12363
- changed: "\u4E3B\u673A\u9501\u5C4F\u72B6\u6001\u6216\u5BC6\u7801\u8F93\u5165\u7126\u70B9\u5DF2\u53D8\u5316\uFF0C\u5DF2\u505C\u6B62\u8F93\u5165\uFF0C\u8BF7\u91CD\u8BD5\u3002",
12362
+ focus: "\u65E0\u6CD5\u5411\u4E3B\u673A\u9501\u5C4F\u754C\u9762\u53D1\u9001\u5524\u9192\u6309\u952E\uFF0C\u5C1A\u672A\u8F93\u5165\u5BC6\u7801\u3002\u8BF7\u68C0\u67E5\u4E3B\u673A\u6D3B\u52A8\u56FE\u5F62\u4F1A\u8BDD\u540E\u91CD\u8BD5\u3002",
12363
+ changed: "\u4E3B\u673A\u9501\u5C4F\u72B6\u6001\u6216\u6D3B\u52A8\u4F1A\u8BDD\u5DF2\u53D8\u5316\uFF0C\u5DF2\u505C\u6B62\u8F93\u5165\uFF0C\u8BF7\u91CD\u8BD5\u3002",
12364
12364
  timeout: "\u4E3B\u673A\u5C4F\u5E55\u4ECD\u9501\u5B9A\uFF0C\u7CFB\u7EDF\u672A\u63A5\u53D7\u89E3\u9501\u8F93\u5165\uFF1B\u8BF7\u68C0\u67E5\u8F85\u52A9\u529F\u80FD\u6743\u9650\u548C\u9501\u5C4F\u754C\u9762\u540E\u91CD\u8BD5\u3002",
12365
12365
  password: "\u4E3B\u673A\u5BC6\u7801\u683C\u5F0F\u65E0\u6548\u3002"
12366
12366
  };
12367
- var executeScreenLock = (file, args, input, signal) => new Promise((resolve4, reject) => {
12367
+ var executeScreenLock = (file, args, input, signal) => new Promise((resolve5, reject) => {
12368
12368
  if (signal?.aborted) {
12369
12369
  reject(new ScreenLockError("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u5C4F\u5E55\u89E3\u9501\u5DF2\u53D6\u6D88\u3002"));
12370
12370
  return;
@@ -12392,7 +12392,7 @@ var executeScreenLock = (file, args, input, signal) => new Promise((resolve4, re
12392
12392
  clearTimeout(timer);
12393
12393
  if (signal?.aborted) reject(new ScreenLockError("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u5C4F\u5E55\u89E3\u9501\u5DF2\u53D6\u6D88\u3002"));
12394
12394
  else if (code !== 0 || output.length > 4096) reject(new ScreenLockError(timedOut ? "\u5C4F\u5E55\u89E3\u9501\u64CD\u4F5C\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5\u4E3B\u673A\u72B6\u6001\u540E\u91CD\u8BD5\u3002" : nativeErrors[diagnostic.trim()] ?? "\u5C4F\u5E55\u89E3\u9501\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u4E3B\u673A\u7684\u56FE\u5F62\u4F1A\u8BDD\u3001\u7CFB\u7EDF\u6743\u9650\u53CA\u4F9D\u8D56\u3002"));
12395
- else resolve4(output);
12395
+ else resolve5(output);
12396
12396
  });
12397
12397
  child.stdin.end(input);
12398
12398
  });
@@ -15059,8 +15059,8 @@ var OpenCodeAdapter = class {
15059
15059
  return;
15060
15060
  }
15061
15061
  if (["failed", "expired"].includes(state.status)) throw new Error("OpenCode OAuth failed");
15062
- await new Promise((resolve4, reject) => {
15063
- const timer = setTimeout(resolve4, 1e3);
15062
+ await new Promise((resolve5, reject) => {
15063
+ const timer = setTimeout(resolve5, 1e3);
15064
15064
  controller.signal.addEventListener("abort", () => {
15065
15065
  clearTimeout(timer);
15066
15066
  reject(controller.signal.reason);
@@ -15120,7 +15120,7 @@ var OpenCodeAdapter = class {
15120
15120
  } catch {
15121
15121
  if (!controller.signal.aborted) this.emit(project, "octopus.disconnected", {});
15122
15122
  }
15123
- if (!controller.signal.aborted) await new Promise((resolve4) => setTimeout(resolve4, 2e3));
15123
+ if (!controller.signal.aborted) await new Promise((resolve5) => setTimeout(resolve5, 2e3));
15124
15124
  }
15125
15125
  };
15126
15126
  void loop();
@@ -15172,8 +15172,8 @@ var OpenCodeAdapter = class {
15172
15172
  this.emit(project, "terminal.closed", { id: p.id });
15173
15173
  }
15174
15174
  });
15175
- await new Promise((resolve4, reject) => {
15176
- socket2.once("open", resolve4);
15175
+ await new Promise((resolve5, reject) => {
15176
+ socket2.once("open", resolve5);
15177
15177
  socket2.once("error", reject);
15178
15178
  });
15179
15179
  socket2.on("error", () => {
@@ -15356,8 +15356,8 @@ var Runtime = class {
15356
15356
  detached: true,
15357
15357
  stdio: ["ignore", log.fd, log.fd]
15358
15358
  });
15359
- await new Promise((resolve4, reject) => {
15360
- child.once("spawn", resolve4);
15359
+ await new Promise((resolve5, reject) => {
15360
+ child.once("spawn", resolve5);
15361
15361
  child.once("error", reject);
15362
15362
  });
15363
15363
  child.on("error", () => {
@@ -15385,7 +15385,7 @@ var Runtime = class {
15385
15385
  while (Date.now() < deadline) {
15386
15386
  if (child.exitCode !== null || child.signalCode !== null) break;
15387
15387
  if (await this.health()) return this.status();
15388
- await new Promise((resolve4) => setTimeout(resolve4, 250));
15388
+ await new Promise((resolve5) => setTimeout(resolve5, 250));
15389
15389
  }
15390
15390
  if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
15391
15391
  await rm2(this.endpointStatePath, { force: true }).catch(() => {
@@ -15410,13 +15410,13 @@ var Runtime = class {
15410
15410
  async reserveFallbackUrl(configured) {
15411
15411
  const host = configured.hostname === "[::1]" ? "::1" : configured.hostname;
15412
15412
  const server = createServer();
15413
- await new Promise((resolve4, reject) => {
15413
+ await new Promise((resolve5, reject) => {
15414
15414
  server.once("error", reject);
15415
- server.listen(0, host, resolve4);
15415
+ server.listen(0, host, resolve5);
15416
15416
  });
15417
15417
  const address = server.address();
15418
15418
  const port = typeof address === "object" && address ? address.port : 0;
15419
- await new Promise((resolve4, reject) => server.close((error) => error ? reject(error) : resolve4()));
15419
+ await new Promise((resolve5, reject) => server.close((error) => error ? reject(error) : resolve5()));
15420
15420
  if (!port) throw new Error("\u65E0\u6CD5\u4E3A OpenCode \u5206\u914D\u5907\u7528\u56DE\u73AF\u7AEF\u53E3");
15421
15421
  const fallback = new URL(configured.origin);
15422
15422
  fallback.port = String(port);
@@ -15483,7 +15483,7 @@ var LineRpc = class {
15483
15483
  return this.closed || this.child.exitCode !== null || this.child.signalCode !== null;
15484
15484
  }
15485
15485
  request(method, params, timeoutMs = 35e3) {
15486
- const attempt = (wrapped) => new Promise((resolve4, reject) => {
15486
+ const attempt = (wrapped) => new Promise((resolve5, reject) => {
15487
15487
  if (this.closed) {
15488
15488
  reject(interrupted(`App server connection closed before ${method}`));
15489
15489
  return;
@@ -15493,7 +15493,7 @@ var LineRpc = class {
15493
15493
  this.pending.delete(String(id));
15494
15494
  reject(Object.assign(new Error(`Timed out waiting for ${method}`), { name: "TimeoutError" }));
15495
15495
  }, timeoutMs);
15496
- this.pending.set(String(id), { resolve: (value) => resolve4(value), reject, timer });
15496
+ this.pending.set(String(id), { resolve: (value) => resolve5(value), reject, timer });
15497
15497
  try {
15498
15498
  this.write(this.protocol === "pi" ? { ...params, type: method, id: String(id) } : wrapped ? wrapEnvelope({ method, id, params }) : { method, id, params });
15499
15499
  } catch (error) {
@@ -15710,7 +15710,7 @@ var StdioRuntime = class {
15710
15710
  if (!child) return;
15711
15711
  this.refreshing.add(projectId);
15712
15712
  try {
15713
- const exited = child.exitCode !== null || child.signalCode !== null ? Promise.resolve() : new Promise((resolve4) => child.once("exit", () => resolve4()));
15713
+ const exited = child.exitCode !== null || child.signalCode !== null ? Promise.resolve() : new Promise((resolve5) => child.once("exit", () => resolve5()));
15714
15714
  this.kill(projectId);
15715
15715
  await exited;
15716
15716
  } finally {
@@ -15837,8 +15837,8 @@ ${bytes.toString("utf8")}`);
15837
15837
  if (this.children.get(projectId) === child) this.onExited(projectId, `\u8FDB\u7A0B\u9000\u51FA\uFF08${code ?? signal}\uFF09`);
15838
15838
  });
15839
15839
  try {
15840
- await new Promise((resolve4, reject) => {
15841
- child.once("spawn", resolve4);
15840
+ await new Promise((resolve5, reject) => {
15841
+ child.once("spawn", resolve5);
15842
15842
  child.once("error", reject);
15843
15843
  });
15844
15844
  this.failures.delete(projectId);
@@ -17191,11 +17191,11 @@ ${bytes.toString("utf8")}`);
17191
17191
  this.processes.delete(key);
17192
17192
  const { child, rpc } = entry;
17193
17193
  if (!child.pid || child.exitCode !== null || child.signalCode !== null) return;
17194
- await new Promise((resolve4) => {
17194
+ await new Promise((resolve5) => {
17195
17195
  const timer = setTimeout(() => child.kill("SIGKILL"), 5e3);
17196
17196
  child.once("exit", () => {
17197
17197
  clearTimeout(timer);
17198
- resolve4();
17198
+ resolve5();
17199
17199
  });
17200
17200
  rpc.destroy();
17201
17201
  child.kill("SIGTERM");
@@ -17347,10 +17347,61 @@ var ProjectRegistry = class {
17347
17347
 
17348
17348
  // src/terminal.ts
17349
17349
  import { randomUUID as randomUUID16 } from "node:crypto";
17350
- import { access } from "node:fs/promises";
17351
- import { constants as constants4 } from "node:fs";
17350
+ import { access as access2, stat as stat10 } from "node:fs/promises";
17351
+ import { constants as constants5 } from "node:fs";
17352
17352
  import { platform as platform3, userInfo as userInfo4 } from "node:os";
17353
17353
  import * as pty from "node-pty";
17354
+
17355
+ // src/terminal-support.ts
17356
+ import { createRequire } from "node:module";
17357
+ import { access, chmod as chmod4, stat as stat9 } from "node:fs/promises";
17358
+ import { constants as constants4 } from "node:fs";
17359
+ import { dirname as dirname6, resolve as resolve4 } from "node:path";
17360
+ var require2 = createRequire(import.meta.url);
17361
+ function terminalHelperPath() {
17362
+ const utilsPath = require2.resolve("node-pty/lib/utils.js");
17363
+ const native = require2(utilsPath).loadNativeModule("pty");
17364
+ return resolve4(dirname6(utilsPath), native.dir, "spawn-helper");
17365
+ }
17366
+ async function prepareTerminalHelper(path, fix) {
17367
+ const info = await stat9(path);
17368
+ if (!info.isFile()) throw new Error(`\u7EC8\u7AEF\u8F85\u52A9\u7A0B\u5E8F\u4E0D\u662F\u666E\u901A\u6587\u4EF6\uFF1A${path}`);
17369
+ try {
17370
+ await access(path, constants4.X_OK);
17371
+ return false;
17372
+ } catch {
17373
+ if (!fix) throw new Error(`\u7EC8\u7AEF\u8F85\u52A9\u7A0B\u5E8F\u7F3A\u5C11\u6267\u884C\u6743\u9650\uFF1A${path}\uFF1B\u8FD0\u884C octopus-proxy doctor --fix \u4FEE\u590D`);
17374
+ try {
17375
+ await chmod4(path, info.mode & 511 | 73);
17376
+ await access(path, constants4.X_OK);
17377
+ return true;
17378
+ } catch (error) {
17379
+ throw new Error(`\u65E0\u6CD5\u4FEE\u590D\u7EC8\u7AEF\u8F85\u52A9\u7A0B\u5E8F\u6267\u884C\u6743\u9650\uFF1A${path}\uFF1B\u8BF7\u7531\u5B89\u88C5\u8BE5 Proxy \u7684\u8D26\u53F7\u8FD0\u884C octopus-proxy doctor --fix\uFF0C\u6216\u91CD\u65B0\u5B89\u88C5 Proxy\u3002${error instanceof Error ? error.message : String(error)}`);
17380
+ }
17381
+ }
17382
+ }
17383
+ async function ensureTerminalSupport(fix = true) {
17384
+ if (process.platform !== "darwin") return { repaired: false };
17385
+ const helper = terminalHelperPath();
17386
+ try {
17387
+ return { helper, repaired: await prepareTerminalHelper(helper, fix) };
17388
+ } catch (error) {
17389
+ if (error.code === "ENOENT") {
17390
+ throw new Error(`\u7EC8\u7AEF\u8F85\u52A9\u7A0B\u5E8F\u7F3A\u5931\uFF1A${helper}\uFF1B\u8BF7\u91CD\u65B0\u5B89\u88C5 @skylandnpm/octopus-proxy`);
17391
+ }
17392
+ throw error;
17393
+ }
17394
+ }
17395
+ async function checkTerminalSupport(fix) {
17396
+ try {
17397
+ const result = await ensureTerminalSupport(fix);
17398
+ return { name: "\u7EC8\u7AEF PTY \u652F\u6301", status: "ok", detail: result.helper ? `${result.repaired ? "\u5DF2\u4FEE\u590D\u6267\u884C\u6743\u9650\uFF1A" : ""}${result.helper}\uFF08${process.arch}\uFF09` : "\u5F53\u524D\u5E73\u53F0\u65E0\u9700 macOS spawn-helper" };
17399
+ } catch (error) {
17400
+ return { name: "\u7EC8\u7AEF PTY \u652F\u6301", status: "fail", detail: error instanceof Error ? error.message : String(error) };
17401
+ }
17402
+ }
17403
+
17404
+ // src/terminal.ts
17354
17405
  var TerminalManager = class {
17355
17406
  constructor(workspace, emit) {
17356
17407
  this.workspace = workspace;
@@ -17372,16 +17423,27 @@ var TerminalManager = class {
17372
17423
  const count = [...this.terminals.values()].filter((t) => t.owner === owner).length;
17373
17424
  if (count >= 8) throw new Error("Terminal limit reached");
17374
17425
  const cwd = await this.workspace.path(project, typeof p.cwd === "string" ? p.cwd : ".");
17426
+ if (!(await stat10(cwd)).isDirectory()) throw new Error(`\u7EC8\u7AEF\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u76EE\u5F55\uFF1A${cwd}`);
17375
17427
  const shell = process.env.SHELL || (platform3() === "darwin" ? "/bin/zsh" : "/bin/sh");
17376
- await access(shell, constants4.X_OK);
17428
+ try {
17429
+ await access2(shell, constants5.X_OK);
17430
+ } catch {
17431
+ throw new Error(`\u7EC8\u7AEF Shell \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C\uFF1A${shell}\uFF1B\u8BF7\u4FEE\u6B63 Proxy \u670D\u52A1\u7684 SHELL \u73AF\u5883\u53D8\u91CF`);
17432
+ }
17433
+ const { helper } = await ensureTerminalSupport();
17377
17434
  const id = randomUUID16();
17378
- const terminalProcess = pty.spawn(shell, [], {
17379
- name: "xterm-256color",
17380
- cols: 80,
17381
- rows: 24,
17382
- cwd,
17383
- env: { PATH: process.env.PATH || "/usr/bin:/bin", HOME: userInfo4().homedir, TERM: "xterm-256color", USER: userInfo4().username }
17384
- });
17435
+ let terminalProcess;
17436
+ try {
17437
+ terminalProcess = pty.spawn(shell, [], {
17438
+ name: "xterm-256color",
17439
+ cols: 80,
17440
+ rows: 24,
17441
+ cwd,
17442
+ env: { PATH: process.env.PATH || "/usr/bin:/bin", HOME: userInfo4().homedir, TERM: "xterm-256color", USER: userInfo4().username }
17443
+ });
17444
+ } catch (error) {
17445
+ throw new Error(`\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}\uFF08shell=${shell}\uFF0Ccwd=${cwd}${helper ? `\uFF0Chelper=${helper}` : ""}\uFF0C${process.platform}/${process.arch}\uFF0CNode ${process.version}\uFF09\uFF1B\u8BF7\u5728 Proxy \u4E3B\u673A\u8FD0\u884C octopus-proxy doctor --fix \u68C0\u67E5`);
17446
+ }
17385
17447
  const terminal2 = { id, project, owner, process: terminalProcess, history: "" };
17386
17448
  this.terminals.set(id, terminal2);
17387
17449
  terminalProcess.onData((data) => {
@@ -17434,7 +17496,7 @@ var TerminalManager = class {
17434
17496
  import { execFile as execFile6 } from "node:child_process";
17435
17497
  import { promisify as promisify6 } from "node:util";
17436
17498
  import { createHash as createHash9, randomUUID as randomUUID17 } from "node:crypto";
17437
- import { chmod as chmod4, mkdir as mkdir10, mkdtemp, readFile as readFile9, rename as rename5, rm as rm4, stat as stat9 } from "node:fs/promises";
17499
+ import { chmod as chmod5, mkdir as mkdir10, mkdtemp, readFile as readFile9, rename as rename5, rm as rm4, stat as stat11 } from "node:fs/promises";
17438
17500
  import { join as join13 } from "node:path";
17439
17501
  import { fileURLToPath as fileURLToPath5 } from "node:url";
17440
17502
  var DESKTOP_IMAGE_LIMIT = 20 * 1024 * 1024;
@@ -17504,12 +17566,12 @@ var PlatformDesktop = class {
17504
17566
  await mkdir10(this.stateDirectory, { recursive: true, mode: 448 });
17505
17567
  const target = join13(this.stateDirectory, "octopus-desktop-capture");
17506
17568
  const source = fileURLToPath5(new URL("../native/desktop-macos.m", import.meta.url));
17507
- const sourceInfo = await stat9(source), existing = await stat9(target).catch(() => null);
17569
+ const sourceInfo = await stat11(source), existing = await stat11(target).catch(() => null);
17508
17570
  if (existing?.isFile() && existing.mtimeMs >= sourceInfo.mtimeMs && (existing.mode & 63) === 0) return target;
17509
17571
  const temporary = `${target}.${randomUUID17()}`;
17510
17572
  try {
17511
17573
  await this.execute("/usr/bin/clang", ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "VideoToolbox", "-framework", "ImageIO", source, "-o", temporary]);
17512
- await chmod4(temporary, 448);
17574
+ await chmod5(temporary, 448);
17513
17575
  await rename5(temporary, target);
17514
17576
  return target;
17515
17577
  } finally {
@@ -17617,7 +17679,7 @@ var PlatformDesktop = class {
17617
17679
  await this.execute("import", args);
17618
17680
  }
17619
17681
  }
17620
- const info = await stat9(output);
17682
+ const info = await stat11(output);
17621
17683
  if (!info.isFile() || !info.size || info.size > DESKTOP_IMAGE_LIMIT) throw new Error("\u684C\u9762\u622A\u56FE\u4E3A\u7A7A\u6216\u8D85\u8FC7 20 MiB \u4E0A\u9650\u3002");
17622
17684
  return { display, bytes: await readFile9(output) };
17623
17685
  } finally {
@@ -17834,6 +17896,7 @@ export {
17834
17896
  ensureHostIdentity,
17835
17897
  createRuntime,
17836
17898
  createAdapter,
17899
+ checkTerminalSupport,
17837
17900
  DESKTOP_PERMISSION_GUIDANCE,
17838
17901
  requestDesktopPermission,
17839
17902
  checkDesktop,
package/dist/cli.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  Workspace,
10
10
  atomicJson,
11
11
  checkDesktop,
12
+ checkTerminalSupport,
12
13
  createAdapter,
13
14
  createRuntime,
14
15
  daemonEntryPath,
@@ -27,10 +28,10 @@ import {
27
28
  runDaemon,
28
29
  supportedPlatform,
29
30
  systemdUnitPath
30
- } from "./chunk-6DTK4JVV.js";
31
+ } from "./chunk-OXA2S7UY.js";
31
32
 
32
33
  // src/cli.ts
33
- import { spawn } from "node:child_process";
34
+ import { spawn as spawn2 } from "node:child_process";
34
35
  import { access as access4 } from "node:fs/promises";
35
36
 
36
37
  // src/doctor.ts
@@ -161,9 +162,9 @@ async function launchdUninstall() {
161
162
  }
162
163
  async function launchdStatus() {
163
164
  try {
164
- const { stdout: stdout2 } = await run2("launchctl", ["print", `${guiDomain()}/${SERVICE_LABEL}`]);
165
- const running = /state = running/.test(stdout2);
166
- const pid = Number(/pid = (\d+)/.exec(stdout2)?.[1]);
165
+ const { stdout: stdout3 } = await run2("launchctl", ["print", `${guiDomain()}/${SERVICE_LABEL}`]);
166
+ const running = /state = running/.test(stdout3);
167
+ const pid = Number(/pid = (\d+)/.exec(stdout3)?.[1]);
167
168
  return { loaded: true, running, pid: Number.isFinite(pid) && pid > 0 ? pid : void 0 };
168
169
  } catch {
169
170
  return { loaded: false, running: false };
@@ -212,8 +213,8 @@ async function writeUnitFile(text) {
212
213
  }
213
214
  async function systemctl(args) {
214
215
  try {
215
- const { stdout: stdout2 } = await run3("systemctl", ["--user", ...args]);
216
- return stdout2;
216
+ const { stdout: stdout3 } = await run3("systemctl", ["--user", ...args]);
217
+ return stdout3;
217
218
  } catch (error) {
218
219
  const stderr = error instanceof Error ? error.stderr ?? error.message : String(error);
219
220
  if (/Failed to connect to bus|D-Bus connection/i.test(String(stderr))) {
@@ -342,6 +343,7 @@ var run4 = promisify4(execFile4);
342
343
  async function runDoctor(opts) {
343
344
  const results = [];
344
345
  const record = (name, status, detail) => results.push({ name, status, detail });
346
+ results.push(await checkTerminalSupport(opts.fix));
345
347
  let config;
346
348
  try {
347
349
  const info = await stat2(opts.configPath).catch((error) => {
@@ -377,8 +379,8 @@ async function runDoctor(opts) {
377
379
  record(checkName, "fail", `${command2} \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C`);
378
380
  } else {
379
381
  try {
380
- const { stdout: stdout2 } = await run4(command2, [...descriptor.versionArgs], { timeout: 15e3 });
381
- const version = stdout2.trim().split("\n")[0].trim();
382
+ const { stdout: stdout3 } = await run4(command2, [...descriptor.versionArgs], { timeout: 15e3 });
383
+ const version = stdout3.trim().split("\n")[0].trim();
382
384
  if (version === descriptor.baseline) record(checkName, "ok", `${command2}\uFF08${version}\uFF09`);
383
385
  else if (descriptor.versionPolicy === "exact") record(checkName, "warn", `${command2} \u7248\u672C\u4E3A ${version}\uFF0C\u5DF2\u9A8C\u8BC1\u57FA\u7EBF\u662F ${descriptor.baseline}\uFF08\u8BF7\u786E\u8BA4\u5916\u90E8\u670D\u52A1 API \u517C\u5BB9\uFF09`);
384
386
  else record(checkName, "warn", `${command2} \u7248\u672C\u4E3A ${version}\uFF0C\u5DF2\u9A8C\u8BC1\u57FA\u7EBF\u662F ${descriptor.baseline}\uFF1B\u5DE5\u5177\u534F\u8BAE\u53EF\u80FD\u6F02\u79FB`);
@@ -719,6 +721,137 @@ async function manageProjects(configPath2, remove) {
719
721
  }
720
722
  }
721
723
 
724
+ // src/update.ts
725
+ import { execFile as execFile6, spawn } from "node:child_process";
726
+ import { readFile as readFile3, realpath as realpath2 } from "node:fs/promises";
727
+ import { join as join4 } from "node:path";
728
+ import { stdin as stdin2, stdout as stdout2 } from "node:process";
729
+ import { createInterface as createInterface2 } from "node:readline/promises";
730
+ import { setTimeout as delay } from "node:timers/promises";
731
+ import { fileURLToPath } from "node:url";
732
+ import { promisify as promisify6 } from "node:util";
733
+ var PACKAGE = "@skylandnpm/octopus-proxy";
734
+ var REGISTRY = "https://registry.npmjs.org";
735
+ var packageDirectory = fileURLToPath(new URL("../", import.meta.url));
736
+ var run6 = promisify6(execFile6);
737
+ function parseVersion(version) {
738
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*))?(?:\+[\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*)?$/.exec(version);
739
+ if (!match) throw new Error(`\u65E0\u6548\u7684\u7248\u672C\u53F7\uFF1A${version}`);
740
+ const prerelease = match[4]?.split(".") ?? [];
741
+ if (prerelease.some((part) => /^\d+$/.test(part) && part.length > 1 && part.startsWith("0"))) throw new Error(`\u65E0\u6548\u7684\u7248\u672C\u53F7\uFF1A${version}`);
742
+ return { core: match.slice(1, 4).map((part) => BigInt(part)), prerelease };
743
+ }
744
+ function isNewerVersion(latest, current) {
745
+ const a = parseVersion(latest), b = parseVersion(current);
746
+ for (let i = 0; i < 3; i++) if (a.core[i] !== b.core[i]) return a.core[i] > b.core[i];
747
+ if (!a.prerelease.length || !b.prerelease.length) return !a.prerelease.length && b.prerelease.length > 0;
748
+ for (let i = 0; i < Math.max(a.prerelease.length, b.prerelease.length); i++) {
749
+ const x = a.prerelease[i], y = b.prerelease[i];
750
+ if (x === y) continue;
751
+ if (x === void 0 || y === void 0) return y === void 0;
752
+ const xNumeric = /^\d+$/.test(x), yNumeric = /^\d+$/.test(y);
753
+ if (xNumeric && yNumeric) return BigInt(x) > BigInt(y);
754
+ if (xNumeric !== yNumeric) return !xNumeric;
755
+ return x > y;
756
+ }
757
+ return false;
758
+ }
759
+ function message(error) {
760
+ if (!(error instanceof Error)) return String(error);
761
+ return String(error.stderr || error.message).trim();
762
+ }
763
+ async function npmOutput(args) {
764
+ try {
765
+ const { stdout: stdout3 } = await run6("npm", args, { timeout: 3e4, maxBuffer: 1024 * 1024 });
766
+ return stdout3.trim();
767
+ } catch (error) {
768
+ throw new Error(`npm ${args[0]} \u5931\u8D25\uFF1A${message(error)}`);
769
+ }
770
+ }
771
+ async function confirmUpdate(latest) {
772
+ if (!stdin2.isTTY) throw new Error("\u66F4\u65B0\u9700\u8981\u5728\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u4E2D\u786E\u8BA4\uFF1B\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C octopus-proxy update");
773
+ const rl = createInterface2({ input: stdin2, output: stdout2 });
774
+ try {
775
+ return /^(y|yes|是|确认)$/i.test((await rl.question(`\u662F\u5426\u8986\u76D6\u5B89\u88C5 ${proxyVersion} \u2192 ${latest}\uFF1F\u66F4\u65B0\u4F1A\u505C\u6B62\u5E76\u91CD\u542F\u670D\u52A1 [y/N]: `)).trim());
776
+ } finally {
777
+ rl.close();
778
+ }
779
+ }
780
+ async function installVersion(version, prefix) {
781
+ const child = spawn("npm", ["install", "--global", "--prefix", prefix, `${PACKAGE}@${version}`, "--prefer-online", "--registry", REGISTRY], { stdio: "inherit" });
782
+ await new Promise((resolve2, reject) => {
783
+ child.once("error", (error) => reject(new Error(`\u65E0\u6CD5\u6267\u884C npm install\uFF1A${message(error)}`)));
784
+ child.once("exit", (code, signal) => code === 0 ? resolve2() : reject(new Error(`npm install \u5931\u8D25\uFF08${signal ? `\u4FE1\u53F7 ${signal}` : `\u9000\u51FA\u7801 ${code}`}\uFF09`)));
785
+ });
786
+ }
787
+ async function restartAndVerify() {
788
+ await controlService("restart");
789
+ for (let attempt = 0; attempt < 20; attempt++) {
790
+ if ((await serviceStatus()).active) return;
791
+ await delay(250);
792
+ }
793
+ throw new Error(`\u670D\u52A1\u91CD\u542F\u540E\u672A\u8FD0\u884C\uFF1B\u8BF7\u8FD0\u884C octopus-proxy doctor / logs \u68C0\u67E5\uFF08${logLocationHint()}\uFF09`);
794
+ }
795
+ async function runUpdate() {
796
+ console.log(`\u5F53\u524D\u7248\u672C\uFF1A${proxyVersion}\uFF1B\u6B63\u5728\u67E5\u8BE2 npm \u6700\u65B0\u7248\u672C\u2026`);
797
+ const output = await npmOutput(["view", `${PACKAGE}@latest`, "version", "--json", "--prefer-online", "--registry", REGISTRY]);
798
+ let latest;
799
+ try {
800
+ latest = JSON.parse(output);
801
+ } catch {
802
+ throw new Error("npm view \u8FD4\u56DE\u4E86\u65E0\u6548\u7684\u7248\u672C\u4FE1\u606F");
803
+ }
804
+ if (typeof latest !== "string") throw new Error("npm view \u8FD4\u56DE\u4E86\u65E0\u6548\u7684\u7248\u672C\u4FE1\u606F");
805
+ if (!isNewerVersion(latest, proxyVersion)) {
806
+ console.log(`\u65E0\u9700\u66F4\u65B0\uFF1A\u5F53\u524D\u7248\u672C ${proxyVersion}\uFF0Cnpm \u6700\u65B0\u7248\u672C ${latest}`);
807
+ return;
808
+ }
809
+ console.log(`\u53D1\u73B0\u65B0\u7248\u672C\uFF1A${latest}`);
810
+ if (!await confirmUpdate(latest)) {
811
+ console.log("\u5DF2\u53D6\u6D88\u66F4\u65B0");
812
+ return;
813
+ }
814
+ const prefix = await npmOutput(["prefix", "--global"]);
815
+ const target = join4(prefix, "lib", "node_modules", PACKAGE);
816
+ const installedDirectory = await realpath2(target).catch(() => null);
817
+ if (installedDirectory !== await realpath2(packageDirectory)) {
818
+ throw new Error("\u5F53\u524D\u547D\u4EE4\u4E0D\u5C5E\u4E8E npm \u7684\u5168\u5C40\u5B89\u88C5\u76EE\u5F55\uFF1B\u8BF7\u786E\u8BA4 npm \u4E0E octopus-proxy \u4F7F\u7528\u540C\u4E00\u5957 Node.js \u73AF\u5883\uFF0C\u5E76\u4ECE\u5168\u5C40\u5B89\u88C5\u8FD0\u884C update");
819
+ }
820
+ const state = await serviceStatus();
821
+ try {
822
+ if (state.installed) {
823
+ console.log("\u6B63\u5728\u505C\u6B62\u670D\u52A1\u2026");
824
+ await controlService("stop");
825
+ if ((await serviceStatus()).active) throw new Error("\u670D\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u5DF2\u4E2D\u6B62\u8986\u76D6\u5B89\u88C5");
826
+ }
827
+ console.log(`\u6B63\u5728\u5B89\u88C5 ${PACKAGE}@${latest}\u2026`);
828
+ await installVersion(latest, prefix);
829
+ const installed = JSON.parse(await readFile3(join4(target, "package.json"), "utf8"));
830
+ if (installed.version !== latest) throw new Error(`\u5B89\u88C5\u7248\u672C\u6821\u9A8C\u5931\u8D25\uFF1A\u671F\u671B ${latest}\uFF0C\u5B9E\u9645 ${installed.version ?? "\u672A\u77E5"}`);
831
+ } catch (error) {
832
+ let recovery = "";
833
+ if (state.active) {
834
+ console.log("\u66F4\u65B0\u5931\u8D25\uFF0C\u6B63\u5728\u5C1D\u8BD5\u6062\u590D\u670D\u52A1\u2026");
835
+ try {
836
+ await restartAndVerify();
837
+ recovery = "\uFF1B\u670D\u52A1\u5DF2\u6062\u590D\u8FD0\u884C";
838
+ } catch (restartError) {
839
+ recovery = `\uFF1B\u670D\u52A1\u6062\u590D\u5931\u8D25\uFF1A${message(restartError)}`;
840
+ }
841
+ }
842
+ throw new Error(`\u66F4\u65B0\u5931\u8D25\uFF1A${message(error)}${recovery}`);
843
+ }
844
+ if (state.installed) {
845
+ console.log("\u5B89\u88C5\u5B8C\u6210\uFF0C\u6B63\u5728\u91CD\u542F\u670D\u52A1\u2026");
846
+ try {
847
+ await restartAndVerify();
848
+ } catch (error) {
849
+ throw new Error(`\u5DF2\u66F4\u65B0\u81F3 ${latest}\uFF0C\u4F46\u670D\u52A1\u91CD\u542F\u5931\u8D25\uFF1A${message(error)}`);
850
+ }
851
+ }
852
+ console.log(`\u66F4\u65B0\u6210\u529F\uFF1A${proxyVersion} \u2192 ${latest}\uFF1B${state.installed ? "\u670D\u52A1\u5DF2\u91CD\u542F\u5E76\u8FD0\u884C" : "\u5C1A\u672A\u5B89\u88C5\u670D\u52A1\uFF0C\u53EF\u8FD0\u884C octopus-proxy install \u5B89\u88C5"}`);
853
+ }
854
+
722
855
  // src/cli.ts
723
856
  var USAGE = `octopus-proxy \u2014 Octopus \u4E3B\u673A\u4EE3\u7406
724
857
 
@@ -729,6 +862,7 @@ var USAGE = `octopus-proxy \u2014 Octopus \u4E3B\u673A\u4EE3\u7406
729
862
  projects \u5217\u51FA\u6388\u6743\u9879\u76EE\uFF1B\u53EF\u6309\u5E8F\u53F7\u5220\u9664\u9519\u8BEF\u914D\u7F6E\u9879
730
863
  install \u6821\u9A8C\u914D\u7F6E\u540E\u5B89\u88C5/\u66F4\u65B0\u7CFB\u7EDF\u670D\u52A1\uFF08\u5E42\u7B49\uFF0C\u81EA\u52A8\u542F\u52A8\uFF09
731
864
  uninstall \u505C\u6B62\u5E76\u79FB\u9664\u7CFB\u7EDF\u670D\u52A1\uFF08\u4FDD\u7559\u914D\u7F6E\u4E0E\u72B6\u6001\u6570\u636E\uFF09
865
+ update \u68C0\u67E5 npm \u6700\u65B0\u7248\u672C\uFF0C\u786E\u8BA4\u540E\u505C\u6B62\u670D\u52A1\u3001\u8986\u76D6\u5B89\u88C5\u5E76\u91CD\u542F
732
866
  start \u542F\u52A8\u670D\u52A1
733
867
  stop \u505C\u6B62\u670D\u52A1
734
868
  restart \u91CD\u542F\u670D\u52A1
@@ -804,7 +938,7 @@ function parse(argv) {
804
938
  return { command: command2, flags: flags2, projectArgs: projectArgs2 };
805
939
  }
806
940
  async function showLogs(follow) {
807
- const child = isMacOS() ? spawn("tail", ["-n", "200", ...follow ? ["-F"] : [], launchdLogPath()], { stdio: "inherit" }) : spawn("journalctl", ["--user", "-u", SYSTEMD_UNIT, "-n", "200", ...follow ? ["-f"] : ["--no-pager"]], { stdio: "inherit" });
941
+ const child = isMacOS() ? spawn2("tail", ["-n", "200", ...follow ? ["-F"] : [], launchdLogPath()], { stdio: "inherit" }) : spawn2("journalctl", ["--user", "-u", SYSTEMD_UNIT, "-n", "200", ...follow ? ["-f"] : ["--no-pager"]], { stdio: "inherit" });
808
942
  if (isMacOS() && !await access4(launchdLogPath()).then(() => true, () => false)) {
809
943
  console.error(`\u65E5\u5FD7\u6587\u4EF6\u5C1A\u672A\u751F\u6210\uFF1A${launchdLogPath()}`);
810
944
  process.exit(1);
@@ -855,6 +989,9 @@ try {
855
989
  case "projects":
856
990
  await manageProjects(configPath, flags["--remove"]);
857
991
  break;
992
+ case "update":
993
+ await runUpdate();
994
+ break;
858
995
  case "identity": {
859
996
  const config = await loadConfig(configPath);
860
997
  const identity = await ensureHostIdentity(config.stateDirectory);
package/dist/main.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ConfigError,
4
4
  runDaemon
5
- } from "./chunk-6DTK4JVV.js";
5
+ } from "./chunk-OXA2S7UY.js";
6
6
 
7
7
  // src/main.ts
8
8
  runDaemon(process.env.OCTOPUS_PROXY_CONFIG ?? "proxy.json").catch((error) => {
@@ -2,6 +2,7 @@
2
2
  #import <ApplicationServices/ApplicationServices.h>
3
3
  #include <unistd.h>
4
4
  #include <string.h>
5
+ #include <libproc.h>
5
6
 
6
7
  static void wipe(void *buffer, size_t length) {
7
8
  volatile unsigned char *bytes = buffer;
@@ -29,39 +30,31 @@ static void printStatus(BOOL locked) {
29
30
  AXIsProcessTrusted() && CGPreflightPostEventAccess() ? "true" : "false");
30
31
  }
31
32
 
32
- static AXUIElementRef passwordFocus(void) {
33
- AXUIElementRef system = AXUIElementCreateSystemWide();
34
- AXUIElementSetMessagingTimeout(system, 0.5);
35
- CFTypeRef focused = NULL;
36
- AXError result = AXUIElementCopyAttributeValue(system, kAXFocusedUIElementAttribute, &focused);
37
- CFRelease(system);
38
- if (result != kAXErrorSuccess || !focused || CFGetTypeID(focused) != AXUIElementGetTypeID()) {
39
- if (focused) CFRelease(focused);
40
- return NULL;
41
- }
42
- pid_t pid = 0;
43
- AXUIElementRef field = (AXUIElementRef)focused;
44
- AXUIElementSetMessagingTimeout(field, 0.5);
45
- NSRunningApplication *app = nil;
46
- if (AXUIElementGetPid(field, &pid) == kAXErrorSuccess) app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid];
47
- CFTypeRef subrole = NULL;
48
- AXUIElementCopyAttributeValue(field, kAXSubroleAttribute, &subrole);
49
- BOOL secure = subrole && CFEqual(subrole, kAXSecureTextFieldSubrole);
50
- if (subrole) CFRelease(subrole);
51
- if (!secure || ![app.bundleIdentifier isEqualToString:@"com.apple.loginwindow"] ||
52
- ![app.executableURL.path isEqualToString:@"/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow"]) {
53
- CFRelease(field); return NULL;
54
- }
55
- return field;
33
+ // loginwindow belongs to a system process and is not guaranteed to appear in
34
+ // NSRunningApplication. Verify its executable through libproc instead.
35
+ static BOOL isLoginWindow(pid_t pid) {
36
+ char path[PROC_PIDPATHINFO_MAXSIZE] = {0};
37
+ return pid > 0 && proc_pidpath(pid, path, sizeof(path)) > 0 &&
38
+ strcmp(path, "/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow") == 0;
56
39
  }
57
40
 
58
- static BOOL stillFocused(AXUIElementRef field) {
59
- BOOL locked = NO;
60
- if (!lockState(&locked) || !locked) return NO;
61
- AXUIElementRef current = passwordFocus();
62
- BOOL same = current && CFEqual(current, field);
63
- if (current) CFRelease(current);
64
- return same;
41
+ static pid_t loginWindowPID(void) {
42
+ static pid_t cached = 0;
43
+ if (isLoginWindow(cached)) return cached;
44
+ int size = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
45
+ if (size <= 0 || size > 1024 * 1024) return 0;
46
+ pid_t *pids = calloc(1, size);
47
+ if (!pids) return 0;
48
+ int actual = proc_listpids(PROC_ALL_PIDS, 0, pids, size);
49
+ cached = 0;
50
+ for (int i = 0; i < actual / (int)sizeof(pid_t); i++) {
51
+ char name[64] = {0};
52
+ if (proc_name(pids[i], name, sizeof(name)) > 0 && strcmp(name, "loginwindow") == 0 && isLoginWindow(pids[i])) {
53
+ cached = pids[i]; break;
54
+ }
55
+ }
56
+ free(pids);
57
+ return cached;
65
58
  }
66
59
 
67
60
  static BOOL key(CGKeyCode code, CGEventFlags flags, const UniChar *text, size_t length) {
@@ -76,12 +69,41 @@ static BOOL key(CGKeyCode code, CGEventFlags flags, const UniChar *text, size_t
76
69
  return YES;
77
70
  }
78
71
 
72
+ // The locked console owns keyboard input. Space reveals the password prompt,
73
+ // and Backspace removes that Space if the field was already visible. Do not
74
+ // depend on loginwindow exposing an Accessibility tree (some versions do not).
75
+ static BOOL wakePasswordPrompt(void) {
76
+ const CGKeyCode keys[] = {56, 49, 51};
77
+ const useconds_t settle[] = {1000000, 500000, 200000};
78
+ for (int i = 0; i < 3; i++) {
79
+ BOOL locked = NO;
80
+ if (!lockState(&locked)) return NO;
81
+ if (!locked) return YES;
82
+ if (!key(keys[i], 0, NULL, 0)) return NO;
83
+ usleep(settle[i]);
84
+ }
85
+ return YES;
86
+ }
87
+
88
+ static BOOL lockedKey(CGKeyCode code, CGEventFlags flags, const UniChar *text, size_t length) {
89
+ BOOL locked = NO;
90
+ return lockState(&locked) && locked && key(code, flags, text, length);
91
+ }
92
+
93
+ static void printDiagnostics(BOOL locked) {
94
+ // Read-only; no wake, focus, field contents, screenshots, or input events.
95
+ printf("{\"locked\":%s,\"canUnlock\":%s,\"loginwindowFound\":%s,\"inputMethod\":\"space-backspace\"}",
96
+ locked ? "true" : "false", AXIsProcessTrusted() && CGPreflightPostEventAccess() ? "true" : "false",
97
+ loginWindowPID() ? "true" : "false");
98
+ }
99
+
79
100
  int main(int argc, const char *argv[]) {
80
101
  @autoreleasepool {
81
102
  if (getuid() == 0 || argc != 2) return fail("session");
82
103
  BOOL locked = NO;
83
104
  if (!lockState(&locked)) return fail("session");
84
105
  if (strcmp(argv[1], "status") == 0) { printStatus(locked); return 0; }
106
+ if (strcmp(argv[1], "diagnose") == 0) { printDiagnostics(locked); return 0; }
85
107
  if (strcmp(argv[1], "unlock") != 0) return fail("session");
86
108
  if (!locked) { printStatus(NO); return 0; }
87
109
  if (!AXIsProcessTrusted() || !CGPreflightPostEventAccess()) return fail("permission");
@@ -92,31 +114,27 @@ int main(int argc, const char *argv[]) {
92
114
  NSString *password = [[NSString alloc] initWithBytes:bytes length:count encoding:NSUTF8StringEncoding];
93
115
  wipe(bytes, sizeof(bytes));
94
116
  if (!password || [password rangeOfCharacterFromSet:NSCharacterSet.controlCharacterSet].location != NSNotFound) return fail("password");
95
- // A non-text key wakes the password prompt. Never type a secret into an
96
- // arbitrary app, username field, clipboard, or a blind screen coordinate.
97
- if (!lockState(&locked) || !locked) return fail("changed");
98
- if (!key(56, 0, NULL, 0)) return fail("focus");
99
- AXUIElementRef field = NULL;
100
- for (int i = 0; i < 20 && !field; i++) { usleep(100000); field = passwordFocus(); }
101
- if (!field) return fail("focus");
102
- if (!stillFocused(field) || !key(0, kCGEventFlagMaskCommand, NULL, 0) ||
103
- !stillFocused(field) || !key(51, 0, NULL, 0)) { CFRelease(field); return fail("changed"); }
117
+ if (!wakePasswordPrompt()) return fail(lockState(&locked) ? "focus" : "session");
118
+ if (!lockState(&locked)) return fail("session");
119
+ // Saver dismissal, Touch ID or a manual unlock may finish during wake.
120
+ if (!locked) { printStatus(NO); return 0; }
121
+ if (!lockedKey(0, kCGEventFlagMaskCommand, NULL, 0) ||
122
+ !lockedKey(51, 0, NULL, 0)) return fail("changed");
104
123
  UniChar characters[1024] = {0};
105
124
  NSUInteger length = password.length;
106
- if (length > 1024) { CFRelease(field); return fail("password"); }
125
+ if (length > 1024) return fail("password");
107
126
  [password getCharacters:characters range:NSMakeRange(0, length)];
108
127
  password = nil;
109
128
  BOOL sent = YES;
110
- // One Unicode scalar per event avoids event-string truncation. Check the
111
- // secure loginwindow focus and console lock immediately before each event.
129
+ // One Unicode scalar per event avoids event-string truncation. Verify the
130
+ // same user's locked console immediately before every password event.
112
131
  for (NSUInteger i = 0; i < length;) {
113
132
  NSUInteger units = CFStringIsSurrogateHighCharacter(characters[i]) && i + 1 < length && CFStringIsSurrogateLowCharacter(characters[i + 1]) ? 2 : 1;
114
- if (!stillFocused(field) || !key(0, 0, &characters[i], units)) { sent = NO; break; }
133
+ if (!lockedKey(0, 0, &characters[i], units)) { sent = NO; break; }
115
134
  i += units;
116
135
  }
117
136
  wipe(characters, sizeof(characters));
118
- if (!sent || !stillFocused(field) || !key(36, 0, NULL, 0)) { CFRelease(field); return fail("changed"); }
119
- CFRelease(field);
137
+ if (!sent || !lockedKey(36, 0, NULL, 0)) return fail("changed");
120
138
  for (int i = 0; i < 50; i++) {
121
139
  usleep(100000);
122
140
  if (!lockState(&locked)) return fail("session");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skylandnpm/octopus-proxy",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "type": "module",
5
5
  "description": "Octopus host proxy: OpenCode, Codex, ZCode and Pi adapters with service installer CLI",
6
6
  "bin": {