@oldsuns/pi-switch 0.3.1 → 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 +243 -224
- package/bin/pi-switch.js +12 -1
- package/package.json +10 -4
- package/pi-switch-native.darwin-arm64.node +0 -0
- package/pi-switch-native.darwin-x64.node +0 -0
- package/pi-switch-native.linux-x64-gnu.node +0 -0
- package/pi-switch-native.linux-x64-musl.node +0 -0
- package/pi-switch-native.win32-x64-msvc.node +0 -0
- package/web/native-client.js +51 -0
- package/web/native-worker.js +17 -0
- package/web/public/app.js +949 -0
- package/web/public/appearance.js +24 -0
- package/web/public/dialogs.js +190 -0
- package/web/public/favicon.svg +1 -0
- package/web/public/index.html +23 -0
- package/web/public/overview.js +83 -0
- package/web/public/profile-order.js +47 -0
- package/web/public/profiles.js +134 -0
- package/web/public/session-tree.js +120 -0
- package/web/public/sessions.js +164 -0
- package/web/public/settings.js +47 -0
- package/web/public/shell.js +48 -0
- package/web/public/styles.css +763 -0
- package/web/public/ui.js +135 -0
- package/web/server.js +137 -0
- package/web/start.js +63 -0
package/README.md
CHANGED
|
@@ -1,224 +1,243 @@
|
|
|
1
|
-
# pi-switch
|
|
2
|
-
|
|
3
|
-
维护本地 provider 库并按需同步 [Pi](https://github.com/earendil-works/pi) 配置的终端 TUI
|
|
4
|
-
|
|
5
|
-
CLI:`pi-switch` · npm 包:`@oldsuns/pi-switch` · Node 薄壳 + Rust/napi 原生核心
|
|
6
|
-
|
|
7
|
-
## 功能
|
|
8
|
-
|
|
9
|
-
- **主页**:provider / model 计数、默认模型、关键路径;启动后台静默检查 npm 新版本,有更新时弹出确认对话框可一键安装
|
|
10
|
-
- **配置 (Profiles)**:本地 provider 库与 Pi 启用子集;新建 / 编辑 / 删除 / 复制;在线导入模型
|
|
11
|
-
- **会话 (Sessions)**:按工作目录浏览 JSONL session,使用 Tree/Markdown 视图区分用户/Pi消息,支持筛选、复制与删除
|
|
12
|
-
- **设置 (Settings)**:语言、models.dev 元数据、默认参数、重载、校验、备份、OpenCode 导入、自动检查更新、手动检查更新
|
|
13
|
-
- 完整库保存在 `~/.pi-switch/providers.json`,只有已启用项写入 Pi 的 `models.json`
|
|
14
|
-
|
|
15
|
-
## 要求
|
|
16
|
-
|
|
17
|
-
- Node.js `>= 20`
|
|
18
|
-
- 预构建原生模块当前仅覆盖 Windows (msvc x64)——直接 `npm install -g` 即装即用
|
|
19
|
-
- 本地构建原生模块(仅开发者)需要 Rust 工具链与 `@napi-rs/cli`
|
|
20
|
-
|
|
21
|
-
## 快速开始
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
# 全局安装
|
|
25
|
-
npm install -g @oldsuns/pi-switch
|
|
26
|
-
|
|
27
|
-
# 打开 TUI
|
|
28
|
-
pi-switch
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
首次运行会自动把现有 Pi agent 目录中的 `models.json` 全量导入本地库,**不修改** Pi 配置;随后即可在 Profiles 中勾选要同步到 Pi 的 provider / model。Pi agent 目录使用非空 `PI_CODING_AGENT_DIR`,未设置时为 `~/.pi/agent`。
|
|
32
|
-
|
|
33
|
-
CLI:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
pi-switch # 打开 TUI(等同 tui)
|
|
37
|
-
pi-switch tui
|
|
38
|
-
pi-switch
|
|
39
|
-
pi-switch --
|
|
40
|
-
pi-switch
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
86
|
-
|
|
|
87
|
-
| `
|
|
88
|
-
|
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
|
111
|
-
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
Pi `
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
`~/.pi
|
|
172
|
-
|
|
173
|
-
|
|
|
174
|
-
|------|------|
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
1
|
+
# pi-switch
|
|
2
|
+
|
|
3
|
+
维护本地 provider 库并按需同步 [Pi](https://github.com/earendil-works/pi) 配置的终端 TUI 与本地 Web 界面:管理 provider / 模型 / 默认模型,以及 Pi session 的列表、预览与删除。
|
|
4
|
+
|
|
5
|
+
CLI:`pi-switch` · npm 包:`@oldsuns/pi-switch` · Node 薄壳 + Rust/napi 原生核心
|
|
6
|
+
|
|
7
|
+
## 功能
|
|
8
|
+
|
|
9
|
+
- **主页**:provider / model 计数、默认模型、关键路径;启动后台静默检查 npm 新版本,有更新时弹出确认对话框可一键安装
|
|
10
|
+
- **配置 (Profiles)**:本地 provider 库与 Pi 启用子集;新建 / 编辑 / 删除 / 复制;在线导入模型
|
|
11
|
+
- **会话 (Sessions)**:按工作目录浏览 JSONL session,使用 Tree/Markdown 视图区分用户/Pi消息,支持筛选、复制与删除
|
|
12
|
+
- **设置 (Settings)**:语言、models.dev 元数据、默认参数、重载、校验、备份、OpenCode 导入、自动检查更新、手动检查更新
|
|
13
|
+
- 完整库保存在 `~/.pi-switch/providers.json`,只有已启用项写入 Pi 的 `models.json`
|
|
14
|
+
|
|
15
|
+
## 要求
|
|
16
|
+
|
|
17
|
+
- Node.js `>= 20`
|
|
18
|
+
- 预构建原生模块当前仅覆盖 Windows (msvc x64)——直接 `npm install -g` 即装即用
|
|
19
|
+
- 本地构建原生模块(仅开发者)需要 Rust 工具链与 `@napi-rs/cli`
|
|
20
|
+
|
|
21
|
+
## 快速开始
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# 全局安装
|
|
25
|
+
npm install -g @oldsuns/pi-switch
|
|
26
|
+
|
|
27
|
+
# 打开 TUI
|
|
28
|
+
pi-switch
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
首次运行会自动把现有 Pi agent 目录中的 `models.json` 全量导入本地库,**不修改** Pi 配置;随后即可在 Profiles 中勾选要同步到 Pi 的 provider / model。Pi agent 目录使用非空 `PI_CODING_AGENT_DIR`,未设置时为 `~/.pi/agent`。
|
|
32
|
+
|
|
33
|
+
CLI:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pi-switch # 打开 TUI(等同 tui)
|
|
37
|
+
pi-switch tui
|
|
38
|
+
pi-switch --web # 启动本地 Web 界面并打开浏览器
|
|
39
|
+
pi-switch --web --port 5210 --no-open # 指定端口,只打印访问地址
|
|
40
|
+
pi-switch doctor # 校验配置与默认模型
|
|
41
|
+
pi-switch --version # / -v
|
|
42
|
+
pi-switch --help # / -h / help
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Windows 上无需 Rust:预构建原生模块随 npm 包分发,`pi-switch` 开箱即用。本地开发见下文「开发者」。
|
|
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
|
+
|
|
60
|
+
## 开发者
|
|
61
|
+
|
|
62
|
+
从源码构建:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install
|
|
66
|
+
npm run build:native:debug
|
|
67
|
+
node ./bin/pi-switch.js
|
|
68
|
+
# 启动本地 Web 界面
|
|
69
|
+
npm run dev:web
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
贡献流程见 [CONTRIBUTING.md](./CONTRIBUTING.md)。
|
|
73
|
+
|
|
74
|
+
Web 实现与使用说明见 [web/README.md](./web/README.md)。
|
|
75
|
+
|
|
76
|
+
## 界面与快捷键
|
|
77
|
+
|
|
78
|
+
全局导航:`j/k` 或方向键移动;菜单中 `Enter` / `l` 进入内容,各内容页按自己的方向键切换焦点;`?` 帮助;`q` 退出(多数界面 `Ctrl+C` 也退出,会话预览除外)。
|
|
79
|
+
|
|
80
|
+
### 配置 (Profiles)
|
|
81
|
+
|
|
82
|
+
| 键 | 作用 |
|
|
83
|
+
|----|------|
|
|
84
|
+
| `n` / `e` / `d` / `c` | 新建 / 编辑 / 删除 / 复制当前焦点(provider 或 model) |
|
|
85
|
+
| `Space`(provider) | 同步到 Pi / 取消同步;`[x]` 已同步,`[ ]` 不同步 |
|
|
86
|
+
| `Space`(model) | 设为默认模型(仅已同步到 Pi 的 provider) |
|
|
87
|
+
| `i` | 从当前 provider 在线导入模型 |
|
|
88
|
+
| `/` | 筛选 provider |
|
|
89
|
+
| `Enter` / `l` | 进入模型列表 |
|
|
90
|
+
| `Esc` / `h` | 从模型回到 provider,或从 provider 回菜单 |
|
|
91
|
+
| `r` | 从磁盘重载配置 |
|
|
92
|
+
| `b` | 浏览备份 |
|
|
93
|
+
| `v` | 校验配置(doctor) |
|
|
94
|
+
|
|
95
|
+
新建 provider 默认同步到 Pi,表单可关闭;复制继承源的同步状态。
|
|
96
|
+
|
|
97
|
+
Provider 表单:`baseUrl`、`api`(`openai-completions` / `openai-responses` / `anthropic-messages` / `google-generative-ai`)、`apiKey`、`authHeader`、Headers(独立 `User-Agent` + 其余 JSON)、`compat`(含一等开关 Session affinity = `sendSessionAffinityHeaders`)。
|
|
98
|
+
|
|
99
|
+
Model 表单:`id`、`name`、API override、reasoning、文本/图像输入、context window、max tokens、thinking levels(`thinkingLevelMap`)。`cost`、`modelOverrides`、OAuth 等未知字段会无损保留。字段语义以 [Pi Custom Models](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/models.md) 为准。
|
|
100
|
+
|
|
101
|
+
### 会话 (Sessions)
|
|
102
|
+
|
|
103
|
+
| 键 | 作用 |
|
|
104
|
+
|----|------|
|
|
105
|
+
| `/` | 筛选 |
|
|
106
|
+
| `n` | 仅显示手动命名的 session(不是新建) |
|
|
107
|
+
| `r` | 重新扫描磁盘 |
|
|
108
|
+
| `u` | 预览仅用户消息 |
|
|
109
|
+
| `Right` | 从会话列表进入树预览 |
|
|
110
|
+
| 预览 `Left` | 从树预览返回会话列表 |
|
|
111
|
+
| 预览 `Up` / `Down` | 按树节点切换,默认定位当前 active leaf |
|
|
112
|
+
| 预览 `PageUp` / `PageDown` | 按当前可视区域逐页滚动 |
|
|
113
|
+
| 预览 `v` | 切换 Tree 单行预览和完整 Markdown 阅读 |
|
|
114
|
+
| 预览 `Tab` | 折叠或展开当前真实分叉点的全部下级 |
|
|
115
|
+
| 预览 `Ctrl+Left/Right` | 在视觉分支层级间移动:跳过同 lane 的串行消息,进入上一级/下一级分支段 |
|
|
116
|
+
| 预览 `Alt+Left/Right` | 从当前位置切换最近分叉点的相邻分支 |
|
|
117
|
+
| 滚轮 | 按行滚动长消息 |
|
|
118
|
+
| 预览 `Ctrl+C` | 复制当前消息到剪贴板(不退出) |
|
|
119
|
+
| `d` | 删除当前 session(确认后优先 trash) |
|
|
120
|
+
|
|
121
|
+
预览按 Pi 官方 session 的 `id` / `parentId` 构建树。默认 Tree 模式参考 Pi 原生 `/tree`:每个节点只占一行,显示 `User:` / `Pi:` 和单行摘要;只有真实分叉才增加缩进并使用 `├─` / `└─`,串行消息保持同一 lane。深层分支不会为所有消息预留固定宽度;仅在当前节点过深时水平平移树正文,并固定保留左侧光标 gutter。按 `v` 可切换到完整阅读模式,显示完整 Markdown 正文;每条消息按自身树深度分配前缀,树栏最多约占三分之一宽度。`Ctrl+Left/Right` 在分支段层级间移动,`Alt+Left/Right` 切换相邻分支,`Tab` 仅折叠真实分叉点。可见消息挂到最近可见祖先,`session_info`、tool 等 bookkeeping 节点不会伪装成对话消息。黄标题 = 手动命名;白标题 = 使用第一条用户消息。
|
|
122
|
+
|
|
123
|
+
Session 根目录优先级:非空 `PI_CODING_AGENT_SESSION_DIR` → `<Pi agent dir>/sessions`。
|
|
124
|
+
|
|
125
|
+
剪贴板:Windows `clip`、macOS `pbcopy`、Linux `wl-copy` / `xclip` / `xsel`。
|
|
126
|
+
|
|
127
|
+
### 设置 (Settings)
|
|
128
|
+
|
|
129
|
+
| 项 | 说明 |
|
|
130
|
+
|----|------|
|
|
131
|
+
| 语言 | English / 中文(`en` / `zh-CN`) |
|
|
132
|
+
| 从 models.dev 获取模型信息 | 开关实时元数据(默认开) |
|
|
133
|
+
| 自动检查更新 | 启动时后台自动检查 npm `@oldsuns/pi-switch` 新版本(开关,默认开) |
|
|
134
|
+
| 检查更新(手动) | 立即检查新版本(绕过 24h 缓存) |
|
|
135
|
+
| 默认模型参数 | 仅关闭实时元数据时显示,用于导入缺省 |
|
|
136
|
+
| 重载配置 | 从磁盘重读 |
|
|
137
|
+
| 验证配置 | doctor |
|
|
138
|
+
| 浏览备份 | 恢复 version 3 或兼容的 version 2 备份 |
|
|
139
|
+
| 从 OpenCode 导入 | 只读导入 `opencode.json` |
|
|
140
|
+
|
|
141
|
+
`Enter` / `Space` 执行当前项。
|
|
142
|
+
|
|
143
|
+
## Provider 库与 Pi 同步
|
|
144
|
+
|
|
145
|
+
- `~/.pi-switch/providers.json` 是完整本地库;`<Pi agent dir>/models.json` 只含当前已同步到 Pi 的子集。
|
|
146
|
+
- 首次运行把现有 `models.json` 全量导入本地库,不修改 Pi 配置。
|
|
147
|
+
- 已同步 provider 的编辑与 model 变更会同步两份文件;不同步项只更新本地库。
|
|
148
|
+
- 在线导入 model **不会**隐式同步到 Pi。
|
|
149
|
+
- 启动或手动重载时,以 `models.json` 中同 ID provider 为准回灌本地库;外部从 Pi 删除的 provider 仍作为不同步项保留。
|
|
150
|
+
- 从 Pi 移除当前默认 provider 会先确认并清除默认模型;`d` 永久删除本地副本,必要时同时从 Pi 删除。
|
|
151
|
+
|
|
152
|
+
## 模型导入与价格
|
|
153
|
+
|
|
154
|
+
在线导入(Profiles 中 `i`):
|
|
155
|
+
|
|
156
|
+
1. 按 provider 的 `api` / `baseUrl` / 鉴权请求模型列表。
|
|
157
|
+
2. **NewAPI 网关价格(best-effort)**:去掉 `baseUrl` 尾部 `/v1` 后依次尝试
|
|
158
|
+
- `GET /api/ratio_config`(可能含 `create_cache_ratio`)
|
|
159
|
+
- `GET /api/pricing`
|
|
160
|
+
成功则按 NewAPI 换算覆盖模型 `cost`(`1 USD = 500_000 quota`,每 1M tokens 成本 ≈ `ratio × 2` USD);失败静默忽略。
|
|
161
|
+
3. 若开启 models.dev 元数据:请求 `https://models.dev/api.json`,补全 `contextWindow`、`maxTokens`、`cost`、reasoning 等。
|
|
162
|
+
- **在线导入**遇到同 model ID 多源歧义时:需要选择元数据来源;缺少匹配元数据或 models.dev 不可达时,选中的模型使用 Settings 中的默认参数导入,并明确提示。
|
|
163
|
+
- 先选择服务商返回的模型 ID,再为选中项获取元数据和价格;TUI 与 Web 使用相同流程。
|
|
164
|
+
- 网关价格在 catalog 元数据之上叠加。
|
|
165
|
+
4. 关闭实时元数据:使用 Settings 中的默认参数;空字段回落 Pi 官方默认(context window `128000`、max tokens `16384`、cost `0`)。
|
|
166
|
+
|
|
167
|
+
**OpenCode 导入**(Settings):只读 `~/.config/opencode/opencode.json`,可全选或勾选 provider;导入项默认同步到 Pi。若 models.dev 仍有歧义,**需要用户选择候选**。OpenCode 配置本身不会被修改。
|
|
168
|
+
|
|
169
|
+
## 配置路径与 Settings 字段
|
|
170
|
+
|
|
171
|
+
`<Pi agent dir>` 使用非空 `PI_CODING_AGENT_DIR`,未设置时为 `~/.pi/agent`。`PI_CODING_AGENT_SESSION_DIR` 只覆盖 Session 根目录,优先级仍高于 `<Pi agent dir>/sessions`。
|
|
172
|
+
|
|
173
|
+
| 路径 | 角色 |
|
|
174
|
+
|------|------|
|
|
175
|
+
| `~/.pi-switch/providers.json` | 完整本地 provider 库(`version: 1`) |
|
|
176
|
+
| `~/.pi-switch/settings.json` | pi-switch 私有设置;无自定义值时可不存在 |
|
|
177
|
+
| `<Pi agent dir>/models.json` | 已同步到 Pi 的 provider 子集 |
|
|
178
|
+
| `<Pi agent dir>/settings.json` | Pi 设置,包括默认 provider / model |
|
|
179
|
+
| `~/.config/opencode/opencode.json` | OpenCode 只读导入源 |
|
|
180
|
+
| `~/.pi-switch/backups/` | version 3 备份(providers + models + Pi settings + pi-switch settings),最多 10 份 |
|
|
181
|
+
| `~/.pi-switch/write.lock` | 写入互斥锁 |
|
|
182
|
+
| `~/.pi-switch/update.json` | npm 新版本检查缓存(`lastCheck` + `latest` + `dismissed`,每 24h 最多联网一次) |
|
|
183
|
+
|
|
184
|
+
Pi `settings.json` 中由 pi-switch 管理的字段:
|
|
185
|
+
|
|
186
|
+
| 字段 | 含义 |
|
|
187
|
+
|------|------|
|
|
188
|
+
| `defaultProvider` + `defaultModel` | 默认模型(成对存在或同时缺省) |
|
|
189
|
+
|
|
190
|
+
`~/.pi-switch/settings.json` 的字段:
|
|
191
|
+
|
|
192
|
+
| 字段 | 含义 |
|
|
193
|
+
|------|------|
|
|
194
|
+
| `language` | `en` \| `zh-CN` |
|
|
195
|
+
| `fetchModelMetadata` | 是否拉 models.dev(默认 `true`) |
|
|
196
|
+
| `checkForUpdates` | 是否启动时检查 npm 新版本(默认 `true`) |
|
|
197
|
+
| `modelDefaults` | 关闭实时元数据时的导入缺省(context / maxTokens / cost) |
|
|
198
|
+
|
|
199
|
+
升级时若 Pi `settings.json` 仍包含旧 `piSwitch` 对象,启动或重载会自动迁移到 `~/.pi-switch/settings.json`,本地已有字段优先,旧对象只补齐缺失字段;迁移成功后从 Pi settings 删除旧对象。两份文件中的未知字段都会保留。
|
|
200
|
+
|
|
201
|
+
## 数据安全
|
|
202
|
+
|
|
203
|
+
- 写前备份 `providers.json`、Pi `models.json` / `settings.json` 和 pi-switch `settings.json` 到 `~/.pi-switch/backups/`(version 3);最多保留最近 10 份。现有 version 2 备份仍可恢复并自动拆分设置,version 1 备份不支持恢复。
|
|
204
|
+
- 写入使用 `write.lock` 互斥;异常残留锁时 `doctor` 会提示。
|
|
205
|
+
- `providers.json` 损坏时归档为 `corrupt-providers-*.json`,再从当前 Pi 配置重建,启动时显示归档路径。
|
|
206
|
+
- 原子写入,只 patch 目标字段,保留未知 JSON;格式错误时停止写入并显示错误。
|
|
207
|
+
- 支持 Pi 的 `$ENV` / `${ENV}` 插值与 `$$` / `$!` 转义;`!command` 原样保存,在线拉取**不会**执行它。
|
|
208
|
+
- Session 删除只作用于选中的 JSONL,并校验路径必须位于 session 根目录内;优先调用系统 `trash`,失败后再永久删除。
|
|
209
|
+
- 正常退出、错误和 panic 都会恢复 raw mode、alternate screen 与光标。
|
|
210
|
+
|
|
211
|
+
## 自动更新检查与安装
|
|
212
|
+
|
|
213
|
+
启动时若 Settings 中「自动检查更新」开启(默认开),pi-switch 会在后台线程静默访问 npm registry 查询 `@oldsuns/pi-switch` 的 `latest` 版本,与当前版本对比:
|
|
214
|
+
|
|
215
|
+
- 有新版本:弹出确认对话框(显示 `当前 → 最新`),用户可选择立即安装或跳过。跳过后同一版本不再弹窗,但主页仍显示横幅。手动检查(Settings 中「检查更新」)始终弹窗。
|
|
216
|
+
- 无新版本、离线或请求失败:静默无提示,不打扰用户。
|
|
217
|
+
|
|
218
|
+
安装流程:
|
|
219
|
+
|
|
220
|
+
1. 用户在确认对话框按 `Enter/y` 确认 → 后台执行 `npm install -g @oldsuns/pi-switch`,显示加载动画。
|
|
221
|
+
2. 安装成功 → 通知「更新已安装,请重启 pi-switch 生效」。
|
|
222
|
+
3. 安装失败 → 弹出错误通知(如权限不足、网络问题)。
|
|
223
|
+
4. 用户按 `Esc/n` 跳过 → 记录已跳过的版本到缓存,同一版本不再自动弹窗;主页横幅仍提示有新版本。
|
|
224
|
+
|
|
225
|
+
检查结果缓存在 `~/.pi-switch/update.json`,自动检查每 24h 最多联网一次。后台线程独立于目录导入任务,不阻塞 UI。Settings 中可随时关闭「自动检查更新」。
|
|
226
|
+
|
|
227
|
+
## 验证
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
cargo test --locked --lib
|
|
231
|
+
cargo fmt -- --check
|
|
232
|
+
cargo clippy --locked --all-targets -- -D warnings
|
|
233
|
+
npm run build:native:debug
|
|
234
|
+
npm run pack:check
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## 许可
|
|
238
|
+
|
|
239
|
+
[MIT](./LICENSE)
|
|
240
|
+
|
|
241
|
+
## 致谢
|
|
242
|
+
|
|
243
|
+
感谢 [LINUX DO](https://linux.do) 社区的讨论与反馈。
|
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.
|
|
4
|
-
"description": "A
|
|
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
|
-
"
|
|
30
|
-
"build:native
|
|
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
|
|
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 });
|