badgr-cli 1.0.18 → 1.0.20
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 +1 -1
- package/src/commands/run.js +63 -9
- package/src/commands/serve.js +7 -4
package/package.json
CHANGED
package/src/commands/run.js
CHANGED
|
@@ -18,6 +18,7 @@ export function parseRunArgs(args) {
|
|
|
18
18
|
if (args[i] === '--image') { flags.image = args[++i]; i++; continue; }
|
|
19
19
|
if (args[i] === '--count') { flags.count = parseInt(args[++i], 10); i++; continue; }
|
|
20
20
|
if (args[i] === '--region') { flags.region = args[++i]; i++; continue; }
|
|
21
|
+
if (args[i] === '--tier') { flags.tier = args[++i]; i++; continue; }
|
|
21
22
|
if (args[i] === '--max-price') { flags.maxPrice = parseFloat(args[++i]); i++; continue; }
|
|
22
23
|
if (args[i] === '--name') { flags.name = args[++i]; i++; continue; }
|
|
23
24
|
if (args[i] === '--detach') { flags.detach = true; i++; continue; }
|
|
@@ -81,6 +82,42 @@ function renderStatusBar(chalk, { elapsedMs, ratePerHour, gpuUtil, cpuUtil, maxR
|
|
|
81
82
|
return chalk.dim(' ' + parts.join(' • '));
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
// Wait for status to leave 'starting' (smoke check running on backend).
|
|
86
|
+
// Returns the final dep object with status 'running' or 'failed'.
|
|
87
|
+
async function waitForRunning(config, depId, chalk) {
|
|
88
|
+
const POLL_MS = 3000;
|
|
89
|
+
const TIMEOUT_MS = 120_000; // 2 min max for smoke check
|
|
90
|
+
const startMs = Date.now();
|
|
91
|
+
let dots = 0;
|
|
92
|
+
|
|
93
|
+
process.stdout.write(chalk.dim(' Waiting for container to start'));
|
|
94
|
+
const ticker = setInterval(() => {
|
|
95
|
+
process.stdout.write('.');
|
|
96
|
+
dots++;
|
|
97
|
+
}, 1000);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
while (Date.now() - startMs < TIMEOUT_MS) {
|
|
101
|
+
await new Promise(r => setTimeout(r, POLL_MS));
|
|
102
|
+
const dep = await callApi(`/deployments/${depId}`, {
|
|
103
|
+
apiKey: config.apiKey,
|
|
104
|
+
baseUrl: config.baseUrl,
|
|
105
|
+
});
|
|
106
|
+
if (dep.status !== 'starting') {
|
|
107
|
+
clearInterval(ticker);
|
|
108
|
+
process.stdout.write('\n');
|
|
109
|
+
return dep;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} finally {
|
|
113
|
+
clearInterval(ticker);
|
|
114
|
+
if (dots > 0) process.stdout.write('\n');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Timed out waiting — return whatever we have
|
|
118
|
+
return await callApi(`/deployments/${depId}`, { apiKey: config.apiKey, baseUrl: config.baseUrl });
|
|
119
|
+
}
|
|
120
|
+
|
|
84
121
|
async function attachToJob(config, depId, { chalk, maxRuntimeMs = null, maxCost = null, ratePerHour = 0, onTeardown }) {
|
|
85
122
|
const TERMINAL = new Set(['stopped', 'failed', 'completed']);
|
|
86
123
|
const POLL_MS = 4000;
|
|
@@ -129,14 +166,14 @@ async function attachToJob(config, depId, { chalk, maxRuntimeMs = null, maxCost
|
|
|
129
166
|
if (maxCost !== null && spentSoFar >= maxCost) {
|
|
130
167
|
tearing = true;
|
|
131
168
|
stopTicker();
|
|
132
|
-
onTeardown('max-cost');
|
|
169
|
+
await onTeardown('max-cost');
|
|
133
170
|
return { status: 'timeout', exitCode: null, runtimeMs: elapsedMs, failureType: null };
|
|
134
171
|
}
|
|
135
172
|
|
|
136
173
|
if (maxRuntimeMs !== null && elapsedMs >= maxRuntimeMs) {
|
|
137
174
|
tearing = true;
|
|
138
175
|
stopTicker();
|
|
139
|
-
onTeardown('max-runtime');
|
|
176
|
+
await onTeardown('max-runtime');
|
|
140
177
|
return { status: 'timeout', exitCode: null, runtimeMs: elapsedMs, failureType: null };
|
|
141
178
|
}
|
|
142
179
|
|
|
@@ -158,7 +195,7 @@ async function attachToJob(config, depId, { chalk, maxRuntimeMs = null, maxCost
|
|
|
158
195
|
} else if (consecutiveErrs >= HEARTBEAT_KILL_POLLS) {
|
|
159
196
|
tearing = true;
|
|
160
197
|
stopTicker();
|
|
161
|
-
onTeardown('heartbeat-lost');
|
|
198
|
+
await onTeardown('heartbeat-lost');
|
|
162
199
|
return { status: 'failed', exitCode: null, runtimeMs: elapsedMs, failureType: 'infrastructure' };
|
|
163
200
|
}
|
|
164
201
|
}
|
|
@@ -202,8 +239,9 @@ async function attachToJob(config, depId, { chalk, maxRuntimeMs = null, maxCost
|
|
|
202
239
|
// Skip provider util lines — they're shown in the status bar instead.
|
|
203
240
|
if (/\b(gpu_util|cpu_util|provider_status|uptime)=/.test(line)) continue;
|
|
204
241
|
|
|
242
|
+
const isErrorLine = /^error\b/i.test(line) || /Error response from daemon/i.test(line);
|
|
205
243
|
stopTicker();
|
|
206
|
-
console.log(` ${chalk.dim(line)}`);
|
|
244
|
+
console.log(` ${isErrorLine ? chalk.red(line) : chalk.dim(line)}`);
|
|
207
245
|
startTicker();
|
|
208
246
|
}
|
|
209
247
|
} catch {
|
|
@@ -347,6 +385,7 @@ export async function runCommand(config, args, chalk) {
|
|
|
347
385
|
...(effectiveRegion ? { region: effectiveRegion } : {}),
|
|
348
386
|
max_price_per_hour: flags.maxPrice,
|
|
349
387
|
name: flags.name,
|
|
388
|
+
...(flags.tier ? { tier: flags.tier } : {}),
|
|
350
389
|
};
|
|
351
390
|
}
|
|
352
391
|
|
|
@@ -399,7 +438,7 @@ export async function runCommand(config, args, chalk) {
|
|
|
399
438
|
const d2 = err2.errorData;
|
|
400
439
|
if (d2?.code === 'PROVISIONING_FAILED' || d2?.code === 'PROVIDER_ADAPTER_ERROR') {
|
|
401
440
|
if (d2?.low_cost_provider_failed) {
|
|
402
|
-
console.error(chalk.red(`\n ✗
|
|
441
|
+
console.error(chalk.red(`\n ✗ Tier 2 unavailable. Tier 1 also unavailable. Try again shortly.\n`));
|
|
403
442
|
} else {
|
|
404
443
|
console.error(chalk.red(`\n ✗ Badgr found ${chosen.gpu} capacity but could not start the machine. Please try again.\n`));
|
|
405
444
|
}
|
|
@@ -416,7 +455,7 @@ export async function runCommand(config, args, chalk) {
|
|
|
416
455
|
}
|
|
417
456
|
} else if (d?.code === 'PROVISIONING_FAILED' || d?.code === 'PROVIDER_ADAPTER_ERROR') {
|
|
418
457
|
if (d?.low_cost_provider_failed) {
|
|
419
|
-
console.error(chalk.red(`\n ✗
|
|
458
|
+
console.error(chalk.red(`\n ✗ Tier 2 unavailable. Tier 1 also unavailable. Try again shortly.\n`));
|
|
420
459
|
} else {
|
|
421
460
|
console.error(chalk.red(`\n ✗ Badgr found ${gpu} capacity but could not start the machine. Please try again.\n`));
|
|
422
461
|
}
|
|
@@ -434,8 +473,8 @@ export async function runCommand(config, args, chalk) {
|
|
|
434
473
|
}
|
|
435
474
|
}
|
|
436
475
|
|
|
437
|
-
if (dep.provider_fallback_note === '
|
|
438
|
-
console.log(chalk.yellow(' ℹ
|
|
476
|
+
if (dep.provider_fallback_note === 'tier2_failed_using_tier1') {
|
|
477
|
+
console.log(chalk.yellow(' ℹ Tier 2 unavailable. Running on Tier 1 instead.\n'));
|
|
439
478
|
}
|
|
440
479
|
|
|
441
480
|
const rcptId = dep.receipt_id || generateReceiptId();
|
|
@@ -451,6 +490,7 @@ export async function runCommand(config, args, chalk) {
|
|
|
451
490
|
console.log();
|
|
452
491
|
console.log(chalk.bold(` Job ID: ${chalk.cyan(dep.deployment_id)}`));
|
|
453
492
|
console.log(` ${chalk.bold('GPU:')} ${dep.gpu_type} × ${dep.gpu_count}`);
|
|
493
|
+
if (dep.tier) console.log(` ${chalk.bold('Tier:')} ${dep.tier}`);
|
|
454
494
|
if (dep.cost_per_hour > 0) console.log(` ${chalk.bold('Rate:')} $${dep.cost_per_hour.toFixed(2)}/hr`);
|
|
455
495
|
console.log(` ${chalk.bold('Receipt:')} ${chalk.dim(rcptId)}`);
|
|
456
496
|
|
|
@@ -460,6 +500,20 @@ export async function runCommand(config, args, chalk) {
|
|
|
460
500
|
return;
|
|
461
501
|
}
|
|
462
502
|
|
|
503
|
+
// If the backend is still running the smoke check, wait for it to finish.
|
|
504
|
+
if (dep.status === 'starting') {
|
|
505
|
+
console.log();
|
|
506
|
+
dep = await waitForRunning(config, dep.deployment_id, chalk);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (dep.status === 'failed') {
|
|
510
|
+
console.error(chalk.red('\n ✗ Container failed to start (infrastructure error).\n'));
|
|
511
|
+
console.error(chalk.dim(' The backend retried automatically. All attempts failed.'));
|
|
512
|
+
console.error(chalk.dim(` Contact support with receipt ID: ${rcptId}`));
|
|
513
|
+
console.log();
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
|
|
463
517
|
const ratePerHour = dep.cost_per_hour || 0;
|
|
464
518
|
console.log(chalk.dim('\n ── Live status (Ctrl+C to stop) ─────────────────────────────────\n'));
|
|
465
519
|
|
|
@@ -523,7 +577,7 @@ export async function runCommand(config, args, chalk) {
|
|
|
523
577
|
}
|
|
524
578
|
console.log();
|
|
525
579
|
process.exit(exitCode ?? 1);
|
|
526
|
-
} else {
|
|
580
|
+
} else if (finalStatus === 'completed' && (exitCode === 0 || exitCode === null)) {
|
|
527
581
|
console.log(chalk.green(`\n ✓ Complete\n`));
|
|
528
582
|
}
|
|
529
583
|
}
|
package/src/commands/serve.js
CHANGED
|
@@ -17,6 +17,7 @@ export function parseServeArgs(args) {
|
|
|
17
17
|
if (args[i] === '--gpu') { flags.gpu = args[++i]; i++; continue; }
|
|
18
18
|
if (args[i] === '--count') { flags.count = parseInt(args[++i], 10); i++; continue; }
|
|
19
19
|
if (args[i] === '--region') { flags.region = args[++i]; i++; continue; }
|
|
20
|
+
if (args[i] === '--tier') { flags.tier = args[++i]; i++; continue; }
|
|
20
21
|
if (args[i] === '--max-price') { flags.maxPrice = parseFloat(args[++i]); i++; continue; }
|
|
21
22
|
if (args[i] === '--name') { flags.name = args[++i]; i++; continue; }
|
|
22
23
|
if (args[i] === '--no-wait') { flags.noWait = true; i++; continue; }
|
|
@@ -79,6 +80,7 @@ export async function serveCommand(config, args, chalk) {
|
|
|
79
80
|
...(regionOverride || effectiveRegion ? { region: regionOverride || effectiveRegion } : {}),
|
|
80
81
|
max_price_per_hour: flags.maxPrice,
|
|
81
82
|
name: flags.name,
|
|
83
|
+
...(flags.tier ? { tier: flags.tier } : {}),
|
|
82
84
|
};
|
|
83
85
|
}
|
|
84
86
|
|
|
@@ -125,7 +127,7 @@ export async function serveCommand(config, args, chalk) {
|
|
|
125
127
|
const d2 = err2.errorData;
|
|
126
128
|
if (d2?.code === 'PROVISIONING_FAILED' || d2?.code === 'PROVIDER_ADAPTER_ERROR') {
|
|
127
129
|
if (d2?.low_cost_provider_failed) {
|
|
128
|
-
console.error(chalk.red(`\n ✗
|
|
130
|
+
console.error(chalk.red(`\n ✗ Tier 2 unavailable. Tier 1 also unavailable. Try again shortly.\n`));
|
|
129
131
|
} else {
|
|
130
132
|
console.error(chalk.red(`\n ✗ Badgr found ${chosen.gpu} capacity but could not start the endpoint. Please try again.\n`));
|
|
131
133
|
}
|
|
@@ -137,7 +139,7 @@ export async function serveCommand(config, args, chalk) {
|
|
|
137
139
|
}
|
|
138
140
|
} else if (d?.code === 'PROVISIONING_FAILED' || d?.code === 'PROVIDER_ADAPTER_ERROR') {
|
|
139
141
|
if (d?.low_cost_provider_failed) {
|
|
140
|
-
console.error(chalk.red(`\n ✗
|
|
142
|
+
console.error(chalk.red(`\n ✗ Tier 2 unavailable. Tier 1 also unavailable. Try again shortly.\n`));
|
|
141
143
|
} else {
|
|
142
144
|
console.error(chalk.red(`\n ✗ Badgr found capacity but could not start the endpoint. Please try again.\n`));
|
|
143
145
|
}
|
|
@@ -161,8 +163,8 @@ export async function serveCommand(config, args, chalk) {
|
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
|
|
164
|
-
if (dep.provider_fallback_note === '
|
|
165
|
-
console.log(chalk.yellow(' ℹ
|
|
166
|
+
if (dep.provider_fallback_note === 'tier2_failed_using_tier1') {
|
|
167
|
+
console.log(chalk.yellow(' ℹ Tier 2 unavailable. Running on Tier 1 instead.\n'));
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
addDeployment({
|
|
@@ -215,6 +217,7 @@ export async function serveCommand(config, args, chalk) {
|
|
|
215
217
|
console.log(` ${chalk.bold('Base URL:')} ${chalk.cyan(endpointUrl)}`);
|
|
216
218
|
console.log(` ${chalk.bold('Model:')} ${dep.model || model}`);
|
|
217
219
|
console.log(` ${chalk.bold('GPU:')} ${dep.gpu_type} × ${dep.gpu_count}`);
|
|
220
|
+
if (dep.tier) console.log(` ${chalk.bold('Tier:')} ${dep.tier}`);
|
|
218
221
|
if (dep.cost_per_hour > 0) console.log(` ${chalk.bold('Rate:')} $${dep.cost_per_hour.toFixed(2)}/hr`);
|
|
219
222
|
console.log(` ${chalk.bold('Logs:')} ${chalk.dim(`badgr logs ${dep.deployment_id}`)}`);
|
|
220
223
|
console.log(` ${chalk.bold('Stop billing:')} ${chalk.dim(`badgr down ${dep.deployment_id}`)}`);
|