badgr-cli 1.0.18 → 1.0.19

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.18",
3
+ "version": "1.0.19",
4
4
  "description": "Badgr, run or serve GPU workloads from one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -81,6 +81,42 @@ function renderStatusBar(chalk, { elapsedMs, ratePerHour, gpuUtil, cpuUtil, maxR
81
81
  return chalk.dim(' ' + parts.join(' • '));
82
82
  }
83
83
 
84
+ // Wait for status to leave 'starting' (smoke check running on backend).
85
+ // Returns the final dep object with status 'running' or 'failed'.
86
+ async function waitForRunning(config, depId, chalk) {
87
+ const POLL_MS = 3000;
88
+ const TIMEOUT_MS = 120_000; // 2 min max for smoke check
89
+ const startMs = Date.now();
90
+ let dots = 0;
91
+
92
+ process.stdout.write(chalk.dim(' Waiting for container to start'));
93
+ const ticker = setInterval(() => {
94
+ process.stdout.write('.');
95
+ dots++;
96
+ }, 1000);
97
+
98
+ try {
99
+ while (Date.now() - startMs < TIMEOUT_MS) {
100
+ await new Promise(r => setTimeout(r, POLL_MS));
101
+ const dep = await callApi(`/deployments/${depId}`, {
102
+ apiKey: config.apiKey,
103
+ baseUrl: config.baseUrl,
104
+ });
105
+ if (dep.status !== 'starting') {
106
+ clearInterval(ticker);
107
+ process.stdout.write('\n');
108
+ return dep;
109
+ }
110
+ }
111
+ } finally {
112
+ clearInterval(ticker);
113
+ if (dots > 0) process.stdout.write('\n');
114
+ }
115
+
116
+ // Timed out waiting — return whatever we have
117
+ return await callApi(`/deployments/${depId}`, { apiKey: config.apiKey, baseUrl: config.baseUrl });
118
+ }
119
+
84
120
  async function attachToJob(config, depId, { chalk, maxRuntimeMs = null, maxCost = null, ratePerHour = 0, onTeardown }) {
85
121
  const TERMINAL = new Set(['stopped', 'failed', 'completed']);
86
122
  const POLL_MS = 4000;
@@ -129,14 +165,14 @@ async function attachToJob(config, depId, { chalk, maxRuntimeMs = null, maxCost
129
165
  if (maxCost !== null && spentSoFar >= maxCost) {
130
166
  tearing = true;
131
167
  stopTicker();
132
- onTeardown('max-cost');
168
+ await onTeardown('max-cost');
133
169
  return { status: 'timeout', exitCode: null, runtimeMs: elapsedMs, failureType: null };
134
170
  }
135
171
 
136
172
  if (maxRuntimeMs !== null && elapsedMs >= maxRuntimeMs) {
137
173
  tearing = true;
138
174
  stopTicker();
139
- onTeardown('max-runtime');
175
+ await onTeardown('max-runtime');
140
176
  return { status: 'timeout', exitCode: null, runtimeMs: elapsedMs, failureType: null };
141
177
  }
142
178
 
@@ -158,7 +194,7 @@ async function attachToJob(config, depId, { chalk, maxRuntimeMs = null, maxCost
158
194
  } else if (consecutiveErrs >= HEARTBEAT_KILL_POLLS) {
159
195
  tearing = true;
160
196
  stopTicker();
161
- onTeardown('heartbeat-lost');
197
+ await onTeardown('heartbeat-lost');
162
198
  return { status: 'failed', exitCode: null, runtimeMs: elapsedMs, failureType: 'infrastructure' };
163
199
  }
164
200
  }
@@ -202,8 +238,9 @@ async function attachToJob(config, depId, { chalk, maxRuntimeMs = null, maxCost
202
238
  // Skip provider util lines — they're shown in the status bar instead.
203
239
  if (/\b(gpu_util|cpu_util|provider_status|uptime)=/.test(line)) continue;
204
240
 
241
+ const isErrorLine = /^error\b/i.test(line) || /Error response from daemon/i.test(line);
205
242
  stopTicker();
206
- console.log(` ${chalk.dim(line)}`);
243
+ console.log(` ${isErrorLine ? chalk.red(line) : chalk.dim(line)}`);
207
244
  startTicker();
208
245
  }
209
246
  } catch {
@@ -460,6 +497,20 @@ export async function runCommand(config, args, chalk) {
460
497
  return;
461
498
  }
462
499
 
500
+ // If the backend is still running the smoke check, wait for it to finish.
501
+ if (dep.status === 'starting') {
502
+ console.log();
503
+ dep = await waitForRunning(config, dep.deployment_id, chalk);
504
+ }
505
+
506
+ if (dep.status === 'failed') {
507
+ console.error(chalk.red('\n ✗ Container failed to start (infrastructure error).\n'));
508
+ console.error(chalk.dim(' The backend retried automatically. All attempts failed.'));
509
+ console.error(chalk.dim(` Contact support with receipt ID: ${rcptId}`));
510
+ console.log();
511
+ process.exit(1);
512
+ }
513
+
463
514
  const ratePerHour = dep.cost_per_hour || 0;
464
515
  console.log(chalk.dim('\n ── Live status (Ctrl+C to stop) ─────────────────────────────────\n'));
465
516
 
@@ -523,7 +574,7 @@ export async function runCommand(config, args, chalk) {
523
574
  }
524
575
  console.log();
525
576
  process.exit(exitCode ?? 1);
526
- } else {
577
+ } else if (finalStatus === 'completed' && (exitCode === 0 || exitCode === null)) {
527
578
  console.log(chalk.green(`\n ✓ Complete\n`));
528
579
  }
529
580
  }