badgr-cli 1.0.45 → 1.0.47

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.45",
3
+ "version": "1.0.47",
4
4
  "description": "Badgr — run or serve GPU workloads from one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,9 @@
14
14
  "dependencies": {
15
15
  "@inquirer/prompts": "^8.5.2",
16
16
  "archiver": "^7.0.1",
17
- "chalk": "^5.3.0"
17
+ "chalk": "^5.3.0",
18
+ "js-yaml": "^4.1.0",
19
+ "tar": "^7.4.3"
18
20
  },
19
21
  "devDependencies": {
20
22
  "vitest": "^4.1.8"
package/src/api.js CHANGED
@@ -97,6 +97,36 @@ export async function callApi(path, { method = 'GET', apiKey, baseUrl, body, tim
97
97
  return res.json();
98
98
  }
99
99
 
100
+ // ---- Uploads (POST /v1/uploads — generic blob store) ------------------------
101
+
102
+ /**
103
+ * Upload a blob (project zip, dataset, input archive, ...) to the generic
104
+ * /v1/uploads store. Returns the parsed JSON response ({ upload_id,
105
+ * code_uri, expires_in }) — callers pick whichever field they need.
106
+ *
107
+ * Uses Node's built-in FormData/Blob/fetch (Node >=18) rather than a
108
+ * multipart HTTP library — no extra dependency, and fetch derives the
109
+ * multipart boundary/Content-Type from the FormData body automatically
110
+ * (setting Content-Type manually breaks the boundary).
111
+ */
112
+ export async function uploadBlob(config, { data, filename, contentType }) {
113
+ const form = new FormData();
114
+ const blob = contentType ? new Blob([data], { type: contentType }) : new Blob([data]);
115
+ form.append('file', blob, filename);
116
+
117
+ const baseUrl = config.baseUrl.replace(/\/v1\/?$/, '');
118
+ const res = await fetch(`${baseUrl}/v1/uploads`, {
119
+ method: 'POST',
120
+ body: form,
121
+ headers: { Authorization: `Bearer ${config.apiKey}` },
122
+ });
123
+ if (!res.ok) {
124
+ const text = await res.text().catch(() => '');
125
+ throw new Error(`Upload failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ''}`);
126
+ }
127
+ return res.json();
128
+ }
129
+
100
130
  // ---- Core: run & serve (POST /v1/run, POST /v1/serve) ----------------------
101
131
 
102
132
  export function runJob(config, body) {
@@ -168,6 +198,33 @@ export async function terminateDeployment(config, deploymentId) {
168
198
  throw lastErr;
169
199
  }
170
200
 
201
+ export function restartDeployment(config, deploymentId) {
202
+ return callApi(`/deployments/${deploymentId}/restart`, {
203
+ method: 'POST',
204
+ apiKey: config.apiKey,
205
+ baseUrl: config.baseUrl,
206
+ timeoutMs: 30_000,
207
+ });
208
+ }
209
+
210
+ export function rerunDeployment(config, deploymentId) {
211
+ return callApi(`/deployments/${deploymentId}/rerun`, {
212
+ method: 'POST',
213
+ apiKey: config.apiKey,
214
+ baseUrl: config.baseUrl,
215
+ timeoutMs: 30_000,
216
+ });
217
+ }
218
+
219
+ export function heartbeatDeployment(config, deploymentId) {
220
+ return callApi(`/deployments/${deploymentId}/heartbeat`, {
221
+ method: 'POST',
222
+ apiKey: config.apiKey,
223
+ baseUrl: config.baseUrl,
224
+ timeoutMs: 10_000,
225
+ });
226
+ }
227
+
171
228
  export function getDeploymentLogs(config, deploymentId) {
172
229
  return callApi(`/deployments/${deploymentId}/logs`, {
173
230
  apiKey: config.apiKey,
package/src/badgr.js CHANGED
@@ -19,8 +19,12 @@ import { transcribeCommand } from './commands/transcribe.js';
19
19
  import { embedCommand } from './commands/embed.js';
20
20
  import { templateCommand } from './commands/template.js';
21
21
  import { workloadCommand } from './commands/workload.js';
22
+ import { batchCommand } from './commands/batch.js';
22
23
  import { workspaceCommand } from './commands/workspace.js';
23
24
  import { detectCommand } from './commands/detect.js';
25
+ import { restartCommand } from './commands/restart.js';
26
+ import { rerunCommand } from './commands/rerun.js';
27
+ import { heartbeatCommand } from './commands/heartbeat.js';
24
28
 
25
29
  const HELP = `
26
30
  ${chalk.bold('badgr')} — run or serve GPU workloads from one command
@@ -34,6 +38,9 @@ ${chalk.bold('COMMANDS')}
34
38
  ${chalk.cyan('badgr status')} Show what's running and what's billing
35
39
  ${chalk.cyan('badgr logs <id>')} Stream logs for a running job or endpoint
36
40
  ${chalk.cyan('badgr down <id>')} Stop a deployment and end billing
41
+ ${chalk.cyan('badgr restart <id>')} Relaunch an endpoint with the same config and API key
42
+ ${chalk.cyan('badgr rerun <id>')} Replay a past job or endpoint with its exact original spec
43
+ ${chalk.cyan('badgr heartbeat <id>')} Reset an endpoint's idle-timeout clock
37
44
  ${chalk.cyan('badgr receipts')} Show cost history
38
45
  ${chalk.cyan('badgr test')} Run an end-to-end test (provision → run → teardown)
39
46
  ${chalk.cyan('badgr capacity')} Check what GPU capacity is available right now
@@ -42,6 +49,11 @@ ${chalk.bold('COMMANDS')}
42
49
  ${chalk.cyan('badgr workload run <name>')} Rerun a saved workload
43
50
  ${chalk.cyan('badgr workspace list')} List workspace trackers (job history + cost per named context)
44
51
  ${chalk.cyan('badgr workspace create')} Create a workspace tracker (link jobs to a named storage path)
52
+ ${chalk.cyan('badgr batch run <workload.yml>')} Run a generic containerized batch job with artifact capture
53
+ ${chalk.cyan('badgr batch status <run_id>')} Show status, failure reason, cost, teardown
54
+ ${chalk.cyan('badgr batch artifacts <run_id>')} Download and extract output artifacts
55
+ ${chalk.cyan('badgr batch receipt <run_id>')} Show the full batch receipt
56
+ ${chalk.cyan('badgr batch compare <a> <b>')} Compare success_metric between two runs
45
57
 
46
58
  ${chalk.bold('SHORTCUTS')} ${chalk.dim('(wrappers around run / serve for common workloads)')}
47
59
  ${chalk.cyan('badgr comfyui run <workflow.json>')} Launch ComfyUI, return endpoint URL
@@ -89,6 +101,11 @@ ${chalk.bold('EXAMPLES')}
89
101
  ${chalk.dim('# Generate embeddings:')}
90
102
  badgr embed BAAI/bge-large-en-v1.5 documents.txt --max-cost 2
91
103
 
104
+ ${chalk.dim('# Generic batch workload (CV/video/scientific batch, sim, physical-AI eval):')}
105
+ badgr batch run workload.yml
106
+ badgr batch artifacts dep-abc123
107
+ badgr batch compare dep-abc123 dep-def456
108
+
92
109
  ${chalk.dim('# Tier 2 — marketplace routing, lower-cost options:')}
93
110
  badgr serve meta-llama/Llama-3.1-8B-Instruct --tier 2 --max-cost 10
94
111
 
@@ -158,6 +175,9 @@ async function main() {
158
175
  case 'status': return statusCommand(config, rest, chalk);
159
176
  case 'logs': return logsCommand(config, rest, chalk);
160
177
  case 'down': return downCommand(config, rest, chalk);
178
+ case 'restart': return restartCommand(config, rest, chalk);
179
+ case 'rerun': return rerunCommand(config, rest, chalk);
180
+ case 'heartbeat': return heartbeatCommand(config, rest, chalk);
161
181
  case 'receipts': return receiptsCommand(config, rest, chalk);
162
182
  case 'models': return modelsCommand(config, chalk);
163
183
  case 'capacity': return capacityCommand(config, rest, chalk);
@@ -169,6 +189,7 @@ async function main() {
169
189
  case 'embed': return embedCommand(config, rest, chalk);
170
190
  case 'template': return templateCommand(config, rest, chalk);
171
191
  case 'workload': return workloadCommand(config, rest, chalk);
192
+ case 'batch': return batchCommand(config, rest, chalk);
172
193
  case 'workspace': return workspaceCommand(config, rest, chalk);
173
194
  // legacy aliases kept for compatibility
174
195
  case 'up': return upCommand(config, rest, chalk);