@datarails-ai/dr-cli 1.6.164 → 1.6.166

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
@@ -457,6 +457,7 @@ dr skill print <name> # print an embedded skill's SKILL.md
457
457
  | Desk | `desk teams list`, `desk support-email set <team> <email>`, `desk smtp set/rotate/get/test <team>`, `desk sendgrid register <domain>/status/verify`, `desk ticket create <team> --title --requester-email [--attach <path> ...] [--no-thread]` (`<team>` is an id, support email, or team name. `ticket create` **emails the requester**, so it is confirm-gated and keeps the word gate on prod — see `docs/how-to-create-a-desk-ticket.md`) |
458
458
  | Connectors | `connectors publish <dataqueryFile>`, `connectors query repoint <queryId>`, `connectors binding get <fileboxUuid>` (drives the connectors control-plane over VPN) |
459
459
  | 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) |
460
+ | Agent release pins | `agents served`, `agents pin/unpin` (two-step confirm): the version each deployment serves; `--server`, `--servers <keys>` or `--production` |
460
461
  | Skill | `skill` — prints embedded `SKILL.md` |
461
462
 
462
463
  ### 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.164",
11
+ version: "1.6.166",
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.32",
43
+ "@datarails-ai/dr-appspace-cli": "0.12.33",
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-26T10:42:12.958Z";
72
+ var BUILD_TIME = "2026-10-26T13:44:23.917Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -707,6 +707,9 @@ function keys() {
707
707
  return ordered.map((s) => s.key);
708
708
  }
709
709
  var PROD_KEYS = /* @__PURE__ */ new Set(["US", "US2", "UK", "CA", "EU", "STUDIO", "DEMO"]);
710
+ function prodKeys() {
711
+ return ordered.filter((s) => PROD_KEYS.has(s.key)).map((s) => s.key);
712
+ }
710
713
  function isProdServer(input) {
711
714
  let server;
712
715
  try {
@@ -4371,6 +4374,11 @@ var PROD_GATE_TIER_A = /* @__PURE__ */ new Set([
4371
4374
  "managed-agent|set-global",
4372
4375
  "managed-agent|set-visibility",
4373
4376
  "managed-agent|archive",
4377
+ // Release pins (dr agents pin / unpin). A pin decides which agent version customers get on
4378
+ // a deployment, and unpinning can jump it several versions at once; one run can cover every
4379
+ // production server. The plan prints each server's current pin, which is the undo.
4380
+ "managed-agent|pin",
4381
+ "managed-agent|unpin",
4374
4382
  "agent-skill|delete",
4375
4383
  // template library: a published entry is offered to every customer org on the server.
4376
4384
  // Not `publish`: a Tier-A-only `publish` verb would make gen-scopes-map classify every
@@ -10050,14 +10058,14 @@ function parseRow3(cells, lineNo, seenSources, seenTargets) {
10050
10058
  );
10051
10059
  }
10052
10060
  seenSources.set(sourceKey2, lineNo);
10053
- const targetKey = target.toLowerCase();
10054
- const priorTarget = seenTargets.get(targetKey);
10061
+ const targetKey2 = target.toLowerCase();
10062
+ const priorTarget = seenTargets.get(targetKey2);
10055
10063
  if (priorTarget !== void 0) {
10056
10064
  throw new Error(
10057
10065
  `--file: line ${lineNo}: duplicate target "${target}" (already on line ${priorTarget})`
10058
10066
  );
10059
10067
  }
10060
- seenTargets.set(targetKey, lineNo);
10068
+ seenTargets.set(targetKey2, lineNo);
10061
10069
  return { lineNo, source, target };
10062
10070
  }
10063
10071
  function requireEmail(value, lineNo, column) {
@@ -21600,7 +21608,7 @@ function registerRescanCommand(root) {
21600
21608
  }
21601
21609
 
21602
21610
  // src/commands/reports.ts
21603
- import { isDeepStrictEqual } from "util";
21611
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
21604
21612
 
21605
21613
  // src/api/reports.ts
21606
21614
  async function list17(client, options) {
@@ -21692,6 +21700,18 @@ function coerceOptionValue(value) {
21692
21700
  return value;
21693
21701
  }
21694
21702
  function buildCalculatedRow(params) {
21703
+ return buildCalculatedValue(
21704
+ params,
21705
+ "--position is required (name of the existing row to anchor to; run `budget-builder build` to list the available anchors)"
21706
+ );
21707
+ }
21708
+ function buildCalculatedColumn(params) {
21709
+ return buildCalculatedValue(
21710
+ params,
21711
+ '--position is required (name of the existing column to anchor to, e.g. "01/31/2026/Actuals")'
21712
+ );
21713
+ }
21714
+ function buildCalculatedValue(params, missingPositionMessage) {
21695
21715
  const name = params.name.trim();
21696
21716
  if (name === "") {
21697
21717
  throw new Error("--name is required");
@@ -21702,9 +21722,7 @@ function buildCalculatedRow(params) {
21702
21722
  }
21703
21723
  const positionValue = (params.positionValue ?? "").trim();
21704
21724
  if (positionValue === "") {
21705
- throw new Error(
21706
- "--position is required (name of the existing row to anchor to; run `budget-builder build` to list the available anchors)"
21707
- );
21725
+ throw new Error(missingPositionMessage);
21708
21726
  }
21709
21727
  return {
21710
21728
  name,
@@ -21735,19 +21753,27 @@ function assertNoPeriodLabelRefs(formula) {
21735
21753
  );
21736
21754
  }
21737
21755
  function extractRowValidation(resp, rowName) {
21756
+ return extractCustomValueValidation(resp, rowName, "rows");
21757
+ }
21758
+ function extractCustomValueValidation(resp, name, axis) {
21759
+ const label = axis === "rows" ? "row" : "column";
21760
+ const noVerdictMap = {
21761
+ valid: false,
21762
+ message: "server returned no custom_value_validation.cols verdict map"
21763
+ };
21738
21764
  if (resp === null || typeof resp !== "object") {
21739
- return { valid: true, message: "" };
21765
+ return axis === "cols" ? noVerdictMap : { valid: true, message: "" };
21740
21766
  }
21741
21767
  const obj = resp;
21742
21768
  const validation = obj["custom_value_validation"];
21743
21769
  if (validation !== null && typeof validation === "object") {
21744
- const rowsMap = validation["rows"];
21745
- if (rowsMap !== null && typeof rowsMap === "object") {
21746
- const entry = rowsMap[rowName];
21770
+ const axisMap = validation[axis];
21771
+ if (axisMap !== null && typeof axisMap === "object") {
21772
+ const entry = axisMap[name];
21747
21773
  if (entry === void 0 || entry === null || typeof entry !== "object") {
21748
21774
  return {
21749
21775
  valid: false,
21750
- message: `server returned no verdict for row ${JSON.stringify(rowName)}`
21776
+ message: `server returned no verdict for ${label} ${JSON.stringify(name)}`
21751
21777
  };
21752
21778
  }
21753
21779
  const verdict = entry;
@@ -21771,12 +21797,15 @@ function extractRowValidation(resp, rowName) {
21771
21797
  };
21772
21798
  }
21773
21799
  }
21800
+ if (axis === "cols") {
21801
+ return noVerdictMap;
21802
+ }
21774
21803
  const rows = obj["calculatedRows"];
21775
21804
  if (Array.isArray(rows)) {
21776
21805
  for (const r of rows) {
21777
21806
  if (r === null || typeof r !== "object") continue;
21778
21807
  const row = r;
21779
- if (row["name"] !== rowName) continue;
21808
+ if (row["name"] !== name) continue;
21780
21809
  if (row["invalid"] === true) {
21781
21810
  return { valid: false, message: tooltipOf(row) };
21782
21811
  }
@@ -21793,6 +21822,62 @@ function tooltipOf(obj) {
21793
21822
  return "server marked the formula invalid (no errorTooltip provided)";
21794
21823
  }
21795
21824
 
21825
+ // src/commands/_reportsShared.ts
21826
+ function parseReportIdArg(idStr) {
21827
+ const n = Number(idStr);
21828
+ if (!Number.isInteger(n) || String(n) !== idStr) {
21829
+ throw new Error(`invalid report id: "${idStr}"`);
21830
+ }
21831
+ return n;
21832
+ }
21833
+ function regenerationError(resp) {
21834
+ if (resp === null || typeof resp !== "object" || Array.isArray(resp)) return void 0;
21835
+ const details = resp["generated_report_details"];
21836
+ if (details === null || typeof details !== "object" || Array.isArray(details)) return void 0;
21837
+ const err = details["error"];
21838
+ return typeof err === "string" && err !== "" ? err : void 0;
21839
+ }
21840
+
21841
+ // src/commands/reports_customColumns.ts
21842
+ import { isDeepStrictEqual } from "util";
21843
+
21844
+ // src/api/budgetBuilder.ts
21845
+ import { randomUUID } from "crypto";
21846
+ var TREE_TO_REPORT_PATH = "/api/exreports/reports/tree_to_report";
21847
+ var VALIDATE_CUSTOM_VALUE_PATH = "/api/exreports/reports/validate_custom_value";
21848
+ var SAVE_REPORT_PATH = "/api/exreports/reports";
21849
+ function withTraceId(options) {
21850
+ const { traceId, headers, ...rest } = options ?? {};
21851
+ return {
21852
+ ...rest,
21853
+ headers: { "X-Trace-id": traceId ?? randomUUID(), ...headers }
21854
+ };
21855
+ }
21856
+ async function treeToReport(client, definition, options) {
21857
+ const out = await client.putJSON(TREE_TO_REPORT_PATH, definition, withTraceId(options));
21858
+ if (out === null) {
21859
+ throw new Error(`PUT ${TREE_TO_REPORT_PATH}: empty response from tree_to_report`);
21860
+ }
21861
+ return out;
21862
+ }
21863
+ async function validateCustomValue(client, definition, options) {
21864
+ return client.putJSON(VALIDATE_CUSTOM_VALUE_PATH, definition, withTraceId(options));
21865
+ }
21866
+ async function save(client, definition, options) {
21867
+ const { timeoutMs, pollIntervalMs, sleep: sleep3, now, ...requestOptions } = options ?? {};
21868
+ const out = await writeWithCacheKeyPolling(client, "POST", SAVE_REPORT_PATH, definition, {
21869
+ ...withTraceId(requestOptions),
21870
+ timeoutMs,
21871
+ pollIntervalMs,
21872
+ sleep: sleep3,
21873
+ now
21874
+ });
21875
+ if (out === null) {
21876
+ throw new Error(`POST ${SAVE_REPORT_PATH}: empty response from budget-builder save`);
21877
+ }
21878
+ return out;
21879
+ }
21880
+
21796
21881
  // src/commands/_reportWriteTimeout.ts
21797
21882
  var REPORT_WRITE_TIMEOUT_FLAG_HELP = 'Polling budget in milliseconds while the server answers "still running" (orgs with large-reports support process the write on a worker)';
21798
21883
  var REPORT_WRITE_TIMEOUT_DEFAULT = String(WRITE_POLL_TIMEOUT_MS);
@@ -21811,6 +21896,238 @@ function reportWriteCallTimeoutMs(timeoutMs) {
21811
21896
  return timeoutMs + REPORT_WRITE_SETUP_OVERHEAD_MS;
21812
21897
  }
21813
21898
 
21899
+ // src/commands/reports_customColumns.ts
21900
+ var MAX_ANCHORS_SHOWN = 20;
21901
+ function isPlainObject2(v) {
21902
+ return v !== null && typeof v === "object" && !Array.isArray(v);
21903
+ }
21904
+ function extractColumnAnchors(resp) {
21905
+ if (!isPlainObject2(resp)) return [];
21906
+ const raw2 = resp["cols_position_options"];
21907
+ if (!Array.isArray(raw2)) return [];
21908
+ return raw2.map((o) => isPlainObject2(o) ? o["value"] : void 0).filter((v) => typeof v === "string" && v !== "").map((v) => decodeShape(v));
21909
+ }
21910
+ async function anchorsHint(client, definition, signal) {
21911
+ let anchors;
21912
+ try {
21913
+ anchors = extractColumnAnchors(
21914
+ await treeToReport(client, definition, { signal })
21915
+ );
21916
+ } catch {
21917
+ return "";
21918
+ }
21919
+ if (anchors.length === 0) return "";
21920
+ const shown = anchors.slice(0, MAX_ANCHORS_SHOWN);
21921
+ const more = anchors.length - shown.length;
21922
+ return ` Valid column names for --position and formula refs (${anchors.length}): ` + shown.map((a) => JSON.stringify(a)).join(", ") + (more > 0 ? `, \u2026 +${more} more` : "");
21923
+ }
21924
+ async function validateColumnAgainstServer(client, id, flags, signal) {
21925
+ if (id <= 0) {
21926
+ throw new Error("report id must be positive");
21927
+ }
21928
+ const column = buildCalculatedColumn({
21929
+ name: flags.name,
21930
+ formula: flags.formula,
21931
+ ...flags.format !== void 0 ? { format: flags.format } : {},
21932
+ ...flags.positionValue !== void 0 ? { positionValue: flags.positionValue } : {},
21933
+ ...flags.before ? { afterPositionValue: false } : {}
21934
+ });
21935
+ const report = await get10(client, id, { signal });
21936
+ const type = typeof report.type === "string" ? report.type : "";
21937
+ if (type.toLowerCase() !== "financials") {
21938
+ throw new Error(
21939
+ `report ${id} is a ${JSON.stringify(type)} report \u2014 custom columns exist on Financials (Report Builder) reports only; budget columns are generated from the budget year and cycle`
21940
+ );
21941
+ }
21942
+ const stored = isPlainObject2(report.payload) ? decodeShape(report.payload) : {};
21943
+ const existing = Array.isArray(stored["calculatedCols"]) ? stored["calculatedCols"] : [];
21944
+ if (existing.some((c) => isPlainObject2(c) && c["name"] === column.name)) {
21945
+ throw new Error(
21946
+ `report ${id} already has a custom column named ${JSON.stringify(column.name)} \u2014 pick a different --name`
21947
+ );
21948
+ }
21949
+ const withColumn = {
21950
+ ...stored,
21951
+ calculatedCols: [...existing, column]
21952
+ };
21953
+ const resp = await validateCustomValue(client, withColumn, { signal });
21954
+ const verdict = extractCustomValueValidation(resp, column.name, "cols");
21955
+ if (!verdict.valid) {
21956
+ throw new Error(
21957
+ `custom column ${JSON.stringify(column.name)} rejected: ${verdict.message.replace(/\.+$/, "")}.` + await anchorsHint(client, stored, signal)
21958
+ );
21959
+ }
21960
+ return { report, stored, existing, column, resp, warning: verdict.warning };
21961
+ }
21962
+ async function runReportsValidateColumn(cfg) {
21963
+ const { column, resp, warning } = await validateColumnAgainstServer(
21964
+ cfg.client,
21965
+ cfg.id,
21966
+ cfg.column,
21967
+ cfg.signal
21968
+ );
21969
+ if (cfg.asJson) {
21970
+ writeJSONIndented(cfg.out, resp);
21971
+ return;
21972
+ }
21973
+ if (warning !== void 0) {
21974
+ cfg.out.write(`warning: ${warning}
21975
+ `);
21976
+ }
21977
+ cfg.out.write(`Custom column ${JSON.stringify(column.name)} is valid for report ${cfg.id}.
21978
+ `);
21979
+ }
21980
+ async function runReportsAddColumn(cfg) {
21981
+ const { report, stored, existing, column, warning } = await validateColumnAgainstServer(
21982
+ cfg.client,
21983
+ cfg.id,
21984
+ cfg.column,
21985
+ cfg.signal
21986
+ );
21987
+ const savedColumn = { ...column, invalid: false, errorTooltip: "" };
21988
+ const body = { calculatedCols: [...existing, savedColumn] };
21989
+ const total = body.calculatedCols.length;
21990
+ if (warning !== void 0 && !cfg.asJson) {
21991
+ cfg.out.write(`warning: ${warning}
21992
+ `);
21993
+ }
21994
+ if (!cfg.confirm) {
21995
+ if (cfg.asJson) {
21996
+ writeJSONIndented(cfg.out, {
21997
+ action: "add-column",
21998
+ report_id: cfg.id,
21999
+ dry_run: true,
22000
+ ...warning !== void 0 ? { warning } : {},
22001
+ body
22002
+ });
22003
+ return;
22004
+ }
22005
+ cfg.out.write(`Target: account ${cfg.client.serverKey}, operator ${cfg.client.email}
22006
+ `);
22007
+ cfg.out.write(
22008
+ `dry-run: would add custom column ${JSON.stringify(column.name)} to report ${cfg.id} ${JSON.stringify(report.name)} (${total} custom column${plural(total)} after). Re-run with --confirm to apply.
22009
+ `
22010
+ );
22011
+ cfg.out.write(` formula: ${column.formula}
22012
+ `);
22013
+ cfg.out.write(
22014
+ ` position: ${column.afterPositionValue ? "right of" : "left of"} ${JSON.stringify(column.positionValue)}
22015
+ `
22016
+ );
22017
+ cfg.out.write(` format: ${column.format}
22018
+ `);
22019
+ return;
22020
+ }
22021
+ const current = await get10(cfg.client, cfg.id, { signal: cfg.signal });
22022
+ const currentPayload = isPlainObject2(current.payload) ? decodeShape(current.payload) : {};
22023
+ if (!isDeepStrictEqual(currentPayload, stored)) {
22024
+ throw new Error(
22025
+ `report ${cfg.id} changed while the column was being validated \u2014 nothing was written; re-run to validate against the current report`
22026
+ );
22027
+ }
22028
+ let resp;
22029
+ try {
22030
+ resp = await update5(cfg.client, cfg.id, body, {
22031
+ signal: cfg.signal,
22032
+ timeoutMs: cfg.timeoutMs,
22033
+ pollIntervalMs: cfg.pollIntervalMs
22034
+ });
22035
+ } catch (err) {
22036
+ if (err instanceof CacheKeyWriteTimeoutError) {
22037
+ throw new Error(
22038
+ `${err.message}. The column may still be added \u2014 check with \`dr reports get ${cfg.id} --json\` before retrying, or raise --timeout.`,
22039
+ { cause: err }
22040
+ );
22041
+ }
22042
+ throw err;
22043
+ }
22044
+ if (cfg.asJson) {
22045
+ cfg.out.write(JSON.stringify(resp) + "\n");
22046
+ } else {
22047
+ cfg.out.write(
22048
+ `Added custom column ${JSON.stringify(column.name)} to report ${cfg.id} ${JSON.stringify(report.name)} (${total} custom column${plural(total)}).
22049
+ `
22050
+ );
22051
+ }
22052
+ const regenError = regenerationError(resp);
22053
+ if (regenError !== void 0) {
22054
+ throw new Error(
22055
+ `report ${cfg.id} was updated, but the platform could not regenerate its Excel output: ` + regenError
22056
+ );
22057
+ }
22058
+ }
22059
+ function columnFlagsFromOpts(opts) {
22060
+ return {
22061
+ name: opts.name ?? "",
22062
+ formula: opts.formula ?? "",
22063
+ format: opts.format,
22064
+ positionValue: opts.position,
22065
+ before: opts.before === true
22066
+ };
22067
+ }
22068
+ function addColumnFlags(cmd) {
22069
+ return cmd.argument("<id>", "report id (positive integer)").requiredOption("--name <name>", "Custom column name (unique within the report)").requiredOption(
22070
+ "--formula <formula>",
22071
+ 'Column formula using bracket-wrapped COLUMN names, e.g. "[01/31/2026/Actuals]-[01/31/2026/Budget]" (nested column levels are joined with /)'
22072
+ ).option("--format <format>", "Display format for the column (default: '#,###')").requiredOption(
22073
+ "--position <column-name>",
22074
+ "Name of the EXISTING column to place this one next to \u2014 a rejected column lists the valid names"
22075
+ ).option("--before", "Place the column LEFT of the --position anchor (default: right)", false);
22076
+ }
22077
+ function registerReportsColumnCommands(reports) {
22078
+ const validateCmd = addColumnFlags(
22079
+ reports.command("validate-column").description(
22080
+ "Check a custom column against a Financials report (PUT validate_custom_value) without changing anything"
22081
+ )
22082
+ ).option("--json", "Output the raw validation response JSON", false).action(async (idStr, opts) => {
22083
+ const id = parseReportIdArg(idStr);
22084
+ const column = columnFlagsFromOpts(opts);
22085
+ const setup = await setupCall(opts.server, opts.account);
22086
+ try {
22087
+ await runReportsValidateColumn({
22088
+ client: setup.client,
22089
+ id,
22090
+ column,
22091
+ asJson: opts.json === true,
22092
+ signal: setup.signal,
22093
+ out: process.stdout
22094
+ });
22095
+ } finally {
22096
+ setup.cleanup();
22097
+ }
22098
+ });
22099
+ addServerFlag(validateCmd);
22100
+ addAccountFlag(validateCmd);
22101
+ const addCmd = addColumnFlags(
22102
+ reports.command("add-column").description(
22103
+ "Validate a custom column and add it to a Financials report (validate_custom_value + PUT report; dry-run unless --confirm)"
22104
+ )
22105
+ ).option("--confirm", "Actually add the column (default is dry-run)", false).option("--timeout <ms>", REPORT_WRITE_TIMEOUT_FLAG_HELP, REPORT_WRITE_TIMEOUT_DEFAULT).option("--json", "Output JSON: the dry-run plan, or the raw server response", false).action(async (idStr, opts) => {
22106
+ const id = parseReportIdArg(idStr);
22107
+ const column = columnFlagsFromOpts(opts);
22108
+ const timeoutMs = parseReportWriteTimeout(opts.timeout);
22109
+ const setup = await setupCall(opts.server, opts.account, {
22110
+ timeoutMs: reportWriteCallTimeoutMs(timeoutMs)
22111
+ });
22112
+ try {
22113
+ await runReportsAddColumn({
22114
+ client: setup.client,
22115
+ id,
22116
+ column,
22117
+ confirm: opts.confirm === true,
22118
+ asJson: opts.json === true,
22119
+ timeoutMs,
22120
+ signal: setup.signal,
22121
+ out: process.stdout
22122
+ });
22123
+ } finally {
22124
+ setup.cleanup();
22125
+ }
22126
+ });
22127
+ addServerFlag(addCmd);
22128
+ addAccountFlag(addCmd);
22129
+ }
22130
+
21814
22131
  // src/commands/reports.ts
21815
22132
  async function runReportsList(cfg) {
21816
22133
  const all = await list17(cfg.client, { signal: cfg.signal });
@@ -21865,7 +22182,7 @@ ${JSON.stringify(resp)}
21865
22182
  var DATE_PICKER_PRESETS = ["today", "yesterday", "this_month", "prev_month"];
21866
22183
  var FILE_LINK_KEYS = ["document_data", "report_space_data", "workflow_data"];
21867
22184
  var NOTHING_TO_UPDATE = "nothing to update \u2014 pass --payload-file, --name, --option, or --date-picker-preset";
21868
- function isPlainObject2(v) {
22185
+ function isPlainObject3(v) {
21869
22186
  return v !== null && typeof v === "object" && !Array.isArray(v);
21870
22187
  }
21871
22188
  function assertDatePickerPreset(value) {
@@ -21884,7 +22201,7 @@ async function readRequestedChanges(flags) {
21884
22201
  if (flags.payloadFile !== void 0) {
21885
22202
  const raw2 = await readJsonPayload(flags.payloadFile);
21886
22203
  const inner = raw2["payload"];
21887
- if (isPlainObject2(inner)) {
22204
+ if (isPlainObject3(inner)) {
21888
22205
  Object.assign(changes, inner);
21889
22206
  if (typeof raw2["id"] === "number") sourceReportId = raw2["id"];
21890
22207
  } else {
@@ -21912,7 +22229,7 @@ async function readRequestedChanges(flags) {
21912
22229
  function previewValue(v) {
21913
22230
  if (v === void 0) return "(unset)";
21914
22231
  if (Array.isArray(v)) return `[${v.length} item${plural(v.length)}]`;
21915
- if (isPlainObject2(v)) {
22232
+ if (isPlainObject3(v)) {
21916
22233
  const n = Object.keys(v).length;
21917
22234
  return `{${n} key${plural(n)}}`;
21918
22235
  }
@@ -21922,10 +22239,10 @@ function previewLines(stored, body) {
21922
22239
  const lines = [];
21923
22240
  for (const [key, next] of Object.entries(body)) {
21924
22241
  const prev = stored[key];
21925
- if (isPlainObject2(prev) && isPlainObject2(next)) {
22242
+ if (isPlainObject3(prev) && isPlainObject3(next)) {
21926
22243
  const subKeys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
21927
22244
  for (const sub2 of subKeys) {
21928
- if (isDeepStrictEqual(prev[sub2], next[sub2])) continue;
22245
+ if (isDeepStrictEqual2(prev[sub2], next[sub2])) continue;
21929
22246
  lines.push(`${key}.${sub2}: ${previewValue(prev[sub2])} \u2192 ${previewValue(next[sub2])}`);
21930
22247
  }
21931
22248
  continue;
@@ -21934,13 +22251,6 @@ function previewLines(stored, body) {
21934
22251
  }
21935
22252
  return lines;
21936
22253
  }
21937
- function regenerationError(resp) {
21938
- if (!isPlainObject2(resp)) return void 0;
21939
- const details = resp["generated_report_details"];
21940
- if (!isPlainObject2(details)) return void 0;
21941
- const err = details["error"];
21942
- return typeof err === "string" && err !== "" ? err : void 0;
21943
- }
21944
22254
  async function runReportsUpdate(cfg) {
21945
22255
  if (cfg.id <= 0) {
21946
22256
  throw new Error("report id must be positive");
@@ -21957,15 +22267,15 @@ async function runReportsUpdate(cfg) {
21957
22267
  `report ${cfg.id} is a ${JSON.stringify(type)} report \u2014 \`reports update\` edits Financials (Report Builder) reports only; budget reports cannot be edited from the CLI (edit them in Budget management)`
21958
22268
  );
21959
22269
  }
21960
- const stored = isPlainObject2(report.payload) ? report.payload : {};
22270
+ const stored = isPlainObject3(report.payload) ? report.payload : {};
21961
22271
  if (Object.keys(assignments).length > 0) {
21962
22272
  const fileOptions = changes["options"];
21963
- const base = isPlainObject2(fileOptions) ? fileOptions : isPlainObject2(stored["options"]) ? stored["options"] : {};
22273
+ const base = isPlainObject3(fileOptions) ? fileOptions : isPlainObject3(stored["options"]) ? stored["options"] : {};
21964
22274
  changes["options"] = { ...base, ...assignments };
21965
22275
  }
21966
22276
  const body = {};
21967
22277
  for (const [key, value] of Object.entries(changes)) {
21968
- if (!isDeepStrictEqual(value, stored[key])) body[key] = value;
22278
+ if (!isDeepStrictEqual2(value, stored[key])) body[key] = value;
21969
22279
  }
21970
22280
  const changedKeys = Object.keys(body);
21971
22281
  if (sourceReportId !== void 0 && sourceReportId !== cfg.id) {
@@ -22030,7 +22340,7 @@ async function runReportsUpdate(cfg) {
22030
22340
  if (cfg.asJson) {
22031
22341
  cfg.out.write(JSON.stringify(resp) + "\n");
22032
22342
  } else {
22033
- const respObj = isPlainObject2(resp) ? resp : {};
22343
+ const respObj = isPlainObject3(resp) ? resp : {};
22034
22344
  const name = typeof respObj["name"] === "string" ? respObj["name"] : typeof body["name"] === "string" ? body["name"] : report.name;
22035
22345
  const updatedAt = strOr(
22036
22346
  typeof respObj["updated_at"] === "string" ? respObj["updated_at"] : "",
@@ -22141,13 +22451,6 @@ function renderReportsTable(out, reports) {
22141
22451
  }
22142
22452
  writeTable17(out, rows);
22143
22453
  }
22144
- function parseReportIdArg(idStr) {
22145
- const n = Number(idStr);
22146
- if (!Number.isInteger(n) || String(n) !== idStr) {
22147
- throw new Error(`invalid report id: "${idStr}"`);
22148
- }
22149
- return n;
22150
- }
22151
22454
  function parseTableIdFlag4(raw2) {
22152
22455
  if (raw2 === void 0 || raw2 === "") return void 0;
22153
22456
  const n = Number(raw2);
@@ -22293,49 +22596,11 @@ function registerReportsCommand(root) {
22293
22596
  });
22294
22597
  addServerFlag(deleteCmd);
22295
22598
  addAccountFlag(deleteCmd);
22599
+ registerReportsColumnCommands(reports);
22296
22600
  }
22297
22601
 
22298
22602
  // src/commands/budgetBuilder.ts
22299
22603
  import { promises as fs12 } from "fs";
22300
-
22301
- // src/api/budgetBuilder.ts
22302
- import { randomUUID } from "crypto";
22303
- var TREE_TO_REPORT_PATH = "/api/exreports/reports/tree_to_report";
22304
- var VALIDATE_CUSTOM_VALUE_PATH = "/api/exreports/reports/validate_custom_value";
22305
- var SAVE_REPORT_PATH = "/api/exreports/reports";
22306
- function withTraceId(options) {
22307
- const { traceId, headers, ...rest } = options ?? {};
22308
- return {
22309
- ...rest,
22310
- headers: { "X-Trace-id": traceId ?? randomUUID(), ...headers }
22311
- };
22312
- }
22313
- async function treeToReport(client, definition, options) {
22314
- const out = await client.putJSON(TREE_TO_REPORT_PATH, definition, withTraceId(options));
22315
- if (out === null) {
22316
- throw new Error(`PUT ${TREE_TO_REPORT_PATH}: empty response from tree_to_report`);
22317
- }
22318
- return out;
22319
- }
22320
- async function validateCustomValue(client, definition, options) {
22321
- return client.putJSON(VALIDATE_CUSTOM_VALUE_PATH, definition, withTraceId(options));
22322
- }
22323
- async function save(client, definition, options) {
22324
- const { timeoutMs, pollIntervalMs, sleep: sleep3, now, ...requestOptions } = options ?? {};
22325
- const out = await writeWithCacheKeyPolling(client, "POST", SAVE_REPORT_PATH, definition, {
22326
- ...withTraceId(requestOptions),
22327
- timeoutMs,
22328
- pollIntervalMs,
22329
- sleep: sleep3,
22330
- now
22331
- });
22332
- if (out === null) {
22333
- throw new Error(`POST ${SAVE_REPORT_PATH}: empty response from budget-builder save`);
22334
- }
22335
- return out;
22336
- }
22337
-
22338
- // src/commands/budgetBuilder.ts
22339
22604
  function assertValidDesign(value, flag) {
22340
22605
  if (typeof value !== "string" || !BUDGET_DESIGN_STYLES.includes(value)) {
22341
22606
  throw new Error(
@@ -22379,7 +22644,7 @@ function designSummary(def) {
22379
22644
  const newDesign = def.options?.standardDesign === true ? "off" : "on";
22380
22645
  return `design=${design}, new design=${newDesign}`;
22381
22646
  }
22382
- var MAX_ANCHORS_SHOWN = 20;
22647
+ var MAX_ANCHORS_SHOWN2 = 20;
22383
22648
  function extractRowAnchors(resp) {
22384
22649
  if (resp === null || typeof resp !== "object") return [];
22385
22650
  const raw2 = resp["rows_position_options"];
@@ -22405,7 +22670,7 @@ async function runBudgetBuilderBuild(cfg) {
22405
22670
  `);
22406
22671
  const anchors = extractRowAnchors(resp);
22407
22672
  if (anchors.length > 0) {
22408
- const shown = anchors.slice(0, MAX_ANCHORS_SHOWN);
22673
+ const shown = anchors.slice(0, MAX_ANCHORS_SHOWN2);
22409
22674
  const more = anchors.length - shown.length;
22410
22675
  cfg.out.write(
22411
22676
  `Row anchors for --position (${anchors.length}): ` + shown.map((a) => JSON.stringify(a)).join(", ") + (more > 0 ? `, \u2026 +${more} more` : "") + "\n"
@@ -33639,7 +33904,7 @@ function describeValue(v) {
33639
33904
  return typeof v;
33640
33905
  }
33641
33906
  }
33642
- function isPlainObject3(v) {
33907
+ function isPlainObject4(v) {
33643
33908
  return v !== null && typeof v === "object" && !Array.isArray(v);
33644
33909
  }
33645
33910
  function tableIdProblems(value) {
@@ -33655,7 +33920,7 @@ function tableIdProblems(value) {
33655
33920
  );
33656
33921
  }
33657
33922
  value.forEach((entry, i) => {
33658
- if (!isPlainObject3(entry)) {
33923
+ if (!isPlainObject4(entry)) {
33659
33924
  out.push(`config.table_id[${i}] must be an object, got ${describeValue(entry)}.`);
33660
33925
  } else if (typeof entry["match_type"] !== "string") {
33661
33926
  out.push(
@@ -33667,7 +33932,7 @@ function tableIdProblems(value) {
33667
33932
  }
33668
33933
  function validateMapperConfigShape(config) {
33669
33934
  if (config === void 0) return;
33670
- if (!isPlainObject3(config)) {
33935
+ if (!isPlainObject4(config)) {
33671
33936
  throw new Error(`config must be a JSON object, got ${describeValue(config)}. ${GET_HINT}`);
33672
33937
  }
33673
33938
  const problems = [];
@@ -33683,7 +33948,7 @@ function validateMapperConfigShape(config) {
33683
33948
  continue;
33684
33949
  }
33685
33950
  value.forEach((entry, i) => {
33686
- if (!isPlainObject3(entry)) {
33951
+ if (!isPlainObject4(entry)) {
33687
33952
  problems.push(`config.${key}[${i}] must be an object, got ${describeValue(entry)}.`);
33688
33953
  }
33689
33954
  });
@@ -62481,7 +62746,7 @@ async function readHiddenRulesPayload(path13) {
62481
62746
  raw2.forEach((entry, idx) => {
62482
62747
  const ctx = `rules[${idx}]`;
62483
62748
  const base = validateCreate(entry, ctx);
62484
- if (!isPlainObject4(entry)) {
62749
+ if (!isPlainObject5(entry)) {
62485
62750
  throw new Error(`--file: ${ctx} must be a JSON object`);
62486
62751
  }
62487
62752
  const ctxnId = entry["customer_transaction_id"];
@@ -62540,11 +62805,11 @@ function parseReorderShorthand(raw2) {
62540
62805
  }
62541
62806
  return { rules: entries };
62542
62807
  }
62543
- function isPlainObject4(v) {
62808
+ function isPlainObject5(v) {
62544
62809
  return typeof v === "object" && v !== null && !Array.isArray(v);
62545
62810
  }
62546
62811
  function validateCreate(raw2, ctx) {
62547
- if (!isPlainObject4(raw2)) {
62812
+ if (!isPlainObject5(raw2)) {
62548
62813
  throw new Error(`--file: ${ctx} must be a JSON object`);
62549
62814
  }
62550
62815
  if (!Array.isArray(raw2["conditions"]) || raw2["conditions"].length === 0) {
@@ -62600,7 +62865,7 @@ function validateCreate(raw2, ctx) {
62600
62865
  return out;
62601
62866
  }
62602
62867
  function validateCondition(raw2, ctx) {
62603
- if (!isPlainObject4(raw2)) {
62868
+ if (!isPlainObject5(raw2)) {
62604
62869
  throw new Error(`--file: ${ctx} must be a JSON object`);
62605
62870
  }
62606
62871
  const inputField = raw2["input_field"];
@@ -62665,7 +62930,7 @@ function validateArgsForOperation(op, args, ctx) {
62665
62930
  }
62666
62931
  }
62667
62932
  function validateReorder(raw2, ctx) {
62668
- if (!isPlainObject4(raw2)) {
62933
+ if (!isPlainObject5(raw2)) {
62669
62934
  throw new Error(`--file: ${ctx} must be a JSON object`);
62670
62935
  }
62671
62936
  if (!Array.isArray(raw2["rules"]) || raw2["rules"].length === 0) {
@@ -62674,7 +62939,7 @@ function validateReorder(raw2, ctx) {
62674
62939
  const seenIds = /* @__PURE__ */ new Set();
62675
62940
  const entries = [];
62676
62941
  raw2["rules"].forEach((entry, idx) => {
62677
- if (!isPlainObject4(entry)) {
62942
+ if (!isPlainObject5(entry)) {
62678
62943
  throw new Error(`--file: ${ctx}.rules[${idx}] must be a JSON object`);
62679
62944
  }
62680
62945
  const id = entry["id"];
@@ -75447,6 +75712,15 @@ function setAgentGlobal(client, body, options) {
75447
75712
  function setAgentVisibility(client, body, options) {
75448
75713
  return write(client, "v1/ManagedAgents/setAgentVisibility", body, options);
75449
75714
  }
75715
+ function getServedAgentVersion(client, body, options) {
75716
+ return read(client, "v1/ManagedAgents/getServedAgentVersion", body, options);
75717
+ }
75718
+ function pinAgentVersion(client, body, options) {
75719
+ return write(client, "v1/ManagedAgents/pinAgentVersion", body, options);
75720
+ }
75721
+ function unpinAgent(client, body, options) {
75722
+ return write(client, "v1/ManagedAgents/unpinAgent", body, options);
75723
+ }
75450
75724
  function listSkills(client, body, options) {
75451
75725
  return read(client, "v1/ManagedAgents/listSkills", body, options);
75452
75726
  }
@@ -76138,6 +76412,356 @@ function renderToolsProposalText(proposal, resolved) {
76138
76412
  return lines.join("\n");
76139
76413
  }
76140
76414
 
76415
+ // src/commands/agents_pin.ts
76416
+ var AGENT_ID = "<agentId>";
76417
+ var REQUEST_BUDGET_MS = 3e4;
76418
+ function resolvePinTargets(opts) {
76419
+ const named = opts.servers !== void 0;
76420
+ const production = opts.production === true;
76421
+ const chosen = [opts.server !== void 0, named, production].filter(Boolean).length;
76422
+ if (chosen > 1) {
76423
+ throw new Error("pass one of --server, --servers or --production");
76424
+ }
76425
+ if (production) return [...prodKeys()];
76426
+ if (!named) return void 0;
76427
+ const keys2 = splitList(opts.servers).map((k) => lookup(k).key);
76428
+ if (keys2.length === 0) throw new Error("--servers: expected comma-separated server keys");
76429
+ return [...new Set(keys2)];
76430
+ }
76431
+ function targetKey(t) {
76432
+ try {
76433
+ return lookup(t.client.serverKey).key;
76434
+ } catch {
76435
+ return t.client.serverKey;
76436
+ }
76437
+ }
76438
+ function targetFlag(targets) {
76439
+ const keys2 = targets.map((t) => targetKey(t));
76440
+ const prod = prodKeys();
76441
+ if (keys2.length === prod.length && keys2.every((k, i) => k === prod[i])) return "--production";
76442
+ if (keys2.length === 1) return `--server ${shellArg(keys2[0] ?? "")}`;
76443
+ return `--servers ${shellArg(keys2.join(","))}`;
76444
+ }
76445
+ function gateServer(targets) {
76446
+ const keys2 = targets.map((t) => targetKey(t));
76447
+ return keys2.find((k) => isProdServer(k)) ?? keys2[0] ?? "";
76448
+ }
76449
+ function versionText(v) {
76450
+ return v !== void 0 ? `v${v}` : "nothing";
76451
+ }
76452
+ function describeServed(state) {
76453
+ const serves = `serves ${versionText(state.servedVersion)}`;
76454
+ const pin = state.pinnedVersion !== void 0 ? `pinned v${state.pinnedVersion}` + (state.pinnedBy !== void 0 ? ` by ${state.pinnedBy}` : "") + (state.pinnedAt !== void 0 ? ` on ${state.pinnedAt.slice(0, 10)}` : "") : "not pinned";
76455
+ const reason = state.reason !== void 0 ? ` (${state.reason})` : "";
76456
+ return `${serves} \xB7 ${pin}${reason}`;
76457
+ }
76458
+ function padKey(key, width) {
76459
+ return key.padEnd(width, " ");
76460
+ }
76461
+ function keyWidth(targets) {
76462
+ return Math.max(...targets.map((t) => targetKey(t).length));
76463
+ }
76464
+ function errorText(err) {
76465
+ return err instanceof Error ? err.message : String(err);
76466
+ }
76467
+ function summaryError(summary, causes) {
76468
+ const refusal = causes.find((e) => e instanceof ScopeDeniedError || e instanceof TenantPinError);
76469
+ const first = causes[0];
76470
+ const oneClass = first instanceof Error && first.constructor !== Error && causes.every((e) => e instanceof Error && e.constructor === first.constructor);
76471
+ const keep = refusal ?? (oneClass ? first : void 0);
76472
+ if (keep instanceof Error) {
76473
+ keep.message = summary;
76474
+ return keep;
76475
+ }
76476
+ return new Error(summary, {
76477
+ cause: causes.length === 1 ? first : new AggregateError(causes, summary)
76478
+ });
76479
+ }
76480
+ function forJson(outcomes) {
76481
+ return outcomes.map((o) => ({
76482
+ server: o.server,
76483
+ ...o.result !== void 0 && { result: o.result },
76484
+ ...o.error !== void 0 && { error: o.error }
76485
+ }));
76486
+ }
76487
+ function causesOf(outcomes) {
76488
+ return outcomes.filter((o) => o.error !== void 0).map((o) => o.cause);
76489
+ }
76490
+ async function readServed(targets, agentId) {
76491
+ const outcomes = [];
76492
+ for (const t of targets) {
76493
+ t.extendTimeout?.(REQUEST_BUDGET_MS);
76494
+ try {
76495
+ const result = await getServedAgentVersion(t.client, { agentId }, { signal: t.signal });
76496
+ outcomes.push({ server: targetKey(t), result });
76497
+ } catch (err) {
76498
+ outcomes.push({ server: targetKey(t), error: errorText(err), cause: err });
76499
+ } finally {
76500
+ t.pauseTimeout?.();
76501
+ }
76502
+ }
76503
+ return outcomes;
76504
+ }
76505
+ function failedServers(outcomes) {
76506
+ return outcomes.filter((o) => o.error !== void 0).map((o) => o.server);
76507
+ }
76508
+ async function runAgentsServed(cfg) {
76509
+ const agentId = requireId(cfg.id, AGENT_ID);
76510
+ const outcomes = await readServed(cfg.targets, agentId);
76511
+ if (cfg.asJson) {
76512
+ writeJSONIndented(cfg.out, forJson(outcomes));
76513
+ } else {
76514
+ const width = keyWidth(cfg.targets);
76515
+ for (const o of outcomes) {
76516
+ const line = o.error !== void 0 ? `could not read: ${o.error}` : describeServed(o.result ?? {});
76517
+ cfg.out.write(`${padKey(o.server, width)} ${line}
76518
+ `);
76519
+ }
76520
+ }
76521
+ const failed = failedServers(outcomes);
76522
+ if (failed.length > 0) {
76523
+ throw summaryError(
76524
+ `could not read ${failed.length} of ${outcomes.length} server(s): ${failed.join(", ")}`,
76525
+ causesOf(outcomes)
76526
+ );
76527
+ }
76528
+ }
76529
+ async function writeEach(cfg, verb, write2) {
76530
+ const prose = proseStream(cfg);
76531
+ const width = keyWidth(cfg.targets);
76532
+ const outcomes = [];
76533
+ for (const t of cfg.targets) {
76534
+ t.extendTimeout?.(REQUEST_BUDGET_MS);
76535
+ try {
76536
+ const result = await write2(t);
76537
+ outcomes.push({ server: targetKey(t), result });
76538
+ const after = result !== null ? describeServed(result) : "done";
76539
+ prose.write(`${padKey(targetKey(t), width)} ${verb} \u2014 ${after}
76540
+ `);
76541
+ } catch (err) {
76542
+ outcomes.push({ server: targetKey(t), error: errorText(err), cause: err });
76543
+ prose.write(`${padKey(targetKey(t), width)} FAILED \u2014 ${errorText(err)}
76544
+ `);
76545
+ }
76546
+ }
76547
+ if (cfg.asJson) writeJSONIndented(cfg.out, forJson(outcomes));
76548
+ const failed = failedServers(outcomes);
76549
+ if (failed.length > 0) {
76550
+ const done = outcomes.filter((o) => o.error === void 0).map((o) => o.server);
76551
+ throw summaryError(
76552
+ `${verb === "pinned" ? "pin" : "unpin"} failed on ${failed.join(", ")}` + (done.length > 0 ? ` but succeeded on ${done.join(", ")} \u2014 those now serve differently` : "") + ". Re-run for the failed server(s) only with --servers.",
76553
+ causesOf(outcomes)
76554
+ );
76555
+ }
76556
+ }
76557
+ async function planLines(targets, agentId, change) {
76558
+ const outcomes = await readServed(targets, agentId);
76559
+ const failed = failedServers(outcomes);
76560
+ if (failed.length > 0) {
76561
+ const detail = outcomes.filter((o) => o.error !== void 0).map((o) => `${o.server}: ${o.error}`).join("; ");
76562
+ throw summaryError(
76563
+ `could not read the current pin on ${failed.join(", ")} \u2014 ${detail}`,
76564
+ causesOf(outcomes)
76565
+ );
76566
+ }
76567
+ const width = keyWidth(targets);
76568
+ const lines = outcomes.map(
76569
+ (o) => `${padKey(o.server, width)} ${describeServed(o.result ?? {})} \u2192 ${change(o.result ?? {})}`
76570
+ );
76571
+ return lines;
76572
+ }
76573
+ async function assertVersionExists(target, agentId, version) {
76574
+ target.extendTimeout?.(REQUEST_BUDGET_MS);
76575
+ let res;
76576
+ try {
76577
+ res = await listAgentVersions(target.client, { agentId }, { signal: target.signal });
76578
+ } finally {
76579
+ target.pauseTimeout?.();
76580
+ }
76581
+ const data4 = res?.data;
76582
+ if (!Array.isArray(data4)) return;
76583
+ if (!data4.some((v) => v.version === version)) {
76584
+ const known = data4.map((v) => v.version).filter((v) => typeof v === "number").sort((a, b) => b - a);
76585
+ throw new Error(
76586
+ `${agentId} has no version ${version}` + (known.length > 0 ? ` (latest is v${known[0]})` : "")
76587
+ );
76588
+ }
76589
+ }
76590
+ function requireVersion(version) {
76591
+ if (version === void 0) throw new Error("missing required option: --version-number <n>");
76592
+ return requirePositiveInt(version, "--version-number");
76593
+ }
76594
+ function pinAction(keys2, agentId, version) {
76595
+ return { resource: "managed-agent", id: `${keys2.join(",")}:${agentId}:v${version}`, verb: "pin" };
76596
+ }
76597
+ function unpinAction(keys2, agentId) {
76598
+ return { resource: "managed-agent", id: `${keys2.join(",")}:${agentId}`, verb: "unpin" };
76599
+ }
76600
+ async function runAgentsPin(cfg) {
76601
+ const agentId = requireId(cfg.id, AGENT_ID);
76602
+ const version = requireVersion(cfg.version);
76603
+ const flag = targetFlag(cfg.targets);
76604
+ const keys2 = cfg.targets.map((t) => targetKey(t));
76605
+ const action = pinAction(keys2, agentId, version);
76606
+ await preflightConfirmToken(action, cfg.token);
76607
+ const first = cfg.targets[0];
76608
+ if (first === void 0) throw new Error("no server to pin on");
76609
+ await assertVersionExists(first, agentId, version);
76610
+ await twoStepConfirm({
76611
+ out: cfg.out,
76612
+ action,
76613
+ token: cfg.token,
76614
+ server: gateServer(cfg.targets),
76615
+ confirmCmd: `dr agents pin --id ${shellArg(agentId)} --version-number ${version} ${flag}`,
76616
+ describe: async () => [
76617
+ `agent: ${agentId}`,
76618
+ `pin: v${version} on ${keys2.join(", ")}`,
76619
+ ...await planLines(cfg.targets, agentId, () => `pinned v${version}`),
76620
+ "Each server keeps serving the pinned version until someone pins another or unpins it."
76621
+ ],
76622
+ execute: () => writeEach(
76623
+ cfg,
76624
+ "pinned",
76625
+ (t) => pinAgentVersion(t.client, { agentId, version }, { signal: t.signal })
76626
+ )
76627
+ });
76628
+ }
76629
+ async function runAgentsUnpin(cfg) {
76630
+ const agentId = requireId(cfg.id, AGENT_ID);
76631
+ const flag = targetFlag(cfg.targets);
76632
+ const keys2 = cfg.targets.map((t) => targetKey(t));
76633
+ const action = unpinAction(keys2, agentId);
76634
+ await preflightConfirmToken(action, cfg.token);
76635
+ await twoStepConfirm({
76636
+ out: cfg.out,
76637
+ action,
76638
+ token: cfg.token,
76639
+ server: gateServer(cfg.targets),
76640
+ confirmCmd: `dr agents unpin --id ${shellArg(agentId)} ${flag}`,
76641
+ describe: async () => [
76642
+ `agent: ${agentId}`,
76643
+ `unpin on ${keys2.join(", ")}`,
76644
+ ...await planLines(cfg.targets, agentId, () => "not pinned"),
76645
+ "An unpinned server serves the newest version on its SDK track, which can be several versions ahead of the pin."
76646
+ ],
76647
+ execute: () => writeEach(cfg, "unpinned", (t) => unpinAgent(t.client, { agentId }, { signal: t.signal }))
76648
+ });
76649
+ }
76650
+ async function openEach(keys2, open3) {
76651
+ const setups = [];
76652
+ const failures = [];
76653
+ const causes = [];
76654
+ for (const key of keys2) {
76655
+ try {
76656
+ setups.push(await open3(key));
76657
+ } catch (err) {
76658
+ failures.push(`${key}: ${errorText(err)}`);
76659
+ causes.push(err);
76660
+ }
76661
+ }
76662
+ if (failures.length > 0) {
76663
+ for (const s of setups) s.cleanup();
76664
+ throw summaryError(
76665
+ `cannot open ${failures.length} of ${keys2.length} server(s) \u2014 nothing was read or written:
76666
+ ` + failures.join("\n "),
76667
+ causes
76668
+ );
76669
+ }
76670
+ return setups;
76671
+ }
76672
+ function knownKeys(opts) {
76673
+ const keys2 = resolvePinTargets(opts);
76674
+ if (keys2 !== void 0 || opts.server === void 0) return keys2;
76675
+ try {
76676
+ return [lookup(opts.server.trim()).key];
76677
+ } catch {
76678
+ return void 0;
76679
+ }
76680
+ }
76681
+ async function preflightTargets(opts, token, action) {
76682
+ if (token === void 0 || token.trim() === "") return;
76683
+ const keys2 = knownKeys(opts);
76684
+ if (keys2 !== void 0) await preflightConfirmToken(action(keys2), token);
76685
+ }
76686
+ async function openTargets(opts) {
76687
+ const keys2 = resolvePinTargets(opts);
76688
+ const open3 = async (key) => {
76689
+ const setup = await setupCall(key, opts.account);
76690
+ setup.pauseTimeout();
76691
+ return setup;
76692
+ };
76693
+ const setups = keys2 === void 0 ? [await open3(opts.server)] : await openEach(keys2, open3);
76694
+ const targets = setups.map((s) => ({
76695
+ client: s.client,
76696
+ signal: s.signal,
76697
+ pauseTimeout: s.pauseTimeout,
76698
+ extendTimeout: s.extendTimeout
76699
+ }));
76700
+ return { targets, setups };
76701
+ }
76702
+ function withPinTargets(cmd) {
76703
+ withTarget(cmd);
76704
+ cmd.option(
76705
+ "--servers <keys>",
76706
+ "Comma-separated server keys, e.g. US,UK. Each needs its own dr login."
76707
+ );
76708
+ cmd.option("--production", "Every production server: US, US2, UK, CA, EU, STUDIO, DEMO.");
76709
+ return cmd;
76710
+ }
76711
+ function registerAgentPinCommands(agents) {
76712
+ const served = agents.command("served").description(
76713
+ "Show which version of an agent each server serves, and the release pin it holds, if any."
76714
+ ).option("--id <agentId>", "Agent id.").option("--json", JSON_HELP).action(async (opts) => {
76715
+ const { targets, setups } = await openTargets(opts);
76716
+ try {
76717
+ await runAgentsServed({ targets, ...std(), id: opts.id, asJson: opts.json === true });
76718
+ } finally {
76719
+ for (const s of setups) s.cleanup();
76720
+ }
76721
+ });
76722
+ withPinTargets(served);
76723
+ const pin = agents.command("pin").description(
76724
+ "Hold a server on one agent version: it serves that version whatever is saved later. (two-step confirm)"
76725
+ ).option("--id <agentId>", "Agent id.").option("--version-number <n>", "The version to serve.").option("--confirm-token <token>", CONFIRM_HELP).option("--json", JSON_HELP).action(async (opts) => {
76726
+ const agentId = requireId(opts.id, AGENT_ID);
76727
+ const version = requireVersion(opts.versionNumber);
76728
+ await preflightTargets(opts, opts.confirmToken, (keys2) => pinAction(keys2, agentId, version));
76729
+ const { targets, setups } = await openTargets(opts);
76730
+ try {
76731
+ await runAgentsPin({
76732
+ targets,
76733
+ ...std(),
76734
+ id: opts.id,
76735
+ version: opts.versionNumber,
76736
+ token: opts.confirmToken,
76737
+ asJson: opts.json === true
76738
+ });
76739
+ } finally {
76740
+ for (const s of setups) s.cleanup();
76741
+ }
76742
+ });
76743
+ withPinTargets(pin);
76744
+ const unpin = agents.command("unpin").description(
76745
+ "Drop a server's pin: it goes back to serving the newest version on its SDK track. (two-step confirm)"
76746
+ ).option("--id <agentId>", "Agent id.").option("--confirm-token <token>", CONFIRM_HELP).option("--json", JSON_HELP).action(async (opts) => {
76747
+ const agentId = requireId(opts.id, AGENT_ID);
76748
+ await preflightTargets(opts, opts.confirmToken, (keys2) => unpinAction(keys2, agentId));
76749
+ const { targets, setups } = await openTargets(opts);
76750
+ try {
76751
+ await runAgentsUnpin({
76752
+ targets,
76753
+ ...std(),
76754
+ id: opts.id,
76755
+ token: opts.confirmToken,
76756
+ asJson: opts.json === true
76757
+ });
76758
+ } finally {
76759
+ for (const s of setups) s.cleanup();
76760
+ }
76761
+ });
76762
+ withPinTargets(unpin);
76763
+ }
76764
+
76141
76765
  // src/commands/agents_skills.ts
76142
76766
  import { mkdir as mkdir4, writeFile as writeFile7 } from "fs/promises";
76143
76767
  import { dirname as dirname9, join as join17 } from "path";
@@ -76707,7 +77331,7 @@ function registerAgentStudioCommands(agents) {
76707
77331
  }
76708
77332
 
76709
77333
  // src/commands/agents.ts
76710
- var AGENT_ID = "<agentId>";
77334
+ var AGENT_ID2 = "<agentId>";
76711
77335
  function agentRef(agentId, version) {
76712
77336
  return version !== void 0 ? { agentId, version: requirePositiveInt(version, "--version-number") } : { agentId };
76713
77337
  }
@@ -76773,7 +77397,7 @@ async function runAgentsList(cfg) {
76773
77397
  }
76774
77398
  }
76775
77399
  async function runAgentsGet(cfg) {
76776
- const agentId = requireId(cfg.id, AGENT_ID);
77400
+ const agentId = requireId(cfg.id, AGENT_ID2);
76777
77401
  const ref2 = agentRef(agentId, cfg.version);
76778
77402
  const [res, cliScopes] = await Promise.all([
76779
77403
  getAgent(cfg.client, ref2, { signal: cfg.signal }),
@@ -76794,7 +77418,7 @@ async function tryFetchCliScopes(client, agentId, signal) {
76794
77418
  }
76795
77419
  }
76796
77420
  async function runAgentsVersions(cfg) {
76797
- const agentId = requireId(cfg.id, AGENT_ID);
77421
+ const agentId = requireId(cfg.id, AGENT_ID2);
76798
77422
  writeJSONIndented(
76799
77423
  cfg.out,
76800
77424
  await listAgentVersions(cfg.client, { agentId }, { signal: cfg.signal })
@@ -76815,7 +77439,7 @@ async function runAgentsMcpServers(cfg) {
76815
77439
  `);
76816
77440
  }
76817
77441
  async function runAgentsCliScopes(cfg) {
76818
- const agentId = requireId(cfg.id, AGENT_ID);
77442
+ const agentId = requireId(cfg.id, AGENT_ID2);
76819
77443
  const res = await getAgentCliScopes(cfg.client, { agentId }, { signal: cfg.signal });
76820
77444
  if (cfg.asJson) {
76821
77445
  writeJSONIndented(cfg.out, res);
@@ -76838,7 +77462,7 @@ async function runAgentsScopeRegistry(cfg) {
76838
77462
  `);
76839
77463
  }
76840
77464
  async function runAgentsTestLink(cfg) {
76841
- const agentId = requireId(cfg.id, AGENT_ID);
77465
+ const agentId = requireId(cfg.id, AGENT_ID2);
76842
77466
  const url = resolveTestChatLink({
76843
77467
  client: cfg.client,
76844
77468
  agentId,
@@ -76867,7 +77491,7 @@ async function runAgentsTestLink(cfg) {
76867
77491
  }
76868
77492
  }
76869
77493
  async function runAgentsRefresh(cfg) {
76870
- const agentId = requireId(cfg.id, AGENT_ID);
77494
+ const agentId = requireId(cfg.id, AGENT_ID2);
76871
77495
  const hasPlan = (cfg.plan ?? "").trim() !== "";
76872
77496
  if (cfg.apply === true && !hasPlan) {
76873
77497
  throw new Error(
@@ -76987,7 +77611,7 @@ Applied. ${agentId} (base v${version}) \u2192 v${res?.version ?? "?"}.
76987
77611
  }
76988
77612
  }
76989
77613
  async function runAgentsPull(cfg) {
76990
- const agentId = requireId(cfg.id, AGENT_ID);
77614
+ const agentId = requireId(cfg.id, AGENT_ID2);
76991
77615
  const agent = await getAgent(cfg.client, agentRef(agentId, cfg.version), {
76992
77616
  signal: cfg.signal
76993
77617
  });
@@ -77025,7 +77649,7 @@ async function runAgentsPull(cfg) {
77025
77649
  );
77026
77650
  }
77027
77651
  async function runAgentsUpdate(cfg) {
77028
- const agentId = requireId(cfg.id, AGENT_ID);
77652
+ const agentId = requireId(cfg.id, AGENT_ID2);
77029
77653
  const toggleCli = cfg.exposeCli !== void 0 ? parseExposureToggle("cli", cfg.exposeCli) : void 0;
77030
77654
  const removeSdk = cfg.exposeSdk !== void 0 ? requireExposureRemoval("sdk", cfg.exposeSdk) : void 0;
77031
77655
  const mcpServers = cfg.mcpServers !== void 0 ? parseMcpServers(cfg.mcpServers) : void 0;
@@ -77220,7 +77844,7 @@ function reachMessage(kind, agentId, value) {
77220
77844
  `;
77221
77845
  }
77222
77846
  async function runReachFlip(cfg, kind, write2) {
77223
- const agentId = requireId(cfg.id, AGENT_ID);
77847
+ const agentId = requireId(cfg.id, AGENT_ID2);
77224
77848
  const value = requireBool(cfg.value, "--value");
77225
77849
  const serverKey = cfg.client.serverKey;
77226
77850
  const action = {
@@ -77267,7 +77891,7 @@ async function runAgentsSetGlobal(cfg) {
77267
77891
  );
77268
77892
  }
77269
77893
  async function runAgentsSetVisibility(cfg) {
77270
- const agentId = requireId(cfg.id, AGENT_ID);
77894
+ const agentId = requireId(cfg.id, AGENT_ID2);
77271
77895
  const everyone = cfg.everyone === true;
77272
77896
  const grants = cfg.grant ?? [];
77273
77897
  if (everyone && grants.length > 0) {
@@ -77327,7 +77951,7 @@ function describeVisibility(visibility) {
77327
77951
  return `restricted to ${formatVisibilityGrants(visibility.grants)}`;
77328
77952
  }
77329
77953
  async function runAgentsArchive(cfg) {
77330
- const agentId = requireId(cfg.id, AGENT_ID);
77954
+ const agentId = requireId(cfg.id, AGENT_ID2);
77331
77955
  const serverKey = cfg.client.serverKey;
77332
77956
  const action = {
77333
77957
  resource: "managed-agent",
@@ -77703,6 +78327,7 @@ function registerAgentsCommand(root) {
77703
78327
  }
77704
78328
  });
77705
78329
  withTarget(archive);
78330
+ registerAgentPinCommands(agents);
77706
78331
  registerAgentStudioCommands(agents);
77707
78332
  registerAgentSkillsCommand(agents);
77708
78333
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "1.6.164",
3
+ "cliVersion": "1.6.166",
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": {
@@ -1047,6 +1047,22 @@
1047
1047
  }
1048
1048
  ]
1049
1049
  },
1050
+ "reports validate-column": {
1051
+ "requires": [
1052
+ {
1053
+ "resource": "tables",
1054
+ "action": "read"
1055
+ }
1056
+ ]
1057
+ },
1058
+ "reports add-column": {
1059
+ "requires": [
1060
+ {
1061
+ "resource": "tables",
1062
+ "action": "write"
1063
+ }
1064
+ ]
1065
+ },
1050
1066
  "budget-builder build": {
1051
1067
  "requires": [
1052
1068
  {
@@ -4969,6 +4985,30 @@
4969
4985
  }
4970
4986
  ]
4971
4987
  },
4988
+ "agents served": {
4989
+ "requires": [
4990
+ {
4991
+ "resource": "managed-agents",
4992
+ "action": "read"
4993
+ }
4994
+ ]
4995
+ },
4996
+ "agents pin": {
4997
+ "requires": [
4998
+ {
4999
+ "resource": "managed-agents",
5000
+ "action": "destructive"
5001
+ }
5002
+ ]
5003
+ },
5004
+ "agents unpin": {
5005
+ "requires": [
5006
+ {
5007
+ "resource": "managed-agents",
5008
+ "action": "destructive"
5009
+ }
5010
+ ]
5011
+ },
4972
5012
  "agents catalog": {
4973
5013
  "requires": [
4974
5014
  {
@@ -273,6 +273,7 @@ area's gotchas.
273
273
  from the widget's own settings only.
274
274
  - `storyboards list|get|slides|prepare|delete`
275
275
  - `reports list|get|clone|update|delete` — `update <id>` edits a Financials (Report Builder) report: `--payload-file` (payload keys, or `reports get --json` output), `--name`, `--option k=v`, `--date-picker-preset`; dry-run unless `--confirm`
276
+ - `reports validate-column|add-column <id> --name <s> --formula <s> --position <col> [--before] [--format <f>]` — custom (calculated) column on a Financials report, stored in `payload.calculatedCols`. Formula refs and `--position` are COLUMN names with nested levels joined by `/` (`[01/31/2026/Actuals]-[01/31/2026/Budget]`); a rejected column lists the valid names. `--before` = left of the anchor (default right). `add-column` is dry-run unless `--confirm`. Budget reports have no custom columns (their columns come from the budget year/cycle)
276
277
  - `extables list|get|clone|delete`
277
278
 
278
279
  **Budget Builder (Budget management templates)** → [reference/budget-builder.md](reference/budget-builder.md)
@@ -111,7 +111,10 @@ dr reports get <new-id> --server <key> --json # round-trip check; echo settings
111
111
  - `budget_info.scenario` wire values are lowercase: `budget` | `forecast` |
112
112
  `manual` (the UI's "leave empty / manual" option = `manual`).
113
113
  - Budget COLUMNS are auto-generated server-side from `budget_info` +
114
- `date_picker` — leave `cols: []`; only rows are authored.
114
+ `date_picker` — leave `cols: []`; only rows are authored. There are no
115
+ custom columns on a budget template: the server ignores `calculatedCols`
116
+ for `type: "budget"` and the web Budget Builder has no control for them.
117
+ Custom columns exist on Financials reports only (`dr reports add-column`).
115
118
  - Row HIERARCHY is encoded by nesting, NOT by flat array order: the child
116
119
  field node goes in the parent node's `children: [...]` array (recursive).
117
120
  Flat siblings in `rows[]` render as independent top-level row fields.
@@ -64,6 +64,9 @@ delete-all`; `filebox archive` and `filebox data-query clear`;
64
64
  `agents archive` and the three agent-reach flips `agents set-listed` /
65
65
  `set-global` / `set-visibility` (each changes who can reach the agent for every
66
66
  organization at once);
67
+ `agents pin` / `unpin` (decide which agent version customers get on a server; one
68
+ run with `--production` covers every production server, so relay the plan's
69
+ per-server lines — they are also the undo);
67
70
  `error-enrichments import` (replaces every rule on the server, zeroes all
68
71
  hits/feedback counters, and reassigns every rule id); and
69
72
  `dashboards push --override` / `filebox push --override`. `import` is **not** an
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-cli",
3
- "version": "1.6.164",
3
+ "version": "1.6.166",
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.32",
20
+ "@datarails-ai/dr-appspace-cli": "0.12.33",
21
21
  "@modelcontextprotocol/sdk": "^1.29.0",
22
22
  "@napi-rs/keyring": "^1.3.0",
23
23
  "commander": "^12.1.0",