agentsw 0.4.0 → 0.5.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 +10 -1
- package/README.zh-CN.md +9 -1
- package/dist/apps.js +46 -7
- package/dist/commands.js +142 -14
- package/dist/discover.js +30 -0
- package/dist/fsutil.js +20 -2
- package/dist/i18n.js +8 -1
- package/dist/index.js +15 -1
- package/dist/menu.js +6 -2
- package/dist/sources/ccswitch.js +4 -2
- package/dist/store.js +2 -2
- package/dist/targets/dsh.js +2 -2
- package/dist/targets/hermes.js +4 -4
- package/dist/targets/opencode.js +3 -2
- package/dist/targets/workbuddy.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,6 +65,11 @@ npx agentsw # zero install: opens the interactive menu
|
|
|
65
65
|
npm install -g agentsw # requires Node >= 22.12
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
The provider sync and config management work on Linux, macOS, and Windows. On Windows,
|
|
69
|
+
agentsw stores its own state under `%APPDATA%\agentsw`; native app-data locations are
|
|
70
|
+
used for agents that require them. Set `AGENTSW_HOME` when running from Git Bash/WSL or
|
|
71
|
+
when a portable home directory is desired.
|
|
72
|
+
|
|
68
73
|
## Quick start
|
|
69
74
|
|
|
70
75
|
```bash
|
|
@@ -195,7 +200,11 @@ asw remove myproxy --prune
|
|
|
195
200
|
`apiKeyEnv: AGENTSW_<ID>_API_KEY`, the key goes into `$DSH_HOME/.credentials.yaml`
|
|
196
201
|
(`refs:`, mode 0600). `$DSH_HOME` defaults to `~/.dsh`.
|
|
197
202
|
- cc-switch's database is opened read-only and never written to.
|
|
198
|
-
- The apps manager
|
|
203
|
+
- The apps manager supports native Windows commands for Claude Code, Codex CLI, pi,
|
|
204
|
+
opencode, Hermes, and DeepSeek Harness. Oh My Pi and prime-agent still need their own
|
|
205
|
+
Windows installation method; WorkBuddy is installed and updated by its desktop app.
|
|
206
|
+
- `OPENCODE_CONFIG_DIR`, `WORKBUDDY_CONFIG_DIR`, `CODEBUDDY_CONFIG_DIR`, `HERMES_HOME`,
|
|
207
|
+
and `DSH_HOME` can override native config locations.
|
|
199
208
|
|
|
200
209
|
## Language
|
|
201
210
|
|
package/README.zh-CN.md
CHANGED
|
@@ -64,6 +64,10 @@ npx agentsw # 免安装,直接打开交互菜单
|
|
|
64
64
|
npm install -g agentsw # 需要 Node >= 22.12
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
供应商同步与配置管理支持 Linux、macOS 和 Windows。Windows 下 agentsw 自身状态默认存放在
|
|
68
|
+
`%APPDATA%\agentsw`,需要原生应用数据目录的智能体会使用对应目录。通过 Git Bash/WSL 运行,
|
|
69
|
+
或需要可移植目录时,可以设置 `AGENTSW_HOME`。
|
|
70
|
+
|
|
67
71
|
## 快速开始
|
|
68
72
|
|
|
69
73
|
```bash
|
|
@@ -190,7 +194,11 @@ asw remove myproxy --prune
|
|
|
190
194
|
`apiKeyEnv: AGENTSW_<ID>_API_KEY` 引用,密钥写入 `$DSH_HOME/.credentials.yaml`
|
|
191
195
|
的 `refs:`(权限 0600)。`$DSH_HOME` 默认为 `~/.dsh`。
|
|
192
196
|
- cc-switch 的数据库以只读方式打开,永不写入。
|
|
193
|
-
-
|
|
197
|
+
- 安装管理器在 Windows 下为 Claude Code、Codex CLI、pi、opencode、Hermes 和 DeepSeek Harness
|
|
198
|
+
使用原生安装命令。Oh My Pi 与 prime-agent 仍需使用各自的 Windows 安装方式;WorkBuddy
|
|
199
|
+
由桌面应用自行安装和更新。
|
|
200
|
+
- `OPENCODE_CONFIG_DIR`、`WORKBUDDY_CONFIG_DIR`、`CODEBUDDY_CONFIG_DIR`、`HERMES_HOME` 与
|
|
201
|
+
`DSH_HOME` 可覆盖原生配置目录。
|
|
194
202
|
|
|
195
203
|
## 语言
|
|
196
204
|
|
package/dist/apps.js
CHANGED
|
@@ -1,7 +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 { home, readJsonIfExists } from "./fsutil.js";
|
|
4
|
+
import { appDataDir, home, readJsonIfExists } from "./fsutil.js";
|
|
5
5
|
export const appPackages = [
|
|
6
6
|
{
|
|
7
7
|
id: "claude",
|
|
@@ -10,6 +10,7 @@ export const appPackages = [
|
|
|
10
10
|
latest: { kind: "npm", name: "@anthropic-ai/claude-code" },
|
|
11
11
|
installCmd: "curl -fsSL https://claude.ai/install.sh | bash",
|
|
12
12
|
upgradeCmd: "claude update",
|
|
13
|
+
windowsInstallCmd: "npm install -g @anthropic-ai/claude-code@latest",
|
|
13
14
|
},
|
|
14
15
|
{
|
|
15
16
|
id: "codex",
|
|
@@ -17,6 +18,7 @@ export const appPackages = [
|
|
|
17
18
|
binary: "codex",
|
|
18
19
|
latest: { kind: "npm", name: "@openai/codex" },
|
|
19
20
|
installCmd: "npm install -g @openai/codex@latest",
|
|
21
|
+
windowsInstallCmd: "npm install -g @openai/codex@latest",
|
|
20
22
|
},
|
|
21
23
|
{
|
|
22
24
|
id: "omp",
|
|
@@ -32,6 +34,7 @@ export const appPackages = [
|
|
|
32
34
|
binary: "pi",
|
|
33
35
|
latest: { kind: "npm", name: "@earendil-works/pi-coding-agent" },
|
|
34
36
|
installCmd: "npm install -g @earendil-works/pi-coding-agent@latest",
|
|
37
|
+
windowsInstallCmd: "npm install -g @earendil-works/pi-coding-agent@latest",
|
|
35
38
|
},
|
|
36
39
|
{
|
|
37
40
|
id: "prime",
|
|
@@ -47,6 +50,7 @@ export const appPackages = [
|
|
|
47
50
|
latest: { kind: "npm", name: "opencode-ai" },
|
|
48
51
|
installCmd: "curl -fsSL https://opencode.ai/install | bash",
|
|
49
52
|
upgradeCmd: "opencode upgrade",
|
|
53
|
+
windowsInstallCmd: "npm install -g opencode-ai@latest",
|
|
50
54
|
},
|
|
51
55
|
{
|
|
52
56
|
id: "hermes",
|
|
@@ -55,17 +59,20 @@ export const appPackages = [
|
|
|
55
59
|
latest: { kind: "pypi", name: "hermes-agent" },
|
|
56
60
|
installCmd: "uv tool install hermes-agent || pipx install hermes-agent",
|
|
57
61
|
upgradeCmd: "uv tool upgrade hermes-agent || pipx upgrade hermes-agent",
|
|
62
|
+
windowsInstallCmd: "uv tool install hermes-agent || pipx install hermes-agent",
|
|
63
|
+
windowsUpgradeCmd: "uv tool upgrade hermes-agent || pipx upgrade hermes-agent",
|
|
58
64
|
},
|
|
59
65
|
{
|
|
60
66
|
id: "workbuddy",
|
|
61
67
|
name: "WorkBuddy",
|
|
62
68
|
// Electron desktop app: version from its data dir; managed by its own updater.
|
|
63
69
|
localVersion: () => {
|
|
64
|
-
const
|
|
70
|
+
const dir = workbuddyDataDir();
|
|
71
|
+
const j = readJsonIfExists(path.join(dir, "last-launch.json"));
|
|
65
72
|
const v = (j?.version ?? j?.appVersion);
|
|
66
73
|
if (v)
|
|
67
74
|
return v;
|
|
68
|
-
const r = readJsonIfExists(path.join(
|
|
75
|
+
const r = readJsonIfExists(path.join(dir, "app", "renderer-version.json"));
|
|
69
76
|
return r?.version ?? undefined;
|
|
70
77
|
},
|
|
71
78
|
},
|
|
@@ -75,13 +82,19 @@ export const appPackages = [
|
|
|
75
82
|
binary: "dsh",
|
|
76
83
|
latest: { kind: "npm", name: "@deepseek-ai/dsh" },
|
|
77
84
|
installCmd: "npm install -g @deepseek-ai/dsh@latest",
|
|
85
|
+
windowsInstallCmd: "npm install -g @deepseek-ai/dsh@latest",
|
|
78
86
|
},
|
|
79
87
|
];
|
|
88
|
+
function workbuddyDataDir() {
|
|
89
|
+
return process.env.WORKBUDDY_CONFIG_DIR?.trim() || process.env.CODEBUDDY_CONFIG_DIR?.trim() ||
|
|
90
|
+
(process.platform === "win32" ? appDataDir("workbuddy") : path.join(home, ".workbuddy"));
|
|
91
|
+
}
|
|
80
92
|
const SEMVERISH = /\d+\.\d+(\.\d+)?([-.+][\w.-]+)?/;
|
|
81
93
|
export function installedVersion(app) {
|
|
82
94
|
if (app.binary) {
|
|
83
95
|
try {
|
|
84
|
-
const
|
|
96
|
+
const binary = executableName(app.binary);
|
|
97
|
+
const out = execFileSync(binary, app.versionArgs ?? ["--version"], {
|
|
85
98
|
encoding: "utf8",
|
|
86
99
|
timeout: 15000,
|
|
87
100
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -126,7 +139,7 @@ export async function latestVersion(app) {
|
|
|
126
139
|
case "brew": {
|
|
127
140
|
// Local tap metadata first (works for taps; refreshed by `brew update`), then core API.
|
|
128
141
|
try {
|
|
129
|
-
const out = execSync(`brew info --json=v2 ${src.name}`, { encoding: "utf8", timeout: 30000 });
|
|
142
|
+
const out = execSync(`brew info --json=v2 ${src.name}`, { encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] });
|
|
130
143
|
const data = JSON.parse(out);
|
|
131
144
|
const stable = data.formulae?.[0]?.versions?.stable;
|
|
132
145
|
if (stable)
|
|
@@ -164,12 +177,38 @@ export function isNewer(installed, latest) {
|
|
|
164
177
|
export function runShell(command) {
|
|
165
178
|
execSync(command, { stdio: "inherit", env: process.env });
|
|
166
179
|
}
|
|
180
|
+
/** Resolve the platform-specific command while keeping the package table readable. */
|
|
181
|
+
export function appCommand(app, action, platform = process.platform) {
|
|
182
|
+
if (platform === "win32")
|
|
183
|
+
return action === "install" ? app.windowsInstallCmd : app.windowsUpgradeCmd ?? app.windowsInstallCmd;
|
|
184
|
+
return action === "install" ? app.installCmd : app.upgradeCmd ?? app.installCmd;
|
|
185
|
+
}
|
|
186
|
+
function executableName(binary) {
|
|
187
|
+
if (process.platform !== "win32")
|
|
188
|
+
return binary;
|
|
189
|
+
for (const suffix of ["", ".cmd", ".exe", ".bat"]) {
|
|
190
|
+
const candidate = `${binary}${suffix}`;
|
|
191
|
+
if (binaryOnPath(candidate))
|
|
192
|
+
return candidate;
|
|
193
|
+
}
|
|
194
|
+
return binary;
|
|
195
|
+
}
|
|
167
196
|
export function binaryOnPath(binary) {
|
|
168
197
|
const dirs = (process.env.PATH ?? "").split(path.delimiter);
|
|
169
198
|
return dirs.some((d) => {
|
|
170
199
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
200
|
+
const names = process.platform === "win32" && !path.extname(binary)
|
|
201
|
+
? [binary, ...((process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((s) => `${binary}${s.toLowerCase()}`))]
|
|
202
|
+
: [binary];
|
|
203
|
+
return names.some((name) => {
|
|
204
|
+
try {
|
|
205
|
+
fs.accessSync(path.join(d, name), process.platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
173
212
|
}
|
|
174
213
|
catch {
|
|
175
214
|
return false;
|
package/dist/commands.js
CHANGED
|
@@ -4,10 +4,11 @@ import { loadStore, saveStore, getProvider, configFile } from "./store.js";
|
|
|
4
4
|
import { scanCandidates, normalizeUrl } from "./import.js";
|
|
5
5
|
import { enrichModels, loadCatalog, searchCatalog } from "./modelsdev.js";
|
|
6
6
|
import { resolveTargets, supportsProtocol, targets } from "./targets/index.js";
|
|
7
|
-
import { discoverProviderModels } from "./discover.js";
|
|
8
|
-
import { appPackages, installedVersion, isNewer, latestVersion, runShell } from "./apps.js";
|
|
7
|
+
import { discoverProviderModels, probeProtocols } from "./discover.js";
|
|
8
|
+
import { appCommand, appPackages, installedVersion, isNewer, latestVersion, runShell } from "./apps.js";
|
|
9
9
|
import { drainPendingWrites, readTextIfExists, setDryRun } from "./fsutil.js";
|
|
10
10
|
import { applyModelFilter } from "./filter.js";
|
|
11
|
+
import { slugFromBaseUrl } from "./slug.js";
|
|
11
12
|
import { t } from "./i18n.js";
|
|
12
13
|
function fail(message) {
|
|
13
14
|
process.stderr.write(pc.red(`error: ${message}\n`));
|
|
@@ -57,6 +58,13 @@ function modelRows(models) {
|
|
|
57
58
|
]);
|
|
58
59
|
}
|
|
59
60
|
const MODEL_HEADER = ["MODEL", "CTX", "IN", "OUT", "REASONING", "$IN/$OUT per M"];
|
|
61
|
+
/** Print a dim hint when some models have no models.dev metadata. */
|
|
62
|
+
function printUncatalogedHint(models) {
|
|
63
|
+
const bare = models.filter((m) => m.contextWindow === undefined && !m.cost);
|
|
64
|
+
if (bare.length > 0) {
|
|
65
|
+
console.log(pc.dim(`${bare.length} model(s) have no models.dev metadata (shown as "-")`));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
60
68
|
/** Guess the models.dev provider whose API host matches the configured baseUrl. */
|
|
61
69
|
function guessProviderHint(catalog, baseUrl) {
|
|
62
70
|
if (!catalog)
|
|
@@ -178,7 +186,11 @@ export async function cmdAdd(opts) {
|
|
|
178
186
|
const manualIds = (answers.models ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
179
187
|
let modelIds = manualIds;
|
|
180
188
|
const modelFilter = parseFilterOpts(opts);
|
|
181
|
-
|
|
189
|
+
const shouldDiscover = opts.discover || manualIds.length === 0;
|
|
190
|
+
if (shouldDiscover) {
|
|
191
|
+
if (!opts.discover) {
|
|
192
|
+
process.stderr.write(pc.dim(t("add.autoDiscover") + "\n"));
|
|
193
|
+
}
|
|
182
194
|
process.stderr.write(pc.dim(`${t("add.discovering", { url: baseUrl })}\n`));
|
|
183
195
|
const discovered = await discoverProviderModels({ baseUrl, apiKey: answers.apiKey, protocol });
|
|
184
196
|
console.log(t("add.providerLists", { count: discovered.length }));
|
|
@@ -222,8 +234,118 @@ export async function cmdAdd(opts) {
|
|
|
222
234
|
console.log(pc.green(t("add.saved", { status: savedStatus, id: pc.bold(provider.id), protocol })));
|
|
223
235
|
console.log(t("add.metadata", { matched, total: models.length }) + (hint ? t("add.providerHint", { hint }) : ""));
|
|
224
236
|
console.log(table(modelRows(models), MODEL_HEADER));
|
|
237
|
+
printUncatalogedHint(models);
|
|
225
238
|
console.log(pc.dim(`\n${t("add.next", { id: provider.id })}`));
|
|
226
239
|
}
|
|
240
|
+
/** Shared logic to create and save a single provider from discovered models. */
|
|
241
|
+
async function createProvider(opts) {
|
|
242
|
+
const { store, id, name, protocol, openaiApi, baseUrl, apiKey, modelIds, modelFilter } = opts;
|
|
243
|
+
const catalog = await loadCatalog();
|
|
244
|
+
const hint = guessProviderHint(catalog, baseUrl);
|
|
245
|
+
const models = enrichModels(catalog, modelIds, hint);
|
|
246
|
+
const matched = models.filter((m) => m.contextWindow !== undefined).length;
|
|
247
|
+
const defaultModel = opts.defaultModel ?? modelIds[0];
|
|
248
|
+
if (!modelIds.includes(defaultModel))
|
|
249
|
+
fail(t("add.defaultMissing", { model: defaultModel }));
|
|
250
|
+
const provider = {
|
|
251
|
+
id,
|
|
252
|
+
name,
|
|
253
|
+
protocol,
|
|
254
|
+
...(openaiApi ? { openaiApi } : {}),
|
|
255
|
+
baseUrl,
|
|
256
|
+
apiKey,
|
|
257
|
+
models,
|
|
258
|
+
defaultModel,
|
|
259
|
+
smallModel: opts.smallModel,
|
|
260
|
+
reasoningEffort: opts.reasoningEffort,
|
|
261
|
+
modelsDevId: hint,
|
|
262
|
+
modelFilter,
|
|
263
|
+
};
|
|
264
|
+
const existed = store.providers[provider.id] !== undefined;
|
|
265
|
+
store.providers[provider.id] = provider;
|
|
266
|
+
if (!store.active)
|
|
267
|
+
store.active = provider.id;
|
|
268
|
+
const savedStatus = t(existed ? "add.updated" : "add.added");
|
|
269
|
+
console.log(pc.green(t("add.saved", { status: savedStatus, id: pc.bold(provider.id), protocol })));
|
|
270
|
+
console.log(t("add.metadata", { matched, total: models.length }) + (hint ? t("add.providerHint", { hint }) : ""));
|
|
271
|
+
console.log(table(modelRows(models), MODEL_HEADER));
|
|
272
|
+
printUncatalogedHint(models);
|
|
273
|
+
return provider;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Quick add: only base URL + API key needed. Auto-detects protocol(s) by probing
|
|
277
|
+
* /v1/models with both openai and anthropic auth headers. When both succeed,
|
|
278
|
+
* creates two providers with -openai / -anthropic suffixes.
|
|
279
|
+
*/
|
|
280
|
+
export async function cmdQuickAdd(opts) {
|
|
281
|
+
const store = loadStore();
|
|
282
|
+
const interactive = process.stdin.isTTY && !opts.yes;
|
|
283
|
+
let baseUrl;
|
|
284
|
+
let apiKey;
|
|
285
|
+
let baseId;
|
|
286
|
+
if (interactive) {
|
|
287
|
+
const answers = await prompts([
|
|
288
|
+
{ type: "text", name: "baseUrl", message: t("add.baseUrl"), validate: (v) => (/^https?:\/\//.test(v) ? true : t("add.baseUrlInvalid")) },
|
|
289
|
+
{ type: "password", name: "apiKey", message: t("add.apiKey") },
|
|
290
|
+
], { onCancel: () => fail(t("add.cancelled")) });
|
|
291
|
+
baseUrl = answers.baseUrl.replace(/\/+$/, "");
|
|
292
|
+
apiKey = answers.apiKey;
|
|
293
|
+
baseId = opts.id ?? slugFromBaseUrl(baseUrl);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
if (!opts.baseUrl)
|
|
297
|
+
fail(t("add.fieldRequired", { field: "base-url" }));
|
|
298
|
+
if (!opts.apiKey)
|
|
299
|
+
fail(t("add.fieldRequired", { field: "api-key" }));
|
|
300
|
+
baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
301
|
+
apiKey = opts.apiKey;
|
|
302
|
+
baseId = opts.id ?? slugFromBaseUrl(baseUrl);
|
|
303
|
+
}
|
|
304
|
+
process.stderr.write(pc.dim(t("quick.probing", { url: baseUrl }) + "\n"));
|
|
305
|
+
const protocols = await probeProtocols({ baseUrl, apiKey });
|
|
306
|
+
if (protocols.length === 0) {
|
|
307
|
+
fail(t("quick.noProtocol"));
|
|
308
|
+
}
|
|
309
|
+
const modelFilter = parseFilterOpts(opts);
|
|
310
|
+
const multi = protocols.length > 1;
|
|
311
|
+
const createdIds = [];
|
|
312
|
+
for (const protocol of protocols) {
|
|
313
|
+
const suffix = multi ? `-${protocol}` : "";
|
|
314
|
+
const id = `${baseId}${suffix}`;
|
|
315
|
+
const name = multi ? `${baseId} (${protocol})` : baseId;
|
|
316
|
+
process.stderr.write(pc.dim(`${t("add.discovering", { url: baseUrl })} [${protocol}]\n`));
|
|
317
|
+
const discovered = await discoverProviderModels({ baseUrl, apiKey, protocol });
|
|
318
|
+
console.log(t("add.providerLists", { count: discovered.length }));
|
|
319
|
+
let modelIds = discovered;
|
|
320
|
+
const pinned = [...(opts.defaultModel ? [opts.defaultModel] : [])];
|
|
321
|
+
const outcome = applyModelFilter(modelIds, modelFilter, pinned);
|
|
322
|
+
reportDropped(outcome.dropped);
|
|
323
|
+
modelIds = outcome.kept;
|
|
324
|
+
if (modelIds.length === 0) {
|
|
325
|
+
console.log(pc.yellow(t("quick.noModelsAfterFilter", { id })));
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const provider = await createProvider({
|
|
329
|
+
store,
|
|
330
|
+
id,
|
|
331
|
+
name,
|
|
332
|
+
protocol,
|
|
333
|
+
baseUrl,
|
|
334
|
+
apiKey,
|
|
335
|
+
modelIds,
|
|
336
|
+
defaultModel: opts.defaultModel,
|
|
337
|
+
smallModel: opts.smallModel,
|
|
338
|
+
reasoningEffort: opts.reasoningEffort,
|
|
339
|
+
modelFilter,
|
|
340
|
+
});
|
|
341
|
+
createdIds.push(provider.id);
|
|
342
|
+
console.log(pc.dim(`\n${t("add.next", { id: provider.id })}`));
|
|
343
|
+
}
|
|
344
|
+
saveStore(store);
|
|
345
|
+
if (createdIds.length > 0) {
|
|
346
|
+
console.log(pc.green(t("quick.summary", { count: createdIds.length, ids: createdIds.join(", ") })));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
227
349
|
export function cmdList() {
|
|
228
350
|
const store = loadStore();
|
|
229
351
|
const ids = Object.keys(store.providers);
|
|
@@ -399,6 +521,7 @@ export async function cmdModels(query, opts) {
|
|
|
399
521
|
const provider = getProvider(store, opts.provider);
|
|
400
522
|
console.log(`${pc.bold(provider.id)} (${provider.protocol}) · ${provider.baseUrl}`);
|
|
401
523
|
console.log(table(modelRows(provider.models), MODEL_HEADER));
|
|
524
|
+
printUncatalogedHint(provider.models);
|
|
402
525
|
return;
|
|
403
526
|
}
|
|
404
527
|
const catalog = await loadCatalog({ refresh: opts.refresh });
|
|
@@ -463,6 +586,7 @@ export async function cmdDiscover(id, opts) {
|
|
|
463
586
|
(added.length ? `\n new: ${added.join(", ")}` : "") +
|
|
464
587
|
(gone.length ? `\n removed upstream: ${gone.join(", ")}` : ""));
|
|
465
588
|
console.log(table(modelRows(provider.models), MODEL_HEADER));
|
|
589
|
+
printUncatalogedHint(provider.models);
|
|
466
590
|
if (opts.sync) {
|
|
467
591
|
console.log("");
|
|
468
592
|
await cmdSync({ provider: id, apps: opts.apps });
|
|
@@ -477,13 +601,14 @@ async function collectAppRows() {
|
|
|
477
601
|
Promise.resolve(installedVersion(app)),
|
|
478
602
|
latestVersion(app),
|
|
479
603
|
]);
|
|
604
|
+
const knownInstalled = !!installed && installed !== "?";
|
|
480
605
|
return {
|
|
481
606
|
id: app.id,
|
|
482
607
|
name: app.name,
|
|
483
608
|
installed,
|
|
484
609
|
latest,
|
|
485
|
-
upgradable:
|
|
486
|
-
installable: !installed && !!app
|
|
610
|
+
upgradable: knownInstalled && !!latest && isNewer(installed, latest),
|
|
611
|
+
installable: !installed && !!appCommand(app, "install"),
|
|
487
612
|
};
|
|
488
613
|
}));
|
|
489
614
|
}
|
|
@@ -496,11 +621,13 @@ export async function cmdApps() {
|
|
|
496
621
|
r.latest ?? pc.dim("?"),
|
|
497
622
|
r.upgradable
|
|
498
623
|
? pc.yellow("upgrade available")
|
|
499
|
-
: r.installed
|
|
500
|
-
?
|
|
501
|
-
: r.installable
|
|
624
|
+
: !r.installed
|
|
625
|
+
? r.installable
|
|
502
626
|
? pc.dim("installable")
|
|
503
|
-
: pc.dim("-")
|
|
627
|
+
: pc.dim("-")
|
|
628
|
+
: r.installed === "?" || !r.latest
|
|
629
|
+
? pc.dim("unknown")
|
|
630
|
+
: pc.green("up to date"),
|
|
504
631
|
]), ["APP", "INSTALLED", "LATEST", "STATUS"]));
|
|
505
632
|
const upgradable = rows.filter((r) => r.upgradable).map((r) => r.id);
|
|
506
633
|
if (upgradable.length)
|
|
@@ -510,15 +637,16 @@ export async function cmdInstall(id) {
|
|
|
510
637
|
const app = appPackages.find((a) => a.id === id);
|
|
511
638
|
if (!app)
|
|
512
639
|
fail(`unknown app "${id}" (supported: ${appPackages.map((a) => a.id).join(", ")})`);
|
|
513
|
-
|
|
514
|
-
|
|
640
|
+
const installCmd = appCommand(app, "install");
|
|
641
|
+
if (!installCmd)
|
|
642
|
+
fail(`${app.name} is not installable on ${process.platform} (or is managed by its desktop app)`);
|
|
515
643
|
const installed = installedVersion(app);
|
|
516
644
|
if (installed) {
|
|
517
645
|
console.log(`${app.name} already installed (${installed}); use \`agentsw upgrade ${id}\``);
|
|
518
646
|
return;
|
|
519
647
|
}
|
|
520
|
-
console.log(`installing ${app.name}: ${pc.dim(
|
|
521
|
-
runShell(
|
|
648
|
+
console.log(`installing ${app.name}: ${pc.dim(installCmd)}`);
|
|
649
|
+
runShell(installCmd);
|
|
522
650
|
console.log(pc.green(`${app.name} installed: ${installedVersion(app) ?? "version unknown"}`));
|
|
523
651
|
}
|
|
524
652
|
export async function cmdUpgrade(ids) {
|
|
@@ -538,7 +666,7 @@ export async function cmdUpgrade(ids) {
|
|
|
538
666
|
selected = appPackages.filter((a) => upgradable.includes(a.id));
|
|
539
667
|
}
|
|
540
668
|
for (const app of selected) {
|
|
541
|
-
const cmd = app
|
|
669
|
+
const cmd = appCommand(app, "upgrade");
|
|
542
670
|
if (!cmd) {
|
|
543
671
|
console.log(`${pc.yellow("skip")} ${app.id}: not CLI-upgradable`);
|
|
544
672
|
continue;
|
package/dist/discover.js
CHANGED
|
@@ -19,3 +19,33 @@ export async function discoverProviderModels(opts) {
|
|
|
19
19
|
throw new Error(`${url} returned no models`);
|
|
20
20
|
return [...new Set(ids)].sort();
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Probe which wire protocols an endpoint supports by hitting /v1/models
|
|
24
|
+
* with each protocol's auth headers. Returns the protocols that responded
|
|
25
|
+
* with a valid model list (at least one model id).
|
|
26
|
+
*/
|
|
27
|
+
export async function probeProtocols(opts) {
|
|
28
|
+
const base = opts.baseUrl.replace(/\/+$/, "");
|
|
29
|
+
const url = /\/v\d+$/.test(base) ? `${base}/models` : `${base}/v1/models`;
|
|
30
|
+
const attempts = [
|
|
31
|
+
{ protocol: "openai", headers: { authorization: `Bearer ${opts.apiKey}` } },
|
|
32
|
+
{ protocol: "anthropic", headers: { "x-api-key": opts.apiKey, "anthropic-version": "2023-06-01" } },
|
|
33
|
+
];
|
|
34
|
+
const supported = [];
|
|
35
|
+
for (const a of attempts) {
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(url, { headers: a.headers, signal: AbortSignal.timeout(15000) });
|
|
38
|
+
if (!res.ok)
|
|
39
|
+
continue;
|
|
40
|
+
const body = (await res.json());
|
|
41
|
+
const rows = body.data ?? body.models ?? [];
|
|
42
|
+
const ids = rows.map((r) => r.id).filter((id) => typeof id === "string" && id.length > 0);
|
|
43
|
+
if (ids.length > 0)
|
|
44
|
+
supported.push(a.protocol);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
/* network error or timeout — protocol not supported */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return supported;
|
|
51
|
+
}
|
package/dist/fsutil.js
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
|
|
4
|
+
// AGENTSW_HOME is an explicit portable/test override. Native Windows otherwise
|
|
5
|
+
// uses USERPROFILE/os.homedir(), while Unix follows HOME as expected.
|
|
6
|
+
export const home = process.env.AGENTSW_HOME?.trim() ||
|
|
7
|
+
(process.platform === "win32" ? process.env.USERPROFILE?.trim() || os.homedir() : process.env.HOME?.trim() || os.homedir());
|
|
8
|
+
/** Windows roaming application data; Unix keeps the existing XDG-style layout. */
|
|
9
|
+
export function appDataDir(app) {
|
|
10
|
+
// AGENTSW_HOME is also a portable-layout override for tests and Git Bash.
|
|
11
|
+
if (process.platform === "win32" && !process.env.AGENTSW_HOME) {
|
|
12
|
+
return path.join(process.env.APPDATA?.trim() || path.join(home, "AppData", "Roaming"), app);
|
|
13
|
+
}
|
|
14
|
+
return path.join(home, ".config", app);
|
|
15
|
+
}
|
|
16
|
+
/** Windows local application data, used by agents whose native config lives there. */
|
|
17
|
+
export function localAppDataDir(app) {
|
|
18
|
+
if (process.platform === "win32" && !process.env.AGENTSW_HOME) {
|
|
19
|
+
return path.join(process.env.LOCALAPPDATA?.trim() || path.join(home, "AppData", "Local"), app);
|
|
20
|
+
}
|
|
21
|
+
return path.join(home, `.${app}`);
|
|
22
|
+
}
|
|
5
23
|
export function expandHome(p) {
|
|
6
24
|
return p.startsWith("~") ? path.join(home, p.slice(1)) : p;
|
|
7
25
|
}
|
|
@@ -43,7 +61,7 @@ export function writeFileAtomic(file, content, mode) {
|
|
|
43
61
|
fs.writeFileSync(tmp, content, mode !== undefined ? { mode } : undefined);
|
|
44
62
|
fs.renameSync(tmp, file);
|
|
45
63
|
}
|
|
46
|
-
export const backupsDir = path.join(
|
|
64
|
+
export const backupsDir = path.join(appDataDir("agentsw"), "backups");
|
|
47
65
|
/** Copy `file` into the backups dir with a timestamp suffix. No-op if file missing. */
|
|
48
66
|
export function backupFile(file) {
|
|
49
67
|
if (!fs.existsSync(file) || dryRun)
|
package/dist/i18n.js
CHANGED
|
@@ -7,6 +7,7 @@ const messages = {
|
|
|
7
7
|
"error.language": { en: "unsupported language \"{value}\"; use en or zh-CN", "zh-CN": "不支持语言 \"{value}\";请使用 en 或 zh-CN" },
|
|
8
8
|
"error.unknownCommand": { en: "unknown command '{value}', see --help", "zh-CN": "未知命令 '{value}',请查看 --help" },
|
|
9
9
|
"cmd.add": { en: "add or update a provider (interactive when flags are omitted)", "zh-CN": "添加或更新供应商(省略参数时进入交互流程)" },
|
|
10
|
+
"cmd.quick": { en: "quick add — auto-detect protocol(s) and models from just base URL + API key", "zh-CN": "快速添加 — 只需 base URL + API key,自动探测协议和模型" },
|
|
10
11
|
"cmd.list": { en: "list configured providers", "zh-CN": "列出已配置供应商" },
|
|
11
12
|
"cmd.remove": { en: "remove a provider", "zh-CN": "删除供应商" },
|
|
12
13
|
"cmd.prune": { en: "remove a provider's entries from app configs (keeps it in agentsw)", "zh-CN": "从应用配置移除供应商条目(仍保留在 agentsw)" },
|
|
@@ -99,7 +100,7 @@ const messages = {
|
|
|
99
100
|
"add.baseUrl": { en: "base URL", "zh-CN": "base URL" },
|
|
100
101
|
"add.baseUrlInvalid": { en: "must start with http(s)://", "zh-CN": "必须以 http(s):// 开头" },
|
|
101
102
|
"add.apiKey": { en: "API key", "zh-CN": "API key" },
|
|
102
|
-
"add.models": { en: "model ids (comma separated)", "zh-CN": "模型 id(
|
|
103
|
+
"add.models": { en: "model ids (comma separated, or leave blank to auto-discover)", "zh-CN": "模型 id(逗号分隔,留空则自动发现)" },
|
|
103
104
|
"add.cancelled": { en: "cancelled", "zh-CN": "已取消" },
|
|
104
105
|
"import.already": { en: "already configured as {id}", "zh-CN": "已配置为 {id}" },
|
|
105
106
|
"import.noneNew": { en: "nothing new to import (every discovered provider is already configured)", "zh-CN": "没有可导入的新供应商(发现项均已配置)" },
|
|
@@ -137,6 +138,7 @@ const messages = {
|
|
|
137
138
|
"add.discovering": { en: "discovering models from {url} ...", "zh-CN": "正在从 {url} 发现模型..." },
|
|
138
139
|
"add.providerLists": { en: "provider lists {count} model(s) via /v1/models", "zh-CN": "供应商通过 /v1/models 返回 {count} 个模型" },
|
|
139
140
|
"add.atLeastOne": { en: "at least one model id is required (or use --discover)", "zh-CN": "至少需要一个模型 id(或使用 --discover)" },
|
|
141
|
+
"add.autoDiscover": { en: "no models entered — discovering from /v1/models automatically", "zh-CN": "未输入模型 — 自动从 /v1/models 发现" },
|
|
140
142
|
"add.defaultMissing": { en: "default model {model} is not in the model list", "zh-CN": "默认模型 {model} 不在模型列表中" },
|
|
141
143
|
"add.smallMissing": { en: "small model {model} is not in the model list", "zh-CN": "小型模型 {model} 不在模型列表中" },
|
|
142
144
|
"add.added": { en: "added", "zh-CN": "已添加" },
|
|
@@ -145,6 +147,11 @@ const messages = {
|
|
|
145
147
|
"add.metadata": { en: "models.dev metadata: {matched}/{total} models matched", "zh-CN": "models.dev 元数据:匹配 {matched}/{total} 个模型" },
|
|
146
148
|
"add.providerHint": { en: " (provider hint: {hint})", "zh-CN": " (供应商提示:{hint})" },
|
|
147
149
|
"add.next": { en: "next: agentsw use {id}", "zh-CN": "下一步: agentsw use {id}" },
|
|
150
|
+
"quick.probing": { en: "probing protocols at {url} ...", "zh-CN": "正在探测 {url} 支持的协议..." },
|
|
151
|
+
"quick.noProtocol": { en: "no supported protocol found at this endpoint (tried openai and anthropic)", "zh-CN": "该端点未检测到支持的协议(已尝试 openai 和 anthropic)" },
|
|
152
|
+
"quick.noModelsAfterFilter": { en: "{id}: no models left after filtering, skipping", "zh-CN": "{id}: 过滤后无剩余模型,跳过" },
|
|
153
|
+
"quick.summary": { en: "created {count} provider(s): {ids}", "zh-CN": "创建了 {count} 个供应商: {ids}" },
|
|
154
|
+
"menu.quickAdd": { en: "quick add (auto-detect protocol + models)", "zh-CN": "快速添加(自动探测协议 + 模型)" },
|
|
148
155
|
"import.skip": { en: "skip", "zh-CN": "跳过" },
|
|
149
156
|
"import.modelsCount": { en: "{count} models", "zh-CN": "{count} 个模型" },
|
|
150
157
|
"table.protocol": { en: "PROTOCOL", "zh-CN": "协议" },
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { Command, InvalidArgumentError } from "commander";
|
|
4
|
-
import { cmdAdd, cmdApps, cmdDiscover, cmdImport, cmdInstall, cmdList, cmdModels, cmdRefreshMeta, cmdPrune, cmdRemove, cmdStatus, cmdSync, cmdUpgrade, cmdUse, } from "./commands.js";
|
|
4
|
+
import { cmdAdd, cmdQuickAdd, cmdApps, cmdDiscover, cmdImport, cmdInstall, cmdList, cmdModels, cmdRefreshMeta, cmdPrune, cmdRemove, cmdStatus, cmdSync, cmdUpgrade, cmdUse, } from "./commands.js";
|
|
5
5
|
import { detectSystemLocale, extractCliLocale, normalizeLocale, setLocale, t } from "./i18n.js";
|
|
6
6
|
import { cmdMenu } from "./menu.js";
|
|
7
7
|
import { loadStore } from "./store.js";
|
|
@@ -54,6 +54,20 @@ program
|
|
|
54
54
|
.option("--models-dev <id>", t("opt.modelsDev"))
|
|
55
55
|
.option("-y, --yes", t("opt.yes"))
|
|
56
56
|
.action(cmdAdd);
|
|
57
|
+
program
|
|
58
|
+
.command("quick")
|
|
59
|
+
.description(t("cmd.quick"))
|
|
60
|
+
.option("--id <slug>", t("opt.id"))
|
|
61
|
+
.option("--base-url <url>", t("opt.baseUrl"))
|
|
62
|
+
.option("--api-key <key>", t("opt.apiKey"))
|
|
63
|
+
.option("--default-model <id>", t("opt.defaultModel"))
|
|
64
|
+
.option("--small-model <id>", t("opt.smallModel"))
|
|
65
|
+
.option("--reasoning-effort <level>", t("opt.reasoning"))
|
|
66
|
+
.option("--include <globs>", t("opt.include"))
|
|
67
|
+
.option("--exclude <globs>", t("opt.exclude"))
|
|
68
|
+
.option("--no-dedup", t("opt.noDedup"))
|
|
69
|
+
.option("-y, --yes", t("opt.yes"))
|
|
70
|
+
.action(cmdQuickAdd);
|
|
57
71
|
program.command("list").alias("ls").description(t("cmd.list")).action(cmdList);
|
|
58
72
|
program
|
|
59
73
|
.command("remove <id>")
|
package/dist/menu.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
2
|
import prompts from "prompts";
|
|
3
|
-
import { cmdAdd, cmdApps, cmdDiscover, cmdImport, cmdList, cmdRemove, cmdStatus, cmdSync, cmdUpgrade, cmdUse, } from "./commands.js";
|
|
3
|
+
import { cmdAdd, cmdQuickAdd, cmdApps, cmdDiscover, cmdImport, cmdList, cmdRemove, cmdStatus, cmdSync, cmdUpgrade, cmdUse, } from "./commands.js";
|
|
4
4
|
import { getLocale, setLocale, t } from "./i18n.js";
|
|
5
5
|
import { loadStore, saveStore } from "./store.js";
|
|
6
6
|
function bye() {
|
|
@@ -74,6 +74,7 @@ export async function cmdMenu() {
|
|
|
74
74
|
hint: t("menu.selectInstructions"),
|
|
75
75
|
choices: [
|
|
76
76
|
{ title: t("menu.add"), value: "add" },
|
|
77
|
+
{ title: t("menu.quickAdd"), value: "quickAdd" },
|
|
77
78
|
{ title: t("menu.import"), value: "import" },
|
|
78
79
|
{ title: t("menu.use"), value: "use" },
|
|
79
80
|
{ title: t("menu.status"), value: "status" },
|
|
@@ -88,7 +89,10 @@ export async function cmdMenu() {
|
|
|
88
89
|
}, cancel);
|
|
89
90
|
if (action === undefined || action === "quit")
|
|
90
91
|
return;
|
|
91
|
-
if (action === "
|
|
92
|
+
if (action === "quickAdd") {
|
|
93
|
+
await cmdQuickAdd({});
|
|
94
|
+
}
|
|
95
|
+
else if (action === "add") {
|
|
92
96
|
const { src } = await prompts({
|
|
93
97
|
type: "select",
|
|
94
98
|
name: "src",
|
package/dist/sources/ccswitch.js
CHANGED
|
@@ -2,11 +2,13 @@ import fs from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { parse as parseToml } from "smol-toml";
|
|
5
|
-
import { home } from "../fsutil.js";
|
|
5
|
+
import { appDataDir, home } from "../fsutil.js";
|
|
6
6
|
import { slugFromBaseUrl } from "../slug.js";
|
|
7
7
|
import { classifyApi } from "../targets/wire.js";
|
|
8
8
|
/** cc-switch (the Tauri desktop switcher) keeps every managed app's providers in one SQLite file. */
|
|
9
|
-
const storeFile =
|
|
9
|
+
const storeFile = process.platform === "win32" && !process.env.AGENTSW_HOME
|
|
10
|
+
? path.join(appDataDir("cc-switch"), "cc-switch.db")
|
|
11
|
+
: path.join(home, ".cc-switch", "cc-switch.db");
|
|
10
12
|
/** Source id shown in the `import` preview. */
|
|
11
13
|
export const CC_SWITCH_SOURCE = "cc-switch";
|
|
12
14
|
/** Claude Code / Claude Desktop rows: the env block, same shape agentsw's claude adapter writes. */
|
package/dist/store.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
export const configDir =
|
|
2
|
+
import { appDataDir, readJsonIfExists, writeFileAtomic } from "./fsutil.js";
|
|
3
|
+
export const configDir = appDataDir("agentsw");
|
|
4
4
|
export const configFile = path.join(configDir, "config.json");
|
|
5
5
|
export function loadStore() {
|
|
6
6
|
const store = readJsonIfExists(configFile);
|
package/dist/targets/dsh.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import YAML from "yaml";
|
|
4
|
-
import { backupFile, expandHome, home, readTextIfExists, writeFileAtomic } from "../fsutil.js";
|
|
4
|
+
import { backupFile, expandHome, home, localAppDataDir, readTextIfExists, writeFileAtomic } from "../fsutil.js";
|
|
5
5
|
import { apiValue, classifyApi, mergeModels } from "./wire.js";
|
|
6
6
|
/** `$DSH_HOME`, or `~/.dsh` (packages/util/home-paths `resolveDshHome`). */
|
|
7
7
|
function dshHome() {
|
|
8
8
|
const env = process.env.DSH_HOME?.trim();
|
|
9
|
-
return env ? expandHome(env) : path.join(home, ".dsh");
|
|
9
|
+
return env ? expandHome(env) : process.platform === "win32" ? localAppDataDir("dsh") : path.join(home, ".dsh");
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Settings document: `<harness home>/settings.yaml` by default; the extension
|
package/dist/targets/hermes.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import YAML from "yaml";
|
|
4
|
-
import { backupFile, readTextIfExists, writeFileAtomic } from "../fsutil.js";
|
|
4
|
+
import { backupFile, home, localAppDataDir, readTextIfExists, writeFileAtomic } from "../fsutil.js";
|
|
5
5
|
function hermesHome() {
|
|
6
6
|
const env = process.env.HERMES_HOME?.trim();
|
|
7
7
|
if (env)
|
|
8
8
|
return env;
|
|
9
|
-
if (process.platform === "win32"
|
|
10
|
-
return
|
|
9
|
+
if (process.platform === "win32") {
|
|
10
|
+
return localAppDataDir("hermes");
|
|
11
11
|
}
|
|
12
|
-
return path.join(
|
|
12
|
+
return path.join(home, ".hermes");
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* hermes (NousResearch): providers dict + model section in ~/.hermes/config.yaml,
|
package/dist/targets/opencode.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
const
|
|
3
|
+
import { appDataDir, backupFile, readJsonIfExists, writeFileAtomic } from "../fsutil.js";
|
|
4
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR?.trim() || appDataDir("opencode");
|
|
5
|
+
const configFile = path.join(configDir, "opencode.json");
|
|
5
6
|
/**
|
|
6
7
|
* opencode: custom providers in ~/.config/opencode/opencode.json "provider" map,
|
|
7
8
|
* backed by @ai-sdk/openai-compatible or @ai-sdk/anthropic npm loaders.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { backupFile, home, readJsonIfExists, writeFileAtomic } from "../fsutil.js";
|
|
3
|
+
import { appDataDir, backupFile, home, readJsonIfExists, writeFileAtomic } from "../fsutil.js";
|
|
4
4
|
import { slugFromBaseUrl } from "../slug.js";
|
|
5
5
|
function configDir() {
|
|
6
|
-
return process.env.WORKBUDDY_CONFIG_DIR ?? process.env.CODEBUDDY_CONFIG_DIR ??
|
|
6
|
+
return process.env.WORKBUDDY_CONFIG_DIR?.trim() ?? process.env.CODEBUDDY_CONFIG_DIR?.trim() ??
|
|
7
|
+
(process.platform === "win32" ? appDataDir("workbuddy") : path.join(home, ".workbuddy"));
|
|
7
8
|
}
|
|
8
9
|
/**
|
|
9
10
|
* models.json accepts a raw array (desktop writes) or {models, availableModels} (CLI writes).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentsw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "CLI provider switcher for coding agents: manage OpenAI/Anthropic-protocol providers in one place and sync them, with models.dev metadata, into Claude Code, Codex, omp, pi, prime-agent, opencode, Hermes, WorkBuddy and DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|