@datarails-ai/dr-cli 1.6.101 → 1.6.102
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 +1 -0
- package/dist/cli.js +392 -29
- package/dist/scopes-map.json +17 -1
- package/dist/skills/dr-cli/SKILL.md +4 -1
- package/dist/skills/dr-cli/reference/dashboards-widgets.md +23 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -397,6 +397,7 @@ dr skill print <name> # print an embedded skill's SKILL.md
|
|
|
397
397
|
| Data models | `data-models list/get` |
|
|
398
398
|
| Datamappers | `datamappers list/get/create/update/delete` |
|
|
399
399
|
| Dashboards | `dashboards list/get/create/delete/prepare`, `dashboards set-date-picker <id> --mode <preset> \| --year YYYY \| --quarter YYYY-Qn \| --month YYYY-MM \| --day YYYY-MM-DD` |
|
|
400
|
+
| Folders | `folders list/create`, `folders reorder --container <root\|id> (--items folder:12,entity:87 \| --move <kind:id> --to-index <n>)`, `folders move <id> --parent <root\|id>` (dashboard / story picker tree, flag-gated; reorder and move are two-step confirm; a dashboard moves with `dashboards set-folder`) |
|
|
400
401
|
| Functions | `functions list/get/create/delete` |
|
|
401
402
|
| Dynamic ranges | `dynamic-ranges list/get/data/create/delete` |
|
|
402
403
|
| Lookups (LUTs) | `lut list/create/update/delete/reorder/unmapped` (alias `lookups`) |
|
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.
|
|
11
|
+
version: "1.6.102",
|
|
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.10.
|
|
43
|
+
"@datarails-ai/dr-appspace-cli": "0.10.22",
|
|
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-
|
|
72
|
+
var BUILD_TIME = "2026-10-07T11:02:43.513Z";
|
|
73
73
|
function formatBuildTime(iso) {
|
|
74
74
|
if (iso === "") return "";
|
|
75
75
|
const d = new Date(iso);
|
|
@@ -574,11 +574,18 @@ var UnknownAccountError = class extends Error {
|
|
|
574
574
|
var SCOPE_DENIED_CODE = 40301;
|
|
575
575
|
var SCOPE_UNDECLARED_CODE = 40302;
|
|
576
576
|
var ScopeDeniedError = class extends Error {
|
|
577
|
-
|
|
577
|
+
/**
|
|
578
|
+
* @param identity The identity whose ceiling refused, when it is NOT necessarily the one
|
|
579
|
+
* the command line selected — `dr login-as --as <operator>` mints with the operator's
|
|
580
|
+
* credential, not the `--server`/`--account` one. Named, the message points `dr whoami`
|
|
581
|
+
* at it; omitted, "this identity" is the one the reader just ran the command as.
|
|
582
|
+
*/
|
|
583
|
+
constructor(method, path11, code, required2, identity2) {
|
|
578
584
|
super("");
|
|
579
585
|
this.method = method;
|
|
580
586
|
this.path = path11;
|
|
581
587
|
this.code = code;
|
|
588
|
+
this.identity = identity2;
|
|
582
589
|
this.name = "ScopeDeniedError";
|
|
583
590
|
this.required = required2;
|
|
584
591
|
this.message = this.render();
|
|
@@ -586,6 +593,7 @@ var ScopeDeniedError = class extends Error {
|
|
|
586
593
|
method;
|
|
587
594
|
path;
|
|
588
595
|
code;
|
|
596
|
+
identity;
|
|
589
597
|
/** `resource:action` the command needs, joined when it needs more than one. Filled in by
|
|
590
598
|
* `cli.ts` after construction (see the class docstring); `undefined` until then, and for
|
|
591
599
|
* a command the local map says nothing about. */
|
|
@@ -604,6 +612,10 @@ var ScopeDeniedError = class extends Error {
|
|
|
604
612
|
return `${this.method} ${this.path}: this endpoint is not yet scope-declared on the server; report to #dr-cli`;
|
|
605
613
|
}
|
|
606
614
|
const needs = this.required === void 0 || this.required === "" ? "" : ` (needs ${this.required})`;
|
|
615
|
+
if (this.identity !== void 0) {
|
|
616
|
+
const { server, account } = this.identity;
|
|
617
|
+
return `${this.method} ${this.path}: the credential scope of ${account} on ${server} does not allow it${needs}; run 'dr whoami --server ${server} --account ${account}' to see the scopes it carries`;
|
|
618
|
+
}
|
|
607
619
|
return `${this.method} ${this.path}: this identity's credential scope does not allow it${needs}; run 'dr whoami' to see the scopes this identity carries`;
|
|
608
620
|
}
|
|
609
621
|
};
|
|
@@ -3670,6 +3682,11 @@ var PROD_GATE_TIER_B = /* @__PURE__ */ new Set([
|
|
|
3670
3682
|
// cash (dr cash …). rules delete / delete-all / hidden-rules delete → Tier A.
|
|
3671
3683
|
"cash-rule|update",
|
|
3672
3684
|
"cash-rules|reorder",
|
|
3685
|
+
// dashboard / story folders (dr folders reorder, dr folders move). Reorder is
|
|
3686
|
+
// per-user (only the caller's own picker order changes) and a move is undone by
|
|
3687
|
+
// another move.
|
|
3688
|
+
"folder-container|reorder",
|
|
3689
|
+
"folder|move",
|
|
3673
3690
|
"cash-hidden-rules|upsert",
|
|
3674
3691
|
"cash-ai-rules|trigger",
|
|
3675
3692
|
// metrics (dr metrics …). delete / dimension|category delete → A.
|
|
@@ -6079,7 +6096,17 @@ async function resolveTargetUserId(client, targetEmail, signal) {
|
|
|
6079
6096
|
`no user you can log in as matches "${targetEmail}".${near} Use --user-id <n> to impersonate by id.`
|
|
6080
6097
|
);
|
|
6081
6098
|
}
|
|
6082
|
-
|
|
6099
|
+
var LOGINAS_REQUIRED_SCOPE = "support-manager:write";
|
|
6100
|
+
function friendlyLoginAsError(err, targetEmail, explicitOperator) {
|
|
6101
|
+
if (err.code === "insufficient_scope" && err.httpStatus === 403) {
|
|
6102
|
+
return new ScopeDeniedError(
|
|
6103
|
+
"POST",
|
|
6104
|
+
"/jwt/api/loginas/",
|
|
6105
|
+
SCOPE_DENIED_CODE,
|
|
6106
|
+
LOGINAS_REQUIRED_SCOPE,
|
|
6107
|
+
explicitOperator
|
|
6108
|
+
);
|
|
6109
|
+
}
|
|
6083
6110
|
switch (err.code) {
|
|
6084
6111
|
case "not_permitted":
|
|
6085
6112
|
return new Error(`You are not permitted to impersonate ${targetEmail}.`);
|
|
@@ -6139,7 +6166,10 @@ async function runLoginAs(cfg) {
|
|
|
6139
6166
|
signal: cfg.signal
|
|
6140
6167
|
});
|
|
6141
6168
|
} catch (err) {
|
|
6142
|
-
if (err instanceof LoginAsError)
|
|
6169
|
+
if (err instanceof LoginAsError) {
|
|
6170
|
+
const explicitOperator = cfg.operatorFlag !== void 0 && cfg.operatorFlag !== "" ? { server: cfg.serverKey, account: operatorEmail } : void 0;
|
|
6171
|
+
throw friendlyLoginAsError(err, cfg.targetEmail, explicitOperator);
|
|
6172
|
+
}
|
|
6143
6173
|
throw err;
|
|
6144
6174
|
}
|
|
6145
6175
|
let storageEmail = cfg.targetEmail;
|
|
@@ -14366,8 +14396,15 @@ function registerDashboardsCommand(root) {
|
|
|
14366
14396
|
addAccountFlag(setFolderCmd);
|
|
14367
14397
|
}
|
|
14368
14398
|
|
|
14399
|
+
// src/commands/folders.ts
|
|
14400
|
+
import { createHash as createHash3 } from "crypto";
|
|
14401
|
+
|
|
14369
14402
|
// src/api/folders.ts
|
|
14370
14403
|
var FOLDER_ENTITY_TYPES = ["dashboard", "story"];
|
|
14404
|
+
var FOLDER_FLAGS = {
|
|
14405
|
+
dashboard: "dashboards__folders",
|
|
14406
|
+
story: "storytelling__folders"
|
|
14407
|
+
};
|
|
14371
14408
|
async function list9(client, entityType, options) {
|
|
14372
14409
|
const out = await client.getJSON(
|
|
14373
14410
|
`/api/folders/?entity_type=${entityType}`,
|
|
@@ -14384,6 +14421,39 @@ async function create4(client, entityType, name, parentId, options) {
|
|
|
14384
14421
|
}
|
|
14385
14422
|
return out;
|
|
14386
14423
|
}
|
|
14424
|
+
async function reorder(client, payload, options) {
|
|
14425
|
+
const path11 = "/api/folders/reorder";
|
|
14426
|
+
const res = await client.do("POST", path11, JSON.stringify(payload), options);
|
|
14427
|
+
if (!res.ok) {
|
|
14428
|
+
throw new Error(`POST ${path11}: HTTP ${res.status}: ${await res.text()}`);
|
|
14429
|
+
}
|
|
14430
|
+
}
|
|
14431
|
+
async function move(client, folderId, parentId, options) {
|
|
14432
|
+
const path11 = `/api/folders/${folderId}`;
|
|
14433
|
+
const out = await client.patchJSON(path11, { parent_id: parentId }, options);
|
|
14434
|
+
if (out === null) {
|
|
14435
|
+
throw new Error(`unexpected empty response from PATCH ${path11}`);
|
|
14436
|
+
}
|
|
14437
|
+
return out;
|
|
14438
|
+
}
|
|
14439
|
+
var STORY_PAGE_SIZE = 100;
|
|
14440
|
+
async function listEntities(client, entityType, options) {
|
|
14441
|
+
if (entityType === "dashboard") {
|
|
14442
|
+
return await client.getJSON("/api/dashboards/", options) ?? [];
|
|
14443
|
+
}
|
|
14444
|
+
const stories = [];
|
|
14445
|
+
for (let page = 1; ; page++) {
|
|
14446
|
+
const env = await client.getJSON(
|
|
14447
|
+
`/api/storytelling/?page_size=${STORY_PAGE_SIZE}&page=${page}`,
|
|
14448
|
+
options
|
|
14449
|
+
);
|
|
14450
|
+
if (env === null || env.results.length === 0) break;
|
|
14451
|
+
stories.push(...env.results);
|
|
14452
|
+
if (env.next === null) break;
|
|
14453
|
+
}
|
|
14454
|
+
const listed = new Set(stories.map((s) => s.id));
|
|
14455
|
+
return stories.filter((s) => s.parent_story === null || s.is_roll_forwarded || !listed.has(s.parent_story)).map(({ id, name, folder_id, position }) => ({ id, name, folder_id, position }));
|
|
14456
|
+
}
|
|
14387
14457
|
|
|
14388
14458
|
// src/commands/folders.ts
|
|
14389
14459
|
async function runFoldersList(cfg) {
|
|
@@ -14393,6 +14463,235 @@ async function runFoldersList(cfg) {
|
|
|
14393
14463
|
return;
|
|
14394
14464
|
}
|
|
14395
14465
|
renderFolderTree(cfg.out, tree);
|
|
14466
|
+
const entities = await listEntities(cfg.client, cfg.entityType, {
|
|
14467
|
+
signal: cfg.signal
|
|
14468
|
+
});
|
|
14469
|
+
if (tree.length === 0 && entities.length > 0 && entities.every((e) => e.position === null)) {
|
|
14470
|
+
cfg.out.write(
|
|
14471
|
+
`
|
|
14472
|
+
folders are disabled for ${cfg.entityType} on this server (Unleash flag ${FOLDER_FLAGS[cfg.entityType]} is off) \u2014 no member order to show.
|
|
14473
|
+
`
|
|
14474
|
+
);
|
|
14475
|
+
return;
|
|
14476
|
+
}
|
|
14477
|
+
const names = /* @__PURE__ */ new Map();
|
|
14478
|
+
const walk = (n) => {
|
|
14479
|
+
names.set(n.id, n.name);
|
|
14480
|
+
for (const c of n.children ?? []) walk(c);
|
|
14481
|
+
};
|
|
14482
|
+
for (const root of tree) walk(root);
|
|
14483
|
+
for (const [containerId, members] of buildContainerMembers(tree, entities)) {
|
|
14484
|
+
cfg.out.write(`
|
|
14485
|
+
${containerLabel(containerId, names)}:
|
|
14486
|
+
`);
|
|
14487
|
+
if (members.length === 0) {
|
|
14488
|
+
cfg.out.write(" (empty)\n");
|
|
14489
|
+
continue;
|
|
14490
|
+
}
|
|
14491
|
+
const rows = members.map((m) => ({
|
|
14492
|
+
cells: [` ${m.kind}:${m.id}`, m.name, String(m.position ?? "-")]
|
|
14493
|
+
}));
|
|
14494
|
+
writeTable9(cfg.out, rows);
|
|
14495
|
+
}
|
|
14496
|
+
}
|
|
14497
|
+
function buildContainerMembers(tree, entities) {
|
|
14498
|
+
const out = /* @__PURE__ */ new Map();
|
|
14499
|
+
const add3 = (containerId, children2) => {
|
|
14500
|
+
out.set(
|
|
14501
|
+
containerId,
|
|
14502
|
+
children2.map((f) => ({
|
|
14503
|
+
kind: "folder",
|
|
14504
|
+
id: f.id,
|
|
14505
|
+
name: f.name,
|
|
14506
|
+
position: f.position ?? null
|
|
14507
|
+
}))
|
|
14508
|
+
);
|
|
14509
|
+
for (const f of children2) add3(f.id, f.children ?? []);
|
|
14510
|
+
};
|
|
14511
|
+
add3(null, tree);
|
|
14512
|
+
for (const e of entities) {
|
|
14513
|
+
const bucket = out.get(e.folder_id);
|
|
14514
|
+
if (bucket === void 0) continue;
|
|
14515
|
+
bucket.push({ kind: "entity", id: e.id, name: e.name, position: e.position });
|
|
14516
|
+
}
|
|
14517
|
+
const rank = (m) => m.position ?? Number.MAX_SAFE_INTEGER;
|
|
14518
|
+
for (const members of out.values()) {
|
|
14519
|
+
members.sort(
|
|
14520
|
+
(a, b) => rank(a) - rank(b) || (a.kind === b.kind ? 0 : a.kind === "folder" ? -1 : 1) || a.id - b.id
|
|
14521
|
+
);
|
|
14522
|
+
}
|
|
14523
|
+
return out;
|
|
14524
|
+
}
|
|
14525
|
+
var ITEM_RE = /^(folder|entity):([1-9][0-9]*)$/;
|
|
14526
|
+
function parseReorderItems(raw) {
|
|
14527
|
+
const tokens = raw.split(",").map((t) => t.trim());
|
|
14528
|
+
return tokens.map((t) => {
|
|
14529
|
+
const m = ITEM_RE.exec(t);
|
|
14530
|
+
if (m === null) {
|
|
14531
|
+
throw new Error(`invalid --items entry "${t}" (expected: folder:<id> | entity:<id>)`);
|
|
14532
|
+
}
|
|
14533
|
+
return { kind: m[1], id: Number(m[2]) };
|
|
14534
|
+
});
|
|
14535
|
+
}
|
|
14536
|
+
function parseContainerOpt(raw, flag) {
|
|
14537
|
+
if (raw === "root") return null;
|
|
14538
|
+
if (!/^[1-9][0-9]*$/.test(raw)) {
|
|
14539
|
+
throw new Error(`invalid ${flag} "${raw}" (expected: root | positive folder id)`);
|
|
14540
|
+
}
|
|
14541
|
+
return Number(raw);
|
|
14542
|
+
}
|
|
14543
|
+
function moveItem(items, item, toIndex) {
|
|
14544
|
+
const from = items.findIndex((i) => i.kind === item.kind && i.id === item.id);
|
|
14545
|
+
if (from === -1) {
|
|
14546
|
+
throw new Error(`${item.kind}:${item.id} is not in this container`);
|
|
14547
|
+
}
|
|
14548
|
+
const rest = items.filter((_, i) => i !== from).map((i) => ({ kind: i.kind, id: i.id }));
|
|
14549
|
+
rest.splice(Math.min(toIndex, rest.length), 0, { kind: item.kind, id: item.id });
|
|
14550
|
+
return rest;
|
|
14551
|
+
}
|
|
14552
|
+
function containerLabel(containerId, names) {
|
|
14553
|
+
if (containerId === null) return "root";
|
|
14554
|
+
const name = names.get(containerId);
|
|
14555
|
+
return name === void 0 ? `folder ${containerId}` : `folder ${containerId} (${name})`;
|
|
14556
|
+
}
|
|
14557
|
+
var itemKey = (i) => `${i.kind}:${i.id}`;
|
|
14558
|
+
function reorderConfirmId(entityType, containerArg, request) {
|
|
14559
|
+
const sha = createHash3("sha256").update(request).digest("hex").slice(0, 12);
|
|
14560
|
+
return `${entityType}:${containerArg}:${sha}`;
|
|
14561
|
+
}
|
|
14562
|
+
function flagOffError(entityType, err) {
|
|
14563
|
+
return new Error(
|
|
14564
|
+
`folders are not enabled for ${entityType} on this server (Unleash flag ${FOLDER_FLAGS[entityType]} is off?): ${err.message}`
|
|
14565
|
+
);
|
|
14566
|
+
}
|
|
14567
|
+
async function runFoldersReorder(cfg) {
|
|
14568
|
+
const hasItems = cfg.items !== void 0 && cfg.items.trim() !== "";
|
|
14569
|
+
const hasMove = cfg.move !== void 0 && cfg.move.trim() !== "";
|
|
14570
|
+
if (hasItems && hasMove) throw new Error("--items and --move are mutually exclusive");
|
|
14571
|
+
if (!hasItems && !hasMove) throw new Error("one of --items or --move is required");
|
|
14572
|
+
let toIndex = 0;
|
|
14573
|
+
if (hasMove) {
|
|
14574
|
+
if (cfg.toIndex === void 0 || !/^[0-9]+$/.test(cfg.toIndex)) {
|
|
14575
|
+
throw new Error("--move needs --to-index <n> (non-negative integer)");
|
|
14576
|
+
}
|
|
14577
|
+
toIndex = Number(cfg.toIndex);
|
|
14578
|
+
}
|
|
14579
|
+
const given = parseReorderItems(hasItems ? cfg.items ?? "" : cfg.move ?? "");
|
|
14580
|
+
if (hasMove && given.length !== 1) throw new Error("--move takes a single kind:id");
|
|
14581
|
+
const containerArg = cfg.container === null ? "root" : String(cfg.container);
|
|
14582
|
+
const firstGiven = given[0];
|
|
14583
|
+
const request = hasMove && firstGiven !== void 0 ? `move:${itemKey(firstGiven)}@${toIndex}` : given.map(itemKey).join(",");
|
|
14584
|
+
const action = {
|
|
14585
|
+
resource: "folder-container",
|
|
14586
|
+
id: reorderConfirmId(cfg.entityType, containerArg, request),
|
|
14587
|
+
verb: "reorder"
|
|
14588
|
+
};
|
|
14589
|
+
await preflightConfirmToken(action, cfg.token);
|
|
14590
|
+
const opts = { signal: cfg.signal };
|
|
14591
|
+
const [tree, entities] = await Promise.all([
|
|
14592
|
+
list9(cfg.client, cfg.entityType, opts),
|
|
14593
|
+
listEntities(cfg.client, cfg.entityType, opts)
|
|
14594
|
+
]);
|
|
14595
|
+
const current = buildContainerMembers(tree, entities).get(cfg.container) ?? [];
|
|
14596
|
+
const items = hasMove && firstGiven !== void 0 ? moveItem(current, firstGiven, toIndex) : given;
|
|
14597
|
+
const payload = {
|
|
14598
|
+
entity_type: cfg.entityType,
|
|
14599
|
+
container_id: cfg.container,
|
|
14600
|
+
items
|
|
14601
|
+
};
|
|
14602
|
+
const flags = hasItems ? `--items ${cfg.items?.replace(/\s+/g, "") ?? ""}` : `--move ${cfg.move?.trim() ?? ""} --to-index ${toIndex}`;
|
|
14603
|
+
await twoStepConfirm({
|
|
14604
|
+
out: cfg.out,
|
|
14605
|
+
action,
|
|
14606
|
+
token: cfg.token,
|
|
14607
|
+
server: cfg.serverFlag,
|
|
14608
|
+
confirmCmd: `dr folders reorder --entity-type ${cfg.entityType} --container ${containerArg} ${flags} --server ${cfg.serverFlag}`,
|
|
14609
|
+
describe: async () => {
|
|
14610
|
+
const byKey2 = new Map(current.map((m, i) => [itemKey(m), { m, i }]));
|
|
14611
|
+
const lines = [`container: ${containerArg} (${cfg.entityType})`, "current order:"];
|
|
14612
|
+
if (current.length === 0) lines.push(" (empty)");
|
|
14613
|
+
current.forEach((m, i) => lines.push(` ${i} ${itemKey(m)} ${m.name}`));
|
|
14614
|
+
lines.push("new order:");
|
|
14615
|
+
items.forEach((it, i) => {
|
|
14616
|
+
const was = byKey2.get(itemKey(it));
|
|
14617
|
+
const note = was === void 0 ? "not in container" : was.i === i ? "" : `was ${was.i}`;
|
|
14618
|
+
const name = was?.m.name ?? "?";
|
|
14619
|
+
lines.push(` ${i} ${itemKey(it)} ${name}${note === "" ? "" : ` (${note})`}`);
|
|
14620
|
+
});
|
|
14621
|
+
const sent = new Set(items.map(itemKey));
|
|
14622
|
+
const missing = current.filter((m) => !sent.has(itemKey(m))).map(itemKey);
|
|
14623
|
+
const foreign = items.filter((it) => !byKey2.has(itemKey(it))).map(itemKey);
|
|
14624
|
+
if (missing.length > 0) lines.push(`missing from payload: ${missing.join(", ")}`);
|
|
14625
|
+
if (foreign.length > 0) lines.push(`not in container: ${foreign.join(", ")}`);
|
|
14626
|
+
lines.push(`payload: ${JSON.stringify(payload)}`);
|
|
14627
|
+
return lines;
|
|
14628
|
+
},
|
|
14629
|
+
execute: async () => {
|
|
14630
|
+
try {
|
|
14631
|
+
await reorder(cfg.client, payload, opts);
|
|
14632
|
+
} catch (err) {
|
|
14633
|
+
if (isNotFoundError(err) && err instanceof Error) throw flagOffError(cfg.entityType, err);
|
|
14634
|
+
throw err;
|
|
14635
|
+
}
|
|
14636
|
+
const where = cfg.container === null ? "root" : `folder ${cfg.container}`;
|
|
14637
|
+
cfg.out.write(`Reordered ${items.length} items in ${where}.
|
|
14638
|
+
`);
|
|
14639
|
+
}
|
|
14640
|
+
});
|
|
14641
|
+
}
|
|
14642
|
+
async function runFoldersMove(cfg) {
|
|
14643
|
+
const parentArg = cfg.parentId === null ? "root" : String(cfg.parentId);
|
|
14644
|
+
const action = {
|
|
14645
|
+
resource: "folder",
|
|
14646
|
+
id: `${cfg.entityType}:${cfg.folderId}->${parentArg}`,
|
|
14647
|
+
verb: "move"
|
|
14648
|
+
};
|
|
14649
|
+
await preflightConfirmToken(action, cfg.token);
|
|
14650
|
+
const opts = { signal: cfg.signal };
|
|
14651
|
+
await twoStepConfirm({
|
|
14652
|
+
out: cfg.out,
|
|
14653
|
+
action,
|
|
14654
|
+
token: cfg.token,
|
|
14655
|
+
server: cfg.serverFlag,
|
|
14656
|
+
confirmCmd: `dr folders move ${cfg.folderId} --parent ${parentArg} --entity-type ${cfg.entityType} --server ${cfg.serverFlag}`,
|
|
14657
|
+
describe: async () => {
|
|
14658
|
+
const tree = await list9(cfg.client, cfg.entityType, opts);
|
|
14659
|
+
const byId = /* @__PURE__ */ new Map();
|
|
14660
|
+
const walk = (n) => {
|
|
14661
|
+
byId.set(n.id, n);
|
|
14662
|
+
for (const c of n.children ?? []) walk(c);
|
|
14663
|
+
};
|
|
14664
|
+
for (const root of tree) walk(root);
|
|
14665
|
+
const names = new Map([...byId].map(([id, n]) => [id, n.name]));
|
|
14666
|
+
const folder = byId.get(cfg.folderId);
|
|
14667
|
+
const to = containerLabel(cfg.parentId, names);
|
|
14668
|
+
if (folder === void 0) {
|
|
14669
|
+
return [
|
|
14670
|
+
`folder ${cfg.folderId}: not in the ${cfg.entityType} tree (wrong --entity-type, flag off, or no access) -> ${to}`
|
|
14671
|
+
];
|
|
14672
|
+
}
|
|
14673
|
+
return [
|
|
14674
|
+
`folder ${folder.id} (${folder.name}): ${containerLabel(folder.parent_id, names)} -> ${to}`,
|
|
14675
|
+
"the folder is appended at the end of the destination (Admin / Data Manager only)"
|
|
14676
|
+
];
|
|
14677
|
+
},
|
|
14678
|
+
execute: async () => {
|
|
14679
|
+
try {
|
|
14680
|
+
await move(cfg.client, cfg.folderId, cfg.parentId, opts);
|
|
14681
|
+
} catch (err) {
|
|
14682
|
+
if (isNotFoundError(err) && err instanceof Error) {
|
|
14683
|
+
throw new Error(
|
|
14684
|
+
`folder ${cfg.folderId} not found, or folders are disabled (${FOLDER_FLAGS[cfg.entityType]}): ${err.message}`
|
|
14685
|
+
);
|
|
14686
|
+
}
|
|
14687
|
+
throw err;
|
|
14688
|
+
}
|
|
14689
|
+
cfg.out.write(
|
|
14690
|
+
`Moved folder ${cfg.folderId} to ${parentArg === "root" ? "root" : `folder ${parentArg}`}.
|
|
14691
|
+
`
|
|
14692
|
+
);
|
|
14693
|
+
}
|
|
14694
|
+
});
|
|
14396
14695
|
}
|
|
14397
14696
|
async function runFoldersCreate(cfg) {
|
|
14398
14697
|
if (cfg.name === "") {
|
|
@@ -14525,6 +14824,70 @@ function registerFoldersCommand(root) {
|
|
|
14525
14824
|
);
|
|
14526
14825
|
addServerFlag(createCmd);
|
|
14527
14826
|
addAccountFlag(createCmd);
|
|
14827
|
+
const reorderCmd = cmd.command("reorder").description(
|
|
14828
|
+
"Set the interleaved order of one container (two-step confirm). The order is per-user. --items sends exactly what is given; --move splices the current members and sends them all."
|
|
14829
|
+
).option(
|
|
14830
|
+
"--entity-type <t>",
|
|
14831
|
+
`folder entity type: ${FOLDER_ENTITY_TYPES.join(" | ")}`,
|
|
14832
|
+
"dashboard"
|
|
14833
|
+
).requiredOption("--container <c>", "root | folder id").option("--items <list>", "full ordered list, e.g. folder:12,entity:87 (sent verbatim)").option("--move <kind:id>", "item to move, e.g. entity:87").option("--to-index <n>", "0-based target index for --move (past the end = last)").option("--confirm-token <token>", "Token from a prior plan run \u2014 required to actually reorder").action(
|
|
14834
|
+
async (opts) => {
|
|
14835
|
+
const entityType = parseEntityTypeOpt(opts.entityType ?? "dashboard");
|
|
14836
|
+
const container = parseContainerOpt(opts.container, "--container");
|
|
14837
|
+
const serverKey = resolveServer(opts.server);
|
|
14838
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
14839
|
+
try {
|
|
14840
|
+
await runFoldersReorder({
|
|
14841
|
+
client: setup.client,
|
|
14842
|
+
entityType,
|
|
14843
|
+
container,
|
|
14844
|
+
items: opts.items,
|
|
14845
|
+
move: opts.move,
|
|
14846
|
+
toIndex: opts.toIndex,
|
|
14847
|
+
token: opts.confirmToken,
|
|
14848
|
+
serverFlag: serverKey,
|
|
14849
|
+
signal: setup.signal,
|
|
14850
|
+
out: process.stdout
|
|
14851
|
+
});
|
|
14852
|
+
} finally {
|
|
14853
|
+
setup.cleanup();
|
|
14854
|
+
}
|
|
14855
|
+
}
|
|
14856
|
+
);
|
|
14857
|
+
addServerFlag(reorderCmd);
|
|
14858
|
+
addAccountFlag(reorderCmd);
|
|
14859
|
+
const moveCmd = cmd.command("move").description(
|
|
14860
|
+
"Move a folder under another folder or to root (two-step confirm). Appends at the end of the destination; Admin / Data Manager only."
|
|
14861
|
+
).argument("<folderId>", "folder id (positive integer)").requiredOption("--parent <p>", "root | destination folder id").option(
|
|
14862
|
+
"--entity-type <t>",
|
|
14863
|
+
`tree to describe the move from: ${FOLDER_ENTITY_TYPES.join(" | ")}`,
|
|
14864
|
+
"dashboard"
|
|
14865
|
+
).option("--confirm-token <token>", "Token from a prior plan run \u2014 required to actually move").action(
|
|
14866
|
+
async (idStr, opts) => {
|
|
14867
|
+
const folderId = parseContainerOpt(idStr, "<folderId>");
|
|
14868
|
+
if (folderId === null) throw new Error('invalid <folderId> "root"');
|
|
14869
|
+
const parentId = parseContainerOpt(opts.parent, "--parent");
|
|
14870
|
+
const entityType = parseEntityTypeOpt(opts.entityType ?? "dashboard");
|
|
14871
|
+
const serverKey = resolveServer(opts.server);
|
|
14872
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
14873
|
+
try {
|
|
14874
|
+
await runFoldersMove({
|
|
14875
|
+
client: setup.client,
|
|
14876
|
+
entityType,
|
|
14877
|
+
folderId,
|
|
14878
|
+
parentId,
|
|
14879
|
+
token: opts.confirmToken,
|
|
14880
|
+
serverFlag: serverKey,
|
|
14881
|
+
signal: setup.signal,
|
|
14882
|
+
out: process.stdout
|
|
14883
|
+
});
|
|
14884
|
+
} finally {
|
|
14885
|
+
setup.cleanup();
|
|
14886
|
+
}
|
|
14887
|
+
}
|
|
14888
|
+
);
|
|
14889
|
+
addServerFlag(moveCmd);
|
|
14890
|
+
addAccountFlag(moveCmd);
|
|
14528
14891
|
}
|
|
14529
14892
|
|
|
14530
14893
|
// src/api/dynamicRanges.ts
|
|
@@ -17040,7 +17403,7 @@ async function update3(client, id, body, options) {
|
|
|
17040
17403
|
async function deleteById6(client, id, options) {
|
|
17041
17404
|
await client.deleteJSON(`/api/templates/lutdocuments/${id}`, options);
|
|
17042
17405
|
}
|
|
17043
|
-
async function
|
|
17406
|
+
async function reorder2(client, body, options) {
|
|
17044
17407
|
await client.postJSON("/api/templates/lutdocuments/update_orders", body, options);
|
|
17045
17408
|
}
|
|
17046
17409
|
var LUT_SAVE_POLL_INTERVAL_MS = 2e3;
|
|
@@ -18170,7 +18533,7 @@ async function runLutReorder(cfg) {
|
|
|
18170
18533
|
body = await readJsonPayload(payloadFile);
|
|
18171
18534
|
count = Array.isArray(body["objects"]) ? body["objects"].length : 0;
|
|
18172
18535
|
}
|
|
18173
|
-
await
|
|
18536
|
+
await reorder2(cfg.client, body, { signal: cfg.signal });
|
|
18174
18537
|
cfg.out.write(`Reordered ${count} lookup${plural(count)}.
|
|
18175
18538
|
`);
|
|
18176
18539
|
}
|
|
@@ -20136,7 +20499,7 @@ function parseDimensionFilters(raw) {
|
|
|
20136
20499
|
}
|
|
20137
20500
|
|
|
20138
20501
|
// src/commands/metricsCreate.ts
|
|
20139
|
-
import { createHash as
|
|
20502
|
+
import { createHash as createHash4 } from "crypto";
|
|
20140
20503
|
|
|
20141
20504
|
// src/api/metricsMonolith.ts
|
|
20142
20505
|
async function create9(client, body, options) {
|
|
@@ -23588,7 +23951,7 @@ async function runMetricsDelete(cfg) {
|
|
|
23588
23951
|
});
|
|
23589
23952
|
}
|
|
23590
23953
|
function shortHash(input) {
|
|
23591
|
-
return
|
|
23954
|
+
return createHash4("sha256").update(input).digest("hex").slice(0, 12);
|
|
23592
23955
|
}
|
|
23593
23956
|
function buildMetricsBatchDeleteAction(ids) {
|
|
23594
23957
|
const sorted = [...new Set(ids)].sort((a, b) => a - b);
|
|
@@ -25401,7 +25764,7 @@ async function getOrgReportingCurrencies(client, organizationId, options) {
|
|
|
25401
25764
|
}
|
|
25402
25765
|
|
|
25403
25766
|
// src/commands/fxConfig.ts
|
|
25404
|
-
import { createHash as
|
|
25767
|
+
import { createHash as createHash5 } from "crypto";
|
|
25405
25768
|
|
|
25406
25769
|
// src/api/widgets.ts
|
|
25407
25770
|
async function list18(client, dashboardId, options) {
|
|
@@ -25964,7 +26327,7 @@ async function runFxSetPosting(cfg) {
|
|
|
25964
26327
|
verb: "set-fx"
|
|
25965
26328
|
} : {
|
|
25966
26329
|
resource: "field-batch",
|
|
25967
|
-
id:
|
|
26330
|
+
id: createHash5("sha256").update(
|
|
25968
26331
|
changes.map((c) => `${c.field.id}:posting`).slice().sort().join("|")
|
|
25969
26332
|
).digest("hex").slice(0, 12),
|
|
25970
26333
|
verb: "set-fx"
|
|
@@ -26418,7 +26781,7 @@ function buildSetReportingAction(changes) {
|
|
|
26418
26781
|
};
|
|
26419
26782
|
}
|
|
26420
26783
|
const canonical = changes.map((c) => `${c.field.id}:reporting:${c.code}:${c.symbol ?? ""}`).slice().sort().join("|");
|
|
26421
|
-
const sha =
|
|
26784
|
+
const sha = createHash5("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
26422
26785
|
return { resource: "field-batch", id: sha, verb: "set-fx" };
|
|
26423
26786
|
}
|
|
26424
26787
|
function renderReportingOutcome(out, results, skipped, notFxTables, asJson) {
|
|
@@ -27398,7 +27761,7 @@ function registerFxCommand(root) {
|
|
|
27398
27761
|
}
|
|
27399
27762
|
|
|
27400
27763
|
// src/commands/errorEnrichments.ts
|
|
27401
|
-
import { createHash as
|
|
27764
|
+
import { createHash as createHash6 } from "crypto";
|
|
27402
27765
|
import { promises as fs13 } from "fs";
|
|
27403
27766
|
import * as path4 from "path";
|
|
27404
27767
|
|
|
@@ -27938,7 +28301,7 @@ async function runEnrichmentsExport(cfg) {
|
|
|
27938
28301
|
requireSweepSuccess(results);
|
|
27939
28302
|
}
|
|
27940
28303
|
function shortHash2(input) {
|
|
27941
|
-
return
|
|
28304
|
+
return createHash6("sha256").update(input).digest("hex").slice(0, 12);
|
|
27942
28305
|
}
|
|
27943
28306
|
function canonicalBatch(parts) {
|
|
27944
28307
|
return stableJson(parts);
|
|
@@ -28733,7 +29096,7 @@ function registerErrorEnrichmentsCommand(root) {
|
|
|
28733
29096
|
}
|
|
28734
29097
|
|
|
28735
29098
|
// src/commands/skill.ts
|
|
28736
|
-
import { createHash as
|
|
29099
|
+
import { createHash as createHash7 } from "crypto";
|
|
28737
29100
|
import { readFile as readFile3, mkdir as mkdir2, readdir as readdir2, rm as rm2, rmdir, stat, writeFile as writeFile3 } from "fs/promises";
|
|
28738
29101
|
import { dirname as dirname6, join as join8, resolve as resolve2 } from "path";
|
|
28739
29102
|
|
|
@@ -28895,7 +29258,7 @@ async function runSkillPrint(cfg) {
|
|
|
28895
29258
|
throw new Error(`skill "${skill.name}" has no file "${relPath}" \u2014 available: ${available}`);
|
|
28896
29259
|
}
|
|
28897
29260
|
if (cfg.hash) {
|
|
28898
|
-
const hex =
|
|
29261
|
+
const hex = createHash7("sha256").update(hit.bytes).digest("hex");
|
|
28899
29262
|
cfg.out.write(hex + "\n");
|
|
28900
29263
|
return;
|
|
28901
29264
|
}
|
|
@@ -32760,7 +33123,7 @@ async function itResync(client, id, reqOptions) {
|
|
|
32760
33123
|
// src/commands/filebox.ts
|
|
32761
33124
|
import { promises as fsPromises, existsSync as existsSync2 } from "fs";
|
|
32762
33125
|
import * as nodePath from "path";
|
|
32763
|
-
import { createHash as
|
|
33126
|
+
import { createHash as createHash8 } from "crypto";
|
|
32764
33127
|
|
|
32765
33128
|
// src/commands/filebox_admin.ts
|
|
32766
33129
|
import { promises as fs15 } from "fs";
|
|
@@ -35389,7 +35752,7 @@ function buildIncludeBatchAction(docId, include, targets) {
|
|
|
35389
35752
|
const canonical = `${docId}|${include ? "on" : "off"}|${targets.map((v) => v.id).slice().sort((a, b) => a - b).join(",")}`;
|
|
35390
35753
|
return {
|
|
35391
35754
|
resource: "filebox-version-batch",
|
|
35392
|
-
id:
|
|
35755
|
+
id: createHash8("sha256").update(canonical).digest("hex").slice(0, 12),
|
|
35393
35756
|
verb: includeStateWords(include).verb
|
|
35394
35757
|
};
|
|
35395
35758
|
}
|
|
@@ -42232,7 +42595,7 @@ function registerWidgetsCommand(root) {
|
|
|
42232
42595
|
}
|
|
42233
42596
|
|
|
42234
42597
|
// src/commands/reconciliation.ts
|
|
42235
|
-
import { createHash as
|
|
42598
|
+
import { createHash as createHash9 } from "crypto";
|
|
42236
42599
|
import { promises as fs18 } from "fs";
|
|
42237
42600
|
|
|
42238
42601
|
// src/api/reconciliation.ts
|
|
@@ -43859,7 +44222,7 @@ function chunkByJsonBytes(rows, maxBytes) {
|
|
|
43859
44222
|
return chunks;
|
|
43860
44223
|
}
|
|
43861
44224
|
function settingsActionId(settings) {
|
|
43862
|
-
return
|
|
44225
|
+
return createHash9("sha256").update(JSON.stringify(settings)).digest("hex").slice(0, 12);
|
|
43863
44226
|
}
|
|
43864
44227
|
async function runReconciliationAccountsUpdateSettings(cfg) {
|
|
43865
44228
|
const action = {
|
|
@@ -44237,7 +44600,7 @@ async function runReconciliationAccountsSetAssignee(cfg) {
|
|
|
44237
44600
|
var ASSIGNEE_BATCH_CHUNK_SIZE = 100;
|
|
44238
44601
|
function assigneeBatchActionId(date, rows) {
|
|
44239
44602
|
const canonical = rows.map((r) => [r.accountId, r.type, r.userId, r.userName, r.userEmail]);
|
|
44240
|
-
return
|
|
44603
|
+
return createHash9("sha256").update(JSON.stringify([date, canonical])).digest("hex").slice(0, 12);
|
|
44241
44604
|
}
|
|
44242
44605
|
async function runReconciliationAccountsSetAssigneeBatch(cfg) {
|
|
44243
44606
|
const action = {
|
|
@@ -44391,7 +44754,7 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
|
|
|
44391
44754
|
}
|
|
44392
44755
|
function clearBatchActionId(date, rows) {
|
|
44393
44756
|
const canonical = rows.map((r) => [r.accountId, r.type]);
|
|
44394
|
-
return
|
|
44757
|
+
return createHash9("sha256").update(JSON.stringify(["clear", date, canonical])).digest("hex").slice(0, 12);
|
|
44395
44758
|
}
|
|
44396
44759
|
async function runReconciliationAccountsClearAssigneeBatch(cfg) {
|
|
44397
44760
|
const action = {
|
|
@@ -46281,7 +46644,7 @@ function renderForError(v) {
|
|
|
46281
46644
|
}
|
|
46282
46645
|
|
|
46283
46646
|
// src/commands/mecWorkflow.ts
|
|
46284
|
-
import { createHash as
|
|
46647
|
+
import { createHash as createHash10 } from "crypto";
|
|
46285
46648
|
|
|
46286
46649
|
// src/api/workflow.ts
|
|
46287
46650
|
function buildQueryString(params) {
|
|
@@ -48332,7 +48695,7 @@ async function runMecAccountsTaskStatuses(cfg) {
|
|
|
48332
48695
|
renderMecAccountsTasksOrStatuses(cfg.out, resp, "statuses");
|
|
48333
48696
|
}
|
|
48334
48697
|
function shortHash3(input) {
|
|
48335
|
-
return
|
|
48698
|
+
return createHash10("sha256").update(input).digest("hex").slice(0, 12);
|
|
48336
48699
|
}
|
|
48337
48700
|
async function runMecWorkflowCreate(cfg) {
|
|
48338
48701
|
const action = {
|
|
@@ -50716,7 +51079,7 @@ function wireMec(program) {
|
|
|
50716
51079
|
}
|
|
50717
51080
|
|
|
50718
51081
|
// src/commands/workflow.ts
|
|
50719
|
-
import { createHash as
|
|
51082
|
+
import { createHash as createHash11 } from "crypto";
|
|
50720
51083
|
function dash6(s) {
|
|
50721
51084
|
if (s === null || s === void 0) return "-";
|
|
50722
51085
|
if (typeof s === "number") return String(s);
|
|
@@ -51185,7 +51548,7 @@ function renderGroupList(out, rows) {
|
|
|
51185
51548
|
`);
|
|
51186
51549
|
}
|
|
51187
51550
|
function shortHash4(input) {
|
|
51188
|
-
return
|
|
51551
|
+
return createHash11("sha256").update(input).digest("hex").slice(0, 12);
|
|
51189
51552
|
}
|
|
51190
51553
|
async function runWorkflowCreate(cfg) {
|
|
51191
51554
|
const action = {
|
|
@@ -53344,7 +53707,7 @@ function wireWorkflow(root) {
|
|
|
53344
53707
|
}
|
|
53345
53708
|
|
|
53346
53709
|
// src/commands/cash.ts
|
|
53347
|
-
import { createHash as
|
|
53710
|
+
import { createHash as createHash12 } from "crypto";
|
|
53348
53711
|
import { promises as fs25 } from "fs";
|
|
53349
53712
|
|
|
53350
53713
|
// src/api/cash.ts
|
|
@@ -54435,7 +54798,7 @@ async function runCashRulesDeleteAll(cfg) {
|
|
|
54435
54798
|
return;
|
|
54436
54799
|
}
|
|
54437
54800
|
const ids = targets.map((r) => r.id).sort((a, b) => a - b);
|
|
54438
|
-
const sha =
|
|
54801
|
+
const sha = createHash12("sha256").update(ids.join(",")).digest("hex").slice(0, 12);
|
|
54439
54802
|
const action = {
|
|
54440
54803
|
resource: "cash-rules-batch",
|
|
54441
54804
|
id: `${templateId}:${sha}`,
|
package/dist/scopes-map.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"cliVersion": "1.6.
|
|
3
|
+
"cliVersion": "1.6.102",
|
|
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": {
|
|
@@ -635,6 +635,22 @@
|
|
|
635
635
|
}
|
|
636
636
|
]
|
|
637
637
|
},
|
|
638
|
+
"folders reorder": {
|
|
639
|
+
"requires": [
|
|
640
|
+
{
|
|
641
|
+
"resource": "dashboards",
|
|
642
|
+
"action": "write"
|
|
643
|
+
}
|
|
644
|
+
]
|
|
645
|
+
},
|
|
646
|
+
"folders move": {
|
|
647
|
+
"requires": [
|
|
648
|
+
{
|
|
649
|
+
"resource": "dashboards",
|
|
650
|
+
"action": "write"
|
|
651
|
+
}
|
|
652
|
+
]
|
|
653
|
+
},
|
|
638
654
|
"dynamic-ranges list": {
|
|
639
655
|
"requires": [
|
|
640
656
|
{
|
|
@@ -244,7 +244,10 @@ area's gotchas.
|
|
|
244
244
|
subcommand.
|
|
245
245
|
|
|
246
246
|
**Dashboards, widgets, storyboards, reports, extables** → [reference/dashboards-widgets.md](reference/dashboards-widgets.md)
|
|
247
|
-
- `dashboards list|get|create|delete|prepare|set-date-picker`
|
|
247
|
+
- `dashboards list|get|create|delete|prepare|set-date-picker|set-folder`
|
|
248
|
+
- `folders list|create|reorder|move` — dashboard / story picker folders;
|
|
249
|
+
`reorder` and `move` are two-step confirm. Reorder is per-user and takes the
|
|
250
|
+
container's full member set (`folders list` shows it).
|
|
248
251
|
- `dashboards push <id> --to <orgs> [--override]` — copy a dashboard into other
|
|
249
252
|
organizations (two-step confirm). Destinations must come from
|
|
250
253
|
`organizations push-targets`.
|
|
@@ -34,6 +34,29 @@ dashboard is added, with `' Copy'` appended if the name is taken.
|
|
|
34
34
|
The push is queued and returns no job id — results arrive as in-app
|
|
35
35
|
notifications. Confirm with `dashboards list --account <destination>`.
|
|
36
36
|
|
|
37
|
+
### Folders (picker tree)
|
|
38
|
+
|
|
39
|
+
Gated per entity type by Unleash `dashboards__folders` / `storytelling__folders`.
|
|
40
|
+
Flag off: `folders list` shows no tree and says the flag is off; every write 404s.
|
|
41
|
+
|
|
42
|
+
- `folders list [--entity-type dashboard|story]` — the tree, then each
|
|
43
|
+
container's members in picker order as `kind:id name position` (`root` first).
|
|
44
|
+
This is the input for a reorder. `--json` is the raw tree only.
|
|
45
|
+
- `folders create --name <n> [--parent <id>]`.
|
|
46
|
+
- `folders reorder --container <root|folderId> (--items folder:12,entity:87,… | --move <kind:id> --to-index <n>)`
|
|
47
|
+
— two-step confirm. The order is **per user**, and the server takes only the
|
|
48
|
+
FULL set of the container's members visible to the caller, so anything partial,
|
|
49
|
+
foreign or duplicated is a 400. `--move` fetches the current members, splices
|
|
50
|
+
the item in (an index past the end lands it last) and sends them all.
|
|
51
|
+
`--items` is sent exactly as given, which is how you reproduce a 400; the plan
|
|
52
|
+
lists what is missing from the payload and what is not in the container. The
|
|
53
|
+
400 body is printed verbatim.
|
|
54
|
+
- `folders move <folderId> --parent <root|folderId>` — two-step confirm.
|
|
55
|
+
Re-parents the folder and appends it at the destination's end. Admin / Data
|
|
56
|
+
Manager only.
|
|
57
|
+
- `dashboards set-folder <id> (--folder <fid> | --root)` moves a dashboard
|
|
58
|
+
(`PUT /api/dashboards/<id>` `{folder_id}`). It is appended at the end.
|
|
59
|
+
|
|
37
60
|
## Widgets
|
|
38
61
|
|
|
39
62
|
- `widgets list`, `widgets get <id>`, `widgets data <id>` (alias `widget`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datarails-ai/dr-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.102",
|
|
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.10.
|
|
20
|
+
"@datarails-ai/dr-appspace-cli": "0.10.22",
|
|
21
21
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
22
22
|
"@napi-rs/keyring": "^1.3.0",
|
|
23
23
|
"commander": "^12.1.0",
|