llm-api-gateway-cli 1.0.5 → 1.0.6
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.en.md +116 -2
- package/README.md +101 -2
- package/cli-agent.js +186 -49
- package/cli-anthropic.js +57 -13
- package/cli-claude-code.js +59 -12
- package/cli-openai.js +48 -11
- package/lib/commands.js +397 -70
- package/lib/config.js +22 -2
- package/lib/configcmd.js +146 -45
- package/lib/hub.js +435 -43
- package/lib/i18n.js +80 -0
- package/lib/launcher.js +78 -24
- package/lib/mcp.js +16 -6
- package/lib/mcpadmin.js +331 -0
- package/lib/setup.js +101 -36
- package/package.json +1 -1
- package/public/manual.html +107 -3
- package/public/mcp.css +155 -0
- package/public/mcp.html +135 -0
- package/public/mcp.js +327 -0
- package/public/sessions.css +198 -0
- package/public/sessions.html +89 -0
- package/public/sessions.js +281 -0
- package/public/shell.html +8 -0
- package/public/shell.js +20 -4
- package/public/task.css +5 -0
- package/public/task.html +14 -1
package/lib/config.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import { DEFAULT_BASE_URL, strArg } from './common.js';
|
|
12
12
|
import { resolveSecretKey } from './secrets.js';
|
|
13
13
|
import { resolveSettings, settingsFilePath } from './settings.js';
|
|
14
|
+
// CLI 侧语言:这四个 CLI 入口的报错与提示都从这里取词(默认中文)
|
|
15
|
+
import { pick } from './i18n.js';
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* 缺密钥时的统一提示(各 CLI 文案一致,避免用户按提示改了还是不通)。
|
|
@@ -24,6 +26,24 @@ export const KEY_HINT =
|
|
|
24
26
|
+ '② 或 gateway-agent config set key sk-xxx / 在任务页·聊天页「设置」里填(都存 credentials.json,仅本用户可读);'
|
|
25
27
|
+ '③ 或 --key sk-xxx、环境变量 SK / GATEWAY_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY,或在 .env 中写 GATEWAY_KEY=sk-xxx';
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* 英文版提示(只给 CLI 侧用)。
|
|
31
|
+
*
|
|
32
|
+
* 为什么不直接把 KEY_HINT 改成 `pick(...)`:它是**模块级常量**,import 时就求值了,
|
|
33
|
+
* 而语言是入口脚本 parseArgs 之后才 setLang 的 —— 那样永远只会拿到中文(见 lib/i18n.js 纪律 3)。
|
|
34
|
+
* 所以保留 KEY_HINT 这个常量的中文原样(既有测试与调用方都按值用它),
|
|
35
|
+
* 需要按语言取词的地方调 `keyHint()`。
|
|
36
|
+
*/
|
|
37
|
+
export const KEY_HINT_EN =
|
|
38
|
+
'Missing sk- key: (1) run gateway-agent setup (first-time setup: it asks once for the gateway URL and the key); '
|
|
39
|
+
+ '(2) or gateway-agent config set key sk-xxx, or fill it in the task/chat page under "Settings" (both write credentials.json, readable by this user only); '
|
|
40
|
+
+ '(3) or --key sk-xxx, env SK / GATEWAY_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY, or GATEWAY_KEY=sk-xxx in .env';
|
|
41
|
+
|
|
42
|
+
/** 按当前 CLI 语言取密钥提示(默认中文) */
|
|
43
|
+
export function keyHint() {
|
|
44
|
+
return pick(KEY_HINT, KEY_HINT_EN);
|
|
45
|
+
}
|
|
46
|
+
|
|
27
47
|
/**
|
|
28
48
|
* 数字参数:非法值(NaN / 布尔 / 空串)一律返回 undefined。
|
|
29
49
|
* 之前直接 Number(v) 会把 --temperature abc 变成 NaN 塞给上游,报错信息很难看懂。
|
|
@@ -80,11 +100,11 @@ export function resolveConfig(args = {}, { defaultModel = '', defaultMaxTokens,
|
|
|
80
100
|
};
|
|
81
101
|
}
|
|
82
102
|
|
|
83
|
-
/**
|
|
103
|
+
/** 把异常压成一行可读文字(上游错误、HTTP 状态、普通异常都覆盖) */
|
|
84
104
|
export function errorMessage(e) {
|
|
85
105
|
const status = e?.status ?? e?.statusCode ?? '';
|
|
86
106
|
const message = e?.error?.message || e?.message || String(e);
|
|
87
|
-
return `[错误]${status ? ` HTTP ${status}` : ''}: ${message}
|
|
107
|
+
return pick(`[错误]${status ? ` HTTP ${status}` : ''}: ${message}`, `[error]${status ? ` HTTP ${status}` : ''}: ${message}`);
|
|
88
108
|
}
|
|
89
109
|
|
|
90
110
|
/**
|
package/lib/configcmd.js
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* 密钥(`key`)走**另一条路**:它不进 config.json,而是写 `<数据根>/credentials.json`(仅属主可读),
|
|
12
12
|
* 所以 `set key` 不是「被拒绝」,而是「写到另一个文件」。命令行的说法与 Web 设置面板完全一致 ——
|
|
13
13
|
* 这也是「不用手写 .env 就能用」的落点。
|
|
14
|
+
*
|
|
15
|
+
* 输出语言(20260922):文案全部走 `pick(中文, 英文)`,默认中文一字不变;
|
|
16
|
+
* 语言由入口脚本 setLang 设好,这里只管取词。**pick 必须在函数里调用**(见 lib/i18n.js 纪律 3)。
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
import {
|
|
@@ -24,10 +27,19 @@ import {
|
|
|
24
27
|
SETTINGS_SCHEMA,
|
|
25
28
|
} from './settings.js';
|
|
26
29
|
import { clearSecret, keyRow, secretFilePath, secretSourceText, writeSecret } from './secrets.js';
|
|
30
|
+
import { pick } from './i18n.js';
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
/** 来源标签:中英各留一份,取词在函数里做 —— 顶层 pick 会在 setLang 之前求值,永远拿到中文 */
|
|
33
|
+
const SOURCE_LABEL_ZH = { flag: '命令行', env: '环境变量', file: '配置文件', default: '内置默认' };
|
|
34
|
+
const SOURCE_LABEL_EN = { flag: 'command line', env: 'environment variable', file: 'config file', default: 'built-in default' };
|
|
29
35
|
/** 密钥的来源只有三层(它不在 config.json 里),所以单独一套标签 */
|
|
30
|
-
const
|
|
36
|
+
const SECRET_SOURCE_LABEL_ZH = { flag: '命令行 --key', env: '环境变量', file: '密钥文件', default: '未配置' };
|
|
37
|
+
const SECRET_SOURCE_LABEL_EN = { flag: 'command line --key', env: 'environment variable', file: 'secret file', default: 'not configured' };
|
|
38
|
+
|
|
39
|
+
/** 取标签;认不出的来源原样回显(与旧行为一致)。
|
|
40
|
+
* 注意 pick() 只认字符串:整张表传进去会因为 en 不是字符串而**静默回落中文**,所以按 key 逐个取词。 */
|
|
41
|
+
const sourceLabel = (s) => pick(SOURCE_LABEL_ZH[s], SOURCE_LABEL_EN[s]) || s;
|
|
42
|
+
const secretSourceLabel = (s) => pick(SECRET_SOURCE_LABEL_ZH[s], SECRET_SOURCE_LABEL_EN[s]) || s;
|
|
31
43
|
|
|
32
44
|
/** CLI 传进来的值是字符串;这里粗切一下,`set system 你是助手 请简短` 的剩余部分都算值 */
|
|
33
45
|
export function splitConfigArg(arg) {
|
|
@@ -37,9 +49,9 @@ export function splitConfigArg(arg) {
|
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
const show = (v) => {
|
|
40
|
-
if (v === null || v === undefined) return '(未设)';
|
|
41
|
-
if (Array.isArray(v)) return v.length ? `${v.length}
|
|
42
|
-
if (typeof v === 'string') return v.length > 40 ? `${v.slice(0, 39)}…` : v || '(空串)';
|
|
52
|
+
if (v === null || v === undefined) return pick('(未设)', '(not set)');
|
|
53
|
+
if (Array.isArray(v)) return v.length ? pick(`${v.length} 项`, `${v.length} item(s)`) : pick('(空)', '(empty)');
|
|
54
|
+
if (typeof v === 'string') return v.length > 40 ? `${v.slice(0, 39)}…` : v || pick('(空串)', '(empty string)');
|
|
43
55
|
return String(v);
|
|
44
56
|
};
|
|
45
57
|
|
|
@@ -47,8 +59,18 @@ const show = (v) => {
|
|
|
47
59
|
function overrideWarning(resolved, key) {
|
|
48
60
|
const source = resolved.sources[key];
|
|
49
61
|
const entry = SETTINGS_SCHEMA.find((e) => e.key === key);
|
|
50
|
-
if (source === 'flag')
|
|
51
|
-
|
|
62
|
+
if (source === 'flag') {
|
|
63
|
+
return pick(
|
|
64
|
+
`注意:当前生效值来自命令行参数(${entry?.flag || key}),它优先于配置文件,你写的值现在不会生效。`,
|
|
65
|
+
`Note: the effective value comes from the command-line option (${entry?.flag || key}), which overrides the config file — what you just wrote will not take effect.`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (source === 'env') {
|
|
69
|
+
return pick(
|
|
70
|
+
`注意:当前生效值来自环境变量 ${entry?.env},它优先于配置文件,你写的值现在不会生效。`,
|
|
71
|
+
`Note: the effective value comes from environment variable ${entry?.env}, which overrides the config file — what you just wrote will not take effect.`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
52
74
|
return '';
|
|
53
75
|
}
|
|
54
76
|
|
|
@@ -69,8 +91,12 @@ export function configCommand(argv = [], { env = process.env, file = settingsFil
|
|
|
69
91
|
return {
|
|
70
92
|
code: 0,
|
|
71
93
|
out:
|
|
72
|
-
`${r.file}\n${r.exists ? '(已存在)' : '(还没有这份文件;set 一次就会创建)'}\n` +
|
|
73
|
-
`${s.file}\n${
|
|
94
|
+
`${r.file}\n${r.exists ? pick('(已存在)', '(exists)') : pick('(还没有这份文件;set 一次就会创建)', '(no such file yet; one `config set` creates it)')}\n` +
|
|
95
|
+
`${s.file}\n${
|
|
96
|
+
s.hasKey
|
|
97
|
+
? pick(`(已存在,${s.mask},来自${secretSourceText(s.source)})`, `(exists, ${s.mask}, from ${secretSourceText(s.source)})`)
|
|
98
|
+
: pick('(还没有;config set key sk-xxx 或页面设置里填)', '(not set yet; run `config set key sk-xxx` or fill it in the settings page)')
|
|
99
|
+
}\n`,
|
|
74
100
|
err: '',
|
|
75
101
|
};
|
|
76
102
|
}
|
|
@@ -80,10 +106,16 @@ export function configCommand(argv = [], { env = process.env, file = settingsFil
|
|
|
80
106
|
// 密钥行排在最前:它是「能不能用」的前提,而它不在 config.json 里,单独标出来免得用户去文件里翻
|
|
81
107
|
const secret = secretRow();
|
|
82
108
|
const rows = [
|
|
83
|
-
['配置项', '当前值', '来源'],
|
|
84
|
-
[
|
|
109
|
+
[pick('配置项', 'Setting'), pick('当前值', 'Current value'), pick('来源', 'Source')],
|
|
110
|
+
[
|
|
111
|
+
'key',
|
|
112
|
+
secret.hasKey
|
|
113
|
+
? pick(`${secret.mask}(存 ${secret.file})`, `${secret.mask} (in ${secret.file})`)
|
|
114
|
+
: pick('(未配置)', '(not configured)'),
|
|
115
|
+
secretSourceLabel(secret.source),
|
|
116
|
+
],
|
|
85
117
|
...SETTINGS_SCHEMA.map((e) => {
|
|
86
|
-
const row = [e.key, show(getByPath(r.values, e.key)),
|
|
118
|
+
const row = [e.key, show(getByPath(r.values, e.key)), sourceLabel(r.sources[e.key])];
|
|
87
119
|
if (e.restart) row[0] = `${e.key} *`;
|
|
88
120
|
return row;
|
|
89
121
|
}),
|
|
@@ -91,32 +123,45 @@ export function configCommand(argv = [], { env = process.env, file = settingsFil
|
|
|
91
123
|
const w = [0, 1, 2].map((i) => Math.max(...rows.map((row) => [...row[i]].length)));
|
|
92
124
|
const body = rows.map((row, i) => row.map((c, ci) => pad(c, w[ci])).join(' ').trimEnd()).join('\n');
|
|
93
125
|
const warns = [...r.warnings, ...[secret.warning].filter(Boolean)];
|
|
94
|
-
const warn = warns.length ?
|
|
126
|
+
const warn = warns.length ? `${pick('\n\n[警告]\n', '\n\n[Warnings]\n')}${warns.map((x) => ` ${x}`).join('\n')}` : '';
|
|
95
127
|
return {
|
|
96
128
|
code: 0,
|
|
97
129
|
out:
|
|
98
130
|
`${body}\n\n` +
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
131
|
+
pick(
|
|
132
|
+
`来源:命令行 > 环境变量 > 配置文件 > 内置默认。带 * 的改了要重启服务才生效。\n` +
|
|
133
|
+
`配置文件:${r.file}${r.exists ? '' : '(还没有,用 config set 创建)'}\n` +
|
|
134
|
+
`密钥不入配置文件:key 存在 ${secret.file}(仅本用户可读),环境变量 / --key 给的密钥优先于它。\n` +
|
|
135
|
+
`改某项:gateway-agent config set <键> <值>;改回来:config unset <键>`,
|
|
136
|
+
`Source: command line > environment variable > config file > built-in default. Rows marked * only take effect after a service restart.\n` +
|
|
137
|
+
`Config file: ${r.file}${r.exists ? '' : ' (none yet; create it with config set)'}\n` +
|
|
138
|
+
`The secret is not stored in the config file: key lives in ${secret.file} (readable by this user only), and a key from the environment / --key takes precedence over it.\n` +
|
|
139
|
+
`Change a setting: gateway-agent config set <key> <value>; revert: config unset <key>`,
|
|
140
|
+
) +
|
|
141
|
+
`${warn}\n`,
|
|
103
142
|
err: '',
|
|
104
143
|
};
|
|
105
144
|
}
|
|
106
145
|
|
|
107
146
|
if (action === 'get') {
|
|
108
|
-
if (!key) return { code: 1, out: '', err: '用法:gateway-agent config get <键>\n' };
|
|
147
|
+
if (!key) return { code: 1, out: '', err: pick('用法:gateway-agent config get <键>\n', 'Usage: gateway-agent config get <key>\n') };
|
|
109
148
|
if (key === 'key') {
|
|
110
149
|
const s = secretRow();
|
|
111
150
|
const lines = [
|
|
112
151
|
// 明文永不回显:命令行输出经常被贴进 issue / 日志
|
|
113
|
-
`key = ${s.hasKey ? s.mask : '(未配置)'}`,
|
|
114
|
-
` 来源 ${
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
152
|
+
`key = ${s.hasKey ? s.mask : pick('(未配置)', '(not configured)')}`,
|
|
153
|
+
pick(` 来源 ${secretSourceLabel(s.source)}`, ` Source ${secretSourceLabel(s.source)}`),
|
|
154
|
+
pick(
|
|
155
|
+
` 存放 ${s.file}${s.fileMode ? `(权限 ${s.fileMode.toString(8)})` : ''}`,
|
|
156
|
+
` Location ${s.file}${s.fileMode ? ` (mode ${s.fileMode.toString(8)})` : ''}`,
|
|
157
|
+
),
|
|
158
|
+
pick(
|
|
159
|
+
' 说明 密钥不写进 config.json;这里只回掩码,明文请回网关后台重新生成',
|
|
160
|
+
' Note the key is never written to config.json; only a mask is shown here — regenerate the plaintext in the gateway console',
|
|
161
|
+
),
|
|
162
|
+
pick(' 环境变量 SK / GATEWAY_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY', ' Env SK / GATEWAY_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY'),
|
|
163
|
+
pick(' 命令行 --key', ' Flag --key'),
|
|
164
|
+
s.warning ? pick(` 警告 ${s.warning}`, ` Warning ${s.warning}`) : '',
|
|
120
165
|
].filter(Boolean);
|
|
121
166
|
return { code: 0, out: `${lines.join('\n')}\n`, err: '' };
|
|
122
167
|
}
|
|
@@ -125,18 +170,27 @@ export function configCommand(argv = [], { env = process.env, file = settingsFil
|
|
|
125
170
|
const r = resolved();
|
|
126
171
|
const lines = [
|
|
127
172
|
`${key} = ${JSON.stringify(getByPath(r.values, key))}`,
|
|
128
|
-
` 来源 ${
|
|
129
|
-
` 内置默认 ${JSON.stringify(defaultValueOf(entry))}`,
|
|
130
|
-
` 说明 ${entry.desc}`,
|
|
131
|
-
entry.env ? ` 环境变量 ${entry.env}` : '',
|
|
132
|
-
entry.flag ? ` 命令行 ${entry.flag}` : '',
|
|
133
|
-
entry.restart ? ' 注意 改这项需要重启服务才生效' : '',
|
|
173
|
+
pick(` 来源 ${sourceLabel(r.sources[key])}`, ` Source ${sourceLabel(r.sources[key])}`),
|
|
174
|
+
pick(` 内置默认 ${JSON.stringify(defaultValueOf(entry))}`, ` Default ${JSON.stringify(defaultValueOf(entry))}`),
|
|
175
|
+
pick(` 说明 ${entry.desc}`, ` Note ${entry.desc}`),
|
|
176
|
+
entry.env ? pick(` 环境变量 ${entry.env}`, ` Env ${entry.env}`) : '',
|
|
177
|
+
entry.flag ? pick(` 命令行 ${entry.flag}`, ` Flag ${entry.flag}`) : '',
|
|
178
|
+
entry.restart ? pick(' 注意 改这项需要重启服务才生效', ' Note changing this takes effect only after a service restart') : '',
|
|
134
179
|
].filter(Boolean);
|
|
135
180
|
return { code: 0, out: `${lines.join('\n')}\n`, err: '' };
|
|
136
181
|
}
|
|
137
182
|
|
|
138
183
|
if (action === 'set') {
|
|
139
|
-
if (!key || rest.length === 0)
|
|
184
|
+
if (!key || rest.length === 0) {
|
|
185
|
+
return {
|
|
186
|
+
code: 1,
|
|
187
|
+
out: '',
|
|
188
|
+
err: pick(
|
|
189
|
+
'用法:gateway-agent config set <键> <值>(字符串带空格时整段都要写上)\n',
|
|
190
|
+
'Usage: gateway-agent config set <key> <value> (pass the whole string when the value contains spaces)\n',
|
|
191
|
+
),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
140
194
|
const value = rest.join(' ');
|
|
141
195
|
// 密钥:写 credentials.json,不碰 config.json(红线没变,只是换了落点)
|
|
142
196
|
if (key === 'key') {
|
|
@@ -144,12 +198,17 @@ export function configCommand(argv = [], { env = process.env, file = settingsFil
|
|
|
144
198
|
const w = writeSecret(value, { file: secretFile });
|
|
145
199
|
const s = secretRow();
|
|
146
200
|
const override = s.source === 'env' || s.source === 'flag'
|
|
147
|
-
?
|
|
201
|
+
? pick(
|
|
202
|
+
'\n注意:当前进程的密钥来自环境变量 / --key,它优先于这个文件,你写的值现在不会生效。',
|
|
203
|
+
'\nNote: this process takes the key from the environment / --key, which overrides this file — what you just wrote will not take effect.',
|
|
204
|
+
)
|
|
148
205
|
: '';
|
|
149
|
-
const perm = w.mode ? `(权限 ${w.mode.toString(8)})` : '(Windows:靠用户目录 ACL)';
|
|
150
206
|
return {
|
|
151
207
|
code: 0,
|
|
152
|
-
out:
|
|
208
|
+
out: pick(
|
|
209
|
+
`已写入 ${w.file}${w.mode ? `(权限 ${w.mode.toString(8)})` : '(Windows:靠用户目录 ACL)'}:key = ${s.mask}${w.warning ? `\n${w.warning}` : ''}${override}\n`,
|
|
210
|
+
`Written to ${w.file}${w.mode ? ` (mode ${w.mode.toString(8)})` : ' (Windows: relies on the user-directory ACL)'}: key = ${s.mask}${w.warning ? `\n${w.warning}` : ''}${override}\n`,
|
|
211
|
+
),
|
|
153
212
|
err: '',
|
|
154
213
|
};
|
|
155
214
|
} catch (e) {
|
|
@@ -161,11 +220,11 @@ export function configCommand(argv = [], { env = process.env, file = settingsFil
|
|
|
161
220
|
const r = resolved();
|
|
162
221
|
const warn = overrideWarning(r, key);
|
|
163
222
|
const entry = SETTINGS_SCHEMA.find((e) => e.key === key);
|
|
164
|
-
const restart = entry?.restart ? '\n(这项要重启服务才生效)' : '';
|
|
223
|
+
const restart = entry?.restart ? pick('\n(这项要重启服务才生效)', '\n(this only takes effect after a service restart)') : '';
|
|
165
224
|
const shown = show(getByPath(r.values, key));
|
|
166
225
|
return {
|
|
167
226
|
code: 0,
|
|
168
|
-
out: `已写入 ${res.file}:${key} = ${shown}${restart}${warn ? `\n${warn}` : ''}\n`,
|
|
227
|
+
out: `${pick(`已写入 ${res.file}:${key} = ${shown}`, `Wrote ${key} = ${shown} to ${res.file}`)}${restart}${warn ? `\n${warn}` : ''}\n`,
|
|
169
228
|
err: '',
|
|
170
229
|
};
|
|
171
230
|
} catch (e) {
|
|
@@ -174,14 +233,19 @@ export function configCommand(argv = [], { env = process.env, file = settingsFil
|
|
|
174
233
|
}
|
|
175
234
|
|
|
176
235
|
if (action === 'unset' || action === 'rm') {
|
|
177
|
-
if (!key) return { code: 1, out: '', err: '用法:gateway-agent config unset <键>\n' };
|
|
236
|
+
if (!key) return { code: 1, out: '', err: pick('用法:gateway-agent config unset <键>\n', 'Usage: gateway-agent config unset <key>\n') };
|
|
178
237
|
if (key === 'key') {
|
|
179
238
|
const cleared = clearSecret({ file: secretFile });
|
|
180
239
|
const s = secretRow();
|
|
181
|
-
const still = s.hasKey
|
|
240
|
+
const still = s.hasKey
|
|
241
|
+
? pick(`当前生效的仍是${secretSourceLabel(s.source)}的密钥:${s.mask}`, `the effective key still comes from ${secretSourceLabel(s.source)}: ${s.mask}`)
|
|
242
|
+
: pick('现在没有可用的密钥了。', 'there is no usable key now.');
|
|
182
243
|
return {
|
|
183
244
|
code: 0,
|
|
184
|
-
out:
|
|
245
|
+
out: pick(
|
|
246
|
+
`${cleared.removed ? `已删掉 ${cleared.file},` : `本来就没有 ${cleared.file},`}${still}\n`,
|
|
247
|
+
`${cleared.removed ? `Removed ${cleared.file}; ` : `there was no ${cleared.file}; `}${still}\n`,
|
|
248
|
+
),
|
|
185
249
|
err: '',
|
|
186
250
|
};
|
|
187
251
|
}
|
|
@@ -191,15 +255,31 @@ export function configCommand(argv = [], { env = process.env, file = settingsFil
|
|
|
191
255
|
const now = show(getByPath(r.values, key));
|
|
192
256
|
const warn = overrideWarning(r, key);
|
|
193
257
|
if (!res.removed.length) {
|
|
194
|
-
return {
|
|
258
|
+
return {
|
|
259
|
+
code: 0,
|
|
260
|
+
out:
|
|
261
|
+
pick(
|
|
262
|
+
`${key} 本来就没在配置文件里,现在用的仍是${sourceLabel(r.sources[key])}的值:${now}`,
|
|
263
|
+
`${key} was not in the config file; the value in effect is still the one from ${sourceLabel(r.sources[key])}: ${now}`,
|
|
264
|
+
) + `${warn ? `\n${warn}` : ''}\n`,
|
|
265
|
+
err: '',
|
|
266
|
+
};
|
|
195
267
|
}
|
|
196
|
-
return {
|
|
268
|
+
return {
|
|
269
|
+
code: 0,
|
|
270
|
+
out:
|
|
271
|
+
pick(
|
|
272
|
+
`已从 ${res.file} 删掉 ${key},现在生效的是:${now}`,
|
|
273
|
+
`Removed ${key} from ${res.file}; the value now in effect is: ${now}`,
|
|
274
|
+
) + `${warn ? `\n${warn}` : ''}\n`,
|
|
275
|
+
err: '',
|
|
276
|
+
};
|
|
197
277
|
} catch (e) {
|
|
198
278
|
return { code: 1, out: '', err: `${e.message}\n` };
|
|
199
279
|
}
|
|
200
280
|
}
|
|
201
281
|
|
|
202
|
-
return { code: 1, out: '', err: `未知子命令 ${action}\n\n${configHelp()}` };
|
|
282
|
+
return { code: 1, out: '', err: `${pick(`未知子命令 ${action}`, `Unknown subcommand ${action}`)}\n\n${configHelp()}` };
|
|
203
283
|
}
|
|
204
284
|
|
|
205
285
|
/** 中文对齐:按显示宽度补齐(中文算两格),否则表格会歪 */
|
|
@@ -209,8 +289,8 @@ function pad(text, width) {
|
|
|
209
289
|
return s + ' '.repeat(Math.max(0, width - w));
|
|
210
290
|
}
|
|
211
291
|
|
|
212
|
-
|
|
213
|
-
|
|
292
|
+
/** config help 正文:两块模板,函数里 pick(见 lib/i18n.js 纪律 3) */
|
|
293
|
+
const CONFIG_HELP_ZH = `用法:gateway-agent config <子命令>
|
|
214
294
|
|
|
215
295
|
list 列出所有配置项、当前值与来源(默认)
|
|
216
296
|
get <键> 查看某一项(含默认值、说明、对应的环境变量与命令行选项)
|
|
@@ -227,4 +307,25 @@ Web 界面「设置」里的密钥输入框写的是同一个文件 —— 命
|
|
|
227
307
|
|
|
228
308
|
REPL 里同一套:/config、/config set <键> <值>、/config get <键>、/config unset <键>
|
|
229
309
|
`;
|
|
310
|
+
|
|
311
|
+
const CONFIG_HELP_EN = `Usage: gateway-agent config <subcommand>
|
|
312
|
+
|
|
313
|
+
list list every setting with its current value and source (default)
|
|
314
|
+
get <key> show one setting (default value, description, matching env var and CLI option)
|
|
315
|
+
set <key> <value> write to the config file (atomic write; bad input fails loudly and lists valid keys)
|
|
316
|
+
unset <key> remove a setting and fall back to the built-in default
|
|
317
|
+
path print the paths of the config file and the secret file
|
|
318
|
+
|
|
319
|
+
The config file path can be overridden with the LLM_GATEWAY_CONFIG environment variable.
|
|
320
|
+
Priority: command line > environment variable > config file > built-in default — the "Source" column of list tells you which layer the effective value comes from.
|
|
321
|
+
|
|
322
|
+
The secret (key) takes another path: config set key sk-xxx writes <data root>/credentials.json (readable by this user only)
|
|
323
|
+
and is **never written to config.json**; a key from the command line / environment / .env takes precedence over it. get key returns only a mask, never the plaintext.
|
|
324
|
+
The key input in the Web "Settings" panel writes the same file — the CLI and the UI never store two copies.
|
|
325
|
+
|
|
326
|
+
The REPL uses the same set: /config, /config set <key> <value>, /config get <key>, /config unset <key>
|
|
327
|
+
`;
|
|
328
|
+
|
|
329
|
+
export function configHelp() {
|
|
330
|
+
return pick(CONFIG_HELP_ZH, CONFIG_HELP_EN);
|
|
230
331
|
}
|