pi-web-ui 0.59.0 → 0.59.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -161,13 +161,36 @@ npm i -g --allow-scripts=node-pty,@google/genai,protobufjs pi-web-ui@latest
161
161
 
162
162
  ## Quick start
163
163
 
164
- **Start**
164
+ **Start (foreground)**
165
165
 
166
166
  ```bash
167
167
  pi-web-ui # foreground, http://localhost:8787
168
- PI_WEB_PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui # custom port / workspace
169
168
  ```
170
169
 
170
+ **Start flags & environment variables** — every setting can be passed as a `--flag` on the command
171
+ line **or** set as an environment variable (flag wins). Pick whichever you prefer:
172
+
173
+ | Flag | Env var | Default | Purpose |
174
+ | --- | --- | --- | --- |
175
+ | `--port <n>` | `PI_WEB_PORT` | `8787` | HTTP port |
176
+ | `--cwd <dir>` | `PI_WEB_CWD` | current dir | workspace root (read/write/terminal) |
177
+ | `--data-dir <dir>` | `PI_WEB_DATA_DIR` | `~/.pi-web` | data dir (sessions, plugins, uploads) |
178
+ | `--engine <pi\|dsh>` | `PI_WEB_ENGINE` | `pi` | agent engine; `--engine dsh` = DeepSeek Harness |
179
+ | `--host <addr>` | `PI_WEB_HOST` | `127.0.0.1` | listen address (`0.0.0.0` for LAN/Docker) |
180
+ | `--agent-dir <dir>` | `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi config dir (auth.json, models, skills) |
181
+ | _env only_ | `PI_WEB_TOKEN` | empty | optional shared auth token |
182
+ | _env only_ | `PI_WEB_DSH_*` | — | dsh runtime, patches & debug settings |
183
+
184
+ The two are equivalent — pick one:
185
+
186
+ ```bash
187
+ pi-web-ui --engine dsh --port 9000 --cwd /path/to/project
188
+ PI_WEB_ENGINE=dsh PI_WEB_PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui
189
+ ```
190
+
191
+ For the DSH engine also install the runtime (`npm i -g @deepseek-ai/dsh@0.1.1-rc.2`) and set a
192
+ DeepSeek API key (read from `~/.pi/agent/auth.json`, set in the provider/API-key panel).
193
+
171
194
  **Stop**
172
195
 
173
196
  - **Foreground**: press `Ctrl+C` in the terminal running it.
@@ -213,9 +236,15 @@ socket drives `quiesce`/`unquiesce`.
213
236
  - **Windows** → Task Scheduler logon task (hidden PowerShell window, no black console)
214
237
 
215
238
  Options: `--port` (default 8787), `--cwd` (workspace), `--data-dir` (sessions),
216
- `--name` (custom service name). Rerunning `server install` with new options
217
- regenerates the config and restarts the service — that's how you change its
218
- port/cwd.
239
+ `--engine <pi|dsh>`, `--host`, `--agent-dir`, `--name` (custom service name). Rerunning
240
+ `server install` with new options regenerates the config and restarts the service — that's how
241
+ you change its port/cwd/engine. `--engine` / `--host` / `--agent-dir` are baked into the service
242
+ automatically; env-only vars (`PI_WEB_TOKEN`, `PI_WEB_DSH_*`) must be added to the service config
243
+ by hand. See the [start flags table](#quick-start) above.
244
+
245
+ ```bash
246
+ pi-web-ui server install --engine dsh --port 9000 --cwd /path/to/project
247
+ ```
219
248
 
220
249
 
221
250
  ## Plugins
package/README.zh-CN.md CHANGED
@@ -119,11 +119,36 @@ npm i -g --allow-scripts=node-pty,@google/genai,protobufjs pi-web-ui@latest
119
119
 
120
120
  ## 启动
121
121
 
122
+ **前台启动**
123
+
122
124
  ```bash
123
125
  pi-web-ui # 前台,http://localhost:8787
124
- PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui # 自定义端口 / 工作目录
125
126
  ```
126
127
 
128
+ **启动参数 & 环境变量** —— 每个设置既能用命令行的 `--flag` 传,也能用环境变量设(flag 优先)。
129
+ 二者任选一种即可:
130
+
131
+ | 参数 | 环境变量 | 默认 | 作用 |
132
+ | --- | --- | --- | --- |
133
+ | `--port <n>` | `PI_WEB_PORT` | `8787` | HTTP 端口 |
134
+ | `--cwd <dir>` | `PI_WEB_CWD` | 当前目录 | 工作区根(读/写/终端) |
135
+ | `--data-dir <dir>` | `PI_WEB_DATA_DIR` | `~/.pi-web` | 数据目录(会话/插件/上传) |
136
+ | `--engine <pi\|dsh>` | `PI_WEB_ENGINE` | `pi` | 智能体引擎;`--engine dsh` = DeepSeek Harness |
137
+ | `--host <addr>` | `PI_WEB_HOST` | `127.0.0.1` | 监听地址(`0.0.0.0` 供局域网/Docker) |
138
+ | `--agent-dir <dir>` | `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi 配置目录(auth.json、模型、技能) |
139
+ | _仅环境变量_ | `PI_WEB_TOKEN` | 空 | 可选共享鉴权口令 |
140
+ | _仅环境变量_ | `PI_WEB_DSH_*` | — | dsh 运行时、补丁与调试设置 |
141
+
142
+ 两者等价 —— 任选其一:
143
+
144
+ ```bash
145
+ pi-web-ui --engine dsh --port 9000 --cwd /path/to/project
146
+ PI_WEB_ENGINE=dsh PI_WEB_PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui
147
+ ```
148
+
149
+ 若用 dsh 引擎,需先安装运行时(`npm i -g @deepseek-ai/dsh@0.1.1-rc.2`)并准备 DeepSeek API key
150
+ (读 `~/.pi/agent/auth.json`,在服务商/API key 面板设置)。
151
+
127
152
  ## 停止
128
153
 
129
154
  - **前台**:在运行它的终端里按 `Ctrl+C`。
@@ -167,8 +192,14 @@ pi-web-ui server unquiesce # 解除排空,恢复接收新工
167
192
  - **Windows** → 计划任务(登录自启,隐藏 PowerShell 窗口,无黑窗)
168
193
 
169
194
  选项:`--port`(默认 8787)、`--cwd`(工作目录)、`--data-dir`(会话目录)、
170
- `--name`(自定义服务名)。重复执行 `server install` 并传入新选项即可重新生成配置
171
- 并重启服务 —— 这就是修改已装服务端口/工作目录的方式。
195
+ `--engine <pi|dsh>`、`--host`、`--agent-dir`、`--name`(自定义服务名)。重复执行 `server install`
196
+ 并传入新选项即可重新生成配置并重启服务 —— 这就是修改已装服务端口/工作目录/引擎的方式。
197
+ `--engine` / `--host` / `--agent-dir` 会自动烘焙进服务;仅环境变量的(`PI_WEB_TOKEN`、
198
+ `PI_WEB_DSH_*`)需手动写进服务配置。见上方「启动参数 & 环境变量」表。
199
+
200
+ ```bash
201
+ pi-web-ui server install --engine dsh --port 9000 --cwd /path/to/project
202
+ ```
172
203
 
173
204
  ## 界面插件
174
205
 
package/bin/pi-web-ui.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  * pi-web-ui CLI.
4
4
  *
5
5
  * pi-web-ui 启动生产服务器(前台,Ctrl+C 停止,自动打开浏览器)
6
- * pi-web-ui --port 9000 --cwd /path 同上,覆盖端口 / 工作目录 / 数据目录
6
+ * pi-web-ui --engine dsh --port 9000 --cwd /path 同上,覆盖引擎/端口/工作目录/数据目录
7
7
  * pi-web-ui --no-browser 启动但不自动打开浏览器
8
8
  * pi-web-ui --version | --help
9
9
  * pi-web-ui server install [选项] 安装系统服务(开机自启)并启动
@@ -21,8 +21,8 @@
21
21
  * PowerShell 启动脚本 / VBS 启动器 / PID 文件生成在
22
22
  * %APPDATA%\pi-web-ui\
23
23
  *
24
- * 环境变量(前台与系统服务均适用):PI_WEB_PORT / PI_WEB_CWD / PI_WEB_DATA_DIR /
25
- * PI_CODING_AGENT_DIR
24
+ * 环境变量(flag 优先,环境变量后备):PI_WEB_PORT / PI_WEB_CWD / PI_WEB_DATA_DIR /
25
+ * PI_WEB_ENGINE / PI_WEB_HOST / PI_CODING_AGENT_DIR;token 仅环境变量,不走命令行。
26
26
  */
27
27
  import { spawn, spawnSync } from "node:child_process";
28
28
  import { createConnection } from "node:net";
@@ -66,7 +66,7 @@ const HELP = `pi-web-ui v${pkg.version} — web chat for the pi coding agent
66
66
 
67
67
  用法:
68
68
  pi-web-ui 启动服务器(前台,Ctrl+C 停止,自动打开浏览器)
69
- pi-web-ui --port 9000 --cwd /path 启动并指定端口 / 工作目录 / 数据目录
69
+ pi-web-ui --engine dsh --port 9000 --cwd /path 启动并指定引擎 / 端口 / 工作目录 / 数据目录
70
70
  pi-web-ui --no-browser 启动但不自动打开浏览器
71
71
  pi-web-ui server install [选项] 安装系统服务(开机自启)并启动
72
72
  pi-web-ui server shortcut [选项] 在桌面创建「一键启动」图标(启动服务并打开浏览器)
@@ -80,6 +80,9 @@ server 选项:
80
80
  --port <n> 端口(默认 8787,或 $PI_WEB_PORT)
81
81
  --cwd <dir> 工作目录(默认 $PI_WEB_CWD 或用户主目录;前台启动默认当前目录)
82
82
  --data-dir <dir> 会话数据目录(默认 <cwd>/.pi-web)
83
+ --engine <pi|dsh> 智能体引擎(默认 $PI_WEB_ENGINE 或 pi)
84
+ --host <addr> 监听地址(默认 $PI_WEB_HOST 或 127.0.0.1;0.0.0.0 供局域网/容器)
85
+ --agent-dir <dir> pi 配置目录(默认 $PI_CODING_AGENT_DIR 或 ~/.pi/agent)
83
86
  --name <name> 服务名(默认 pi-web-ui;macOS 的 launchd label
84
87
  为 com.xingshuyin.pi-web-ui,自定义名时为 com.<name>.server)
85
88
  --print 只打印将生成的配置文件,不实际安装
@@ -100,8 +103,10 @@ server 选项:
100
103
  --data-dir <dir> 数据目录(默认 ~/.pi-web)
101
104
  --force 目标已存在时覆盖
102
105
 
103
- 环境变量(前台与系统服务均适用):
104
- PI_WEB_PORT / PI_WEB_CWD / PI_WEB_DATA_DIR / PI_CODING_AGENT_DIR
106
+ 环境变量(flag 优先,环境变量后备):
107
+ PI_WEB_PORT / PI_WEB_CWD / PI_WEB_DATA_DIR / PI_WEB_ENGINE / PI_WEB_HOST /
108
+ PI_CODING_AGENT_DIR。
109
+ 鉴权口令 PI_WEB_TOKEN:仅环境变量(不走命令行,避免被 ps 看到),需要时手动加入服务配置。
105
110
  `;
106
111
 
107
112
  /** Minimum Node required by the pi SDK (its dist uses `import … with { type: "json" }`). */
@@ -143,6 +148,9 @@ function parseFlags(argv) {
143
148
  cwd: undefined,
144
149
  dataDir: undefined,
145
150
  name: undefined,
151
+ engine: undefined,
152
+ host: undefined,
153
+ agentDir: undefined,
146
154
  print: false,
147
155
  noBrowser: false,
148
156
  force: false,
@@ -174,6 +182,15 @@ function parseFlags(argv) {
174
182
  case "--data-dir":
175
183
  opts.dataDir = take("--data-dir");
176
184
  break;
185
+ case "--engine":
186
+ opts.engine = take("--engine");
187
+ break;
188
+ case "--host":
189
+ opts.host = take("--host");
190
+ break;
191
+ case "--agent-dir":
192
+ opts.agentDir = take("--agent-dir");
193
+ break;
177
194
  case "--name":
178
195
  opts.name = take("--name");
179
196
  break;
@@ -261,6 +278,12 @@ async function startForeground(opts) {
261
278
  if (opts.port) process.env.PI_WEB_PORT = opts.port;
262
279
  if (opts.cwd) process.env.PI_WEB_CWD = resolve(opts.cwd);
263
280
  if (opts.dataDir) process.env.PI_WEB_DATA_DIR = resolve(opts.dataDir);
281
+ if (opts.engine) {
282
+ if (opts.engine !== "pi" && opts.engine !== "dsh") fail(`无效引擎: ${opts.engine}(仅支持 pi / dsh)`);
283
+ process.env.PI_WEB_ENGINE = opts.engine;
284
+ }
285
+ if (opts.host) process.env.PI_WEB_HOST = opts.host;
286
+ if (opts.agentDir) process.env.PI_CODING_AGENT_DIR = resolve(opts.agentDir);
264
287
  const url = `http://localhost:${effectivePort(opts)}`;
265
288
  await import(pathToFileURL(SERVER_ENTRY).href);
266
289
  if (!opts.noBrowser) openBrowserWhenUp(url);
@@ -563,8 +586,8 @@ function buildWinHiddenVbs(ps1Path) {
563
586
  * -WindowStyle Hidden so nothing flashes on double-click.
564
587
  */
565
588
  function installWinShortcut(opts) {
566
- const { name, port, cwd, dataDir } = serviceOptions(opts);
567
- const env = serviceEnv(port, cwd, dataDir);
589
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
590
+ const env = serviceEnv(port, cwd, dataDir, engine, host, agentDir);
568
591
  const url = `http://localhost:${port}`;
569
592
  const ps1Path = winShortcutPs1Path(name);
570
593
  const ps1 = buildWinShortcutPs1(
@@ -668,13 +691,13 @@ if [ -n "\${SERVER_PID:-}" ]; then wait "$SERVER_PID"; fi
668
691
  }
669
692
 
670
693
  function installMacShortcut(opts) {
671
- const { name, port, cwd, dataDir } = serviceOptions(opts);
694
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
672
695
  const url = `http://localhost:${port}`;
673
696
  const script = buildMacShortcut(
674
697
  serviceLabel(name),
675
698
  launchAgentPlist(name),
676
699
  url,
677
- serviceEnv(port, cwd, dataDir),
700
+ serviceEnv(port, cwd, dataDir, engine, host, agentDir),
678
701
  );
679
702
  const path = join(homedir(), "Desktop", SHORTCUT_MAC_NAME);
680
703
  if (opts.print) {
@@ -726,7 +749,7 @@ if [ -n "\${SERVER_PID:-}" ]; then wait "$SERVER_PID"; fi
726
749
  }
727
750
 
728
751
  function installLinuxShortcut(opts) {
729
- const { name, port, cwd, dataDir } = serviceOptions(opts);
752
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
730
753
  const url = `http://localhost:${port}`;
731
754
  const scriptDir = join(homedir(), ".local", "share", "pi-web-ui");
732
755
  const scriptPath = join(scriptDir, `${name}-start.sh`);
@@ -978,10 +1001,17 @@ function serviceOptions(opts) {
978
1001
  } else if (process.env.PI_WEB_DATA_DIR) {
979
1002
  dataDir = resolve(process.env.PI_WEB_DATA_DIR);
980
1003
  }
981
- return { name, port, cwd, dataDir };
1004
+ // 引擎/监听地址/agent 配置目录:flag 优先,环境变量后备(token 不走命令行,仅环境变量)。
1005
+ const engine = opts.engine ?? process.env.PI_WEB_ENGINE ?? "pi";
1006
+ if (engine !== "pi" && engine !== "dsh") fail(`无效引擎: ${engine}(仅支持 pi / dsh)`);
1007
+ const host = opts.host ?? process.env.PI_WEB_HOST;
1008
+ const agentDir = opts.agentDir
1009
+ ? resolve(opts.agentDir)
1010
+ : process.env.PI_CODING_AGENT_DIR;
1011
+ return { name, port, cwd, dataDir, engine, host, agentDir };
982
1012
  }
983
1013
 
984
- function serviceEnv(port, cwd, dataDir) {
1014
+ function serviceEnv(port, cwd, dataDir, engine, host, agentDir) {
985
1015
  const env = {
986
1016
  PI_WEB_PORT: port,
987
1017
  PI_WEB_CWD: cwd,
@@ -996,14 +1026,17 @@ function serviceEnv(port, cwd, dataDir) {
996
1026
  if (!isWin && process.env.LANG) env.LANG = process.env.LANG;
997
1027
  if (!isWin && process.env.LC_ALL) env.LC_ALL = process.env.LC_ALL;
998
1028
  if (dataDir) env.PI_WEB_DATA_DIR = dataDir;
1029
+ if (engine === "dsh") env.PI_WEB_ENGINE = "dsh"; // 仅非默认引擎才烘焙,保持服务单元简洁
1030
+ if (host) env.PI_WEB_HOST = host;
1031
+ if (agentDir) env.PI_CODING_AGENT_DIR = agentDir;
999
1032
  return env;
1000
1033
  }
1001
1034
 
1002
1035
  function installLaunchd(opts) {
1003
- const { name, port, cwd, dataDir } = serviceOptions(opts);
1036
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
1004
1037
  const label = serviceLabel(name);
1005
1038
  const plist = launchAgentPlist(name);
1006
- const content = buildPlist(label, cwd, serviceEnv(port, cwd, dataDir));
1039
+ const content = buildPlist(label, cwd, serviceEnv(port, cwd, dataDir, engine, host, agentDir));
1007
1040
  if (opts.print) {
1008
1041
  console.log(`# ${plist}\n${content}`);
1009
1042
  return;
@@ -1026,8 +1059,8 @@ function installLaunchd(opts) {
1026
1059
  }
1027
1060
 
1028
1061
  function installSystemd(opts) {
1029
- const { name, port, cwd, dataDir } = serviceOptions(opts);
1030
- const content = buildUnit(cwd, serviceEnv(port, cwd, dataDir));
1062
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
1063
+ const content = buildUnit(cwd, serviceEnv(port, cwd, dataDir, engine, host, agentDir));
1031
1064
  const unitPath = systemdUnitPath(name);
1032
1065
  if (opts.print) {
1033
1066
  console.log(`# ${unitPath}\n${content}`);
@@ -1075,8 +1108,8 @@ function uninstallSystemd(opts) {
1075
1108
  }
1076
1109
 
1077
1110
  function installWindows(opts) {
1078
- const { name, port, cwd, dataDir } = serviceOptions(opts);
1079
- const env = serviceEnv(port, cwd, dataDir);
1111
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
1112
+ const env = serviceEnv(port, cwd, dataDir, engine, host, agentDir);
1080
1113
  const ps1Path = winPs1Path(name);
1081
1114
  const vbsPath = winVbsPath(name);
1082
1115
  const pidPath = winPidFilePath(name);
@@ -105,13 +105,16 @@ function tokenOk(req) {
105
105
  return requestTokens(req).includes(AUTH_TOKEN);
106
106
  }
107
107
  if (AUTH_TOKEN) {
108
- // /api/health 保持开放:无敏感信息,容器/监控探针需要它
108
+ // /api/health 保持开放:无敏感信息,容器/监控探针需要它。
109
+ // 但绝不能因命中 /api/health 就反射下发真实 token cookie(安全漏洞)。
109
110
  app.use((req, res, next) => {
110
- if (req.path === "/api/health" || tokenOk(req)) {
111
- // 浏览器经 ?token= 首次进入后下发 HttpOnly cookie,后续导航/资源请求免带参数
112
- if (!req.headers.cookie?.includes("pi_web_token=")) {
113
- res.setHeader("Set-Cookie", `pi_web_token=${encodeURIComponent(AUTH_TOKEN)}; Path=/; HttpOnly; SameSite=Strict; Max-Age=31536000`);
114
- }
111
+ const ok = tokenOk(req);
112
+ // 浏览器经 ?token= 首次进入后下发 HttpOnly cookie,后续导航/资源请求免带参数;
113
+ // 只有请求确实携带着有效 token 时才下发——匿名命中 /api/health 不触发。
114
+ if (ok && !req.headers.cookie?.includes("pi_web_token=")) {
115
+ res.setHeader("Set-Cookie", `pi_web_token=${encodeURIComponent(AUTH_TOKEN)}; Path=/; HttpOnly; SameSite=Strict; Max-Age=31536000`);
116
+ }
117
+ if (req.path === "/api/health" || ok) {
115
118
  next();
116
119
  return;
117
120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-web-ui",
3
- "version": "0.59.0",
3
+ "version": "0.59.1",
4
4
  "description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
5
5
  "license": "MIT",
6
6
  "author": {