agentsw 0.9.2 → 0.10.1
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 +50 -4
- package/README.zh-CN.md +44 -3
- package/dist/app-paths.js +127 -0
- package/dist/apps.js +11 -15
- package/dist/commands.js +375 -205
- package/dist/discover.js +21 -11
- package/dist/gateway.js +77 -18
- package/dist/i18n.js +147 -3
- package/dist/index.js +14 -2
- package/dist/menu.js +141 -121
- package/dist/metadata.js +34 -9
- package/dist/modelsdev.js +51 -21
- package/dist/progress.js +116 -0
- package/dist/provider-actions.js +29 -9
- package/dist/remove.js +15 -30
- package/dist/rename.js +17 -32
- package/dist/report.js +67 -0
- package/dist/store.js +25 -0
- package/dist/targets/claudecode.js +12 -9
- package/dist/targets/codex.js +22 -13
- package/dist/targets/dsh.js +20 -35
- package/dist/targets/hermes.js +11 -20
- package/dist/targets/omp.js +15 -7
- package/dist/targets/opencode.js +42 -25
- package/dist/targets/pistyle.js +10 -21
- package/dist/targets/wire.js +22 -7
- package/dist/targets/workbuddy.js +6 -4
- package/dist/ui.js +157 -0
- package/package.json +2 -2
package/dist/discover.js
CHANGED
|
@@ -4,10 +4,15 @@ function modelListUrl(baseUrl) {
|
|
|
4
4
|
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
5
5
|
throw new Error("provider URL must use HTTP or HTTPS");
|
|
6
6
|
const pathname = url.pathname.replace(/\/+$/, "");
|
|
7
|
-
url.pathname =
|
|
7
|
+
url.pathname =
|
|
8
|
+
/\/v\d+(?:beta\d*)?$/.test(pathname) ? `${pathname}/models`
|
|
9
|
+
: /\/models$/.test(pathname) ? pathname
|
|
10
|
+
: `${pathname}/v1/models`;
|
|
8
11
|
url.hash = "";
|
|
9
12
|
return url;
|
|
10
13
|
}
|
|
14
|
+
/** Hard cap on model-list pages; an endpoint emitting endless fresh cursors must not loop forever. */
|
|
15
|
+
export const MAX_MODEL_PAGES = 100;
|
|
11
16
|
function parseModelPage(body) {
|
|
12
17
|
if (!body || typeof body !== "object")
|
|
13
18
|
throw new Error("invalid model-list response");
|
|
@@ -30,7 +35,7 @@ function parseModelPage(body) {
|
|
|
30
35
|
* Resellers expose the id list here but no metadata (context, thinking, modalities) —
|
|
31
36
|
* metadata is filled from models.dev afterwards (enrichModels).
|
|
32
37
|
*/
|
|
33
|
-
export async function discoverProviderModels(opts) {
|
|
38
|
+
export async function discoverProviderModels(opts, hooks = {}) {
|
|
34
39
|
const url = modelListUrl(opts.baseUrl);
|
|
35
40
|
const headers = opts.protocol === "anthropic"
|
|
36
41
|
? { "x-api-key": opts.apiKey, "anthropic-version": "2023-06-01" }
|
|
@@ -40,21 +45,25 @@ export async function discoverProviderModels(opts) {
|
|
|
40
45
|
const initialCursor = url.searchParams.get("after_id");
|
|
41
46
|
if (initialCursor)
|
|
42
47
|
cursors.add(initialCursor);
|
|
43
|
-
|
|
48
|
+
for (let page = 1;; page++) {
|
|
49
|
+
if (page > MAX_MODEL_PAGES) {
|
|
50
|
+
throw new Error(`model-list pagination exceeded ${MAX_MODEL_PAGES} pages; the endpoint keeps reporting more models`);
|
|
51
|
+
}
|
|
44
52
|
const res = await fetch(url.toString(), { headers, signal: AbortSignal.timeout(20000) });
|
|
45
53
|
if (!res.ok)
|
|
46
54
|
throw new Error(`model discovery -> HTTP ${res.status}`);
|
|
47
|
-
const
|
|
48
|
-
for (const id of
|
|
55
|
+
const parsed = parseModelPage(await res.json());
|
|
56
|
+
for (const id of parsed.ids)
|
|
49
57
|
ids.add(id);
|
|
50
|
-
|
|
58
|
+
hooks.onPage?.({ page, count: ids.size });
|
|
59
|
+
if (!parsed.hasMore)
|
|
51
60
|
break;
|
|
52
|
-
if (!
|
|
61
|
+
if (!parsed.lastId || !parsed.lastId.trim())
|
|
53
62
|
throw new Error("model-list pagination is missing last_id");
|
|
54
|
-
if (cursors.has(
|
|
63
|
+
if (cursors.has(parsed.lastId))
|
|
55
64
|
throw new Error("model-list pagination repeated a cursor");
|
|
56
|
-
cursors.add(
|
|
57
|
-
url.searchParams.set("after_id",
|
|
65
|
+
cursors.add(parsed.lastId);
|
|
66
|
+
url.searchParams.set("after_id", parsed.lastId);
|
|
58
67
|
}
|
|
59
68
|
if (ids.size === 0)
|
|
60
69
|
throw new Error("provider returned no models");
|
|
@@ -65,7 +74,7 @@ export async function discoverProviderModels(opts) {
|
|
|
65
74
|
* with each protocol's auth headers. Returns the protocols that responded
|
|
66
75
|
* with a valid model list (at least one model id).
|
|
67
76
|
*/
|
|
68
|
-
export async function probeProtocols(opts) {
|
|
77
|
+
export async function probeProtocols(opts, hooks = {}) {
|
|
69
78
|
const url = modelListUrl(opts.baseUrl).toString();
|
|
70
79
|
const attempts = [
|
|
71
80
|
{ protocol: "openai", headers: { authorization: `Bearer ${opts.apiKey}` } },
|
|
@@ -73,6 +82,7 @@ export async function probeProtocols(opts) {
|
|
|
73
82
|
];
|
|
74
83
|
const supported = [];
|
|
75
84
|
for (const a of attempts) {
|
|
85
|
+
hooks.onAttempt?.(a.protocol);
|
|
76
86
|
try {
|
|
77
87
|
const res = await fetch(url, { headers: a.headers, signal: AbortSignal.timeout(15000) });
|
|
78
88
|
if (!res.ok)
|
package/dist/gateway.js
CHANGED
|
@@ -2,10 +2,18 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { readJsonIfExists, writeFileAtomic } from "./fsutil.js";
|
|
4
4
|
import { configDir } from "./store.js";
|
|
5
|
+
import { warn } from "./ui.js";
|
|
5
6
|
const API_URL = "https://ai-gateway.vercel.sh/v1/models";
|
|
6
7
|
const CACHE_FILE = path.join(configDir, "ai-gateway.json");
|
|
7
8
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
8
9
|
const MAX_BODY_BYTES = 5 * 1024 * 1024;
|
|
10
|
+
/** Whole-request budget. A cold handshake through a tunnel measures ~3s here, so this is generous. */
|
|
11
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
12
|
+
const MAX_ATTEMPTS = 2;
|
|
13
|
+
const RETRY_DELAY_MS = 300;
|
|
14
|
+
/** The endpoint answered — a status, an oversize body, a missing one. A retry repeats the answer. */
|
|
15
|
+
class DefinitiveFailure extends Error {
|
|
16
|
+
}
|
|
9
17
|
const REASONING_EFFORTS = {
|
|
10
18
|
none: true, minimal: true, low: true, medium: true, high: true, xhigh: true, max: true,
|
|
11
19
|
};
|
|
@@ -136,10 +144,10 @@ async function readResponseBody(response) {
|
|
|
136
144
|
const contentLength = response.headers.get("content-length");
|
|
137
145
|
if (contentLength !== null && Number(contentLength) > MAX_BODY_BYTES) {
|
|
138
146
|
await response.body?.cancel().catch(() => { });
|
|
139
|
-
throw new
|
|
147
|
+
throw new DefinitiveFailure("AI Gateway catalog exceeds size limit");
|
|
140
148
|
}
|
|
141
149
|
if (!response.body)
|
|
142
|
-
throw new
|
|
150
|
+
throw new DefinitiveFailure("Missing AI Gateway catalog body");
|
|
143
151
|
const reader = response.body.getReader();
|
|
144
152
|
const decoder = new TextDecoder();
|
|
145
153
|
const chunks = [];
|
|
@@ -151,7 +159,7 @@ async function readResponseBody(response) {
|
|
|
151
159
|
break;
|
|
152
160
|
size += value.byteLength;
|
|
153
161
|
if (size > MAX_BODY_BYTES)
|
|
154
|
-
throw new
|
|
162
|
+
throw new DefinitiveFailure("AI Gateway catalog exceeds size limit");
|
|
155
163
|
chunks.push(decoder.decode(value, { stream: true }));
|
|
156
164
|
}
|
|
157
165
|
chunks.push(decoder.decode());
|
|
@@ -165,6 +173,65 @@ async function readResponseBody(response) {
|
|
|
165
173
|
reader.releaseLock();
|
|
166
174
|
}
|
|
167
175
|
}
|
|
176
|
+
/** Diagnostics for a failure the user cannot otherwise see; opt in with AGENTSW_DEBUG=1. */
|
|
177
|
+
function debug(message) {
|
|
178
|
+
if (process.env.AGENTSW_DEBUG !== "1")
|
|
179
|
+
return;
|
|
180
|
+
warn(message);
|
|
181
|
+
}
|
|
182
|
+
/** One-line reason for a failed attempt, for `AGENTSW_DEBUG` only — never part of the warning. */
|
|
183
|
+
function describeFailure(error, timeoutMs) {
|
|
184
|
+
if (error instanceof Error && error.name === "TimeoutError")
|
|
185
|
+
return `no response within ${timeoutMs}ms`;
|
|
186
|
+
const cause = error instanceof Error ? error.cause : undefined;
|
|
187
|
+
const code = cause && typeof cause === "object" && "code" in cause ? ` (${String(cause.code)})` : "";
|
|
188
|
+
return `${error instanceof Error ? error.message : String(error)}${code}`;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Whole-request budget, overridable with `AGENTSW_GATEWAY_TIMEOUT_MS`. An unusable value is
|
|
192
|
+
* ignored rather than fatal: this is an escape hatch for a slow route, not configuration.
|
|
193
|
+
*/
|
|
194
|
+
function timeoutMs() {
|
|
195
|
+
const raw = process.env.AGENTSW_GATEWAY_TIMEOUT_MS;
|
|
196
|
+
if (raw === undefined)
|
|
197
|
+
return DEFAULT_TIMEOUT_MS;
|
|
198
|
+
const value = Number(raw);
|
|
199
|
+
return Number.isFinite(value) && value > 0 ? value : DEFAULT_TIMEOUT_MS;
|
|
200
|
+
}
|
|
201
|
+
async function requestOnce(budget) {
|
|
202
|
+
const response = await fetch(API_URL, {
|
|
203
|
+
signal: AbortSignal.timeout(budget),
|
|
204
|
+
credentials: "omit",
|
|
205
|
+
redirect: "error",
|
|
206
|
+
});
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
await response.body?.cancel().catch(() => { });
|
|
209
|
+
throw new DefinitiveFailure(`HTTP ${response.status}`);
|
|
210
|
+
}
|
|
211
|
+
return readResponseBody(response);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Read the catalog text, retrying once when the attempt failed before the endpoint could
|
|
215
|
+
* answer. The likeliest failure by far is a transient one: a cold TLS handshake over whatever
|
|
216
|
+
* route this machine has measures an order of magnitude slower than a warm one, so a stall or a
|
|
217
|
+
* reset is far more common than a bad answer. A `DefinitiveFailure` is not retried — the second
|
|
218
|
+
* request would only repeat it — and neither is a body that arrived but would not parse, which
|
|
219
|
+
* is why parsing stays outside this call.
|
|
220
|
+
*/
|
|
221
|
+
async function requestCatalogText() {
|
|
222
|
+
const budget = timeoutMs();
|
|
223
|
+
for (let attempt = 1;; attempt++) {
|
|
224
|
+
try {
|
|
225
|
+
return await requestOnce(budget);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (error instanceof DefinitiveFailure || attempt >= MAX_ATTEMPTS)
|
|
229
|
+
throw error;
|
|
230
|
+
debug(`AI Gateway attempt ${attempt} failed (${describeFailure(error, budget)}); retrying`);
|
|
231
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
168
235
|
/** Optional supplement: offline never fetches, and every failure leaves discovery usable. */
|
|
169
236
|
export async function loadGatewayCatalog(opts = {}) {
|
|
170
237
|
const cached = readCache();
|
|
@@ -173,29 +240,21 @@ export async function loadGatewayCatalog(opts = {}) {
|
|
|
173
240
|
if (!opts.refresh && cached && Date.now() - Date.parse(cached.fetchedAt) < CACHE_TTL_MS)
|
|
174
241
|
return cached;
|
|
175
242
|
try {
|
|
176
|
-
const
|
|
177
|
-
signal: AbortSignal.timeout(15_000),
|
|
178
|
-
credentials: "omit",
|
|
179
|
-
redirect: "error",
|
|
180
|
-
});
|
|
181
|
-
if (!response.ok) {
|
|
182
|
-
await response.body?.cancel().catch(() => { });
|
|
183
|
-
throw new Error("AI Gateway catalog request failed");
|
|
184
|
-
}
|
|
185
|
-
const body = JSON.parse(await readResponseBody(response));
|
|
243
|
+
const body = JSON.parse(await requestCatalogText());
|
|
186
244
|
const catalog = parseGatewayCatalog(body, new Date().toISOString());
|
|
187
245
|
try {
|
|
188
246
|
writeFileAtomic(CACHE_FILE, JSON.stringify({ version: 1, fetchedAt: catalog.fetchedAt, body }) + "\n", 0o600);
|
|
189
247
|
}
|
|
190
248
|
catch {
|
|
191
|
-
|
|
249
|
+
warn("could not cache AI Gateway metadata");
|
|
192
250
|
}
|
|
193
251
|
return catalog;
|
|
194
252
|
}
|
|
195
|
-
catch {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
253
|
+
catch (error) {
|
|
254
|
+
debug(`AI Gateway metadata unavailable: ${describeFailure(error, timeoutMs())}`);
|
|
255
|
+
warn(cached
|
|
256
|
+
? "could not refresh AI Gateway metadata; using cached metadata"
|
|
257
|
+
: "could not load AI Gateway metadata");
|
|
199
258
|
return cached;
|
|
200
259
|
}
|
|
201
260
|
}
|
package/dist/i18n.js
CHANGED
|
@@ -6,6 +6,7 @@ const messages = {
|
|
|
6
6
|
"help.language": { en: "UI language: en | zh-CN (also AGENTSW_LANG)", "zh-CN": "界面语言: en | zh-CN (也可用 AGENTSW_LANG)" },
|
|
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
|
+
"error.limit": { en: "--limit must be a positive integer", "zh-CN": "--limit 必须是正整数" },
|
|
9
10
|
"cmd.add": { en: "add or update a provider (interactive when flags are omitted)", "zh-CN": "添加或更新供应商(省略参数时进入交互流程)" },
|
|
10
11
|
"cmd.quick": { en: "quick add — auto-detect protocol(s) and models from just base URL + API key", "zh-CN": "快速添加 — 只需 base URL + API key,自动探测协议和模型" },
|
|
11
12
|
"cmd.list": { en: "list configured providers (or agent-local entries with --apps)", "zh-CN": "列出已配置供应商(--apps 可查看智能体独有配置)" },
|
|
@@ -130,8 +131,6 @@ const messages = {
|
|
|
130
131
|
"menu.discoverFor": { en: "which provider's model list should be updated?", "zh-CN": "选择要更新模型列表的供应商" },
|
|
131
132
|
"menu.pushRefresh": { en: "also sync this provider to agent configs after updating its model list?", "zh-CN": "更新模型列表后,是否同时将该供应商同步到智能体配置?" },
|
|
132
133
|
"menu.removeProvider": { en: "which provider configuration should be deleted?", "zh-CN": "选择要删除配置的供应商" },
|
|
133
|
-
"menu.reallyRemove": { en: "really remove {id}?", "zh-CN": "确认删除 {id}?" },
|
|
134
|
-
"menu.pruneConfigs": { en: "also remove its entries from app configs?", "zh-CN": "同时从应用配置中移除其条目?" },
|
|
135
134
|
"menu.upgrade": { en: "upgrade all installed agents that have updates?", "zh-CN": "是否升级所有有新版本的已安装智能体?" },
|
|
136
135
|
"menu.installApp": { en: "Install a new coding agent", "zh-CN": "安装新的智能体" },
|
|
137
136
|
"menu.pickApp": { en: "which agent to install?", "zh-CN": "安装哪个智能体?" },
|
|
@@ -149,7 +148,6 @@ const messages = {
|
|
|
149
148
|
"add.baseUrlInvalid": { en: "must start with http(s)://", "zh-CN": "必须以 http(s):// 开头" },
|
|
150
149
|
"add.apiKey": { en: "API key", "zh-CN": "API 密钥(API key)" },
|
|
151
150
|
"add.models": { en: "model ids (comma separated, or leave blank to auto-discover)", "zh-CN": "模型 id(逗号分隔,留空则自动发现)" },
|
|
152
|
-
"add.cancelled": { en: "cancelled", "zh-CN": "已取消" },
|
|
153
151
|
"import.already": { en: "already configured as {id}", "zh-CN": "已配置为 {id}" },
|
|
154
152
|
"import.noneNew": { en: "nothing new to import (every discovered provider is already configured)", "zh-CN": "没有可导入的新供应商(发现项均已配置)" },
|
|
155
153
|
"import.noneFound": { en: "no custom providers found in supported app configs or in cc-switch", "zh-CN": "支持的应用配置与 cc-switch 中均未发现自定义供应商" },
|
|
@@ -258,6 +256,152 @@ const messages = {
|
|
|
258
256
|
},
|
|
259
257
|
"refresh.checked": { en: "checked model metadata ({changed} provider(s) changed)", "zh-CN": "已检查模型元数据({changed} 个供应商有变化)" },
|
|
260
258
|
"refresh.next": { en: "run `agentsw sync` to push updated metadata into app configs", "zh-CN": "运行 `agentsw sync` 将更新的元数据写入各智能体配置" },
|
|
259
|
+
"opt.json": {
|
|
260
|
+
en: "machine-readable JSON on stdout; disables prompts and progress",
|
|
261
|
+
"zh-CN": "stdout 输出机器可读 JSON(同时关闭交互提问与进度)",
|
|
262
|
+
},
|
|
263
|
+
"opt.quiet": {
|
|
264
|
+
en: "suppress progress, hints and warnings (keep results and errors)",
|
|
265
|
+
"zh-CN": "抑制进度、提示与警告(保留结果与错误)",
|
|
266
|
+
},
|
|
267
|
+
"opt.noColor": { en: "disable colored output (NO_COLOR also works)", "zh-CN": "关闭彩色输出(也支持 NO_COLOR)" },
|
|
268
|
+
"error.jsonRequiresCommand": {
|
|
269
|
+
en: "--json requires an explicit command (for example: agentsw status --json)",
|
|
270
|
+
"zh-CN": "--json 需要指定命令(例如 agentsw status --json)",
|
|
271
|
+
},
|
|
272
|
+
"common.cancelled": { en: "cancelled", "zh-CN": "已取消" },
|
|
273
|
+
"menu.cancelledAction": { en: "cancelled — back to menu", "zh-CN": "已取消 —— 返回菜单" },
|
|
274
|
+
"menu.back": { en: "← Back to main menu", "zh-CN": "← 返回主菜单" },
|
|
275
|
+
"progress.probe": { en: "probing {url} ({protocol}) …", "zh-CN": "正在探测 {url}({protocol})…" },
|
|
276
|
+
"progress.discoverPage": {
|
|
277
|
+
en: "listing models from {url} — page {page}, {count} models",
|
|
278
|
+
"zh-CN": "正在从 {url} 获取模型列表 —— 第 {page} 页,{count} 个模型",
|
|
279
|
+
},
|
|
280
|
+
"progress.catalog": { en: "loading models.dev catalog …", "zh-CN": "正在加载 models.dev 目录…" },
|
|
281
|
+
"progress.gateway": { en: "loading AI Gateway catalog …", "zh-CN": "正在加载 AI Gateway 目录…" },
|
|
282
|
+
"progress.metadata": { en: "supplementing model metadata …", "zh-CN": "正在补全模型元数据…" },
|
|
283
|
+
"progress.apps": {
|
|
284
|
+
en: "checking installed and latest versions … ({done}/{total})",
|
|
285
|
+
"zh-CN": "正在检查已安装版本与最新版本…({done}/{total})",
|
|
286
|
+
},
|
|
287
|
+
"progress.refresh": { en: "refreshing model metadata …", "zh-CN": "正在刷新模型元数据…" },
|
|
288
|
+
"add.retryDiscovery": {
|
|
289
|
+
en: "model discovery failed: {error}. Try again with the same endpoint?",
|
|
290
|
+
"zh-CN": "模型发现失败:{error}。用同一端点重试吗?",
|
|
291
|
+
},
|
|
292
|
+
"quick.retryProbe": {
|
|
293
|
+
en: "no protocol detected at {url}. Re-enter the base URL and API key?",
|
|
294
|
+
"zh-CN": "{url} 未探测到可用协议。重新输入 base URL 与 API key?",
|
|
295
|
+
},
|
|
296
|
+
"import.retryDiscovery": {
|
|
297
|
+
en: "discovery failed for {id}: {error}. Retry discovery for this provider?",
|
|
298
|
+
"zh-CN": "{id} 的模型发现失败:{error}。为该供应商重试?",
|
|
299
|
+
},
|
|
300
|
+
"import.skippedAfterFailure": {
|
|
301
|
+
en: "skipped {id}: discovery failed; add it later with agentsw add",
|
|
302
|
+
"zh-CN": "已跳过 {id}:发现失败;之后可用 agentsw add 补上",
|
|
303
|
+
},
|
|
304
|
+
"models.catalogUnavailable": {
|
|
305
|
+
en: "models.dev catalog unavailable (offline and no cache)\nrun: agentsw models --refresh (or check your network)",
|
|
306
|
+
"zh-CN": "models.dev 目录不可用(离线且无缓存)\n请运行 agentsw models --refresh(或检查网络)",
|
|
307
|
+
},
|
|
308
|
+
"models.usage": {
|
|
309
|
+
en: "usage: agentsw models <query> | agentsw models --provider <id>",
|
|
310
|
+
"zh-CN": "用法: agentsw models <查询> | agentsw models --provider <id>",
|
|
311
|
+
},
|
|
312
|
+
"models.noMatch": {
|
|
313
|
+
en: "no models.dev entries match \"{query}\"\nrun: agentsw models --refresh to update the catalog",
|
|
314
|
+
"zh-CN": "models.dev 中没有匹配 \"{query}\" 的条目\n请运行 agentsw models --refresh 更新目录",
|
|
315
|
+
},
|
|
316
|
+
"models.metadataRequiresProvider": {
|
|
317
|
+
en: "--metadata requires --provider (run: agentsw models --provider <id> --metadata)",
|
|
318
|
+
"zh-CN": "--metadata 需要配合 --provider(请运行 agentsw models --provider <id> --metadata)",
|
|
319
|
+
},
|
|
320
|
+
"status.noProviders": {
|
|
321
|
+
en: "no providers saved yet\nrun: agentsw add — or agentsw import to pull providers from your agents",
|
|
322
|
+
"zh-CN": "尚未保存任何供应商\n请运行 agentsw add,或用 agentsw import 从各智能体导入",
|
|
323
|
+
},
|
|
324
|
+
"apps.checking": { en: "checking installed and latest versions ...", "zh-CN": "正在检查已安装版本与最新版本..." },
|
|
325
|
+
"apps.notInstalled": { en: "not installed", "zh-CN": "未安装" },
|
|
326
|
+
"apps.installable": { en: "installable", "zh-CN": "可安装" },
|
|
327
|
+
"apps.upToDate": { en: "up to date", "zh-CN": "已是最新" },
|
|
328
|
+
"apps.upgradeAvailable": { en: "upgrade available", "zh-CN": "可升级" },
|
|
329
|
+
"apps.unknown": { en: "unknown", "zh-CN": "未知" },
|
|
330
|
+
"apps.checkFailedProbe": { en: "installed version check failed", "zh-CN": "已安装版本检查失败" },
|
|
331
|
+
"apps.checkFailedUnknown": { en: "installed version unknown", "zh-CN": "已安装版本未知" },
|
|
332
|
+
"apps.checkFailedLatest": { en: "latest version unavailable", "zh-CN": "无法获取最新版本" },
|
|
333
|
+
"apps.upgradeWith": { en: "upgrade with: agentsw upgrade {ids}", "zh-CN": "升级命令: agentsw upgrade {ids}" },
|
|
334
|
+
"install.unknownApp": { en: "unknown app \"{value}\" (supported: {apps})", "zh-CN": "未知应用 \"{value}\"(支持:{apps})" },
|
|
335
|
+
"install.notInstallable": {
|
|
336
|
+
en: "{name} is not installable on {platform} (or is managed by its desktop app)",
|
|
337
|
+
"zh-CN": "{name} 无法在 {platform} 上安装(或由桌面应用自行管理)",
|
|
338
|
+
},
|
|
339
|
+
"install.already": {
|
|
340
|
+
en: "{name} already installed ({version}); use `agentsw upgrade {id}`",
|
|
341
|
+
"zh-CN": "{name} 已安装({version});可用 `agentsw upgrade {id}`",
|
|
342
|
+
},
|
|
343
|
+
"install.installing": { en: "installing {name}: {command}", "zh-CN": "正在安装 {name}:{command}" },
|
|
344
|
+
"install.notDetected": {
|
|
345
|
+
en: "{name}: installer completed but the app is still not detected; check the installation and PATH",
|
|
346
|
+
"zh-CN": "{name}:安装命令已结束但仍未检测到该应用;请检查安装与 PATH",
|
|
347
|
+
},
|
|
348
|
+
"install.versionUnknown": {
|
|
349
|
+
en: "{name}: installer completed; app detected but version unknown",
|
|
350
|
+
"zh-CN": "{name}:安装命令已结束;检测到应用但版本未知",
|
|
351
|
+
},
|
|
352
|
+
"install.installed": { en: "{name} installed: {version}", "zh-CN": "{name} 已安装:{version}" },
|
|
353
|
+
"upgrade.unknownApps": { en: "unknown app(s): {ids}", "zh-CN": "未知应用:{ids}" },
|
|
354
|
+
"upgrade.checking": { en: "checking versions ...", "zh-CN": "正在检查版本..." },
|
|
355
|
+
"upgrade.statusUnknown": { en: "{id}: {reason}; update status unknown", "zh-CN": "{id}:{reason};升级状态未知" },
|
|
356
|
+
"upgrade.cannotDetermine": {
|
|
357
|
+
en: "could not determine update status for every installed app",
|
|
358
|
+
"zh-CN": "无法确定所有已安装应用的升级状态",
|
|
359
|
+
},
|
|
360
|
+
"upgrade.allCurrent": { en: "all checked apps are up to date", "zh-CN": "已检查的应用均为最新" },
|
|
361
|
+
"upgrade.none": { en: "no installed CLI-managed apps to upgrade", "zh-CN": "没有可升级的、由 CLI 管理的应用" },
|
|
362
|
+
"upgrade.notCli": { en: "{id}: not CLI-upgradable", "zh-CN": "{id}:不支持通过 CLI 升级" },
|
|
363
|
+
"upgrade.notInstalled": {
|
|
364
|
+
en: "{id}: not installed (use `agentsw install {id}`)",
|
|
365
|
+
"zh-CN": "{id}:未安装(请使用 `agentsw install {id}`)",
|
|
366
|
+
},
|
|
367
|
+
"upgrade.upgrading": { en: "upgrading {name}: {command}", "zh-CN": "正在升级 {name}:{command}" },
|
|
368
|
+
"upgrade.unknownVersion": {
|
|
369
|
+
en: "upgrade command completed but installed version is unknown or app is not detected",
|
|
370
|
+
"zh-CN": "升级命令已结束,但已安装版本未知或未检测到应用",
|
|
371
|
+
},
|
|
372
|
+
"upgrade.olderThanAvailable": {
|
|
373
|
+
en: "upgrade command completed but {version} is older than available {expected}",
|
|
374
|
+
"zh-CN": "升级命令已结束,但 {version} 低于可用的 {expected}",
|
|
375
|
+
},
|
|
376
|
+
"upgrade.done": { en: "{id} -> {version}", "zh-CN": "{id} -> {version}" },
|
|
377
|
+
"upgrade.failed": { en: "{id} upgrade failed: {message}", "zh-CN": "{id} 升级失败:{message}" },
|
|
378
|
+
"discover.defaultMissing": {
|
|
379
|
+
en: "default model {model} no longer listed; keeping it anyway",
|
|
380
|
+
"zh-CN": "默认模型 {model} 已不在列表中;仍然保留",
|
|
381
|
+
},
|
|
382
|
+
"discover.next": {
|
|
383
|
+
en: "run `agentsw sync` to push into app configs",
|
|
384
|
+
"zh-CN": "运行 `agentsw sync` 写入各智能体配置",
|
|
385
|
+
},
|
|
386
|
+
"dryRun.skip": { en: "skip", "zh-CN": "跳过" },
|
|
387
|
+
"dryRun.wouldWrite": {
|
|
388
|
+
en: "dry run — {count} file(s) would be written:",
|
|
389
|
+
"zh-CN": "预演 — 将写入 {count} 个文件:",
|
|
390
|
+
},
|
|
391
|
+
"preview.unchanged": { en: "unchanged", "zh-CN": "未变化" },
|
|
392
|
+
"preview.header": { en: "--- {file} (redacted configuration)", "zh-CN": "--- {file}(已脱敏配置)" },
|
|
393
|
+
"preview.contentWithheld": {
|
|
394
|
+
en: "[content withheld: unsupported or malformed configuration]",
|
|
395
|
+
"zh-CN": "[内容已隐去:配置格式不支持或无法解析]",
|
|
396
|
+
},
|
|
397
|
+
"preview.onlyRedacted": {
|
|
398
|
+
en: "[only redacted values or formatting changed]",
|
|
399
|
+
"zh-CN": "[仅脱敏值或格式发生变化]",
|
|
400
|
+
},
|
|
401
|
+
"preview.failedSafely": {
|
|
402
|
+
en: "configuration could not be previewed safely",
|
|
403
|
+
"zh-CN": "无法安全预览配置",
|
|
404
|
+
},
|
|
261
405
|
};
|
|
262
406
|
let current = detectSystemLocale();
|
|
263
407
|
export function normalizeLocale(value) {
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { detectSystemLocale, extractCliLocale, normalizeLocale, setLocale, t } f
|
|
|
6
6
|
import { cmdMenu } from "./menu.js";
|
|
7
7
|
import { loadStore } from "./store.js";
|
|
8
8
|
import { cmdListLocalProviders, cmdRemoveProvider, cmdRename } from "./provider-actions.js";
|
|
9
|
+
import { configureOutput, error, isJson, setCommandName } from "./ui.js";
|
|
9
10
|
// Locale lookup is best effort: help/version and app-local commands do not need the store.
|
|
10
11
|
function savedLocale() {
|
|
11
12
|
try {
|
|
@@ -31,7 +32,16 @@ program
|
|
|
31
32
|
.name("agentsw")
|
|
32
33
|
.description(t("root.description"))
|
|
33
34
|
.option("--lang <locale>", t("help.language"), parseLocale)
|
|
35
|
+
.option("--json", t("opt.json"))
|
|
36
|
+
.option("-q, --quiet", t("opt.quiet"))
|
|
37
|
+
.option("--no-color", t("opt.noColor"))
|
|
34
38
|
.version(version);
|
|
39
|
+
// Root options are accepted on either side of the subcommand; read the merged view and publish it once.
|
|
40
|
+
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
41
|
+
const opts = actionCommand.optsWithGlobals();
|
|
42
|
+
configureOutput({ json: opts.json === true, quiet: opts.quiet === true, version });
|
|
43
|
+
setCommandName(actionCommand.name());
|
|
44
|
+
});
|
|
35
45
|
// bare invocation -> interactive menu (help on non-TTY); unknown subcommand -> error.
|
|
36
46
|
// excess arguments are surfaced by us, so the message names the offending command.
|
|
37
47
|
program
|
|
@@ -40,6 +50,9 @@ program
|
|
|
40
50
|
const cmd = handlerArgs[handlerArgs.length - 1];
|
|
41
51
|
if (cmd.args.length > 0)
|
|
42
52
|
program.error(t("error.unknownCommand", { value: cmd.args[0] }));
|
|
53
|
+
// The menu is inherently interactive; reject instead of silently ignoring the flag.
|
|
54
|
+
if (isJson())
|
|
55
|
+
error(t("error.jsonRequiresCommand"));
|
|
43
56
|
if (process.stdin.isTTY)
|
|
44
57
|
return cmdMenu();
|
|
45
58
|
program.outputHelp();
|
|
@@ -175,6 +188,5 @@ program
|
|
|
175
188
|
.option("--gateway-models <json>", t("opt.gatewayModels"))
|
|
176
189
|
.action(cmdRefreshMeta);
|
|
177
190
|
program.parseAsync().catch((err) => {
|
|
178
|
-
|
|
179
|
-
process.exit(1);
|
|
191
|
+
error(err.message);
|
|
180
192
|
});
|