agentsw 0.7.1 → 0.8.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/README.md +106 -15
- package/README.zh-CN.md +93 -14
- package/dist/apps.js +48 -44
- package/dist/commands.js +278 -90
- package/dist/config-transaction.js +169 -84
- package/dist/discover.js +55 -17
- package/dist/envfile.js +101 -20
- package/dist/fsutil.js +109 -14
- package/dist/gateway.js +201 -0
- package/dist/i18n.js +24 -7
- package/dist/import.js +2 -2
- package/dist/index.js +42 -2
- package/dist/jsonc.js +4 -10
- package/dist/menu.js +22 -1
- package/dist/metadata.js +400 -0
- package/dist/modelsdev.js +35 -11
- package/dist/provider-identity.js +25 -0
- package/dist/remove.js +84 -38
- package/dist/rename.js +91 -58
- package/dist/store.js +38 -7
- package/dist/target-transaction.js +25 -0
- package/dist/targets/claudecode.js +12 -3
- package/dist/targets/codex.js +34 -3
- package/dist/targets/dsh.js +117 -65
- package/dist/targets/hermes.js +98 -49
- package/dist/targets/omp.js +11 -47
- package/dist/targets/opencode.js +27 -6
- package/dist/targets/pistyle.js +12 -8
- package/dist/targets/wire.js +15 -9
- package/dist/targets/workbuddy.js +151 -76
- package/dist/yaml.js +39 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ switching to vfing (openai) · default model glm-5.3-flash
|
|
|
23
23
|
|
|
24
24
|
skip claude Claude Code does not support openai-protocol providers
|
|
25
25
|
ok codex ~/.codex/config.toml, ~/.codex/auth.json
|
|
26
|
-
backup: ~/.config/agentsw/backups/
|
|
26
|
+
backup: ~/.config/agentsw/backups/transaction-<unique>
|
|
27
27
|
ok omp ~/.omp/agent/models.yml
|
|
28
28
|
select in omp with: omp --model vfing/glm-5.3-flash
|
|
29
29
|
skip pi pi not detected (pass --apps pi to force)
|
|
@@ -39,18 +39,18 @@ ok dsh ~/.dsh/settings.yaml, ~/.dsh/.credentials.yaml
|
|
|
39
39
|
- **It runs where a desktop app cannot.** Headless servers, containers, devcontainers, CI.
|
|
40
40
|
`npx agentsw use myproxy -a codex,omp` is one line in a provisioning script; nine GUI
|
|
41
41
|
clicks are not.
|
|
42
|
-
- **`--dry-run` prints diffs.**
|
|
43
|
-
|
|
42
|
+
- **`--dry-run` prints redacted diffs.** Preview changes without writing files or creating
|
|
43
|
+
backups. Existing configuration files are backed up when actual changes are committed.
|
|
44
44
|
- **Non-destructive by design.** Only the fields agentsw owns are rewritten. Provider-level
|
|
45
45
|
keys it does not model — `authHeader`, `headers`, `compat`, `discovery` — per-model extras
|
|
46
46
|
like `thinkingLevelMap`, and YAML comments all survive a re-sync.
|
|
47
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
|
|
48
|
+
agentsw enriches them from [models.dev](https://models.dev), automatically fills meaningful gaps from AI Gateway when needed, and writes what each app
|
|
49
49
|
actually reads: `thinkingLevelMap` for pi/prime, `limit`/`attachment` for opencode,
|
|
50
50
|
`contextWindow`/`maxTokens` for omp, `reasoningEfforts`/`input` for dsh.
|
|
51
51
|
- **Adopts what you already have** — including [cc-switch](https://github.com/farion1231/cc-switch).
|
|
52
52
|
`import` scans every installed agent *and* cc-switch's own store, merges duplicates
|
|
53
|
-
(same protocol +
|
|
53
|
+
(same protocol + normalized endpoint + credentials), and never writes back to either.
|
|
54
54
|
- **Wire-aware.** `/v1/chat/completions` and `/v1/responses` are different endpoints;
|
|
55
55
|
agentsw tracks which one a provider speaks and never downgrades a working one.
|
|
56
56
|
|
|
@@ -62,7 +62,7 @@ imports from it rather than competing with it.
|
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
npx agentsw # zero install: opens the interactive menu
|
|
65
|
-
npm install -g agentsw # requires Node >= 22.
|
|
65
|
+
npm install -g agentsw # requires Node >= 22.13 (built-in SQLite without an extra flag)
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
The provider sync and config management work on Linux, macOS, and Windows. On Windows,
|
|
@@ -139,13 +139,15 @@ imported sub · openai · https://new.vfing.de/v1 · 18 models [from omp]
|
|
|
139
139
|
imported zhipu-glm · anthropic · https://open.bigmodel.cn/api/anthropic · 1 models [from cc-switch]
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
In an interactive terminal, omitting `--all` opens a multi-select preview before provider
|
|
143
|
+
records are saved. `--all`, or non-interactive stdin, imports all eligible new entries;
|
|
144
|
+
use `--all` explicitly in scripts. Model discovery may update the local metadata cache.
|
|
143
145
|
|
|
144
146
|
## Model discovery filters
|
|
145
147
|
|
|
146
148
|
`--discover` lists model ids from the provider's `/v1/models`, then enriches each id from
|
|
147
|
-
models.dev
|
|
148
|
-
every `discover`:
|
|
149
|
+
models.dev first, with automatic AI Gateway supplementation when needed. Reseller lists are noisy,
|
|
150
|
+
so filters are persisted per provider and re-applied on every `discover`:
|
|
149
151
|
|
|
150
152
|
```bash
|
|
151
153
|
# snapshot duplicates (gpt-5.2-latest, glm-4.7-250414, ...) are DROPPED by default
|
|
@@ -158,6 +160,77 @@ asw discover myproxy --no-filter # clear it
|
|
|
158
160
|
|
|
159
161
|
Explicit `--models` entries and the default model are never filtered out.
|
|
160
162
|
|
|
163
|
+
## Automatic model metadata
|
|
164
|
+
|
|
165
|
+
**No extra flags are needed.** New providers and providers without a saved mode use `auto`:
|
|
166
|
+
models.dev is checked first, then the public
|
|
167
|
+
[AI Gateway model catalog](https://vercel.com/docs/ai-gateway/models-and-providers#dynamic-model-discovery)
|
|
168
|
+
is consulted only for missing core parameters (context window, maximum output, reasoning or image input),
|
|
169
|
+
to refresh unchanged Gateway-supplied fields that models.dev has not replaced, or to verify an identity
|
|
170
|
+
conflict in previously tracked automatic values. Missing names, prices, optional input limits or
|
|
171
|
+
reasoning-effort lists alone do not trigger a Gateway lookup.
|
|
172
|
+
agentsw directly fetches `https://ai-gateway.vercel.sh/v1/models` without provider credentials or an AI SDK dependency.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Refresh only saved models using the saved mode (auto if unset)
|
|
176
|
+
# No model discovery or agent config writes
|
|
177
|
+
asw refresh --provider myproxy
|
|
178
|
+
|
|
179
|
+
# Inspect effective mode, sources, conflicts and reference pricing (no API key)
|
|
180
|
+
asw models --provider myproxy --metadata
|
|
181
|
+
|
|
182
|
+
# Custom or ambiguous names need explicit aliases; wire model IDs stay unchanged
|
|
183
|
+
asw refresh --provider myproxy --metadata-mode auto \
|
|
184
|
+
--gateway-models '{"my-model":"openai/gpt-5.4"}'
|
|
185
|
+
|
|
186
|
+
# Always consult Gateway, even when core parameters are complete
|
|
187
|
+
asw refresh --provider myproxy --metadata-mode on
|
|
188
|
+
|
|
189
|
+
# Stop future Gateway fetches; retain saved model parameters
|
|
190
|
+
asw refresh --provider myproxy --metadata-mode off
|
|
191
|
+
|
|
192
|
+
# Return an explicitly disabled provider to automatic mode
|
|
193
|
+
asw refresh --provider myproxy --metadata-mode auto
|
|
194
|
+
|
|
195
|
+
# Preview and apply separately; sync does not query catalogs
|
|
196
|
+
asw sync --provider myproxy --dry-run
|
|
197
|
+
asw sync --provider myproxy
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The menu's **Configure and refresh model metadata** action offers **Automatic (recommended)**,
|
|
201
|
+
**Always supplement**, and **Disabled**, with the provider's current mode selected. Onboarding has no extra prompts.
|
|
202
|
+
`add`, `quick`, `discover`, `import`, and `refresh` accept `--metadata-mode <auto|on|off>`.
|
|
203
|
+
The existing `--gateway-metadata` and `--no-gateway-metadata` flags remain explicit `on` and `off` choices.
|
|
204
|
+
Omitted options preserve saved settings: an existing `false` remains off until you select `auto` or `on`.
|
|
205
|
+
Invalid modes and conflicting mode/boolean flags are rejected before fetching or saving.
|
|
206
|
+
Without `--provider`, `refresh` applies to all providers, including any supplied mode.
|
|
207
|
+
`--gateway-models` replaces the whole alias map (`{}` clears it); aliases do not change the saved mode or override `off`.
|
|
208
|
+
Import still skips already configured accounts.
|
|
209
|
+
|
|
210
|
+
- **Field-level priority:** manual values > models.dev > Gateway gap filling. Tracked automatic values may
|
|
211
|
+
refresh; subsequent manual edits are retained. Legacy values without provenance are conservatively treated
|
|
212
|
+
as manual overrides. Unmodeled extension fields survive enrichment too.
|
|
213
|
+
- **Exact identities:** automatic lookup can map a bare, case-sensitive ID (no `/`) to a unique Gateway
|
|
214
|
+
`creator/model` identity when models.dev evidence is absent or agrees. Ambiguous creator evidence in either
|
|
215
|
+
catalog is rejected; custom or ambiguous names need explicit aliases. Qualified IDs never have their prefixes
|
|
216
|
+
stripped, and matching never guesses from substrings, case folding or versions. Auto/on mode also uses conservative
|
|
217
|
+
exact models.dev matching; off retains the legacy models.dev lookup. Unmatched models remain usable.
|
|
218
|
+
Alias changes clear unchanged automatic values tied to confirmed superseded identities, never manual edits.
|
|
219
|
+
- **No routing changes:** metadata never adds available models or changes IDs, protocols, URLs, defaults or
|
|
220
|
+
accounts. Gateway capabilities are reference specs, not proof of reseller support. `supported_specifications`
|
|
221
|
+
is not a URL version.
|
|
222
|
+
- **Reference-only prices:** Gateway prices are converted to USD per million tokens and stored separately in
|
|
223
|
+
`metadata.gateway.referenceCost`, not effective provider `cost` or runtime agent pricing. Variable/tiered
|
|
224
|
+
pricing is flagged separately.
|
|
225
|
+
Auto does not fetch solely to refresh this reference snapshot: check `metadata.gateway.fetchedAt`, or use
|
|
226
|
+
`refresh --provider <id> --metadata-mode on` to request fresh reference prices.
|
|
227
|
+
- **Auditable and optional:** `metadata.fields` records origins, value snapshots and timestamps;
|
|
228
|
+
`metadata.conflicts` records disagreements with retained values. This audit stays in agentsw, never runtime agent configs.
|
|
229
|
+
Gateway has a separate 24-hour cache; failures use stale cached data or skip supplementation.
|
|
230
|
+
- **Automatic lookup is not automatic sync:** enrichment updates saved metadata during add/quick/discover/import/refresh.
|
|
231
|
+
It does not add an automatic push to agents. Sync behavior is unchanged: ordinary `sync` only writes saved settings
|
|
232
|
+
and fetches neither model lists nor metadata catalogs.
|
|
233
|
+
|
|
161
234
|
## Commands
|
|
162
235
|
|
|
163
236
|
| Command | What it does |
|
|
@@ -173,7 +246,8 @@ Explicit `--models` entries and the default model are never filtered out.
|
|
|
173
246
|
| `sync` | re-apply the active provider (after an agent update, say) |
|
|
174
247
|
| `discover <id> [--sync]` | refresh the model list + metadata from `/v1/models` |
|
|
175
248
|
| `models [query]` | search the models.dev catalog |
|
|
176
|
-
| `refresh` |
|
|
249
|
+
| `refresh [--provider <id>]` | refresh saved metadata and optionally configure Gateway; keep the model list |
|
|
250
|
+
| `models --provider <id> --metadata` | inspect field sources, conflicts and reference pricing as JSON |
|
|
177
251
|
| `prune <id>` / `remove <id> [--prune]` | remove from app configs / from the store |
|
|
178
252
|
| `remove <id> --apps omp` | delete only the selected app's entry; supports `--dry-run` |
|
|
179
253
|
| `apps` / `install <app>` / `upgrade` | agent version manager |
|
|
@@ -204,8 +278,18 @@ sync of a managed provider can add it back to that app.
|
|
|
204
278
|
|
|
205
279
|
## How your configs are treated
|
|
206
280
|
|
|
207
|
-
-
|
|
208
|
-
|
|
281
|
+
- Existing provider-store and agent configuration files are backed up before committed
|
|
282
|
+
changes to `~/.config/agentsw/backups/transaction-*`. New files have no prior copy;
|
|
283
|
+
cache refreshes are not configuration backups. Dry runs create neither files nor backups.
|
|
284
|
+
- Each adapter stages all its files before committing; failed writes trigger best-effort
|
|
285
|
+
rollback. A multi-agent sync is not one filesystem-wide transaction: successful agents
|
|
286
|
+
remain updated if another fails, and failures are reported with a nonzero exit status.
|
|
287
|
+
- The provider store lives at `~/.config/agentsw/config.json` (mode 0600). Concurrent stale
|
|
288
|
+
saves are rejected instead of overwriting newer records; retry after the other writer finishes.
|
|
289
|
+
- Existing file permissions are preserved and new configuration files default to 0600.
|
|
290
|
+
- A busy write lock reports its owner PID/time. If a writer crashed, stop all agentsw
|
|
291
|
+
writers and confirm the lock is abandoned before manually removing the reported
|
|
292
|
+
`.write.lock` file; active or unknown locks are never automatically stolen.
|
|
209
293
|
- A sync overwrites only the fields agentsw owns. Unmodeled provider-level keys and per-model
|
|
210
294
|
extras survive; an owned field that stops applying is cleared rather than left stale, and a
|
|
211
295
|
per-model `api`/`baseUrl` override that contradicts the route is dropped (it would silently
|
|
@@ -219,8 +303,10 @@ sync of a managed provider can add it back to that app.
|
|
|
219
303
|
- Codex only speaks the Responses API; a chat-completions-only endpoint cannot be used with
|
|
220
304
|
it, and the sync output says so.
|
|
221
305
|
- DeepSeek Harness keeps secrets out of `settings.yaml`: the route carries
|
|
222
|
-
`apiKeyEnv: AGENTSW_<ID>_API_KEY`, the key goes into `$DSH_HOME/.credentials.yaml`
|
|
306
|
+
`apiKeyEnv: AGENTSW_<ID>_<DIGEST>_API_KEY`, the key goes into `$DSH_HOME/.credentials.yaml`
|
|
223
307
|
(`refs:`, mode 0600). `$DSH_HOME` defaults to `~/.dsh`.
|
|
308
|
+
The digest distinguishes IDs such as `foo-bar` and `foo_bar`; legacy generated references
|
|
309
|
+
remain recognizable. External/custom references are not renamed or removed indiscriminately.
|
|
224
310
|
- cc-switch's database is opened read-only and never written to.
|
|
225
311
|
- The apps manager supports native Windows commands for Claude Code, Codex CLI, pi,
|
|
226
312
|
opencode, Hermes, and DeepSeek Harness. Oh My Pi and prime-agent still need their own
|
|
@@ -250,8 +336,13 @@ npm run dev -- ... # run from source via tsx
|
|
|
250
336
|
|
|
251
337
|
Each agent is one adapter in [`src/targets/`](./src/targets) implementing
|
|
252
338
|
`apply` / `prune` / `current` / `candidates`; read-only import sources live in
|
|
253
|
-
[`src/sources/`](./src/sources).
|
|
254
|
-
[`src/targets/index.ts`](./src/targets/index.ts)
|
|
339
|
+
[`src/sources/`](./src/sources). To add an app, register it in
|
|
340
|
+
[`src/targets/index.ts`](./src/targets/index.ts), wrap its implementation with
|
|
341
|
+
`transactionalTarget()`, and add its schemas/references to `rename.ts` and `remove.ts`.
|
|
342
|
+
Use shared credential, YAML/JSONC, URL and model helpers; add apply/prune/import,
|
|
343
|
+
rename/removal, malformed-input, dry-run and account-isolation tests. If installable,
|
|
344
|
+
also add its platform commands to `apps.ts`. WorkBuddy local deletion uses the stable
|
|
345
|
+
account selector shown by `list --apps workbuddy`, not an ambiguous hostname alone.
|
|
255
346
|
|
|
256
347
|
## License
|
|
257
348
|
|
package/README.zh-CN.md
CHANGED
|
@@ -23,7 +23,7 @@ switching to vfing (openai) · default model glm-5.3-flash
|
|
|
23
23
|
|
|
24
24
|
skip claude Claude Code does not support openai-protocol providers
|
|
25
25
|
ok codex ~/.codex/config.toml, ~/.codex/auth.json
|
|
26
|
-
backup: ~/.config/agentsw/backups/
|
|
26
|
+
backup: ~/.config/agentsw/backups/transaction-<unique>
|
|
27
27
|
ok omp ~/.omp/agent/models.yml
|
|
28
28
|
select in omp with: omp --model vfing/glm-5.3-flash
|
|
29
29
|
skip pi pi not detected (pass --apps pi to force)
|
|
@@ -38,18 +38,18 @@ ok dsh ~/.dsh/settings.yaml, ~/.dsh/.credentials.yaml
|
|
|
38
38
|
|
|
39
39
|
- **它能去桌面应用去不了的地方。** 无头服务器、容器、devcontainer、CI。
|
|
40
40
|
`npx agentsw use myproxy -a codex,omp` 在开机脚本里是一行;GUI 点九次不是。
|
|
41
|
-
- **`--dry-run`
|
|
42
|
-
|
|
41
|
+
- **`--dry-run` 输出脱敏 diff。** 写盘前先预览;预览不写文件、不创建备份。
|
|
42
|
+
正式提交变更时,已有配置文件才会先备份。
|
|
43
43
|
- **默认不破坏现有配置。** 只覆盖 agentsw 自己管理的字段:它不建模的供应商级键
|
|
44
44
|
(`authHeader`、`headers`、`compat`、`discovery`)、`thinkingLevelMap` 之类的模型级字段、
|
|
45
45
|
以及 YAML 注释,都会在重新同步后原样保留。
|
|
46
46
|
- **写的是元数据,不只是模型 id。** 转售商 `/v1/models` 只返回裸 id;agentsw 从
|
|
47
|
-
[models.dev](https://models.dev)
|
|
47
|
+
[models.dev](https://models.dev) 优先补全,并在有实际缺口时自动查询 AI Gateway,按各应用真正读取的字段下发:pi/prime 的
|
|
48
48
|
`thinkingLevelMap`、opencode 的 `limit`/`attachment`、omp 的 `contextWindow`/`maxTokens`、
|
|
49
49
|
dsh 的 `reasoningEfforts`/`input`。
|
|
50
50
|
- **接管你已有的配置——包括 [cc-switch](https://github.com/farion1231/cc-switch)。**
|
|
51
51
|
`import` 会扫描所有已安装的智能体**以及 cc-switch 自己的存储**,合并重复项
|
|
52
|
-
(相同协议 +
|
|
52
|
+
(相同协议 + 归一化端点 + 凭据),并且对两者都只读不写。
|
|
53
53
|
- **区分接口形态。** `/v1/chat/completions` 与 `/v1/responses` 是两个不同端点;
|
|
54
54
|
agentsw 记录供应商说的是哪一种,并且绝不把可用的 responses 降级。
|
|
55
55
|
|
|
@@ -61,7 +61,7 @@ ok dsh ~/.dsh/settings.yaml, ~/.dsh/.credentials.yaml
|
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
63
|
npx agentsw # 免安装,直接打开交互菜单
|
|
64
|
-
npm install -g agentsw # 需要 Node >= 22.
|
|
64
|
+
npm install -g agentsw # 需要 Node >= 22.13,内置 SQLite 无需额外启动参数
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
供应商同步与配置管理支持 Linux、macOS 和 Windows。Windows 下 agentsw 自身状态默认存放在
|
|
@@ -137,11 +137,13 @@ imported sub · openai · https://new.vfing.de/v1 · 18 models [from omp]
|
|
|
137
137
|
imported zhipu-glm · anthropic · https://open.bigmodel.cn/api/anthropic · 1 models [from cc-switch]
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
在交互终端中,不加 `--all` 会先展示多选预览,选定后才保存供应商记录。
|
|
141
|
+
使用 `--all` 或非交互输入时会导入全部符合条件的新条目;脚本中建议明确写 `--all`。
|
|
142
|
+
模型发现过程中可能更新本地元数据缓存。
|
|
141
143
|
|
|
142
144
|
## 模型发现过滤
|
|
143
145
|
|
|
144
|
-
`--discover` 从供应商 `/v1/models` 拉取模型 id
|
|
146
|
+
`--discover` 从供应商 `/v1/models` 拉取模型 id,优先从 models.dev 补全元数据,必要时自动查询 AI Gateway。
|
|
145
147
|
转售商列表噪音大,过滤规则持久化在供应商上,每次 `discover` 自动复用:
|
|
146
148
|
|
|
147
149
|
```bash
|
|
@@ -155,6 +157,69 @@ asw discover myproxy --no-filter # 清除规则
|
|
|
155
157
|
|
|
156
158
|
手动 `--models` 指定的 id 和默认模型永不被过滤。
|
|
157
159
|
|
|
160
|
+
## 自动补全模型元数据
|
|
161
|
+
|
|
162
|
+
**默认无需额外参数。** 新供应商和未保存模式的供应商使用 `auto`:先查询 models.dev,
|
|
163
|
+
仅在核心参数(上下文窗口、最大输出、推理或图片输入能力)仍缺失、已有 Gateway 自动字段
|
|
164
|
+
未被手动修改或 models.dev 替代而需刷新,或需核实旧自动值的身份冲突时,才查询公开的
|
|
165
|
+
[AI Gateway 模型目录](https://vercel.com/docs/ai-gateway/models-and-providers#dynamic-model-discovery)。
|
|
166
|
+
仅缺名称、价格、可选输入上限或推理等级列表,不会触发 Gateway 查询。
|
|
167
|
+
直接请求 `https://ai-gateway.vercel.sh/v1/models`,不携带供应商密钥,也不安装 AI SDK。
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# 按保存的模式刷新已有模型参数(未设置时为 auto)
|
|
171
|
+
# 不发现新模型、不写智能体配置
|
|
172
|
+
asw refresh --provider myproxy
|
|
173
|
+
|
|
174
|
+
# 查看生效模式、字段来源、冲突和参考价格;JSON 不含 API key
|
|
175
|
+
asw models --provider myproxy --metadata
|
|
176
|
+
|
|
177
|
+
# 自定义或有歧义的名称需显式映射;实际调用的模型 ID 不变
|
|
178
|
+
asw refresh --provider myproxy --metadata-mode auto \
|
|
179
|
+
--gateway-models '{"my-model":"openai/gpt-5.4"}'
|
|
180
|
+
|
|
181
|
+
# 即使核心参数完整,也始终查询 Gateway
|
|
182
|
+
asw refresh --provider myproxy --metadata-mode on
|
|
183
|
+
|
|
184
|
+
# 停止后续 Gateway 获取;保留已保存的参数
|
|
185
|
+
asw refresh --provider myproxy --metadata-mode off
|
|
186
|
+
|
|
187
|
+
# 将显式关闭的供应商恢复为自动模式
|
|
188
|
+
asw refresh --provider myproxy --metadata-mode auto
|
|
189
|
+
|
|
190
|
+
# 单独预览、同步;sync 不查询目录
|
|
191
|
+
asw sync --provider myproxy --dry-run
|
|
192
|
+
asw sync --provider myproxy
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
交互菜单的“设置并刷新模型参数补充源”提供“自动按需(推荐)”“始终补充”“关闭”,
|
|
196
|
+
默认选中该供应商当前模式;添加供应商时不增加提问。
|
|
197
|
+
`add`、`quick`、`discover`、`import`、`refresh` 均支持 `--metadata-mode <auto|on|off>`。
|
|
198
|
+
原有 `--gateway-metadata` / `--no-gateway-metadata` 仍分别表示显式 `on` / `off`。
|
|
199
|
+
省略参数会保留已保存的设置:已有 `false` 仍保持关闭,直到主动选择 `auto` 或 `on`。
|
|
200
|
+
无效模式或相互冲突的模式/布尔开关,会在查询或保存之前报错。
|
|
201
|
+
`refresh` 不传 `--provider` 会作用于全部供应商,包括更新传入的模式。
|
|
202
|
+
`--gateway-models` 替换整个映射,传 `{}` 清空;映射不会更改保存的模式,也不会覆盖 `off`。
|
|
203
|
+
导入仍跳过已经配置的账号。
|
|
204
|
+
|
|
205
|
+
- **按字段合并:** 用户手动值优先,其次 models.dev,最后 Gateway 补缺。已记录来源的自动值可以刷新,
|
|
206
|
+
手动修改后会保留;旧版本中没有来源记录的已有值保守视为手动值,不强制覆盖。未知扩展字段也保留。
|
|
207
|
+
- **精确匹配:** 不含 `/` 的裸 ID 可按大小写精确匹配唯一的 Gateway `creator/model` 身份,
|
|
208
|
+
前提是 models.dev 没有身份线索或线索一致。任一目录中的创建者身份有歧义时拒绝自动匹配;
|
|
209
|
+
自定义或有歧义的名称仍需显式映射。带命名空间的 ID 不会剥离前缀,不按子串、大小写折叠或版本相似度猜测。
|
|
210
|
+
`auto` / `on` 模式下 models.dev 也采用保守的精确匹配;`off` 保留原有 models.dev 查询行为。
|
|
211
|
+
匹配失败不妨碍模型继续使用。修改/删除映射时,只清理确认已失效身份的自动值,保留手动修改。
|
|
212
|
+
- **不改变调用语义:** 目录不会给供应商增加模型,不改模型 ID、协议、URL、默认模型或账户;
|
|
213
|
+
Gateway 能力是参考规格,不保证转售商实际实现。`supported_specifications` 不是 URL 版本。
|
|
214
|
+
- **价格仅供参考:** Gateway 价格转换为美元/百万 token,单独保存在 `metadata.gateway.referenceCost`,
|
|
215
|
+
不写入供应商的有效 `cost`,也不下发为智能体计费价格;分档/供应商差异另有标记。
|
|
216
|
+
自动模式不会仅为刷新参考价格而查询:可查看 `metadata.gateway.fetchedAt` 判断快照时间,
|
|
217
|
+
需要重新查询参考价格时使用 `refresh --provider <id> --metadata-mode on`。
|
|
218
|
+
- **可追溯、可降级:** `metadata.fields` 记录来源、原值快照和时间,`metadata.conflicts` 记录保留值与冲突。
|
|
219
|
+
审计信息仅保存在 agentsw,不写入智能体运行配置。Gateway 目录独立缓存 24 小时;请求失败可使用旧缓存,无缓存时只跳过补充。
|
|
220
|
+
- **自动查询不等于自动同步:** 添加、快速添加、发现、导入、刷新时补全保存的元数据,并不新增自动推送。
|
|
221
|
+
同步行为不变:普通 `sync` 仅写入已保存的设置,既不获取模型列表,也不请求元数据目录。
|
|
222
|
+
|
|
158
223
|
## 命令
|
|
159
224
|
|
|
160
225
|
| 命令 | 作用 |
|
|
@@ -170,7 +235,8 @@ asw discover myproxy --no-filter # 清除规则
|
|
|
170
235
|
| `sync` | 重新应用当前供应商(比如某个智能体升级之后) |
|
|
171
236
|
| `discover <id> [--sync]` | 从 `/v1/models` 刷新模型列表与元数据 |
|
|
172
237
|
| `models [query]` | 搜索 models.dev 目录 |
|
|
173
|
-
| `refresh` |
|
|
238
|
+
| `refresh [--provider <id>]` | 刷新已有模型参数,可设置 Gateway 补充源,不改变模型列表 |
|
|
239
|
+
| `models --provider <id> --metadata` | 以 JSON 查看字段来源、冲突和参考价格 |
|
|
174
240
|
| `prune <id>` / `remove <id> [--prune]` | 从各应用配置中清除 / 从存储中删除 |
|
|
175
241
|
| `remove <id> --apps omp` | 仅删除指定智能体内的条目,支持 `--dry-run` |
|
|
176
242
|
| `apps` / `install <app>` / `upgrade` | 智能体版本管理 |
|
|
@@ -200,8 +266,16 @@ asw remove unused-provider --apps omp
|
|
|
200
266
|
|
|
201
267
|
## 它如何对待你的配置
|
|
202
268
|
|
|
203
|
-
-
|
|
204
|
-
|
|
269
|
+
- 正式提交变更前,已有中央存储和智能体配置先备份到
|
|
270
|
+
`~/.config/agentsw/backups/transaction-*`。新文件没有旧副本;缓存刷新不属于配置备份。
|
|
271
|
+
预览不写文件,也不创建备份。
|
|
272
|
+
- 每个适配器先准备全部文件再提交,写入失败时尝试回滚。多智能体同步不是全局文件系统事务:
|
|
273
|
+
某个智能体失败时,其他成功项仍保留,并通过非零退出码报告失败。
|
|
274
|
+
- 供应商存储位于 `~/.config/agentsw/config.json`(权限 0600)。并发写入导致快照过期时,
|
|
275
|
+
拒绝覆盖新记录;等待其他写入完成后重试。
|
|
276
|
+
- 保留已有文件权限,新建配置文件默认使用 0600。
|
|
277
|
+
- 写锁占用时会显示持有者 PID 和时间。若进程曾异常退出,先停止所有 agentsw 写入并确认
|
|
278
|
+
锁已遗留,再手动删除报错中指定的 `.write.lock` 后重试;不会自动抢占活动或状态不明的锁。
|
|
205
279
|
- 同步只覆盖 agentsw 自己管理的字段:未建模的供应商级键与模型级扩展字段保留;
|
|
206
280
|
自己管理但本次不再写出的字段会被清除而非留成陈旧值;与新路由矛盾的模型级
|
|
207
281
|
`api`/`baseUrl` 覆盖会被删除(否则它会静默盖过供应商条目)并在输出中点名。
|
|
@@ -212,8 +286,9 @@ asw remove unused-provider --apps omp
|
|
|
212
286
|
手动指定;同步绝不把已有的 responses 端点降级。
|
|
213
287
|
- Codex 只支持 Responses API;仅提供 chat-completions 的端点无法用于 Codex,同步输出会提示。
|
|
214
288
|
- DeepSeek Harness 不把密钥写进 `settings.yaml`:路由里只保存
|
|
215
|
-
`apiKeyEnv: AGENTSW_<ID>_API_KEY` 引用,密钥写入 `$DSH_HOME/.credentials.yaml`
|
|
289
|
+
`apiKeyEnv: AGENTSW_<ID>_<DIGEST>_API_KEY` 引用,密钥写入 `$DSH_HOME/.credentials.yaml`
|
|
216
290
|
的 `refs:`(权限 0600)。`$DSH_HOME` 默认为 `~/.dsh`。
|
|
291
|
+
摘要用于区分 `foo-bar` 和 `foo_bar` 等 ID;旧版生成引用仍可识别,不随意修改外部或自定义引用。
|
|
217
292
|
- cc-switch 的数据库以只读方式打开,永不写入。
|
|
218
293
|
- 安装管理器在 Windows 下为 Claude Code、Codex CLI、pi、opencode、Hermes 和 DeepSeek Harness
|
|
219
294
|
使用原生安装命令。Oh My Pi 与 prime-agent 仍需使用各自的 Windows 安装方式;WorkBuddy
|
|
@@ -242,8 +317,12 @@ npm run dev -- ... # 通过 tsx 从源码运行
|
|
|
242
317
|
|
|
243
318
|
每个智能体是 [`src/targets/`](./src/targets) 下的一个适配器,实现
|
|
244
319
|
`apply` / `prune` / `current` / `candidates`;只读的导入来源放在
|
|
245
|
-
[`src/sources/`](./src/sources)
|
|
246
|
-
[`src/targets/index.ts`](./src/targets/index.ts)
|
|
320
|
+
[`src/sources/`](./src/sources)。新增应用时,在
|
|
321
|
+
[`src/targets/index.ts`](./src/targets/index.ts) 注册,用 `transactionalTarget()` 包装适配器,
|
|
322
|
+
并为 `rename.ts`、`remove.ts` 补充对应配置结构与引用处理。
|
|
323
|
+
复用共享凭据、YAML/JSONC、URL 和模型工具,补齐同步、清理、导入、重命名、删除、
|
|
324
|
+
异常输入、预览及账户隔离测试;支持安装的应用还需在 `apps.ts` 中注册平台命令。
|
|
325
|
+
删除 WorkBuddy 本地账户时,使用 `list --apps workbuddy` 显示的稳定账户标识,避免同域名歧义。
|
|
247
326
|
|
|
248
327
|
## 许可证
|
|
249
328
|
|
package/dist/apps.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync, execSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import semver from "semver";
|
|
4
5
|
import { appDataDir, home, readJsonIfExists } from "./fsutil.js";
|
|
5
6
|
export const appPackages = [
|
|
6
7
|
{
|
|
@@ -137,12 +138,20 @@ export function installedVersion(app) {
|
|
|
137
138
|
if (app.binary) {
|
|
138
139
|
try {
|
|
139
140
|
const binary = executableName(app.binary);
|
|
140
|
-
const
|
|
141
|
+
const args = app.versionArgs ?? ["--version"];
|
|
142
|
+
const isBatch = process.platform === "win32" && /\.(cmd|bat)$/i.test(binary);
|
|
143
|
+
const quote = (value) => {
|
|
144
|
+
if (/["%\r\n]/.test(value))
|
|
145
|
+
throw new Error("unsupported Windows version-probe argument");
|
|
146
|
+
return `"${value}"`;
|
|
147
|
+
};
|
|
148
|
+
const out = execFileSync(isBatch ? process.env.ComSpec || "cmd.exe" : binary, isBatch ? ["/d", "/s", "/c", `"${[binary, ...args].map(quote).join(" ")}"`] : args, {
|
|
141
149
|
encoding: "utf8",
|
|
142
150
|
timeout: 15000,
|
|
143
151
|
stdio: ["ignore", "pipe", "pipe"],
|
|
152
|
+
...(isBatch ? { windowsVerbatimArguments: true } : {}),
|
|
144
153
|
});
|
|
145
|
-
return SEMVERISH.exec(out)?.[0];
|
|
154
|
+
return normalizeAppVersion(SEMVERISH.exec(out)?.[0]) ?? "?";
|
|
146
155
|
}
|
|
147
156
|
catch {
|
|
148
157
|
// binary present but probe failed (e.g. runtime version gate) -> installed, version unknown
|
|
@@ -200,25 +209,27 @@ export async function latestVersion(app) {
|
|
|
200
209
|
return undefined;
|
|
201
210
|
}
|
|
202
211
|
}
|
|
203
|
-
/**
|
|
212
|
+
/** Accept SemVer (including prereleases), with the legacy two-component display form. */
|
|
213
|
+
export function normalizeAppVersion(value) {
|
|
214
|
+
if (typeof value !== "string")
|
|
215
|
+
return undefined;
|
|
216
|
+
const version = value.trim();
|
|
217
|
+
return semver.valid(version) ?? (/^v?\d+\.\d+$/.test(version) ? semver.valid(`${version}.0`) ?? undefined : undefined);
|
|
218
|
+
}
|
|
219
|
+
/** Build metadata has no precedence; prereleases sort before the corresponding release. */
|
|
204
220
|
export function isNewer(installed, latest) {
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
const a = pa[i] ?? 0;
|
|
209
|
-
const b = pb[i] ?? 0;
|
|
210
|
-
if (Number.isNaN(a) || Number.isNaN(b))
|
|
211
|
-
break;
|
|
212
|
-
if (b > a)
|
|
213
|
-
return true;
|
|
214
|
-
if (b < a)
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
return false;
|
|
221
|
+
const before = normalizeAppVersion(installed);
|
|
222
|
+
const after = normalizeAppVersion(latest);
|
|
223
|
+
return before !== undefined && after !== undefined && semver.gt(after, before);
|
|
218
224
|
}
|
|
219
225
|
/** Run an install/upgrade command with live output. Throws on nonzero exit. */
|
|
220
226
|
export function runShell(command) {
|
|
221
|
-
|
|
227
|
+
if (process.platform === "win32") {
|
|
228
|
+
execSync(command, { stdio: "inherit", env: process.env });
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
execFileSync("bash", ["-o", "pipefail", "-c", command], { stdio: "inherit", env: process.env });
|
|
232
|
+
}
|
|
222
233
|
}
|
|
223
234
|
/** Resolve the platform-specific command while keeping the package table readable. */
|
|
224
235
|
export function appCommand(app, action, platform = process.platform) {
|
|
@@ -227,34 +238,27 @@ export function appCommand(app, action, platform = process.platform) {
|
|
|
227
238
|
return action === "install" ? app.installCmd : app.upgradeCmd ?? app.installCmd;
|
|
228
239
|
}
|
|
229
240
|
function executableName(binary) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
241
|
+
return findBinary(binary) ?? binary;
|
|
242
|
+
}
|
|
243
|
+
function findBinary(binary) {
|
|
244
|
+
const names = process.platform === "win32" && !path.extname(binary)
|
|
245
|
+
? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean).map((suffix) => `${binary}${suffix.toLowerCase()}`)
|
|
246
|
+
: [binary];
|
|
247
|
+
const dirs = path.isAbsolute(binary) ? [""] : (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
|
|
248
|
+
for (const dir of dirs) {
|
|
249
|
+
for (const name of names) {
|
|
250
|
+
const file = path.resolve(dir.replace(/^"|"$/g, ""), name);
|
|
251
|
+
try {
|
|
252
|
+
if (!fs.statSync(file).isFile())
|
|
253
|
+
continue;
|
|
254
|
+
fs.accessSync(file, process.platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK);
|
|
255
|
+
return file;
|
|
256
|
+
}
|
|
257
|
+
catch { /* Try the next executable; directories and inaccessible files are not commands. */ }
|
|
258
|
+
}
|
|
236
259
|
}
|
|
237
|
-
return
|
|
260
|
+
return undefined;
|
|
238
261
|
}
|
|
239
262
|
export function binaryOnPath(binary) {
|
|
240
|
-
|
|
241
|
-
return dirs.some((d) => {
|
|
242
|
-
try {
|
|
243
|
-
const names = process.platform === "win32" && !path.extname(binary)
|
|
244
|
-
? [binary, ...((process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((s) => `${binary}${s.toLowerCase()}`))]
|
|
245
|
-
: [binary];
|
|
246
|
-
return names.some((name) => {
|
|
247
|
-
try {
|
|
248
|
-
fs.accessSync(path.join(d, name), process.platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK);
|
|
249
|
-
return true;
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
});
|
|
263
|
+
return findBinary(binary) !== undefined;
|
|
260
264
|
}
|