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/src/onboarding.js CHANGED
@@ -115,17 +115,76 @@ async function ensureFunded(config, chalk) {
115
115
  console.log(chalk.green('\n ✓ Payment confirmed\n'));
116
116
  }
117
117
 
118
- export async function ensureBadgrReady(config, chalk) {
118
+ /**
119
+ * Just-in-time login only, no funding gate — for commands that manage or
120
+ * inspect an *existing* resource (stop it, read its logs, download its
121
+ * artifacts, reset its idle timer) rather than provisioning new spend.
122
+ * Critically, `badgr down` must never be blocked behind "add funds first" —
123
+ * a $0-balance user with a still-running deployment needs to be able to
124
+ * stop it without a funding detour. Same browser-login-and-resume flow as
125
+ * ensureBadgrReady, just without the ensureFunded step.
126
+ */
127
+ export async function ensureLoggedInReady(config, chalk) {
119
128
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
120
129
  // No human to click a browser link — fail fast with the existing message.
121
130
  requireApiKey(config);
122
131
  return config;
123
132
  }
133
+ if (!config.apiKey) {
134
+ return ensureLoggedIn(config, chalk);
135
+ }
136
+ return config;
137
+ }
124
138
 
125
- let cfg = config;
126
- if (!cfg.apiKey) {
127
- cfg = await ensureLoggedIn(cfg, chalk);
139
+ /**
140
+ * Login + funding gate — for commands that provision new spend (run,
141
+ * serve, comfyui, launch/job, batch run, train, transcribe, embed, deploy,
142
+ * shell, sbatch, up). Reuses ensureLoggedInReady for the login half so
143
+ * there is exactly one login flow, not two.
144
+ */
145
+ export async function ensureBadgrReady(config, chalk) {
146
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
147
+ // No human to click a browser link — fail fast with the existing
148
+ // message, same as ensureLoggedInReady, and skip ensureFunded too:
149
+ // there's no one to click the billing checkout link either.
150
+ requireApiKey(config);
151
+ return config;
128
152
  }
153
+ const cfg = await ensureLoggedInReady(config, chalk);
129
154
  await ensureFunded(cfg, chalk);
130
155
  return cfg;
131
156
  }
157
+
158
+ /**
159
+ * Both ensureLoggedInReady and ensureBadgrReady only ever check whether a
160
+ * key is *configured* -- neither can tell a stored key is stale/expired/
161
+ * revoked without actually calling the API, so a command with a bad saved
162
+ * key sailed straight past both checks and only found out from the real
163
+ * request's own 401, which every command before this helper just printed
164
+ * ("Invalid API key ... run: badgr login") and exited. That's a dead end
165
+ * for the exact case ensureLoggedInReady/ensureBadgrReady exist to avoid:
166
+ * the person is sitting right there at an interactive terminal.
167
+ *
168
+ * withReauthRetry(config, chalk, fn) calls fn(config) once; on a 401/403
169
+ * in an interactive TTY, it opens the same browser login link
170
+ * ensureLoggedIn always has, waits for it, then retries fn exactly once
171
+ * with the fresh key. Any other error (including a repeat 401/403 after a
172
+ * successful re-login, or non-interactive/CI) is re-thrown unchanged for
173
+ * the caller's own existing error handling.
174
+ *
175
+ * Returns { config, result } -- callers should keep using the returned
176
+ * config (not their original variable) for anything they do afterward, so
177
+ * a refreshed key from a mid-command reauth is actually used for the rest
178
+ * of that same run instead of only being picked up by the next command.
179
+ */
180
+ export async function withReauthRetry(config, chalk, fn) {
181
+ try {
182
+ return { config, result: await fn(config) };
183
+ } catch (err) {
184
+ if (err.httpStatus !== 401 && err.httpStatus !== 403) throw err;
185
+ if (!process.stdin.isTTY || !process.stdout.isTTY) throw err;
186
+ console.log(chalk.yellow('\n Your saved API key was rejected. Signing in again...'));
187
+ const freshConfig = await ensureLoggedIn(config, chalk);
188
+ return { config: freshConfig, result: await fn(freshConfig) };
189
+ }
190
+ }
package/src/progress.js CHANGED
@@ -33,11 +33,14 @@ export function clearBlock(lineCount) {
33
33
  // `badgr serve` (deployment) and `badgr run` (job): elapsed time, a status
34
34
  // word, an *estimated* spend (never presented as confirmed while billing is
35
35
  // still live), and the exact commands to inspect logs or stop billing.
36
- export function renderLiveBlock(chalk, { stageLine, elapsedSec, statusWord, spend, id }) {
36
+ export function renderLiveBlock(chalk, { stageLine, elapsedSec, statusWord, spend, id, maxCost }) {
37
+ const spendLine = maxCost
38
+ ? ` Estimated spend: ~$${spend.toFixed(2)} / $${maxCost.toFixed(2)}`
39
+ : ` Estimated spend: ~$${spend.toFixed(2)}`;
37
40
  return [
38
41
  chalk.dim(`${stageLine} ${elapsedSec}s elapsed`),
39
42
  chalk.dim(` Status: ${statusWord}`),
40
- chalk.dim(` Estimated spend: ~$${spend.toFixed(2)}`),
43
+ chalk.dim(spendLine),
41
44
  chalk.dim(` Logs: badgr logs ${id}`),
42
45
  chalk.dim(` Stop billing: badgr down ${id}`),
43
46
  ];