@wavespeed/cli 0.4.11 → 0.4.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 CHANGED
@@ -165,14 +165,10 @@ wavespeed init Create a wavespeed.json with defaults +
165
165
  wavespeed skill install Drop SKILL.md for coding agents
166
166
  ```
167
167
 
168
- **Pricing is an estimate.** `wavespeed price` quotes from the model's pricing
169
- formula, and many models bill from an input — audio/video duration, frame count,
170
- text length. Those inputs are optional in the quote: ask for a price without
171
- them and the formula collapses to the model's **base price**, which is the floor
172
- of its range, not a typical run. `wavespeed price wavespeed-ai/infinitetalk`
173
- says `$0.15`; a 60s audio actually bills `$1.80`. The command names the inputs it
174
- was blind to (`unpriced_inputs` / `at_base_price` in `--json`) — supply them for
175
- a real quote. Whatever it says, the amount actually charged for a run is
168
+ **Pricing is an estimate.** `wavespeed price` quotes the inputs you pass, and many
169
+ models bill from an input — audio/video duration, frame count, text length. Pass
170
+ the same inputs you would pass to `wavespeed run`: a quote made without them can
171
+ be lower than the real charge. The amount actually charged for a run is
176
172
  authoritative.
177
173
 
178
174
  ## Auth
@@ -1,7 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
- import { fetchPricing, fetchModels } from '../lib/api.js';
4
- import { missingPriceVars, isFloorQuote } from '../lib/pricing-vars.js';
3
+ import { fetchPricing, modelLlmsTxtUrl } from '../lib/api.js';
5
4
  import { parseInputs } from '../lib/inputs.js';
6
5
  import { resolveModelToken } from '../lib/config.js';
7
6
  import { collectLocalFiles, resolveLocalFiles } from '../lib/local-files.js';
@@ -74,33 +73,14 @@ export function registerPrice(program) {
74
73
  const spinner = !opts.json ? ora('Calculating price…').start() : null;
75
74
  try {
76
75
  const data = await fetchPricing(resolved.model, inputs);
77
- // /model/price evaluates the model's formula against whatever inputs we
78
- // send and never complains about the ones we left out — a formula with
79
- // every variable missing collapses to base_price. That is the floor of
80
- // the model's range, not a representative charge, so name the inputs
81
- // this quote could not see rather than presenting the floor as "the"
82
- // price. Catalog is cached 1h; a lookup failure only costs the detail.
83
- let unpriced = [];
84
- let atFloor = false;
85
- try {
86
- const { models } = await fetchModels();
87
- const meta = models.find((m) => m.model_id === resolved.model);
88
- const props = meta?.api_schema?.api_schemas?.[0]?.request_schema?.properties;
89
- unpriced = missingPriceVars(meta?.formula, inputs, props);
90
- atFloor = isFloorQuote(meta?.formula, inputs);
91
- }
92
- catch {
93
- /* no catalog — fall back to the generic disclaimer below */
94
- }
95
76
  spinner?.stop();
96
77
  const disclaimer = 'Estimate only, for reference — the amount actually charged for a run is authoritative.';
97
78
  if (opts.json) {
98
79
  process.stdout.write(JSON.stringify({
99
80
  ...data,
100
81
  estimate: true,
101
- unpriced_inputs: unpriced,
102
- at_base_price: atFloor,
103
82
  disclaimer,
83
+ llms_txt: modelLlmsTxtUrl(resolved.model),
104
84
  uploaded: uploadedUrls,
105
85
  }, null, 2) + '\n');
106
86
  return;
@@ -113,19 +93,6 @@ export function registerPrice(program) {
113
93
  (data.discounted_price > 0 && data.discounted_price !== data.price
114
94
  ? chalk.gray(` (list $${data.price})`)
115
95
  : ''));
116
- if (unpriced.length > 0) {
117
- console.log(' ' +
118
- chalk.yellow('note: ') +
119
- chalk.yellow(`this model is priced from ${unpriced.join(', ')}, which ${unpriced.length === 1 ? 'was' : 'were'} not supplied.`));
120
- console.log(' ' +
121
- chalk.gray(atFloor
122
- ? 'The figure above is the base price — the floor of this model’s range, not a typical run.'
123
- : 'The figure above prices the rest at their defaults and can move once these are set.'));
124
- console.log(' ' +
125
- chalk.gray('Pass ') +
126
- chalk.cyan(unpriced.map((v) => `-i ${v}=…`).join(' ')) +
127
- chalk.gray(' for a representative quote.'));
128
- }
129
96
  console.log();
130
97
  for (const [file, url] of Object.entries(uploadedUrls)) {
131
98
  console.log(' ' + chalk.gray('uploaded: ') + chalk.gray(file + ' → ') + chalk.cyan(url));
@@ -135,6 +102,7 @@ export function registerPrice(program) {
135
102
  }
136
103
  console.log();
137
104
  console.log(chalk.gray('Run it: ') + chalk.cyan(`wavespeed run ${modelArg}` + (opts.prompt ? ` -p "${opts.prompt}"` : '')));
105
+ console.log(chalk.gray('Model inputs: ') + chalk.cyan(modelLlmsTxtUrl(resolved.model)));
138
106
  console.log(chalk.gray(disclaimer));
139
107
  console.log();
140
108
  }
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
- import { fetchModels } from '../lib/api.js';
3
+ import { fetchModels, modelLlmsTxtUrl } from '../lib/api.js';
4
4
  function getRequestSchema(model) {
5
5
  return model.api_schema?.api_schemas?.[0]?.request_schema;
6
6
  }
@@ -116,6 +116,7 @@ export function registerSchema(program) {
116
116
  chalk.white(model.type ?? 'unknown') +
117
117
  (model.base_price !== undefined ? chalk.gray(' base price: ') + chalk.green(`$${model.base_price}`) : ''));
118
118
  console.log(chalk.gray(' page: ') + chalk.cyan(`https://wavespeed.ai/models/${model.model_id}`));
119
+ console.log(chalk.gray(' llms: ') + chalk.cyan(modelLlmsTxtUrl(model.model_id)));
119
120
  if (model.description) {
120
121
  console.log(wrap(model.description, ' '));
121
122
  }
package/dist/lib/api.js CHANGED
@@ -130,6 +130,10 @@ async function apiPost(path, body, opts = {}) {
130
130
  export async function fetchBalance() {
131
131
  return apiGet('/api/v3/balance');
132
132
  }
133
+ // The model page's markdown summary for agents (inputs, endpoint, examples).
134
+ export function modelLlmsTxtUrl(modelId) {
135
+ return `https://wavespeed.ai/models/${modelId}/llms.txt`;
136
+ }
133
137
  export async function fetchPricing(modelId, inputs) {
134
138
  // /model/price is the documented pricing endpoint; /model/pricing is a
135
139
  // legacy contract kept for old clients and absent from the public docs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wavespeed/cli",
3
- "version": "0.4.11",
3
+ "version": "0.4.12",
4
4
  "description": "WaveSpeed CLI — one command to run any WaveSpeed AI model from your terminal.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,75 +0,0 @@
1
- // Pricing formulas are JSONata expressions returned by /api/v3/models, e.g.
2
- // {"total_price": $max([5, $min([$ceil(get_duration_v3(audio)), 600])]) / 5 * base_price}
3
- // {"total_price": base_price * duration / 5}
4
- //
5
- // The pricing endpoint evaluates them server-side and happily accepts a partial
6
- // (or empty) `inputs` object — a formula whose variables are all missing
7
- // collapses to `base_price`, which is the floor of the model's price range, not
8
- // a typical charge. `wavespeed price wavespeed-ai/infinitetalk` with no inputs
9
- // quotes $0.15 while a 60s audio actually costs $1.80. So we parse the formula
10
- // to find which inputs move the price, and say which ones the quote is blind to.
11
- const RESERVED = new Set(['base_price', 'total_price', 'true', 'false', 'null']);
12
- /**
13
- * Variable names a pricing formula reads from the model's inputs, in first-seen
14
- * order. Excludes JSONata builtins (`$max`), helper calls (`get_duration_v3`),
15
- * string literals, and `base_price`/`total_price`.
16
- *
17
- * Returns [] for a flat-priced model (`base_price * 1`) or an unparseable
18
- * formula — callers should treat [] as "nothing input-dependent to warn about".
19
- */
20
- export function extractPriceVars(formula) {
21
- if (!formula)
22
- return [];
23
- // Drop string literals first so "720p" / "total_price" keys never register.
24
- const stripped = formula.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
25
- const vars = [];
26
- const seen = new Set();
27
- const re = /(\$?)([A-Za-z_][A-Za-z0-9_]*)\s*(\()?/g;
28
- let m;
29
- while ((m = re.exec(stripped)) !== null) {
30
- const [, dollar, name, call] = m;
31
- if (dollar)
32
- continue; // $max, $ceil, $length — JSONata builtin
33
- if (call)
34
- continue; // get_duration_v3(...) — server-side helper
35
- if (RESERVED.has(name))
36
- continue;
37
- if (seen.has(name))
38
- continue;
39
- seen.add(name);
40
- vars.push(name);
41
- }
42
- return vars;
43
- }
44
- /**
45
- * Price-driving variables the quote is genuinely blind to.
46
- *
47
- * A driver the caller omitted but that carries a schema default is NOT blind —
48
- * the server priced it at that default, which is exactly what a run would use.
49
- * Only drivers with no default (infinitetalk's `audio`, seedance's
50
- * `reference_videos`) leave the quote uninformed, and those are the ones that
51
- * silently collapse the formula toward base_price.
52
- */
53
- export function missingPriceVars(formula, inputs, schemaProps) {
54
- return extractPriceVars(formula).filter((v) => {
55
- const val = inputs[v];
56
- if (val !== undefined && val !== null && val !== '')
57
- return false;
58
- const prop = schemaProps?.[v];
59
- return !(prop && 'default' in prop);
60
- });
61
- }
62
- /**
63
- * True when every price driver is unsupplied — the case where the formula
64
- * degenerates to `base_price` and the quote is the floor of the model's range
65
- * rather than a representative charge.
66
- */
67
- export function isFloorQuote(formula, inputs) {
68
- const vars = extractPriceVars(formula);
69
- if (vars.length === 0)
70
- return false;
71
- return vars.every((v) => {
72
- const val = inputs[v];
73
- return val === undefined || val === null || val === '';
74
- });
75
- }