@datarails-ai/dr-cli 1.6.185 → 1.6.186

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.185",
11
+ version: "1.6.186",
12
12
  description: "Datarails CLI",
13
13
  type: "module",
14
14
  engines: {
@@ -69,7 +69,7 @@ var package_default = {
69
69
  };
70
70
 
71
71
  // src/buildInfo.ts
72
- var BUILD_TIME = "2026-11-01T20:44:10.716Z";
72
+ var BUILD_TIME = "2026-11-02T07:19:02.800Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -23605,7 +23605,7 @@ async function getById2(client, id, options) {
23605
23605
  return out;
23606
23606
  }
23607
23607
  function scenarioContextQuery(ctx) {
23608
- return `scenario=${encodeURIComponent(ctx.scenario)}&scenario_cycle=${encodeURIComponent(ctx.scenario_cycle)}&planning_scenario=${encodeURIComponent(ctx.planning_scenario)}`;
23608
+ return `scenario=${encodeURIComponent(ctx.scenario)}&scenario_cycle=${encodeURIComponent(ctx.scenario_cycle)}&cycle_year=${ctx.cycle_year ?? ""}&planning_scenario=${encodeURIComponent(ctx.planning_scenario)}`;
23609
23609
  }
23610
23610
  var NO_DRIVER_404_RE = /: HTTP 404: \{\}\s*$/;
23611
23611
  async function getDriver(client, id, ctx, options) {
@@ -28323,27 +28323,54 @@ import { promises as fs14 } from "fs";
28323
28323
  var DEFAULT_SCENARIO = "Actuals";
28324
28324
  function addScenarioContextFlags(cmd) {
28325
28325
  return cmd.option("--scenario <name>", "Scenario name \u2014 free text, matched exactly", DEFAULT_SCENARIO).option("--cycle <label>", "Scenario cycle label \u2014 free text, matched exactly (default: none)").option(
28326
+ "--cycle-year <yyyy>",
28327
+ "Fiscal year of the --cycle, e.g. 2027 \u2014 required with --cycle, refused without it"
28328
+ ).option(
28326
28329
  "--planning-scenario <name>",
28327
28330
  "Planning scenario name \u2014 free text, matched exactly (default: none)"
28328
28331
  );
28329
28332
  }
28333
+ function parseCycleYear(raw2) {
28334
+ const s = raw2.trim();
28335
+ if (!/^\d{4}$/.test(s)) {
28336
+ throw new Error(`invalid --cycle-year "${raw2}" (expected a four-digit fiscal year, e.g. 2027)`);
28337
+ }
28338
+ return Number(s);
28339
+ }
28330
28340
  function resolveScenarioContext(opts) {
28331
28341
  const scenario = opts.scenario ?? "";
28342
+ const cycle = opts.cycle ?? "";
28343
+ const cycleYear = opts.cycleYear === void 0 ? null : parseCycleYear(opts.cycleYear);
28344
+ if (cycle !== "" && cycleYear === null) {
28345
+ throw new Error(
28346
+ `--cycle ${JSON.stringify(cycle)} needs --cycle-year <yyyy>: a cycle name repeats every year, so the server addresses a cycle only with its fiscal year`
28347
+ );
28348
+ }
28349
+ if (cycle === "" && cycleYear !== null) {
28350
+ throw new Error("--cycle-year is given only with --cycle <label>");
28351
+ }
28332
28352
  return {
28333
28353
  scenario: scenario === "" ? DEFAULT_SCENARIO : scenario,
28334
- scenario_cycle: opts.cycle ?? "",
28354
+ scenario_cycle: cycle,
28355
+ cycle_year: cycleYear,
28335
28356
  planning_scenario: opts.planningScenario ?? ""
28336
28357
  };
28337
28358
  }
28338
28359
  function formatScenarioContext(ctx) {
28339
- return `scenario=${JSON.stringify(ctx.scenario)} cycle=${JSON.stringify(ctx.scenario_cycle)} planning-scenario=${JSON.stringify(ctx.planning_scenario)}`;
28360
+ return `scenario=${JSON.stringify(ctx.scenario)} cycle=${JSON.stringify(ctx.scenario_cycle)} ` + (ctx.cycle_year === null ? "" : `cycle-year=${ctx.cycle_year} `) + `planning-scenario=${JSON.stringify(ctx.planning_scenario)}`;
28340
28361
  }
28341
28362
  function sameScenarioContext(a, b) {
28342
- return a.scenario === b.scenario && a.scenario_cycle === b.scenario_cycle && a.planning_scenario === b.planning_scenario;
28363
+ const yearUnknown = a.cycle_year === void 0 || b.cycle_year === void 0;
28364
+ return a.scenario === b.scenario && a.scenario_cycle === b.scenario_cycle && (yearUnknown || a.cycle_year === b.cycle_year) && a.planning_scenario === b.planning_scenario;
28365
+ }
28366
+ function findScenarioContext(rows, ctx) {
28367
+ const exact = rows.findIndex((r) => r.cycle_year !== void 0 && sameScenarioContext(r, ctx));
28368
+ return exact >= 0 ? exact : rows.findIndex((r) => sameScenarioContext(r, ctx));
28343
28369
  }
28344
28370
  function scenarioContextFlags(ctx) {
28345
28371
  const parts = [`--scenario ${shellQuote(ctx.scenario)}`];
28346
28372
  if (ctx.scenario_cycle !== "") parts.push(`--cycle ${shellQuote(ctx.scenario_cycle)}`);
28373
+ if (ctx.cycle_year !== null) parts.push(`--cycle-year ${ctx.cycle_year}`);
28347
28374
  if (ctx.planning_scenario !== "") {
28348
28375
  parts.push(`--planning-scenario ${shellQuote(ctx.planning_scenario)}`);
28349
28376
  }
@@ -28518,6 +28545,7 @@ function renderDriver(out, d) {
28518
28545
  writeRows4(out, [
28519
28546
  ["scenario", dash(d.scenario)],
28520
28547
  ["cycle", dash(d.scenario_cycle)],
28548
+ ["cycle_year", dash(d.cycle_year)],
28521
28549
  ["planning_scenario", dash(d.planning_scenario)],
28522
28550
  ["unit", dash(d.unit)],
28523
28551
  ["is_additive", dash(d.is_additive)],
@@ -28546,12 +28574,15 @@ function renderCalculations(out, calcs) {
28546
28574
  out.write("(no scenario calculations)\n");
28547
28575
  return;
28548
28576
  }
28549
- const rows = [["ID", "SCENARIO", "CYCLE", "PLANNING_SCENARIO", "FORMULA"]];
28577
+ const rows = [
28578
+ ["ID", "SCENARIO", "CYCLE", "CYCLE_YEAR", "PLANNING_SCENARIO", "FORMULA"]
28579
+ ];
28550
28580
  for (const c of calcs) {
28551
28581
  rows.push([
28552
28582
  dash(c.id),
28553
28583
  dash(c.scenario),
28554
28584
  dash(c.scenario_cycle),
28585
+ dash(c.cycle_year),
28555
28586
  dash(c.planning_scenario),
28556
28587
  c.presented_formula
28557
28588
  ]);
@@ -28681,10 +28712,21 @@ function toCalcPayload(c) {
28681
28712
  return {
28682
28713
  scenario: c.scenario,
28683
28714
  scenario_cycle: c.scenario_cycle,
28715
+ cycle_year: c.cycle_year ?? null,
28684
28716
  planning_scenario: c.planning_scenario,
28685
28717
  presented_formula: c.presented_formula
28686
28718
  };
28687
28719
  }
28720
+ function assertKeptRowsHaveYear(id, calculations, skip) {
28721
+ if (!calculations.some((c) => c.cycle_year !== void 0)) return;
28722
+ const bad = calculations.find(
28723
+ (c, i) => i !== skip && c.scenario_cycle !== "" && (c.cycle_year ?? null) === null
28724
+ );
28725
+ if (bad === void 0) return;
28726
+ throw new Error(
28727
+ `metric ${id} keeps a calculation for ${formatScenarioContext({ ...bad, cycle_year: null })}${bad.id === void 0 ? "" : ` (id ${bad.id})`} with no cycle year; the server refuses any calculations list holding a cycle without its fiscal year, so nothing was changed. Fix or remove that calculation in the web app first.`
28728
+ );
28729
+ }
28688
28730
  async function readCalculations(client, id, signal) {
28689
28731
  const m = decodeShape(await getById2(client, id, { signal }));
28690
28732
  return { name: m.name ?? "", calculations: m.calculations ?? [] };
@@ -28701,12 +28743,13 @@ async function runCalculationsAdd(cfg) {
28701
28743
  const presented_formula = toWireFormula(cfg.formula);
28702
28744
  const { calculations } = await readCalculations(cfg.client, cfg.id, cfg.signal);
28703
28745
  const current = calculations.map(toCalcPayload);
28704
- const idx = current.findIndex((c) => sameScenarioContext(c, cfg.ctx));
28746
+ const idx = findScenarioContext(calculations, cfg.ctx);
28705
28747
  if (idx >= 0 && !cfg.replace) {
28706
28748
  throw new Error(
28707
28749
  `metric ${cfg.id} already has a calculation for ${formatScenarioContext(cfg.ctx)} (${current[idx].presented_formula}); pass --replace to overwrite it`
28708
28750
  );
28709
28751
  }
28752
+ assertKeptRowsHaveYear(cfg.id, calculations, idx);
28710
28753
  const entry = { ...cfg.ctx, presented_formula };
28711
28754
  const next = idx >= 0 ? current.map((c, i) => i === idx ? entry : c) : [...current, entry];
28712
28755
  const updated = await update6(
@@ -28747,13 +28790,15 @@ async function runCalculationsRemove(cfg) {
28747
28790
  describe: async () => {
28748
28791
  const { name, calculations } = await readCalculations(cfg.client, cfg.id, cfg.signal);
28749
28792
  const all = calculations.map(toCalcPayload);
28750
- const hit = all.find((c) => sameScenarioContext(c, cfg.ctx));
28793
+ const idx = findScenarioContext(calculations, cfg.ctx);
28794
+ const hit = all[idx];
28751
28795
  if (hit === void 0) {
28752
28796
  throw new Error(
28753
28797
  `metric ${cfg.id} has no calculation for ${formatScenarioContext(cfg.ctx)}; list them with \`dr metrics calculations list ${cfg.id}\``
28754
28798
  );
28755
28799
  }
28756
- remaining = all.filter((c) => !sameScenarioContext(c, cfg.ctx));
28800
+ assertKeptRowsHaveYear(cfg.id, calculations, idx);
28801
+ remaining = all.filter((_, i) => i !== idx);
28757
28802
  return [
28758
28803
  `metric: ${JSON.stringify(name === "" ? "(unnamed)" : name)}`,
28759
28804
  `context: ${formatScenarioContext(cfg.ctx)}`,
@@ -28932,7 +28977,7 @@ function collectArray5(value, previous) {
28932
28977
  }
28933
28978
  function wireMetricsDrivers(metrics) {
28934
28979
  const driver = metrics.command("driver").description(
28935
- "Driver definition and values of a DRIVER metric, per scenario context (--scenario / --cycle / --planning-scenario: free-text names, matched exactly)"
28980
+ "Driver definition and values of a DRIVER metric, per scenario context (--scenario / --cycle / --planning-scenario: free-text names, matched exactly; --cycle-year: the fiscal year, required with --cycle)"
28936
28981
  );
28937
28982
  const driverGetCmd = addScenarioContextFlags(
28938
28983
  driver.command("get").description("Show the driver definition for one scenario context").argument("<id>", "DRIVER metric id (positive integer)")
@@ -29068,7 +29113,7 @@ function wireMetricsDrivers(metrics) {
29068
29113
  addServerFlag(valuesSetCmd);
29069
29114
  addAccountFlag(valuesSetCmd);
29070
29115
  const calculations = metrics.command("calculations").description(
29071
- "Scenario-bound formulas of a CALC metric, one per scenario context (--scenario / --cycle / --planning-scenario: free-text names, matched exactly)"
29116
+ "Scenario-bound formulas of a CALC metric, one per scenario context (--scenario / --cycle / --planning-scenario: free-text names, matched exactly; --cycle-year: the fiscal year, required with --cycle)"
29072
29117
  );
29073
29118
  const calcListCmd = calculations.command("list").description("List a CALC metric's scenario calculations").argument("<id>", "CALC metric id (positive integer)").option("--json", "Output raw JSON instead of a table", false).action(async (idStr, opts) => {
29074
29119
  const id = parseMetricIdArg(idStr);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "1.6.185",
3
+ "cliVersion": "1.6.186",
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": {
@@ -241,7 +241,7 @@ area's gotchas.
241
241
  **Metrics (KPIs) & functions** → [reference/metrics.md](reference/metrics.md)
242
242
  - `metrics list|get|data|drilldown`
243
243
  - `metrics create base|calc|driver`, `metrics update|delete`
244
- - `metrics driver get|set|delete`, `metrics driver values get|set`, `metrics calculations list|add|remove`, `metrics what-if`: drivers, scenario-bound formulas and what-if runs, addressed per scenario context (`--scenario` / `--cycle` / `--planning-scenario`, matched exactly)
244
+ - `metrics driver get|set|delete`, `metrics driver values get|set`, `metrics calculations list|add|remove`, `metrics what-if`: drivers, scenario-bound formulas and what-if runs, addressed per scenario context (`--scenario` / `--cycle` / `--planning-scenario`, matched exactly; `--cycle` needs `--cycle-year <yyyy>`)
245
245
  - `metrics re-aggregate` — rebuild pre-aggregated metric values (targeted ids, or `--all` org-wide); add `--wait [--timeout <s>]` on targeted ids to block until done (exits non-zero on failure/timeout)
246
246
  - `metrics migrate` — org-wide metrics+dimensions migration (V1→V2 backfill; `--dry-run` first)
247
247
  - `metrics categories list|get|create|update|delete`
@@ -146,10 +146,14 @@ cycle management). When either is off it answers 400/403/405 with a message,
146
146
  and the CLI prints that message unchanged.
147
147
 
148
148
  **Scenario context.** `driver …` and `calculations add|remove` address one
149
- context with `--scenario <name>` (default `Actuals`), `--cycle <label>` and
150
- `--planning-scenario <name>`. All three are free-text names and must match
151
- exactly. A context never inherits from another one, so a driver set under
152
- `Budget` is a different row from the `Actuals` one.
149
+ context with `--scenario <name>` (default `Actuals`), `--cycle <label>`,
150
+ `--cycle-year <yyyy>` and `--planning-scenario <name>`. The three names are
151
+ free text and must match exactly. A context never inherits from another one,
152
+ so a driver set under `Budget` is a different row from the `Actuals` one.
153
+ A cycle name repeats every year, so `--cycle` needs `--cycle-year` (the
154
+ cycle's fiscal year, e.g. `--cycle "0+12" --cycle-year 2027`), and
155
+ `--cycle-year` is refused without `--cycle`. The same cycle in another year
156
+ is another context. `calculations list` shows the year in a CYCLE_YEAR column.
153
157
 
154
158
  - `metrics create driver --name <n> [--description] [--category <id>]
155
159
  [--dimensions <ids>]` creates a DRIVER metric. It has no table, value or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-cli",
3
- "version": "1.6.185",
3
+ "version": "1.6.186",
4
4
  "description": "Datarails CLI",
5
5
  "type": "module",
6
6
  "engines": {