badgr-cli 1.1.4 → 1.1.6

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
@@ -167,17 +167,15 @@ secrets, and runs free static checks as part of a Badgr Smoke Test.
167
167
  Nothing runs on a GPU without explicit `--approve`.
168
168
 
169
169
  ```bash
170
- # Free Badgr Smoke Test — no login required
170
+ # badgr diagnose IS the Badgr Smoke Test — free, no login, no GPU.
171
+ # Applicable free structural checks run automatically, every time.
171
172
  badgr diagnose "https://github.com/org/repo/issues/123"
172
173
  badgr diagnose ajayrajtp/vllm_gemma412b:latest
173
174
  badgr diagnose ./vllm-error.log
174
175
  badgr diagnose "https://github.com/org/repo"
175
176
  badgr diagnose workflow.json
176
177
 
177
- # Free mechanical validation of the produced command still no GPU, no login
178
- badgr diagnose "https://github.com/org/repo/issues/123" --smoke
179
-
180
- # Approve a capped smoke test after diagnosis (login required)
178
+ # Start a real Badgr Run (paid GPU execution) after diagnosis (login required)
181
179
  badgr diagnose "https://github.com/org/repo/issues/123" --approve
182
180
 
183
181
  # Resume an existing case, e.g. one shared via a case link
@@ -190,8 +188,8 @@ badgr diagnose "https://github.com/org/repo/issues/123" --json
190
188
 
191
189
  | Flag | Description |
192
190
  |------|-------------|
193
- | `--smoke` | Free. Runs real, cheap mechanical validation of the produced command (syntax, CLI-entrypoint corroboration, referenced-file presence, Docker `ENTRYPOINT`/`CMD` consistency, required env vars, plus a client-side check of any local file path you pasted). Never starts a GPU, never requires login. Combine with `--approve` — the smoke checks print first, then the normal approve flow runs. Not the same as `badgr run --smoke`, which launches a real, billable GPU job |
194
- | `--approve` | Approve the capped smoke test after diagnosis (opens browser sign-in automatically if not logged in) |
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 |
195
193
  | `--docker <image>` | Force Docker-image intake (override auto-detect) |
196
194
  | `--repo <url>` | Force repository intake (override auto-detect) |
197
195
  | `--comfyui <path>` | Force ComfyUI workflow intake (override auto-detect) |
@@ -199,13 +197,13 @@ badgr diagnose "https://github.com/org/repo/issues/123" --json
199
197
  | `--json` | Machine-readable JSON output |
200
198
 
201
199
  Every run prints one status: `NEEDS INFO` (fields still missing) →
202
- `READY` (complete evidence-backed command, no smoke run) → `SMOKE CHECKED`
203
- (`--smoke` ran, every applicable check passed or was skipped) / `INVALID`
204
- (`--smoke` ran and a check actually failed) → `VERIFIED` (reserved for an
205
- actual successful GPU-provisioned run — never assigned from static
206
- resolution or `--smoke`). `READY`/`SMOKE CHECKED` results also print the
207
- canonical `badgr run`/`badgr serve` command `--approve` would run, plus a
208
- shareable case link — pass that case ID or URL back into `badgr diagnose
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
209
207
  <case_id_or_url> --approve` to resume the same case without re-diagnosing.
210
208
 
211
209
  `badgr run-issue` is an alias for `badgr diagnose`, matching the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badgr-cli",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "Badgr — run or serve GPU workloads from one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  "dependencies": {
15
15
  "@inquirer/prompts": "^8.5.2",
16
16
  "archiver": "^7.0.1",
17
- "badgr-shared": "0.1.1",
17
+ "badgr-shared": "^0.1.2",
18
18
  "chalk": "^5.3.0",
19
19
  "js-yaml": "^4.1.0",
20
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
@@ -119,7 +119,7 @@ ${chalk.bold('EXAMPLES')}
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();