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