@skylandnpm/octopus-proxy 0.1.51 → 0.1.53
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 +18 -1
- package/dist/{chunk-55TEBVWY.js → chunk-X7FBZI2P.js} +101 -20
- package/dist/cli.js +64 -21
- package/dist/main.js +1 -1
- package/package.json +1 -1
- package/proxy.example.json +0 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Proxy 主动连接 Hub 的 `/ws/proxy-chat`,无需开放入站端口或安装
|
|
|
20
20
|
|
|
21
21
|
macOS 12.3 及以上运行 `octopus-proxy onboard` 时,配置保存后、服务启动前会准备截屏辅助程序并申请系统屏幕录制权限;已授权时不会重复弹窗。交互模式和 `--non-interactive` 均执行此步骤,`--no-install-service` 也会申请。请在系统弹窗中允许访问;拒绝、超时或辅助程序编译失败时,向导提示原因与授权路径,并继续初始化。在“系统设置 → 隐私与安全性 → 屏幕录制(或屏幕与系统音频录制)”中应授权弹窗显示的实际程序,可能是 Terminal、iTerm、编辑器或 node,不保证出现 `octopus-desktop-capture`。onboard 和 doctor 会显示辅助程序路径,文件名不等于系统授权名称。终端授权不代表后台服务已授权,需在 Claw 点击截屏,让实际服务申请权限,按实际名称授权后重启 Proxy。Linux 没有通用的截屏授权弹窗,需使用桌面用户的图形会话环境;平台依赖与设置见[桌面快照说明](../../docs/claw-desktop-snapshots.md)。
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
基础设置完成后,向导会提示至少需要配置一个工作项目。新项目依次填写项目 ID、名称(默认使用项目 ID)和目录;完成第一个项目后,可选择继续添加更多项目,或直接回车完成项目配置并继续保存。只有选择继续添加时,才会进入下一个项目 ID 的输入流程。
|
|
24
24
|
|
|
25
25
|
`octopus-proxy doctor` 会显示“桌面截屏支持”和“屏幕录制权限”:macOS 检查系统版本、辅助程序、活动显示器和当前授权状态;未授权时给出 onboard 申请及系统设置指引。Linux 检查图形会话、显示器和截屏依赖,缺少依赖、无桌面或不支持的 Wayland 环境会显示具体提示;Linux 无统一的授权查询接口,因此会明确显示权限尚无法确认,需在 Claw 实际截屏验证。诊断不触发授权弹窗、不截屏,`doctor --fix` 也不会自动申请截屏权限。这些检查使用运行 doctor 的用户与环境;服务以其他启动程序或图形会话运行时,其权限与环境需要单独确认。
|
|
26
26
|
|
|
@@ -102,6 +102,23 @@ octopus-proxy update
|
|
|
102
102
|
|
|
103
103
|
安装或版本校验失败时不会触发服务重启;包安装失败不保证旧版本文件完整保留。已安装新版本但重启失败时会单独提示,可运行 `octopus-proxy doctor` 和 `octopus-proxy logs` 排查。此命令用于 npm 全局安装,覆盖安装前会检查 npm 安装目录是否与当前命令一致;源码、本地安装或 Node.js 环境不一致时会中止并给出提示。
|
|
104
104
|
|
|
105
|
+
## 服务启动时报 `Unexpected token '??='`
|
|
106
|
+
|
|
107
|
+
这通常表示服务实际使用了旧版 Node.js。错误栈中 `.nvm/versions/node/v22.../lib/node_modules` 是包的安装位置,不能说明运行它的 Node.js 版本。安装器只会选择 Node.js 22 或更高版本;旧版安装器可能优先选中能执行 `--version` 的旧系统 Node。
|
|
108
|
+
|
|
109
|
+
对于通过 NVM 安装的环境,可切换到安装 Proxy 的 Node.js 版本,并重写服务定义,明确指定服务使用的 Node。例如包安装在 `v22.22.0` 下时:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
nvm use 22.22.0
|
|
113
|
+
node --version
|
|
114
|
+
octopus-proxy install --force --node-path "$(node -p 'process.execPath')"
|
|
115
|
+
octopus-proxy status
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`install --force` 会重写并重新加载服务定义,保留配置和状态数据;自定义配置时带上原来的 `--config <path>`。单独运行 `restart` 会继续使用已有服务定义中的 Node 路径。NVM 中被固定的版本需要保留,删除该版本后应重新安装服务。
|
|
119
|
+
|
|
120
|
+
`octopus-proxy doctor` 会分别显示当前命令和已安装服务的 Node.js 路径、版本及最低要求(Node.js >= 22)。服务检查读取已保存的 launchd/systemd 定义,再执行其中 Node 的 `--version`;版本过低、路径失效或版本输出无法识别都会标记为失败。配置无效时仍会检查 Node;尚未安装服务时提示未检查服务版本。`doctor --fix` 重写服务定义后会重新检查保存的 Node 路径和版本。
|
|
121
|
+
|
|
105
122
|
## 发布
|
|
106
123
|
|
|
107
124
|
在 `apps/octopus-proxy` 目录执行 `npm run publish`:先递增 `package.json` 的补丁版本,再编译,最后发布。即使编译或发布失败,版本号也会保留递增结果,重试时继续递增。版本更新不会自动创建 Git 提交或标签。先登录有权发布 `@skylandnpm` 包的 npm 账号:
|
|
@@ -3321,8 +3321,8 @@ var require_utils = __commonJS({
|
|
|
3321
3321
|
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3322
3322
|
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3323
3323
|
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
3324
|
-
function reescapeHostDelimiters(host,
|
|
3325
|
-
const re =
|
|
3324
|
+
function reescapeHostDelimiters(host, isIP2) {
|
|
3325
|
+
const re = isIP2 ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
3326
3326
|
re.lastIndex = 0;
|
|
3327
3327
|
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
3328
3328
|
}
|
|
@@ -4011,8 +4011,8 @@ var require_fast_uri = __commonJS({
|
|
|
4011
4011
|
const host = matches[4];
|
|
4012
4012
|
return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
|
|
4013
4013
|
}
|
|
4014
|
-
function canonicalizeHost(parsed, options, schemeHandler,
|
|
4015
|
-
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) &&
|
|
4014
|
+
function canonicalizeHost(parsed, options, schemeHandler, isIP2) {
|
|
4015
|
+
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP2 === false && nonSimpleDomain(parsed.host)) {
|
|
4016
4016
|
try {
|
|
4017
4017
|
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
4018
4018
|
} catch (e) {
|
|
@@ -4039,7 +4039,7 @@ var require_fast_uri = __commonJS({
|
|
|
4039
4039
|
let malformedHost = false;
|
|
4040
4040
|
let malformedIPLiteral = false;
|
|
4041
4041
|
let malformedScheme = false;
|
|
4042
|
-
let
|
|
4042
|
+
let isIP2 = false;
|
|
4043
4043
|
if (options.reference === "suffix") {
|
|
4044
4044
|
if (options.scheme) {
|
|
4045
4045
|
uri = options.scheme + ":" + uri;
|
|
@@ -4102,15 +4102,15 @@ var require_fast_uri = __commonJS({
|
|
|
4102
4102
|
const bracketedIPLiteral = isIPLiteral(parsed.host);
|
|
4103
4103
|
const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
|
|
4104
4104
|
const ipv6result = normalizeIPv6(parsed.host);
|
|
4105
|
-
|
|
4105
|
+
isIP2 = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
|
|
4106
4106
|
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
|
|
4107
|
-
parsed.host =
|
|
4107
|
+
parsed.host = isIP2 ? ipv6result.host : ipv6result.host.toLowerCase();
|
|
4108
4108
|
if (malformedIPLiteral) {
|
|
4109
4109
|
parsed.error = parsed.error || "URI host is malformed.";
|
|
4110
4110
|
malformedAuthorityOrPort = true;
|
|
4111
4111
|
}
|
|
4112
4112
|
} else {
|
|
4113
|
-
|
|
4113
|
+
isIP2 = true;
|
|
4114
4114
|
}
|
|
4115
4115
|
}
|
|
4116
4116
|
if (parsed.scheme === void 0 && parsed.userinfo === void 0 && parsed.host === void 0 && parsed.port === void 0 && parsed.query === void 0 && !parsed.path) {
|
|
@@ -4127,14 +4127,14 @@ var require_fast_uri = __commonJS({
|
|
|
4127
4127
|
}
|
|
4128
4128
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
|
|
4129
4129
|
if (!malformedIPLiteral) {
|
|
4130
|
-
malformedHost = canonicalizeHost(parsed, options, schemeHandler,
|
|
4130
|
+
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP2);
|
|
4131
4131
|
}
|
|
4132
4132
|
if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
|
|
4133
|
-
let host =
|
|
4134
|
-
if (!
|
|
4133
|
+
let host = isIP2 ? parsed.host : normalizePercentEncoding(parsed.host, true);
|
|
4134
|
+
if (!isIP2) {
|
|
4135
4135
|
host = normalizePercentEncoding(host.toLowerCase());
|
|
4136
4136
|
}
|
|
4137
|
-
parsed.host = reescapeHostDelimiters(host,
|
|
4137
|
+
parsed.host = reescapeHostDelimiters(host, isIP2);
|
|
4138
4138
|
}
|
|
4139
4139
|
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4140
4140
|
if (parsed.path) {
|
|
@@ -13335,6 +13335,71 @@ var PlatformDesktopSession = class {
|
|
|
13335
13335
|
import { fork } from "node:child_process";
|
|
13336
13336
|
import { randomBytes as randomBytes2, randomUUID as randomUUID16 } from "node:crypto";
|
|
13337
13337
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
13338
|
+
|
|
13339
|
+
// src/remote-stun.ts
|
|
13340
|
+
import { Resolver } from "node:dns/promises";
|
|
13341
|
+
import { isIP } from "node:net";
|
|
13342
|
+
var publicStunHost = "stun.cloudflare.com";
|
|
13343
|
+
var dnsJsonEndpoints = ["https://dns.alidns.com/resolve", "https://cloudflare-dns.com/dns-query"];
|
|
13344
|
+
var STUN_DNS_TIMEOUT = 1500;
|
|
13345
|
+
function defaultRemoteStunUrls(hubUrl) {
|
|
13346
|
+
return [.../* @__PURE__ */ new Set([`stun:${new URL(hubUrl).hostname}:3478`, `stun:${publicStunHost}:3478`])];
|
|
13347
|
+
}
|
|
13348
|
+
function publicAddress(address, family) {
|
|
13349
|
+
if (isIP(address) !== family) return false;
|
|
13350
|
+
if (family === 6) return /^[23][\da-f]{0,3}:/i.test(address);
|
|
13351
|
+
const [a, b] = address.split(".").map(Number);
|
|
13352
|
+
return a !== 0 && a !== 10 && a !== 127 && a < 224 && !(a === 100 && b >= 64 && b <= 127) && !(a === 169 && b === 254) && !(a === 172 && b >= 16 && b <= 31) && !(a === 192 && b === 168) && !(a === 198 && (b === 18 || b === 19));
|
|
13353
|
+
}
|
|
13354
|
+
async function systemLookup(family) {
|
|
13355
|
+
const resolver = new Resolver({ timeout: 500, tries: 1 });
|
|
13356
|
+
try {
|
|
13357
|
+
return await (family === 6 ? resolver.resolve6(publicStunHost) : resolver.resolve4(publicStunHost));
|
|
13358
|
+
} finally {
|
|
13359
|
+
resolver.cancel();
|
|
13360
|
+
}
|
|
13361
|
+
}
|
|
13362
|
+
async function queryDnsJson(endpoint, family, signal) {
|
|
13363
|
+
const type = family === 6 ? 28 : 1;
|
|
13364
|
+
const url = new URL(endpoint);
|
|
13365
|
+
url.searchParams.set("name", publicStunHost);
|
|
13366
|
+
url.searchParams.set("type", String(type));
|
|
13367
|
+
const response = await fetch(url, { headers: { accept: "application/dns-json" }, signal, redirect: "error" });
|
|
13368
|
+
if (!response.ok) throw new Error("STUN DNS request failed");
|
|
13369
|
+
const text = await response.text();
|
|
13370
|
+
if (text.length > 16384) throw new Error("STUN DNS response too large");
|
|
13371
|
+
const body = JSON.parse(text);
|
|
13372
|
+
if (body.Status !== 0 || !Array.isArray(body.Answer)) throw new Error("STUN DNS answer unavailable");
|
|
13373
|
+
return body.Answer.filter((record2) => record2.type === type && typeof record2.data === "string").map((record2) => record2.data);
|
|
13374
|
+
}
|
|
13375
|
+
async function resolvePublicStunAddresses(lookup = systemLookup, query = queryDnsJson) {
|
|
13376
|
+
const families = await Promise.all([6, 4].map(async (family) => {
|
|
13377
|
+
const valid = (addresses) => addresses.filter((address) => publicAddress(address, family)).slice(0, 1);
|
|
13378
|
+
const native = valid(await lookup(family).catch(() => []));
|
|
13379
|
+
if (native.length) return native;
|
|
13380
|
+
const controller = new AbortController();
|
|
13381
|
+
const timer = setTimeout(() => controller.abort(), STUN_DNS_TIMEOUT);
|
|
13382
|
+
try {
|
|
13383
|
+
return await Promise.any(dnsJsonEndpoints.map(async (endpoint) => {
|
|
13384
|
+
const addresses = valid(await query(endpoint, family, controller.signal));
|
|
13385
|
+
if (!addresses.length) throw new Error("No public STUN address");
|
|
13386
|
+
return addresses;
|
|
13387
|
+
})).catch(() => []);
|
|
13388
|
+
} finally {
|
|
13389
|
+
clearTimeout(timer);
|
|
13390
|
+
controller.abort();
|
|
13391
|
+
}
|
|
13392
|
+
}));
|
|
13393
|
+
return families.flat();
|
|
13394
|
+
}
|
|
13395
|
+
async function discoverRemoteStunUrls(hubUrl, configured, resolve5 = resolvePublicStunAddresses) {
|
|
13396
|
+
if (configured) return [...configured];
|
|
13397
|
+
const addresses = await resolve5().catch(() => []);
|
|
13398
|
+
const numeric = addresses.map((address) => `stun:${isIP(address) === 6 ? `[${address}]` : address}:3478`);
|
|
13399
|
+
return [.../* @__PURE__ */ new Set([...numeric, ...defaultRemoteStunUrls(hubUrl)])].slice(0, 4);
|
|
13400
|
+
}
|
|
13401
|
+
|
|
13402
|
+
// src/remote-manager.ts
|
|
13338
13403
|
function spawnWorker(message) {
|
|
13339
13404
|
const worker = fileURLToPath4(new URL(import.meta.url.endsWith(".ts") ? "./remote-worker.ts" : "./remote-worker.js", import.meta.url));
|
|
13340
13405
|
const child = fork(worker, [], { stdio: ["ignore", "ignore", "ignore", "ipc"], execArgv: import.meta.url.endsWith(".ts") ? ["--import", "tsx"] : [] });
|
|
@@ -13342,7 +13407,7 @@ function spawnWorker(message) {
|
|
|
13342
13407
|
return child;
|
|
13343
13408
|
}
|
|
13344
13409
|
var RemoteManager = class {
|
|
13345
|
-
constructor(host, desktop, desktopSession, send, touch, workerFactory = spawnWorker, probe = probeRemote) {
|
|
13410
|
+
constructor(host, desktop, desktopSession, send, touch, workerFactory = spawnWorker, probe = probeRemote, discoverStun = discoverRemoteStunUrls) {
|
|
13346
13411
|
this.host = host;
|
|
13347
13412
|
this.desktop = desktop;
|
|
13348
13413
|
this.desktopSession = desktopSession;
|
|
@@ -13350,13 +13415,14 @@ var RemoteManager = class {
|
|
|
13350
13415
|
this.touch = touch;
|
|
13351
13416
|
this.workerFactory = workerFactory;
|
|
13352
13417
|
this.probe = probe;
|
|
13418
|
+
this.discoverStun = discoverStun;
|
|
13353
13419
|
this.config = RemoteConfigSchema.parse(host.desktopRemote ?? {});
|
|
13354
13420
|
}
|
|
13355
13421
|
current;
|
|
13356
13422
|
stopping = false;
|
|
13357
13423
|
config;
|
|
13358
13424
|
stunUrls() {
|
|
13359
|
-
return this.config.stunUrls ??
|
|
13425
|
+
return this.config.stunUrls ?? defaultRemoteStunUrls(this.host.hubUrl);
|
|
13360
13426
|
}
|
|
13361
13427
|
async status() {
|
|
13362
13428
|
const base = { qualityModes: RemoteQualitySchema.options, platform: process.platform, passwordInput: process.platform === "darwin", enabled: this.config.enabled, busy: !!this.current, stunUrls: this.stunUrls(), budget: { ...REMOTE_DEFAULTS, totalBitrate: this.config.totalBitrate, videoMaxBitrate: this.config.videoMaxBitrate } };
|
|
@@ -13404,7 +13470,10 @@ var RemoteManager = class {
|
|
|
13404
13470
|
const session = { remoteId: randomUUID16(), owner, closed: false, signaling: Promise.resolve(), signals: 0, signalWindow: Date.now(), candidates: 0 };
|
|
13405
13471
|
this.current = session;
|
|
13406
13472
|
try {
|
|
13407
|
-
const permissions = await
|
|
13473
|
+
const [permissions, stunUrls] = await Promise.all([
|
|
13474
|
+
this.probe(this.host.stateDirectory, this.config),
|
|
13475
|
+
this.discoverStun(this.host.hubUrl, this.config.stunUrls)
|
|
13476
|
+
]);
|
|
13408
13477
|
if (!permissions.session || !permissions.capture || !permissions.input) throw new Error("\u8FDC\u63A7\u9700\u8981\u4E3B\u673A\u56FE\u5F62\u4F1A\u8BDD\u3001\u5C4F\u5E55\u5F55\u5236\u53CA\u8F85\u52A9\u529F\u80FD\u6743\u9650");
|
|
13409
13478
|
if (permissions.locked === true && permissions.lockCapture === false) throw new Error("\u9501\u5C4F\u8FDC\u63A7\u9700\u8981 macOS 14 \u53CA\u4EE5\u4E0A\u53CA\u65B0\u7248 Xcode Command Line Tools\uFF1B\u5F53\u524D\u4E3B\u673A\u8BF7\u5148\u672C\u5730\u89E3\u9501");
|
|
13410
13479
|
const identity = await this.desktopSession.sessionIdentity();
|
|
@@ -13413,7 +13482,7 @@ var RemoteManager = class {
|
|
|
13413
13482
|
const layout = await this.desktop.layoutIdentity?.(display.id);
|
|
13414
13483
|
if (session.closed || this.current !== session) throw new Error("\u5DE5\u4F5C\u533A\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u8FDC\u63A7\u5DF2\u53D6\u6D88");
|
|
13415
13484
|
const bindToken = randomBytes2(32).toString("base64url"), expiresAt = Date.now() + 2e4;
|
|
13416
|
-
const child = this.workerFactory({ remoteId: session.remoteId, bindToken, expiresAt, display, helper: permissions.helper, quality, legacy: payload.quality === void 0, config: this.config, stunUrls
|
|
13485
|
+
const child = this.workerFactory({ remoteId: session.remoteId, bindToken, expiresAt, display, helper: permissions.helper, quality, legacy: payload.quality === void 0, config: this.config, stunUrls });
|
|
13417
13486
|
session.child = child;
|
|
13418
13487
|
child.on("message", (message) => {
|
|
13419
13488
|
if (this.current !== session || session.closed || !message || typeof message !== "object") return;
|
|
@@ -13447,7 +13516,7 @@ var RemoteManager = class {
|
|
|
13447
13516
|
}
|
|
13448
13517
|
}, 1e3);
|
|
13449
13518
|
session.monitor.unref();
|
|
13450
|
-
return { remoteId: session.remoteId, bindToken, expiresAt: new Date(expiresAt).toISOString(), display, revision: 1, stunUrls
|
|
13519
|
+
return { remoteId: session.remoteId, bindToken, expiresAt: new Date(expiresAt).toISOString(), display, revision: 1, stunUrls, budget, quality, qualityModes: RemoteQualitySchema.options, protocolVersion: 1, heartbeatAck: true };
|
|
13451
13520
|
} catch (error) {
|
|
13452
13521
|
this.finish(session);
|
|
13453
13522
|
throw error;
|
|
@@ -13490,10 +13559,19 @@ import { join as join12 } from "node:path";
|
|
|
13490
13559
|
import { homedir as homedir2, platform as platform4 } from "node:os";
|
|
13491
13560
|
var run4 = promisify7(execFile7);
|
|
13492
13561
|
var VERSION_MANAGED_SEGMENTS = /* @__PURE__ */ new Set([".nvm", "nvm", ".volta", "volta", ".fnm", "fnm", ".asdf", "asdf", ".mise", "mise"]);
|
|
13562
|
+
var MIN_NODE_MAJOR = 22;
|
|
13563
|
+
function parseNodeVersion(version) {
|
|
13564
|
+
const match = /^v(\d+)\.\d+\.\d+(?:-[\w.-]+)?$/.exec(version);
|
|
13565
|
+
if (!match) throw new Error(`\u65E0\u6CD5\u8BC6\u522B Node.js \u7248\u672C\uFF1A${version || "\u7A7A\u8F93\u51FA"}`);
|
|
13566
|
+
return { version, supported: Number(match[1]) >= MIN_NODE_MAJOR };
|
|
13567
|
+
}
|
|
13568
|
+
async function probeNodeVersion(path) {
|
|
13569
|
+
const { stdout } = await run4(path, ["--version"], { timeout: 5e3 });
|
|
13570
|
+
return parseNodeVersion(stdout.trim());
|
|
13571
|
+
}
|
|
13493
13572
|
var execProbe = async (path) => {
|
|
13494
13573
|
try {
|
|
13495
|
-
await
|
|
13496
|
-
return true;
|
|
13574
|
+
return (await probeNodeVersion(path)).supported;
|
|
13497
13575
|
} catch {
|
|
13498
13576
|
return false;
|
|
13499
13577
|
}
|
|
@@ -13507,7 +13585,7 @@ function nodeCandidates() {
|
|
|
13507
13585
|
async function resolveNodePath(options = {}) {
|
|
13508
13586
|
const probe = options.probe ?? execProbe;
|
|
13509
13587
|
if (options.pinned) {
|
|
13510
|
-
if (!await probe(options.pinned)) throw new Error(`--node-path ${options.pinned} is not a working
|
|
13588
|
+
if (!await probe(options.pinned)) throw new Error(`--node-path ${options.pinned} is not a working Node.js >= ${MIN_NODE_MAJOR} executable; check ${options.pinned} --version`);
|
|
13511
13589
|
return { nodePath: options.pinned, source: "pinned" };
|
|
13512
13590
|
}
|
|
13513
13591
|
let exec2 = options.execPath ?? process.execPath;
|
|
@@ -13825,6 +13903,9 @@ export {
|
|
|
13825
13903
|
checkTerminalSupport,
|
|
13826
13904
|
PlatformDesktopSession,
|
|
13827
13905
|
RemoteManager,
|
|
13906
|
+
MIN_NODE_MAJOR,
|
|
13907
|
+
parseNodeVersion,
|
|
13908
|
+
probeNodeVersion,
|
|
13828
13909
|
isVersionManagedPath,
|
|
13829
13910
|
resolveNodePath,
|
|
13830
13911
|
toolSearchDirs,
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ConfigError,
|
|
4
|
+
MIN_NODE_MAJOR,
|
|
4
5
|
PlatformDesktopSession,
|
|
5
6
|
RemoteManager,
|
|
6
7
|
SERVICE_LABEL,
|
|
@@ -23,6 +24,8 @@ import {
|
|
|
23
24
|
launchdLogPath,
|
|
24
25
|
launchdPlistPath,
|
|
25
26
|
loadConfig,
|
|
27
|
+
parseNodeVersion,
|
|
28
|
+
probeNodeVersion,
|
|
26
29
|
proxyVersion,
|
|
27
30
|
resolveCommandOnPath,
|
|
28
31
|
resolveConfigPath,
|
|
@@ -31,7 +34,7 @@ import {
|
|
|
31
34
|
supportedPlatform,
|
|
32
35
|
systemdUnitPath,
|
|
33
36
|
toolSearchDirs
|
|
34
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-X7FBZI2P.js";
|
|
35
38
|
import {
|
|
36
39
|
DESKTOP_PERMISSION_GUIDANCE,
|
|
37
40
|
PlatformDesktop,
|
|
@@ -45,7 +48,7 @@ import { access as access4 } from "node:fs/promises";
|
|
|
45
48
|
|
|
46
49
|
// src/doctor.ts
|
|
47
50
|
import { access as access2, chmod, constants, mkdir as mkdir3, stat } from "node:fs/promises";
|
|
48
|
-
import { isAbsolute as
|
|
51
|
+
import { isAbsolute as isAbsolute3, join } from "node:path";
|
|
49
52
|
import { execFile as execFile3 } from "node:child_process";
|
|
50
53
|
import { promisify as promisify3 } from "node:util";
|
|
51
54
|
|
|
@@ -233,7 +236,7 @@ async function readServiceFile() {
|
|
|
233
236
|
async function buildServiceDefinition(opts) {
|
|
234
237
|
const config = await loadConfig(opts.configPath);
|
|
235
238
|
const resolution = await resolveNodePath({ pinned: opts.nodePath });
|
|
236
|
-
if (!resolution.nodePath) throw new Error(
|
|
239
|
+
if (!resolution.nodePath) throw new Error(`\u672A\u627E\u5230 Node.js >= ${MIN_NODE_MAJOR} \u7684\u53EF\u6267\u884C\u6587\u4EF6\uFF1B\u8BF7\u5B89\u88C5\u53D7\u652F\u6301\u7684 Node.js\uFF0C\u6216\u901A\u8FC7 --node-path \u6307\u5B9A\u7EDD\u5BF9\u8DEF\u5F84`);
|
|
237
240
|
const opencodeDir = isAbsolute(config.tool.command) ? [dirname3(config.tool.command)] : [];
|
|
238
241
|
const pathEnv = [dirname3(resolution.nodePath), ...opencodeDir, "/usr/local/bin", "/usr/bin", "/bin"].filter((dir, index, all) => all.indexOf(dir) === index).join(":");
|
|
239
242
|
return { def: { nodePath: resolution.nodePath, entryPath: daemonEntryPath(), configPath: resolve(opts.configPath), pathEnv }, warning: resolution.warning };
|
|
@@ -291,12 +294,47 @@ function logLocationHint() {
|
|
|
291
294
|
return isMacOS() ? launchdLogPath() : "journalctl --user -u octopus-proxy";
|
|
292
295
|
}
|
|
293
296
|
|
|
297
|
+
// src/service-definition.ts
|
|
298
|
+
import { isAbsolute as isAbsolute2 } from "node:path";
|
|
299
|
+
function serviceNodePath(text, macOS) {
|
|
300
|
+
let path;
|
|
301
|
+
if (macOS) {
|
|
302
|
+
const match = /<key>\s*ProgramArguments\s*<\/key>\s*<array>\s*<string>([\s\S]*?)<\/string>/.exec(text);
|
|
303
|
+
const entities = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
304
|
+
path = match?.[1]?.replace(/&(amp|lt|gt|quot|apos);/g, (_, entity) => entities[entity]);
|
|
305
|
+
} else {
|
|
306
|
+
const match = /^[ \t]*ExecStart[ \t]*=[ \t]*(?:"((?:\\.|[^"\\])*)"|([^\s"]+))/m.exec(text);
|
|
307
|
+
path = (match?.[1] ?? match?.[2])?.replace(/\\(["\\])/g, "$1").replaceAll("%%", "%");
|
|
308
|
+
}
|
|
309
|
+
return path && isAbsolute2(path) ? path : null;
|
|
310
|
+
}
|
|
311
|
+
|
|
294
312
|
// src/doctor.ts
|
|
295
313
|
import { randomUUID } from "node:crypto";
|
|
296
314
|
var run3 = promisify3(execFile3);
|
|
315
|
+
function nodeVersionCheck(name, path, info) {
|
|
316
|
+
return {
|
|
317
|
+
name,
|
|
318
|
+
status: info.supported ? "ok" : "fail",
|
|
319
|
+
detail: `${path}\uFF08${info.version}\uFF09\uFF1B\u8981\u6C42 Node.js >= ${MIN_NODE_MAJOR}${info.supported ? "" : "\uFF1B\u8BF7\u5347\u7EA7 Node.js\uFF0C\u5E76\u8FD0\u884C octopus-proxy install --force --node-path <Node\u7EDD\u5BF9\u8DEF\u5F84> \u91CD\u5199\u670D\u52A1\u5B9A\u4E49"}`
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
async function checkServiceNodeVersion() {
|
|
323
|
+
const name = "\u670D\u52A1 Node.js \u7248\u672C";
|
|
324
|
+
const existing = await readServiceFile();
|
|
325
|
+
if (existing === null) return { name, status: "warn", detail: "\u5C1A\u672A\u5B89\u88C5\u670D\u52A1\uFF0C\u65E0\u6CD5\u68C0\u67E5\u670D\u52A1\u4F7F\u7528\u7684 Node.js \u7248\u672C" };
|
|
326
|
+
const path = serviceNodePath(existing, isMacOS());
|
|
327
|
+
if (!path) return { name, status: "fail", detail: "\u65E0\u6CD5\u4ECE\u670D\u52A1\u5B9A\u4E49\u8BFB\u53D6 Node \u7EDD\u5BF9\u8DEF\u5F84\uFF1B\u8FD0\u884C octopus-proxy install --force \u91CD\u5199\u670D\u52A1\u5B9A\u4E49" };
|
|
328
|
+
try {
|
|
329
|
+
return nodeVersionCheck(name, path, await probeNodeVersion(path));
|
|
330
|
+
} catch (error) {
|
|
331
|
+
return { name, status: "fail", detail: `${path} --version \u68C0\u67E5\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}\uFF1B\u8BF7\u786E\u8BA4 Node \u8DEF\u5F84\u6709\u6548\u5E76\u8FD0\u884C octopus-proxy install --force \u91CD\u5199\u670D\u52A1\u5B9A\u4E49` };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
297
334
|
async function runDoctor(opts) {
|
|
298
335
|
const results = [];
|
|
299
336
|
const record = (name, status, detail) => results.push({ name, status, detail });
|
|
337
|
+
results.push(nodeVersionCheck("\u5F53\u524D Node.js \u7248\u672C", process.execPath, parseNodeVersion(process.version)));
|
|
300
338
|
results.push(await checkTerminalSupport(opts.fix));
|
|
301
339
|
let config;
|
|
302
340
|
try {
|
|
@@ -335,7 +373,7 @@ async function runDoctor(opts) {
|
|
|
335
373
|
const descriptor = describeTool(config.tool.kind);
|
|
336
374
|
const checkName = `${descriptor.label} \u547D\u4EE4`;
|
|
337
375
|
const command2 = config.tool.command;
|
|
338
|
-
if (!
|
|
376
|
+
if (!isAbsolute3(command2)) {
|
|
339
377
|
record(checkName, "warn", `\u5F53\u524D\u4E3A\u300C${command2}\u300D\uFF0C\u4F9D\u8D56 PATH \u67E5\u627E\uFF1B\u670D\u52A1\u73AF\u5883\u5EFA\u8BAE\u6539\u7528\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u91CD\u65B0\u8FD0\u884C onboard \u6216\u624B\u5DE5\u4FEE\u6539\u914D\u7F6E\uFF09`);
|
|
340
378
|
} else if (!await access2(command2, constants.X_OK).then(() => true, () => false)) {
|
|
341
379
|
record(checkName, "fail", `${command2} \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C`);
|
|
@@ -399,6 +437,7 @@ async function runDoctor(opts) {
|
|
|
399
437
|
record("\u670D\u52A1\u5B9A\u4E49", "fail", error instanceof Error ? error.message : String(error));
|
|
400
438
|
}
|
|
401
439
|
}
|
|
440
|
+
results.push(await checkServiceNodeVersion());
|
|
402
441
|
const state = await serviceStatus();
|
|
403
442
|
if (state.active) record("\u670D\u52A1\u72B6\u6001", "ok", `\u8FD0\u884C\u4E2D${state.pid ? `\uFF08pid ${state.pid}\uFF09` : ""}`);
|
|
404
443
|
else if (!state.installed) record("\u670D\u52A1\u72B6\u6001", "warn", "\u5C1A\u672A\u5B89\u88C5\u670D\u52A1");
|
|
@@ -420,7 +459,7 @@ import { stdin, stdout } from "node:process";
|
|
|
420
459
|
import { access as access3, constants as constants2, mkdir as mkdir4, stat as stat2 } from "node:fs/promises";
|
|
421
460
|
import { execFile as execFile4 } from "node:child_process";
|
|
422
461
|
import { platform } from "node:os";
|
|
423
|
-
import { isAbsolute as
|
|
462
|
+
import { isAbsolute as isAbsolute4, join as join2 } from "node:path";
|
|
424
463
|
import { promisify as promisify4 } from "node:util";
|
|
425
464
|
var DEFAULT_HUB_URL = "https://octopus.wuyehao.com";
|
|
426
465
|
async function ask(rl, question, options = {}) {
|
|
@@ -459,7 +498,7 @@ var MACOS_TOOL_EXAMPLES = {
|
|
|
459
498
|
};
|
|
460
499
|
async function validateToolCommand(value, kind) {
|
|
461
500
|
const command2 = expandHome(value);
|
|
462
|
-
if (!
|
|
501
|
+
if (!isAbsolute4(command2)) return "\u670D\u52A1\u73AF\u5883\u9700\u8981\u7EDD\u5BF9\u8DEF\u5F84";
|
|
463
502
|
try {
|
|
464
503
|
if (!(await stat2(command2)).isFile()) return "\u547D\u4EE4\u8DEF\u5F84\u5FC5\u987B\u6307\u5411\u53EF\u6267\u884C\u6587\u4EF6";
|
|
465
504
|
await access3(command2, constants2.X_OK);
|
|
@@ -492,7 +531,7 @@ async function runOnboard(opts) {
|
|
|
492
531
|
hubUrl = await ask(rl, `Hub \u5730\u5740\uFF08${hubUrl} , \u56DE\u8F66\u76F4\u63A5\u786E\u8BA4\uFF09`, { default: hubUrl, showDefault: false, validate: validUrl });
|
|
493
532
|
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 });
|
|
494
533
|
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" });
|
|
495
|
-
stateDirectory = expandHome(await ask(rl, "\u72B6\u6001\u76EE\u5F55", { default: stateDirectory, validate: (value) =>
|
|
534
|
+
stateDirectory = expandHome(await ask(rl, "\u72B6\u6001\u76EE\u5F55", { default: stateDirectory, validate: (value) => isAbsolute4(expandHome(value)) ? void 0 : "\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" }));
|
|
496
535
|
const previousToolKind = toolKind;
|
|
497
536
|
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 ")}` });
|
|
498
537
|
if (toolKind !== previousToolKind) toolCommand = toolKind === existing?.tool.kind ? existing.tool.command : "";
|
|
@@ -514,12 +553,13 @@ async function runOnboard(opts) {
|
|
|
514
553
|
toolCommand = expandHome(await ask(rl, `${descriptor.label} \u547D\u4EE4\uFF08\u56DE\u8F66\u4F1A\u9A8C\u8BC1\u8DEF\u5F84\uFF09`, { default: toolCommand || found || example, validate: (value) => validateToolCommand(value, toolKind) }));
|
|
515
554
|
console.log(` \u5DF2\u786E\u8BA4 ${descriptor.label} \u547D\u4EE4\u53EF\u6267\u884C\uFF1A${toolCommand}`);
|
|
516
555
|
}
|
|
556
|
+
console.log("\n\u57FA\u7840\u8BBE\u7F6E\u5DF2\u5B8C\u6210\uFF0C\u81F3\u5C11\u9700\u8981\u914D\u7F6E\u4E00\u4E2A\u5DE5\u4F5C\u9879\u76EE\u3002");
|
|
517
557
|
if (projects.length) console.log("\n\u5DF2\u6709\u6388\u6743\u9879\u76EE\uFF08\u76F4\u63A5\u56DE\u8F66\u4FDD\u7559\u5404\u9879\uFF09\uFF1A");
|
|
518
558
|
for (const [index, project] of projects.entries()) {
|
|
519
559
|
project.name = await ask(rl, " \u9879\u76EE\u540D\u79F0", { default: project.name });
|
|
520
560
|
for (; ; ) {
|
|
521
561
|
project.directory = expandHome(await ask(rl, ` \u300C${project.name}\u300D\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09`, { default: project.directory }));
|
|
522
|
-
if (!
|
|
562
|
+
if (!isAbsolute4(project.directory)) {
|
|
523
563
|
console.log(" \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");
|
|
524
564
|
continue;
|
|
525
565
|
}
|
|
@@ -531,14 +571,24 @@ async function runOnboard(opts) {
|
|
|
531
571
|
}
|
|
532
572
|
project.id = await ask(rl, " \u9879\u76EE ID", { default: project.id ?? slug(project.name), validate: (value) => validateProjectId(value, projects.slice(0, index)) });
|
|
533
573
|
}
|
|
534
|
-
console.log("\n\u6DFB\u52A0\u6388\u6743\u9879\u76EE\uFF08\u540D\u79F0\u7559\u7A7A\u7ED3\u675F\uFF09\uFF1A");
|
|
535
574
|
for (; ; ) {
|
|
536
|
-
|
|
537
|
-
|
|
575
|
+
if (projects.length) {
|
|
576
|
+
console.log(projects.length === 1 ? "\n\u60A8\u5DF2\u5B8C\u6210\u7B2C\u4E00\u4E2A\u9879\u76EE\u914D\u7F6E\uFF0C\u53EF\u4EE5\u7EE7\u7EED\u6DFB\u52A0\u66F4\u591A\u9879\u76EE\uFF0C\u6216\u8005\u5148\u5B8C\u6210\u9879\u76EE\u914D\u7F6E\u3002" : `
|
|
577
|
+
\u60A8\u5DF2\u5B8C\u6210 ${projects.length} \u4E2A\u9879\u76EE\u914D\u7F6E\uFF0C\u53EF\u4EE5\u7EE7\u7EED\u6DFB\u52A0\u66F4\u591A\u9879\u76EE\uFF0C\u6216\u8005\u5148\u5B8C\u6210\u9879\u76EE\u914D\u7F6E\u3002`);
|
|
578
|
+
const addMore = await ask(rl, " \u662F\u5426\u7EE7\u7EED\u6DFB\u52A0\u66F4\u591A\u9879\u76EE\uFF1F(y/N\uFF0C\u56DE\u8F66\u5B8C\u6210\u9879\u76EE\u914D\u7F6E)", {
|
|
579
|
+
default: "n",
|
|
580
|
+
showDefault: false,
|
|
581
|
+
validate: (value) => /^(y|yes|n|no)$/i.test(value) ? void 0 : "\u8BF7\u8F93\u5165 y \u6216 n"
|
|
582
|
+
});
|
|
583
|
+
if (!/^(y|yes)$/i.test(addMore)) break;
|
|
584
|
+
}
|
|
585
|
+
console.log("\n\u6DFB\u52A0\u5DE5\u4F5C\u9879\u76EE\uFF1A");
|
|
586
|
+
const id = await ask(rl, " \u9879\u76EE ID", { validate: (value) => validateProjectId(value, projects) });
|
|
587
|
+
const name = await ask(rl, " \u9879\u76EE\u540D\u79F0", { default: id });
|
|
538
588
|
let directory = "";
|
|
539
589
|
for (; ; ) {
|
|
540
590
|
directory = expandHome(await ask(rl, ` \u300C${name}\u300D\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09`));
|
|
541
|
-
if (!
|
|
591
|
+
if (!isAbsolute4(directory)) {
|
|
542
592
|
console.log(" \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");
|
|
543
593
|
continue;
|
|
544
594
|
}
|
|
@@ -548,14 +598,7 @@ async function runOnboard(opts) {
|
|
|
548
598
|
}
|
|
549
599
|
break;
|
|
550
600
|
}
|
|
551
|
-
const id = await ask(rl, " \u9879\u76EE ID", { default: slug(name), validate: (value) => validateProjectId(value, projects) });
|
|
552
601
|
projects.push({ id, name, directory });
|
|
553
|
-
const addMore = await ask(rl, " \u7EE7\u7EED\u6DFB\u52A0\u66F4\u591A\u6388\u6743\u9879\u76EE\uFF1F(y/N)", {
|
|
554
|
-
default: "n",
|
|
555
|
-
showDefault: false,
|
|
556
|
-
validate: (value) => /^(y|yes|n|no)$/i.test(value) ? void 0 : "\u8BF7\u8F93\u5165 y \u6216 n"
|
|
557
|
-
});
|
|
558
|
-
if (!/^(y|yes)$/i.test(addMore)) break;
|
|
559
602
|
}
|
|
560
603
|
rl.close();
|
|
561
604
|
if (!projects.length) throw new Error("\u81F3\u5C11\u9700\u8981\u5F55\u5165\u4E00\u4E2A\u6388\u6743\u9879\u76EE");
|
|
@@ -577,10 +620,10 @@ async function runOnboard(opts) {
|
|
|
577
620
|
if (seenIds.has(project.id)) throw new Error(`\u9879\u76EE ID\u300C${project.id}\u300D\u91CD\u590D\uFF0C\u8BF7\u8C03\u6574\u9879\u76EE\u540D\u79F0\u6216\u624B\u5DE5\u6307\u5B9A\u4E0D\u540C ID`);
|
|
578
621
|
seenIds.add(project.id);
|
|
579
622
|
}
|
|
580
|
-
if (projects.some((project) => !
|
|
623
|
+
if (projects.some((project) => !isAbsolute4(project.directory))) throw new Error("--project \u7684\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");
|
|
581
624
|
const descriptor = describeTool(toolKind);
|
|
582
625
|
toolCommand = toolCommand ? expandHome(toolCommand) : await resolveCommandOnPath(toolKind, toolSearchDirs(toolKind)) ?? descriptor.defaultCommand;
|
|
583
|
-
if (!
|
|
626
|
+
if (!isAbsolute4(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`);
|
|
584
627
|
}
|
|
585
628
|
if (configExists) {
|
|
586
629
|
if (opts.interactive) {
|
package/dist/main.js
CHANGED
package/package.json
CHANGED
package/proxy.example.json
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
"tool": { "kind": "opencode", "command": "/Users/luhuili/.opencode/bin/opencode", "baseUrl": "http://127.0.0.1:4096", "mode": "external", "password": "replace-with-a-random-secret-at-least-20-characters" },
|
|
8
8
|
"desktopRemote": {
|
|
9
9
|
"enabled": true,
|
|
10
|
-
"stunUrls": ["stun:hub.example.com:3478"],
|
|
11
10
|
"totalBitrate": 22000000,
|
|
12
11
|
"videoMaxBitrate": 16000000
|
|
13
12
|
}
|