agentsw 0.3.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 luocheng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,227 @@
1
+ <div align="center">
2
+
3
+ # agentsw
4
+
5
+ **One provider, every coding agent — from the terminal.**
6
+
7
+ A CLI that keeps your OpenAI- and Anthropic-protocol providers — base URL, key, model list,
8
+ metadata — in one place and writes the active one into nine agents' own config files,
9
+ without touching the settings you hand-tuned.
10
+
11
+ [![npm version](https://img.shields.io/npm/v/agentsw?logo=npm&logoColor=white)](https://www.npmjs.com/package/agentsw)
12
+ [![CI](https://img.shields.io/github/actions/workflow/status/tchivs/agentsw/ci.yml?branch=main&logo=github&logoColor=white&label=CI)](https://github.com/tchivs/agentsw/actions/workflows/ci.yml)
13
+ [![node version](https://img.shields.io/node/v/agentsw?logo=nodedotjs&logoColor=white)](#install)
14
+ [![license](https://img.shields.io/npm/l/agentsw?color=blue)](./LICENSE)
15
+
16
+ [Install](#install) · [Quick start](#quick-start) · [Supported apps](#supported-apps) · [Commands](#commands) · [简体中文](./README.zh-CN.md)
17
+
18
+ </div>
19
+
20
+ ```console
21
+ $ asw use vfing -m glm-5.3-flash
22
+ switching to vfing (openai) · default model glm-5.3-flash
23
+
24
+ skip claude Claude Code does not support openai-protocol providers
25
+ ok codex ~/.codex/config.toml, ~/.codex/auth.json
26
+ backup: ~/.config/agentsw/backups/config.toml.2026-08-31T15-58-24-678Z
27
+ ok omp ~/.omp/agent/models.yml
28
+ select in omp with: omp --model vfing/glm-5.3-flash
29
+ skip pi pi not detected (pass --apps pi to force)
30
+ ok opencode ~/.config/opencode/opencode.json
31
+ ok dsh ~/.dsh/settings.yaml, ~/.dsh/.credentials.yaml
32
+ select in dsh with the model picker, or run: dsh web
33
+ ```
34
+
35
+ `agentsw` and the short `asw` are the same binary.
36
+
37
+ ## Why a CLI
38
+
39
+ - **It runs where a desktop app cannot.** Headless servers, containers, devcontainers, CI.
40
+ `npx agentsw use myproxy -a codex,omp` is one line in a provisioning script; nine GUI
41
+ clicks are not.
42
+ - **`--dry-run` prints diffs.** Review what a switch will write before it writes it, or pipe
43
+ it into a code review. Every file is backed up first regardless.
44
+ - **Non-destructive by design.** Only the fields agentsw owns are rewritten. Provider-level
45
+ keys it does not model — `authHeader`, `headers`, `compat`, `discovery` — per-model extras
46
+ like `thinkingLevelMap`, and YAML comments all survive a re-sync.
47
+ - **Model metadata, not just an id list.** Reseller `/v1/models` endpoints return bare ids.
48
+ agentsw enriches them from [models.dev](https://models.dev) and writes what each app
49
+ actually reads: `thinkingLevelMap` for pi/prime, `limit`/`attachment` for opencode,
50
+ `contextWindow`/`maxTokens` for omp, `reasoningEfforts`/`input` for dsh.
51
+ - **Adopts what you already have** — including [cc-switch](https://github.com/farion1231/cc-switch).
52
+ `import` scans every installed agent *and* cc-switch's own store, merges duplicates
53
+ (same protocol + base URL), and never writes back to either.
54
+ - **Wire-aware.** `/v1/chat/completions` and `/v1/responses` are different endpoints;
55
+ agentsw tracks which one a provider speaks and never downgrades a working one.
56
+
57
+ Want a GUI, MCP/Skills sync, a usage dashboard or a local failover proxy? Use
58
+ [cc-switch](https://github.com/farion1231/cc-switch) — it is excellent at that, and agentsw
59
+ imports from it rather than competing with it.
60
+
61
+ ## Install
62
+
63
+ ```bash
64
+ npx agentsw # zero install: opens the interactive menu
65
+ npm install -g agentsw # requires Node >= 22.12
66
+ ```
67
+
68
+ ## Quick start
69
+
70
+ ```bash
71
+ agentsw # no args: interactive menu (add, import, use, status, apps)
72
+ ```
73
+
74
+ The first run on an empty store offers to import the providers already configured in your
75
+ agents, and asks for English or 简体中文. Prefer flags? The same flow, unattended:
76
+
77
+ ```bash
78
+ # add a provider and let it discover its own model list
79
+ asw add -y --id vfing --protocol openai --openai-api responses \
80
+ --base-url https://api.example.com/v1 --api-key sk-... --discover
81
+
82
+ asw use vfing # point every detected agent at it
83
+ asw use vfing --dry-run # ...or just show the diffs
84
+ asw status # what each agent points at right now
85
+ ```
86
+
87
+ ```console
88
+ $ asw status
89
+ config: ~/.config/agentsw/config.json
90
+ active provider: vfing
91
+
92
+ APP FOUND PROTOCOLS CURRENT CONFIG
93
+ --------- ----- ---------------- ------------------------------------ ---------------------------
94
+ claude yes anthropic - ~/.claude/settings.json
95
+ codex yes openai vfing · glm-5.3-flash ~/.codex/config.toml
96
+ omp yes openai+anthropic providers: sub, sub-anthropic, vfing ~/.omp/agent/models.yml
97
+ pi no openai+anthropic - ~/.pi/agent/models.json
98
+ opencode yes openai+anthropic vfing/glm-5.3-flash ~/.config/opencode/opencode.json
99
+ dsh yes openai+anthropic vfing · glm-5.3-flash ~/.dsh/settings.yaml
100
+ ```
101
+
102
+ ## Supported apps
103
+
104
+ | App | Config written | Protocols |
105
+ |---|---|---|
106
+ | [Claude Code](https://www.npmjs.com/package/@anthropic-ai/claude-code) | `~/.claude/settings.json` env block | anthropic |
107
+ | [Codex CLI](https://www.npmjs.com/package/@openai/codex) | `~/.codex/config.toml` + `auth.json` (Responses API only) | openai |
108
+ | [Oh My Pi](https://omp.sh) (omp) | `~/.omp/agent/models.yml` (comments preserved) | both |
109
+ | [pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) | `~/.pi/agent/models.json` + `settings.json` | both |
110
+ | [prime-agent](https://github.com/PrimeIntellect-ai/prime-agent) | `~/.prime/agent/models.json` + `settings.json` | both |
111
+ | [opencode](https://opencode.ai) | `~/.config/opencode/opencode.json` | both |
112
+ | [Hermes](https://pypi.org/project/hermes-agent/) | `~/.hermes/config.yaml` + `.env` (comments preserved) | both |
113
+ | WorkBuddy | `~/.workbuddy/models.json` + `settings.json` | openai |
114
+ | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) | `~/.dsh/settings.yaml` + `.credentials.yaml` | both |
115
+
116
+ An agent that is not installed is skipped, not guessed at. `--apps codex,omp` narrows a run;
117
+ `--apps` with an uninstalled agent forces it.
118
+
119
+ ## Import what you already configured
120
+
121
+ `import` reads every agent's config **and cc-switch's SQLite store** (read-only), resolves
122
+ inline and env-backed keys, and merges candidates that share a protocol + base URL — model
123
+ lists and source apps are unioned. Different protocols on the same host stay separate.
124
+
125
+ ```console
126
+ $ asw import --all
127
+ ID PROTOCOL BASE URL MODELS FROM KEY
128
+ ------------- --------- -------------------------------------- ------ --------- ---
129
+ any anthropic https://a-ocnfniawgw.cn-shanghai... 1 cc-switch yes
130
+ sub openai https://new.vfing.de/v1 18 omp yes
131
+ sub-anthropic anthropic https://new.vfing.de 18 omp yes
132
+ zhipu-glm anthropic https://open.bigmodel.cn/api/anthropic 1 cc-switch yes
133
+ imported sub · openai · https://new.vfing.de/v1 · 18 models [from omp]
134
+ imported zhipu-glm · anthropic · https://open.bigmodel.cn/api/anthropic · 1 models [from cc-switch]
135
+ ```
136
+
137
+ Without `--all` the list is a multi-select preview; nothing is written until you pick.
138
+
139
+ ## Model discovery filters
140
+
141
+ `--discover` lists model ids from the provider's `/v1/models`, then enriches each id from
142
+ models.dev. Reseller lists are noisy, so filters are persisted per provider and re-applied on
143
+ every `discover`:
144
+
145
+ ```bash
146
+ # snapshot duplicates (gpt-5.2-latest, glm-4.7-250414, ...) are DROPPED by default
147
+ # whenever the bare id is also listed; snapshot-only models are kept as-is
148
+ asw add -y --discover --exclude "*embedding*,*video*" --id myproxy ...
149
+ asw add -y --discover --no-dedup ... # keep duplicates
150
+ asw discover myproxy --include "gpt-*,glm-*" # update the persisted filter
151
+ asw discover myproxy --no-filter # clear it
152
+ ```
153
+
154
+ Explicit `--models` entries and the default model are never filtered out.
155
+
156
+ ## Commands
157
+
158
+ | Command | What it does |
159
+ |---|---|
160
+ | `agentsw` | interactive menu (no arguments) |
161
+ | `add` | add or update a provider; `--discover` fills the model list |
162
+ | `import [--all]` | adopt providers from your agents' configs and cc-switch |
163
+ | `list` / `status` | configured providers / what each agent points at |
164
+ | `use <id>` | switch every detected agent; `-a codex,omp`, `-m <model>`, `--dry-run` |
165
+ | `sync` | re-apply the active provider (after an agent update, say) |
166
+ | `discover <id> [--sync]` | refresh the model list + metadata from `/v1/models` |
167
+ | `models [query]` | search the models.dev catalog |
168
+ | `refresh` | re-fetch metadata for every configured provider |
169
+ | `prune <id>` / `remove <id> [--prune]` | remove from app configs / from the store |
170
+ | `apps` / `install <app>` / `upgrade` | agent version manager |
171
+
172
+ ```bash
173
+ asw use myproxy -a codex,omp -m glm-5.2
174
+ asw discover myproxy --sync
175
+ asw remove myproxy --prune
176
+ ```
177
+
178
+ ## How your configs are treated
179
+
180
+ - Every modified file is backed up first to `~/.config/agentsw/backups/`.
181
+ - The provider store lives at `~/.config/agentsw/config.json` (mode 0600).
182
+ - A sync overwrites only the fields agentsw owns. Unmodeled provider-level keys and per-model
183
+ extras survive; an owned field that stops applying is cleared rather than left stale, and a
184
+ per-model `api`/`baseUrl` override that contradicts the route is dropped (it would silently
185
+ win over the entry) and reported.
186
+ - YAML configs (omp, Hermes, dsh) keep their comments. `~/.codex/config.toml` does not —
187
+ TOML round-trip drops them, which is why the backup happens first.
188
+ - OpenAI endpoints come in two wires: `/v1/chat/completions` and `/v1/responses`. Imported
189
+ providers keep whatever the config declared — on the provider or on its models — the
190
+ interactive `add` asks, and `--openai-api responses` sets it by hand. A sync never
191
+ downgrades an existing responses entry.
192
+ - Codex only speaks the Responses API; a chat-completions-only endpoint cannot be used with
193
+ it, and the sync output says so.
194
+ - DeepSeek Harness keeps secrets out of `settings.yaml`: the route carries
195
+ `apiKeyEnv: AGENTSW_<ID>_API_KEY`, the key goes into `$DSH_HOME/.credentials.yaml`
196
+ (`refs:`, mode 0600). `$DSH_HOME` defaults to `~/.dsh`.
197
+ - cc-switch's database is opened read-only and never written to.
198
+ - The apps manager (install/upgrade) currently targets macOS/Linux.
199
+
200
+ ## Language
201
+
202
+ The first interactive launch asks for English or 简体中文 and saves the choice; the menu can
203
+ change it later. A one-run override wins over the saved choice, and with neither the system
204
+ locale decides.
205
+
206
+ ```bash
207
+ asw --lang zh-CN
208
+ AGENTSW_LANG=en asw
209
+ ```
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ npm install
215
+ npm run build # tsc -> dist/
216
+ npm test # node:test suite (filter semantics, adapter roundtrips)
217
+ npm run dev -- ... # run from source via tsx
218
+ ```
219
+
220
+ Each agent is one adapter in [`src/targets/`](./src/targets) implementing
221
+ `apply` / `prune` / `current` / `candidates`; read-only import sources live in
222
+ [`src/sources/`](./src/sources). Adding an app is one file plus a line in
223
+ [`src/targets/index.ts`](./src/targets/index.ts).
224
+
225
+ ## License
226
+
227
+ MIT
@@ -0,0 +1,221 @@
1
+ <div align="center">
2
+
3
+ # agentsw
4
+
5
+ **一个供应商,管遍所有编码智能体——在终端里。**
6
+
7
+ 一个命令行工具:把 OpenAI 协议 / Anthropic 协议的供应商——base URL、密钥、模型列表、
8
+ 元数据——集中管理,再把当前选中的那个写进九个智能体各自的配置文件,
9
+ 且不动你手工调过的设置。
10
+
11
+ [![npm version](https://img.shields.io/npm/v/agentsw?logo=npm&logoColor=white)](https://www.npmjs.com/package/agentsw)
12
+ [![CI](https://img.shields.io/github/actions/workflow/status/tchivs/agentsw/ci.yml?branch=main&logo=github&logoColor=white&label=CI)](https://github.com/tchivs/agentsw/actions/workflows/ci.yml)
13
+ [![node version](https://img.shields.io/node/v/agentsw?logo=nodedotjs&logoColor=white)](#安装)
14
+ [![license](https://img.shields.io/npm/l/agentsw?color=blue)](./LICENSE)
15
+
16
+ [安装](#安装) · [快速开始](#快速开始) · [支持的应用](#支持的应用) · [命令](#命令) · [English](./README.md)
17
+
18
+ </div>
19
+
20
+ ```console
21
+ $ asw use vfing -m glm-5.3-flash
22
+ switching to vfing (openai) · default model glm-5.3-flash
23
+
24
+ skip claude Claude Code does not support openai-protocol providers
25
+ ok codex ~/.codex/config.toml, ~/.codex/auth.json
26
+ backup: ~/.config/agentsw/backups/config.toml.2026-08-31T15-58-24-678Z
27
+ ok omp ~/.omp/agent/models.yml
28
+ select in omp with: omp --model vfing/glm-5.3-flash
29
+ skip pi pi not detected (pass --apps pi to force)
30
+ ok opencode ~/.config/opencode/opencode.json
31
+ ok dsh ~/.dsh/settings.yaml, ~/.dsh/.credentials.yaml
32
+ select in dsh with the model picker, or run: dsh web
33
+ ```
34
+
35
+ `agentsw` 和短名 `asw` 是同一个可执行文件。
36
+
37
+ ## 为什么是 CLI
38
+
39
+ - **它能去桌面应用去不了的地方。** 无头服务器、容器、devcontainer、CI。
40
+ `npx agentsw use myproxy -a codex,omp` 在开机脚本里是一行;GUI 点九次不是。
41
+ - **`--dry-run` 直接打 diff。** 写盘前先看清楚要改什么,或者把 diff 丢进 code review。
42
+ 无论如何每个文件都会先备份。
43
+ - **默认不破坏现有配置。** 只覆盖 agentsw 自己管理的字段:它不建模的供应商级键
44
+ (`authHeader`、`headers`、`compat`、`discovery`)、`thinkingLevelMap` 之类的模型级字段、
45
+ 以及 YAML 注释,都会在重新同步后原样保留。
46
+ - **写的是元数据,不只是模型 id。** 转售商 `/v1/models` 只返回裸 id;agentsw 从
47
+ [models.dev](https://models.dev) 补全,并按各应用真正读取的字段下发:pi/prime 的
48
+ `thinkingLevelMap`、opencode 的 `limit`/`attachment`、omp 的 `contextWindow`/`maxTokens`、
49
+ dsh 的 `reasoningEfforts`/`input`。
50
+ - **接管你已有的配置——包括 [cc-switch](https://github.com/farion1231/cc-switch)。**
51
+ `import` 会扫描所有已安装的智能体**以及 cc-switch 自己的存储**,合并重复项
52
+ (相同协议 + base URL),并且对两者都只读不写。
53
+ - **区分接口形态。** `/v1/chat/completions` 与 `/v1/responses` 是两个不同端点;
54
+ agentsw 记录供应商说的是哪一种,并且绝不把可用的 responses 降级。
55
+
56
+ 想要 GUI、MCP/Skills 同步、用量仪表盘或本地故障转移代理?用
57
+ [cc-switch](https://github.com/farion1231/cc-switch)——那些它做得很好;agentsw 从它导入,
58
+ 而不是跟它抢。
59
+
60
+ ## 安装
61
+
62
+ ```bash
63
+ npx agentsw # 免安装,直接打开交互菜单
64
+ npm install -g agentsw # 需要 Node >= 22.12
65
+ ```
66
+
67
+ ## 快速开始
68
+
69
+ ```bash
70
+ agentsw # 不带参数:交互菜单(添加、导入、切换、状态、应用管理)
71
+ ```
72
+
73
+ 存储为空时,首次运行会询问是否导入各智能体里已配置的供应商,并让你选择
74
+ English 或简体中文。想用参数跑?同样的流程,全自动:
75
+
76
+ ```bash
77
+ # 添加供应商,并让它自己发现模型列表
78
+ asw add -y --id vfing --protocol openai --openai-api responses \
79
+ --base-url https://api.example.com/v1 --api-key sk-... --discover
80
+
81
+ asw use vfing # 把所有检测到的智能体指向它
82
+ asw use vfing --dry-run # ……或者只看 diff
83
+ asw status # 各智能体当前指向什么
84
+ ```
85
+
86
+ ```console
87
+ $ asw status
88
+ config: ~/.config/agentsw/config.json
89
+ active provider: vfing
90
+
91
+ APP FOUND PROTOCOLS CURRENT CONFIG
92
+ --------- ----- ---------------- ------------------------------------ ---------------------------
93
+ claude yes anthropic - ~/.claude/settings.json
94
+ codex yes openai vfing · glm-5.3-flash ~/.codex/config.toml
95
+ omp yes openai+anthropic providers: sub, sub-anthropic, vfing ~/.omp/agent/models.yml
96
+ pi no openai+anthropic - ~/.pi/agent/models.json
97
+ opencode yes openai+anthropic vfing/glm-5.3-flash ~/.config/opencode/opencode.json
98
+ dsh yes openai+anthropic vfing · glm-5.3-flash ~/.dsh/settings.yaml
99
+ ```
100
+
101
+ ## 支持的应用
102
+
103
+ | 应用 | 写入的配置 | 协议 |
104
+ |---|---|---|
105
+ | [Claude Code](https://www.npmjs.com/package/@anthropic-ai/claude-code) | `~/.claude/settings.json` env 块 | anthropic |
106
+ | [Codex CLI](https://www.npmjs.com/package/@openai/codex) | `~/.codex/config.toml` + `auth.json`(仅 Responses API) | openai |
107
+ | [Oh My Pi](https://omp.sh) (omp) | `~/.omp/agent/models.yml`(保留注释) | 双协议 |
108
+ | [pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) | `~/.pi/agent/models.json` + `settings.json` | 双协议 |
109
+ | [prime-agent](https://github.com/PrimeIntellect-ai/prime-agent) | `~/.prime/agent/models.json` + `settings.json` | 双协议 |
110
+ | [opencode](https://opencode.ai) | `~/.config/opencode/opencode.json` | 双协议 |
111
+ | [Hermes](https://pypi.org/project/hermes-agent/) | `~/.hermes/config.yaml` + `.env`(保留注释) | 双协议 |
112
+ | WorkBuddy | `~/.workbuddy/models.json` + `settings.json` | openai |
113
+ | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) | `~/.dsh/settings.yaml` + `.credentials.yaml` | 双协议 |
114
+
115
+ 未安装的应用会被跳过,而不是瞎猜。`--apps codex,omp` 只跑指定应用;
116
+ 对未检测到的应用显式传 `--apps` 可强制写入。
117
+
118
+ ## 导入你已有的配置
119
+
120
+ `import` 读取每个智能体的配置**以及 cc-switch 的 SQLite 存储**(只读),解析内联或环境变量
121
+ 形式的密钥,并把「协议 + base URL」相同的候选合并——模型列表与来源应用取并集。
122
+ 同一域名下的不同协议不会误合并。
123
+
124
+ ```console
125
+ $ asw import --all
126
+ ID PROTOCOL BASE URL MODELS FROM KEY
127
+ ------------- --------- -------------------------------------- ------ --------- ---
128
+ any anthropic https://a-ocnfniawgw.cn-shanghai... 1 cc-switch yes
129
+ sub openai https://new.vfing.de/v1 18 omp yes
130
+ sub-anthropic anthropic https://new.vfing.de 18 omp yes
131
+ zhipu-glm anthropic https://open.bigmodel.cn/api/anthropic 1 cc-switch yes
132
+ imported sub · openai · https://new.vfing.de/v1 · 18 models [from omp]
133
+ imported zhipu-glm · anthropic · https://open.bigmodel.cn/api/anthropic · 1 models [from cc-switch]
134
+ ```
135
+
136
+ 不加 `--all` 时先展示多选预览,选定之前不写任何文件。
137
+
138
+ ## 模型发现过滤
139
+
140
+ `--discover` 从供应商 `/v1/models` 拉取模型 id,再从 models.dev 补全元数据。
141
+ 转售商列表噪音大,过滤规则持久化在供应商上,每次 `discover` 自动复用:
142
+
143
+ ```bash
144
+ # 快照重复项(gpt-5.2-latest、glm-4.7-250414 等)默认丢弃——
145
+ # 仅当裸名也在列表中才算重复;只有快照没有裸名的模型原样保留
146
+ asw add -y --discover --exclude "*embedding*,*video*" --id myproxy ...
147
+ asw add -y --discover --no-dedup ... # 保留重复项
148
+ asw discover myproxy --include "gpt-*,glm-*" # 更新持久化过滤规则
149
+ asw discover myproxy --no-filter # 清除规则
150
+ ```
151
+
152
+ 手动 `--models` 指定的 id 和默认模型永不被过滤。
153
+
154
+ ## 命令
155
+
156
+ | 命令 | 作用 |
157
+ |---|---|
158
+ | `agentsw` | 交互菜单(不带参数) |
159
+ | `add` | 添加或更新供应商;`--discover` 自动填模型列表 |
160
+ | `import [--all]` | 接管各智能体配置与 cc-switch 里已有的供应商 |
161
+ | `list` / `status` | 已配置的供应商 / 各智能体当前指向 |
162
+ | `use <id>` | 切换所有检测到的智能体;`-a codex,omp`、`-m <model>`、`--dry-run` |
163
+ | `sync` | 重新应用当前供应商(比如某个智能体升级之后) |
164
+ | `discover <id> [--sync]` | 从 `/v1/models` 刷新模型列表与元数据 |
165
+ | `models [query]` | 搜索 models.dev 目录 |
166
+ | `refresh` | 重新拉取所有供应商的元数据 |
167
+ | `prune <id>` / `remove <id> [--prune]` | 从各应用配置中清除 / 从存储中删除 |
168
+ | `apps` / `install <app>` / `upgrade` | 智能体版本管理 |
169
+
170
+ ```bash
171
+ asw use myproxy -a codex,omp -m glm-5.2
172
+ asw discover myproxy --sync
173
+ asw remove myproxy --prune
174
+ ```
175
+
176
+ ## 它如何对待你的配置
177
+
178
+ - 每个被修改的文件先备份到 `~/.config/agentsw/backups/`。
179
+ - 供应商存储位于 `~/.config/agentsw/config.json`(权限 0600)。
180
+ - 同步只覆盖 agentsw 自己管理的字段:未建模的供应商级键与模型级扩展字段保留;
181
+ 自己管理但本次不再写出的字段会被清除而非留成陈旧值;与新路由矛盾的模型级
182
+ `api`/`baseUrl` 覆盖会被删除(否则它会静默盖过供应商条目)并在输出中点名。
183
+ - YAML 配置(omp、Hermes、dsh)保留注释;`~/.codex/config.toml` 不行——TOML 往返会丢注释,
184
+ 所以才先备份。
185
+ - OpenAI 端点有两种接口形态:`/v1/chat/completions` 与 `/v1/responses`。导入时沿用配置里
186
+ 声明的形态(供应商级或模型级),交互式 `add` 会询问,也可用 `--openai-api responses`
187
+ 手动指定;同步绝不把已有的 responses 端点降级。
188
+ - Codex 只支持 Responses API;仅提供 chat-completions 的端点无法用于 Codex,同步输出会提示。
189
+ - DeepSeek Harness 不把密钥写进 `settings.yaml`:路由里只保存
190
+ `apiKeyEnv: AGENTSW_<ID>_API_KEY` 引用,密钥写入 `$DSH_HOME/.credentials.yaml`
191
+ 的 `refs:`(权限 0600)。`$DSH_HOME` 默认为 `~/.dsh`。
192
+ - cc-switch 的数据库以只读方式打开,永不写入。
193
+ - 安装管理器(install/upgrade)目前仅支持 macOS/Linux。
194
+
195
+ ## 语言
196
+
197
+ 首次打开交互菜单会选择 English 或简体中文并持久化,之后可在菜单中切换。
198
+ 单次覆盖优先于已保存设置;两者都没有时按系统语言判断。
199
+
200
+ ```bash
201
+ asw --lang zh-CN
202
+ AGENTSW_LANG=en asw
203
+ ```
204
+
205
+ ## 开发
206
+
207
+ ```bash
208
+ npm install
209
+ npm run build # tsc -> dist/
210
+ npm test # node:test 测试(过滤语义、适配器往返)
211
+ npm run dev -- ... # 通过 tsx 从源码运行
212
+ ```
213
+
214
+ 每个智能体是 [`src/targets/`](./src/targets) 下的一个适配器,实现
215
+ `apply` / `prune` / `current` / `candidates`;只读的导入来源放在
216
+ [`src/sources/`](./src/sources)。新增一个应用 = 一个文件 +
217
+ [`src/targets/index.ts`](./src/targets/index.ts) 里的一行。
218
+
219
+ ## 许可证
220
+
221
+ MIT
package/dist/apps.js ADDED
@@ -0,0 +1,178 @@
1
+ import { execFileSync, execSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { home, readJsonIfExists } from "./fsutil.js";
5
+ export const appPackages = [
6
+ {
7
+ id: "claude",
8
+ name: "Claude Code",
9
+ binary: "claude",
10
+ latest: { kind: "npm", name: "@anthropic-ai/claude-code" },
11
+ installCmd: "curl -fsSL https://claude.ai/install.sh | bash",
12
+ upgradeCmd: "claude update",
13
+ },
14
+ {
15
+ id: "codex",
16
+ name: "Codex CLI",
17
+ binary: "codex",
18
+ latest: { kind: "npm", name: "@openai/codex" },
19
+ installCmd: "npm install -g @openai/codex@latest",
20
+ },
21
+ {
22
+ id: "omp",
23
+ name: "Oh My Pi",
24
+ binary: "omp",
25
+ latest: { kind: "brew", name: "omp" },
26
+ installCmd: "brew install omp",
27
+ upgradeCmd: "brew upgrade omp",
28
+ },
29
+ {
30
+ id: "pi",
31
+ name: "pi",
32
+ binary: "pi",
33
+ latest: { kind: "npm", name: "@earendil-works/pi-coding-agent" },
34
+ installCmd: "npm install -g @earendil-works/pi-coding-agent@latest",
35
+ },
36
+ {
37
+ id: "prime",
38
+ name: "prime-agent",
39
+ binary: "prime-agent",
40
+ latest: { kind: "github", name: "PrimeIntellect-ai/prime-agent" },
41
+ installCmd: "curl -fsSL https://raw.githubusercontent.com/PrimeIntellect-ai/prime-agent/main/install.sh | bash",
42
+ },
43
+ {
44
+ id: "opencode",
45
+ name: "opencode",
46
+ binary: "opencode",
47
+ latest: { kind: "npm", name: "opencode-ai" },
48
+ installCmd: "curl -fsSL https://opencode.ai/install | bash",
49
+ upgradeCmd: "opencode upgrade",
50
+ },
51
+ {
52
+ id: "hermes",
53
+ name: "Hermes",
54
+ binary: "hermes",
55
+ latest: { kind: "pypi", name: "hermes-agent" },
56
+ installCmd: "uv tool install hermes-agent || pipx install hermes-agent",
57
+ upgradeCmd: "uv tool upgrade hermes-agent || pipx upgrade hermes-agent",
58
+ },
59
+ {
60
+ id: "workbuddy",
61
+ name: "WorkBuddy",
62
+ // Electron desktop app: version from its data dir; managed by its own updater.
63
+ localVersion: () => {
64
+ const j = readJsonIfExists(path.join(home, ".workbuddy", "last-launch.json"));
65
+ const v = (j?.version ?? j?.appVersion);
66
+ if (v)
67
+ return v;
68
+ const r = readJsonIfExists(path.join(home, ".workbuddy", "app", "renderer-version.json"));
69
+ return r?.version ?? undefined;
70
+ },
71
+ },
72
+ {
73
+ id: "dsh",
74
+ name: "DeepSeek Harness",
75
+ binary: "dsh",
76
+ latest: { kind: "npm", name: "@deepseek-ai/dsh" },
77
+ installCmd: "npm install -g @deepseek-ai/dsh@latest",
78
+ },
79
+ ];
80
+ const SEMVERISH = /\d+\.\d+(\.\d+)?([-.+][\w.-]+)?/;
81
+ export function installedVersion(app) {
82
+ if (app.binary) {
83
+ try {
84
+ const out = execFileSync(app.binary, app.versionArgs ?? ["--version"], {
85
+ encoding: "utf8",
86
+ timeout: 15000,
87
+ stdio: ["ignore", "pipe", "pipe"],
88
+ });
89
+ return SEMVERISH.exec(out)?.[0];
90
+ }
91
+ catch {
92
+ // binary present but probe failed (e.g. runtime version gate) -> installed, version unknown
93
+ if (binaryOnPath(app.binary))
94
+ return "?";
95
+ return app.localVersion?.();
96
+ }
97
+ }
98
+ return app.localVersion?.();
99
+ }
100
+ async function fetchJson(url, headers) {
101
+ const res = await fetch(url, { headers, signal: AbortSignal.timeout(15000) });
102
+ if (!res.ok)
103
+ throw new Error(`${url} -> ${res.status}`);
104
+ return res.json();
105
+ }
106
+ export async function latestVersion(app) {
107
+ const src = app.latest;
108
+ if (!src)
109
+ return undefined;
110
+ try {
111
+ switch (src.kind) {
112
+ case "npm": {
113
+ const data = (await fetchJson(`https://registry.npmjs.org/${src.name}/latest`));
114
+ return data.version;
115
+ }
116
+ case "pypi": {
117
+ const data = (await fetchJson(`https://pypi.org/pypi/${src.name}/json`));
118
+ return data.info?.version;
119
+ }
120
+ case "github": {
121
+ const data = (await fetchJson(`https://api.github.com/repos/${src.name}/releases/latest`, {
122
+ "user-agent": "agentsw",
123
+ }));
124
+ return data.tag_name ? (SEMVERISH.exec(data.tag_name)?.[0] ?? data.tag_name) : undefined;
125
+ }
126
+ case "brew": {
127
+ // Local tap metadata first (works for taps; refreshed by `brew update`), then core API.
128
+ try {
129
+ const out = execSync(`brew info --json=v2 ${src.name}`, { encoding: "utf8", timeout: 30000 });
130
+ const data = JSON.parse(out);
131
+ const stable = data.formulae?.[0]?.versions?.stable;
132
+ if (stable)
133
+ return stable;
134
+ }
135
+ catch {
136
+ /* brew missing or formula unknown locally */
137
+ }
138
+ const data = (await fetchJson(`https://formulae.brew.sh/api/formula/${src.name}.json`));
139
+ return data.versions?.stable;
140
+ }
141
+ }
142
+ }
143
+ catch {
144
+ return undefined;
145
+ }
146
+ }
147
+ /** numeric-aware semver-ish comparison; true when b is newer than a */
148
+ export function isNewer(installed, latest) {
149
+ const pa = installed.split(/[.+-]/).map(Number);
150
+ const pb = latest.split(/[.+-]/).map(Number);
151
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
152
+ const a = pa[i] ?? 0;
153
+ const b = pb[i] ?? 0;
154
+ if (Number.isNaN(a) || Number.isNaN(b))
155
+ break;
156
+ if (b > a)
157
+ return true;
158
+ if (b < a)
159
+ return false;
160
+ }
161
+ return false;
162
+ }
163
+ /** Run an install/upgrade command with live output. Throws on nonzero exit. */
164
+ export function runShell(command) {
165
+ execSync(command, { stdio: "inherit", env: process.env });
166
+ }
167
+ export function binaryOnPath(binary) {
168
+ const dirs = (process.env.PATH ?? "").split(path.delimiter);
169
+ return dirs.some((d) => {
170
+ try {
171
+ fs.accessSync(path.join(d, binary), fs.constants.X_OK);
172
+ return true;
173
+ }
174
+ catch {
175
+ return false;
176
+ }
177
+ });
178
+ }