@wavespeed/cli 0.4.16 → 0.4.18
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 +87 -17
- package/dist/lib/setup-agents.js +233 -0
- package/package.json +4 -2
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 [claude
|
|
144
|
+
wavespeed setup [agent] Point claude, codex, opencode, openclaw, hermes, cline, qwen or miniagent 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
|
@@ -2,16 +2,24 @@ import chalk from 'chalk';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
5
6
|
import { confirm } from '@inquirer/prompts';
|
|
7
|
+
import JSON5 from 'json5';
|
|
6
8
|
import { getApiKey, userConfig } from '../lib/config.js';
|
|
7
9
|
import { clientAttributionHeaders } from '../lib/client-headers.js';
|
|
8
10
|
import { emitJson, emitJsonError, info, setJsonMode } from '../lib/log.js';
|
|
9
|
-
import { DEFAULT_MODEL, KEY_ENV, LLM_BASE_URL, applyCodexToml, applyJson, claudeEdits, opencodeEdits, revertCodexToml, revertJson, validateModel, } from '../lib/setup-agents.js';
|
|
11
|
+
import { DEFAULT_MODEL, KEY_ENV, LLM_BASE_URL, applyCodexToml, applyHermesYaml, applyJson, applyYamlScalars, claudeEdits, miniAgentValues, openclawEdits, opencodeEdits, qwenEdits, revertCodexToml, revertHermesYaml, revertJson, revertYamlScalars, validateModel, } from '../lib/setup-agents.js';
|
|
10
12
|
const home = os.homedir();
|
|
11
13
|
const AGENTS = [
|
|
12
14
|
{ id: 'claude', name: 'Claude Code', file: path.join(home, '.claude', 'settings.json'), command: 'claude', kind: 'json', keyInFile: true },
|
|
13
15
|
{ id: 'codex', name: 'Codex', file: path.join(home, '.codex', 'config.toml'), command: 'codex', kind: 'toml', keyInFile: false },
|
|
14
16
|
{ id: 'opencode', name: 'OpenCode', file: path.join(home, '.config', 'opencode', 'opencode.json'), command: 'opencode', kind: 'json', keyInFile: false },
|
|
17
|
+
{ id: 'openclaw', name: 'OpenClaw', file: path.join(home, '.openclaw', 'openclaw.json'), command: 'openclaw', kind: 'json5', keyInFile: false },
|
|
18
|
+
{ id: 'hermes', name: 'Hermes', file: path.join(home, '.hermes', 'config.yaml'), command: 'hermes', kind: 'yaml', keyInFile: false },
|
|
19
|
+
{ id: 'qwen', name: 'Qwen Code', file: path.join(home, '.qwen', 'settings.json'), command: 'qwen', kind: 'json', keyInFile: false },
|
|
20
|
+
{ id: 'miniagent', name: 'Mini-Agent', file: path.join(home, '.mini-agent', 'config', 'config.yaml'), command: 'mini-agent', kind: 'yamlscalars', keyInFile: true },
|
|
21
|
+
// Cline's CLI keeps provider settings (including the key) in this file; we write it only through `cline auth`.
|
|
22
|
+
{ id: 'cline', name: 'Cline', file: path.join(home, '.cline', 'data', 'settings', 'providers.json'), command: 'cline', kind: 'cline', keyInFile: true },
|
|
15
23
|
];
|
|
16
24
|
const shown = (p) => p.replace(home, '~');
|
|
17
25
|
function onPath(cmd) {
|
|
@@ -43,14 +51,14 @@ export async function selfCheck(apiKey, model) {
|
|
|
43
51
|
return { ok: false, detail: err.message ?? String(err) };
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
|
-
function readJson(file) {
|
|
54
|
+
function readJson(file, json5 = false) {
|
|
47
55
|
if (!fs.existsSync(file))
|
|
48
56
|
return {};
|
|
49
57
|
const raw = fs.readFileSync(file, 'utf8');
|
|
50
58
|
if (raw.trim() === '')
|
|
51
59
|
return {};
|
|
52
60
|
try {
|
|
53
|
-
const v = JSON.parse(raw);
|
|
61
|
+
const v = json5 ? JSON5.parse(raw) : JSON.parse(raw);
|
|
54
62
|
if (v && typeof v === 'object' && !Array.isArray(v))
|
|
55
63
|
return v;
|
|
56
64
|
}
|
|
@@ -76,7 +84,7 @@ function envHint() {
|
|
|
76
84
|
export function registerSetup(program) {
|
|
77
85
|
program
|
|
78
86
|
.command('setup [agent]')
|
|
79
|
-
.description('Point a coding agent (claude, codex, opencode) at the WaveSpeed LLM gateway')
|
|
87
|
+
.description('Point a coding agent (claude, codex, opencode, openclaw, hermes, cline, qwen, miniagent) at the WaveSpeed LLM gateway')
|
|
80
88
|
.option('--model <id>', `Model to use (default: ${DEFAULT_MODEL})`)
|
|
81
89
|
.option('--dry-run', 'Show what would change without writing anything')
|
|
82
90
|
.option('--undo', "Restore the agent's config to what it was before setup")
|
|
@@ -108,6 +116,12 @@ export function registerSetup(program) {
|
|
|
108
116
|
// ----- undo -----
|
|
109
117
|
if (opts.undo) {
|
|
110
118
|
try {
|
|
119
|
+
const state = userConfig.get('setup')?.[agent.id];
|
|
120
|
+
const forget = () => {
|
|
121
|
+
const rest = { ...(userConfig.get('setup') ?? {}) };
|
|
122
|
+
delete rest[agent.id];
|
|
123
|
+
userConfig.set('setup', rest);
|
|
124
|
+
};
|
|
111
125
|
if (agent.kind === 'toml') {
|
|
112
126
|
if (!fs.existsSync(agent.file))
|
|
113
127
|
return fail(`${shown(agent.file)} does not exist; nothing to undo`);
|
|
@@ -116,17 +130,43 @@ export function registerSetup(program) {
|
|
|
116
130
|
if (!opts.dryRun && after !== before)
|
|
117
131
|
fs.writeFileSync(agent.file, after);
|
|
118
132
|
}
|
|
133
|
+
else if (!state) {
|
|
134
|
+
return fail(`No recorded ${agent.name} setup to undo`);
|
|
135
|
+
}
|
|
136
|
+
else if (agent.kind === 'cline') {
|
|
137
|
+
const bak = `${agent.file}.wavespeed.bak`;
|
|
138
|
+
if (!opts.dryRun) {
|
|
139
|
+
if (state.previous.existed && fs.existsSync(bak))
|
|
140
|
+
fs.copyFileSync(bak, agent.file);
|
|
141
|
+
else
|
|
142
|
+
fs.rmSync(agent.file, { force: true });
|
|
143
|
+
fs.rmSync(bak, { force: true });
|
|
144
|
+
forget();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (agent.kind === 'yamlscalars') {
|
|
148
|
+
const after = revertYamlScalars(fs.readFileSync(state.file, 'utf8'), state.previous);
|
|
149
|
+
if (!opts.dryRun) {
|
|
150
|
+
if (after.trim() === '')
|
|
151
|
+
fs.rmSync(state.file, { force: true });
|
|
152
|
+
else
|
|
153
|
+
fs.writeFileSync(state.file, after);
|
|
154
|
+
forget();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else if (agent.kind === 'yaml') {
|
|
158
|
+
const after = revertHermesYaml(fs.readFileSync(state.file, 'utf8'), state.previous);
|
|
159
|
+
if (!opts.dryRun) {
|
|
160
|
+
fs.writeFileSync(state.file, after);
|
|
161
|
+
forget();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
119
164
|
else {
|
|
120
|
-
const
|
|
121
|
-
if (!state)
|
|
122
|
-
return fail(`No recorded ${agent.name} setup to undo`);
|
|
123
|
-
const doc = readJson(state.file);
|
|
165
|
+
const doc = readJson(state.file, agent.kind === 'json5');
|
|
124
166
|
const after = JSON.stringify(revertJson(doc, state.previous), null, 2) + '\n';
|
|
125
167
|
if (!opts.dryRun) {
|
|
126
168
|
fs.writeFileSync(state.file, after);
|
|
127
|
-
|
|
128
|
-
delete rest[agent.id];
|
|
129
|
-
userConfig.set('setup', rest);
|
|
169
|
+
forget();
|
|
130
170
|
}
|
|
131
171
|
}
|
|
132
172
|
}
|
|
@@ -149,16 +189,33 @@ export function registerSetup(program) {
|
|
|
149
189
|
catch (err) {
|
|
150
190
|
return fail(err.message);
|
|
151
191
|
}
|
|
152
|
-
let next;
|
|
192
|
+
let next = '';
|
|
153
193
|
let previous = [];
|
|
154
194
|
try {
|
|
155
195
|
if (agent.kind === 'toml') {
|
|
156
196
|
next = applyCodexToml(fs.existsSync(agent.file) ? fs.readFileSync(agent.file, 'utf8') : '', model);
|
|
157
197
|
}
|
|
198
|
+
else if (agent.kind === 'yaml') {
|
|
199
|
+
const r = applyHermesYaml(fs.existsSync(agent.file) ? fs.readFileSync(agent.file, 'utf8') : '', model);
|
|
200
|
+
next = r.text;
|
|
201
|
+
previous = r.previous;
|
|
202
|
+
}
|
|
203
|
+
else if (agent.kind === 'yamlscalars') {
|
|
204
|
+
const r = applyYamlScalars(fs.existsSync(agent.file) ? fs.readFileSync(agent.file, 'utf8') : '', miniAgentValues(model, apiKey));
|
|
205
|
+
next = r.text;
|
|
206
|
+
previous = r.previous;
|
|
207
|
+
}
|
|
208
|
+
else if (agent.kind === 'cline') {
|
|
209
|
+
previous = { existed: fs.existsSync(agent.file) };
|
|
210
|
+
if (!onPath('cline'))
|
|
211
|
+
throw new Error('Cline CLI not found. Install it with `npm i -g cline`. (The VS Code extension has no config file; set it up in its settings UI.)');
|
|
212
|
+
}
|
|
158
213
|
else {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
214
|
+
const base = readJson(agent.file, agent.kind === 'json5');
|
|
215
|
+
const edits = agent.id === 'claude' ? claudeEdits(model, apiKey) : agent.id === 'openclaw' ? openclawEdits(model, base) : agent.id === 'qwen' ? qwenEdits(model) : opencodeEdits(model);
|
|
216
|
+
const r = applyJson(base, edits);
|
|
217
|
+
previous = r.previous;
|
|
218
|
+
next = JSON.stringify(r.doc, null, 2) + '\n';
|
|
162
219
|
}
|
|
163
220
|
}
|
|
164
221
|
catch (err) {
|
|
@@ -180,8 +237,21 @@ export function registerSetup(program) {
|
|
|
180
237
|
if (!(await confirm({ message: 'Continue?', default: false })))
|
|
181
238
|
return fail('Cancelled; nothing written.');
|
|
182
239
|
}
|
|
183
|
-
|
|
184
|
-
|
|
240
|
+
if (agent.kind === 'cline') {
|
|
241
|
+
const bak = `${agent.file}.wavespeed.bak`;
|
|
242
|
+
if (fs.existsSync(agent.file) && !fs.existsSync(bak))
|
|
243
|
+
fs.copyFileSync(agent.file, bak);
|
|
244
|
+
const r = spawnSync('cline', ['auth', '-p', 'openai-compatible', '-k', apiKey, '-b', LLM_BASE_URL, '-m', model], { stdio: ['ignore', 'ignore', 'pipe'], encoding: 'utf8', shell: process.platform === 'win32' });
|
|
245
|
+
if (r.status !== 0)
|
|
246
|
+
return fail(`cline auth failed: ${(r.stderr || r.error?.message || '').trim().slice(0, 300)}`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const hadComments = agent.kind === 'json5' && exists && /^\s*\/\/|\/\*/m.test(fs.readFileSync(agent.file, 'utf8'));
|
|
250
|
+
writeFile(agent.file, next, agent.keyInFile);
|
|
251
|
+
if (hadComments)
|
|
252
|
+
info(chalk.yellow(`Comments in ${shown(agent.file)} were not kept; the original is saved as ${shown(agent.file)}.wavespeed.bak`));
|
|
253
|
+
}
|
|
254
|
+
if (agent.kind !== 'toml')
|
|
185
255
|
userConfig.set('setup', { ...(userConfig.get('setup') ?? {}), [agent.id]: { file: agent.file, previous } });
|
|
186
256
|
const check = opts.check ? await selfCheck(apiKey, model) : null;
|
|
187
257
|
if (opts.json) {
|
package/dist/lib/setup-agents.js
CHANGED
|
@@ -12,6 +12,7 @@ export function validateModel(model) {
|
|
|
12
12
|
}
|
|
13
13
|
return model;
|
|
14
14
|
}
|
|
15
|
+
import { parseDocument } from 'yaml';
|
|
15
16
|
function isObj(v) {
|
|
16
17
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
17
18
|
}
|
|
@@ -86,6 +87,238 @@ export function opencodeEdits(model) {
|
|
|
86
87
|
{ path: ['model'], value: `wavespeed/${m}` },
|
|
87
88
|
];
|
|
88
89
|
}
|
|
90
|
+
export function openclawEdits(model, doc) {
|
|
91
|
+
const m = validateModel(model);
|
|
92
|
+
const edits = [
|
|
93
|
+
{ path: ['agents', 'defaults', 'model', 'primary'], value: `wavespeed/${m}` },
|
|
94
|
+
{ path: ['models', 'mode'], value: 'merge' },
|
|
95
|
+
{
|
|
96
|
+
path: ['models', 'providers', 'wavespeed'],
|
|
97
|
+
value: {
|
|
98
|
+
baseUrl: LLM_BASE_URL,
|
|
99
|
+
apiKey: `\${${KEY_ENV}}`,
|
|
100
|
+
api: 'openai-completions',
|
|
101
|
+
models: [{ id: m, name: m, contextWindow: 200000, maxTokens: 32000 }],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
// A user-maintained model allowlist would otherwise reject the new model.
|
|
106
|
+
const agents = doc.agents;
|
|
107
|
+
const defaults = agents?.defaults;
|
|
108
|
+
if (isObj(defaults?.models))
|
|
109
|
+
edits.push({ path: ['agents', 'defaults', 'models', `wavespeed/${m}`], value: {} });
|
|
110
|
+
return edits;
|
|
111
|
+
}
|
|
112
|
+
export function qwenEdits(model) {
|
|
113
|
+
const m = validateModel(model);
|
|
114
|
+
return [
|
|
115
|
+
{ path: ['modelProviders', 'wavespeed'], value: [{ id: m, name: m, envKey: KEY_ENV, baseUrl: LLM_BASE_URL }] },
|
|
116
|
+
{ path: ['providerProtocol', 'wavespeed'], value: 'openai' },
|
|
117
|
+
{ path: ['security', 'auth', 'selectedType'], value: 'openai' },
|
|
118
|
+
{ path: ['model', 'name'], value: m },
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
// ---------- Mini-Agent (flat YAML scalars; the key lives in the file) ----------
|
|
122
|
+
export function miniAgentValues(model, apiKey) {
|
|
123
|
+
return { api_key: apiKey, api_base: LLM_BASE_URL, model: validateModel(model), provider: 'openai' };
|
|
124
|
+
}
|
|
125
|
+
/** Set top-level `key: "value"` pairs in place. Returns the raw text each key held before (null = absent). */
|
|
126
|
+
export function applyYamlScalars(input, values) {
|
|
127
|
+
let text = input;
|
|
128
|
+
if (text !== '' && !text.endsWith('\n'))
|
|
129
|
+
text += '\n';
|
|
130
|
+
const doc = parseDocument(text);
|
|
131
|
+
if (doc.errors.length)
|
|
132
|
+
throw new Error(`config.yaml is not valid YAML: ${doc.errors[0].message}`);
|
|
133
|
+
const root = doc.contents;
|
|
134
|
+
// null (not undefined) marks "absent": the state is stored as JSON, which drops undefined values.
|
|
135
|
+
const previous = {};
|
|
136
|
+
const edits = [];
|
|
137
|
+
let tail = '';
|
|
138
|
+
for (const [key, value] of Object.entries(values)) {
|
|
139
|
+
const node = root?.get?.(key, true);
|
|
140
|
+
const quoted = JSON.stringify(value);
|
|
141
|
+
if (node?.range) {
|
|
142
|
+
previous[key] = text.slice(node.range[0], node.range[1]);
|
|
143
|
+
edits.push({ start: node.range[0], end: node.range[1], text: quoted });
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
previous[key] = null;
|
|
147
|
+
tail += `${key}: ${quoted}\n`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { text: [...edits].sort((a, b) => b.start - a.start).reduce((t, e) => t.slice(0, e.start) + e.text + t.slice(e.end), text) + tail, previous };
|
|
151
|
+
}
|
|
152
|
+
export function revertYamlScalars(input, previous) {
|
|
153
|
+
let text = input;
|
|
154
|
+
const doc = parseDocument(text);
|
|
155
|
+
const root = doc.contents;
|
|
156
|
+
const edits = [];
|
|
157
|
+
for (const [key, raw] of Object.entries(previous)) {
|
|
158
|
+
const node = root?.get?.(key, true);
|
|
159
|
+
if (!node?.range)
|
|
160
|
+
continue;
|
|
161
|
+
if (raw !== null) {
|
|
162
|
+
edits.push({ start: node.range[0], end: node.range[1], text: raw });
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const pair = root.items.find((p) => p.key?.value === key);
|
|
166
|
+
const ks = pair.key.range[0];
|
|
167
|
+
edits.push({ start: lineStart(text, ks), end: lineEnd(text, node.range[1]), text: '' });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return [...edits].sort((a, b) => b.start - a.start).reduce((t, e) => t.slice(0, e.start) + e.text + t.slice(e.end), text);
|
|
171
|
+
}
|
|
172
|
+
const lineStart = (t, off) => t.lastIndexOf('\n', off - 1) + 1;
|
|
173
|
+
const lineEnd = (t, off) => {
|
|
174
|
+
const i = t.indexOf('\n', off);
|
|
175
|
+
return i < 0 ? t.length : i + 1;
|
|
176
|
+
};
|
|
177
|
+
/** End of the line holding the last real character before `off` (a node's range can run over trailing blank lines). */
|
|
178
|
+
const contentLineEnd = (t, off) => lineEnd(t, t.slice(0, off).trimEnd().length - 1);
|
|
179
|
+
function applyEdits(text, edits) {
|
|
180
|
+
return [...edits].sort((x, y) => y.start - x.start).reduce((t, e) => t.slice(0, e.start) + e.text + t.slice(e.end), text);
|
|
181
|
+
}
|
|
182
|
+
function entryLines(pad) {
|
|
183
|
+
return [
|
|
184
|
+
`${pad}- name: wavespeed`,
|
|
185
|
+
`${pad} base_url: ${LLM_BASE_URL}`,
|
|
186
|
+
`${pad} key_env: ${KEY_ENV}`,
|
|
187
|
+
`${pad} api_mode: chat_completions`,
|
|
188
|
+
'',
|
|
189
|
+
].join('\n');
|
|
190
|
+
}
|
|
191
|
+
function dashPad(text, itemStart) {
|
|
192
|
+
const dash = text.lastIndexOf('-', itemStart - 1);
|
|
193
|
+
return { dash, pad: ' '.repeat(dash - lineStart(text, dash)) };
|
|
194
|
+
}
|
|
195
|
+
function parseYaml(text) {
|
|
196
|
+
const doc = parseDocument(text);
|
|
197
|
+
if (doc.errors.length)
|
|
198
|
+
throw new Error(`config.yaml is not valid YAML: ${doc.errors[0].message}`);
|
|
199
|
+
return doc;
|
|
200
|
+
}
|
|
201
|
+
export function applyHermesYaml(input, model) {
|
|
202
|
+
const m = validateModel(model);
|
|
203
|
+
let text = input;
|
|
204
|
+
if (text !== '' && !text.endsWith('\n'))
|
|
205
|
+
text += '\n';
|
|
206
|
+
const doc = parseYaml(text);
|
|
207
|
+
const edits = [];
|
|
208
|
+
let tail = '';
|
|
209
|
+
const previous = { hadModelBlock: false, hadList: false };
|
|
210
|
+
const want = { default: JSON.stringify(m), provider: '"wavespeed"' };
|
|
211
|
+
const root = doc.contents;
|
|
212
|
+
const modelNode = root?.get?.('model', true);
|
|
213
|
+
if (modelNode === undefined || modelNode === null) {
|
|
214
|
+
tail += `model:\n default: ${want.default}\n provider: ${want.provider}\n`;
|
|
215
|
+
}
|
|
216
|
+
else if (modelNode.items) {
|
|
217
|
+
previous.hadModelBlock = true;
|
|
218
|
+
const missing = [];
|
|
219
|
+
for (const key of ['default', 'provider']) {
|
|
220
|
+
const v = modelNode.get?.(key, true);
|
|
221
|
+
if (v && v.range) {
|
|
222
|
+
const raw = text.slice(v.range[0], v.range[1]);
|
|
223
|
+
if (key === 'default')
|
|
224
|
+
previous.defaultRaw = raw;
|
|
225
|
+
else
|
|
226
|
+
previous.providerRaw = raw;
|
|
227
|
+
edits.push({ start: v.range[0], end: v.range[1], text: want[key] });
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
missing.push(key);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (missing.length) {
|
|
234
|
+
const first = modelNode.items[0]?.key?.range?.[0];
|
|
235
|
+
if (first === undefined)
|
|
236
|
+
throw new Error('model: block in config.yaml is empty or inline; edit it by hand');
|
|
237
|
+
const pad = ' '.repeat(first - lineStart(text, first));
|
|
238
|
+
edits.push({ start: lineStart(text, first), end: lineStart(text, first), text: missing.map((k) => `${pad}${k}: ${want[k]}\n`).join('') });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
throw new Error('model: in config.yaml is not a block; edit it by hand');
|
|
243
|
+
}
|
|
244
|
+
const list = root?.get?.('custom_providers', true);
|
|
245
|
+
if (list === undefined || list === null) {
|
|
246
|
+
tail += `custom_providers:\n${entryLines(' ')}`;
|
|
247
|
+
}
|
|
248
|
+
else if (list.items && list.flow !== true) {
|
|
249
|
+
previous.hadList = true;
|
|
250
|
+
const mine = list.items.find((it) => it?.get?.('name') === 'wavespeed');
|
|
251
|
+
if (mine?.range) {
|
|
252
|
+
const { dash, pad } = dashPad(text, mine.range[0]);
|
|
253
|
+
const end = contentLineEnd(text, mine.range[1]);
|
|
254
|
+
previous.entryRaw = text.slice(lineStart(text, dash), end);
|
|
255
|
+
edits.push({ start: lineStart(text, dash), end, text: entryLines(pad) });
|
|
256
|
+
}
|
|
257
|
+
else if (list.items.length > 0) {
|
|
258
|
+
const last = list.items[list.items.length - 1];
|
|
259
|
+
const { pad } = dashPad(text, list.items[0].range[0]);
|
|
260
|
+
const at = contentLineEnd(text, last.range[1]);
|
|
261
|
+
edits.push({ start: at, end: at, text: entryLines(pad) });
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
throw new Error('custom_providers: in config.yaml is empty; edit it by hand');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else if (list.range && list.value === null) {
|
|
268
|
+
// `custom_providers:` with nothing under it
|
|
269
|
+
previous.hadList = false;
|
|
270
|
+
const at = lineEnd(text, list.range[0]);
|
|
271
|
+
edits.push({ start: at, end: at, text: entryLines(' ') });
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
throw new Error('custom_providers: in config.yaml is not a block list; edit it by hand');
|
|
275
|
+
}
|
|
276
|
+
return { text: applyEdits(text, edits) + tail, previous };
|
|
277
|
+
}
|
|
278
|
+
export function revertHermesYaml(input, previous) {
|
|
279
|
+
let text = input;
|
|
280
|
+
if (!previous.hadModelBlock) {
|
|
281
|
+
text = text.replace(/^model:\n {2}default: "[^\n]*"\n {2}provider: "wavespeed"\n/m, '');
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
const doc = parseYaml(text);
|
|
285
|
+
const modelNode = doc.contents?.get?.('model', true);
|
|
286
|
+
const edits = [];
|
|
287
|
+
for (const [key, raw] of [['default', previous.defaultRaw], ['provider', previous.providerRaw]]) {
|
|
288
|
+
const v = modelNode?.get?.(key, true);
|
|
289
|
+
if (!v?.range)
|
|
290
|
+
continue;
|
|
291
|
+
if (raw !== undefined) {
|
|
292
|
+
edits.push({ start: v.range[0], end: v.range[1], text: raw });
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
const pair = modelNode.items.find((p) => p.key?.value === key);
|
|
296
|
+
const ks = pair.key.range[0];
|
|
297
|
+
edits.push({ start: lineStart(text, ks), end: lineEnd(text, v.range[1]), text: '' });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
text = applyEdits(text, edits);
|
|
301
|
+
}
|
|
302
|
+
const doc = parseYaml(text);
|
|
303
|
+
const list = doc.contents?.get?.('custom_providers', true);
|
|
304
|
+
if (list?.items) {
|
|
305
|
+
const mine = list.items.find((it) => it?.get?.('name') === 'wavespeed');
|
|
306
|
+
if (mine?.range) {
|
|
307
|
+
const { dash } = dashPad(text, mine.range[0]);
|
|
308
|
+
const start = lineStart(text, dash);
|
|
309
|
+
const end = contentLineEnd(text, mine.range[1]);
|
|
310
|
+
text = text.slice(0, start) + (previous.entryRaw ?? '') + text.slice(end);
|
|
311
|
+
}
|
|
312
|
+
if (!previous.hadList) {
|
|
313
|
+
const again = parseYaml(text);
|
|
314
|
+
const l2 = again.contents?.get?.('custom_providers', true);
|
|
315
|
+
if (l2?.items?.length === 0 || (l2 && l2.items === undefined)) {
|
|
316
|
+
text = text.replace(/^custom_providers:[ \t]*\n?/m, '');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return text;
|
|
321
|
+
}
|
|
89
322
|
// ---------- Codex (TOML, edited as text so the user's comments survive) ----------
|
|
90
323
|
const TOP_OPEN = '# >>> wavespeed (managed by `wavespeed setup`) >>>';
|
|
91
324
|
const TOP_CLOSE = '# <<< wavespeed <<<';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wavespeed/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.18",
|
|
4
4
|
"description": "WaveSpeed CLI — one command to run any WaveSpeed AI model from your terminal.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -54,9 +54,11 @@
|
|
|
54
54
|
"clipboardy": "^4.0.0",
|
|
55
55
|
"commander": "^12.1.0",
|
|
56
56
|
"conf": "^13.0.0",
|
|
57
|
+
"json5": "^2.2.3",
|
|
57
58
|
"open": "^10.1.0",
|
|
58
59
|
"ora": "^8.1.0",
|
|
59
|
-
"wavespeed": "^0.3.4"
|
|
60
|
+
"wavespeed": "^0.3.4",
|
|
61
|
+
"yaml": "^2.9.1"
|
|
60
62
|
},
|
|
61
63
|
"devDependencies": {
|
|
62
64
|
"@types/node": "^20.14.0",
|