@armadra/agent 0.6.0 → 0.6.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/CHANGELOG.md +26 -0
- package/CHANGELOG.zh-CN.md +20 -0
- package/dist/ai/providers/discovered-cache.d.ts +48 -0
- package/dist/ai/providers/discovered-cache.js +102 -0
- package/dist/ai/providers/model-visibility.d.ts +33 -0
- package/dist/ai/providers/model-visibility.js +70 -0
- package/dist/ai/providers/registry.d.ts +2 -0
- package/dist/ai/providers/registry.js +6 -1
- package/dist/auth/chatgpt/cli.d.ts +3 -1
- package/dist/auth/chatgpt/cli.js +31 -4
- package/dist/bundle/ama.cjs +1343 -640
- package/dist/cli/compose-providers.d.ts +1 -0
- package/dist/cli/compose-providers.js +6 -0
- package/dist/cli/subcommands/config-set.js +19 -11
- package/dist/cli/subcommands/models-discover.d.ts +2 -1
- package/dist/cli/subcommands/models-discover.js +17 -1
- package/dist/cli/subcommands/models-enable.d.ts +13 -0
- package/dist/cli/subcommands/models-enable.js +145 -0
- package/dist/cli/subcommands/models.d.ts +2 -0
- package/dist/cli/subcommands/models.js +10 -2
- package/dist/config/json-schema.js +1 -1
- package/dist/config/schema-w5.js +9 -2
- package/dist/config/settings-registry.d.ts +3 -0
- package/dist/config/settings-registry.js +1 -0
- package/dist/config/types-w5.d.ts +7 -0
- package/dist/config/types-w5.js +2 -0
- package/dist/i18n/catalog.d.ts +43 -3
- package/dist/i18n/messages/auth.d.ts +4 -2
- package/dist/i18n/messages/auth.js +14 -2
- package/dist/i18n/messages/cli-help.js +4 -0
- package/dist/i18n/messages/config-keys.d.ts +4 -0
- package/dist/i18n/messages/config-keys.js +6 -2
- package/dist/i18n/messages/config.d.ts +6 -0
- package/dist/i18n/messages/config.js +2 -0
- package/dist/i18n/messages/errors.d.ts +1 -1
- package/dist/i18n/messages/interactive-startup.d.ts +0 -4
- package/dist/i18n/messages/interactive-startup.js +0 -4
- package/dist/i18n/messages/interactive.d.ts +0 -4
- package/dist/i18n/messages/panels.d.ts +43 -0
- package/dist/i18n/messages/panels.js +43 -0
- package/dist/i18n/messages/settings.d.ts +7 -0
- package/dist/i18n/messages/settings.js +7 -0
- package/dist/i18n/messages/subcommands-config.d.ts +2 -0
- package/dist/i18n/messages/subcommands-config.js +2 -0
- package/dist/i18n/messages/subcommands-models.d.ts +34 -0
- package/dist/i18n/messages/subcommands-models.js +34 -0
- package/dist/i18n/messages/subcommands.d.ts +28 -0
- package/dist/i18n/messages/subcommands.js +3 -0
- package/dist/modes/interactive/commands.d.ts +3 -0
- package/dist/modes/interactive/commands.js +18 -11
- package/dist/modes/interactive/config-ui.js +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -0
- package/dist/modes/interactive/model-items.d.ts +45 -0
- package/dist/modes/interactive/model-items.js +134 -0
- package/dist/modes/interactive/model-picker.d.ts +57 -0
- package/dist/modes/interactive/model-picker.js +191 -0
- package/dist/modes/interactive/startup-ui.d.ts +3 -9
- package/dist/modes/interactive/startup-ui.js +8 -60
- package/dist/tui/components/select-list.d.ts +2 -0
- package/dist/tui/components/select-list.js +7 -0
- package/docs/en/providers.md +2 -0
- package/docs/en/tui.md +8 -1
- package/docs/providers.md +6 -0
- package/docs/tui.md +8 -1
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* - [W6-O] ChatGPT 登录(docs/wave6-plan.md D14):`chatgpt` 的缺省渠道按 auth.json 条目的 flavor 定(用户写了
|
|
13
13
|
* `defaultChannel` 时不动);`AMA_CHATGPT_BASE_URL` 改该渠道地址;`auth.chatgpt.originator` 改 codex 渠道的
|
|
14
14
|
* `originator` 头;OAuth 刷新拿到 `auth.chatgpt` 配置与同一份 env。
|
|
15
|
+
* - 发现缓存(`<dataDir>/models/discovered/<provider>.json`)并入模型表为空的供应商(discovered-cache.ts)。
|
|
15
16
|
*/
|
|
16
17
|
import type { ApiRegistry } from "../ai/apis/api.js";
|
|
17
18
|
import { ProviderRegistry } from "../ai/providers/registry.js";
|
|
@@ -12,9 +12,12 @@
|
|
|
12
12
|
* - [W6-O] ChatGPT 登录(docs/wave6-plan.md D14):`chatgpt` 的缺省渠道按 auth.json 条目的 flavor 定(用户写了
|
|
13
13
|
* `defaultChannel` 时不动);`AMA_CHATGPT_BASE_URL` 改该渠道地址;`auth.chatgpt.originator` 改 codex 渠道的
|
|
14
14
|
* `originator` 头;OAuth 刷新拿到 `auth.chatgpt` 配置与同一份 env。
|
|
15
|
+
* - 发现缓存(`<dataDir>/models/discovered/<provider>.json`)并入模型表为空的供应商(discovered-cache.ts)。
|
|
15
16
|
*/
|
|
16
17
|
import { loadModelsDevIndex } from "../ai/providers/models-dev-cache.js";
|
|
17
18
|
import { ProviderRegistry, discoverLocalModels } from "../ai/providers/registry.js";
|
|
19
|
+
import { mergeDiscoveredModels } from "../ai/providers/discovered-cache.js";
|
|
20
|
+
import { lazyIndex } from "../ai/providers/enrich.js";
|
|
18
21
|
import { readOAuthEntry } from "../auth/oauth/token-store.js";
|
|
19
22
|
import { CHATGPT_BASE_URLS, CHATGPT_PROVIDER_ID } from "../auth/chatgpt/presets.js";
|
|
20
23
|
export const LOCAL_PROBE_TIMEOUT_MS = 300;
|
|
@@ -104,6 +107,9 @@ export async function buildProviderRegistry(input, options = {}) {
|
|
|
104
107
|
if (provider !== undefined)
|
|
105
108
|
registry = make({ provider, apiKey: cli.apiKey });
|
|
106
109
|
}
|
|
110
|
+
// 发现缓存(`ama auth login chatgpt` / `ama models discover chatgpt`)并入模型表为空的供应商
|
|
111
|
+
if (dataDir !== undefined)
|
|
112
|
+
mergeDiscoveredModels(registry, dataDir, lazyIndex(() => loadModelsDevIndex(dataDir)));
|
|
107
113
|
const probe = options.probeLocal ?? env[NO_LOCAL_PROBE_ENV] !== "1";
|
|
108
114
|
if (probe && config.defaultModel === undefined && !anyKeyConfigured(registry)) {
|
|
109
115
|
await probeLocalProviders(registry, options.probeTimeoutMs ?? LOCAL_PROBE_TIMEOUT_MS);
|
|
@@ -62,16 +62,24 @@ function textRow(value) {
|
|
|
62
62
|
function scopeLabel(scope) {
|
|
63
63
|
return msg().settings.scope[scope];
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Receipt of a write. When a higher layer (env, project, cli, profile) still hides the key, the first line shows the
|
|
67
|
+
* value just written and its layer, and the second line names the overriding source and the effective value.
|
|
68
|
+
*/
|
|
69
|
+
function report(io, result, written) {
|
|
66
70
|
const s = msg().settings;
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
io.stdout(`${s.
|
|
74
|
-
|
|
71
|
+
const effective = formatSettingValue(result.after.value);
|
|
72
|
+
const scope = scopeLabel(result.scope);
|
|
73
|
+
const by = result.after.envName ?? result.overriddenBy;
|
|
74
|
+
if (written === undefined)
|
|
75
|
+
io.stdout(`${s.unsetDone(result.key, effective, scope)}\n`);
|
|
76
|
+
else if (by === undefined)
|
|
77
|
+
io.stdout(`${s.setDone(result.key, effective, scope)}\n`);
|
|
78
|
+
else
|
|
79
|
+
io.stdout(`${s.writtenTo(result.key, formatSettingValue(written), scope)}\n`);
|
|
80
|
+
if (by === undefined)
|
|
81
|
+
return;
|
|
82
|
+
io.stdout(`${written === undefined ? s.stillOverridden(result.key, by) : s.overriddenNow(by, effective)}\n`);
|
|
75
83
|
}
|
|
76
84
|
/** Confirm persisting `permission.mode full-auto`: TTY asks, otherwise `--yes` is required. */
|
|
77
85
|
async function confirmBypass(io, yes, deps) {
|
|
@@ -118,7 +126,7 @@ export async function runConfigEdit(action, argv, io, deps = {}) {
|
|
|
118
126
|
return ExitCode.Ok;
|
|
119
127
|
}
|
|
120
128
|
if (action === "unset") {
|
|
121
|
-
report(io, setConfigValue({ ...input, scope, key, value: undefined }),
|
|
129
|
+
report(io, setConfigValue({ ...input, scope, key, value: undefined }), undefined);
|
|
122
130
|
return ExitCode.Ok;
|
|
123
131
|
}
|
|
124
132
|
const raw = positionals.slice(1).join(" ");
|
|
@@ -132,6 +140,6 @@ export async function runConfigEdit(action, argv, io, deps = {}) {
|
|
|
132
140
|
// first write to a fresh config dir: lay out config.json + config.schema.json like `ama init`
|
|
133
141
|
if (scope === "user" && !existsSync(join(input.configDir, CONFIG_FILE)))
|
|
134
142
|
initConfigDir(input.configDir);
|
|
135
|
-
report(io, setConfigValue({ ...input, scope, key, value }), value
|
|
143
|
+
report(io, setConfigValue({ ...input, scope, key, value }), value);
|
|
136
144
|
return ExitCode.Ok;
|
|
137
145
|
}
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* 匹配结果;`--write` 跳过 models.dev 标明不支持工具调用的模型。元数据不写进配置——运行时从缓存补。
|
|
19
19
|
*
|
|
20
20
|
* [W6-O] `chatgpt`:按登录的 flavor 调对应后端的模型列表(SIWC `GET /v1/models` 筛 `visibility: list`;codex
|
|
21
|
-
* `GET /models?client_version=…`),只取 slug 与显示名;`--probe`
|
|
21
|
+
* `GET /models?client_version=…`),只取 slug 与显示名;`--probe` 不适用(订阅后端只有一种协议)。结果另写进
|
|
22
|
+
* 发现缓存 `<dataDir>/models/discovered/chatgpt.json`(discovered-cache.ts),`/model` 选择器由此列出。
|
|
22
23
|
*/
|
|
23
24
|
import type { Api, Model, ProviderData, ProviderRegistryApi } from "../../ai/types.js";
|
|
24
25
|
import type { ModelsAction } from "./models.js";
|
|
@@ -18,13 +18,15 @@
|
|
|
18
18
|
* 匹配结果;`--write` 跳过 models.dev 标明不支持工具调用的模型。元数据不写进配置——运行时从缓存补。
|
|
19
19
|
*
|
|
20
20
|
* [W6-O] `chatgpt`:按登录的 flavor 调对应后端的模型列表(SIWC `GET /v1/models` 筛 `visibility: list`;codex
|
|
21
|
-
* `GET /models?client_version=…`),只取 slug 与显示名;`--probe`
|
|
21
|
+
* `GET /models?client_version=…`),只取 slug 与显示名;`--probe` 不适用(订阅后端只有一种协议)。结果另写进
|
|
22
|
+
* 发现缓存 `<dataDir>/models/discovered/chatgpt.json`(discovered-cache.ts),`/model` 选择器由此列出。
|
|
22
23
|
*/
|
|
23
24
|
import { authHeaders, mergeHeaders } from "../../ai/http.js";
|
|
24
25
|
import { listChatGptModels } from "../../auth/chatgpt/backend-client.js";
|
|
25
26
|
import { CHATGPT_PROVIDER_ID } from "../../auth/chatgpt/presets.js";
|
|
26
27
|
import { liveToken } from "../../auth/oauth/live.js";
|
|
27
28
|
import { discoverLocalModels, materializeModel } from "../../ai/providers/registry.js";
|
|
29
|
+
import { writeDiscoveredCache } from "../../ai/providers/discovered-cache.js";
|
|
28
30
|
import { withCustomDefaults } from "../../ai/providers/catalog.js";
|
|
29
31
|
import { describeModelsDev, loadModelsDevIndex } from "../../ai/providers/models-dev-cache.js";
|
|
30
32
|
import { matchLabel, modelsDevFields } from "../../ai/providers/models-dev.js";
|
|
@@ -197,6 +199,8 @@ async function run(ctx) {
|
|
|
197
199
|
}
|
|
198
200
|
const configured = new Map(provider.models.map((m) => [m.id, m]));
|
|
199
201
|
io.stdout(m.found(provider.id, found.length, modelsUrl(provider)));
|
|
202
|
+
if (provider.id === CHATGPT_PROVIDER_ID)
|
|
203
|
+
cacheChatGpt(ctx, found, key.apiKey);
|
|
200
204
|
// models.dev 只读本地(快照 ⊕ `ama models refresh` 的覆盖),不联网。
|
|
201
205
|
const mdIndex = loadModelsDevIndex(ctx.level.dataDir);
|
|
202
206
|
io.stdout(`${describeModelsDev(ctx.level.dataDir)}\n`);
|
|
@@ -252,6 +256,18 @@ async function run(ctx) {
|
|
|
252
256
|
}
|
|
253
257
|
return ExitCode.Ok;
|
|
254
258
|
}
|
|
259
|
+
/** chatgpt 的发现结果写进发现缓存(选择器与注册表读它);写失败不影响列表输出。 */
|
|
260
|
+
function cacheChatGpt(ctx, found, apiKey) {
|
|
261
|
+
try {
|
|
262
|
+
const flavor = liveToken(apiKey)?.flavor;
|
|
263
|
+
const models = found.map((m) => ({ id: m.id, ...(m.name !== m.id ? { name: m.name } : {}) }));
|
|
264
|
+
const path = writeDiscoveredCache(ctx.level.dataDir, CHATGPT_PROVIDER_ID, { models, flavor });
|
|
265
|
+
ctx.io.stdout(msg().subcommands.discover.cached(path));
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
ctx.io.stderr(`ama: ${error.message}\n`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
255
271
|
export const DISCOVER_ACTION = {
|
|
256
272
|
usage: "ama models discover <provider> [--probe] [--write] [--limit <n>] [--concurrency <n>] [--probe-timeout <ms>]",
|
|
257
273
|
required: "<provider>",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama models enable <ref…>` / `ama models disable <ref…>` / `ama models list --enabled`:编辑与查看用户级
|
|
3
|
+
* `models.enabled`(模型选择器的显式清单,见 ai/providers/model-visibility.ts)。
|
|
4
|
+
*
|
|
5
|
+
* 写回走 config/edit.ts(重读目标文件、校验、`.bak` 备份、原子写);`models` 只认用户级,没有 `--project`。
|
|
6
|
+
* 引用要是 `provider/model[@channel]` 或 `provider/*`,供应商必须存在;模型不在模型表里只警告(中转站与
|
|
7
|
+
* 订阅后端接受任意 id)。清单移空时删掉这个键。
|
|
8
|
+
*/
|
|
9
|
+
import type { ModelsAction, ModelsActionContext } from "./models.js";
|
|
10
|
+
/** `ama models list --enabled`:清单(及各项供应商状态);没设清单时列出选择器缺省显示的模型。 */
|
|
11
|
+
export declare function listEnabled(ctx: ModelsActionContext): Promise<number>;
|
|
12
|
+
export declare const ENABLE_ACTION: ModelsAction;
|
|
13
|
+
export declare const DISABLE_ACTION: ModelsAction;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama models enable <ref…>` / `ama models disable <ref…>` / `ama models list --enabled`:编辑与查看用户级
|
|
3
|
+
* `models.enabled`(模型选择器的显式清单,见 ai/providers/model-visibility.ts)。
|
|
4
|
+
*
|
|
5
|
+
* 写回走 config/edit.ts(重读目标文件、校验、`.bak` 备份、原子写);`models` 只认用户级,没有 `--project`。
|
|
6
|
+
* 引用要是 `provider/model[@channel]` 或 `provider/*`,供应商必须存在;模型不在模型表里只警告(中转站与
|
|
7
|
+
* 订阅后端接受任意 id)。清单移空时删掉这个键。
|
|
8
|
+
*/
|
|
9
|
+
import { accessReady, addEnabled, enabledList, providerAccess, removeEnabled, splitEnabledRef, } from "../../ai/providers/model-visibility.js";
|
|
10
|
+
import { readSnapshot, setConfigValue } from "../../config/edit.js";
|
|
11
|
+
import { MODELS_ENABLED_REF } from "../../config/types-w5.js";
|
|
12
|
+
import { msg } from "../../i18n/index.js";
|
|
13
|
+
import { UsageError } from "../args.js";
|
|
14
|
+
import { ExitCode } from "../exit-codes.js";
|
|
15
|
+
function layerInput(ctx) {
|
|
16
|
+
const input = {
|
|
17
|
+
configDir: ctx.level.configDir,
|
|
18
|
+
cwd: ctx.io.cwd,
|
|
19
|
+
env: ctx.io.env,
|
|
20
|
+
};
|
|
21
|
+
if (ctx.level.profile !== undefined) {
|
|
22
|
+
input.profile = { config: ctx.level.profile.config };
|
|
23
|
+
input.hasProfile = true;
|
|
24
|
+
}
|
|
25
|
+
return input;
|
|
26
|
+
}
|
|
27
|
+
/** 校验引用;供应商不存在返回退出码,模型不在表里只警告。 */
|
|
28
|
+
function checkRefs(ctx, refs) {
|
|
29
|
+
const m = msg().subcommands.modelsEnabled;
|
|
30
|
+
for (const ref of refs) {
|
|
31
|
+
const parts = splitEnabledRef(ref);
|
|
32
|
+
if (!MODELS_ENABLED_REF.test(ref) || parts === undefined)
|
|
33
|
+
throw new UsageError(m.badRef(ref));
|
|
34
|
+
const provider = ctx.registry.get(parts.provider);
|
|
35
|
+
if (provider === undefined) {
|
|
36
|
+
ctx.io.stderr(msg().subcommands.common.providerNotFound(parts.provider));
|
|
37
|
+
return ExitCode.NoModel;
|
|
38
|
+
}
|
|
39
|
+
if (parts.model !== "*" &&
|
|
40
|
+
provider.models.length > 0 &&
|
|
41
|
+
!provider.models.some((model) => model.id === parts.model))
|
|
42
|
+
ctx.io.stderr(m.unknownModel(ref));
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
function userList(input) {
|
|
47
|
+
return enabledList(readSnapshot(input).user?.models?.enabled);
|
|
48
|
+
}
|
|
49
|
+
async function enable(ctx) {
|
|
50
|
+
const m = msg().subcommands.modelsEnabled;
|
|
51
|
+
const refs = [...new Set(ctx.args)];
|
|
52
|
+
const bad = checkRefs(ctx, refs);
|
|
53
|
+
if (bad !== undefined)
|
|
54
|
+
return bad;
|
|
55
|
+
const input = layerInput(ctx);
|
|
56
|
+
const current = userList(input);
|
|
57
|
+
const fresh = refs.filter((ref) => !(current ?? []).includes(ref));
|
|
58
|
+
if (fresh.length === 0) {
|
|
59
|
+
ctx.io.stdout(m.alreadyListed(refs.join(", ")));
|
|
60
|
+
return ExitCode.Ok;
|
|
61
|
+
}
|
|
62
|
+
const result = setConfigValue({
|
|
63
|
+
...input,
|
|
64
|
+
scope: "user",
|
|
65
|
+
key: "models.enabled",
|
|
66
|
+
value: addEnabled(current, fresh),
|
|
67
|
+
});
|
|
68
|
+
ctx.io.stdout(m.added(fresh.join(", "), result.path));
|
|
69
|
+
return ExitCode.Ok;
|
|
70
|
+
}
|
|
71
|
+
async function disable(ctx) {
|
|
72
|
+
const m = msg().subcommands.modelsEnabled;
|
|
73
|
+
const refs = [...new Set(ctx.args)];
|
|
74
|
+
for (const ref of refs)
|
|
75
|
+
if (!MODELS_ENABLED_REF.test(ref))
|
|
76
|
+
throw new UsageError(m.badRef(ref));
|
|
77
|
+
const input = layerInput(ctx);
|
|
78
|
+
const current = userList(input) ?? [];
|
|
79
|
+
const listed = refs.filter((ref) => current.includes(ref));
|
|
80
|
+
const missing = refs.filter((ref) => !current.includes(ref));
|
|
81
|
+
if (missing.length > 0)
|
|
82
|
+
ctx.io.stderr(m.notListed(missing.join(", ")));
|
|
83
|
+
if (listed.length === 0)
|
|
84
|
+
return missing.length > 0 ? ExitCode.RuntimeError : ExitCode.Ok;
|
|
85
|
+
const next = removeEnabled(current, listed);
|
|
86
|
+
const result = setConfigValue({ ...input, scope: "user", key: "models.enabled", value: next });
|
|
87
|
+
ctx.io.stdout(next === undefined ? m.cleared(result.path) : m.removed(listed.join(", "), result.path));
|
|
88
|
+
return ExitCode.Ok;
|
|
89
|
+
}
|
|
90
|
+
/** `ama models list --enabled`:清单(及各项供应商状态);没设清单时列出选择器缺省显示的模型。 */
|
|
91
|
+
export async function listEnabled(ctx) {
|
|
92
|
+
const m = msg().subcommands.modelsEnabled;
|
|
93
|
+
const labels = msg().panels.model;
|
|
94
|
+
const list = enabledList(ctx.level.merged.config.models?.enabled);
|
|
95
|
+
const status = async (providerId) => {
|
|
96
|
+
const provider = ctx.registry.get(providerId);
|
|
97
|
+
if (provider === undefined)
|
|
98
|
+
return "?";
|
|
99
|
+
const access = await providerAccess(ctx.registry, provider);
|
|
100
|
+
return access === "none" ? labels.noKey : labels[access];
|
|
101
|
+
};
|
|
102
|
+
if (list !== undefined) {
|
|
103
|
+
ctx.io.stdout(m.listHeader(ctx.level.userConfigPath));
|
|
104
|
+
for (const ref of list) {
|
|
105
|
+
const parts = splitEnabledRef(ref);
|
|
106
|
+
const provider = parts === undefined ? undefined : ctx.registry.get(parts.provider);
|
|
107
|
+
const known = parts === undefined ||
|
|
108
|
+
parts.model === "*" ||
|
|
109
|
+
provider === undefined ||
|
|
110
|
+
provider.models.length === 0 ||
|
|
111
|
+
provider.models.some((model) => model.id === parts.model);
|
|
112
|
+
const notes = [
|
|
113
|
+
parts === undefined ? "?" : await status(parts.provider),
|
|
114
|
+
...(known ? [] : [m.notInTable]),
|
|
115
|
+
];
|
|
116
|
+
ctx.io.stdout(` ${ref} ${notes.join(" · ")}\n`);
|
|
117
|
+
}
|
|
118
|
+
return ExitCode.Ok;
|
|
119
|
+
}
|
|
120
|
+
ctx.io.stdout(m.listUnset);
|
|
121
|
+
let shown = 0;
|
|
122
|
+
for (const provider of ctx.registry.list()) {
|
|
123
|
+
const access = await providerAccess(ctx.registry, provider);
|
|
124
|
+
if (!accessReady(access))
|
|
125
|
+
continue;
|
|
126
|
+
const label = access === "none" ? labels.noKey : labels[access];
|
|
127
|
+
for (const model of provider.models) {
|
|
128
|
+
ctx.io.stdout(` ${provider.id}/${model.id} ${label}\n`);
|
|
129
|
+
shown++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (shown === 0)
|
|
133
|
+
ctx.io.stdout(m.nothing);
|
|
134
|
+
return ExitCode.Ok;
|
|
135
|
+
}
|
|
136
|
+
export const ENABLE_ACTION = {
|
|
137
|
+
usage: "ama models enable <provider/model[@channel]|provider/*>…",
|
|
138
|
+
required: "<provider/model>",
|
|
139
|
+
run: enable,
|
|
140
|
+
};
|
|
141
|
+
export const DISABLE_ACTION = {
|
|
142
|
+
usage: "ama models disable <provider/model[@channel]|provider/*>…",
|
|
143
|
+
required: "<provider/model>",
|
|
144
|
+
run: disable,
|
|
145
|
+
};
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* 选项表与必填位置参数都从表里来。
|
|
10
10
|
*
|
|
11
11
|
* [W5-M1] `refresh`:models.dev 显式刷新(`refresh-catalog` 保留为别名)。
|
|
12
|
+
*
|
|
13
|
+
* `enable` / `disable` / `list --enabled`:模型选择器清单 `models.enabled`(models-enable.ts)。
|
|
12
14
|
*/
|
|
13
15
|
import type { ProviderRegistryApi } from "../../ai/types.js";
|
|
14
16
|
import type { CliIo, RuntimeDeps } from "../deps.js";
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* 选项表与必填位置参数都从表里来。
|
|
10
10
|
*
|
|
11
11
|
* [W5-M1] `refresh`:models.dev 显式刷新(`refresh-catalog` 保留为别名)。
|
|
12
|
+
*
|
|
13
|
+
* `enable` / `disable` / `list --enabled`:模型选择器清单 `models.enabled`(models-enable.ts)。
|
|
12
14
|
*/
|
|
13
15
|
import { formatModelRef, modelRefOf } from "../../ai/providers/channels.js";
|
|
14
16
|
import { describeRefresh, modelsDevCachePath, refreshModelsDev, } from "../../ai/providers/models-dev-cache.js";
|
|
@@ -18,6 +20,7 @@ import { ExitCode } from "../exit-codes.js";
|
|
|
18
20
|
import { buildRegistry, loadUserLevel } from "./context.js";
|
|
19
21
|
import { CACHE_PROBE_ACTION } from "./models-cache-probe.js";
|
|
20
22
|
import { DISCOVER_ACTION } from "./models-discover.js";
|
|
23
|
+
import { DISABLE_ACTION, ENABLE_ACTION, listEnabled } from "./models-enable.js";
|
|
21
24
|
import { metadataOf, modelFlags, sourcesLine } from "./model-meta.js";
|
|
22
25
|
import { msg } from "../../i18n/index.js";
|
|
23
26
|
const COMMON_VALUE_OPTIONS = ["profile", "auth-file"];
|
|
@@ -113,9 +116,12 @@ const REFRESH_ACTION = {
|
|
|
113
116
|
/** 动作表:键是子命令名,顺序即用法文本顺序。 */
|
|
114
117
|
export const MODELS_ACTIONS = Object.freeze({
|
|
115
118
|
list: {
|
|
116
|
-
usage: "ama models list [--provider <id>]",
|
|
119
|
+
usage: "ama models list [--provider <id>] [--enabled]",
|
|
117
120
|
valueOptions: ["provider"],
|
|
118
|
-
|
|
121
|
+
flagOptions: ["enabled"],
|
|
122
|
+
run: (ctx) => ctx.flags.has("enabled")
|
|
123
|
+
? listEnabled(ctx)
|
|
124
|
+
: list(ctx.io, ctx.registry, ctx.values.get("provider")),
|
|
119
125
|
},
|
|
120
126
|
check: {
|
|
121
127
|
usage: "ama models check <provider/id>",
|
|
@@ -132,6 +138,8 @@ export const MODELS_ACTIONS = Object.freeze({
|
|
|
132
138
|
return msg().subcommands.models.refreshCatalogUsage;
|
|
133
139
|
},
|
|
134
140
|
},
|
|
141
|
+
enable: ENABLE_ACTION,
|
|
142
|
+
disable: DISABLE_ACTION,
|
|
135
143
|
});
|
|
136
144
|
export function modelsUsage() {
|
|
137
145
|
return msg().subcommands.models.usage(Object.values(MODELS_ACTIONS).map((a) => a.usage));
|
|
@@ -151,7 +151,7 @@ function w5Sections() {
|
|
|
151
151
|
},
|
|
152
152
|
}),
|
|
153
153
|
subagents: object({ maxConcurrent: num(1, 64), maxPending: num(0, 1024), defaultModel: str() }),
|
|
154
|
-
models: object({ aliases: object({ fast: str(), strong: str() }) }),
|
|
154
|
+
models: object({ aliases: object({ fast: str(), strong: str() }), enabled: strings }),
|
|
155
155
|
fallbackModel: str(),
|
|
156
156
|
limits: object({ maxTurns: num(1), maxCostUsd: num(0) }),
|
|
157
157
|
reminders: object({
|
package/dist/config/schema-w5.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { PERMISSION_MODES_STRICT_FIRST } from "../permissions/types.js";
|
|
8
8
|
import { msg } from "../i18n/index.js";
|
|
9
9
|
import { Checker, THINKING_LEVELS, checkSection, join } from "./checker.js";
|
|
10
|
-
import { AGENTS_RESERVED_KEYS, IMAGE_RESIZE_MODES, PLAN_BASH_MODES_STRICT_FIRST, PLAN_UNATTENDED_MODES, STATUS_LINE_MODES, } from "./types-w5.js";
|
|
10
|
+
import { AGENTS_RESERVED_KEYS, IMAGE_RESIZE_MODES, MODELS_ENABLED_REF, PLAN_BASH_MODES_STRICT_FIRST, PLAN_UNATTENDED_MODES, STATUS_LINE_MODES, } from "./types-w5.js";
|
|
11
11
|
/** 第五波新增的顶层键(schema.ts 的 CONFIG_KEYS 并入)。 */
|
|
12
12
|
export const W5_CONFIG_KEYS = [
|
|
13
13
|
"images",
|
|
@@ -88,7 +88,14 @@ export function validateConfigW5(c, config) {
|
|
|
88
88
|
c.number(s, "maxPending", p, 0, 1024);
|
|
89
89
|
c.string(s, "defaultModel", p);
|
|
90
90
|
});
|
|
91
|
-
checkSection(c, config, "models", ["aliases"], (s, p) => {
|
|
91
|
+
checkSection(c, config, "models", ["aliases", "enabled"], (s, p) => {
|
|
92
|
+
c.stringArray(s, "enabled", p);
|
|
93
|
+
const enabled = s["enabled"];
|
|
94
|
+
if (Array.isArray(enabled))
|
|
95
|
+
enabled.forEach((ref, i) => {
|
|
96
|
+
if (typeof ref === "string" && !MODELS_ENABLED_REF.test(ref))
|
|
97
|
+
c.error(join(join(p, "enabled"), i), msg().config.schema.modelsEnabled);
|
|
98
|
+
});
|
|
92
99
|
const aliases = s["aliases"];
|
|
93
100
|
const ap = join(p, "aliases");
|
|
94
101
|
if (aliases === undefined || !c.object(aliases, ap))
|
|
@@ -59,6 +59,9 @@ export declare const SETTING_HINTS: readonly [{
|
|
|
59
59
|
}, {
|
|
60
60
|
readonly key: "auth.chatgpt";
|
|
61
61
|
readonly hint: "auth";
|
|
62
|
+
}, {
|
|
63
|
+
readonly key: "models.enabled";
|
|
64
|
+
readonly hint: "modelsEnabled";
|
|
62
65
|
}];
|
|
63
66
|
export type SettingHintId = (typeof SETTING_HINTS)[number]["hint"];
|
|
64
67
|
type Schema = Record<string, unknown>;
|
|
@@ -106,6 +106,7 @@ export const SETTING_HINTS = [
|
|
|
106
106
|
{ key: "agents.<id>", hint: "agents" },
|
|
107
107
|
{ key: "hooks", hint: "hooks" },
|
|
108
108
|
{ key: "auth.chatgpt", hint: "auth" },
|
|
109
|
+
{ key: "models.enabled", hint: "modelsEnabled" },
|
|
109
110
|
];
|
|
110
111
|
/** Schema node of a dotted key (undefined when the key is not in the schema). */
|
|
111
112
|
export function schemaAt(key, root = buildConfigJsonSchema()) {
|
|
@@ -68,12 +68,19 @@ export interface SubagentsConfig {
|
|
|
68
68
|
/** 子会话缺省模型;不设继承父会话。 */
|
|
69
69
|
defaultModel?: string;
|
|
70
70
|
}
|
|
71
|
+
/** `models.enabled` 的一项:`provider/model[@channel]` 或 `provider/*`。 */
|
|
72
|
+
export declare const MODELS_ENABLED_REF: RegExp;
|
|
71
73
|
/** [W5-G] 模型别名(子 Agent 定义的 `model: fast | strong`)。 */
|
|
72
74
|
export interface ModelsConfig {
|
|
73
75
|
aliases?: {
|
|
74
76
|
fast?: string;
|
|
75
77
|
strong?: string;
|
|
76
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* 模型选择器的显式清单(`provider/model[@channel]`,`provider/*` 整个供应商):设置后 `/model` 只显示清单内
|
|
81
|
+
* 的模型(加当前模型);不设时显示已配置 key / 登录 / 本地可达的供应商的全部模型。只认用户级。
|
|
82
|
+
*/
|
|
83
|
+
enabled?: string[];
|
|
77
84
|
}
|
|
78
85
|
/** [W5-H1] 档一裁剪参数(其余随窗口缩放的常量不暴露)。 */
|
|
79
86
|
export interface PruneConfig {
|
package/dist/config/types-w5.js
CHANGED
|
@@ -18,3 +18,5 @@ export function agentEntry(agents, agentId) {
|
|
|
18
18
|
const entry = agents[agentId];
|
|
19
19
|
return typeof entry === "object" && !Array.isArray(entry) ? entry : undefined;
|
|
20
20
|
}
|
|
21
|
+
/** `models.enabled` 的一项:`provider/model[@channel]` 或 `provider/*`。 */
|
|
22
|
+
export const MODELS_ENABLED_REF = /^[^/\s]+\/\S+$/;
|
package/dist/i18n/catalog.d.ts
CHANGED
|
@@ -392,6 +392,19 @@ declare const en: {
|
|
|
392
392
|
noHits: (all: boolean) => "No matches\n" | "No matches (only sessions of this directory; --all searches all)\n";
|
|
393
393
|
truncated: (limit: number) => string;
|
|
394
394
|
};
|
|
395
|
+
modelsEnabled: {
|
|
396
|
+
badRef: (ref: string) => string;
|
|
397
|
+
unknownModel: (ref: string) => string;
|
|
398
|
+
added: (refs: string, path: string) => string;
|
|
399
|
+
alreadyListed: (refs: string) => string;
|
|
400
|
+
removed: (refs: string, path: string) => string;
|
|
401
|
+
notListed: (refs: string) => string;
|
|
402
|
+
cleared: (path: string) => string;
|
|
403
|
+
listHeader: (path: string) => string;
|
|
404
|
+
listUnset: string;
|
|
405
|
+
notInTable: string;
|
|
406
|
+
nothing: string;
|
|
407
|
+
};
|
|
395
408
|
configShow: {
|
|
396
409
|
usage: string;
|
|
397
410
|
unknownAction: (action: string) => string;
|
|
@@ -441,6 +454,7 @@ declare const en: {
|
|
|
441
454
|
probeHeader: (count: number, order: string, requests: number, extra: number, limit: number) => string;
|
|
442
455
|
unavailable: string;
|
|
443
456
|
probeStopped: (reason: string) => string;
|
|
457
|
+
cached: (path: string) => string;
|
|
444
458
|
};
|
|
445
459
|
};
|
|
446
460
|
interactive: {
|
|
@@ -636,8 +650,6 @@ declare const en: {
|
|
|
636
650
|
hintFilter: (arrows: string) => string;
|
|
637
651
|
hintNumbers: (arrows: string, n: number) => string;
|
|
638
652
|
hint: (arrows: string) => string;
|
|
639
|
-
local: string;
|
|
640
|
-
noKey: string;
|
|
641
653
|
justNow: string;
|
|
642
654
|
minutesAgo: (n: number) => string;
|
|
643
655
|
hoursAgo: (n: number) => string;
|
|
@@ -917,6 +929,27 @@ declare const en: {
|
|
|
917
929
|
thinkingUnsupported: string;
|
|
918
930
|
emptyText: string;
|
|
919
931
|
};
|
|
932
|
+
model: {
|
|
933
|
+
local: string;
|
|
934
|
+
key: string;
|
|
935
|
+
oauth: string;
|
|
936
|
+
needsLogin: string;
|
|
937
|
+
noKey: string;
|
|
938
|
+
current: string;
|
|
939
|
+
listed: string;
|
|
940
|
+
otherChannels: (names: string) => string;
|
|
941
|
+
discoverHint: (provider: string) => string;
|
|
942
|
+
loginHint: (provider: string) => string;
|
|
943
|
+
footerConfigured: string;
|
|
944
|
+
footerAll: string;
|
|
945
|
+
emptyConfigured: string;
|
|
946
|
+
added: (ref: string) => string;
|
|
947
|
+
addedFirst: (ref: string) => string;
|
|
948
|
+
removed: (ref: string) => string;
|
|
949
|
+
wildcard: (ref: string, pattern: string) => string;
|
|
950
|
+
noKeyHint: (provider: string) => string;
|
|
951
|
+
needsLoginHint: (provider: string) => string;
|
|
952
|
+
};
|
|
920
953
|
plan: {
|
|
921
954
|
title: string;
|
|
922
955
|
titleVersion: (version: number) => string;
|
|
@@ -1351,6 +1384,7 @@ declare const en: {
|
|
|
1351
1384
|
"models.aliases": string;
|
|
1352
1385
|
"models.aliases.fast": string;
|
|
1353
1386
|
"models.aliases.strong": string;
|
|
1387
|
+
"models.enabled": string;
|
|
1354
1388
|
fallbackModel: string;
|
|
1355
1389
|
limits: string;
|
|
1356
1390
|
"limits.maxTurns": string;
|
|
@@ -1377,6 +1411,7 @@ declare const en: {
|
|
|
1377
1411
|
"subagents.defaultModel": string;
|
|
1378
1412
|
"models.aliases.fast": string;
|
|
1379
1413
|
"models.aliases.strong": string;
|
|
1414
|
+
"models.enabled": string;
|
|
1380
1415
|
fallbackModel: string;
|
|
1381
1416
|
"limits.maxTurns": string;
|
|
1382
1417
|
"limits.maxCostUsd": string;
|
|
@@ -1402,6 +1437,7 @@ declare const en: {
|
|
|
1402
1437
|
stringArray: string;
|
|
1403
1438
|
array: string;
|
|
1404
1439
|
modelsDev: string;
|
|
1440
|
+
modelsEnabled: string;
|
|
1405
1441
|
noChannels: string;
|
|
1406
1442
|
unknownChannelOf: (name: string, available: readonly string[]) => string;
|
|
1407
1443
|
modelInput: string;
|
|
@@ -1937,11 +1973,12 @@ declare const en: {
|
|
|
1937
1973
|
siwcLimitHint: string;
|
|
1938
1974
|
freePlanHint: (plan: string) => string;
|
|
1939
1975
|
modelHint: string;
|
|
1976
|
+
modelsCached: (count: number) => string;
|
|
1940
1977
|
};
|
|
1941
1978
|
errors: {
|
|
1942
1979
|
portsBusy: (ports: string, codex: boolean) => string;
|
|
1943
1980
|
timeout: string;
|
|
1944
|
-
denied: (error: string) => string;
|
|
1981
|
+
denied: (error: string, codexFallback: boolean) => string;
|
|
1945
1982
|
stateMismatch: string;
|
|
1946
1983
|
noCode: string;
|
|
1947
1984
|
invalidToken: (reason: string) => string;
|
|
@@ -2116,6 +2153,8 @@ declare const en: {
|
|
|
2116
2153
|
setDone: (key: string, value: string, scope: string) => string;
|
|
2117
2154
|
unsetDone: (key: string, value: string, scope: string) => string;
|
|
2118
2155
|
stillOverridden: (key: string, source: string) => string;
|
|
2156
|
+
writtenTo: (key: string, value: string, scope: string) => string;
|
|
2157
|
+
overriddenNow: (source: string, effective: string) => string;
|
|
2119
2158
|
slashUsage: string;
|
|
2120
2159
|
errors: {
|
|
2121
2160
|
unknownKey: (key: string) => string;
|
|
@@ -2144,6 +2183,7 @@ declare const en: {
|
|
|
2144
2183
|
agents: string;
|
|
2145
2184
|
hooks: string;
|
|
2146
2185
|
auth: string;
|
|
2186
|
+
modelsEnabled: string;
|
|
2147
2187
|
};
|
|
2148
2188
|
cli: {
|
|
2149
2189
|
usage: string;
|
|
@@ -45,11 +45,12 @@ export declare const en: {
|
|
|
45
45
|
siwcLimitHint: string;
|
|
46
46
|
freePlanHint: (plan: string) => string;
|
|
47
47
|
modelHint: string;
|
|
48
|
+
modelsCached: (count: number) => string;
|
|
48
49
|
};
|
|
49
50
|
errors: {
|
|
50
51
|
portsBusy: (ports: string, codex: boolean) => string;
|
|
51
52
|
timeout: string;
|
|
52
|
-
denied: (error: string) => string;
|
|
53
|
+
denied: (error: string, codexFallback: boolean) => string;
|
|
53
54
|
stateMismatch: string;
|
|
54
55
|
noCode: string;
|
|
55
56
|
invalidToken: (reason: string) => string;
|
|
@@ -135,11 +136,12 @@ export declare const zh: {
|
|
|
135
136
|
siwcLimitHint: string;
|
|
136
137
|
freePlanHint: (plan: string) => string;
|
|
137
138
|
modelHint: string;
|
|
139
|
+
modelsCached: (count: number) => string;
|
|
138
140
|
};
|
|
139
141
|
errors: {
|
|
140
142
|
portsBusy: (ports: string, codex: boolean) => string;
|
|
141
143
|
timeout: string;
|
|
142
|
-
denied: (error: string) => string;
|
|
144
|
+
denied: (error: string, codexFallback: boolean) => string;
|
|
143
145
|
stateMismatch: string;
|
|
144
146
|
noCode: string;
|
|
145
147
|
invalidToken: (reason: string) => string;
|
|
@@ -46,11 +46,18 @@ export const en = {
|
|
|
46
46
|
siwcLimitHint: "Tip: set a weekly limit for ama in ChatGPT → Settings → Usage → App limits. For personal use only; never share one sign-in between several people.",
|
|
47
47
|
freePlanHint: (plan) => `Note: the "${plan}" plan may not include subscription usage for ama.`,
|
|
48
48
|
modelHint: "Use it with --model chatgpt/<model>; list the models your account can use with ama models discover chatgpt.",
|
|
49
|
+
modelsCached: (count) => `Your account can use ${count} model(s); pick one with /model or --model chatgpt/<model>.`,
|
|
49
50
|
},
|
|
50
51
|
errors: {
|
|
51
52
|
portsBusy: (ports, codex) => `callback port ${ports} is in use: finish the other sign-in first, or use --paste${codex ? " / --device" : ""}`,
|
|
52
53
|
timeout: "sign-in timed out; run the command again",
|
|
53
|
-
denied: (error) => `sign-in was not authorized (${error})
|
|
54
|
+
denied: (error, codexFallback) => `sign-in was not authorized (${error}). Possible causes:\n` +
|
|
55
|
+
" 1. The authorization page was cancelled, or using your ChatGPT plan was not checked: run ama auth login chatgpt again and keep that option checked\n" +
|
|
56
|
+
" 2. The account or plan is not eligible: plan usage is shared with Plus / Pro only; Team / Enterprise workspaces may not offer it; the browser may be signed in to another account\n" +
|
|
57
|
+
" 3. Your region is not supported" +
|
|
58
|
+
(codexFallback
|
|
59
|
+
? "\nYou can also try the fallback: ama auth login chatgpt --flavor codex"
|
|
60
|
+
: ""),
|
|
54
61
|
stateMismatch: "the pasted URL does not belong to this sign-in (state mismatch); start again",
|
|
55
62
|
noCode: "the pasted URL has no code; copy the whole address after the browser redirects",
|
|
56
63
|
invalidToken: (reason) => `id_token failed verification (${reason}); sign-in rejected`,
|
|
@@ -137,11 +144,16 @@ export const zh = {
|
|
|
137
144
|
siwcLimitHint: "提示:可在 ChatGPT → 设置 → Usage → App limits 给 ama 设周上限。仅限本人使用,不要让多人共用一个登录。",
|
|
138
145
|
freePlanHint: (plan) => `注意:「${plan}」计划可能不含给 ama 用的订阅额度。`,
|
|
139
146
|
modelHint: "用 --model chatgpt/<模型> 使用;账户可用的模型用 ama models discover chatgpt 查看。",
|
|
147
|
+
modelsCached: (count) => `账户可用 ${count} 个模型,用 /model 或 --model chatgpt/<模型> 选择。`,
|
|
140
148
|
},
|
|
141
149
|
errors: {
|
|
142
150
|
portsBusy: (ports, codex) => `回调端口 ${ports} 被占用:请先结束其它登录,或用 --paste${codex ? " / --device" : ""}`,
|
|
143
151
|
timeout: "登录超时,请重新运行命令",
|
|
144
|
-
denied: (error) => `授权未通过(${error}
|
|
152
|
+
denied: (error, codexFallback) => `授权未通过(${error})。可能的原因:\n` +
|
|
153
|
+
" 1. 在授权页取消了,或没有勾选使用 ChatGPT 套餐额度:重新运行 ama auth login chatgpt 并确认勾选\n" +
|
|
154
|
+
" 2. 账户或套餐不符合:额度共享只对 Plus / Pro 开放;Team / Enterprise 工作空间可能未开放;浏览器可能登录了别的账户\n" +
|
|
155
|
+
" 3. 所在地区受限" +
|
|
156
|
+
(codexFallback ? "\n也可以改用备用方式:ama auth login chatgpt --flavor codex" : ""),
|
|
145
157
|
stateMismatch: "粘贴的 URL 不属于这次登录(state 不符),请重新开始",
|
|
146
158
|
noCode: "粘贴的 URL 里没有 code;请在浏览器跳转后复制完整地址",
|
|
147
159
|
invalidToken: (reason) => `id_token 校验失败(${reason}),已拒绝这次登录`,
|
|
@@ -91,6 +91,8 @@ Subcommands
|
|
|
91
91
|
Timeline of turns, requests, tools and subagents (text / HTML / JSON)
|
|
92
92
|
ama models list [--provider <id>] List models (with source and key status)
|
|
93
93
|
ama models check <provider/id> Send one minimal request to check availability
|
|
94
|
+
ama models enable|disable <provider/model|provider/*>…
|
|
95
|
+
Edit the /model list (models.enabled); list --enabled shows it
|
|
94
96
|
ama models discover <provider> [--probe] [--write] [--limit N]
|
|
95
97
|
List models from a relay's /v1/models, probe protocols, write the config
|
|
96
98
|
ama models refresh [--provider <id>] Refresh models.dev metadata into the data directory (startup stays
|
|
@@ -206,6 +208,8 @@ export const zh = {
|
|
|
206
208
|
回合、请求、工具与子 Agent 的时间线(文本 / HTML / JSON)
|
|
207
209
|
ama models list [--provider <id>] 列出模型(含来源与 key 状态)
|
|
208
210
|
ama models check <provider/id> 发一次最小请求检查可用性
|
|
211
|
+
ama models enable|disable <provider/model|provider/*>…
|
|
212
|
+
编辑 /model 的模型清单(models.enabled);list --enabled 查看
|
|
209
213
|
ama models discover <provider> [--probe] [--write] [--limit N]
|
|
210
214
|
从中转 /v1/models 列出模型,探测协议并写入配置
|
|
211
215
|
ama models refresh [--provider <id>] 联网刷新 models.dev 元数据到数据目录(启动不联网;
|