@odla-ai/cli 0.37.1 → 0.38.0

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/dist/bin.cjs CHANGED
@@ -1322,6 +1322,74 @@ var init_argv = __esm({
1322
1322
  }
1323
1323
  });
1324
1324
 
1325
+ // src/admin-spend.ts
1326
+ async function call(ctx, method, scope) {
1327
+ const url = `${ctx.platformUrl.replace(/\/$/, "")}/registry/platform/spend?scope=${encodeURIComponent(scope)}`;
1328
+ const response2 = await ctx.doFetch(url, {
1329
+ method,
1330
+ headers: { authorization: `Bearer ${ctx.token}` }
1331
+ });
1332
+ const body = await response2.json().catch(() => ({}));
1333
+ if (!response2.ok) {
1334
+ const detail = body.error?.message ?? body.error?.code ?? `registry returned ${response2.status}`;
1335
+ throw new Error(`spend ${method} failed: ${detail} (${response2.status})`);
1336
+ }
1337
+ return body;
1338
+ }
1339
+ async function spendShow(ctx, scope) {
1340
+ const view = await call(ctx, "GET", scope);
1341
+ if (ctx.json) return ctx.out.log(JSON.stringify(view, null, 2));
1342
+ ctx.out.log(`scope: ${view.scope}`);
1343
+ ctx.out.log(`day: ${view.day} (UTC)`);
1344
+ ctx.out.log(
1345
+ `spent: ${money(view.spentUsd)} of ${view.capUsd > 0 ? money(view.capUsd) : "(no cap set)"}`
1346
+ );
1347
+ ctx.out.log(`calls: ${view.calls}`);
1348
+ if (view.unpricedCalls > 0) {
1349
+ ctx.out.log(
1350
+ `unpriced: ${view.unpricedCalls} call(s) had no price, so "spent" is a floor, not the total.`
1351
+ );
1352
+ }
1353
+ if (!view.latched) return ctx.out.log("status: running");
1354
+ ctx.out.log(
1355
+ `status: HALTED \u2014 reached ${money(view.latched.capUsd)} on ${view.latched.day} (${view.latched.reason}), at ${money(view.latched.costUsd)}.`
1356
+ );
1357
+ ctx.out.log("A new day does not clear this. Resume with:");
1358
+ ctx.out.log(` odla-ai admin spend reset ${view.scope}`);
1359
+ }
1360
+ async function spendReset(ctx, scope) {
1361
+ const result = await call(ctx, "DELETE", scope);
1362
+ if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
1363
+ ctx.out.log(result.message);
1364
+ if (result.stillOverCap) {
1365
+ ctx.out.log(
1366
+ ` today: ${money(result.spentUsd)} against a ${money(result.capUsd)} cap.`
1367
+ );
1368
+ }
1369
+ }
1370
+ async function adminSpend(parsed, ctx) {
1371
+ const action2 = parsed.positionals[2];
1372
+ const scope = parsed.positionals[3] ?? stringOpt(parsed.options.scope);
1373
+ if (action2 !== "show" && action2 !== "reset") {
1374
+ throw new Error('unknown spend command. Try "odla-ai admin spend show <scope>".');
1375
+ }
1376
+ if (!scope) {
1377
+ throw new Error(
1378
+ `"admin spend ${action2}" needs a scope, e.g. odla-ai admin spend ${action2} app:my-app:<incarnation>`
1379
+ );
1380
+ }
1381
+ return action2 === "show" ? spendShow(ctx, scope) : spendReset(ctx, scope);
1382
+ }
1383
+ var money;
1384
+ var init_admin_spend = __esm({
1385
+ "src/admin-spend.ts"() {
1386
+ "use strict";
1387
+ init_cjs_shims();
1388
+ init_argv();
1389
+ money = (value2) => `$${value2.toFixed(2)}`;
1390
+ }
1391
+ });
1392
+
1325
1393
  // src/ai-config-validation.ts
1326
1394
  function validateAiConfig(cfg, path) {
1327
1395
  if (cfg.ai === void 0) return;
@@ -2141,6 +2209,31 @@ var init_operator_context = __esm({
2141
2209
  async function adminCommand(parsed, deps = {}) {
2142
2210
  const area = parsed.positionals[1];
2143
2211
  const action2 = parsed.positionals[2];
2212
+ if (area === "spend") {
2213
+ assertArgs(parsed, JSON_OPTIONS, 4);
2214
+ const context2 = await resolveOperatorContext(parsed, { allowMissingConfig: true });
2215
+ const out = deps.stdout ?? console;
2216
+ const doFetch = deps.fetch ?? fetch;
2217
+ const token = await getDeveloperToken(
2218
+ context2.cfg,
2219
+ {
2220
+ configPath: context2.cfg.configPath,
2221
+ token: stringOpt(parsed.options.token),
2222
+ email: stringOpt(parsed.options.email),
2223
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
2224
+ openApprovalUrl: deps.openUrl
2225
+ },
2226
+ doFetch,
2227
+ out
2228
+ );
2229
+ return adminSpend(parsed, {
2230
+ platformUrl: context2.platform.value,
2231
+ token,
2232
+ doFetch,
2233
+ json: parsed.options.json === true,
2234
+ out
2235
+ });
2236
+ }
2144
2237
  const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
2145
2238
  const credentials = action2 === "credentials";
2146
2239
  const models = action2 === "models";
@@ -2190,6 +2283,8 @@ var init_admin_command = __esm({
2190
2283
  "use strict";
2191
2284
  init_cjs_shims();
2192
2285
  init_admin_ai();
2286
+ init_admin_spend();
2287
+ init_token();
2193
2288
  init_argv();
2194
2289
  init_operator_context();
2195
2290
  CONTEXT_OPTIONS = ["platform", "config", "context", "token", "open", "email"];
@@ -2265,7 +2360,8 @@ async function fetchIdentity(platformUrl, token, doFetch) {
2265
2360
  email,
2266
2361
  admin: body.admin === true,
2267
2362
  machine,
2268
- scopes
2363
+ scopes,
2364
+ projects: Array.isArray(body.projects) ? body.projects.map(String) : null
2269
2365
  };
2270
2366
  }
2271
2367
  function credentialLabel(identity) {
@@ -2335,6 +2431,13 @@ async function whoamiCommand(parsed, deps = {}) {
2335
2431
  out.log(`credential id: ${identity.credential.id}`);
2336
2432
  out.log(`admin: ${identity.admin ? "yes" : "no"}`);
2337
2433
  if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
2434
+ if (identity.projects === null) {
2435
+ out.log("projects: (this registry does not report project grants)");
2436
+ } else if (identity.projects.length) {
2437
+ out.log(`projects: ${identity.projects.join(", ")}`);
2438
+ } else {
2439
+ out.log("projects: (none \u2014 every pm and discuss call will be refused)");
2440
+ }
2338
2441
  if (!identity.admin) {
2339
2442
  if (identity.scopes.includes("platform:runbook:write")) {
2340
2443
  out.log("\nThis exact scope can read and edit all platform runbook content.");
@@ -6211,17 +6314,17 @@ var init_cli_project = __esm({
6211
6314
  }
6212
6315
  });
6213
6316
 
6214
- // ../harness/dist/chunk-3QP4VDQS.js
6317
+ // ../harness/dist/chunk-LNQNFGQC.js
6215
6318
  var HARNESS_PROTOCOL_VERSION;
6216
- var init_chunk_3QP4VDQS = __esm({
6217
- "../harness/dist/chunk-3QP4VDQS.js"() {
6319
+ var init_chunk_LNQNFGQC = __esm({
6320
+ "../harness/dist/chunk-LNQNFGQC.js"() {
6218
6321
  "use strict";
6219
6322
  init_cjs_shims();
6220
6323
  HARNESS_PROTOCOL_VERSION = 1;
6221
6324
  }
6222
6325
  });
6223
6326
 
6224
- // ../harness/dist/chunk-GKDKIU4P.js
6327
+ // ../harness/dist/chunk-K76I2TCQ.js
6225
6328
  function assertPinnedImage(image) {
6226
6329
  if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
6227
6330
  }
@@ -6570,8 +6673,8 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
6570
6673
  }
6571
6674
  }
6572
6675
  var import_child_process, import_fs, import_promises2, import_path, import_process, import_promises3, import_os, import_path2, import_child_process2, import_path3, import_promises4, import_os2, import_path4, import_child_process3, DIGEST_IMAGE, SKIP_WORKSPACE_DIRS, SECRET_WORKSPACE_FILE;
6573
- var init_chunk_GKDKIU4P = __esm({
6574
- "../harness/dist/chunk-GKDKIU4P.js"() {
6676
+ var init_chunk_K76I2TCQ = __esm({
6677
+ "../harness/dist/chunk-K76I2TCQ.js"() {
6575
6678
  "use strict";
6576
6679
  init_cjs_shims();
6577
6680
  import_child_process = require("child_process");
@@ -7569,7 +7672,7 @@ var init_code2 = __esm({
7569
7672
  }
7570
7673
  });
7571
7674
 
7572
- // ../harness/dist/chunk-ANNX7VGK.js
7675
+ // ../harness/dist/chunk-UVGZHNLW.js
7573
7676
  async function digestStagedWorkspace(root, limits) {
7574
7677
  const files = [];
7575
7678
  const walk = async (directory) => {
@@ -7647,7 +7750,7 @@ function createCodeRuntimeControlClient(options) {
7647
7750
  throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
7648
7751
  }
7649
7752
  const request3 = options.fetch ?? fetch;
7650
- const call3 = async (path, body, timeoutMs = requestTimeoutMs) => {
7753
+ const call4 = async (path, body, timeoutMs = requestTimeoutMs) => {
7651
7754
  const timeout = AbortSignal.timeout(timeoutMs);
7652
7755
  const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
7653
7756
  let response2;
@@ -7677,17 +7780,17 @@ function createCodeRuntimeControlClient(options) {
7677
7780
  return {
7678
7781
  heartbeat: async (version, capabilities) => {
7679
7782
  validateHeartbeat(version, capabilities);
7680
- return parseSnapshot(await call3("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
7783
+ return parseSnapshot(await call4("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
7681
7784
  },
7682
7785
  acknowledge: async (commandId, result) => {
7683
7786
  if (!/^ccmd_[0-9a-f]{32}$/.test(commandId)) throw new TypeError("invalid Code runtime command id");
7684
- await call3(`/registry/code/runtime/commands/${commandId}/ack`, result);
7787
+ await call4(`/registry/code/runtime/commands/${commandId}/ack`, result);
7685
7788
  },
7686
7789
  source: async (sessionId) => parseSource(
7687
- await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
7790
+ await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
7688
7791
  ),
7689
7792
  infer: async (sessionId, inference) => {
7690
- const value2 = record5(await call3(
7793
+ const value2 = record5(await call4(
7691
7794
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
7692
7795
  inference,
7693
7796
  modelRequestTimeoutMs
@@ -7698,11 +7801,11 @@ function createCodeRuntimeControlClient(options) {
7698
7801
  return value2;
7699
7802
  },
7700
7803
  review: async (sessionId, review) => parseReview(
7701
- await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
7804
+ await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
7702
7805
  ),
7703
7806
  submitCandidate: async (sessionId, checkpointId, verification) => {
7704
7807
  if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
7705
- return parseCandidate(await call3(
7808
+ return parseCandidate(await call4(
7706
7809
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/candidates`,
7707
7810
  { checkpointId, verification }
7708
7811
  ));
@@ -7712,21 +7815,21 @@ function createCodeRuntimeControlClient(options) {
7712
7815
  if (!/^[A-Za-z0-9._:-]{1,120}$/.test(eventId) || !event || typeof event !== "object" || new TextEncoder().encode(serialized).byteLength > 24e3) {
7713
7816
  throw new TypeError("invalid Code session event");
7714
7817
  }
7715
- await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
7818
+ await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
7716
7819
  },
7717
7820
  recallMemories: async (sessionId, subjects, limit) => {
7718
- const response2 = await call3(
7821
+ const response2 = await call4(
7719
7822
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/recall`,
7720
7823
  { subjects: [...subjects], limit }
7721
7824
  );
7722
7825
  return Array.isArray(response2.memories) ? response2.memories : [];
7723
7826
  },
7724
7827
  rememberMemory: async (sessionId, memory) => {
7725
- await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
7828
+ await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
7726
7829
  },
7727
7830
  reportSessionFailure: async (sessionId, message2) => {
7728
7831
  if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
7729
- await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
7832
+ await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
7730
7833
  }
7731
7834
  };
7732
7835
  }
@@ -8512,7 +8615,7 @@ async function materializeCommandWorkspace(input) {
8512
8615
  }
8513
8616
  function codeSkill(opts) {
8514
8617
  let seq = 0;
8515
- const call3 = async (tool, input, signal) => {
8618
+ const call4 = async (tool, input, signal) => {
8516
8619
  const startedAt = Date.now();
8517
8620
  const response2 = await opts.broker.execute(
8518
8621
  { lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
@@ -8534,7 +8637,7 @@ function codeSkill(opts) {
8534
8637
  },
8535
8638
  additionalProperties: false
8536
8639
  },
8537
- handler: (input, ctx) => call3("sandbox.read", input, ctx.signal)
8640
+ handler: (input, ctx) => call4("sandbox.read", input, ctx.signal)
8538
8641
  };
8539
8642
  const applyPatch = {
8540
8643
  name: "odla_apply_git_diff",
@@ -8545,7 +8648,7 @@ function codeSkill(opts) {
8545
8648
  properties: { patch: { type: "string", minLength: 1, maxLength: 262144 } },
8546
8649
  additionalProperties: false
8547
8650
  },
8548
- handler: (input, ctx) => call3("sandbox.apply_patch", input, ctx.signal)
8651
+ handler: (input, ctx) => call4("sandbox.apply_patch", input, ctx.signal)
8549
8652
  };
8550
8653
  const runRecipe = {
8551
8654
  name: "odla_run_recipe",
@@ -8556,7 +8659,7 @@ function codeSkill(opts) {
8556
8659
  properties: { recipeId: { type: "string", minLength: 1, maxLength: 120, pattern: "^[a-zA-Z0-9._:-]+$" } },
8557
8660
  additionalProperties: false
8558
8661
  },
8559
- handler: (input, ctx) => call3("sandbox.run_recipe", input, ctx.signal)
8662
+ handler: (input, ctx) => call4("sandbox.run_recipe", input, ctx.signal)
8560
8663
  };
8561
8664
  const listFiles2 = {
8562
8665
  name: "odla_list",
@@ -8569,7 +8672,7 @@ function codeSkill(opts) {
8569
8672
  },
8570
8673
  additionalProperties: false
8571
8674
  },
8572
- handler: (input, ctx) => call3("sandbox.list", input, ctx.signal)
8675
+ handler: (input, ctx) => call4("sandbox.list", input, ctx.signal)
8573
8676
  };
8574
8677
  const searchFiles = {
8575
8678
  name: "odla_search",
@@ -8585,7 +8688,7 @@ function codeSkill(opts) {
8585
8688
  },
8586
8689
  additionalProperties: false
8587
8690
  },
8588
- handler: (input, ctx) => call3("sandbox.search", input, ctx.signal)
8691
+ handler: (input, ctx) => call4("sandbox.search", input, ctx.signal)
8589
8692
  };
8590
8693
  const graphTool = (name, tool, description, required) => ({
8591
8694
  name,
@@ -8596,7 +8699,7 @@ function codeSkill(opts) {
8596
8699
  properties: { query: { type: "string", maxLength: 512 } },
8597
8700
  additionalProperties: false
8598
8701
  },
8599
- handler: (input, ctx) => call3(tool, input, ctx.signal)
8702
+ handler: (input, ctx) => call4(tool, input, ctx.signal)
8600
8703
  });
8601
8704
  const orientation = [
8602
8705
  graphTool(
@@ -8635,9 +8738,9 @@ async function runCodeAgent(options) {
8635
8738
  lease: options.lease,
8636
8739
  workspaceDir: options.workspaceDir,
8637
8740
  surface,
8638
- onToolCall: (call3) => {
8639
- toolCalls.push(call3);
8640
- options.onToolCall?.(call3);
8741
+ onToolCall: (call4) => {
8742
+ toolCalls.push(call4);
8743
+ options.onToolCall?.(call4);
8641
8744
  }
8642
8745
  });
8643
8746
  const compaction = options.compaction === void 0 ? (0, import_ai4.keepRecentExchanges)({ whenInputTokensExceed: 12e4, keep: 3 }) : options.compaction;
@@ -8722,6 +8825,9 @@ async function handleCodeRuntimeInference(input) {
8722
8825
  call: request3.call
8723
8826
  });
8724
8827
  state2.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
8828
+ const { costUsd } = response2.receipt;
8829
+ if (costUsd === void 0) state2.costKnown = false;
8830
+ else state2.costUsd += costUsd;
8725
8831
  await input.event({
8726
8832
  type: "usage",
8727
8833
  provider: response2.receipt.provider,
@@ -8731,7 +8837,9 @@ async function handleCodeRuntimeInference(input) {
8731
8837
  durationMs: Date.now() - startedAt,
8732
8838
  interactionId: command.commandId,
8733
8839
  interactionTokens: state2.tokens,
8734
- interactionMaxTokens: metadata2.maxTokensPerInteraction
8840
+ interactionMaxTokens: metadata2.maxTokensPerInteraction,
8841
+ ...costUsd === void 0 ? {} : { costUsd },
8842
+ ...state2.costKnown ? { interactionCostUsd: state2.costUsd } : {}
8735
8843
  }).catch(() => void 0);
8736
8844
  return {
8737
8845
  protocolVersion: HARNESS_PROTOCOL_VERSION,
@@ -9522,10 +9630,16 @@ async function startGoalPursuit(input) {
9522
9630
  attempt: async ({ prompt }) => {
9523
9631
  const result = await input.attempt(prompt);
9524
9632
  return {
9525
- // The runtime charges tokens through the control plane's own
9526
- // per-interaction reservation, so the goal budget bounds ATTEMPTS here
9527
- // and the token ceiling is enforced where the credential lives.
9528
- tokens: 0,
9633
+ // What the attempt actually spent, so the runner's token_budget and
9634
+ // cost_budget checks can be reached. This used to be a hardcoded 0 with
9635
+ // no cost at all, which made maxTokens and maxUsd unreachable while
9636
+ // callers reasonably read them as hard ceilings.
9637
+ //
9638
+ // costUsd is omitted rather than zeroed when any call in the attempt
9639
+ // was unpriced: the runner only enforces a cost budget while the cost
9640
+ // is known, and a zero would make it enforce against a lie.
9641
+ tokens: result.tokens ?? 0,
9642
+ ...result.costUsd === void 0 ? {} : { costUsd: result.costUsd },
9529
9643
  ...result.status === "failed" ? { error: result.error ?? "attempt failed" } : {}
9530
9644
  };
9531
9645
  },
@@ -9554,12 +9668,12 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
9554
9668
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
9555
9669
  }
9556
9670
  var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, CodePiRuntimeEngine;
9557
- var init_chunk_ANNX7VGK = __esm({
9558
- "../harness/dist/chunk-ANNX7VGK.js"() {
9671
+ var init_chunk_UVGZHNLW = __esm({
9672
+ "../harness/dist/chunk-UVGZHNLW.js"() {
9559
9673
  "use strict";
9560
9674
  init_cjs_shims();
9561
- init_chunk_GKDKIU4P();
9562
- init_chunk_3QP4VDQS();
9675
+ init_chunk_K76I2TCQ();
9676
+ init_chunk_LNQNFGQC();
9563
9677
  import_crypto = require("crypto");
9564
9678
  import_promises5 = require("fs/promises");
9565
9679
  import_path5 = require("path");
@@ -9973,7 +10087,7 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
9973
10087
  recipeAuthorization: this.options.recipeAuthorization
9974
10088
  }, lease, metadata2.role));
9975
10089
  const startedAt = Date.now();
9976
- const interaction = { tokens: 0, noticeEmitted: false };
10090
+ const interaction = { tokens: 0, noticeEmitted: false, costUsd: 0, costKnown: true };
9977
10091
  const inference = createCodeRuntimeInference({
9978
10092
  command,
9979
10093
  metadata: metadata2,
@@ -10011,7 +10125,11 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
10011
10125
  await this.#diagnostic(command, active, detail);
10012
10126
  await this.#failure(command, active, detail);
10013
10127
  }
10014
- return result;
10128
+ return {
10129
+ ...result,
10130
+ tokens: interaction.tokens,
10131
+ ...interaction.costKnown ? { costUsd: interaction.costUsd } : {}
10132
+ };
10015
10133
  }
10016
10134
  /** Report every brokered effect as it starts and finishes. */
10017
10135
  #observed(command, active, broker) {
@@ -10070,8 +10188,8 @@ var init_node = __esm({
10070
10188
  "../harness/dist/node.js"() {
10071
10189
  "use strict";
10072
10190
  init_cjs_shims();
10073
- init_chunk_ANNX7VGK();
10074
- init_chunk_GKDKIU4P();
10191
+ init_chunk_UVGZHNLW();
10192
+ init_chunk_K76I2TCQ();
10075
10193
  MEASURED_PREMIUM = Object.freeze({
10076
10194
  /** 3 racers vs pure depth at equal budget: 21,044 / 7,936. */
10077
10195
  racePerRacer: 0.55,
@@ -11175,6 +11293,8 @@ Usage:
11175
11293
  odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
11176
11294
  odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
11177
11295
  odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
11296
+ odla-ai admin spend show <app:<id>:<incarnation>> [--context <name>] [--json]
11297
+ odla-ai admin spend reset <app:<id>:<incarnation>> [--context <name>] [--json]
11178
11298
  odla-ai security github connect [--repo owner/name] [--env dev] [continue in Studio; human session required]
11179
11299
  odla-ai security github disconnect --source <id> [--env dev] [continue in Studio; human session required]
11180
11300
  odla-ai security plan [--env dev] [--json]
@@ -11269,6 +11389,9 @@ Commands:
11269
11389
  human session connects one); "code grant request|list|approve|revoke" then
11270
11390
  governs unattended access: an agent may request, only a human may approve.
11271
11391
  admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
11392
+ "admin spend" reads one app's daily inference spend and resumes it
11393
+ after a cap halts it. The cap is per UTC day; exhausting it LATCHES,
11394
+ and a new day does not clear the latch \u2014 resuming is deliberate.
11272
11395
  security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
11273
11396
  pm Project management (via @odla-ai/pm): Products contain Projects;
11274
11397
  projects contain goals, kanban tasks, decisions, and bugs. Use
@@ -11276,6 +11399,12 @@ Commands:
11276
11399
  pass --app/--project explicitly. Same device-grant auth as "app".
11277
11400
  Status changes and comments post to each item's @odla-ai/chat
11278
11401
  discussion thread.
11402
+ NOTE: "--column ready" is OWNER-ONLY. Creating a task in Ready
11403
+ approves its complete execution contract, so it needs pm.plan,
11404
+ which no device enrollment or handshake approval can grant. An
11405
+ agent proposes in Backlog (the default); a human owner or a
11406
+ pm.plan agent promotes. This is deliberate, not a permission
11407
+ gap \u2014 it was reported as one.
11279
11408
  bug Intent-first alias for PM bugs. "bug report" writes to
11280
11409
  odla PM; odla product defects do not belong in GitHub Issues.
11281
11410
  discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
@@ -14700,7 +14829,7 @@ var init_device_command = __esm({
14700
14829
  });
14701
14830
 
14702
14831
  // src/runbook-actions.ts
14703
- async function call(ctx, method, path, body) {
14832
+ async function call2(ctx, method, path, body) {
14704
14833
  const res = await ctx.doFetch(`${ctx.platformUrl.replace(/\/$/, "")}/registry/pm${path}`, {
14705
14834
  method,
14706
14835
  headers: {
@@ -14722,7 +14851,7 @@ async function call(ctx, method, path, body) {
14722
14851
  throw new Error(message2);
14723
14852
  }
14724
14853
  async function bySlug(ctx, slug) {
14725
- const page2 = await call(
14854
+ const page2 = await call2(
14726
14855
  ctx,
14727
14856
  "GET",
14728
14857
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
@@ -14731,7 +14860,7 @@ async function bySlug(ctx, slug) {
14731
14860
  if (filtered) return filtered;
14732
14861
  const limit = 100;
14733
14862
  for (let offset = 0; ; offset += limit) {
14734
- const fallback = await call(
14863
+ const fallback = await call2(
14735
14864
  ctx,
14736
14865
  "GET",
14737
14866
  `/runbook?app=${encodeURIComponent(ctx.appId)}&limit=${limit}&offset=${offset}`
@@ -14751,7 +14880,7 @@ async function runbookList(ctx, all, query) {
14751
14880
  const params = new URLSearchParams();
14752
14881
  if (!all) params.set("app", ctx.appId);
14753
14882
  if (query) params.set("q", query);
14754
- const page2 = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
14883
+ const page2 = await call2(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
14755
14884
  if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
14756
14885
  if (!page2.records.length) return ctx.out.log("(no runbooks)");
14757
14886
  ctx.out.log(["SLUG", "STATUS", "V", "SCOPE", "UPDATED", "TITLE"].join(" "));
@@ -14768,7 +14897,7 @@ async function runbookGet(ctx, slug) {
14768
14897
  ctx.out.log(runbook.body);
14769
14898
  }
14770
14899
  async function runbookNew(ctx, slug, title, body, summary, requires) {
14771
- const created = await call(ctx, "POST", "/runbook", {
14900
+ const created = await call2(ctx, "POST", "/runbook", {
14772
14901
  appId: ctx.appId,
14773
14902
  input: { slug, title, body, ...summary ? { summary } : {}, ...requires ? { requires } : {} }
14774
14903
  });
@@ -14776,7 +14905,7 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
14776
14905
  }
14777
14906
  async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
14778
14907
  const runbook = await bySlug(ctx, slug);
14779
- const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
14908
+ const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
14780
14909
  // An empty --requires clears the declaration; omitting the flag leaves
14781
14910
  // whatever is there, so an ordinary body edit never drops it.
14782
14911
  patch: {
@@ -14791,7 +14920,7 @@ async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
14791
14920
  }
14792
14921
  async function runbookStatus(ctx, slug, status) {
14793
14922
  const runbook = await bySlug(ctx, slug);
14794
- const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
14923
+ const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
14795
14924
  patch: { status }
14796
14925
  });
14797
14926
  ctx.out.log(ctx.json ? JSON.stringify(result, null, 2) : `${slug} \u2192 ${status}`);
@@ -14800,7 +14929,7 @@ async function runbookVisibility(ctx, slug, visibility) {
14800
14929
  if (visibility !== "operator" && visibility !== "admin")
14801
14930
  throw new Error(`visibility must be "operator" or "admin", got "${visibility}"`);
14802
14931
  const runbook = await bySlug(ctx, slug);
14803
- const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
14932
+ const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
14804
14933
  patch: { visibility }
14805
14934
  });
14806
14935
  if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
@@ -14810,7 +14939,7 @@ async function runbookVisibility(ctx, slug, visibility) {
14810
14939
  }
14811
14940
  async function runbookHistory(ctx, slug) {
14812
14941
  const runbook = await bySlug(ctx, slug);
14813
- const page2 = await call(ctx, "GET", `/runbook/${encodeURIComponent(runbook.id)}/revisions`);
14942
+ const page2 = await call2(ctx, "GET", `/runbook/${encodeURIComponent(runbook.id)}/revisions`);
14814
14943
  if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
14815
14944
  ctx.out.log(`${slug} is at v${runbook.version}`);
14816
14945
  if (!page2.records.length) return ctx.out.log("(no earlier versions)");
@@ -14820,7 +14949,7 @@ async function runbookHistory(ctx, slug) {
14820
14949
  }
14821
14950
  async function runbookRevert(ctx, slug, version) {
14822
14951
  const runbook = await bySlug(ctx, slug);
14823
- const result = await call(
14952
+ const result = await call2(
14824
14953
  ctx,
14825
14954
  "POST",
14826
14955
  `/runbook/${encodeURIComponent(runbook.id)}/revert`,
@@ -14831,7 +14960,7 @@ async function runbookRevert(ctx, slug, version) {
14831
14960
  }
14832
14961
  async function runbookRemove(ctx, slug) {
14833
14962
  const runbook = await bySlug(ctx, slug);
14834
- await call(ctx, "DELETE", `/runbook/${encodeURIComponent(runbook.id)}`);
14963
+ await call2(ctx, "DELETE", `/runbook/${encodeURIComponent(runbook.id)}`);
14835
14964
  ctx.out.log(`removed ${slug}`);
14836
14965
  }
14837
14966
  var import_node_fs21, PLATFORM_SCOPE, stamp;
@@ -14902,7 +15031,7 @@ ${counts.created} created, ${counts.updated} updated, ${counts.unchanged} unchan
14902
15031
  async function upsert(ctx, r, visibility) {
14903
15032
  const found = await bySlug(ctx, r.slug).catch(() => null);
14904
15033
  if (!found) {
14905
- await call(ctx, "POST", "/runbook", {
15034
+ await call2(ctx, "POST", "/runbook", {
14906
15035
  appId: ctx.appId,
14907
15036
  input: {
14908
15037
  slug: r.slug,
@@ -14921,7 +15050,7 @@ async function upsert(ctx, r, visibility) {
14921
15050
  ctx.out.log(` = ${r.slug} (already current at v${found.version})`);
14922
15051
  return "unchanged";
14923
15052
  }
14924
- const result = await call(
15053
+ const result = await call2(
14925
15054
  ctx,
14926
15055
  "PATCH",
14927
15056
  `/runbook/${encodeURIComponent(found.id)}`,
@@ -15160,7 +15289,7 @@ async function assessImpact(ctx, surfaces, all, limit) {
15160
15289
  for (const surface of surfaces) {
15161
15290
  const params = new URLSearchParams({ q: surface.query, limit: String(limit) });
15162
15291
  if (!all) params.set("app", ctx.appId);
15163
- const result = await call(ctx, "GET", `/runbook/search?${params}`);
15292
+ const result = await call2(ctx, "GET", `/runbook/search?${params}`);
15164
15293
  const runbooks = result.outcome === "ranked" ? foldHits(result.hits) : result.candidates.map((c) => ({ ...c, version: 0, sections: [] }));
15165
15294
  out.push({ surface, runbooks });
15166
15295
  }
@@ -15256,7 +15385,7 @@ function lintRunbook(runbook, installed) {
15256
15385
  async function runbookLint(ctx, all) {
15257
15386
  const params = new URLSearchParams();
15258
15387
  if (!all) params.set("app", ctx.appId);
15259
- const page2 = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
15388
+ const page2 = await call2(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
15260
15389
  const installed = { "@odla-ai/cli": cliVersion() };
15261
15390
  const findings = page2.records.flatMap((runbook) => lintRunbook(runbook, installed));
15262
15391
  if (ctx.json) return ctx.out.log(JSON.stringify({ checked: page2.records.length, findings }, null, 2));
@@ -15291,7 +15420,7 @@ async function runbookSearch(ctx, query, all, limit) {
15291
15420
  const params = new URLSearchParams({ q: query });
15292
15421
  if (!all) params.set("app", ctx.appId);
15293
15422
  if (limit) params.set("limit", String(limit));
15294
- const result = await call(ctx, "GET", `/runbook/search?${params}`);
15423
+ const result = await call2(ctx, "GET", `/runbook/search?${params}`);
15295
15424
  if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
15296
15425
  if (result.outcome === "empty-corpus") return ctx.out.log("no runbooks are available to search");
15297
15426
  if (result.outcome === "no-match") return ctx.out.log(`no runbook mentions "${query}"`);
@@ -15309,7 +15438,7 @@ async function runbookSearch(ctx, query, all, limit) {
15309
15438
  }
15310
15439
  }
15311
15440
  async function runbookAsk(ctx, question, all) {
15312
- const result = await call(ctx, "POST", "/runbook/ask", {
15441
+ const result = await call2(ctx, "POST", "/runbook/ask", {
15313
15442
  question,
15314
15443
  ...all ? {} : { app: ctx.appId }
15315
15444
  });
@@ -15339,7 +15468,7 @@ async function runbookAsk(ctx, question, all) {
15339
15468
  }
15340
15469
  async function runbookComment(ctx, slug, body) {
15341
15470
  const found = await bySlug(ctx, slug);
15342
- await call(ctx, "POST", `/runbook/${encodeURIComponent(found.id)}/comments`, { body });
15471
+ await call2(ctx, "POST", `/runbook/${encodeURIComponent(found.id)}/comments`, { body });
15343
15472
  ctx.out.log(`commented on ${slug} (v${found.version})`);
15344
15473
  }
15345
15474
  var JSDOC_POINTER;