badgr-cli 1.1.3 → 1.1.5

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/README.md CHANGED
@@ -13,7 +13,7 @@ Badgr supports many GPU workloads through two commands: `serve` for persistent e
13
13
  npm install -g badgr-cli
14
14
  ```
15
15
 
16
- **Jump to:** [Quick start](#quick-start) · [Coding agents (`badgr launch`)](#coding--testing-agents-badgr-launch) · [Image generation](#also-try-image-generation) · [`badgr doctor`](#something-not-working-badgr-doctor) · [Commands](#commands) · [`serve` options](#badgr-serve-options) · [`run` options](#badgr-run-options) · [Receipts](#receipts) · [OpenAI compatibility](#openai-compatibility) · [GPU options](#gpu-options) · [Advanced](#advanced) · [Requirements](#requirements)
16
+ **Jump to:** [Quick start](#quick-start) · [Coding agents (`badgr launch`)](#coding--testing-agents-badgr-launch) · [Image generation](#also-try-image-generation) · [`badgr doctor`](#something-not-working-badgr-doctor) · [`badgr diagnose`](#diagnose-a-gpu-issue-badgr-diagnose) · [Commands](#commands) · [`serve` options](#badgr-serve-options) · [`run` options](#badgr-run-options) · [Receipts](#receipts) · [OpenAI compatibility](#openai-compatibility) · [GPU options](#gpu-options) · [Advanced](#advanced) · [Requirements](#requirements)
17
17
 
18
18
  ---
19
19
 
@@ -159,21 +159,78 @@ Run `badgr doctor --help` for the full flag list. Details in
159
159
 
160
160
  ---
161
161
 
162
+ ## Badgr Smoke Test: `badgr diagnose`
163
+
164
+ Paste a GitHub issue, a Docker image, a repo URL, a log file, a ComfyUI
165
+ workflow, or a raw conversation — Badgr auto-detects the input, redacts
166
+ secrets, and runs free static checks as part of a Badgr Smoke Test.
167
+ Nothing runs on a GPU without explicit `--approve`.
168
+
169
+ ```bash
170
+ # badgr diagnose IS the Badgr Smoke Test — free, no login, no GPU.
171
+ # Applicable free structural checks run automatically, every time.
172
+ badgr diagnose "https://github.com/org/repo/issues/123"
173
+ badgr diagnose ajayrajtp/vllm_gemma412b:latest
174
+ badgr diagnose ./vllm-error.log
175
+ badgr diagnose "https://github.com/org/repo"
176
+ badgr diagnose workflow.json
177
+
178
+ # Start a real Badgr Run (paid GPU execution) after diagnosis (login required)
179
+ badgr diagnose "https://github.com/org/repo/issues/123" --approve
180
+
181
+ # Resume an existing case, e.g. one shared via a case link
182
+ badgr diagnose repro_xxxxxxxx --approve
183
+ badgr diagnose "https://aibadgr.com/repro/repro_xxxxxxxx" --approve
184
+
185
+ # Machine-readable output
186
+ badgr diagnose "https://github.com/org/repo/issues/123" --json
187
+ ```
188
+
189
+ | Flag | Description |
190
+ |------|-------------|
191
+ | `--approve` | Start a real Badgr Run after diagnosis — actual GPU execution, capped at a hard maximum spend (opens browser sign-in automatically if not logged in) |
192
+ | `--smoke` | Deprecated, no-op. The free mechanical checks this used to gate (syntax, CLI-entrypoint corroboration, referenced-file presence, Docker `ENTRYPOINT`/`CMD` consistency, required env vars, plus a client-side local-file check) now always run as part of plain `badgr diagnose`. Kept only so existing scripts that pass it don't break. Not the same as `badgr run --smoke`, which launches a real, billable GPU job |
193
+ | `--docker <image>` | Force Docker-image intake (override auto-detect) |
194
+ | `--repo <url>` | Force repository intake (override auto-detect) |
195
+ | `--comfyui <path>` | Force ComfyUI workflow intake (override auto-detect) |
196
+ | `--github <url>` | Include a GitHub issue URL found inside pasted text as additional context (opt-in, never fetched automatically) |
197
+ | `--json` | Machine-readable JSON output |
198
+
199
+ Every run prints one status: `NEEDS INFO` (fields still missing) →
200
+ `READY` (complete evidence-backed command, no applicable checks to run) →
201
+ `SMOKE CHECKED` (applicable checks ran, all passed or were skipped) /
202
+ `INVALID` (an applicable check actually failed) → `VERIFIED` (reserved for
203
+ an actual successful Badgr Run — never assigned from diagnosis alone).
204
+ `READY`/`SMOKE CHECKED` results also print the canonical `badgr
205
+ run`/`badgr serve` command `--approve` would run, plus a shareable case
206
+ link — pass that case ID or URL back into `badgr diagnose
207
+ <case_id_or_url> --approve` to resume the same case without re-diagnosing.
208
+
209
+ `badgr run-issue` is an alias for `badgr diagnose`, matching the
210
+ [aibadgr.com/run-issue](https://aibadgr.com/run-issue) Badgr Smoke Test
211
+ web flow.
212
+
213
+ ---
214
+
162
215
  ## Commands
163
216
 
164
217
  ```text
165
218
  login
166
219
  connect
167
220
  doctor
221
+ diagnose
168
222
  run
169
223
  launch
170
- task
224
+ job
171
225
  serve
172
226
  status
173
227
  logs
174
228
  pull
175
229
  artifacts
176
230
  down
231
+ restart
232
+ rerun
233
+ heartbeat
177
234
  receipts
178
235
  test
179
236
  ```
@@ -183,6 +240,7 @@ test
183
240
  | `badgr login` | Save API key to `~/.badgr/config.json` |
184
241
  | `badgr connect <provider>` | Store a provider credential (`anthropic`, `openai`) for `badgr launch` |
185
242
  | `badgr doctor` | Diagnose a GPU workload failure — read-only, no login needed |
243
+ | `badgr diagnose "<input>"` | Run a Badgr Smoke Test on a GitHub issue, Docker image, repo, log, or workflow — free, no GPU until `--approve` |
186
244
  | `badgr run <command>` | Run a one-off GPU job (any container command) |
187
245
  | `badgr launch cline\|claude\|codex\|playwright "<task>"` | Run a coding/testing agent on a CPU VM — image + command auto-selected |
188
246
  | `badgr job <agent> "<instruction>" --check "<cmd>"` | Tracked coding-agent job via `POST /v1/jobs` (type: agent) |
@@ -192,10 +250,13 @@ test
192
250
  | `badgr pull <id>` | Pull a code-editing agent's patch as a local git diff/branch |
193
251
  | `badgr artifacts <id>` | Download non-patch outputs (test reports, screenshots, traces) |
194
252
  | `badgr down <id>` | Terminate a deployment — stops billing immediately |
253
+ | `badgr restart <id>` | Relaunch an endpoint with the same config, on a new deployment ID, keeping its API key |
254
+ | `badgr rerun <id>` | Replay a past job or endpoint with its exact original spec, on a new deployment ID |
255
+ | `badgr heartbeat <id>` | Reset an endpoint's idle-timeout clock (see `--idle-timeout` under [`badgr serve` options](#badgr-serve-options)) |
195
256
  | `badgr receipts [n]` | Cost, route, and retry receipts (default 10) |
196
257
  | `badgr test` | Run an end-to-end test (provision → run → teardown) |
197
258
 
198
- More commands below, under [Advanced](#advanced): `comfyui`, `train`, `transcribe`, `embed`, `workload`, `workspace`, `batch`, `sbatch`, `capacity`, `billing`.
259
+ More commands below, under [Advanced](#advanced): `comfyui`, `train`, `transcribe`, `embed`, `workload`, `workspace`, `batch`, `sbatch`, `capacity`, `billing`, `models`, `template`.
199
260
 
200
261
  `badgr serve` — for anything that needs a persistent endpoint: LLM serving, embeddings, image generation APIs, transcription APIs.
201
262
 
@@ -228,16 +289,38 @@ Each command section below lists only its own extra flags.
228
289
  badgr serve meta-llama/Llama-3.1-8B-Instruct --gpu L40S --region EU
229
290
  ```
230
291
 
292
+ Endpoints bill continuously, so `serve` requires either `--max-cost` or `--persistent` (or `--dry-run` to just preview) — it refuses to start otherwise.
293
+
231
294
  | Flag | Default | Description |
232
295
  |------|---------|-------------|
233
296
  | `--image <img>` | — | Serve a custom container instead of a HuggingFace model |
234
297
  | `--task <task>` | — | vLLM task override, e.g. `embed` for embedding models |
235
- | `--health-path <path>` | auto | Readiness path to poll (auto-detected for ComfyUI `/system_stats`) |
298
+ | `--runtime llama.cpp\|ollama` | vLLM | Serve via a different runtime instead of vLLM |
299
+ | `--hf-repo <repo>` | — | HuggingFace repo for a GGUF file (with `--runtime llama.cpp`), e.g. `org/model-repo` |
300
+ | `--hf-file <file>` | — | GGUF filename within that repo, e.g. `model.gguf` (required with `--runtime llama.cpp`) |
301
+ | `--idle-timeout <min>` | — | Auto-stop after N minutes with no `badgr heartbeat` call — see [`badgr heartbeat`](#badgr-heartbeat) |
302
+ | `--persistent` | off | Run until manually stopped — satisfies the cost-control requirement in place of `--max-cost` |
303
+ | `--check-nodes <n1,n2>` | — | For ComfyUI-shaped images: verify custom nodes are installed after startup |
304
+ | `--health-path <path>` | auto | Readiness path to poll (auto-detected: ComfyUI → `/system_stats`, llama.cpp → `/health`) |
236
305
  | `--no-wait` | off | Skip endpoint health check and return immediately |
306
+ | `--yes` / `-y` | off | Skip duplicate-deployment warning |
307
+ | `--dry-run` | — | Preview the plan (GPU, price) without provisioning |
237
308
  | `--list-aliases` | — | List blessed vLLM model aliases (`qwen-7b`, `llama-8b`, `qwen-coder-7b`) and exit — no provisioning, no API key required |
238
309
 
239
310
  Blessed aliases expand to a full model ID + preset GPU, e.g. `badgr serve qwen-7b` → `Qwen/Qwen2.5-7B-Instruct` on an RTX 4090. Run `badgr serve --list-aliases` to see the current list.
240
311
 
312
+ ```bash
313
+ # Serve a Hugging Face GGUF file via llama.cpp instead of vLLM
314
+ badgr serve --runtime llama.cpp --hf-repo org/model-repo --hf-file model.gguf --max-cost 10
315
+
316
+ # Serve Open WebUI, a chat UI, pointed at a model endpoint
317
+ badgr serve openwebui --model qwen-7b --max-cost 10
318
+ badgr serve openwebui --connect <existing-endpoint-url> # connect to an endpoint you already have
319
+
320
+ # Auto-stop only when idle — requires periodic badgr heartbeat calls to stay up
321
+ badgr serve qwen-7b --idle-timeout 30 --max-cost 10
322
+ ```
323
+
241
324
  ### Model support levels
242
325
 
243
326
  `badgr serve qwen-7b` is the happy path — a tested route with no extra setup. `badgr serve` also accepts any other model ID or a custom container:
@@ -269,10 +352,21 @@ badgr run . --image mycompany/custom:latest --cmd "python train.py" --max-cost 5
269
352
 
270
353
  Badgr zips and uploads the folder (Flow 1) or clones the repo (Flow 2), picks a generic runner, installs deps, runs the command, stores outputs for 48 hours, and tears down the GPU. `--max-cost` is required.
271
354
 
355
+ ```bash
356
+ # No GPU needed — run on a plain CPU VM instead
357
+ badgr run . --cmd "npm test" --no-gpu --max-cost 1
358
+
359
+ # Describe basic compute needs instead of a GPU model — Badgr finds a compatible machine
360
+ badgr run . --cpu 16 --memory 64GB --gpu-memory 24GB --max-cost 5
361
+ ```
362
+
272
363
  | Flag | Default | Description |
273
364
  |------|---------|-------------|
274
365
  | `--cmd <command>` | — | Command to run inside the uploaded project or cloned repo (required for folder/GitHub flows) |
275
- | `--min-vram <GB>` | — | Minimum VRAM in GB — optional constraint for Auto routing |
366
+ | `--min-vram <GB>` | — | Minimum VRAM in GB — optional constraint for Auto routing (alias: `--gpu-memory`) |
367
+ | `--cpu <cores>` | — | Minimum CPU cores (for a CPU-only run) |
368
+ | `--memory <size>` | — | Minimum RAM, e.g. `64GB` |
369
+ | `--no-gpu` | off | Run on a CPU-only VM — no GPU is provisioned (conflicts with `--gpu`/`--min-vram`) |
276
370
  | `--image <img>` | — | Custom Docker image — bypasses the runner |
277
371
  | `--max-runtime <min>` | — | Auto-stop after N minutes |
278
372
  | `--save <name>` | — | Save this job as a named workload after it completes |
@@ -320,13 +414,14 @@ Productized batch image generation — runs a list of prompts through a **blesse
320
414
  ```bash
321
415
  badgr comfyui batch --workflow sdxl-basic --prompts prompts.txt --max-cost 10
322
416
  badgr comfyui batch --workflow sdxl-basic --prompt "a cat on a beach" --prompt "a dog in the park" --max-cost 5
417
+ badgr comfyui batch --workflow flux-basic --prompt "a neon city at night" --max-cost 5
323
418
  ```
324
419
 
325
- Blessed workflows: `sdxl-basic` (SDXL text-to-image, default sampler settings). Max 20 prompts per batch.
420
+ Blessed workflows: `sdxl-basic` (SDXL text-to-image, default sampler settings), `flux-basic` (FLUX.1-schnell text-to-image). Max 20 prompts per batch.
326
421
 
327
422
  | Flag | Default | Description |
328
423
  |------|---------|-------------|
329
- | `--workflow <name>` | — | Blessed workflow ID (required) — currently `sdxl-basic` |
424
+ | `--workflow <name>` | — | Blessed workflow ID (required) — `sdxl-basic` or `flux-basic` |
330
425
  | `--prompts <file>` | — | Text file, one prompt per line |
331
426
  | `--prompt <text>` | — | Inline prompt (repeatable) — combine with `--prompts` if needed |
332
427
  | `--max-runtime <min>` | 60 | Auto-stop after N minutes |
@@ -350,6 +445,37 @@ Each receipt includes runtime, estimated/settled cost, status, retries, teardown
350
445
 
351
446
  ---
352
447
 
448
+ ## Managing a deployment
449
+
450
+ ```bash
451
+ badgr status # what's running and billing
452
+ badgr logs dep-abc123 # fetch current log output
453
+ badgr logs dep-abc123 --follow # stream logs until the deployment reaches a terminal state
454
+ badgr pull dep-abc123 # pull a coding agent's patch as a local git diff
455
+ badgr pull dep-abc123 --branch # ...as a new local branch instead of a diff
456
+ badgr pull dep-abc123 --diff-only # print the diff, don't touch the working tree
457
+ badgr artifacts dep-abc123 # download non-patch outputs (test reports, screenshots, traces)
458
+ badgr down dep-abc123 # terminate one deployment, stop billing
459
+ badgr down --all # terminate everything running, with a confirmation prompt
460
+ badgr down --all --yes # ...skip the confirmation prompt
461
+ badgr restart dep-abc123 # relaunch an endpoint with the same config — new ID, same API key
462
+ badgr rerun dep-abc123 # replay a past job/endpoint with its exact original spec — new ID
463
+ badgr heartbeat dep-abc123 # reset an endpoint's idle-timeout clock (see --idle-timeout on badgr serve)
464
+ ```
465
+
466
+ | Command | Flag | Description |
467
+ |---------|------|-------------|
468
+ | `badgr logs <id>` | `--follow` / `-f` | Stream/poll logs until the deployment reaches a terminal state instead of a one-shot fetch |
469
+ | `badgr pull <id>` | `--branch` | Create a new local git branch from the patch instead of leaving it as an unstaged diff |
470
+ | `badgr pull <id>` | `--diff-only` | Print the raw diff, don't touch the local working tree at all |
471
+ | `badgr pull <id>` | `--yes` / `-y` | Skip the confirmation prompt before applying |
472
+ | `badgr down <id\|--all>` | `--all` | Terminate every running deployment instead of one by ID |
473
+ | `badgr down <id\|--all>` | `--yes` / `-y` | Skip the confirmation prompt |
474
+
475
+ `badgr restart` is endpoint-only (it tears down the current pod and relaunches with the same GPU/model/price/runtime caps and endpoint API key, so existing clients keep working against a new URL). `badgr rerun` works for both one-off jobs and endpoints, replaying the exact original image/command/env/GPU/caps, and never tears down the source deployment.
476
+
477
+ ---
478
+
353
479
  ## OpenAI compatibility
354
480
 
355
481
  `badgr serve` provisions a vLLM endpoint that is fully OpenAI-compatible:
@@ -417,22 +543,26 @@ Less common commands — training, transcription, embeddings, and the workload/w
417
543
  badgr train config.yaml --gpu A100 --max-runtime 240 --env HF_TOKEN=$HF_TOKEN
418
544
  ```
419
545
 
420
- Detects framework (axolotl, unsloth, trl) from the config file, but **only Axolotl configs run today** — `unsloth`/`trl`/unrecognized configs are blocked before provisioning rather than billing a GPU that's guaranteed to fail. Default max-runtime is 120 min.
546
+ Detects framework (axolotl, unsloth, trl) from the config file. **Axolotl and TRL configs run today** — `unsloth`/unrecognized configs are blocked before provisioning rather than billing a GPU that's guaranteed to fail. Default max-runtime is 120 min.
421
547
 
422
548
  ```bash
423
549
  badgr train lora --base-model mistralai/Mistral-7B-v0.1 --dataset ./train.jsonl --preset small --max-cost 20
550
+
551
+ # Resume from a prior job's checkpoint instead of starting over
552
+ badgr train lora --base-model mistralai/Mistral-7B-v0.1 --dataset ./train.jsonl --resume https://.../checkpoint --max-cost 20
424
553
  ```
425
554
 
426
555
  Productized LoRA training — pass a base model and dataset, no Axolotl config file needed. Badgr generates the config from a preset and returns a downloadable adapter.
427
556
 
428
557
  | Flag | Default | Description |
429
558
  |------|---------|-------------|
430
- | `--framework <name>` | auto-detect | Force framework: `axolotl`, `unsloth`, `trl` (only `axolotl` currently runs) |
559
+ | `--framework <name>` | auto-detect | Force framework: `axolotl`, `unsloth`, `trl` (`axolotl`/`trl` currently run; `unsloth` is blocked pre-provisioning) |
431
560
  | `--base-model <id>` | — | HuggingFace model ID (required for `train lora`) — validated to exist before provisioning |
432
561
  | `--dataset <path\|url>` | — | Local file, direct URL, or `s3://` URI |
433
562
  | `--file-id <id>` | — | Badgr upload ID instead of `--dataset` |
434
563
  | `--preset small\|medium` | `small` | `small` = RTX 4090, rank 16, 3 epochs. `medium` = A100, rank 32, 5 epochs |
435
564
  | `--gpu-type <type>` | preset default | GPU type override for `train lora` |
565
+ | `--resume <checkpoint-url>` | — | Continue training from a prior job's checkpoint instead of starting fresh |
436
566
  | `--dry-run` | — | Preview the job without provisioning |
437
567
 
438
568
  On completion, `train lora` prints an `adapter_url` — download with `GET /v1/jobs/{job_id}/adapter`, or via `badgr workload info` if saved.
@@ -502,8 +632,11 @@ badgr batch status dep-abc123
502
632
  badgr batch artifacts dep-abc123
503
633
  badgr batch receipt dep-abc123
504
634
  badgr batch compare dep-abc123 dep-def456
635
+ badgr batch compare dep-abc123 dep-def456 --key accuracy --higher-is-better
505
636
  ```
506
637
 
638
+ `batch compare` reads each run's `success_metric` by default; `--key <metric>` compares a different field from the receipt instead, and `--higher-is-better` (default) / `--higher-is-better false` controls which run is reported as the winner.
639
+
507
640
  For CV/video/scientific batch, simulation, and physical-AI eval workloads — runs a container from a `workload.yml` spec and captures output artifacts automatically.
508
641
 
509
642
  **Fan-out** — run the same program once per file in a directory, one deployment per input, in parallel:
@@ -539,14 +672,44 @@ Translates `--cpus-per-task`/`--mem`/`--gres`/`--time`/`--export` from a real `.
539
672
  | `--max-concurrency <n>` | 5 | Cap in-flight array tasks |
540
673
  | `--dry-run` | — | Preview the translated job without provisioning |
541
674
 
542
- ### Other commands
675
+ ### `badgr models` — GPU catalog and pricing
676
+
677
+ ```bash
678
+ badgr models
679
+ ```
680
+
681
+ Lists available GPU types cheapest-first, with VRAM and hourly rate — pulled live from your account when logged in, falling back to the local catalog otherwise. No flags.
682
+
683
+ ### `badgr template` — pre-built workload templates
543
684
 
544
- - `badgr capacity [--gpu <type>]` — check available GPU capacity right now
545
- - `badgr billing status` / `badgr billing add <amount>` — check balance / add funds
685
+ ```bash
686
+ badgr template list
687
+ badgr template info axolotl
688
+ badgr serve template vllm --model meta-llama/Llama-3.1-8B-Instruct --max-cost 10
689
+ badgr run template axolotl --config ./config.yaml --max-cost 10
690
+ ```
691
+
692
+ Provider-neutral templates for common frameworks (`vllm`, `invokeai`, `comfyui`, `axolotl`, `unsloth`, …). `template list`/`template info <name>` just browse the catalog; launching always goes through `badgr serve template <name>` or `badgr run template <name>`, which apply the template's default flags before handing off to the normal `serve`/`run` path.
693
+
694
+ ### `badgr capacity` — check live availability
695
+
696
+ ```bash
697
+ badgr capacity # cheapest runnable GPU across all types
698
+ badgr capacity --gpu A100 # a specific GPU type
699
+ badgr capacity --gpu A100 --region EU # region-filtered
700
+ badgr capacity --gpu A100 --max-price 2.50 # price-capped
701
+ ```
702
+
703
+ ### `badgr billing`
704
+
705
+ ```bash
706
+ badgr billing status # current balance
707
+ badgr billing add 20 # add funds — $5 minimum top-up
708
+ ```
546
709
 
547
710
  ---
548
711
 
549
712
  ## Requirements
550
713
 
551
- - Node.js 18+
714
+ - Node.js 20.10+
552
715
  - A Badgr account — sign up at [aibadgr.com](https://aibadgr.com)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badgr-cli",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Badgr — run or serve GPU workloads from one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "dependencies": {
15
15
  "@inquirer/prompts": "^8.5.2",
16
16
  "archiver": "^7.0.1",
17
+ "badgr-shared": "^0.1.2",
17
18
  "chalk": "^5.3.0",
18
19
  "js-yaml": "^4.1.0",
19
20
  "tar": "^7.4.3"
package/src/api.js CHANGED
@@ -228,6 +228,34 @@ export async function terminateDeployment(config, deploymentId) {
228
228
  throw lastErr;
229
229
  }
230
230
 
231
+ // ---- Admin (michaelhireitem@gmail.com only) ---------------------------------
232
+ // CLI counterparts to the web admin dashboard's own /admin/api/deployments/*
233
+ // routes (main.py) -- those are gated by a browser session cookie the CLI's
234
+ // Authorization: Bearer <api_key> auth can never satisfy, so
235
+ // deployment_routes.py exposes these two under /v1/admin/deployments/*
236
+ // instead, gated by the same is_admin_email() check via API key. A non-admin
237
+ // caller gets a plain 403 from callApi, same as any other access-denied route.
238
+
239
+ // Every non-terminal deployment across every user (not just the caller's
240
+ // own), each carrying its owner's user_email/username so a stuck or
241
+ // forgotten GPU can be identified without logging into that customer's
242
+ // account. Backs `badgr status --admin`.
243
+ export function listAllActiveDeploymentsAdmin(config) {
244
+ return callApi('/admin/deployments/active', { apiKey: config.apiKey, baseUrl: config.baseUrl, timeoutMs: 15_000 });
245
+ }
246
+
247
+ // Force-terminate any deployment regardless of owner -- bypasses the
248
+ // per-user ownership check DELETE /deployments/{id} enforces. Backs
249
+ // `badgr down <id> --admin`.
250
+ export function adminTerminateDeployment(config, deploymentId) {
251
+ return callApi(`/admin/deployments/${deploymentId}`, {
252
+ method: 'DELETE',
253
+ apiKey: config.apiKey,
254
+ baseUrl: config.baseUrl,
255
+ timeoutMs: 20_000,
256
+ });
257
+ }
258
+
231
259
  export function restartDeployment(config, deploymentId) {
232
260
  return callApi(`/deployments/${deploymentId}/restart`, {
233
261
  method: 'POST',
package/src/badgr.js CHANGED
@@ -47,7 +47,7 @@ ${chalk.bold('COMMANDS')}
47
47
  ${chalk.cyan('badgr job <agent> "<instruction>" --check "<command>"')} Bounded coding-agent job, tracked at /jobs (POST /v1/jobs, type: agent)
48
48
  ${chalk.cyan('badgr serve <model>')} Serve a model with an OpenAI-compatible endpoint
49
49
  ${chalk.cyan('badgr serve openwebui')} Serve Open WebUI — chat UI, connects to a model endpoint
50
- ${chalk.cyan('badgr diagnose "<input>"')} Diagnose any GPU issue for free — GitHub issue, Docker image, log, repo, or text
50
+ ${chalk.cyan('badgr diagnose "<input>"')} Run a free Badgr Smoke Test on any GPU issue — GitHub issue, Docker image, log, repo, or text
51
51
  ${chalk.cyan('badgr doctor')} Local GPU / model-fit diagnosis (read-only, no login needed)
52
52
  ${chalk.cyan('badgr status')} Show what's running and what's billing
53
53
  ${chalk.cyan('badgr logs <id>')} Stream logs for a running job or endpoint
@@ -113,13 +113,13 @@ ${chalk.bold('EXAMPLES')}
113
113
  ${chalk.dim('# badgr job — bounded coding-agent job with a pass/fail check, tracked at /jobs:')}
114
114
  badgr job cline "Run the Chromium tests and tell me what failed" --check "npm run test:chromium" --max-cost 1
115
115
 
116
- ${chalk.dim('# Diagnose any GPU issue for free (no login needed):')}
116
+ ${chalk.dim('# Run a free Badgr Smoke Test on any GPU issue (no login needed):')}
117
117
  badgr diagnose "https://github.com/org/repo/issues/123"
118
118
  badgr diagnose ./error.log
119
119
  badgr diagnose --docker ajayrajtp/vllm_gemma412b:latest
120
120
  badgr diagnose --repo https://github.com/org/repo
121
121
  badgr diagnose --comfyui workflow.json
122
- badgr diagnose "https://github.com/org/repo/issues/123" --approve ${chalk.dim('# approve a capped smoke test')}
122
+ badgr diagnose "https://github.com/org/repo/issues/123" --approve ${chalk.dim('# start a real Badgr Run')}
123
123
 
124
124
  ${chalk.dim('# Local model-fit and workload diagnosis (no network):')}
125
125
  badgr doctor
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { requireApiKey, CONFIG_DIR } from '../config.js';
3
+ import { CONFIG_DIR } from '../config.js';
4
+ import { ensureLoggedInReady, withReauthRetry } from '../onboarding.js';
4
5
  import { downloadAndExtractArtifact } from '../artifactDownload.js';
5
6
 
6
7
  /**
@@ -37,7 +38,7 @@ export function listFilesRecursive(dir, prefix = '') {
37
38
  }
38
39
 
39
40
  export async function artifactsCommand(config, args, chalk) {
40
- requireApiKey(config);
41
+ config = await ensureLoggedInReady(config, chalk);
41
42
  const { deploymentId, flags } = parseArtifactsArgs(args);
42
43
  if (!deploymentId) {
43
44
  console.error(chalk.red('\n Usage: badgr artifacts <deployment-id> [--output <dir>]\n'));
@@ -49,7 +50,8 @@ export async function artifactsCommand(config, args, chalk) {
49
50
  const destDir = flags.output ? path.resolve(flags.output) : defaultDestDir(deploymentId);
50
51
 
51
52
  try {
52
- await downloadAndExtractArtifact(config, deploymentId, destDir);
53
+ const refreshed = await withReauthRetry(config, chalk, cfg => downloadAndExtractArtifact(cfg, deploymentId, destDir));
54
+ config = refreshed.config;
53
55
  } catch (err) {
54
56
  if (err.httpStatus === 404) {
55
57
  console.error(chalk.red(`\n ✗ No artifact found for ${deploymentId}.\n`));
@@ -17,7 +17,8 @@ import { existsSync, readFileSync, unlinkSync, statSync, readdirSync } from 'fs'
17
17
  import { join, resolve } from 'path';
18
18
  import os from 'os';
19
19
 
20
- import { requireApiKey, CONFIG_DIR } from '../config.js';
20
+ import { CONFIG_DIR } from '../config.js';
21
+ import { ensureBadgrReady, ensureLoggedInReady } from '../onboarding.js';
21
22
  import { parseWorkloadYaml } from '../workloadSpec.js';
22
23
  import { addReceipt, updateReceipt, generateReceiptId, loadStore, selectedComputeFromDeployment } from '../store.js';
23
24
  import { normalizeTier, callWithFallback } from '../fallback.js';
@@ -392,7 +393,7 @@ async function runSubcommand(config, args, chalk) {
392
393
  return;
393
394
  }
394
395
 
395
- requireApiKey(config);
396
+ config = await ensureBadgrReady(config, chalk);
396
397
 
397
398
  let spec;
398
399
  try {
@@ -593,7 +594,7 @@ async function statusSubcommand(config, args, chalk) {
593
594
  process.exitCode = 1;
594
595
  return;
595
596
  }
596
- requireApiKey(config);
597
+ config = await ensureLoggedInReady(config, chalk);
597
598
 
598
599
  let dep;
599
600
  try {
@@ -628,7 +629,7 @@ async function artifactsSubcommand(config, args, chalk) {
628
629
  process.exitCode = 1;
629
630
  return;
630
631
  }
631
- requireApiKey(config);
632
+ config = await ensureLoggedInReady(config, chalk);
632
633
 
633
634
  let destDir;
634
635
  try {
@@ -676,7 +677,7 @@ async function receiptSubcommand(config, args, chalk) {
676
677
  process.exitCode = 1;
677
678
  return;
678
679
  }
679
- requireApiKey(config);
680
+ config = await ensureLoggedInReady(config, chalk);
680
681
 
681
682
  const local = findBatchRun(runId);
682
683
  let dep = null;
@@ -1,4 +1,4 @@
1
- import { requireApiKey } from '../config.js';
1
+ import { ensureLoggedInReady } from '../onboarding.js';
2
2
  import { callApi } from '../api.js';
3
3
  import { openBrowser } from '../browser.js';
4
4
 
@@ -17,7 +17,7 @@ EXAMPLES
17
17
  `;
18
18
 
19
19
  async function billingStatus(config, chalk) {
20
- requireApiKey(config);
20
+ config = await ensureLoggedInReady(config, chalk);
21
21
  try {
22
22
  const apiUrl = config.baseUrl.replace('/v1', '').replace('/api/v1', '');
23
23
  const data = await callApi('/api/me', {
@@ -47,7 +47,7 @@ async function billingStatus(config, chalk) {
47
47
  }
48
48
 
49
49
  async function billingAdd(config, amount, chalk) {
50
- requireApiKey(config);
50
+ config = await ensureLoggedInReady(config, chalk);
51
51
  const amountInt = parseInt(amount, 10);
52
52
  if (!amountInt || amountInt < 5) {
53
53
  console.error(chalk.red(' Minimum top-up is $5. Example: badgr billing add 5'));
@@ -1,4 +1,4 @@
1
- import { requireApiKey } from '../config.js';
1
+ import { ensureLoggedInReady } from '../onboarding.js';
2
2
  import { callApi } from '../api.js';
3
3
 
4
4
  /**
@@ -19,7 +19,7 @@ function parseCapacityArgs(args) {
19
19
 
20
20
  export async function capacityCommand(config, args, chalk) {
21
21
  const flags = parseCapacityArgs(args);
22
- requireApiKey(config);
22
+ config = await ensureLoggedInReady(config, chalk);
23
23
 
24
24
  // No --max-price: no cap. Availability browsing shouldn't silently hide
25
25
  // real capacity behind an unrequested price ceiling.
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'crypto';
2
2
  import { findCheapest, findById } from '../router.js';
3
- import { requireApiKey } from '../config.js';
3
+ import { ensureBadgrReady } from '../onboarding.js';
4
4
 
5
5
  export function parseDeployArgs(args) {
6
6
  const nameFlag = args.indexOf('--name');
@@ -24,7 +24,7 @@ export async function deployCommand(config, args, chalk) {
24
24
  return;
25
25
  }
26
26
 
27
- requireApiKey(config);
27
+ config = await ensureBadgrReady(config, chalk);
28
28
 
29
29
  const deployId = randomUUID().split('-')[0];
30
30
  const name = rawName ?? script.replace(/[^a-z0-9]/gi, '-').replace(/-+/g, '-').toLowerCase();