gpteam 0.1.9 → 0.1.12
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 +8 -2
- package/lib/cli.js +30 -0
- package/lib/client-install.js +105 -0
- package/lib/config.js +66 -29
- package/lib/help.js +3 -2
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -6,11 +6,15 @@ Interactive GPTeam API client configurator.
|
|
|
6
6
|
npx gpteam
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
The CLI asks for an API key, validates it with `/v1/models`, detects available models, benchmarks all production ingress endpoints with real API requests, then backs up old files and writes the selected client configuration. The next steps are blocked until the key validation succeeds.
|
|
9
|
+
The CLI asks for an API key, validates it with `/v1/models`, checks whether the selected client command is installed, detects available models, benchmarks all production ingress endpoints with real API requests, then backs up old files and writes the selected client configuration. The next steps are blocked until the key validation succeeds. Client detection runs before model selection and benchmarking, so a missing local client does not waste API probes.
|
|
10
|
+
|
|
11
|
+
When a selected client is missing, the interactive CLI shows the exact install command and asks before running it. `--install-client` skips that confirmation for scripted setup. Codex, Claude Code, OpenCode, and OpenClaw use npm-based install commands; OpenCode uses the current `opencode-ai` package. Windows is blocked for OpenClaw because GPTeam's OpenClaw config writer currently supports macOS and Linux only.
|
|
10
12
|
|
|
11
13
|
Recommendation order is deterministic: success rate first, then an experience score. The score weighs first SSE event time, total completion time, p90 completion tail latency, and health-check time, so a node with one very slow probe is not recommended just because its median result looks good. The result table also marks the fastest full completion separately from the balanced recommendation, because deep or long-output model runs may care more about full completion time than first-token responsiveness. Model prompts use "configurable context" wording because the value is written to the client-side Codex `model_context_window`; it must not be confused with a public marketing total-window label.
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
Client config writing follows the same safety pattern as cc-switch: keep Codex top-level fields separate from provider tables, preserve unrelated sections such as MCP servers, merge OpenCode/OpenClaw providers additively, and stop before writing when an existing JSON/JSON5 config cannot be parsed. GPTeam keeps two proxy-specific extensions on top of that baseline: Codex explicitly disables WebSocket prewarm, and Claude Code writes the reasoning-effort header through `ANTHROPIC_CUSTOM_HEADERS`.
|
|
16
|
+
|
|
17
|
+
Claude Code is written to `~/.claude/settings.json` under the `env` section, using the GPTeam `/anthropic` base URL. OpenClaw writes `models.providers.gpteam` and also selects `gpteam/<model>` under `agents.defaults.model`, so the chosen model is active without an extra manual step.
|
|
14
18
|
|
|
15
19
|
Supported clients:
|
|
16
20
|
|
|
@@ -25,3 +29,5 @@ Useful non-interactive smoke checks:
|
|
|
25
29
|
npx gpteam --help
|
|
26
30
|
npx gpteam --version
|
|
27
31
|
```
|
|
32
|
+
|
|
33
|
+
`--install-client` is for scripted setup after you also provide the usual non-interactive inputs such as `--api-key`, `--client`, `--model`, and `--node`.
|
package/lib/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import readline from 'node:readline/promises';
|
|
2
2
|
import { stdin as input, stdout as output } from 'node:process';
|
|
3
3
|
import { benchmarkNodes, formatMs } from './bench.js';
|
|
4
|
+
import { ensureClientInstalled, formatInstallCommand } from './client-install.js';
|
|
4
5
|
import { CLIENTS, writeClientConfig } from './config.js';
|
|
5
6
|
import { getHelpText, PACKAGE_NAME, PACKAGE_VERSION } from './help.js';
|
|
6
7
|
import { modelByID, validateApiKey } from './models.js';
|
|
@@ -30,6 +31,7 @@ export async function runCli(argv = []) {
|
|
|
30
31
|
|
|
31
32
|
printStep(theme, 2, 5, '选择客户端和模型');
|
|
32
33
|
const client = await choose(rl, '请选择客户端类型', CLIENTS, args.client, theme);
|
|
34
|
+
await ensureSelectedClientInstalled(client.id, args, rl, theme);
|
|
33
35
|
const models = validation.models;
|
|
34
36
|
const model = await chooseModel(rl, models, args.model, theme);
|
|
35
37
|
const contextLength = await askContextLength(rl, model, args.context);
|
|
@@ -251,6 +253,34 @@ function printStatus(theme, label, detail) {
|
|
|
251
253
|
console.log(`${theme.ok(`[${label}]`)} ${detail}`);
|
|
252
254
|
}
|
|
253
255
|
|
|
256
|
+
async function ensureSelectedClientInstalled(clientID, args, rl, theme) {
|
|
257
|
+
const result = await ensureClientInstalled(clientID, {
|
|
258
|
+
autoInstall: cliFlagEnabled(args.installClient) || cliFlagEnabled(args.yes) || cliFlagEnabled(args.y),
|
|
259
|
+
confirmInstall: shouldPromptForInstall() ? (spec) => confirmClientInstall(rl, spec, theme) : undefined
|
|
260
|
+
});
|
|
261
|
+
if (result.status === 'installed') {
|
|
262
|
+
printStatus(theme, '已检测', `${result.spec.label} 命令可用:${result.spec.command}`);
|
|
263
|
+
} else {
|
|
264
|
+
printStatus(theme, '已安装', `${result.spec.label} 安装完成。`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function confirmClientInstall(rl, spec, theme) {
|
|
269
|
+
printStatus(theme, '未检测到', `${spec.label} 命令 ${spec.command}`);
|
|
270
|
+
printHint(theme, `将执行安装命令:${formatInstallCommand(spec)}`);
|
|
271
|
+
printHint(theme, `安装后会继续配置,可用 ${spec.versionHint} 自检版本。`);
|
|
272
|
+
const answer = await rl.question('现在安装吗?输入 y 继续,其他输入停止:');
|
|
273
|
+
return ['y', 'yes'].includes(answer.trim().toLowerCase());
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function shouldPromptForInstall() {
|
|
277
|
+
return Boolean(input.isTTY && output.isTTY);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function cliFlagEnabled(value) {
|
|
281
|
+
return value === true || ['1', 'true', 'yes', 'y'].includes(String(value || '').toLowerCase());
|
|
282
|
+
}
|
|
283
|
+
|
|
254
284
|
function toCamel(value) {
|
|
255
285
|
return value.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
256
286
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export const CLIENT_INSTALL_SPECS = {
|
|
4
|
+
codex: {
|
|
5
|
+
id: 'codex',
|
|
6
|
+
label: 'Codex',
|
|
7
|
+
command: 'codex',
|
|
8
|
+
installCommand: ['npm', ['i', '-g', '@openai/codex@latest']],
|
|
9
|
+
versionHint: 'codex --version'
|
|
10
|
+
},
|
|
11
|
+
opencode: {
|
|
12
|
+
id: 'opencode',
|
|
13
|
+
label: 'OpenCode',
|
|
14
|
+
command: 'opencode',
|
|
15
|
+
installCommand: ['npm', ['i', '-g', 'opencode-ai@latest']],
|
|
16
|
+
versionHint: 'opencode --version'
|
|
17
|
+
},
|
|
18
|
+
'claude-code': {
|
|
19
|
+
id: 'claude-code',
|
|
20
|
+
label: 'Claude Code',
|
|
21
|
+
command: 'claude',
|
|
22
|
+
installCommand: ['npm', ['i', '-g', '@anthropic-ai/claude-code@latest']],
|
|
23
|
+
versionHint: 'claude --version'
|
|
24
|
+
},
|
|
25
|
+
openclaw: {
|
|
26
|
+
id: 'openclaw',
|
|
27
|
+
label: 'OpenClaw',
|
|
28
|
+
command: 'openclaw',
|
|
29
|
+
installCommand: ['npm', ['i', '-g', 'openclaw@latest']],
|
|
30
|
+
versionHint: 'openclaw --version',
|
|
31
|
+
windowsUnsupported: true
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function resolveClientInstallSpec(clientID) {
|
|
36
|
+
const spec = CLIENT_INSTALL_SPECS[clientID];
|
|
37
|
+
if (!spec) throw new Error(`未知客户端:${clientID}`);
|
|
38
|
+
return spec;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function ensureClientInstalled(clientID, options = {}) {
|
|
42
|
+
const spec = resolveClientInstallSpec(clientID);
|
|
43
|
+
const platform = options.platform || process.platform;
|
|
44
|
+
if (spec.windowsUnsupported && platform === 'win32') {
|
|
45
|
+
throw new Error(`Windows 暂不支持自动安装或自动配置 ${spec.label}。请在 macOS / Linux 环境运行配置。`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const commandExists = options.commandExists || defaultCommandExists;
|
|
49
|
+
if (await commandExists(spec.command)) return { status: 'installed', spec };
|
|
50
|
+
|
|
51
|
+
if (!await shouldInstallMissingClient(spec, options)) {
|
|
52
|
+
throw new Error(`未安装 ${spec.label},已停止配置。安装后重新运行 npx gpteam。`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await runInstallCommand(spec, options.runCommand || defaultRunCommand, platform);
|
|
56
|
+
if (!await commandExists(spec.command)) {
|
|
57
|
+
throw new Error(`安装完成但仍未检测到 ${spec.label} 命令 ${spec.command}。请检查 npm 全局 bin 是否在 PATH 中,然后运行 ${spec.versionHint}。`);
|
|
58
|
+
}
|
|
59
|
+
return { status: 'installed-after-run', spec };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function formatInstallCommand(spec) {
|
|
63
|
+
return [spec.installCommand[0], ...spec.installCommand[1]].join(' ');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function defaultCommandExists(command) {
|
|
67
|
+
const checker = process.platform === 'win32' ? 'where' : 'sh';
|
|
68
|
+
const args = process.platform === 'win32' ? [command] : ['-c', `command -v ${shellQuote(command)}`];
|
|
69
|
+
const result = await defaultRunCommand(checker, args, { stdio: 'ignore' });
|
|
70
|
+
return result.status === 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function shouldInstallMissingClient(spec, options) {
|
|
74
|
+
if (options.autoInstall) return true;
|
|
75
|
+
if (!options.confirmInstall) return false;
|
|
76
|
+
return options.confirmInstall(spec);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function runInstallCommand(spec, runCommand, platform) {
|
|
80
|
+
const [command, args] = spec.installCommand;
|
|
81
|
+
const result = await runCommand(resolveSpawnCommand(command, platform), args, { stdio: 'inherit' });
|
|
82
|
+
if (result.status !== 0) {
|
|
83
|
+
throw new Error(`${spec.label} 安装失败,命令:${formatInstallCommand(spec)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function resolveSpawnCommand(command, platform = process.platform) {
|
|
88
|
+
if (platform === 'win32' && command === 'npm') return 'npm.cmd';
|
|
89
|
+
return command;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function defaultRunCommand(command, args, options = {}) {
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
const child = spawn(command, args, {
|
|
95
|
+
stdio: options.stdio || 'ignore',
|
|
96
|
+
shell: options.shell || false
|
|
97
|
+
});
|
|
98
|
+
child.on('error', () => resolve({ status: 127 }));
|
|
99
|
+
child.on('close', (status) => resolve({ status: status ?? 1 }));
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function shellQuote(value) {
|
|
104
|
+
return `'${String(value).replace(/'/g, "'\\''")}'`;
|
|
105
|
+
}
|
package/lib/config.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import JSON5 from 'json5';
|
|
4
5
|
import { endpointRoot } from './nodes.js';
|
|
5
6
|
|
|
7
|
+
const PROVIDER_ID = 'gpteam';
|
|
8
|
+
|
|
6
9
|
export const CLIENTS = [
|
|
7
10
|
{ id: 'codex', label: 'Codex' },
|
|
8
11
|
{ id: 'opencode', label: 'OpenCode' },
|
|
@@ -13,13 +16,13 @@ export const CLIENTS = [
|
|
|
13
16
|
export function writeClientConfig(clientID, settings) {
|
|
14
17
|
if (clientID === 'codex') return writeCodexConfig(settings);
|
|
15
18
|
if (clientID === 'opencode') return writeOpenCodeConfig(settings);
|
|
16
|
-
if (clientID === 'claude-code') return
|
|
19
|
+
if (clientID === 'claude-code') return writeClaudeCodeConfig(settings);
|
|
17
20
|
if (clientID === 'openclaw') return writeOpenClawConfig(settings);
|
|
18
21
|
throw new Error(`未知客户端:${clientID}`);
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
export function writeCodexConfig(settings) {
|
|
22
|
-
const dir = process.env.CODEX_HOME || path.join(
|
|
25
|
+
const dir = process.env.CODEX_HOME || path.join(homeDir(), '.codex');
|
|
23
26
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
24
27
|
const configPath = path.join(dir, 'config.toml');
|
|
25
28
|
const authPath = path.join(dir, 'auth.json');
|
|
@@ -56,17 +59,19 @@ export function writeCodexConfig(settings) {
|
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
export function writeOpenCodeConfig(settings) {
|
|
59
|
-
const dir = path.join(
|
|
62
|
+
const dir = path.join(homeDir(), '.config', 'opencode');
|
|
60
63
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
61
64
|
const filePath = path.join(dir, 'opencode.json');
|
|
62
65
|
backupIfExists(filePath);
|
|
63
66
|
const config = readJSON(filePath, { $schema: 'https://opencode.ai/config.json' });
|
|
64
67
|
config.provider = config.provider && typeof config.provider === 'object' ? config.provider : {};
|
|
65
|
-
config.provider
|
|
68
|
+
config.provider[PROVIDER_ID] = {
|
|
66
69
|
npm: '@ai-sdk/openai',
|
|
70
|
+
name: 'GPTeam',
|
|
67
71
|
options: {
|
|
68
72
|
apiKey: settings.apiKey,
|
|
69
|
-
baseURL: settings.node.baseUrl
|
|
73
|
+
baseURL: settings.node.baseUrl,
|
|
74
|
+
setCacheKey: true
|
|
70
75
|
},
|
|
71
76
|
models: {
|
|
72
77
|
[settings.model]: {
|
|
@@ -78,7 +83,7 @@ export function writeOpenCodeConfig(settings) {
|
|
|
78
83
|
variants: {
|
|
79
84
|
[settings.effort]: {
|
|
80
85
|
reasoningEffort: settings.effort,
|
|
81
|
-
reasoningSummary:
|
|
86
|
+
reasoningSummary: 'auto',
|
|
82
87
|
textVerbosity: 'medium'
|
|
83
88
|
}
|
|
84
89
|
}
|
|
@@ -89,30 +94,33 @@ export function writeOpenCodeConfig(settings) {
|
|
|
89
94
|
return [filePath];
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
export function
|
|
93
|
-
const dir = path.join(
|
|
97
|
+
export function writeClaudeCodeConfig(settings) {
|
|
98
|
+
const dir = path.join(homeDir(), '.claude');
|
|
94
99
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
95
|
-
const filePath =
|
|
100
|
+
const filePath = claudeSettingsPath(dir);
|
|
96
101
|
backupIfExists(filePath);
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
const config = readJSON(filePath, {});
|
|
103
|
+
config.env = config.env && typeof config.env === 'object' ? config.env : {};
|
|
104
|
+
Object.assign(config.env, {
|
|
105
|
+
ANTHROPIC_AUTH_TOKEN: settings.apiKey,
|
|
106
|
+
ANTHROPIC_BASE_URL: claudeBaseUrl(settings.node.baseUrl),
|
|
107
|
+
ANTHROPIC_MODEL: settings.model,
|
|
108
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: settings.model,
|
|
109
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: settings.model,
|
|
110
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: settings.model,
|
|
111
|
+
ANTHROPIC_CUSTOM_HEADERS: `X-Codex-Reasoning-Effort: ${settings.effort}`
|
|
112
|
+
});
|
|
113
|
+
writeJSON(filePath, config);
|
|
108
114
|
return [filePath];
|
|
109
115
|
}
|
|
110
116
|
|
|
117
|
+
export const writeClaudeCodeEnv = writeClaudeCodeConfig;
|
|
118
|
+
|
|
111
119
|
export function writeOpenClawConfig(settings) {
|
|
112
120
|
if (process.platform === 'win32') {
|
|
113
121
|
throw new Error('OpenClaw 自动配置当前只支持 macOS / Linux');
|
|
114
122
|
}
|
|
115
|
-
const dir = path.join(
|
|
123
|
+
const dir = path.join(homeDir(), '.openclaw');
|
|
116
124
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
117
125
|
const filePath = path.join(dir, 'openclaw.json');
|
|
118
126
|
backupIfExists(filePath);
|
|
@@ -122,7 +130,7 @@ export function writeOpenClawConfig(settings) {
|
|
|
122
130
|
config.models.providers = config.models.providers && typeof config.models.providers === 'object'
|
|
123
131
|
? config.models.providers
|
|
124
132
|
: {};
|
|
125
|
-
config.models.providers
|
|
133
|
+
config.models.providers[PROVIDER_ID] = {
|
|
126
134
|
baseUrl: settings.node.baseUrl,
|
|
127
135
|
apiKey: settings.apiKey,
|
|
128
136
|
api: 'openai-responses',
|
|
@@ -134,10 +142,43 @@ export function writeOpenClawConfig(settings) {
|
|
|
134
142
|
maxTokens: Number(settings.maxOutputTokens)
|
|
135
143
|
}]
|
|
136
144
|
};
|
|
145
|
+
config.agents = config.agents && typeof config.agents === 'object' ? config.agents : {};
|
|
146
|
+
config.agents.defaults = config.agents.defaults && typeof config.agents.defaults === 'object'
|
|
147
|
+
? config.agents.defaults
|
|
148
|
+
: {};
|
|
149
|
+
const qualifiedModel = `${PROVIDER_ID}/${settings.model}`;
|
|
150
|
+
config.agents.defaults.model = {
|
|
151
|
+
primary: qualifiedModel,
|
|
152
|
+
fallbacks: []
|
|
153
|
+
};
|
|
154
|
+
config.agents.defaults.models = config.agents.defaults.models && typeof config.agents.defaults.models === 'object'
|
|
155
|
+
? config.agents.defaults.models
|
|
156
|
+
: {};
|
|
157
|
+
config.agents.defaults.models[qualifiedModel] = {
|
|
158
|
+
alias: settings.model
|
|
159
|
+
};
|
|
137
160
|
writeJSON(filePath, config);
|
|
138
161
|
return [filePath];
|
|
139
162
|
}
|
|
140
163
|
|
|
164
|
+
function homeDir() {
|
|
165
|
+
return process.env.GPTEAM_CONFIG_TEST_HOME || os.homedir();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function claudeSettingsPath(dir) {
|
|
169
|
+
const settings = path.join(dir, 'settings.json');
|
|
170
|
+
if (fs.existsSync(settings)) return settings;
|
|
171
|
+
const legacy = path.join(dir, 'claude.json');
|
|
172
|
+
if (fs.existsSync(legacy)) return legacy;
|
|
173
|
+
return settings;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function claudeBaseUrl(baseUrl) {
|
|
177
|
+
const root = endpointRoot(baseUrl);
|
|
178
|
+
if (/\/anthropic\/?$/.test(root)) return root.replace(/\/$/, '');
|
|
179
|
+
return `${root}/anthropic`;
|
|
180
|
+
}
|
|
181
|
+
|
|
141
182
|
function stripCodexManagedConfig(raw) {
|
|
142
183
|
const rootLines = [];
|
|
143
184
|
const rest = [];
|
|
@@ -236,9 +277,9 @@ function writeTextAtomic(filePath, text, mode) {
|
|
|
236
277
|
function readJSON(filePath, fallback) {
|
|
237
278
|
if (!fs.existsSync(filePath)) return fallback;
|
|
238
279
|
try {
|
|
239
|
-
return
|
|
240
|
-
} catch {
|
|
241
|
-
|
|
280
|
+
return JSON5.parse(fs.readFileSync(filePath, 'utf8'));
|
|
281
|
+
} catch (error) {
|
|
282
|
+
throw new Error(`无法解析配置文件 ${filePath}:${error.message}。为避免覆盖旧配置,已停止写入`);
|
|
242
283
|
}
|
|
243
284
|
}
|
|
244
285
|
|
|
@@ -249,7 +290,3 @@ function writeJSON(filePath, value) {
|
|
|
249
290
|
function tomlString(value) {
|
|
250
291
|
return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
251
292
|
}
|
|
252
|
-
|
|
253
|
-
function shellQuote(value) {
|
|
254
|
-
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
255
|
-
}
|
package/lib/help.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const PACKAGE_NAME = 'gpteam';
|
|
2
|
-
export const PACKAGE_VERSION = '0.1.
|
|
2
|
+
export const PACKAGE_VERSION = '0.1.12';
|
|
3
3
|
|
|
4
4
|
export function getHelpText() {
|
|
5
5
|
return [
|
|
@@ -18,9 +18,10 @@ export function getHelpText() {
|
|
|
18
18
|
' --node <id> jp-direct / jp-split / hk-split / us-split',
|
|
19
19
|
' --rounds <n> 每个入口测速轮数,默认 3',
|
|
20
20
|
' --max-output-tokens <n> 测速输出上限,默认 648',
|
|
21
|
+
' --install-client 客户端命令缺失时直接安装,不再二次确认',
|
|
21
22
|
' --help 显示帮助',
|
|
22
23
|
' --version 显示版本',
|
|
23
24
|
'',
|
|
24
|
-
'说明:输入 key 后会先请求 /v1/models
|
|
25
|
+
'说明:输入 key 后会先请求 /v1/models 校验,通过后才继续。选好客户端后会检测本机命令是否可用,缺失时提示安装;Windows 暂不支持自动配置 OpenClaw。测速会请求 GET /api/health 和流式 POST /v1/responses。入口之间并行测速,写新配置前会先备份旧配置。'
|
|
25
26
|
].join('\n');
|
|
26
27
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gpteam",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "GPTeam API interactive client configurator and ingress benchmark CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"test": "node --test test/*.test.mjs"
|
|
16
16
|
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"json5": "^2.2.3"
|
|
19
|
+
},
|
|
17
20
|
"engines": {
|
|
18
21
|
"node": ">=18.18.0"
|
|
19
22
|
},
|