@datarails-ai/dr-cli 1.6.130 → 1.6.131

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.130",
11
+ version: "1.6.131",
12
12
  description: "Datarails CLI",
13
13
  type: "module",
14
14
  engines: {
@@ -40,7 +40,7 @@ var package_default = {
40
40
  postinstall: "node scripts/postinstall.mjs"
41
41
  },
42
42
  dependencies: {
43
- "@datarails-ai/dr-appspace-cli": "0.12.8",
43
+ "@datarails-ai/dr-appspace-cli": "0.12.9",
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-13T13:04:51.581Z";
72
+ var BUILD_TIME = "2026-10-14T12:32:00.887Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -2268,6 +2268,13 @@ var AuthorizedClient = class _AuthorizedClient {
2268
2268
  async postJSON(path12, payload, options) {
2269
2269
  return this.requestJson("POST", path12, payload, options);
2270
2270
  }
2271
+ /**
2272
+ * {@link AuthorizedClient.postJSON}, also returning the final response's headers. Same
2273
+ * recovery ladder and error messages.
2274
+ */
2275
+ async postJSONWithHeaders(path12, payload, options) {
2276
+ return this.requestJsonWithHeaders("POST", path12, payload, options);
2277
+ }
2271
2278
  /**
2272
2279
  * Issues a `PUT` with `payload` JSON-encoded as the body and decodes the
2273
2280
  * JSON response into `T`. Mirrors `client.go:148` `PutJSON`.
@@ -2498,6 +2505,10 @@ var AuthorizedClient = class _AuthorizedClient {
2498
2505
  * replayable); they go through `do()` directly. See Task 1.14.
2499
2506
  */
2500
2507
  async requestJson(method, path12, payload, options) {
2508
+ return (await this.requestJsonWithHeaders(method, path12, payload, options)).data;
2509
+ }
2510
+ /** {@link AuthorizedClient.requestJson}, also returning the final response's headers. */
2511
+ async requestJsonWithHeaders(method, path12, payload, options) {
2501
2512
  const body = payload === void 0 ? void 0 : JSON.stringify(payload);
2502
2513
  const sentBearer = this.hasBearer();
2503
2514
  let res = await this.doWithAuth(method, path12, body, options, true);
@@ -2545,9 +2556,9 @@ var AuthorizedClient = class _AuthorizedClient {
2545
2556
  await this._store.touch(this._serverKey, this._email);
2546
2557
  } catch {
2547
2558
  }
2548
- if (text2 === "" || text2 === '""') return null;
2559
+ if (text2 === "" || text2 === '""') return { data: null, headers: res.headers };
2549
2560
  try {
2550
- return JSON.parse(text2);
2561
+ return { data: JSON.parse(text2), headers: res.headers };
2551
2562
  } catch (err) {
2552
2563
  const msg = err instanceof Error ? err.message : String(err);
2553
2564
  throw new Error(`${method} ${path12}: decode: ${msg}`);
@@ -3699,6 +3710,9 @@ var PROD_GATE_TIER_B = /* @__PURE__ */ new Set([
3699
3710
  // the pre-write `export` backup restores any rule the batch got wrong.
3700
3711
  "error-enrichment-batch|create",
3701
3712
  "error-enrichment-batch|update",
3713
+ // A new classification type is additive: it changes no existing rule and
3714
+ // only widens the names a rule may be filed under.
3715
+ "error-enrichment-type|create",
3702
3716
  // cash (dr cash …). rules delete / delete-all / hidden-rules delete → Tier A.
3703
3717
  "cash-rule|update",
3704
3718
  "cash-rules|reorder",
@@ -3754,6 +3768,16 @@ var PROD_GATE_TIER_B = /* @__PURE__ */ new Set([
3754
3768
  "filebox|set-sync-frequency",
3755
3769
  "filebox|lock",
3756
3770
  "filebox-date-tag|lock",
3771
+ // SuiteQL custom reports (dr filebox custom-reports create / apply). Both rewrite one
3772
+ // filebox's query, as the ungated `filebox data-query set` does, and are undone by
3773
+ // re-applying the previous query; create also adds a report row the UI can delete.
3774
+ "filebox-custom-report|create",
3775
+ "filebox-custom-report|apply",
3776
+ // NetSuite readiness stamp (dr integrations netsuite readiness). Writes one config key,
3777
+ // bundle_version 3, on one connection, only after a CONFIRMED probe; the server refuses a
3778
+ // connection that is not a 3.0 migration candidate (409), and the ungated
3779
+ // `integrations update` can rewrite the key.
3780
+ "netsuite-bundle-version|stamp",
3757
3781
  // collection-processes (dr collection-processes …).
3758
3782
  "collection-process|create",
3759
3783
  "collection-process|set-tag-config",
@@ -5843,6 +5867,52 @@ function isBadGatewayError(err) {
5843
5867
  if (/^\S+ \S+: HTTP 502(?::|\s|$)/.test(err.message)) return true;
5844
5868
  return /\(HTTP 502\)$/.test(err.message);
5845
5869
  }
5870
+ async function readRawJson(res) {
5871
+ const rawText = await res.text();
5872
+ let body = null;
5873
+ try {
5874
+ body = rawText ? JSON.parse(rawText) : null;
5875
+ } catch {
5876
+ body = null;
5877
+ }
5878
+ return { status: res.status, body, rawText };
5879
+ }
5880
+ var DETAIL_MAX_CHARS = 300;
5881
+ function isSuccess(status2) {
5882
+ return status2 >= 200 && status2 < 300;
5883
+ }
5884
+ function rawErrorDetail(res) {
5885
+ return truncate4(uncappedErrorDetail(res), DETAIL_MAX_CHARS);
5886
+ }
5887
+ function uncappedErrorDetail(res) {
5888
+ const { body } = res;
5889
+ if (typeof body === "string") return body;
5890
+ if (body !== null && typeof body === "object" && !Array.isArray(body)) {
5891
+ const detail = body.detail;
5892
+ if (typeof detail === "string") return detail;
5893
+ const fields = [];
5894
+ for (const [field, value] of Object.entries(body)) {
5895
+ const messages = Array.isArray(value) ? value : [value];
5896
+ if (messages.length > 0 && messages.every((m) => typeof m === "string")) {
5897
+ fields.push(`${field}: ${messages.join(" ")}`);
5898
+ }
5899
+ }
5900
+ if (fields.length > 0) return fields.join("; ");
5901
+ }
5902
+ return res.rawText.trim();
5903
+ }
5904
+ var FAILURE_PHRASES = {
5905
+ 400: "rejected",
5906
+ 403: "refused",
5907
+ 404: "not found, or not visible to this account",
5908
+ 409: "refused",
5909
+ 502: "failed upstream"
5910
+ };
5911
+ function describeRawFailure(what, res, overrides = {}) {
5912
+ const detail = decodeShape(rawErrorDetail(res));
5913
+ const phrase = overrides[res.status] ?? FAILURE_PHRASES[res.status];
5914
+ return phrase === void 0 ? `${what}: unexpected status ${res.status}: ${detail}` : `${what} ${phrase} (${res.status}): ${detail}`;
5915
+ }
5846
5916
 
5847
5917
  // src/api/users.ts
5848
5918
  async function list2(client, options) {
@@ -19114,12 +19184,52 @@ async function clearDataQuery(client, id, reqOptions) {
19114
19184
  return client.do("DELETE", `/api/documents/${id}/data_query`, void 0, reqOptions);
19115
19185
  }
19116
19186
  async function syncDataQuery(client, id, body, reqOptions) {
19117
- const out = await client.postJSON(
19187
+ return (await syncDataQueryTimed(client, id, body, reqOptions)).body;
19188
+ }
19189
+ async function syncDataQueryTimed(client, id, body, reqOptions) {
19190
+ const { data: data4, headers } = await client.postJSONWithHeaders(
19118
19191
  `/api/documents/${id}/data_query_sync`,
19119
19192
  body,
19120
19193
  reqOptions
19121
19194
  );
19122
- return out ?? {};
19195
+ const serverTimeMs = Date.parse(headers.get("date") ?? "");
19196
+ return {
19197
+ body: data4 !== null && typeof data4 === "object" && !Array.isArray(data4) ? data4 : {},
19198
+ serverTimeMs: Number.isFinite(serverTimeMs) ? serverTimeMs : void 0
19199
+ };
19200
+ }
19201
+ async function listDocumentEvents(client, id, action, page, reqOptions) {
19202
+ return client.getJSON(
19203
+ `/api/documents/${id}/logs?action=${encodeURIComponent(action)}&skip=${page.skip}&limit=${page.limit}`,
19204
+ reqOptions
19205
+ );
19206
+ }
19207
+ async function listCustomReports(client, id, reqOptions) {
19208
+ const res = await client.do(
19209
+ "GET",
19210
+ `/api/documents/${id}/data_query_custom_reports`,
19211
+ void 0,
19212
+ reqOptions
19213
+ );
19214
+ return readRawJson(res);
19215
+ }
19216
+ async function createCustomReport(client, id, body, reqOptions) {
19217
+ const res = await client.do(
19218
+ "POST",
19219
+ `/api/documents/${id}/data_query_custom_reports`,
19220
+ JSON.stringify(body),
19221
+ reqOptions
19222
+ );
19223
+ return readRawJson(res);
19224
+ }
19225
+ async function applyCustomReport(client, id, reportId, reqOptions) {
19226
+ const res = await client.do(
19227
+ "POST",
19228
+ `/api/documents/${id}/data_query_custom_reports/${reportId}/apply`,
19229
+ JSON.stringify({}),
19230
+ reqOptions
19231
+ );
19232
+ return readRawJson(res);
19123
19233
  }
19124
19234
  async function getAutoCalculateSettings(client, docId, reqOptions) {
19125
19235
  const path12 = `/api/documents/${docId}/auto_calculate_settings`;
@@ -28878,6 +28988,7 @@ import * as path5 from "path";
28878
28988
 
28879
28989
  // src/api/errorEnrichments.ts
28880
28990
  var BASE = "/api/v3/data_integrations/error-enrichments/";
28991
+ var TYPES_BASE = "/api/v3/data_integrations/error-enrichment-types/";
28881
28992
  function filtersQuery(filters) {
28882
28993
  if (filters === void 0) return "";
28883
28994
  const params = new URLSearchParams();
@@ -28964,9 +29075,31 @@ async function testRegex(client, regex, samples, options) {
28964
29075
  }
28965
29076
  return out;
28966
29077
  }
28967
-
28968
- // src/types/errorEnrichment.ts
28969
- var ENRICHMENT_CLASSIFICATIONS = ["DataSource", "Filebox", "Unclassified"];
29078
+ async function listTypes(client, options) {
29079
+ const res = await client.do("GET", TYPES_BASE, void 0, options);
29080
+ const text2 = await res.text();
29081
+ if (res.status === 404) return null;
29082
+ if (res.status !== 200) {
29083
+ throw new Error(`GET ${TYPES_BASE}: HTTP ${res.status}: ${text2.trim().slice(0, 200)}`);
29084
+ }
29085
+ let parsed2;
29086
+ try {
29087
+ parsed2 = JSON.parse(text2);
29088
+ } catch {
29089
+ parsed2 = void 0;
29090
+ }
29091
+ if (!Array.isArray(parsed2)) {
29092
+ throw new Error(`GET ${TYPES_BASE}: unexpected response: ${text2.trim().slice(0, 200)}`);
29093
+ }
29094
+ return parsed2;
29095
+ }
29096
+ async function createType(client, type, options) {
29097
+ const out = await client.postJSON(TYPES_BASE, type, options);
29098
+ if (out === null) {
29099
+ throw new Error("error-enrichment-types create: empty response");
29100
+ }
29101
+ return out;
29102
+ }
28970
29103
 
28971
29104
  // src/commands/errorEnrichments.ts
28972
29105
  function writeTable22(out, rows) {
@@ -29176,7 +29309,10 @@ async function runEnrichmentsList(cfg) {
29176
29309
  const results = await sweep(
29177
29310
  cfg.servers,
29178
29311
  cfg.makeSetup,
29179
- (client, signal) => list19(client, cfg.filters, { signal }),
29312
+ async (client, signal) => {
29313
+ await requireKnownClassifications(client, cfg.filters, signal);
29314
+ return list19(client, cfg.filters, { signal });
29315
+ },
29180
29316
  cfg.errOut
29181
29317
  );
29182
29318
  const single = cfg.servers.length === 1;
@@ -29204,7 +29340,10 @@ async function runEnrichmentsStats(cfg) {
29204
29340
  const results = await sweep(
29205
29341
  cfg.servers,
29206
29342
  cfg.makeSetup,
29207
- (client, signal) => stats(client, cfg.filters, { signal }),
29343
+ async (client, signal) => {
29344
+ await requireKnownClassifications(client, cfg.filters, signal);
29345
+ return stats(client, cfg.filters, { signal });
29346
+ },
29208
29347
  cfg.errOut
29209
29348
  );
29210
29349
  const single = cfg.servers.length === 1;
@@ -29232,6 +29371,47 @@ async function runEnrichmentsStats(cfg) {
29232
29371
  }
29233
29372
  requireSweepSuccess(results);
29234
29373
  }
29374
+ function renderTypesTable(out, types) {
29375
+ if (types.length === 0) {
29376
+ out.write("(no types)\n");
29377
+ return;
29378
+ }
29379
+ const rows = [{ cells: ["NAME", "DESCRIPTION"] }];
29380
+ for (const t of types) rows.push({ cells: [t.name, oneLine2(t.description, 80)] });
29381
+ writeTable22(out, rows);
29382
+ }
29383
+ async function runEnrichmentTypesList(cfg) {
29384
+ const results = await sweep(
29385
+ cfg.servers,
29386
+ cfg.makeSetup,
29387
+ async (client, signal) => {
29388
+ const types = await listTypes(client, { signal });
29389
+ if (types === null) throw new Error("this server does not serve error-enrichment types yet");
29390
+ return types;
29391
+ },
29392
+ cfg.errOut
29393
+ );
29394
+ const single = cfg.servers.length === 1;
29395
+ const only = results[0];
29396
+ if (cfg.asJson) {
29397
+ if (single) {
29398
+ if (only?.status === "ok") writeJSONIndented(cfg.out, only.value ?? []);
29399
+ } else {
29400
+ writeJSONIndented(cfg.out, sweepJson(results, "types"));
29401
+ }
29402
+ } else {
29403
+ for (const r of results) {
29404
+ if (!single) {
29405
+ const suffix = r.status === "ok" ? `${(r.value ?? []).length} type${plural((r.value ?? []).length)}` : `${r.status}: ${r.message ?? ""}`;
29406
+ cfg.out.write(`== ${r.server} \u2014 ${suffix} ==
29407
+ `);
29408
+ }
29409
+ if (r.status === "ok") renderTypesTable(cfg.out, r.value ?? []);
29410
+ if (!single) cfg.out.write("\n");
29411
+ }
29412
+ }
29413
+ requireSweepSuccess(results);
29414
+ }
29235
29415
  function oneLine2(s, max) {
29236
29416
  return truncate3(s.replace(/\s+/g, " "), max);
29237
29417
  }
@@ -29726,6 +29906,45 @@ async function runEnrichmentsImport(cfg) {
29726
29906
  }
29727
29907
  });
29728
29908
  }
29909
+ function requireTypeName(name) {
29910
+ if (name.trim() === "") {
29911
+ throw new Error("--name must not be empty");
29912
+ }
29913
+ }
29914
+ async function runEnrichmentTypesCreate(cfg) {
29915
+ requireTypeName(cfg.name);
29916
+ const action = {
29917
+ resource: "error-enrichment-type",
29918
+ id: cfg.name,
29919
+ verb: "create"
29920
+ };
29921
+ await preflightConfirmToken(action, cfg.token);
29922
+ const description = cfg.description === void 0 ? "" : ` --description ${shellQuote(cfg.description)}`;
29923
+ await twoStepConfirm({
29924
+ out: cfg.out,
29925
+ action,
29926
+ token: cfg.token,
29927
+ server: cfg.serverFlag,
29928
+ confirmCmd: `dr error-enrichments types create --name ${shellQuote(cfg.name)}${description} ${cmdTail(cfg.serverFlag, cfg.accountFlag, void 0)}`,
29929
+ describe: async () => [
29930
+ `type: ${cfg.name}`,
29931
+ ...cfg.description === void 0 ? [] : [`description: ${truncate3(cfg.description, 120)}`],
29932
+ `adds a classification type on ${cfg.serverFlag}; this CLI has no verb to rename or remove it`
29933
+ ],
29934
+ execute: async () => {
29935
+ const created = await createType(
29936
+ cfg.client,
29937
+ {
29938
+ name: cfg.name,
29939
+ ...cfg.description === void 0 ? {} : { description: cfg.description }
29940
+ },
29941
+ { signal: cfg.signal }
29942
+ );
29943
+ cfg.out.write(`Created error-enrichment type ${created.name} on ${cfg.serverFlag}.
29944
+ `);
29945
+ }
29946
+ });
29947
+ }
29729
29948
  function collectArray6(value, previous) {
29730
29949
  return [...previous, value];
29731
29950
  }
@@ -29908,18 +30127,20 @@ function resolveUpdateTargets(idArgs, entries, rule) {
29908
30127
  }
29909
30128
  return { patches: ids.map((id) => ({ id, patch: rule })), mode: "shared" };
29910
30129
  }
29911
- function requireKnownClassifications(values) {
29912
- const unknown = (values ?? []).filter(
29913
- (v) => !ENRICHMENT_CLASSIFICATIONS.includes(v)
29914
- );
30130
+ async function requireKnownClassifications(client, filters, signal) {
30131
+ const wanted = filters.classifications ?? [];
30132
+ if (wanted.length === 0) return;
30133
+ const types = await listTypes(client, { signal });
30134
+ if (types === null) return;
30135
+ const known = new Set(types.map((t) => t.name));
30136
+ const unknown = wanted.filter((v) => !known.has(v));
29915
30137
  if (unknown.length > 0) {
29916
30138
  throw new Error(
29917
- `unknown --classification ${unknown.map((v) => JSON.stringify(v)).join(", ")}. Valid: ${ENRICHMENT_CLASSIFICATIONS.join(", ")}`
30139
+ `unknown --classification ${unknown.map((v) => JSON.stringify(v)).join(", ")}. Valid: ${types.map((t) => t.name).join(", ")} (see dr error-enrichments types list)`
29918
30140
  );
29919
30141
  }
29920
30142
  }
29921
30143
  function filtersFromOpts(opts) {
29922
- requireKnownClassifications(opts.classification);
29923
30144
  return {
29924
30145
  integrationTypes: opts.integrationType,
29925
30146
  classifications: opts.classification,
@@ -29938,7 +30159,10 @@ function addRuleFieldFlags(cmd, payloadHelp) {
29938
30159
  'Raw error example (repeatable). To clear all examples, use --payload-file with {"examples": []}',
29939
30160
  collectArray6,
29940
30161
  []
29941
- ).option("--classification <c>", "DataSource | Filebox | Unclassified").option("--approved <bool>", "true | false");
30162
+ ).option(
30163
+ "--classification <c>",
30164
+ "Classification type name (see `dr error-enrichments types list`)"
30165
+ ).option("--approved <bool>", "true | false");
29942
30166
  }
29943
30167
  function addSweepFlags(cmd) {
29944
30168
  return cmd.option(
@@ -29953,7 +30177,12 @@ function addFilterFlags(cmd) {
29953
30177
  "Filter by integration type (repeatable)",
29954
30178
  collectArray6,
29955
30179
  []
29956
- ).option("--classification <c>", "Filter by classification (repeatable)", collectArray6, []).option("--approved <bool>", "Filter by approved state (true | false)").option("--search <q>", "Search pattern / description / type name");
30180
+ ).option(
30181
+ "--classification <c>",
30182
+ "Filter by classification type name (repeatable; see `dr error-enrichments types list`)",
30183
+ collectArray6,
30184
+ []
30185
+ ).option("--approved <bool>", "Filter by approved state (true | false)").option("--search <q>", "Search pattern / description / type name");
29957
30186
  }
29958
30187
  function defaultSweepSetup(accountFlag) {
29959
30188
  return (serverKey) => setupCall(serverKey, accountFlag);
@@ -30204,6 +30433,49 @@ function registerErrorEnrichmentsCommand(root) {
30204
30433
  });
30205
30434
  addServerFlag(importCmd);
30206
30435
  addAccountFlag(importCmd);
30436
+ const types = group.command("types").description("Manage the classification types rules can be filed under (per-server)");
30437
+ const typesListCmd = types.command("list").description("List classification types on one server or across servers").option("--json", "Output raw JSON instead of tables", false).action(
30438
+ async (opts) => {
30439
+ const servers = resolveTargetServers({
30440
+ server: opts.server,
30441
+ allServers: opts.allServers === true,
30442
+ serversCsv: opts.servers,
30443
+ includeTest: opts.includeTest === true
30444
+ });
30445
+ await runEnrichmentTypesList({
30446
+ servers,
30447
+ makeSetup: defaultSweepSetup(normaliseAccount(opts.account)),
30448
+ asJson: opts.json === true,
30449
+ out: process.stdout,
30450
+ errOut: process.stderr
30451
+ });
30452
+ }
30453
+ );
30454
+ addServerFlag(typesListCmd);
30455
+ addAccountFlag(typesListCmd);
30456
+ addSweepFlags(typesListCmd);
30457
+ const typesCreateCmd = types.command("create").description("Add a classification type (two-step confirm)").requiredOption("--name <name>", "Type name, e.g. Data-source:Timeout").option("--description <text>", "What the type covers").option("--confirm-token <token>", "Token from the plan step").action(
30458
+ async (opts) => {
30459
+ requireTypeName(opts.name);
30460
+ const setup = await setupCall(opts.server, normaliseAccount(opts.account));
30461
+ try {
30462
+ await runEnrichmentTypesCreate({
30463
+ client: setup.client,
30464
+ name: opts.name,
30465
+ description: opts.description,
30466
+ token: opts.confirmToken,
30467
+ serverFlag: resolveServer(opts.server),
30468
+ accountFlag: normaliseAccount(opts.account),
30469
+ signal: setup.signal,
30470
+ out: process.stdout
30471
+ });
30472
+ } finally {
30473
+ setup.cleanup();
30474
+ }
30475
+ }
30476
+ );
30477
+ addServerFlag(typesCreateCmd);
30478
+ addAccountFlag(typesCreateCmd);
30207
30479
  }
30208
30480
 
30209
30481
  // src/commands/skill.ts
@@ -34143,6 +34415,8 @@ function registerDocumentsCommand(root) {
34143
34415
 
34144
34416
  // src/commands/integrations.ts
34145
34417
  import { Option as Option2 } from "commander";
34418
+ import { constants as fsConstants } from "fs";
34419
+ import { lstat, open as open2 } from "fs/promises";
34146
34420
 
34147
34421
  // src/api/integrations.ts
34148
34422
  async function list23(client, opts, reqOptions) {
@@ -34167,7 +34441,7 @@ async function getDataSourcesPage(client, page, reqOptions) {
34167
34441
  const out = await client.getJSON(path12, reqOptions);
34168
34442
  return out ?? { next: null, results: [] };
34169
34443
  }
34170
- async function listTypes(client, search, reqOptions) {
34444
+ async function listTypes2(client, search, reqOptions) {
34171
34445
  let path12 = "/api/v3/data_integrations/types/";
34172
34446
  const s = (search ?? "").trim();
34173
34447
  if (s !== "") {
@@ -34272,14 +34546,25 @@ async function sftpProvision(client, fileboxId, body, reqOptions) {
34272
34546
  JSON.stringify(body),
34273
34547
  reqOptions
34274
34548
  );
34275
- const rawText = await res.text();
34276
- let parsed2 = null;
34277
- try {
34278
- parsed2 = rawText ? JSON.parse(rawText) : null;
34279
- } catch {
34280
- parsed2 = null;
34281
- }
34282
- return { status: res.status, body: parsed2, rawText };
34549
+ return readRawJson(res);
34550
+ }
34551
+ async function netsuiteBundleReadiness(client, id, dryRun, reqOptions) {
34552
+ const res = await client.do(
34553
+ "POST",
34554
+ `/api/v3/data_integrations/data-sources/${id}/netsuite/bundle-readiness`,
34555
+ JSON.stringify({ dry_run: dryRun }),
34556
+ reqOptions
34557
+ );
34558
+ return readRawJson(res);
34559
+ }
34560
+ async function netsuiteSavedSearchSql(client, id, savedSearchId, reqOptions) {
34561
+ const res = await client.do(
34562
+ "GET",
34563
+ `/api/v3/data_integrations/data-sources/${id}/netsuite/saved-search-sql?saved_search_id=${encodeURIComponent(savedSearchId)}`,
34564
+ void 0,
34565
+ reqOptions
34566
+ );
34567
+ return readRawJson(res);
34283
34568
  }
34284
34569
  async function shareUsers2(client, id, emails, reqOptions) {
34285
34570
  const out = await client.postJSON(
@@ -34356,7 +34641,7 @@ async function itResync(client, id, reqOptions) {
34356
34641
  // src/commands/filebox.ts
34357
34642
  import { promises as fsPromises, existsSync as existsSync2 } from "fs";
34358
34643
  import * as nodePath from "path";
34359
- import { createHash as createHash9 } from "crypto";
34644
+ import { createHash as createHash10 } from "crypto";
34360
34645
 
34361
34646
  // src/commands/filebox_admin.ts
34362
34647
  import { promises as fs15 } from "fs";
@@ -36420,6 +36705,290 @@ function registerFileboxRescanVersionCommand(fb) {
36420
36705
  addAccountFlag(cmd);
36421
36706
  }
36422
36707
 
36708
+ // src/commands/filebox_customReports.ts
36709
+ import { createHash as createHash9 } from "crypto";
36710
+ import { readFile as readFile5 } from "fs/promises";
36711
+ function shortHash4(input) {
36712
+ return createHash9("sha256").update(input).digest("hex").slice(0, 12);
36713
+ }
36714
+ function isCustomReport(v) {
36715
+ if (v === null || typeof v !== "object") return false;
36716
+ const r = v;
36717
+ return typeof r.id === "number" && typeof r.name === "string";
36718
+ }
36719
+ async function fetchCustomReports(client, fileboxId, signal) {
36720
+ const res = await listCustomReports(client, fileboxId, { signal });
36721
+ if (!isSuccess(res.status)) {
36722
+ throw new Error(describeRawFailure("list custom reports", res));
36723
+ }
36724
+ const body = decodeShape(res.body);
36725
+ const reports = body?.custom_reports;
36726
+ if (!Array.isArray(reports) || !reports.every(isCustomReport)) {
36727
+ throw new Error(
36728
+ `list custom reports: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
36729
+ );
36730
+ }
36731
+ return { body, reports };
36732
+ }
36733
+ function usedBy(n) {
36734
+ const count = typeof n === "number" ? n : 0;
36735
+ return `${count} filebox${count === 1 ? "" : "es"}`;
36736
+ }
36737
+ function writeLinkResult(cfg, what, res, headline) {
36738
+ if (!isSuccess(res.status)) {
36739
+ throw new Error(describeRawFailure(what, res));
36740
+ }
36741
+ const body = decodeShape(res.body);
36742
+ if (body === null || typeof body !== "object" || !isCustomReport(body.custom_report)) {
36743
+ throw new Error(
36744
+ `${what}: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
36745
+ );
36746
+ }
36747
+ if (cfg.asJson) {
36748
+ writeJSONIndented(cfg.out, body);
36749
+ return;
36750
+ }
36751
+ const report = body.custom_report;
36752
+ cfg.out.write(`${headline(report)} (used by ${usedBy(report.usage_count)}).
36753
+ `);
36754
+ }
36755
+ function describeCurrentQuery(raw) {
36756
+ let config = raw?.config;
36757
+ if (typeof config === "string") {
36758
+ try {
36759
+ config = JSON.parse(config);
36760
+ } catch {
36761
+ config = null;
36762
+ }
36763
+ }
36764
+ if (config === null || typeof config !== "object") return "no data query";
36765
+ const c = config;
36766
+ const type = typeof c["objectType"] === "string" && c["objectType"] !== "" ? c["objectType"] : "-";
36767
+ const key = c["source_query_key"];
36768
+ const reportId = c["custom_report_id"];
36769
+ const source = typeof key === "string" && key !== "" ? `library ${key}` : typeof reportId === "number" ? `custom report #${reportId}` : "custom";
36770
+ const query = c["query"];
36771
+ const length = typeof query === "string" ? `${query.length} chars` : "none";
36772
+ return `objectType ${type}, source ${source}, query ${length}`;
36773
+ }
36774
+ async function describeFileboxTarget(client, fileboxId, signal) {
36775
+ const details = decodeShape(await getDetails(client, fileboxId, { signal }));
36776
+ const dataQuery = decodeShape(await getDataQueryRaw(client, fileboxId, { signal }));
36777
+ return [`filebox: ${fileboxId} "${details.name}"`, `current: ${describeCurrentQuery(dataQuery)}`];
36778
+ }
36779
+ async function runFileboxCustomReportsList(cfg) {
36780
+ const { body, reports } = await fetchCustomReports(cfg.client, cfg.fileboxId, cfg.signal);
36781
+ if (cfg.asJson) {
36782
+ writeJSONIndented(cfg.out, body);
36783
+ return;
36784
+ }
36785
+ if (reports.length === 0) {
36786
+ cfg.out.write(`No custom reports on the connection of filebox ${cfg.fileboxId}.
36787
+ `);
36788
+ return;
36789
+ }
36790
+ const rows = [
36791
+ ["ID", "NAME", "USED BY"],
36792
+ ...reports.map((r) => [String(r.id), r.name, usedBy(r.usage_count)])
36793
+ ];
36794
+ const widths = [0, 1].map((i) => Math.max(...rows.map((row) => (row[i] ?? "").length)));
36795
+ for (const row of rows) {
36796
+ const [id = "", name = "", used = ""] = row;
36797
+ cfg.out.write(
36798
+ `${id.padEnd((widths[0] ?? 0) + 2)}${name.padEnd((widths[1] ?? 0) + 2)}${used}
36799
+ `
36800
+ );
36801
+ }
36802
+ cfg.out.write(`${reports.length} custom report${plural(reports.length)}
36803
+ `);
36804
+ }
36805
+ function customReportCreateAction(fileboxId, name, query) {
36806
+ const hash = shortHash4(JSON.stringify([name.trim(), query]));
36807
+ return { resource: "filebox-custom-report", id: `${fileboxId}:${hash}`, verb: "create" };
36808
+ }
36809
+ async function readCustomReportQueryFile(path12) {
36810
+ if (path12.trim() === "") {
36811
+ throw new Error("--query-file is required");
36812
+ }
36813
+ const query = await readFile5(path12, "utf8");
36814
+ if (query.trim() === "") {
36815
+ throw new Error(`--query-file ${path12} is empty`);
36816
+ }
36817
+ return query;
36818
+ }
36819
+ async function runFileboxCustomReportsCreate(cfg) {
36820
+ const name = cfg.name.trim();
36821
+ if (name === "") {
36822
+ throw new Error("--name is required");
36823
+ }
36824
+ const action = customReportCreateAction(cfg.fileboxId, name, cfg.query);
36825
+ await preflightConfirmToken(action, cfg.token);
36826
+ const firstLine = cfg.query.trim().split("\n")[0] ?? "";
36827
+ await twoStepConfirm({
36828
+ out: cfg.out,
36829
+ action,
36830
+ token: cfg.token,
36831
+ server: cfg.serverFlag,
36832
+ confirmCmd: `dr filebox custom-reports create ${cfg.fileboxId} --name ${shellQuote(name)} --query-file ${shellQuote(cfg.queryFile)} --server ${cfg.serverFlag}`,
36833
+ describe: async () => [
36834
+ ...await describeFileboxTarget(cfg.client, cfg.fileboxId, cfg.signal),
36835
+ `name: "${name}" (unique on the filebox's NetSuite connection)`,
36836
+ `query: ${cfg.query.length} chars, starting "${truncate3(firstLine, 60)}"`,
36837
+ "effect: creates the custom report and links this filebox to it, replacing the filebox's query"
36838
+ ],
36839
+ execute: async () => {
36840
+ const res = await createCustomReport(
36841
+ cfg.client,
36842
+ cfg.fileboxId,
36843
+ { name, query: cfg.query },
36844
+ { signal: cfg.signal }
36845
+ );
36846
+ writeLinkResult(
36847
+ cfg,
36848
+ "create custom report",
36849
+ res,
36850
+ (r) => `Created custom report #${r.id} "${r.name}" and linked filebox ${cfg.fileboxId} to it`
36851
+ );
36852
+ }
36853
+ });
36854
+ }
36855
+ function customReportApplyAction(fileboxId, reportId, query) {
36856
+ return {
36857
+ resource: "filebox-custom-report",
36858
+ id: `${fileboxId}:${reportId}:${shortHash4(query)}`,
36859
+ verb: "apply"
36860
+ };
36861
+ }
36862
+ async function runFileboxCustomReportsApply(cfg) {
36863
+ const { reports } = await fetchCustomReports(cfg.client, cfg.fileboxId, cfg.signal);
36864
+ const report = reports.find((r) => r.id === cfg.reportId);
36865
+ if (report === void 0) {
36866
+ throw new Error(
36867
+ `custom report ${cfg.reportId} is not on the connection of filebox ${cfg.fileboxId}; see dr filebox custom-reports list ${cfg.fileboxId}`
36868
+ );
36869
+ }
36870
+ const query = typeof report.query === "string" ? report.query : "";
36871
+ const action = customReportApplyAction(cfg.fileboxId, cfg.reportId, query);
36872
+ await preflightConfirmToken(action, cfg.token);
36873
+ await twoStepConfirm({
36874
+ out: cfg.out,
36875
+ action,
36876
+ token: cfg.token,
36877
+ server: cfg.serverFlag,
36878
+ confirmCmd: `dr filebox custom-reports apply ${cfg.fileboxId} ${cfg.reportId} --server ${cfg.serverFlag}`,
36879
+ describe: async () => [
36880
+ ...await describeFileboxTarget(cfg.client, cfg.fileboxId, cfg.signal),
36881
+ `report: #${report.id} "${report.name}" (used by ${usedBy(report.usage_count)})`,
36882
+ `sql: ${query.length} chars, sha256 ${shortHash4(query)}`,
36883
+ "effect: links this filebox to the report, replacing the filebox's query with the report's"
36884
+ ],
36885
+ execute: async () => {
36886
+ const res = await applyCustomReport(cfg.client, cfg.fileboxId, cfg.reportId, {
36887
+ signal: cfg.signal
36888
+ });
36889
+ writeLinkResult(
36890
+ cfg,
36891
+ "apply custom report",
36892
+ res,
36893
+ (r) => `Linked filebox ${cfg.fileboxId} to custom report #${r.id} "${r.name}"`
36894
+ );
36895
+ }
36896
+ });
36897
+ }
36898
+ function registerFileboxCustomReportsCommands(fb) {
36899
+ const reports = fb.command("custom-reports").description(
36900
+ "SuiteQL custom reports of a filebox's NetSuite connection: named queries its fileboxes share"
36901
+ );
36902
+ const listCmd = reports.command("list").description(
36903
+ "List the custom reports of the filebox's NetSuite connection, with how many fileboxes use each"
36904
+ ).argument("<filebox_id>", "filebox id (positive integer)").option("--json", "Output the raw response as JSON (decoded)", false).action(async (idStr, opts) => {
36905
+ const fileboxId = parsePositiveIntArg(idStr, "<filebox_id>");
36906
+ const setup = await setupCall(opts.server, opts.account);
36907
+ try {
36908
+ await runFileboxCustomReportsList({
36909
+ client: setup.client,
36910
+ fileboxId,
36911
+ asJson: opts.json === true,
36912
+ signal: setup.signal,
36913
+ out: process.stdout
36914
+ });
36915
+ } finally {
36916
+ setup.cleanup();
36917
+ }
36918
+ });
36919
+ addServerFlag(listCmd);
36920
+ addAccountFlag(listCmd);
36921
+ const createCmd = reports.command("create").description(
36922
+ "Create a custom report on the filebox's NetSuite connection and link the filebox to it, replacing its query (two-step confirmation). The filebox's data query must already be SuiteQL. 409 means the name is taken."
36923
+ ).argument("<filebox_id>", "filebox id (positive integer)").requiredOption("--name <name>", "Report name, unique on the connection (required)").requiredOption("--query-file <path>", "File holding the SuiteQL query (required)").option(
36924
+ "--confirm-token <token>",
36925
+ "Token from a prior plan run - required to actually create",
36926
+ ""
36927
+ ).option("--json", "Output the raw response as JSON (decoded)", false).action(
36928
+ async (idStr, opts) => {
36929
+ const fileboxId = parsePositiveIntArg(idStr, "<filebox_id>");
36930
+ if (opts.name.trim() === "") {
36931
+ throw new Error("--name is required");
36932
+ }
36933
+ const query = await readCustomReportQueryFile(opts.queryFile);
36934
+ await preflightConfirmToken(
36935
+ customReportCreateAction(fileboxId, opts.name, query),
36936
+ opts.confirmToken
36937
+ );
36938
+ const serverKey = resolveServer(opts.server);
36939
+ const setup = await setupCall(opts.server, opts.account);
36940
+ try {
36941
+ await runFileboxCustomReportsCreate({
36942
+ client: setup.client,
36943
+ fileboxId,
36944
+ name: opts.name,
36945
+ query,
36946
+ token: opts.confirmToken,
36947
+ serverFlag: serverKey,
36948
+ queryFile: opts.queryFile,
36949
+ asJson: opts.json === true,
36950
+ signal: setup.signal,
36951
+ out: process.stdout
36952
+ });
36953
+ } finally {
36954
+ setup.cleanup();
36955
+ }
36956
+ }
36957
+ );
36958
+ addServerFlag(createCmd);
36959
+ addAccountFlag(createCmd);
36960
+ const applyCmd = reports.command("apply").description(
36961
+ "Link the filebox to an existing custom report of its connection, replacing its query with the report's (two-step confirmation)"
36962
+ ).argument("<filebox_id>", "filebox id (positive integer)").argument("<report_id>", "custom report id (from custom-reports list)").option(
36963
+ "--confirm-token <token>",
36964
+ "Token from a prior plan run - required to actually apply",
36965
+ ""
36966
+ ).option("--json", "Output the raw response as JSON (decoded)", false).action(
36967
+ async (idStr, reportIdStr, opts) => {
36968
+ const fileboxId = parsePositiveIntArg(idStr, "<filebox_id>");
36969
+ const reportId = parsePositiveIntArg(reportIdStr, "<report_id>");
36970
+ const serverKey = resolveServer(opts.server);
36971
+ const setup = await setupCall(opts.server, opts.account);
36972
+ try {
36973
+ await runFileboxCustomReportsApply({
36974
+ client: setup.client,
36975
+ fileboxId,
36976
+ reportId,
36977
+ token: opts.confirmToken,
36978
+ serverFlag: serverKey,
36979
+ asJson: opts.json === true,
36980
+ signal: setup.signal,
36981
+ out: process.stdout
36982
+ });
36983
+ } finally {
36984
+ setup.cleanup();
36985
+ }
36986
+ }
36987
+ );
36988
+ addServerFlag(applyCmd);
36989
+ addAccountFlag(applyCmd);
36990
+ }
36991
+
36423
36992
  // src/api/forecast.ts
36424
36993
  var SCENARIO_PARAM = "Forecast";
36425
36994
  async function listScenarios(client, filter = {}, options) {
@@ -36804,6 +37373,11 @@ var DQ_WAIT_POLL_INTERVAL_MS = 5e3;
36804
37373
  var DQ_WAIT_POLL_MAX_INTERVAL_MS = 3e4;
36805
37374
  var DQ_WAIT_DEFAULT_TIMEOUT_SEC = 1800;
36806
37375
  var DQ_WAIT_TIMEOUT_MAX_SEC = 86400;
37376
+ var SYNC_EVENT_ACTION = "integration_query_status_changed";
37377
+ var EMPTY_SYNC_EVENT_PREFIX = "Sync completed successfully, but no new data was retrieved due to an empty response";
37378
+ var SYNC_EVENT_LIMIT = 50;
37379
+ var SYNC_EVENT_MAX_PAGES = 10;
37380
+ var DATE_HEADER_RESOLUTION_MS = 1e3;
36807
37381
  var DQ_WAIT_SETUP_OVERHEAD_MS = 6e4;
36808
37382
  function dataQuerySyncCallTimeoutMs(wait, timeoutSec) {
36809
37383
  if (!wait) return void 0;
@@ -36838,7 +37412,7 @@ function maxId(rows) {
36838
37412
  function ref(v) {
36839
37413
  return { id: v.id, version: v.version };
36840
37414
  }
36841
- function buildDataQuerySyncBaseline(periods, versions, dataQueryRaw) {
37415
+ function buildDataQuerySyncBaseline(periods, versions, dataQueryRaw, eventsRaw = null) {
36842
37416
  const dataQuery = asRecord(dataQueryRaw);
36843
37417
  return {
36844
37418
  periods: periods.map((p) => {
@@ -36849,7 +37423,8 @@ function buildDataQuerySyncBaseline(periods, versions, dataQueryRaw) {
36849
37423
  };
36850
37424
  }),
36851
37425
  maxVersionId: maxId(versions),
36852
- updateTime: stamp(dataQuery["update_time"])
37426
+ updateTime: stamp(dataQuery["update_time"]),
37427
+ emptyEventIds: Array.isArray(eventsRaw) ? new Set(emptySyncEvents(eventsRaw).map((e) => e.id)) : null
36853
37428
  };
36854
37429
  }
36855
37430
  function classifyDataQuerySyncProgress(periods, baseline, versions, dataQueryRaw) {
@@ -36882,11 +37457,12 @@ function classifyDataQuerySyncProgress(periods, baseline, versions, dataQueryRaw
36882
37457
  }
36883
37458
  async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal, preRead) {
36884
37459
  try {
36885
- const [versions, dataQuery] = await Promise.all([
37460
+ const [versions, dataQuery, events] = await Promise.all([
36886
37461
  listVersions(client, fileboxId, {}, { signal }),
36887
- preRead !== void 0 ? Promise.resolve(preRead.raw) : getDataQueryRaw(client, fileboxId, { signal })
37462
+ preRead !== void 0 ? Promise.resolve(preRead.raw) : getDataQueryRaw(client, fileboxId, { signal }),
37463
+ readSyncEvents(client, fileboxId, signal)
36888
37464
  ]);
36889
- return buildDataQuerySyncBaseline(periods, versions, dataQuery);
37465
+ return buildDataQuerySyncBaseline(periods, versions, dataQuery, events);
36890
37466
  } catch (err) {
36891
37467
  const msg = err instanceof Error ? err.message : String(err);
36892
37468
  throw new Error(
@@ -36894,6 +37470,61 @@ async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal,
36894
37470
  );
36895
37471
  }
36896
37472
  }
37473
+ function emptySyncEvents(eventsRaw) {
37474
+ const out = [];
37475
+ for (const raw of eventsRaw) {
37476
+ const e = asRecord(raw);
37477
+ const id = e["_id"];
37478
+ const text2 = e["text"];
37479
+ if (typeof id !== "string" || id === "" || typeof text2 !== "string") continue;
37480
+ const decoded = decodeShape(text2);
37481
+ if (!decoded.startsWith(EMPTY_SYNC_EVENT_PREFIX)) continue;
37482
+ const at = decoded.lastIndexOf(" for ");
37483
+ const label = at >= 0 && decoded.endsWith(".") ? decoded.slice(at + " for ".length, -1) : void 0;
37484
+ const datetime = typeof e["datetime"] === "number" ? e["datetime"] : void 0;
37485
+ out.push({ id, datetime, label });
37486
+ }
37487
+ return out;
37488
+ }
37489
+ function findEmptyPeriods(periods, baseline, progress, eventsRaw, sinceMs) {
37490
+ const empty = /* @__PURE__ */ new Set();
37491
+ const known = baseline.emptyEventIds;
37492
+ if (known === null || sinceMs === void 0 || !Array.isArray(eventsRaw)) return empty;
37493
+ if (progress.error !== void 0 || progress.unmatchedNewVersions.length > 0) return empty;
37494
+ const fresh = emptySyncEvents(eventsRaw).filter(
37495
+ (e) => !known.has(e.id) && e.datetime !== void 0 && e.datetime >= sinceMs
37496
+ );
37497
+ periods.forEach((p, i) => {
37498
+ if (progress.periods[i]?.result !== "pending") return;
37499
+ const matches = fresh.some(
37500
+ (e) => p.eventLabel === void 0 ? periods.length === 1 : e.label === p.eventLabel || e.label === void 0 && periods.length === 1
37501
+ );
37502
+ if (matches) empty.add(i);
37503
+ });
37504
+ return empty;
37505
+ }
37506
+ async function readSyncEvents(client, fileboxId, signal, sinceMs) {
37507
+ const events = [];
37508
+ try {
37509
+ for (let page = 0; page < SYNC_EVENT_MAX_PAGES; page++) {
37510
+ const rows = await listDocumentEvents(
37511
+ client,
37512
+ fileboxId,
37513
+ SYNC_EVENT_ACTION,
37514
+ { skip: page * SYNC_EVENT_LIMIT, limit: SYNC_EVENT_LIMIT },
37515
+ { signal }
37516
+ );
37517
+ if (!Array.isArray(rows)) return null;
37518
+ events.push(...rows);
37519
+ if (sinceMs === void 0 || rows.length < SYNC_EVENT_LIMIT) break;
37520
+ const oldest = asRecord(rows[rows.length - 1])["datetime"];
37521
+ if (typeof oldest !== "number" || oldest < sinceMs) break;
37522
+ }
37523
+ } catch {
37524
+ return null;
37525
+ }
37526
+ return events;
37527
+ }
36897
37528
  var defaultSleep6 = (ms, signal) => new Promise((resolve3, reject) => {
36898
37529
  if (signal?.aborted) {
36899
37530
  reject(signal.reason ?? new Error("aborted"));
@@ -36923,15 +37554,42 @@ async function waitForDataQuerySync(cfg) {
36923
37554
  `
36924
37555
  );
36925
37556
  }
37557
+ const sinceMs = cfg.triggerServerTimeMs === void 0 ? void 0 : cfg.triggerServerTimeMs - DATE_HEADER_RESOLUTION_MS;
37558
+ let warnedEvents = false;
37559
+ const warnEvents = (why) => {
37560
+ if (warnedEvents) return;
37561
+ warnedEvents = true;
37562
+ cfg.errOut.write(
37563
+ `warning: ${why} \u2014 a period whose sync returns no rows cannot be confirmed and will time out
37564
+ `
37565
+ );
37566
+ };
37567
+ if (sinceMs === void 0) warnEvents("the sync response carried no Date header");
37568
+ if (cfg.baseline.emptyEventIds === null) warnEvents("the filebox's sync events are unreadable");
37569
+ const settledEmpty = /* @__PURE__ */ new Set();
36926
37570
  let progress;
36927
37571
  let outcome;
36928
37572
  for (; ; ) {
36929
37573
  try {
36930
- const [versions, dataQuery] = await Promise.all([
37574
+ const [versions, dataQuery, events] = await Promise.all([
36931
37575
  listVersions(cfg.client, cfg.fileboxId, {}, { signal: cfg.signal }),
36932
- getDataQueryRaw(cfg.client, cfg.fileboxId, { signal: cfg.signal })
37576
+ getDataQueryRaw(cfg.client, cfg.fileboxId, { signal: cfg.signal }),
37577
+ readSyncEvents(cfg.client, cfg.fileboxId, cfg.signal, sinceMs)
36933
37578
  ]);
36934
- progress = classifyDataQuerySyncProgress(cfg.periods, cfg.baseline, versions, dataQuery);
37579
+ if (events === null) warnEvents("the filebox's sync events are unreadable");
37580
+ else if (emptySyncEvents(events).some((e) => e.datetime === void 0)) {
37581
+ warnEvents("a sync event carries no numeric timestamp");
37582
+ }
37583
+ const polled = classifyDataQuerySyncProgress(cfg.periods, cfg.baseline, versions, dataQuery);
37584
+ for (const i of findEmptyPeriods(cfg.periods, cfg.baseline, polled, events, sinceMs)) {
37585
+ settledEmpty.add(i);
37586
+ }
37587
+ progress = {
37588
+ ...polled,
37589
+ periods: polled.periods.map(
37590
+ (p, i) => p.result === "pending" && settledEmpty.has(i) ? { period: p.period, result: "empty" } : p
37591
+ )
37592
+ };
36935
37593
  } catch (err) {
36936
37594
  if (cfg.signal.aborted) {
36937
37595
  outcome = "cancelled";
@@ -36992,6 +37650,7 @@ function renderSummary2(cfg, progress, outcome, waitedSec) {
36992
37650
  const count = (r) => progress.periods.filter((p) => p.result === r).length;
36993
37651
  const parts = [`${count("new_version")} new version${plural(count("new_version"))}`];
36994
37652
  parts.push(`${count("no_change")} unchanged`);
37653
+ if (count("empty") > 0) parts.push(`${count("empty")} empty`);
36995
37654
  if (count("pending") > 0) parts.push(`${count("pending")} unfinished`);
36996
37655
  const headline = {
36997
37656
  completed: "Sync finished",
@@ -37012,6 +37671,11 @@ function renderSummary2(cfg, progress, outcome, waitedSec) {
37012
37671
  } else if (p.result === "no_change") {
37013
37672
  cfg.out.write(
37014
37673
  ` ${p.period}: unchanged \u2014 identical to version #${p.version.id} (v${p.version.version}), so no new version was written
37674
+ `
37675
+ );
37676
+ } else if (p.result === "empty") {
37677
+ cfg.out.write(
37678
+ ` ${p.period}: empty \u2014 the server logged an empty response for this period, so nothing was written
37015
37679
  `
37016
37680
  );
37017
37681
  } else if (outcome === "timed_out") {
@@ -37991,7 +38655,7 @@ function buildIncludeBatchAction(docId, include, targets) {
37991
38655
  const canonical = `${docId}|${include ? "on" : "off"}|${targets.map((v) => v.id).slice().sort((a, b) => a - b).join(",")}`;
37992
38656
  return {
37993
38657
  resource: "filebox-version-batch",
37994
- id: createHash9("sha256").update(canonical).digest("hex").slice(0, 12),
38658
+ id: createHash10("sha256").update(canonical).digest("hex").slice(0, 12),
37995
38659
  verb: includeStateWords(include).verb
37996
38660
  };
37997
38661
  }
@@ -39338,23 +40002,30 @@ function resolveDateForSubType(subType, parsed2) {
39338
40002
  const { year, month0, day } = parsed2;
39339
40003
  switch ((subType ?? "").toLowerCase()) {
39340
40004
  case "year":
39341
- return { value: utcDateToNoonEpoch(year, 0, 1), label: String(year) };
40005
+ return {
40006
+ value: utcDateToNoonEpoch(year, 0, 1),
40007
+ label: String(year),
40008
+ eventLabel: String(year)
40009
+ };
39342
40010
  case "quarter": {
39343
40011
  const q = Math.floor(month0 / 3) + 1;
39344
40012
  return {
39345
40013
  value: utcDateToNoonEpoch(year, (q - 1) * 3, 1),
39346
- label: `Q${q} ${year}`
40014
+ label: `Q${q} ${year}`,
40015
+ eventLabel: `Q${q} ${year}`
39347
40016
  };
39348
40017
  }
39349
40018
  case "month":
39350
40019
  return {
39351
40020
  value: utcDateToNoonEpoch(year, month0, 1),
39352
- label: `${MONTH_NAMES[month0] ?? ""} ${year}`
40021
+ label: `${MONTH_NAMES[month0] ?? ""} ${year}`,
40022
+ eventLabel: `${MONTH_NAMES[month0] ?? ""} ${year}`
39353
40023
  };
39354
40024
  case "day":
39355
40025
  return {
39356
40026
  value: utcDateToNoonEpoch(year, month0, day),
39357
- label: `${MONTH_NAMES_SHORT[month0] ?? ""} ${day}, ${year}`
40027
+ label: `${MONTH_NAMES_SHORT[month0] ?? ""} ${day}, ${year}`,
40028
+ eventLabel: `${String(month0 + 1).padStart(2, "0")}/${String(day).padStart(2, "0")}/${year}`
39358
40029
  };
39359
40030
  case "week": {
39360
40031
  const date = new Date(Date.UTC(year, month0, day));
@@ -39365,7 +40036,8 @@ function resolveDateForSubType(subType, parsed2) {
39365
40036
  return {
39366
40037
  // Noon-UTC convention, same as the other branches.
39367
40038
  value: Math.floor(monday.getTime() / 1e3) + 12 * 60 * 60,
39368
- label: `Week ${week} ${isoYear}`
40039
+ label: `Week ${week} ${isoYear}`,
40040
+ eventLabel: `W${String(week).padStart(2, "0")} ${isoYear}`
39369
40041
  };
39370
40042
  }
39371
40043
  default:
@@ -39418,8 +40090,8 @@ async function runFileboxDataQuerySync(cfg) {
39418
40090
  ].map((t) => dateTags.map((tag) => resolveDateForSubType(tagSubType(tag), t).value));
39419
40091
  for (const parsed2 of parsedDates) {
39420
40092
  const resolved = dateTags.map((tag) => {
39421
- const { value, label: label2 } = resolveDateForSubType(tagSubType(tag), parsed2);
39422
- return { id: tag.id, name: tag.name, value, label: label2 };
40093
+ const { value, label: label2, eventLabel } = resolveDateForSubType(tagSubType(tag), parsed2);
40094
+ return { id: tag.id, name: tag.name, value, label: label2, eventLabel };
39423
40095
  });
39424
40096
  dateTargets.push(
39425
40097
  JSON.stringify(resolved.map(({ id, name, value }) => ({ id, name, value })))
@@ -39430,6 +40102,8 @@ async function runFileboxDataQuerySync(cfg) {
39430
40102
  periodLabels.push(label);
39431
40103
  waitPeriods.push({
39432
40104
  label,
40105
+ // The server joins a version's DATE labels in tag-id order.
40106
+ eventLabel: [...resolved].sort((a, b) => a.id - b.id).map((r) => r.eventLabel).join(", "),
39433
40107
  tags: dateTags.map((tag, i) => ({
39434
40108
  id: tag.id,
39435
40109
  inPeriod: (stored) => samePeriodAs(tagSubType(tag), stored, resolved[i].value)
@@ -39470,9 +40144,17 @@ async function runFileboxDataQuerySync(cfg) {
39470
40144
  if (multipleTags.length > 0) {
39471
40145
  body["multiple_tags"] = multipleTags;
39472
40146
  }
39473
- const resp = await syncDataQuery(cfg.client, cfg.id, body, {
39474
- signal: cfg.signal
39475
- });
40147
+ let resp;
40148
+ let triggerServerTimeMs;
40149
+ if (cfg.wait === void 0) {
40150
+ resp = await syncDataQuery(cfg.client, cfg.id, body, { signal: cfg.signal });
40151
+ } else {
40152
+ const timed = await syncDataQueryTimed(cfg.client, cfg.id, body, {
40153
+ signal: cfg.signal
40154
+ });
40155
+ resp = timed.body;
40156
+ triggerServerTimeMs = timed.serverTimeMs;
40157
+ }
39476
40158
  let detail = "OK";
39477
40159
  if (typeof resp["details"] === "string" && resp["details"] !== "") {
39478
40160
  detail = resp["details"];
@@ -39500,6 +40182,7 @@ async function runFileboxDataQuerySync(cfg) {
39500
40182
  await waitForDataQuerySync({
39501
40183
  client: cfg.client,
39502
40184
  fileboxId: cfg.id,
40185
+ triggerServerTimeMs,
39503
40186
  periods: waitPeriods,
39504
40187
  baseline,
39505
40188
  timeoutSec: cfg.wait.timeoutSec,
@@ -40814,7 +41497,7 @@ function registerFileboxCommand(root) {
40814
41497
  []
40815
41498
  ).option(
40816
41499
  "--wait",
40817
- "After queueing, wait until every --date period has finished, and say whether each wrote a new version or was unchanged (identical output writes no version). Exits non-zero on a failed sync or a timeout. Not combinable with --tag.",
41500
+ "After queueing, wait until every --date period has finished, and say whether each wrote a new version, was unchanged (identical output writes no version), or was empty (the server logged an empty response for that period). Exits non-zero on a failed sync or a timeout. Not combinable with --tag.",
40818
41501
  false
40819
41502
  ).option(
40820
41503
  "--timeout <seconds>",
@@ -40850,6 +41533,7 @@ function registerFileboxCommand(root) {
40850
41533
  registerFileboxAdminCommands(fb, setupCall, addServerFlag, addAccountFlag);
40851
41534
  registerFileboxKindCommands(fb);
40852
41535
  registerFileboxRescanVersionCommand(fb);
41536
+ registerFileboxCustomReportsCommands(fb);
40853
41537
  }
40854
41538
 
40855
41539
  // src/commands/integrations.ts
@@ -41055,7 +41739,7 @@ function parseDataSourceIntegration(raw) {
41055
41739
  return i;
41056
41740
  }
41057
41741
  async function runIntegrationsTypes(cfg) {
41058
- const items = await listTypes(cfg.client, cfg.search, {
41742
+ const items = await listTypes2(cfg.client, cfg.search, {
41059
41743
  signal: cfg.signal
41060
41744
  });
41061
41745
  if (cfg.asJson) {
@@ -41669,6 +42353,172 @@ Finish it with: dr integrations sftp provision ${id} (plus the same --date-*/--b
41669
42353
  `);
41670
42354
  writeSftpKit(cfg.out, kit);
41671
42355
  }
42356
+ var NETSUITE_CALL_TIMEOUT_MS = 12e4;
42357
+ function netsuiteStampAction(id) {
42358
+ return { resource: "netsuite-bundle-version", id: String(id), verb: "stamp" };
42359
+ }
42360
+ function describeNetSuiteFailure(what, id, res) {
42361
+ return describeRawFailure(what, res, {
42362
+ 404: `found no NetSuite connection ${id} visible to this account`
42363
+ });
42364
+ }
42365
+ var SAVED_SEARCH_ID_RE = /^(?:-?[0-9]+|customsearch[A-Za-z0-9_]*)$/;
42366
+ function parseSavedSearchId(raw) {
42367
+ const id = raw.trim();
42368
+ if (id === "") {
42369
+ throw new Error("--saved-search is required");
42370
+ }
42371
+ if (id.length > 64 || !SAVED_SEARCH_ID_RE.test(id)) {
42372
+ throw new Error(
42373
+ `invalid --saved-search "${id}" (expected a numeric internal id, optionally negative, or a customsearch* script id)`
42374
+ );
42375
+ }
42376
+ return id;
42377
+ }
42378
+ async function writeSqlFile(path12, sql) {
42379
+ const existing = await lstat(path12).catch((err) => {
42380
+ if (err.code === "ENOENT") return void 0;
42381
+ throw err;
42382
+ });
42383
+ if (existing !== void 0 && !existing.isFile()) {
42384
+ throw new Error(
42385
+ existing.isSymbolicLink() ? "it is a symlink" : "it exists and is not a regular file"
42386
+ );
42387
+ }
42388
+ const flags = fsConstants.O_WRONLY | fsConstants.O_CREAT | (fsConstants.O_NOFOLLOW ?? 0);
42389
+ const handle = await open2(path12, flags, 384);
42390
+ try {
42391
+ const st = await handle.stat();
42392
+ if (!st.isFile() || st.nlink !== 1) throw new Error("it is hard-linked or not a regular file");
42393
+ if (existing !== void 0 && (st.ino !== existing.ino || st.dev !== existing.dev)) {
42394
+ throw new Error("it changed while being opened");
42395
+ }
42396
+ await handle.chmod(384);
42397
+ await handle.truncate(0);
42398
+ await handle.writeFile(sql);
42399
+ } finally {
42400
+ await handle.close();
42401
+ }
42402
+ }
42403
+ async function probeNetSuiteReadiness(cfg) {
42404
+ const res = await netsuiteBundleReadiness(cfg.client, cfg.id, cfg.dryRun, {
42405
+ signal: cfg.signal
42406
+ });
42407
+ if (!isSuccess(res.status)) {
42408
+ throw new Error(describeNetSuiteFailure("bundle readiness", cfg.id, res));
42409
+ }
42410
+ const body = decodeShape(res.body);
42411
+ if (body === null || typeof body !== "object" || typeof body.outcome !== "string") {
42412
+ throw new Error(
42413
+ `bundle readiness: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
42414
+ );
42415
+ }
42416
+ const detail = typeof body.detail === "string" ? body.detail : "";
42417
+ if (cfg.asJson) {
42418
+ writeJSONIndented(cfg.out, body);
42419
+ } else {
42420
+ const version = typeof body.bundle_version === "number" ? String(body.bundle_version) : "none";
42421
+ cfg.out.write(`NetSuite bundle readiness for integration ${cfg.id}: ${body.outcome}
42422
+ `);
42423
+ cfg.out.write(` Detail: ${orDash2(detail)}
42424
+ `);
42425
+ cfg.out.write(` Bundle version: ${version}
42426
+ `);
42427
+ if (body.dry_run === true) {
42428
+ cfg.out.write(" Dry run: nothing was stamped\n");
42429
+ }
42430
+ }
42431
+ if (!cfg.dryRun && body.outcome !== "CONFIRMED") {
42432
+ throw new Error(
42433
+ `readiness probe did not confirm (${body.outcome}): ${orDash2(detail)}; nothing was stamped`
42434
+ );
42435
+ }
42436
+ }
42437
+ async function runIntegrationsNetSuiteReadiness(cfg) {
42438
+ if (cfg.id <= 0) {
42439
+ throw new Error("integration id must be positive");
42440
+ }
42441
+ if (cfg.dryRun) {
42442
+ if (!isPlanPhase(cfg.token)) {
42443
+ throw new Error("--confirm-token does not apply to --dry-run, which writes nothing");
42444
+ }
42445
+ await probeNetSuiteReadiness(cfg);
42446
+ return;
42447
+ }
42448
+ const action = netsuiteStampAction(cfg.id);
42449
+ await preflightConfirmToken(action, cfg.token);
42450
+ await twoStepConfirm({
42451
+ out: cfg.out,
42452
+ action,
42453
+ token: cfg.token,
42454
+ server: cfg.serverFlag,
42455
+ confirmCmd: `dr integrations netsuite readiness ${cfg.id} --server ${cfg.serverFlag}`,
42456
+ describe: async () => {
42457
+ const i = decodeShape(
42458
+ await getById2(cfg.client, cfg.id, { signal: cfg.signal })
42459
+ );
42460
+ return [
42461
+ `name: "${i.name}"`,
42462
+ `type: ${integrationTypeName(i)}`,
42463
+ `org: ${orDash2(i.organization_name)} (id=${i.organization})`,
42464
+ "probe: one live SuiteQL call that checks the bundle role and permissions",
42465
+ "effect: on CONFIRMED, stamps bundle_version 3 (the 3.0 bundle) on the connection config",
42466
+ "preview without writing: re-run with --dry-run"
42467
+ ];
42468
+ },
42469
+ execute: () => probeNetSuiteReadiness(cfg)
42470
+ });
42471
+ }
42472
+ async function runIntegrationsNetSuiteSavedSearchSql(cfg) {
42473
+ if (cfg.id <= 0) {
42474
+ throw new Error("integration id must be positive");
42475
+ }
42476
+ const savedSearchId = parseSavedSearchId(cfg.savedSearchId);
42477
+ const res = await netsuiteSavedSearchSql(cfg.client, cfg.id, savedSearchId, {
42478
+ signal: cfg.signal
42479
+ });
42480
+ if (!isSuccess(res.status)) {
42481
+ throw new Error(describeNetSuiteFailure("saved-search SQL", cfg.id, res));
42482
+ }
42483
+ const body = decodeShape(res.body);
42484
+ if (body === null || typeof body !== "object" || typeof body.sql !== "string") {
42485
+ throw new Error(
42486
+ `saved-search SQL: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
42487
+ );
42488
+ }
42489
+ const sql = body.sql;
42490
+ const outPath = cfg.outPath !== void 0 && cfg.outPath !== "" ? cfg.outPath : void 0;
42491
+ let writeError;
42492
+ if (outPath !== void 0) {
42493
+ try {
42494
+ await writeSqlFile(outPath, sql);
42495
+ } catch (err) {
42496
+ writeError = err instanceof Error ? err.message : String(err);
42497
+ }
42498
+ }
42499
+ const written = outPath !== void 0 && writeError === void 0;
42500
+ if (cfg.asJson) {
42501
+ writeJSONIndented(cfg.out, body);
42502
+ } else {
42503
+ const name = typeof body.saved_search_name === "string" && body.saved_search_name !== "" ? body.saved_search_name : "(not listed by the bundle)";
42504
+ const key = typeof body.source_query_key === "string" ? body.source_query_key : "";
42505
+ cfg.out.write(`Saved search ${savedSearchId}: ${name}
42506
+ `);
42507
+ cfg.out.write(` Library match: ${key || "none \u2014 custom report"}
42508
+ `);
42509
+ if (written) {
42510
+ cfg.out.write(` SQL: written to ${outPath} (${sql.length} chars)
42511
+ `);
42512
+ } else {
42513
+ cfg.out.write(" SQL:\n");
42514
+ cfg.out.write(sql.endsWith("\n") ? sql : `${sql}
42515
+ `);
42516
+ }
42517
+ }
42518
+ if (writeError !== void 0) {
42519
+ throw new Error(`could not write --out ${outPath}: ${writeError} (the SQL is printed above)`);
42520
+ }
42521
+ }
41672
42522
  async function runIntegrationsITList(cfg) {
41673
42523
  if (cfg.page < 1) {
41674
42524
  throw new Error("--page must be >= 1");
@@ -42257,6 +43107,80 @@ function registerIntegrationsCommand(root) {
42257
43107
  addAccountFlag(shareGroupsCmd);
42258
43108
  registerITCommands(integrations);
42259
43109
  registerSftpCommands(integrations);
43110
+ registerNetSuiteCommands(integrations);
43111
+ }
43112
+ function registerNetSuiteCommands(parent) {
43113
+ const netsuite = parent.command("netsuite").description("NetSuite 3.0 -> SuiteQL in-place migration helpers");
43114
+ const readinessCmd = netsuite.command("readiness").description(
43115
+ 'Probe whether a NetSuite 3.0 connection is ready for SuiteQL (one live SuiteQL call). On CONFIRMED it stamps bundle_version 3 on the connection (two-step confirmation); --dry-run probes without writing. Any other outcome stamps nothing and, without --dry-run, exits non-zero. 409 (refused) causes, from the server: "Connection is SuiteQL-native or has no NetSuite 3.0 filebox", "connection account id is not a valid NetSuite account id"; missing credentials are the NOT_CONNECTED outcome, not a 409.'
43116
+ ).argument("<integration_id>", "NetSuite connection (data integration) id").option("--dry-run", "Probe only: never stamp the connection, no confirmation needed", false).option(
43117
+ "--confirm-token <token>",
43118
+ "Token from a prior plan run - required to actually stamp",
43119
+ ""
43120
+ ).option("--json", "Output the raw response as JSON", false).action(
43121
+ async (idStr, opts) => {
43122
+ const id = parseIntegrationIdArg(idStr);
43123
+ const dryRun = opts.dryRun === true;
43124
+ if (dryRun && !isPlanPhase(opts.confirmToken)) {
43125
+ throw new Error("--confirm-token does not apply to --dry-run, which writes nothing");
43126
+ }
43127
+ if (!dryRun) {
43128
+ await preflightConfirmToken(netsuiteStampAction(id), opts.confirmToken);
43129
+ }
43130
+ const serverKey = resolveServer(opts.server);
43131
+ await withSetup(
43132
+ opts.server,
43133
+ opts.account,
43134
+ async ({ client, signal }) => {
43135
+ await runIntegrationsNetSuiteReadiness({
43136
+ client,
43137
+ id,
43138
+ dryRun,
43139
+ token: opts.confirmToken,
43140
+ serverFlag: serverKey,
43141
+ asJson: opts.json === true,
43142
+ signal,
43143
+ out: process.stdout
43144
+ });
43145
+ },
43146
+ NETSUITE_CALL_TIMEOUT_MS
43147
+ );
43148
+ }
43149
+ );
43150
+ addServerFlag(readinessCmd);
43151
+ addAccountFlag(readinessCmd);
43152
+ const savedSearchSqlCmd = netsuite.command("saved-search-sql").description(
43153
+ `Show a saved search's SQL as translated by the connection's 3.0 bundle, its name, and the SuiteQL library entry it matches (none: create a custom report instead). Read-only. 409 (refused) causes, from the server: "Connection is SuiteQL-native or has no NetSuite 3.0 filebox", "connection account id is not a valid NetSuite account id", "connection has no credentials: <fields>". 502: the bundle call failed, or the SQL is over 100,000 characters.`
43154
+ ).argument("<integration_id>", "NetSuite connection (data integration) id").requiredOption(
43155
+ "--saved-search <id>",
43156
+ "NetSuite saved search id: a numeric internal id (negative for standard searches) or a customsearch* script id (required)"
43157
+ ).option(
43158
+ "--out <path>",
43159
+ "Also write the decoded SQL to this file, owner-only (0600); a regular file is overwritten, a symlink or hard-linked file is refused. If the write fails the SQL is printed and the command exits non-zero (e.g. for filebox custom-reports create --query-file)"
43160
+ ).option("--json", "Output the raw response as JSON", false).action(
43161
+ async (idStr, opts) => {
43162
+ const id = parseIntegrationIdArg(idStr);
43163
+ parseSavedSearchId(opts.savedSearch);
43164
+ await withSetup(
43165
+ opts.server,
43166
+ opts.account,
43167
+ async ({ client, signal }) => {
43168
+ await runIntegrationsNetSuiteSavedSearchSql({
43169
+ client,
43170
+ id,
43171
+ savedSearchId: opts.savedSearch,
43172
+ outPath: opts.out,
43173
+ asJson: opts.json === true,
43174
+ signal,
43175
+ out: process.stdout
43176
+ });
43177
+ },
43178
+ NETSUITE_CALL_TIMEOUT_MS
43179
+ );
43180
+ }
43181
+ );
43182
+ addServerFlag(savedSearchSqlCmd);
43183
+ addAccountFlag(savedSearchSqlCmd);
42260
43184
  }
42261
43185
  function registerSftpCommands(parent) {
42262
43186
  const sftp = parent.command("sftp").description("Self-serve SFTP fileboxes (the embedded SFTP integration)");
@@ -42899,7 +43823,7 @@ function registerDevFetch(connectors) {
42899
43823
 
42900
43824
  // src/commands/connectorsPoll.ts
42901
43825
  import * as readline2 from "readline";
42902
- import { createHash as createHash10 } from "crypto";
43826
+ import { createHash as createHash11 } from "crypto";
42903
43827
 
42904
43828
  // src/types/connectors.ts
42905
43829
  var POLL_ITEM_STATES = [
@@ -43269,7 +44193,7 @@ function confirmDigest(scope, payload) {
43269
44193
  controlPlane: scope.controlPlane,
43270
44194
  payload: Object.fromEntries(Object.entries(payload).sort(([a], [b]) => a.localeCompare(b)))
43271
44195
  });
43272
- return createHash10("sha256").update(canonical).digest("hex").slice(0, 12);
44196
+ return createHash11("sha256").update(canonical).digest("hex").slice(0, 12);
43273
44197
  }
43274
44198
  function pollConfirmAction(filebox, verb, scope, payload = {}) {
43275
44199
  return {
@@ -46256,7 +47180,7 @@ function registerWidgetsCommand(root) {
46256
47180
  }
46257
47181
 
46258
47182
  // src/commands/reconciliation.ts
46259
- import { createHash as createHash11 } from "crypto";
47183
+ import { createHash as createHash12 } from "crypto";
46260
47184
  import { promises as fs18 } from "fs";
46261
47185
 
46262
47186
  // src/api/reconciliation.ts
@@ -46785,7 +47709,7 @@ function wireReconciliationProcesses(parent) {
46785
47709
  }
46786
47710
 
46787
47711
  // src/commands/reconciliation_evidences.ts
46788
- import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
47712
+ import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
46789
47713
  import { basename as basename6 } from "path";
46790
47714
 
46791
47715
  // src/commands/_processResolver.ts
@@ -46912,7 +47836,7 @@ async function runReconciliationEvidencesCreate(cfg) {
46912
47836
  }
46913
47837
  let buf;
46914
47838
  try {
46915
- buf = await readFile5(cfg.filePath);
47839
+ buf = await readFile6(cfg.filePath);
46916
47840
  } catch (err) {
46917
47841
  const m = err instanceof Error ? err.message : String(err);
46918
47842
  throw new Error(`read --file: ${m}`);
@@ -47837,7 +48761,7 @@ function chunkByJsonBytes(rows, maxBytes) {
47837
48761
  return chunks;
47838
48762
  }
47839
48763
  function settingsActionId(settings) {
47840
- return createHash11("sha256").update(JSON.stringify(settings)).digest("hex").slice(0, 12);
48764
+ return createHash12("sha256").update(JSON.stringify(settings)).digest("hex").slice(0, 12);
47841
48765
  }
47842
48766
  async function runReconciliationAccountsUpdateSettings(cfg) {
47843
48767
  const action = {
@@ -48215,7 +49139,7 @@ async function runReconciliationAccountsSetAssignee(cfg) {
48215
49139
  var ASSIGNEE_BATCH_CHUNK_SIZE = 100;
48216
49140
  function assigneeBatchActionId(date, rows) {
48217
49141
  const canonical = rows.map((r) => [r.accountId, r.type, r.userId, r.userName, r.userEmail]);
48218
- return createHash11("sha256").update(JSON.stringify([date, canonical])).digest("hex").slice(0, 12);
49142
+ return createHash12("sha256").update(JSON.stringify([date, canonical])).digest("hex").slice(0, 12);
48219
49143
  }
48220
49144
  async function runReconciliationAccountsSetAssigneeBatch(cfg) {
48221
49145
  const action = {
@@ -48369,7 +49293,7 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
48369
49293
  }
48370
49294
  function clearBatchActionId(date, rows) {
48371
49295
  const canonical = rows.map((r) => [r.accountId, r.type]);
48372
- return createHash11("sha256").update(JSON.stringify(["clear", date, canonical])).digest("hex").slice(0, 12);
49296
+ return createHash12("sha256").update(JSON.stringify(["clear", date, canonical])).digest("hex").slice(0, 12);
48373
49297
  }
48374
49298
  async function runReconciliationAccountsClearAssigneeBatch(cfg) {
48375
49299
  const action = {
@@ -49692,7 +50616,7 @@ function applyProse(data4, overlay, warn2 = () => {
49692
50616
  }
49693
50617
 
49694
50618
  // src/commands/_validateReportRender.ts
49695
- import { readFile as readFile6 } from "fs/promises";
50619
+ import { readFile as readFile7 } from "fs/promises";
49696
50620
  import { join as join13 } from "path";
49697
50621
  var MARKER = "<!--__DATA_BLOCK__-->";
49698
50622
  function renderReportHtml(data4, templateHtml) {
@@ -49707,7 +50631,7 @@ function reportTemplatePath() {
49707
50631
  return join13(findSkillDir("dr-validate"), "report-template.html");
49708
50632
  }
49709
50633
  async function readReportTemplate() {
49710
- return readFile6(reportTemplatePath(), "utf8");
50634
+ return readFile7(reportTemplatePath(), "utf8");
49711
50635
  }
49712
50636
 
49713
50637
  // src/commands/validate.ts
@@ -50259,7 +51183,7 @@ function renderForError(v) {
50259
51183
  }
50260
51184
 
50261
51185
  // src/commands/mecWorkflow.ts
50262
- import { createHash as createHash12 } from "crypto";
51186
+ import { createHash as createHash13 } from "crypto";
50263
51187
 
50264
51188
  // src/api/workflow.ts
50265
51189
  function buildQueryString(params) {
@@ -52351,13 +53275,13 @@ async function runMecAccountsTaskStatuses(cfg) {
52351
53275
  }
52352
53276
  renderMecAccountsTasksOrStatuses(cfg.out, resp, "statuses");
52353
53277
  }
52354
- function shortHash4(input) {
52355
- return createHash12("sha256").update(input).digest("hex").slice(0, 12);
53278
+ function shortHash5(input) {
53279
+ return createHash13("sha256").update(input).digest("hex").slice(0, 12);
52356
53280
  }
52357
53281
  async function runMecWorkflowCreate(cfg) {
52358
53282
  const action = {
52359
53283
  resource: "mec-workflow-template",
52360
- id: shortHash4(JSON.stringify(cfg.payload)),
53284
+ id: shortHash5(JSON.stringify(cfg.payload)),
52361
53285
  verb: "create"
52362
53286
  };
52363
53287
  await preflightConfirmToken(action, cfg.token);
@@ -53004,7 +53928,7 @@ async function runMecTaskDelete(cfg) {
53004
53928
  const sortedIds = [...cfg.ids].map((r) => r.id).sort((a, b) => a - b);
53005
53929
  const action = {
53006
53930
  resource: "mec-workflow-task-batch",
53007
- id: `${cfg.stepId}:${shortHash4(sortedIds.join(","))}`,
53931
+ id: `${cfg.stepId}:${shortHash5(sortedIds.join(","))}`,
53008
53932
  verb: "bulk-delete"
53009
53933
  };
53010
53934
  await preflightConfirmToken(action, cfg.token);
@@ -53167,7 +54091,7 @@ async function runMecTaskSignOff(cfg) {
53167
54091
  const sortedIds = [...cfg.ids].sort((a, b) => a - b);
53168
54092
  const action = {
53169
54093
  resource: "mec-workflow-task",
53170
- id: `${cfg.stepId}:${shortHash4(sortedIds.join(","))}`,
54094
+ id: `${cfg.stepId}:${shortHash5(sortedIds.join(","))}`,
53171
54095
  verb: cfg.isSignOff ? "sign-off" : "reopen"
53172
54096
  };
53173
54097
  await preflightConfirmToken(action, cfg.token);
@@ -53366,7 +54290,7 @@ async function runMecMemberGrantBulk(cfg) {
53366
54290
  const hashInput = cfg.payload.emails.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
53367
54291
  const action = {
53368
54292
  resource: "mec-workflow-member-grant",
53369
- id: shortHash4(hashInput),
54293
+ id: shortHash5(hashInput),
53370
54294
  verb: "bulk"
53371
54295
  };
53372
54296
  await preflightConfirmToken(action, cfg.token);
@@ -53522,7 +54446,7 @@ async function runMecGroupGrantBulk(cfg) {
53522
54446
  const hashInput = cfg.payload.permission_groups.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
53523
54447
  const action = {
53524
54448
  resource: "mec-workflow-group-grant",
53525
- id: shortHash4(hashInput),
54449
+ id: shortHash5(hashInput),
53526
54450
  verb: "bulk"
53527
54451
  };
53528
54452
  await preflightConfirmToken(action, cfg.token);
@@ -54736,7 +55660,7 @@ function wireMec(program) {
54736
55660
  }
54737
55661
 
54738
55662
  // src/commands/workflow.ts
54739
- import { createHash as createHash13 } from "crypto";
55663
+ import { createHash as createHash14 } from "crypto";
54740
55664
  function dash6(s) {
54741
55665
  if (s === null || s === void 0) return "-";
54742
55666
  if (typeof s === "number") return String(s);
@@ -55204,13 +56128,13 @@ function renderGroupList(out, rows) {
55204
56128
  (${rows.length} group${plural(rows.length)})
55205
56129
  `);
55206
56130
  }
55207
- function shortHash5(input) {
55208
- return createHash13("sha256").update(input).digest("hex").slice(0, 12);
56131
+ function shortHash6(input) {
56132
+ return createHash14("sha256").update(input).digest("hex").slice(0, 12);
55209
56133
  }
55210
56134
  async function runWorkflowCreate(cfg) {
55211
56135
  const action = {
55212
56136
  resource: "workflow-template",
55213
- id: shortHash5(JSON.stringify(cfg.payload)),
56137
+ id: shortHash6(JSON.stringify(cfg.payload)),
55214
56138
  verb: "create"
55215
56139
  };
55216
56140
  await preflightConfirmToken(action, cfg.token);
@@ -55852,7 +56776,7 @@ async function runTaskDelete(cfg) {
55852
56776
  const sortedIds = [...cfg.ids].map((r) => r.id).sort((a, b) => a - b);
55853
56777
  const action = {
55854
56778
  resource: "workflow-task-batch",
55855
- id: `${cfg.stepId}:${shortHash5(sortedIds.join(","))}`,
56779
+ id: `${cfg.stepId}:${shortHash6(sortedIds.join(","))}`,
55856
56780
  verb: "bulk-delete"
55857
56781
  };
55858
56782
  await preflightConfirmToken(action, cfg.token);
@@ -56015,7 +56939,7 @@ async function runTaskSignOff(cfg) {
56015
56939
  const sortedIds = [...cfg.ids].sort((a, b) => a - b);
56016
56940
  const action = {
56017
56941
  resource: "workflow-task",
56018
- id: `${cfg.stepId}:${shortHash5(sortedIds.join(","))}`,
56942
+ id: `${cfg.stepId}:${shortHash6(sortedIds.join(","))}`,
56019
56943
  verb: cfg.isSignOff ? "sign-off" : "reopen"
56020
56944
  };
56021
56945
  await preflightConfirmToken(action, cfg.token);
@@ -56212,7 +57136,7 @@ async function runMemberGrantBulk(cfg) {
56212
57136
  const hashInput = cfg.payload.emails.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
56213
57137
  const action = {
56214
57138
  resource: "workflow-member-grant",
56215
- id: shortHash5(hashInput),
57139
+ id: shortHash6(hashInput),
56216
57140
  verb: "bulk"
56217
57141
  };
56218
57142
  await preflightConfirmToken(action, cfg.token);
@@ -56368,7 +57292,7 @@ async function runGroupGrantBulk(cfg) {
56368
57292
  const hashInput = cfg.payload.permission_groups.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
56369
57293
  const action = {
56370
57294
  resource: "workflow-group-grant",
56371
- id: shortHash5(hashInput),
57295
+ id: shortHash6(hashInput),
56372
57296
  verb: "bulk"
56373
57297
  };
56374
57298
  await preflightConfirmToken(action, cfg.token);
@@ -57364,7 +58288,7 @@ function wireWorkflow(root) {
57364
58288
  }
57365
58289
 
57366
58290
  // src/commands/cash.ts
57367
- import { createHash as createHash14 } from "crypto";
58291
+ import { createHash as createHash15 } from "crypto";
57368
58292
  import { promises as fs25 } from "fs";
57369
58293
 
57370
58294
  // src/api/cash.ts
@@ -58455,7 +59379,7 @@ async function runCashRulesDeleteAll(cfg) {
58455
59379
  return;
58456
59380
  }
58457
59381
  const ids = targets.map((r) => r.id).sort((a, b) => a - b);
58458
- const sha = createHash14("sha256").update(ids.join(",")).digest("hex").slice(0, 12);
59382
+ const sha = createHash15("sha256").update(ids.join(",")).digest("hex").slice(0, 12);
58459
59383
  const action = {
58460
59384
  resource: "cash-rules-batch",
58461
59385
  id: `${templateId}:${sha}`,
@@ -68173,7 +69097,7 @@ function parseDueDate(raw) {
68173
69097
  }
68174
69098
  return trimmed;
68175
69099
  }
68176
- async function readTicketDescription(inline, file, readFile8 = (p) => fs31.readFile(p, "utf8")) {
69100
+ async function readTicketDescription(inline, file, readFile9 = (p) => fs31.readFile(p, "utf8")) {
68177
69101
  if (inline !== void 0 && file !== void 0) {
68178
69102
  throw new Error("pass --description or --description-file, not both.");
68179
69103
  }
@@ -68181,7 +69105,7 @@ async function readTicketDescription(inline, file, readFile8 = (p) => fs31.readF
68181
69105
  if (file.trim() === "") throw new Error("--description-file needs a path.");
68182
69106
  let body;
68183
69107
  try {
68184
- body = await readFile8(file);
69108
+ body = await readFile9(file);
68185
69109
  } catch (err) {
68186
69110
  const msg = err instanceof Error ? err.message : String(err);
68187
69111
  throw new Error(`--description-file ${file}: ${msg}`);
@@ -69880,7 +70804,7 @@ function commandPath(actionCommand) {
69880
70804
  // src/telemetry/config.ts
69881
70805
  import { homedir as homedir6 } from "os";
69882
70806
  import { join as join16 } from "path";
69883
- import { readFile as readFile7 } from "fs/promises";
70807
+ import { readFile as readFile8 } from "fs/promises";
69884
70808
  var TELEMETRY_ENV = "DR_CLI_TELEMETRY";
69885
70809
  var DISABLED_VALUES = /* @__PURE__ */ new Set(["0", "false", "off", "no"]);
69886
70810
  function isTelemetryEnabled(env = process.env) {
@@ -69918,7 +70842,7 @@ function remoteSettingsPath() {
69918
70842
  async function readCollectorConfig(path12 = remoteSettingsPath()) {
69919
70843
  let raw;
69920
70844
  try {
69921
- raw = await readFile7(path12, "utf8");
70845
+ raw = await readFile8(path12, "utf8");
69922
70846
  } catch {
69923
70847
  return null;
69924
70848
  }