badgr-cli 1.0.1 → 1.0.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 +6 -6
- package/package.json +11 -3
- 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 +1 -2
- package/src/commands/serve.js +1 -2
- 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
|
@@ -55,7 +55,7 @@ badgr serve meta-llama/Llama-3.1-8B-Instruct --gpu L40S --region EU
|
|
|
55
55
|
| Flag | Default | Description |
|
|
56
56
|
|------|---------|-------------|
|
|
57
57
|
| `--gpu <type>` | RTX_4090 | GPU: RTX_4090, L40S, A6000, A100, H100 |
|
|
58
|
-
| `--region
|
|
58
|
+
| `--region <region>` | — | Optional region preference. If omitted, Badgr chooses best available capacity. |
|
|
59
59
|
| `--count <n>` | 1 | Number of GPUs (1–8) |
|
|
60
60
|
| `--max-price <$/hr>` | — | Hard spend cap per GPU-hour |
|
|
61
61
|
| `--dry-run` | — | Preview routing without provisioning |
|
|
@@ -73,7 +73,7 @@ badgr run python train.py --gpu A100 --env HF_TOKEN=$HF_TOKEN
|
|
|
73
73
|
| `--gpu <type>` | RTX_4090 | GPU type |
|
|
74
74
|
| `--image <img>` | python:3.11-slim | Docker image |
|
|
75
75
|
| `--env KEY=VALUE` | — | Environment variable (repeatable) |
|
|
76
|
-
| `--region
|
|
76
|
+
| `--region <region>` | — | Optional region preference. If omitted, Badgr chooses best available capacity. |
|
|
77
77
|
| `--max-price <$/hr>` | — | Hard spend cap per GPU-hour |
|
|
78
78
|
| `--detach` | — | Launch and return immediately |
|
|
79
79
|
|
|
@@ -83,7 +83,7 @@ badgr run python train.py --gpu A100 --env HF_TOKEN=$HF_TOKEN
|
|
|
83
83
|
|
|
84
84
|
Badgr automatically searches available GPU capacity across its verified compute network.
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
Badgr automatically searches verified GPU capacity and chooses the best eligible route for your GPU type, workload, price cap, and optional region preference.
|
|
87
87
|
|
|
88
88
|
Preview before provisioning:
|
|
89
89
|
|
|
@@ -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
|
|
|
@@ -135,7 +135,7 @@ const client = new OpenAI({
|
|
|
135
135
|
|
|
136
136
|
## GPU options
|
|
137
137
|
|
|
138
|
-
| Flag value | GPU | VRAM |
|
|
138
|
+
| Flag value | GPU | VRAM | Estimated Badgr price/hr |
|
|
139
139
|
|-----------|-----|------|-------------|
|
|
140
140
|
| RTX_4090 | NVIDIA RTX 4090 | 24 GB | $0.65–0.89 |
|
|
141
141
|
| L40S | NVIDIA L40S | 48 GB | $1.10–1.40 |
|
|
@@ -143,7 +143,7 @@ const client = new OpenAI({
|
|
|
143
143
|
| A100 | NVIDIA A100 | 80 GB | $1.20–1.50 |
|
|
144
144
|
| H100 | NVIDIA H100 | 80 GB | $2.80–3.10 |
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
Prices shown are estimated Badgr rates. Final $/GPU-hour is confirmed before provisioning and may vary by GPU type, availability, region, workload, and runtime.
|
|
147
147
|
|
|
148
148
|
---
|
|
149
149
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "badgr-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Badgr — run or serve GPU workloads from one command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"badgr": "
|
|
7
|
+
"badgr": "src/badgr.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node src/badgr.js",
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=18.0.0"
|
|
23
23
|
},
|
|
24
|
-
"keywords": [
|
|
24
|
+
"keywords": [
|
|
25
|
+
"gpu",
|
|
26
|
+
"cli",
|
|
27
|
+
"ai",
|
|
28
|
+
"compute",
|
|
29
|
+
"modal",
|
|
30
|
+
"gateway",
|
|
31
|
+
"openai"
|
|
32
|
+
],
|
|
25
33
|
"license": "MIT"
|
|
26
34
|
}
|
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 {
|
|
@@ -134,7 +134,6 @@ export async function runCommand(config, args, chalk) {
|
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
console.log(chalk.bold(`\n Job ID: ${chalk.cyan(dep.deployment_id)}`));
|
|
137
|
-
console.log(` ${chalk.bold('Provider:')} ${dep.provider}`);
|
|
138
137
|
console.log(` ${chalk.bold('GPU:')} ${dep.gpu_type} × ${dep.gpu_count}`);
|
|
139
138
|
if (dep.cost_per_hour > 0) console.log(` ${chalk.bold('Rate:')} $${dep.cost_per_hour.toFixed(2)}/hr`);
|
|
140
139
|
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 {
|
|
@@ -144,7 +144,6 @@ export async function serveCommand(config, args, chalk) {
|
|
|
144
144
|
console.log(` ${chalk.bold('Deployment:')} ${chalk.cyan(dep.deployment_id)}`);
|
|
145
145
|
console.log(` ${chalk.bold('Model:')} ${dep.model || model}`);
|
|
146
146
|
console.log(` ${chalk.bold('GPU:')} ${dep.gpu_type} × ${dep.gpu_count}`);
|
|
147
|
-
console.log(` ${chalk.bold('Provider:')} ${dep.provider}`);
|
|
148
147
|
console.log(` ${chalk.bold('Endpoint:')} ${chalk.cyan(endpointUrl)}`);
|
|
149
148
|
if (dep.cost_per_hour > 0) console.log(` ${chalk.bold('Rate:')} $${dep.cost_per_hour.toFixed(2)}/hr`);
|
|
150
149
|
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(() => {
|