@skylandnpm/octopus-proxy 0.1.40 → 0.1.42
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 +4 -0
- package/dist/{chunk-OXA2S7UY.js → chunk-HB3ETNMW.js} +264 -47
- package/dist/cli.js +53 -104
- package/dist/main.js +1 -1
- package/native/desktop-macos.m +44 -17
- package/native/screen-unlock-macos.m +12 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
491
|
-
this.code = optimizeExpr(this.code, names,
|
|
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,
|
|
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,
|
|
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,
|
|
578
|
+
optimizeNames(names, constants7) {
|
|
579
579
|
var _a;
|
|
580
|
-
this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names,
|
|
581
|
-
if (!(super.optimizeNames(names,
|
|
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,
|
|
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,
|
|
607
|
-
if (!super.optimizeNames(names,
|
|
606
|
+
optimizeNames(names, constants7) {
|
|
607
|
+
if (!super.optimizeNames(names, constants7))
|
|
608
608
|
return;
|
|
609
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
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,
|
|
646
|
-
if (!super.optimizeNames(names,
|
|
645
|
+
optimizeNames(names, constants7) {
|
|
646
|
+
if (!super.optimizeNames(names, constants7))
|
|
647
647
|
return;
|
|
648
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
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,
|
|
690
|
+
optimizeNames(names, constants7) {
|
|
691
691
|
var _a, _b;
|
|
692
|
-
super.optimizeNames(names,
|
|
693
|
-
(_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names,
|
|
694
|
-
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names,
|
|
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,
|
|
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 =
|
|
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 &&
|
|
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
|
|
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,
|
|
11990
|
+
serial(id, run5) {
|
|
11991
11991
|
const work = (this.pending.get(id) ?? Promise.resolve()).catch(() => {
|
|
11992
|
-
}).then(
|
|
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",
|
|
@@ -12467,12 +12469,12 @@ var PlatformScreenLock = class {
|
|
|
12467
12469
|
if (signal.aborted) throw new ScreenLockError("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u5C4F\u5E55\u89E3\u9501\u5DF2\u53D6\u6D88\u3002");
|
|
12468
12470
|
};
|
|
12469
12471
|
check();
|
|
12470
|
-
if (!password || Buffer.byteLength(password) > 1024 || /[\x00-\x1f\x7f]/.test(password)) throw new ScreenLockError("\u4E3B\u673A\u5BC6\u7801\u683C\u5F0F\u65E0\u6548\u3002");
|
|
12471
|
-
if (Date.now() < this.blockedUntil) throw new ScreenLockError("\u5BC6\u7801\u9A8C\u8BC1\u5931\u8D25\u6B21\u6570\u8FC7\u591A\uFF0C\u8BF7\u7B49\u5F85 30 \u79D2\u540E\u91CD\u8BD5\u3002");
|
|
12472
12472
|
const status = await this.status();
|
|
12473
12473
|
check();
|
|
12474
12474
|
if (!status.locked) return status;
|
|
12475
12475
|
if (!status.canUnlock) throw new ScreenLockError(status.message ?? "\u4E3B\u673A\u4E0D\u652F\u6301\u5C4F\u5E55\u89E3\u9501\u3002");
|
|
12476
|
+
if (!password || Buffer.byteLength(password) > 1024 || /[\x00-\x1f\x7f]/.test(password)) throw new ScreenLockError("\u4E3B\u673A\u5BC6\u7801\u683C\u5F0F\u65E0\u6548\u3002");
|
|
12477
|
+
if (Date.now() < this.blockedUntil) throw new ScreenLockError("\u5BC6\u7801\u9A8C\u8BC1\u5931\u8D25\u6B21\u6570\u8FC7\u591A\uFF0C\u8BF7\u7B49\u5F85 30 \u79D2\u540E\u91CD\u8BD5\u3002");
|
|
12476
12478
|
let valid;
|
|
12477
12479
|
try {
|
|
12478
12480
|
valid = await this.authenticate(this.stateDirectory, this.account.username, password);
|
|
@@ -12489,6 +12491,10 @@ var PlatformScreenLock = class {
|
|
|
12489
12491
|
}
|
|
12490
12492
|
this.failures = 0;
|
|
12491
12493
|
if (this.system === "darwin") {
|
|
12494
|
+
const current = await this.status();
|
|
12495
|
+
check();
|
|
12496
|
+
if (!current.locked) return current;
|
|
12497
|
+
if (!current.canUnlock) throw new ScreenLockError(current.message ?? "\u4E3B\u673A\u4E0D\u652F\u6301\u5C4F\u5E55\u89E3\u9501\u3002");
|
|
12492
12498
|
const helper = await this.macHelper();
|
|
12493
12499
|
check();
|
|
12494
12500
|
const bytes = Buffer.from(password, "utf8"), input = Buffer.alloc(bytes.length + 2);
|
|
@@ -12507,7 +12513,8 @@ var PlatformScreenLock = class {
|
|
|
12507
12513
|
}
|
|
12508
12514
|
const session = await this.linuxSession(signal);
|
|
12509
12515
|
check();
|
|
12510
|
-
if (session.locked)
|
|
12516
|
+
if (!session.locked) return { ...status, locked: false };
|
|
12517
|
+
await this.execute("loginctl", ["--no-ask-password", "unlock-session", session.id], void 0, signal);
|
|
12511
12518
|
for (let i = 0; i < 20; i++) {
|
|
12512
12519
|
check();
|
|
12513
12520
|
const current = await this.linuxSession(signal);
|
|
@@ -12533,7 +12540,7 @@ var Journal = class {
|
|
|
12533
12540
|
if (active.hash !== hash) return Promise.resolve(this.failure(command, "Request id reused"));
|
|
12534
12541
|
return active.result;
|
|
12535
12542
|
}
|
|
12536
|
-
const
|
|
12543
|
+
const run5 = async () => {
|
|
12537
12544
|
const path = join6(this.directory, `${command.id}.json`);
|
|
12538
12545
|
const readOnly = command.operation.startsWith("desktop.") || isReadOperation(command.operation) || isAuthentication(command.operation);
|
|
12539
12546
|
if (!readOnly) {
|
|
@@ -12555,8 +12562,8 @@ var Journal = class {
|
|
|
12555
12562
|
return response;
|
|
12556
12563
|
};
|
|
12557
12564
|
const key = this.queueKey(command);
|
|
12558
|
-
const result = key === void 0 ?
|
|
12559
|
-
}).then(
|
|
12565
|
+
const result = key === void 0 ? run5() : (this.queues.get(key) ?? Promise.resolve()).catch(() => {
|
|
12566
|
+
}).then(run5);
|
|
12560
12567
|
if (key !== void 0) {
|
|
12561
12568
|
this.queues.set(key, result);
|
|
12562
12569
|
result.finally(() => {
|
|
@@ -12664,9 +12671,9 @@ var Artifacts = class {
|
|
|
12664
12671
|
this.emit = emit;
|
|
12665
12672
|
}
|
|
12666
12673
|
pending = /* @__PURE__ */ new Map();
|
|
12667
|
-
serial(project,
|
|
12674
|
+
serial(project, run5) {
|
|
12668
12675
|
const result = (this.pending.get(project) ?? Promise.resolve()).catch(() => {
|
|
12669
|
-
}).then(
|
|
12676
|
+
}).then(run5);
|
|
12670
12677
|
this.pending.set(project, result);
|
|
12671
12678
|
return result;
|
|
12672
12679
|
}
|
|
@@ -15068,7 +15075,7 @@ var OpenCodeAdapter = class {
|
|
|
15068
15075
|
});
|
|
15069
15076
|
}
|
|
15070
15077
|
};
|
|
15071
|
-
const
|
|
15078
|
+
const run5 = async () => {
|
|
15072
15079
|
if (method.type === "key") {
|
|
15073
15080
|
if (typeof input.key !== "string" || !input.key.trim()) throw new Error("API Key required");
|
|
15074
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);
|
|
@@ -15086,7 +15093,7 @@ var OpenCodeAdapter = class {
|
|
|
15086
15093
|
};
|
|
15087
15094
|
} else await wait();
|
|
15088
15095
|
};
|
|
15089
|
-
void
|
|
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" }));
|
|
15090
15097
|
return { ...flow };
|
|
15091
15098
|
}
|
|
15092
15099
|
subscribe(project) {
|
|
@@ -15901,6 +15908,8 @@ import { join as join11 } from "node:path";
|
|
|
15901
15908
|
var DAEMON_OPERATIONS = [
|
|
15902
15909
|
"runtime.status",
|
|
15903
15910
|
"runtime.snapshot",
|
|
15911
|
+
"runtime.models",
|
|
15912
|
+
"runtime.switchTool",
|
|
15904
15913
|
"runtime.start",
|
|
15905
15914
|
"runtime.stop",
|
|
15906
15915
|
"runtime.restart",
|
|
@@ -17765,23 +17774,228 @@ var DesktopManager = class {
|
|
|
17765
17774
|
}
|
|
17766
17775
|
};
|
|
17767
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
|
+
|
|
17768
17969
|
// src/daemon.ts
|
|
17769
17970
|
async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
17770
17971
|
const config = await loadConfig(configPath);
|
|
17771
17972
|
await mkdir11(config.stateDirectory, { recursive: true, mode: 448 });
|
|
17772
|
-
const workspace = new Workspace(config.projects),
|
|
17973
|
+
const workspace = new Workspace(config.projects), uploads = new Uploads(workspace, join15(config.stateDirectory, "uploads")), journal = new Journal(join15(config.stateDirectory, "journal"));
|
|
17773
17974
|
let secureChat;
|
|
17774
17975
|
function emit(projectId, event, data) {
|
|
17775
17976
|
secureChat?.publish(projectId, event, data);
|
|
17776
17977
|
}
|
|
17777
17978
|
const terminals = new TerminalManager(workspace, (owner, project, event, data) => secureChat?.sendTo(owner, project, event, data));
|
|
17778
17979
|
const desktop = new DesktopManager(new PlatformDesktop(config.stateDirectory), new PlatformScreenLock(config.stateDirectory));
|
|
17779
|
-
const
|
|
17780
|
-
|
|
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);
|
|
17781
17988
|
async function dispatch(c, secureSessionId) {
|
|
17782
17989
|
if (c.operation.startsWith("desktop.")) return desktop.dispatch(c, secureSessionId);
|
|
17783
17990
|
if (c.operation.startsWith("terminal.")) return terminals.dispatch(c, secureSessionId);
|
|
17784
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;
|
|
17785
17999
|
if (c.operation === "runtime.status") return { ...await runtime.status(), capabilities: adapter.supportedOperations() };
|
|
17786
18000
|
if (c.operation === "runtime.snapshot") return buildProxySnapshot(config, runtime, adapter, c.projectId);
|
|
17787
18001
|
if (c.operation === "runtime.logs") return runtime.logs();
|
|
@@ -17790,17 +18004,17 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
|
17790
18004
|
if (c.operation === "runtime.restart") return runtime.restart();
|
|
17791
18005
|
if (c.operation === "workspace.projects") return publicProjects(config);
|
|
17792
18006
|
if (c.operation === "workspace.projects.add") {
|
|
17793
|
-
const result2 = await
|
|
18007
|
+
const result2 = await tools.exclusive(() => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter).add(p));
|
|
17794
18008
|
emit(String(p.id), "octopus.resync", {});
|
|
17795
18009
|
return result2;
|
|
17796
18010
|
}
|
|
17797
18011
|
if (c.operation === "workspace.projects.update") {
|
|
17798
|
-
const result2 = await
|
|
18012
|
+
const result2 = await tools.exclusive(() => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter).update(p));
|
|
17799
18013
|
emit(String(p.id), "octopus.resync", {});
|
|
17800
18014
|
return result2;
|
|
17801
18015
|
}
|
|
17802
18016
|
if (c.operation === "workspace.projects.remove") {
|
|
17803
|
-
const result2 = await
|
|
18017
|
+
const result2 = await tools.exclusive(() => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter).remove(p));
|
|
17804
18018
|
emit(String(p.id), "octopus.resync", {});
|
|
17805
18019
|
return result2;
|
|
17806
18020
|
}
|
|
@@ -17842,7 +18056,7 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
|
17842
18056
|
terminals.closeOwned(owner);
|
|
17843
18057
|
desktop.closeOwned(owner);
|
|
17844
18058
|
}, authenticate);
|
|
17845
|
-
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));
|
|
17846
18060
|
secureChat.start();
|
|
17847
18061
|
const scan = setInterval(async () => {
|
|
17848
18062
|
for (const p of config.projects) await artifacts.scan(p.id).catch(() => {
|
|
@@ -17859,8 +18073,7 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
|
17859
18073
|
secureChat?.stop();
|
|
17860
18074
|
terminals.close();
|
|
17861
18075
|
desktop.close();
|
|
17862
|
-
|
|
17863
|
-
adapter.close();
|
|
18076
|
+
tools.close();
|
|
17864
18077
|
}
|
|
17865
18078
|
function onSignal() {
|
|
17866
18079
|
stop();
|
|
@@ -17900,5 +18113,9 @@ export {
|
|
|
17900
18113
|
DESKTOP_PERMISSION_GUIDANCE,
|
|
17901
18114
|
requestDesktopPermission,
|
|
17902
18115
|
checkDesktop,
|
|
18116
|
+
isVersionManagedPath,
|
|
18117
|
+
resolveNodePath,
|
|
18118
|
+
toolSearchDirs,
|
|
18119
|
+
resolveCommandOnPath,
|
|
17903
18120
|
runDaemon
|
|
17904
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
|
-
|
|
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
|
|
39
|
-
import { isAbsolute as isAbsolute2, join
|
|
40
|
-
import { execFile as
|
|
41
|
-
import { promisify as
|
|
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
|
|
106
|
-
import { promisify
|
|
54
|
+
import { execFile } from "node:child_process";
|
|
55
|
+
import { promisify } from "node:util";
|
|
107
56
|
import { userInfo } from "node:os";
|
|
108
|
-
var
|
|
57
|
+
var run = promisify(execFile);
|
|
109
58
|
var guiDomain = () => `gui/${userInfo().uid}`;
|
|
110
59
|
function xml(value) {
|
|
111
60
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -138,22 +87,22 @@ async function ignoreMissing(action) {
|
|
|
138
87
|
}
|
|
139
88
|
}
|
|
140
89
|
async function launchdLoad() {
|
|
141
|
-
await ignoreMissing(() =>
|
|
142
|
-
await
|
|
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(() =>
|
|
146
|
-
await
|
|
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(() =>
|
|
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
|
|
103
|
+
await run("launchctl", ["kickstart", "-k", target]);
|
|
155
104
|
} else {
|
|
156
|
-
await
|
|
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
|
|
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
|
|
178
|
-
import { promisify as
|
|
179
|
-
var
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
459
|
-
import { execFile as
|
|
460
|
-
import { platform
|
|
461
|
-
import { isAbsolute as isAbsolute3, join as
|
|
462
|
-
import { promisify as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 (
|
|
651
|
-
console.log(`\u622A\u5C4F\u8F85\u52A9\u7A0B\u5E8F\u8DEF\u5F84\uFF1A${
|
|
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
|
|
726
|
-
import { readFile as readFile3, realpath
|
|
727
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
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)}`);
|
|
@@ -812,9 +761,9 @@ async function runUpdate() {
|
|
|
812
761
|
return;
|
|
813
762
|
}
|
|
814
763
|
const prefix = await npmOutput(["prefix", "--global"]);
|
|
815
|
-
const target =
|
|
816
|
-
const installedDirectory = await
|
|
817
|
-
if (installedDirectory !== await
|
|
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();
|
|
@@ -826,7 +775,7 @@ async function runUpdate() {
|
|
|
826
775
|
}
|
|
827
776
|
console.log(`\u6B63\u5728\u5B89\u88C5 ${PACKAGE}@${latest}\u2026`);
|
|
828
777
|
await installVersion(latest, prefix);
|
|
829
|
-
const installed = JSON.parse(await readFile3(
|
|
778
|
+
const installed = JSON.parse(await readFile3(join3(target, "package.json"), "utf8"));
|
|
830
779
|
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
780
|
} catch (error) {
|
|
832
781
|
let recovery = "";
|
package/dist/main.js
CHANGED
package/native/desktop-macos.m
CHANGED
|
@@ -29,6 +29,39 @@ static NSDictionary *displayInfo(CGDirectDisplayID displayId) {
|
|
|
29
29
|
@"width": @(CGDisplayPixelsWide(displayId)), @"height": @(CGDisplayPixelsHigh(displayId))};
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
static BOOL saveImage(CGImageRef image, NSString *path) {
|
|
33
|
+
if (!image) return NO;
|
|
34
|
+
CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:path], CFSTR("public.png"), 1, NULL);
|
|
35
|
+
if (!destination) return NO;
|
|
36
|
+
CGImageDestinationAddImage(destination, image, NULL);
|
|
37
|
+
BOOL saved = CGImageDestinationFinalize(destination);
|
|
38
|
+
CFRelease(destination);
|
|
39
|
+
return saved;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static void finishImage(BOOL saved) {
|
|
43
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
44
|
+
if (finished) return;
|
|
45
|
+
if (saved) { resultCode = 0; finished = YES; }
|
|
46
|
+
else fail(@"无法保存桌面 PNG 截图,请检查 Proxy 状态目录。" );
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
|
|
51
|
+
static void captureImage(SCContentFilter *filter, SCStreamConfiguration *configuration, NSString *path) API_AVAILABLE(macos(14.0));
|
|
52
|
+
static void captureImage(SCContentFilter *filter, SCStreamConfiguration *configuration, NSString *path) {
|
|
53
|
+
// A stream may start successfully without delivering frames while the screen
|
|
54
|
+
// is locked. The screenshot API completes a single capture without that wait.
|
|
55
|
+
[SCScreenshotManager captureImageWithFilter:filter configuration:configuration completionHandler:^(CGImageRef image, NSError *error) {
|
|
56
|
+
if (error || !image) {
|
|
57
|
+
dispatch_async(dispatch_get_main_queue(), ^{ if (!finished) fail(permissionMessage()); });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
finishImage(saveImage(image, path));
|
|
61
|
+
}];
|
|
62
|
+
}
|
|
63
|
+
#endif
|
|
64
|
+
|
|
32
65
|
@interface DesktopFrame : NSObject <SCStreamOutput, SCStreamDelegate>
|
|
33
66
|
@property(nonatomic, strong) SCStream *stream;
|
|
34
67
|
@property(nonatomic, copy) NSString *path;
|
|
@@ -49,23 +82,11 @@ static NSDictionary *displayInfo(CGDirectDisplayID displayId) {
|
|
|
49
82
|
self.received = YES;
|
|
50
83
|
CGImageRef image = NULL;
|
|
51
84
|
OSStatus conversion = VTCreateCGImageFromCVPixelBuffer(pixel, NULL, &image);
|
|
52
|
-
BOOL saved =
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
saved = CGImageDestinationFinalize(destination);
|
|
58
|
-
CFRelease(destination);
|
|
59
|
-
}
|
|
60
|
-
CGImageRelease(image);
|
|
61
|
-
}
|
|
62
|
-
[stream stopCaptureWithCompletionHandler:^(NSError *error) {
|
|
63
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
64
|
-
if (finished) return;
|
|
65
|
-
if (saved) { resultCode = 0; finished = YES; }
|
|
66
|
-
else fail(@"无法保存桌面 PNG 截图,请检查 Proxy 状态目录。" );
|
|
67
|
-
});
|
|
68
|
-
}];
|
|
85
|
+
BOOL saved = conversion == noErr && saveImage(image, self.path);
|
|
86
|
+
if (image) CGImageRelease(image);
|
|
87
|
+
// Stopping a stream is cleanup; a saved PNG must not depend on its callback.
|
|
88
|
+
[stream stopCaptureWithCompletionHandler:nil];
|
|
89
|
+
finishImage(saved);
|
|
69
90
|
}
|
|
70
91
|
@end
|
|
71
92
|
|
|
@@ -121,6 +142,12 @@ int main(int argc, const char *argv[]) {
|
|
|
121
142
|
configuration.showsCursor = YES;
|
|
122
143
|
configuration.queueDepth = 3;
|
|
123
144
|
configuration.minimumFrameInterval = CMTimeMake(1, 30);
|
|
145
|
+
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
|
|
146
|
+
if (@available(macOS 14.0, *)) {
|
|
147
|
+
captureImage(filter, configuration, frame.path);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
#endif
|
|
124
151
|
frame.stream = [[SCStream alloc] initWithFilter:filter configuration:configuration delegate:frame];
|
|
125
152
|
NSError *outputError = nil;
|
|
126
153
|
if (![frame.stream addStreamOutput:frame type:SCStreamOutputTypeScreen sampleHandlerQueue:dispatch_queue_create("octopus.desktop.frame", DISPATCH_QUEUE_SERIAL) error:&outputError]) {
|
|
@@ -63,25 +63,22 @@ static BOOL key(CGKeyCode code, CGEventFlags flags, const UniChar *text, size_t
|
|
|
63
63
|
if (!down || !up) { if (down) CFRelease(down); if (up) CFRelease(up); return NO; }
|
|
64
64
|
CGEventSetFlags(down, flags); CGEventSetFlags(up, flags);
|
|
65
65
|
if (text) { CGEventKeyboardSetUnicodeString(down, length, text); CGEventKeyboardSetUnicodeString(up, length, text); }
|
|
66
|
-
CGEventPost(kCGHIDEventTap, down);
|
|
66
|
+
CGEventPost(kCGHIDEventTap, down);
|
|
67
|
+
usleep(50000);
|
|
68
|
+
CGEventPost(kCGHIDEventTap, up);
|
|
67
69
|
CFRelease(down); CFRelease(up);
|
|
68
|
-
usleep(
|
|
70
|
+
usleep(50000);
|
|
69
71
|
return YES;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// depend on loginwindow exposing an Accessibility tree (some versions do not).
|
|
74
|
+
// Space reveals and focuses the locked console's password prompt. Allow the
|
|
75
|
+
// transition to finish before typing; no Accessibility discovery is required.
|
|
75
76
|
static BOOL wakePasswordPrompt(void) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (!locked) return YES;
|
|
82
|
-
if (!key(keys[i], 0, NULL, 0)) return NO;
|
|
83
|
-
usleep(settle[i]);
|
|
84
|
-
}
|
|
77
|
+
BOOL locked = NO;
|
|
78
|
+
if (!lockState(&locked)) return NO;
|
|
79
|
+
if (!locked) return YES;
|
|
80
|
+
if (!key(49, 0, NULL, 0)) return NO;
|
|
81
|
+
usleep(1000000);
|
|
85
82
|
return YES;
|
|
86
83
|
}
|
|
87
84
|
|
|
@@ -92,7 +89,7 @@ static BOOL lockedKey(CGKeyCode code, CGEventFlags flags, const UniChar *text, s
|
|
|
92
89
|
|
|
93
90
|
static void printDiagnostics(BOOL locked) {
|
|
94
91
|
// Read-only; no wake, focus, field contents, screenshots, or input events.
|
|
95
|
-
printf("{\"locked\":%s,\"canUnlock\":%s,\"loginwindowFound\":%s,\"inputMethod\":\"space-
|
|
92
|
+
printf("{\"locked\":%s,\"canUnlock\":%s,\"loginwindowFound\":%s,\"inputMethod\":\"space-password\"}",
|
|
96
93
|
locked ? "true" : "false", AXIsProcessTrusted() && CGPreflightPostEventAccess() ? "true" : "false",
|
|
97
94
|
loginWindowPID() ? "true" : "false");
|
|
98
95
|
}
|
|
@@ -118,8 +115,6 @@ int main(int argc, const char *argv[]) {
|
|
|
118
115
|
if (!lockState(&locked)) return fail("session");
|
|
119
116
|
// Saver dismissal, Touch ID or a manual unlock may finish during wake.
|
|
120
117
|
if (!locked) { printStatus(NO); return 0; }
|
|
121
|
-
if (!lockedKey(0, kCGEventFlagMaskCommand, NULL, 0) ||
|
|
122
|
-
!lockedKey(51, 0, NULL, 0)) return fail("changed");
|
|
123
118
|
UniChar characters[1024] = {0};
|
|
124
119
|
NSUInteger length = password.length;
|
|
125
120
|
if (length > 1024) return fail("password");
|