@skylandnpm/octopus-proxy 0.1.41 → 0.1.43

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
@@ -98,9 +98,9 @@ octopus-proxy onboard --tool pi
98
98
  octopus-proxy update
99
99
  ```
100
100
 
101
- 命令通过 `npm view @skylandnpm/octopus-proxy@latest version` 查询最新版本。存在新版本时,输入 `y` 或 `yes` 确认覆盖安装;直接回车或输入其他内容会取消。确认后依次停止已安装的服务、使用 npm 全局安装查到的版本、重启服务,并校验安装版本和运行状态后反馈结果。配置、主机身份和已有服务定义会保留;未安装服务时仅更新 npm 包。
101
+ 命令通过 `npm view @skylandnpm/octopus-proxy@latest version` 查询最新版本。存在新版本时,输入 `y` 或 `yes` 确认覆盖安装;直接回车或输入其他内容会取消。确认后使用 npm 全局安装查到的版本,校验安装版本成功后才重启已安装的服务,并检查运行状态后反馈结果。安装期间不会主动停止服务。配置、主机身份和已有服务定义会保留;未安装服务时仅更新 npm 包。
102
102
 
103
- 更新失败时会尝试恢复原先运行的服务,并报告安装及恢复结果;包安装失败不保证旧版本文件完整保留。已安装新版本但重启失败时会单独提示,可运行 `octopus-proxy doctor` 和 `octopus-proxy logs` 排查。此命令用于 npm 全局安装,停止服务前会检查 npm 安装目录是否与当前命令一致;源码、本地安装或 Node.js 环境不一致时会中止并给出提示。
103
+ 安装或版本校验失败时不会触发服务重启;包安装失败不保证旧版本文件完整保留。已安装新版本但重启失败时会单独提示,可运行 `octopus-proxy doctor` 和 `octopus-proxy logs` 排查。此命令用于 npm 全局安装,覆盖安装前会检查 npm 安装目录是否与当前命令一致;源码、本地安装或 Node.js 环境不一致时会中止并给出提示。
104
104
 
105
105
  ## 发布
106
106
 
@@ -116,6 +116,10 @@ npm run publish
116
116
 
117
117
  ## 编码工具账号与 OpenCode 服务
118
118
 
119
+ Claw 机器人设置可通过已解锁的端到端会话查询和切换当前机器人连接的 Proxy 智能体。`runtime.models` 接收 `{ "kind": "codex" }`(也支持 `opencode`、`zcode`、`pi`),临时查询所选工具原生模型目录,不修改当前 `tool`。`runtime.switchTool` 使用相同参数,先启动新工具并验证原生连接,成功后以 0600 权限原子保存配置,再关闭旧工具的 Proxy 管理进程;外部 OpenCode 服务继续由主机管理。
120
+
121
+ 各工具的命令、OpenCode 地址和服务密码保存在配置文件的 `toolProfiles` 中,切回时恢复。首次选择工具会在此 Proxy 主机探测命令;自定义安装位置可预先设置 `toolProfiles.<kind>`,格式与 `tool` 相同,且其中 `kind` 必须匹配。智能体未安装、启动或连接失败时保留当前工具和配置。已打开工作区会通过 `octopus.resync` 更新能力、会话和模型。
122
+
119
123
  OpenCode 2.0.19 使用 external HTTP 模式。`tool.command` 应为 `/Users/luhuili/.opencode/bin/opencode`,`tool.baseUrl` 明确指向回环服务,`tool.password` 必填并由 onboard 自动生成。Proxy 可拉起未监听的服务,也可连接以同一密码预先启动的服务;若配置端口已被密码不同的服务占用,Proxy 会在随机回环端口启动自己的实例并在后续重启时复用,避免接管或停止未知进程。启动日志保存在 `stateDirectory/opencode-serve.log`。如需只连接自行管理的服务,可设置 `autoStart: false`。旧顶层配置、managed 模式和 v1 API 不再兼容;Proxy 不停止或重启已有 OpenCode 服务。
120
124
 
121
125
  Claw 的“编码工具账号”窗口支持 Codex API Key/设备码、ZCode 原生 Z.AI OAuth、OpenCode 原生 API Key/OAuth。凭据由编码工具保存,模型列表从原生接口读取。详细行为与验证边界见 [编码工具账号](../../docs/tool-authentication.md)。
@@ -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, constants6) {
405
+ optimizeNames(names, constants7) {
406
406
  if (!names[this.name.str])
407
407
  return;
408
408
  if (this.rhs)
409
- this.rhs = optimizeExpr(this.rhs, names, constants6);
409
+ this.rhs = optimizeExpr(this.rhs, names, constants7);
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, constants6) {
426
+ optimizeNames(names, constants7) {
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, constants6);
429
+ this.rhs = optimizeExpr(this.rhs, names, constants7);
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, constants6) {
491
- this.code = optimizeExpr(this.code, names, constants6);
490
+ optimizeNames(names, constants7) {
491
+ this.code = optimizeExpr(this.code, names, constants7);
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, constants6) {
520
+ optimizeNames(names, constants7) {
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, constants6))
525
+ if (n.optimizeNames(names, constants7))
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, constants6) {
578
+ optimizeNames(names, constants7) {
579
579
  var _a;
580
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants6);
581
- if (!(super.optimizeNames(names, constants6) || this.else))
580
+ this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants7);
581
+ if (!(super.optimizeNames(names, constants7) || this.else))
582
582
  return;
583
- this.condition = optimizeExpr(this.condition, names, constants6);
583
+ this.condition = optimizeExpr(this.condition, names, constants7);
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, constants6) {
607
- if (!super.optimizeNames(names, constants6))
606
+ optimizeNames(names, constants7) {
607
+ if (!super.optimizeNames(names, constants7))
608
608
  return;
609
- this.iteration = optimizeExpr(this.iteration, names, constants6);
609
+ this.iteration = optimizeExpr(this.iteration, names, constants7);
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, constants6) {
646
- if (!super.optimizeNames(names, constants6))
645
+ optimizeNames(names, constants7) {
646
+ if (!super.optimizeNames(names, constants7))
647
647
  return;
648
- this.iterable = optimizeExpr(this.iterable, names, constants6);
648
+ this.iterable = optimizeExpr(this.iterable, names, constants7);
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, constants6) {
690
+ optimizeNames(names, constants7) {
691
691
  var _a, _b;
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);
692
+ super.optimizeNames(names, constants7);
693
+ (_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants7);
694
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants7);
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, constants6) {
995
+ function optimizeExpr(expr, names, constants7) {
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 = constants6[n.str];
1010
+ const c = constants7[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 && constants6[c.str] !== void 0);
1017
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants7[c.str] !== void 0);
1018
1018
  }
1019
1019
  }
1020
1020
  function subtractNames(names, from) {
@@ -11715,7 +11715,7 @@ async function ensureHostIdentity(stateDirectory) {
11715
11715
 
11716
11716
  // src/daemon.ts
11717
11717
  import { mkdir as mkdir11 } from "node:fs/promises";
11718
- import { join as join14 } from "node:path";
11718
+ import { join as join15 } from "node:path";
11719
11719
 
11720
11720
  // src/workspace-diff.ts
11721
11721
  import { execFile as execFile2 } from "node:child_process";
@@ -11987,9 +11987,9 @@ var Uploads = class {
11987
11987
  this.directory = directory;
11988
11988
  }
11989
11989
  pending = /* @__PURE__ */ new Map();
11990
- serial(id, run4) {
11990
+ serial(id, run5) {
11991
11991
  const work = (this.pending.get(id) ?? Promise.resolve()).catch(() => {
11992
- }).then(run4);
11992
+ }).then(run5);
11993
11993
  this.pending.set(id, work);
11994
11994
  const clear = () => {
11995
11995
  if (this.pending.get(id) === work) this.pending.delete(id);
@@ -12197,6 +12197,8 @@ var Operations = [
12197
12197
  "auth.cancel",
12198
12198
  "runtime.status",
12199
12199
  "runtime.snapshot",
12200
+ "runtime.models",
12201
+ "runtime.switchTool",
12200
12202
  "runtime.start",
12201
12203
  "runtime.stop",
12202
12204
  "runtime.restart",
@@ -12538,7 +12540,7 @@ var Journal = class {
12538
12540
  if (active.hash !== hash) return Promise.resolve(this.failure(command, "Request id reused"));
12539
12541
  return active.result;
12540
12542
  }
12541
- const run4 = async () => {
12543
+ const run5 = async () => {
12542
12544
  const path = join6(this.directory, `${command.id}.json`);
12543
12545
  const readOnly = command.operation.startsWith("desktop.") || isReadOperation(command.operation) || isAuthentication(command.operation);
12544
12546
  if (!readOnly) {
@@ -12560,8 +12562,8 @@ var Journal = class {
12560
12562
  return response;
12561
12563
  };
12562
12564
  const key = this.queueKey(command);
12563
- const result = key === void 0 ? run4() : (this.queues.get(key) ?? Promise.resolve()).catch(() => {
12564
- }).then(run4);
12565
+ const result = key === void 0 ? run5() : (this.queues.get(key) ?? Promise.resolve()).catch(() => {
12566
+ }).then(run5);
12565
12567
  if (key !== void 0) {
12566
12568
  this.queues.set(key, result);
12567
12569
  result.finally(() => {
@@ -12669,9 +12671,9 @@ var Artifacts = class {
12669
12671
  this.emit = emit;
12670
12672
  }
12671
12673
  pending = /* @__PURE__ */ new Map();
12672
- serial(project, run4) {
12674
+ serial(project, run5) {
12673
12675
  const result = (this.pending.get(project) ?? Promise.resolve()).catch(() => {
12674
- }).then(run4);
12676
+ }).then(run5);
12675
12677
  this.pending.set(project, result);
12676
12678
  return result;
12677
12679
  }
@@ -15073,7 +15075,7 @@ var OpenCodeAdapter = class {
15073
15075
  });
15074
15076
  }
15075
15077
  };
15076
- const run4 = async () => {
15078
+ const run5 = async () => {
15077
15079
  if (method.type === "key") {
15078
15080
  if (typeof input.key !== "string" || !input.key.trim()) throw new Error("API Key required");
15079
15081
  await this.request(project, "POST", `/api/integration/${this.identifier(integration.id)}/connect/key`, { key: input.key, ...method.label ? { label: method.label } : {}, ...Object.keys(input).length > 1 ? { answer: input } : {} }, controller.signal);
@@ -15091,7 +15093,7 @@ var OpenCodeAdapter = class {
15091
15093
  };
15092
15094
  } else await wait();
15093
15095
  };
15094
- void run4().catch(() => this.auth.update(project, flow.flowId, { status: "failed", message: "OpenCode \u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8BA4\u8BC1\u4FE1\u606F\u4E0E\u4F9B\u5E94\u5546\u914D\u7F6E\u540E\u91CD\u8BD5" }));
15096
+ void run5().catch(() => this.auth.update(project, flow.flowId, { status: "failed", message: "OpenCode \u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8BA4\u8BC1\u4FE1\u606F\u4E0E\u4F9B\u5E94\u5546\u914D\u7F6E\u540E\u91CD\u8BD5" }));
15095
15097
  return { ...flow };
15096
15098
  }
15097
15099
  subscribe(project) {
@@ -15906,6 +15908,8 @@ import { join as join11 } from "node:path";
15906
15908
  var DAEMON_OPERATIONS = [
15907
15909
  "runtime.status",
15908
15910
  "runtime.snapshot",
15911
+ "runtime.models",
15912
+ "runtime.switchTool",
15909
15913
  "runtime.start",
15910
15914
  "runtime.stop",
15911
15915
  "runtime.restart",
@@ -17770,23 +17774,228 @@ var DesktopManager = class {
17770
17774
  }
17771
17775
  };
17772
17776
 
17777
+ // src/tool-selection.ts
17778
+ import { randomBytes as randomBytes2, randomUUID as randomUUID18 } from "node:crypto";
17779
+ import { access as access3, constants as constants6, readFile as readFile10 } from "node:fs/promises";
17780
+ import { platform as platform5 } from "node:os";
17781
+ import { isDeepStrictEqual } from "node:util";
17782
+
17783
+ // src/node-resolution.ts
17784
+ import { realpath as realpath7, stat as stat12 } from "node:fs/promises";
17785
+ import { execFile as execFile7 } from "node:child_process";
17786
+ import { promisify as promisify7 } from "node:util";
17787
+ import { join as join14 } from "node:path";
17788
+ import { homedir as homedir2, platform as platform4 } from "node:os";
17789
+ var run4 = promisify7(execFile7);
17790
+ var VERSION_MANAGED_SEGMENTS = /* @__PURE__ */ new Set([".nvm", "nvm", ".volta", "volta", ".fnm", "fnm", ".asdf", "asdf", ".mise", "mise"]);
17791
+ var execProbe = async (path) => {
17792
+ try {
17793
+ await run4(path, ["--version"], { timeout: 5e3 });
17794
+ return true;
17795
+ } catch {
17796
+ return false;
17797
+ }
17798
+ };
17799
+ function isVersionManagedPath(path) {
17800
+ return path.split("/").some((segment) => VERSION_MANAGED_SEGMENTS.has(segment));
17801
+ }
17802
+ function nodeCandidates() {
17803
+ return platform4() === "darwin" ? ["/opt/homebrew/bin/node", "/opt/homebrew/opt/node/bin/node", "/usr/local/bin/node", "/usr/bin/node"] : ["/usr/local/bin/node", "/usr/bin/node", "/snap/bin/node"];
17804
+ }
17805
+ async function resolveNodePath(options = {}) {
17806
+ const probe = options.probe ?? execProbe;
17807
+ if (options.pinned) {
17808
+ if (!await probe(options.pinned)) throw new Error(`--node-path ${options.pinned} is not a working node executable`);
17809
+ return { nodePath: options.pinned, source: "pinned" };
17810
+ }
17811
+ let exec = options.execPath ?? process.execPath;
17812
+ try {
17813
+ exec = await realpath7(exec);
17814
+ } catch {
17815
+ }
17816
+ if (!isVersionManagedPath(exec) && await probe(exec)) return { nodePath: exec, source: "exec" };
17817
+ for (const candidate of nodeCandidates()) if (!isVersionManagedPath(candidate) && await probe(candidate)) return { nodePath: candidate, source: "candidate" };
17818
+ if (await probe(exec)) return { nodePath: exec, source: "exec", warning: `only a version-manager node was found (${exec}); install node with the system package manager or pass --node-path, otherwise the service breaks when that node is upgraded` };
17819
+ return { nodePath: null, source: "exec" };
17820
+ }
17821
+ function toolSearchDirs(kind) {
17822
+ const dirs = [...describeTool(kind).searchDirs, join14(homedir2(), ".local", "bin"), join14(homedir2(), ".opencode", "bin"), join14(homedir2(), ".codex", "bin")];
17823
+ return [...new Set(dirs)];
17824
+ }
17825
+ async function resolveCommandOnPath(name, extraDirs = []) {
17826
+ const dirs = [...(process.env.PATH ?? "").split(":"), ...extraDirs].filter(Boolean);
17827
+ for (const dir of new Set(dirs)) {
17828
+ const full = join14(dir, name);
17829
+ try {
17830
+ const info = await stat12(full);
17831
+ if (info.isFile() && info.mode & 73) return full;
17832
+ } catch {
17833
+ }
17834
+ }
17835
+ return null;
17836
+ }
17837
+
17838
+ // src/tool-selection.ts
17839
+ var selectionSchema = z.object({ kind: z.enum(TOOL_KINDS) }).strict();
17840
+ var ToolSelection = class {
17841
+ constructor(configPath, config, create) {
17842
+ this.configPath = configPath;
17843
+ this.config = config;
17844
+ this.create = create;
17845
+ this.current = create({ ...config, tool: { ...config.tool } });
17846
+ }
17847
+ current;
17848
+ pending = Promise.resolve();
17849
+ closed = false;
17850
+ discovered = /* @__PURE__ */ new Map();
17851
+ exclusive(work) {
17852
+ const result = this.pending.catch(() => {
17853
+ }).then(() => {
17854
+ if (this.closed) throw new Error("Proxy \u5DF2\u505C\u6B62");
17855
+ return work();
17856
+ });
17857
+ this.pending = result;
17858
+ return result;
17859
+ }
17860
+ models(payload, projectId) {
17861
+ const { kind } = selectionSchema.parse(payload);
17862
+ return this.exclusive(async () => {
17863
+ if (kind === this.current.runtime.kind) {
17864
+ return { ...await buildProxySnapshot(this.config, this.current.runtime, this.current.adapter, projectId), activeTool: kind };
17865
+ }
17866
+ const raw = await this.readCurrent();
17867
+ const tool = await this.toolConfig(kind, raw);
17868
+ const candidateConfig = { ...this.config, tool };
17869
+ candidateConfig.projects = [this.config.projects.find((project) => project.id === projectId) ?? this.config.projects[0]];
17870
+ const candidate = this.create(candidateConfig);
17871
+ try {
17872
+ await candidate.runtime.init();
17873
+ return { ...await buildProxySnapshot(candidateConfig, candidate.runtime, candidate.adapter, projectId), activeTool: this.current.runtime.kind };
17874
+ } finally {
17875
+ await this.release(candidate);
17876
+ }
17877
+ });
17878
+ }
17879
+ switchTool(payload) {
17880
+ const { kind } = selectionSchema.parse(payload);
17881
+ return this.exclusive(async () => {
17882
+ if (kind === this.current.runtime.kind) return this.current.runtime.status();
17883
+ const raw = await this.readCurrent();
17884
+ const tool = await this.toolConfig(kind, raw);
17885
+ const candidate = this.create({ ...this.config, tool });
17886
+ let committed = false;
17887
+ try {
17888
+ await candidate.runtime.init();
17889
+ const status = await candidate.runtime.status();
17890
+ if (!status.healthy) throw new Error(status.error ?? `${describeTool(kind).label} \u670D\u52A1\u672A\u5C31\u7EEA`);
17891
+ if (candidate.adapter.supportedOperations().includes("configuration.models")) {
17892
+ await candidate.adapter.dispatch(this.modelCommand(candidate.runtime.config.projects[0].id));
17893
+ }
17894
+ const latest = await this.readCurrent();
17895
+ if (this.closed) throw new Error("Proxy \u5DF2\u505C\u6B62");
17896
+ await atomicJson(this.configPath, {
17897
+ ...latest,
17898
+ tool,
17899
+ toolProfiles: { ...this.profiles(latest), [this.config.tool.kind]: this.config.tool, [kind]: tool }
17900
+ });
17901
+ const previous = this.current;
17902
+ this.current = candidate;
17903
+ this.config.tool = tool;
17904
+ committed = true;
17905
+ await this.release(previous);
17906
+ return candidate.runtime.status();
17907
+ } finally {
17908
+ if (!committed) await this.release(candidate);
17909
+ }
17910
+ });
17911
+ }
17912
+ modelCommand(projectId) {
17913
+ return { type: "command", id: randomUUID18(), botId: this.config.botId, operation: "configuration.models", projectId, payload: {} };
17914
+ }
17915
+ profiles(raw) {
17916
+ return raw.toolProfiles && typeof raw.toolProfiles === "object" && !Array.isArray(raw.toolProfiles) ? raw.toolProfiles : {};
17917
+ }
17918
+ async toolConfig(kind, raw) {
17919
+ const saved = this.profiles(raw)[kind];
17920
+ if (saved) {
17921
+ const tool2 = ToolSchema.parse(saved);
17922
+ if (tool2.kind !== kind) throw new Error("Proxy \u667A\u80FD\u4F53\u914D\u7F6E\u4E0E\u7C7B\u578B\u4E0D\u4E00\u81F4");
17923
+ return { ...tool2, command: expandHome(tool2.command || describeTool(kind).defaultCommand), autoStart: true };
17924
+ }
17925
+ const discovered = this.discovered.get(kind);
17926
+ if (discovered) return discovered;
17927
+ let command = await resolveCommandOnPath(kind, toolSearchDirs(kind));
17928
+ if (!command && platform5() === "darwin") {
17929
+ const candidates = kind === "codex" ? [
17930
+ "/Applications/ChatGPT.app/Contents/Resources/codex-cli/CodexCLI.app/Contents/MacOS/codex",
17931
+ "/Applications/ChatGPT.app/Contents/Resources/codex"
17932
+ ] : kind === "zcode" ? ["/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs"] : [];
17933
+ for (const path of candidates) {
17934
+ if (await access3(path, constants6.X_OK).then(() => true, () => false)) {
17935
+ command = path;
17936
+ break;
17937
+ }
17938
+ }
17939
+ }
17940
+ if (!command) throw new Error(`${describeTool(kind).label} \u672A\u5B89\u88C5\u6216\u547D\u4EE4\u4E0D\u53EF\u6267\u884C\uFF1B\u8BF7\u5728\u6B64 Proxy \u4E3B\u673A\u914D\u7F6E toolProfiles.${kind}.command \u540E\u91CD\u8BD5`);
17941
+ const tool = ToolSchema.parse({
17942
+ kind,
17943
+ command,
17944
+ autoStart: true,
17945
+ ...kind === "opencode" ? { baseUrl: "http://127.0.0.1:4096", password: randomBytes2(32).toString("base64url") } : {}
17946
+ });
17947
+ this.discovered.set(kind, tool);
17948
+ return tool;
17949
+ }
17950
+ async readCurrent() {
17951
+ const disk = await loadConfig(this.configPath);
17952
+ if (!isDeepStrictEqual(JSON.parse(JSON.stringify(disk.tool)), JSON.parse(JSON.stringify(this.config.tool))) || !isDeepStrictEqual(disk.projects, this.config.projects) || disk.botId !== this.config.botId || disk.secretKey !== this.config.secretKey) {
17953
+ throw new Error("Proxy \u914D\u7F6E\u5DF2\u5728\u4E3B\u673A\u4E0A\u88AB\u4FEE\u6539\uFF1B\u8BF7\u91CD\u542F Proxy \u540E\u518D\u5207\u6362\u667A\u80FD\u4F53");
17954
+ }
17955
+ return JSON.parse(await readFile10(this.configPath, "utf8"));
17956
+ }
17957
+ async release(instance) {
17958
+ instance.adapter.close();
17959
+ instance.runtime.detach();
17960
+ if (instance.runtime.kind !== "opencode") await instance.runtime.stop().catch(() => {
17961
+ });
17962
+ }
17963
+ close() {
17964
+ this.closed = true;
17965
+ void this.release(this.current);
17966
+ }
17967
+ };
17968
+
17773
17969
  // src/daemon.ts
17774
17970
  async function runDaemon(configPath, authenticate = verifyHostPassword) {
17775
17971
  const config = await loadConfig(configPath);
17776
17972
  await mkdir11(config.stateDirectory, { recursive: true, mode: 448 });
17777
- const workspace = new Workspace(config.projects), runtime = createRuntime(config), uploads = new Uploads(workspace, join14(config.stateDirectory, "uploads")), journal = new Journal(join14(config.stateDirectory, "journal"));
17973
+ const workspace = new Workspace(config.projects), uploads = new Uploads(workspace, join15(config.stateDirectory, "uploads")), journal = new Journal(join15(config.stateDirectory, "journal"));
17778
17974
  let secureChat;
17779
17975
  function emit(projectId, event, data) {
17780
17976
  secureChat?.publish(projectId, event, data);
17781
17977
  }
17782
17978
  const terminals = new TerminalManager(workspace, (owner, project, event, data) => secureChat?.sendTo(owner, project, event, data));
17783
17979
  const desktop = new DesktopManager(new PlatformDesktop(config.stateDirectory), new PlatformScreenLock(config.stateDirectory));
17784
- const adapter = createAdapter(runtime, workspace, uploads, emit), artifacts = new Artifacts(workspace, join14(config.stateDirectory, "artifacts"), emit);
17785
- const projectRegistry = new ProjectRegistry(configPath, config, runtime, adapter);
17980
+ const tools = new ToolSelection(configPath, config, (toolConfig) => {
17981
+ const runtime = createRuntime(toolConfig);
17982
+ const adapter = createAdapter(runtime, workspace, uploads, (project, event, data) => {
17983
+ if (tools.current.runtime === runtime) emit(project, event, data);
17984
+ });
17985
+ return { runtime, adapter };
17986
+ });
17987
+ const artifacts = new Artifacts(workspace, join15(config.stateDirectory, "artifacts"), emit);
17786
17988
  async function dispatch(c, secureSessionId) {
17787
17989
  if (c.operation.startsWith("desktop.")) return desktop.dispatch(c, secureSessionId);
17788
17990
  if (c.operation.startsWith("terminal.")) return terminals.dispatch(c, secureSessionId);
17789
17991
  const p = c.payload;
17992
+ if (c.operation === "runtime.models") return tools.models(p, c.projectId);
17993
+ if (c.operation === "runtime.switchTool") {
17994
+ const result2 = await tools.switchTool(p);
17995
+ emit("", "octopus.resync", { toolChanged: true });
17996
+ return result2;
17997
+ }
17998
+ const { runtime, adapter } = tools.current;
17790
17999
  if (c.operation === "runtime.status") return { ...await runtime.status(), capabilities: adapter.supportedOperations() };
17791
18000
  if (c.operation === "runtime.snapshot") return buildProxySnapshot(config, runtime, adapter, c.projectId);
17792
18001
  if (c.operation === "runtime.logs") return runtime.logs();
@@ -17795,17 +18004,17 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
17795
18004
  if (c.operation === "runtime.restart") return runtime.restart();
17796
18005
  if (c.operation === "workspace.projects") return publicProjects(config);
17797
18006
  if (c.operation === "workspace.projects.add") {
17798
- const result2 = await projectRegistry.add(p);
18007
+ const result2 = await tools.exclusive(() => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter).add(p));
17799
18008
  emit(String(p.id), "octopus.resync", {});
17800
18009
  return result2;
17801
18010
  }
17802
18011
  if (c.operation === "workspace.projects.update") {
17803
- const result2 = await projectRegistry.update(p);
18012
+ const result2 = await tools.exclusive(() => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter).update(p));
17804
18013
  emit(String(p.id), "octopus.resync", {});
17805
18014
  return result2;
17806
18015
  }
17807
18016
  if (c.operation === "workspace.projects.remove") {
17808
- const result2 = await projectRegistry.remove(p);
18017
+ const result2 = await tools.exclusive(() => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter).remove(p));
17809
18018
  emit(String(p.id), "octopus.resync", {});
17810
18019
  return result2;
17811
18020
  }
@@ -17847,7 +18056,7 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
17847
18056
  terminals.closeOwned(owner);
17848
18057
  desktop.closeOwned(owner);
17849
18058
  }, authenticate);
17850
- await runtime.init().catch((error) => console.error(`${runtime.kind} startup:`, error.message));
18059
+ await tools.current.runtime.init().catch((error) => console.error(`${tools.current.runtime.kind} startup:`, error.message));
17851
18060
  secureChat.start();
17852
18061
  const scan = setInterval(async () => {
17853
18062
  for (const p of config.projects) await artifacts.scan(p.id).catch(() => {
@@ -17864,8 +18073,7 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
17864
18073
  secureChat?.stop();
17865
18074
  terminals.close();
17866
18075
  desktop.close();
17867
- runtime.detach();
17868
- adapter.close();
18076
+ tools.close();
17869
18077
  }
17870
18078
  function onSignal() {
17871
18079
  stop();
@@ -17905,5 +18113,9 @@ export {
17905
18113
  DESKTOP_PERMISSION_GUIDANCE,
17906
18114
  requestDesktopPermission,
17907
18115
  checkDesktop,
18116
+ isVersionManagedPath,
18117
+ resolveNodePath,
18118
+ toolSearchDirs,
18119
+ resolveCommandOnPath,
17908
18120
  runDaemon
17909
18121
  };
package/dist/cli.js CHANGED
@@ -19,93 +19,42 @@ import {
19
19
  ensureHostIdentity,
20
20
  expandHome,
21
21
  isMacOS,
22
+ isVersionManagedPath,
22
23
  launchdLogPath,
23
24
  launchdPlistPath,
24
25
  loadConfig,
25
26
  proxyVersion,
26
27
  requestDesktopPermission,
28
+ resolveCommandOnPath,
27
29
  resolveConfigPath,
30
+ resolveNodePath,
28
31
  runDaemon,
29
32
  supportedPlatform,
30
- systemdUnitPath
31
- } from "./chunk-WSL4LYYW.js";
33
+ systemdUnitPath,
34
+ toolSearchDirs
35
+ } from "./chunk-HB3ETNMW.js";
32
36
 
33
37
  // src/cli.ts
34
38
  import { spawn as spawn2 } from "node:child_process";
35
39
  import { access as access4 } from "node:fs/promises";
36
40
 
37
41
  // src/doctor.ts
38
- import { access as access2, chmod, constants, mkdir as mkdir3, stat as stat2 } from "node:fs/promises";
39
- import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
40
- import { execFile as execFile4 } from "node:child_process";
41
- import { promisify as promisify4 } from "node:util";
42
+ import { access as access2, chmod, constants, mkdir as mkdir3, stat } from "node:fs/promises";
43
+ import { isAbsolute as isAbsolute2, join } from "node:path";
44
+ import { execFile as execFile3 } from "node:child_process";
45
+ import { promisify as promisify3 } from "node:util";
42
46
 
43
47
  // src/service.ts
44
48
  import { readFile, access } from "node:fs/promises";
45
49
  import { dirname as dirname3, isAbsolute, resolve } from "node:path";
46
50
 
47
- // src/node-resolution.ts
48
- import { realpath, stat } from "node:fs/promises";
49
- import { execFile } from "node:child_process";
50
- import { promisify } from "node:util";
51
- import { join } from "node:path";
52
- import { homedir, platform } from "node:os";
53
- var run = promisify(execFile);
54
- var VERSION_MANAGED_SEGMENTS = /* @__PURE__ */ new Set([".nvm", "nvm", ".volta", "volta", ".fnm", "fnm", ".asdf", "asdf", ".mise", "mise"]);
55
- var execProbe = async (path) => {
56
- try {
57
- await run(path, ["--version"], { timeout: 5e3 });
58
- return true;
59
- } catch {
60
- return false;
61
- }
62
- };
63
- function isVersionManagedPath(path) {
64
- return path.split("/").some((segment) => VERSION_MANAGED_SEGMENTS.has(segment));
65
- }
66
- function nodeCandidates() {
67
- return platform() === "darwin" ? ["/opt/homebrew/bin/node", "/opt/homebrew/opt/node/bin/node", "/usr/local/bin/node", "/usr/bin/node"] : ["/usr/local/bin/node", "/usr/bin/node", "/snap/bin/node"];
68
- }
69
- async function resolveNodePath(options = {}) {
70
- const probe = options.probe ?? execProbe;
71
- if (options.pinned) {
72
- if (!await probe(options.pinned)) throw new Error(`--node-path ${options.pinned} is not a working node executable`);
73
- return { nodePath: options.pinned, source: "pinned" };
74
- }
75
- let exec = options.execPath ?? process.execPath;
76
- try {
77
- exec = await realpath(exec);
78
- } catch {
79
- }
80
- if (!isVersionManagedPath(exec) && await probe(exec)) return { nodePath: exec, source: "exec" };
81
- for (const candidate of nodeCandidates()) if (!isVersionManagedPath(candidate) && await probe(candidate)) return { nodePath: candidate, source: "candidate" };
82
- if (await probe(exec)) return { nodePath: exec, source: "exec", warning: `only a version-manager node was found (${exec}); install node with the system package manager or pass --node-path, otherwise the service breaks when that node is upgraded` };
83
- return { nodePath: null, source: "exec" };
84
- }
85
- function toolSearchDirs(kind) {
86
- const dirs = [...describeTool(kind).searchDirs, join(homedir(), ".local", "bin"), join(homedir(), ".opencode", "bin"), join(homedir(), ".codex", "bin")];
87
- return [...new Set(dirs)];
88
- }
89
- async function resolveCommandOnPath(name, extraDirs = []) {
90
- const dirs = [...(process.env.PATH ?? "").split(":"), ...extraDirs].filter(Boolean);
91
- for (const dir of new Set(dirs)) {
92
- const full = join(dir, name);
93
- try {
94
- const info = await stat(full);
95
- if (info.isFile() && info.mode & 73) return full;
96
- } catch {
97
- }
98
- }
99
- return null;
100
- }
101
-
102
51
  // src/launchd.ts
103
52
  import { mkdir, rm, writeFile } from "node:fs/promises";
104
53
  import { dirname } from "node:path";
105
- import { execFile as execFile2 } from "node:child_process";
106
- import { promisify as promisify2 } from "node:util";
54
+ import { execFile } from "node:child_process";
55
+ import { promisify } from "node:util";
107
56
  import { userInfo } from "node:os";
108
- var run2 = promisify2(execFile2);
57
+ var run = promisify(execFile);
109
58
  var guiDomain = () => `gui/${userInfo().uid}`;
110
59
  function xml(value) {
111
60
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
@@ -138,22 +87,22 @@ async function ignoreMissing(action) {
138
87
  }
139
88
  }
140
89
  async function launchdLoad() {
141
- await ignoreMissing(() => run2("launchctl", ["bootout", `${guiDomain()}/${SERVICE_LABEL}`]));
142
- await run2("launchctl", ["bootstrap", guiDomain(), launchdPlistPath()]);
90
+ await ignoreMissing(() => run("launchctl", ["bootout", `${guiDomain()}/${SERVICE_LABEL}`]));
91
+ await run("launchctl", ["bootstrap", guiDomain(), launchdPlistPath()]);
143
92
  }
144
93
  async function launchdStart() {
145
- await ignoreMissing(() => run2("launchctl", ["bootstrap", guiDomain(), launchdPlistPath()]));
146
- await run2("launchctl", ["kickstart", `${guiDomain()}/${SERVICE_LABEL}`]);
94
+ await ignoreMissing(() => run("launchctl", ["bootstrap", guiDomain(), launchdPlistPath()]));
95
+ await run("launchctl", ["kickstart", `${guiDomain()}/${SERVICE_LABEL}`]);
147
96
  }
148
97
  async function launchdStop() {
149
- await ignoreMissing(() => run2("launchctl", ["bootout", `${guiDomain()}/${SERVICE_LABEL}`]));
98
+ await ignoreMissing(() => run("launchctl", ["bootout", `${guiDomain()}/${SERVICE_LABEL}`]));
150
99
  }
151
100
  async function launchdRestart() {
152
101
  const target = `${guiDomain()}/${SERVICE_LABEL}`;
153
102
  if ((await launchdStatus()).loaded) {
154
- await run2("launchctl", ["kickstart", "-k", target]);
103
+ await run("launchctl", ["kickstart", "-k", target]);
155
104
  } else {
156
- await run2("launchctl", ["bootstrap", guiDomain(), launchdPlistPath()]);
105
+ await run("launchctl", ["bootstrap", guiDomain(), launchdPlistPath()]);
157
106
  }
158
107
  }
159
108
  async function launchdUninstall() {
@@ -162,7 +111,7 @@ async function launchdUninstall() {
162
111
  }
163
112
  async function launchdStatus() {
164
113
  try {
165
- const { stdout: stdout3 } = await run2("launchctl", ["print", `${guiDomain()}/${SERVICE_LABEL}`]);
114
+ const { stdout: stdout3 } = await run("launchctl", ["print", `${guiDomain()}/${SERVICE_LABEL}`]);
166
115
  const running = /state = running/.test(stdout3);
167
116
  const pid = Number(/pid = (\d+)/.exec(stdout3)?.[1]);
168
117
  return { loaded: true, running, pid: Number.isFinite(pid) && pid > 0 ? pid : void 0 };
@@ -174,9 +123,9 @@ async function launchdStatus() {
174
123
  // src/systemd.ts
175
124
  import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
176
125
  import { dirname as dirname2 } from "node:path";
177
- import { execFile as execFile3 } from "node:child_process";
178
- import { promisify as promisify3 } from "node:util";
179
- var run3 = promisify3(execFile3);
126
+ import { execFile as execFile2 } from "node:child_process";
127
+ import { promisify as promisify2 } from "node:util";
128
+ var run2 = promisify2(execFile2);
180
129
  function quoteSystemdArg(value) {
181
130
  const escaped = value.replaceAll("%", "%%").replaceAll('"', '\\"');
182
131
  return /[\s"\\]/.test(value) ? `"${escaped}"` : escaped;
@@ -213,7 +162,7 @@ async function writeUnitFile(text) {
213
162
  }
214
163
  async function systemctl(args) {
215
164
  try {
216
- const { stdout: stdout3 } = await run3("systemctl", ["--user", ...args]);
165
+ const { stdout: stdout3 } = await run2("systemctl", ["--user", ...args]);
217
166
  return stdout3;
218
167
  } catch (error) {
219
168
  const stderr = error instanceof Error ? error.stderr ?? error.message : String(error);
@@ -339,14 +288,14 @@ function logLocationHint() {
339
288
 
340
289
  // src/doctor.ts
341
290
  import { randomUUID } from "node:crypto";
342
- var run4 = promisify4(execFile4);
291
+ var run3 = promisify3(execFile3);
343
292
  async function runDoctor(opts) {
344
293
  const results = [];
345
294
  const record = (name, status, detail) => results.push({ name, status, detail });
346
295
  results.push(await checkTerminalSupport(opts.fix));
347
296
  let config;
348
297
  try {
349
- const info = await stat2(opts.configPath).catch((error) => {
298
+ const info = await stat(opts.configPath).catch((error) => {
350
299
  if (error.code === "ENOENT") throw new Error(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${opts.configPath}\uFF1B\u5148\u8FD0\u884C octopus-proxy onboard`);
351
300
  throw error;
352
301
  });
@@ -379,7 +328,7 @@ async function runDoctor(opts) {
379
328
  record(checkName, "fail", `${command2} \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C`);
380
329
  } else {
381
330
  try {
382
- const { stdout: stdout3 } = await run4(command2, [...descriptor.versionArgs], { timeout: 15e3 });
331
+ const { stdout: stdout3 } = await run3(command2, [...descriptor.versionArgs], { timeout: 15e3 });
383
332
  const version = stdout3.trim().split("\n")[0].trim();
384
333
  if (version === descriptor.baseline) record(checkName, "ok", `${command2}\uFF08${version}\uFF09`);
385
334
  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`);
@@ -396,7 +345,7 @@ async function runDoctor(opts) {
396
345
  if (config) {
397
346
  const probeConfig = { ...config, tool: { ...config.tool, autoStart: false } };
398
347
  const runtime = createRuntime(probeConfig), workspace = new Workspace(config.projects);
399
- const adapter = createAdapter(runtime, workspace, new Uploads(workspace, join2(config.stateDirectory, "uploads")), () => {
348
+ const adapter = createAdapter(runtime, workspace, new Uploads(workspace, join(config.stateDirectory, "uploads")), () => {
400
349
  });
401
350
  try {
402
351
  await runtime.init();
@@ -455,11 +404,11 @@ async function runDoctor(opts) {
455
404
  import { createInterface } from "node:readline/promises";
456
405
  import { randomBytes } from "node:crypto";
457
406
  import { stdin, stdout } from "node:process";
458
- import { access as access3, constants as constants2, mkdir as mkdir4, stat as stat3 } from "node:fs/promises";
459
- import { execFile as execFile5 } from "node:child_process";
460
- import { platform as platform2 } from "node:os";
461
- import { isAbsolute as isAbsolute3, join as join3 } from "node:path";
462
- import { promisify as promisify5 } from "node:util";
407
+ import { access as access3, constants as constants2, mkdir as mkdir4, stat as stat2 } from "node:fs/promises";
408
+ import { execFile as execFile4 } from "node:child_process";
409
+ import { platform } from "node:os";
410
+ import { isAbsolute as isAbsolute3, join as join2 } from "node:path";
411
+ import { promisify as promisify4 } from "node:util";
463
412
  var DEFAULT_HUB_URL = "https://octopus.wuyehao.com";
464
413
  async function ask(rl, question, options = {}) {
465
414
  for (; ; ) {
@@ -488,7 +437,7 @@ var validUrl = (value) => {
488
437
  var validId = (value) => !value ? "\u9879\u76EE ID \u4E0D\u80FD\u4E3A\u7A7A" : /^[a-zA-Z0-9_-]+$/.test(value) ? void 0 : "\u4EC5\u9650\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26";
489
438
  var validateProjectId = (value, previousProjects) => validId(value) ?? (previousProjects.some((project) => project.id === value) ? `\u9879\u76EE ID\u300C${value}\u300D\u5DF2\u5B58\u5728\uFF0C\u8BF7\u8F93\u5165\u5176\u4ED6 ID` : void 0);
490
439
  var validBotId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value) ? void 0 : "\u8BF7\u8F93\u5165\u673A\u5668\u4EBA UUID";
491
- var run5 = promisify5(execFile5);
440
+ var run4 = promisify4(execFile4);
492
441
  var MACOS_TOOL_EXAMPLES = {
493
442
  opencode: "/Users/luhuili/.opencode/bin/opencode",
494
443
  codex: "/Applications/ChatGPT.app/Contents/Resources/codex",
@@ -499,20 +448,20 @@ async function validateToolCommand(value, kind) {
499
448
  const command2 = expandHome(value);
500
449
  if (!isAbsolute3(command2)) return "\u670D\u52A1\u73AF\u5883\u9700\u8981\u7EDD\u5BF9\u8DEF\u5F84";
501
450
  try {
502
- if (!(await stat3(command2)).isFile()) return "\u547D\u4EE4\u8DEF\u5F84\u5FC5\u987B\u6307\u5411\u53EF\u6267\u884C\u6587\u4EF6";
451
+ if (!(await stat2(command2)).isFile()) return "\u547D\u4EE4\u8DEF\u5F84\u5FC5\u987B\u6307\u5411\u53EF\u6267\u884C\u6587\u4EF6";
503
452
  await access3(command2, constants2.X_OK);
504
453
  } catch {
505
454
  return `\u547D\u4EE4\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C\uFF1A${command2}`;
506
455
  }
507
456
  try {
508
- await run5(command2, [...describeTool(kind).versionArgs], { timeout: 1e4 });
457
+ await run4(command2, [...describeTool(kind).versionArgs], { timeout: 1e4 });
509
458
  } catch {
510
459
  return `${describeTool(kind).label} \u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u786E\u8BA4\u547D\u4EE4\u8DEF\u5F84\uFF1A${command2}`;
511
460
  }
512
461
  return void 0;
513
462
  }
514
463
  async function runOnboard(opts) {
515
- const configExists = await stat3(opts.configPath).then(() => true, (error) => {
464
+ const configExists = await stat2(opts.configPath).then(() => true, (error) => {
516
465
  if (error.code === "ENOENT") return false;
517
466
  throw error;
518
467
  });
@@ -536,7 +485,7 @@ async function runOnboard(opts) {
536
485
  if (toolKind !== previousToolKind) toolCommand = toolKind === existing?.tool.kind ? existing.tool.command : "";
537
486
  const descriptor = describeTool(toolKind);
538
487
  const found = await resolveCommandOnPath(toolKind, toolSearchDirs(toolKind));
539
- const example = platform2() === "darwin" ? MACOS_TOOL_EXAMPLES[toolKind] : void 0;
488
+ const example = platform() === "darwin" ? MACOS_TOOL_EXAMPLES[toolKind] : void 0;
540
489
  if (toolKind === "opencode") {
541
490
  toolCommand = expandHome(await ask(rl, "OpenCode \u547D\u4EE4\uFF08Claw \u8FDC\u7A0B\u542F\u52A8\u65F6\u4F7F\u7528\uFF09", { default: toolCommand || found || example, validate: (value) => validateToolCommand(value, toolKind) }));
542
491
  console.log(" Proxy \u53EF\u4ECE Claw \u8FDC\u7A0B\u542F\u52A8 OpenCode\uFF1B\u5DF2\u6709\u670D\u52A1\u4F1A\u76F4\u63A5\u8FDE\u63A5\u3002");
@@ -561,7 +510,7 @@ async function runOnboard(opts) {
561
510
  console.log(" \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");
562
511
  continue;
563
512
  }
564
- if (!await stat3(project.directory).then(() => true, () => false)) {
513
+ if (!await stat2(project.directory).then(() => true, () => false)) {
565
514
  console.log(" \u76EE\u5F55\u4E0D\u5B58\u5728");
566
515
  continue;
567
516
  }
@@ -580,7 +529,7 @@ async function runOnboard(opts) {
580
529
  console.log(" \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");
581
530
  continue;
582
531
  }
583
- if (!await stat3(directory).then(() => true, () => false)) {
532
+ if (!await stat2(directory).then(() => true, () => false)) {
584
533
  console.log(" \u76EE\u5F55\u4E0D\u5B58\u5728");
585
534
  continue;
586
535
  }
@@ -647,8 +596,8 @@ async function runOnboard(opts) {
647
596
  stateDirectory = expandHome(stateDirectory);
648
597
  await mkdir4(stateDirectory, { recursive: true, mode: 448 });
649
598
  try {
650
- if (platform2() === "darwin") {
651
- console.log(`\u622A\u5C4F\u8F85\u52A9\u7A0B\u5E8F\u8DEF\u5F84\uFF1A${join3(stateDirectory, "octopus-desktop-capture")}\uFF08\u6587\u4EF6\u540D\u4E0D\u4E00\u5B9A\u662F\u7CFB\u7EDF\u6388\u6743\u540D\u79F0\uFF09`);
599
+ if (platform() === "darwin") {
600
+ console.log(`\u622A\u5C4F\u8F85\u52A9\u7A0B\u5E8F\u8DEF\u5F84\uFF1A${join2(stateDirectory, "octopus-desktop-capture")}\uFF08\u6587\u4EF6\u540D\u4E0D\u4E00\u5B9A\u662F\u7CFB\u7EDF\u6388\u6743\u540D\u79F0\uFF09`);
652
601
  console.log("\u6B63\u5728\u68C0\u67E5\u5C4F\u5E55\u5F55\u5236\u6743\u9650\uFF1B\u5982\u51FA\u73B0\u7CFB\u7EDF\u6388\u6743\u63D0\u793A\uFF0C\u8BF7\u6309\u5F39\u7A97\u4E2D\u7684\u5B9E\u9645\u7A0B\u5E8F\u540D\u79F0\u6388\u6743\uFF0C\u53EF\u80FD\u663E\u793A Terminal\u3001iTerm\u3001\u7F16\u8F91\u5668\u6216 node\u3002");
653
602
  }
654
603
  const permission = await requestDesktopPermission(stateDirectory);
@@ -722,18 +671,18 @@ async function manageProjects(configPath2, remove) {
722
671
  }
723
672
 
724
673
  // 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";
674
+ import { execFile as execFile5, spawn } from "node:child_process";
675
+ import { readFile as readFile3, realpath } from "node:fs/promises";
676
+ import { join as join3 } from "node:path";
728
677
  import { stdin as stdin2, stdout as stdout2 } from "node:process";
729
678
  import { createInterface as createInterface2 } from "node:readline/promises";
730
679
  import { setTimeout as delay } from "node:timers/promises";
731
680
  import { fileURLToPath } from "node:url";
732
- import { promisify as promisify6 } from "node:util";
681
+ import { promisify as promisify5 } from "node:util";
733
682
  var PACKAGE = "@skylandnpm/octopus-proxy";
734
683
  var REGISTRY = "https://registry.npmjs.org";
735
684
  var packageDirectory = fileURLToPath(new URL("../", import.meta.url));
736
- var run6 = promisify6(execFile6);
685
+ var run5 = promisify5(execFile5);
737
686
  function parseVersion(version) {
738
687
  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
688
  if (!match) throw new Error(`\u65E0\u6548\u7684\u7248\u672C\u53F7\uFF1A${version}`);
@@ -762,7 +711,7 @@ function message(error) {
762
711
  }
763
712
  async function npmOutput(args) {
764
713
  try {
765
- const { stdout: stdout3 } = await run6("npm", args, { timeout: 3e4, maxBuffer: 1024 * 1024 });
714
+ const { stdout: stdout3 } = await run5("npm", args, { timeout: 3e4, maxBuffer: 1024 * 1024 });
766
715
  return stdout3.trim();
767
716
  } catch (error) {
768
717
  throw new Error(`npm ${args[0]} \u5931\u8D25\uFF1A${message(error)}`);
@@ -772,7 +721,7 @@ async function confirmUpdate(latest) {
772
721
  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
722
  const rl = createInterface2({ input: stdin2, output: stdout2 });
774
723
  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());
724
+ return /^(y|yes|是|确认)$/i.test((await rl.question(`\u662F\u5426\u8986\u76D6\u5B89\u88C5 ${proxyVersion} \u2192 ${latest}\uFF1F\u5B89\u88C5\u5E76\u6821\u9A8C\u6210\u529F\u540E\u4F1A\u91CD\u542F\u670D\u52A1 [y/N]: `)).trim());
776
725
  } finally {
777
726
  rl.close();
778
727
  }
@@ -812,34 +761,19 @@ async function runUpdate() {
812
761
  return;
813
762
  }
814
763
  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)) {
764
+ const target = join3(prefix, "lib", "node_modules", PACKAGE);
765
+ const installedDirectory = await realpath(target).catch(() => null);
766
+ if (installedDirectory !== await realpath(packageDirectory)) {
818
767
  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
768
  }
820
769
  const state = await serviceStatus();
821
770
  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
771
  console.log(`\u6B63\u5728\u5B89\u88C5 ${PACKAGE}@${latest}\u2026`);
828
772
  await installVersion(latest, prefix);
829
- const installed = JSON.parse(await readFile3(join4(target, "package.json"), "utf8"));
773
+ const installed = JSON.parse(await readFile3(join3(target, "package.json"), "utf8"));
830
774
  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
775
  } 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}`);
776
+ throw new Error(`\u66F4\u65B0\u5931\u8D25\uFF1A${message(error)}`);
843
777
  }
844
778
  if (state.installed) {
845
779
  console.log("\u5B89\u88C5\u5B8C\u6210\uFF0C\u6B63\u5728\u91CD\u542F\u670D\u52A1\u2026");
@@ -862,7 +796,7 @@ var USAGE = `octopus-proxy \u2014 Octopus \u4E3B\u673A\u4EE3\u7406
862
796
  projects \u5217\u51FA\u6388\u6743\u9879\u76EE\uFF1B\u53EF\u6309\u5E8F\u53F7\u5220\u9664\u9519\u8BEF\u914D\u7F6E\u9879
863
797
  install \u6821\u9A8C\u914D\u7F6E\u540E\u5B89\u88C5/\u66F4\u65B0\u7CFB\u7EDF\u670D\u52A1\uFF08\u5E42\u7B49\uFF0C\u81EA\u52A8\u542F\u52A8\uFF09
864
798
  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
799
+ update \u68C0\u67E5 npm \u6700\u65B0\u7248\u672C\uFF0C\u786E\u8BA4\u540E\u8986\u76D6\u5B89\u88C5\uFF0C\u6821\u9A8C\u6210\u529F\u540E\u91CD\u542F
866
800
  start \u542F\u52A8\u670D\u52A1
867
801
  stop \u505C\u6B62\u670D\u52A1
868
802
  restart \u91CD\u542F\u670D\u52A1
package/dist/main.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ConfigError,
4
4
  runDaemon
5
- } from "./chunk-WSL4LYYW.js";
5
+ } from "./chunk-HB3ETNMW.js";
6
6
 
7
7
  // src/main.ts
8
8
  runDaemon(process.env.OCTOPUS_PROXY_CONFIG ?? "proxy.json").catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skylandnpm/octopus-proxy",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "type": "module",
5
5
  "description": "Octopus host proxy: OpenCode, Codex, ZCode and Pi adapters with service installer CLI",
6
6
  "bin": {