@wavespeed/cli 0.4.19 → 0.4.21
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 +1 -1
- package/dist/commands/setup.js +43 -4
- package/dist/lib/setup-agents.js +35 -0
- package/package.json +1 -1
- package/skills/SKILL.md +2 -1
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ wavespeed logout Clear stored API key
|
|
|
141
141
|
wavespeed status Show masked key, base URL, useful links
|
|
142
142
|
wavespeed config [--default-model …] View / update CLI defaults
|
|
143
143
|
wavespeed upgrade [--check] [--json] Upgrade to the latest version (--check only reports)
|
|
144
|
-
wavespeed setup [agent] Point claude, codex, opencode, openclaw, hermes, cline, qwen
|
|
144
|
+
wavespeed setup [agent] Point a coding agent (claude, codex, gemini, opencode, openclaw, hermes, kilo, cline, qwen, droid, crush, aider, continue, goose, miniagent, dsh) at the WaveSpeed LLM gateway (--dry-run, --undo, --model)
|
|
145
145
|
|
|
146
146
|
# generation
|
|
147
147
|
wavespeed run [model|alias] -p "…" Run any model or alias (uses defaultModel if omitted)
|
package/dist/commands/setup.js
CHANGED
|
@@ -9,10 +9,11 @@ import JSON5 from 'json5';
|
|
|
9
9
|
import { getApiKey, userConfig } from '../lib/config.js';
|
|
10
10
|
import { clientAttributionHeaders } from '../lib/client-headers.js';
|
|
11
11
|
import { emitJson, emitJsonError, info, setJsonMode } from '../lib/log.js';
|
|
12
|
-
import { DEFAULT_MODEL, KEY_ENV, LLM_BASE_URL, aiderValues, applyCodexToml, applyContinueYaml, applyDshPatch, applyHermesYaml, applyJson, applyYamlScalars, claudeEdits, crushEdits, droidEdits, gooseProviderFile, goosePlainValues, miniAgentValues, openclawEdits, opencodeEdits, qwenEdits, revertCodexToml, revertContinueYaml, revertDshPatch, revertHermesYaml, revertJson, revertYamlScalars, validateModel, } from '../lib/setup-agents.js';
|
|
12
|
+
import { DEFAULT_MODEL, KEY_ENV, LLM_BASE_URL, aiderValues, applyCodexToml, applyContinueYaml, applyDshPatch, applyHermesYaml, applyGeminiEnv, applyJson, applyYamlScalars, claudeEdits, crushEdits, droidEdits, geminiEdits, gooseProviderFile, goosePlainValues, miniAgentValues, openclawEdits, opencodeEdits, qwenEdits, revertCodexToml, revertContinueYaml, revertDshPatch, revertGeminiEnv, revertHermesYaml, revertJson, revertYamlScalars, validateModel, } from '../lib/setup-agents.js';
|
|
13
13
|
const home = os.homedir();
|
|
14
14
|
// Goose: ~/.config/goose on macOS and Linux, %APPDATA%\Block\goose\config on Windows (per its docs; Windows not tested here).
|
|
15
15
|
const GOOSE_DIR = process.platform === 'win32' ? path.join(process.env.APPDATA ?? path.join(home, 'AppData', 'Roaming'), 'Block', 'goose', 'config') : path.join(home, '.config', 'goose');
|
|
16
|
+
const GEMINI_SETTINGS_FILE = path.join(home, '.gemini', 'settings.json');
|
|
16
17
|
const GOOSE_PROVIDER_FILE = path.join(GOOSE_DIR, 'custom_providers', 'wavespeed.json');
|
|
17
18
|
const AGENTS = [
|
|
18
19
|
{ id: 'codex', name: 'Codex', file: path.join(home, '.codex', 'config.toml'), command: 'codex', kind: 'toml', keyInFile: false },
|
|
@@ -29,6 +30,8 @@ const AGENTS = [
|
|
|
29
30
|
{ id: 'continue', name: 'Continue CLI', file: path.join(home, '.continue', 'config.yaml'), command: 'cn', kind: 'yamlseq', keyInFile: false },
|
|
30
31
|
{ id: 'goose', name: 'Goose', file: path.join(GOOSE_DIR, 'config.yaml'), command: 'goose', kind: 'goose', keyInFile: false },
|
|
31
32
|
{ id: 'dsh', name: 'DeepSeek Harness', file: path.join(process.env.DSH_HOME || path.join(home, '.dsh'), 'cordis.patch.yml'), command: 'dsh', kind: 'dsh', keyInFile: false },
|
|
33
|
+
// Gemini CLI reads the base URL and key from ~/.gemini/.env (trusted folders only); the model and auth type live in settings.json.
|
|
34
|
+
{ id: 'gemini', name: 'Gemini CLI', file: path.join(home, '.gemini', '.env'), command: 'gemini', kind: 'gemini', keyInFile: true },
|
|
32
35
|
// Cline's CLI keeps provider settings (including the key) in this file; we write it only through `cline auth`.
|
|
33
36
|
{ id: 'cline', name: 'Cline', file: path.join(home, '.cline', 'data', 'settings', 'providers.json'), command: 'cline', kind: 'cline', keyInFile: true },
|
|
34
37
|
// Cline's CLI keeps provider settings (including the key) in this file; we write it only through `cline auth`.
|
|
@@ -98,7 +101,7 @@ function envHint() {
|
|
|
98
101
|
export function registerSetup(program) {
|
|
99
102
|
program
|
|
100
103
|
.command('setup [agent]')
|
|
101
|
-
.description('Point a coding agent (claude, codex, opencode, openclaw, hermes, cline, qwen, miniagent, kilo, droid, crush, aider, continue, goose, dsh) at the WaveSpeed LLM gateway')
|
|
104
|
+
.description('Point a coding agent (claude, codex, opencode, openclaw, hermes, cline, qwen, miniagent, kilo, droid, crush, aider, continue, goose, dsh, gemini) at the WaveSpeed LLM gateway')
|
|
102
105
|
.option('--model <id>', `Model to use (default: ${DEFAULT_MODEL})`)
|
|
103
106
|
.option('--dry-run', 'Show what would change without writing anything')
|
|
104
107
|
.option('--undo', "Restore the agent's config to what it was before setup")
|
|
@@ -143,7 +146,7 @@ export function registerSetup(program) {
|
|
|
143
146
|
return;
|
|
144
147
|
}
|
|
145
148
|
// Untouched since setup wrote it: put the original bytes back (or remove a file we created).
|
|
146
|
-
const exact = !!state?.hash && agent.kind !== 'cline' && agent.kind !== 'goose' &&
|
|
149
|
+
const exact = !!state?.hash && agent.kind !== 'cline' && agent.kind !== 'goose' && agent.kind !== 'gemini' &&
|
|
147
150
|
fs.existsSync(state.file) && sha256(fs.readFileSync(state.file)) === state.hash;
|
|
148
151
|
if (exact && state) {
|
|
149
152
|
const bak = `${state.file}.wavespeed.bak`;
|
|
@@ -210,6 +213,26 @@ export function registerSetup(program) {
|
|
|
210
213
|
forget();
|
|
211
214
|
}
|
|
212
215
|
}
|
|
216
|
+
else if (agent.kind === 'gemini') {
|
|
217
|
+
const prev = state.previous;
|
|
218
|
+
const envAfter = revertGeminiEnv(fs.readFileSync(state.file, 'utf8'));
|
|
219
|
+
if (!opts.dryRun) {
|
|
220
|
+
if (envAfter.trim() === '' && !state.existed)
|
|
221
|
+
fs.rmSync(state.file, { force: true });
|
|
222
|
+
else
|
|
223
|
+
fs.writeFileSync(state.file, envAfter);
|
|
224
|
+
fs.rmSync(`${state.file}.wavespeed.bak`, { force: true });
|
|
225
|
+
if (fs.existsSync(GEMINI_SETTINGS_FILE)) {
|
|
226
|
+
const doc = revertJson(readJson(GEMINI_SETTINGS_FILE, false), prev.settings);
|
|
227
|
+
if (!prev.settingsExisted && Object.keys(doc).length === 0)
|
|
228
|
+
fs.rmSync(GEMINI_SETTINGS_FILE, { force: true });
|
|
229
|
+
else
|
|
230
|
+
fs.writeFileSync(GEMINI_SETTINGS_FILE, JSON.stringify(doc, null, 2) + '\n');
|
|
231
|
+
}
|
|
232
|
+
fs.rmSync(`${GEMINI_SETTINGS_FILE}.wavespeed.bak`, { force: true });
|
|
233
|
+
forget();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
213
236
|
else if (agent.kind === 'goose') {
|
|
214
237
|
const prev = state.previous;
|
|
215
238
|
const after = revertYamlScalars(fs.readFileSync(state.file, 'utf8'), prev.scalars);
|
|
@@ -268,6 +291,7 @@ export function registerSetup(program) {
|
|
|
268
291
|
}
|
|
269
292
|
let next = '';
|
|
270
293
|
let previous = [];
|
|
294
|
+
let geminiSettings = '';
|
|
271
295
|
try {
|
|
272
296
|
if (agent.kind === 'toml') {
|
|
273
297
|
next = applyCodexToml(fs.existsSync(agent.file) ? fs.readFileSync(agent.file, 'utf8') : '', model);
|
|
@@ -297,6 +321,12 @@ export function registerSetup(program) {
|
|
|
297
321
|
next = r.text;
|
|
298
322
|
previous = { scalars: r.previous, providerRaw: fs.existsSync(GOOSE_PROVIDER_FILE) ? fs.readFileSync(GOOSE_PROVIDER_FILE, 'utf8') : null };
|
|
299
323
|
}
|
|
324
|
+
else if (agent.kind === 'gemini') {
|
|
325
|
+
const r = applyJson(readJson(GEMINI_SETTINGS_FILE, false), geminiEdits(model));
|
|
326
|
+
geminiSettings = JSON.stringify(r.doc, null, 2) + '\n';
|
|
327
|
+
previous = { settings: r.previous, settingsExisted: fs.existsSync(GEMINI_SETTINGS_FILE) };
|
|
328
|
+
next = applyGeminiEnv(fs.existsSync(agent.file) ? fs.readFileSync(agent.file, 'utf8') : '', apiKey);
|
|
329
|
+
}
|
|
300
330
|
else if (agent.kind === 'cline') {
|
|
301
331
|
previous = { existed: fs.existsSync(agent.file) };
|
|
302
332
|
if (!onPath('cline'))
|
|
@@ -317,7 +347,7 @@ export function registerSetup(program) {
|
|
|
317
347
|
if (opts.dryRun) {
|
|
318
348
|
if (opts.json)
|
|
319
349
|
return emitJson({ agent: agent.id, model, file: agent.file, dry_run: true, will_create: !exists });
|
|
320
|
-
info(chalk.bold(`Would ${exists ? 'update' : 'create'} ${shown(agent.file)}${agent.kind === 'goose' ? ` and ${shown(GOOSE_PROVIDER_FILE)}` : ''}`) + chalk.gray(` (model ${model}${agent.keyInFile ? '; your API key would be stored in this file' : ''})`));
|
|
350
|
+
info(chalk.bold(`Would ${exists ? 'update' : 'create'} ${shown(agent.file)}${agent.kind === 'goose' ? ` and ${shown(GOOSE_PROVIDER_FILE)}` : agent.kind === 'gemini' ? ` and ${shown(GEMINI_SETTINGS_FILE)}` : ''}`) + chalk.gray(` (model ${model}${agent.keyInFile ? '; your API key would be stored in this file' : ''})`));
|
|
321
351
|
info(chalk.gray('Nothing written.'));
|
|
322
352
|
return;
|
|
323
353
|
}
|
|
@@ -340,6 +370,12 @@ export function registerSetup(program) {
|
|
|
340
370
|
else {
|
|
341
371
|
const hadComments = agent.kind === 'json5' && exists && /^\s*\/\/|\/\*/m.test(fs.readFileSync(agent.file, 'utf8'));
|
|
342
372
|
writeFile(agent.file, next, agent.keyInFile, !!userConfig.get('setup')?.[agent.id]);
|
|
373
|
+
if (agent.kind === 'gemini') {
|
|
374
|
+
const bak = `${GEMINI_SETTINGS_FILE}.wavespeed.bak`;
|
|
375
|
+
if (fs.existsSync(GEMINI_SETTINGS_FILE) && !fs.existsSync(bak))
|
|
376
|
+
fs.copyFileSync(GEMINI_SETTINGS_FILE, bak);
|
|
377
|
+
fs.writeFileSync(GEMINI_SETTINGS_FILE, geminiSettings);
|
|
378
|
+
}
|
|
343
379
|
if (agent.kind === 'goose') {
|
|
344
380
|
fs.mkdirSync(path.dirname(GOOSE_PROVIDER_FILE), { recursive: true });
|
|
345
381
|
fs.writeFileSync(GOOSE_PROVIDER_FILE, gooseProviderFile(model));
|
|
@@ -367,6 +403,9 @@ export function registerSetup(program) {
|
|
|
367
403
|
if (!agent.keyInFile) {
|
|
368
404
|
info(`\nThe key is read from ${chalk.bold(KEY_ENV)}; set it in the shell that launches ${agent.command}:\n ${chalk.cyan(envHint())}\n Keys: https://wavespeed.ai/accesskey`);
|
|
369
405
|
}
|
|
406
|
+
if (agent.kind === 'gemini') {
|
|
407
|
+
info(chalk.yellow('\nGemini CLI reads ~/.gemini/.env only in folders it trusts: accept the trust prompt on first start, or set GEMINI_CLI_TRUST_WORKSPACE=true for scripts and CI.'));
|
|
408
|
+
}
|
|
370
409
|
info(`\nStart it with ${chalk.cyan(agent.command)}. Revert with ${chalk.cyan(`wavespeed setup ${agent.id} --undo`)}.`);
|
|
371
410
|
if (check && !check.ok)
|
|
372
411
|
process.exit(1);
|
package/dist/lib/setup-agents.js
CHANGED
|
@@ -576,3 +576,38 @@ export function applyCodexToml(text, model) {
|
|
|
576
576
|
const body = edited.join('\n').replace(/\n+$/, '');
|
|
577
577
|
return [...top, body, ...tail].join('\n').replace(/\n{3,}/g, '\n\n');
|
|
578
578
|
}
|
|
579
|
+
// ---------- Gemini CLI (managed block in ~/.gemini/.env + two keys in settings.json) ----------
|
|
580
|
+
const ENV_OPEN = '# >>> wavespeed (managed by `wavespeed setup`) >>>';
|
|
581
|
+
const ENV_CLOSE = '# <<< wavespeed <<<';
|
|
582
|
+
export function geminiEdits(model) {
|
|
583
|
+
const m = validateModel(model);
|
|
584
|
+
return [
|
|
585
|
+
{ path: ['security', 'auth', 'selectedType'], value: 'gemini-api-key' },
|
|
586
|
+
{ path: ['model', 'name'], value: m },
|
|
587
|
+
];
|
|
588
|
+
}
|
|
589
|
+
export function revertGeminiEnv(text) {
|
|
590
|
+
const out = [];
|
|
591
|
+
let skipping = false;
|
|
592
|
+
for (const line of text.split('\n')) {
|
|
593
|
+
if (line.trim() === ENV_OPEN) {
|
|
594
|
+
skipping = true;
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (skipping) {
|
|
598
|
+
if (line.trim() === ENV_CLOSE)
|
|
599
|
+
skipping = false;
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
out.push(line);
|
|
603
|
+
}
|
|
604
|
+
return out.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\n+$/, '\n').replace(/^\n$/, '');
|
|
605
|
+
}
|
|
606
|
+
/** dotenv lets the last duplicate win, so the managed block goes at the end. */
|
|
607
|
+
export function applyGeminiEnv(input, apiKey) {
|
|
608
|
+
if (/[\r\n]/.test(apiKey))
|
|
609
|
+
throw new Error('API key contains a line break');
|
|
610
|
+
const base = revertGeminiEnv(input);
|
|
611
|
+
const head = base.trim() === '' ? '' : base.replace(/\n*$/, '\n\n');
|
|
612
|
+
return head + [ENV_OPEN, `GOOGLE_GEMINI_BASE_URL=${CLAUDE_BASE_URL}`, `GEMINI_API_KEY=${apiKey}`, ENV_CLOSE, ''].join('\n');
|
|
613
|
+
}
|
package/package.json
CHANGED
package/skills/SKILL.md
CHANGED
|
@@ -77,4 +77,5 @@ The CLI never modifies the user's prompt or inputs. The single exception is expl
|
|
|
77
77
|
- Don't invent model IDs. Always confirm via `wavespeed models` or `wavespeed schema <id>` before running.
|
|
78
78
|
- Use `--json` on every `run` so you can read `outputs[0]` programmatically.
|
|
79
79
|
- `wavespeed delete` requires `--yes` when run non-interactively (that includes you).
|
|
80
|
-
-
|
|
80
|
+
- Cost and spend: `wavespeed price <id> -i k=v --json` estimates a run without generating or charging; `wavespeed balance` shows remaining credit; `wavespeed usage` (totals, per-model) and `wavespeed billings` (per-charge records) show what was spent.
|
|
81
|
+
- A `run` that fails with a gateway or network error (502/503/504, connection reset, timeout) does NOT mean the task failed, and the submit request is never retried automatically, so re-running can create a duplicate paid task. Do not re-run immediately. If the error contains a task id (`task_id` / prediction id), keep checking it with `wavespeed show <id> --json` until its status is terminal (`completed`, `failed`, `cancelled`, `timeout`, `deleted`). If no id was returned, check recent predictions with `wavespeed history --json` (add `--model <id>`; default window is the last 24h) and re-run once only if none matches. A client-side wait timeout or an interrupted `run` does not mean the task failed: tasks can run for a long time, so do not give up after a fixed time.
|