@wavespeed/cli 0.1.0 → 0.2.0
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 +17 -3
- package/dist/cli.js +14 -0
- package/dist/commands/auth.js +9 -0
- package/dist/commands/balance.js +42 -0
- package/dist/commands/delete.js +39 -0
- package/dist/commands/history.js +96 -0
- package/dist/commands/open.js +48 -0
- package/dist/commands/price.js +51 -0
- package/dist/commands/schema.js +1 -0
- package/dist/commands/show.js +76 -0
- package/dist/commands/topup.js +22 -0
- package/dist/lib/api.js +57 -0
- package/dist/lib/dynamic-help.js +1 -0
- package/dist/lib/links.js +13 -0
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
<p><strong>One command, every WaveSpeed model. Image, video, audio, 3D — from your terminal.</strong></p>
|
|
8
8
|
<p>
|
|
9
9
|
<a href="https://wavespeed.ai">🌐 wavespeed.ai</a> •
|
|
10
|
+
<a href="https://wavespeed.ai/cli">💻 wavespeed.ai/cli</a> •
|
|
10
11
|
<a href="https://wavespeed.ai/models">📚 Models</a> •
|
|
11
12
|
<a href="https://wavespeed.ai/docs">📖 Docs</a> •
|
|
12
|
-
<a href="https://github.com/WaveSpeedAI/cli/issues">🐛 Issues</a>
|
|
13
|
+
<a href="https://github.com/WaveSpeedAI/wavespeed-cli/issues">🐛 Issues</a>
|
|
13
14
|
</p>
|
|
14
15
|
<p>
|
|
15
16
|
<a href="https://www.npmjs.com/package/@wavespeed/cli"><img alt="npm" src="https://img.shields.io/npm/v/@wavespeed/cli?style=flat-square&color=7c5cff"></a>
|
|
@@ -114,20 +115,33 @@ The skill teaches the agent the three-step pattern: `models` to find, `run <id>
|
|
|
114
115
|
## Commands
|
|
115
116
|
|
|
116
117
|
```
|
|
118
|
+
# auth & config
|
|
117
119
|
wavespeed login Browser + paste-key wizard (clipboard-aware)
|
|
118
120
|
wavespeed logout Clear stored API key
|
|
119
|
-
wavespeed status Show masked key, base URL,
|
|
121
|
+
wavespeed status Show masked key, base URL, useful links
|
|
120
122
|
wavespeed config [--default-model …] View / update CLI defaults
|
|
121
123
|
|
|
124
|
+
# generation
|
|
122
125
|
wavespeed run [model|alias] -p "…" Run any model or alias (uses defaultModel if omitted)
|
|
123
126
|
wavespeed run <model|alias> -h Dynamic schema-based help (alias-aware)
|
|
124
127
|
wavespeed schema <model> Pretty-print a model's input schema
|
|
125
128
|
wavespeed models [query] Browse the live catalog (cached 1h)
|
|
126
129
|
wavespeed aliases List aliases from wavespeed.json + user config
|
|
127
130
|
|
|
131
|
+
# files
|
|
128
132
|
wavespeed upload <file...> Upload local file(s) → CDN URLs
|
|
129
133
|
wavespeed download <url...> Save URLs to disk
|
|
130
134
|
|
|
135
|
+
# account & history
|
|
136
|
+
wavespeed balance Show your current credit balance
|
|
137
|
+
wavespeed price <model> -i k=v Estimate the cost of a run (no charge)
|
|
138
|
+
wavespeed top-up Open https://wavespeed.ai/top-up
|
|
139
|
+
wavespeed history [--limit --status …] List recent predictions (last 24h)
|
|
140
|
+
wavespeed show <id> Full details for a past prediction
|
|
141
|
+
wavespeed delete <id...> Remove predictions from your history
|
|
142
|
+
|
|
143
|
+
# misc
|
|
144
|
+
wavespeed open [target] Jump to dashboard / models / docs / … in browser
|
|
131
145
|
wavespeed init Create a wavespeed.json with defaults + alias stubs
|
|
132
146
|
wavespeed skill install Drop SKILL.md for coding agents
|
|
133
147
|
```
|
|
@@ -144,7 +158,7 @@ Get a key at [wavespeed.ai/accesskey](https://wavespeed.ai/accesskey).
|
|
|
144
158
|
## Development
|
|
145
159
|
|
|
146
160
|
```bash
|
|
147
|
-
git clone https://github.com/WaveSpeedAI/cli.git
|
|
161
|
+
git clone https://github.com/WaveSpeedAI/wavespeed-cli.git
|
|
148
162
|
cd cli
|
|
149
163
|
npm install
|
|
150
164
|
npm run dev -- run google/nano-banana-2/text-to-image -p "a serene mountain lake"
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,13 @@ import { registerSchema } from './commands/schema.js';
|
|
|
14
14
|
import { registerAliases } from './commands/aliases.js';
|
|
15
15
|
import { registerInit } from './commands/init.js';
|
|
16
16
|
import { registerSkill } from './commands/skill.js';
|
|
17
|
+
import { registerBalance } from './commands/balance.js';
|
|
18
|
+
import { registerPrice } from './commands/price.js';
|
|
19
|
+
import { registerTopUp } from './commands/topup.js';
|
|
20
|
+
import { registerHistory } from './commands/history.js';
|
|
21
|
+
import { registerShow } from './commands/show.js';
|
|
22
|
+
import { registerDelete } from './commands/delete.js';
|
|
23
|
+
import { registerOpen } from './commands/open.js';
|
|
17
24
|
import { detectRunHelp, printDynamicRunHelp } from './lib/dynamic-help.js';
|
|
18
25
|
// Read version from package.json at runtime so a bump in package.json is
|
|
19
26
|
// reflected automatically (no second place to keep in sync).
|
|
@@ -50,6 +57,13 @@ registerDownload(program);
|
|
|
50
57
|
registerModels(program);
|
|
51
58
|
registerSchema(program);
|
|
52
59
|
registerAliases(program);
|
|
60
|
+
registerBalance(program);
|
|
61
|
+
registerPrice(program);
|
|
62
|
+
registerTopUp(program);
|
|
63
|
+
registerHistory(program);
|
|
64
|
+
registerShow(program);
|
|
65
|
+
registerDelete(program);
|
|
66
|
+
registerOpen(program);
|
|
53
67
|
registerInit(program);
|
|
54
68
|
registerSkill(program);
|
|
55
69
|
program
|
package/dist/commands/auth.js
CHANGED
|
@@ -4,6 +4,7 @@ import open from 'open';
|
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import clipboard from 'clipboardy';
|
|
6
6
|
import { userConfig, getBaseUrl, getApiKey, loadProjectConfig, PROJECT_FILE } from '../lib/config.js';
|
|
7
|
+
import { LINKS } from '../lib/links.js';
|
|
7
8
|
const KEY_URL = 'https://wavespeed.ai/accesskey';
|
|
8
9
|
function mask(key) {
|
|
9
10
|
if (key.length < 12)
|
|
@@ -143,6 +144,14 @@ export function registerAuth(program) {
|
|
|
143
144
|
console.log(' ' + chalk.gray('Project: ') + chalk.cyan(PROJECT_FILE) + chalk.gray(' detected'));
|
|
144
145
|
}
|
|
145
146
|
console.log();
|
|
147
|
+
console.log(chalk.bold('Useful links'));
|
|
148
|
+
const width = Math.max(...Object.keys(LINKS).map((k) => k.length));
|
|
149
|
+
for (const [name, link] of Object.entries(LINKS)) {
|
|
150
|
+
console.log(' ' + chalk.gray(name.padEnd(width)) + ' ' + chalk.cyan(link.url));
|
|
151
|
+
}
|
|
152
|
+
console.log();
|
|
153
|
+
console.log(chalk.gray('Jump in your browser: ') + chalk.cyan('wavespeed open <target>'));
|
|
154
|
+
console.log();
|
|
146
155
|
});
|
|
147
156
|
program
|
|
148
157
|
.command('config')
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import { fetchBalance } from '../lib/api.js';
|
|
5
|
+
const TOPUP_URL = 'https://wavespeed.ai/top-up';
|
|
6
|
+
export function registerBalance(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('balance')
|
|
9
|
+
.description('Show the credit balance on your Wavespeed account')
|
|
10
|
+
.option('--json', 'Emit JSON: {balance, currency, top_up_url}')
|
|
11
|
+
.option('--top-up', 'Open the top-up page in your browser after printing')
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
const spinner = !opts.json ? ora('Checking balance…').start() : null;
|
|
14
|
+
let balance;
|
|
15
|
+
try {
|
|
16
|
+
const data = await fetchBalance();
|
|
17
|
+
balance = data.balance;
|
|
18
|
+
spinner?.stop();
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
spinner?.fail(err.message ?? String(err));
|
|
22
|
+
if (opts.json) {
|
|
23
|
+
process.stdout.write(JSON.stringify({ error: err.message ?? String(err) }, null, 2) + '\n');
|
|
24
|
+
}
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
if (opts.json) {
|
|
28
|
+
process.stdout.write(JSON.stringify({ balance, currency: 'USD', top_up_url: TOPUP_URL }, null, 2) + '\n');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const display = balance.toFixed(balance < 10 ? 3 : 2);
|
|
32
|
+
console.log();
|
|
33
|
+
console.log(chalk.bold('Balance: ') + chalk.green(`$${display}`));
|
|
34
|
+
console.log(chalk.gray('Top up: ') + chalk.cyan(TOPUP_URL));
|
|
35
|
+
console.log();
|
|
36
|
+
if (opts.topUp) {
|
|
37
|
+
await open(TOPUP_URL).catch(() => {
|
|
38
|
+
/* opening failed — the URL is already in the output */
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { confirm } from '@inquirer/prompts';
|
|
4
|
+
import { deletePredictions } from '../lib/api.js';
|
|
5
|
+
export function registerDelete(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('delete')
|
|
8
|
+
.description('Delete one or more predictions from your history')
|
|
9
|
+
.argument('<id...>', 'Prediction ID(s) to delete')
|
|
10
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
11
|
+
.option('--json', 'Emit JSON: {deleted: [...ids]}')
|
|
12
|
+
.action(async (ids, opts) => {
|
|
13
|
+
if (!opts.yes && !opts.json) {
|
|
14
|
+
const go = await confirm({
|
|
15
|
+
message: `Delete ${ids.length} prediction${ids.length === 1 ? '' : 's'} from your history? This cannot be undone.`,
|
|
16
|
+
default: false,
|
|
17
|
+
});
|
|
18
|
+
if (!go) {
|
|
19
|
+
console.log(chalk.gray('Aborted.'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const spinner = !opts.json ? ora(`Deleting ${ids.length} prediction${ids.length === 1 ? '' : 's'}…`).start() : null;
|
|
24
|
+
try {
|
|
25
|
+
await deletePredictions(ids);
|
|
26
|
+
spinner?.succeed(`Deleted ${ids.length} prediction${ids.length === 1 ? '' : 's'}.`);
|
|
27
|
+
if (opts.json) {
|
|
28
|
+
process.stdout.write(JSON.stringify({ deleted: ids }, null, 2) + '\n');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
spinner?.fail(err.message ?? String(err));
|
|
33
|
+
if (opts.json) {
|
|
34
|
+
process.stdout.write(JSON.stringify({ error: err.message ?? String(err) }, null, 2) + '\n');
|
|
35
|
+
}
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { fetchHistory } from '../lib/api.js';
|
|
4
|
+
function statusBadge(status) {
|
|
5
|
+
const map = {
|
|
6
|
+
completed: chalk.bgGreen.black,
|
|
7
|
+
failed: chalk.bgRed.white,
|
|
8
|
+
processing: chalk.bgYellow.black,
|
|
9
|
+
pending: chalk.bgYellow.black,
|
|
10
|
+
created: chalk.bgGray.white,
|
|
11
|
+
};
|
|
12
|
+
const fn = map[status] ?? chalk.bgGray.white;
|
|
13
|
+
return fn(` ${status} `);
|
|
14
|
+
}
|
|
15
|
+
function formatTime(iso) {
|
|
16
|
+
try {
|
|
17
|
+
const d = new Date(iso);
|
|
18
|
+
return d.toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return iso;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function firstOutput(item) {
|
|
25
|
+
const out = item.outputs?.[0];
|
|
26
|
+
if (typeof out === 'string')
|
|
27
|
+
return out;
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
export function registerHistory(program) {
|
|
31
|
+
program
|
|
32
|
+
.command('history')
|
|
33
|
+
.description('List your recent predictions (default: last 24h)')
|
|
34
|
+
.option('--page <n>', 'Page number', '1')
|
|
35
|
+
.option('--limit <n>', 'Page size (1-100)', '20')
|
|
36
|
+
.option('--model <id>', 'Filter by model ID')
|
|
37
|
+
.option('--status <s>', 'Filter by status (completed, failed, processing, pending)')
|
|
38
|
+
.option('--since <iso>', 'Show predictions created after this ISO timestamp')
|
|
39
|
+
.option('--until <iso>', 'Show predictions created before this ISO timestamp')
|
|
40
|
+
.option('--json', 'Emit JSON: {page, items: [...]}')
|
|
41
|
+
.action(async (opts) => {
|
|
42
|
+
const spinner = !opts.json ? ora('Fetching history…').start() : null;
|
|
43
|
+
try {
|
|
44
|
+
const data = await fetchHistory({
|
|
45
|
+
page: parseInt(opts.page, 10),
|
|
46
|
+
pageSize: parseInt(opts.limit, 10),
|
|
47
|
+
model: opts.model,
|
|
48
|
+
status: opts.status,
|
|
49
|
+
createdAfter: opts.since,
|
|
50
|
+
createdBefore: opts.until,
|
|
51
|
+
});
|
|
52
|
+
spinner?.stop();
|
|
53
|
+
if (opts.json) {
|
|
54
|
+
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const items = data.items ?? [];
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(chalk.bold(`${items.length} prediction${items.length === 1 ? '' : 's'}`) +
|
|
60
|
+
chalk.gray(` · page ${data.page}`));
|
|
61
|
+
console.log();
|
|
62
|
+
if (items.length === 0) {
|
|
63
|
+
console.log(chalk.gray(' no predictions in this range. Try --since "2026-01-01" for a wider window.'));
|
|
64
|
+
console.log();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const item of items) {
|
|
68
|
+
const promptText = item.inputs?.prompt ?? item.input?.prompt ?? '';
|
|
69
|
+
const shortPrompt = promptText.replace(/\s+/g, ' ').trim().slice(0, 60);
|
|
70
|
+
console.log(statusBadge(item.status) +
|
|
71
|
+
' ' +
|
|
72
|
+
chalk.cyan(item.id) +
|
|
73
|
+
' ' +
|
|
74
|
+
chalk.gray(formatTime(item.created_at)));
|
|
75
|
+
console.log(' ' + chalk.gray('model: ') + chalk.white(item.model));
|
|
76
|
+
if (shortPrompt)
|
|
77
|
+
console.log(' ' + chalk.gray('prompt: ') + chalk.white(shortPrompt + (promptText.length > 60 ? '…' : '')));
|
|
78
|
+
const out = firstOutput(item);
|
|
79
|
+
if (out)
|
|
80
|
+
console.log(' ' + chalk.gray('output: ') + chalk.cyan(out));
|
|
81
|
+
if (item.error)
|
|
82
|
+
console.log(' ' + chalk.gray('error: ') + chalk.red(item.error));
|
|
83
|
+
console.log();
|
|
84
|
+
}
|
|
85
|
+
console.log(chalk.gray('Details: ') + chalk.cyan('wavespeed show <id>') + chalk.gray(' delete: ') + chalk.cyan('wavespeed delete <id>'));
|
|
86
|
+
console.log();
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
spinner?.fail(err.message ?? String(err));
|
|
90
|
+
if (opts.json) {
|
|
91
|
+
process.stdout.write(JSON.stringify({ error: err.message ?? String(err) }, null, 2) + '\n');
|
|
92
|
+
}
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import open from 'open';
|
|
3
|
+
import { LINKS } from '../lib/links.js';
|
|
4
|
+
export function registerOpen(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('open')
|
|
7
|
+
.description('Open a Wavespeed page in your browser (dashboard, models, history, top-up, …)')
|
|
8
|
+
.argument('[target]', `Target to open: ${Object.keys(LINKS).join(', ')}. Omit to list them.`)
|
|
9
|
+
.option('--print', 'Print the URL, do not open the browser')
|
|
10
|
+
.option('--json', 'Emit the link map as JSON (only when no target is given)')
|
|
11
|
+
.action(async (target, opts) => {
|
|
12
|
+
if (!target) {
|
|
13
|
+
if (opts.json) {
|
|
14
|
+
process.stdout.write(JSON.stringify(LINKS, null, 2) + '\n');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
console.log();
|
|
18
|
+
console.log(chalk.bold('Useful Wavespeed pages'));
|
|
19
|
+
console.log();
|
|
20
|
+
const width = Math.max(...Object.keys(LINKS).map((k) => k.length));
|
|
21
|
+
for (const [name, link] of Object.entries(LINKS)) {
|
|
22
|
+
console.log(' ' + chalk.cyan(name.padEnd(width)) + ' ' + chalk.white(link.url));
|
|
23
|
+
console.log(' ' + ' '.repeat(width) + ' ' + chalk.gray(link.desc));
|
|
24
|
+
}
|
|
25
|
+
console.log();
|
|
26
|
+
console.log(chalk.gray('Use: ') + chalk.cyan('wavespeed open <target>'));
|
|
27
|
+
console.log();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const link = LINKS[target];
|
|
31
|
+
if (!link) {
|
|
32
|
+
const msg = `Unknown target "${target}". Available: ${Object.keys(LINKS).join(', ')}.`;
|
|
33
|
+
console.error(chalk.red('Error: ') + msg);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
if (opts.print) {
|
|
37
|
+
process.stdout.write(link.url + '\n');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log(chalk.gray('Opening ') + chalk.cyan(link.url));
|
|
41
|
+
try {
|
|
42
|
+
await open(link.url);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
/* Browser launch failed — URL was printed above */
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { fetchPricing } from '../lib/api.js';
|
|
4
|
+
import { parseInputs } from '../lib/inputs.js';
|
|
5
|
+
import { resolveModelToken } from '../lib/config.js';
|
|
6
|
+
export function registerPrice(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('price')
|
|
9
|
+
.description("Calculate the cost of running a model with specific inputs (no generation, no charge)")
|
|
10
|
+
.argument('<model>', 'Model ID or alias')
|
|
11
|
+
.option('-i, --input <pair...>', 'Inputs as key=value (same syntax as `wavespeed run`)', [])
|
|
12
|
+
.option('-p, --prompt <text>', 'Shorthand for --input prompt=<text>')
|
|
13
|
+
.option('--json', 'Emit JSON')
|
|
14
|
+
.action(async (modelArg, opts) => {
|
|
15
|
+
const resolved = resolveModelToken(modelArg);
|
|
16
|
+
if (!resolved) {
|
|
17
|
+
const msg = `"${modelArg}" is neither a model ID nor a known alias.`;
|
|
18
|
+
if (opts.json)
|
|
19
|
+
process.stdout.write(JSON.stringify({ error: msg }, null, 2) + '\n');
|
|
20
|
+
else
|
|
21
|
+
console.error(chalk.red('Error: ') + msg);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const inputs = { ...(resolved.defaultInput ?? {}) };
|
|
25
|
+
Object.assign(inputs, parseInputs(opts.input ?? []));
|
|
26
|
+
if (opts.prompt)
|
|
27
|
+
inputs.prompt = opts.prompt;
|
|
28
|
+
const spinner = !opts.json ? ora('Calculating price…').start() : null;
|
|
29
|
+
try {
|
|
30
|
+
const data = await fetchPricing(resolved.model, inputs);
|
|
31
|
+
spinner?.stop();
|
|
32
|
+
if (opts.json) {
|
|
33
|
+
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
console.log();
|
|
37
|
+
console.log(chalk.bold('Estimated cost'));
|
|
38
|
+
console.log(' ' + chalk.gray('model: ') + chalk.cyan(data.model_id));
|
|
39
|
+
console.log(' ' + chalk.gray('price: ') + chalk.green(`$${data.unit_price}`) + chalk.gray(` ${data.currency ?? 'USD'} per call`));
|
|
40
|
+
console.log();
|
|
41
|
+
console.log(chalk.gray('Run it: ') + chalk.cyan(`wavespeed run ${modelArg}` + (opts.prompt ? ` -p "${opts.prompt}"` : '')));
|
|
42
|
+
console.log();
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
spinner?.fail(err.message ?? String(err));
|
|
46
|
+
if (opts.json)
|
|
47
|
+
process.stdout.write(JSON.stringify({ error: err.message ?? String(err) }, null, 2) + '\n');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
package/dist/commands/schema.js
CHANGED
|
@@ -115,6 +115,7 @@ export function registerSchema(program) {
|
|
|
115
115
|
console.log(chalk.gray(' type: ') +
|
|
116
116
|
chalk.white(model.type ?? 'unknown') +
|
|
117
117
|
(model.base_price !== undefined ? chalk.gray(' base price: ') + chalk.green(`$${model.base_price}`) : ''));
|
|
118
|
+
console.log(chalk.gray(' page: ') + chalk.cyan(`https://wavespeed.ai/models/${model.model_id}`));
|
|
118
119
|
if (model.description) {
|
|
119
120
|
console.log(wrap(model.description, ' '));
|
|
120
121
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { fetchPrediction } from '../lib/api.js';
|
|
4
|
+
import { downloadOutputs, printOutputs } from '../lib/output.js';
|
|
5
|
+
import { downloadOptsFromFlag } from '../lib/download-flag.js';
|
|
6
|
+
function formatTime(iso) {
|
|
7
|
+
try {
|
|
8
|
+
return new Date(iso).toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return iso;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function registerShow(program) {
|
|
15
|
+
program
|
|
16
|
+
.command('show')
|
|
17
|
+
.description('Show full details for a past prediction')
|
|
18
|
+
.argument('<id>', 'Prediction ID (from `wavespeed history` or a previous `run --json` response)')
|
|
19
|
+
.option('--download [path]', 'Save outputs locally (optional path template, e.g. "./out/{index}.{ext}")')
|
|
20
|
+
.option('--output-dir <dir>', 'Directory for --download when no path is given')
|
|
21
|
+
.option('--json', 'Emit the raw prediction record as JSON')
|
|
22
|
+
.action(async (id, opts) => {
|
|
23
|
+
const spinner = !opts.json ? ora(`Fetching ${id}…`).start() : null;
|
|
24
|
+
try {
|
|
25
|
+
const item = await fetchPrediction(id);
|
|
26
|
+
spinner?.stop();
|
|
27
|
+
const urls = Array.isArray(item.outputs)
|
|
28
|
+
? item.outputs.filter((o) => typeof o === 'string')
|
|
29
|
+
: [];
|
|
30
|
+
let saved = [];
|
|
31
|
+
const dlOpts = downloadOptsFromFlag(opts.download, {
|
|
32
|
+
model: item.model,
|
|
33
|
+
prompt: typeof item.inputs?.prompt === 'string'
|
|
34
|
+
? item.inputs.prompt
|
|
35
|
+
: typeof item.input?.prompt === 'string'
|
|
36
|
+
? item.input.prompt
|
|
37
|
+
: undefined,
|
|
38
|
+
outputDir: opts.outputDir,
|
|
39
|
+
});
|
|
40
|
+
if (urls.length > 0 && dlOpts) {
|
|
41
|
+
const dl = !opts.json ? ora('Downloading…').start() : null;
|
|
42
|
+
saved = await downloadOutputs(urls, dlOpts);
|
|
43
|
+
dl?.succeed(`Saved ${saved.length} file${saved.length > 1 ? 's' : ''}.`);
|
|
44
|
+
}
|
|
45
|
+
if (opts.json) {
|
|
46
|
+
process.stdout.write(JSON.stringify({ ...item, saved }, null, 2) + '\n');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
console.log();
|
|
50
|
+
console.log(chalk.bold('▶ ') + chalk.cyan(item.id));
|
|
51
|
+
console.log(' ' + chalk.gray('model: ') + chalk.white(item.model));
|
|
52
|
+
console.log(' ' + chalk.gray('status: ') + chalk.white(item.status));
|
|
53
|
+
console.log(' ' + chalk.gray('created: ') + chalk.white(formatTime(item.created_at)));
|
|
54
|
+
if (item.executionTime !== undefined && item.executionTime > 0) {
|
|
55
|
+
console.log(' ' + chalk.gray('elapsed: ') + chalk.white(`${item.executionTime}ms`));
|
|
56
|
+
}
|
|
57
|
+
const promptText = item.inputs?.prompt ??
|
|
58
|
+
item.input?.prompt;
|
|
59
|
+
if (promptText)
|
|
60
|
+
console.log(' ' + chalk.gray('prompt: ') + chalk.white(promptText));
|
|
61
|
+
if (item.error)
|
|
62
|
+
console.log(' ' + chalk.gray('error: ') + chalk.red(item.error));
|
|
63
|
+
if (urls.length > 0)
|
|
64
|
+
printOutputs(saved, urls);
|
|
65
|
+
else
|
|
66
|
+
console.log('\n ' + chalk.gray('(no outputs)') + '\n');
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
spinner?.fail(err.message ?? String(err));
|
|
70
|
+
if (opts.json) {
|
|
71
|
+
process.stdout.write(JSON.stringify({ error: err.message ?? String(err) }, null, 2) + '\n');
|
|
72
|
+
}
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import open from 'open';
|
|
3
|
+
const TOPUP_URL = 'https://wavespeed.ai/top-up';
|
|
4
|
+
export function registerTopUp(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('top-up')
|
|
7
|
+
.description('Open the Wavespeed top-up page in your browser')
|
|
8
|
+
.option('--print', 'Just print the URL, do not open the browser')
|
|
9
|
+
.action(async (opts) => {
|
|
10
|
+
console.log();
|
|
11
|
+
console.log(chalk.bold('Top up: ') + chalk.cyan(TOPUP_URL));
|
|
12
|
+
console.log();
|
|
13
|
+
if (opts.print)
|
|
14
|
+
return;
|
|
15
|
+
try {
|
|
16
|
+
await open(TOPUP_URL);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
/* browser launch failed; the URL is printed above */
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
package/dist/lib/api.js
CHANGED
|
@@ -65,3 +65,60 @@ export function clearModelsCache() {
|
|
|
65
65
|
/* nothing to clear */
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
+
// --- Account + prediction endpoints (not exposed by the wavespeed SDK) ---
|
|
69
|
+
function authHeaders() {
|
|
70
|
+
const apiKey = getApiKey();
|
|
71
|
+
if (!apiKey)
|
|
72
|
+
throw new Error('No API key configured. Run `wavespeed login`.');
|
|
73
|
+
return { Authorization: `Bearer ${apiKey}` };
|
|
74
|
+
}
|
|
75
|
+
async function apiGet(path) {
|
|
76
|
+
const res = await fetch(`${getBaseUrl()}${path}`, { headers: authHeaders() });
|
|
77
|
+
if (!res.ok)
|
|
78
|
+
throw new Error(`GET ${path} failed: ${res.status} ${res.statusText}`);
|
|
79
|
+
const json = (await res.json());
|
|
80
|
+
if (json.code !== 200)
|
|
81
|
+
throw new Error(json.message || `API returned code ${json.code}`);
|
|
82
|
+
return json.data;
|
|
83
|
+
}
|
|
84
|
+
async function apiPost(path, body) {
|
|
85
|
+
const res = await fetch(`${getBaseUrl()}${path}`, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: { ...authHeaders(), 'Content-Type': 'application/json' },
|
|
88
|
+
body: JSON.stringify(body),
|
|
89
|
+
});
|
|
90
|
+
if (!res.ok)
|
|
91
|
+
throw new Error(`POST ${path} failed: ${res.status} ${res.statusText}`);
|
|
92
|
+
const json = (await res.json());
|
|
93
|
+
if (json.code !== 200)
|
|
94
|
+
throw new Error(json.message || `API returned code ${json.code}`);
|
|
95
|
+
return json.data;
|
|
96
|
+
}
|
|
97
|
+
export async function fetchBalance() {
|
|
98
|
+
return apiGet('/api/v3/balance');
|
|
99
|
+
}
|
|
100
|
+
export async function fetchPricing(modelId, inputs) {
|
|
101
|
+
return apiPost('/api/v3/model/pricing', { model_id: modelId, inputs });
|
|
102
|
+
}
|
|
103
|
+
export async function fetchHistory(query = {}) {
|
|
104
|
+
const now = new Date();
|
|
105
|
+
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
106
|
+
const body = {
|
|
107
|
+
page: query.page ?? 1,
|
|
108
|
+
page_size: query.pageSize ?? 20,
|
|
109
|
+
created_after: query.createdAfter ?? oneDayAgo.toISOString(),
|
|
110
|
+
created_before: query.createdBefore ?? now.toISOString(),
|
|
111
|
+
include_inputs: true,
|
|
112
|
+
};
|
|
113
|
+
if (query.model)
|
|
114
|
+
body.model = query.model;
|
|
115
|
+
if (query.status)
|
|
116
|
+
body.status = query.status;
|
|
117
|
+
return apiPost('/api/v3/predictions', body);
|
|
118
|
+
}
|
|
119
|
+
export async function fetchPrediction(id) {
|
|
120
|
+
return apiGet(`/api/v3/predictions/${id}/result`);
|
|
121
|
+
}
|
|
122
|
+
export async function deletePredictions(ids) {
|
|
123
|
+
await apiPost('/api/v3/predictions/delete', { ids });
|
|
124
|
+
}
|
package/dist/lib/dynamic-help.js
CHANGED
|
@@ -100,6 +100,7 @@ export async function printDynamicRunHelp(token) {
|
|
|
100
100
|
(model.base_price !== undefined
|
|
101
101
|
? chalk.gray(' base price: ') + chalk.green(`$${model.base_price}`)
|
|
102
102
|
: ''));
|
|
103
|
+
console.log(chalk.gray(' page: ') + chalk.cyan(`https://wavespeed.ai/models/${model.model_id}`));
|
|
103
104
|
if (model.description)
|
|
104
105
|
console.log(wrap(model.description, ' '));
|
|
105
106
|
if (aliasDefaults && Object.keys(aliasDefaults).length > 0) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Single source of truth for the in-product URLs the CLI surfaces. Used by
|
|
2
|
+
// `wavespeed open` and the `status` footer so a label update happens in one
|
|
3
|
+
// spot.
|
|
4
|
+
export const LINKS = {
|
|
5
|
+
dashboard: { url: 'https://wavespeed.ai/dashboard', desc: 'Your account dashboard' },
|
|
6
|
+
models: { url: 'https://wavespeed.ai/models', desc: 'Browse the full model catalog' },
|
|
7
|
+
history: { url: 'https://wavespeed.ai/history', desc: 'Your prediction history on the web' },
|
|
8
|
+
'top-up': { url: 'https://wavespeed.ai/top-up', desc: 'Add credit to your account' },
|
|
9
|
+
accesskey: { url: 'https://wavespeed.ai/accesskey', desc: 'Manage your API keys' },
|
|
10
|
+
docs: { url: 'https://wavespeed.ai/docs', desc: 'API reference & guides' },
|
|
11
|
+
cli: { url: 'https://wavespeed.ai/cli', desc: 'The CLI marketing & docs page' },
|
|
12
|
+
github: { url: 'https://github.com/WaveSpeedAI/wavespeed-cli', desc: 'CLI source on GitHub' },
|
|
13
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wavespeed/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "WaveSpeed CLI — one command to run any WaveSpeed AI model from your terminal.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"prepublishOnly": "npm run build",
|
|
24
24
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
25
25
|
"lint": "tsc --noEmit",
|
|
26
|
-
"test": "
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:smoke": "tsx src/cli.ts --version"
|
|
27
28
|
},
|
|
28
29
|
"engines": {
|
|
29
30
|
"node": ">=18"
|
|
@@ -42,10 +43,10 @@
|
|
|
42
43
|
"homepage": "https://wavespeed.ai",
|
|
43
44
|
"repository": {
|
|
44
45
|
"type": "git",
|
|
45
|
-
"url": "https://github.com/WaveSpeedAI/cli.git"
|
|
46
|
+
"url": "https://github.com/WaveSpeedAI/wavespeed-cli.git"
|
|
46
47
|
},
|
|
47
48
|
"bugs": {
|
|
48
|
-
"url": "https://github.com/WaveSpeedAI/cli/issues"
|
|
49
|
+
"url": "https://github.com/WaveSpeedAI/wavespeed-cli/issues"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
52
|
"@inquirer/prompts": "^7.0.0",
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"@types/node": "^20.14.0",
|
|
62
63
|
"prettier": "^3.3.0",
|
|
63
64
|
"tsx": "^4.19.0",
|
|
64
|
-
"typescript": "^5.5.0"
|
|
65
|
+
"typescript": "^5.5.0",
|
|
66
|
+
"vitest": "^4.1.6"
|
|
65
67
|
}
|
|
66
68
|
}
|