badgr-cli 1.0.2 → 1.0.4
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 +1 -1
- package/package.json +1 -1
- package/src/api.js +36 -10
- package/src/badgr.js +2 -2
- package/src/commands/down.js +0 -1
- package/src/commands/login.js +4 -4
- package/src/commands/logs.js +1 -1
- package/src/commands/receipts.js +0 -1
- package/src/commands/run.js +4 -3
- package/src/commands/serve.js +4 -3
- package/src/commands/status.js +4 -7
- package/src/commands/up.js +2 -3
- package/tests/config.test.js +1 -1
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ badgr receipts # last 10
|
|
|
102
102
|
badgr receipts 50 # last 50
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
Each receipt includes: receipt ID, GPU type, provisioning latency,
|
|
105
|
+
Each receipt includes: receipt ID, GPU type, provisioning latency, price/hr, retry count, and status.
|
|
106
106
|
|
|
107
107
|
---
|
|
108
108
|
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -1,16 +1,42 @@
|
|
|
1
1
|
export async function callApi(path, { method = 'GET', apiKey, baseUrl, body } = {}) {
|
|
2
2
|
const url = `${baseUrl}${path}`;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
let res;
|
|
4
|
+
try {
|
|
5
|
+
res = await fetch(url, {
|
|
6
|
+
method,
|
|
7
|
+
headers: {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
10
|
+
},
|
|
11
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
12
|
+
});
|
|
13
|
+
} catch (cause) {
|
|
14
|
+
// Network-level failure: DNS, connection refused, timeout, etc.
|
|
15
|
+
const msg = cause?.message ?? String(cause);
|
|
16
|
+
const hint =
|
|
17
|
+
msg.includes('ECONNREFUSED') ? `\n Hint: Connection refused — is the server running at ${baseUrl}?` :
|
|
18
|
+
msg.includes('ENOTFOUND') ? `\n Hint: DNS lookup failed — check your internet connection or BADGR_API_URL` :
|
|
19
|
+
msg.includes('ETIMEDOUT') ? `\n Hint: Request timed out — the server may be overloaded` :
|
|
20
|
+
`\n Hint: Check your network connection and that BADGR_API_URL is correct`;
|
|
21
|
+
throw new Error(`Cannot reach ${url} (${msg})${hint}`);
|
|
22
|
+
}
|
|
11
23
|
if (!res.ok) {
|
|
12
|
-
|
|
13
|
-
|
|
24
|
+
let detail = '';
|
|
25
|
+
try {
|
|
26
|
+
const text = await res.text();
|
|
27
|
+
// Try to extract a readable detail from JSON error bodies
|
|
28
|
+
const json = JSON.parse(text);
|
|
29
|
+
detail = json?.detail ?? json?.message ?? json?.error ?? text;
|
|
30
|
+
} catch {
|
|
31
|
+
detail = res.statusText || '';
|
|
32
|
+
}
|
|
33
|
+
const hint =
|
|
34
|
+
res.status === 401 ? '\n Hint: Invalid or missing API key — run: badgr login' :
|
|
35
|
+
res.status === 403 ? '\n Hint: Access denied — check your API key permissions' :
|
|
36
|
+
res.status === 404 ? `\n Hint: Endpoint not found — check BADGR_API_URL (currently: ${baseUrl})` :
|
|
37
|
+
res.status === 502 || res.status === 503 ? '\n Hint: Server error — no GPU capacity available or backend is down' :
|
|
38
|
+
'';
|
|
39
|
+
throw new Error(`${method} ${path} → HTTP ${res.status}: ${detail}${hint}`);
|
|
14
40
|
}
|
|
15
41
|
return res.json();
|
|
16
42
|
}
|
package/src/badgr.js
CHANGED
|
@@ -55,8 +55,8 @@ ${chalk.bold('OPENAI-COMPATIBLE SERVING')}
|
|
|
55
55
|
${chalk.dim(' client = OpenAI(api_key="sk-...", base_url="https://api.badgr.ai/v1")')}
|
|
56
56
|
${chalk.dim(' client.chat.completions.create(model="dep_xxx", messages=[...])')}
|
|
57
57
|
|
|
58
|
-
${chalk.bold('
|
|
59
|
-
${chalk.dim('
|
|
58
|
+
${chalk.bold('ROUTING')}
|
|
59
|
+
${chalk.dim('Badgr automatically selects best available GPU capacity for your request.')}
|
|
60
60
|
`;
|
|
61
61
|
|
|
62
62
|
async function main() {
|
package/src/commands/down.js
CHANGED
|
@@ -45,7 +45,6 @@ export async function downCommand(config, args, chalk) {
|
|
|
45
45
|
|
|
46
46
|
console.log(chalk.green(`\n✓ Deployment stopped: ${dep.name}\n`));
|
|
47
47
|
console.log(` ${chalk.bold('ID:')} ${dep.deployment_id}`);
|
|
48
|
-
console.log(` ${chalk.bold('Provider:')} ${dep.provider}`);
|
|
49
48
|
console.log(` ${chalk.bold('GPU:')} ${dep.gpu_type} × ${dep.gpu_count}`);
|
|
50
49
|
if (dep.started_at) {
|
|
51
50
|
const uptime = Math.round((Date.now() / 1000 - dep.started_at) / 60);
|
package/src/commands/login.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { input } from '@inquirer/prompts';
|
|
2
2
|
|
|
3
3
|
export async function loginCommand(chalk, saveConfigFn) {
|
|
4
|
-
console.log(chalk.bold('\n🔑
|
|
4
|
+
console.log(chalk.bold('\n🔑 Badgr Login\n'));
|
|
5
5
|
|
|
6
6
|
const apiKey = await input({
|
|
7
|
-
message: 'Enter your
|
|
7
|
+
message: 'Enter your Badgr API key:',
|
|
8
8
|
validate: v => v.trim() ? true : 'API key is required',
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
const baseUrl = await input({
|
|
12
12
|
message: 'API base URL:',
|
|
13
|
-
default: 'https://api.
|
|
13
|
+
default: 'https://api.badgr.ai/v1',
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
const config = saveConfigFn({ apiKey: apiKey.trim(), baseUrl: baseUrl.trim() });
|
|
17
|
-
console.log(chalk.green('\n✓ Logged in — config saved to ~/.
|
|
17
|
+
console.log(chalk.green('\n✓ Logged in — config saved to ~/.badgr/config.json\n'));
|
|
18
18
|
console.log(chalk.dim(` Base URL: ${config.baseUrl}\n`));
|
|
19
19
|
return config;
|
|
20
20
|
}
|
package/src/commands/logs.js
CHANGED
|
@@ -37,7 +37,7 @@ export async function logsCommand(config, args, chalk) {
|
|
|
37
37
|
} catch (err) {
|
|
38
38
|
console.log(chalk.yellow(` Could not fetch logs: ${err.message}`));
|
|
39
39
|
if (localDep) {
|
|
40
|
-
console.log(chalk.dim(`\n
|
|
40
|
+
console.log(chalk.dim(`\n GPU: ${localDep.gpu} Type: ${localDep.type}`));
|
|
41
41
|
console.log(chalk.dim(` Endpoint: ${localDep.endpointUrl}`));
|
|
42
42
|
}
|
|
43
43
|
}
|
package/src/commands/receipts.js
CHANGED
|
@@ -15,7 +15,6 @@ function printReceipt(r, chalk) {
|
|
|
15
15
|
|
|
16
16
|
console.log(` ${chalk.cyan(id)}`);
|
|
17
17
|
console.log(` ${chalk.bold('action:')} ${action}`);
|
|
18
|
-
console.log(` ${chalk.bold('provider:')} ${prov}`);
|
|
19
18
|
if (lat !== undefined) console.log(` ${chalk.bold('latency:')} ${fmtMs(lat)}`);
|
|
20
19
|
if (cost) console.log(` ${chalk.bold('cost:')} $${typeof cost === 'number' ? cost.toFixed(4) : cost}/hr`);
|
|
21
20
|
if (r.retries !== undefined) console.log(` ${chalk.bold('retries:')} ${r.retries}`);
|
package/src/commands/run.js
CHANGED
|
@@ -99,7 +99,7 @@ export async function runCommand(config, args, chalk) {
|
|
|
99
99
|
console.log(` ${chalk.bold('GPU:')} ${gpu}`);
|
|
100
100
|
if (detach) console.log(` ${chalk.dim('(detached — returns immediately)')}`);
|
|
101
101
|
console.log();
|
|
102
|
-
console.log(chalk.dim('
|
|
102
|
+
console.log(chalk.dim(' Finding best available GPU capacity...'));
|
|
103
103
|
|
|
104
104
|
let dep;
|
|
105
105
|
try {
|
|
@@ -118,7 +118,9 @@ export async function runCommand(config, args, chalk) {
|
|
|
118
118
|
},
|
|
119
119
|
});
|
|
120
120
|
} catch (err) {
|
|
121
|
-
console.error(chalk.red(`\n ✗ Job failed to start: ${err.message}
|
|
121
|
+
console.error(chalk.red(`\n ✗ Job failed to start: ${err.message}`));
|
|
122
|
+
console.error(chalk.dim(`\n API URL: ${config.baseUrl}`));
|
|
123
|
+
console.error(chalk.dim(` Config: badgr config Docs: badgr --help\n`));
|
|
122
124
|
process.exit(1);
|
|
123
125
|
}
|
|
124
126
|
|
|
@@ -134,7 +136,6 @@ export async function runCommand(config, args, chalk) {
|
|
|
134
136
|
});
|
|
135
137
|
|
|
136
138
|
console.log(chalk.bold(`\n Job ID: ${chalk.cyan(dep.deployment_id)}`));
|
|
137
|
-
console.log(` ${chalk.bold('Provider:')} ${dep.provider}`);
|
|
138
139
|
console.log(` ${chalk.bold('GPU:')} ${dep.gpu_type} × ${dep.gpu_count}`);
|
|
139
140
|
if (dep.cost_per_hour > 0) console.log(` ${chalk.bold('Rate:')} $${dep.cost_per_hour.toFixed(2)}/hr`);
|
|
140
141
|
console.log(` ${chalk.bold('Receipt:')} ${chalk.dim(rcptId)}`);
|
package/src/commands/serve.js
CHANGED
|
@@ -68,7 +68,7 @@ export async function serveCommand(config, args, chalk) {
|
|
|
68
68
|
console.log(` ${chalk.bold('Model:')} ${model}`);
|
|
69
69
|
console.log(` ${chalk.bold('GPU:')} ${gpu}`);
|
|
70
70
|
console.log();
|
|
71
|
-
console.log(chalk.dim('
|
|
71
|
+
console.log(chalk.dim(' Finding best available GPU capacity...'));
|
|
72
72
|
|
|
73
73
|
let dep;
|
|
74
74
|
try {
|
|
@@ -86,7 +86,9 @@ export async function serveCommand(config, args, chalk) {
|
|
|
86
86
|
},
|
|
87
87
|
});
|
|
88
88
|
} catch (err) {
|
|
89
|
-
console.error(chalk.red(`\n ✗ Serve failed: ${err.message}
|
|
89
|
+
console.error(chalk.red(`\n ✗ Serve failed: ${err.message}`));
|
|
90
|
+
console.error(chalk.dim(`\n API URL: ${config.baseUrl}`));
|
|
91
|
+
console.error(chalk.dim(` Config: badgr config Docs: badgr --help\n`));
|
|
90
92
|
return;
|
|
91
93
|
}
|
|
92
94
|
|
|
@@ -144,7 +146,6 @@ export async function serveCommand(config, args, chalk) {
|
|
|
144
146
|
console.log(` ${chalk.bold('Deployment:')} ${chalk.cyan(dep.deployment_id)}`);
|
|
145
147
|
console.log(` ${chalk.bold('Model:')} ${dep.model || model}`);
|
|
146
148
|
console.log(` ${chalk.bold('GPU:')} ${dep.gpu_type} × ${dep.gpu_count}`);
|
|
147
|
-
console.log(` ${chalk.bold('Provider:')} ${dep.provider}`);
|
|
148
149
|
console.log(` ${chalk.bold('Endpoint:')} ${chalk.cyan(endpointUrl)}`);
|
|
149
150
|
if (dep.cost_per_hour > 0) console.log(` ${chalk.bold('Rate:')} $${dep.cost_per_hour.toFixed(2)}/hr`);
|
|
150
151
|
console.log(`\n ${chalk.bold('Receipt:')} ${chalk.dim(rcptId)}`);
|
package/src/commands/status.js
CHANGED
|
@@ -47,15 +47,14 @@ export async function statusCommand(config, args, chalk) {
|
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const cols = { name: 16, type: 9, gpu: 11,
|
|
50
|
+
const cols = { name: 16, type: 9, gpu: 11, status: 14, price: 9 };
|
|
51
51
|
|
|
52
52
|
const header =
|
|
53
53
|
'Name'.padEnd(cols.name) +
|
|
54
54
|
'Type'.padEnd(cols.type) +
|
|
55
55
|
'GPU'.padEnd(cols.gpu) +
|
|
56
|
-
'Provider'.padEnd(cols.provider) +
|
|
57
56
|
'Status'.padEnd(cols.status) +
|
|
58
|
-
'
|
|
57
|
+
'Price/hr';
|
|
59
58
|
console.log(' ' + chalk.bold(header));
|
|
60
59
|
console.log(' ' + '─'.repeat(Object.values(cols).reduce((a, b) => a + b, 0) + 8));
|
|
61
60
|
|
|
@@ -65,18 +64,16 @@ export async function statusCommand(config, args, chalk) {
|
|
|
65
64
|
: d.status === 'provisioning'
|
|
66
65
|
? chalk.yellow(d.status.padEnd(cols.status))
|
|
67
66
|
: chalk.dim(d.status.padEnd(cols.status));
|
|
68
|
-
const
|
|
67
|
+
const price = d.cost_per_hour > 0 ? `$${d.cost_per_hour.toFixed(2)}` : '—';
|
|
69
68
|
const name = (d.name || d.deployment_id || '').slice(0, cols.name - 1);
|
|
70
|
-
const provider = (d.provider || '—').padEnd(cols.provider);
|
|
71
69
|
const gpu = (d.gpu_type || '—').slice(0, cols.gpu - 1).padEnd(cols.gpu);
|
|
72
70
|
console.log(
|
|
73
71
|
' ' +
|
|
74
72
|
name.padEnd(cols.name) +
|
|
75
73
|
(d.workload_type || '—').padEnd(cols.type) +
|
|
76
74
|
gpu +
|
|
77
|
-
provider +
|
|
78
75
|
statusColor +
|
|
79
|
-
|
|
76
|
+
price
|
|
80
77
|
);
|
|
81
78
|
});
|
|
82
79
|
|
package/src/commands/up.js
CHANGED
|
@@ -29,7 +29,7 @@ function printRoutePlan(plan, chalk) {
|
|
|
29
29
|
});
|
|
30
30
|
console.log();
|
|
31
31
|
console.log(` Estimated range: $${cheapestRate.toFixed(2)}–$${lane2[lane2.length - 1].ratePerHour.toFixed(2)}/hr`);
|
|
32
|
-
console.log(chalk.dim(`
|
|
32
|
+
console.log(chalk.dim(` Estimated Badgr price: ~$${costWithOverhead.toFixed(2)}/hr`));
|
|
33
33
|
}
|
|
34
34
|
console.log();
|
|
35
35
|
console.log(chalk.dim(' Remove --dry-run to provision.'));
|
|
@@ -65,7 +65,7 @@ export async function upCommand(config, args, chalk) {
|
|
|
65
65
|
console.log(` ${'─'.repeat(40)}`);
|
|
66
66
|
specLines(spec).forEach(l => console.log(` ${l}`));
|
|
67
67
|
console.log();
|
|
68
|
-
console.log(chalk.dim('
|
|
68
|
+
console.log(chalk.dim(' Finding best available GPU capacity...'));
|
|
69
69
|
|
|
70
70
|
let dep;
|
|
71
71
|
try {
|
|
@@ -119,7 +119,6 @@ export async function upCommand(config, args, chalk) {
|
|
|
119
119
|
console.log(` ${chalk.bold('Type:')} ${dep.workload_type}`);
|
|
120
120
|
if (dep.model) console.log(` ${chalk.bold('Model:')} ${dep.model}`);
|
|
121
121
|
console.log(` ${chalk.bold('GPU:')} ${dep.gpu_type} × ${dep.gpu_count}`);
|
|
122
|
-
console.log(` ${chalk.bold('Provider:')} ${dep.provider}`);
|
|
123
122
|
console.log(` ${chalk.bold('Endpoint:')} ${chalk.cyan(dep.endpoint_url || config.baseUrl)}`);
|
|
124
123
|
if (dep.cost_per_hour > 0) console.log(` ${chalk.bold('Rate:')} $${dep.cost_per_hour.toFixed(2)}/hr`);
|
|
125
124
|
console.log(`\n ${chalk.bold('Receipt ID:')} ${chalk.dim(dep.receipt_id)}`);
|
package/tests/config.test.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from 'path';
|
|
|
4
4
|
import { rmSync, existsSync } from 'fs';
|
|
5
5
|
import { loadConfig, saveConfig, requireApiKey, DEFAULTS } from '../src/config.js';
|
|
6
6
|
|
|
7
|
-
const tmp = join(tmpdir(), `
|
|
7
|
+
const tmp = join(tmpdir(), `badgr-cli-test-${process.pid}`);
|
|
8
8
|
const testConfigFile = join(tmp, 'config.json');
|
|
9
9
|
|
|
10
10
|
afterEach(() => {
|