@datarails-ai/dr-cli 1.6.122 → 1.6.124

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.122",
11
+ version: "1.6.124",
12
12
  description: "Datarails CLI",
13
13
  type: "module",
14
14
  engines: {
@@ -69,7 +69,7 @@ var package_default = {
69
69
  };
70
70
 
71
71
  // src/buildInfo.ts
72
- var BUILD_TIME = "2026-10-12T10:51:59.680Z";
72
+ var BUILD_TIME = "2026-10-12T11:50:53.998Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -891,7 +891,7 @@ async function exchangeApiKey(host, apiKey, options) {
891
891
  }
892
892
  function describeApiKeyExchangeFailure(err, identity2) {
893
893
  if (err.httpStatus === 401) {
894
- return `the server refused the API key for ${identity2} \u2014 it may be revoked or expired, API keys may not be enabled for your organization (ask an admin), or the server may be temporarily unable to evaluate that.`;
894
+ return `the server refused the API key for ${identity2} \u2014 it may be revoked or expired, API keys may not be enabled for your organization (ask an admin), or the server may be temporarily unable to evaluate that. If the key was revoked or expired, start a new session with a new key; retrying with this one will not help.`;
895
895
  }
896
896
  if (err.httpStatus === 404) {
897
897
  return `${identity2}: this server does not support API keys (the exchange endpoint answered 404).`;
@@ -4474,13 +4474,14 @@ function isBatchTimeoutClamped(callCount) {
4474
4474
  const n = Number.isFinite(callCount) ? Math.max(1, Math.floor(callCount)) : 1;
4475
4475
  return DEFAULT_CALL_TIMEOUT_MS + n * BATCH_PER_TARGET_TIMEOUT_MS > BATCH_MAX_TIMEOUT_MS;
4476
4476
  }
4477
- function extendForBatch(extend, callCount) {
4477
+ var TIMEOUT_FLAG_REMEDY = "re-run it with --timeout <seconds> (a floor, so a larger value only ever widens the budget)";
4478
+ function extendForBatch(extend, callCount, remedy = TIMEOUT_FLAG_REMEDY) {
4478
4479
  extend?.(batchTimeoutMs(callCount));
4479
4480
  if (!isBatchTimeoutClamped(callCount)) return;
4480
4481
  const budgetS = BATCH_MAX_TIMEOUT_MS / 1e3;
4481
4482
  const perCallMs = Math.floor(BATCH_MAX_TIMEOUT_MS / Math.floor(callCount));
4482
4483
  process.stderr.write(
4483
- `warning: ${callCount} calls exceed the ${budgetS}s ceiling on the auto-sized budget \u2014 about ${perCallMs}ms per call. If the run aborts part-way, re-run it with --timeout <seconds> (a floor, so a larger value only ever widens the budget).
4484
+ `warning: ${callCount} calls exceed the ${budgetS}s ceiling on the auto-sized budget \u2014 about ${perCallMs}ms per call. If the run aborts part-way, ${remedy}.
4484
4485
  `
4485
4486
  );
4486
4487
  }
@@ -5419,8 +5420,8 @@ async function openBrowser(url) {
5419
5420
  async function readLine(stdin) {
5420
5421
  return new Promise((resolve3, reject) => {
5421
5422
  let data4 = "";
5422
- const onData = (chunk) => {
5423
- data4 += chunk.toString();
5423
+ const onData = (chunk2) => {
5424
+ data4 += chunk2.toString();
5424
5425
  const i = data4.indexOf("\n");
5425
5426
  if (i >= 0) {
5426
5427
  cleanup();
@@ -5512,8 +5513,8 @@ async function readHiddenLineFromTty(prompt, stdin, out) {
5512
5513
  cleanup();
5513
5514
  reject(err);
5514
5515
  };
5515
- const onData = (chunk) => {
5516
- const s = typeof chunk === "string" ? chunk : chunk.toString("utf8");
5516
+ const onData = (chunk2) => {
5517
+ const s = typeof chunk2 === "string" ? chunk2 : chunk2.toString("utf8");
5517
5518
  for (const ch of s) {
5518
5519
  if (ch === "\r" || ch === "\n") {
5519
5520
  cleanup();
@@ -5561,8 +5562,8 @@ async function readFirstLineFromPipe(stdin) {
5561
5562
  cleanup();
5562
5563
  resolve3(line.endsWith("\r") ? line.slice(0, -1) : line);
5563
5564
  };
5564
- const onData = (chunk) => {
5565
- data4 += typeof chunk === "string" ? chunk : chunk.toString("utf8");
5565
+ const onData = (chunk2) => {
5566
+ data4 += typeof chunk2 === "string" ? chunk2 : chunk2.toString("utf8");
5566
5567
  const i = data4.indexOf("\n");
5567
5568
  if (i >= 0) finish(data4.slice(0, i));
5568
5569
  };
@@ -6371,15 +6372,15 @@ async function commandDelete(client, id, options) {
6371
6372
  function strOr(s, fallback) {
6372
6373
  return s === "" ? fallback : s;
6373
6374
  }
6374
- function parsePositiveIntsArgs(args) {
6375
+ function parsePositiveIntsArgs(args, label = "device id") {
6375
6376
  const out = [];
6376
6377
  for (const a of args) {
6377
6378
  const n = Number(a);
6378
6379
  if (!Number.isInteger(n) || String(n) !== a) {
6379
- throw new Error(`invalid device id "${a}": not a number`);
6380
+ throw new Error(`invalid ${label} "${a}": not a number`);
6380
6381
  }
6381
6382
  if (n <= 0) {
6382
- throw new Error(`invalid device id "${a}": must be positive`);
6383
+ throw new Error(`invalid ${label} "${a}": must be positive`);
6383
6384
  }
6384
6385
  out.push(n);
6385
6386
  }
@@ -6392,6 +6393,16 @@ function parsePositiveIntArg(raw, label) {
6392
6393
  }
6393
6394
  return n;
6394
6395
  }
6396
+ function chunk(items, size) {
6397
+ if (!Number.isInteger(size) || size <= 0) {
6398
+ throw new Error("chunk: size must be a positive integer");
6399
+ }
6400
+ const out = [];
6401
+ for (let i = 0; i < items.length; i += size) {
6402
+ out.push(items.slice(i, i + size));
6403
+ }
6404
+ return out;
6405
+ }
6395
6406
 
6396
6407
  // src/commands/_retry.ts
6397
6408
  var AMBIGUOUS_STATUSES = /* @__PURE__ */ new Set([502, 504, 524]);
@@ -19121,6 +19132,22 @@ async function updateAutoCalculateSettings(client, docId, body, reqOptions) {
19121
19132
  async function downloadVersion(client, versionId, reqOptions) {
19122
19133
  return client.do("GET", `/api/documents/versions/${versionId}/file`, void 0, reqOptions);
19123
19134
  }
19135
+ var KIND_EXPLANATION_PATH = "/api/workspace/documents/kind/explanation";
19136
+ var KIND_SUMMARY_PATH = "/api/workspace/documents/kind/summary";
19137
+ var KIND_EXPLAIN_MAX_IDS = 200;
19138
+ var KIND_EXPLAIN_MAX_IDS_WITH_HISTORY = 20;
19139
+ function kindExplanationPath(query) {
19140
+ let path12 = `${KIND_EXPLANATION_PATH}?ids=${query.ids.join(",")}`;
19141
+ if (query.asOf !== void 0) path12 += `&as_of=${encodeURIComponent(query.asOf)}`;
19142
+ if (query.history === true) path12 += "&history=true";
19143
+ return path12;
19144
+ }
19145
+ async function getKindExplanation(client, query, reqOptions) {
19146
+ return client.do("GET", kindExplanationPath(query), void 0, reqOptions);
19147
+ }
19148
+ async function getKindSummary(client, reqOptions) {
19149
+ return client.do("GET", KIND_SUMMARY_PATH, void 0, reqOptions);
19150
+ }
19124
19151
  async function listBucketTemplates(client, bucketId, reqOptions) {
19125
19152
  const out = await client.getJSON(
19126
19153
  `/api/buckets/${bucketId}/templates`,
@@ -35462,6 +35489,678 @@ function registerFileboxAdminCommands(fb, setupCall2, addServerFlag2, addAccount
35462
35489
  addAccountFlag2(vrSetCmd);
35463
35490
  }
35464
35491
 
35492
+ // src/commands/_reconciliationDate.ts
35493
+ function validateDateString(orig, flagName) {
35494
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(orig)) {
35495
+ throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD)`);
35496
+ }
35497
+ const parts = orig.split("-").map(Number);
35498
+ const [year, month1, day] = parts;
35499
+ const probe = new Date(Date.UTC(year, month1 - 1, day));
35500
+ if (probe.getUTCFullYear() !== year || probe.getUTCMonth() + 1 !== month1 || probe.getUTCDate() !== day) {
35501
+ throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD)`);
35502
+ }
35503
+ return { year, month1, day };
35504
+ }
35505
+ function parseDateFlag(orig, flagName) {
35506
+ let normalized = orig;
35507
+ if (/^\d{4}-\d{2}$/.test(orig)) {
35508
+ normalized = `${orig}-01`;
35509
+ }
35510
+ let year;
35511
+ let month1;
35512
+ try {
35513
+ ({ year, month1 } = validateDateString(normalized, flagName));
35514
+ } catch {
35515
+ throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD or YYYY-MM)`);
35516
+ }
35517
+ return Math.floor(Date.UTC(year, month1, 0) / 1e3);
35518
+ }
35519
+ function parseDateFlagEpoch(orig, flagName) {
35520
+ let normalized = orig;
35521
+ if (/^\d{4}-\d{2}$/.test(orig)) {
35522
+ normalized = `${orig}-01`;
35523
+ }
35524
+ let year;
35525
+ let month1;
35526
+ try {
35527
+ ({ year, month1 } = validateDateString(normalized, flagName));
35528
+ } catch {
35529
+ throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD or YYYY-MM)`);
35530
+ }
35531
+ return Math.floor(Date.UTC(year, month1 - 1, 1, 12) / 1e3);
35532
+ }
35533
+ function parseDateFlagDay(orig, flagName) {
35534
+ const { year, month1, day } = validateDateString(orig, flagName);
35535
+ return Math.floor(Date.UTC(year, month1 - 1, day) / 1e3);
35536
+ }
35537
+
35538
+ // src/commands/filebox_kind.ts
35539
+ function parseKindExplainIds(args) {
35540
+ const parts = args.flatMap((arg) => arg.split(","));
35541
+ return [...new Set(parsePositiveIntsArgs(parts, "filebox id"))];
35542
+ }
35543
+ function parseKindAsOf(raw) {
35544
+ if (raw === void 0) return void 0;
35545
+ parseDateFlagDay(raw, "--as-of");
35546
+ return raw;
35547
+ }
35548
+ var SUPPORT_ONLY_CODES = /* @__PURE__ */ new Set([
35549
+ "kind_explanation_support_only",
35550
+ "kind_summary_support_only"
35551
+ ]);
35552
+ function jsonObject(text2) {
35553
+ try {
35554
+ const parsed2 = JSON.parse(text2);
35555
+ if (typeof parsed2 === "object" && parsed2 !== null && !Array.isArray(parsed2)) {
35556
+ return parsed2;
35557
+ }
35558
+ } catch {
35559
+ }
35560
+ return void 0;
35561
+ }
35562
+ function describeKindErrorBody(text2) {
35563
+ const raw = truncate3(text2.trim(), 200);
35564
+ const body = jsonObject(text2);
35565
+ if (body === void 0) return raw;
35566
+ for (const key of ["message", "detail"]) {
35567
+ const value = body[key];
35568
+ if (typeof value === "string" && value !== "") return value;
35569
+ }
35570
+ const parts = Object.entries(body).map(([field, value]) => {
35571
+ const messages = (Array.isArray(value) ? value : [value]).map(
35572
+ (m) => typeof m === "string" ? m : JSON.stringify(m)
35573
+ );
35574
+ return field === "non_field_errors" ? messages.join("; ") : `${field}: ${messages.join("; ")}`;
35575
+ });
35576
+ return parts.length > 0 ? parts.join("; ") : raw;
35577
+ }
35578
+ function kindReadError(status2, text2, ctx) {
35579
+ const server = ctx.client.serverKey;
35580
+ const body = jsonObject(text2);
35581
+ const code = typeof body?.["code"] === "string" ? body["code"] : void 0;
35582
+ if (status2 === 405 && code !== void 0 && SUPPORT_ONLY_CODES.has(code)) {
35583
+ const scope = ctx.command === "kind-explain" ? "only the support seat of the fileboxes' own organization gets an answer" : "it answers the support seat of an organization, for that organization";
35584
+ return new Error(
35585
+ `${ctx.command}: the server refused ${ctx.client.email} (HTTP 405 ${code}: ${describeKindErrorBody(text2)}) \u2014 ${scope}. Log in as that organization's support seat, then run the command as it:
35586
+ dr login-as <support seat email> --server ${server}
35587
+ dr filebox ${ctx.command} \u2026 --server ${server} --account <support seat email>`
35588
+ );
35589
+ }
35590
+ if (status2 === 404) {
35591
+ return new Error(
35592
+ `GET ${ctx.route}: HTTP 404 \u2014 ${server} most likely does not have the filebox-type endpoints yet. They ship with dr-datarails-server PR #8010 (DR-53666); until that change is deployed on ${server}, ${ctx.command} has nothing to ask.`
35593
+ );
35594
+ }
35595
+ if (status2 === 400) {
35596
+ return new Error(
35597
+ `${ctx.command}: the server rejected the request (HTTP 400): ${describeKindErrorBody(text2)}`
35598
+ );
35599
+ }
35600
+ if (status2 === 403) {
35601
+ return new Error(
35602
+ `${ctx.command}: the server refused ${ctx.client.email} (HTTP 403): ${describeKindErrorBody(text2)} \u2014 a blocked seat gets this answer. Check the seat with \`dr whoami --server ${server} --account ${ctx.client.email}\`.`
35603
+ );
35604
+ }
35605
+ return new Error(`GET ${ctx.route}: HTTP ${status2}: ${truncate3(text2.trim(), 200)}`);
35606
+ }
35607
+ async function readKindAnswer(res, ctx) {
35608
+ const text2 = await res.text();
35609
+ if (res.status < 200 || res.status >= 300) throw kindReadError(res.status, text2, ctx);
35610
+ return text2;
35611
+ }
35612
+ function parseKindBody(text2, route) {
35613
+ try {
35614
+ return JSON.parse(text2);
35615
+ } catch (err) {
35616
+ const msg = err instanceof Error ? err.message : String(err);
35617
+ throw new Error(`GET ${route}: decode: ${msg}`);
35618
+ }
35619
+ }
35620
+ function answeredAsOf(body) {
35621
+ if (typeof body !== "object" || body === null) return void 0;
35622
+ const asOf = body["as_of"];
35623
+ return typeof asOf === "string" ? asOf : void 0;
35624
+ }
35625
+ function withNewline(text2) {
35626
+ return text2.endsWith("\n") ? text2 : `${text2}
35627
+ `;
35628
+ }
35629
+ function mergeKindBodies(bodies) {
35630
+ const [first, ...rest] = bodies;
35631
+ if (rest.length === 0) return first;
35632
+ const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
35633
+ if (!isObject(first)) throw new Error("kind-explain: the server answered with no JSON object");
35634
+ const merged = {};
35635
+ for (const [key, value] of Object.entries(first)) {
35636
+ const lists = [value, ...rest.map((body) => isObject(body) ? body[key] : void 0)];
35637
+ merged[key] = lists.every(Array.isArray) ? lists.flatMap((list28) => list28) : value;
35638
+ }
35639
+ return merged;
35640
+ }
35641
+ async function runFileboxKindExplain(cfg) {
35642
+ if (cfg.ids.length === 0) throw new Error("kind-explain: at least one filebox id is required");
35643
+ const ctx = {
35644
+ command: "kind-explain",
35645
+ route: KIND_EXPLANATION_PATH,
35646
+ client: cfg.client
35647
+ };
35648
+ const batches = chunk(
35649
+ cfg.ids,
35650
+ cfg.history ? KIND_EXPLAIN_MAX_IDS_WITH_HISTORY : KIND_EXPLAIN_MAX_IDS
35651
+ );
35652
+ if (batches.length > 1) {
35653
+ extendForBatch(
35654
+ cfg.extendTimeout,
35655
+ batches.length,
35656
+ "split the ids over several runs (kind-explain has no --timeout)"
35657
+ );
35658
+ }
35659
+ const needsParse = batches.length > 1 || !cfg.asJson;
35660
+ const texts = [];
35661
+ const bodies = [];
35662
+ let asOf = cfg.asOf;
35663
+ for (const [index, ids] of batches.entries()) {
35664
+ const res = await getKindExplanation(
35665
+ cfg.client,
35666
+ { ids, asOf, history: cfg.history },
35667
+ { signal: cfg.signal }
35668
+ );
35669
+ let text2;
35670
+ try {
35671
+ text2 = await readKindAnswer(res, ctx);
35672
+ } catch (err) {
35673
+ if (index === 0 || !(err instanceof Error)) throw err;
35674
+ throw new Error(
35675
+ `request ${index + 1} of ${batches.length} failed, nothing was printed: ${err.message}`
35676
+ );
35677
+ }
35678
+ texts.push(text2);
35679
+ if (!needsParse) continue;
35680
+ const body = parseKindBody(text2, KIND_EXPLANATION_PATH);
35681
+ bodies.push(body);
35682
+ asOf ??= answeredAsOf(body);
35683
+ }
35684
+ if (cfg.asJson) {
35685
+ cfg.out.write(
35686
+ batches.length === 1 ? withNewline(texts[0] ?? "") : `${JSON.stringify(mergeKindBodies(bodies))}
35687
+ `
35688
+ );
35689
+ return;
35690
+ }
35691
+ const answer = decodeShape(mergeKindBodies(bodies));
35692
+ renderKindExplanation(cfg.out, answer, { showAsOf: cfg.asOf !== void 0 });
35693
+ }
35694
+ var KIND_LABELS = {
35695
+ manual_input: "Manual input",
35696
+ model: "Model",
35697
+ report: "Report",
35698
+ lookup: "Lookup",
35699
+ integration: "Integration"
35700
+ };
35701
+ var FORMAT_LABELS = { excel: "Excel", ppt: "PPT", pdf: "PDF" };
35702
+ var NO_SOURCE = "source not recorded";
35703
+ var SOURCE_WORDS = {
35704
+ backfill: "migration",
35705
+ creation_flow: "flow",
35706
+ user: "user",
35707
+ support: "support",
35708
+ default: "default",
35709
+ "(none)": NO_SOURCE
35710
+ };
35711
+ var SOURCE_PHRASES = {
35712
+ backfill: "set by the migration",
35713
+ creation_flow: "set by a flow (at creation, or a connection added later)",
35714
+ user: "set by a user",
35715
+ support: "set by support",
35716
+ default: "set by default"
35717
+ };
35718
+ var SETTERS = {
35719
+ backfill: "the migration",
35720
+ creation_flow: "a flow",
35721
+ user: "a user",
35722
+ support: "support",
35723
+ default: "default, at creation"
35724
+ };
35725
+ var UNSURE_PHRASES = {
35726
+ non_latest_version_link_only: "Datarails data is linked only on an older version, not on the latest one",
35727
+ mapper_with_data_source: "a mapper and a data source, but nothing pulled out of Datarails: an intake file or an integration",
35728
+ empty_doc_type: "the file has no extension, so its format says nothing",
35729
+ broken_dimension_tags_json: "the filebox's tags could not be read",
35730
+ archived: "the filebox is archived",
35731
+ format_signal_cannot_identify_a_deck: "neither its connections nor its extension say what it is"
35732
+ };
35733
+ var HISTORY_REASONS = {
35734
+ lookup_attached_to_table: "a table attached it as a lookup",
35735
+ // The old "Filebox type" setting and the report builder both record these.
35736
+ use_type_set_to_lookup: "a LUT/FX label set",
35737
+ use_type_relabelled_out_of_lookup: "a LUT/FX label removed",
35738
+ data_source_connected: "a data source was connected while it was empty",
35739
+ data_source_connected_to_a_report: "a data source was connected to a Report",
35740
+ cloud_drive_connected: "a synced cloud-drive file was connected while it was empty",
35741
+ cloud_drive_connected_to_a_report: "a synced cloud-drive file was connected to a Report",
35742
+ email_upload_turned_on: "email upload turned on",
35743
+ api_upload_turned_on: "API upload turned on",
35744
+ report_format_from_first_file: "its first file set the Report's format"
35745
+ };
35746
+ var SELF_SERVE_KINDS = /* @__PURE__ */ new Set(["manual_input", "model", "report"]);
35747
+ var UPLOAD_CHANNEL_WORDS = { email: "email", api: "API" };
35748
+ var EVIDENCE_LABELS = [
35749
+ ["mappers", "mapper"],
35750
+ ["lookup_tables", "lookup"],
35751
+ ["pulled_out", "pulled"],
35752
+ ["read_into", "read into"],
35753
+ ["data_query", "source"],
35754
+ ["cloud_drive", "drive"],
35755
+ ["budget_builder", "budget"],
35756
+ ["upload_channels", "upload"]
35757
+ ];
35758
+ var SEPARATOR = " | ";
35759
+ var LABEL_WIDTH = 10;
35760
+ var AGREES_COLUMN = 44;
35761
+ function kindLabel(token) {
35762
+ return KIND_LABELS[token] ?? token;
35763
+ }
35764
+ function kindsText(kinds, reportFormat) {
35765
+ if (kinds.length === 0) return "no type";
35766
+ return kinds.map(
35767
+ (kind) => kind === "report" && reportFormat !== null ? `Report (${FORMAT_LABELS[reportFormat] ?? reportFormat})` : kindLabel(kind)
35768
+ ).join(" + ");
35769
+ }
35770
+ function setBy(source) {
35771
+ if (source === null || source === void 0) return NO_SOURCE;
35772
+ return SOURCE_PHRASES[source] ?? `set by ${source}`;
35773
+ }
35774
+ function storedText(stored) {
35775
+ if (stored === null) return "no type yet";
35776
+ const [firstSource] = stored.sources;
35777
+ if (stored.sources.every((source) => source === firstSource)) {
35778
+ return `${kindsText(stored.kinds, stored.report_format)}, ${setBy(firstSource)}`;
35779
+ }
35780
+ return stored.kinds.map((kind, i) => `${kindsText([kind], stored.report_format)}, ${setBy(stored.sources[i])}`).join("; ");
35781
+ }
35782
+ function flowNote(answer) {
35783
+ if (answer.history !== void 0 || !answer.stored?.sources.includes("creation_flow")) return [];
35784
+ return ["--history shows a connection added later; a creation records nothing"];
35785
+ }
35786
+ function slotsText(slots) {
35787
+ if (slots === null || slots.primary === null) return "no type";
35788
+ const kinds = [slots.primary, slots.secondary].filter((k) => k !== null);
35789
+ return kindsText(kinds, slots.report_format);
35790
+ }
35791
+ function momentText(iso) {
35792
+ const ms = Date.parse(iso);
35793
+ return Number.isFinite(ms) ? formatUnixSeconds(Math.floor(ms / 1e3)) : iso;
35794
+ }
35795
+ function splitReasons(text2) {
35796
+ return text2 === null || text2 === void 0 || text2 === "" ? [] : text2.split(SEPARATOR);
35797
+ }
35798
+ function reasonText(text2) {
35799
+ const colon = text2.indexOf(": ");
35800
+ const token = colon > 0 ? text2.slice(0, colon) : "";
35801
+ const head = Object.hasOwn(KIND_LABELS, token) ? `${kindLabel(token)}${text2.slice(colon)}` : text2;
35802
+ return head.replace(
35803
+ /(does not fit beside )([a-z_+]+)$/,
35804
+ (_match, lead2, slots) => `${lead2}${slots.split("+").map(kindLabel).join(" + ")}`
35805
+ );
35806
+ }
35807
+ function lookUp(phrases, code) {
35808
+ return (Object.hasOwn(phrases, code) ? phrases[code] : void 0) ?? code;
35809
+ }
35810
+ function isByHand(source) {
35811
+ return source === "user" || source === "support";
35812
+ }
35813
+ function settersText(stored) {
35814
+ const [firstSource] = stored.sources;
35815
+ if (stored.sources.every((source) => source === firstSource)) {
35816
+ return firstSource === null || firstSource === void 0 ? `, ${NO_SOURCE}` : ` by ${lookUp(SETTERS, firstSource)}`;
35817
+ }
35818
+ const slots = stored.kinds.map((kind, i) => {
35819
+ const source = stored.sources[i];
35820
+ return source === null || source === void 0 ? `${kindLabel(kind)} (${NO_SOURCE})` : `${kindLabel(kind)} by ${lookUp(SETTERS, source)}`;
35821
+ });
35822
+ return `, ${slots.join(" and ")}`;
35823
+ }
35824
+ function uploadText(channels) {
35825
+ return `${channels.map((channel) => lookUp(UPLOAD_CHANNEL_WORDS, channel)).join(" and ")} upload`;
35826
+ }
35827
+ function integrationNotes(integration) {
35828
+ if (integration === null || integration.counts_as_integration || integration.quiet !== true) {
35829
+ return [];
35830
+ }
35831
+ return ["A data source is connected but quiet, so the content decided the type."];
35832
+ }
35833
+ function uploadKeptModelNotes(stored, derived, integration) {
35834
+ const channels = integration?.upload_channels ?? [];
35835
+ if (channels.length === 0 || !stored.kinds.includes("model") || !derived.includes("integration")) {
35836
+ return [];
35837
+ }
35838
+ const upload2 = uploadText(channels);
35839
+ const several = channels.length > 1;
35840
+ return [
35841
+ `${upload2.charAt(0).toUpperCase()}${upload2.slice(1)} ${several ? "are" : "is"} on: the migration puts Integration in place of a Model, but turning ${several ? "an" : "the"} upload on after it was typed keeps the Model, so the stored type stays ${kindsText(stored.kinds, stored.report_format)}.`
35842
+ ];
35843
+ }
35844
+ function kindMoveNeedsSupport(current, next) {
35845
+ const held = new Set(current);
35846
+ const wanted = new Set(next);
35847
+ const changed = [...held, ...wanted].filter((kind) => held.has(kind) !== wanted.has(kind));
35848
+ const integrationStays = held.has("integration") && wanted.has("integration");
35849
+ if (integrationStays && changed.every((kind) => kind === "report")) return false;
35850
+ return [...held, ...wanted].some((kind) => !SELF_SERVE_KINDS.has(kind));
35851
+ }
35852
+ function whoMayChange(stored, derived) {
35853
+ if (kindMoveNeedsSupport(stored, derived)) {
35854
+ return "To change it: the support seat only, in the filebox settings, as Lookup or Integration is held before or after the move.";
35855
+ }
35856
+ return stored.includes("integration") ? "To change it: the owner or the support seat, in the filebox settings: Integration stays, only Report or its format changes." : "To change it: the owner or the support seat, in the filebox settings.";
35857
+ }
35858
+ function meaningLines(answer) {
35859
+ const derived = kindsText(answer.derived.kinds, answer.derived.report_format);
35860
+ const notes = integrationNotes(answer.integration);
35861
+ if (answer.stored === null) {
35862
+ const why = answer.rollout ? "No type stored yet: the migration has not reached this filebox." : "No type stored: the organization is outside the rollout.";
35863
+ return [why, `The migration would make it ${derived}.`, ...notes];
35864
+ }
35865
+ const stored = `${kindsText(answer.stored.kinds, answer.stored.report_format)}${settersText(answer.stored)}`;
35866
+ const byHand = answer.stored.sources.some(isByHand);
35867
+ if (answer.agrees) {
35868
+ return [
35869
+ byHand ? `Set to ${stored}, and its connections say the same.` : `Typed ${stored}, and its connections still say so.`,
35870
+ ...notes
35871
+ ];
35872
+ }
35873
+ if (byHand) {
35874
+ return [
35875
+ `Set on purpose to ${stored}; its connections alone would make it ${derived}.`,
35876
+ "A type set by hand stays: the migration never overwrites it."
35877
+ ];
35878
+ }
35879
+ return [
35880
+ `Typed ${stored}, but its connections now make it ${derived}.`,
35881
+ "The connections changed after it was typed, and a type does not follow them by itself.",
35882
+ ...uploadKeptModelNotes(answer.stored, answer.derived.kinds, answer.integration),
35883
+ ...timeNotes(answer.integration),
35884
+ whoMayChange(answer.stored.kinds, answer.derived.kinds),
35885
+ ...notes
35886
+ ];
35887
+ }
35888
+ function timeNotes(integration) {
35889
+ if (integration === null || integration.quiet === null) return [];
35890
+ if ((integration.upload_channels ?? []).length > 0) return [];
35891
+ return [
35892
+ `Time alone can change the answer too: a connected integration is quiet after ${integration.window_days} days with no file, sync attempt or schedule.`,
35893
+ "To judge it as the migration did: --as-of <backfill day>."
35894
+ ];
35895
+ }
35896
+ function writeField(out, label, values, indent = " ") {
35897
+ if (values.length === 0) return;
35898
+ const head = label.length >= LABEL_WIDTH ? `${label} ` : label.padEnd(LABEL_WIDTH);
35899
+ const pad = " ".repeat(head.length);
35900
+ values.forEach((value, i) => out.write(`${indent}${i === 0 ? head : pad}${value}
35901
+ `));
35902
+ }
35903
+ function labelledLines(rows) {
35904
+ const width = Math.max(LABEL_WIDTH, ...rows.map(([label]) => label.length + 2));
35905
+ return rows.flatMap(
35906
+ ([label, items]) => items.map((item, i) => `${(i === 0 ? label : "").padEnd(width)}${item}`)
35907
+ );
35908
+ }
35909
+ function evidenceLines(evidence) {
35910
+ const rows = EVIDENCE_LABELS.map(
35911
+ ([field, label]) => [label, splitReasons(evidence?.[field])]
35912
+ ).filter(([, items]) => items.length > 0);
35913
+ return rows.length === 0 ? ["none"] : labelledLines(rows);
35914
+ }
35915
+ function integrationLines(integration) {
35916
+ const channels = integration.upload_channels ?? [];
35917
+ const head = [
35918
+ ...integration.quiet === null ? [] : [`${integration.quiet ? "quiet" : "live"} over the last ${integration.window_days} days`],
35919
+ ...channels.length > 0 ? [`${uploadText(channels)} on, no quiet rule`] : [],
35920
+ integration.counts_as_integration ? "counts as Integration" : "does not count as Integration"
35921
+ ].join(" \xB7 ");
35922
+ if (integration.quiet === null) return [head, "no data query or synced cloud drive connected"];
35923
+ return [
35924
+ head,
35925
+ ...labelledLines([
35926
+ [
35927
+ "last file in window",
35928
+ [integration.last_file_in_window ? momentText(integration.last_file_in_window) : "none"]
35929
+ ],
35930
+ [
35931
+ "last sync attempt",
35932
+ [integration.last_sync_attempt ? momentText(integration.last_sync_attempt) : "none"]
35933
+ ],
35934
+ ["schedule", [integration.schedule ?? "none"]],
35935
+ ["auto sync", [integration.auto_sync ? "on" : "off"]]
35936
+ ])
35937
+ ];
35938
+ }
35939
+ function writeHistory(out, history) {
35940
+ if (history.length === 0) {
35941
+ writeField(out, "history", ["none recorded (or the audit server could not be read)"]);
35942
+ return;
35943
+ }
35944
+ out.write(" history\n");
35945
+ const indent = " ";
35946
+ const rows = [["WHEN", "BY", "OPERATOR", "BEFORE \u2192 AFTER", "REASON"]];
35947
+ for (const entry of history) {
35948
+ rows.push([
35949
+ entry.at ? momentText(entry.at) : "-",
35950
+ entry.by ?? "-",
35951
+ entry.operator ?? "-",
35952
+ `${slotsText(entry.before)} \u2192 ${slotsText(entry.after)}`,
35953
+ entry.reason ? lookUp(HISTORY_REASONS, entry.reason) : "retyped in the filebox settings"
35954
+ ]);
35955
+ }
35956
+ writeRows(
35957
+ out,
35958
+ rows.map(([first, ...others]) => [`${indent}${first ?? ""}`, ...others])
35959
+ );
35960
+ }
35961
+ function writeFilebox(out, answer) {
35962
+ const rollout = answer.rollout ? "" : " (outside the rollout)";
35963
+ out.write(
35964
+ `Filebox ${answer.id} "${answer.name}" \xB7 organization ${answer.organization_id}${rollout}
35965
+ `
35966
+ );
35967
+ writeField(out, "meaning", meaningLines(answer));
35968
+ writeField(out, "stored", [storedText(answer.stored), ...flowNote(answer)]);
35969
+ const derived = kindsText(answer.derived.kinds, answer.derived.report_format);
35970
+ const mark = answer.agrees === null ? "" : answer.agrees ? "\u2713 agrees" : "\u2717 disagrees";
35971
+ writeField(out, "rules", [
35972
+ mark === "" ? derived : `${derived.padEnd(Math.max(AGREES_COLUMN, derived.length + 2))}${mark}`
35973
+ ]);
35974
+ writeField(out, "rule", [answer.derived.rule]);
35975
+ if (answer.derived.unknown_reason) {
35976
+ writeField(out, "unsure", [lookUp(UNSURE_PHRASES, answer.derived.unknown_reason)]);
35977
+ }
35978
+ writeField(
35979
+ out,
35980
+ "why",
35981
+ [answer.why.primary, answer.why.secondary].filter((w) => !!w).map(reasonText)
35982
+ );
35983
+ writeField(out, "dropped", splitReasons(answer.why.dropped).map(reasonText));
35984
+ writeField(out, "why not", splitReasons(answer.why.not).map(reasonText));
35985
+ writeField(out, "evidence", evidenceLines(answer.evidence));
35986
+ if (answer.integration) writeField(out, "integration", integrationLines(answer.integration));
35987
+ if (answer.history !== void 0) writeHistory(out, answer.history);
35988
+ }
35989
+ var NOT_FOUND_HINT = "Not found also holds a filebox of another organization: only that organization's support seat gets its answer.\n";
35990
+ function renderKindExplanation(out, answer, opts) {
35991
+ let first = true;
35992
+ if (opts.showAsOf) {
35993
+ out.write(`As of ${answer.as_of}
35994
+ `);
35995
+ first = false;
35996
+ }
35997
+ for (const filebox of answer.fileboxes) {
35998
+ if (!first) out.write("\n");
35999
+ writeFilebox(out, filebox);
36000
+ first = false;
36001
+ }
36002
+ if (answer.not_found.length > 0) {
36003
+ if (!first) out.write("\n");
36004
+ for (const id of answer.not_found) out.write(`Not found: filebox ${id}
36005
+ `);
36006
+ out.write(NOT_FOUND_HINT);
36007
+ }
36008
+ }
36009
+ async function runFileboxKindSummary(cfg) {
36010
+ const ctx = {
36011
+ command: "kind-summary",
36012
+ route: KIND_SUMMARY_PATH,
36013
+ client: cfg.client
36014
+ };
36015
+ const res = await getKindSummary(cfg.client, { signal: cfg.signal });
36016
+ const text2 = await readKindAnswer(res, ctx);
36017
+ if (cfg.asJson) {
36018
+ cfg.out.write(withNewline(text2));
36019
+ return;
36020
+ }
36021
+ renderKindSummary(
36022
+ cfg.out,
36023
+ decodeShape(parseKindBody(text2, KIND_SUMMARY_PATH))
36024
+ );
36025
+ }
36026
+ function countText(n) {
36027
+ return n.toLocaleString("en-US");
36028
+ }
36029
+ function countsLine(entries) {
36030
+ return [...entries].sort(([a, x], [b, y]) => y - x || a.localeCompare(b)).map(([label, n]) => `${label} ${countText(n)}`).join(" \xB7 ");
36031
+ }
36032
+ var UNTYPED_KEY = "(untyped)";
36033
+ function renderKindSummary(out, summary) {
36034
+ const rollout = summary.rollout ? "" : " (outside the rollout)";
36035
+ out.write(`Organization ${summary.organization_id}${rollout}
36036
+ `);
36037
+ const header = ["fileboxes", "typed", "untyped"];
36038
+ const rows = ["live", "archived"].map(
36039
+ (state) => [
36040
+ state,
36041
+ [summary[state].fileboxes, summary[state].typed, summary[state].untyped].map(countText)
36042
+ ]
36043
+ );
36044
+ const widths = header.map(
36045
+ (h, i) => Math.max(h.length, ...rows.map(([, v]) => (v[i] ?? "").length))
36046
+ );
36047
+ const line = (cells) => cells.map((cell, i) => cell.padStart(widths[i] ?? 0)).join(" ");
36048
+ out.write(`${" ".repeat(2 + LABEL_WIDTH)}${line(header)}
36049
+ `);
36050
+ for (const [state, values] of rows) out.write(` ${state.padEnd(LABEL_WIDTH)}${line(values)}
36051
+ `);
36052
+ const sources = Object.entries(summary.sources).map(
36053
+ ([source, n]) => [lookUp(SOURCE_WORDS, source), n]
36054
+ );
36055
+ writeField(out, "set by", [sources.length === 0 ? "none" : countsLine(sources)]);
36056
+ const kinds = Object.entries(summary.kinds);
36057
+ const combinations = (pairs) => countsLine(
36058
+ kinds.filter(([combination]) => combination.includes("+") === pairs).map(([combination, n]) => [kindsText(combination.split("+"), null), n])
36059
+ );
36060
+ const typeLines = [combinations(false), combinations(true)].filter((l) => l !== "");
36061
+ writeField(out, "types", typeLines.length === 0 ? ["none"] : typeLines);
36062
+ if (summary.upload_channel_kinds === void 0) return;
36063
+ const uploads = Object.entries(summary.upload_channel_kinds).map(
36064
+ ([combination, n]) => [
36065
+ combination === UNTYPED_KEY ? "untyped" : kindsText(combination.split("+"), null),
36066
+ n
36067
+ ]
36068
+ );
36069
+ writeField(
36070
+ out,
36071
+ "uploads",
36072
+ uploads.length === 0 ? ["no live filebox has email or API upload on"] : ["live fileboxes with email / API upload on, by stored type", countsLine(uploads)]
36073
+ );
36074
+ }
36075
+ var SUPPORT_SEAT_HELP = [
36076
+ "",
36077
+ "Support seat only. The server answers the support seat of the organization",
36078
+ "asked about and refuses any other seat (HTTP 405), even a support person",
36079
+ "working in a customer user's seat. Log in as that seat, then run as it:",
36080
+ " dr login-as <support seat email> --server <S>"
36081
+ ];
36082
+ function registerFileboxKindCommands(fb) {
36083
+ const explainCmd = fb.command("kind-explain").description(
36084
+ "Why fileboxes have their type: the stored type and who set it, what the migration's rules make of it, whether the two agree, the rule, why, and the evidence (read-only, support seat only)"
36085
+ ).argument("<filebox_ids...>", 'filebox ids (positive integers; "12,34" also accepted)').option(
36086
+ "--history",
36087
+ `Also list the recorded type changes (${KIND_EXPLAIN_MAX_IDS_WITH_HISTORY} ids per request)`,
36088
+ false
36089
+ ).option(
36090
+ "--as-of <date>",
36091
+ "Judge a quiet integration as of this day (YYYY-MM-DD, its end UTC); every other signal is read as it is now"
36092
+ ).option("--json", "Print the server's answer instead of the readable form (see below)", false).addHelpText(
36093
+ "after",
36094
+ [
36095
+ ...SUPPORT_SEAT_HELP,
36096
+ " dr filebox kind-explain 48213 48999 --server <S> --account <support seat email>",
36097
+ "A filebox of another organization, a folder, or an id that does not exist is",
36098
+ 'listed as "Not found". It changes nothing and answers whatever the',
36099
+ "organization's feature flag; each filebox says whether it is in the rollout.",
36100
+ "",
36101
+ `More than ${KIND_EXPLAIN_MAX_IDS} ids (${KIND_EXPLAIN_MAX_IDS_WITH_HISTORY} with --history) are sent as several requests, one`,
36102
+ "after another, the later ones pinned to the first answer's as_of. --json",
36103
+ "prints a single request's body unchanged; for several it prints one body in",
36104
+ "the server's shape: fileboxes and not_found concatenated in request order,",
36105
+ "as_of from the first. Either way --json keeps the HTML entities the server",
36106
+ 'sends (a name reads "P&amp;L"): unlike `filebox list --json`, nothing is decoded.'
36107
+ ].join("\n")
36108
+ ).action(
36109
+ async (idArgs, opts) => {
36110
+ const ids = parseKindExplainIds(idArgs);
36111
+ const asOf = parseKindAsOf(opts.asOf);
36112
+ const setup = await setupCall(opts.server, opts.account);
36113
+ try {
36114
+ await runFileboxKindExplain({
36115
+ client: setup.client,
36116
+ ids,
36117
+ asOf,
36118
+ history: opts.history === true,
36119
+ asJson: opts.json === true,
36120
+ signal: setup.signal,
36121
+ out: process.stdout,
36122
+ extendTimeout: setup.extendTimeout
36123
+ });
36124
+ } finally {
36125
+ setup.cleanup();
36126
+ }
36127
+ }
36128
+ );
36129
+ addServerFlag(explainCmd);
36130
+ addAccountFlag(explainCmd);
36131
+ const summaryCmd = fb.command("kind-summary").description(
36132
+ "How far the seat's organization is typed: live and archived fileboxes, typed and untyped, who set the types, and every stored combination (read-only, support seat only)"
36133
+ ).option("--json", "Print the server's answer unchanged", false).addHelpText(
36134
+ "after",
36135
+ [
36136
+ ...SUPPORT_SEAT_HELP,
36137
+ " dr filebox kind-summary --server <S> --account <support seat email>",
36138
+ 'It answers for the organization of that seat. "set by" and "types" count',
36139
+ 'live and archived fileboxes together; in "set by", "flow" is a flow that',
36140
+ "typed the filebox at creation or when a connection was added later (a table",
36141
+ "attaching it as a lookup, a LUT/FX label, a data source or synced drive",
36142
+ 'connected, email / API upload turned on). "uploads" counts live fileboxes with',
36143
+ "email or API upload on by their stored type: the rules make each an",
36144
+ "Integration, dropping a Model, but turning an upload on keeps a Model, so a",
36145
+ "Model counted there is expected drift (kind-explain says why)."
36146
+ ].join("\n")
36147
+ ).action(async (opts) => {
36148
+ const setup = await setupCall(opts.server, opts.account);
36149
+ try {
36150
+ await runFileboxKindSummary({
36151
+ client: setup.client,
36152
+ asJson: opts.json === true,
36153
+ signal: setup.signal,
36154
+ out: process.stdout
36155
+ });
36156
+ } finally {
36157
+ setup.cleanup();
36158
+ }
36159
+ });
36160
+ addServerFlag(summaryCmd);
36161
+ addAccountFlag(summaryCmd);
36162
+ }
36163
+
35465
36164
  // src/commands/_typedError.ts
35466
36165
  function isTypedError(err) {
35467
36166
  return !(err instanceof Error) || err.constructor !== Error || "code" in err;
@@ -36148,11 +36847,11 @@ function classifyDataQuerySyncProgress(periods, baseline, versions, dataQueryRaw
36148
36847
  }
36149
36848
  return out;
36150
36849
  }
36151
- async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal) {
36850
+ async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal, preRead) {
36152
36851
  try {
36153
36852
  const [versions, dataQuery] = await Promise.all([
36154
36853
  listVersions(client, fileboxId, {}, { signal }),
36155
- getDataQueryRaw(client, fileboxId, { signal })
36854
+ preRead !== void 0 ? Promise.resolve(preRead.raw) : getDataQueryRaw(client, fileboxId, { signal })
36156
36855
  ]);
36157
36856
  return buildDataQuerySyncBaseline(periods, versions, dataQuery);
36158
36857
  } catch (err) {
@@ -36252,7 +36951,8 @@ function renderSummary2(cfg, progress, outcome, waitedSec) {
36252
36951
  })),
36253
36952
  unmatched_new_versions: progress.unmatchedNewVersions,
36254
36953
  error: progress.error ?? null,
36255
- waited_seconds: waitedSec
36954
+ waited_seconds: waitedSec,
36955
+ warnings: cfg.warnings ?? []
36256
36956
  });
36257
36957
  return;
36258
36958
  }
@@ -36310,6 +37010,63 @@ function failureMessage(fileboxId, progress, outcome, waitedSec) {
36310
37010
  return `data-query sync on filebox ${fileboxId} did not finish within ${waitedSec}s \u2014 ${pending.length} period(s) unfinished: ${pendingList}. Increase --timeout, or re-check with 'dr filebox versions ${fileboxId}'; do not blindly re-trigger.`;
36311
37011
  }
36312
37012
 
37013
+ // src/commands/_aggregatorBalance.ts
37014
+ var AGGREGATOR_TYPE_NAMES = /* @__PURE__ */ new Set([
37015
+ "plaid",
37016
+ "quiltt",
37017
+ "saltedge",
37018
+ "flinks"
37019
+ ]);
37020
+ function asRecord2(v) {
37021
+ return typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
37022
+ }
37023
+ function parsedRecord(raw) {
37024
+ if (typeof raw === "string") {
37025
+ try {
37026
+ return asRecord2(JSON.parse(raw));
37027
+ } catch {
37028
+ return void 0;
37029
+ }
37030
+ }
37031
+ return asRecord2(raw);
37032
+ }
37033
+ async function detectAggregatorBalanceQuery(client, fileboxId, signal) {
37034
+ let dataQueryRaw;
37035
+ try {
37036
+ dataQueryRaw = await getDataQueryRaw(client, fileboxId, { signal });
37037
+ } catch {
37038
+ if (signal.aborted) throw signal.reason ?? new Error("aborted");
37039
+ return void 0;
37040
+ }
37041
+ const dataQuery = asRecord2(dataQueryRaw);
37042
+ const config = parsedRecord(dataQuery?.["config"]);
37043
+ const objectType = config?.["objectType"] ?? parsedRecord(config?.["query"])?.["objectType"];
37044
+ const rawConnection = dataQuery?.["connection"];
37045
+ const connectionId = typeof rawConnection === "number" || typeof rawConnection === "string" ? Number(rawConnection) : NaN;
37046
+ if (typeof objectType !== "string" || objectType.toLowerCase() !== "balances" || !Number.isInteger(connectionId) || connectionId <= 0) {
37047
+ return { dataQueryRaw };
37048
+ }
37049
+ let type;
37050
+ try {
37051
+ const integration = await getById2(client, connectionId, { signal });
37052
+ type = asRecord2(integration.type);
37053
+ } catch {
37054
+ if (signal.aborted) throw signal.reason ?? new Error("aborted");
37055
+ return { dataQueryRaw };
37056
+ }
37057
+ const name = type?.["name"];
37058
+ if (typeof name !== "string" || !AGGREGATOR_TYPE_NAMES.has(name.toLowerCase())) {
37059
+ return { dataQueryRaw };
37060
+ }
37061
+ const display = type?.["display_name"];
37062
+ const aggregator = typeof display === "string" && display.trim() !== "" ? display.trim() : name;
37063
+ return { dataQueryRaw, aggregator };
37064
+ }
37065
+ function aggregatorBalanceWarning(fileboxId, aggregator, periodCount) {
37066
+ const what = periodCount > 1 ? `these ${periodCount} periods will all get today's balance (identical versions under different dates)` : "that period will get today's balance, labelled as if it were that period's";
37067
+ return `filebox ${fileboxId} syncs balances from ${aggregator}, an aggregator: it returns today's balance whatever --date asks for, so ${what}. Backfill only transactions; for balances, sync the current period only and let the nightly schedule build the history. Multi-period balance backfill works only on a direct bank API connection.`;
37068
+ }
37069
+
36313
37070
  // src/commands/filebox.ts
36314
37071
  async function fileToBlob(filePath) {
36315
37072
  const buf = await fsPromises.readFile(filePath);
@@ -36623,7 +37380,7 @@ ${JSON.stringify(result)}
36623
37380
  `
36624
37381
  );
36625
37382
  }
36626
- function asRecord2(v) {
37383
+ function asRecord3(v) {
36627
37384
  return v !== null && typeof v === "object" ? v : void 0;
36628
37385
  }
36629
37386
  function numOf(r, k) {
@@ -36633,9 +37390,9 @@ function strOf(r, k) {
36633
37390
  return r !== void 0 && typeof r[k] === "string" ? r[k] : void 0;
36634
37391
  }
36635
37392
  function extractUploadedVersionMeta(result) {
36636
- const r = asRecord2(result);
36637
- const dv = asRecord2(r?.["document_version"]);
36638
- const docObj = asRecord2(r?.["document"]);
37393
+ const r = asRecord3(result);
37394
+ const dv = asRecord3(r?.["document_version"]);
37395
+ const docObj = asRecord3(r?.["document"]);
36639
37396
  return {
36640
37397
  id: numOf(r, "id") ?? numOf(dv, "id") ?? numOf(docObj, "latest_version_id") ?? 0,
36641
37398
  version: numOf(r, "version") ?? numOf(dv, "version") ?? 0,
@@ -36794,7 +37551,7 @@ async function runFileboxUploadVersion(cfg) {
36794
37551
  ...cycleTagVerdictJSON(confirmation)
36795
37552
  });
36796
37553
  } else if (cfg.label !== void 0) {
36797
- cfg.out.write(JSON.stringify({ ...asRecord2(result), ...labelJSON }) + "\n");
37554
+ cfg.out.write(JSON.stringify({ ...asRecord3(result), ...labelJSON }) + "\n");
36798
37555
  } else {
36799
37556
  cfg.out.write(JSON.stringify(result) + "\n");
36800
37557
  }
@@ -37600,22 +38357,68 @@ async function runFileboxSetVersionWorkflow(cfg) {
37600
38357
  `document ${cfg.docId} has no WORKFLOW tag in its config (available: ${availMsg})`
37601
38358
  );
37602
38359
  }
37603
- const tagsToUpdate = JSON.stringify([
37604
- { id: tag.id, value: cfg.status, name: "WORKFLOW", label: cfg.status }
37605
- ]);
37606
- const res = await setVersionTagsAndLabel(
37607
- cfg.client,
37608
- cfg.versionId,
37609
- { tags_to_update: tagsToUpdate },
37610
- { signal: cfg.signal }
37611
- );
37612
- if (res.status >= 200 && res.status < 300) {
37613
- cfg.out.write(`Set WORKFLOW tag on version ${cfg.versionId}: "${cfg.status}".
37614
- `);
38360
+ const err = cfg.err ?? process.stderr;
38361
+ const { target } = await loadTargetVersion(cfg.client, cfg.docId, cfg.versionId, cfg.signal);
38362
+ const current = tagValueOf(target.tags, tag.id);
38363
+ const previous = typeof current === "string" ? current : null;
38364
+ const writer = new FullTagSetWriter({
38365
+ client: cfg.client,
38366
+ docId: cfg.docId,
38367
+ versionId: cfg.versionId,
38368
+ snapshot: target.tags,
38369
+ tag: { id: tag.id, value: cfg.status, name: tag.name, label: cfg.status },
38370
+ action: "set version workflow",
38371
+ signal: cfg.signal,
38372
+ sleep: cfg.sleep,
38373
+ settleMs: cfg.tagSettleDelayMs
38374
+ });
38375
+ await writer.write();
38376
+ const kept = await keepDateTagValue({
38377
+ writer,
38378
+ seen: await writer.readBack(),
38379
+ dateBefore: target.date_tag_value,
38380
+ versionId: cfg.versionId,
38381
+ docId: cfg.docId,
38382
+ what: "WORKFLOW tag",
38383
+ signal: cfg.signal,
38384
+ err
38385
+ });
38386
+ const { seen, dateAfter, datePreserved } = kept;
38387
+ const now = seen === null ? void 0 : tagValueOf(seen.tags, tag.id);
38388
+ const verified = seen !== null && now === cfg.status;
38389
+ if (seen === null) {
38390
+ err.write(
38391
+ `warning: could not read version ${cfg.versionId} back to verify its WORKFLOW tag \u2014 confirm with \`dr filebox versions ${cfg.docId}\`
38392
+ `
38393
+ );
38394
+ } else if (!verified) {
38395
+ err.write(
38396
+ `warning: version ${cfg.versionId} reads WORKFLOW ${JSON.stringify(now ?? null)} after the write, not "${cfg.status}" \u2014 confirm with \`dr filebox versions ${cfg.docId}\`
38397
+ `
38398
+ );
38399
+ }
38400
+ if (cfg.asJson) {
38401
+ writeJSONIndented(cfg.out, {
38402
+ version_id: cfg.versionId,
38403
+ doc_id: cfg.docId,
38404
+ tag_id: tag.id,
38405
+ previous,
38406
+ status: cfg.status,
38407
+ verified,
38408
+ reapplied: kept.reapplied,
38409
+ date_tag_value: dateAfter,
38410
+ date_tag_value_preserved: datePreserved
38411
+ });
37615
38412
  return;
37616
38413
  }
37617
- const body = await res.text();
37618
- throw new Error(`set version workflow refused (HTTP ${res.status}): ${body.trim()}`);
38414
+ const verdict = !verified ? "unverified" : kept.reapplied ? "verified after one re-apply" : "verified";
38415
+ cfg.out.write(
38416
+ `Set WORKFLOW tag on version ${cfg.versionId}: ${JSON.stringify(previous ?? "(unset)")} -> "${cfg.status}" (${verdict}).
38417
+ `
38418
+ );
38419
+ if (datePreserved === false) {
38420
+ cfg.out.write(dateNotPreservedLine(dateAfter, target.date_tag_value));
38421
+ }
37619
38422
  }
37620
38423
  var FILE_STATUS_VALUES = ["Draft", "Final"];
37621
38424
  function parseFileStatus(raw) {
@@ -37716,6 +38519,110 @@ function fullTagSetWith(stored, write) {
37716
38519
  }
37717
38520
  return [...kept, write].sort((a, b) => a.id - b.id);
37718
38521
  }
38522
+ var FullTagSetWriter = class {
38523
+ constructor(cfg) {
38524
+ this.cfg = cfg;
38525
+ this.base = cfg.snapshot ?? [];
38526
+ }
38527
+ cfg;
38528
+ base;
38529
+ /** PUT the full set; a non-2xx answer throws `TagWriteRefusedError`. */
38530
+ async write() {
38531
+ const { cfg } = this;
38532
+ const res = await setVersionTagsAndLabel(
38533
+ cfg.client,
38534
+ cfg.versionId,
38535
+ { tags_to_update: JSON.stringify(fullTagSetWith(this.base, cfg.tag)) },
38536
+ { signal: cfg.signal }
38537
+ );
38538
+ if (res.status < 200 || res.status >= 300) {
38539
+ const body = (await res.text()).trim();
38540
+ throw new TagWriteRefusedError(
38541
+ `${cfg.action} refused (HTTP ${res.status}): ${body}`,
38542
+ `(HTTP ${res.status}): ${body}`
38543
+ );
38544
+ }
38545
+ }
38546
+ /** Wait the settle delay, then read the version back (`null` = unreadable). */
38547
+ async readBack() {
38548
+ const { cfg } = this;
38549
+ const sleep3 = cfg.sleep ?? defaultCycleTagSleep;
38550
+ await sleep3(cfg.settleMs ?? CYCLE_TAG_SETTLE_DELAY_MS, cfg.signal);
38551
+ return readVersionForConfirm(cfg.client, cfg.versionId, cfg.docId, cfg.signal);
38552
+ }
38553
+ /** Build the next write from this read-back instead of the earlier snapshot. */
38554
+ refreshBase(v) {
38555
+ if (v !== null && v.tags.length > 0) {
38556
+ this.base = v.tags;
38557
+ }
38558
+ }
38559
+ };
38560
+ async function keepDateTagValue(cfg) {
38561
+ const { dateBefore, err } = cfg;
38562
+ let seen = cfg.seen;
38563
+ const dateMoved = (v) => v !== null && typeof dateBefore === "number" && v.row.date_tag_value !== void 0 && v.row.date_tag_value !== dateBefore;
38564
+ let reapplied = false;
38565
+ let dateUnknown = false;
38566
+ if (dateMoved(seen)) {
38567
+ err.write(
38568
+ `warning: date_tag_value on version ${cfg.versionId} changed (${JSON.stringify(dateBefore)} -> ${JSON.stringify(seen.row.date_tag_value ?? null)}) when its ${cfg.what} was set; re-applying the full tag set once
38569
+ `
38570
+ );
38571
+ reapplied = true;
38572
+ cfg.writer.refreshBase(seen);
38573
+ let refused = false;
38574
+ try {
38575
+ await cfg.writer.write();
38576
+ } catch (e) {
38577
+ if (cfg.signal.aborted) {
38578
+ throw e;
38579
+ }
38580
+ if (e instanceof TagWriteRefusedError) {
38581
+ refused = true;
38582
+ err.write(`warning: re-applying the full tag set was refused ${e.reason}
38583
+ `);
38584
+ } else {
38585
+ err.write(
38586
+ `warning: re-applying the full tag set failed (${e instanceof Error ? e.message : String(e)}); its outcome is unknown, reading the version again
38587
+ `
38588
+ );
38589
+ }
38590
+ }
38591
+ const after = refused ? seen : await cfg.writer.readBack();
38592
+ if (after === null) {
38593
+ dateUnknown = true;
38594
+ err.write(
38595
+ `warning: could not read version ${cfg.versionId} back to confirm its date_tag_value after re-applying the full tag set \u2014 check it with \`dr filebox versions ${cfg.docId} --json\`
38596
+ `
38597
+ );
38598
+ } else {
38599
+ seen = after;
38600
+ }
38601
+ }
38602
+ const dateAfter = dateUnknown || seen === null || seen.row.date_tag_value === void 0 ? null : seen.row.date_tag_value;
38603
+ const datePreserved = dateUnknown || seen === null || dateBefore === void 0 || seen.row.date_tag_value === void 0 ? null : !dateMoved(seen);
38604
+ if (datePreserved === false) {
38605
+ err.write(
38606
+ `warning: date_tag_value on version ${cfg.versionId} is still ${JSON.stringify(dateAfter)} (was ${JSON.stringify(dateBefore)}) after one re-apply; its tags still carry the date. Restore it with \`dr filebox set-version-date-tag ${cfg.versionId} --doc ${cfg.docId} --<dimension> <date>\` (it sends the date tag alone; on a planning filebox use \`dr filebox set-version-cycle ${cfg.versionId} --doc ${cfg.docId} --date <date> --scenario <name>\` instead, which keeps the cycle), then check \`dr filebox versions ${cfg.docId} --json\`
38607
+ `
38608
+ );
38609
+ }
38610
+ return { seen, reapplied, dateAfter, datePreserved };
38611
+ }
38612
+ function dateNotPreservedLine(dateAfter, dateBefore) {
38613
+ return `date_tag_value NOT preserved: it reads ${JSON.stringify(dateAfter)}, was ${JSON.stringify(dateBefore)} (see the warning above).
38614
+ `;
38615
+ }
38616
+ async function loadTargetVersion(client, docId, versionId, signal) {
38617
+ const rows = await listVersions(client, docId, { includeAutoSaves: true }, { signal });
38618
+ const target = rows.find((r) => r.id === versionId);
38619
+ if (target === void 0) {
38620
+ throw new Error(
38621
+ `version ${versionId} is not a version of filebox ${docId} \u2014 list them with \`dr filebox versions ${docId} --all\``
38622
+ );
38623
+ }
38624
+ return { rows, target };
38625
+ }
37719
38626
  async function runFileboxSetVersionFileStatus(cfg) {
37720
38627
  if (cfg.docId <= 0) {
37721
38628
  throw new Error("document id must be positive");
@@ -37724,8 +38631,6 @@ async function runFileboxSetVersionFileStatus(cfg) {
37724
38631
  throw new Error("version id must be positive");
37725
38632
  }
37726
38633
  const err = cfg.err ?? process.stderr;
37727
- const sleep3 = cfg.sleep ?? defaultCycleTagSleep;
37728
- const settleMs = cfg.tagSettleDelayMs ?? CYCLE_TAG_SETTLE_DELAY_MS;
37729
38634
  const details = await getDetails(cfg.client, cfg.docId, { signal: cfg.signal });
37730
38635
  const tag = (details.tags_config ?? []).find(isFileStatusTag);
37731
38636
  if (tag === void 0) {
@@ -37739,18 +38644,12 @@ async function runFileboxSetVersionFileStatus(cfg) {
37739
38644
  `File Status on document ${cfg.docId} does not allow "${cfg.status}" (allowed: ${tag.values.map((v) => JSON.stringify(v)).join(" | ")})`
37740
38645
  );
37741
38646
  }
37742
- const rows = await listVersions(
38647
+ const { rows, target } = await loadTargetVersion(
37743
38648
  cfg.client,
37744
38649
  cfg.docId,
37745
- { includeAutoSaves: true },
37746
- { signal: cfg.signal }
38650
+ cfg.versionId,
38651
+ cfg.signal
37747
38652
  );
37748
- const target = rows.find((r) => r.id === cfg.versionId);
37749
- if (target === void 0) {
37750
- throw new Error(
37751
- `version ${cfg.versionId} is not a version of filebox ${cfg.docId} \u2014 list them with \`dr filebox versions ${cfg.docId} --all\``
37752
- );
37753
- }
37754
38653
  const current = tagValueOf(target.tags, tag.id);
37755
38654
  const previous = typeof current === "string" ? current : null;
37756
38655
  const options = tag.options ?? {};
@@ -37803,45 +38702,25 @@ async function runFileboxSetVersionFileStatus(cfg) {
37803
38702
  );
37804
38703
  }
37805
38704
  }
37806
- const statusWrite = {
37807
- id: tag.id,
37808
- value: cfg.status,
37809
- name: tag.name,
37810
- label: cfg.status
37811
- };
37812
- let base = target.tags ?? [];
37813
- const refreshBase = (v) => {
37814
- if (v !== null && v.tags.length > 0) {
37815
- base = v.tags;
37816
- }
37817
- };
37818
- const write = async () => {
37819
- const res = await setVersionTagsAndLabel(
37820
- cfg.client,
37821
- cfg.versionId,
37822
- { tags_to_update: JSON.stringify(fullTagSetWith(base, statusWrite)) },
37823
- { signal: cfg.signal }
37824
- );
37825
- if (res.status < 200 || res.status >= 300) {
37826
- const body = await res.text();
37827
- throw new TagWriteRefusedError(
37828
- `set version file status refused (HTTP ${res.status}): ${body.trim()}`,
37829
- `(HTTP ${res.status}): ${body.trim()}`
37830
- );
37831
- }
37832
- };
37833
- const readBack = async () => {
37834
- await sleep3(settleMs, cfg.signal);
37835
- return readVersionForConfirm(cfg.client, cfg.versionId, cfg.docId, cfg.signal);
37836
- };
37837
- await write();
38705
+ const writer = new FullTagSetWriter({
38706
+ client: cfg.client,
38707
+ docId: cfg.docId,
38708
+ versionId: cfg.versionId,
38709
+ snapshot: target.tags,
38710
+ tag: { id: tag.id, value: cfg.status, name: tag.name, label: cfg.status },
38711
+ action: "set version file status",
38712
+ signal: cfg.signal,
38713
+ sleep: cfg.sleep,
38714
+ settleMs: cfg.tagSettleDelayMs
38715
+ });
38716
+ await writer.write();
37838
38717
  let reapplied = false;
37839
- let seen = await readBack();
38718
+ let seen = await writer.readBack();
37840
38719
  if (seen !== null && tagValueOf(seen.tags, tag.id) !== cfg.status) {
37841
38720
  reapplied = true;
37842
- refreshBase(seen);
37843
- await write();
37844
- seen = await readBack();
38721
+ writer.refreshBase(seen);
38722
+ await writer.write();
38723
+ seen = await writer.readBack();
37845
38724
  const now = seen === null ? void 0 : tagValueOf(seen.tags, tag.id);
37846
38725
  if (seen !== null && now !== cfg.status) {
37847
38726
  throw new Error(
@@ -37849,53 +38728,19 @@ async function runFileboxSetVersionFileStatus(cfg) {
37849
38728
  );
37850
38729
  }
37851
38730
  }
37852
- const dateBefore = target.date_tag_value;
37853
- const dateMoved = (v) => v !== null && typeof dateBefore === "number" && v.row.date_tag_value !== void 0 && v.row.date_tag_value !== dateBefore;
37854
- let dateUnknown = false;
37855
- if (dateMoved(seen)) {
37856
- err.write(
37857
- `warning: date_tag_value on version ${cfg.versionId} changed (${JSON.stringify(dateBefore)} -> ${JSON.stringify(seen.row.date_tag_value ?? null)}) when its File Status was set; re-applying the full tag set once
37858
- `
37859
- );
37860
- reapplied = true;
37861
- refreshBase(seen);
37862
- let refused = false;
37863
- try {
37864
- await write();
37865
- } catch (e) {
37866
- if (cfg.signal.aborted) {
37867
- throw e;
37868
- }
37869
- if (e instanceof TagWriteRefusedError) {
37870
- refused = true;
37871
- err.write(`warning: re-applying the full tag set was refused ${e.reason}
37872
- `);
37873
- } else {
37874
- err.write(
37875
- `warning: re-applying the full tag set failed (${e instanceof Error ? e.message : String(e)}); its outcome is unknown, reading the version again
37876
- `
37877
- );
37878
- }
37879
- }
37880
- const after = refused ? seen : await readBack();
37881
- if (after === null) {
37882
- dateUnknown = true;
37883
- err.write(
37884
- `warning: could not read version ${cfg.versionId} back to confirm its date_tag_value after re-applying the full tag set \u2014 check it with \`dr filebox versions ${cfg.docId} --json\`
37885
- `
37886
- );
37887
- } else {
37888
- seen = after;
37889
- }
37890
- }
37891
- const dateAfter = dateUnknown || seen === null || seen.row.date_tag_value === void 0 ? null : seen.row.date_tag_value;
37892
- const datePreserved = dateUnknown || seen === null || dateBefore === void 0 || seen.row.date_tag_value === void 0 ? null : !dateMoved(seen);
37893
- if (datePreserved === false) {
37894
- err.write(
37895
- `warning: date_tag_value on version ${cfg.versionId} is still ${JSON.stringify(dateAfter)} (was ${JSON.stringify(dateBefore)}) after one re-apply; its tags still carry the date. Restore it with \`dr filebox set-version-date-tag ${cfg.versionId} --doc ${cfg.docId} --<dimension> <date>\` (it sends the date tag alone; on a planning filebox use \`dr filebox set-version-cycle ${cfg.versionId} --doc ${cfg.docId} --date <date> --scenario <name>\` instead, which keeps the cycle), then check \`dr filebox versions ${cfg.docId} --json\`
37896
- `
37897
- );
37898
- }
38731
+ const kept = await keepDateTagValue({
38732
+ writer,
38733
+ seen,
38734
+ dateBefore: target.date_tag_value,
38735
+ versionId: cfg.versionId,
38736
+ docId: cfg.docId,
38737
+ what: "File Status",
38738
+ signal: cfg.signal,
38739
+ err
38740
+ });
38741
+ seen = kept.seen;
38742
+ reapplied ||= kept.reapplied;
38743
+ const { dateAfter, datePreserved } = kept;
37899
38744
  if (seen === null) {
37900
38745
  err.write(
37901
38746
  `warning: could not read version ${cfg.versionId} back to verify its File Status \u2014 confirm with \`dr filebox versions ${cfg.docId}\`
@@ -37934,10 +38779,7 @@ async function runFileboxSetVersionFileStatus(cfg) {
37934
38779
  `
37935
38780
  );
37936
38781
  if (datePreserved === false) {
37937
- cfg.out.write(
37938
- `date_tag_value NOT preserved: it reads ${JSON.stringify(dateAfter)}, was ${JSON.stringify(dateBefore)} (see the warning above).
37939
- `
37940
- );
38782
+ cfg.out.write(dateNotPreservedLine(dateAfter, target.date_tag_value));
37941
38783
  }
37942
38784
  if (demotedIds.length > 0) {
37943
38785
  cfg.out.write(
@@ -38515,6 +39357,7 @@ async function runFileboxDataQuerySync(cfg) {
38515
39357
  const dateTargets = [];
38516
39358
  const periodLabels = [];
38517
39359
  const waitPeriods = [];
39360
+ const pastOrFuturePeriods = /* @__PURE__ */ new Set();
38518
39361
  if (parsedDates.length > 0) {
38519
39362
  const details = await getDetails(cfg.client, cfg.id, {
38520
39363
  signal: cfg.signal
@@ -38535,6 +39378,11 @@ async function runFileboxDataQuerySync(cfg) {
38535
39378
  );
38536
39379
  }
38537
39380
  }
39381
+ const now = cfg.today ?? /* @__PURE__ */ new Date();
39382
+ const todays = [
39383
+ { year: now.getFullYear(), month0: now.getMonth(), day: now.getDate() },
39384
+ { year: now.getUTCFullYear(), month0: now.getUTCMonth(), day: now.getUTCDate() }
39385
+ ].map((t) => dateTags.map((tag) => resolveDateForSubType(tagSubType(tag), t).value));
38538
39386
  for (const parsed2 of parsedDates) {
38539
39387
  const resolved = dateTags.map((tag) => {
38540
39388
  const { value, label: label2 } = resolveDateForSubType(tagSubType(tag), parsed2);
@@ -38543,6 +39391,8 @@ async function runFileboxDataQuerySync(cfg) {
38543
39391
  dateTargets.push(
38544
39392
  JSON.stringify(resolved.map(({ id, name, value }) => ({ id, name, value })))
38545
39393
  );
39394
+ const isToday = todays.some((t) => resolved.every((r, i) => r.value === t[i]));
39395
+ if (!isToday) pastOrFuturePeriods.add(resolved.map((r) => r.value).join("|"));
38546
39396
  const label = resolved.map((r) => r.label).join(" + ");
38547
39397
  periodLabels.push(label);
38548
39398
  waitPeriods.push({
@@ -38557,7 +39407,31 @@ async function runFileboxDataQuerySync(cfg) {
38557
39407
  if (waitPeriods.length === 0) {
38558
39408
  waitPeriods.push({ label: "latest sync", tags: [] });
38559
39409
  }
38560
- const baseline = cfg.wait === void 0 ? void 0 : await captureDataQuerySyncBaseline(cfg.client, cfg.id, waitPeriods, cfg.signal);
39410
+ const warnings = [];
39411
+ let preReadDataQuery;
39412
+ if (pastOrFuturePeriods.size > 0) {
39413
+ const detected = await detectAggregatorBalanceQuery(cfg.client, cfg.id, cfg.signal);
39414
+ if (detected !== void 0) {
39415
+ preReadDataQuery = { raw: detected.dataQueryRaw };
39416
+ if (detected.aggregator !== void 0) {
39417
+ warnings.push(
39418
+ aggregatorBalanceWarning(cfg.id, detected.aggregator, pastOrFuturePeriods.size)
39419
+ );
39420
+ }
39421
+ }
39422
+ }
39423
+ const errOut = cfg.err ?? cfg.wait?.err ?? process.stderr;
39424
+ for (const w of warnings) {
39425
+ errOut.write(`warning: ${w}
39426
+ `);
39427
+ }
39428
+ const baseline = cfg.wait === void 0 ? void 0 : await captureDataQuerySyncBaseline(
39429
+ cfg.client,
39430
+ cfg.id,
39431
+ waitPeriods,
39432
+ cfg.signal,
39433
+ preReadDataQuery
39434
+ );
38561
39435
  const multipleTags = [...rawTags, ...dateTargets];
38562
39436
  const body = {};
38563
39437
  if (multipleTags.length > 0) {
@@ -38600,6 +39474,7 @@ async function runFileboxDataQuerySync(cfg) {
38600
39474
  out: cfg.out,
38601
39475
  errOut: cfg.wait.err ?? process.stderr,
38602
39476
  signal: cfg.signal,
39477
+ warnings,
38603
39478
  pollIntervalMs: cfg.wait.pollIntervalMs,
38604
39479
  maxPollIntervalMs: cfg.wait.maxPollIntervalMs,
38605
39480
  sleep: cfg.wait.sleep,
@@ -38707,9 +39582,9 @@ async function runFileboxDownload(cfg) {
38707
39582
  const fh = await fsPromises.open(target, "w");
38708
39583
  let total = 0;
38709
39584
  try {
38710
- for (const chunk of head) {
38711
- await fh.write(chunk);
38712
- total += chunk.length;
39585
+ for (const chunk2 of head) {
39586
+ await fh.write(chunk2);
39587
+ total += chunk2.length;
38713
39588
  }
38714
39589
  if (!streamDone) {
38715
39590
  while (true) {
@@ -39635,33 +40510,41 @@ function registerFileboxCommand(root) {
39635
40510
  ).requiredOption(
39636
40511
  "--status <s>",
39637
40512
  "New WORKFLOW value: Not Started | In Progress | Pending | Approved | Rejected. Setting Approved requires the `approve_workflow` permission server-side."
39638
- ).action(async (idStr, opts) => {
39639
- const versionId = parseFileboxIdArg(idStr);
39640
- if (opts.doc === void 0 || opts.doc === "") {
39641
- throw new Error("--doc is required");
39642
- }
39643
- const docId = parseFileboxIdArg(opts.doc);
39644
- const raw = opts.status ?? "";
39645
- if (!WORKFLOW_STATUSES.includes(raw)) {
39646
- throw new Error(
39647
- `invalid --status "${raw}" (expected one of: ${WORKFLOW_STATUSES.map((s) => `"${s}"`).join(" | ")})`
39648
- );
39649
- }
39650
- const status2 = raw;
39651
- const setup = await setupCall(opts.server, opts.account);
39652
- try {
39653
- await runFileboxSetVersionWorkflow({
39654
- client: setup.client,
39655
- docId,
39656
- versionId,
39657
- status: status2,
39658
- signal: setup.signal,
39659
- out: process.stdout
39660
- });
39661
- } finally {
39662
- setup.cleanup();
40513
+ ).option(
40514
+ "--json",
40515
+ "Output a JSON record describing the write instead of the human confirmation",
40516
+ false
40517
+ ).action(
40518
+ async (idStr, opts) => {
40519
+ const versionId = parseFileboxIdArg(idStr);
40520
+ if (opts.doc === void 0 || opts.doc === "") {
40521
+ throw new Error("--doc is required");
40522
+ }
40523
+ const docId = parseFileboxIdArg(opts.doc);
40524
+ const raw = opts.status ?? "";
40525
+ if (!WORKFLOW_STATUSES.includes(raw)) {
40526
+ throw new Error(
40527
+ `invalid --status "${raw}" (expected one of: ${WORKFLOW_STATUSES.map((s) => `"${s}"`).join(" | ")})`
40528
+ );
40529
+ }
40530
+ const status2 = raw;
40531
+ const setup = await setupCall(opts.server, opts.account, { timeoutMs: 5 * 6e4 });
40532
+ try {
40533
+ await runFileboxSetVersionWorkflow({
40534
+ client: setup.client,
40535
+ docId,
40536
+ versionId,
40537
+ status: status2,
40538
+ asJson: opts.json === true,
40539
+ signal: setup.signal,
40540
+ out: process.stdout,
40541
+ err: process.stderr
40542
+ });
40543
+ } finally {
40544
+ setup.cleanup();
40545
+ }
39663
40546
  }
39664
- });
40547
+ );
39665
40548
  addServerFlag(setVersionWorkflowCmd);
39666
40549
  addAccountFlag(setVersionWorkflowCmd);
39667
40550
  const setVersionFileStatusCmd = fb.command("set-version-file-status").description(
@@ -39885,7 +40768,7 @@ function registerFileboxCommand(root) {
39885
40768
  addServerFlag(dqClearCmd);
39886
40769
  addAccountFlag(dqClearCmd);
39887
40770
  const dqSyncCmd = dataQuery.command("sync").description(
39888
- `Manually trigger a sync of the filebox's data-query (async on server). --date YYYY-MM-DD (repeatable) scopes the sync to that date, truncated to whatever granularity the filebox's own date tag uses \u2014 e.g. --date 2026-07-01 syncs "July 2026" on a monthly filebox, "Q3 2026" on a quarterly one, or the exact day/ISO week on a daily/weekly one. Repeat to backfill several periods in one call.`
40771
+ `Manually trigger a sync of the filebox's data-query (async on server). --date YYYY-MM-DD (repeatable) scopes the sync to that date, truncated to whatever granularity the filebox's own date tag uses \u2014 e.g. --date 2026-07-01 syncs "July 2026" on a monthly filebox, "Q3 2026" on a quarterly one, or the exact day/ISO week on a daily/weekly one. Repeat to backfill several periods in one call. Balances from an aggregator (Plaid, Quiltt, Salt Edge, Flinks) are always today's balance whatever --date says, so backfilling them writes identical versions under different dates (the CLI warns): backfill only transactions, sync balances for the current period and let the nightly schedule build the history. Multi-period balance backfill works only on a direct bank API connection.`
39889
40772
  ).argument("<filebox_id>", "filebox id (positive integer)").option(
39890
40773
  "--date <YYYY-MM-DD>",
39891
40774
  "sync whatever date-tag period this date falls in, e.g. 2026-07-01. Repeatable.",
@@ -39932,6 +40815,7 @@ function registerFileboxCommand(root) {
39932
40815
  addServerFlag(dqSyncCmd);
39933
40816
  addAccountFlag(dqSyncCmd);
39934
40817
  registerFileboxAdminCommands(fb, setupCall, addServerFlag, addAccountFlag);
40818
+ registerFileboxKindCommands(fb);
39935
40819
  registerFileboxRescanVersionCommand(fb);
39936
40820
  }
39937
40821
 
@@ -45587,52 +46471,6 @@ async function getAuditExportStatus(client, params, options) {
45587
46471
  return out ?? { job_id: params.jobId, status: "PENDING" };
45588
46472
  }
45589
46473
 
45590
- // src/commands/_reconciliationDate.ts
45591
- function validateDateString(orig, flagName) {
45592
- if (!/^\d{4}-\d{2}-\d{2}$/.test(orig)) {
45593
- throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD)`);
45594
- }
45595
- const parts = orig.split("-").map(Number);
45596
- const [year, month1, day] = parts;
45597
- const probe = new Date(Date.UTC(year, month1 - 1, day));
45598
- if (probe.getUTCFullYear() !== year || probe.getUTCMonth() + 1 !== month1 || probe.getUTCDate() !== day) {
45599
- throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD)`);
45600
- }
45601
- return { year, month1, day };
45602
- }
45603
- function parseDateFlag(orig, flagName) {
45604
- let normalized = orig;
45605
- if (/^\d{4}-\d{2}$/.test(orig)) {
45606
- normalized = `${orig}-01`;
45607
- }
45608
- let year;
45609
- let month1;
45610
- try {
45611
- ({ year, month1 } = validateDateString(normalized, flagName));
45612
- } catch {
45613
- throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD or YYYY-MM)`);
45614
- }
45615
- return Math.floor(Date.UTC(year, month1, 0) / 1e3);
45616
- }
45617
- function parseDateFlagEpoch(orig, flagName) {
45618
- let normalized = orig;
45619
- if (/^\d{4}-\d{2}$/.test(orig)) {
45620
- normalized = `${orig}-01`;
45621
- }
45622
- let year;
45623
- let month1;
45624
- try {
45625
- ({ year, month1 } = validateDateString(normalized, flagName));
45626
- } catch {
45627
- throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD or YYYY-MM)`);
45628
- }
45629
- return Math.floor(Date.UTC(year, month1 - 1, 1, 12) / 1e3);
45630
- }
45631
- function parseDateFlagDay(orig, flagName) {
45632
- const { year, month1, day } = validateDateString(orig, flagName);
45633
- return Math.floor(Date.UTC(year, month1 - 1, day) / 1e3);
45634
- }
45635
-
45636
46474
  // src/commands/_reconciliationSettingsFile.ts
45637
46475
  var ALLOWED_FREQUENCIES = /* @__PURE__ */ new Set(["Monthly", "Quarterly", "Yearly", "Exclude"]);
45638
46476
  var REQUIRED_FIELDS = [
@@ -47029,17 +47867,17 @@ async function runReconciliationAccountsUpdateSettings(cfg) {
47029
47867
  execute: async () => {
47030
47868
  extendForBatch(cfg.extendTimeout, chunks.length);
47031
47869
  let applied = 0;
47032
- for (const chunk of chunks) {
47870
+ for (const chunk2 of chunks) {
47033
47871
  try {
47034
- await updateAccountSettings(cfg.client, chunk, { signal: cfg.signal });
47872
+ await updateAccountSettings(cfg.client, chunk2, { signal: cfg.signal });
47035
47873
  } catch (err) {
47036
47874
  const msg = err instanceof Error ? err.message : String(err);
47037
47875
  throw new Error(
47038
- `PATCH failed after ${applied} of ${changed.length} rows were written (rows ${applied + 1}\u2013${applied + chunk.length} may be partially applied): ${msg}
47876
+ `PATCH failed after ${applied} of ${changed.length} rows were written (rows ${applied + 1}\u2013${applied + chunk2.length} may be partially applied): ${msg}
47039
47877
  Re-run the same command without --confirm-token: rows that landed plan as unchanged, so the new plan covers only the rest.`
47040
47878
  );
47041
47879
  }
47042
- applied += chunk.length;
47880
+ applied += chunk2.length;
47043
47881
  }
47044
47882
  cfg.out.write(
47045
47883
  `Updated ${applied} account setting${plural(applied)} (${unchanged} unchanged).
@@ -47632,10 +48470,10 @@ async function writeMemberChunks(ctx, planned, items, chunkSize) {
47632
48470
  extendForBatch(ctx.extendTimeout, requests * 2);
47633
48471
  let applied = 0;
47634
48472
  for (let i = 0; i < items.length; i += chunkSize) {
47635
- const chunk = items.slice(i, i + chunkSize);
48473
+ const chunk2 = items.slice(i, i + chunkSize);
47636
48474
  let drift;
47637
48475
  try {
47638
- drift = await chunkDrift(ctx, planned, chunk);
48476
+ drift = await chunkDrift(ctx, planned, chunk2);
47639
48477
  } catch (err) {
47640
48478
  const msg = err instanceof Error ? err.message : String(err);
47641
48479
  throw new Error(
@@ -47651,23 +48489,23 @@ The tenant changed since the plan. Re-run the same command without --confirm-tok
47651
48489
  );
47652
48490
  }
47653
48491
  try {
47654
- await updateAccountMembers(ctx.client, chunk, { signal: ctx.signal });
48492
+ await updateAccountMembers(ctx.client, chunk2, { signal: ctx.signal });
47655
48493
  } catch (err) {
47656
48494
  const msg = err instanceof Error ? err.message : String(err);
47657
48495
  throw new Error(
47658
- `PATCH failed after ${applied} of ${items.length} account${plural(items.length)} were written (accounts ${i + 1}\u2013${i + chunk.length} may be partially applied): ${msg}
48496
+ `PATCH failed after ${applied} of ${items.length} account${plural(items.length)} were written (accounts ${i + 1}\u2013${i + chunk2.length} may be partially applied): ${msg}
47659
48497
  Re-run the same command without --confirm-token: rows that landed plan as unchanged, so the new plan covers only the rest.`
47660
48498
  );
47661
48499
  }
47662
- applied += chunk.length;
48500
+ applied += chunk2.length;
47663
48501
  }
47664
48502
  return applied;
47665
48503
  }
47666
- async function chunkDrift(cfg, planned, chunk) {
48504
+ async function chunkDrift(cfg, planned, chunk2) {
47667
48505
  const now = await getAccounts(cfg.client, { date: cfg.date }, { signal: cfg.signal });
47668
48506
  const nowById = new Map(now.map((a) => [a.account_id, a]));
47669
48507
  const drift = [];
47670
- for (const { account_id: id } of chunk) {
48508
+ for (const { account_id: id } of chunk2) {
47671
48509
  const before = planned.get(id);
47672
48510
  const after = nowById.get(id);
47673
48511
  const label = `${before.account_name} (${id})`;
@@ -58649,8 +59487,8 @@ function captureVersion() {
58649
59487
  return new Promise((resolve3) => {
58650
59488
  const child2 = spawn2("dr", ["--version"], { shell: true });
58651
59489
  let out = "";
58652
- child2.stdout.on("data", (chunk) => {
58653
- out += chunk.toString();
59490
+ child2.stdout.on("data", (chunk2) => {
59491
+ out += chunk2.toString();
58654
59492
  });
58655
59493
  child2.on("close", (code) => {
58656
59494
  if (code !== 0) {
@@ -62882,7 +63720,7 @@ async function pollExtractionStatus(client, contractFileIds, options = {}) {
62882
63720
  if (outstanding.length === 0) break;
62883
63721
  if (now() > deadline || inner.signal.aborted) break;
62884
63722
  for (let i = 0; i < outstanding.length; i += MAX_BULK_STATUS_IDS) {
62885
- const chunk = outstanding.slice(i, i + MAX_BULK_STATUS_IDS);
63723
+ const chunk2 = outstanding.slice(i, i + MAX_BULK_STATUS_IDS);
62886
63724
  const tickAc = new AbortController();
62887
63725
  const onInnerAbort = () => tickAc.abort(inner.signal.reason);
62888
63726
  if (inner.signal.aborted) {
@@ -62891,13 +63729,13 @@ async function pollExtractionStatus(client, contractFileIds, options = {}) {
62891
63729
  inner.signal.addEventListener("abort", onInnerAbort, { once: true });
62892
63730
  }
62893
63731
  try {
62894
- const resp = await getBulkProcessStatus(client, chunk, {
63732
+ const resp = await getBulkProcessStatus(client, chunk2, {
62895
63733
  signal: tickAc.signal
62896
63734
  });
62897
- for (let j = 0; j < chunk.length; j++) {
63735
+ for (let j = 0; j < chunk2.length; j++) {
62898
63736
  const entry = resp.results[j];
62899
63737
  if (entry !== void 0 && isTerminalAiStatus(entry.ai_processing_status)) {
62900
- resolved.set(chunk[j], entry);
63738
+ resolved.set(chunk2[j], entry);
62901
63739
  }
62902
63740
  }
62903
63741
  } catch (err) {
@@ -64658,10 +65496,10 @@ async function extractSync(cfg, parts) {
64658
65496
  const chunked = chunkContracts([...parts], MAX_FILES_PER_BATCH);
64659
65497
  let abortedMidRun = false;
64660
65498
  for (let ci = 0; ci < chunked.length; ci++) {
64661
- const chunk = chunked[ci];
65499
+ const chunk2 = chunked[ci];
64662
65500
  try {
64663
65501
  const results = await retryAsync(
64664
- () => uploadFilesWithAi(cfg.client, chunk, {
65502
+ () => uploadFilesWithAi(cfg.client, chunk2, {
64665
65503
  isAdditional: cfg.isAdditional,
64666
65504
  signal: cfg.signal
64667
65505
  }),
@@ -64679,7 +65517,7 @@ async function extractSync(cfg, parts) {
64679
65517
  }
64680
65518
  );
64681
65519
  allResults.push(...results);
64682
- for (const part of chunk) {
65520
+ for (const part of chunk2) {
64683
65521
  sourceFiles.push(part.sourcePath);
64684
65522
  outcomes.push({ path: part.sourcePath, ok: true, detail: "extracted" });
64685
65523
  }
@@ -64687,7 +65525,7 @@ async function extractSync(cfg, parts) {
64687
65525
  const msg = err instanceof Error ? err.message : String(err);
64688
65526
  const kind = classifyFailure(err);
64689
65527
  const detail = kind === "ambiguous" ? `${msg} \u2014 upstream may still be processing; check before re-sending` : msg;
64690
- for (const part of chunk) {
65528
+ for (const part of chunk2) {
64691
65529
  outcomes.push({ path: part.sourcePath, ok: false, detail });
64692
65530
  }
64693
65531
  if (kind === "abort") {
@@ -67694,8 +68532,8 @@ Pipe it (printf '%s\\n' "$APP_PASSWORD" | dr desk smtp set \u2026) or use --pass
67694
68532
  }
67695
68533
  const chunks = [];
67696
68534
  let total = 0;
67697
- for await (const chunk of process.stdin) {
67698
- const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
68535
+ for await (const chunk2 of process.stdin) {
68536
+ const buf = Buffer.isBuffer(chunk2) ? chunk2 : Buffer.from(String(chunk2));
67699
68537
  total += buf.length;
67700
68538
  if (total > maxBytes) {
67701
68539
  throw new Error(