@datarails-ai/dr-cli 1.6.105 → 1.6.106

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/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.105",
11
+ version: "1.6.106",
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.11.2",
43
+ "@datarails-ai/dr-appspace-cli": "0.11.3",
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-07T13:28:02.898Z";
72
+ var BUILD_TIME = "2026-10-07T14:49:41.993Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -5395,18 +5395,18 @@ async function openBrowser(url) {
5395
5395
  // src/commands/_serverPicker.ts
5396
5396
  async function readLine(stdin) {
5397
5397
  return new Promise((resolve3, reject) => {
5398
- let data3 = "";
5398
+ let data4 = "";
5399
5399
  const onData = (chunk) => {
5400
- data3 += chunk.toString();
5401
- const i = data3.indexOf("\n");
5400
+ data4 += chunk.toString();
5401
+ const i = data4.indexOf("\n");
5402
5402
  if (i >= 0) {
5403
5403
  cleanup();
5404
- resolve3(data3.slice(0, i + 1));
5404
+ resolve3(data4.slice(0, i + 1));
5405
5405
  }
5406
5406
  };
5407
5407
  const onEnd = () => {
5408
5408
  cleanup();
5409
- resolve3(data3);
5409
+ resolve3(data4);
5410
5410
  };
5411
5411
  const onError = (err) => {
5412
5412
  cleanup();
@@ -5530,7 +5530,7 @@ async function readFirstLineFromPipe(stdin) {
5530
5530
  stdin.setEncoding?.("utf8");
5531
5531
  stdin.resume?.();
5532
5532
  return new Promise((resolve3, reject) => {
5533
- let data3 = "";
5533
+ let data4 = "";
5534
5534
  let done = false;
5535
5535
  const finish = (line) => {
5536
5536
  if (done) return;
@@ -5539,11 +5539,11 @@ async function readFirstLineFromPipe(stdin) {
5539
5539
  resolve3(line.endsWith("\r") ? line.slice(0, -1) : line);
5540
5540
  };
5541
5541
  const onData = (chunk) => {
5542
- data3 += typeof chunk === "string" ? chunk : chunk.toString("utf8");
5543
- const i = data3.indexOf("\n");
5544
- if (i >= 0) finish(data3.slice(0, i));
5542
+ data4 += typeof chunk === "string" ? chunk : chunk.toString("utf8");
5543
+ const i = data4.indexOf("\n");
5544
+ if (i >= 0) finish(data4.slice(0, i));
5545
5545
  };
5546
- const onEnd = () => finish(data3);
5546
+ const onEnd = () => finish(data4);
5547
5547
  const onError = (err) => {
5548
5548
  if (done) return;
5549
5549
  done = true;
@@ -12711,25 +12711,25 @@ function buildAggregateBody(dimensions, metrics, filters) {
12711
12711
  }
12712
12712
  return body;
12713
12713
  }
12714
- function groupRows(data3) {
12715
- if (!Array.isArray(data3)) return data3;
12716
- return data3.filter((r) => !(isPlainObject(r) && ("col_keys" in r || "row_keys" in r)));
12714
+ function groupRows(data4) {
12715
+ if (!Array.isArray(data4)) return data4;
12716
+ return data4.filter((r) => !(isPlainObject(r) && ("col_keys" in r || "row_keys" in r)));
12717
12717
  }
12718
12718
  function readTotals(metadata) {
12719
12719
  if (!isPlainObject(metadata)) return null;
12720
12720
  const totals = metadata["totals"];
12721
12721
  return isPlainObject(totals) && Object.keys(totals).length > 0 ? totals : null;
12722
12722
  }
12723
- function asRowList(data3) {
12724
- if (Array.isArray(data3)) {
12725
- if (data3.every((x) => isPlainObject(x))) {
12726
- return data3;
12723
+ function asRowList(data4) {
12724
+ if (Array.isArray(data4)) {
12725
+ if (data4.every((x) => isPlainObject(x))) {
12726
+ return data4;
12727
12727
  }
12728
12728
  return null;
12729
12729
  }
12730
- if (isPlainObject(data3)) {
12730
+ if (isPlainObject(data4)) {
12731
12731
  for (const key of ["rows", "data", "results", "items"]) {
12732
- const v = data3[key];
12732
+ const v = data4[key];
12733
12733
  if (Array.isArray(v) && v.every((x) => isPlainObject(x))) {
12734
12734
  return v;
12735
12735
  }
@@ -12762,10 +12762,10 @@ function writeTable5(out, rows) {
12762
12762
  out.write(parts.join("") + "\n");
12763
12763
  }
12764
12764
  }
12765
- function renderAggregateResult(out, data3) {
12766
- const rows = asRowList(data3);
12765
+ function renderAggregateResult(out, data4) {
12766
+ const rows = asRowList(data4);
12767
12767
  if (rows === null) {
12768
- out.write(JSON.stringify(data3, null, 2) + "\n");
12768
+ out.write(JSON.stringify(data4, null, 2) + "\n");
12769
12769
  return;
12770
12770
  }
12771
12771
  if (rows.length === 0) {
@@ -15481,6 +15481,108 @@ function registerDynamicRangesCommand(root) {
15481
15481
  // src/commands/functions.ts
15482
15482
  import { promises as fs11 } from "fs";
15483
15483
 
15484
+ // src/api/_cacheKeyPolling.ts
15485
+ var CacheKeyHttpError = class extends Error {
15486
+ status;
15487
+ path;
15488
+ bodyText;
15489
+ constructor(method, path11, status2, bodyText) {
15490
+ super(`${method} ${path11}: HTTP ${status2}: ${truncate5(bodyText, 200)}`);
15491
+ this.name = "CacheKeyHttpError";
15492
+ this.status = status2;
15493
+ this.path = path11;
15494
+ this.bodyText = bodyText;
15495
+ }
15496
+ };
15497
+ async function putOnce(client, path11, body, options) {
15498
+ const res = await client.do("PUT", path11, body, options);
15499
+ const text2 = await res.text();
15500
+ if (res.status !== 409 && (res.status < 200 || res.status >= 300)) {
15501
+ throw new CacheKeyHttpError("PUT", path11, res.status, text2);
15502
+ }
15503
+ if (text2 === "" || text2 === '""') {
15504
+ return { status: res.status, parsed: null };
15505
+ }
15506
+ try {
15507
+ return { status: res.status, parsed: JSON.parse(text2) };
15508
+ } catch (err) {
15509
+ const msg = err instanceof Error ? err.message : String(err);
15510
+ throw new Error(`PUT ${path11}: decode: ${msg}`);
15511
+ }
15512
+ }
15513
+ function extractCacheKey(body) {
15514
+ if (body === null || typeof body !== "object") return "";
15515
+ const top = body["cache_key"];
15516
+ if (typeof top === "string" && top !== "") return top;
15517
+ const detail = body["detail"];
15518
+ if (detail !== null && typeof detail === "object") {
15519
+ const nested = detail["cache_key"];
15520
+ if (typeof nested === "string" && nested !== "") return nested;
15521
+ }
15522
+ return "";
15523
+ }
15524
+ function withCacheKey(path11, key) {
15525
+ return `${path11}?cache_key=${encodeURIComponent(key)}`;
15526
+ }
15527
+ var CACHE_KEY_POLL_INTERVAL_MS = 500;
15528
+ var abortableSleep = (ms, signal) => new Promise((resolve3, reject) => {
15529
+ if (signal?.aborted) {
15530
+ reject(signal.reason ?? new Error("aborted"));
15531
+ return;
15532
+ }
15533
+ const onAbort = () => {
15534
+ clearTimeout(t);
15535
+ reject(signal?.reason ?? new Error("aborted"));
15536
+ };
15537
+ const t = setTimeout(() => {
15538
+ signal?.removeEventListener("abort", onAbort);
15539
+ resolve3();
15540
+ }, ms);
15541
+ signal?.addEventListener("abort", onAbort, { once: true });
15542
+ });
15543
+ async function putWithCacheKeyPolling(client, path11, payload, options = {}) {
15544
+ const interval = options.pollIntervalMs ?? CACHE_KEY_POLL_INTERVAL_MS;
15545
+ const sleep4 = options.sleep ?? abortableSleep;
15546
+ const label = options.label ?? "data";
15547
+ const body = JSON.stringify(payload);
15548
+ const first = await putOnce(client, path11, body, options);
15549
+ if (first.status !== 409) {
15550
+ return first.parsed;
15551
+ }
15552
+ const cacheKey = extractCacheKey(first.parsed);
15553
+ if (cacheKey === "") {
15554
+ throw new Error(
15555
+ `PUT ${path11}: 409 without cache_key: ${truncate5(JSON.stringify(first.parsed ?? ""), 200)}`
15556
+ );
15557
+ }
15558
+ const pollPath = withCacheKey(path11, cacheKey);
15559
+ for (; ; ) {
15560
+ try {
15561
+ await sleep4(interval, options.signal);
15562
+ } catch (err) {
15563
+ const reason = err instanceof Error ? err.message : String(err);
15564
+ throw new Error(`${label} polling timed out: ${reason}`);
15565
+ }
15566
+ let result;
15567
+ try {
15568
+ result = await putOnce(client, pollPath, body, options);
15569
+ } catch (err) {
15570
+ if (options.signal?.aborted) {
15571
+ const reason = options.signal.reason instanceof Error ? options.signal.reason.message : String(options.signal.reason ?? "aborted");
15572
+ throw new Error(`${label} polling timed out: ${reason}`);
15573
+ }
15574
+ throw err;
15575
+ }
15576
+ if (result.status !== 409) {
15577
+ return result.parsed;
15578
+ }
15579
+ }
15580
+ }
15581
+ function truncate5(s, n) {
15582
+ if (s.length <= n) return s;
15583
+ return s.slice(0, n) + "\u2026";
15584
+ }
15585
+
15484
15586
  // src/api/functions.ts
15485
15587
  function normalize(raw) {
15486
15588
  return {
@@ -15524,6 +15626,16 @@ async function getDetail(client, id, options) {
15524
15626
  default_agg_config: out.default_agg_config ?? null
15525
15627
  };
15526
15628
  }
15629
+ var DEFAULT_AGG_CONFIG = {
15630
+ YTD: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
15631
+ QTD: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
15632
+ RM: { rolling_month_before: 0, is_end_of_period: true },
15633
+ SA: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
15634
+ YTG: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
15635
+ QTG: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
15636
+ MTD: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
15637
+ MTG: { aggregate_by: "month", fnc: "sum", is_end_of_period: true }
15638
+ };
15527
15639
  async function create6(client, args, options) {
15528
15640
  const body = {
15529
15641
  id: -1,
@@ -15534,6 +15646,10 @@ async function create6(client, args, options) {
15534
15646
  calculated_values: [],
15535
15647
  template: args.template
15536
15648
  };
15649
+ if (args.dateField !== void 0) {
15650
+ body.default_agg_datefield = args.dateField;
15651
+ body.default_agg_config = DEFAULT_AGG_CONFIG;
15652
+ }
15537
15653
  const out = await client.postJSON("/api/functions/", body, options);
15538
15654
  if (out === null) {
15539
15655
  return { id: 0, name: "", aggfun: "", value_field: 0, template: 0 };
@@ -15550,7 +15666,9 @@ async function getFull(client, id, options) {
15550
15666
  value_field: 0,
15551
15667
  template: 0,
15552
15668
  calculated_values: [],
15553
- is_fx_aware: false
15669
+ is_fx_aware: false,
15670
+ default_agg_datefield: null,
15671
+ default_agg_config: null
15554
15672
  };
15555
15673
  }
15556
15674
  const links = (out.calculated_values ?? []).map((cv) => ({
@@ -15558,7 +15676,13 @@ async function getFull(client, id, options) {
15558
15676
  field: cv.field ?? null,
15559
15677
  side: cv.side ?? null
15560
15678
  }));
15561
- return { ...normalize(out), calculated_values: links, is_fx_aware: out.is_fx_aware === true };
15679
+ return {
15680
+ ...normalize(out),
15681
+ calculated_values: links,
15682
+ is_fx_aware: out.is_fx_aware === true,
15683
+ default_agg_datefield: out.default_agg_datefield ?? null,
15684
+ default_agg_config: out.default_agg_config ?? null
15685
+ };
15562
15686
  }
15563
15687
  async function update2(client, id, body, options) {
15564
15688
  const out = await client.putJSON(
@@ -15577,6 +15701,140 @@ async function rename2(client, id, name, options) {
15577
15701
  async function deleteById3(client, id, options) {
15578
15702
  await client.deleteJSON(`/api/functions/${id}`, options);
15579
15703
  }
15704
+ async function data2(client, id, body, options = {}) {
15705
+ const out = await putWithCacheKeyPolling(client, `/api/functions/${id}/data/v2`, body, {
15706
+ label: "function data",
15707
+ ...options
15708
+ });
15709
+ return out ?? { query_results: [] };
15710
+ }
15711
+ async function drilldown(client, id, body, options = {}) {
15712
+ return putWithCacheKeyPolling(client, `/api/functions/${id}/drilldown/v4`, body, {
15713
+ label: "function drilldown",
15714
+ ...options
15715
+ });
15716
+ }
15717
+ function parseDrilldown(body) {
15718
+ if (!Array.isArray(body)) {
15719
+ throw new Error(`unexpected drill-down response (not an array): ${preview(body)}`);
15720
+ }
15721
+ const first = body[0];
15722
+ if (first !== null && typeof first === "object" && "error" in first) {
15723
+ const e = first;
15724
+ if (e["error"] === "too_large_data") {
15725
+ return {
15726
+ kind: "too_large",
15727
+ rows_count: Number(e["rows_count"] ?? 0),
15728
+ cols_count: Number(e["cols_count"] ?? 0)
15729
+ };
15730
+ }
15731
+ throw new Error(`drill-down error: ${preview(first)}`);
15732
+ }
15733
+ if (body.length === 0) {
15734
+ return { kind: "rows", rows: [], schema: { fields: [], empty_fields: [] } };
15735
+ }
15736
+ const last = body[body.length - 1];
15737
+ const isSchema = last !== null && typeof last === "object" && Array.isArray(last["fields"]);
15738
+ if (!isSchema) {
15739
+ throw new Error("unexpected drill-down response: no trailing {fields, empty_fields} schema");
15740
+ }
15741
+ const raw = last;
15742
+ const schema = {
15743
+ fields: raw["fields"],
15744
+ empty_fields: Array.isArray(raw["empty_fields"]) ? raw["empty_fields"] : []
15745
+ };
15746
+ return { kind: "rows", rows: body.slice(0, -1), schema };
15747
+ }
15748
+ async function exportDrilldownCsv(client, id, body, options = {}) {
15749
+ const interval = options.pollIntervalMs ?? CACHE_KEY_POLL_INTERVAL_MS;
15750
+ const sleep4 = options.sleep ?? abortableSleep;
15751
+ const path11 = `/api/functions/${id}/drilldown/export_to_csv`;
15752
+ const payload = JSON.stringify(body);
15753
+ let res = await putOnce(client, path11, payload, options);
15754
+ while (res.status === 409) {
15755
+ const key = extractCacheKey(res.parsed);
15756
+ if (key === "") {
15757
+ throw new Error(`PUT ${path11}: 409 without cache_key: ${preview(res.parsed)}`);
15758
+ }
15759
+ for (; ; ) {
15760
+ try {
15761
+ await sleep4(interval, options.signal);
15762
+ } catch (err) {
15763
+ const reason = err instanceof Error ? err.message : String(err);
15764
+ throw new Error(`drill-down CSV export polling timed out: ${reason}`);
15765
+ }
15766
+ const status2 = await client.postJSON(
15767
+ "/api/cache/cache_keys_status",
15768
+ { cache_keys: [key] },
15769
+ options
15770
+ );
15771
+ const entry = status2?.[key];
15772
+ if (entry !== null && typeof entry === "object") {
15773
+ const e = entry;
15774
+ const status3 = typeof e["status"] === "string" ? e["status"].toUpperCase() : "";
15775
+ const hasError = e["error"] !== void 0 && e["error"] !== null && e["error"] !== false;
15776
+ if (status3 === "FAILURE" || hasError) {
15777
+ throw new Error(
15778
+ `drill-down CSV export failed on the server (cache key ${key}, status ${status3 === "" ? "-" : status3}${hasError ? `, error ${preview(e["error"])}` : ""}); retry, or narrow the drill-down with more --filter flags`
15779
+ );
15780
+ }
15781
+ if (e["is_ready"] === true) break;
15782
+ }
15783
+ }
15784
+ res = await putOnce(client, withCacheKey(path11, key), payload, options);
15785
+ }
15786
+ const parsed2 = res.parsed;
15787
+ const reportId = parsed2 !== null && typeof parsed2 === "object" ? parsed2["drill_down_report_id"] : void 0;
15788
+ if (typeof reportId !== "number") {
15789
+ throw new Error(`PUT ${path11}: no drill_down_report_id in ${preview(parsed2)}`);
15790
+ }
15791
+ const reportPath = `/api/dashboards/report/${reportId}?report_type=drill_down_report`;
15792
+ const report = await client.do("GET", reportPath, void 0, options);
15793
+ if (report.status < 200 || report.status >= 300) {
15794
+ const text2 = await report.text();
15795
+ throw new Error(`GET ${reportPath}: HTTP ${report.status}: ${text2.slice(0, 200)}`);
15796
+ }
15797
+ return { reportId, bytes: new Uint8Array(await report.arrayBuffer()) };
15798
+ }
15799
+ function preview(v) {
15800
+ const s = JSON.stringify(v ?? null);
15801
+ return s.length > 200 ? s.slice(0, 200) + "\u2026" : s;
15802
+ }
15803
+
15804
+ // src/commands/_excelSerial.ts
15805
+ var EXCEL_UNIX_EPOCH_OFFSET_DAYS = 25569;
15806
+ var EXCEL_MAX_SERIAL = 2958465;
15807
+ var MIN_EPOCH_SECONDS = 1e8;
15808
+ var MAX_EPOCH_SECONDS = 1e11;
15809
+ var SERIAL_DATE_FORMS = "a full date (YYYY-MM-DD, M/D/YYYY or an ISO datetime \u2014 a month alone is ambiguous), an Excel serial (e.g. 45688), or Unix epoch seconds";
15810
+ function toExcelSerial(raw) {
15811
+ const s = raw.trim();
15812
+ if (/^\d+(\.\d+)?$/.test(s)) {
15813
+ const n = Number(s);
15814
+ if (n <= EXCEL_MAX_SERIAL) return n;
15815
+ if (Number.isInteger(n) && n >= MIN_EPOCH_SECONDS && n < MAX_EPOCH_SECONDS) {
15816
+ return epochSecondsToSerial(n);
15817
+ }
15818
+ return null;
15819
+ }
15820
+ if (/^\d{4}[-/]\d{1,2}$/.test(s)) return null;
15821
+ const epoch = toEpochSeconds(s);
15822
+ if (epoch === null) return null;
15823
+ return epochSecondsToSerial(Number(epoch));
15824
+ }
15825
+ function epochSecondsToSerial(sec) {
15826
+ return Math.floor(sec / 86400) + EXCEL_UNIX_EPOCH_OFFSET_DAYS;
15827
+ }
15828
+ function excelSerialToIso(n) {
15829
+ if (!Number.isFinite(n)) return String(n);
15830
+ const ms = (Math.floor(n) - EXCEL_UNIX_EPOCH_OFFSET_DAYS) * 864e5;
15831
+ return new Date(ms).toISOString().slice(0, 10);
15832
+ }
15833
+ function epochSecondsToIso(sec) {
15834
+ if (!Number.isFinite(sec)) return String(sec);
15835
+ const iso = new Date(sec * 1e3).toISOString();
15836
+ return sec % 86400 === 0 ? iso.slice(0, 10) : iso.replace(/\.\d{3}Z$/, "Z");
15837
+ }
15580
15838
 
15581
15839
  // src/commands/functions.ts
15582
15840
  var VALID_AGGS2 = ["SUM", "COUNT", "AVG", "MIN", "MAX"];
@@ -15627,19 +15885,37 @@ async function runFunctionsGet(cfg) {
15627
15885
  renderFunctionTable(cfg.out, [fn]);
15628
15886
  renderFunctionFilters(cfg.out, fn);
15629
15887
  }
15888
+ async function resolveDateField(client, templateId, token, signal) {
15889
+ const index = await loadTemplateFields(client, templateId, { signal });
15890
+ const field = resolveField(index, token, "--date-field");
15891
+ if (!isDateField(field)) {
15892
+ throw new Error(
15893
+ `--date-field "${token}": "${field.name}" is a ${field.type} field; the default aggregation date field must be a Date field`
15894
+ );
15895
+ }
15896
+ return field;
15897
+ }
15630
15898
  async function runFunctionsCreate(cfg) {
15631
15899
  validateName(cfg.name);
15632
15900
  const aggUpper = validateAgg(cfg.aggOriginal);
15901
+ const dateField = cfg.dateField === void 0 ? void 0 : await resolveDateField(cfg.client, cfg.template, cfg.dateField, cfg.signal);
15633
15902
  const created = await create6(
15634
15903
  cfg.client,
15635
15904
  {
15636
15905
  name: cfg.name,
15637
15906
  template: cfg.template,
15638
15907
  valueField: cfg.valueField,
15639
- aggfun: aggUpper
15908
+ aggfun: aggUpper,
15909
+ ...dateField !== void 0 ? { dateField: dateField.id } : {}
15640
15910
  },
15641
15911
  { signal: cfg.signal }
15642
15912
  );
15913
+ if (dateField === void 0) {
15914
+ (cfg.err ?? process.stderr).write(
15915
+ `warning: no --date-field given \u2014 period aggregations (DR.YTD/QTD/MTD/\u2026, functions data --agg YTD) will not work for function ${created.id}; set one with: dr functions update ${created.id} --date-field <Date field>
15916
+ `
15917
+ );
15918
+ }
15643
15919
  if (cfg.asJson) {
15644
15920
  writeJSONIndented(cfg.out, toJsonShape(created));
15645
15921
  return;
@@ -15654,13 +15930,14 @@ async function runFunctionsUpdate(cfg) {
15654
15930
  const hasField = cfg.field !== void 0;
15655
15931
  const hasAgg = cfg.aggOriginal !== void 0;
15656
15932
  const hasTemplate = cfg.template !== void 0;
15657
- if (!hasName && !hasField && !hasAgg && !hasTemplate) {
15658
- throw new Error("nothing to update (pass --name/--field/--agg/--template)");
15933
+ const hasDateField = cfg.dateField !== void 0;
15934
+ if (!hasName && !hasField && !hasAgg && !hasTemplate && !hasDateField) {
15935
+ throw new Error("nothing to update (pass --name/--field/--agg/--template/--date-field)");
15659
15936
  }
15660
15937
  if (hasName) validateName(cfg.name);
15661
15938
  const aggUpper = hasAgg ? validateAgg(cfg.aggOriginal) : void 0;
15662
15939
  let result;
15663
- if (!hasField && !hasAgg && !hasTemplate) {
15940
+ if (!hasField && !hasAgg && !hasTemplate && !hasDateField) {
15664
15941
  await rename2(cfg.client, cfg.id, cfg.name, {
15665
15942
  signal: cfg.signal
15666
15943
  });
@@ -15669,6 +15946,13 @@ async function runFunctionsUpdate(cfg) {
15669
15946
  const current = await getFull(cfg.client, cfg.id, {
15670
15947
  signal: cfg.signal
15671
15948
  });
15949
+ const template = hasTemplate ? cfg.template : current.template;
15950
+ if (template !== current.template && current.default_agg_datefield !== null && !hasDateField) {
15951
+ throw new Error(
15952
+ `function ${cfg.id} has default date field ${current.default_agg_datefield}, which belongs to the old template ${current.template}; pass --date-field <Date field of template ${template}> together with --template`
15953
+ );
15954
+ }
15955
+ const dateField = hasDateField ? await resolveDateField(cfg.client, template, cfg.dateField, cfg.signal) : void 0;
15672
15956
  result = await update2(
15673
15957
  cfg.client,
15674
15958
  cfg.id,
@@ -15676,11 +15960,16 @@ async function runFunctionsUpdate(cfg) {
15676
15960
  name: hasName ? cfg.name : current.name,
15677
15961
  value_field: hasField ? cfg.field : current.value_field,
15678
15962
  aggfun: hasAgg ? aggUpper : current.aggfun,
15679
- template: hasTemplate ? cfg.template : current.template,
15963
+ template,
15680
15964
  calculated_values: current.calculated_values,
15681
15965
  // Re-sent so the full PUT can't silently reset the currency-conversion
15682
15966
  // flag (same class of trap as calculated_values above).
15683
- is_fx_aware: current.is_fx_aware
15967
+ is_fx_aware: current.is_fx_aware,
15968
+ ...dateField !== void 0 ? {
15969
+ default_agg_datefield: dateField.id,
15970
+ // Keep a config the function already has; otherwise the platform default.
15971
+ default_agg_config: current.default_agg_config ?? DEFAULT_AGG_CONFIG
15972
+ } : {}
15684
15973
  },
15685
15974
  { signal: cfg.signal }
15686
15975
  );
@@ -15823,6 +16112,365 @@ async function runFunctionsDelete(cfg) {
15823
16112
  }
15824
16113
  });
15825
16114
  }
16115
+ var PERIOD_AGGS = [
16116
+ "DEFAULT",
16117
+ "YTD",
16118
+ "YTG",
16119
+ "QTD",
16120
+ "QTG",
16121
+ "MTD",
16122
+ "MTG"
16123
+ ];
16124
+ function validatePeriodAgg(orig) {
16125
+ const upper = orig.trim().toUpperCase();
16126
+ if (!PERIOD_AGGS.includes(upper)) {
16127
+ throw new Error(`invalid --agg "${orig}" (valid: ${PERIOD_AGGS.join(", ")})`);
16128
+ }
16129
+ return upper;
16130
+ }
16131
+ function parseEvalFilterFlags(raw, flag) {
16132
+ return raw.map((r) => {
16133
+ const idx = r.indexOf("=");
16134
+ const name = idx > 0 ? r.slice(0, idx).trim() : "";
16135
+ const values = idx > 0 ? r.slice(idx + 1).split(",").map((v) => v.trim()).filter((v) => v !== "") : [];
16136
+ if (name === "" || values.length === 0) {
16137
+ throw new Error(`invalid ${flag} "${r}": expected NAME=V1[,V2\u2026]`);
16138
+ }
16139
+ return { flag, name, values };
16140
+ });
16141
+ }
16142
+ var NUMERIC_FIELD_TYPES = /* @__PURE__ */ new Set(["integer", "float", "number", "numeric", "monetary"]);
16143
+ function resolveEvalFilters(index, specs) {
16144
+ const seen = /* @__PURE__ */ new Set();
16145
+ return specs.map((s) => {
16146
+ const field = resolveField(index, s.name, s.flag);
16147
+ if (seen.has(field.id)) {
16148
+ throw new Error(
16149
+ `field "${field.name}" is given more than once; put all its values in one ${s.flag} "${field.name}=V1,V2"`
16150
+ );
16151
+ }
16152
+ seen.add(field.id);
16153
+ const isDate = isDateField(field);
16154
+ if (s.flag === "--date" && !isDate) {
16155
+ throw new Error(
16156
+ `--date "${s.name}=\u2026": "${field.name}" is a ${field.type} field, not Date; use --filter`
16157
+ );
16158
+ }
16159
+ let values;
16160
+ if (isDate) {
16161
+ values = s.values.map((v) => {
16162
+ const serial = toExcelSerial(v);
16163
+ if (serial === null) {
16164
+ throw new Error(
16165
+ `invalid date "${v}" for Date field "${field.name}" (${s.flag}): expected ${SERIAL_DATE_FORMS}`
16166
+ );
16167
+ }
16168
+ return serial;
16169
+ });
16170
+ } else if (NUMERIC_FIELD_TYPES.has(field.type.trim().toLowerCase())) {
16171
+ values = s.values.map((v) => /^-?\d+(\.\d+)?$/.test(v) ? Number(v) : v);
16172
+ } else {
16173
+ values = [...s.values];
16174
+ }
16175
+ return { field, isDate, values, isExcluded: s.flag === "--exclude" };
16176
+ });
16177
+ }
16178
+ function readableQueryError(msg) {
16179
+ const decoded = String(decodeHtmlEntitiesDeep(msg));
16180
+ const inner = [...decoded.matchAll(/\[\s*'((?:[^'\\]|\\.)*)'\s*\]/g)].map((m) => m[1]);
16181
+ return inner.length > 0 ? inner.join("; ") : decoded;
16182
+ }
16183
+ function rethrowEvaluatorError(err, id, what) {
16184
+ if (err instanceof CacheKeyHttpError) {
16185
+ let msg = `function ${id} ${what}: HTTP ${err.status}: ${err.bodyText}`;
16186
+ if (err.status === 400 && err.bodyText.includes("DateAgg")) {
16187
+ msg += `
16188
+ hint: period aggregations need a default date field \u2014 dr functions update ${id} --date-field <Date field>`;
16189
+ }
16190
+ throw new Error(msg);
16191
+ }
16192
+ throw err;
16193
+ }
16194
+ function formatEvalNumber(v) {
16195
+ if (v === null || v === void 0) return "-";
16196
+ if (typeof v !== "number") return typeof v === "string" ? v : JSON.stringify(v);
16197
+ if (!Number.isFinite(v) || Number.isInteger(v)) return String(v);
16198
+ if (Math.abs(v) < 1) return String(Number(v.toPrecision(10)));
16199
+ if (Math.abs(v) >= 1e15) return String(v);
16200
+ return String(Math.round(v * 1e4) / 1e4);
16201
+ }
16202
+ async function runFunctionsData(cfg) {
16203
+ if (cfg.id <= 0) throw new Error("function id must be positive");
16204
+ const agg = validatePeriodAgg(cfg.aggOriginal);
16205
+ const specs = [
16206
+ ...parseEvalFilterFlags(cfg.filters, "--filter"),
16207
+ ...parseEvalFilterFlags(cfg.dates, "--date")
16208
+ ];
16209
+ const fn = await getDetail(cfg.client, cfg.id, { signal: cfg.signal });
16210
+ const resolved = specs.length === 0 ? [] : resolveEvalFilters(
16211
+ await loadTemplateFields(cfg.client, fn.template, { signal: cfg.signal }),
16212
+ specs
16213
+ );
16214
+ const body = {
16215
+ queries: [
16216
+ {
16217
+ client_key: "q1",
16218
+ agg_type: agg,
16219
+ fields: resolved.map((r) => ({ name: r.field.name, values: r.values }))
16220
+ }
16221
+ ],
16222
+ global_filters: [],
16223
+ function_filters: []
16224
+ };
16225
+ let res;
16226
+ try {
16227
+ res = await data2(cfg.client, cfg.id, body, {
16228
+ signal: cfg.signal,
16229
+ pollIntervalMs: cfg.pollIntervalMs,
16230
+ sleep: cfg.sleep
16231
+ });
16232
+ } catch (err) {
16233
+ rethrowEvaluatorError(err, cfg.id, "data");
16234
+ }
16235
+ if (cfg.asJson) writeJSONIndented(cfg.out, res);
16236
+ const results = Array.isArray(res.query_results) ? res.query_results : [];
16237
+ const errors = results.filter((q) => q.error !== void 0 && q.error !== null);
16238
+ if (errors.length > 0) {
16239
+ throw new Error(
16240
+ `function ${cfg.id} data: ` + errors.map((q) => readableQueryError(String(q.error?.msg ?? ""))).join("; ")
16241
+ );
16242
+ }
16243
+ if (cfg.asJson) return;
16244
+ const values = results.flatMap((q) => Array.isArray(q.res) ? q.res : []);
16245
+ if (values.length === 0) {
16246
+ cfg.out.write("(no data: nothing matches these filters)\n");
16247
+ return;
16248
+ }
16249
+ const rows = [{ cells: [...resolved.map((r) => r.field.name), "VALUE"] }];
16250
+ for (const r of values) {
16251
+ const k = Array.isArray(r.k) ? r.k : [];
16252
+ const keyCells = k.length === resolved.length ? k.map(
16253
+ (kv, i) => resolved[i]?.isDate === true && typeof kv === "number" ? excelSerialToIso(kv) : formatEvalNumber(kv)
16254
+ ) : resolved.map((_, i) => i === 0 ? JSON.stringify(k) : "");
16255
+ rows.push({ cells: [...keyCells, formatEvalNumber(r.v)] });
16256
+ }
16257
+ writeTable11(cfg.out, rows);
16258
+ }
16259
+ function aggregateColumn(aggfun, values) {
16260
+ const present = values.filter((v) => v !== null && v !== void 0 && v !== "");
16261
+ const nums = present.map((v) => Number(v)).filter((n) => Number.isFinite(n));
16262
+ switch (aggfun.trim().toUpperCase()) {
16263
+ case "COUNT":
16264
+ return present.length;
16265
+ case "AVG":
16266
+ return nums.length === 0 ? null : nums.reduce((a, b) => a + b, 0) / nums.length;
16267
+ case "MIN":
16268
+ return nums.length === 0 ? null : Math.min(...nums);
16269
+ case "MAX":
16270
+ return nums.length === 0 ? null : Math.max(...nums);
16271
+ default:
16272
+ return nums.reduce((a, b) => a + b, 0);
16273
+ }
16274
+ }
16275
+ function drilldownCell(v, isDate) {
16276
+ if (v === null || v === void 0) return "";
16277
+ if (isDate && typeof v === "number") return epochSecondsToIso(v);
16278
+ if (typeof v === "number") return formatEvalNumber(v);
16279
+ if (typeof v === "string") return v;
16280
+ return JSON.stringify(v);
16281
+ }
16282
+ function drilldownRowsToCsv(rows, schema) {
16283
+ const cols = schema.fields.length > 0 ? schema.fields : Object.keys(rows[0] ?? {}).map((name) => ({ name }));
16284
+ const dateCols = new Set(
16285
+ schema.fields.filter((f) => (f.type ?? "").toLowerCase() === "date").map((f) => f.name)
16286
+ );
16287
+ const cell = (v) => {
16288
+ if (v === null || v === void 0) return "";
16289
+ if (typeof v === "string") return v;
16290
+ return typeof v === "object" ? JSON.stringify(v) : String(v);
16291
+ };
16292
+ const lines = [formatCsvRow(cols.map((c) => c.name))];
16293
+ for (const r of rows) {
16294
+ lines.push(
16295
+ formatCsvRow(
16296
+ cols.map((c) => {
16297
+ const v = r[c.name];
16298
+ return dateCols.has(c.name) && typeof v === "number" ? epochSecondsToIso(v) : cell(v);
16299
+ })
16300
+ )
16301
+ );
16302
+ }
16303
+ return lines.join("\n") + "\n";
16304
+ }
16305
+ async function runFunctionsDrilldown(cfg) {
16306
+ if (cfg.id <= 0) throw new Error("function id must be positive");
16307
+ if (cfg.forceCsv && cfg.outPath === void 0) {
16308
+ throw new Error("--csv needs --out <file.csv>");
16309
+ }
16310
+ if (!Number.isInteger(cfg.limit) || cfg.limit < 0) {
16311
+ throw new Error("--limit must be a non-negative integer (0 = all rows)");
16312
+ }
16313
+ const agg = validatePeriodAgg(cfg.aggOriginal);
16314
+ const specs = [
16315
+ ...parseEvalFilterFlags(cfg.filters, "--filter"),
16316
+ ...parseEvalFilterFlags(cfg.excludes, "--exclude"),
16317
+ ...parseEvalFilterFlags(cfg.dates, "--date")
16318
+ ];
16319
+ const fn = await getDetail(cfg.client, cfg.id, { signal: cfg.signal });
16320
+ const index = await loadTemplateFields(cfg.client, fn.template, { signal: cfg.signal });
16321
+ const resolved = resolveEvalFilters(index, specs);
16322
+ const valueFieldName = index.fields.find((f) => f.id === fn.value_field)?.name ?? `field ${fn.value_field}`;
16323
+ const aggLabel = `${fn.aggfun === "" ? "SUM" : fn.aggfun.toUpperCase()}(${valueFieldName})`;
16324
+ const body = {
16325
+ agg_type: [agg],
16326
+ global_filters: [],
16327
+ function_filters: [
16328
+ resolved.map((r) => ({
16329
+ values: r.values,
16330
+ name: r.field.name,
16331
+ type: r.field.type,
16332
+ field: r.field.id,
16333
+ function_id: cfg.id,
16334
+ is_excluded: r.isExcluded,
16335
+ show_in_graph: false
16336
+ }))
16337
+ ],
16338
+ table_filters: []
16339
+ };
16340
+ const writeFile6 = cfg.writeFile ?? ((p, d) => fs11.writeFile(p, d));
16341
+ const seams = { signal: cfg.signal, pollIntervalMs: cfg.pollIntervalMs, sleep: cfg.sleep };
16342
+ const exportCsv2 = async (reason) => {
16343
+ const outPath = cfg.outPath;
16344
+ let exp;
16345
+ try {
16346
+ exp = await exportDrilldownCsv(cfg.client, cfg.id, body, seams);
16347
+ } catch (err) {
16348
+ rethrowEvaluatorError(err, cfg.id, "drilldown export");
16349
+ }
16350
+ await writeFile6(outPath, exp.bytes);
16351
+ const table2 = parseCsvRows(new TextDecoder().decode(exp.bytes).replace(/^/, ""));
16352
+ const header = table2[0] ?? [];
16353
+ const col = header.findIndex((h) => h.trim() === valueFieldName);
16354
+ const dataRows = table2.slice(1);
16355
+ const aggValue2 = col < 0 ? null : aggregateColumn(
16356
+ fn.aggfun,
16357
+ dataRows.map((r) => r[col])
16358
+ );
16359
+ if (cfg.asJson) {
16360
+ writeJSONIndented(cfg.out, {
16361
+ function_id: cfg.id,
16362
+ out: outPath,
16363
+ source: "export_to_csv",
16364
+ report_id: exp.reportId,
16365
+ bytes: exp.bytes.length,
16366
+ rows: dataRows.length,
16367
+ columns: header.length,
16368
+ aggregate: { label: aggLabel, value: aggValue2 }
16369
+ });
16370
+ return;
16371
+ }
16372
+ cfg.out.write(
16373
+ `Wrote ${dataRows.length} rows \xD7 ${header.length} columns to ${outPath} (server CSV export${reason}, report ${exp.reportId}, ${exp.bytes.length} bytes).
16374
+ `
16375
+ );
16376
+ cfg.out.write(
16377
+ col < 0 ? `(value field "${valueFieldName}" not found in the CSV header; no aggregate)
16378
+ ` : `${aggLabel} over these rows: ${formatEvalNumber(aggValue2)}
16379
+ `
16380
+ );
16381
+ };
16382
+ if (cfg.forceCsv) {
16383
+ await exportCsv2("");
16384
+ return;
16385
+ }
16386
+ let raw;
16387
+ try {
16388
+ raw = await drilldown(cfg.client, cfg.id, body, seams);
16389
+ } catch (err) {
16390
+ rethrowEvaluatorError(err, cfg.id, "drilldown");
16391
+ }
16392
+ const parsed2 = parseDrilldown(raw);
16393
+ if (parsed2.kind === "too_large") {
16394
+ if (cfg.outPath !== void 0) {
16395
+ await exportCsv2(`; inline answer too large: ${parsed2.rows_count} rows`);
16396
+ return;
16397
+ }
16398
+ if (cfg.asJson) writeJSONIndented(cfg.out, raw);
16399
+ throw new Error(
16400
+ `drill-down for function ${cfg.id} is too large to return inline (${parsed2.rows_count} rows \xD7 ${parsed2.cols_count} columns); re-run with --out <file.csv> to export it as CSV, or narrow it with more --filter flags`
16401
+ );
16402
+ }
16403
+ const { rows, schema } = parsed2;
16404
+ const aggValue = aggregateColumn(
16405
+ fn.aggfun,
16406
+ rows.map((r) => r[valueFieldName])
16407
+ );
16408
+ if (cfg.outPath !== void 0) {
16409
+ await writeFile6(cfg.outPath, drilldownRowsToCsv(rows, schema));
16410
+ if (cfg.asJson) {
16411
+ writeJSONIndented(cfg.out, {
16412
+ function_id: cfg.id,
16413
+ out: cfg.outPath,
16414
+ source: "inline",
16415
+ rows: rows.length,
16416
+ columns: schema.fields.length,
16417
+ aggregate: { label: aggLabel, value: aggValue }
16418
+ });
16419
+ return;
16420
+ }
16421
+ cfg.out.write(
16422
+ `Wrote ${rows.length} rows \xD7 ${schema.fields.length} columns to ${cfg.outPath} (inline drill-down).
16423
+ `
16424
+ );
16425
+ cfg.out.write(`${aggLabel} over these rows: ${formatEvalNumber(aggValue)}
16426
+ `);
16427
+ return;
16428
+ }
16429
+ if (cfg.asJson) {
16430
+ writeJSONIndented(cfg.out, raw);
16431
+ return;
16432
+ }
16433
+ cfg.out.write(
16434
+ `${rows.length} rows \xD7 ${schema.fields.length} columns (${schema.empty_fields.length} empty)
16435
+ `
16436
+ );
16437
+ cfg.out.write(`${aggLabel} over these rows: ${formatEvalNumber(aggValue)}
16438
+ `);
16439
+ if (rows.length === 0) return;
16440
+ const byName = new Map(schema.fields.map((f) => [f.name, f]));
16441
+ let cols;
16442
+ if (cfg.columns !== void 0 && cfg.columns.length > 0) {
16443
+ cols = cfg.columns.map((c) => {
16444
+ if (byName.has(c)) return c;
16445
+ const loose = schema.fields.filter((f) => f.name.toLowerCase() === c.toLowerCase());
16446
+ if (loose.length === 1) return loose[0].name;
16447
+ throw new Error(
16448
+ `unknown --columns entry "${c}"; drill-down columns: ${schema.fields.map((f) => f.name).join(", ")}`
16449
+ );
16450
+ });
16451
+ } else {
16452
+ cols = [.../* @__PURE__ */ new Set([...resolved.map((r) => r.field.name), valueFieldName])].filter(
16453
+ (c) => byName.has(c)
16454
+ );
16455
+ }
16456
+ const shown = cfg.limit === 0 ? rows : rows.slice(0, cfg.limit);
16457
+ cfg.out.write("\n");
16458
+ const table = [{ cells: cols }];
16459
+ for (const r of shown) {
16460
+ table.push({
16461
+ cells: cols.map(
16462
+ (c) => drilldownCell(r[c], (byName.get(c)?.type ?? "").toLowerCase() === "date")
16463
+ )
16464
+ });
16465
+ }
16466
+ writeTable11(cfg.out, table);
16467
+ if (shown.length < rows.length) {
16468
+ cfg.out.write(
16469
+ `(showing ${shown.length} of ${rows.length} rows \u2014 --limit 0 for all, --columns to pick columns, --out <file.csv> for every column)
16470
+ `
16471
+ );
16472
+ }
16473
+ }
15826
16474
  function writeTable11(out, rows) {
15827
16475
  if (rows.length === 0) return;
15828
16476
  const widths = [];
@@ -15898,6 +16546,10 @@ function parseIntFlag(raw, flagName) {
15898
16546
  }
15899
16547
  return n;
15900
16548
  }
16549
+ function collectFlag(value, previous) {
16550
+ return [...previous, value];
16551
+ }
16552
+ var DRILLDOWN_EXPORT_TIMEOUT_MS = 3e5;
15901
16553
  function registerFunctionsCommand(root) {
15902
16554
  const fn = root.command("functions").description("Operations on Datarails template functions");
15903
16555
  const listCmd = fn.command("list").description("List functions on this tenant").option("--json", "Output raw JSON instead of a table", false).action(async (opts) => {
@@ -15932,7 +16584,10 @@ function registerFunctionsCommand(root) {
15932
16584
  });
15933
16585
  addServerFlag(getCmd);
15934
16586
  addAccountFlag(getCmd);
15935
- const createCmd = fn.command("create").description("Create a new function").requiredOption("--name <name>", "Function name (no whitespace; required)").requiredOption("--template <id>", "Template ID (required)").requiredOption("--field <id>", "Value field ID (required)").option("--agg <kind>", "Aggregation: SUM, COUNT, AVG, MIN, MAX (default: SUM)", "SUM").option("--json", "Output raw JSON instead of a table", false).action(
16587
+ const createCmd = fn.command("create").description("Create a new function").requiredOption("--name <name>", "Function name (no whitespace; required)").requiredOption("--template <id>", "Template ID (required)").requiredOption("--field <id>", "Value field ID (required)").option("--agg <kind>", "Aggregation: SUM, COUNT, AVG, MIN, MAX (default: SUM)", "SUM").option(
16588
+ "--date-field <id|name>",
16589
+ "Default aggregation date field (a Date field of the template). Needed for DR.YTD/QTD/MTD/\u2026 and `functions data --agg YTD`; without it a warning is printed"
16590
+ ).option("--json", "Output raw JSON instead of a table", false).action(
15936
16591
  async (opts) => {
15937
16592
  const template = parseIntFlag(opts.template, "--template");
15938
16593
  const valueField = parseIntFlag(opts.field, "--field");
@@ -15944,6 +16599,7 @@ function registerFunctionsCommand(root) {
15944
16599
  template,
15945
16600
  valueField,
15946
16601
  aggOriginal: opts.agg ?? "SUM",
16602
+ dateField: opts.dateField,
15947
16603
  asJson: opts.json === true,
15948
16604
  signal: setup.signal,
15949
16605
  out: process.stdout
@@ -15957,7 +16613,10 @@ function registerFunctionsCommand(root) {
15957
16613
  addAccountFlag(createCmd);
15958
16614
  const updateCmd = fn.command("update").description(
15959
16615
  "Update a function (name-only routes to the rename endpoint; structural changes read-modify-write a full PUT)"
15960
- ).argument("<id>", "function id").option("--name <name>", "New function name (no whitespace)").option("--field <id>", "New value field ID").option("--agg <kind>", "New aggregation: SUM, COUNT, AVG, MIN, MAX").option("--template <id>", "New template ID").option("--json", "Output raw JSON instead of a table", false).action(
16616
+ ).argument("<id>", "function id").option("--name <name>", "New function name (no whitespace)").option("--field <id>", "New value field ID").option("--agg <kind>", "New aggregation: SUM, COUNT, AVG, MIN, MAX").option("--template <id>", "New template ID").option(
16617
+ "--date-field <id|name>",
16618
+ "Set the default aggregation date field (a Date field) used by DR.YTD/QTD/MTD/\u2026"
16619
+ ).option("--json", "Output raw JSON instead of a table", false).action(
15961
16620
  async (idStr, opts) => {
15962
16621
  const id = parseFunctionIdArg(idStr);
15963
16622
  const field = opts.field !== void 0 ? parseIntFlag(opts.field, "--field") : void 0;
@@ -15971,6 +16630,7 @@ function registerFunctionsCommand(root) {
15971
16630
  field,
15972
16631
  aggOriginal: opts.agg,
15973
16632
  template,
16633
+ dateField: opts.dateField,
15974
16634
  asJson: opts.json === true,
15975
16635
  signal: setup.signal,
15976
16636
  out: process.stdout
@@ -16016,6 +16676,106 @@ function registerFunctionsCommand(root) {
16016
16676
  );
16017
16677
  addServerFlag(setFiltersCmd);
16018
16678
  addAccountFlag(setFiltersCmd);
16679
+ const dataCmd = fn.command("data").description(
16680
+ "Evaluate a function the way the Excel add-in does (PUT /api/functions/<id>/data/v2): one value per filter-value combination; dates are sent as Excel serials"
16681
+ ).argument("<id>", "function id").option(
16682
+ "--filter <NAME=V1,V2>",
16683
+ "Dimension filter by field name (or id); repeatable. Several values return one result per value. A value on a Date field is converted like --date",
16684
+ collectFlag,
16685
+ []
16686
+ ).option(
16687
+ "--date <NAME=DATE[,DATE]>",
16688
+ 'Date filter, e.g. "Reporting Date=2025-01-31" (YYYY-MM-DD, M/D/YYYY, an ISO datetime, an Excel serial, or epoch seconds; a bare month is refused); repeatable',
16689
+ collectFlag,
16690
+ []
16691
+ ).option(
16692
+ "--agg <period>",
16693
+ "Period aggregation: DEFAULT, YTD, YTG, QTD, QTG, MTD, MTG",
16694
+ "DEFAULT"
16695
+ ).option("--timeout <seconds>", "Whole-command budget in seconds (default 30)").option("--json", "Print the server body verbatim", false).action(
16696
+ async (idStr, opts) => {
16697
+ const id = parseFunctionIdArg(idStr);
16698
+ validatePeriodAgg(opts.agg ?? "DEFAULT");
16699
+ const setup = await setupCall(opts.server, opts.account, batchSetupOptions(opts.timeout));
16700
+ try {
16701
+ await runFunctionsData({
16702
+ client: setup.client,
16703
+ id,
16704
+ filters: opts.filter,
16705
+ dates: opts.date,
16706
+ aggOriginal: opts.agg ?? "DEFAULT",
16707
+ asJson: opts.json === true,
16708
+ signal: setup.signal,
16709
+ out: process.stdout
16710
+ });
16711
+ } finally {
16712
+ setup.cleanup();
16713
+ }
16714
+ }
16715
+ );
16716
+ addServerFlag(dataCmd);
16717
+ addAccountFlag(dataCmd);
16718
+ const drilldownCmd = fn.command("drilldown").description(
16719
+ "The rows behind a function value (PUT /api/functions/<id>/drilldown/v4); with --out, a result too large to return inline is exported as CSV automatically"
16720
+ ).argument("<id>", "function id").option(
16721
+ "--filter <NAME=V1,V2>",
16722
+ "Dimension filter by field name (or id); repeatable",
16723
+ collectFlag,
16724
+ []
16725
+ ).option(
16726
+ "--exclude <NAME=V1,V2>",
16727
+ "Exclude these values of a field; repeatable",
16728
+ collectFlag,
16729
+ []
16730
+ ).option(
16731
+ "--date <NAME=DATE[,DATE]>",
16732
+ 'Date filter, e.g. "Reporting Date=2025-01-31"; repeatable',
16733
+ collectFlag,
16734
+ []
16735
+ ).option(
16736
+ "--agg <period>",
16737
+ "Period aggregation: DEFAULT, YTD, YTG, QTD, QTG, MTD, MTG",
16738
+ "DEFAULT"
16739
+ ).option(
16740
+ "--columns <names>",
16741
+ "Comma-separated columns to print (default: the filtered fields and the value field)"
16742
+ ).option("--limit <n>", "Rows to print; 0 = all", "20").option(
16743
+ "--out <file.csv>",
16744
+ "Write every row and column to a CSV file; a result over the inline cap is fetched through the server CSV export"
16745
+ ).option("--csv", "Always use the server CSV export (needs --out)", false).option(
16746
+ "--timeout <seconds>",
16747
+ "Whole-command budget in seconds (default 30, or 300 with --out)"
16748
+ ).option("--json", "Print the server body verbatim (with --out: a JSON summary)", false).action(
16749
+ async (idStr, opts) => {
16750
+ const id = parseFunctionIdArg(idStr);
16751
+ validatePeriodAgg(opts.agg ?? "DEFAULT");
16752
+ const limit = parseIntFlag(opts.limit ?? "20", "--limit");
16753
+ const columns = opts.columns === void 0 ? void 0 : opts.columns.split(",").map((c) => c.trim()).filter((c) => c !== "");
16754
+ const setupOpts = opts.timeout !== void 0 ? batchSetupOptions(opts.timeout) : opts.out !== void 0 ? { timeoutMs: DRILLDOWN_EXPORT_TIMEOUT_MS } : {};
16755
+ const setup = await setupCall(opts.server, opts.account, setupOpts);
16756
+ try {
16757
+ await runFunctionsDrilldown({
16758
+ client: setup.client,
16759
+ id,
16760
+ filters: opts.filter,
16761
+ excludes: opts.exclude,
16762
+ dates: opts.date,
16763
+ aggOriginal: opts.agg ?? "DEFAULT",
16764
+ columns,
16765
+ limit,
16766
+ outPath: opts.out,
16767
+ forceCsv: opts.csv === true,
16768
+ asJson: opts.json === true,
16769
+ signal: setup.signal,
16770
+ out: process.stdout
16771
+ });
16772
+ } finally {
16773
+ setup.cleanup();
16774
+ }
16775
+ }
16776
+ );
16777
+ addServerFlag(drilldownCmd);
16778
+ addAccountFlag(drilldownCmd);
16019
16779
  const deleteCmd = fn.command("delete").description("Delete a function by ID (two-step confirm)").argument("<id>", "function id (positive integer)").option(
16020
16780
  "--confirm-token <token>",
16021
16781
  "Token from a prior plan run \u2014 required to actually delete",
@@ -16064,7 +16824,7 @@ async function get7(client, alias, options) {
16064
16824
  }
16065
16825
  return out;
16066
16826
  }
16067
- async function data2(client, alias, params, options) {
16827
+ async function data3(client, alias, params, options) {
16068
16828
  const path11 = `/finance-os/api/semantictables/v1/${rawPathSegmentEscape(alias)}/data`;
16069
16829
  const body = {
16070
16830
  limit: params.rows,
@@ -16126,7 +16886,7 @@ async function runSemanticTablesData(cfg) {
16126
16886
  }
16127
16887
  const parsedFilters = parseAggregateFilters(cfg.filters, cfg.excludes);
16128
16888
  const select = parseSelectFields(cfg.fields);
16129
- const env = await data2(
16889
+ const env = await data3(
16130
16890
  cfg.client,
16131
16891
  cfg.alias,
16132
16892
  {
@@ -17459,7 +18219,7 @@ async function saveMappings(client, documentId, body, options = {}) {
17459
18219
  if (first.status === 200) {
17460
18220
  return toResult(first.text);
17461
18221
  }
17462
- const cacheKey = extractCacheKey(first.text);
18222
+ const cacheKey = extractCacheKey2(first.text);
17463
18223
  if (cacheKey === "") {
17464
18224
  throw new Error(
17465
18225
  `POST ${path11}: HTTP ${first.status} without a cache_key to join: ${truncateBody(first.text)}`
@@ -17487,7 +18247,7 @@ async function postOnce(client, path11, payload, signal) {
17487
18247
  }
17488
18248
  throw new Error(`POST ${path11}: HTTP ${res.status}: ${truncateBody(text2)}`);
17489
18249
  }
17490
- function extractCacheKey(text2) {
18250
+ function extractCacheKey2(text2) {
17491
18251
  try {
17492
18252
  const parsed2 = JSON.parse(text2);
17493
18253
  return typeof parsed2.cache_key === "string" ? parsed2.cache_key : "";
@@ -18269,14 +19029,14 @@ async function runLutData(cfg) {
18269
19029
  if (cfg.lutId <= 0) {
18270
19030
  throw new Error("lut id must be positive");
18271
19031
  }
18272
- const data3 = await getMappedData(cfg.client, cfg.documentId, cfg.lutId, {
19032
+ const data4 = await getMappedData(cfg.client, cfg.documentId, cfg.lutId, {
18273
19033
  signal: cfg.signal
18274
19034
  });
18275
19035
  if (cfg.asJson) {
18276
- writeJSONIndented(cfg.out, data3);
19036
+ writeJSONIndented(cfg.out, data4);
18277
19037
  return;
18278
19038
  }
18279
- const keys2 = Object.keys(data3);
19039
+ const keys2 = Object.keys(data4);
18280
19040
  if (keys2.length === 0) {
18281
19041
  cfg.out.write(`(LUT ${cfg.lutId} on document ${cfg.documentId} is empty or unmapped)
18282
19042
  `);
@@ -18285,7 +19045,7 @@ async function runLutData(cfg) {
18285
19045
  const sortedKeys = [...keys2].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
18286
19046
  const rows = [{ cells: ["KEY", "VALUE"] }];
18287
19047
  for (const k of sortedKeys) {
18288
- rows.push({ cells: [k, renderLutValue(data3[k])] });
19048
+ rows.push({ cells: [k, renderLutValue(data4[k])] });
18289
19049
  }
18290
19050
  writeTable15(cfg.out, rows);
18291
19051
  cfg.out.write(`
@@ -20376,14 +21136,14 @@ function renderDataflow(out, items) {
20376
21136
  ];
20377
21137
  const group = groups[col] ?? [];
20378
21138
  for (const it of group) {
20379
- const data3 = extractData(it.data);
21139
+ const data4 = extractData(it.data);
20380
21140
  rows.push({
20381
21141
  cells: [
20382
21142
  String(it.order),
20383
21143
  it.type,
20384
- String(data3.entityId ?? 0),
20385
- data3.name ?? "",
20386
- data3.statusType ?? "",
21144
+ String(data4.entityId ?? 0),
21145
+ data4.name ?? "",
21146
+ data4.statusType ?? "",
20387
21147
  String(it.connections.length),
20388
21148
  String(it.children.length)
20389
21149
  ]
@@ -20484,7 +21244,7 @@ async function getData2(client, params, options) {
20484
21244
  }
20485
21245
  return out;
20486
21246
  }
20487
- async function drilldown(client, params, options) {
21247
+ async function drilldown2(client, params, options) {
20488
21248
  const path11 = "/finance-os/api/metrics/v1/drill_down";
20489
21249
  const body = {
20490
21250
  metric_ids: params.metric_ids
@@ -21750,8 +22510,8 @@ function detectApiTruncation(env) {
21750
22510
  }
21751
22511
  return false;
21752
22512
  }
21753
- function readCountUniqueRows(data3, field) {
21754
- const rows = Array.isArray(data3) ? data3 : null;
22513
+ function readCountUniqueRows(data4, field) {
22514
+ const rows = Array.isArray(data4) ? data4 : null;
21755
22515
  if (rows === null) return null;
21756
22516
  let best = null;
21757
22517
  for (const row of rows) {
@@ -25303,12 +26063,12 @@ async function runMetricsList(cfg) {
25303
26063
  if (cfg.raw) {
25304
26064
  const env2 = await listRaw2(cfg.client, { signal: cfg.signal });
25305
26065
  envelopeErr(env2.success, env2.error);
25306
- const data3 = decodeHtmlEntitiesDeep(env2.data);
26066
+ const data4 = decodeHtmlEntitiesDeep(env2.data);
25307
26067
  if (cfg.asJson) {
25308
- writeJSONIndented(cfg.out, data3);
26068
+ writeJSONIndented(cfg.out, data4);
25309
26069
  return;
25310
26070
  }
25311
- renderMetricsRawTable(cfg.out, data3);
26071
+ renderMetricsRawTable(cfg.out, data4);
25312
26072
  return;
25313
26073
  }
25314
26074
  const env = await listSemantic(cfg.client, {
@@ -25406,7 +26166,7 @@ async function runMetricsDrilldown(cfg) {
25406
26166
  throw new Error(`--id list capped at 50 (got ${cfg.ids.length})`);
25407
26167
  }
25408
26168
  const dimFilters = parseDimensionFilters(cfg.filters);
25409
- const env = await drilldown(
26169
+ const env = await drilldown2(
25410
26170
  cfg.client,
25411
26171
  {
25412
26172
  metric_ids: cfg.ids,
@@ -25853,83 +26613,13 @@ async function getTemplateFields(client, templateId, options) {
25853
26613
  }
25854
26614
  return result;
25855
26615
  }
25856
- async function putDataOnce(client, path11, body, options) {
25857
- const res = await client.do("PUT", path11, body, options);
25858
- const text2 = await res.text();
25859
- if (res.status === 401 || res.status === 403) {
25860
- throw new Error(`PUT ${path11}: HTTP ${res.status}: ${truncate5(text2, 200)}`);
25861
- }
25862
- if (res.status !== 200 && res.status !== 409 && (res.status < 200 || res.status >= 300)) {
25863
- throw new Error(`PUT ${path11}: HTTP ${res.status}: ${truncate5(text2, 200)}`);
25864
- }
25865
- if (text2 === "" || text2 === '""') {
25866
- return { status: res.status, parsed: null };
25867
- }
25868
- try {
25869
- return { status: res.status, parsed: JSON.parse(text2) };
25870
- } catch (err) {
25871
- const msg = err instanceof Error ? err.message : String(err);
25872
- throw new Error(`PUT ${path11}: decode: ${msg}`);
25873
- }
25874
- }
25875
- var WIDGET_DATA_POLL_INTERVAL_MS = 500;
25876
- var defaultSleep6 = (ms, signal) => new Promise((resolve3, reject) => {
25877
- if (signal?.aborted) {
25878
- reject(signal.reason ?? new Error("aborted"));
25879
- return;
25880
- }
25881
- const onAbort = () => {
25882
- clearTimeout(t);
25883
- reject(signal?.reason ?? new Error("aborted"));
25884
- };
25885
- const t = setTimeout(() => {
25886
- signal?.removeEventListener("abort", onAbort);
25887
- resolve3();
25888
- }, ms);
25889
- signal?.addEventListener("abort", onAbort, { once: true });
25890
- });
25891
26616
  async function putDataWithPolling(client, dashboardId, id, payload, options = {}) {
25892
- const interval = options.pollIntervalMs ?? WIDGET_DATA_POLL_INTERVAL_MS;
25893
- const sleep4 = options.sleep ?? defaultSleep6;
25894
- const path11 = `/api/dashboards/${dashboardId}/widgets/${id}/data`;
25895
- const body = JSON.stringify(payload);
25896
- const first = await putDataOnce(client, path11, body, options);
25897
- if (first.status === 200) {
25898
- return first.parsed;
25899
- }
25900
- const task = first.parsed !== null && typeof first.parsed === "object" ? first.parsed : {};
25901
- const cacheKey = typeof task["cache_key"] === "string" ? task["cache_key"] : "";
25902
- if (cacheKey === "") {
25903
- throw new Error(
25904
- `PUT ${path11}: 409 without cache_key: ${truncate5(JSON.stringify(first.parsed ?? ""), 200)}`
25905
- );
25906
- }
25907
- const pollPath = `${path11}?cache_key=${encodeURIComponent(cacheKey)}`;
25908
- for (; ; ) {
25909
- try {
25910
- await sleep4(interval, options.signal);
25911
- } catch (err) {
25912
- const reason = err instanceof Error ? err.message : String(err);
25913
- throw new Error(`widget data polling timed out: ${reason}`);
25914
- }
25915
- let result;
25916
- try {
25917
- result = await putDataOnce(client, pollPath, body, options);
25918
- } catch (err) {
25919
- if (options.signal?.aborted) {
25920
- const reason = options.signal.reason instanceof Error ? options.signal.reason.message : String(options.signal.reason ?? "aborted");
25921
- throw new Error(`widget data polling timed out: ${reason}`);
25922
- }
25923
- throw err;
25924
- }
25925
- if (result.status === 200) {
25926
- return result.parsed;
25927
- }
25928
- }
25929
- }
25930
- function truncate5(s, n) {
25931
- if (s.length <= n) return s;
25932
- return s.slice(0, n) + "\u2026";
26617
+ return putWithCacheKeyPolling(
26618
+ client,
26619
+ `/api/dashboards/${dashboardId}/widgets/${id}/data`,
26620
+ payload,
26621
+ { ...options, label: "widget data" }
26622
+ );
25933
26623
  }
25934
26624
 
25935
26625
  // src/commands/_shellQuote.ts
@@ -30006,16 +30696,16 @@ function validateCalcFields(entries) {
30006
30696
  if (isLikelyValidCalcFormula(entry.formula)) continue;
30007
30697
  const name = entry.name === "" ? "(unnamed)" : entry.name;
30008
30698
  const trimmed = entry.formula.trim();
30009
- const preview = trimmed.slice(0, 80);
30010
- throw new Error(formatCalcFieldError(name, preview, trimmed));
30699
+ const preview2 = trimmed.slice(0, 80);
30700
+ throw new Error(formatCalcFieldError(name, preview2, trimmed));
30011
30701
  }
30012
30702
  const arityViolations = findArityViolations(entries);
30013
30703
  if (arityViolations.length > 0) {
30014
30704
  throw new Error(formatArityViolation(arityViolations[0]));
30015
30705
  }
30016
30706
  }
30017
- function formatCalcFieldError(name, preview, full) {
30018
- const head = `calculated_fields["${name}"]: formula "${preview}"`;
30707
+ function formatCalcFieldError(name, preview2, full) {
30708
+ const head = `calculated_fields["${name}"]: formula "${preview2}"`;
30019
30709
  if (full === "") {
30020
30710
  return `${head} is empty \u2014 every calc field needs a formula.`;
30021
30711
  }
@@ -34802,7 +35492,7 @@ async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal)
34802
35492
  );
34803
35493
  }
34804
35494
  }
34805
- var defaultSleep7 = (ms, signal) => new Promise((resolve3, reject) => {
35495
+ var defaultSleep6 = (ms, signal) => new Promise((resolve3, reject) => {
34806
35496
  if (signal?.aborted) {
34807
35497
  reject(signal.reason ?? new Error("aborted"));
34808
35498
  return;
@@ -34820,7 +35510,7 @@ var defaultSleep7 = (ms, signal) => new Promise((resolve3, reject) => {
34820
35510
  async function waitForDataQuerySync(cfg) {
34821
35511
  let interval = cfg.pollIntervalMs ?? DQ_WAIT_POLL_INTERVAL_MS;
34822
35512
  const maxInterval = cfg.maxPollIntervalMs ?? DQ_WAIT_POLL_MAX_INTERVAL_MS;
34823
- const sleep4 = cfg.sleep ?? defaultSleep7;
35513
+ const sleep4 = cfg.sleep ?? defaultSleep6;
34824
35514
  const now = cfg.now ?? Date.now;
34825
35515
  const start = now();
34826
35516
  const deadline = start + cfg.timeoutSec * 1e3;
@@ -38581,7 +39271,7 @@ function syncCallTimeoutMs(waitMs) {
38581
39271
  if (waitMs <= 0) return void 0;
38582
39272
  return waitMs + SYNC_SETUP_OVERHEAD_MS;
38583
39273
  }
38584
- var defaultSleep8 = (ms, signal) => new Promise((resolve3, reject) => {
39274
+ var defaultSleep7 = (ms, signal) => new Promise((resolve3, reject) => {
38585
39275
  if (signal?.aborted) {
38586
39276
  reject(signal.reason ?? new Error("aborted"));
38587
39277
  return;
@@ -38601,7 +39291,7 @@ async function runIntegrationsSync(cfg) {
38601
39291
  throw new Error("integration id must be positive");
38602
39292
  }
38603
39293
  const interval = cfg.pollIntervalMs ?? INTEGRATIONS_SYNC_POLL_INTERVAL_MS;
38604
- const sleep4 = cfg.sleep ?? defaultSleep8;
39294
+ const sleep4 = cfg.sleep ?? defaultSleep7;
38605
39295
  const resp = await resync(cfg.client, cfg.id, {
38606
39296
  signal: cfg.signal
38607
39297
  });
@@ -44274,7 +44964,7 @@ async function runReconciliationAccountsUpdateSettings(cfg) {
44274
44964
  const current = await getAccountSettings(cfg.client, {}, { signal: cfg.signal });
44275
44965
  const currentByKey = new Map(current.map((s) => [settingsKey(s), s]));
44276
44966
  const changed = [];
44277
- const preview = [];
44967
+ const preview2 = [];
44278
44968
  for (const want of cfg.settings) {
44279
44969
  const have = currentByKey.get(settingsKey(want));
44280
44970
  let what;
@@ -44286,8 +44976,8 @@ async function runReconciliationAccountsUpdateSettings(cfg) {
44286
44976
  what = diffs.join(", ");
44287
44977
  }
44288
44978
  changed.push(want);
44289
- if (preview.length < 5) {
44290
- preview.push(` ${want.entity ?? ""} / ${want.account_group ?? ""} / ${want.name}: ${what}`);
44979
+ if (preview2.length < 5) {
44980
+ preview2.push(` ${want.entity ?? ""} / ${want.account_group ?? ""} / ${want.name}: ${what}`);
44291
44981
  }
44292
44982
  }
44293
44983
  const unchanged = n - changed.length;
@@ -44314,10 +45004,10 @@ async function runReconciliationAccountsUpdateSettings(cfg) {
44314
45004
  `unchanged: ${unchanged} (already match the tenant \u2014 skipped)`,
44315
45005
  `by frequency: ${[...byFrequency].map(([f, c]) => `${f} ${c}`).join(", ")}`,
44316
45006
  "changes:",
44317
- ...preview
45007
+ ...preview2
44318
45008
  ];
44319
- if (changed.length > preview.length) {
44320
- lines.push(` \u2026 (+${changed.length - preview.length} more)`);
45009
+ if (changed.length > preview2.length) {
45010
+ lines.push(` \u2026 (+${changed.length - preview2.length} more)`);
44321
45011
  }
44322
45012
  return lines;
44323
45013
  },
@@ -44743,16 +45433,16 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
44743
45433
  describe: async () => {
44744
45434
  let signOffsReset = 0;
44745
45435
  let bootstraps = 0;
44746
- const preview = [];
45436
+ const preview2 = [];
44747
45437
  for (const id of accountIds) {
44748
45438
  const account = byId.get(id);
44749
45439
  if (account.members.length === 0) bootstraps += 1;
44750
45440
  for (const r of changesByAccount.get(id)) {
44751
45441
  const current = account.members.find((m) => m.type === r.row.type);
44752
45442
  if (current?.is_sign_off === true) signOffsReset += 1;
44753
- if (preview.length < 5) {
45443
+ if (preview2.length < 5) {
44754
45444
  const from = current === void 0 ? "(no slot)" : `${current.name} (user ${current.user_id})`;
44755
- preview.push(
45445
+ preview2.push(
44756
45446
  ` ${account.account_name} (${id}) ${r.row.type}: ${from} \u2192 ${r.name} (user ${r.row.userId})`
44757
45447
  );
44758
45448
  }
@@ -44769,8 +45459,8 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
44769
45459
  `no member slots yet: ${bootstraps} account${plural(bootstraps)} \u2014 both slots are initialized, the unlisted role as Unassigned`
44770
45460
  );
44771
45461
  }
44772
- lines.push("changes:", ...preview);
44773
- if (changeCount > preview.length) lines.push(` \u2026 (+${changeCount - preview.length} more)`);
45462
+ lines.push("changes:", ...preview2);
45463
+ if (changeCount > preview2.length) lines.push(` \u2026 (+${changeCount - preview2.length} more)`);
44774
45464
  return lines;
44775
45465
  },
44776
45466
  execute: async () => {
@@ -44868,13 +45558,13 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
44868
45558
  confirmCmd: cfg.confirmCmd,
44869
45559
  describe: async () => {
44870
45560
  let signOffsReset = 0;
44871
- const preview = [];
45561
+ const preview2 = [];
44872
45562
  for (const id of accountIds) {
44873
45563
  const account = byId.get(id);
44874
45564
  for (const { row, current } of clearsByAccount.get(id)) {
44875
45565
  if (current.is_sign_off) signOffsReset += 1;
44876
- if (preview.length < 5) {
44877
- preview.push(
45566
+ if (preview2.length < 5) {
45567
+ preview2.push(
44878
45568
  ` ${account.account_name} (${id}) ${row.type}: ${current.name} (user ${current.user_id}) \u2192 Unassigned`
44879
45569
  );
44880
45570
  }
@@ -44886,9 +45576,9 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
44886
45576
  `unchanged: ${unchanged.length} (slot already Unassigned, or the account has no member slots \u2014 skipped)`,
44887
45577
  `sign-offs reset: ${signOffsReset}`,
44888
45578
  "changes:",
44889
- ...preview
45579
+ ...preview2
44890
45580
  ];
44891
- if (clearCount > preview.length) lines.push(` \u2026 (+${clearCount - preview.length} more)`);
45581
+ if (clearCount > preview2.length) lines.push(` \u2026 (+${clearCount - preview2.length} more)`);
44892
45582
  return lines;
44893
45583
  },
44894
45584
  execute: async () => {
@@ -46090,9 +46780,9 @@ function sanitizeProse(input, maxLen) {
46090
46780
  }
46091
46781
  return s;
46092
46782
  }
46093
- function applyProse(data3, overlay, warn2 = () => {
46783
+ function applyProse(data4, overlay, warn2 = () => {
46094
46784
  }) {
46095
- const out = JSON.parse(JSON.stringify(data3));
46785
+ const out = JSON.parse(JSON.stringify(data4));
46096
46786
  const rowProse = overlay.rowProse ?? {};
46097
46787
  const byId = new Map(
46098
46788
  out.rows.filter((r) => "id" in r).map((r) => [r.id, r])
@@ -46119,11 +46809,11 @@ function applyProse(data3, overlay, warn2 = () => {
46119
46809
  import { readFile as readFile6 } from "fs/promises";
46120
46810
  import { join as join12 } from "path";
46121
46811
  var MARKER = "<!--__DATA_BLOCK__-->";
46122
- function renderReportHtml(data3, templateHtml) {
46812
+ function renderReportHtml(data4, templateHtml) {
46123
46813
  if (!templateHtml.includes(MARKER)) {
46124
46814
  throw new Error(`report template is missing the ${MARKER} marker`);
46125
46815
  }
46126
- const json = JSON.stringify(data3).replace(/</g, "\\u003c");
46816
+ const json = JSON.stringify(data4).replace(/</g, "\\u003c");
46127
46817
  const block = `<script>window.__DATA__ = ${json};</script>`;
46128
46818
  return templateHtml.replace(MARKER, block);
46129
46819
  }
@@ -46148,25 +46838,25 @@ function validateReportType(raw) {
46148
46838
  throw new Error("--report-type must be 'P&L' or 'BS'");
46149
46839
  }
46150
46840
  async function runSessionCreate(cfg) {
46151
- const data3 = await createSession(cfg.client, cfg.baseUrl, { signal: cfg.signal });
46152
- cfg.out.write(`${data3.session_id ?? ""}
46841
+ const data4 = await createSession(cfg.client, cfg.baseUrl, { signal: cfg.signal });
46842
+ cfg.out.write(`${data4.session_id ?? ""}
46153
46843
  `);
46154
46844
  }
46155
46845
  async function runSessionGet(cfg) {
46156
- const data3 = await getSession(cfg.client, cfg.baseUrl, cfg.sessionId, {
46846
+ const data4 = await getSession(cfg.client, cfg.baseUrl, cfg.sessionId, {
46157
46847
  signal: cfg.signal
46158
46848
  });
46159
46849
  if (cfg.asJson) {
46160
- writeJSONIndented(cfg.out, data3);
46850
+ writeJSONIndented(cfg.out, data4);
46161
46851
  return;
46162
46852
  }
46163
- const config = data3.config ?? {};
46853
+ const config = data4.config ?? {};
46164
46854
  const rows = [
46165
- ["session_id", String(data3.session_id ?? "-")],
46166
- ["created_at", String(data3.created_at ?? "-")],
46167
- ["reconcile_status", String(data3.reconcile_status ?? "-")],
46168
- ["investigation_status", String(data3.investigation_status ?? "-")],
46169
- ["reconcile_error", String(data3.reconcile_error ?? "-")],
46855
+ ["session_id", String(data4.session_id ?? "-")],
46856
+ ["created_at", String(data4.created_at ?? "-")],
46857
+ ["reconcile_status", String(data4.reconcile_status ?? "-")],
46858
+ ["investigation_status", String(data4.investigation_status ?? "-")],
46859
+ ["reconcile_error", String(data4.reconcile_error ?? "-")],
46170
46860
  ["report_type", String(config.report_type ?? "-")],
46171
46861
  ["period", String(config.period ?? "-")]
46172
46862
  ];
@@ -46190,12 +46880,12 @@ async function runSessionDelete(cfg) {
46190
46880
  server: cfg.serverFlag,
46191
46881
  confirmCmd: `dr validate onboarding session delete ${cfg.sessionId} --server ${cfg.serverFlag}`,
46192
46882
  describe: async () => {
46193
- const data3 = await getSession(cfg.client, cfg.baseUrl, cfg.sessionId, {
46883
+ const data4 = await getSession(cfg.client, cfg.baseUrl, cfg.sessionId, {
46194
46884
  signal: cfg.signal
46195
46885
  });
46196
46886
  return [
46197
- `created_at: ${String(data3.created_at ?? "-")}`,
46198
- `reconcile_status: ${String(data3.reconcile_status ?? "-")}`
46887
+ `created_at: ${String(data4.created_at ?? "-")}`,
46888
+ `reconcile_status: ${String(data4.reconcile_status ?? "-")}`
46199
46889
  ];
46200
46890
  },
46201
46891
  execute: async () => {
@@ -46237,14 +46927,14 @@ async function runUpload(cfg) {
46237
46927
  }
46238
46928
  }
46239
46929
  async function runReconcile(cfg) {
46240
- const data3 = await startReconcile(
46930
+ const data4 = await startReconcile(
46241
46931
  cfg.client,
46242
46932
  cfg.baseUrl,
46243
46933
  cfg.sessionId,
46244
46934
  { report_type: cfg.reportType, period: cfg.period, csm_notes: cfg.csmNotes },
46245
46935
  { signal: cfg.signal }
46246
46936
  );
46247
- if (data3.status === "already_running") {
46937
+ if (data4.status === "already_running") {
46248
46938
  cfg.out.write(`reconcile already running for session ${cfg.sessionId}
46249
46939
  `);
46250
46940
  } else {
@@ -46255,12 +46945,12 @@ async function runReconcile(cfg) {
46255
46945
  `);
46256
46946
  }
46257
46947
  async function runStatus(cfg) {
46258
- const data3 = await getSession(cfg.client, cfg.baseUrl, cfg.sessionId, {
46948
+ const data4 = await getSession(cfg.client, cfg.baseUrl, cfg.sessionId, {
46259
46949
  signal: cfg.signal
46260
46950
  });
46261
- const recon = data3.reconcile_status ?? "pending";
46262
- const invest = data3.investigation_status ?? "pending";
46263
- const err = data3.reconcile_error ?? null;
46951
+ const recon = data4.reconcile_status ?? "pending";
46952
+ const invest = data4.investigation_status ?? "pending";
46953
+ const err = data4.reconcile_error ?? null;
46264
46954
  const summary = summaryStatus(recon, invest);
46265
46955
  if (cfg.asJson) {
46266
46956
  writeJSONIndented(cfg.out, {
@@ -46274,13 +46964,13 @@ async function runStatus(cfg) {
46274
46964
  cfg.out.write(`reconcile: ${recon} | investigation: ${invest}
46275
46965
  `);
46276
46966
  if (err !== null && err !== "") {
46277
- const preview = err.length <= ERROR_PREVIEW_MAX ? err : `${err.slice(0, ERROR_PREVIEW_MAX)}...`;
46278
- cfg.out.write(`error: ${preview}
46967
+ const preview2 = err.length <= ERROR_PREVIEW_MAX ? err : `${err.slice(0, ERROR_PREVIEW_MAX)}...`;
46968
+ cfg.out.write(`error: ${preview2}
46279
46969
  `);
46280
46970
  }
46281
46971
  }
46282
46972
  async function runResult(cfg) {
46283
- const data3 = await getResult(
46973
+ const data4 = await getResult(
46284
46974
  cfg.client,
46285
46975
  cfg.baseUrl,
46286
46976
  cfg.sessionId,
@@ -46288,21 +46978,21 @@ async function runResult(cfg) {
46288
46978
  { signal: cfg.signal }
46289
46979
  );
46290
46980
  if (cfg.asJson) {
46291
- writeJSONIndented(cfg.out, data3);
46981
+ writeJSONIndented(cfg.out, data4);
46292
46982
  return;
46293
46983
  }
46294
46984
  if (cfg.reconcile) {
46295
- writeJSONIndented(cfg.out, data3);
46985
+ writeJSONIndented(cfg.out, data4);
46296
46986
  return;
46297
46987
  }
46298
- const report = data3.report;
46988
+ const report = data4.report;
46299
46989
  const markdown = report !== null && report !== void 0 && typeof report === "object" ? report.markdown : void 0;
46300
46990
  if (typeof markdown === "string" && markdown !== "") {
46301
46991
  cfg.out.write(`${markdown}
46302
46992
  `);
46303
46993
  return;
46304
46994
  }
46305
- writeJSONIndented(cfg.out, data3);
46995
+ writeJSONIndented(cfg.out, data4);
46306
46996
  }
46307
46997
  async function runReport(cfg) {
46308
46998
  let result;
@@ -46375,24 +47065,24 @@ async function runReport(cfg) {
46375
47065
  tenant: cfg.org || null,
46376
47066
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
46377
47067
  };
46378
- let data3 = buildReportData(result, meta, balanceCheck);
47068
+ let data4 = buildReportData(result, meta, balanceCheck);
46379
47069
  if (cfg.emitData) {
46380
47070
  if (cfg.outPath) {
46381
- await fs19.writeFile(cfg.outPath, JSON.stringify(data3, null, 2), "utf8");
47071
+ await fs19.writeFile(cfg.outPath, JSON.stringify(data4, null, 2), "utf8");
46382
47072
  cfg.out.write(`wrote data block to ${cfg.outPath}
46383
47073
  `);
46384
47074
  } else {
46385
- writeJSONIndented(cfg.out, data3);
47075
+ writeJSONIndented(cfg.out, data4);
46386
47076
  }
46387
47077
  return;
46388
47078
  }
46389
47079
  if (cfg.prosePath) {
46390
47080
  const overlay = JSON.parse(await fs19.readFile(cfg.prosePath, "utf8"));
46391
- data3 = applyProse(data3, overlay, (m) => process.stderr.write(`${m}
47081
+ data4 = applyProse(data4, overlay, (m) => process.stderr.write(`${m}
46392
47082
  `));
46393
47083
  }
46394
47084
  const template = await readReportTemplate();
46395
- const html = renderReportHtml(data3, template);
47085
+ const html = renderReportHtml(data4, template);
46396
47086
  const outPath = cfg.outPath || `./${cfg.sessionId}-report.html`;
46397
47087
  await fs19.writeFile(outPath, html, "utf8");
46398
47088
  cfg.out.write(`${outPath}
@@ -49757,9 +50447,9 @@ async function runMecMemberGrantBulk(cfg) {
49757
50447
  signal: cfg.signal
49758
50448
  });
49759
50449
  const wirePayload = { ...cfg.payload, role: role.id };
49760
- let preview = null;
50450
+ let preview2 = null;
49761
50451
  try {
49762
- preview = await withMec(
50452
+ preview2 = await withMec(
49763
50453
  () => grantUsersBulkCheck(cfg.client, wirePayload, { signal: cfg.signal })
49764
50454
  );
49765
50455
  } catch {
@@ -49778,8 +50468,8 @@ async function runMecMemberGrantBulk(cfg) {
49778
50468
  "",
49779
50469
  "WARNING: this cascades table permissions for linked LUT templates."
49780
50470
  ];
49781
- if (preview !== null) {
49782
- lines.push(`pre-check response: ${JSON.stringify(preview).slice(0, 200)}`);
50471
+ if (preview2 !== null) {
50472
+ lines.push(`pre-check response: ${JSON.stringify(preview2).slice(0, 200)}`);
49783
50473
  }
49784
50474
  return lines;
49785
50475
  },
@@ -52603,9 +53293,9 @@ async function runMemberGrantBulk(cfg) {
52603
53293
  signal: cfg.signal
52604
53294
  });
52605
53295
  const wirePayload = { ...cfg.payload, role: role.id };
52606
- let preview = null;
53296
+ let preview2 = null;
52607
53297
  try {
52608
- preview = await withWorkflowV2(
53298
+ preview2 = await withWorkflowV2(
52609
53299
  () => grantUsersBulkCheck(cfg.client, wirePayload, { signal: cfg.signal })
52610
53300
  );
52611
53301
  } catch {
@@ -52624,8 +53314,8 @@ async function runMemberGrantBulk(cfg) {
52624
53314
  "",
52625
53315
  "WARNING: this cascades table permissions for linked LUT templates."
52626
53316
  ];
52627
- if (preview !== null) {
52628
- lines.push(`pre-check response: ${JSON.stringify(preview).slice(0, 200)}`);
53317
+ if (preview2 !== null) {
53318
+ lines.push(`pre-check response: ${JSON.stringify(preview2).slice(0, 200)}`);
52629
53319
  }
52630
53320
  return lines;
52631
53321
  },
@@ -54225,12 +54915,12 @@ function parseCsvImportText(text2) {
54225
54915
  `--file: unexpected CSV header \u2014 expected "${EXPECTED_HEADER5.join(",")}", got "${header.slice(0, 3).join(",")}"`
54226
54916
  );
54227
54917
  }
54228
- const data3 = rows.slice(1).filter((r) => r.some((c) => c.trim() !== ""));
54229
- if (data3.length === 0) {
54918
+ const data4 = rows.slice(1).filter((r) => r.some((c) => c.trim() !== ""));
54919
+ if (data4.length === 0) {
54230
54920
  throw new Error("--file: CSV has no data rows (header only)");
54231
54921
  }
54232
54922
  const out = [];
54233
- data3.forEach((row, idx) => {
54923
+ data4.forEach((row, idx) => {
54234
54924
  const lineNo = idx + 2;
54235
54925
  const detailedRule = (row[0] ?? "").trim();
54236
54926
  const category = (row[2] ?? "").trim();
@@ -58992,9 +59682,9 @@ async function runCpList(cfg) {
58992
59682
  }
58993
59683
  async function runCpGet(cfg) {
58994
59684
  const bucket = await getBucket(cfg.client, cfg.id, { signal: cfg.signal });
58995
- const data3 = cfg.noCpData ? void 0 : await getCpData(cfg.client, cfg.id, { signal: cfg.signal });
59685
+ const data4 = cfg.noCpData ? void 0 : await getCpData(cfg.client, cfg.id, { signal: cfg.signal });
58996
59686
  if (cfg.asJson) {
58997
- writeJSONIndented(cfg.out, { bucket, cp_data: data3 ?? null });
59687
+ writeJSONIndented(cfg.out, { bucket, cp_data: data4 ?? null });
58998
59688
  return;
58999
59689
  }
59000
59690
  cfg.out.write(`id: ${bucket.id}
@@ -59007,11 +59697,11 @@ async function runCpGet(cfg) {
59007
59697
  `);
59008
59698
  cfg.out.write(`is_demo: ${bucket.is_demo}
59009
59699
  `);
59010
- if (data3 !== void 0) {
59011
- cfg.out.write(`cp_data.owner_user_id: ${data3.owner_user_id}
59700
+ if (data4 !== void 0) {
59701
+ cfg.out.write(`cp_data.owner_user_id: ${data4.owner_user_id}
59012
59702
  `);
59013
59703
  cfg.out.write(
59014
- `cp_data.level_dimension_tags: ${JSON.stringify(data3.level_dimension_tags)}
59704
+ `cp_data.level_dimension_tags: ${JSON.stringify(data4.level_dimension_tags)}
59015
59705
  `
59016
59706
  );
59017
59707
  }
@@ -60098,7 +60788,7 @@ var EXTRACT_POLL_TIMEOUT_MS = 10 * 60 * 1e3;
60098
60788
  function isTerminalAiStatus(status2) {
60099
60789
  return status2 === "PROCESSED" || status2 === "FAILED";
60100
60790
  }
60101
- async function defaultSleep9(ms, signal) {
60791
+ async function defaultSleep8(ms, signal) {
60102
60792
  if (signal?.aborted === true) return;
60103
60793
  await new Promise((resolve3) => {
60104
60794
  const timer = setTimeout(resolve3, ms);
@@ -60115,7 +60805,7 @@ async function defaultSleep9(ms, signal) {
60115
60805
  async function pollExtractionStatus(client, contractFileIds, options = {}) {
60116
60806
  const interval = options.pollIntervalMs ?? EXTRACT_POLL_INTERVAL_MS;
60117
60807
  const timeout = options.timeoutMs ?? EXTRACT_POLL_TIMEOUT_MS;
60118
- const sleep4 = options.sleep ?? defaultSleep9;
60808
+ const sleep4 = options.sleep ?? defaultSleep8;
60119
60809
  const now = options.now ?? Date.now;
60120
60810
  const resolved = /* @__PURE__ */ new Map();
60121
60811
  if (contractFileIds.length === 0) return { resolved, pending: [] };
@@ -62741,16 +63431,16 @@ function describeBulkCreate(draft, contracts, batchCount, serverKey) {
62741
63431
  }
62742
63432
  if (d.category !== void 0) out.push(`default category: ${d.category}`);
62743
63433
  }
62744
- const preview = contracts.slice(0, DESCRIBE_PREVIEW_ROWS3);
62745
- if (preview.length > 0) {
63434
+ const preview2 = contracts.slice(0, DESCRIBE_PREVIEW_ROWS3);
63435
+ if (preview2.length > 0) {
62746
63436
  out.push("preview:");
62747
- for (let i = 0; i < preview.length; i++) {
62748
- const c = preview[i];
63437
+ for (let i = 0; i < preview2.length; i++) {
63438
+ const c = preview2[i];
62749
63439
  const vendor = c.vendor_name !== void 0 && c.vendor_name !== null && c.vendor_name !== "" ? c.vendor_name : c.vendor_id !== void 0 && c.vendor_id !== null ? c.vendor_id : "?";
62750
63440
  out.push(` [${i}] ${vendor} \u2014 ${c.value} ${c.currency}`);
62751
63441
  }
62752
- if (contracts.length > preview.length) {
62753
- out.push(` \u2026 (${contracts.length - preview.length} more)`);
63442
+ if (contracts.length > preview2.length) {
63443
+ out.push(` \u2026 (${contracts.length - preview2.length} more)`);
62754
63444
  }
62755
63445
  }
62756
63446
  return out;