@wavespeed/cli 0.4.1 → 0.4.3
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 +28 -9
- package/dist/cli.js +1 -1
- package/dist/commands/aliases.js +1 -1
- package/dist/commands/auth.js +3 -2
- package/dist/commands/init.js +1 -1
- package/dist/commands/price.js +43 -2
- package/dist/lib/api.js +3 -2
- package/dist/lib/client-headers.js +40 -0
- package/dist/lib/client.js +3 -2
- package/dist/lib/pricing-vars.js +75 -0
- package/package.json +3 -3
- package/skills/SKILL.md +11 -9
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<a href="https://wavespeed.ai" target="_blank">
|
|
3
|
-
<
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/wavespeed-logo-dark.svg">
|
|
5
|
+
<img src="assets/wavespeed-logo-light.svg" alt="WaveSpeed" width="342" height="48"/>
|
|
6
|
+
</picture>
|
|
4
7
|
</a>
|
|
5
8
|
|
|
6
9
|
<h1>WaveSpeed CLI</h1>
|
|
@@ -74,14 +77,14 @@ wavespeed run ... -p "..." --json | jq '.outputs[0]'
|
|
|
74
77
|
|
|
75
78
|
```jsonc
|
|
76
79
|
{
|
|
77
|
-
"defaultModel": "
|
|
80
|
+
"defaultModel": "bytedance/seedream-v5.0-pro",
|
|
78
81
|
"outputDir": "wavespeed-output",
|
|
79
82
|
"aliases": {
|
|
80
|
-
"hero": { "model": "
|
|
83
|
+
"hero": { "model": "bytedance/seedream-v5.0-pro",
|
|
81
84
|
"input": { "aspect_ratio": "16:9", "resolution": "2k" } },
|
|
82
|
-
"social": { "model": "
|
|
85
|
+
"social": { "model": "bytedance/seedream-v5.0-pro",
|
|
83
86
|
"input": { "aspect_ratio": "1:1", "resolution": "1k" } },
|
|
84
|
-
"vid": { "model": "bytedance/seedance-2.
|
|
87
|
+
"vid": { "model": "bytedance/seedance-2.5/text-to-video",
|
|
85
88
|
"input": { "resolution": "720p", "duration": 5 } }
|
|
86
89
|
}
|
|
87
90
|
}
|
|
@@ -98,10 +101,12 @@ Resolution: positional with `/` is a model ID; otherwise looked up as an alias.
|
|
|
98
101
|
|
|
99
102
|
| Use case | Model |
|
|
100
103
|
|---|---|
|
|
101
|
-
| Text → image | `
|
|
102
|
-
| Image edit | `
|
|
103
|
-
| Text → video | `bytedance/seedance-2.
|
|
104
|
-
| Image → video | `bytedance/seedance-2.
|
|
104
|
+
| Text → image | `bytedance/seedream-v5.0-pro` |
|
|
105
|
+
| Image edit | `bytedance/seedream-v5.0-pro/edit` |
|
|
106
|
+
| Text → video | `bytedance/seedance-2.5/text-to-video` |
|
|
107
|
+
| Image → video | `bytedance/seedance-2.5/image-to-video` |
|
|
108
|
+
| Video edit | `bytedance/seedance-2.5/video-edit` |
|
|
109
|
+
| Video extend | `bytedance/seedance-2.5/video-extend` |
|
|
105
110
|
|
|
106
111
|
Browse alternatives with `wavespeed models <query>`.
|
|
107
112
|
|
|
@@ -115,6 +120,10 @@ wavespeed skill install # writes .claude/skills/wavespeed/SKILL.md
|
|
|
115
120
|
|
|
116
121
|
The skill teaches the agent the three-step pattern: `models` to find, `run <id> -h` to discover params, `run <id> -p "…" --json` to execute. Every command supports `--json` for clean piping.
|
|
117
122
|
|
|
123
|
+
**OpenCode** loads the same file with no extra steps — `.claude/skills/` is on its skill search path.
|
|
124
|
+
|
|
125
|
+
**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).
|
|
126
|
+
|
|
118
127
|
## Commands
|
|
119
128
|
|
|
120
129
|
```
|
|
@@ -152,6 +161,16 @@ wavespeed init Create a wavespeed.json with defaults +
|
|
|
152
161
|
wavespeed skill install Drop SKILL.md for coding agents
|
|
153
162
|
```
|
|
154
163
|
|
|
164
|
+
**Pricing is an estimate.** `wavespeed price` quotes from the model's pricing
|
|
165
|
+
formula, and many models bill from an input — audio/video duration, frame count,
|
|
166
|
+
text length. Those inputs are optional in the quote: ask for a price without
|
|
167
|
+
them and the formula collapses to the model's **base price**, which is the floor
|
|
168
|
+
of its range, not a typical run. `wavespeed price wavespeed-ai/infinitetalk`
|
|
169
|
+
says `$0.15`; a 60s audio actually bills `$1.80`. The command names the inputs it
|
|
170
|
+
was blind to (`unpriced_inputs` / `at_base_price` in `--json`) — supply them for
|
|
171
|
+
a real quote. Whatever it says, the amount actually charged for a run is
|
|
172
|
+
authoritative.
|
|
173
|
+
|
|
155
174
|
## Auth
|
|
156
175
|
|
|
157
176
|
| | Use when |
|
package/dist/cli.js
CHANGED
|
@@ -44,7 +44,7 @@ ${chalk.bold('Quick start:')}
|
|
|
44
44
|
${chalk.cyan('$ wavespeed models # browse the catalog')}
|
|
45
45
|
${chalk.cyan('$ wavespeed run wavespeed-ai/z-image/turbo -h # see the model\'s flags')}
|
|
46
46
|
${chalk.cyan('$ wavespeed run wavespeed-ai/z-image/turbo -p "a cyberpunk skyline" # ~5s')}
|
|
47
|
-
${chalk.cyan('$ wavespeed run bytedance/seedance-2.
|
|
47
|
+
${chalk.cyan('$ wavespeed run bytedance/seedance-2.5/text-to-video -p "drone shot over snowy peaks"')}
|
|
48
48
|
|
|
49
49
|
${chalk.bold('Use from any coding agent (Claude Code, Cursor, Codex):')}
|
|
50
50
|
${chalk.cyan('$ wavespeed skill install')} ${chalk.gray('# drops a SKILL.md so the agent knows how to call wavespeed')}
|
package/dist/commands/aliases.js
CHANGED
|
@@ -23,7 +23,7 @@ export function registerAliases(program) {
|
|
|
23
23
|
console.log(chalk.gray(JSON.stringify({
|
|
24
24
|
aliases: {
|
|
25
25
|
hero: {
|
|
26
|
-
model: '
|
|
26
|
+
model: 'bytedance/seedream-v5.0-pro',
|
|
27
27
|
input: { aspect_ratio: '16:9', resolution: '2k' },
|
|
28
28
|
},
|
|
29
29
|
},
|
package/dist/commands/auth.js
CHANGED
|
@@ -5,6 +5,7 @@ import ora from 'ora';
|
|
|
5
5
|
import clipboard from 'clipboardy';
|
|
6
6
|
import { userConfig, getBaseUrl, getApiKey, loadProjectConfig, PROJECT_FILE } from '../lib/config.js';
|
|
7
7
|
import { LINKS } from '../lib/links.js';
|
|
8
|
+
import { clientAttributionHeaders } from '../lib/client-headers.js';
|
|
8
9
|
const KEY_URL = 'https://wavespeed.ai/accesskey';
|
|
9
10
|
function mask(key) {
|
|
10
11
|
if (key.length < 12)
|
|
@@ -40,7 +41,7 @@ async function validateKey(apiKey, baseUrl) {
|
|
|
40
41
|
let res = null;
|
|
41
42
|
try {
|
|
42
43
|
res = await fetch(`${baseUrl}/api/v3/balance`, {
|
|
43
|
-
headers: { Authorization: `Bearer ${apiKey}
|
|
44
|
+
headers: { Authorization: `Bearer ${apiKey}`, ...clientAttributionHeaders() },
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
catch (err) {
|
|
@@ -108,7 +109,7 @@ async function loginFlow(opts) {
|
|
|
108
109
|
console.log(chalk.bold('Try it:'));
|
|
109
110
|
console.log(' ' + chalk.cyan('wavespeed models # browse the catalog'));
|
|
110
111
|
console.log(' ' + chalk.cyan('wavespeed run wavespeed-ai/z-image/turbo -p "a cyberpunk skyline at golden hour"'));
|
|
111
|
-
console.log(' ' + chalk.cyan('wavespeed run bytedance/seedance-2.
|
|
112
|
+
console.log(' ' + chalk.cyan('wavespeed run bytedance/seedance-2.5/text-to-video -p "drone shot over snowy peaks"'));
|
|
112
113
|
console.log();
|
|
113
114
|
}
|
|
114
115
|
export function registerAuth(program) {
|
package/dist/commands/init.js
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { input, confirm } from '@inquirer/prompts';
|
|
5
5
|
import { PROJECT_FILE } from '../lib/config.js';
|
|
6
|
-
const STARTER_MODEL = '
|
|
6
|
+
const STARTER_MODEL = 'bytedance/seedream-v5.0-pro';
|
|
7
7
|
export function registerInit(program) {
|
|
8
8
|
program
|
|
9
9
|
.command('init')
|
package/dist/commands/price.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
-
import { fetchPricing } from '../lib/api.js';
|
|
3
|
+
import { fetchPricing, fetchModels } from '../lib/api.js';
|
|
4
|
+
import { missingPriceVars, isFloorQuote } from '../lib/pricing-vars.js';
|
|
4
5
|
import { parseInputs } from '../lib/inputs.js';
|
|
5
6
|
import { resolveModelToken } from '../lib/config.js';
|
|
6
7
|
import { collectLocalFiles, resolveLocalFiles } from '../lib/local-files.js';
|
|
@@ -73,9 +74,35 @@ export function registerPrice(program) {
|
|
|
73
74
|
const spinner = !opts.json ? ora('Calculating price…').start() : null;
|
|
74
75
|
try {
|
|
75
76
|
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
|
+
}
|
|
76
95
|
spinner?.stop();
|
|
96
|
+
const disclaimer = 'Estimate only, for reference — the amount actually charged for a run is authoritative.';
|
|
77
97
|
if (opts.json) {
|
|
78
|
-
process.stdout.write(JSON.stringify({
|
|
98
|
+
process.stdout.write(JSON.stringify({
|
|
99
|
+
...data,
|
|
100
|
+
estimate: true,
|
|
101
|
+
unpriced_inputs: unpriced,
|
|
102
|
+
at_base_price: atFloor,
|
|
103
|
+
disclaimer,
|
|
104
|
+
uploaded: uploadedUrls,
|
|
105
|
+
}, null, 2) + '\n');
|
|
79
106
|
return;
|
|
80
107
|
}
|
|
81
108
|
console.log();
|
|
@@ -86,6 +113,19 @@ export function registerPrice(program) {
|
|
|
86
113
|
(data.discounted_price > 0 && data.discounted_price !== data.price
|
|
87
114
|
? chalk.gray(` (list $${data.price})`)
|
|
88
115
|
: ''));
|
|
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
|
+
}
|
|
89
129
|
console.log();
|
|
90
130
|
for (const [file, url] of Object.entries(uploadedUrls)) {
|
|
91
131
|
console.log(' ' + chalk.gray('uploaded: ') + chalk.gray(file + ' → ') + chalk.cyan(url));
|
|
@@ -95,6 +135,7 @@ export function registerPrice(program) {
|
|
|
95
135
|
}
|
|
96
136
|
console.log();
|
|
97
137
|
console.log(chalk.gray('Run it: ') + chalk.cyan(`wavespeed run ${modelArg}` + (opts.prompt ? ` -p "${opts.prompt}"` : '')));
|
|
138
|
+
console.log(chalk.gray(disclaimer));
|
|
98
139
|
console.log();
|
|
99
140
|
}
|
|
100
141
|
catch (err) {
|
package/dist/lib/api.js
CHANGED
|
@@ -5,6 +5,7 @@ import fs from 'node:fs';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import { getApiKey, getBaseUrl } from './config.js';
|
|
8
|
+
import { clientAttributionHeaders } from './client-headers.js';
|
|
8
9
|
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
9
10
|
function cachePath() {
|
|
10
11
|
return path.join(os.homedir(), '.cache', 'wavespeed', 'models.json');
|
|
@@ -44,7 +45,7 @@ export async function fetchModels(opts = {}) {
|
|
|
44
45
|
if (!apiKey)
|
|
45
46
|
throw new Error('No API key configured. Run `wavespeed login`.');
|
|
46
47
|
const res = await fetch(`${baseUrl}/api/v3/models`, {
|
|
47
|
-
headers: { Authorization: `Bearer ${apiKey}
|
|
48
|
+
headers: { Authorization: `Bearer ${apiKey}`, ...clientAttributionHeaders() },
|
|
48
49
|
});
|
|
49
50
|
if (!res.ok) {
|
|
50
51
|
throw await httpError(res, 'GET', '/api/v3/models');
|
|
@@ -70,7 +71,7 @@ function authHeaders() {
|
|
|
70
71
|
const apiKey = getApiKey();
|
|
71
72
|
if (!apiKey)
|
|
72
73
|
throw new Error('No API key configured. Run `wavespeed login`.');
|
|
73
|
-
return { Authorization: `Bearer ${apiKey}
|
|
74
|
+
return { Authorization: `Bearer ${apiKey}`, ...clientAttributionHeaders() };
|
|
74
75
|
}
|
|
75
76
|
// Non-2xx responses still carry the platform's error envelope, and for
|
|
76
77
|
// permission failures that body is the whole point: it names the role that
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Channel-attribution headers (X-Client-Name / X-Client-Version / X-Client-OS)
|
|
2
|
+
// for the CLI's direct REST calls, matching the convention wavespeed-desktop
|
|
3
|
+
// uses. Calls that go through the `wavespeed` SDK get the same headers from
|
|
4
|
+
// the SDK itself (via the clientName option in client.ts).
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
export const CLI_CLIENT_NAME = 'wavespeed-cli';
|
|
10
|
+
// Read version from package.json at runtime so a bump in package.json is
|
|
11
|
+
// reflected automatically (same approach as cli.ts).
|
|
12
|
+
function cliVersion() {
|
|
13
|
+
try {
|
|
14
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
// dist/lib/client-headers.js → ../../package.json
|
|
16
|
+
const pkgPath = path.resolve(here, '..', '..', 'package.json');
|
|
17
|
+
return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return '0.0.0';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Lowercase OS name: darwin / linux / windows (win32 is reported as windows).
|
|
24
|
+
function operatingSystem() {
|
|
25
|
+
const platform = os.platform();
|
|
26
|
+
return platform === 'win32' ? 'windows' : platform;
|
|
27
|
+
}
|
|
28
|
+
// The WAVESPEED_CLIENT_NAME environment variable takes priority so wrapper
|
|
29
|
+
// channels (dsh skill, Claude plugin, Gemini extension) can brand themselves
|
|
30
|
+
// without code changes.
|
|
31
|
+
export function resolveClientName() {
|
|
32
|
+
return process.env.WAVESPEED_CLIENT_NAME || CLI_CLIENT_NAME;
|
|
33
|
+
}
|
|
34
|
+
export function clientAttributionHeaders() {
|
|
35
|
+
return {
|
|
36
|
+
'X-Client-Name': resolveClientName(),
|
|
37
|
+
'X-Client-Version': cliVersion(),
|
|
38
|
+
'X-Client-OS': operatingSystem(),
|
|
39
|
+
};
|
|
40
|
+
}
|
package/dist/lib/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Client } from 'wavespeed';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { getApiKey, getBaseUrl } from './config.js';
|
|
4
|
+
import { CLI_CLIENT_NAME } from './client-headers.js';
|
|
4
5
|
export function requireClient() {
|
|
5
6
|
const apiKey = getApiKey();
|
|
6
7
|
if (!apiKey) {
|
|
@@ -9,11 +10,11 @@ export function requireClient() {
|
|
|
9
10
|
console.error(chalk.gray('Or set ') + chalk.cyan('WAVESPEED_API_KEY') + chalk.gray(' in your environment.\n'));
|
|
10
11
|
process.exit(1);
|
|
11
12
|
}
|
|
12
|
-
return new Client(apiKey, { baseUrl: getBaseUrl() });
|
|
13
|
+
return new Client(apiKey, { baseUrl: getBaseUrl(), clientName: CLI_CLIENT_NAME });
|
|
13
14
|
}
|
|
14
15
|
export function maybeClient() {
|
|
15
16
|
const apiKey = getApiKey();
|
|
16
17
|
if (!apiKey)
|
|
17
18
|
return null;
|
|
18
|
-
return new Client(apiKey, { baseUrl: getBaseUrl() });
|
|
19
|
+
return new Client(apiKey, { baseUrl: getBaseUrl(), clientName: CLI_CLIENT_NAME });
|
|
19
20
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wavespeed/cli",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "WaveSpeed CLI
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "WaveSpeed CLI \u2014 one command to run any WaveSpeed AI model from your terminal.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"conf": "^13.0.0",
|
|
57
57
|
"open": "^10.1.0",
|
|
58
58
|
"ora": "^8.1.0",
|
|
59
|
-
"wavespeed": "^0.
|
|
59
|
+
"wavespeed": "^0.3.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/node": "^20.14.0",
|
package/skills/SKILL.md
CHANGED
|
@@ -11,14 +11,14 @@ You have access to the `wavespeed` CLI. Every generation flows through one verb.
|
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
# 1. FIND a model — search the live catalog
|
|
14
|
-
wavespeed models "
|
|
14
|
+
wavespeed models "seedream"
|
|
15
15
|
wavespeed models --type image-to-video --popular
|
|
16
16
|
|
|
17
17
|
# 2. INSPECT its inputs — dynamic schema, per model
|
|
18
|
-
wavespeed run
|
|
18
|
+
wavespeed run bytedance/seedream-v5.0-pro -h
|
|
19
19
|
|
|
20
20
|
# 3. RUN it — always pass --json so you can read the result
|
|
21
|
-
wavespeed run
|
|
21
|
+
wavespeed run bytedance/seedream-v5.0-pro \
|
|
22
22
|
-p "a cyberpunk skyline at golden hour" \
|
|
23
23
|
-i aspect_ratio="16:9" -i resolution="2k" --json
|
|
24
24
|
```
|
|
@@ -29,10 +29,12 @@ wavespeed run google/nano-banana-2/text-to-image \
|
|
|
29
29
|
|
|
30
30
|
| Use case | Model |
|
|
31
31
|
|---|---|
|
|
32
|
-
| Text → image | `
|
|
33
|
-
| Image edit (instruction-driven) | `
|
|
34
|
-
| Text → video | `bytedance/seedance-2.
|
|
35
|
-
| Image → video | `bytedance/seedance-2.
|
|
32
|
+
| Text → image | `bytedance/seedream-v5.0-pro` |
|
|
33
|
+
| Image edit (instruction-driven) | `bytedance/seedream-v5.0-pro/edit` — requires `images: [url, ...]` |
|
|
34
|
+
| Text → video | `bytedance/seedance-2.5/text-to-video` |
|
|
35
|
+
| Image → video | `bytedance/seedance-2.5/image-to-video` — requires `image: url` |
|
|
36
|
+
| Video edit (instruction-driven) | `bytedance/seedance-2.5/video-edit` — requires `video: url` |
|
|
37
|
+
| Video extend | `bytedance/seedance-2.5/video-extend` — requires `video: url` |
|
|
36
38
|
|
|
37
39
|
These are good starting points. Browse alternatives with `wavespeed models <query>`.
|
|
38
40
|
|
|
@@ -40,12 +42,12 @@ These are good starting points. Browse alternatives with `wavespeed models <quer
|
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
44
|
# Edit an existing image — @path uploads the file and passes its URL (one step)
|
|
43
|
-
wavespeed run
|
|
45
|
+
wavespeed run bytedance/seedream-v5.0-pro/edit \
|
|
44
46
|
-p "replace the background with a sunlit kitchen" \
|
|
45
47
|
-i images='["@./input.jpg"]' --json
|
|
46
48
|
|
|
47
49
|
# Image-to-video — same @ marker for single-URL fields
|
|
48
|
-
wavespeed run bytedance/seedance-2.
|
|
50
|
+
wavespeed run bytedance/seedance-2.5/image-to-video \
|
|
49
51
|
-p "subtle parallax, gentle wind" \
|
|
50
52
|
-i image=@./hero.jpg -i duration=5 --json
|
|
51
53
|
|