@datarails-ai/dr-cli 1.6.74 → 1.6.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # dr-cli
2
2
 
3
- Datarails CLI. Server keys: `US`, `US2`, `UK`, `CA`, `EU`, `DEV`, `DEV-1`,
4
- `TEST` — or pass a full `https://<host>.datarails.com` URL to reach an
3
+ Datarails CLI. Server keys: `US`, `US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEV`,
4
+ `DEV-1`, `TEST` — or pass a full `https://<host>.datarails.com` URL to reach an
5
5
  ad-hoc (e.g. PR-preview) environment. Credentials live in the OS keychain (Windows Credential Manager,
6
6
  macOS Keychain), or in a credential file when one is present — which wins over
7
7
  the keychain, and is how `dr` runs where no keychain exists (CI, headless hosts,
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.74",
11
+ version: "1.6.76",
12
12
  description: "Datarails CLI",
13
13
  type: "module",
14
14
  engines: {
@@ -38,7 +38,7 @@ var package_default = {
38
38
  postinstall: "node scripts/postinstall.mjs"
39
39
  },
40
40
  dependencies: {
41
- "@datarails-ai/dr-appspace-cli": "0.9.5",
41
+ "@datarails-ai/dr-appspace-cli": "0.10.0",
42
42
  "@modelcontextprotocol/sdk": "^1.29.0",
43
43
  "@napi-rs/keyring": "^1.3.0",
44
44
  commander: "^12.1.0",
@@ -67,7 +67,7 @@ var package_default = {
67
67
  };
68
68
 
69
69
  // src/buildInfo.ts
70
- var BUILD_TIME = "2026-09-30T12:03:03.518Z";
70
+ var BUILD_TIME = "2026-09-30T13:02:37.064Z";
71
71
  function formatBuildTime(iso) {
72
72
  if (iso === "") return "";
73
73
  const d = new Date(iso);
@@ -412,6 +412,7 @@ var ordered = [
412
412
  { key: "UK", host: "https://ukapp.datarails.com", subDomain: "uk" },
413
413
  { key: "CA", host: "https://caapp.datarails.com", subDomain: "canada" },
414
414
  { key: "EU", host: "https://euapp.datarails.com", subDomain: "eu" },
415
+ { key: "STUDIO", host: "https://studio.datarails.com", subDomain: "studio" },
415
416
  { key: "DEV", host: "https://dev.datarails.com", subDomain: "dev" },
416
417
  { key: "DEV-1", host: "https://dev-1.datarails.com", subDomain: "dev-1" },
417
418
  { key: "TEST", host: "https://testapp.datarails.com", subDomain: "test" },
@@ -437,7 +438,7 @@ function list() {
437
438
  function keys() {
438
439
  return ordered.map((s) => s.key);
439
440
  }
440
- var PROD_KEYS = /* @__PURE__ */ new Set(["US", "US2", "UK", "CA", "EU", "DEMO"]);
441
+ var PROD_KEYS = /* @__PURE__ */ new Set(["US", "US2", "UK", "CA", "EU", "STUDIO", "DEMO"]);
441
442
  function isProdServer(input) {
442
443
  let server;
443
444
  try {
@@ -5116,7 +5117,7 @@ async function poll(host, path11, deviceCode, options, onSuccess) {
5116
5117
  const interval = options.interval ?? 2e3;
5117
5118
  const maxWait = options.maxWait ?? 6e5;
5118
5119
  const now = options.now ?? Date.now;
5119
- const sleep3 = options.sleep ?? defaultSleep;
5120
+ const sleep4 = options.sleep ?? defaultSleep;
5120
5121
  const signal = options.signal;
5121
5122
  const base = host.replace(/\/+$/, "");
5122
5123
  const deadline = now() + maxWait;
@@ -5144,7 +5145,7 @@ async function poll(host, path11, deviceCode, options, onSuccess) {
5144
5145
  }
5145
5146
  const errCode = tryParseErrorCode(text2);
5146
5147
  if (errCode === "authorization_pending" || errCode === "slow_down") {
5147
- await sleep3(interval, signal);
5148
+ await sleep4(interval, signal);
5148
5149
  continue;
5149
5150
  }
5150
5151
  if (errCode) {
@@ -12048,7 +12049,7 @@ var defaultSleep2 = (ms, signal) => new Promise((resolve3, reject) => {
12048
12049
  async function pollTask(client, label, path11, options = {}) {
12049
12050
  const interval = options.pollIntervalMs ?? AGGREGATE_POLL_INTERVAL_MS;
12050
12051
  const timeout = options.timeoutMs ?? AGGREGATE_POLL_TIMEOUT_MS;
12051
- const sleep3 = options.sleep ?? defaultSleep2;
12052
+ const sleep4 = options.sleep ?? defaultSleep2;
12052
12053
  const now = options.now ?? Date.now;
12053
12054
  const inner = new AbortController();
12054
12055
  const onOuterAbort = () => inner.abort(options.signal?.reason);
@@ -12090,7 +12091,7 @@ async function pollTask(client, label, path11, options = {}) {
12090
12091
  return result.env;
12091
12092
  }
12092
12093
  try {
12093
- await sleep3(interval, inner.signal);
12094
+ await sleep4(interval, inner.signal);
12094
12095
  } catch {
12095
12096
  const reason = inner.signal.reason instanceof Error ? inner.signal.reason.message : String(inner.signal.reason ?? "aborted");
12096
12097
  throw new Error(`${label} polling timed out: ${reason}`);
@@ -16881,7 +16882,7 @@ var LUT_SAVE_POLL_TIMEOUT_MS = 3e5;
16881
16882
  async function saveMappings(client, documentId, body, options = {}) {
16882
16883
  const interval = options.pollIntervalMs ?? LUT_SAVE_POLL_INTERVAL_MS;
16883
16884
  const timeout = options.timeoutMs ?? LUT_SAVE_POLL_TIMEOUT_MS;
16884
- const sleep3 = options.sleep ?? defaultPollSleep;
16885
+ const sleep4 = options.sleep ?? defaultPollSleep;
16885
16886
  const now = options.now ?? Date.now;
16886
16887
  const deadline = now() + timeout;
16887
16888
  const path11 = `/api/documents/${documentId}/web_update_lut`;
@@ -16903,7 +16904,7 @@ async function saveMappings(client, documentId, body, options = {}) {
16903
16904
  `lut save-mappings: the server is still writing after ${Math.round(timeout / 1e3)}s (task ${cacheKey}). The write is still running server-side \u2014 re-run the same command to rejoin it, or raise --timeout.`
16904
16905
  );
16905
16906
  }
16906
- await sleep3(interval, options.signal);
16907
+ await sleep4(interval, options.signal);
16907
16908
  const tick = await postOnce(client, pollPath, payload, options.signal);
16908
16909
  if (tick.status === 200) {
16909
16910
  return toResult(tick.text);
@@ -17201,7 +17202,7 @@ async function exportDocument(client, id, destinationOrgIds, override, reqOption
17201
17202
  async function pollDocumentJob(client, basePath, label, requestId, options = {}) {
17202
17203
  const interval = options.pollIntervalMs ?? AUTO_CALC_POLL_INTERVAL_MS;
17203
17204
  const timeout = options.timeoutMs ?? AUTO_CALC_POLL_TIMEOUT_MS;
17204
- const sleep3 = options.sleep ?? defaultSleep3;
17205
+ const sleep4 = options.sleep ?? defaultSleep3;
17205
17206
  const now = options.now ?? Date.now;
17206
17207
  const deadline = now() + timeout;
17207
17208
  for (; ; ) {
@@ -17229,7 +17230,7 @@ async function pollDocumentJob(client, basePath, label, requestId, options = {})
17229
17230
  `${label} polling timed out after ${timeout}ms (request ${requestId} still processing)`
17230
17231
  );
17231
17232
  }
17232
- await sleep3(interval, options.signal);
17233
+ await sleep4(interval, options.signal);
17233
17234
  }
17234
17235
  }
17235
17236
 
@@ -20454,7 +20455,7 @@ async function waitForReAggregation(cfg) {
20454
20455
  let interval = cfg.pollIntervalMs ?? REAGG_POLL_INTERVAL_MS;
20455
20456
  const maxInterval = cfg.maxPollIntervalMs ?? REAGG_POLL_MAX_INTERVAL_MS;
20456
20457
  const timeout = cfg.timeoutMs ?? REAGG_POLL_TIMEOUT_MS;
20457
- const sleep3 = cfg.sleep ?? defaultSleep4;
20458
+ const sleep4 = cfg.sleep ?? defaultSleep4;
20458
20459
  const now = cfg.now ?? Date.now;
20459
20460
  const accepted = [...cfg.accepted];
20460
20461
  const observedProcessing = /* @__PURE__ */ new Set();
@@ -20489,7 +20490,7 @@ async function waitForReAggregation(cfg) {
20489
20490
  break;
20490
20491
  }
20491
20492
  try {
20492
- await sleep3(interval, cfg.signal);
20493
+ await sleep4(interval, cfg.signal);
20493
20494
  } catch {
20494
20495
  aborted = true;
20495
20496
  break;
@@ -23096,7 +23097,7 @@ async function waitForSuggestionRun(r, cfg, runId) {
23096
23097
  let interval = cfg.pollIntervalMs ?? SUGGESTIONS_POLL_INTERVAL_MS;
23097
23098
  const maxInterval = cfg.maxPollIntervalMs ?? SUGGESTIONS_POLL_MAX_INTERVAL_MS;
23098
23099
  const timeout = cfg.timeoutMs ?? SUGGESTIONS_POLL_TIMEOUT_MS;
23099
- const sleep3 = cfg.sleep ?? defaultSleep5;
23100
+ const sleep4 = cfg.sleep ?? defaultSleep5;
23100
23101
  const now = cfg.now ?? Date.now;
23101
23102
  const errOut = cfg.errOut ?? process.stderr;
23102
23103
  const start = now();
@@ -23126,7 +23127,7 @@ async function waitForSuggestionRun(r, cfg, runId) {
23126
23127
  break;
23127
23128
  }
23128
23129
  try {
23129
- await sleep3(interval, cfg.signal);
23130
+ await sleep4(interval, cfg.signal);
23130
23131
  } catch {
23131
23132
  aborted = true;
23132
23133
  break;
@@ -25321,7 +25322,7 @@ var defaultSleep6 = (ms, signal) => new Promise((resolve3, reject) => {
25321
25322
  });
25322
25323
  async function putDataWithPolling(client, dashboardId, id, payload, options = {}) {
25323
25324
  const interval = options.pollIntervalMs ?? WIDGET_DATA_POLL_INTERVAL_MS;
25324
- const sleep3 = options.sleep ?? defaultSleep6;
25325
+ const sleep4 = options.sleep ?? defaultSleep6;
25325
25326
  const path11 = `/api/dashboards/${dashboardId}/widgets/${id}/data`;
25326
25327
  const body = JSON.stringify(payload);
25327
25328
  const first = await putDataOnce(client, path11, body, options);
@@ -25338,7 +25339,7 @@ async function putDataWithPolling(client, dashboardId, id, payload, options = {}
25338
25339
  const pollPath = `${path11}?cache_key=${encodeURIComponent(cacheKey)}`;
25339
25340
  for (; ; ) {
25340
25341
  try {
25341
- await sleep3(interval, options.signal);
25342
+ await sleep4(interval, options.signal);
25342
25343
  } catch (err) {
25343
25344
  const reason = err instanceof Error ? err.message : String(err);
25344
25345
  throw new Error(`widget data polling timed out: ${reason}`);
@@ -29099,6 +29100,9 @@ function registerSkillCommand(root) {
29099
29100
  });
29100
29101
  }
29101
29102
 
29103
+ // src/commands/datamappers.ts
29104
+ import { setTimeout as sleep3 } from "timers/promises";
29105
+
29102
29106
  // src/api/datamappers.ts
29103
29107
  async function list20(client, tableId, options) {
29104
29108
  const out = await client.getJSON(`/api/templates/${tableId}/mappers`, options);
@@ -29116,6 +29120,14 @@ async function get14(client, tableId, id, options) {
29116
29120
  }
29117
29121
  return out;
29118
29122
  }
29123
+ async function getDetails2(client, id, options) {
29124
+ const path11 = `/api/templates/mappers/${id}`;
29125
+ const out = await client.getJSON(path11, options);
29126
+ if (out === null) {
29127
+ throw new Error(`GET ${path11}: empty response from datamapper details`);
29128
+ }
29129
+ return out;
29130
+ }
29119
29131
  async function create12(client, tableId, body, options) {
29120
29132
  const path11 = `/api/templates/${tableId}/mappers`;
29121
29133
  const out = await client.postJSON(path11, body, options);
@@ -29864,13 +29876,15 @@ async function runDatamappersUpdate(cfg) {
29864
29876
  body = decodedBody;
29865
29877
  }
29866
29878
  validateCalcFields(extractCalcFields(body));
29879
+ const err = cfg.err ?? process.stderr;
29880
+ const scanBaseline = rescanValue === "latest" && cfg.verifyScanDispatch !== void 0 ? await captureScanBaseline(cfg.client, cfg.id, cfg.signal, err) : void 0;
29867
29881
  let updated;
29868
29882
  try {
29869
29883
  updated = await update8(cfg.client, cfg.id, body, {
29870
29884
  signal: cfg.signal
29871
29885
  });
29872
- } catch (err) {
29873
- const msg = err instanceof Error ? err.message : String(err);
29886
+ } catch (err2) {
29887
+ const msg = err2 instanceof Error ? err2.message : String(err2);
29874
29888
  if (/HTTP 500/.test(msg)) {
29875
29889
  const hint = "hint: the server logs an `ETLError: Custom Columns: The creation of '<field>' field has failed due to incorrect formula syntax.` line for this case but the response body is a bare 500 HTML page. Common causes the client-side guard cannot detect: (1) a [field] reference in a calc formula that does not resolve to any header `map_to`, calc-field `name`, or system field (`[FileBox_ID]`, `[System_Year]`, `[Posting Date]`, \u2026), (2) an unsupported function name (try copying the function spelling from another working calc field via `dr datamappers get`), (3) a string literal that's a plain-English placeholder (e.g. `\"Customer or Client name\"`) which parses but the ETL refuses. Iterate on calc-field formulas one at a time. If every formula looks correct and PUT still 500s, fall back to the workaround: `dr datamappers create from-file <table> --file <new.json>` then `dr datamappers delete <old_id> --table <table>` (note: this orphans references \u2014 LUTs/dashboards/functions pointing at the old mapper id break).";
29876
29890
  const responseBody = extractResponseBodyFromError(msg);
@@ -29896,13 +29910,106 @@ Full request + response captured to: ${logPath}
29896
29910
 
29897
29911
  ${hint}${logSuffix}`);
29898
29912
  }
29899
- throw err;
29913
+ throw err2;
29900
29914
  }
29901
29915
  if (cfg.asJson) {
29902
29916
  writeJSONIndented(cfg.out, updated);
29903
- return;
29917
+ } else {
29918
+ renderMappersTable(cfg.out, [updated]);
29919
+ }
29920
+ if (scanBaseline !== void 0) {
29921
+ const opts = cfg.verifyScanDispatch ?? {};
29922
+ const started2 = await waitForNewScan(cfg.client, scanBaseline, cfg.signal, opts);
29923
+ if (!started2) {
29924
+ const docIds = await mappedDocumentIds(
29925
+ cfg.client,
29926
+ scanBaseline.templateId,
29927
+ cfg.id,
29928
+ cfg.signal
29929
+ );
29930
+ err.write(
29931
+ formatNoScanWarning({
29932
+ mapperId: cfg.id,
29933
+ templateId: scanBaseline.templateId,
29934
+ timeoutMs: opts.timeoutMs ?? DEFAULT_SCAN_DISPATCH_TIMEOUT_MS,
29935
+ docIds
29936
+ })
29937
+ );
29938
+ }
29939
+ }
29940
+ }
29941
+ var DEFAULT_SCAN_DISPATCH_TIMEOUT_MS = 3e4;
29942
+ var DEFAULT_SCAN_DISPATCH_POLL_MS = 2e3;
29943
+ function scanFingerprint(report) {
29944
+ let maxScanId = 0;
29945
+ for (const doc of report.documents ?? []) {
29946
+ for (const v of doc.versions ?? []) {
29947
+ if (typeof v.scan_id === "number" && v.scan_id > maxScanId) maxScanId = v.scan_id;
29948
+ }
29949
+ }
29950
+ return `${report.scans_status?.last_updated ?? ""}|${maxScanId}`;
29951
+ }
29952
+ async function captureScanBaseline(client, mapperId, signal, err) {
29953
+ try {
29954
+ const details = await getDetails2(client, mapperId, { signal });
29955
+ const templateId = details.template.id;
29956
+ const report = await getStatus(client, templateId, { signal });
29957
+ return { templateId, fingerprint: scanFingerprint(report) };
29958
+ } catch (e) {
29959
+ if (signal.aborted) throw e;
29960
+ err.write(
29961
+ "note: could not read the scan status before the update, so dr cannot confirm a rescan starts. Check with `dr scans status <template_id>` afterwards.\n"
29962
+ );
29963
+ return void 0;
29904
29964
  }
29905
- renderMappersTable(cfg.out, [updated]);
29965
+ }
29966
+ async function waitForNewScan(client, baseline, signal, opts) {
29967
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SCAN_DISPATCH_TIMEOUT_MS;
29968
+ const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_SCAN_DISPATCH_POLL_MS;
29969
+ const deadline = Date.now() + timeoutMs;
29970
+ for (; ; ) {
29971
+ try {
29972
+ const report = await getStatus(client, baseline.templateId, { signal });
29973
+ if (scanFingerprint(report) !== baseline.fingerprint) return true;
29974
+ } catch (e) {
29975
+ if (signal.aborted) throw e;
29976
+ }
29977
+ const remaining = deadline - Date.now();
29978
+ if (remaining <= 0) return false;
29979
+ await sleep3(Math.min(pollIntervalMs, remaining), void 0, { signal });
29980
+ }
29981
+ }
29982
+ async function mappedDocumentIds(client, templateId, mapperId, signal) {
29983
+ try {
29984
+ const mapper = await get14(client, templateId, mapperId, { signal });
29985
+ return Array.isArray(mapper.mapped_documents_ids) ? mapper.mapped_documents_ids : void 0;
29986
+ } catch (e) {
29987
+ if (signal.aborted) throw e;
29988
+ return void 0;
29989
+ }
29990
+ }
29991
+ function formatNoScanWarning(p) {
29992
+ const secs = Math.round(p.timeoutMs / 1e3);
29993
+ const lines = [
29994
+ `warning: mapper ${p.mapperId} was saved, but no new scan showed up on template ${p.templateId} within ${secs}s.`,
29995
+ ' Either the scan is still queued, or this server predates the DR-54200 fix and ignored rescan="latest", so existing data still has the old config.',
29996
+ ` Check first: dr scans status ${p.templateId}`
29997
+ ];
29998
+ if (p.docIds !== void 0 && p.docIds.length === 0) {
29999
+ lines.push(" The mapper has no bound documents, so there is nothing to scan.");
30000
+ } else {
30001
+ const docFlags = p.docIds !== void 0 ? p.docIds.map((d) => `--doc ${d}`).join(" ") : "--doc <doc_id> [--doc <doc_id> ...]";
30002
+ lines.push(
30003
+ " If no new scan appears there, re-bind the mapper to its current documents with a rescan:"
30004
+ );
30005
+ if (p.docIds === void 0) {
30006
+ lines.push(
30007
+ ` dr datamappers get ${p.mapperId} --table ${p.templateId} --json # read mapped_documents_ids`
30008
+ );
30009
+ }
30010
+ lines.push(` dr datamappers bind ${p.mapperId} ${docFlags} --rescan latest`);
30011
+ }
30012
+ return lines.join("\n") + "\n";
29906
30013
  }
29907
30014
  async function buildConveniencePayload(client, tableId, name, versionId, sheet, flags, signal) {
29908
30015
  const fieldTypes = parseFieldOverrides(flags.fieldOverrides);
@@ -30217,7 +30324,7 @@ function renderMappersTable(out, mappers) {
30217
30324
  }
30218
30325
  ];
30219
30326
  for (const m of mappers) {
30220
- const ordinal = m.ordinal === null ? "-" : String(m.ordinal);
30327
+ const ordinal = m.ordinal == null ? "-" : String(m.ordinal);
30221
30328
  rows.push({
30222
30329
  cells: [
30223
30330
  String(m.id),
@@ -30515,7 +30622,7 @@ function registerDatamappersCommand(root) {
30515
30622
  addServerFlag(fromFileCmd);
30516
30623
  addAccountFlag(fromFileCmd);
30517
30624
  const updateCmd = dm.command("update").description(
30518
- 'Update a datamapper (payload-file only \u2014 server accepts config+sheet_name+rescan). `--rescan latest` (the default) mirrors the UI "Publish and scan": it re-extracts only the LATEST document version onto the live template. For a full historical rescan (every calculated field re-executed against every document version ever uploaded), use `dr rescan table <template_id>` instead \u2014 that is far more expensive and higher-blast.'
30625
+ "Update a datamapper in place (payload-file only \u2014 server accepts config+sheet_name+rescan). `--rescan latest` (the default) re-extracts the LATEST version of each document bound to the mapper. Servers without the DR-54200 fix save the config and start no scan, so after the update dr watches the template for a new scan and, when none shows up, prints how to check `dr scans status` and the `dr datamappers bind` fallback. For a full historical rescan (every calculated field re-executed against every document version ever uploaded), use `dr rescan table <template_id>` instead \u2014 that is far more expensive and higher-blast."
30519
30626
  ).argument("<mapper_id>", "mapper id (positive integer)").option("--payload-file <path>", "Path to a JSON file with the PUT body (required)", "").option(
30520
30627
  "--field <NAME:TYPE>",
30521
30628
  'Set a column type via fields_to_change (Text|Float|Date|Integer). Repeatable. Mirrors the UI "Publish and scan" so a calc column referencing a LUT-injected column materialises instead of 500ing on inferred types. Note: the server blocks a type change (HTTP 400) when the template has more than one mapper.',
@@ -30538,7 +30645,8 @@ function registerDatamappersCommand(root) {
30538
30645
  serverFlag: opts.server ?? "",
30539
30646
  asJson: opts.json === true,
30540
30647
  signal: setup.signal,
30541
- out: process.stdout
30648
+ out: process.stdout,
30649
+ verifyScanDispatch: {}
30542
30650
  });
30543
30651
  } finally {
30544
30652
  setup.cleanup();
@@ -32704,7 +32812,7 @@ async function runIntegrationsSync(cfg) {
32704
32812
  throw new Error("integration id must be positive");
32705
32813
  }
32706
32814
  const interval = cfg.pollIntervalMs ?? INTEGRATIONS_SYNC_POLL_INTERVAL_MS;
32707
- const sleep3 = cfg.sleep ?? defaultSleep7;
32815
+ const sleep4 = cfg.sleep ?? defaultSleep7;
32708
32816
  const resp = await resync(cfg.client, cfg.id, {
32709
32817
  signal: cfg.signal
32710
32818
  });
@@ -32753,7 +32861,7 @@ async function runIntegrationsSync(cfg) {
32753
32861
  return;
32754
32862
  }
32755
32863
  try {
32756
- await sleep3(interval, inner.signal);
32864
+ await sleep4(interval, inner.signal);
32757
32865
  } catch {
32758
32866
  cfg.out.write(
32759
32867
  `Timed out waiting for sync to finish \u2014 last status was ${orDash(lastStatus)}
@@ -37527,7 +37635,7 @@ async function runFileboxSetVersionFileStatus(cfg) {
37527
37635
  throw new Error("version id must be positive");
37528
37636
  }
37529
37637
  const err = cfg.err ?? process.stderr;
37530
- const sleep3 = cfg.sleep ?? defaultCycleTagSleep;
37638
+ const sleep4 = cfg.sleep ?? defaultCycleTagSleep;
37531
37639
  const settleMs = cfg.tagSettleDelayMs ?? CYCLE_TAG_SETTLE_DELAY_MS;
37532
37640
  const details = await getDetails(cfg.client, cfg.docId, { signal: cfg.signal });
37533
37641
  const tag = (details.tags_config ?? []).find(isFileStatusTag);
@@ -37620,7 +37728,7 @@ async function runFileboxSetVersionFileStatus(cfg) {
37620
37728
  }
37621
37729
  };
37622
37730
  const readBack = async () => {
37623
- await sleep3(settleMs, cfg.signal);
37731
+ await sleep4(settleMs, cfg.signal);
37624
37732
  return readVersionForConfirm(cfg.client, cfg.versionId, cfg.docId, cfg.signal);
37625
37733
  };
37626
37734
  await write();
@@ -58372,7 +58480,7 @@ async function defaultSleep8(ms, signal) {
58372
58480
  async function pollExtractionStatus(client, contractFileIds, options = {}) {
58373
58481
  const interval = options.pollIntervalMs ?? EXTRACT_POLL_INTERVAL_MS;
58374
58482
  const timeout = options.timeoutMs ?? EXTRACT_POLL_TIMEOUT_MS;
58375
- const sleep3 = options.sleep ?? defaultSleep8;
58483
+ const sleep4 = options.sleep ?? defaultSleep8;
58376
58484
  const now = options.now ?? Date.now;
58377
58485
  const resolved = /* @__PURE__ */ new Map();
58378
58486
  if (contractFileIds.length === 0) return { resolved, pending: [] };
@@ -58422,7 +58530,7 @@ async function pollExtractionStatus(client, contractFileIds, options = {}) {
58422
58530
  options.onTick?.(resolved.size, contractFileIds.length);
58423
58531
  if (resolved.size >= contractFileIds.length) break;
58424
58532
  if (now() > deadline || inner.signal.aborted) break;
58425
- await sleep3(interval, inner.signal);
58533
+ await sleep4(interval, inner.signal);
58426
58534
  }
58427
58535
  } finally {
58428
58536
  clearTimeout(deadlineTimer);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "1.6.74",
3
+ "cliVersion": "1.6.76",
4
4
  "_contract": "Resolve a command path through `aliases` (canonical prefix + alias segment -> canonical path) before any lookup. A path listed in `pendingCommands`, or whose first segment is a key of `pending`, is UNDECIDED, not unrestricted: fail closed or skip it, and never read a path's absence from `commands` as 'no requirement'. An entry carrying `denied` is UNAVAILABLE to any AGENT-SCOPED credential — one whose `scopes` claim carries at least one `resource:action` entry; a claim that is absent, empty, capability-only or wholly unparseable is UNRESTRICTED, and this mark does not apply to it — whatever else that entry says: it is not a ceiling and it is not always-available, its `denied.reason` says why and `denied.until` names the ticket that lifts it, and the absence of `requires` on it must NEVER be read as 'no requirement'. Only an entry in `commands` with an empty `requires` means always-available.",
5
5
  "commands": {
6
6
  "login": {
@@ -23,7 +23,8 @@ for any command come from `dr <command> --help` (the binary is authoritative).
23
23
  `register-identity`, `bulk-create`, `bulk-set-email`, `bulk-permissions`,
24
24
  `set-email`, `set-email-notifications`, `bulk-set-email-notifications`,
25
25
  `set-password`, `set-field-type`) — on `US`, `US2`, `UK`, `CA`,
26
- `EU`, `DEMO` (DEMO is a customer-facing prod tenant, not a sandbox) must be
26
+ `EU`, `STUDIO`, `DEMO` (DEMO is a customer-facing prod tenant, not a sandbox;
27
+ STUDIO is the free-tier environment and holds real prospects' data) must be
27
28
  confirmed by the user before you run them. `register-identity --confirm` in
28
29
  particular sends real invite emails to customers, so it counts as a prod
29
30
  write. Demonstrate on `DEV` / `DEV-1` / `TEST` / `TEST-1`..`TEST-7` first when
@@ -40,7 +41,7 @@ for any command come from `dr <command> --help` (the binary is authoritative).
40
41
  dialog (or TTY prompt) where the *user* types a fresh challenge word; in a
41
42
  headless shell it refuses non-zero. Don't add your own word ritual in chat.
42
43
  Non-prod (`DEV`/`DEV-1`/`TEST`/`TEST-1`..`TEST-7`) is token-only, a plain
43
- "yes" suffices; `DEMO` counts as prod. Details in
44
+ "yes" suffices; `DEMO` and `STUDIO` count as prod. Details in
44
45
  [reference/safety.md](reference/safety.md).
45
46
  4. **Credentials are sensitive.** Sessions live in the OS keychain, or in a
46
47
  credential file where one is in use — never extract or echo JWTs or session
@@ -103,7 +104,7 @@ update it in the file alongside the key, or the warning keeps naming the old
103
104
  one.
104
105
 
105
106
  `--server <KEY>` is **required on every command that hits an API**. Valid keys:
106
- `US`, `US2`, `UK`, `CA`, `EU`, `DEV`, `DEV-1`, `TEST`, `TEST-1`, `TEST-2`, `TEST-3`, `TEST-4`, `TEST-5`, `TEST-6`, `TEST-7`, `DEMO` (case-insensitive).
107
+ `US`, `US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEV`, `DEV-1`, `TEST`, `TEST-1`, `TEST-2`, `TEST-3`, `TEST-4`, `TEST-5`, `TEST-6`, `TEST-7`, `DEMO` (case-insensitive).
107
108
 
108
109
  You can also pass a full **`https://<host>.datarails.com` URL** instead of a
109
110
  key to reach an ad-hoc / PR-preview environment, e.g.
@@ -539,8 +540,8 @@ returns the source rows behind the number.
539
540
  counts — and a token; re-run with `--confirm-token` to fire it) → poll `scans
540
541
  status` again. This is a **full historical** rescan (every calc field × every
541
542
  version). To only pick up a datamapper change on the live template, prefer
542
- `datamappers update <id> --rescan latest` (latest version only; mirrors "Publish
543
- and scan") — far cheaper and lower-blast.
543
+ `datamappers update <id> --rescan latest` (latest version of the mapper's bound
544
+ documents only) — far cheaper and lower-blast.
544
545
 
545
546
  **Build a `create` body from plain-English intent** (calc models, datamappers,
546
547
  metrics): you resolve names → integer IDs yourself against the tenant
@@ -67,6 +67,12 @@ value-inference behavior. `NAME` is the target **table field** name (the
67
67
  ## Update / preview / bind / delete
68
68
 
69
69
  - `datamappers update`, `datamappers delete` (two-step confirm).
70
+ - `datamappers update --rescan latest` — after the PUT, dr watches the
71
+ template's scan status for up to 30s, because servers without the DR-54200 fix
72
+ save the config but ignore `rescan: "latest"`. If no new scan shows up, it
73
+ prints a warning with a ready `datamappers bind <id> --doc … --rescan latest`
74
+ command. Run `scans status <template_id>` first: the scan may only be queued,
75
+ and `bind` on a queued scan starts a second one. Don't re-run `update`.
70
76
  - `datamappers preview --payload-file <p>` — renders mapper output for sample
71
77
  input ("what would this extract?") before saving.
72
78
  - `datamappers bind <mapper_id> --doc <id> [--rescan latest|all]` — change which
@@ -117,7 +117,7 @@ org 99.
117
117
 
118
118
  Token TTL is 10 minutes — past that, re-run step 1.
119
119
 
120
- For prod (`US`/`US2`/`UK`/`CA`/`EU`/`DEMO`), get the user's in-chat "yes" before
120
+ For prod (`US`/`US2`/`UK`/`CA`/`EU`/`STUDIO`/`DEMO`), get the user's in-chat "yes" before
121
121
  re-invoking with the token; the binary's interactive gate then has the user
122
122
  type a challenge word in a dialog, same as any other destructive write — see
123
123
  [safety.md](safety.md).
@@ -38,7 +38,8 @@ Reads never need confirmation. Gated **writes** fall into two tiers (full table:
38
38
  `src/commands/_prodGateTiers.ts`):
39
39
 
40
40
  - **Tier A — destructive / irreversible / org-wide.** On a prod tenant (`US`,
41
- `US2`, `UK`, `CA`, `EU`, `DEMO` — DEMO is customer-facing) the binary requires a
41
+ `US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEMO` — DEMO is customer-facing, STUDIO holds
42
+ real prospects' data) the binary requires a
42
43
  live human to type a fresh challenge word in a desktop dialog / TTY (see
43
44
  "Binary-enforced interactive gate" below). Relay the plan output (target name
44
45
  + blast radius), get a clear in-chat "yes, do it", then re-invoke with
@@ -105,7 +106,7 @@ the LUT that 4 functions depend on" — before running.
105
106
 
106
107
  The human check is enforced **in the binary** — not by prose an agent could
107
108
  skip or forget. When a valid `--confirm-token` is redeemed for a **Tier-A**
108
- action against a prod tenant (`US`, `US2`, `UK`, `CA`, `EU`, `DEMO` — plus,
109
+ action against a prod tenant (`US`, `US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEMO` — plus,
109
110
  fail-closed, any server the CLI cannot positively classify as non-prod, and any
110
111
  action not positively classified as Tier B), the binary requires a live human:
111
112
 
@@ -150,7 +150,7 @@ except on `update`, which resolves the range's own table automatically.
150
150
  `--confirm-token` — token-only, no word gate even on prod (a rescan re-derives
151
151
  data, it doesn't delete it). To pick up a datamapper change on the live template
152
152
  **without** touching history, use `datamappers update <id> --rescan latest`
153
- instead (mirrors the UI "Publish and scan"); a full rescan will re-run pre-existing
153
+ instead (re-extracts the latest version of the mapper's bound documents); a full rescan will re-run pre-existing
154
154
  calc fields against old rows, so a latent divide-by-null there fails the whole
155
155
  scan. Refuses `UsageStats` on every tenant (system data, not customer-owned).
156
156
  - `dataflow get` — the dependency graph: inputs → mappers → LUTs → table →
@@ -67,7 +67,7 @@ These hold in every sub-skill; the detail is in
67
67
  [reference/safety-backup.md](reference/safety-backup.md).
68
68
 
69
69
  1. **Confirm the target server explicitly** — ask, never infer it from earlier
70
- conversation. `US`, `US2`, `UK`, `CA`, `EU`, `DEMO` are all prod.
70
+ conversation. `US`, `US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEMO` are all prod.
71
71
  2. **Back up every reachable server before the first write** of a session, not
72
72
  just the target — the export is also the diff baseline. One command:
73
73
  `dr error-enrichments export --all-servers --out <dir>`. Tell the user the
@@ -64,9 +64,10 @@ Groundcover envs are not dr-cli server keys. Envs are `prod-us`, `prod-east-us`,
64
64
  | `DEMO` | `prod-demo` |
65
65
  | `US` / `US2` | **ambiguous** across `prod-us`, `prod-east-us`, `prod-west-us-2` |
66
66
  | `EU` | **unmapped** — resolve it before mining (see below) |
67
+ | `STUDIO` | **unmapped** — resolve it before mining (see below) |
67
68
 
68
- `EU` was added to the registry after this table was written and its
69
- groundcover env has not been confirmed. Resolve it the same way as the US
69
+ `EU` and `STUDIO` were added to the registry after this table was written and
70
+ their groundcover envs have not been confirmed. Resolve them the same way as the US
70
71
  ambiguity before mining, and record the answer here once known.
71
72
 
72
73
  Resolve the US ambiguity with
@@ -9,8 +9,8 @@ reading.
9
9
 
10
10
  **1. Confirm the target server explicitly.** Ask (AskUserQuestion) which server
11
11
  to change — never infer it from earlier context in the conversation. Prod keys
12
- are `US`, `US2`, `UK`, `CA`, `EU`, `DEMO` (DEMO is customer-facing prod, not a
13
- sandbox). Demonstrate on `DEV` / `TEST-*` first when the user is undecided.
12
+ are `US`, `US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEMO` (DEMO is customer-facing prod, not a
13
+ sandbox; STUDIO is the free-tier environment and holds real prospects' data). Demonstrate on `DEV` / `TEST-*` first when the user is undecided.
14
14
 
15
15
  **2. Back up every reachable server first.** Before the first write of a
16
16
  session, one command — `export` takes the same sweep flags as `list` / `stats`,
@@ -44,7 +44,7 @@ fixing disliked rules is `dr-error-enrichment-maintain`.
44
44
  **1. Pick the region and the window, and prove they line up.** Ask which server
45
45
  the user intends to improve, then map it to the groundcover env before querying —
46
46
  `UK` is `prod-uk*`, `CA` is `prod-canada-central`, `DEMO` is `prod-demo`, and
47
- `US`/`US2` are ambiguous across the three US envs and `EU` is not mapped at
47
+ `US`/`US2` are ambiguous across the three US envs and `EU` and `STUDIO` are not mapped at
48
48
  all, so resolve those explicitly (see the reference). Mining one region's logs to justify a rule you then write to
49
49
  another is the failure mode this step exists to prevent. Start with a tight
50
50
  window and widen only after it returns rows.
@@ -23,7 +23,7 @@ Before issuing any commands, confirm the following with the user if not already
23
23
  | Input | How to resolve |
24
24
  |---|---|
25
25
  | **Target fileboxes** | IDs/names, or a folder name to expand — run `dr filebox list --server <S>` and filter by parent folder ID |
26
- | **Server** | Ask if not obvious from context (`US`, `US2`, `UK`, `CA`, `EU`, `DEV`, `DEV-1`, `TEST`, `TEST-1`, `TEST-2`, `TEST-3`, `TEST-4`, `TEST-5`, `TEST-6`, `TEST-7`, `DEMO`) |
26
+ | **Server** | Ask if not obvious from context (`US`, `US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEV`, `DEV-1`, `TEST`, `TEST-1`, `TEST-2`, `TEST-3`, `TEST-4`, `TEST-5`, `TEST-6`, `TEST-7`, `DEMO`) |
27
27
  | **Connection** | Run `dr integrations list --server <S>` and show the WorkatoBased options; ask the user to pick |
28
28
  | **Object type** | Ask — commonly `table`; surface the value from a reference filebox if one exists |
29
29
  | **Start date / End date** | Ask — commonly "start of month" / "end of month", which maps to `{{date_tag|somonth}}` / `{{date_tag|eomonth}}` |
@@ -69,7 +69,7 @@ If no reference exists, build the payload from the gathered inputs (see Step 4).
69
69
 
70
70
  ## Step 3 — Enable Upload via API on all target fileboxes
71
71
 
72
- Run in parallel for efficiency. This is a prod write — confirm with the user before running on `US`, `US2`, `UK`, `CA`, `EU`, `DEMO`.
72
+ Run in parallel for efficiency. This is a prod write — confirm with the user before running on `US`, `US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEMO`.
73
73
 
74
74
  ```bash
75
75
  dr documents support-api set <id1> --on --server <S> &
@@ -152,7 +152,7 @@ Offer to export the table as a CSV if the user wants it.
152
152
 
153
153
  ## Guard rails
154
154
 
155
- - **Prod confirmation required.** `support_api` and `data-query set` are writes. On `US`, `US2`, `UK`, `CA`, `EU`, `DEMO`, confirm in-chat before running.
155
+ - **Prod confirmation required.** `support_api` and `data-query set` are writes. On `US`, `US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEMO`, confirm in-chat before running.
156
156
  - **Never read out tokens or JWTs.** The UUID returned is not a secret, but session tokens are — do not surface them.
157
157
  - **Always verify the integration ID from `dr integrations list`** — do not guess or reuse IDs from a prior session without confirming they match the current tenant.
158
158
  - **Always quote file paths** in `--payload-file` on Windows to handle spaces in directory names.
@@ -53,7 +53,7 @@ The skill is invoked in one of these forms:
53
53
 
54
54
  ## Server keys (the universe to probe)
55
55
 
56
- `US`, `US2`, `UK`, `CA`, `EU`, `DEV`, `DEV-1`, `TEST`, `TEST-1`, `TEST-2`, `TEST-3`, `TEST-4`, `TEST-5`, `TEST-6`, `TEST-7`, `DEMO` — case-insensitive.
56
+ `US`, `US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEV`, `DEV-1`, `TEST`, `TEST-1`, `TEST-2`, `TEST-3`, `TEST-4`, `TEST-5`, `TEST-6`, `TEST-7`, `DEMO` — case-insensitive.
57
57
  These are the only valid `--server` values dr-cli accepts.
58
58
 
59
59
  ---
@@ -82,7 +82,7 @@ across all servers wastes turns and can scope the default wrongly.
82
82
  Print verbatim, then wait for the user's reply. Do not guess from
83
83
  context, prior turns, or `userEmail`:
84
84
 
85
- > Which server should I switch to `<email>` on? (`US`, `US2`, `UK`, `CA`, `EU`, `DEV`, `DEV-1`, `TEST`, `TEST-1`, `TEST-2`, `TEST-3`, `TEST-4`, `TEST-5`, `TEST-6`, `TEST-7`, `DEMO`)
85
+ > Which server should I switch to `<email>` on? (`US`, `US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEV`, `DEV-1`, `TEST`, `TEST-1`, `TEST-2`, `TEST-3`, `TEST-4`, `TEST-5`, `TEST-6`, `TEST-7`, `DEMO`)
86
86
 
87
87
  ### 2.2. Probe that one server
88
88
 
@@ -226,7 +226,7 @@ Use the **single-loop pattern** (same reason as Section 2 — parallel
226
226
  tool calls cancel each other on the first non-zero exit):
227
227
 
228
228
  ```bash
229
- for srv in US US2 UK CA EU DEV DEV-1 TEST TEST-1 TEST-2 TEST-3 TEST-4 TEST-5 TEST-6 TEST-7 DEMO; do
229
+ for srv in US US2 UK CA EU STUDIO DEV DEV-1 TEST TEST-1 TEST-2 TEST-3 TEST-4 TEST-5 TEST-6 TEST-7 DEMO; do
230
230
  echo "=== $srv ==="
231
231
  dr whoami --server "$srv" 2>&1 || true
232
232
  done
@@ -728,7 +728,7 @@ the package layout reaches every producer.
728
728
 
729
729
  - **Every write here is prod-tier.** Functions, filebox, mappers, lookups and custom
730
730
  currencies are all mutations. Confirm before running on `US`, `US2`, `UK`, `CA`,
731
- `EU` — **and on `DEMO`**, which is a customer-facing demo tenant, not a sandbox.
731
+ `EU`, `STUDIO` — **and on `DEMO`**, which is a customer-facing demo tenant, not a sandbox.
732
732
  Prefer a `DEV`/`TEST*` rehearsal when the org is unstated.
733
733
  - **Never guess a currency, a base, or a missing pair.** Each is a question for the
734
734
  customer. A wrong rate mislabels real money and nobody downstream can tell.
@@ -38,8 +38,8 @@ config before the operator wastes time debugging downstream symptoms.
38
38
 
39
39
  ## Inputs
40
40
 
41
- - `--server <key>` — required. Valid: `US`, `US2`, `UK`, `CA`, `EU`, `DEV`,
42
- `DEV-1`, `TEST`, `TEST-1`, `TEST-2`, `TEST-3`, `TEST-4`, `TEST-5`,
41
+ - `--server <key>` — required. Valid: `US`, `US2`, `UK`, `CA`, `EU`, `STUDIO`,
42
+ `DEV`, `DEV-1`, `TEST`, `TEST-1`, `TEST-2`, `TEST-3`, `TEST-4`, `TEST-5`,
43
43
  `TEST-6`, `TEST-7`, `DEMO` (case-insensitive). Default per project
44
44
  convention: `US`.
45
45
  - `--report` — optional. Writes a markdown report to cwd in addition to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-cli",
3
- "version": "1.6.74",
3
+ "version": "1.6.76",
4
4
  "description": "Datarails CLI",
5
5
  "type": "module",
6
6
  "engines": {
@@ -17,7 +17,7 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@datarails-ai/dr-appspace-cli": "0.9.5",
20
+ "@datarails-ai/dr-appspace-cli": "0.10.0",
21
21
  "@modelcontextprotocol/sdk": "^1.29.0",
22
22
  "@napi-rs/keyring": "^1.3.0",
23
23
  "commander": "^12.1.0",