@skylandnpm/octopus-proxy 0.1.0 → 0.1.2

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
@@ -7,10 +7,13 @@ Octopus Proxy 是运行在 macOS 或 Linux 主机上的 Octopus 代理,提供
7
7
  ```sh
8
8
  npm install -g @skylandnpm/octopus-proxy@latest --registry https://registry.npmjs.org
9
9
  octopus-proxy help
10
+ octopus-proxy version
10
11
  octopus-proxy onboard
11
12
  ```
12
13
 
13
- 无交互环境可使用 `octopus-proxy onboard --non-interactive`;完整选项见 `octopus-proxy help`。安装服务后可运行 `octopus-proxy status` 和 `octopus-proxy doctor` 检查状态。
14
+ 向导默认使用 `https://octopus.wuyehao.com` 作为 Hub 地址;按需可输入其他地址,或用 `--hub-url` 指定。再次运行向导时会读取现有配置,直接回车即可保留各项设置;机器人 Secret 只提示已配置,不在终端显示原值。macOS 上选择编码工具时,向导优先显示已配置或探测到的命令路径,否则给出该工具的 macOS 路径示例;回车后会检查文件可执行性并运行版本命令,失败时要求重新输入。无交互环境可使用 `octopus-proxy onboard --non-interactive`,未提供 `--hub-url` 时也使用该默认地址;完整选项见 `octopus-proxy help`。安装服务后可运行 `octopus-proxy status` 和 `octopus-proxy doctor` 检查状态。
15
+
16
+ Claw 打开机器人工作区时会通过 Hub 向 Proxy 发送只读 `runtime.snapshot` 命令。响应包含当前运行状态、Hub 和工具设置、授权项目路径,以及工具可提供的模型信息;机器人 Secret、工具密码和提供商凭据不会返回。工作区的“Proxy 信息”按钮可查看这些内容。
14
17
 
15
18
  ## Pi agent
16
19
 
@@ -42,11 +45,12 @@ octopus-proxy onboard --tool pi
42
45
 
43
46
  ## 发布
44
47
 
45
- 与 `apps/octopus-taskhub/cli` 相同,发布命令会先构建,再递增补丁版本,最后发布到公开 npm registry。先提交工作区改动并登录有权发布 `@skylandnpm` 包的 npm 账号:
48
+ 在 `apps/octopus-proxy` 目录执行 `npm publish` 即可。发布前会检查公开 npm registry:如果当前版本已发布,自动递增补丁版本;如果当前版本尚未发布,则保留该版本。随后编译并发布。版本更新只修改本地 `package.json`,不会自动创建 Git 提交或标签。先登录有权发布 `@skylandnpm` 包的 npm 账号:
46
49
 
47
50
  ```sh
48
51
  npm adduser --registry https://registry.npmjs.org
49
- pnpm --filter @skylandnpm/octopus-proxy run build:patch:publish
52
+ cd apps/octopus-proxy
53
+ npm publish
50
54
  ```
51
55
 
52
- 发布前可用 `pnpm --filter @skylandnpm/octopus-proxy build` 检查构建;进入 `apps/octopus-proxy` 后运行 `npm pack --dry-run` 检查包内容。
56
+ 预览包内容可运行 `pnpm build && npm pack --dry-run`。`npm publish --dry-run` 也会编译且不会修改版本号或上传,但当前版本已发布时仍会报版本冲突。
@@ -11658,12 +11658,13 @@ async function loadConfig(path) {
11658
11658
  import WebSocket2 from "ws";
11659
11659
  import { mkdir as mkdir5 } from "node:fs/promises";
11660
11660
  import { join as join9 } from "node:path";
11661
- import { randomUUID as randomUUID10 } from "node:crypto";
11661
+ import { randomUUID as randomUUID11 } from "node:crypto";
11662
11662
 
11663
11663
  // ../../packages/protocol/src/index.ts
11664
11664
  var PROTOCOL_VERSION = "1.0";
11665
11665
  var Operations = [
11666
11666
  "runtime.status",
11667
+ "runtime.snapshot",
11667
11668
  "runtime.start",
11668
11669
  "runtime.stop",
11669
11670
  "runtime.restart",
@@ -11737,7 +11738,7 @@ var CommandSchema = z.object({
11737
11738
  sessionId: z.string().optional(),
11738
11739
  payload: z.record(z.unknown()).default({})
11739
11740
  }).strict();
11740
- var isReadOperation = (operation) => /\.(status|list|get|messages|diff|todos|children|logs|projects|read|search|history|models|agents|commands|providers|mcp)$/.test(operation);
11741
+ var isReadOperation = (operation) => /\.(status|snapshot|list|get|messages|diff|todos|children|logs|projects|read|search|history|models|agents|commands|providers|mcp)$/.test(operation);
11741
11742
 
11742
11743
  // src/workspace-diff.ts
11743
11744
  import { execFile } from "node:child_process";
@@ -12253,29 +12254,29 @@ var Artifacts = class {
12253
12254
  }
12254
12255
  if (!/\.(md|txt)$/i.test(entry.name)) continue;
12255
12256
  seen.add(entry.path);
12256
- const record = index[entry.path] ?? { path: entry.path, revisions: [] };
12257
+ const record2 = index[entry.path] ?? { path: entry.path, revisions: [] };
12257
12258
  if (entry.size > 2 * 1024 * 1024) {
12258
12259
  const notice = "\u5F53\u524D\u6587\u4EF6\u8D85\u8FC7 2 MiB\uFF0C\u672A\u4FDD\u5B58\u65B0\u7684\u4FEE\u8BA2\u5FEB\u7167\uFF1B\u5DF2\u6709\u5386\u53F2\u4ECD\u53EF\u67E5\u770B\u3002";
12259
- if (record.snapshotNotice !== notice) {
12260
- record.snapshotNotice = notice;
12261
- index[entry.path] = record;
12260
+ if (record2.snapshotNotice !== notice) {
12261
+ record2.snapshotNotice = notice;
12262
+ index[entry.path] = record2;
12262
12263
  changed = true;
12263
12264
  }
12264
12265
  continue;
12265
12266
  }
12266
- const data = await readFile3(await this.workspace.path(project, entry.path)), hash = createHash4("sha256").update(data).digest("hex"), previous = record.revisions.at(-1);
12267
- if (record.snapshotNotice) {
12268
- delete record.snapshotNotice;
12267
+ const data = await readFile3(await this.workspace.path(project, entry.path)), hash = createHash4("sha256").update(data).digest("hex"), previous = record2.revisions.at(-1);
12268
+ if (record2.snapshotNotice) {
12269
+ delete record2.snapshotNotice;
12269
12270
  changed = true;
12270
12271
  }
12271
12272
  if (previous?.hash !== hash || previous.deleted) {
12272
12273
  const dir = join4(this.directory, project, "blobs");
12273
12274
  await mkdir3(dir, { recursive: true, mode: 448 });
12274
12275
  await writeFile2(join4(dir, hash), data, { mode: 384 });
12275
- record.revisions.push({ hash, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
12276
+ record2.revisions.push({ hash, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
12276
12277
  changed = true;
12277
12278
  }
12278
- index[entry.path] = record;
12279
+ index[entry.path] = record2;
12279
12280
  }
12280
12281
  };
12281
12282
  for (const dir of this.workspace.project(project).specDirectories) {
@@ -12388,8 +12389,85 @@ var EventStream = class {
12388
12389
  }
12389
12390
  };
12390
12391
 
12392
+ // src/proxy-snapshot.ts
12393
+ import { randomUUID as randomUUID4 } from "node:crypto";
12394
+ function record(value) {
12395
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
12396
+ }
12397
+ function publicModels(value) {
12398
+ const result = [];
12399
+ const providers = record(value).providers;
12400
+ if (!Array.isArray(providers)) return result;
12401
+ for (const providerValue of providers) {
12402
+ const provider = record(providerValue);
12403
+ if (typeof provider.id !== "string") continue;
12404
+ for (const modelValue of Object.values(record(provider.models))) {
12405
+ const model = record(modelValue);
12406
+ if (typeof model.id !== "string") continue;
12407
+ result.push({ id: `${provider.id}/${model.id}`, name: typeof model.name === "string" ? model.name : model.id, isDefault: model.isDefault === true });
12408
+ if (result.length >= 500) return result;
12409
+ }
12410
+ }
12411
+ return result;
12412
+ }
12413
+ async function buildProxySnapshot(config, runtime, adapter, requestedProjectId) {
12414
+ const capabilities = adapter.supportedOperations();
12415
+ const status = record(await runtime.status());
12416
+ const projects = config.projects.map(({ id, name, directory, specDirectories }) => ({ id, name, directory, specDirectories }));
12417
+ const projectId = projects.find((project) => project.id === requestedProjectId)?.id ?? projects[0]?.id;
12418
+ const models = [];
12419
+ let modelCatalogStatus = capabilities.includes("configuration.models") ? "unavailable" : "unsupported";
12420
+ if (status.healthy === true && projectId && capabilities.includes("configuration.models")) {
12421
+ try {
12422
+ const command = { type: "command", id: randomUUID4(), botId: config.botId, operation: "configuration.models", projectId, payload: {} };
12423
+ models.push(...publicModels(await adapter.dispatch(command)));
12424
+ modelCatalogStatus = "available";
12425
+ } catch {
12426
+ modelCatalogStatus = "unavailable";
12427
+ }
12428
+ }
12429
+ let recentModel;
12430
+ if (status.healthy === true && projectId && modelCatalogStatus === "unsupported" && capabilities.includes("sessions.list")) {
12431
+ try {
12432
+ const command = { type: "command", id: randomUUID4(), botId: config.botId, operation: "sessions.list", projectId, payload: {} };
12433
+ const sessions = await adapter.dispatch(command);
12434
+ if (Array.isArray(sessions)) {
12435
+ const withModel = sessions.map(record).filter((session) => {
12436
+ const model = record(session.model);
12437
+ return typeof model.providerId === "string" && typeof model.modelId === "string";
12438
+ }).sort((left, right) => {
12439
+ const leftTime = record(left.time).updated, rightTime = record(right.time).updated;
12440
+ return (typeof rightTime === "number" ? rightTime : 0) - (typeof leftTime === "number" ? leftTime : 0);
12441
+ })[0];
12442
+ if (withModel) {
12443
+ const model = record(withModel.model);
12444
+ recentModel = { providerId: model.providerId, modelId: model.modelId };
12445
+ }
12446
+ }
12447
+ } catch {
12448
+ }
12449
+ }
12450
+ return {
12451
+ runtime: { ...status, capabilities },
12452
+ config: {
12453
+ hubUrl: config.hubUrl,
12454
+ botId: config.botId,
12455
+ stateDirectory: config.stateDirectory,
12456
+ tool: {
12457
+ kind: config.tool.kind,
12458
+ command: config.tool.command,
12459
+ mode: config.tool.mode,
12460
+ autoStart: config.tool.autoStart,
12461
+ ...config.tool.kind === "opencode" ? { baseUrl: config.tool.baseUrl } : {}
12462
+ }
12463
+ },
12464
+ projects,
12465
+ modelCatalog: { projectId, status: modelCatalogStatus, models, ...recentModel ? { recentModel } : {} }
12466
+ };
12467
+ }
12468
+
12391
12469
  // src/media.ts
12392
- import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
12470
+ import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
12393
12471
  import { constants as constants3 } from "node:fs";
12394
12472
  import { open as open3, unlink as unlink3, realpath as realpath3 } from "node:fs/promises";
12395
12473
  import { basename as basename3, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve3, sep as sep2 } from "node:path";
@@ -12606,7 +12684,7 @@ var RemoteMedia = class {
12606
12684
  if (bytes.length === 0 || bytes.length > this.imageLimit()) throw new Error("Image is empty or exceeds size limit");
12607
12685
  const extension = { "image/png": "png", "image/jpeg": "jpg", "image/gif": "gif", "image/webp": "webp", "application/pdf": "pdf" }[mime] ?? "bin";
12608
12686
  const directory = await this.workspace.attachmentDirectory(project, "media", sha256), path = directory + "/asset." + extension;
12609
- const temp = await this.workspace.path(project, directory + "/.octopus-media-" + randomUUID4(), true);
12687
+ const temp = await this.workspace.path(project, directory + "/.octopus-media-" + randomUUID5(), true);
12610
12688
  const file = await open3(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 384);
12611
12689
  try {
12612
12690
  await file.writeFile(bytes);
@@ -14024,7 +14102,7 @@ function applyEdits(text, edits) {
14024
14102
 
14025
14103
  // src/project-config.ts
14026
14104
  import { readFile as readFile4, writeFile as writeFile3, unlink as unlink4 } from "node:fs/promises";
14027
- import { createHash as createHash6, randomUUID as randomUUID5 } from "node:crypto";
14105
+ import { createHash as createHash6, randomUUID as randomUUID6 } from "node:crypto";
14028
14106
  var ProjectConfig = class {
14029
14107
  constructor(workspace, request) {
14030
14108
  this.workspace = workspace;
@@ -14075,7 +14153,7 @@ var ProjectConfig = class {
14075
14153
  if (!this.validator(parse2(updated))) throw new Error("Merged OpenCode configuration is invalid: " + JSON.stringify(this.validator.errors));
14076
14154
  if (exists) await this.workspace.write(project, name, updated, createHash6("sha256").update(original).digest("hex"));
14077
14155
  else {
14078
- const temporary = await this.workspace.path(project, `.octopus-config-${randomUUID5()}.tmp`, true);
14156
+ const temporary = await this.workspace.path(project, `.octopus-config-${randomUUID6()}.tmp`, true);
14079
14157
  try {
14080
14158
  await writeFile3(temporary, updated, { mode: 384, flag: "wx" });
14081
14159
  await this.workspace.publish(project, temporary, name);
@@ -14538,7 +14616,7 @@ import { open as open5 } from "node:fs/promises";
14538
14616
  import { join as join6 } from "node:path";
14539
14617
 
14540
14618
  // src/tools/line-rpc.ts
14541
- import { randomUUID as randomUUID6 } from "node:crypto";
14619
+ import { randomUUID as randomUUID7 } from "node:crypto";
14542
14620
  import { crc32 } from "node:zlib";
14543
14621
  import { StringDecoder } from "node:string_decoder";
14544
14622
  function interrupted(message) {
@@ -14677,37 +14755,37 @@ var LineRpc = class {
14677
14755
  return;
14678
14756
  }
14679
14757
  if (this.protocol === "pi") {
14680
- const record2 = message;
14681
- if (record2.type === "response") {
14682
- const waiter = this.pending.get(String(record2.id));
14758
+ const record3 = message;
14759
+ if (record3.type === "response") {
14760
+ const waiter = this.pending.get(String(record3.id));
14683
14761
  if (waiter) {
14684
14762
  clearTimeout(waiter.timer);
14685
- this.pending.delete(String(record2.id));
14686
- if (record2.success === true) waiter.resolve(record2.data);
14687
- else waiter.reject(new Error(record2.error ?? "Pi RPC command failed"));
14763
+ this.pending.delete(String(record3.id));
14764
+ if (record3.success === true) waiter.resolve(record3.data);
14765
+ else waiter.reject(new Error(record3.error ?? "Pi RPC command failed"));
14688
14766
  }
14689
- } else if (typeof record2.type === "string") this.handlers.onNotification?.(record2.type, message);
14767
+ } else if (typeof record3.type === "string") this.handlers.onNotification?.(record3.type, message);
14690
14768
  return;
14691
14769
  }
14692
- const record = message;
14693
- if (record.id !== void 0 && typeof record.method !== "string") {
14694
- const waiter = this.pending.get(String(record.id));
14770
+ const record2 = message;
14771
+ if (record2.id !== void 0 && typeof record2.method !== "string") {
14772
+ const waiter = this.pending.get(String(record2.id));
14695
14773
  if (waiter) {
14696
14774
  clearTimeout(waiter.timer);
14697
- this.pending.delete(String(record.id));
14698
- if (record.error) waiter.reject(new Error(record.error.message ?? `App server error ${record.error.code ?? ""}`));
14699
- else waiter.resolve(record.result);
14775
+ this.pending.delete(String(record2.id));
14776
+ if (record2.error) waiter.reject(new Error(record2.error.message ?? `App server error ${record2.error.code ?? ""}`));
14777
+ else waiter.resolve(record2.result);
14700
14778
  }
14701
14779
  return;
14702
14780
  }
14703
- if (typeof record.method === "string" && record.id !== void 0) {
14704
- const respond = (result) => this.respond(record.id, result);
14705
- const respondError = (code, messageText) => this.respondError(record.id, code, messageText);
14706
- this.handlers.onServerRequest?.(record.id, record.method, message.params, respond, respondError);
14781
+ if (typeof record2.method === "string" && record2.id !== void 0) {
14782
+ const respond = (result) => this.respond(record2.id, result);
14783
+ const respondError = (code, messageText) => this.respondError(record2.id, code, messageText);
14784
+ this.handlers.onServerRequest?.(record2.id, record2.method, message.params, respond, respondError);
14707
14785
  return;
14708
14786
  }
14709
- if (typeof record.method === "string") {
14710
- this.handlers.onNotification?.(record.method, message.params);
14787
+ if (typeof record2.method === "string") {
14788
+ this.handlers.onNotification?.(record2.method, message.params);
14711
14789
  return;
14712
14790
  }
14713
14791
  }
@@ -14723,7 +14801,7 @@ var LineRpc = class {
14723
14801
  }
14724
14802
  };
14725
14803
  function wrapEnvelope(frame) {
14726
- return { wireVersion: 3, kind: "complete", deliveryKind: "desktop-continuous", logicalFrameId: randomUUID6(), logicalFrameOrdinal: 1, topic: "outbound", subscriptionId: "outbound", frame };
14804
+ return { wireVersion: 3, kind: "complete", deliveryKind: "desktop-continuous", logicalFrameId: randomUUID7(), logicalFrameOrdinal: 1, topic: "outbound", subscriptionId: "outbound", frame };
14727
14805
  }
14728
14806
 
14729
14807
  // src/tools/stdio-runtime.ts
@@ -14900,6 +14978,7 @@ import { join as join7 } from "node:path";
14900
14978
  // src/adapter.ts
14901
14979
  var DAEMON_OPERATIONS = [
14902
14980
  "runtime.status",
14981
+ "runtime.snapshot",
14903
14982
  "runtime.start",
14904
14983
  "runtime.stop",
14905
14984
  "runtime.restart",
@@ -14925,7 +15004,7 @@ function unsupportedOperation(kind, operation) {
14925
15004
  }
14926
15005
 
14927
15006
  // src/tools/codex.ts
14928
- import { randomUUID as randomUUID7 } from "node:crypto";
15007
+ import { randomUUID as randomUUID8 } from "node:crypto";
14929
15008
  var OPERATIONS = [
14930
15009
  ...DAEMON_OPERATIONS,
14931
15010
  "sessions.list",
@@ -14989,7 +15068,7 @@ var CodexAdapter = class {
14989
15068
  }
14990
15069
  onServerRequest(project, id, method, params, respond) {
14991
15070
  const detail = params ?? {};
14992
- const requestId = randomUUID7();
15071
+ const requestId = randomUUID8();
14993
15072
  const threadId = String(detail.threadId ?? "");
14994
15073
  if (["item/permissions/requestApproval", "item/commandExecution/requestApproval", "item/fileChange/requestApproval"].includes(method)) {
14995
15074
  this.approvals.set(requestId, { project, method, requestId, threadId, detail, respond });
@@ -15318,7 +15397,7 @@ var CodexAdapter = class {
15318
15397
  };
15319
15398
 
15320
15399
  // src/tools/zcode.ts
15321
- import { randomUUID as randomUUID8 } from "node:crypto";
15400
+ import { randomUUID as randomUUID9 } from "node:crypto";
15322
15401
  import { realpath as realpath5 } from "node:fs/promises";
15323
15402
  var OPERATIONS2 = [
15324
15403
  ...DAEMON_OPERATIONS,
@@ -15370,7 +15449,7 @@ var ZcodeAdapter = class {
15370
15449
  }
15371
15450
  onServerRequest(project, id, method, params, respond, respondError) {
15372
15451
  const detail = params ?? {};
15373
- const requestId = randomUUID8();
15452
+ const requestId = randomUUID9();
15374
15453
  const sessionId = String(detail.sessionId ?? "");
15375
15454
  if (method === "session/requestRuntimePreferences") {
15376
15455
  respond({ nativeSearchEnhancementsEnabled: false });
@@ -15393,7 +15472,15 @@ var ZcodeAdapter = class {
15393
15472
  return { workspaceKey: root, workspacePath: root };
15394
15473
  }
15395
15474
  sessionProjection(session) {
15396
- return { id: session.sessionId, title: session.title ?? "", directory: session.workspace?.workspacePath, time: { created: session.createdAt, updated: session.updatedAt }, status: session.status };
15475
+ const model = session.model;
15476
+ return {
15477
+ id: session.sessionId,
15478
+ title: session.title ?? "",
15479
+ directory: session.workspace?.workspacePath,
15480
+ time: { created: session.createdAt, updated: session.updatedAt },
15481
+ status: session.status,
15482
+ ...typeof model?.providerId === "string" && typeof model.modelId === "string" ? { model: { providerId: model.providerId, modelId: model.modelId } } : {}
15483
+ };
15397
15484
  }
15398
15485
  rowToPart(row) {
15399
15486
  const data = row.data ?? row;
@@ -15587,7 +15674,7 @@ var ZcodeAdapter = class {
15587
15674
  };
15588
15675
 
15589
15676
  // src/tools/pi.ts
15590
- import { randomUUID as randomUUID9 } from "node:crypto";
15677
+ import { randomUUID as randomUUID10 } from "node:crypto";
15591
15678
  import { readFile as readFile5, readdir as readdir3, rm } from "node:fs/promises";
15592
15679
  import { dirname as dirname4 } from "node:path";
15593
15680
  var OPERATIONS3 = [
@@ -15681,7 +15768,7 @@ var PiAdapter = class {
15681
15768
  }
15682
15769
  onQuestion(project, sessionId, detail) {
15683
15770
  if (!["select", "confirm", "input", "editor"].includes(String(detail.method))) return;
15684
- const id = randomUUID9();
15771
+ const id = randomUUID10();
15685
15772
  void this.rpc(project, sessionId).then((rpc) => {
15686
15773
  if (rpc.exited) return;
15687
15774
  const question = { id, project, sessionId, detail, rpc };
@@ -15781,7 +15868,7 @@ var PiAdapter = class {
15781
15868
  return sessions.sort((a, b) => b.time.updated - a.time.updated);
15782
15869
  }
15783
15870
  if (op === "sessions.create") {
15784
- const session2 = { id: randomUUID9(), title: typeof p.title === "string" && p.title.trim() ? p.title : "\u65B0\u4EFB\u52A1", directory: await this.workspace.root(project), time: { created: Date.now(), updated: Date.now() } };
15871
+ const session2 = { id: randomUUID10(), title: typeof p.title === "string" && p.title.trim() ? p.title : "\u65B0\u4EFB\u52A1", directory: await this.workspace.root(project), time: { created: Date.now(), updated: Date.now() } };
15785
15872
  try {
15786
15873
  const rpc2 = await this.rpc(project, session2.id);
15787
15874
  await rpc2.request("set_session_name", { name: session2.title });
@@ -16057,7 +16144,7 @@ async function runDaemon(configPath) {
16057
16144
  const config = await loadConfig(configPath);
16058
16145
  await mkdir5(config.stateDirectory, { recursive: true, mode: 448 });
16059
16146
  const workspace = new Workspace(config.projects), runtime = createRuntime(config), uploads = new Uploads(workspace, join9(config.stateDirectory, "uploads")), journal = new Journal(join9(config.stateDirectory, "journal"));
16060
- const epoch = randomUUID10();
16147
+ const epoch = randomUUID11();
16061
16148
  let stopping = false, botId = "";
16062
16149
  const sockets = /* @__PURE__ */ new Set();
16063
16150
  const events = new EventStream(epoch, () => {
@@ -16070,6 +16157,7 @@ async function runDaemon(configPath) {
16070
16157
  async function dispatch(c) {
16071
16158
  const p = c.payload;
16072
16159
  if (c.operation === "runtime.status") return { ...await runtime.status(), capabilities: adapter.supportedOperations() };
16160
+ if (c.operation === "runtime.snapshot") return buildProxySnapshot(config, runtime, adapter, c.projectId);
16073
16161
  if (c.operation === "runtime.logs") return runtime.logs();
16074
16162
  if (c.operation === "runtime.start") return runtime.start();
16075
16163
  if (c.operation === "runtime.stop") return runtime.stop();
package/dist/cli.js CHANGED
@@ -17,11 +17,12 @@ import {
17
17
  runDaemon,
18
18
  supportedPlatform,
19
19
  systemdUnitPath
20
- } from "./chunk-TIHWE4WN.js";
20
+ } from "./chunk-3TRTDBUO.js";
21
21
 
22
22
  // src/cli.ts
23
23
  import { spawn } from "node:child_process";
24
- import { access as access3 } from "node:fs/promises";
24
+ import { readFileSync } from "node:fs";
25
+ import { access as access4 } from "node:fs/promises";
25
26
 
26
27
  // src/doctor.ts
27
28
  import { access as access2, chmod, constants, mkdir as mkdir3, stat as stat2 } from "node:fs/promises";
@@ -423,14 +424,18 @@ async function runDoctor(opts) {
423
424
  // src/onboard.ts
424
425
  import { createInterface } from "node:readline/promises";
425
426
  import { stdin, stdout } from "node:process";
426
- import { mkdir as mkdir4, stat as stat3 } from "node:fs/promises";
427
+ import { access as access3, constants as constants2, mkdir as mkdir4, stat as stat3 } from "node:fs/promises";
428
+ import { execFile as execFile5 } from "node:child_process";
429
+ import { platform as platform2 } from "node:os";
427
430
  import { isAbsolute as isAbsolute3 } from "node:path";
431
+ import { promisify as promisify5 } from "node:util";
432
+ var DEFAULT_HUB_URL = "https://octopus.wuyehao.com";
428
433
  async function ask(rl, question, options = {}) {
429
434
  for (; ; ) {
430
- const suffix = options.default !== void 0 ? `\uFF08\u9ED8\u8BA4 ${options.default}\uFF09` : "";
435
+ const suffix = options.default !== void 0 && options.showDefault !== false ? `\uFF08\u9ED8\u8BA4 ${options.defaultDisplay ?? options.default}\uFF09` : "";
431
436
  const answer = (await rl.question(`${question}${suffix}: `)).trim();
432
437
  const value = answer || options.default || "";
433
- const problem = options.validate?.(value);
438
+ const problem = await options.validate?.(value);
434
439
  if (problem) {
435
440
  console.log(` ${problem}`);
436
441
  continue;
@@ -451,24 +456,76 @@ var validUrl = (value) => {
451
456
  };
452
457
  var validId = (value) => /^[a-zA-Z0-9_-]+$/.test(value) ? void 0 : "\u4EC5\u9650\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26";
453
458
  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";
459
+ var run5 = promisify5(execFile5);
460
+ var MACOS_TOOL_EXAMPLES = {
461
+ opencode: "/opt/homebrew/bin/opencode",
462
+ codex: "/Applications/ChatGPT.app/Contents/Resources/codex",
463
+ zcode: "/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs",
464
+ pi: "/opt/homebrew/bin/pi"
465
+ };
466
+ async function validateToolCommand(value, kind) {
467
+ const command2 = expandHome(value);
468
+ if (!isAbsolute3(command2)) return "\u670D\u52A1\u73AF\u5883\u9700\u8981\u7EDD\u5BF9\u8DEF\u5F84";
469
+ try {
470
+ if (!(await stat3(command2)).isFile()) return "\u547D\u4EE4\u8DEF\u5F84\u5FC5\u987B\u6307\u5411\u53EF\u6267\u884C\u6587\u4EF6";
471
+ await access3(command2, constants2.X_OK);
472
+ } catch {
473
+ return `\u547D\u4EE4\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C\uFF1A${command2}`;
474
+ }
475
+ try {
476
+ await run5(command2, [...describeTool(kind).versionArgs], { timeout: 1e4 });
477
+ } catch {
478
+ return `${describeTool(kind).label} \u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u786E\u8BA4\u547D\u4EE4\u8DEF\u5F84\uFF1A${command2}`;
479
+ }
480
+ return void 0;
481
+ }
454
482
  async function runOnboard(opts) {
455
- let hubUrl = opts.hubUrl ?? "", botId = opts.botId ?? "", secretKey = opts.secretKey ?? "", stateDirectory = opts.stateDirectory ?? defaultStateDirectory(), toolCommand = opts.toolCommand ?? "", toolKind = opts.toolKind ?? "opencode", mode = "managed";
456
- let projects = opts.projects.map((project) => ({ ...project, directory: expandHome(project.directory) }));
483
+ const configExists = await stat3(opts.configPath).then(() => true, (error) => {
484
+ if (error.code === "ENOENT") return false;
485
+ throw error;
486
+ });
487
+ const existing = opts.interactive && configExists ? await loadConfig(opts.configPath) : void 0;
488
+ let hubUrl = opts.hubUrl || existing?.hubUrl || DEFAULT_HUB_URL, botId = opts.botId ?? existing?.botId ?? "", secretKey = opts.secretKey ?? existing?.secretKey ?? "", stateDirectory = opts.stateDirectory ?? existing?.stateDirectory ?? defaultStateDirectory();
489
+ let toolKind = opts.toolKind ?? existing?.tool.kind ?? "opencode";
490
+ let toolCommand = opts.toolCommand ?? (toolKind === existing?.tool.kind ? existing.tool.command : "");
491
+ let mode = toolKind === existing?.tool.kind ? existing.tool.mode : "managed";
492
+ let projects = [...existing?.projects ?? [], ...opts.projects].map((project) => ({ ...project, directory: expandHome(project.directory) }));
457
493
  if (opts.interactive) {
458
494
  const rl = createInterface({ input: stdin, output: stdout });
459
495
  console.log("Octopus Proxy \u521D\u59CB\u5316\u5411\u5BFC");
460
496
  console.log(`\u914D\u7F6E\u5C06\u5199\u5165 ${opts.configPath}\uFF1B\u6587\u4EF6\u5305\u542B\u673A\u5668\u4EBA Secret\uFF0C\u4F1A\u4EE5 0600 \u6743\u9650\u4FDD\u5B58\u3002
461
497
  `);
462
- hubUrl = await ask(rl, "Hub \u5730\u5740", { default: hubUrl || void 0, validate: validUrl });
463
- botId = await ask(rl, "\u673A\u5668\u4EBA ID\uFF08\u5728 Claw \u6216 Taskhub \u4E2D\u521B\u5EFA\u5E76\u590D\u5236\uFF09", { validate: validBotId });
464
- secretKey = await ask(rl, "\u673A\u5668\u4EBA Secret\uFF08\u5728 Claw \u6216 Taskhub \u521B\u5EFA\uFF1B\u8F93\u5165\u4F1A\u56DE\u663E\u5728\u7EC8\u7AEF\uFF09", { validate: (value) => value.length >= 20 ? void 0 : "\u8BF7\u8F93\u5165\u5B8C\u6574\u7684\u673A\u5668\u4EBA Secret" });
498
+ hubUrl = await ask(rl, `Hub \u5730\u5740\uFF08${hubUrl} , \u56DE\u8F66\u76F4\u63A5\u786E\u8BA4\uFF09`, { default: hubUrl, showDefault: false, validate: validUrl });
499
+ botId = await ask(rl, "\u673A\u5668\u4EBA ID\uFF08\u5728 Claw \u6216 Taskhub \u4E2D\u521B\u5EFA\u5E76\u590D\u5236\uFF09", { default: botId || void 0, validate: validBotId });
500
+ secretKey = await ask(rl, "\u673A\u5668\u4EBA Secret\uFF08\u5728 Claw \u6216 Taskhub \u521B\u5EFA\uFF1B\u8F93\u5165\u4F1A\u56DE\u663E\u5728\u7EC8\u7AEF\uFF09", { default: secretKey || void 0, defaultDisplay: existing?.secretKey ? "\u5DF2\u914D\u7F6E\uFF0C\u56DE\u8F66\u4FDD\u7559" : "\u5DF2\u63D0\u4F9B\uFF0C\u56DE\u8F66\u4F7F\u7528", validate: (value) => value.length >= 20 ? void 0 : "\u8BF7\u8F93\u5165\u5B8C\u6574\u7684\u673A\u5668\u4EBA Secret" });
465
501
  stateDirectory = expandHome(await ask(rl, "\u72B6\u6001\u76EE\u5F55", { default: stateDirectory, validate: (value) => isAbsolute3(expandHome(value)) ? void 0 : "\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" }));
502
+ const previousToolKind = toolKind;
466
503
  toolKind = await ask(rl, `\u7F16\u7801\u5DE5\u5177\uFF08${TOOL_KINDS.join("/")}\uFF09`, { default: toolKind, validate: (value) => TOOL_KINDS.includes(value) ? void 0 : `\u8F93\u5165 ${TOOL_KINDS.join(" \u6216 ")}` });
504
+ if (toolKind !== previousToolKind) toolCommand = toolKind === existing?.tool.kind ? existing.tool.command : "";
467
505
  const descriptor = describeTool(toolKind);
468
506
  const found = await resolveCommandOnPath(toolKind, toolSearchDirs(toolKind));
469
- toolCommand = await ask(rl, found ? `${descriptor.label} \u547D\u4EE4\uFF08\u63A2\u6D4B\u5230 ${found}\uFF09` : `${descriptor.label} \u547D\u4EE4\uFF08\u672A\u5728 PATH \u4E2D\u63A2\u6D4B\u5230\uFF0C\u8BF7\u8F93\u5165\u7EDD\u5BF9\u8DEF\u5F84\uFF09`, { default: found ?? void 0, validate: (value) => isAbsolute3(expandHome(value)) ? void 0 : "\u670D\u52A1\u73AF\u5883\u9700\u8981\u7EDD\u5BF9\u8DEF\u5F84" });
470
- if (toolKind === "opencode") mode = await ask(rl, "OpenCode \u6A21\u5F0F\uFF08managed=\u7531\u4EE3\u7406\u6258\u7BA1\u542F\u52A8\uFF0Cexternal=\u4EC5\u8FDE\u63A5\u5DF2\u6709\u670D\u52A1\uFF09", { default: "managed", validate: (value) => value === "managed" || value === "external" ? void 0 : "\u8F93\u5165 managed \u6216 external" });
471
- console.log("\n\u5F55\u5165\u6388\u6743\u9879\u76EE\uFF08\u540D\u79F0\u7559\u7A7A\u7ED3\u675F\uFF09\uFF1A");
507
+ const example = platform2() === "darwin" ? MACOS_TOOL_EXAMPLES[toolKind] : void 0;
508
+ toolCommand = expandHome(await ask(rl, found ? `${descriptor.label} \u547D\u4EE4\uFF08\u63A2\u6D4B\u5230 ${found}\uFF1B\u56DE\u8F66\u4F1A\u9A8C\u8BC1\uFF09` : `${descriptor.label} \u547D\u4EE4\uFF08\u56DE\u8F66\u4F1A\u9A8C\u8BC1\u8DEF\u5F84\uFF09`, { default: toolCommand || found || example, validate: (value) => validateToolCommand(value, toolKind) }));
509
+ console.log(` \u5DF2\u786E\u8BA4 ${descriptor.label} \u547D\u4EE4\u53EF\u6267\u884C\uFF1A${toolCommand}`);
510
+ if (toolKind === "opencode") mode = await ask(rl, "OpenCode \u6A21\u5F0F\uFF08managed=\u7531\u4EE3\u7406\u6258\u7BA1\u542F\u52A8\uFF0Cexternal=\u4EC5\u8FDE\u63A5\u5DF2\u6709\u670D\u52A1\uFF09", { default: mode, validate: (value) => value === "managed" || value === "external" ? void 0 : "\u8F93\u5165 managed \u6216 external" });
511
+ if (projects.length) console.log("\n\u5DF2\u6709\u6388\u6743\u9879\u76EE\uFF08\u76F4\u63A5\u56DE\u8F66\u4FDD\u7559\u5404\u9879\uFF09\uFF1A");
512
+ for (const project of projects) {
513
+ project.name = await ask(rl, " \u9879\u76EE\u540D\u79F0", { default: project.name });
514
+ for (; ; ) {
515
+ project.directory = expandHome(await ask(rl, ` \u300C${project.name}\u300D\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09`, { default: project.directory }));
516
+ if (!isAbsolute3(project.directory)) {
517
+ console.log(" \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");
518
+ continue;
519
+ }
520
+ if (!await stat3(project.directory).then(() => true, () => false)) {
521
+ console.log(" \u76EE\u5F55\u4E0D\u5B58\u5728");
522
+ continue;
523
+ }
524
+ break;
525
+ }
526
+ project.id = await ask(rl, " \u9879\u76EE ID", { default: project.id ?? slug(project.name), validate: validId });
527
+ }
528
+ console.log("\n\u6DFB\u52A0\u6388\u6743\u9879\u76EE\uFF08\u540D\u79F0\u7559\u7A7A\u7ED3\u675F\uFF09\uFF1A");
472
529
  for (; ; ) {
473
530
  const name = (await rl.question(" \u9879\u76EE\u540D\u79F0: ")).trim();
474
531
  if (!name) break;
@@ -493,7 +550,6 @@ async function runOnboard(opts) {
493
550
  console.log("");
494
551
  } else {
495
552
  const missing = [];
496
- if (!hubUrl) missing.push("--hub-url");
497
553
  if (!botId) missing.push("--bot-id");
498
554
  if (!secretKey) missing.push("--secret-key");
499
555
  if (!projects.length) missing.push("--project");
@@ -509,12 +565,12 @@ async function runOnboard(opts) {
509
565
  toolCommand = toolCommand ? expandHome(toolCommand) : await resolveCommandOnPath(toolKind, toolSearchDirs(toolKind)) ?? descriptor.defaultCommand;
510
566
  if (!isAbsolute3(toolCommand)) console.log(`\u63D0\u793A\uFF1A\u672A\u5728 PATH \u4E2D\u63A2\u6D4B\u5230 ${descriptor.label}\uFF0C\u914D\u7F6E\u4FDD\u7559\u547D\u4EE4\u540D\u300C${toolCommand}\u300D\uFF1B\u670D\u52A1\u73AF\u5883\u5EFA\u8BAE\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u53EF\u7528 --tool-command \u6307\u5B9A\uFF09`);
511
567
  }
512
- if (await stat3(opts.configPath).then(() => true, () => false)) {
568
+ if (configExists) {
513
569
  if (opts.interactive) {
514
570
  const rl = createInterface({ input: stdin, output: stdout });
515
- const answer = (await rl.question(`\u914D\u7F6E ${opts.configPath} \u5DF2\u5B58\u5728\uFF0C\u8986\u76D6\uFF1F(y/N): `)).trim();
571
+ const answer = (await rl.question(`\u4FDD\u5B58\u5230\u73B0\u6709\u914D\u7F6E ${opts.configPath}\uFF1F(Y/n): `)).trim();
516
572
  rl.close();
517
- if (!/^y(es)?$/i.test(answer)) {
573
+ if (/^(n|no)$/i.test(answer)) {
518
574
  console.log("\u5DF2\u53D6\u6D88\uFF0C\u672A\u5199\u5165\u4EFB\u4F55\u5185\u5BB9\u3002");
519
575
  return;
520
576
  }
@@ -528,8 +584,8 @@ async function runOnboard(opts) {
528
584
  secretKey,
529
585
  stateDirectory,
530
586
  ...loopbackHttp ? { allowInsecureLocalhost: true } : {},
531
- projects: projects.map((project) => ({ id: project.id, name: project.name, directory: project.directory, specDirectories: ["specs", "docs"] })),
532
- tool: { kind: toolKind, command: toolCommand || describeTool(toolKind).defaultCommand, ...toolKind === "opencode" ? { baseUrl: "http://127.0.0.1:4096", mode } : {}, autoStart: true }
587
+ projects: projects.map((project) => ({ id: project.id, name: project.name, directory: project.directory, specDirectories: project.specDirectories ?? ["specs", "docs"] })),
588
+ tool: { kind: toolKind, command: toolCommand || describeTool(toolKind).defaultCommand, ...toolKind === "opencode" ? { baseUrl: toolKind === existing?.tool.kind ? existing.tool.baseUrl : "http://127.0.0.1:4096", mode } : {}, ...toolKind === existing?.tool.kind && existing.tool.password ? { password: existing.tool.password } : {}, autoStart: toolKind === existing?.tool.kind ? existing.tool.autoStart : true }
533
589
  });
534
590
  await loadConfig(opts.configPath);
535
591
  console.log(`\u5DF2\u5199\u5165\u914D\u7F6E\uFF1A${opts.configPath}\uFF080600\uFF09`);
@@ -573,14 +629,15 @@ var USAGE = `octopus-proxy \u2014 Octopus \u4E3B\u673A\u4EE3\u7406
573
629
  doctor \u68C0\u67E5\u914D\u7F6E\u3001OpenCode\u3001node \u4E0E\u670D\u52A1\u5065\u5EB7\uFF08--fix \u81EA\u52A8\u4FEE\u590D\uFF09
574
630
  run \u524D\u53F0\u8FD0\u884C\u4EE3\u7406\uFF08\u8C03\u8BD5\u7528\uFF0CCtrl+C \u9000\u51FA\uFF09
575
631
  logs \u67E5\u770B\u670D\u52A1\u65E5\u5FD7\uFF08-f \u6301\u7EED\u8DDF\u968F\uFF09
632
+ version \u663E\u793A\u7248\u672C\u53F7
576
633
  help \u663E\u793A\u672C\u5E2E\u52A9
577
634
 
578
635
  \u901A\u7528\u9009\u9879:
579
636
  --config <path> \u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 $OCTOPUS_PROXY_CONFIG \u6216 ~/.config/octopus/proxy.json\uFF09
580
637
 
581
638
  onboard \u9009\u9879:
582
- --non-interactive \u975E\u4EA4\u4E92\u6A21\u5F0F\uFF1B\u9700\u63D0\u4F9B Hub \u5730\u5740\u3001\u673A\u5668\u4EBA ID\u3001Secret \u548C\u9879\u76EE
583
- --hub-url <url> Hub \u5730\u5740\uFF08https\uFF09
639
+ --non-interactive \u975E\u4EA4\u4E92\u6A21\u5F0F\uFF1B\u9700\u63D0\u4F9B\u673A\u5668\u4EBA ID\u3001Secret \u548C\u9879\u76EE
640
+ --hub-url <url> Hub \u5730\u5740\uFF08\u9ED8\u8BA4 https://octopus.wuyehao.com\uFF09
584
641
  --bot-id <uuid> Claw \u6216 Taskhub \u4E2D\u521B\u5EFA\u7684\u673A\u5668\u4EBA ID
585
642
  --secret-key <key> \u673A\u5668\u4EBA Secret\uFF08\u5728 Claw \u6216 Taskhub \u521B\u5EFA\uFF09
586
643
  --state-dir <path> \u72B6\u6001\u76EE\u5F55\uFF08\u9ED8\u8BA4 ~/.local/state/octopus\uFF09
@@ -608,6 +665,10 @@ function parse(argv) {
608
665
  flags2["--help"] = "true";
609
666
  continue;
610
667
  }
668
+ if (arg === "-v" || arg === "--version") {
669
+ flags2["--version"] = "true";
670
+ continue;
671
+ }
611
672
  if (arg === "-f") {
612
673
  flags2["--follow"] = "true";
613
674
  continue;
@@ -633,7 +694,7 @@ function parse(argv) {
633
694
  }
634
695
  async function showLogs(follow) {
635
696
  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" });
636
- if (isMacOS() && !await access3(launchdLogPath()).then(() => true, () => false)) {
697
+ if (isMacOS() && !await access4(launchdLogPath()).then(() => true, () => false)) {
637
698
  console.error(`\u65E5\u5FD7\u6587\u4EF6\u5C1A\u672A\u751F\u6210\uFF1A${launchdLogPath()}`);
638
699
  process.exit(1);
639
700
  }
@@ -646,6 +707,11 @@ async function showLogs(follow) {
646
707
  process.exit(code);
647
708
  }
648
709
  var { command, flags, projectArgs } = parse(process.argv.slice(2));
710
+ if (command === "version" || flags["--version"]) {
711
+ const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
712
+ console.log(packageJson.version);
713
+ process.exit(0);
714
+ }
649
715
  if (!command || command === "help" || flags["--help"]) {
650
716
  console.log(USAGE);
651
717
  process.exit(command || flags["--help"] ? 0 : 1);
package/dist/main.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ConfigError,
4
4
  runDaemon
5
- } from "./chunk-TIHWE4WN.js";
5
+ } from "./chunk-3TRTDBUO.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.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Octopus host proxy: OpenCode, Codex, ZCode and Pi adapters with service installer CLI",
6
6
  "bin": {
@@ -15,7 +15,8 @@
15
15
  },
16
16
  "scripts": {
17
17
  "build": "tsc --noEmit && tsup",
18
- "build:patch:publish": "pnpm build && npm version patch && npm publish",
18
+ "prepublishOnly": "node scripts/prepare-publish.mjs",
19
+ "build:patch:publish": "npm publish",
19
20
  "typecheck": "tsc --noEmit",
20
21
  "dev": "tsx src/main.ts",
21
22
  "cli": "tsx src/cli.ts",