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 +21 -0
- package/README.md +227 -0
- package/README.zh-CN.md +221 -0
- package/dist/apps.js +178 -0
- package/dist/commands.js +661 -0
- package/dist/discover.js +21 -0
- package/dist/filter.js +61 -0
- package/dist/fsutil.js +56 -0
- package/dist/i18n.js +214 -0
- package/dist/import.js +66 -0
- package/dist/index.js +113 -0
- package/dist/menu.js +157 -0
- package/dist/modelsdev.js +110 -0
- package/dist/slug.js +18 -0
- package/dist/sources/ccswitch.js +151 -0
- package/dist/store.js +23 -0
- package/dist/targets/claudecode.js +96 -0
- package/dist/targets/codex.js +136 -0
- package/dist/targets/dsh.js +296 -0
- package/dist/targets/hermes.js +173 -0
- package/dist/targets/index.js +26 -0
- package/dist/targets/omp.js +170 -0
- package/dist/targets/opencode.js +135 -0
- package/dist/targets/pistyle.js +208 -0
- package/dist/targets/types.js +1 -0
- package/dist/targets/wire.js +109 -0
- package/dist/targets/workbuddy.js +143 -0
- package/dist/types.js +1 -0
- package/package.json +55 -0
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
|
+
[](https://www.npmjs.com/package/agentsw)
|
|
12
|
+
[](https://github.com/tchivs/agentsw/actions/workflows/ci.yml)
|
|
13
|
+
[](#install)
|
|
14
|
+
[](./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
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# agentsw
|
|
4
|
+
|
|
5
|
+
**一个供应商,管遍所有编码智能体——在终端里。**
|
|
6
|
+
|
|
7
|
+
一个命令行工具:把 OpenAI 协议 / Anthropic 协议的供应商——base URL、密钥、模型列表、
|
|
8
|
+
元数据——集中管理,再把当前选中的那个写进九个智能体各自的配置文件,
|
|
9
|
+
且不动你手工调过的设置。
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/agentsw)
|
|
12
|
+
[](https://github.com/tchivs/agentsw/actions/workflows/ci.yml)
|
|
13
|
+
[](#安装)
|
|
14
|
+
[](./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
|
+
}
|