badgr-cli 1.0.28 → 1.0.29

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.28",
3
+ "version": "1.0.29",
4
4
  "description": "Badgr, run or serve GPU workloads from one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,12 @@
1
1
  import { findDeployment, listDeployments } from '../store.js';
2
2
  import { requireApiKey } from '../config.js';
3
- import { getDeploymentLogs } from '../api.js';
3
+ import { getDeploymentLogs, callApi } from '../api.js';
4
+
5
+ const TERMINAL_STATUSES = new Set(['succeeded', 'completed', 'failed', 'stopped', 'terminated']);
6
+ const FOLLOW_POLL_MS = 3000;
7
+
8
+ // Lines that carry structured metadata shown elsewhere (status bar in `badgr run`).
9
+ const LOG_META_RE = /^\[dep-[^\]]+\] (status|gpu|region|endpoint|cost|receipt|provider_status|uptime)=/;
4
10
 
5
11
  export async function logsCommand(config, args, chalk) {
6
12
  const idOrName = args.find(a => !a.startsWith('--'));
@@ -26,13 +32,18 @@ export async function logsCommand(config, args, chalk) {
26
32
 
27
33
  console.log(chalk.bold(`\nšŸ“‹ Logs: ${localDep?.name ?? deploymentId}\n`));
28
34
 
35
+ // Fetch and print initial batch of logs.
36
+ const seen = new Set();
29
37
  try {
30
38
  const data = await getDeploymentLogs(config, deploymentId);
31
39
  const lines = data?.logs ?? [];
32
- if (lines.length === 0) {
40
+ if (lines.length === 0 && !follow) {
33
41
  console.log(chalk.dim(' No log lines available yet.\n'));
34
42
  } else {
35
- lines.forEach(l => console.log(` ${chalk.dim(l)}`));
43
+ for (const line of lines) {
44
+ seen.add(line);
45
+ if (!LOG_META_RE.test(line)) console.log(` ${chalk.dim(line)}`);
46
+ }
36
47
  }
37
48
  } catch (err) {
38
49
  console.log(chalk.yellow(` Could not fetch logs: ${err.message}`));
@@ -40,10 +51,51 @@ export async function logsCommand(config, args, chalk) {
40
51
  console.log(chalk.dim(`\n GPU: ${localDep.gpu} Type: ${localDep.type}`));
41
52
  console.log(chalk.dim(` Endpoint: ${localDep.endpointUrl}`));
42
53
  }
54
+ if (!follow) { console.log(); return; }
43
55
  }
44
56
 
45
- if (follow) {
46
- console.log(chalk.yellow('\n --follow: live log streaming not yet supported. Poll with `badgr logs`.\n'));
57
+ if (!follow) { console.log(); return; }
58
+
59
+ // --follow: poll until the deployment reaches a terminal state.
60
+ console.log(chalk.dim(' (following — Ctrl+C to stop)\n'));
61
+
62
+ let stopping = false;
63
+ process.once('SIGINT', () => { stopping = true; });
64
+
65
+ while (!stopping) {
66
+ await new Promise(r => setTimeout(r, FOLLOW_POLL_MS));
67
+ if (stopping) break;
68
+
69
+ let status = null;
70
+ try {
71
+ const dep = await callApi(`/deployments/${deploymentId}`, {
72
+ apiKey: config.apiKey,
73
+ baseUrl: config.baseUrl,
74
+ });
75
+ status = dep?.status ?? null;
76
+ } catch {
77
+ // network blip — keep following
78
+ }
79
+
80
+ try {
81
+ const data = await getDeploymentLogs(config, deploymentId);
82
+ for (const line of (data?.logs ?? [])) {
83
+ if (seen.has(line)) continue;
84
+ seen.add(line);
85
+ if (!LOG_META_RE.test(line)) {
86
+ const isErr = /^error\b/i.test(line) || /Error response from daemon/i.test(line);
87
+ console.log(` ${isErr ? chalk.red(line) : chalk.dim(line)}`);
88
+ }
89
+ }
90
+ } catch {
91
+ // logs endpoint temporarily unavailable
92
+ }
93
+
94
+ if (status && TERMINAL_STATUSES.has(status)) {
95
+ console.log(chalk.dim(`\n Job ${status}. No more logs.\n`));
96
+ break;
97
+ }
47
98
  }
99
+
48
100
  console.log();
49
101
  }
@@ -450,12 +450,15 @@ export async function runCommand(config, args, chalk) {
450
450
 
451
451
  const rcptId = dep.receipt_id || generateReceiptId();
452
452
  addReceipt({
453
- receiptId: rcptId,
454
- action: 'badgr run',
455
- deploymentId: dep.deployment_id,
456
- gpu: dep.gpu_type,
457
- status: dep.status,
458
- createdAt: new Date().toISOString(),
453
+ receiptId: rcptId,
454
+ action: 'badgr run',
455
+ deploymentId: dep.deployment_id,
456
+ gpu: dep.gpu_type,
457
+ providerRoute: dep.provider ?? null,
458
+ maxCost: maxCost ?? null,
459
+ maxRuntime: flags.maxRuntime ?? null,
460
+ status: dep.status,
461
+ createdAt: new Date().toISOString(),
459
462
  });
460
463
 
461
464
  console.log();
@@ -506,7 +509,7 @@ export async function runCommand(config, args, chalk) {
506
509
  }
507
510
  const runtimeMs = Date.now() - attachStart;
508
511
  const finalCost = ratePerHour * (runtimeMs / 3_600_000);
509
- updateReceipt(rcptId, { status: reason, runtimeSeconds: Math.round(runtimeMs / 1000), finalCost });
512
+ updateReceipt(rcptId, { status: reason, teardownStatus: 'terminated', runtimeSeconds: Math.round(runtimeMs / 1000), finalCost });
510
513
  console.log(chalk.dim(` Runtime: ${fmtRuntime(runtimeMs)} • Est. cost: $${finalCost.toFixed(4)}`));
511
514
  console.log(chalk.dim(' Job stopped. Billing ended.\n'));
512
515
  process.exit(reason === 'interrupted' ? 0 : 1);
@@ -525,11 +528,12 @@ export async function runCommand(config, args, chalk) {
525
528
 
526
529
  const finalCost = ratePerHour * (runtimeMs / 3_600_000);
527
530
  updateReceipt(rcptId, {
528
- status: finalStatus,
531
+ status: finalStatus,
529
532
  exitCode,
530
533
  runtimeSeconds: Math.round(runtimeMs / 1000),
531
534
  finalCost,
532
535
  failureType,
536
+ teardownStatus: (finalStatus === 'completed' || finalStatus === 'succeeded') ? 'terminated' : 'failed',
533
537
  });
534
538
 
535
539
  console.log(` ${chalk.bold('Runtime:')} ${fmtRuntime(runtimeMs)}`);