badgr-cli 1.0.22 → 1.0.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badgr-cli",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "description": "Badgr, run or serve GPU workloads from one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,6 @@ import readline from 'readline';
2
2
  import { requireApiKey } from '../config.js';
3
3
  import { callApi, terminateDeployment } from '../api.js';
4
4
  import { addReceipt, updateReceipt, generateReceiptId } from '../store.js';
5
- import { rankAlternatives, promptFallback } from '../fallback.js';
6
5
 
7
6
  /**
8
7
  * badgr run python train.py # gpu=auto, attached
@@ -454,56 +453,48 @@ export async function runCommand(config, args, chalk) {
454
453
  } catch (err) {
455
454
  const d = err.errorData;
456
455
  if (d?.code === 'NO_CAPACITY_MATCH') {
457
- if (fallbackMode === 'none') {
458
- console.error(chalk.red(`\n ✗ ${gpu} isn't available right now.\n`));
459
- console.error(chalk.dim(' Pass --fallback closest to auto-select an alternative.'));
460
- process.exit(1);
461
- }
462
-
463
- const pool = [
464
- ...(Array.isArray(d.same_gpu_other_regions) ? d.same_gpu_other_regions : []),
465
- ...(Array.isArray(d.alternatives) ? d.alternatives : []),
466
- ];
467
-
468
- if (pool.length === 0) {
469
- console.error(chalk.red(`\n ✗ ${gpu} isn't available right now and no alternatives were found.\n`));
456
+ if (effectiveTier === '2') {
457
+ console.error(chalk.red('\n ✗ No GPU capacity available right now on any provider.\n'));
470
458
  console.error(chalk.dim(' Try `badgr capacity` to see what\'s available, or try again shortly.'));
471
459
  process.exit(1);
472
460
  }
473
461
 
474
- const ranked = rankAlternatives(gpu, pool, fallbackMode);
475
- const chosen = await promptFallback(gpu, ranked, chalk);
476
-
477
- if (!chosen) {
478
- console.log(chalk.dim('\n Cancelled.\n'));
479
- process.exit(0);
462
+ // Tier 1 (RunPod) is out of capacity — offer budget providers.
463
+ if (process.stdin.isTTY) {
464
+ const answer = await askConfirm(
465
+ `\n RunPod has no suitable capacity. Try budget marketplace GPUs? (Vast.ai, Salad) [${chalk.bold('Enter')}/${chalk.bold('q')}]: `
466
+ );
467
+ if (answer.toLowerCase() === 'q') {
468
+ console.log(chalk.dim('\n Cancelled.\n'));
469
+ process.exit(0);
470
+ }
471
+ } else {
472
+ console.log(chalk.dim('\n RunPod capacity unavailable — falling back to budget providers...\n'));
480
473
  }
481
474
 
482
- console.log(chalk.dim(`\n Trying ${chosen.gpu} in ${chosen.region}...\n`));
483
-
475
+ console.log(chalk.dim(' Searching budget providers (Vast.ai, Salad)...'));
484
476
  try {
485
477
  dep = await callApi('/run', {
486
478
  method: 'POST',
487
479
  apiKey: config.apiKey,
488
480
  baseUrl: config.baseUrl,
489
- body: buildBody(chosen.gpu, chosen.region),
481
+ body: { ...buildBody(), tier: '2' },
490
482
  });
491
483
  } catch (err2) {
492
484
  const d2 = err2.errorData;
493
- if (d2?.code === 'PROVISIONING_FAILED' || d2?.code === 'PROVIDER_ADAPTER_ERROR') {
494
- if (d2?.low_cost_provider_failed) {
495
- console.error(chalk.red(`\n Tier 2 unavailable. Tier 1 also unavailable. Try again shortly.\n`));
496
- } else {
497
- console.error(chalk.red(`\n ✗ Badgr found ${chosen.gpu} capacity but could not start the machine. Please try again.\n`));
498
- }
485
+ if (d2?.code === 'NO_CAPACITY_MATCH') {
486
+ console.error(chalk.red('\n ✗ No GPU capacity available right now on any provider.\n'));
487
+ console.error(chalk.dim(' Try `badgr capacity` to see what\'s available, or try again shortly.'));
488
+ } else if (d2?.code === 'PROVISIONING_FAILED' || d2?.code === 'PROVIDER_ADAPTER_ERROR') {
489
+ console.error(chalk.red(`\n ✗ Budget provider found capacity but could not start the machine. Please try again.\n`));
499
490
  if (process.env.BADGR_DEBUG === '1' || process.env.BADGR_DEBUG === 'true') {
500
491
  if (d2?.debug_error) console.error(chalk.dim(` Provider detail: ${d2.debug_error}`));
492
+ } else {
493
+ console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr run … for a full trace.\n`));
501
494
  }
502
495
  } else {
503
- console.error(chalk.red(`\n ✗ Job failed to start on ${chosen.gpu}: ${err2.message}\n`));
504
- }
505
- if (!(process.env.BADGR_DEBUG === '1' || process.env.BADGR_DEBUG === 'true')) {
506
- console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr run … for a full trace.\n`));
496
+ console.error(chalk.red(`\n ✗ Could not start job on budget providers: ${err2.message}\n`));
497
+ console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr run … for a full trace.`));
507
498
  }
508
499
  process.exit(1);
509
500
  }
@@ -1,7 +1,12 @@
1
+ import readline from 'readline';
1
2
  import { requireApiKey } from '../config.js';
2
3
  import { callApi } from '../api.js';
3
4
  import { addDeployment, addReceipt, updateReceipt, generateReceiptId } from '../store.js';
4
- import { rankAlternatives, promptFallback } from '../fallback.js';
5
+
6
+ function askConfirm(prompt) {
7
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
8
+ return new Promise(resolve => rl.question(prompt, ans => { rl.close(); resolve(ans.trim()); }));
9
+ }
5
10
 
6
11
  /**
7
12
  * badgr serve meta-llama/Llama-3.1-8B-Instruct
@@ -107,46 +112,45 @@ export async function serveCommand(config, args, chalk) {
107
112
  } catch (err) {
108
113
  const d = err.errorData;
109
114
  if (d?.code === 'NO_CAPACITY_MATCH') {
110
- const pool = [
111
- ...(Array.isArray(d.same_gpu_other_regions) ? d.same_gpu_other_regions : []),
112
- ...(Array.isArray(d.alternatives) ? d.alternatives : []),
113
- ];
114
-
115
- if (pool.length === 0) {
116
- console.error(chalk.red(`\n ✗ No GPU capacity available right now.\n`));
115
+ if (effectiveTier === '2') {
116
+ console.error(chalk.red('\n ✗ No GPU capacity available right now on any provider.\n'));
117
117
  console.error(chalk.dim(' Run `badgr capacity` to see alternatives, or try again shortly.'));
118
118
  process.exit(1);
119
119
  }
120
120
 
121
- const ranked = rankAlternatives(gpuLabel, pool, 'closest');
122
- const chosen = await promptFallback(gpuLabel, ranked, chalk);
123
-
124
- if (!chosen) {
125
- console.log(chalk.dim('\n Cancelled.\n'));
126
- process.exit(0);
121
+ // Tier 1 (RunPod) out of capacity — offer budget providers.
122
+ if (process.stdin.isTTY) {
123
+ const answer = await askConfirm(
124
+ `\n RunPod has no suitable capacity. Try budget marketplace GPUs? (Vast.ai, Salad) [${chalk.bold('Enter')}/${chalk.bold('q')}]: `
125
+ );
126
+ if (answer.toLowerCase() === 'q') {
127
+ console.log(chalk.dim('\n Cancelled.\n'));
128
+ process.exit(0);
129
+ }
130
+ } else {
131
+ console.log(chalk.dim('\n RunPod capacity unavailable — falling back to budget providers...\n'));
127
132
  }
128
133
 
129
- console.log(chalk.dim(`\n Trying ${chosen.gpu} in ${chosen.region}...\n`));
130
-
134
+ console.log(chalk.dim(' Searching budget providers (Vast.ai, Salad)...'));
131
135
  try {
132
136
  dep = await callApi('/serve', {
133
137
  method: 'POST',
134
138
  apiKey: config.apiKey,
135
139
  baseUrl: config.baseUrl,
136
- body: buildBody(chosen.gpu, chosen.region),
140
+ body: { ...buildBody(), tier: '2' },
137
141
  });
138
142
  } catch (err2) {
139
143
  const d2 = err2.errorData;
140
- if (d2?.code === 'PROVISIONING_FAILED' || d2?.code === 'PROVIDER_ADAPTER_ERROR') {
141
- if (d2?.low_cost_provider_failed) {
142
- console.error(chalk.red(`\n Tier 2 unavailable. Tier 1 also unavailable. Try again shortly.\n`));
143
- } else {
144
- console.error(chalk.red(`\n ✗ Badgr found ${chosen.gpu} capacity but could not start the endpoint. Please try again.\n`));
145
- }
144
+ if (d2?.code === 'NO_CAPACITY_MATCH') {
145
+ console.error(chalk.red('\n ✗ No GPU capacity available right now on any provider.\n'));
146
+ console.error(chalk.dim(' Run `badgr capacity` to see alternatives, or try again shortly.'));
147
+ } else if (d2?.code === 'PROVISIONING_FAILED' || d2?.code === 'PROVIDER_ADAPTER_ERROR') {
148
+ console.error(chalk.red(`\n ✗ Budget provider found capacity but could not start the endpoint. Please try again.\n`));
149
+ console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr serve … for a full trace.\n`));
146
150
  } else {
147
- console.error(chalk.red(`\n ✗ Failed to start endpoint on ${chosen.gpu}: ${err2.message}\n`));
151
+ console.error(chalk.red(`\n ✗ Failed to start endpoint on budget providers: ${err2.message}\n`));
152
+ console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr serve … for a full trace.\n`));
148
153
  }
149
- console.error(chalk.dim(` Run BADGR_DEBUG=1 badgr serve … for a full trace.\n`));
150
154
  process.exit(1);
151
155
  }
152
156
  } else if (d?.code === 'PROVISIONING_FAILED' || d?.code === 'PROVIDER_ADAPTER_ERROR') {
@@ -47,7 +47,7 @@ export async function statusCommand(config, args, chalk) {
47
47
 
48
48
  console.log(chalk.bold('\nRunning now:\n'));
49
49
  for (const d of running) {
50
- const id = d.name || d.deployment_id;
50
+ const id = d.deployment_id || d.name;
51
51
  const type = d.workload_type === 'endpoint' ? 'endpoint' : 'job';
52
52
  const gpu = d.gpu_type || '—';
53
53
  const rate = d.cost_per_hour > 0 ? chalk.yellow(`$${d.cost_per_hour.toFixed(2)}/hr`) : '';
@@ -74,7 +74,7 @@ export async function statusCommand(config, args, chalk) {
74
74
 
75
75
  console.log(chalk.bold('Stop billing:\n'));
76
76
  for (const d of running) {
77
- const id = d.name || d.deployment_id;
77
+ const id = d.deployment_id || d.name;
78
78
  console.log(` ${chalk.cyan(`badgr down ${id}`)}`);
79
79
  }
80
80
  console.log();
@@ -59,32 +59,50 @@ export async function testCommand(config, chalk) {
59
59
  let depId;
60
60
 
61
61
  // ── 1. Provision ─────────────────────────────────────────────────────────
62
- process.stdout.write(chalk.dim(' Provisioning GPU...'));
62
+ process.stdout.write(chalk.dim(' Provisioning GPU (RunPod)...'));
63
63
  let dep;
64
+ const baseBody = {
65
+ command: TEST_COMMAND,
66
+ image: TEST_IMAGE,
67
+ gpu: 'RTX_3080',
68
+ max_price_per_hour: TEST_MAX_PRICE,
69
+ };
64
70
  try {
65
71
  dep = await callApi('/run', {
66
72
  method: 'POST',
67
73
  apiKey: config.apiKey,
68
74
  baseUrl: config.baseUrl,
69
- body: {
70
- command: TEST_COMMAND,
71
- image: TEST_IMAGE,
72
- tier: '1',
73
- // smoke_test workload → cheapest reliable GPU with ≥4 GB VRAM
74
- gpu: 'RTX_3080',
75
- max_price_per_hour: TEST_MAX_PRICE,
76
- },
75
+ body: { ...baseBody, tier: '1' },
77
76
  });
78
- depId = dep.deployment_id;
79
- process.stdout.write('\n');
80
- step(chalk, true, 'Provisioned', `${dep.deployment_id} on ${dep.gpu_type}`);
81
77
  } catch (err) {
82
- process.stdout.write('\n');
83
- step(chalk, false, 'Provisioned', err.message);
84
- console.log();
85
- console.error(chalk.red(' Test failed — could not provision GPU.\n'));
86
- process.exit(1);
78
+ if (err.errorData?.code === 'NO_CAPACITY_MATCH') {
79
+ process.stdout.write('\n');
80
+ process.stdout.write(chalk.dim(' RunPod unavailable, trying budget providers...'));
81
+ try {
82
+ dep = await callApi('/run', {
83
+ method: 'POST',
84
+ apiKey: config.apiKey,
85
+ baseUrl: config.baseUrl,
86
+ body: { ...baseBody, tier: '2' },
87
+ });
88
+ } catch (err2) {
89
+ process.stdout.write('\n');
90
+ step(chalk, false, 'Provisioned', err2.message);
91
+ console.log();
92
+ console.error(chalk.red(' Test failed — no GPU capacity available on any provider.\n'));
93
+ process.exit(1);
94
+ }
95
+ } else {
96
+ process.stdout.write('\n');
97
+ step(chalk, false, 'Provisioned', err.message);
98
+ console.log();
99
+ console.error(chalk.red(' Test failed — could not provision GPU.\n'));
100
+ process.exit(1);
101
+ }
87
102
  }
103
+ depId = dep.deployment_id;
104
+ process.stdout.write('\n');
105
+ step(chalk, true, 'Provisioned', `${dep.deployment_id} on ${dep.gpu_type}`);
88
106
 
89
107
  // ── 2. Container started ─────────────────────────────────────────────────
90
108
  process.stdout.write(chalk.dim(' Waiting for container to start...'));