oh-my-agent-web 1.0.5 → 1.0.6
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.en.md +2 -0
- package/README.md +2 -0
- package/dist/server/cli.js +8 -0
- package/dist/server/util/prune-pty.js +72 -0
- package/package.json +2 -4
- package/dist/server/files/pick-dir-routes.js +0 -93
- package/scripts/prune-prebuilds.mjs +0 -42
package/README.en.md
CHANGED
|
@@ -43,6 +43,8 @@ npm -g uninstall oh-my-agent-web # uninstall; data in ~/.oh-my-agent-web is
|
|
|
43
43
|
|
|
44
44
|
On Linux, `node-pty` (the embedded terminal) ships prebuilds only for macOS / Windows, so Linux compiles it in place: you need `make`, `g++` and `python3`. When those are missing, both the page and the install script say exactly what's missing — the terminal won't open until then.
|
|
45
45
|
|
|
46
|
+
> **`allow-scripts` notices from npm 11.16+**: newer npm lists every dependency install script you haven't approved yet (`npm warn allow-scripts ...`). This package **ships no install scripts of its own** (the old one only pruned `node-pty` prebuilds for other platforms; that now happens lazily on startup), so anything listed is a transitive dependency such as `node-pty` (native build), `protobufjs`, or `@google/genai`. It is only a notice — the install still completes. To silence it after a review: `npm approve-scripts node-pty protobufjs @google/genai` (or `npm approve-scripts --all`).
|
|
47
|
+
|
|
46
48
|
### One-line install (never touches global npm)
|
|
47
49
|
|
|
48
50
|
macOS / Linux:
|
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ npm -g uninstall oh-my-agent-web # 卸载;~/.oh-my-agent-web 里的数据
|
|
|
42
42
|
|
|
43
43
|
Linux 上 `node-pty`(内嵌终端)只给 macOS / Windows 带预编译产物,Linux 要就地编译:需要 `make` / `g++` / `python3`。缺工具时页面和安装脚本都会直说缺什么,装完终端才开得起来。
|
|
44
44
|
|
|
45
|
+
> **npm 11.16+ 的 `allow-scripts` 提示**:新版 npm 会把「未经你批准的 install 脚本」逐条列出来(`npm warn allow-scripts ...`)。本包**自己没有 install 脚本**(以前那个只用来裁 node-pty 无关平台的预编译产物,现在改到启动时顺手做),所以列表里出现的只会是传递依赖,例如 `node-pty`(要编原生模块)、`protobufjs`、`@google/genai`。这只是一条**提示**,安装照常完成;想让它闭嘴就审一下并批准:`npm approve-scripts node-pty protobufjs @google/genai`(或 `npm approve-scripts --all`)。
|
|
46
|
+
|
|
45
47
|
### 一行脚本(不想碰系统全局 npm)
|
|
46
48
|
|
|
47
49
|
macOS / Linux:
|
package/dist/server/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ import { fileURLToPath } from "node:url";
|
|
|
35
35
|
import { applyStoredOptions } from "./cli-config.js";
|
|
36
36
|
import { migrateLegacyDataDir } from "./util/data-dir.js";
|
|
37
37
|
import { DEFAULT_DATA_DIR_NAME } from "./util/env.js";
|
|
38
|
+
import { prunePtyPrebuilds } from "./util/prune-pty.js";
|
|
38
39
|
import { fetchHealth, showStatus, showVersion, upgrade } from "./cli-info.js";
|
|
39
40
|
import { checkForUpdate, describeUpdate } from "./update-check.js";
|
|
40
41
|
import { defaultIsAlive, describeHolder, readLock } from "./session/lock.js";
|
|
@@ -460,6 +461,13 @@ async function main() {
|
|
|
460
461
|
// 等端口真正释放再起,否则会撞 EADDRINUSE
|
|
461
462
|
await sleep(400);
|
|
462
463
|
}
|
|
464
|
+
// start / restart:顺手裁掉 node-pty 里别的平台的预编译产物(幂等,裁干净之后就是空转)。
|
|
465
|
+
// 这事以前放在 postinstall,但 npm 11.16+ 会把它当成「未批准的 install 脚本」逐个警告,
|
|
466
|
+
// 用户得先 approve 才轮到我们省体积 —— 挪到运行时顺便做,装包时就没有任何脚本要审了。
|
|
467
|
+
const pruned = prunePtyPrebuilds();
|
|
468
|
+
if (pruned?.removed) {
|
|
469
|
+
console.log(`[oh-my-agent-web] node-pty 已裁掉 ${pruned.removed} 个无关平台的预编译产物(保留 ${pruned.keep})`);
|
|
470
|
+
}
|
|
463
471
|
// start / restart:先确认不是「已经在跑了」(三个命令名共用这一处检测)
|
|
464
472
|
if (await detectRunning(parsed.open))
|
|
465
473
|
return;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 瘦身:只保留**当前平台**的 node-pty 预编译产物。
|
|
3
|
+
*
|
|
4
|
+
* 为什么值得做:node-pty 包里为四个平台各带了一份预编译二进制,其中 Windows 那两份
|
|
5
|
+
* 就要 58MB —— 在 macOS / Linux 上它们永远用不到,却会躺在每一次生产安装里
|
|
6
|
+
* (生产依赖 216MB 里它一个人占 62MB)。
|
|
7
|
+
*
|
|
8
|
+
* 为什么**不**再放在 `postinstall`(ADR 变更点):npm 11.16 起把「没被 `allowScripts`
|
|
9
|
+
* 批准过的 install 脚本」逐条打出来,并提示用户 `npm approve-scripts <pkg>`:
|
|
10
|
+
* 一条本来只是省体积的可选优化,变成了每个用户都要面对的一次安全审查 —— 大多数人
|
|
11
|
+
* 不会去 approve,于是体积没省下来、还多一段红色告警。改成**第一次运行时顺手做**:
|
|
12
|
+
* 幂等(裁完就只剩一个目录,再跑是空转)、几十微秒、失败也不影响启动。
|
|
13
|
+
*
|
|
14
|
+
* 安全性:node-pty 运行时只按 `<platform>-<arch>` 目录找自己的预编译产物
|
|
15
|
+
* (见 node-pty/lib/prebuild-loader),删掉别人的不影响本机;`npm ci` 会原样装回来。
|
|
16
|
+
* 没有预编译产物的平台(例如 Linux)本来就要现场编译,这里删光也不会影响编译。
|
|
17
|
+
*/
|
|
18
|
+
import { chmodSync, existsSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
19
|
+
import { createRequire } from "node:module";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
21
|
+
/** node-pty 的 prebuilds 目录;没装(optionalDependencies,装不上很常见)时返回 null */
|
|
22
|
+
function defaultPrebuildsDir() {
|
|
23
|
+
try {
|
|
24
|
+
const pkg = createRequire(import.meta.url).resolve("node-pty/package.json");
|
|
25
|
+
return join(dirname(pkg), "prebuilds");
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 删掉非本平台的预编译产物,并给本平台的 `spawn-helper` 补可执行位
|
|
33
|
+
* (npm 打 tarball 时可能丢掉可执行位)。
|
|
34
|
+
*
|
|
35
|
+
* @param prebuildsDir 仅供单测注入;默认从 node-pty 包位置推出来
|
|
36
|
+
* @returns 没东西可做(node-pty 没装 / 已经裁干净)时返回 null
|
|
37
|
+
*/
|
|
38
|
+
export function prunePtyPrebuilds(prebuildsDir) {
|
|
39
|
+
const dir = prebuildsDir ?? defaultPrebuildsDir();
|
|
40
|
+
if (!dir || !existsSync(dir))
|
|
41
|
+
return null;
|
|
42
|
+
const keep = `${process.platform}-${process.arch}`;
|
|
43
|
+
let removed = 0;
|
|
44
|
+
for (const entry of readdirSync(dir)) {
|
|
45
|
+
if (entry === keep)
|
|
46
|
+
continue;
|
|
47
|
+
try {
|
|
48
|
+
rmSync(join(dir, entry), { recursive: true, force: true });
|
|
49
|
+
removed += 1;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// 只读文件系统 / 权限不足(例如全局装到 /usr/lib 下):留着就是,不该影响启动
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// spawn-helper 必须有可执行位,否则 posix_spawn 起来就是 EACCES
|
|
56
|
+
let fixedMode = false;
|
|
57
|
+
const helper = join(dir, keep, "spawn-helper");
|
|
58
|
+
try {
|
|
59
|
+
const stats = statSync(helper);
|
|
60
|
+
// 只在真的缺可执行位时才动手:否则每次启动都会「修」一遍,白白多一条日志
|
|
61
|
+
if (stats.isFile() && (stats.mode & 0o777) !== 0o755) {
|
|
62
|
+
chmodSync(helper, 0o755);
|
|
63
|
+
fixedMode = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// 没有 spawn-helper(Windows)/ 没有权限:都不该影响启动
|
|
68
|
+
}
|
|
69
|
+
if (removed === 0 && !fixedMode)
|
|
70
|
+
return null;
|
|
71
|
+
return { keep, removed, fixedMode };
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-agent-web",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"bin": {
|
|
5
5
|
"oh-my-agent-web": "dist/server/cli.js",
|
|
6
6
|
"aw": "dist/server/cli.js",
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
-
"web/dist"
|
|
12
|
-
"scripts/prune-prebuilds.mjs"
|
|
11
|
+
"web/dist"
|
|
13
12
|
],
|
|
14
13
|
"description": "oh-my-agent-web — 它是一个多 Agent 统一 Web 工作台,对 Pi Agent 做深度适配,同时也支持 Codex、Claude 等其他 Agent。",
|
|
15
14
|
"author": "duzhenxun",
|
|
@@ -53,7 +52,6 @@
|
|
|
53
52
|
"lint:fix": "oxlint --fix server web/src scripts",
|
|
54
53
|
"format": "prettier --write .",
|
|
55
54
|
"format:check": "prettier --check .",
|
|
56
|
-
"postinstall": "node scripts/prune-prebuilds.mjs",
|
|
57
55
|
"icons": "node scripts/make-icons.mjs"
|
|
58
56
|
},
|
|
59
57
|
"devDependencies": {
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 「浏览电脑里的文件夹」:在**服务端所在的这台电脑**上弹出操作系统的原生选择框。
|
|
3
|
-
*
|
|
4
|
-
* 为什么要在服务端弹:浏览器拿不到本机绝对路径。`showDirectoryPicker()` 只回一个沙箱句柄
|
|
5
|
-
* (`.name` 是目录名,不是路径),拿它当工作区是跑不起来的;用户在网页里手打路径又太折磨。
|
|
6
|
-
* 这个工作台本来就是「单用户 + 跑在自己机器上」,所以最直接的做法就是在宿主机上开一个
|
|
7
|
-
* 原生对话框,把用户选中的绝对路径回传。
|
|
8
|
-
*
|
|
9
|
-
* 只认三种平台的原生方式,拿不到就**如实告诉前端 unsupported**(前端会退回内置目录浏览),
|
|
10
|
-
* 绝不假装成功:
|
|
11
|
-
* - macOS:`osascript` 的 `choose folder`
|
|
12
|
-
* - Windows:PowerShell 的 `FolderBrowserDialog`
|
|
13
|
-
* - Linux:`zenity`(GNOME)或 `kdialog`(KDE)
|
|
14
|
-
*
|
|
15
|
-
* 注意:服务端如果跑在没有图形会话的地方(ssh / launchd 守护进程 / 容器),这里会失败 ——
|
|
16
|
-
* 那时前端会自动降级,功能不会因此不可用。
|
|
17
|
-
*/
|
|
18
|
-
import { execFile } from "node:child_process";
|
|
19
|
-
import { promisify } from "node:util";
|
|
20
|
-
import { normalizeWorkspacePath } from "../util/paths.js";
|
|
21
|
-
const exec = promisify(execFile);
|
|
22
|
-
/** 用户可能翻很久,给足时间;超时按「取消」处理(不把请求挂死) */
|
|
23
|
-
const PICK_TIMEOUT_MS = 180_000;
|
|
24
|
-
/** 同一条命令可能是「用户取消」,也可能是真的出错 —— 分开,别把失败说成取消 */
|
|
25
|
-
function isCancel(stderr, code) {
|
|
26
|
-
const text = stderr.toLowerCase();
|
|
27
|
-
if (text.includes("user cancel") || text.includes("-128"))
|
|
28
|
-
return true;
|
|
29
|
-
// zenity / kdialog 取消时就是退出码 1(且没有别的输出)
|
|
30
|
-
return code === 1 && text.trim() === "";
|
|
31
|
-
}
|
|
32
|
-
const APPLE_SCRIPT = [
|
|
33
|
-
'tell application "System Events" to activate',
|
|
34
|
-
'set chosen to choose folder with prompt "选择项目文件夹"',
|
|
35
|
-
"return POSIX path of chosen",
|
|
36
|
-
].join("\n");
|
|
37
|
-
const POWERSHELL_SCRIPT = [
|
|
38
|
-
"Add-Type -AssemblyName System.Windows.Forms",
|
|
39
|
-
"$dialog = New-Object System.Windows.Forms.FolderBrowserDialog",
|
|
40
|
-
'$dialog.Description = "选择项目文件夹"',
|
|
41
|
-
"if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { Write-Output $dialog.SelectedPath } else { exit 1 }",
|
|
42
|
-
].join("; ");
|
|
43
|
-
/** 按平台挑一条能用的命令;一条都没有就返回 null(→ unsupported) */
|
|
44
|
-
function pickerCommand() {
|
|
45
|
-
if (process.platform === "darwin")
|
|
46
|
-
return { file: "osascript", args: ["-e", APPLE_SCRIPT] };
|
|
47
|
-
if (process.platform === "win32") {
|
|
48
|
-
return { file: "powershell.exe", args: ["-NoProfile", "-STA", "-Command", POWERSHELL_SCRIPT] };
|
|
49
|
-
}
|
|
50
|
-
// Linux / 其它:两个都试(没装 exiftool 也一样,command 不存在会直接失败)
|
|
51
|
-
return { file: "zenity", args: ["--file-selection", "--directory", "--title=选择项目文件夹"] };
|
|
52
|
-
}
|
|
53
|
-
let picking = false;
|
|
54
|
-
async function pickDirectory() {
|
|
55
|
-
const command = pickerCommand();
|
|
56
|
-
if (!command)
|
|
57
|
-
return { unsupported: true, message: `这个平台(${process.platform})没有可用的原生目录选择框` };
|
|
58
|
-
if (picking)
|
|
59
|
-
return { error: "已经有一个目录选择框开着了" };
|
|
60
|
-
picking = true;
|
|
61
|
-
try {
|
|
62
|
-
const { stdout } = await exec(command.file, command.args, { timeout: PICK_TIMEOUT_MS });
|
|
63
|
-
// macOS 的 `POSIX path of chosen` 回的是 `/a/b/`(带尾斜杠)。原样传下去会让会话的
|
|
64
|
-
// workspace 与 pi 写进会话 header 的 cwd(`resolve()` 过)对不上,侧栏里同一个项目
|
|
65
|
-
// 就变成两个组(用户报的「新建项目出来是两个」)。在出口就归一化。
|
|
66
|
-
const path = normalizeWorkspacePath(stdout.trim());
|
|
67
|
-
if (path === "")
|
|
68
|
-
return { canceled: true };
|
|
69
|
-
return { path };
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
const record = error;
|
|
73
|
-
if (isCancel(record.stderr ?? "", typeof record.code === "number" ? record.code : undefined)) {
|
|
74
|
-
return { canceled: true };
|
|
75
|
-
}
|
|
76
|
-
// 命令不存在 / 没有图形会话:如实说「这里开不出来」,前端会退回内置浏览
|
|
77
|
-
const reason = record.stderr?.trim() || record.message || "未知原因";
|
|
78
|
-
return { unsupported: true, message: `这台机器上开不出目录选择框:${reason}` };
|
|
79
|
-
}
|
|
80
|
-
finally {
|
|
81
|
-
picking = false;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
export function registerPickDirRoutes(app) {
|
|
85
|
-
app.post("/api/fs/pick-dir", async (_request, response) => {
|
|
86
|
-
const result = await pickDirectory();
|
|
87
|
-
if ("error" in result) {
|
|
88
|
-
response.status(409).json({ error: { code: "fs.pick_busy", message: result.error } });
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
response.json(result);
|
|
92
|
-
});
|
|
93
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 安装后瘦身:只保留**当前平台**的 node-pty 预编译产物。
|
|
3
|
-
*
|
|
4
|
-
* 为什么值得做:node-pty 包里为四个平台各带了一份预编译二进制,其中 Windows 那两份
|
|
5
|
-
* 就要 58MB —— 在 macOS / Linux 上它们永远用不到,却会躺在每一次生产安装里
|
|
6
|
-
* (生产依赖 216MB 里它一个人占 62MB)。
|
|
7
|
-
*
|
|
8
|
-
* 安全性:node-pty 运行时只按 `<platform>-<arch>` 目录找自己的预编译产物
|
|
9
|
-
* (见 node-pty/lib/...),删掉别人的不影响本机;`npm ci` 会原样装回来。
|
|
10
|
-
* 没有预编译产物的平台(例如 Linux)本来就要现场编译,这里删光也不会影响编译。
|
|
11
|
-
*/
|
|
12
|
-
import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
13
|
-
import { chmodSync } from "node:fs";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
|
|
16
|
-
const PREBUILDS = join("node_modules", "node-pty", "prebuilds");
|
|
17
|
-
const keep = `${process.platform}-${process.arch}`;
|
|
18
|
-
|
|
19
|
-
if (existsSync(PREBUILDS)) {
|
|
20
|
-
let removed = 0;
|
|
21
|
-
for (const entry of readdirSync(PREBUILDS)) {
|
|
22
|
-
if (entry === keep) continue;
|
|
23
|
-
try {
|
|
24
|
-
rmSync(join(PREBUILDS, entry), { recursive: true, force: true });
|
|
25
|
-
removed += 1;
|
|
26
|
-
} catch {
|
|
27
|
-
// 只读文件系统 / 权限不足:留着就是,不该让 npm install 失败
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// spawn-helper 必须有可执行位(npm 打 tarball 时可能丢掉)
|
|
32
|
-
const current = join(PREBUILDS, keep, "spawn-helper");
|
|
33
|
-
if (existsSync(current) && statSync(current).isFile()) {
|
|
34
|
-
try {
|
|
35
|
-
chmodSync(current, 0o755);
|
|
36
|
-
} catch {
|
|
37
|
-
// Windows 上没有可执行位的概念,忽略
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (removed > 0) console.log(`[oh-my-agent-web] node-pty 已裁掉 ${removed} 个无关平台的预编译产物(保留 ${keep})`);
|
|
42
|
-
}
|