badgr-cli 1.1.2 → 1.1.4

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/errors.js CHANGED
@@ -17,7 +17,12 @@ export const CATALOG = {
17
17
  // ── Capacity ──────────────────────────────────────────────────────────────
18
18
 
19
19
  NO_CAPACITY: {
20
+ // Prefer the backend's specific detail (e.g. "CPU launch is not
21
+ // configured on this backend (missing HETZNER_API_TOKEN)") over the
22
+ // generic template — a real config/provider reason is far more
23
+ // actionable than "not available right now" when that's not why.
20
24
  message: (ctx) =>
25
+ ctx.server_message ||
21
26
  `No ${ctx.gpu || 'GPU'} available${ctx.region ? ` in ${ctx.region}` : ''} right now.`,
22
27
  billing: 'never_started',
23
28
  retried: false,
package/src/fallback.js CHANGED
@@ -6,6 +6,13 @@ import { CATALOG, formatCliError } from './errors.js';
6
6
  /** Rates above this threshold trigger a visible warning when no --max-cost is set. */
7
7
  export const HIGH_RATE_THRESHOLD = 3.00;
8
8
 
9
+ // ── badgr run --smoke defaults ────────────────────────────────────────────────
10
+ // Hardcoded default smoke caps, not a config system — --max-cost/--max-runtime
11
+ // still override them when passed explicitly (see run.js). The goal is to stop
12
+ // expensive local smoke tests by default, not build another configuration system.
13
+ export const SMOKE_MAX_COST_USD = 0.25;
14
+ export const SMOKE_MAX_RUNTIME_MINUTES = 10;
15
+
9
16
  /** Normalise --tier flag variants to '1' or '2'. */
10
17
  export function normalizeTier(tier) {
11
18
  return (tier === '2' || tier === 'tier2' || tier === 'tier-2') ? '2' : (tier || '1');
@@ -37,12 +44,16 @@ export class CapacityError extends Error {
37
44
  * @param {object} labels - { thing: 'job'|'endpoint', cmd: 'badgr run'|'badgr serve' }
38
45
  * @param {object} [opts]
39
46
  * @param {boolean} [opts.allowTier2Fallback=true] - set false to disable tier-2 expansion
47
+ * @param {boolean} [opts.singleAttempt=false] - set true to skip both the same-tier
48
+ * provider retry and tier-2 expansion, failing immediately on the first error
49
+ * (badgr run --smoke's "one attempt" guarantee)
40
50
  */
41
51
  export async function callWithFallback(endpoint, callOpts, buildBody, effectiveTier, chalk, labels, opts = {}) {
42
52
  const { callApi } = await import('./api.js');
43
53
  const thing = labels?.thing ?? 'job';
44
54
  const cmd = labels?.cmd ?? 'badgr run';
45
- const allowTier2Fallback = opts.allowTier2Fallback !== false; // default true
55
+ const singleAttempt = opts.singleAttempt === true;
56
+ const allowTier2Fallback = !singleAttempt && opts.allowTier2Fallback !== false; // default true
46
57
 
47
58
  // 220s: comfortably above backend's BADGR_PROVISION_TIMEOUT_SECONDS (default
48
59
  // 200s, itself set above deployment_service.py's 180s routing-search
@@ -82,7 +93,10 @@ export async function callWithFallback(endpoint, callOpts, buildBody, effectiveT
82
93
  if (key) {
83
94
  const ctx = {
84
95
  gpu: d.filters?.gpu ?? d.gpu,
85
- region: d.filters?.region,
96
+ // Server uses the literal string "any" as a filters.region placeholder
97
+ // when no region was requested — don't surface that as if the user
98
+ // had actually asked for a region named "any" (e.g. "in any right now").
99
+ region: d.filters?.region && d.filters.region !== 'any' ? d.filters.region : undefined,
86
100
  failure_category: d.failure_category,
87
101
  low_cost_failed: d.low_cost_provider_failed,
88
102
  server_message: d.message,
@@ -113,7 +127,7 @@ export async function callWithFallback(endpoint, callOpts, buildBody, effectiveT
113
127
  // Provider retry: PROVISIONING_FAILED means the selected provider couldn't launch the slot.
114
128
  // Retry once with prefer_different_provider so the backend routes to a different provider
115
129
  // (e.g. RunPod failed → try Vast.ai or Hyperstack) within the same max_cost budget.
116
- if (d?.code === 'PROVISIONING_FAILED' || d?.code === 'PROVIDER_ADAPTER_ERROR') {
130
+ if (!singleAttempt && (d?.code === 'PROVISIONING_FAILED' || d?.code === 'PROVIDER_ADAPTER_ERROR')) {
117
131
  console.log(chalk.dim('\n Provider unavailable — trying alternative provider...\n'));
118
132
  try {
119
133
  const retryBody = { ...buildBody(), prefer_different_provider: true };
@@ -1,25 +0,0 @@
1
- import { launchCommand } from './launch.js';
2
-
3
- /**
4
- * badgr task "<description>" -- <command>
5
- *
6
- * The MVP version of "assign a task" — a thin label wrapper over
7
- * `badgr launch . -- <command>`. It does not queue, schedule, or track
8
- * anything beyond what `badgr launch` already does; the description is
9
- * printed for the human and otherwise discarded (the receipt/status page
10
- * still key off the deployment ID, same as any other launch).
11
- */
12
- export async function taskCommand(config, args, chalk) {
13
- const description = args[0];
14
- if (!description || description.startsWith('-')) {
15
- console.error(chalk.red('\nUsage: badgr task "<description>" [badgr launch flags...] -- <command>\n'));
16
- console.error(chalk.dim(' The description must come first, before any flags.'));
17
- console.error(chalk.dim(' Example: badgr task "Run the Chromium tests and tell me what failed" --max-cost 1 -- npm run test:chromium'));
18
- console.error(chalk.dim(' Example: badgr task "Fix the failing checkout test" --max-cost 1 -- claude -p "Fix the failing checkout test"\n'));
19
- process.exitCode = 1;
20
- return;
21
- }
22
-
23
- console.log(chalk.dim(` Task: ${description}`));
24
- return launchCommand(config, ['.', ...args.slice(1)], chalk);
25
- }