@datarails-ai/dr-cli 1.6.179 → 1.6.181

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/README.md CHANGED
@@ -435,6 +435,7 @@ dr skill print <name> # print an embedded skill's SKILL.md
435
435
  | Folders | `folders list/create`, `folders reorder --container <root\|id> (--items folder:12,entity:87 \| --move <kind:id> --to-index <n>)`, `folders move <id> --parent <root\|id>` (dashboard / story picker tree, flag-gated; reorder and move are two-step confirm; a dashboard moves with `dashboards set-folder`) |
436
436
  | Functions | `functions list/get/create/delete` |
437
437
  | Dynamic ranges | `dynamic-ranges list/get/data/create/delete` |
438
+ | Calc models | `calcmodels list/get/create/update/set-flow/recalculate/delete`; AGENT builder: `calcmodels agent ask/preview/save/approve` (`save` makes a draft; `approve` is two-step, Tier A) |
438
439
  | Lookups (LUTs) | `lut list/create/update/delete/reorder/unmapped` (alias `lookups`) |
439
440
  | Documents | `documents support-api set <id> --on/--off`, `documents calculate <id> --year/--quarter/--month/--day/--week ... [--backward] [--no-roll]` |
440
441
  | Semantic tables | `semantic-tables list/get` |
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@datarails-ai/dr-cli",
11
- version: "1.6.179",
11
+ version: "1.6.181",
12
12
  description: "Datarails CLI",
13
13
  type: "module",
14
14
  engines: {
@@ -40,7 +40,7 @@ var package_default = {
40
40
  postinstall: "node scripts/postinstall.mjs"
41
41
  },
42
42
  dependencies: {
43
- "@datarails-ai/dr-appspace-cli": "0.12.56",
43
+ "@datarails-ai/dr-appspace-cli": "0.12.58",
44
44
  "@modelcontextprotocol/sdk": "^1.29.0",
45
45
  "@napi-rs/keyring": "^1.3.0",
46
46
  commander: "^12.1.0",
@@ -69,7 +69,7 @@ var package_default = {
69
69
  };
70
70
 
71
71
  // src/buildInfo.ts
72
- var BUILD_TIME = "2026-10-29T15:44:45.951Z";
72
+ var BUILD_TIME = "2026-11-01T14:08:05.005Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -1410,11 +1410,11 @@ function exchangeForSession(store, serverKey, email, session, host, opts) {
1410
1410
  if (err.throttled && cachedIsUsable) return session;
1411
1411
  if (err.transient) {
1412
1412
  if (cachedIsUsable) return session;
1413
- const delay = API_KEY_EXCHANGE_RETRY_DELAYS_MS[attempt];
1414
- if (delay !== void 0) {
1413
+ const delay2 = API_KEY_EXCHANGE_RETRY_DELAYS_MS[attempt];
1414
+ if (delay2 !== void 0) {
1415
1415
  attempt += 1;
1416
1416
  try {
1417
- await sleep(delay, void 0, { signal });
1417
+ await sleep(delay2, void 0, { signal });
1418
1418
  } catch {
1419
1419
  throw new SessionExpiredError(
1420
1420
  `${identity2} (api-key exchange ended before its retry)`
@@ -4438,6 +4438,13 @@ var PROD_GATE_TIER_A = /* @__PURE__ */ new Set([
4438
4438
  "category|delete",
4439
4439
  // calc-models
4440
4440
  "calcmodel|delete",
4441
+ // dr calcmodels agent approve — activates an AGENT model's newest version and starts its
4442
+ // first production run, which writes new rows into a table that reports read. Unlike
4443
+ // `set-flow` / `recalculate` (Tier B), which re-run a model already approved, this puts
4444
+ // numbers nobody has run before into production, so a person confirms it. The plan
4445
+ // prints the version and its impact (rows, pool, allocated, coverage); the token binds
4446
+ // that version.
4447
+ "calcmodel|approve",
4441
4448
  // filebox
4442
4449
  "filebox|archive",
4443
4450
  "filebox-data-query|clear",
@@ -7421,8 +7428,8 @@ async function retryAsync(fn, opts) {
7421
7428
  const kind = classifyFailure(err);
7422
7429
  const retryable = kind === "transient" || kind !== "abort" && opts.alsoRetry?.(err) === true;
7423
7430
  if (!retryable || attempt > retries) throw err;
7424
- const delay = opts.delayMs ?? defaultBackoffMs;
7425
- const waitMs = typeof delay === "function" ? delay(attempt) : delay;
7431
+ const delay2 = opts.delayMs ?? defaultBackoffMs;
7432
+ const waitMs = typeof delay2 === "function" ? delay2(attempt) : delay2;
7426
7433
  opts.onRetry?.(attempt, err, waitMs);
7427
7434
  await sleep2(waitMs, opts.signal);
7428
7435
  }
@@ -35556,7 +35563,7 @@ function registerDatamappersCommand(root) {
35556
35563
  });
35557
35564
  addServerFlag(customFnsCmd);
35558
35565
  addAccountFlag(customFnsCmd);
35559
- const previewCmd = dm.command("preview").description(
35566
+ const previewCmd2 = dm.command("preview").description(
35560
35567
  "Render a mapper config's projected output for a sample input. Posts to `POST /api/templates/preview-data` with the operator-supplied JSON (typically built from a saved mapper config + template_id + document_version + datamapper_id). Output is the server's `{output: {header, projected_config, \u2026}, err}` envelope, emitted as JSON."
35561
35568
  ).requiredOption("--payload-file <path>", "Path to a JSON body for the preview request").option(
35562
35569
  "--json",
@@ -35576,8 +35583,8 @@ function registerDatamappersCommand(root) {
35576
35583
  setup.cleanup();
35577
35584
  }
35578
35585
  });
35579
- addServerFlag(previewCmd);
35580
- addAccountFlag(previewCmd);
35586
+ addServerFlag(previewCmd2);
35587
+ addAccountFlag(previewCmd2);
35581
35588
  const bindCmd = dm.command("bind").description(
35582
35589
  "Update which document IDs a mapper is bound to. The --doc list replaces the mapper's current documents; repeat every filebox you want to keep (dr warns first and names any it drops). Posts to `POST /api/templates/mappers/<id>/binding` with `{selected_document_ids, rescan?}`. The legacy webclient's \"Mapping table with documents\" modal calls this when the operator checks/unchecks docs in the binding picker. Warns first when another mapper of the table on the same ordinal is bound to one of those docs: mappers that share an ordinal are versions of one mapper, so the bind unbinds the other."
35583
35590
  ).argument("<mapper_id>", "mapper id (positive integer)").option("--doc <id>", "Document id to bind (positive integer; repeatable)", collectInts, []).option(
@@ -43482,7 +43489,7 @@ function registerFileboxCommand(root) {
43482
43489
  });
43483
43490
  addServerFlag(sheetsCmd);
43484
43491
  addAccountFlag(sheetsCmd);
43485
- const previewCmd = fb.command("preview").description("Preview raw cell data of a filebox version (row 1 is headers)").argument("<version_id>", "version id (positive integer)").option("--sheet <name>", "Sheet name. Defaults to first sheet from /desc.", "").option("--sheet-ind <n>", "Sheet index (0-based). Defaults to 0.", "0").option("--start <n>", "First row to read (1-based)", "1").option("--rows <n>", "Number of rows to return (start..start+rows-1)", "100").option("--data-only", "Return evaluated values only; omit raw formulas", false).option("--json", "Output raw JSON instead of a table", false).action(
43492
+ const previewCmd2 = fb.command("preview").description("Preview raw cell data of a filebox version (row 1 is headers)").argument("<version_id>", "version id (positive integer)").option("--sheet <name>", "Sheet name. Defaults to first sheet from /desc.", "").option("--sheet-ind <n>", "Sheet index (0-based). Defaults to 0.", "0").option("--start <n>", "First row to read (1-based)", "1").option("--rows <n>", "Number of rows to return (start..start+rows-1)", "100").option("--data-only", "Return evaluated values only; omit raw formulas", false).option("--json", "Output raw JSON instead of a table", false).action(
43486
43493
  async (vidStr, opts) => {
43487
43494
  const versionId = parseVersionIdArg2(vidStr);
43488
43495
  const sheetInd = parsePositiveIntFlag2(opts.sheetInd, "--sheet-ind", 0);
@@ -43507,8 +43514,8 @@ function registerFileboxCommand(root) {
43507
43514
  }
43508
43515
  }
43509
43516
  );
43510
- addServerFlag(previewCmd);
43511
- addAccountFlag(previewCmd);
43517
+ addServerFlag(previewCmd2);
43518
+ addAccountFlag(previewCmd2);
43512
43519
  const createCmd = fb.command("create").description(
43513
43520
  "Create an empty filebox (no file yet). Resolves the date tag config from /versions/tags. Pass --folder to create a folder filebox instead (no date tag, no version, no data \u2014 used to mirror the org-bootstrap hierarchy of Actuals / COA / FX Rates / Configuration)."
43514
43521
  ).option("--name <name>", "Filebox display name (required)", "").option("--scenario <s>", `Scenario: ${VALID_SCENARIOS_HELP}`, "Actuals").option(
@@ -47006,10 +47013,10 @@ function toControlPlaneError(method, path13, err) {
47006
47013
  }
47007
47014
 
47008
47015
  // src/api/awsConnections.ts
47009
- async function call(method, path13, send, what) {
47016
+ async function call(method, path13, send2, what) {
47010
47017
  let out;
47011
47018
  try {
47012
- out = await send();
47019
+ out = await send2();
47013
47020
  } catch (err) {
47014
47021
  throw toControlPlaneError(method, path13, err);
47015
47022
  }
@@ -48184,10 +48191,10 @@ import { readFileSync as readFileSync3 } from "fs";
48184
48191
 
48185
48192
  // src/api/scheduledTasks.ts
48186
48193
  var BASE2 = "/scheduled-tasks";
48187
- async function call2(method, path13, send, what) {
48194
+ async function call2(method, path13, send2, what) {
48188
48195
  let out;
48189
48196
  try {
48190
- out = await send();
48197
+ out = await send2();
48191
48198
  } catch (err) {
48192
48199
  throw toControlPlaneError(method, path13, err);
48193
48200
  }
@@ -65487,6 +65494,742 @@ async function remove(client, id, options) {
65487
65494
  await client.deleteJSON(`/api/templates/${id}/calcmodels`, options);
65488
65495
  }
65489
65496
 
65497
+ // src/commands/calcmodelsAgent.ts
65498
+ import { setTimeout as delay } from "timers/promises";
65499
+
65500
+ // src/types/calcmodelsAgent.ts
65501
+ import { z as z3 } from "zod";
65502
+ var optionalText = z3.string().nullish();
65503
+ var amount = z3.number().nullish();
65504
+ var calcAgentOperationStatusSchema = z3.enum([
65505
+ "PENDING",
65506
+ "RUNNING",
65507
+ "SUCCEEDED",
65508
+ "NO_PROPOSAL",
65509
+ "FAILED"
65510
+ ]);
65511
+ var calcAgentGateErrorSchema = z3.object({
65512
+ code: z3.string(),
65513
+ phase: optionalText,
65514
+ segment: optionalText,
65515
+ suggestion: optionalText,
65516
+ detail: optionalText
65517
+ }).passthrough();
65518
+ var calcAgentTurnAcceptedSchema = z3.object({ operation_id: z3.string().uuid(), conversation_id: z3.string().optional() }).passthrough();
65519
+ var coverageSchema = z3.object({
65520
+ pct: amount,
65521
+ pool_total: amount,
65522
+ allocated_total: amount,
65523
+ unallocated_amount: amount,
65524
+ uncovered_group_count: z3.number().nullish()
65525
+ }).passthrough();
65526
+ var balanceCheckSchema = z3.object({ group: z3.string(), net: z3.number(), ok: z3.boolean() }).passthrough();
65527
+ var warningSchema = z3.object({ code: optionalText, message: optionalText }).passthrough();
65528
+ var calcAgentPreviewResultSchema = z3.object({
65529
+ total_count: z3.number().nullish(),
65530
+ coverage: coverageSchema.nullish(),
65531
+ balance_checks: z3.array(balanceCheckSchema).nullish(),
65532
+ warnings: z3.array(warningSchema).nullish()
65533
+ }).passthrough();
65534
+ var calcAgentDefinitionSchema = z3.object({
65535
+ model_type: optionalText,
65536
+ name: optionalText,
65537
+ destination_template_id: z3.number().nullish()
65538
+ }).passthrough();
65539
+ var calcAgentPreviewSchema = z3.object({
65540
+ status: calcAgentOperationStatusSchema,
65541
+ phase: optionalText,
65542
+ conversation_id: optionalText,
65543
+ answer_text: optionalText,
65544
+ changes_summary: optionalText,
65545
+ definition: calcAgentDefinitionSchema.nullish(),
65546
+ error: calcAgentGateErrorSchema.nullish(),
65547
+ result: calcAgentPreviewResultSchema.nullish()
65548
+ }).passthrough();
65549
+ var calcAgentSaveResultSchema = z3.object({ id: z3.number(), name: z3.string(), version_id: z3.number() }).passthrough();
65550
+ var calcAgentImpactSchema = z3.object({
65551
+ total_rows: amount,
65552
+ pool_total: amount,
65553
+ allocated_total: amount,
65554
+ coverage_pct: amount
65555
+ }).passthrough();
65556
+ var calcAgentApprovalPlanSchema = z3.object({ impact: calcAgentImpactSchema, version_id: z3.number() }).passthrough();
65557
+ var calcAgentApproveResultSchema = z3.object({
65558
+ id: z3.number(),
65559
+ name: z3.string(),
65560
+ impact: calcAgentImpactSchema.nullish(),
65561
+ run_id: z3.string().nullish()
65562
+ }).passthrough();
65563
+
65564
+ // src/api/_htmlEntities.ts
65565
+ var ENTITIES2 = {
65566
+ "&amp;": "&",
65567
+ "&lt;": "<",
65568
+ "&gt;": ">",
65569
+ "&quot;": '"',
65570
+ "&#x27;": "'",
65571
+ "&#39;": "'"
65572
+ };
65573
+ var ENTITY_RE2 = /&(?:amp|lt|gt|quot|#x27|#39);/g;
65574
+ function decodeHtmlDeep(value) {
65575
+ if (typeof value === "string") return value.replace(ENTITY_RE2, (m) => ENTITIES2[m] ?? m);
65576
+ if (Array.isArray(value)) return value.map(decodeHtmlDeep);
65577
+ if (value !== null && typeof value === "object") {
65578
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, decodeHtmlDeep(v)]));
65579
+ }
65580
+ return value;
65581
+ }
65582
+
65583
+ // src/api/calcmodelsAgent.ts
65584
+ var BASE3 = "/api/templates/calcmodels/agent";
65585
+ var APPROVAL_ACK_REQUIRED = "CALC_APPROVAL_ACK_REQUIRED";
65586
+ var CalcAgentHttpError = class extends Error {
65587
+ status;
65588
+ body;
65589
+ code;
65590
+ constructor(status2, body, message) {
65591
+ super(message);
65592
+ this.name = "CalcAgentHttpError";
65593
+ this.status = status2;
65594
+ this.body = body;
65595
+ const code = isRecord(body) ? body["code"] : void 0;
65596
+ this.code = typeof code === "string" ? code : void 0;
65597
+ }
65598
+ /** A numeric field of the body (`calc_model_id`, `version_id`), or undefined. */
65599
+ numberField(key) {
65600
+ const v = isRecord(this.body) ? this.body[key] : void 0;
65601
+ return typeof v === "number" ? v : void 0;
65602
+ }
65603
+ };
65604
+ var CONTROL_CHARS2 = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
65605
+ function escapeControlChars(text2) {
65606
+ return text2.replace(
65607
+ CONTROL_CHARS2,
65608
+ (c) => c === "\r" ? "\\r" : `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`
65609
+ );
65610
+ }
65611
+ function isRecord(v) {
65612
+ return typeof v === "object" && v !== null && !Array.isArray(v);
65613
+ }
65614
+ function textField(body, key) {
65615
+ if (!isRecord(body)) return void 0;
65616
+ const v = body[key];
65617
+ return typeof v === "string" && v !== "" ? v : void 0;
65618
+ }
65619
+ async function send(client, method, path13, payload, options) {
65620
+ const res = await client.do(
65621
+ method,
65622
+ path13,
65623
+ payload === void 0 ? void 0 : JSON.stringify(payload),
65624
+ options
65625
+ );
65626
+ const text2 = await res.text();
65627
+ let body = text2;
65628
+ if (text2 !== "") {
65629
+ try {
65630
+ body = JSON.parse(text2);
65631
+ } catch {
65632
+ body = text2;
65633
+ }
65634
+ }
65635
+ return { status: res.status, body: decodeHtmlDeep(body) };
65636
+ }
65637
+ function describeServerError(body) {
65638
+ if (!isRecord(body)) {
65639
+ const raw2 = typeof body === "string" ? body.trim() : "";
65640
+ return raw2.length > 200 ? `${raw2.slice(0, 200)}\u2026` : raw2;
65641
+ }
65642
+ const code = textField(body, "code");
65643
+ const phase = textField(body, "phase");
65644
+ const parts = [
65645
+ textField(body, "detail"),
65646
+ textField(body, "suggestion"),
65647
+ textField(body, "segment") !== void 0 ? `[${textField(body, "segment")}]` : void 0
65648
+ ].filter((p) => p !== void 0);
65649
+ const head = code !== void 0 ? `${code}${phase !== void 0 ? ` (${phase})` : ""}` : "";
65650
+ if (head === "") return parts.join(" \u2014 ");
65651
+ return parts.length > 0 ? `${head}: ${parts.join(" \u2014 ")}` : head;
65652
+ }
65653
+ function httpError(method, path13, answer, hint) {
65654
+ const detail = escapeControlChars(hint ?? describeServerError(answer.body));
65655
+ const tail = detail !== "" ? `: ${detail}` : "";
65656
+ return new CalcAgentHttpError(
65657
+ answer.status,
65658
+ answer.body,
65659
+ `${method} ${path13}: HTTP ${answer.status}${tail}`
65660
+ );
65661
+ }
65662
+ function parse2(schema, method, path13, body) {
65663
+ const out = schema.safeParse(body);
65664
+ if (!out.success) {
65665
+ const issues = out.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
65666
+ throw new Error(`${method} ${path13}: unexpected response from the server (${issues})`);
65667
+ }
65668
+ return out.data;
65669
+ }
65670
+ async function postTurn(client, input, options) {
65671
+ const path13 = `${BASE3}/turns`;
65672
+ const payload = { message: input.message };
65673
+ if (input.conversationId !== void 0) payload["conversation_id"] = input.conversationId;
65674
+ const answer = await send(client, "POST", path13, payload, options);
65675
+ if (answer.status === 202) return parse2(calcAgentTurnAcceptedSchema, "POST", path13, answer.body);
65676
+ if (answer.status === 404 && input.conversationId !== void 0) {
65677
+ throw httpError(
65678
+ "POST",
65679
+ path13,
65680
+ answer,
65681
+ `conversation ${input.conversationId} not found (only the user who started it can continue it)`
65682
+ );
65683
+ }
65684
+ throw httpError("POST", path13, answer);
65685
+ }
65686
+ async function getPreview2(client, operationId, options) {
65687
+ const path13 = `${BASE3}/preview/${encodeURIComponent(operationId)}`;
65688
+ const answer = await send(client, "GET", path13, void 0, options);
65689
+ if (answer.status === 200) return parse2(calcAgentPreviewSchema, "GET", path13, answer.body);
65690
+ if (answer.status === 404) {
65691
+ throw httpError(
65692
+ "GET",
65693
+ path13,
65694
+ answer,
65695
+ `operation ${operationId} not found (only the user who started the turn can read it)`
65696
+ );
65697
+ }
65698
+ throw httpError("GET", path13, answer);
65699
+ }
65700
+ async function save2(client, input, options) {
65701
+ const payload = {
65702
+ preview_operation_id: input.operationId,
65703
+ name: input.name
65704
+ };
65705
+ if (input.calcModelId !== void 0) payload["calc_model_id"] = input.calcModelId;
65706
+ const answer = await send(client, "POST", BASE3, payload, options);
65707
+ if (answer.status === 201) return parse2(calcAgentSaveResultSchema, "POST", BASE3, answer.body);
65708
+ throw httpError("POST", BASE3, answer);
65709
+ }
65710
+ async function requestApprovalImpact(client, calcModelId, options) {
65711
+ const path13 = `${BASE3}/${calcModelId}/approve`;
65712
+ const answer = await send(client, "POST", path13, {}, options);
65713
+ if (answer.status >= 200 && answer.status < 300) {
65714
+ throw new Error(
65715
+ `POST ${path13}: HTTP ${answer.status} to a request without an acknowledgment; expected ${APPROVAL_ACK_REQUIRED}. Check the model with \`dr calcmodels get ${calcModelId}\`.`
65716
+ );
65717
+ }
65718
+ if (answer.status === 400 && textField(answer.body, "code") === APPROVAL_ACK_REQUIRED) {
65719
+ return parse2(calcAgentApprovalPlanSchema, "POST", path13, answer.body);
65720
+ }
65721
+ throw httpError("POST", path13, answer);
65722
+ }
65723
+ async function confirmApproval(client, calcModelId, versionId, options) {
65724
+ const path13 = `${BASE3}/${calcModelId}/approve`;
65725
+ const answer = await send(
65726
+ client,
65727
+ "POST",
65728
+ path13,
65729
+ { acknowledged: true, version_id: versionId },
65730
+ options
65731
+ );
65732
+ if (answer.status === 200) return parse2(calcAgentApproveResultSchema, "POST", path13, answer.body);
65733
+ throw httpError("POST", path13, answer);
65734
+ }
65735
+
65736
+ // src/commands/_agentCli.ts
65737
+ import { access } from "fs/promises";
65738
+ var JSON_HELP = "Emit raw JSON to stdout instead of a pretty render.";
65739
+ var CONFIRM_HELP = "Token from a prior plan run \u2014 required to actually write";
65740
+ function withTarget(cmd) {
65741
+ addServerFlag(cmd);
65742
+ addAccountFlag(cmd);
65743
+ return cmd;
65744
+ }
65745
+ function std() {
65746
+ return { out: process.stdout, err: process.stderr };
65747
+ }
65748
+ function requireId(value, placeholder) {
65749
+ const id = (value ?? "").trim();
65750
+ if (id === "") {
65751
+ throw new Error(`missing required option: --id ${placeholder}`);
65752
+ }
65753
+ return id;
65754
+ }
65755
+ function requirePositiveInt(value, flag) {
65756
+ const n = /^\d+$/.test(value) ? Number(value) : NaN;
65757
+ if (!Number.isSafeInteger(n) || n <= 0) {
65758
+ throw new Error(`${flag} ${JSON.stringify(value)}: expected a positive integer`);
65759
+ }
65760
+ return n;
65761
+ }
65762
+ function requireBool(value, flag) {
65763
+ if (value === "true") return true;
65764
+ if (value === "false") return false;
65765
+ throw new Error(`${flag} ${JSON.stringify(value ?? "")}: expected true or false`);
65766
+ }
65767
+ function splitList(value) {
65768
+ return [
65769
+ ...new Set(
65770
+ (value ?? "").split(",").map((part) => part.trim()).filter((part) => part !== "")
65771
+ )
65772
+ ];
65773
+ }
65774
+ function isNoneSentinel(parts, flag, value, what) {
65775
+ if (!parts.includes("none")) return false;
65776
+ if (parts.length === 1) return true;
65777
+ throw new Error(`${flag} ${JSON.stringify(value)}: "none" cannot be combined with ${what}`);
65778
+ }
65779
+ function shellArg(value) {
65780
+ return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : shellQuote(value);
65781
+ }
65782
+ function proseStream(cfg) {
65783
+ return cfg.asJson ? cfg.err : cfg.out;
65784
+ }
65785
+ async function assertNoOverwrite(paths, force) {
65786
+ if (force) return;
65787
+ const existing = [];
65788
+ for (const path13 of paths) {
65789
+ try {
65790
+ await access(path13);
65791
+ existing.push(path13);
65792
+ } catch {
65793
+ }
65794
+ }
65795
+ if (existing.length > 0) {
65796
+ throw new Error(
65797
+ `refusing to overwrite existing file(s): ${existing.join(", ")}. Pull into another --dir, or pass --force to replace them.`
65798
+ );
65799
+ }
65800
+ }
65801
+
65802
+ // src/commands/calcmodelsAgent.ts
65803
+ var AGENT_POLL_INTERVAL_MS = 3e3;
65804
+ var AGENT_DEFAULT_TIMEOUT_S = 900;
65805
+ var MAX_TRANSIENT_READ_FAILURES = 5;
65806
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
65807
+ function parseOperationIdArg(raw2) {
65808
+ if (!UUID_RE2.test(raw2)) throw new Error(`invalid operation id: "${raw2}" (expected a UUID)`);
65809
+ return raw2.toLowerCase();
65810
+ }
65811
+ function isLive(p) {
65812
+ return p.status === "PENDING" || p.status === "RUNNING";
65813
+ }
65814
+ function formatNumber(n) {
65815
+ return n === null || n === void 0 ? "n/a" : n.toLocaleString("en-US", { maximumFractionDigits: 2 });
65816
+ }
65817
+ function previewLine(result) {
65818
+ const parts = [`${formatNumber(result.total_count)} rows`];
65819
+ const cov = result.coverage;
65820
+ if (cov) {
65821
+ const pct2 = cov.pct === null || cov.pct === void 0 ? "n/a" : `${formatNumber(cov.pct)}%`;
65822
+ parts.push(
65823
+ `coverage ${pct2} (allocated ${formatNumber(cov.allocated_total)} of pool ${formatNumber(cov.pool_total)})`
65824
+ );
65825
+ }
65826
+ const checks = result.balance_checks ?? [];
65827
+ if (checks.length > 0) {
65828
+ const ok = checks.filter((c) => c.ok).length;
65829
+ parts.push(`balance: ${ok} of ${checks.length} groups net to zero`);
65830
+ }
65831
+ return `preview: ${parts.join(" \xB7 ")}`;
65832
+ }
65833
+ function previewCmd(operationId, serverFlag) {
65834
+ return `dr calcmodels agent preview ${operationId} --server ${serverFlag}`;
65835
+ }
65836
+ function renderPreview(out, p, operationId, serverFlag) {
65837
+ out.write(`status: ${p.status}
65838
+ `);
65839
+ if (p.conversation_id) out.write(`conversation: ${escapeControlChars(p.conversation_id)}
65840
+ `);
65841
+ if (isLive(p)) {
65842
+ out.write(
65843
+ `
65844
+ The turn is still running. Check again with:
65845
+ ${previewCmd(operationId, serverFlag)}
65846
+ `
65847
+ );
65848
+ return;
65849
+ }
65850
+ if (p.answer_text) out.write(`
65851
+ ${escapeControlChars(p.answer_text.trim())}
65852
+ `);
65853
+ if (p.changes_summary) {
65854
+ out.write("\nchanges:\n");
65855
+ for (const line of p.changes_summary.trim().split("\n"))
65856
+ out.write(` ${escapeControlChars(line)}
65857
+ `);
65858
+ }
65859
+ const savable = p.status === "SUCCEEDED" && p.result !== null && p.result !== void 0;
65860
+ if (p.status === "SUCCEEDED") {
65861
+ const d = p.definition;
65862
+ if (d) {
65863
+ out.write(
65864
+ `
65865
+ proposal: ${escapeControlChars(JSON.stringify(d.name ?? ""))} (${escapeControlChars(d.model_type ?? "unknown")}) \u2192 destination table ${d.destination_template_id ?? "n/a"}
65866
+ `
65867
+ );
65868
+ }
65869
+ if (p.result) {
65870
+ out.write(`${previewLine(p.result)}
65871
+ `);
65872
+ for (const w of p.result.warnings ?? []) {
65873
+ const label = [w.code, w.message].filter((s) => !!s).join(": ");
65874
+ if (label !== "") out.write(`warning: ${escapeControlChars(label)}
65875
+ `);
65876
+ }
65877
+ } else {
65878
+ out.write(
65879
+ "preview: none ran. The server saves only a turn whose preview ran (CALC_PREVIEW_NOT_READY).\n"
65880
+ );
65881
+ }
65882
+ }
65883
+ const next = [];
65884
+ if (savable) {
65885
+ next.push(`save the draft: dr calcmodels agent save ${operationId} --server ${serverFlag}`);
65886
+ }
65887
+ if (p.conversation_id) {
65888
+ next.push(
65889
+ `reply: dr calcmodels agent ask '<reply>' --conversation ${shellQuote(p.conversation_id)} --server ${serverFlag}`
65890
+ );
65891
+ }
65892
+ if (next.length > 0) out.write(`
65893
+ next:
65894
+ ${next.map((l) => ` ${l}
65895
+ `).join("")}`);
65896
+ }
65897
+ function writeOutcome(out, p, operationId, serverFlag, asJson) {
65898
+ if (asJson) writeJSONIndented(out, { operation_id: operationId, ...p });
65899
+ else renderPreview(out, p, operationId, serverFlag);
65900
+ if (p.status === "FAILED") {
65901
+ const why = p.error ? escapeControlChars(describeServerError(p.error)) : "the server gave no error detail";
65902
+ throw new Error(`turn ${operationId} failed: ${why}`);
65903
+ }
65904
+ }
65905
+ var ReadTimeoutError = class extends Error {
65906
+ constructor(operationId, ms) {
65907
+ super(`reading operation ${operationId} took longer than ${ms} ms`);
65908
+ this.name = "ReadTimeoutError";
65909
+ }
65910
+ };
65911
+ function isTransient(err) {
65912
+ if (err instanceof ReadTimeoutError) return true;
65913
+ if (err instanceof CalcAgentHttpError) return err.status >= 500;
65914
+ return err instanceof TypeError;
65915
+ }
65916
+ var AGENT_READ_TIMEOUT_MS = 3e4;
65917
+ var AGENT_READ_TIMEOUT_FLOOR_MS = 5e3;
65918
+ var defaultSleep8 = (ms, signal) => delay(ms, void 0, { signal });
65919
+ async function runAgentAsk(cfg) {
65920
+ const accepted = await postTurn(
65921
+ cfg.client,
65922
+ {
65923
+ message: cfg.message,
65924
+ ...cfg.conversationId !== void 0 && { conversationId: cfg.conversationId }
65925
+ },
65926
+ { signal: cfg.signal }
65927
+ );
65928
+ const op = accepted.operation_id;
65929
+ const conv = accepted.conversation_id;
65930
+ cfg.err.write(
65931
+ `turn started: operation ${op}${conv ? `, conversation ${escapeControlChars(conv)}` : ""}
65932
+ `
65933
+ );
65934
+ if (!cfg.wait) {
65935
+ if (cfg.asJson) {
65936
+ writeJSONIndented(cfg.out, accepted);
65937
+ return;
65938
+ }
65939
+ cfg.out.write(`operation: ${op}
65940
+ Read it with:
65941
+ ${previewCmd(op, cfg.serverFlag)}
65942
+ `);
65943
+ return;
65944
+ }
65945
+ cfg.err.write(
65946
+ `Waiting for the agent. Ctrl-C stops the wait, not the turn; resume with:
65947
+ ${previewCmd(op, cfg.serverFlag)}
65948
+ `
65949
+ );
65950
+ const preview2 = await pollPreview(cfg, op);
65951
+ writeOutcome(cfg.out, preview2, op, cfg.serverFlag, cfg.asJson);
65952
+ }
65953
+ async function pollPreview(cfg, operationId) {
65954
+ const interval = cfg.pollIntervalMs ?? AGENT_POLL_INTERVAL_MS;
65955
+ const sleep3 = cfg.sleep ?? defaultSleep8;
65956
+ const now = cfg.now ?? Date.now;
65957
+ const deadline = now() + cfg.timeoutMs;
65958
+ const readMax = cfg.readTimeoutMs ?? AGENT_READ_TIMEOUT_MS;
65959
+ const readFloor = cfg.readTimeoutFloorMs ?? AGENT_READ_TIMEOUT_FLOOR_MS;
65960
+ let failures = 0;
65961
+ for (; ; ) {
65962
+ await sleep3(Math.max(0, Math.min(interval, deadline - now())), cfg.signal);
65963
+ try {
65964
+ const budget = Math.min(readMax, Math.max(deadline - now(), readFloor));
65965
+ const p = await readWithin(cfg, operationId, budget);
65966
+ failures = 0;
65967
+ if (!isLive(p)) return p;
65968
+ } catch (err) {
65969
+ failures += 1;
65970
+ if (!isTransient(err)) throw err;
65971
+ if (failures > MAX_TRANSIENT_READ_FAILURES && now() < deadline) throw err;
65972
+ }
65973
+ if (now() >= deadline) {
65974
+ throw new Error(
65975
+ `turn ${operationId} is still running after ${Math.round(cfg.timeoutMs / 1e3)}s. It keeps running on the server; read it with: ${previewCmd(operationId, cfg.serverFlag)}`
65976
+ );
65977
+ }
65978
+ }
65979
+ }
65980
+ async function readWithin(cfg, operationId, budgetMs) {
65981
+ const ctl = new AbortController();
65982
+ const relay = () => ctl.abort(cfg.signal.reason);
65983
+ if (cfg.signal.aborted) relay();
65984
+ else cfg.signal.addEventListener("abort", relay, { once: true });
65985
+ const timer = setTimeout(() => ctl.abort(), budgetMs);
65986
+ try {
65987
+ return await getPreview2(cfg.client, operationId, { signal: ctl.signal });
65988
+ } catch (err) {
65989
+ if (ctl.signal.aborted && !cfg.signal.aborted)
65990
+ throw new ReadTimeoutError(operationId, budgetMs);
65991
+ throw err;
65992
+ } finally {
65993
+ clearTimeout(timer);
65994
+ cfg.signal.removeEventListener("abort", relay);
65995
+ }
65996
+ }
65997
+ async function runAgentPreview(cfg) {
65998
+ const p = await getPreview2(cfg.client, cfg.operationId, { signal: cfg.signal });
65999
+ writeOutcome(cfg.out, p, cfg.operationId, cfg.serverFlag, cfg.asJson);
66000
+ }
66001
+ var PREVIEW_ALREADY_SAVED = "CALC_PREVIEW_ALREADY_SAVED";
66002
+ var VERSION_CHANGED = "CALC_APPROVAL_VERSION_CHANGED";
66003
+ async function runAgentSave(cfg) {
66004
+ let name = cfg.name;
66005
+ if (name !== void 0 && name.trim() === "") throw new Error("--name must not be empty");
66006
+ if (name === void 0) {
66007
+ const p = await getPreview2(cfg.client, cfg.operationId, { signal: cfg.signal });
66008
+ name = p.definition?.name ?? void 0;
66009
+ if (name === void 0 || name.trim() === "") {
66010
+ throw new Error(
66011
+ `--name is required: operation ${cfg.operationId} has no proposed name (status ${p.status})`
66012
+ );
66013
+ }
66014
+ }
66015
+ let saved;
66016
+ try {
66017
+ saved = await save2(
66018
+ cfg.client,
66019
+ {
66020
+ operationId: cfg.operationId,
66021
+ name,
66022
+ ...cfg.calcModelId !== void 0 && { calcModelId: cfg.calcModelId }
66023
+ },
66024
+ { signal: cfg.signal }
66025
+ );
66026
+ } catch (err) {
66027
+ if (err instanceof CalcAgentHttpError && err.code === PREVIEW_ALREADY_SAVED) {
66028
+ const modelId = err.numberField("calc_model_id");
66029
+ if (modelId !== void 0) {
66030
+ throw new Error(
66031
+ `${err.message}. To add this turn as a new version of model ${modelId}: dr calcmodels agent save ${cfg.operationId} --model ${modelId} --server ${cfg.serverFlag}`,
66032
+ { cause: err }
66033
+ );
66034
+ }
66035
+ }
66036
+ throw err;
66037
+ }
66038
+ if (cfg.asJson) {
66039
+ writeJSONIndented(cfg.out, saved);
66040
+ return;
66041
+ }
66042
+ cfg.out.write(
66043
+ `Saved draft calc model ${saved.id} ${escapeControlChars(JSON.stringify(saved.name))} (version ${saved.version_id}). This version does not run until it is approved.
66044
+
66045
+ next:
66046
+ dr calcmodels agent approve ${saved.id} --server ${cfg.serverFlag}
66047
+ `
66048
+ );
66049
+ }
66050
+ function approveAction(calcModelId, versionId) {
66051
+ return { resource: "calcmodel", id: `${calcModelId}@v${versionId}`, verb: "approve" };
66052
+ }
66053
+ function versionChanged(calcModelId, planned, current, cause) {
66054
+ const newer = current !== void 0 ? ` (${current})` : "";
66055
+ return new Error(
66056
+ `calc model ${calcModelId}: a newer version${newer} was saved after the plan for version ${planned}. Review it: re-run without --confirm-token. Nothing was activated.`,
66057
+ cause === void 0 ? void 0 : { cause }
66058
+ );
66059
+ }
66060
+ function impactLines(calcModelId, plan) {
66061
+ const i = plan.impact;
66062
+ const pct2 = i.coverage_pct === null || i.coverage_pct === void 0 ? "n/a" : `${formatNumber(i.coverage_pct)}%`;
66063
+ return [
66064
+ `calc model: ${calcModelId}`,
66065
+ `version: ${plan.version_id}`,
66066
+ `rows the first run writes: ${formatNumber(i.total_rows)}`,
66067
+ `allocated total: ${formatNumber(i.allocated_total)} of pool total ${formatNumber(i.pool_total)} (coverage ${pct2})`,
66068
+ "Approving activates this version and starts its first production run at once."
66069
+ ];
66070
+ }
66071
+ async function runAgentApprove(cfg) {
66072
+ const executing = cfg.token !== void 0 && cfg.token.trim() !== "";
66073
+ if (executing && cfg.versionId === void 0) {
66074
+ throw new Error(
66075
+ "--version-id is required with --confirm-token: copy the command the plan printed"
66076
+ );
66077
+ }
66078
+ if (executing) await preflightConfirmToken(approveAction(cfg.id, cfg.versionId), cfg.token);
66079
+ const plan = await requestApprovalImpact(cfg.client, cfg.id, { signal: cfg.signal });
66080
+ if (executing && plan.version_id !== cfg.versionId) {
66081
+ throw versionChanged(cfg.id, cfg.versionId, plan.version_id);
66082
+ }
66083
+ const versionId = executing ? cfg.versionId : plan.version_id;
66084
+ await twoStepConfirm({
66085
+ out: cfg.out,
66086
+ action: approveAction(cfg.id, versionId),
66087
+ token: cfg.token,
66088
+ server: cfg.serverFlag,
66089
+ confirmCmd: `dr calcmodels agent approve ${cfg.id} --version-id ${versionId} --server ${cfg.serverFlag}${cfg.asJson ? " --json" : ""}`,
66090
+ describe: () => Promise.resolve(impactLines(cfg.id, plan)),
66091
+ execute: async () => {
66092
+ let result;
66093
+ try {
66094
+ result = await confirmApproval(cfg.client, cfg.id, versionId, {
66095
+ signal: cfg.signal
66096
+ });
66097
+ } catch (err) {
66098
+ if (err instanceof CalcAgentHttpError && err.code === VERSION_CHANGED) {
66099
+ throw versionChanged(cfg.id, versionId, err.numberField("version_id"), err);
66100
+ }
66101
+ throw err;
66102
+ }
66103
+ if (cfg.asJson) {
66104
+ writeJSONIndented(cfg.out, result);
66105
+ return;
66106
+ }
66107
+ const run2 = result.run_id ? `First run: ${escapeControlChars(result.run_id)}.` : `No run was started: the model is not runnable now. Check it with dr calcmodels get ${cfg.id} --server ${cfg.serverFlag}.`;
66108
+ cfg.out.write(`Activated calc model ${cfg.id} (version ${versionId}). ${run2}
66109
+ `);
66110
+ }
66111
+ });
66112
+ }
66113
+ var ASK_SETUP_HEADROOM_MS = 6e4;
66114
+ function registerCalcmodelsAgentCommand(cm) {
66115
+ const agent = cm.command("agent").description(
66116
+ "Build an AGENT (allocation) calc model with the builder agent \u2014 the web builder endpoints. Super Admin + the calc_model_agent_builder flag."
66117
+ );
66118
+ const askCmd = agent.command("ask").description(
66119
+ "Send one message to the builder agent and wait for its answer. Ctrl-C stops the wait, not the turn."
66120
+ ).argument("<message>", "what to build, or a reply to the agent").option("--conversation <id>", "continue this conversation (from a previous answer)").option("--no-wait", "return after the server accepts the turn; read it with `preview`").option(
66121
+ "--timeout <seconds>",
66122
+ `stop waiting after this long (default ${AGENT_DEFAULT_TIMEOUT_S})`
66123
+ ).option("--json", "Output raw JSON", false).action(
66124
+ async (message, opts) => {
66125
+ if (message.trim() === "") throw new Error("<message> must not be empty");
66126
+ const timeoutMs = parsePositiveSecondsFlag(
66127
+ opts.timeout,
66128
+ "--timeout",
66129
+ AGENT_DEFAULT_TIMEOUT_S * 1e3
66130
+ );
66131
+ const serverKey = resolveServer(opts.server);
66132
+ const setup = await setupCall(opts.server, opts.account, {
66133
+ timeoutMs: timeoutMs + ASK_SETUP_HEADROOM_MS
66134
+ });
66135
+ try {
66136
+ await runAgentAsk({
66137
+ client: setup.client,
66138
+ message,
66139
+ ...opts.conversation !== void 0 && { conversationId: opts.conversation },
66140
+ wait: opts.wait !== false,
66141
+ timeoutMs,
66142
+ asJson: opts.json === true,
66143
+ serverFlag: serverKey,
66144
+ signal: setup.signal,
66145
+ out: process.stdout,
66146
+ err: process.stderr
66147
+ });
66148
+ } finally {
66149
+ setup.cleanup();
66150
+ }
66151
+ }
66152
+ );
66153
+ addServerFlag(askCmd);
66154
+ addAccountFlag(askCmd);
66155
+ const previewCmdDef = agent.command("preview").description("Read one turn: the agent's answer, its proposal, and the preview figures").argument("<operation_id>", "the operation id `ask` printed").option("--json", "Output raw JSON", false).action(async (opArg, opts) => {
66156
+ const operationId = parseOperationIdArg(opArg);
66157
+ const serverKey = resolveServer(opts.server);
66158
+ const setup = await setupCall(opts.server, opts.account);
66159
+ try {
66160
+ await runAgentPreview({
66161
+ client: setup.client,
66162
+ operationId,
66163
+ asJson: opts.json === true,
66164
+ serverFlag: serverKey,
66165
+ signal: setup.signal,
66166
+ out: process.stdout
66167
+ });
66168
+ } finally {
66169
+ setup.cleanup();
66170
+ }
66171
+ });
66172
+ addServerFlag(previewCmdDef);
66173
+ addAccountFlag(previewCmdDef);
66174
+ const saveCmd = agent.command("save").description("Save a succeeded turn as a DRAFT calc model. Nothing runs until `approve`.").argument("<operation_id>", "the operation id of a SUCCEEDED turn").option("--name <name>", "model name (default: the agent's proposed name)").option("--model <calc_model_id>", "add this turn as a new version of an existing AGENT model").option("--json", "Output raw JSON", false).action(async (opArg, opts) => {
66175
+ const operationId = parseOperationIdArg(opArg);
66176
+ const calcModelId = opts.model === void 0 ? void 0 : requirePositiveInt(opts.model, "--model");
66177
+ const serverKey = resolveServer(opts.server);
66178
+ const setup = await setupCall(opts.server, opts.account);
66179
+ try {
66180
+ await runAgentSave({
66181
+ client: setup.client,
66182
+ operationId,
66183
+ ...opts.name !== void 0 && { name: opts.name },
66184
+ ...calcModelId !== void 0 && { calcModelId },
66185
+ asJson: opts.json === true,
66186
+ serverFlag: serverKey,
66187
+ signal: setup.signal,
66188
+ out: process.stdout
66189
+ });
66190
+ } finally {
66191
+ setup.cleanup();
66192
+ }
66193
+ });
66194
+ addServerFlag(saveCmd);
66195
+ addAccountFlag(saveCmd);
66196
+ const approveCmd = agent.command("approve").description(
66197
+ "Activate an AGENT model's newest version and start its first production run (two-step confirm; a person confirms on production)."
66198
+ ).argument("<calc_model_id>", "the model id `save` printed").option("--version-id <n>", "the version the plan showed (required with --confirm-token)").option("--confirm-token <token>", "Token from a prior plan run \u2014 required to activate", "").option("--json", "Output raw JSON of the activation", false).action(
66199
+ async (idArg, opts) => {
66200
+ const id = requirePositiveInt(idArg, "<calc_model_id>");
66201
+ const versionId = opts.versionId === void 0 ? void 0 : requirePositiveInt(opts.versionId, "--version-id");
66202
+ const token = opts.confirmToken;
66203
+ if (token !== void 0 && token.trim() !== "") {
66204
+ if (versionId === void 0) {
66205
+ throw new Error(
66206
+ "--version-id is required with --confirm-token: copy the command the plan printed"
66207
+ );
66208
+ }
66209
+ await preflightConfirmToken(approveAction(id, versionId), token);
66210
+ }
66211
+ const serverKey = resolveServer(opts.server);
66212
+ const setup = await setupCall(opts.server, opts.account);
66213
+ try {
66214
+ await runAgentApprove({
66215
+ client: setup.client,
66216
+ id,
66217
+ ...versionId !== void 0 && { versionId },
66218
+ token,
66219
+ asJson: opts.json === true,
66220
+ serverFlag: serverKey,
66221
+ signal: setup.signal,
66222
+ out: process.stdout
66223
+ });
66224
+ } finally {
66225
+ setup.cleanup();
66226
+ }
66227
+ }
66228
+ );
66229
+ addServerFlag(approveCmd);
66230
+ addAccountFlag(approveCmd);
66231
+ }
66232
+
65490
66233
  // src/commands/calcmodels.ts
65491
66234
  async function runCalcmodelsList(cfg) {
65492
66235
  const models = await list25(cfg.client, { signal: cfg.signal });
@@ -65891,6 +66634,7 @@ function registerCalcmodelsCommand(root) {
65891
66634
  });
65892
66635
  addServerFlag(deleteCmd);
65893
66636
  addAccountFlag(deleteCmd);
66637
+ registerCalcmodelsAgentCommand(cm);
65894
66638
  }
65895
66639
 
65896
66640
  // src/commands/calcVals.ts
@@ -68875,13 +69619,13 @@ import { promises as fsPromises3 } from "fs";
68875
69619
  import * as nodePath3 from "path";
68876
69620
 
68877
69621
  // src/api/spendControl.ts
68878
- var BASE3 = "/api/spend-control/v1";
69622
+ var BASE4 = "/api/spend-control/v1";
68879
69623
  var BASE_V2 = "/api/spend-control/v2";
68880
69624
  var MAX_BULK_STATUS_IDS = 50;
68881
69625
  var MAX_PAGE_SIZE = 100;
68882
69626
  async function listBillingFrequencies(client, options) {
68883
69627
  const out = await client.getJSON(
68884
- `${BASE3}/catalogs/billing-frequencies/`,
69628
+ `${BASE4}/catalogs/billing-frequencies/`,
68885
69629
  options
68886
69630
  );
68887
69631
  return out ?? [];
@@ -68915,7 +69659,7 @@ function buildContractListQuery(params) {
68915
69659
  }
68916
69660
  async function listContracts(client, params = {}, options) {
68917
69661
  const qs = buildContractListQuery(params);
68918
- const path13 = qs === "" ? `${BASE3}/contracts/` : `${BASE3}/contracts/?${qs}`;
69662
+ const path13 = qs === "" ? `${BASE4}/contracts/` : `${BASE4}/contracts/?${qs}`;
68919
69663
  const out = await client.getJSON(path13, options);
68920
69664
  if (out === null) {
68921
69665
  return { items: [], total: 0, page: null, size: null, pages: null };
@@ -68943,10 +69687,10 @@ async function getContract(client, contractId, params = {}, options) {
68943
69687
  qs.set("currency_code", params.convertTo);
68944
69688
  }
68945
69689
  const suffix = qs.toString() === "" ? "" : `?${qs.toString()}`;
68946
- return client.getJSON(`${BASE3}/contracts/${contractId}${suffix}`, options);
69690
+ return client.getJSON(`${BASE4}/contracts/${contractId}${suffix}`, options);
68947
69691
  }
68948
69692
  async function deleteContract(client, contractId, options) {
68949
- await client.deleteJSON(`${BASE3}/contracts/${contractId}`, options);
69693
+ await client.deleteJSON(`${BASE4}/contracts/${contractId}`, options);
68950
69694
  }
68951
69695
  async function uploadFilesWithAi(client, files, options = {}) {
68952
69696
  if (files.length === 0) return [];
@@ -68954,7 +69698,7 @@ async function uploadFilesWithAi(client, files, options = {}) {
68954
69698
  throw new Error(`uploadFilesWithAi: max 10 files per call, got ${files.length}`);
68955
69699
  }
68956
69700
  const out = await client.postMultipartFiles(
68957
- `${BASE3}/contracts/files-with-ai`,
69701
+ `${BASE4}/contracts/files-with-ai`,
68958
69702
  { is_additional: options.isAdditional === true ? "true" : "false" },
68959
69703
  files,
68960
69704
  "files",
@@ -68964,7 +69708,7 @@ async function uploadFilesWithAi(client, files, options = {}) {
68964
69708
  }
68965
69709
  async function uploadContractFile(client, file, options = {}) {
68966
69710
  return client.postMultipartFiles(
68967
- `${BASE3}/contracts/file`,
69711
+ `${BASE4}/contracts/file`,
68968
69712
  { is_additional: options.isAdditional === true ? "true" : "false" },
68969
69713
  [file],
68970
69714
  "file",
@@ -68977,7 +69721,7 @@ async function listContractFiles(client, contractId, params = {}, options) {
68977
69721
  if (params.size !== void 0) qs.set("size", String(params.size));
68978
69722
  const suffix = qs.toString() === "" ? "" : `?${qs.toString()}`;
68979
69723
  const out = await client.getJSON(
68980
- `${BASE3}/contracts/${contractId}/files${suffix}`,
69724
+ `${BASE4}/contracts/${contractId}/files${suffix}`,
68981
69725
  options
68982
69726
  );
68983
69727
  if (out === null) {
@@ -69003,13 +69747,13 @@ async function listAllContractFiles(client, contractId, options) {
69003
69747
  }
69004
69748
  async function getContractFileDownload(client, fileId, options) {
69005
69749
  return client.postJSON(
69006
- `${BASE3}/contracts/files/${fileId}/download`,
69750
+ `${BASE4}/contracts/files/${fileId}/download`,
69007
69751
  {},
69008
69752
  options
69009
69753
  );
69010
69754
  }
69011
69755
  async function deleteContractFile(client, fileId, options) {
69012
- await client.deleteJSON(`${BASE3}/contracts/files/${fileId}`, options);
69756
+ await client.deleteJSON(`${BASE4}/contracts/files/${fileId}`, options);
69013
69757
  }
69014
69758
  async function uploadFileWithAiAsync(client, file, options = {}) {
69015
69759
  return client.postMultipartFiles(
@@ -69045,7 +69789,7 @@ var EXTRACT_POLL_TIMEOUT_MS = 10 * 60 * 1e3;
69045
69789
  function isTerminalAiStatus(status2) {
69046
69790
  return status2 === "PROCESSED" || status2 === "FAILED";
69047
69791
  }
69048
- async function defaultSleep8(ms, signal) {
69792
+ async function defaultSleep9(ms, signal) {
69049
69793
  if (signal?.aborted === true) return;
69050
69794
  await new Promise((resolve3) => {
69051
69795
  const timer = setTimeout(resolve3, ms);
@@ -69062,7 +69806,7 @@ async function defaultSleep8(ms, signal) {
69062
69806
  async function pollExtractionStatus(client, contractFileIds, options = {}) {
69063
69807
  const interval = options.pollIntervalMs ?? EXTRACT_POLL_INTERVAL_MS;
69064
69808
  const timeout = options.timeoutMs ?? EXTRACT_POLL_TIMEOUT_MS;
69065
- const sleep3 = options.sleep ?? defaultSleep8;
69809
+ const sleep3 = options.sleep ?? defaultSleep9;
69066
69810
  const now = options.now ?? Date.now;
69067
69811
  const resolved = /* @__PURE__ */ new Map();
69068
69812
  if (contractFileIds.length === 0) return { resolved, pending: [] };
@@ -69129,14 +69873,14 @@ async function bulkCreateContracts(client, body, options) {
69129
69873
  throw new Error(`bulkCreateContracts: max 10 contracts per call, got ${body.contracts.length}`);
69130
69874
  }
69131
69875
  const out = await client.postJSON(
69132
- `${BASE3}/contracts/bulk-create`,
69876
+ `${BASE4}/contracts/bulk-create`,
69133
69877
  body,
69134
69878
  options
69135
69879
  );
69136
69880
  return out ?? { contracts: [] };
69137
69881
  }
69138
69882
  async function partialUpdateContract(client, contractId, body, options) {
69139
- return client.patchJSON(`${BASE3}/contracts/${contractId}`, body, options);
69883
+ return client.patchJSON(`${BASE4}/contracts/${contractId}`, body, options);
69140
69884
  }
69141
69885
 
69142
69886
  // src/types/spendControl.ts
@@ -69556,7 +70300,7 @@ function chunkContracts(contracts, size = 10) {
69556
70300
  }
69557
70301
 
69558
70302
  // src/api/spendControlVendors.ts
69559
- var BASE4 = "/api/spend-control/v1/vendors";
70303
+ var BASE5 = "/api/spend-control/v1/vendors";
69560
70304
  var MAX_PAGE_SIZE2 = 100;
69561
70305
  function buildVendorListQuery(params) {
69562
70306
  const qs = new URLSearchParams();
@@ -69587,19 +70331,19 @@ function emptyPage() {
69587
70331
  return { items: [], total: 0, page: null, size: null, pages: null };
69588
70332
  }
69589
70333
  async function listVendors(client, params = {}, options) {
69590
- const out = await client.getJSON(withQuery(`${BASE4}/`, params), options);
70334
+ const out = await client.getJSON(withQuery(`${BASE5}/`, params), options);
69591
70335
  return out ?? emptyPage();
69592
70336
  }
69593
70337
  async function listAppStoreVendors(client, params = {}, options) {
69594
70338
  const out = await client.getJSON(
69595
- withQuery(`${BASE4}/app-store`, params),
70339
+ withQuery(`${BASE5}/app-store`, params),
69596
70340
  options
69597
70341
  );
69598
70342
  return out ?? emptyPage();
69599
70343
  }
69600
70344
  async function listVendorsWithContracts(client, params = {}, options) {
69601
70345
  const out = await client.getJSON(
69602
- withQuery(`${BASE4}/with-contracts`, params),
70346
+ withQuery(`${BASE5}/with-contracts`, params),
69603
70347
  options
69604
70348
  );
69605
70349
  return out ?? emptyPage();
@@ -69616,24 +70360,24 @@ async function listAllVendors(fetchPage, client, params = {}, options) {
69616
70360
  return collected;
69617
70361
  }
69618
70362
  async function listVendorCategories(client, options) {
69619
- const out = await client.getJSON(`${BASE4}/categories`, options);
70363
+ const out = await client.getJSON(`${BASE5}/categories`, options);
69620
70364
  return out ?? [];
69621
70365
  }
69622
70366
  async function listVendorsWithMultipleContracts(client, options) {
69623
- const out = await client.getJSON(`${BASE4}/with-multiple-contracts`, options);
70367
+ const out = await client.getJSON(`${BASE5}/with-multiple-contracts`, options);
69624
70368
  return out ?? [];
69625
70369
  }
69626
70370
  async function getVendorOwners(client, vendorId, options) {
69627
- return client.getJSON(`${BASE4}/${encodeURIComponent(vendorId)}/owners/`, options);
70371
+ return client.getJSON(`${BASE5}/${encodeURIComponent(vendorId)}/owners/`, options);
69628
70372
  }
69629
70373
  async function createVendor(client, name, options) {
69630
- return client.postJSON(`${BASE4}/`, { name }, options);
70374
+ return client.postJSON(`${BASE5}/`, { name }, options);
69631
70375
  }
69632
70376
  async function bulkUpdateVendorVisibility(client, vendors, options) {
69633
70377
  if (vendors.length === 0) {
69634
70378
  throw new Error("bulkUpdateVendorVisibility: at least one vendor is required");
69635
70379
  }
69636
- const out = await client.patchJSON(`${BASE4}/visibility`, { vendors }, options);
70380
+ const out = await client.patchJSON(`${BASE5}/visibility`, { vendors }, options);
69637
70381
  return out ?? [];
69638
70382
  }
69639
70383
 
@@ -74746,103 +75490,37 @@ import { promises as fs36 } from "fs";
74746
75490
  import { Option as Option4 } from "commander";
74747
75491
 
74748
75492
  // src/api/templateLibrary.ts
74749
- var BASE5 = "/api/report_library/management";
75493
+ var BASE6 = "/api/report_library/management";
74750
75494
  async function list28(client, options) {
74751
- return await client.getJSON(BASE5, options) ?? [];
75495
+ return await client.getJSON(BASE6, options) ?? [];
74752
75496
  }
74753
75497
  async function create19(client, body, options) {
74754
- const out = await client.postJSON(BASE5, body, options);
75498
+ const out = await client.postJSON(BASE6, body, options);
74755
75499
  if (out === null) {
74756
- throw new Error(`unexpected empty response from POST ${BASE5}`);
75500
+ throw new Error(`unexpected empty response from POST ${BASE6}`);
74757
75501
  }
74758
75502
  return out;
74759
75503
  }
74760
75504
  async function update12(client, id, body, options) {
74761
- const out = await client.putJSON(`${BASE5}/${id}`, body, options);
75505
+ const out = await client.putJSON(`${BASE6}/${id}`, body, options);
74762
75506
  if (out === null) {
74763
- throw new Error(`unexpected empty response from PUT ${BASE5}/${id}`);
75507
+ throw new Error(`unexpected empty response from PUT ${BASE6}/${id}`);
74764
75508
  }
74765
75509
  return out;
74766
75510
  }
74767
75511
  async function uploadImage(client, id, png, filename, options) {
74768
75512
  const out = await client.putMultipart(
74769
- `${BASE5}/${id}`,
75513
+ `${BASE6}/${id}`,
74770
75514
  {},
74771
75515
  new Blob([png], { type: "image/png" }),
74772
75516
  { ...options, fileFieldName: "image", filename }
74773
75517
  );
74774
75518
  if (out === null) {
74775
- throw new Error(`unexpected empty response from PUT ${BASE5}/${id} (image)`);
75519
+ throw new Error(`unexpected empty response from PUT ${BASE6}/${id} (image)`);
74776
75520
  }
74777
75521
  return out;
74778
75522
  }
74779
75523
 
74780
- // src/commands/_agentCli.ts
74781
- import { access } from "fs/promises";
74782
- var JSON_HELP = "Emit raw JSON to stdout instead of a pretty render.";
74783
- var CONFIRM_HELP = "Token from a prior plan run \u2014 required to actually write";
74784
- function withTarget(cmd) {
74785
- addServerFlag(cmd);
74786
- addAccountFlag(cmd);
74787
- return cmd;
74788
- }
74789
- function std() {
74790
- return { out: process.stdout, err: process.stderr };
74791
- }
74792
- function requireId(value, placeholder) {
74793
- const id = (value ?? "").trim();
74794
- if (id === "") {
74795
- throw new Error(`missing required option: --id ${placeholder}`);
74796
- }
74797
- return id;
74798
- }
74799
- function requirePositiveInt(value, flag) {
74800
- const n = /^\d+$/.test(value) ? Number(value) : NaN;
74801
- if (!Number.isSafeInteger(n) || n <= 0) {
74802
- throw new Error(`${flag} ${JSON.stringify(value)}: expected a positive integer`);
74803
- }
74804
- return n;
74805
- }
74806
- function requireBool(value, flag) {
74807
- if (value === "true") return true;
74808
- if (value === "false") return false;
74809
- throw new Error(`${flag} ${JSON.stringify(value ?? "")}: expected true or false`);
74810
- }
74811
- function splitList(value) {
74812
- return [
74813
- ...new Set(
74814
- (value ?? "").split(",").map((part) => part.trim()).filter((part) => part !== "")
74815
- )
74816
- ];
74817
- }
74818
- function isNoneSentinel(parts, flag, value, what) {
74819
- if (!parts.includes("none")) return false;
74820
- if (parts.length === 1) return true;
74821
- throw new Error(`${flag} ${JSON.stringify(value)}: "none" cannot be combined with ${what}`);
74822
- }
74823
- function shellArg(value) {
74824
- return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : shellQuote(value);
74825
- }
74826
- function proseStream(cfg) {
74827
- return cfg.asJson ? cfg.err : cfg.out;
74828
- }
74829
- async function assertNoOverwrite(paths, force) {
74830
- if (force) return;
74831
- const existing = [];
74832
- for (const path13 of paths) {
74833
- try {
74834
- await access(path13);
74835
- existing.push(path13);
74836
- } catch {
74837
- }
74838
- }
74839
- if (existing.length > 0) {
74840
- throw new Error(
74841
- `refusing to overwrite existing file(s): ${existing.join(", ")}. Pull into another --dir, or pass --force to replace them.`
74842
- );
74843
- }
74844
- }
74845
-
74846
75524
  // src/commands/_templateLibraryImages.ts
74847
75525
  var TEMPLATE_LIBRARY_IMAGE_COLORS = ["pink", "purple", "yellow"];
74848
75526
  var TEMPLATE_LIBRARY_IMAGE_FILENAMES = {
@@ -75699,11 +76377,11 @@ import { mkdir as mkdir5, writeFile as writeFile8 } from "fs/promises";
75699
76377
  import { join as join18 } from "path";
75700
76378
 
75701
76379
  // src/api/aiService.ts
75702
- import { z as z3 } from "zod";
76380
+ import { z as z4 } from "zod";
75703
76381
  var AI_SERVICE_MINT_PATH = "/api/ai-service/v1/Authentication/authenticate";
75704
76382
  var ERROR_BODY_MAX_CHARS = 1e3;
75705
- var MintResponseSchema = z3.object({ token: z3.string().min(1) });
75706
- var ErrorEnvelopeSchema = z3.object({ code: z3.string(), message: z3.string() });
76383
+ var MintResponseSchema = z4.object({ token: z4.string().min(1) });
76384
+ var ErrorEnvelopeSchema = z4.object({ code: z4.string(), message: z4.string() });
75707
76385
  function parseErrorEnvelope(text2) {
75708
76386
  let parsed2;
75709
76387
  try {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "1.6.179",
3
+ "cliVersion": "1.6.181",
4
4
  "_contract": "Resolve a command path through `aliases` (canonical prefix + alias segment -> canonical path) before any lookup. A path listed in `pendingCommands`, or whose first segment is a key of `pending`, is UNDECIDED, not unrestricted: fail closed or skip it, and never read a path's absence from `commands` as 'no requirement'. An entry carrying `denied` is UNAVAILABLE to any AGENT-SCOPED credential — one whose `scopes` claim carries at least one `resource:action` entry; a claim that is absent, empty, capability-only or wholly unparseable is UNRESTRICTED, and this mark does not apply to it — whatever else that entry says: it is not a ceiling and it is not always-available, its `denied.reason` says why and `denied.until` names the ticket that lifts it, and the absence of `requires` on it must NEVER be read as 'no requirement'. Only an entry in `commands` with an empty `requires` means always-available.",
5
5
  "commands": {
6
6
  "login": {
@@ -4089,6 +4089,38 @@
4089
4089
  }
4090
4090
  ]
4091
4091
  },
4092
+ "calcmodels agent ask": {
4093
+ "requires": [
4094
+ {
4095
+ "resource": "tables",
4096
+ "action": "write"
4097
+ }
4098
+ ]
4099
+ },
4100
+ "calcmodels agent preview": {
4101
+ "requires": [
4102
+ {
4103
+ "resource": "tables",
4104
+ "action": "read"
4105
+ }
4106
+ ]
4107
+ },
4108
+ "calcmodels agent save": {
4109
+ "requires": [
4110
+ {
4111
+ "resource": "tables",
4112
+ "action": "write"
4113
+ }
4114
+ ]
4115
+ },
4116
+ "calcmodels agent approve": {
4117
+ "requires": [
4118
+ {
4119
+ "resource": "tables",
4120
+ "action": "destructive"
4121
+ }
4122
+ ]
4123
+ },
4092
4124
  "calc-vals list": {
4093
4125
  "requires": [
4094
4126
  {
@@ -233,6 +233,7 @@ area's gotchas.
233
233
 
234
234
  **Calc models** → [reference/calcmodels.md](reference/calcmodels.md)
235
235
  - `calcmodels list|get|create|update|set-flow|recalculate|delete` (alias `calc-models`)
236
+ - `calcmodels agent ask|preview|save|approve` — build an AGENT (allocation) model with the builder agent; `approve` is Tier A
236
237
 
237
238
  **Calculated values (per-table)** → [reference/calc-vals.md](reference/calc-vals.md)
238
239
  - `calc-vals list|get|create|update|delete` (alias `calcvals`) — `--table <id>` required on every subcommand
@@ -69,6 +69,60 @@ names to integer IDs against the tenant — never ask the operator for IDs:
69
69
  For `update`: `dr calcmodels get <id> --server <S> --json` → splice the requested
70
70
  change → write to a tmpfile → `dr calcmodels update <id> --file <tmpfile> --server <S>`.
71
71
 
72
+ ## AGENT models (builder)
73
+
74
+ An AGENT model is an allocation model that the calc model builder's agent writes
75
+ from plain English. `dr calcmodels agent` uses the web builder's endpoints. The
76
+ server allows it only for a Super Admin, on an org with the
77
+ `calc_model_agent_builder` flag, inside a per-org cap on running turns. The CLI
78
+ does not check these; it prints the server's 403 or 429. `create`/`update` above
79
+ are for the three older types only.
80
+
81
+ Flow:
82
+
83
+ 1. `dr calcmodels agent ask "<what to build>" --server <S>` — one turn. It
84
+ prints the operation id at once, then waits (3 s polls, default 900 s,
85
+ `--timeout <s>`). Ctrl-C or the timeout stop the wait only; read the turn
86
+ later with `dr calcmodels agent preview <operation_id> --server <S>`.
87
+ 2. Read the outcome by `status`:
88
+ - `NO_PROPOSAL` — the agent asks a question. Ask the operator. Do not answer
89
+ it yourself. Then `dr calcmodels agent ask "<reply>" --conversation <id> --server <S>`,
90
+ with the conversation id the answer printed.
91
+ - `SUCCEEDED` — a proposal and its preview: rows, coverage (allocated of
92
+ pool), balance groups, warnings. Show these to the operator. To change it,
93
+ reply in the same conversation. "preview: none ran" means it cannot be saved.
94
+ - `FAILED` — exits non-zero with the gate code, phase and suggestion.
95
+ 3. `dr calcmodels agent save <operation_id> [--name <n>] --server <S>` — saves a
96
+ DRAFT. The name defaults to the agent's proposed name. The new version
97
+ does not run until it is approved. `--model <id>` adds the turn as a new
98
+ version of that AGENT model; an active model keeps running its current
99
+ version until then.
100
+ 4. `dr calcmodels agent approve <id> --server <S>` — two-step. The plan prints
101
+ the version and its impact (rows the first run writes, allocated total,
102
+ pool total, coverage). Show it to the operator and stop. Run the printed
103
+ command (`--version-id <n> --confirm-token <t>`) only after the operator
104
+ gives a clear in-chat "yes" to that version and impact, on every server.
105
+ Approving activates the version and starts its first production run at once.
106
+
107
+ Gotchas:
108
+
109
+ - `approve` is Tier A (see safety.md). On a production server the token run
110
+ also opens a dialog or terminal prompt where the person types a word; with
111
+ no interactive channel the binary refuses. On a non-production server the
112
+ token alone activates, so the operator's in-chat "yes" is the only check.
113
+ - `save` never activates, and `ask`/`save` write no rows to the destination
114
+ table. Only `approve` starts a run that does.
115
+ - A newer version saved after the plan stops the token run before anything is
116
+ activated ("a newer version … was saved"). Re-run `approve` without the
117
+ token and show the new impact.
118
+ - `CALC_PREVIEW_ALREADY_SAVED`: that turn is already model N. The error
119
+ prints the `save … --model N` command for a new version.
120
+ - `CALC_TURN_RATE_LIMITED` (429): the org has too many running turns. Wait.
121
+ A 409 means a turn already runs on this conversation.
122
+ - Only the user who started a turn can read or continue it (404 otherwise).
123
+ - Out of scope: opening a saved model's definition for edit (no server read
124
+ endpoint), archiving AGENT models.
125
+
72
126
  ## Customer-facing docs (KB)
73
127
 
74
128
  Help Center → **"Getting Data into Datarails"** → Data Management. Find current articles with `dr kb search "calc model beginning balance"` (or
@@ -21,6 +21,7 @@ Gated commands include: `dashboards delete`, `datamappers delete`,
21
21
  `dynamic-ranges delete`, `extables delete`, `functions delete`, `lut delete`,
22
22
  `reports delete`, `storyboards delete`, `metrics delete`, `metrics categories
23
23
  delete`, `metrics dimensions delete`, `calcmodels delete|set-flow|recalculate`,
24
+ `calcmodels agent approve`,
24
25
  `reconciliation filebox delete`, `integrations delete`, `integrations it
25
26
  delete`, `devices command-delete`, `validate onboarding session delete`,
26
27
  `widgets delete`, `cash rules delete|delete-all|reorder|update`,
@@ -67,6 +68,9 @@ organization at once);
67
68
  `agents pin` / `unpin` (decide which agent version customers get on a server; one
68
69
  run with `--production` covers every production server, so relay the plan's
69
70
  per-server lines — they are also the undo);
71
+ `calcmodels agent approve` (activates an AGENT calc model and starts its first
72
+ production run, which writes new rows; the token is bound to the version whose
73
+ impact the plan printed);
70
74
  `error-enrichments import` (replaces every rule on the server, zeroes all
71
75
  hits/feedback counters, and reassigns every rule id); and
72
76
  `dashboards push --override` / `filebox push --override`. `import` is **not** an
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-cli",
3
- "version": "1.6.179",
3
+ "version": "1.6.181",
4
4
  "description": "Datarails CLI",
5
5
  "type": "module",
6
6
  "engines": {
@@ -17,7 +17,7 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@datarails-ai/dr-appspace-cli": "0.12.56",
20
+ "@datarails-ai/dr-appspace-cli": "0.12.58",
21
21
  "@modelcontextprotocol/sdk": "^1.29.0",
22
22
  "@napi-rs/keyring": "^1.3.0",
23
23
  "commander": "^12.1.0",