@wavespeed/cli 0.4.28 → 0.4.30

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 CHANGED
@@ -137,7 +137,7 @@ The skill teaches the agent the shortcuts for images and videos (`generate`, `ed
137
137
 
138
138
  **OpenCode** loads the same file with no extra steps — `.claude/skills/` is on its skill search path. For the MCP tools as well, add the plugin to your OpenCode config — `"plugin": ["@wavespeed/opencode"]` — and run `opencode mcp auth wavespeed` ([WaveSpeedAI/wavespeed-opencode](https://github.com/WaveSpeedAI/wavespeed-opencode)).
139
139
 
140
- **Codex** reads `.agents/skills/`, which the same command writes. For a one-line install that also wires up the MCP server: `codex plugin marketplace add WaveSpeedAI/codex-plugin-wavespeed-cli` ([WaveSpeedAI/codex-plugin-wavespeed-cli](https://github.com/WaveSpeedAI/codex-plugin-wavespeed-cli)).
140
+ **Codex** reads `.agents/skills/`, which the same command writes. For a one-line install that also wires up the MCP server: `codex plugin marketplace add WaveSpeedAI/codex-plugins` ([WaveSpeedAI/codex-plugins](https://github.com/WaveSpeedAI/codex-plugins)).
141
141
 
142
142
  **DeepSeek Harness (dsh)**: the same skill is packaged for dsh's `.dsh/skills` layout at [WaveSpeedAI/wavespeed-dsh-skill](https://github.com/WaveSpeedAI/wavespeed-dsh-skill).
143
143
 
@@ -190,7 +190,8 @@ wavespeed skill install Drop SKILL.md for coding agents
190
190
  models bill from an input — audio/video duration, frame count, text length. Pass
191
191
  the same inputs you would pass to `wavespeed run`: a quote made without them can
192
192
  be lower than the real charge. The amount actually charged for a run is
193
- authoritative.
193
+ authoritative. Like `run`, `price` refuses an input the model does not accept (a typo would
194
+ otherwise be ignored by the quote); `--no-validate` skips the check.
194
195
 
195
196
  ## Auth
196
197
 
@@ -2,7 +2,7 @@ import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { fetchPricing, modelPlaygroundUrl } from '../lib/api.js';
4
4
  import { parseInputs } from '../lib/inputs.js';
5
- import { modelInputProps } from '../lib/validate-inputs.js';
5
+ import { modelInputProps, unknownInputsFor } from '../lib/validate-inputs.js';
6
6
  import { resolveModelToken } from '../lib/config.js';
7
7
  import { collectLocalFiles, resolveLocalFiles } from '../lib/local-files.js';
8
8
  import { uploadWithCache } from '../lib/upload-cache.js';
@@ -15,6 +15,7 @@ export function registerPrice(program) {
15
15
  .option('-i, --input <pair...>', 'Inputs as key=value (same syntax as `wavespeed run`)', [])
16
16
  .option('-p, --prompt <text>', 'Shorthand for --input prompt=<text>')
17
17
  .option('--upload', 'Upload @file inputs first so media-based pricing is exact (quota applies; identical bytes cached 24h)')
18
+ .option('--no-validate', 'Skip checking the inputs against the model schema')
18
19
  .option('--json', 'Emit JSON')
19
20
  .action(async (modelArg, opts) => {
20
21
  const resolved = resolveModelToken(modelArg);
@@ -30,6 +31,27 @@ export function registerPrice(program) {
30
31
  Object.assign(inputs, parseInputs(opts.input ?? []));
31
32
  if (opts.prompt)
32
33
  inputs.prompt = opts.prompt;
34
+ // A key the model does not declare is dropped by the pricer, so a typo would quote a price that ignores it.
35
+ if (opts.validate !== false) {
36
+ const report = await unknownInputsFor(resolved.model, inputs);
37
+ if (report) {
38
+ const msg = `Model ${resolved.model} does not accept: ${report.unknown.join(', ')}. ` +
39
+ `Unknown inputs are ignored, so this quote would not account for them.`;
40
+ if (opts.json) {
41
+ process.stdout.write(JSON.stringify({ error: msg, unknown_inputs: report.unknown, accepted_inputs: report.known }, null, 2) + '\n');
42
+ }
43
+ else {
44
+ console.error(chalk.red('Error: ') + `unknown input${report.unknown.length > 1 ? 's' : ''} for ` + chalk.cyan(resolved.model) + ':');
45
+ for (const k of report.unknown) {
46
+ const hint = report.suggestions.get(k);
47
+ console.error(chalk.yellow(` ${k}` + (hint ? ` (did you mean \`${hint}\`?)` : '')));
48
+ }
49
+ console.error(chalk.gray(' Accepted inputs: ') + report.known.join(', '));
50
+ console.error(chalk.gray(' Bypass check: ') + chalk.cyan('--no-validate'));
51
+ }
52
+ process.exit(1);
53
+ }
54
+ }
33
55
  // Pricing that depends on media (e.g. per-second video upscaling) is
34
56
  // measured server-side from the URL, so a local path is useless to the
35
57
  // pricing engine. Uploading is slow and quota-limited, so it is opt-in:
@@ -5,8 +5,8 @@ import fs from 'node:fs';
5
5
  import { requireClient } from '../lib/client.js';
6
6
  import { parseInputs, withInputSyntaxHint } from '../lib/inputs.js';
7
7
  import { resolveLocalFiles } from '../lib/local-files.js';
8
- import { fetchModels, submitPrediction, waitForPrediction } from '../lib/api.js';
9
- import { findUnknownInputs, modelInputProps } from '../lib/validate-inputs.js';
8
+ import { submitPrediction, waitForPrediction } from '../lib/api.js';
9
+ import { modelInputProps, unknownInputsFor } from '../lib/validate-inputs.js';
10
10
  import { uploadWithCache } from '../lib/upload-cache.js';
11
11
  import { downloadOutputs, printOutputs } from '../lib/output.js';
12
12
  import { emitJson, emitJsonError, isJsonMode, log, setJsonMode } from '../lib/log.js';
@@ -106,41 +106,27 @@ export function registerRun(program) {
106
106
  // avoid stale-cache false positives, re-fetches the catalog once
107
107
  // before rejecting.
108
108
  if (opts.validate !== false) {
109
- try {
110
- let { models } = await fetchModels();
111
- let found = models.find((m) => m.model_id === model);
112
- let report = findUnknownInputs(input, found);
113
- if (report) {
114
- ({ models } = await fetchModels({ refresh: true }));
115
- found = models.find((m) => m.model_id === model);
116
- report = findUnknownInputs(input, found);
109
+ const report = await unknownInputsFor(model, input);
110
+ if (report) {
111
+ const lines = report.unknown.map((k) => {
112
+ const hint = report.suggestions.get(k);
113
+ return ` ${k}` + (hint ? ` (did you mean \`${hint}\`?)` : '');
114
+ });
115
+ const msg = `Model ${model} does not accept: ${report.unknown.join(', ')}. ` +
116
+ `The API silently drops unknown inputs, so this would bill a run that ignores them.`;
117
+ if (isJsonMode()) {
118
+ emitJsonError(msg, { unknown_inputs: report.unknown, accepted_inputs: report.known });
117
119
  }
118
- if (report) {
119
- const lines = report.unknown.map((k) => {
120
- const hint = report.suggestions.get(k);
121
- return ` ${k}` + (hint ? ` (did you mean \`${hint}\`?)` : '');
122
- });
123
- const msg = `Model ${model} does not accept: ${report.unknown.join(', ')}. ` +
124
- `The API silently drops unknown inputs, so this would bill a run that ignores them.`;
125
- if (isJsonMode()) {
126
- emitJsonError(msg, { unknown_inputs: report.unknown, accepted_inputs: report.known });
127
- }
128
- else {
129
- console.error(chalk.red('Error: ') + `unknown input${report.unknown.length > 1 ? 's' : ''} for ` + chalk.cyan(model) + ':');
130
- for (const l of lines)
131
- console.error(chalk.yellow(l));
132
- console.error(chalk.gray(' The API silently drops unknown inputs — the run would be billed with them ignored.'));
133
- console.error(chalk.gray(' Accepted inputs: ') + report.known.join(', '));
134
- console.error(chalk.gray(' Full schema: ') + chalk.cyan(`wavespeed schema ${model}`));
135
- console.error(chalk.gray(' Bypass check: ') + chalk.cyan('--no-validate'));
136
- }
137
- process.exit(1);
120
+ else {
121
+ console.error(chalk.red('Error: ') + `unknown input${report.unknown.length > 1 ? 's' : ''} for ` + chalk.cyan(model) + ':');
122
+ for (const l of lines)
123
+ console.error(chalk.yellow(l));
124
+ console.error(chalk.gray(' The API silently drops unknown inputs — the run would be billed with them ignored.'));
125
+ console.error(chalk.gray(' Accepted inputs: ') + report.known.join(', '));
126
+ console.error(chalk.gray(' Full schema: ') + chalk.cyan(`wavespeed schema ${model}`));
127
+ console.error(chalk.gray(' Bypass check: ') + chalk.cyan('--no-validate'));
138
128
  }
139
- }
140
- catch {
141
- // Catalog unreachable — validation is best-effort, never a blocker.
142
- // (The unknown-input rejection above exits the process directly and
143
- // is not routed through here.)
129
+ process.exit(1);
144
130
  }
145
131
  }
146
132
  // `@path` inputs become hosted URLs before submission. Only the
package/dist/lib/api.js CHANGED
@@ -150,7 +150,7 @@ export function modelPlaygroundUrl(modelId) {
150
150
  export async function fetchPricing(modelId, inputs) {
151
151
  // /model/price is the documented pricing endpoint; /model/pricing is a
152
152
  // legacy contract kept for old clients and absent from the public docs.
153
- return apiPost('/api/v3/model/price', { model_id: modelId, inputs });
153
+ return apiPost('/api/v3/model/price', { model_id: modelId, input: inputs });
154
154
  }
155
155
  /** The run time in ms the API reports (`timings.inference`), or undefined when absent or zero. */
156
156
  export function inferenceMs(item) {
@@ -109,3 +109,22 @@ export function findUnknownInputs(input, model) {
109
109
  }
110
110
  return { unknown, known, suggestions };
111
111
  }
112
+ /**
113
+ * Inputs the model's schema does not declare, checked against the live catalog. The API silently drops unknown
114
+ * keys, so a typo bills a run (or quotes a price) that ignores it. Fails open: null when the catalog has no schema
115
+ * or cannot be reached; re-fetches once before reporting, so a stale cache cannot block a valid input.
116
+ */
117
+ export async function unknownInputsFor(model, input) {
118
+ try {
119
+ let { models } = await fetchModels();
120
+ let report = findUnknownInputs(input, models.find((m) => m.model_id === model));
121
+ if (report) {
122
+ ({ models } = await fetchModels({ refresh: true }));
123
+ report = findUnknownInputs(input, models.find((m) => m.model_id === model));
124
+ }
125
+ return report;
126
+ }
127
+ catch {
128
+ return null;
129
+ }
130
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wavespeed/cli",
3
- "version": "0.4.28",
3
+ "version": "0.4.30",
4
4
  "description": "WaveSpeed CLI — one command to run any WaveSpeed AI model from your terminal.",
5
5
  "publishConfig": {
6
6
  "access": "public"