@oldsuns/pi-switch 0.3.2 → 0.3.4

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
@@ -1,6 +1,6 @@
1
1
  # pi-switch
2
2
 
3
- 维护本地 provider 库并按需同步 [Pi](https://github.com/earendil-works/pi) 配置的终端 TUI:管理 provider / 模型 / 默认模型,以及 Pi session 的列表、预览与删除。
3
+ 维护本地 provider 库并按需同步 [Pi](https://github.com/earendil-works/pi) 配置的终端 TUI 与本地 Web 界面:管理 provider / 模型 / 默认模型,以及 Pi session 的列表、预览与删除。
4
4
 
5
5
  CLI:`pi-switch` · npm 包:`@oldsuns/pi-switch` · Node 薄壳 + Rust/napi 原生核心
6
6
 
@@ -35,6 +35,8 @@ CLI:
35
35
  ```bash
36
36
  pi-switch # 打开 TUI(等同 tui)
37
37
  pi-switch tui
38
+ pi-switch --web # 启动本地 Web 界面并打开浏览器
39
+ pi-switch --web --port 5210 --no-open # 指定端口,只打印访问地址
38
40
  pi-switch doctor # 校验配置与默认模型
39
41
  pi-switch --version # / -v
40
42
  pi-switch --help # / -h / help
@@ -42,6 +44,19 @@ pi-switch --help # / -h / help
42
44
 
43
45
  Windows 上无需 Rust:预构建原生模块随 npm 包分发,`pi-switch` 开箱即用。本地开发见下文「开发者」。
44
46
 
47
+ ## Web 界面
48
+
49
+ `pi-switch --web` 启动仅监听 `127.0.0.1` 的本地服务,自动选择空闲端口并打开浏览器;终端会打印访问地址,按 `Ctrl+C` 停止服务。可使用 `--port` 指定端口、`--no-open` 禁用自动打开。
50
+
51
+ - **主页**:本地 provider / 模型计数、Pi 可用模型、直接切换默认模型、最近会话与常用操作。
52
+ - **配置**:provider / 模型主从分栏、搜索、创建、编辑、复制、删除、同步与默认模型;在线多选导入、元数据来源选择。
53
+ - **会话**:按工作目录浏览,支持命名筛选、用户消息筛选、分支折叠、树状与 Markdown 阅读、复制和删除。
54
+ - **设置**:中英文、模型元数据与缺省参数、配置检查、备份恢复、OpenCode 导入、手动检查和安装更新。自动检查更新开关与 TUI 共享,控制 TUI 启动时的检查。
55
+
56
+ Web 在「设置 → 外观」中提供 Catppuccin Mocha 暗色与 Latte 亮色主题,切换立即生效,并在浏览器中保存选择。沿用 TUI 的四页导航和状态语义,支持窄屏布局、键盘操作与减少动画偏好。按 `1`–`4` 切换页面,`/` 搜索,`?` 查看完整快捷键;文本输入期间不触发快捷键。
57
+
58
+ Web 与 TUI 共用 Rust 文档层,使用相同的文件锁、备份、校验和未知字段保留规则。取消默认 provider 的同步、删除和恢复备份均会明确确认。浏览器不会将 API key 保存到 localStorage;会话 Markdown 中的原始 HTML 以文本展示,图片以替代文字展示,不自动加载远程图片。
59
+
45
60
  ## 开发者
46
61
 
47
62
  从源码构建:
@@ -50,10 +65,14 @@ Windows 上无需 Rust:预构建原生模块随 npm 包分发,`pi-switch`
50
65
  npm install
51
66
  npm run build:native:debug
52
67
  node ./bin/pi-switch.js
68
+ # 启动本地 Web 界面
69
+ npm run dev:web
53
70
  ```
54
71
 
55
72
  贡献流程见 [CONTRIBUTING.md](./CONTRIBUTING.md)。
56
73
 
74
+ Web 实现与使用说明见 [web/README.md](./web/README.md)。
75
+
57
76
  ## 界面与快捷键
58
77
 
59
78
  全局导航:`j/k` 或方向键移动;菜单中 `Enter` / `l` 进入内容,各内容页按自己的方向键切换焦点;`?` 帮助;`q` 退出(多数界面 `Ctrl+C` 也退出,会话预览除外)。
@@ -140,8 +159,8 @@ Session 根目录优先级:非空 `PI_CODING_AGENT_SESSION_DIR` → `<Pi agent
140
159
  - `GET /api/pricing`
141
160
  成功则按 NewAPI 换算覆盖模型 `cost`(`1 USD = 500_000 quota`,每 1M tokens 成本 ≈ `ratio × 2` USD);失败静默忽略。
142
161
  3. 若开启 models.dev 元数据:请求 `https://models.dev/api.json`,补全 `contextWindow`、`maxTokens`、`cost`、reasoning 等。
143
- - **在线导入**遇到同 model ID 多源歧义时:**自动取第一个候选**,不弹选择框;缺少可用元数据的模型跳过并提示计数。
144
- - 导入列表会标注价格来源:`ratio_config` `models.dev`。
162
+ - **在线导入**遇到同 model ID 多源歧义时:需要选择元数据来源;缺少匹配元数据或 models.dev 不可达时,选中的模型使用 Settings 中的默认参数导入,并明确提示。
163
+ - 先选择服务商返回的模型 ID,再为选中项获取元数据和价格;TUI 与 Web 使用相同流程。
145
164
  - 网关价格在 catalog 元数据之上叠加。
146
165
  4. 关闭实时元数据:使用 Settings 中的默认参数;空字段回落 Pi 官方默认(context window `128000`、max tokens `16384`、cost `0`)。
147
166
 
package/bin/pi-switch.js CHANGED
@@ -8,6 +8,9 @@ if (command === "--help" || command === "-h" || command === "help") {
8
8
 
9
9
  Usage:
10
10
  pi-switch open the terminal UI
11
+ pi-switch --web open the local Web interface
12
+ pi-switch --web --port 5210 --no-open
13
+ choose a port without opening a browser
11
14
  pi-switch doctor validate Pi documents and defaults
12
15
  pi-switch --version print the native module version
13
16
  `);
@@ -21,7 +24,15 @@ Usage:
21
24
  if (checks.some((check) => !check.ok)) process.exitCode = 1;
22
25
  } else if (!command || command === "tui") {
23
26
  runTui();
27
+ } else if (command === "--web" || command === "web") {
28
+ try {
29
+ const { parseWebOptions, startWeb } = await import("../web/start.js");
30
+ await startWeb(parseWebOptions(process.argv.slice(3)));
31
+ } catch (error) {
32
+ console.error(error.message);
33
+ process.exitCode = 1;
34
+ }
24
35
  } else {
25
- console.error("Usage: pi-switch [tui|doctor|--version]");
36
+ console.error("Usage: pi-switch [tui|--web|doctor|--version|--help]");
26
37
  process.exitCode = 2;
27
38
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oldsuns/pi-switch",
3
- "version": "0.3.2",
4
- "description": "A focused terminal UI for Pi provider and model configuration",
3
+ "version": "0.3.4",
4
+ "description": "A local terminal and Web UI for Pi provider and model configuration",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "pi-switch": "./bin/pi-switch.js"
@@ -10,6 +10,11 @@
10
10
  "types": "./index.d.ts",
11
11
  "files": [
12
12
  "bin",
13
+ "web/public",
14
+ "web/server.js",
15
+ "web/start.js",
16
+ "web/native-client.js",
17
+ "web/native-worker.js",
13
18
  "index.js",
14
19
  "index.d.ts",
15
20
  "pi-switch-native.cjs",
@@ -26,8 +31,9 @@
26
31
  ]
27
32
  },
28
33
  "scripts": {
29
- "build:native": "napi build --platform --release --dts native.d.ts",
30
- "build:native:debug": "napi build --platform --dts native.d.ts",
34
+ "dev:web": "node ./bin/pi-switch.js --web",
35
+ "build:native": "napi build --platform --no-js --release --dts native.d.ts",
36
+ "build:native:debug": "napi build --platform --no-js --dts native.d.ts",
31
37
  "test": "cargo test --locked --lib",
32
38
  "check": "cargo fmt --check && cargo clippy --locked --all-targets -- -D warnings",
33
39
  "pack:check": "npm pack --dry-run"
Binary file
Binary file
Binary file
@@ -0,0 +1,51 @@
1
+ import { Worker } from "node:worker_threads";
2
+
3
+ export async function createNativeClient() {
4
+ const worker = new Worker(new URL("./native-worker.js", import.meta.url));
5
+ const pending = new Map();
6
+ let nextId = 0;
7
+ let failure;
8
+ let readyResolve;
9
+ let readyReject;
10
+ const ready = new Promise((resolve, reject) => {
11
+ readyResolve = resolve;
12
+ readyReject = reject;
13
+ });
14
+
15
+ function fail(error) {
16
+ failure = error;
17
+ readyReject(error);
18
+ for (const entry of pending.values()) entry.reject(error);
19
+ pending.clear();
20
+ }
21
+
22
+ worker.on("error", fail);
23
+ worker.on("exit", (code) => fail(new Error("The native Web worker stopped (exit " + code + ").")));
24
+ worker.on("message", (message) => {
25
+ if (message.ready) {
26
+ readyResolve();
27
+ return;
28
+ }
29
+ const entry = pending.get(message.id);
30
+ if (!entry) return;
31
+ pending.delete(message.id);
32
+ if (message.error) entry.reject(new Error(message.error));
33
+ else entry.resolve(message.result);
34
+ });
35
+ await ready;
36
+
37
+ return {
38
+ request(payload) {
39
+ if (failure) return Promise.reject(failure);
40
+ return new Promise((resolve, reject) => {
41
+ const id = ++nextId;
42
+ pending.set(id, { resolve, reject });
43
+ worker.postMessage({ id, payload });
44
+ });
45
+ },
46
+ async close() {
47
+ fail(new Error("The Web server is shutting down."));
48
+ await worker.terminate();
49
+ },
50
+ };
51
+ }
@@ -0,0 +1,17 @@
1
+ import { parentPort } from "node:worker_threads";
2
+ import binding from "../pi-switch-native.cjs";
3
+
4
+ if (typeof binding.WebSession !== "function") {
5
+ throw new Error("The native module has no Web interface. Run npm run build:native:debug first.");
6
+ }
7
+ const session = new binding.WebSession();
8
+
9
+ parentPort.on("message", ({ id, payload }) => {
10
+ try {
11
+ const result = JSON.parse(session.request(JSON.stringify(payload)));
12
+ parentPort.postMessage({ id, result });
13
+ } catch (error) {
14
+ parentPort.postMessage({ id, error: error.message });
15
+ }
16
+ });
17
+ parentPort.postMessage({ ready: true });