badgr-cli 1.1.1 → 1.1.3

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.
Files changed (73) hide show
  1. package/LICENSE +207 -0
  2. package/README.md +13 -6
  3. package/package.json +44 -2
  4. package/src/api.js +16 -0
  5. package/src/badgr.js +26 -10
  6. package/src/commands/batch.js +11 -0
  7. package/src/commands/billing.js +3 -3
  8. package/src/commands/comfyui.js +31 -15
  9. package/src/commands/connect.js +4 -1
  10. package/src/commands/diagnose.js +493 -0
  11. package/src/commands/embed.js +13 -10
  12. package/src/commands/job.js +246 -0
  13. package/src/commands/launch.js +152 -16
  14. package/src/commands/login.js +75 -20
  15. package/src/commands/run.js +74 -16
  16. package/src/commands/sbatch.js +6 -1
  17. package/src/commands/serve.js +44 -30
  18. package/src/commands/train.js +8 -12
  19. package/src/commands/transcribe.js +13 -10
  20. package/src/credentials.js +33 -0
  21. package/src/envFlag.js +10 -0
  22. package/src/fallback.js +13 -2
  23. package/src/onboarding.js +8 -1
  24. package/src/progress.js +48 -0
  25. package/src/commands/task.js +0 -25
  26. package/tests/agent-images.test.js +0 -17
  27. package/tests/api.test.js +0 -168
  28. package/tests/artifactDownload.test.js +0 -113
  29. package/tests/artifacts.test.js +0 -168
  30. package/tests/batch.test.js +0 -641
  31. package/tests/browser.test.js +0 -51
  32. package/tests/capacity.test.js +0 -68
  33. package/tests/commands.test.js +0 -417
  34. package/tests/config.test.js +0 -96
  35. package/tests/connect.test.js +0 -83
  36. package/tests/detect.test.js +0 -191
  37. package/tests/down.test.js +0 -150
  38. package/tests/errors.test.js +0 -130
  39. package/tests/fallback-timeout.test.js +0 -41
  40. package/tests/fanout.test.js +0 -124
  41. package/tests/gpu-doctor-classifiers.test.js +0 -402
  42. package/tests/gpu-doctor-doctor.test.js +0 -304
  43. package/tests/gpu-doctor-probe-cache.test.js +0 -110
  44. package/tests/gpu-doctor-probes.test.js +0 -257
  45. package/tests/heartbeat.test.js +0 -70
  46. package/tests/job-progress-poll.test.js +0 -136
  47. package/tests/launch-command-argv.test.js +0 -93
  48. package/tests/launch-readiness.test.js +0 -403
  49. package/tests/launch.test.js +0 -440
  50. package/tests/onboarding.test.js +0 -134
  51. package/tests/productized-dry-run.test.js +0 -141
  52. package/tests/productized-runners.test.js +0 -237
  53. package/tests/pull.test.js +0 -266
  54. package/tests/rerun.test.js +0 -94
  55. package/tests/restart.test.js +0 -88
  56. package/tests/router.test.js +0 -98
  57. package/tests/run-lifecycle.test.js +0 -1054
  58. package/tests/sbatch.test.js +0 -190
  59. package/tests/secrets.test.js +0 -16
  60. package/tests/serve-apps.test.js +0 -189
  61. package/tests/serve-lifecycle.test.js +0 -931
  62. package/tests/slurm.test.js +0 -77
  63. package/tests/spec.test.js +0 -201
  64. package/tests/status.test.js +0 -73
  65. package/tests/store.test.js +0 -187
  66. package/tests/task.test.js +0 -109
  67. package/tests/template.test.js +0 -556
  68. package/tests/train-lora-dataset.test.js +0 -176
  69. package/tests/upload.test.js +0 -79
  70. package/tests/workload-rerun.test.js +0 -56
  71. package/tests/workload-spec.test.js +0 -180
  72. package/tests/workload-templates.test.js +0 -865
  73. package/tests/workload-workspace-paths.test.js +0 -46
package/LICENSE ADDED
@@ -0,0 +1,207 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship made available under
36
+ the License, as indicated by a copyright notice that is included in
37
+ or attached to the work (an example is provided in the Appendix below).
38
+
39
+ "Derivative Works" shall mean any work, whether in Source or Object
40
+ form, that is based on (or derived from) the Work and for which the
41
+ editorial revisions, annotations, elaborations, or other modifications
42
+ represent, as a whole, an original work of authorship. For the purposes
43
+ of this License, Derivative Works shall not include works that remain
44
+ separable from, or merely link (or bind by name) to the interfaces of,
45
+ the Work and Derivative Works thereof.
46
+
47
+ "Contribution" shall mean, as submitted to the Licensor for inclusion
48
+ in the Work by the copyright owner or by an individual or Legal Entity
49
+ authorized to submit on behalf of the copyright owner. For the purposes
50
+ of this definition, "submitted" means any form of electronic, verbal,
51
+ or written communication sent to the Licensor or its representatives,
52
+ including but not limited to communication on electronic mailing lists,
53
+ source code control systems, and issue tracking systems that are managed
54
+ by, or on behalf of, the Licensor for the purpose of discussing and
55
+ improving the Work, but excluding communication that is conspicuously
56
+ marked or designated in writing by the copyright owner as "Not a
57
+ Contribution."
58
+
59
+ "Contributor" shall mean Licensor and any Legal Entity on behalf of
60
+ whom a Contribution has been received by the Licensor and included
61
+ within the Work.
62
+
63
+ 2. Grant of Copyright License. Subject to the terms and conditions of
64
+ this License, each Contributor hereby grants to You a perpetual,
65
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
66
+ copyright license to reproduce, prepare Derivative Works of,
67
+ publicly display, publicly perform, sublicense, and distribute the
68
+ Work and such Derivative Works in Source or Object form.
69
+
70
+ 3. Grant of Patent License. Subject to the terms and conditions of
71
+ this License, each Contributor hereby grants to You a perpetual,
72
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
73
+ (except as stated in this section) patent license to make, have made,
74
+ use, offer to sell, sell, import, and otherwise transfer the Work,
75
+ where such license applies only to those patent claims licensable
76
+ by such Contributor that are necessarily infringed by their
77
+ Contribution(s) alone or by the combination of their Contribution(s)
78
+ with the Work to which such Contribution(s) was submitted. If You
79
+ institute patent litigation against any entity (including a cross-claim
80
+ or counterclaim in a lawsuit) alleging that the Work or any
81
+ Contribution embodied within the Work constitutes direct or
82
+ contributory patent infringement, then any patent licenses granted to
83
+ You under this License for that Work shall terminate as of the date
84
+ such litigation is filed.
85
+
86
+ 4. Redistribution. You may reproduce and distribute copies of the
87
+ Work or Derivative Works thereof in any medium, with or without
88
+ modifications, and in Source or Object form, provided that You
89
+ meet the following conditions:
90
+
91
+ (a) You must give any other recipients of the Work or Derivative
92
+ Works a copy of this License; and
93
+
94
+ (b) You must cause any modified files to carry prominent notices
95
+ stating that You changed the files; and
96
+
97
+ (c) You must retain, in the Source form of any Derivative Works
98
+ that You distribute, all copyright, patent, trademark, and
99
+ attribution notices from the Source form of the Work,
100
+ excluding those notices that do not pertain to any part of
101
+ the Derivative Works; and
102
+
103
+ (d) If the Work includes a "NOTICE" text file as part of its
104
+ distribution, You must include a readable copy of the
105
+ attribution notices contained within such NOTICE file, in
106
+ at least one of the following places: within a NOTICE text
107
+ file distributed as part of the Derivative Works; within
108
+ the Source form or documentation, if provided along with the
109
+ Derivative Works; or, within a display generated by the
110
+ Derivative Works, if and wherever such third-party notices
111
+ normally appear. The contents of the NOTICE file are for
112
+ informational purposes only and do not modify the License.
113
+ You may add Your own attribution notices within Derivative
114
+ Works that You distribute, alongside or as an addendum to
115
+ the NOTICE text from the Work, provided that such additional
116
+ attribution notices cannot be construed as modifying the License.
117
+
118
+ You may add Your own license statement for Your modifications and
119
+ may provide additional grant of rights to use, copy, modify, merge,
120
+ publish, distribute, sublicense, and/or sell copies of the Work,
121
+ and to permit persons to whom the Work is furnished to do so,
122
+ subject to the following conditions:
123
+
124
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
125
+ any Contribution intentionally submitted for inclusion in the Work
126
+ by You to the Licensor shall be under the terms and conditions of
127
+ this License, without any additional terms or conditions.
128
+ Notwithstanding the above, nothing herein shall supersede or modify
129
+ the terms of any separate license agreement you may have executed
130
+ with Licensor regarding such Contributions.
131
+
132
+ 6. Trademarks. This License does not grant permission to use the trade
133
+ names, trademarks, service marks, or product names of the Licensor,
134
+ except as required for reasonable and customary use in describing the
135
+ origin of the Work and reproducing the content of the NOTICE file.
136
+
137
+ 7. Disclaimer of Warranty. Unless required by applicable law or
138
+ agreed to in writing, Licensor provides the Work (and each
139
+ Contributor provides its Contributions) on an "AS IS" BASIS,
140
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
141
+ implied, including, without limitation, any warranties or conditions
142
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
143
+ PARTICULAR PURPOSE. You are solely responsible for determining the
144
+ appropriateness of using or reproducing the Work and assume any
145
+ risks associated with Your exercise of permissions under this License.
146
+
147
+ 8. Limitation of Liability. In no event and under no legal theory,
148
+ whether in tort (including negligence), contract, or otherwise,
149
+ unless required by applicable law (such as deliberate and grossly
150
+ negligent acts) or agreed to in writing, shall any Contributor be
151
+ liable to You for damages, including any direct, indirect, special,
152
+ incidental, or exemplary damages of any character arising as a
153
+ result of this License or out of the use or inability to use the
154
+ Work (including but not limited to damages for loss of goodwill,
155
+ work stoppage, computer failure or malfunction, or all other
156
+ commercial damages or losses), even if such Contributor has been
157
+ advised of the possibility of such damages.
158
+
159
+ 9. Accepting Warranty or Additional Liability. While redistributing
160
+ the Work or Derivative Works thereof, You may choose to offer,
161
+ and charge a fee for, acceptance of support, warranty, indemnity,
162
+ or other liability obligations and/or rights consistent with this
163
+ License. However, in accepting such obligations, You may offer such
164
+ conditions only on Your own behalf and on Your sole responsibility,
165
+ not on behalf of any other Contributor, and only if You agree to
166
+ indemnify, defend, and hold each Contributor harmless for any
167
+ liability incurred by, or claims asserted against, such Contributor
168
+ by reason of your accepting any such warranty or additional liability.
169
+
170
+ END OF TERMS AND CONDITIONS
171
+
172
+ APPENDIX: How to apply the Apache License to your work.
173
+
174
+ To apply the Apache License to your work, attach the following
175
+ boilerplate notice, with the fields enclosed by brackets "[]"
176
+ replaced with your own identifying information. (Don't include
177
+ the brackets!) The text should be enclosed in the appropriate
178
+ comment syntax for the format in use. Please also include a
179
+ "NOTICE" file as part of the distribution with the contents:
180
+
181
+ Copyright [yyyy] [name of copyright owner]
182
+
183
+ Licensed under the Apache License, Version 2.0 (the "License");
184
+ you may not use this file except in compliance with the License.
185
+ You may obtain a copy of the License at
186
+
187
+ http://www.apache.org/licenses/LICENSE-2.0
188
+
189
+ Unless required by applicable law or agreed to in writing, software
190
+ distributed under the License is distributed on an "AS IS" BASIS,
191
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
192
+ See the License for the specific language governing permissions and
193
+ limitations under the License.
194
+
195
+ Copyright 2024 AI Badgr
196
+
197
+ Licensed under the Apache License, Version 2.0 (the "License");
198
+ you may not use this file except in compliance with the License.
199
+ You may obtain a copy of the License at
200
+
201
+ http://www.apache.org/licenses/LICENSE-2.0
202
+
203
+ Unless required by applicable law or agreed to in writing, software
204
+ distributed under the License is distributed on an "AS IS" BASIS,
205
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
206
+ See the License for the specific language governing permissions and
207
+ limitations under the License.
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # badgr-cli
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/badgr-cli.svg)](https://www.npmjs.com/package/badgr-cli)
4
+ [![npm downloads](https://img.shields.io/npm/dm/badgr-cli.svg)](https://www.npmjs.com/package/badgr-cli)
5
+ [![license](https://img.shields.io/npm/l/badgr-cli.svg)](https://github.com/michaelmanly/badgr-cli/blob/main/LICENSE)
6
+ [![node](https://img.shields.io/node/v/badgr-cli.svg)](https://www.npmjs.com/package/badgr-cli)
7
+
3
8
  Badgr supports many GPU workloads through two commands: `serve` for persistent endpoints, `run` for jobs.
4
9
 
5
10
  > **Safety promise:** every run has `--max-cost`, live logs, automatic teardown, and a receipt. Run `badgr down <id>` any time to stop billing immediately.
@@ -8,6 +13,8 @@ Badgr supports many GPU workloads through two commands: `serve` for persistent e
8
13
  npm install -g badgr-cli
9
14
  ```
10
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)
17
+
11
18
  ---
12
19
 
13
20
  ## Quick start
@@ -96,13 +103,13 @@ Everything before `--` is a `badgr launch` flag; everything after `--` is passed
96
103
  | `--region US\|EU\|AU` | — | Region preference |
97
104
  | `--size small\|medium\|browser` | per-workload default | VM class override |
98
105
 
99
- ### `badgr task` — a labeled launch
106
+ ### `badgr job` — a tracked coding-agent job
100
107
 
101
108
  ```bash
102
- badgr task "Run the Chromium tests and tell me what failed" --max-cost 1 -- npm run test:chromium
109
+ badgr job cline "Run the Chromium tests and tell me what failed" --check "npm run test:chromium" --max-cost 1
103
110
  ```
104
111
 
105
- A thin wrapper over `badgr launch . -- <command>` the description is printed for the human and otherwise discarded; the receipt and `badgr status` still key off the deployment ID like any other launch.
112
+ Submits via `POST /v1/jobs` (`type: "agent"`) the same Jobs API used by every other job type. Requires `--check <command>` (verifies success); tracked at `/jobs` with a `job_id`, status, logs, output, cost, and time.
106
113
 
107
114
  ---
108
115
 
@@ -148,7 +155,7 @@ badgr doctor --json
148
155
  ```
149
156
 
150
157
  Run `badgr doctor --help` for the full flag list. Details in
151
- [`docs/gpu-doctor.md`](../../docs/gpu-doctor.md).
158
+ [`docs/gpu-doctor.md`](https://github.com/michaelmanly/gpu-ai/blob/main/docs/gpu-doctor.md).
152
159
 
153
160
  ---
154
161
 
@@ -178,7 +185,7 @@ test
178
185
  | `badgr doctor` | Diagnose a GPU workload failure — read-only, no login needed |
179
186
  | `badgr run <command>` | Run a one-off GPU job (any container command) |
180
187
  | `badgr launch cline\|claude\|codex\|playwright "<task>"` | Run a coding/testing agent on a CPU VM — image + command auto-selected |
181
- | `badgr task "<desc>" -- <command>` | Label + launch thin wrapper over `badgr launch . -- <command>` |
188
+ | `badgr job <agent> "<instruction>" --check "<cmd>"` | Tracked coding-agent job via `POST /v1/jobs` (type: agent) |
182
189
  | `badgr serve <model>` | Start a persistent OpenAI-compatible endpoint |
183
190
  | `badgr status` | Show what's running and what's billing |
184
191
  | `badgr logs <id>` | Fetch log output from a deployment |
@@ -384,7 +391,7 @@ Badgr Auto selects the best eligible GPU for your workload. Add `--gpu <type>` o
384
391
  | A100 | NVIDIA A100 | 40–80 GB | Training, inference |
385
392
  | H100 | NVIDIA H100 | 80 GB | Large model training |
386
393
 
387
- Additional GPU types may be routable depending on current capacity — check with `badgr capacity`. Pricing is confirmed before provisioning; use `--dry-run` to see it first. Full GPU support details: see [NOTES.md](../../NOTES.md#gpu-support) in the repo root.
394
+ Additional GPU types may be routable depending on current capacity — check with `badgr capacity`. Pricing is confirmed before provisioning; use `--dry-run` to see it first. Full GPU support details: see [NOTES.md](https://github.com/michaelmanly/gpu-ai/blob/main/NOTES.md#gpu-support).
388
395
 
389
396
  ---
390
397
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badgr-cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Badgr — run or serve GPU workloads from one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,6 +24,14 @@
24
24
  "engines": {
25
25
  "node": ">=20.10.0"
26
26
  },
27
+ "homepage": "https://aibadgr.com",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/michaelmanly/badgr-cli.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/michaelmanly/badgr-cli/issues"
34
+ },
27
35
  "keywords": [
28
36
  "gpu",
29
37
  "cli",
@@ -31,11 +39,45 @@
31
39
  "compute",
32
40
  "gateway",
33
41
  "openai",
42
+ "openai-compatible",
34
43
  "workload",
35
44
  "workspace",
36
45
  "llm",
37
46
  "inference",
38
- "fine-tuning"
47
+ "fine-tuning",
48
+ "lora",
49
+ "vllm",
50
+ "gpu-cloud",
51
+ "cloud-gpu",
52
+ "gpu-rental",
53
+ "on-demand-gpu",
54
+ "serverless-gpu",
55
+ "gpu-provisioning",
56
+ "machine-learning",
57
+ "deep-learning",
58
+ "model-serving",
59
+ "model-training",
60
+ "training",
61
+ "huggingface",
62
+ "cuda",
63
+ "h100",
64
+ "a100",
65
+ "rtx-4090",
66
+ "coding-agent",
67
+ "ai-agent",
68
+ "claude-code",
69
+ "codex",
70
+ "playwright",
71
+ "comfyui",
72
+ "stable-diffusion",
73
+ "image-generation",
74
+ "transcription",
75
+ "whisper",
76
+ "embeddings",
77
+ "slurm",
78
+ "batch-jobs",
79
+ "devops",
80
+ "mlops"
39
81
  ],
40
82
  "license": "Apache-2.0"
41
83
  }
package/src/api.js CHANGED
@@ -153,6 +153,22 @@ export function quoteRun(config, body) {
153
153
  });
154
154
  }
155
155
 
156
+ // Live, no-provisioning price/capacity check for a GPU dry-run preview (see
157
+ // backend run_serve_routes.py's GET /v1/capacity/suggestions). Used by every
158
+ // GPU-provisioning command's --dry-run to show a real observed rate instead
159
+ // of just the requested config — callers must treat a failure here as
160
+ // non-fatal (best-effort) and never let it block or delay the dry-run.
161
+ export function getCapacityPreview(config, { gpu, region, maxPrice } = {}) {
162
+ const params = new URLSearchParams({ gpu: gpu || 'RTX_4090' });
163
+ if (region) params.set('region', region);
164
+ if (maxPrice != null) params.set('max_price', String(maxPrice));
165
+ return callApi(`/capacity/suggestions?${params}`, {
166
+ apiKey: config.apiKey,
167
+ baseUrl: config.baseUrl,
168
+ timeoutMs: 8_000,
169
+ });
170
+ }
171
+
156
172
  export function serveModel(config, body) {
157
173
  return callApi('/serve', {
158
174
  method: 'POST',
package/src/badgr.js CHANGED
@@ -9,7 +9,7 @@ import { logsCommand } from './commands/logs.js';
9
9
  import { receiptsCommand } from './commands/receipts.js';
10
10
  import { runCommand } from './commands/run.js';
11
11
  import { launchCommand } from './commands/launch.js';
12
- import { taskCommand } from './commands/task.js';
12
+ import { jobCommand } from './commands/job.js';
13
13
  import { artifactsCommand } from './commands/artifacts.js';
14
14
  import { serveCommand } from './commands/serve.js';
15
15
  import { modelsCommand } from './commands/models.js';
@@ -31,22 +31,24 @@ import { rerunCommand } from './commands/rerun.js';
31
31
  import { heartbeatCommand } from './commands/heartbeat.js';
32
32
  import { pullCommand } from './commands/pull.js';
33
33
  import { doctorCommand } from './commands/doctor.js';
34
+ import { diagnoseCommand } from './commands/diagnose.js';
34
35
  import { connectCommand } from './commands/connect.js';
35
36
 
36
37
  const HELP = `
37
38
  ${chalk.bold('badgr')} — run or serve GPU workloads from one command
38
39
 
39
40
  ${chalk.bold('COMMANDS')}
40
- ${chalk.cyan('badgr login')} Authenticate with your API key
41
+ ${chalk.cyan('badgr login')} Open a browser to log in (or: badgr login --key <value>)
41
42
  ${chalk.cyan('badgr connect <provider>')} Credential setup (badgr connect anthropic/openai) — also prompted inline by badgr launch if missing
42
43
  ${chalk.cyan('badgr detect <path>')} Inspect a project and report the GPU job Badgr would run
43
44
  ${chalk.cyan('badgr run <command>')} Run a one-off GPU job
44
45
  ${chalk.cyan('badgr launch cline|claude|codex|playwright "<task>"')} Run a coding/testing workload on a CPU VM — image + command auto-selected
45
46
  ${chalk.cyan('badgr launch <source> -- <command>')} Advanced escape hatch: run any other command on a CPU VM
46
- ${chalk.cyan('badgr task "<desc>" -- <command>')} Label + launch thin wrapper over badgr launch . -- <command>
47
+ ${chalk.cyan('badgr job <agent> "<instruction>" --check "<command>"')} Bounded coding-agent job, tracked at /jobs (POST /v1/jobs, type: agent)
47
48
  ${chalk.cyan('badgr serve <model>')} Serve a model with an OpenAI-compatible endpoint
48
49
  ${chalk.cyan('badgr serve openwebui')} Serve Open WebUI — chat UI, connects to a model endpoint
49
- ${chalk.cyan('badgr doctor')} Diagnose why a GPU workload is likely failing (read-only, no login needed)
50
+ ${chalk.cyan('badgr diagnose "<input>"')} Diagnose any GPU issue for free GitHub issue, Docker image, log, repo, or text
51
+ ${chalk.cyan('badgr doctor')} Local GPU / model-fit diagnosis (read-only, no login needed)
50
52
  ${chalk.cyan('badgr status')} Show what's running and what's billing
51
53
  ${chalk.cyan('badgr logs <id>')} Stream logs for a running job or endpoint
52
54
  ${chalk.cyan('badgr pull <id>')} Safely pull cloud-agent patch artifacts
@@ -108,10 +110,18 @@ ${chalk.bold('EXAMPLES')}
108
110
  badgr launch https://github.com/user/repo --max-cost 1 -- python narrgo.py
109
111
  badgr artifacts <id>
110
112
 
111
- ${chalk.dim('# badgr task is a thin label wrapper over badgr launch . -- <command>:')}
112
- badgr task "Run the Chromium tests and tell me what failed" --max-cost 1 -- npm run test:chromium
113
+ ${chalk.dim('# badgr job bounded coding-agent job with a pass/fail check, tracked at /jobs:')}
114
+ badgr job cline "Run the Chromium tests and tell me what failed" --check "npm run test:chromium" --max-cost 1
113
115
 
114
- ${chalk.dim('# Diagnose why a GPU workload is likely failing (local, read-only, no login):')}
116
+ ${chalk.dim('# Diagnose any GPU issue for free (no login needed):')}
117
+ badgr diagnose "https://github.com/org/repo/issues/123"
118
+ badgr diagnose ./error.log
119
+ badgr diagnose --docker ajayrajtp/vllm_gemma412b:latest
120
+ badgr diagnose --repo https://github.com/org/repo
121
+ badgr diagnose --comfyui workflow.json
122
+ badgr diagnose "https://github.com/org/repo/issues/123" --approve ${chalk.dim('# approve a capped smoke test')}
123
+
124
+ ${chalk.dim('# Local model-fit and workload diagnosis (no network):')}
115
125
  badgr doctor
116
126
  badgr doctor --model Qwen/Qwen2.5-7B-Instruct --serve
117
127
  badgr doctor --logs ./vllm.log
@@ -195,6 +205,10 @@ ${chalk.bold('badgr run OPTIONS')}
195
205
  --no-gpu Run on a CPU-only VM — no GPU is provisioned (conflicts with --gpu/--gpu-memory)
196
206
  --tier 1 Managed provider routing (default)
197
207
  --tier 2 Marketplace provider routing, lower-cost options
208
+ --smoke Cheapest compatible provider for a local/dev test run — defaults: $0.25
209
+ max cost, 10min max runtime (override with --max-cost/--max-runtime).
210
+ Always: one attempt, mandatory teardown, no --workspace.
211
+ Same as setting BADGR_DEV_CHEAPEST=1. An explicit --tier overrides this.
198
212
  --image <image> Docker image (default: python:3.11-slim)
199
213
  --env KEY=VALUE Set an environment variable (repeatable)
200
214
  --count <n> Number of GPUs (default: 1)
@@ -265,12 +279,12 @@ async function main() {
265
279
  }
266
280
 
267
281
  switch (cmd) {
268
- case 'login': return loginCommand(chalk, saveConfig);
282
+ case 'login': return loginCommand(chalk, saveConfig, rest);
269
283
  case 'connect': return connectCommand(rest, chalk);
270
284
  case 'detect': return detectCommand(config, rest, chalk);
271
285
  case 'run': return runCommand(config, rest, chalk);
272
286
  case 'launch': return launchCommand(config, rest, chalk);
273
- case 'task': return taskCommand(config, rest, chalk);
287
+ case 'job': return jobCommand(config, rest, chalk);
274
288
  case 'artifacts': return artifactsCommand(config, rest, chalk);
275
289
  case 'pull': return pullCommand(config, rest, chalk);
276
290
  case 'serve': return serveCommand(config, rest, chalk);
@@ -280,7 +294,9 @@ async function main() {
280
294
  case 'restart': return restartCommand(config, rest, chalk);
281
295
  case 'rerun': return rerunCommand(config, rest, chalk);
282
296
  case 'heartbeat': return heartbeatCommand(config, rest, chalk);
283
- case 'doctor': return doctorCommand(config, rest, chalk);
297
+ case 'diagnose': return diagnoseCommand(config, rest, chalk);
298
+ case 'run-issue': return diagnoseCommand(config, rest, chalk); // alias — matches the /run-issue web flow name
299
+ case 'doctor': return doctorCommand(config, rest, chalk);
284
300
  case 'receipts': return receiptsCommand(config, rest, chalk);
285
301
  case 'models': return modelsCommand(config, chalk);
286
302
  case 'capacity': return capacityCommand(config, rest, chalk);
@@ -25,6 +25,7 @@ import { monitorBatchJob, fmtRuntime } from '../batch.js';
25
25
  import { runFanOut, DEFAULT_CONCURRENCY } from '../fanout.js';
26
26
  import { uploadBlob } from '../api.js';
27
27
  import { downloadAndExtractArtifact } from '../artifactDownload.js';
28
+ import { printCapacityPreview, formatTierLabel } from '../progress.js';
28
29
  import { logsCommand } from './logs.js';
29
30
 
30
31
  // Server-side watchdog / batch.js reason strings, mapped to the vocabulary
@@ -240,17 +241,21 @@ async function runFanOutSubcommand(config, spec, flags, chalk, yamlPath) {
240
241
  const [{ containerPath }] = spec.inputs;
241
242
  const effectiveTier = normalizeTier(flags.tier);
242
243
  const concurrency = flags.maxConcurrency ?? DEFAULT_CONCURRENCY;
244
+ const resolvedGpu = flags.gpu ? flags.gpu.toUpperCase().replace(/-/g, '_') : 'AUTO';
243
245
 
244
246
  console.log(chalk.bold(`\n📦 Batch fan-out: ${spec.name}\n`));
245
247
  console.log(` ${chalk.bold('Image:')} ${spec.image}`);
246
248
  console.log(` ${chalk.bold('Command:')} ${spec.command.join(' ')}`);
247
249
  console.log(` ${chalk.bold('Inputs dir:')} ${fanOutDir} (${files.length} task(s), max ${Math.min(concurrency, files.length)} concurrent)`);
250
+ console.log(` ${chalk.bold('GPU:')} ${resolvedGpu}`);
251
+ console.log(` ${chalk.bold('Tier:')} ${formatTierLabel(effectiveTier)}`);
248
252
  console.log(` ${chalk.bold('Max cost:')} $${spec.maxCost.toFixed(2)} per task`);
249
253
  console.log(` ${chalk.bold('Max runtime:')} ${spec.maxRuntimeMinutes}min per task`);
250
254
  console.log();
251
255
 
252
256
  if (flags.dryRun) {
253
257
  console.log(chalk.bold(`⚡ Dry run — no GPU will be provisioned (${files.length} task(s) would be submitted)\n`));
258
+ await printCapacityPreview(chalk, config, { gpu: resolvedGpu, region: flags.region?.toUpperCase() });
254
259
  return;
255
260
  }
256
261
 
@@ -402,9 +407,14 @@ async function runSubcommand(config, args, chalk) {
402
407
  return runFanOutSubcommand(config, spec, flags, chalk, yamlPath);
403
408
  }
404
409
 
410
+ const resolvedGpu = flags.gpu ? flags.gpu.toUpperCase().replace(/-/g, '_') : 'AUTO';
411
+ const previewTier = normalizeTier(flags.tier);
412
+
405
413
  console.log(chalk.bold(`\n📦 Batch: ${spec.name}\n`));
406
414
  console.log(` ${chalk.bold('Image:')} ${spec.image}`);
407
415
  console.log(` ${chalk.bold('Command:')} ${spec.command.join(' ')}`);
416
+ console.log(` ${chalk.bold('GPU:')} ${resolvedGpu}`);
417
+ console.log(` ${chalk.bold('Tier:')} ${formatTierLabel(previewTier)}`);
408
418
  console.log(` ${chalk.bold('Max cost:')} $${spec.maxCost.toFixed(2)}`);
409
419
  console.log(` ${chalk.bold('Max runtime:')} ${spec.maxRuntimeMinutes}min`);
410
420
  if (spec.inputs.length) console.log(` ${chalk.bold('Inputs:')} ${spec.inputs.length} path(s)`);
@@ -413,6 +423,7 @@ async function runSubcommand(config, args, chalk) {
413
423
 
414
424
  if (flags.dryRun) {
415
425
  console.log(chalk.bold('⚡ Dry run — no GPU will be provisioned\n'));
426
+ await printCapacityPreview(chalk, config, { gpu: resolvedGpu, region: flags.region?.toUpperCase() });
416
427
  return;
417
428
  }
418
429
 
@@ -7,7 +7,7 @@ badgr billing — manage your AI Badgr balance
7
7
 
8
8
  COMMANDS
9
9
  badgr billing status Show current balance
10
- badgr billing add <amount> Open checkout to add balance (minimum $10)
10
+ badgr billing add <amount> Open checkout to add balance (minimum $5)
11
11
 
12
12
  EXAMPLES
13
13
  badgr billing status
@@ -49,8 +49,8 @@ async function billingStatus(config, chalk) {
49
49
  async function billingAdd(config, amount, chalk) {
50
50
  requireApiKey(config);
51
51
  const amountInt = parseInt(amount, 10);
52
- if (!amountInt || amountInt < 10) {
53
- console.error(chalk.red(' Minimum top-up is $10. Example: badgr billing add 10'));
52
+ if (!amountInt || amountInt < 5) {
53
+ console.error(chalk.red(' Minimum top-up is $5. Example: badgr billing add 5'));
54
54
  process.exit(1);
55
55
  }
56
56
 
@@ -7,12 +7,14 @@
7
7
  */
8
8
  import { readFileSync, existsSync } from 'fs';
9
9
  import { ensureBadgrReady } from '../onboarding.js';
10
+ import { requireApiKey } from '../config.js';
10
11
  import { callApi, listDeployments } from '../api.js';
11
12
  import { addDeployment, addReceipt, updateReceipt, generateReceiptId } from '../store.js';
12
13
  import { normalizeTier, callWithFallback } from '../fallback.js';
13
14
  import { formatCliError } from '../errors.js';
14
15
  import { BLESSED_COMFY_WORKFLOWS } from '../catalog.js';
15
- import { pollJobUntilTerminal, renderJobClosingBlock, stage as _stage, stageDone as _stageDone, writeBlock as _writeBlock, clearBlock as _clearBlock, renderLiveBlock as _renderLiveBlock, printFailureClass as _printFailureClass } from '../progress.js';
16
+ import { parseEnvFlag } from '../envFlag.js';
17
+ import { pollJobUntilTerminal, renderJobClosingBlock, stage as _stage, stageDone as _stageDone, writeBlock as _writeBlock, clearBlock as _clearBlock, renderLiveBlock as _renderLiveBlock, printFailureClass as _printFailureClass, printCapacityPreview, formatTierLabel } from '../progress.js';
16
18
 
17
19
  const COMFYUI_IMAGE = process.env.COMFYUI_IMAGE || 'yanwk/comfyui-boot:cu126-megapak';
18
20
  const HEALTH_PATH = '/system_stats';
@@ -35,6 +37,7 @@ export function parseComfyuiArgs(args) {
35
37
  if (a === '--check-nodes') { flags.checkNodes = args[++i]; i++; continue; }
36
38
  if (a === '--persistent') { flags.persistent = true; i++; continue; }
37
39
  if (a === '--yes' || a === '-y') { flags.yes = true; i++; continue; }
40
+ if (a === '--dry-run') { flags.dryRun = true; i++; continue; }
38
41
  if (a === '--env') {
39
42
  const kv = args[++i]; i++;
40
43
  if (!flags.env) flags.env = [];
@@ -46,15 +49,6 @@ export function parseComfyuiArgs(args) {
46
49
  return { workflow: positional[0] || null, flags };
47
50
  }
48
51
 
49
- function parseEnvFlag(envList) {
50
- const obj = {};
51
- for (const kv of (envList || [])) {
52
- const idx = kv.indexOf('=');
53
- if (idx > 0) obj[kv.slice(0, idx)] = kv.slice(idx + 1);
54
- }
55
- return obj;
56
- }
57
-
58
52
  // Live block while ComfyUI comes up — same generic "Checking health" stage
59
53
  // line and status block as every other launch path (see serve.js's
60
54
  // waitForEndpoint), so ComfyUI doesn't read as a different UX than vLLM.
@@ -179,16 +173,20 @@ export async function comfyBatchCommand(config, args, chalk) {
179
173
 
180
174
  if (flags.dryRun) {
181
175
  const workflowSpec = BLESSED_COMFY_WORKFLOWS[flags.workflow];
176
+ const resolvedGpu = flags.gpuType || workflowSpec?.gpu_type;
182
177
  console.log(chalk.bold('\n⚡ Dry run — no GPU will be provisioned\n'));
183
178
  console.log(` ${chalk.bold('Workflow:')} ${flags.workflow}${workflowSpec ? '' : chalk.yellow(' (unknown — server will reject this)')}`);
184
179
  if (workflowSpec) {
185
- console.log(` ${chalk.bold('GPU:')} ${flags.gpuType || workflowSpec.gpu_type}`);
180
+ console.log(` ${chalk.bold('GPU:')} ${resolvedGpu}`);
186
181
  console.log(` ${chalk.dim(workflowSpec.description)}`);
187
182
  }
188
183
  console.log(` ${chalk.bold('Prompts:')} ${prompts.length}${prompts.length > 20 ? chalk.yellow(' (exceeds the 20-prompt limit — server will reject this)') : ''}`);
184
+ console.log(` ${chalk.bold('Tier:')} ${formatTierLabel(normalizeTier(flags.tier))}`);
189
185
  if (flags.maxCost) console.log(` ${chalk.bold('Max cost:')} $${flags.maxCost}`);
190
186
  console.log(` ${chalk.bold('Max runtime:')} ${flags.maxRuntime ?? 60}min`);
191
- console.log(chalk.dim('\n Remove --dry-run to submit.\n'));
187
+ console.log();
188
+ if (resolvedGpu) await printCapacityPreview(chalk, config, { gpu: resolvedGpu });
189
+ console.log(chalk.dim(' Remove --dry-run to submit.\n'));
192
190
  return;
193
191
  }
194
192
 
@@ -282,9 +280,15 @@ export async function comfyuiCommand(config, args, chalk) {
282
280
  return;
283
281
  }
284
282
 
285
- config = await ensureBadgrReady(config, chalk);
283
+ // A dry run previews the plan only and never provisions or spends anything,
284
+ // so it doesn't need the interactive browser login — just a stored key.
285
+ if (flags.dryRun) {
286
+ requireApiKey(config);
287
+ } else {
288
+ config = await ensureBadgrReady(config, chalk);
289
+ }
286
290
 
287
- if (!flags.maxCost && !flags.persistent) {
291
+ if (!flags.maxCost && !flags.persistent && !flags.dryRun) {
288
292
  console.error(chalk.red('\n ✗ ComfyUI endpoints bill continuously. Specify a spending limit:\n'));
289
293
  console.error(chalk.dim(' --max-cost 5 auto-stop when $5 is reached'));
290
294
  console.error(chalk.dim(' --persistent run until you stop it manually\n'));
@@ -328,17 +332,29 @@ export async function comfyuiCommand(config, args, chalk) {
328
332
  const STAGE_TOTAL = 4;
329
333
  let stageN = 1;
330
334
 
331
- console.log(chalk.bold('\n⚡ Running ComfyUI\n'));
335
+ console.log(chalk.bold(flags.dryRun
336
+ ? '\n⚡ Dry run — no GPU will be provisioned — ComfyUI\n'
337
+ : '\n⚡ Running ComfyUI\n'));
332
338
  console.log(` ${chalk.bold('Workflow:')} ${workflow} (${nodeCount} nodes)`);
333
339
  console.log(` ${chalk.bold('Image:')} ${COMFYUI_IMAGE}`);
334
340
  console.log(` ${chalk.bold('GPU:')} ${gpu === 'AUTO' ? chalk.dim('auto (16+ GB VRAM)') : gpu}`);
341
+ if (flags.env?.length) console.log(` ${chalk.bold('Env:')} ${flags.env.join(', ')}`);
342
+ if (flags.dryRun) console.log(` ${chalk.bold('Tier:')} ${formatTierLabel(effectiveTier)}`);
335
343
  if (flags.maxCost) {
336
344
  console.log(` ${chalk.bold('Max cost:')} $${flags.maxCost.toFixed(2)} (auto-stop)`);
345
+ } else if (flags.dryRun) {
346
+ console.log(` ${chalk.bold('Max cost:')} ${chalk.dim('none')}`);
337
347
  } else {
338
348
  console.log(chalk.yellow(' ⚠ Persistent — billing until: badgr down <id>'));
339
349
  }
340
350
  console.log();
341
351
 
352
+ if (flags.dryRun) {
353
+ await printCapacityPreview(chalk, config, { gpu, region: flags.region?.toUpperCase(), maxPrice: flags.maxPrice });
354
+ console.log(chalk.dim(' Remove --dry-run to provision.\n'));
355
+ return;
356
+ }
357
+
342
358
  // Duplicate check
343
359
  if (config.apiKey) {
344
360
  try {