@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 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, paths
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
@@ -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
+ }
@@ -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
+ }
@@ -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.1.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": "tsx src/cli.ts --version"
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
  }