@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.js CHANGED
@@ -174,7 +174,7 @@ function absoluteEntryPath(entryPath) {
174
174
 
175
175
  // src/bin.ts
176
176
  var argv = process.argv.slice(2);
177
- requireCurrentCliForProvision(argv).then(() => requireCoherentProvisionRuntime(argv)).then(async () => (await import("./cli-OH6PCYZ3.js")).runCli()).catch((err) => {
177
+ requireCurrentCliForProvision(argv).then(() => requireCoherentProvisionRuntime(argv)).then(async () => (await import("./cli-DZKEPGBZ.js")).runCli()).catch((err) => {
178
178
  console.error(redactSecrets(`odla-ai: ${err instanceof Error ? err.message : String(err)}`));
179
179
  process.exitCode = exitCodeFor(err);
180
180
  });
@@ -1023,6 +1023,66 @@ function addOption(options, name, value2) {
1023
1023
  else options[name] = [String(current), String(value2)];
1024
1024
  }
1025
1025
 
1026
+ // src/admin-spend.ts
1027
+ async function call(ctx, method, scope) {
1028
+ const url = `${ctx.platformUrl.replace(/\/$/, "")}/registry/platform/spend?scope=${encodeURIComponent(scope)}`;
1029
+ const response2 = await ctx.doFetch(url, {
1030
+ method,
1031
+ headers: { authorization: `Bearer ${ctx.token}` }
1032
+ });
1033
+ const body = await response2.json().catch(() => ({}));
1034
+ if (!response2.ok) {
1035
+ const detail = body.error?.message ?? body.error?.code ?? `registry returned ${response2.status}`;
1036
+ throw new Error(`spend ${method} failed: ${detail} (${response2.status})`);
1037
+ }
1038
+ return body;
1039
+ }
1040
+ var money = (value2) => `$${value2.toFixed(2)}`;
1041
+ async function spendShow(ctx, scope) {
1042
+ const view = await call(ctx, "GET", scope);
1043
+ if (ctx.json) return ctx.out.log(JSON.stringify(view, null, 2));
1044
+ ctx.out.log(`scope: ${view.scope}`);
1045
+ ctx.out.log(`day: ${view.day} (UTC)`);
1046
+ ctx.out.log(
1047
+ `spent: ${money(view.spentUsd)} of ${view.capUsd > 0 ? money(view.capUsd) : "(no cap set)"}`
1048
+ );
1049
+ ctx.out.log(`calls: ${view.calls}`);
1050
+ if (view.unpricedCalls > 0) {
1051
+ ctx.out.log(
1052
+ `unpriced: ${view.unpricedCalls} call(s) had no price, so "spent" is a floor, not the total.`
1053
+ );
1054
+ }
1055
+ if (!view.latched) return ctx.out.log("status: running");
1056
+ ctx.out.log(
1057
+ `status: HALTED \u2014 reached ${money(view.latched.capUsd)} on ${view.latched.day} (${view.latched.reason}), at ${money(view.latched.costUsd)}.`
1058
+ );
1059
+ ctx.out.log("A new day does not clear this. Resume with:");
1060
+ ctx.out.log(` odla-ai admin spend reset ${view.scope}`);
1061
+ }
1062
+ async function spendReset(ctx, scope) {
1063
+ const result = await call(ctx, "DELETE", scope);
1064
+ if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
1065
+ ctx.out.log(result.message);
1066
+ if (result.stillOverCap) {
1067
+ ctx.out.log(
1068
+ ` today: ${money(result.spentUsd)} against a ${money(result.capUsd)} cap.`
1069
+ );
1070
+ }
1071
+ }
1072
+ async function adminSpend(parsed, ctx) {
1073
+ const action2 = parsed.positionals[2];
1074
+ const scope = parsed.positionals[3] ?? stringOpt(parsed.options.scope);
1075
+ if (action2 !== "show" && action2 !== "reset") {
1076
+ throw new Error('unknown spend command. Try "odla-ai admin spend show <scope>".');
1077
+ }
1078
+ if (!scope) {
1079
+ throw new Error(
1080
+ `"admin spend ${action2}" needs a scope, e.g. odla-ai admin spend ${action2} app:my-app:<incarnation>`
1081
+ );
1082
+ }
1083
+ return action2 === "show" ? spendShow(ctx, scope) : spendReset(ctx, scope);
1084
+ }
1085
+
1026
1086
  // src/operator-context.ts
1027
1087
  import { existsSync as existsSync6 } from "fs";
1028
1088
  import { join as join5, resolve as resolve4 } from "path";
@@ -1802,6 +1862,31 @@ var SET_OPTIONS = [
1802
1862
  async function adminCommand(parsed, deps = {}) {
1803
1863
  const area = parsed.positionals[1];
1804
1864
  const action2 = parsed.positionals[2];
1865
+ if (area === "spend") {
1866
+ assertArgs(parsed, JSON_OPTIONS, 4);
1867
+ const context2 = await resolveOperatorContext(parsed, { allowMissingConfig: true });
1868
+ const out = deps.stdout ?? console;
1869
+ const doFetch = deps.fetch ?? fetch;
1870
+ const token = await getDeveloperToken(
1871
+ context2.cfg,
1872
+ {
1873
+ configPath: context2.cfg.configPath,
1874
+ token: stringOpt(parsed.options.token),
1875
+ email: stringOpt(parsed.options.email),
1876
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
1877
+ openApprovalUrl: deps.openUrl
1878
+ },
1879
+ doFetch,
1880
+ out
1881
+ );
1882
+ return adminSpend(parsed, {
1883
+ platformUrl: context2.platform.value,
1884
+ token,
1885
+ doFetch,
1886
+ json: parsed.options.json === true,
1887
+ out
1888
+ });
1889
+ }
1805
1890
  const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
1806
1891
  const credentials = action2 === "credentials";
1807
1892
  const models = action2 === "models";
@@ -1905,7 +1990,8 @@ async function fetchIdentity(platformUrl, token, doFetch) {
1905
1990
  email,
1906
1991
  admin: body.admin === true,
1907
1992
  machine,
1908
- scopes
1993
+ scopes,
1994
+ projects: Array.isArray(body.projects) ? body.projects.map(String) : null
1909
1995
  };
1910
1996
  }
1911
1997
  function credentialLabel(identity) {
@@ -1975,6 +2061,13 @@ async function whoamiCommand(parsed, deps = {}) {
1975
2061
  out.log(`credential id: ${identity.credential.id}`);
1976
2062
  out.log(`admin: ${identity.admin ? "yes" : "no"}`);
1977
2063
  if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
2064
+ if (identity.projects === null) {
2065
+ out.log("projects: (this registry does not report project grants)");
2066
+ } else if (identity.projects.length) {
2067
+ out.log(`projects: ${identity.projects.join(", ")}`);
2068
+ } else {
2069
+ out.log("projects: (none \u2014 every pm and discuss call will be refused)");
2070
+ }
1978
2071
  if (!identity.admin) {
1979
2072
  if (identity.scopes.includes("platform:runbook:write")) {
1980
2073
  out.log("\nThis exact scope can read and edit all platform runbook content.");
@@ -5477,10 +5570,10 @@ import { existsSync as existsSync11 } from "fs";
5477
5570
  import { cpus, hostname, totalmem } from "os";
5478
5571
  import { resolve as resolve11 } from "path";
5479
5572
 
5480
- // ../harness/dist/chunk-3QP4VDQS.js
5573
+ // ../harness/dist/chunk-LNQNFGQC.js
5481
5574
  var HARNESS_PROTOCOL_VERSION = 1;
5482
5575
 
5483
- // ../harness/dist/chunk-GKDKIU4P.js
5576
+ // ../harness/dist/chunk-K76I2TCQ.js
5484
5577
  import { execFile, spawn as spawn3 } from "child_process";
5485
5578
  import { constants } from "fs";
5486
5579
  import { access } from "fs/promises";
@@ -5853,7 +5946,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
5853
5946
  }
5854
5947
  }
5855
5948
 
5856
- // ../harness/dist/chunk-ANNX7VGK.js
5949
+ // ../harness/dist/chunk-UVGZHNLW.js
5857
5950
  import { createHash as createHash3 } from "crypto";
5858
5951
  import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
5859
5952
  import { relative as relative4, resolve as resolve10 } from "path";
@@ -6190,7 +6283,7 @@ function validateSnapshot(snapshot, limits) {
6190
6283
  }
6191
6284
  }
6192
6285
 
6193
- // ../harness/dist/chunk-ANNX7VGK.js
6286
+ // ../harness/dist/chunk-UVGZHNLW.js
6194
6287
  import { spawn as spawn4 } from "child_process";
6195
6288
  import { lstat as lstat2 } from "fs/promises";
6196
6289
  import { resolve as resolve23, sep as sep3 } from "path";
@@ -6495,7 +6588,7 @@ function looksLikeDestination(value2) {
6495
6588
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
6496
6589
  }
6497
6590
 
6498
- // ../harness/dist/chunk-ANNX7VGK.js
6591
+ // ../harness/dist/chunk-UVGZHNLW.js
6499
6592
  import { readFile as readFile4, stat as stat2 } from "fs/promises";
6500
6593
  import { readFile as readFile3 } from "fs/promises";
6501
6594
  import { join as join33 } from "path";
@@ -6763,7 +6856,7 @@ async function buildCodeGraph(input) {
6763
6856
  return builder.build();
6764
6857
  }
6765
6858
 
6766
- // ../harness/dist/chunk-ANNX7VGK.js
6859
+ // ../harness/dist/chunk-UVGZHNLW.js
6767
6860
  import { createHash as createHash32 } from "crypto";
6768
6861
  async function digestStagedWorkspace(root, limits) {
6769
6862
  const files = [];
@@ -6883,7 +6976,7 @@ function createCodeRuntimeControlClient(options) {
6883
6976
  throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
6884
6977
  }
6885
6978
  const request3 = options.fetch ?? fetch;
6886
- const call3 = async (path, body, timeoutMs = requestTimeoutMs) => {
6979
+ const call4 = async (path, body, timeoutMs = requestTimeoutMs) => {
6887
6980
  const timeout = AbortSignal.timeout(timeoutMs);
6888
6981
  const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
6889
6982
  let response2;
@@ -6913,17 +7006,17 @@ function createCodeRuntimeControlClient(options) {
6913
7006
  return {
6914
7007
  heartbeat: async (version, capabilities) => {
6915
7008
  validateHeartbeat(version, capabilities);
6916
- return parseSnapshot(await call3("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
7009
+ return parseSnapshot(await call4("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
6917
7010
  },
6918
7011
  acknowledge: async (commandId, result) => {
6919
7012
  if (!/^ccmd_[0-9a-f]{32}$/.test(commandId)) throw new TypeError("invalid Code runtime command id");
6920
- await call3(`/registry/code/runtime/commands/${commandId}/ack`, result);
7013
+ await call4(`/registry/code/runtime/commands/${commandId}/ack`, result);
6921
7014
  },
6922
7015
  source: async (sessionId) => parseSource(
6923
- await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
7016
+ await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
6924
7017
  ),
6925
7018
  infer: async (sessionId, inference) => {
6926
- const value2 = record5(await call3(
7019
+ const value2 = record5(await call4(
6927
7020
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
6928
7021
  inference,
6929
7022
  modelRequestTimeoutMs
@@ -6934,11 +7027,11 @@ function createCodeRuntimeControlClient(options) {
6934
7027
  return value2;
6935
7028
  },
6936
7029
  review: async (sessionId, review) => parseReview(
6937
- await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
7030
+ await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
6938
7031
  ),
6939
7032
  submitCandidate: async (sessionId, checkpointId, verification) => {
6940
7033
  if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
6941
- return parseCandidate(await call3(
7034
+ return parseCandidate(await call4(
6942
7035
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/candidates`,
6943
7036
  { checkpointId, verification }
6944
7037
  ));
@@ -6948,21 +7041,21 @@ function createCodeRuntimeControlClient(options) {
6948
7041
  if (!/^[A-Za-z0-9._:-]{1,120}$/.test(eventId) || !event || typeof event !== "object" || new TextEncoder().encode(serialized).byteLength > 24e3) {
6949
7042
  throw new TypeError("invalid Code session event");
6950
7043
  }
6951
- await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
7044
+ await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
6952
7045
  },
6953
7046
  recallMemories: async (sessionId, subjects, limit) => {
6954
- const response2 = await call3(
7047
+ const response2 = await call4(
6955
7048
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/recall`,
6956
7049
  { subjects: [...subjects], limit }
6957
7050
  );
6958
7051
  return Array.isArray(response2.memories) ? response2.memories : [];
6959
7052
  },
6960
7053
  rememberMemory: async (sessionId, memory) => {
6961
- await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
7054
+ await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
6962
7055
  },
6963
7056
  reportSessionFailure: async (sessionId, message2) => {
6964
7057
  if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
6965
- await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
7058
+ await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
6966
7059
  }
6967
7060
  };
6968
7061
  }
@@ -7851,7 +7944,7 @@ var SYSTEM_PROMPT_FOR = {
7851
7944
  };
7852
7945
  function codeSkill(opts) {
7853
7946
  let seq = 0;
7854
- const call3 = async (tool, input, signal) => {
7947
+ const call4 = async (tool, input, signal) => {
7855
7948
  const startedAt = Date.now();
7856
7949
  const response2 = await opts.broker.execute(
7857
7950
  { lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
@@ -7873,7 +7966,7 @@ function codeSkill(opts) {
7873
7966
  },
7874
7967
  additionalProperties: false
7875
7968
  },
7876
- handler: (input, ctx) => call3("sandbox.read", input, ctx.signal)
7969
+ handler: (input, ctx) => call4("sandbox.read", input, ctx.signal)
7877
7970
  };
7878
7971
  const applyPatch = {
7879
7972
  name: "odla_apply_git_diff",
@@ -7884,7 +7977,7 @@ function codeSkill(opts) {
7884
7977
  properties: { patch: { type: "string", minLength: 1, maxLength: 262144 } },
7885
7978
  additionalProperties: false
7886
7979
  },
7887
- handler: (input, ctx) => call3("sandbox.apply_patch", input, ctx.signal)
7980
+ handler: (input, ctx) => call4("sandbox.apply_patch", input, ctx.signal)
7888
7981
  };
7889
7982
  const runRecipe = {
7890
7983
  name: "odla_run_recipe",
@@ -7895,7 +7988,7 @@ function codeSkill(opts) {
7895
7988
  properties: { recipeId: { type: "string", minLength: 1, maxLength: 120, pattern: "^[a-zA-Z0-9._:-]+$" } },
7896
7989
  additionalProperties: false
7897
7990
  },
7898
- handler: (input, ctx) => call3("sandbox.run_recipe", input, ctx.signal)
7991
+ handler: (input, ctx) => call4("sandbox.run_recipe", input, ctx.signal)
7899
7992
  };
7900
7993
  const listFiles2 = {
7901
7994
  name: "odla_list",
@@ -7908,7 +8001,7 @@ function codeSkill(opts) {
7908
8001
  },
7909
8002
  additionalProperties: false
7910
8003
  },
7911
- handler: (input, ctx) => call3("sandbox.list", input, ctx.signal)
8004
+ handler: (input, ctx) => call4("sandbox.list", input, ctx.signal)
7912
8005
  };
7913
8006
  const searchFiles = {
7914
8007
  name: "odla_search",
@@ -7924,7 +8017,7 @@ function codeSkill(opts) {
7924
8017
  },
7925
8018
  additionalProperties: false
7926
8019
  },
7927
- handler: (input, ctx) => call3("sandbox.search", input, ctx.signal)
8020
+ handler: (input, ctx) => call4("sandbox.search", input, ctx.signal)
7928
8021
  };
7929
8022
  const graphTool = (name, tool, description, required) => ({
7930
8023
  name,
@@ -7935,7 +8028,7 @@ function codeSkill(opts) {
7935
8028
  properties: { query: { type: "string", maxLength: 512 } },
7936
8029
  additionalProperties: false
7937
8030
  },
7938
- handler: (input, ctx) => call3(tool, input, ctx.signal)
8031
+ handler: (input, ctx) => call4(tool, input, ctx.signal)
7939
8032
  });
7940
8033
  const orientation = [
7941
8034
  graphTool(
@@ -7974,9 +8067,9 @@ async function runCodeAgent(options) {
7974
8067
  lease: options.lease,
7975
8068
  workspaceDir: options.workspaceDir,
7976
8069
  surface,
7977
- onToolCall: (call3) => {
7978
- toolCalls.push(call3);
7979
- options.onToolCall?.(call3);
8070
+ onToolCall: (call4) => {
8071
+ toolCalls.push(call4);
8072
+ options.onToolCall?.(call4);
7980
8073
  }
7981
8074
  });
7982
8075
  const compaction = options.compaction === void 0 ? keepRecentExchanges({ whenInputTokensExceed: 12e4, keep: 3 }) : options.compaction;
@@ -8061,6 +8154,9 @@ async function handleCodeRuntimeInference(input) {
8061
8154
  call: request3.call
8062
8155
  });
8063
8156
  state2.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
8157
+ const { costUsd } = response2.receipt;
8158
+ if (costUsd === void 0) state2.costKnown = false;
8159
+ else state2.costUsd += costUsd;
8064
8160
  await input.event({
8065
8161
  type: "usage",
8066
8162
  provider: response2.receipt.provider,
@@ -8070,7 +8166,9 @@ async function handleCodeRuntimeInference(input) {
8070
8166
  durationMs: Date.now() - startedAt,
8071
8167
  interactionId: command.commandId,
8072
8168
  interactionTokens: state2.tokens,
8073
- interactionMaxTokens: metadata2.maxTokensPerInteraction
8169
+ interactionMaxTokens: metadata2.maxTokensPerInteraction,
8170
+ ...costUsd === void 0 ? {} : { costUsd },
8171
+ ...state2.costKnown ? { interactionCostUsd: state2.costUsd } : {}
8074
8172
  }).catch(() => void 0);
8075
8173
  return {
8076
8174
  protocolVersion: HARNESS_PROTOCOL_VERSION,
@@ -8914,10 +9012,16 @@ async function startGoalPursuit(input) {
8914
9012
  attempt: async ({ prompt }) => {
8915
9013
  const result = await input.attempt(prompt);
8916
9014
  return {
8917
- // The runtime charges tokens through the control plane's own
8918
- // per-interaction reservation, so the goal budget bounds ATTEMPTS here
8919
- // and the token ceiling is enforced where the credential lives.
8920
- tokens: 0,
9015
+ // What the attempt actually spent, so the runner's token_budget and
9016
+ // cost_budget checks can be reached. This used to be a hardcoded 0 with
9017
+ // no cost at all, which made maxTokens and maxUsd unreachable while
9018
+ // callers reasonably read them as hard ceilings.
9019
+ //
9020
+ // costUsd is omitted rather than zeroed when any call in the attempt
9021
+ // was unpriced: the runner only enforces a cost budget while the cost
9022
+ // is known, and a zero would make it enforce against a lie.
9023
+ tokens: result.tokens ?? 0,
9024
+ ...result.costUsd === void 0 ? {} : { costUsd: result.costUsd },
8921
9025
  ...result.status === "failed" ? { error: result.error ?? "attempt failed" } : {}
8922
9026
  };
8923
9027
  },
@@ -9131,7 +9235,7 @@ var CodePiRuntimeEngine = class {
9131
9235
  recipeAuthorization: this.options.recipeAuthorization
9132
9236
  }, lease, metadata2.role));
9133
9237
  const startedAt = Date.now();
9134
- const interaction = { tokens: 0, noticeEmitted: false };
9238
+ const interaction = { tokens: 0, noticeEmitted: false, costUsd: 0, costKnown: true };
9135
9239
  const inference = createCodeRuntimeInference({
9136
9240
  command,
9137
9241
  metadata: metadata2,
@@ -9169,7 +9273,11 @@ var CodePiRuntimeEngine = class {
9169
9273
  await this.#diagnostic(command, active, detail);
9170
9274
  await this.#failure(command, active, detail);
9171
9275
  }
9172
- return result;
9276
+ return {
9277
+ ...result,
9278
+ tokens: interaction.tokens,
9279
+ ...interaction.costKnown ? { costUsd: interaction.costUsd } : {}
9280
+ };
9173
9281
  }
9174
9282
  /** Report every brokered effect as it starts and finishes. */
9175
9283
  #observed(command, active, broker) {
@@ -10278,6 +10386,8 @@ Usage:
10278
10386
  odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
10279
10387
  odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
10280
10388
  odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
10389
+ odla-ai admin spend show <app:<id>:<incarnation>> [--context <name>] [--json]
10390
+ odla-ai admin spend reset <app:<id>:<incarnation>> [--context <name>] [--json]
10281
10391
  odla-ai security github connect [--repo owner/name] [--env dev] [continue in Studio; human session required]
10282
10392
  odla-ai security github disconnect --source <id> [--env dev] [continue in Studio; human session required]
10283
10393
  odla-ai security plan [--env dev] [--json]
@@ -10370,6 +10480,9 @@ Commands:
10370
10480
  human session connects one); "code grant request|list|approve|revoke" then
10371
10481
  governs unattended access: an agent may request, only a human may approve.
10372
10482
  admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
10483
+ "admin spend" reads one app's daily inference spend and resumes it
10484
+ after a cap halts it. The cap is per UTC day; exhausting it LATCHES,
10485
+ and a new day does not clear the latch \u2014 resuming is deliberate.
10373
10486
  security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
10374
10487
  pm Project management (via @odla-ai/pm): Products contain Projects;
10375
10488
  projects contain goals, kanban tasks, decisions, and bugs. Use
@@ -10377,6 +10490,12 @@ Commands:
10377
10490
  pass --app/--project explicitly. Same device-grant auth as "app".
10378
10491
  Status changes and comments post to each item's @odla-ai/chat
10379
10492
  discussion thread.
10493
+ NOTE: "--column ready" is OWNER-ONLY. Creating a task in Ready
10494
+ approves its complete execution contract, so it needs pm.plan,
10495
+ which no device enrollment or handshake approval can grant. An
10496
+ agent proposes in Backlog (the default); a human owner or a
10497
+ pm.plan agent promotes. This is deliberate, not a permission
10498
+ gap \u2014 it was reported as one.
10380
10499
  bug Intent-first alias for PM bugs. "bug report" writes to
10381
10500
  odla PM; odla product defects do not belong in GitHub Issues.
10382
10501
  discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
@@ -13514,7 +13633,7 @@ function defaultDeviceName() {
13514
13633
  // src/runbook-actions.ts
13515
13634
  import { readFileSync as readFileSync10 } from "fs";
13516
13635
  var PLATFORM_SCOPE = "$platform";
13517
- async function call(ctx, method, path, body) {
13636
+ async function call2(ctx, method, path, body) {
13518
13637
  const res = await ctx.doFetch(`${ctx.platformUrl.replace(/\/$/, "")}/registry/pm${path}`, {
13519
13638
  method,
13520
13639
  headers: {
@@ -13536,7 +13655,7 @@ async function call(ctx, method, path, body) {
13536
13655
  throw new Error(message2);
13537
13656
  }
13538
13657
  async function bySlug(ctx, slug) {
13539
- const page2 = await call(
13658
+ const page2 = await call2(
13540
13659
  ctx,
13541
13660
  "GET",
13542
13661
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
@@ -13545,7 +13664,7 @@ async function bySlug(ctx, slug) {
13545
13664
  if (filtered) return filtered;
13546
13665
  const limit = 100;
13547
13666
  for (let offset = 0; ; offset += limit) {
13548
- const fallback = await call(
13667
+ const fallback = await call2(
13549
13668
  ctx,
13550
13669
  "GET",
13551
13670
  `/runbook?app=${encodeURIComponent(ctx.appId)}&limit=${limit}&offset=${offset}`
@@ -13566,7 +13685,7 @@ async function runbookList(ctx, all, query) {
13566
13685
  const params = new URLSearchParams();
13567
13686
  if (!all) params.set("app", ctx.appId);
13568
13687
  if (query) params.set("q", query);
13569
- const page2 = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
13688
+ const page2 = await call2(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
13570
13689
  if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
13571
13690
  if (!page2.records.length) return ctx.out.log("(no runbooks)");
13572
13691
  ctx.out.log(["SLUG", "STATUS", "V", "SCOPE", "UPDATED", "TITLE"].join(" "));
@@ -13583,7 +13702,7 @@ async function runbookGet(ctx, slug) {
13583
13702
  ctx.out.log(runbook.body);
13584
13703
  }
13585
13704
  async function runbookNew(ctx, slug, title, body, summary, requires) {
13586
- const created = await call(ctx, "POST", "/runbook", {
13705
+ const created = await call2(ctx, "POST", "/runbook", {
13587
13706
  appId: ctx.appId,
13588
13707
  input: { slug, title, body, ...summary ? { summary } : {}, ...requires ? { requires } : {} }
13589
13708
  });
@@ -13591,7 +13710,7 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
13591
13710
  }
13592
13711
  async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
13593
13712
  const runbook = await bySlug(ctx, slug);
13594
- const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
13713
+ const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
13595
13714
  // An empty --requires clears the declaration; omitting the flag leaves
13596
13715
  // whatever is there, so an ordinary body edit never drops it.
13597
13716
  patch: {
@@ -13606,7 +13725,7 @@ async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
13606
13725
  }
13607
13726
  async function runbookStatus(ctx, slug, status) {
13608
13727
  const runbook = await bySlug(ctx, slug);
13609
- const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
13728
+ const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
13610
13729
  patch: { status }
13611
13730
  });
13612
13731
  ctx.out.log(ctx.json ? JSON.stringify(result, null, 2) : `${slug} \u2192 ${status}`);
@@ -13615,7 +13734,7 @@ async function runbookVisibility(ctx, slug, visibility) {
13615
13734
  if (visibility !== "operator" && visibility !== "admin")
13616
13735
  throw new Error(`visibility must be "operator" or "admin", got "${visibility}"`);
13617
13736
  const runbook = await bySlug(ctx, slug);
13618
- const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
13737
+ const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
13619
13738
  patch: { visibility }
13620
13739
  });
13621
13740
  if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
@@ -13625,7 +13744,7 @@ async function runbookVisibility(ctx, slug, visibility) {
13625
13744
  }
13626
13745
  async function runbookHistory(ctx, slug) {
13627
13746
  const runbook = await bySlug(ctx, slug);
13628
- const page2 = await call(ctx, "GET", `/runbook/${encodeURIComponent(runbook.id)}/revisions`);
13747
+ const page2 = await call2(ctx, "GET", `/runbook/${encodeURIComponent(runbook.id)}/revisions`);
13629
13748
  if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
13630
13749
  ctx.out.log(`${slug} is at v${runbook.version}`);
13631
13750
  if (!page2.records.length) return ctx.out.log("(no earlier versions)");
@@ -13635,7 +13754,7 @@ async function runbookHistory(ctx, slug) {
13635
13754
  }
13636
13755
  async function runbookRevert(ctx, slug, version) {
13637
13756
  const runbook = await bySlug(ctx, slug);
13638
- const result = await call(
13757
+ const result = await call2(
13639
13758
  ctx,
13640
13759
  "POST",
13641
13760
  `/runbook/${encodeURIComponent(runbook.id)}/revert`,
@@ -13646,7 +13765,7 @@ async function runbookRevert(ctx, slug, version) {
13646
13765
  }
13647
13766
  async function runbookRemove(ctx, slug) {
13648
13767
  const runbook = await bySlug(ctx, slug);
13649
- await call(ctx, "DELETE", `/runbook/${encodeURIComponent(runbook.id)}`);
13768
+ await call2(ctx, "DELETE", `/runbook/${encodeURIComponent(runbook.id)}`);
13650
13769
  ctx.out.log(`removed ${slug}`);
13651
13770
  }
13652
13771
 
@@ -13707,7 +13826,7 @@ ${counts.created} created, ${counts.updated} updated, ${counts.unchanged} unchan
13707
13826
  async function upsert(ctx, r, visibility) {
13708
13827
  const found = await bySlug(ctx, r.slug).catch(() => null);
13709
13828
  if (!found) {
13710
- await call(ctx, "POST", "/runbook", {
13829
+ await call2(ctx, "POST", "/runbook", {
13711
13830
  appId: ctx.appId,
13712
13831
  input: {
13713
13832
  slug: r.slug,
@@ -13726,7 +13845,7 @@ async function upsert(ctx, r, visibility) {
13726
13845
  ctx.out.log(` = ${r.slug} (already current at v${found.version})`);
13727
13846
  return "unchanged";
13728
13847
  }
13729
- const result = await call(
13848
+ const result = await call2(
13730
13849
  ctx,
13731
13850
  "PATCH",
13732
13851
  `/runbook/${encodeURIComponent(found.id)}`,
@@ -13954,7 +14073,7 @@ async function assessImpact(ctx, surfaces, all, limit) {
13954
14073
  for (const surface of surfaces) {
13955
14074
  const params = new URLSearchParams({ q: surface.query, limit: String(limit) });
13956
14075
  if (!all) params.set("app", ctx.appId);
13957
- const result = await call(ctx, "GET", `/runbook/search?${params}`);
14076
+ const result = await call2(ctx, "GET", `/runbook/search?${params}`);
13958
14077
  const runbooks = result.outcome === "ranked" ? foldHits(result.hits) : result.candidates.map((c) => ({ ...c, version: 0, sections: [] }));
13959
14078
  out.push({ surface, runbooks });
13960
14079
  }
@@ -14037,7 +14156,7 @@ function lintRunbook(runbook, installed) {
14037
14156
  async function runbookLint(ctx, all) {
14038
14157
  const params = new URLSearchParams();
14039
14158
  if (!all) params.set("app", ctx.appId);
14040
- const page2 = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
14159
+ const page2 = await call2(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
14041
14160
  const installed = { "@odla-ai/cli": cliVersion() };
14042
14161
  const findings = page2.records.flatMap((runbook) => lintRunbook(runbook, installed));
14043
14162
  if (ctx.json) return ctx.out.log(JSON.stringify({ checked: page2.records.length, findings }, null, 2));
@@ -14062,7 +14181,7 @@ async function runbookSearch(ctx, query, all, limit) {
14062
14181
  const params = new URLSearchParams({ q: query });
14063
14182
  if (!all) params.set("app", ctx.appId);
14064
14183
  if (limit) params.set("limit", String(limit));
14065
- const result = await call(ctx, "GET", `/runbook/search?${params}`);
14184
+ const result = await call2(ctx, "GET", `/runbook/search?${params}`);
14066
14185
  if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
14067
14186
  if (result.outcome === "empty-corpus") return ctx.out.log("no runbooks are available to search");
14068
14187
  if (result.outcome === "no-match") return ctx.out.log(`no runbook mentions "${query}"`);
@@ -14081,7 +14200,7 @@ async function runbookSearch(ctx, query, all, limit) {
14081
14200
  }
14082
14201
  var JSDOC_POINTER = "Runbooks are procedure. For what an export does \u2014 arguments, defaults, errors \u2014 read its JSDoc: the installed package's .d.ts, or its page under https://odla.ai/docs.";
14083
14202
  async function runbookAsk(ctx, question, all) {
14084
- const result = await call(ctx, "POST", "/runbook/ask", {
14203
+ const result = await call2(ctx, "POST", "/runbook/ask", {
14085
14204
  question,
14086
14205
  ...all ? {} : { app: ctx.appId }
14087
14206
  });
@@ -14111,7 +14230,7 @@ async function runbookAsk(ctx, question, all) {
14111
14230
  }
14112
14231
  async function runbookComment(ctx, slug, body) {
14113
14232
  const found = await bySlug(ctx, slug);
14114
- await call(ctx, "POST", `/runbook/${encodeURIComponent(found.id)}/comments`, { body });
14233
+ await call2(ctx, "POST", `/runbook/${encodeURIComponent(found.id)}/comments`, { body });
14115
14234
  ctx.out.log(`commented on ${slug} (v${found.version})`);
14116
14235
  }
14117
14236
 
@@ -15281,4 +15400,4 @@ export {
15281
15400
  isTerminalHostedSecurityStatus,
15282
15401
  runCli
15283
15402
  };
15284
- //# sourceMappingURL=chunk-XFIFFYNH.js.map
15403
+ //# sourceMappingURL=chunk-DKOJBWRJ.js.map