@datarails-ai/dr-cli 1.6.185 → 1.6.187

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
@@ -332,9 +332,9 @@ are covered); a missing `--server`, or a missing `--account` in customer mode;
332
332
  `--user-id`); a host override (`--app-space-host`, `DR_APP_SPACE_HOST`,
333
333
  `--connectors-url`); in org mode, any command the scopes map does not prove
334
334
  read-only, plus the few read-scoped commands with side effects
335
- (`devices request-logs`, `desk smtp test`, `dashboards prepare`,
336
- `storyboards prepare`). A pin that is set but malformed refuses every command
337
- (`--help` and `--version` still print). A refusal exits 3, like a scope denial.
335
+ (`devices request-logs`, `desk smtp test`, `dashboards prepare`). A pin that is
336
+ set but malformed refuses every command (`--help` and `--version` still print).
337
+ A refusal exits 3, like a scope denial.
338
338
 
339
339
  ### API keys (headless and CI)
340
340
 
@@ -465,6 +465,7 @@ dr skill print <name> # print an embedded skill's SKILL.md
465
465
  | Connectors | `connectors publish <dataqueryFile>`, `connectors query repoint <queryId>`, `connectors binding get <fileboxUuid>` (drives the connectors control-plane over VPN) |
466
466
  | Agent Studio | `agents list/get/versions/test-link/refresh/pull/update/create/mcp-servers/cli-scopes/scope-registry`, `agents set-listed/set-global/set-visibility/archive` (two-step confirm), `agents models/catalog/system-entities`, `agents skills list/get/versions/bundle/pull/create/publish/delete` (`delete` two-step confirm). A CLI-exposed agent's `dr` sessions are capped by its CLI scopes (`resource:action`, default `*:read`): set with `update --cli-scopes <list>` / `--reset-cli-scopes [--cli-scopes-reason <text>]` or `create --expose-cli --cli-scopes <list>`, read with `cli-scopes`; the scope history and restore stay in the Backoffice (service-token only) |
467
467
  | Agent release pins | `agents served`, `agents pin/unpin` (two-step confirm): the version each deployment serves; `--server`, `--servers <keys>` or `--production` |
468
+ | Agent schedules | `agents schedules list/create/update/delete` (`delete` two-step confirm): ai-service cron runs of a managed agent, as the session user. `--agent-id` defaults to the token's `agent_id` claim. Like every `agents` command, an agent needs `managed-agents:write` in its CLI scopes (`managed-agents:destructive` for `delete`): `agents update --id <agentId> --cli-scopes '*:read,managed-agents:write,managed-agents:destructive'` |
468
469
  | Skill | `skill` — prints embedded `SKILL.md` |
469
470
 
470
471
  ### Connectors: TLS over VPN
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.187",
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-02T11:55:21.441Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -4390,7 +4390,11 @@ var PROD_GATE_TIER_B = /* @__PURE__ */ new Set([
4390
4390
  "template-library|update",
4391
4391
  "template-library|unpublish",
4392
4392
  "template-library|archive",
4393
- "template-library|restore"
4393
+ "template-library|restore",
4394
+ // agent schedules (dr agents schedules delete). One org row; the plan prints its cron,
4395
+ // timezone, agent, prompt and run-as user, and `dr agents schedules create` with them
4396
+ // restores it (running as the re-creator: create cannot set the run-as user).
4397
+ "managed-agent-schedule|delete"
4394
4398
  ]);
4395
4399
  var PROD_GATE_TIER_A = /* @__PURE__ */ new Set([
4396
4400
  // workflow / mec — soft-delete + cascade deletes. `archive` also covers the
@@ -5211,8 +5215,7 @@ var ORG_MODE_UNSCOPED_ALLOWED = /* @__PURE__ */ new Set([
5211
5215
  var ORG_MODE_READ_SCOPED_REFUSED = /* @__PURE__ */ new Set([
5212
5216
  "devices request-logs",
5213
5217
  "desk smtp test",
5214
- "dashboards prepare",
5215
- "storyboards prepare"
5218
+ "dashboards prepare"
5216
5219
  ]);
5217
5220
  var LOCAL_COMMANDS_WITH_TARGET_FLAGS = /* @__PURE__ */ new Set([
5218
5221
  "app-space scaffold"
@@ -23605,7 +23608,7 @@ async function getById2(client, id, options) {
23605
23608
  return out;
23606
23609
  }
23607
23610
  function scenarioContextQuery(ctx) {
23608
- return `scenario=${encodeURIComponent(ctx.scenario)}&scenario_cycle=${encodeURIComponent(ctx.scenario_cycle)}&planning_scenario=${encodeURIComponent(ctx.planning_scenario)}`;
23611
+ return `scenario=${encodeURIComponent(ctx.scenario)}&scenario_cycle=${encodeURIComponent(ctx.scenario_cycle)}&cycle_year=${ctx.cycle_year ?? ""}&planning_scenario=${encodeURIComponent(ctx.planning_scenario)}`;
23609
23612
  }
23610
23613
  var NO_DRIVER_404_RE = /: HTTP 404: \{\}\s*$/;
23611
23614
  async function getDriver(client, id, ctx, options) {
@@ -28323,27 +28326,54 @@ import { promises as fs14 } from "fs";
28323
28326
  var DEFAULT_SCENARIO = "Actuals";
28324
28327
  function addScenarioContextFlags(cmd) {
28325
28328
  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(
28329
+ "--cycle-year <yyyy>",
28330
+ "Fiscal year of the --cycle, e.g. 2027 \u2014 required with --cycle, refused without it"
28331
+ ).option(
28326
28332
  "--planning-scenario <name>",
28327
28333
  "Planning scenario name \u2014 free text, matched exactly (default: none)"
28328
28334
  );
28329
28335
  }
28336
+ function parseCycleYear(raw2) {
28337
+ const s = raw2.trim();
28338
+ if (!/^\d{4}$/.test(s)) {
28339
+ throw new Error(`invalid --cycle-year "${raw2}" (expected a four-digit fiscal year, e.g. 2027)`);
28340
+ }
28341
+ return Number(s);
28342
+ }
28330
28343
  function resolveScenarioContext(opts) {
28331
28344
  const scenario = opts.scenario ?? "";
28345
+ const cycle = opts.cycle ?? "";
28346
+ const cycleYear = opts.cycleYear === void 0 ? null : parseCycleYear(opts.cycleYear);
28347
+ if (cycle !== "" && cycleYear === null) {
28348
+ throw new Error(
28349
+ `--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`
28350
+ );
28351
+ }
28352
+ if (cycle === "" && cycleYear !== null) {
28353
+ throw new Error("--cycle-year is given only with --cycle <label>");
28354
+ }
28332
28355
  return {
28333
28356
  scenario: scenario === "" ? DEFAULT_SCENARIO : scenario,
28334
- scenario_cycle: opts.cycle ?? "",
28357
+ scenario_cycle: cycle,
28358
+ cycle_year: cycleYear,
28335
28359
  planning_scenario: opts.planningScenario ?? ""
28336
28360
  };
28337
28361
  }
28338
28362
  function formatScenarioContext(ctx) {
28339
- return `scenario=${JSON.stringify(ctx.scenario)} cycle=${JSON.stringify(ctx.scenario_cycle)} planning-scenario=${JSON.stringify(ctx.planning_scenario)}`;
28363
+ 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
28364
  }
28341
28365
  function sameScenarioContext(a, b) {
28342
- return a.scenario === b.scenario && a.scenario_cycle === b.scenario_cycle && a.planning_scenario === b.planning_scenario;
28366
+ const yearUnknown = a.cycle_year === void 0 || b.cycle_year === void 0;
28367
+ return a.scenario === b.scenario && a.scenario_cycle === b.scenario_cycle && (yearUnknown || a.cycle_year === b.cycle_year) && a.planning_scenario === b.planning_scenario;
28368
+ }
28369
+ function findScenarioContext(rows, ctx) {
28370
+ const exact = rows.findIndex((r) => r.cycle_year !== void 0 && sameScenarioContext(r, ctx));
28371
+ return exact >= 0 ? exact : rows.findIndex((r) => sameScenarioContext(r, ctx));
28343
28372
  }
28344
28373
  function scenarioContextFlags(ctx) {
28345
28374
  const parts = [`--scenario ${shellQuote(ctx.scenario)}`];
28346
28375
  if (ctx.scenario_cycle !== "") parts.push(`--cycle ${shellQuote(ctx.scenario_cycle)}`);
28376
+ if (ctx.cycle_year !== null) parts.push(`--cycle-year ${ctx.cycle_year}`);
28347
28377
  if (ctx.planning_scenario !== "") {
28348
28378
  parts.push(`--planning-scenario ${shellQuote(ctx.planning_scenario)}`);
28349
28379
  }
@@ -28518,6 +28548,7 @@ function renderDriver(out, d) {
28518
28548
  writeRows4(out, [
28519
28549
  ["scenario", dash(d.scenario)],
28520
28550
  ["cycle", dash(d.scenario_cycle)],
28551
+ ["cycle_year", dash(d.cycle_year)],
28521
28552
  ["planning_scenario", dash(d.planning_scenario)],
28522
28553
  ["unit", dash(d.unit)],
28523
28554
  ["is_additive", dash(d.is_additive)],
@@ -28546,12 +28577,15 @@ function renderCalculations(out, calcs) {
28546
28577
  out.write("(no scenario calculations)\n");
28547
28578
  return;
28548
28579
  }
28549
- const rows = [["ID", "SCENARIO", "CYCLE", "PLANNING_SCENARIO", "FORMULA"]];
28580
+ const rows = [
28581
+ ["ID", "SCENARIO", "CYCLE", "CYCLE_YEAR", "PLANNING_SCENARIO", "FORMULA"]
28582
+ ];
28550
28583
  for (const c of calcs) {
28551
28584
  rows.push([
28552
28585
  dash(c.id),
28553
28586
  dash(c.scenario),
28554
28587
  dash(c.scenario_cycle),
28588
+ dash(c.cycle_year),
28555
28589
  dash(c.planning_scenario),
28556
28590
  c.presented_formula
28557
28591
  ]);
@@ -28681,10 +28715,21 @@ function toCalcPayload(c) {
28681
28715
  return {
28682
28716
  scenario: c.scenario,
28683
28717
  scenario_cycle: c.scenario_cycle,
28718
+ cycle_year: c.cycle_year ?? null,
28684
28719
  planning_scenario: c.planning_scenario,
28685
28720
  presented_formula: c.presented_formula
28686
28721
  };
28687
28722
  }
28723
+ function assertKeptRowsHaveYear(id, calculations, skip) {
28724
+ if (!calculations.some((c) => c.cycle_year !== void 0)) return;
28725
+ const bad = calculations.find(
28726
+ (c, i) => i !== skip && c.scenario_cycle !== "" && (c.cycle_year ?? null) === null
28727
+ );
28728
+ if (bad === void 0) return;
28729
+ throw new Error(
28730
+ `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.`
28731
+ );
28732
+ }
28688
28733
  async function readCalculations(client, id, signal) {
28689
28734
  const m = decodeShape(await getById2(client, id, { signal }));
28690
28735
  return { name: m.name ?? "", calculations: m.calculations ?? [] };
@@ -28701,12 +28746,13 @@ async function runCalculationsAdd(cfg) {
28701
28746
  const presented_formula = toWireFormula(cfg.formula);
28702
28747
  const { calculations } = await readCalculations(cfg.client, cfg.id, cfg.signal);
28703
28748
  const current = calculations.map(toCalcPayload);
28704
- const idx = current.findIndex((c) => sameScenarioContext(c, cfg.ctx));
28749
+ const idx = findScenarioContext(calculations, cfg.ctx);
28705
28750
  if (idx >= 0 && !cfg.replace) {
28706
28751
  throw new Error(
28707
28752
  `metric ${cfg.id} already has a calculation for ${formatScenarioContext(cfg.ctx)} (${current[idx].presented_formula}); pass --replace to overwrite it`
28708
28753
  );
28709
28754
  }
28755
+ assertKeptRowsHaveYear(cfg.id, calculations, idx);
28710
28756
  const entry = { ...cfg.ctx, presented_formula };
28711
28757
  const next = idx >= 0 ? current.map((c, i) => i === idx ? entry : c) : [...current, entry];
28712
28758
  const updated = await update6(
@@ -28747,13 +28793,15 @@ async function runCalculationsRemove(cfg) {
28747
28793
  describe: async () => {
28748
28794
  const { name, calculations } = await readCalculations(cfg.client, cfg.id, cfg.signal);
28749
28795
  const all = calculations.map(toCalcPayload);
28750
- const hit = all.find((c) => sameScenarioContext(c, cfg.ctx));
28796
+ const idx = findScenarioContext(calculations, cfg.ctx);
28797
+ const hit = all[idx];
28751
28798
  if (hit === void 0) {
28752
28799
  throw new Error(
28753
28800
  `metric ${cfg.id} has no calculation for ${formatScenarioContext(cfg.ctx)}; list them with \`dr metrics calculations list ${cfg.id}\``
28754
28801
  );
28755
28802
  }
28756
- remaining = all.filter((c) => !sameScenarioContext(c, cfg.ctx));
28803
+ assertKeptRowsHaveYear(cfg.id, calculations, idx);
28804
+ remaining = all.filter((_, i) => i !== idx);
28757
28805
  return [
28758
28806
  `metric: ${JSON.stringify(name === "" ? "(unnamed)" : name)}`,
28759
28807
  `context: ${formatScenarioContext(cfg.ctx)}`,
@@ -28932,7 +28980,7 @@ function collectArray5(value, previous) {
28932
28980
  }
28933
28981
  function wireMetricsDrivers(metrics) {
28934
28982
  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)"
28983
+ "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
28984
  );
28937
28985
  const driverGetCmd = addScenarioContextFlags(
28938
28986
  driver.command("get").description("Show the driver definition for one scenario context").argument("<id>", "DRIVER metric id (positive integer)")
@@ -29068,7 +29116,7 @@ function wireMetricsDrivers(metrics) {
29068
29116
  addServerFlag(valuesSetCmd);
29069
29117
  addAccountFlag(valuesSetCmd);
29070
29118
  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)"
29119
+ "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
29120
  );
29073
29121
  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
29122
  const id = parseMetricIdArg(idStr);
@@ -55634,9 +55682,9 @@ async function listTemplates(client, params, options) {
55634
55682
  );
55635
55683
  return out ?? [];
55636
55684
  }
55637
- async function getTemplate(client, id, options) {
55685
+ async function getTemplate(client, id, options, params) {
55638
55686
  const out = await client.getJSON(
55639
- `/api/processes/v2/process_templates/${id}`,
55687
+ `/api/processes/v2/process_templates/${id}${buildQueryString(params)}`,
55640
55688
  options
55641
55689
  );
55642
55690
  if (out === null) {
@@ -55655,9 +55703,9 @@ async function createTemplate(client, payload, options) {
55655
55703
  }
55656
55704
  return out;
55657
55705
  }
55658
- async function updateTemplate(client, id, payload, options) {
55706
+ async function updateTemplate(client, id, payload, options, params) {
55659
55707
  const out = await client.putJSON(
55660
- `/api/processes/v2/process_templates/${id}`,
55708
+ `/api/processes/v2/process_templates/${id}${buildQueryString(params)}`,
55661
55709
  payload,
55662
55710
  options
55663
55711
  );
@@ -57123,6 +57171,10 @@ function validateTagsArray2(raw2) {
57123
57171
  }
57124
57172
 
57125
57173
  // src/commands/mecWorkflow.ts
57174
+ var MEC_DETAIL = { only_mec: true };
57175
+ function getMecTemplate(client, id, signal, params = {}) {
57176
+ return getTemplate(client, id, { signal }, { ...params, ...MEC_DETAIL });
57177
+ }
57126
57178
  function dash6(s) {
57127
57179
  if (s === null || s === void 0) return "-";
57128
57180
  if (typeof s === "number") return String(s);
@@ -57258,7 +57310,7 @@ function renderMecWorkflowList(out, rows) {
57258
57310
  }
57259
57311
  async function runMecWorkflowGet(cfg) {
57260
57312
  await requireMec(cfg.client, { signal: cfg.signal });
57261
- const tmpl = await withMec(() => getTemplate(cfg.client, cfg.id, { signal: cfg.signal }));
57313
+ const tmpl = await withMec(() => getMecTemplate(cfg.client, cfg.id, cfg.signal));
57262
57314
  if (cfg.asJson) {
57263
57315
  writeJSONIndented(cfg.out, tmpl);
57264
57316
  return;
@@ -57743,9 +57795,7 @@ async function runMecWorkflowUpdate(cfg) {
57743
57795
  server: cfg.serverFlag,
57744
57796
  confirmCmd: `dr mec workflow update ${cfg.id} --file ${cfg.payloadFile} --server ${cfg.serverFlag}`,
57745
57797
  describe: async () => {
57746
- const current = await withMec(
57747
- () => getTemplate(cfg.client, cfg.id, { signal: cfg.signal })
57748
- );
57798
+ const current = await withMec(() => getMecTemplate(cfg.client, cfg.id, cfg.signal));
57749
57799
  const lines = [`current name: ${current.name}`];
57750
57800
  if (cfg.payload.config?.name !== void 0 && cfg.payload.config.name !== current.name) {
57751
57801
  lines.push(`new name: ${cfg.payload.config.name}`);
@@ -57760,9 +57810,7 @@ async function runMecWorkflowUpdate(cfg) {
57760
57810
  },
57761
57811
  execute: async () => {
57762
57812
  const updated = await withMec(
57763
- () => updateTemplate(cfg.client, cfg.id, cfg.payload, {
57764
- signal: cfg.signal
57765
- })
57813
+ () => updateTemplate(cfg.client, cfg.id, cfg.payload, { signal: cfg.signal }, MEC_DETAIL)
57766
57814
  );
57767
57815
  if (cfg.asJson) {
57768
57816
  writeJSONIndented(cfg.out, updated);
@@ -57783,9 +57831,7 @@ async function runMecWorkflowState(cfg) {
57783
57831
  };
57784
57832
  await preflightConfirmToken(action, cfg.token);
57785
57833
  await requireMec(cfg.client, { signal: cfg.signal });
57786
- const current = await withMec(
57787
- () => getTemplate(cfg.client, cfg.id, { signal: cfg.signal })
57788
- );
57834
+ const current = await withMec(() => getMecTemplate(cfg.client, cfg.id, cfg.signal));
57789
57835
  let cascadeWarning = [];
57790
57836
  if (cfg.active) {
57791
57837
  cascadeWarning = await collectMecActivationCascade(cfg.client, current, cfg.signal);
@@ -57835,8 +57881,8 @@ async function collectMecActivationCascade(client, template, signal) {
57835
57881
  const lines = [];
57836
57882
  const instancesAll = template.process_instances ?? [];
57837
57883
  if (instancesAll.length === 0) return lines;
57838
- const latest = instancesAll[instancesAll.length - 1];
57839
- if (latest === void 0) return lines;
57884
+ const created = (p) => Date.parse(p.create_time ?? "") || 0;
57885
+ const latest = instancesAll.reduce((a, b) => created(b) > created(a) ? b : a);
57840
57886
  let process2;
57841
57887
  try {
57842
57888
  process2 = await withMec(() => getProcess(client, latest.id, { signal }));
@@ -57865,7 +57911,9 @@ async function runMecWorkflowArchive(cfg) {
57865
57911
  await requireMec(cfg.client, { signal: cfg.signal });
57866
57912
  let current = null;
57867
57913
  try {
57868
- current = await withMec(() => getTemplate(cfg.client, cfg.id, { signal: cfg.signal }));
57914
+ current = await withMec(
57915
+ () => getMecTemplate(cfg.client, cfg.id, cfg.signal, cfg.archive ? {} : { is_archive: true })
57916
+ );
57869
57917
  } catch {
57870
57918
  current = null;
57871
57919
  }
@@ -57880,7 +57928,7 @@ async function runMecWorkflowArchive(cfg) {
57880
57928
  return [
57881
57929
  `id: ${cfg.id}`,
57882
57930
  `target archived: ${cfg.archive ? "yes" : "no"}`,
57883
- "(template not visible via active-list GET \u2014 likely already in the opposite state)"
57931
+ `(template not visible via the ${cfg.archive ? "active" : "archived"}-template detail GET \u2014 likely already in the opposite state)`
57884
57932
  ];
57885
57933
  }
57886
57934
  return [
@@ -57916,9 +57964,7 @@ async function runMecWorkflowClone(cfg) {
57916
57964
  `cloning MEC workflow template ${cfg.templateId} (carries existing members forward)`
57917
57965
  );
57918
57966
  await requireMec(cfg.client, { signal: cfg.signal });
57919
- const current = await withMec(
57920
- () => getTemplate(cfg.client, cfg.templateId, { signal: cfg.signal })
57921
- );
57967
+ const current = await withMec(() => getMecTemplate(cfg.client, cfg.templateId, cfg.signal));
57922
57968
  const dateConfig = resolveDateTagConfig(current);
57923
57969
  const source = resolveCopyFrom(current, cfg.copyFrom, dateConfig.id);
57924
57970
  const extraTags = resolveExtraTags(current, parseExtraTagFlags(cfg.tagFlags), dateConfig.id);
@@ -76527,6 +76573,9 @@ async function aiServicePost(client, path13, body, attribute, options) {
76527
76573
  const fresh = await tokenFor(client, signal, true);
76528
76574
  res = await sendOnce(url, fresh.token, payload, extra, signal);
76529
76575
  }
76576
+ return parseResponse(rel, res);
76577
+ }
76578
+ async function parseResponse(rel, res) {
76530
76579
  const text2 = await res.text();
76531
76580
  if (!res.ok) {
76532
76581
  const envelope = parseErrorEnvelope(text2);
@@ -76633,6 +76682,26 @@ function listModels(client, options) {
76633
76682
  function getSystemEntities(client, options) {
76634
76683
  return read(client, "v1/ManagedAgents/getSystemEntities", {}, options);
76635
76684
  }
76685
+ async function writeSchedule(client, path13, body, options) {
76686
+ const res = await write(client, path13, body, options);
76687
+ const schedule = res !== null && typeof res === "object" ? res.schedule : void 0;
76688
+ if (schedule === null || typeof schedule !== "object" || Array.isArray(schedule)) {
76689
+ throw new Error(`POST /ai-service/external/${path13}: response has no schedule object`);
76690
+ }
76691
+ return res;
76692
+ }
76693
+ function listSchedules(client, body, options) {
76694
+ return read(client, "v1/ManagedAgentSchedules/listSchedules", body, options);
76695
+ }
76696
+ function createSchedule(client, body, options) {
76697
+ return writeSchedule(client, "v1/ManagedAgentSchedules/createSchedule", body, options);
76698
+ }
76699
+ function updateSchedule(client, body, options) {
76700
+ return writeSchedule(client, "v1/ManagedAgentSchedules/updateSchedule", body, options);
76701
+ }
76702
+ function deleteSchedule(client, body, options) {
76703
+ return write(client, "v1/ManagedAgentSchedules/deleteSchedule", body, options);
76704
+ }
76636
76705
 
76637
76706
  // src/commands/_agentCliScopes.ts
76638
76707
  var DEFAULT_CLI_SCOPES = ["*:read"];
@@ -77644,6 +77713,218 @@ function registerAgentPinCommands(agents) {
77644
77713
  withPinTargets(unpin);
77645
77714
  }
77646
77715
 
77716
+ // src/commands/agents_schedules.ts
77717
+ var SCHEDULE_ID = "<scheduleId>";
77718
+ function requirePrompt(prompt) {
77719
+ if (prompt.trim() === "") {
77720
+ throw new Error("--prompt must not be empty (omit it, or use update --clear-prompt)");
77721
+ }
77722
+ return prompt;
77723
+ }
77724
+ function tokenAgentId(client) {
77725
+ try {
77726
+ return firstString(decodeJwtClaims(client.session.jwtAccess ?? ""), "agent_id");
77727
+ } catch {
77728
+ return "";
77729
+ }
77730
+ }
77731
+ function agentIdFor(flag, client) {
77732
+ const id = (flag ?? "").trim();
77733
+ return id !== "" ? id : tokenAgentId(client);
77734
+ }
77735
+ function scheduleLine(s) {
77736
+ const state = s.enabled === void 0 ? "?" : s.enabled ? "enabled" : "disabled";
77737
+ return [
77738
+ s.id ?? "?",
77739
+ state,
77740
+ `cron "${s.crontab ?? "?"}" ${s.timezone ?? "UTC"}`,
77741
+ `agent ${s.agentId ?? "?"}`,
77742
+ `last ${s.lastRunStatus ?? "-"}`
77743
+ ].join(" ");
77744
+ }
77745
+ function writeResult(cfg, verb, res) {
77746
+ if (cfg.asJson) {
77747
+ writeJSONIndented(cfg.out, res);
77748
+ return;
77749
+ }
77750
+ cfg.out.write(`${verb} ${scheduleLine(res.schedule)}
77751
+ `);
77752
+ }
77753
+ async function runSchedulesList(cfg) {
77754
+ const agentId = agentIdFor(cfg.agentId, cfg.client);
77755
+ const res = await listSchedules(cfg.client, agentId !== "" ? { agentId } : {}, {
77756
+ signal: cfg.signal
77757
+ });
77758
+ if (cfg.asJson) {
77759
+ writeJSONIndented(cfg.out, res);
77760
+ return;
77761
+ }
77762
+ const list29 = res.schedules ?? [];
77763
+ if (list29.length === 0) {
77764
+ cfg.out.write("(no schedules)\n");
77765
+ return;
77766
+ }
77767
+ for (const s of list29) cfg.out.write(`${scheduleLine(s)}
77768
+ `);
77769
+ }
77770
+ async function runSchedulesCreate(cfg) {
77771
+ const agentId = agentIdFor(cfg.agentId, cfg.client);
77772
+ if (agentId === "") {
77773
+ throw new Error(
77774
+ "missing required option: --agent-id <agentId> (the session token names no agent)"
77775
+ );
77776
+ }
77777
+ if (cfg.cron === void 0) throw new Error("missing required option: --cron <expr>");
77778
+ const body = { agentId, crontab: parseCron(cfg.cron) };
77779
+ if (cfg.tz !== void 0) body.timezone = cfg.tz;
77780
+ if (cfg.prompt !== void 0) body.prompt = requirePrompt(cfg.prompt);
77781
+ if (cfg.environmentId !== void 0) body.environmentId = cfg.environmentId;
77782
+ if (cfg.enabled !== void 0) body.enabled = requireBool(cfg.enabled, "--enabled");
77783
+ writeResult(cfg, "Created", await createSchedule(cfg.client, body, { signal: cfg.signal }));
77784
+ }
77785
+ async function runSchedulesUpdate(cfg) {
77786
+ const id = requireId(cfg.id, SCHEDULE_ID);
77787
+ if (cfg.prompt !== void 0 && cfg.clearPrompt === true) {
77788
+ throw new Error("give --prompt or --clear-prompt, not both");
77789
+ }
77790
+ const body = { id };
77791
+ if (cfg.cron !== void 0) body.crontab = parseCron(cfg.cron);
77792
+ if (cfg.tz !== void 0) body.timezone = cfg.tz;
77793
+ if (cfg.prompt !== void 0) body.prompt = requirePrompt(cfg.prompt);
77794
+ if (cfg.clearPrompt === true) body.prompt = null;
77795
+ if (cfg.environmentId !== void 0) body.environmentId = cfg.environmentId;
77796
+ if (cfg.enabled !== void 0) body.enabled = requireBool(cfg.enabled, "--enabled");
77797
+ if (Object.keys(body).length === 1) throw new Error("nothing to change: give at least one flag");
77798
+ writeResult(cfg, "Updated", await updateSchedule(cfg.client, body, { signal: cfg.signal }));
77799
+ }
77800
+ async function runSchedulesDelete(cfg) {
77801
+ const id = requireId(cfg.id, SCHEDULE_ID);
77802
+ const serverKey = cfg.client.serverKey;
77803
+ const action = {
77804
+ resource: "managed-agent-schedule",
77805
+ id: `${serverKey}:${id}`,
77806
+ verb: "delete"
77807
+ };
77808
+ await preflightConfirmToken(action, cfg.token);
77809
+ await twoStepConfirm({
77810
+ out: proseStream(cfg),
77811
+ action,
77812
+ token: cfg.token,
77813
+ server: serverKey,
77814
+ confirmCmd: `dr agents schedules delete --id ${shellArg(id)} --server ${shellArg(serverKey)}`,
77815
+ describe: async () => {
77816
+ const agentId = tokenAgentId(cfg.client);
77817
+ const res = await listSchedules(cfg.client, agentId !== "" ? { agentId } : {}, {
77818
+ signal: cfg.signal
77819
+ });
77820
+ const s = (res.schedules ?? []).find((row) => row.id === id);
77821
+ if (s === void 0) {
77822
+ throw new Error(`no schedule ${id} in this org (see: dr agents schedules list)`);
77823
+ }
77824
+ return [
77825
+ scheduleLine(s),
77826
+ `prompt: ${JSON.stringify(s.prompt ?? null)}`,
77827
+ `environment: ${s.environmentId ?? "(resolved per run)"}`,
77828
+ // `create` cannot set this: a restored schedule runs as whoever re-creates it.
77829
+ `runs as: user ${s.runAsUserId ?? "?"}`
77830
+ ];
77831
+ },
77832
+ execute: async () => {
77833
+ const res = await deleteSchedule(cfg.client, { id }, { signal: cfg.signal });
77834
+ proseStream(cfg).write(`Deleted schedule ${id}.
77835
+ `);
77836
+ if (cfg.asJson) writeJSONIndented(cfg.out, res);
77837
+ }
77838
+ });
77839
+ }
77840
+ var AGENT_ID_HELP = "Agent id (default: the session token's agent_id claim, which a managed agent's token carries).";
77841
+ function registerAgentSchedulesCommands(agents) {
77842
+ const schedules = agents.command("schedules").description(
77843
+ "Cron schedules that open a conversation with a managed agent, as the session user."
77844
+ );
77845
+ const list29 = schedules.command("list").description("List the org's schedules.").option(
77846
+ "--agent-id <agentId>",
77847
+ "Only this agent's (default: the session token's agent_id claim; without one, every agent's)."
77848
+ ).option("--json", JSON_HELP).action(async (opts) => {
77849
+ const setup = await setupCall(opts.server, opts.account);
77850
+ try {
77851
+ await runSchedulesList({
77852
+ client: setup.client,
77853
+ signal: setup.signal,
77854
+ ...std(),
77855
+ agentId: opts.agentId,
77856
+ asJson: opts.json === true
77857
+ });
77858
+ } finally {
77859
+ setup.cleanup();
77860
+ }
77861
+ });
77862
+ withTarget(list29);
77863
+ const create20 = schedules.command("create").description("Schedule an agent; its runs act as you.").option("--agent-id <agentId>", AGENT_ID_HELP).option(
77864
+ "--cron <expr>",
77865
+ 'Five-field cron line, runs at least 5 minutes apart, e.g. "0 8 * * 1".'
77866
+ ).option("--tz <iana>", "Timezone the cron line is read in (default UTC).").option("--prompt <text>", "What each run is asked (default: a bare trigger turn).").option(
77867
+ "--environment-id <id>",
77868
+ "Anthropic environment for the runs (default: resolved per run)."
77869
+ ).option("--enabled <true|false>", "Create it enabled (default true).").option("--json", JSON_HELP).action(async (opts) => {
77870
+ const setup = await setupCall(opts.server, opts.account);
77871
+ try {
77872
+ await runSchedulesCreate({
77873
+ client: setup.client,
77874
+ signal: setup.signal,
77875
+ ...std(),
77876
+ agentId: opts.agentId,
77877
+ cron: opts.cron,
77878
+ tz: opts.tz,
77879
+ prompt: opts.prompt,
77880
+ environmentId: opts.environmentId,
77881
+ enabled: opts.enabled,
77882
+ asJson: opts.json === true
77883
+ });
77884
+ } finally {
77885
+ setup.cleanup();
77886
+ }
77887
+ });
77888
+ withTarget(create20);
77889
+ const update13 = schedules.command("update").description("Change a schedule (only the flags given change); its runs then act as you.").option("--id <scheduleId>", "Schedule id.").option("--cron <expr>", "New five-field cron line.").option("--tz <iana>", "New timezone.").option("--prompt <text>", "New prompt.").option("--clear-prompt", "Remove the prompt (runs get a bare trigger turn).").option("--environment-id <id>", "New Anthropic environment.").option("--enabled <true|false>", "Enable or disable it.").option("--json", JSON_HELP).action(async (opts) => {
77890
+ const setup = await setupCall(opts.server, opts.account);
77891
+ try {
77892
+ await runSchedulesUpdate({
77893
+ client: setup.client,
77894
+ signal: setup.signal,
77895
+ ...std(),
77896
+ id: opts.id,
77897
+ cron: opts.cron,
77898
+ tz: opts.tz,
77899
+ prompt: opts.prompt,
77900
+ clearPrompt: opts.clearPrompt,
77901
+ environmentId: opts.environmentId,
77902
+ enabled: opts.enabled,
77903
+ asJson: opts.json === true
77904
+ });
77905
+ } finally {
77906
+ setup.cleanup();
77907
+ }
77908
+ });
77909
+ withTarget(update13);
77910
+ const del = schedules.command("delete").description("Delete a schedule. (two-step confirm)").option("--id <scheduleId>", "Schedule id.").option("--confirm-token <token>", CONFIRM_HELP).option("--json", JSON_HELP).action(async (opts) => {
77911
+ const setup = await setupCall(opts.server, opts.account);
77912
+ try {
77913
+ await runSchedulesDelete({
77914
+ client: setup.client,
77915
+ signal: setup.signal,
77916
+ ...std(),
77917
+ id: opts.id,
77918
+ token: opts.confirmToken,
77919
+ asJson: opts.json === true
77920
+ });
77921
+ } finally {
77922
+ setup.cleanup();
77923
+ }
77924
+ });
77925
+ withTarget(del);
77926
+ }
77927
+
77647
77928
  // src/commands/agents_skills.ts
77648
77929
  import { mkdir as mkdir4, writeFile as writeFile7 } from "fs/promises";
77649
77930
  import { dirname as dirname9, join as join17 } from "path";
@@ -79212,6 +79493,7 @@ function registerAgentsCommand(root) {
79212
79493
  registerAgentPinCommands(agents);
79213
79494
  registerAgentStudioCommands(agents);
79214
79495
  registerAgentSkillsCommand(agents);
79496
+ registerAgentSchedulesCommands(agents);
79215
79497
  }
79216
79498
 
79217
79499
  // src/auth/versionCheck.ts
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "1.6.185",
3
+ "cliVersion": "1.6.187",
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": {
@@ -695,7 +695,7 @@
695
695
  "requires": [
696
696
  {
697
697
  "resource": "tables",
698
- "action": "read"
698
+ "action": "write"
699
699
  }
700
700
  ]
701
701
  },
@@ -851,7 +851,7 @@
851
851
  "requires": [
852
852
  {
853
853
  "resource": "dashboards",
854
- "action": "read"
854
+ "action": "write"
855
855
  }
856
856
  ]
857
857
  },
@@ -1067,7 +1067,7 @@
1067
1067
  "requires": [
1068
1068
  {
1069
1069
  "resource": "tables",
1070
- "action": "read"
1070
+ "action": "write"
1071
1071
  }
1072
1072
  ]
1073
1073
  },
@@ -1075,7 +1075,7 @@
1075
1075
  "requires": [
1076
1076
  {
1077
1077
  "resource": "tables",
1078
- "action": "read"
1078
+ "action": "write"
1079
1079
  }
1080
1080
  ]
1081
1081
  },
@@ -1083,7 +1083,7 @@
1083
1083
  "requires": [
1084
1084
  {
1085
1085
  "resource": "tables",
1086
- "action": "read"
1086
+ "action": "write"
1087
1087
  }
1088
1088
  ]
1089
1089
  },
@@ -1863,7 +1863,7 @@
1863
1863
  "requires": [
1864
1864
  {
1865
1865
  "resource": "integrations",
1866
- "action": "read"
1866
+ "action": "write"
1867
1867
  }
1868
1868
  ]
1869
1869
  },
@@ -5220,6 +5220,38 @@
5220
5220
  "action": "destructive"
5221
5221
  }
5222
5222
  ]
5223
+ },
5224
+ "agents schedules list": {
5225
+ "requires": [
5226
+ {
5227
+ "resource": "managed-agents",
5228
+ "action": "read"
5229
+ }
5230
+ ]
5231
+ },
5232
+ "agents schedules create": {
5233
+ "requires": [
5234
+ {
5235
+ "resource": "managed-agents",
5236
+ "action": "write"
5237
+ }
5238
+ ]
5239
+ },
5240
+ "agents schedules update": {
5241
+ "requires": [
5242
+ {
5243
+ "resource": "managed-agents",
5244
+ "action": "write"
5245
+ }
5246
+ ]
5247
+ },
5248
+ "agents schedules delete": {
5249
+ "requires": [
5250
+ {
5251
+ "resource": "managed-agents",
5252
+ "action": "destructive"
5253
+ }
5254
+ ]
5223
5255
  }
5224
5256
  },
5225
5257
  "aliases": {
@@ -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
@@ -57,7 +57,9 @@ Reads never need confirmation. Gated **writes** fall into two tiers (full table:
57
57
  every server, because the agent and skill registry is shared by every
58
58
  environment (a write on DEV-1 changes production agents too).
59
59
 
60
- **Tier A is the smaller set:** every `delete` (across all resources), plus
60
+ **Tier A is the smaller set:** every `delete` (across all resources, except
61
+ `agents schedules delete`: one schedule row, which `create` restores, though
62
+ the restored row runs as whoever re-creates it), plus
61
63
  `workflow`/`mec workflow` `archive` (and its `delete` alias), `process delete`,
62
64
  `step delete`, `task delete`; `forecast scenarios bulk-edit`; `cash rules
63
65
  delete-all`; `filebox archive` and `filebox data-query clear`;
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.187",
4
4
  "description": "Datarails CLI",
5
5
  "type": "module",
6
6
  "engines": {