@datarails-ai/dr-cli 1.6.148 → 1.6.150

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.148",
11
+ version: "1.6.150",
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.20",
43
+ "@datarails-ai/dr-appspace-cli": "0.12.21",
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-20T10:51:37.333Z";
72
+ var BUILD_TIME = "2026-10-20T20:43:46.967Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -23109,6 +23109,50 @@ function toWireFormula(input) {
23109
23109
  return wire;
23110
23110
  }
23111
23111
 
23112
+ // src/commands/_metricsFormat.ts
23113
+ var CURRENCY_PREFIXES = ['"$"', "\u20AC", "\xA3", "\u20AA"];
23114
+ var DIGITS = ["#,###", "####"];
23115
+ var DECIMALS = ["", ".#", ".##", ".###", ".####"];
23116
+ var SCALES = ["", "[kilo]k", "[mega]M"];
23117
+ var METRIC_FORMATS = (() => {
23118
+ const signs = [
23119
+ { prefix: "", suffix: "" },
23120
+ ...CURRENCY_PREFIXES.map((prefix) => ({ prefix, suffix: "" })),
23121
+ { prefix: "", suffix: "%" }
23122
+ ];
23123
+ const all = /* @__PURE__ */ new Set();
23124
+ for (const { prefix, suffix } of signs) {
23125
+ for (const digits of DIGITS) {
23126
+ for (const decimals of DECIMALS) {
23127
+ for (const scale of SCALES) {
23128
+ all.add(`${prefix}${digits}${decimals}${scale}${suffix}`);
23129
+ }
23130
+ }
23131
+ }
23132
+ }
23133
+ return all;
23134
+ })();
23135
+ var METRIC_FORMAT_HELP = `Display number format (quote it). Only formats the metric editor can produce are accepted, built in this order: optional currency prefix $ \u20AC \xA3 \u20AA; digits #,### (thousands separator) or ####; optional decimals .# to .####; optional scale [kilo]k or [mega]M; optional % suffix (not with a currency). Examples: '#,###', '$#,###.##', '#,###[mega]M', '####.#%'. $ is stored quoted ("$"#,###.##), as the editor stores it; either spelling is accepted.`;
23136
+ function validateMetricFormat(raw2) {
23137
+ const trimmed = raw2.trim();
23138
+ const canonical = trimmed.startsWith("$") ? `"$"${trimmed.slice(1)}` : trimmed;
23139
+ if (METRIC_FORMATS.has(canonical)) return canonical;
23140
+ const hashed = canonical.replace(/0/g, "#");
23141
+ const didYouMean = hashed !== canonical && METRIC_FORMATS.has(hashed) ? ` Did you mean '${hashed}'?` : "";
23142
+ throw new Error(
23143
+ [
23144
+ `invalid --format ${JSON.stringify(raw2)}: not a format the metric editor can produce.${didYouMean}`,
23145
+ "Build it from these parts, in order (each optional part may be left out):",
23146
+ " currency prefix: $ \u20AC \xA3 \u20AA (not together with %)",
23147
+ " digits: #,### or #### (with / without thousands separator)",
23148
+ " decimals: .# .## .### .#### (1-4 places)",
23149
+ " scale: [kilo]k or [mega]M (thousands / millions)",
23150
+ " percent suffix: %",
23151
+ "Examples: '#,###' '$#,###.##' '#,###[mega]M' '####.#%'"
23152
+ ].join("\n")
23153
+ );
23154
+ }
23155
+
23112
23156
  // src/commands/_reAggregateWait.ts
23113
23157
  var REAGG_POLL_INTERVAL_MS = 2e3;
23114
23158
  var REAGG_POLL_MAX_INTERVAL_MS = 15e3;
@@ -25952,6 +25996,7 @@ async function validateDimensionsExist(client, ids, template, signal) {
25952
25996
  }
25953
25997
  async function runMetricsCreateBase(cfg) {
25954
25998
  validateMetricName(cfg.name);
25999
+ const format = cfg.format === void 0 ? void 0 : validateMetricFormat(cfg.format);
25955
26000
  const { aggregator, isCumulative } = resolveMetricAggregator(cfg.aggOriginal);
25956
26001
  const filterList = parseMetricFilters(cfg.filters, cfg.excludeFilters);
25957
26002
  const dims = parseDimensionsList(cfg.dimensions);
@@ -25973,17 +26018,19 @@ async function runMetricsCreateBase(cfg) {
25973
26018
  if (dims.length > 0) body.dimensions = dims;
25974
26019
  if (cfg.description !== void 0) body.description = cfg.description;
25975
26020
  if (cfg.category !== void 0) body.category = cfg.category;
26021
+ if (format !== void 0) body.configuration = { presented_formula: "", format };
25976
26022
  const created = await create9(cfg.client, body, {
25977
26023
  signal: cfg.signal
25978
26024
  });
25979
26025
  if (cfg.asJson) {
25980
- writeJSONIndented(cfg.out, created);
26026
+ writeJSONIndented(cfg.out, decodeHtmlEntitiesDeep(created));
25981
26027
  return;
25982
26028
  }
25983
26029
  renderCreateTable(cfg.out, created);
25984
26030
  }
25985
26031
  async function runMetricsCreateCalc(cfg) {
25986
26032
  validateMetricName(cfg.name);
26033
+ const format = cfg.format === void 0 ? void 0 : validateMetricFormat(cfg.format);
25987
26034
  if (cfg.formula.trim() === "") {
25988
26035
  throw new Error("--formula is required and must be non-empty");
25989
26036
  }
@@ -25993,7 +26040,7 @@ async function runMetricsCreateCalc(cfg) {
25993
26040
  const body = {
25994
26041
  name: cfg.name,
25995
26042
  kind: "CALC",
25996
- configuration: { presented_formula },
26043
+ configuration: format === void 0 ? { presented_formula } : { presented_formula, format },
25997
26044
  is_approved_by_user: true
25998
26045
  };
25999
26046
  if (dims.length > 0) body.dimensions = dims;
@@ -26003,7 +26050,7 @@ async function runMetricsCreateCalc(cfg) {
26003
26050
  signal: cfg.signal
26004
26051
  });
26005
26052
  if (cfg.asJson) {
26006
- writeJSONIndented(cfg.out, created);
26053
+ writeJSONIndented(cfg.out, decodeHtmlEntitiesDeep(created));
26007
26054
  return;
26008
26055
  }
26009
26056
  renderCreateTable(cfg.out, created);
@@ -26030,6 +26077,7 @@ async function runMetricsCreateDriver(cfg) {
26030
26077
  renderCreateTable(cfg.out, created);
26031
26078
  }
26032
26079
  async function runMetricsUpdate(cfg) {
26080
+ const format = cfg.format === void 0 ? void 0 : validateMetricFormat(cfg.format);
26033
26081
  const body = {};
26034
26082
  if (cfg.name !== void 0) {
26035
26083
  validateMetricName(cfg.name);
@@ -26061,16 +26109,27 @@ async function runMetricsUpdate(cfg) {
26061
26109
  if (wantsFilterChange) {
26062
26110
  body.filters = parseMetricFilters(cfg.filters, cfg.excludeFilters);
26063
26111
  }
26064
- if (Object.keys(body).length === 0) {
26112
+ if (Object.keys(body).length === 0 && format === void 0) {
26065
26113
  throw new Error(
26066
- "no fields to update; provide at least one of --name, --description, --category, --dimensions, --hide/--unhide, --time-aggregation, --aggregator, --template, --value-field, --date-field, --filter/--exclude-filter, or --clear-filters"
26114
+ "no fields to update; provide at least one of --name, --description, --category, --dimensions, --hide/--unhide, --time-aggregation, --aggregator, --template, --value-field, --date-field, --filter/--exclude-filter, --clear-filters, or --format"
26067
26115
  );
26068
26116
  }
26117
+ if (format !== void 0) {
26118
+ const env = await get12(cfg.client, cfg.id, { signal: cfg.signal });
26119
+ envelopeErr(env.success, env.error);
26120
+ const current = decodeShape(env.data.configuration ?? {});
26121
+ const formula = current["presented_formula"];
26122
+ body.configuration = {
26123
+ ...current,
26124
+ presented_formula: typeof formula === "string" ? formula : "",
26125
+ format
26126
+ };
26127
+ }
26069
26128
  const updated = await update6(cfg.client, cfg.id, body, {
26070
26129
  signal: cfg.signal
26071
26130
  });
26072
26131
  if (cfg.asJson) {
26073
- writeJSONIndented(cfg.out, updated);
26132
+ writeJSONIndented(cfg.out, decodeHtmlEntitiesDeep(updated));
26074
26133
  return;
26075
26134
  }
26076
26135
  renderCreateTable(cfg.out, updated);
@@ -26729,7 +26788,8 @@ function renderDimensionPreview(out, resp, limit) {
26729
26788
  `);
26730
26789
  }
26731
26790
  }
26732
- function renderCreateTable(out, m) {
26791
+ function renderCreateTable(out, raw2) {
26792
+ const m = decodeHtmlEntitiesDeep(raw2);
26733
26793
  const dash10 = (v) => v === null || v === void 0 || v === "" ? "-" : String(v);
26734
26794
  const rows = [
26735
26795
  [
@@ -26741,7 +26801,8 @@ function renderCreateTable(out, m) {
26741
26801
  "DATE_FIELD",
26742
26802
  "VALUE_FIELD",
26743
26803
  "DIMS",
26744
- "HIDDEN"
26804
+ "HIDDEN",
26805
+ "FORMAT"
26745
26806
  ],
26746
26807
  [
26747
26808
  String(m.id),
@@ -26752,7 +26813,8 @@ function renderCreateTable(out, m) {
26752
26813
  dash10(m.date_field),
26753
26814
  dash10(m.value_field),
26754
26815
  String(m.dimensions?.length ?? 0),
26755
- String(m.is_hidden)
26816
+ String(m.is_hidden),
26817
+ dash10(m.configuration?.format)
26756
26818
  ]
26757
26819
  ];
26758
26820
  const widths = rows[0].map((_, i) => Math.max(...rows.map((r) => (r[i] ?? "").length)));
@@ -26812,12 +26874,13 @@ function wireMetricsCreate(metrics) {
26812
26874
  ).option("--description <s>", "Description").option("--category <id>", "Category ID").option(
26813
26875
  "--dimensions <ids>",
26814
26876
  "Comma-separated Dimension PKs (NOT template field IDs \u2014 list via 'dr metrics dimensions list')"
26815
- ).option("--json", "Output raw JSON instead of a table", false).action(
26877
+ ).option("--format <f>", METRIC_FORMAT_HELP).option("--json", "Output raw JSON instead of a table", false).action(
26816
26878
  async (opts) => {
26817
26879
  const template = validateFieldId(opts.template ?? "", "--template");
26818
26880
  const valueField = validateFieldId(opts.valueField ?? "", "--value-field");
26819
26881
  const dateField = validateFieldId(opts.dateField ?? "", "--date-field");
26820
26882
  const category = opts.category === void 0 ? void 0 : validateFieldId(opts.category, "--category");
26883
+ if (opts.format !== void 0) validateMetricFormat(opts.format);
26821
26884
  const setup = await setupCall(opts.server, opts.account);
26822
26885
  try {
26823
26886
  await runMetricsCreateBase({
@@ -26833,6 +26896,7 @@ function wireMetricsCreate(metrics) {
26833
26896
  dimensions: opts.dimensions,
26834
26897
  description: opts.description,
26835
26898
  category,
26899
+ format: opts.format,
26836
26900
  asJson: opts.json === true,
26837
26901
  signal: setup.signal,
26838
26902
  out: process.stdout
@@ -26850,9 +26914,10 @@ function wireMetricsCreate(metrics) {
26850
26914
  ).option("--description <s>", "Description").option("--category <id>", "Category ID").option(
26851
26915
  "--dimensions <ids>",
26852
26916
  "Comma-separated Dimension PKs (NOT template field IDs \u2014 list via 'dr metrics dimensions list')"
26853
- ).option("--json", "Output raw JSON instead of a table", false).action(
26917
+ ).option("--format <f>", METRIC_FORMAT_HELP).option("--json", "Output raw JSON instead of a table", false).action(
26854
26918
  async (opts) => {
26855
26919
  const category = opts.category === void 0 ? void 0 : validateFieldId(opts.category, "--category");
26920
+ if (opts.format !== void 0) validateMetricFormat(opts.format);
26856
26921
  const setup = await setupCall(opts.server, opts.account);
26857
26922
  try {
26858
26923
  await runMetricsCreateCalc({
@@ -26862,6 +26927,7 @@ function wireMetricsCreate(metrics) {
26862
26927
  dimensions: opts.dimensions,
26863
26928
  description: opts.description,
26864
26929
  category,
26930
+ format: opts.format,
26865
26931
  asJson: opts.json === true,
26866
26932
  signal: setup.signal,
26867
26933
  out: process.stdout
@@ -27208,7 +27274,10 @@ function wireMetricsCreate(metrics) {
27208
27274
  'Exclude filter "<field_id>=<v1>[,v2,...]" \u2014 replaces the existing filter list (repeatable; pair with --filter)',
27209
27275
  collectArray4,
27210
27276
  []
27211
- ).option("--clear-filters", "Clear all filters on the metric (sends `filters: []`)", false).option("--hide", "Set is_hidden=true").option("--unhide", "Set is_hidden=false").option("--json", "Output the raw updated metric as JSON", false).addHelpText(
27277
+ ).option("--clear-filters", "Clear all filters on the metric (sends `filters: []`)", false).option("--hide", "Set is_hidden=true").option("--unhide", "Set is_hidden=false").option(
27278
+ "--format <f>",
27279
+ `${METRIC_FORMAT_HELP} To reset to the editor default, pass '#,###'; there is no empty format.`
27280
+ ).option("--json", "Output the raw updated metric as JSON", false).addHelpText(
27212
27281
  "after",
27213
27282
  [
27214
27283
  "",
@@ -27230,6 +27299,7 @@ function wireMetricsCreate(metrics) {
27230
27299
  ).action(
27231
27300
  async (idStr, opts) => {
27232
27301
  const id = parseMetricIdArg(idStr);
27302
+ if (opts.format !== void 0) validateMetricFormat(opts.format);
27233
27303
  if (opts.hide === true && opts.unhide === true) {
27234
27304
  throw new Error("--hide and --unhide are mutually exclusive");
27235
27305
  }
@@ -27273,6 +27343,7 @@ function wireMetricsCreate(metrics) {
27273
27343
  filters: opts.filter ?? [],
27274
27344
  excludeFilters: opts.excludeFilter ?? [],
27275
27345
  clearFilters: opts.clearFilters === true,
27346
+ format: opts.format,
27276
27347
  asJson: opts.json === true,
27277
27348
  signal: setup.signal,
27278
27349
  out: process.stdout
@@ -33302,13 +33373,20 @@ import { promises as fs16 } from "fs";
33302
33373
  import * as os3 from "os";
33303
33374
  import * as path7 from "path";
33304
33375
  var RESPONSE_BODY_MAX_BYTES = 8 * 1024;
33376
+ var MAX_NAME_SUFFIX = 99;
33305
33377
  async function writeErrorLog(opts) {
33306
- const filename = `dr-cli-error-${timestampStamp()}.md`;
33378
+ const stamp2 = timestampStamp();
33307
33379
  const dir = await chooseDir(opts.dirOverride);
33308
- const filePath = path7.join(dir, filename);
33309
33380
  const md = formatLog(opts);
33310
- await fs16.writeFile(filePath, md, { encoding: "utf8" });
33311
- return filePath;
33381
+ for (let n = 0; ; n++) {
33382
+ const filePath = path7.join(dir, `dr-cli-error-${stamp2}${n === 0 ? "" : `-${n}`}.md`);
33383
+ try {
33384
+ await fs16.writeFile(filePath, md, { encoding: "utf8", mode: 384, flag: "wx" });
33385
+ return filePath;
33386
+ } catch (err) {
33387
+ if (err.code !== "EEXIST" || n >= MAX_NAME_SUFFIX) throw err;
33388
+ }
33389
+ }
33312
33390
  }
33313
33391
  function timestampStamp() {
33314
33392
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
@@ -63813,7 +63891,7 @@ async function runEntitiesMarkCompleted(cfg) {
63813
63891
  signal: cfg.signal
63814
63892
  });
63815
63893
  cfg.out.write(
63816
- `Marked entity ${cfg.documentId} as manually completed (server-side path uses 'complited' \u2014 typo preserved verbatim).
63894
+ `Cleared the export-error badge on document ${cfg.documentId} (server-side path uses 'complited' \u2014 typo preserved verbatim).
63817
63895
  `
63818
63896
  );
63819
63897
  }
@@ -64055,7 +64133,7 @@ function registerEntitiesCommand(root) {
64055
64133
  addServerFlag(deleteCmd);
64056
64134
  addAccountFlag(deleteCmd);
64057
64135
  const markCompletedCmd = ent.command("mark-completed").description(
64058
- "Mark an entity as manually completed \u2014 IT support override for stuck-state recovery (PUT /api/documents/<doc>/mark_as_manually_complited; server-side path keeps the 'complited' typo)"
64136
+ "Clear a document's stale export-error badge \u2014 marks nothing completed and does not touch sync state (PUT /api/documents/<doc>/mark_as_manually_complited; server-side path keeps the 'complited' typo)"
64059
64137
  ).argument("<doc_id>", "document id (positive integer)").action(async (idStr, opts) => {
64060
64138
  const docId = parsePositiveIntArg(idStr, "<doc_id>");
64061
64139
  const setup = await setupCall(opts.server, opts.account);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "1.6.148",
3
+ "cliVersion": "1.6.150",
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": {
@@ -1724,6 +1724,10 @@
1724
1724
  {
1725
1725
  "resource": "fileboxes",
1726
1726
  "action": "write"
1727
+ },
1728
+ {
1729
+ "resource": "tables",
1730
+ "action": "read"
1727
1731
  }
1728
1732
  ]
1729
1733
  },
@@ -1732,6 +1736,10 @@
1732
1736
  {
1733
1737
  "resource": "fileboxes",
1734
1738
  "action": "write"
1739
+ },
1740
+ {
1741
+ "resource": "tables",
1742
+ "action": "read"
1735
1743
  }
1736
1744
  ]
1737
1745
  },
@@ -1740,6 +1748,10 @@
1740
1748
  {
1741
1749
  "resource": "fileboxes",
1742
1750
  "action": "write"
1751
+ },
1752
+ {
1753
+ "resource": "tables",
1754
+ "action": "read"
1743
1755
  }
1744
1756
  ]
1745
1757
  },
@@ -1982,187 +1994,187 @@
1982
1994
  "connectors publish": {
1983
1995
  "denied": {
1984
1996
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
1985
- "until": "DR-53511"
1997
+ "until": "DR-56164"
1986
1998
  }
1987
1999
  },
1988
2000
  "connectors query repoint": {
1989
2001
  "denied": {
1990
2002
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
1991
- "until": "DR-53511"
2003
+ "until": "DR-56164"
1992
2004
  }
1993
2005
  },
1994
2006
  "connectors query create": {
1995
2007
  "denied": {
1996
2008
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
1997
- "until": "DR-53511"
2009
+ "until": "DR-56164"
1998
2010
  }
1999
2011
  },
2000
2012
  "connectors query list": {
2001
2013
  "denied": {
2002
2014
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2003
- "until": "DR-53511"
2015
+ "until": "DR-56164"
2004
2016
  }
2005
2017
  },
2006
2018
  "connectors query delete": {
2007
2019
  "denied": {
2008
2020
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2009
- "until": "DR-53511"
2021
+ "until": "DR-56164"
2010
2022
  }
2011
2023
  },
2012
2024
  "connectors connector create": {
2013
2025
  "denied": {
2014
2026
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2015
- "until": "DR-53511"
2027
+ "until": "DR-56164"
2016
2028
  }
2017
2029
  },
2018
2030
  "connectors artifact deregister": {
2019
2031
  "denied": {
2020
2032
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2021
- "until": "DR-53511"
2033
+ "until": "DR-56164"
2022
2034
  }
2023
2035
  },
2024
2036
  "connectors promote": {
2025
2037
  "denied": {
2026
2038
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2027
- "until": "DR-53511"
2039
+ "until": "DR-56164"
2028
2040
  }
2029
2041
  },
2030
2042
  "connectors binding get": {
2031
2043
  "denied": {
2032
2044
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2033
- "until": "DR-53511"
2045
+ "until": "DR-56164"
2034
2046
  }
2035
2047
  },
2036
2048
  "connectors sync cancel": {
2037
2049
  "denied": {
2038
2050
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2039
- "until": "DR-53511"
2051
+ "until": "DR-56164"
2040
2052
  }
2041
2053
  },
2042
2054
  "connectors active get": {
2043
2055
  "denied": {
2044
2056
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2045
- "until": "DR-53511"
2057
+ "until": "DR-56164"
2046
2058
  }
2047
2059
  },
2048
2060
  "connectors dev-fetch": {
2049
2061
  "denied": {
2050
2062
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2051
- "until": "DR-53511"
2063
+ "until": "DR-56164"
2052
2064
  }
2053
2065
  },
2054
2066
  "connectors poll enable": {
2055
2067
  "denied": {
2056
2068
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2057
- "until": "DR-53511"
2069
+ "until": "DR-56164"
2058
2070
  }
2059
2071
  },
2060
2072
  "connectors poll set": {
2061
2073
  "denied": {
2062
2074
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2063
- "until": "DR-53511"
2075
+ "until": "DR-56164"
2064
2076
  }
2065
2077
  },
2066
2078
  "connectors poll disable": {
2067
2079
  "denied": {
2068
2080
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2069
- "until": "DR-53511"
2081
+ "until": "DR-56164"
2070
2082
  }
2071
2083
  },
2072
2084
  "connectors poll get": {
2073
2085
  "denied": {
2074
2086
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2075
- "until": "DR-53511"
2087
+ "until": "DR-56164"
2076
2088
  }
2077
2089
  },
2078
2090
  "connectors poll run": {
2079
2091
  "denied": {
2080
2092
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2081
- "until": "DR-53511"
2093
+ "until": "DR-56164"
2082
2094
  }
2083
2095
  },
2084
2096
  "connectors poll resume": {
2085
2097
  "denied": {
2086
2098
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2087
- "until": "DR-53511"
2099
+ "until": "DR-56164"
2088
2100
  }
2089
2101
  },
2090
2102
  "connectors poll items": {
2091
2103
  "denied": {
2092
2104
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2093
- "until": "DR-53511"
2105
+ "until": "DR-56164"
2094
2106
  }
2095
2107
  },
2096
2108
  "connectors poll retry": {
2097
2109
  "denied": {
2098
2110
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2099
- "until": "DR-53511"
2111
+ "until": "DR-56164"
2100
2112
  }
2101
2113
  },
2102
2114
  "connectors poll skip": {
2103
2115
  "denied": {
2104
2116
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2105
- "until": "DR-53511"
2117
+ "until": "DR-56164"
2106
2118
  }
2107
2119
  },
2108
2120
  "connectors poll seed": {
2109
2121
  "denied": {
2110
2122
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2111
- "until": "DR-53511"
2123
+ "until": "DR-56164"
2112
2124
  }
2113
2125
  },
2114
2126
  "connectors tasks create": {
2115
2127
  "denied": {
2116
2128
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2117
- "until": "DR-53511"
2129
+ "until": "DR-56164"
2118
2130
  }
2119
2131
  },
2120
2132
  "connectors tasks list": {
2121
2133
  "denied": {
2122
2134
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2123
- "until": "DR-53511"
2135
+ "until": "DR-56164"
2124
2136
  }
2125
2137
  },
2126
2138
  "connectors tasks get": {
2127
2139
  "denied": {
2128
2140
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2129
- "until": "DR-53511"
2141
+ "until": "DR-56164"
2130
2142
  }
2131
2143
  },
2132
2144
  "connectors tasks update": {
2133
2145
  "denied": {
2134
2146
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2135
- "until": "DR-53511"
2147
+ "until": "DR-56164"
2136
2148
  }
2137
2149
  },
2138
2150
  "connectors tasks delete": {
2139
2151
  "denied": {
2140
2152
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2141
- "until": "DR-53511"
2153
+ "until": "DR-56164"
2142
2154
  }
2143
2155
  },
2144
2156
  "connectors tasks run": {
2145
2157
  "denied": {
2146
2158
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2147
- "until": "DR-53511"
2159
+ "until": "DR-56164"
2148
2160
  }
2149
2161
  },
2150
2162
  "connectors tasks pause": {
2151
2163
  "denied": {
2152
2164
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2153
- "until": "DR-53511"
2165
+ "until": "DR-56164"
2154
2166
  }
2155
2167
  },
2156
2168
  "connectors tasks resume": {
2157
2169
  "denied": {
2158
2170
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2159
- "until": "DR-53511"
2171
+ "until": "DR-56164"
2160
2172
  }
2161
2173
  },
2162
2174
  "connectors tasks runs": {
2163
2175
  "denied": {
2164
2176
  "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2165
- "until": "DR-53511"
2177
+ "until": "DR-56164"
2166
2178
  }
2167
2179
  },
2168
2180
  "filebox list": {
@@ -3853,10 +3865,6 @@
3853
3865
  },
3854
3866
  "entities mark-completed": {
3855
3867
  "requires": [
3856
- {
3857
- "resource": "integrations",
3858
- "action": "write"
3859
- },
3860
3868
  {
3861
3869
  "resource": "fileboxes",
3862
3870
  "action": "write"
@@ -601,6 +601,11 @@ network traffic or hand-edits JSON. Full recipes in the relevant reference file.
601
601
  `[M].SHIFT(...)` / `[M].AS_OF(...)` — SHIFT unit `consumption_period`
602
602
  follows the query grain; `PERIODS_IN_YEAR` metrics reject day-grain
603
603
  queries. See reference/metrics.md.
604
+ - **Metric `--format`** (`metrics create base|calc`, `metrics update`) accepts
605
+ only what the web app's Format editor produces: `[$ € £ ₪]` + `#,###`|`####`
606
+ + `[.# … .####]` + `[[kilo]k|[mega]M]` + `[%]` (no currency with `%`).
607
+ Single-quote it (`'$#,###.##'`); there is no empty format — reset with
608
+ `'#,###'`. See reference/metrics.md.
604
609
  - **`metrics data --agg`** is `DEFAULT|MTD|QTD|YTD|MTG|QTG|YTG`. The `*TG` "to
605
610
  go" values accumulate **forward** from the current bucket (inclusive) to the
606
611
  period end — the mirror of `*TD`. See reference/metrics.md.
@@ -169,7 +169,7 @@ i.e. integration data sources at the company level.
169
169
  - `entities rename <doc_ids> --alias <s>` (CSV doc ids),
170
170
  `entities rename-group <doc_ids> --group <s>` (move into a group).
171
171
  - `entities delete <doc_ids>` — two-step confirm.
172
- - `entities mark-completed <doc_id>` — IT-support override for a stuck state.
172
+ - `entities mark-completed <doc_id>` — clears the document's stale export-error badge; it marks nothing completed and does not touch sync state.
173
173
 
174
174
  ## Customer-facing docs (KB)
175
175
 
@@ -92,13 +92,41 @@ Required: `--name`, `--template <id>`, `--value-field <id>`, `--date-field <id>`
92
92
  - No `--time-aggregation` — a CALC rollup inherits time-aggregation from each
93
93
  referenced BASE, so a single CALC-level value would be meaningless.
94
94
 
95
+ ## Display format (`--format`)
96
+
97
+ `metrics create base`, `metrics create calc` and `metrics update <id>` take
98
+ `--format '<f>'`, stored at `configuration.format` (the FORMAT column of
99
+ `metrics list`). Only the strings the web app's metric Format editor can
100
+ produce are accepted; anything else is rejected before any request. Build it
101
+ from these parts, in this order:
102
+
103
+ | Part | Values |
104
+ |------|--------|
105
+ | currency prefix (optional) | `$` `€` `£` `₪` |
106
+ | digits | `#,###` (thousands separator) or `####` |
107
+ | decimals (optional) | `.#` `.##` `.###` `.####` |
108
+ | scale (optional) | `[kilo]k` (thousands) or `[mega]M` (millions) |
109
+ | percent suffix (optional) | `%` — never together with a currency |
110
+
111
+ - Examples: `'#,###'`, `'$#,###.##'`, `'€####[mega]M'`, `'####.#%'`.
112
+ - Always single-quote the value: an unquoted `#` starts a shell comment.
113
+ - `$` is stored quoted, as the editor stores it: `$#,###` is saved (and shown
114
+ in FORMAT) as `"$"#,###`. Both spellings are accepted on input.
115
+ - Excel-style `0` placeholders (`#,##0.00`) and anything else outside the
116
+ table are rejected; the error suggests the `#` spelling when there is one.
117
+ - There is no empty format. The editor's default is `#,###`; to reset a
118
+ metric, pass `--format '#,###'`.
119
+ - On `update`, the CLI reads the metric first and sends its configuration back
120
+ with only `format` changed, so a CALC formula is left exactly as stored.
121
+
95
122
  ## Update / delete
96
123
 
97
124
  - `metrics update <id>` — partial PATCH. Flags: `--name`, `--description`,
98
125
  `--category`, `--dimensions`, `--hide`/`--unhide`, `--time-aggregation`,
99
126
  `--aggregator`, `--template`, `--value-field`, `--date-field`, `--filter`,
100
- `--exclude-filter`, `--clear-filters`. `--description ""` clears to `null` on
101
- the wire (matching how `get` round-trips an untouched field).
127
+ `--exclude-filter`, `--clear-filters`, `--format` (see above).
128
+ `--description ""` clears to `null` on the wire (matching how `get`
129
+ round-trips an untouched field).
102
130
  - **No `--formula`** — CALC formulas and other create-time fields (kind,
103
131
  template, value/date field, aggregator) are intentionally not editable in
104
132
  place; changing them yields a different metric. To fix a formula:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-cli",
3
- "version": "1.6.148",
3
+ "version": "1.6.150",
4
4
  "description": "Datarails CLI",
5
5
  "type": "module",
6
6
  "engines": {
@@ -17,7 +17,7 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@datarails-ai/dr-appspace-cli": "0.12.20",
20
+ "@datarails-ai/dr-appspace-cli": "0.12.21",
21
21
  "@modelcontextprotocol/sdk": "^1.29.0",
22
22
  "@napi-rs/keyring": "^1.3.0",
23
23
  "commander": "^12.1.0",