@datarails-ai/dr-cli 1.6.180 → 1.6.181
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 +796 -118
- package/dist/scopes-map.json +33 -1
- package/dist/skills/dr-cli/SKILL.md +1 -0
- package/dist/skills/dr-cli/reference/calcmodels.md +54 -0
- package/dist/skills/dr-cli/reference/safety.md +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -435,6 +435,7 @@ dr skill print <name> # print an embedded skill's SKILL.md
|
|
|
435
435
|
| 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`) |
|
|
436
436
|
| Functions | `functions list/get/create/delete` |
|
|
437
437
|
| Dynamic ranges | `dynamic-ranges list/get/data/create/delete` |
|
|
438
|
+
| Calc models | `calcmodels list/get/create/update/set-flow/recalculate/delete`; AGENT builder: `calcmodels agent ask/preview/save/approve` (`save` makes a draft; `approve` is two-step, Tier A) |
|
|
438
439
|
| Lookups (LUTs) | `lut list/create/update/delete/reorder/unmapped` (alias `lookups`) |
|
|
439
440
|
| Documents | `documents support-api set <id> --on/--off`, `documents calculate <id> --year/--quarter/--month/--day/--week ... [--backward] [--no-roll]` |
|
|
440
441
|
| Semantic tables | `semantic-tables list/get` |
|
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.181",
|
|
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.
|
|
43
|
+
"@datarails-ai/dr-appspace-cli": "0.12.58",
|
|
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-11-
|
|
72
|
+
var BUILD_TIME = "2026-11-01T14:08:05.005Z";
|
|
73
73
|
function formatBuildTime(iso) {
|
|
74
74
|
if (iso === "") return "";
|
|
75
75
|
const d = new Date(iso);
|
|
@@ -1410,11 +1410,11 @@ function exchangeForSession(store, serverKey, email, session, host, opts) {
|
|
|
1410
1410
|
if (err.throttled && cachedIsUsable) return session;
|
|
1411
1411
|
if (err.transient) {
|
|
1412
1412
|
if (cachedIsUsable) return session;
|
|
1413
|
-
const
|
|
1414
|
-
if (
|
|
1413
|
+
const delay2 = API_KEY_EXCHANGE_RETRY_DELAYS_MS[attempt];
|
|
1414
|
+
if (delay2 !== void 0) {
|
|
1415
1415
|
attempt += 1;
|
|
1416
1416
|
try {
|
|
1417
|
-
await sleep(
|
|
1417
|
+
await sleep(delay2, void 0, { signal });
|
|
1418
1418
|
} catch {
|
|
1419
1419
|
throw new SessionExpiredError(
|
|
1420
1420
|
`${identity2} (api-key exchange ended before its retry)`
|
|
@@ -4438,6 +4438,13 @@ var PROD_GATE_TIER_A = /* @__PURE__ */ new Set([
|
|
|
4438
4438
|
"category|delete",
|
|
4439
4439
|
// calc-models
|
|
4440
4440
|
"calcmodel|delete",
|
|
4441
|
+
// dr calcmodels agent approve — activates an AGENT model's newest version and starts its
|
|
4442
|
+
// first production run, which writes new rows into a table that reports read. Unlike
|
|
4443
|
+
// `set-flow` / `recalculate` (Tier B), which re-run a model already approved, this puts
|
|
4444
|
+
// numbers nobody has run before into production, so a person confirms it. The plan
|
|
4445
|
+
// prints the version and its impact (rows, pool, allocated, coverage); the token binds
|
|
4446
|
+
// that version.
|
|
4447
|
+
"calcmodel|approve",
|
|
4441
4448
|
// filebox
|
|
4442
4449
|
"filebox|archive",
|
|
4443
4450
|
"filebox-data-query|clear",
|
|
@@ -7421,8 +7428,8 @@ async function retryAsync(fn, opts) {
|
|
|
7421
7428
|
const kind = classifyFailure(err);
|
|
7422
7429
|
const retryable = kind === "transient" || kind !== "abort" && opts.alsoRetry?.(err) === true;
|
|
7423
7430
|
if (!retryable || attempt > retries) throw err;
|
|
7424
|
-
const
|
|
7425
|
-
const waitMs = typeof
|
|
7431
|
+
const delay2 = opts.delayMs ?? defaultBackoffMs;
|
|
7432
|
+
const waitMs = typeof delay2 === "function" ? delay2(attempt) : delay2;
|
|
7426
7433
|
opts.onRetry?.(attempt, err, waitMs);
|
|
7427
7434
|
await sleep2(waitMs, opts.signal);
|
|
7428
7435
|
}
|
|
@@ -35556,7 +35563,7 @@ function registerDatamappersCommand(root) {
|
|
|
35556
35563
|
});
|
|
35557
35564
|
addServerFlag(customFnsCmd);
|
|
35558
35565
|
addAccountFlag(customFnsCmd);
|
|
35559
|
-
const
|
|
35566
|
+
const previewCmd2 = dm.command("preview").description(
|
|
35560
35567
|
"Render a mapper config's projected output for a sample input. Posts to `POST /api/templates/preview-data` with the operator-supplied JSON (typically built from a saved mapper config + template_id + document_version + datamapper_id). Output is the server's `{output: {header, projected_config, \u2026}, err}` envelope, emitted as JSON."
|
|
35561
35568
|
).requiredOption("--payload-file <path>", "Path to a JSON body for the preview request").option(
|
|
35562
35569
|
"--json",
|
|
@@ -35576,8 +35583,8 @@ function registerDatamappersCommand(root) {
|
|
|
35576
35583
|
setup.cleanup();
|
|
35577
35584
|
}
|
|
35578
35585
|
});
|
|
35579
|
-
addServerFlag(
|
|
35580
|
-
addAccountFlag(
|
|
35586
|
+
addServerFlag(previewCmd2);
|
|
35587
|
+
addAccountFlag(previewCmd2);
|
|
35581
35588
|
const bindCmd = dm.command("bind").description(
|
|
35582
35589
|
"Update which document IDs a mapper is bound to. The --doc list replaces the mapper's current documents; repeat every filebox you want to keep (dr warns first and names any it drops). Posts to `POST /api/templates/mappers/<id>/binding` with `{selected_document_ids, rescan?}`. The legacy webclient's \"Mapping table with documents\" modal calls this when the operator checks/unchecks docs in the binding picker. Warns first when another mapper of the table on the same ordinal is bound to one of those docs: mappers that share an ordinal are versions of one mapper, so the bind unbinds the other."
|
|
35583
35590
|
).argument("<mapper_id>", "mapper id (positive integer)").option("--doc <id>", "Document id to bind (positive integer; repeatable)", collectInts, []).option(
|
|
@@ -43482,7 +43489,7 @@ function registerFileboxCommand(root) {
|
|
|
43482
43489
|
});
|
|
43483
43490
|
addServerFlag(sheetsCmd);
|
|
43484
43491
|
addAccountFlag(sheetsCmd);
|
|
43485
|
-
const
|
|
43492
|
+
const previewCmd2 = fb.command("preview").description("Preview raw cell data of a filebox version (row 1 is headers)").argument("<version_id>", "version id (positive integer)").option("--sheet <name>", "Sheet name. Defaults to first sheet from /desc.", "").option("--sheet-ind <n>", "Sheet index (0-based). Defaults to 0.", "0").option("--start <n>", "First row to read (1-based)", "1").option("--rows <n>", "Number of rows to return (start..start+rows-1)", "100").option("--data-only", "Return evaluated values only; omit raw formulas", false).option("--json", "Output raw JSON instead of a table", false).action(
|
|
43486
43493
|
async (vidStr, opts) => {
|
|
43487
43494
|
const versionId = parseVersionIdArg2(vidStr);
|
|
43488
43495
|
const sheetInd = parsePositiveIntFlag2(opts.sheetInd, "--sheet-ind", 0);
|
|
@@ -43507,8 +43514,8 @@ function registerFileboxCommand(root) {
|
|
|
43507
43514
|
}
|
|
43508
43515
|
}
|
|
43509
43516
|
);
|
|
43510
|
-
addServerFlag(
|
|
43511
|
-
addAccountFlag(
|
|
43517
|
+
addServerFlag(previewCmd2);
|
|
43518
|
+
addAccountFlag(previewCmd2);
|
|
43512
43519
|
const createCmd = fb.command("create").description(
|
|
43513
43520
|
"Create an empty filebox (no file yet). Resolves the date tag config from /versions/tags. Pass --folder to create a folder filebox instead (no date tag, no version, no data \u2014 used to mirror the org-bootstrap hierarchy of Actuals / COA / FX Rates / Configuration)."
|
|
43514
43521
|
).option("--name <name>", "Filebox display name (required)", "").option("--scenario <s>", `Scenario: ${VALID_SCENARIOS_HELP}`, "Actuals").option(
|
|
@@ -47006,10 +47013,10 @@ function toControlPlaneError(method, path13, err) {
|
|
|
47006
47013
|
}
|
|
47007
47014
|
|
|
47008
47015
|
// src/api/awsConnections.ts
|
|
47009
|
-
async function call(method, path13,
|
|
47016
|
+
async function call(method, path13, send2, what) {
|
|
47010
47017
|
let out;
|
|
47011
47018
|
try {
|
|
47012
|
-
out = await
|
|
47019
|
+
out = await send2();
|
|
47013
47020
|
} catch (err) {
|
|
47014
47021
|
throw toControlPlaneError(method, path13, err);
|
|
47015
47022
|
}
|
|
@@ -48184,10 +48191,10 @@ import { readFileSync as readFileSync3 } from "fs";
|
|
|
48184
48191
|
|
|
48185
48192
|
// src/api/scheduledTasks.ts
|
|
48186
48193
|
var BASE2 = "/scheduled-tasks";
|
|
48187
|
-
async function call2(method, path13,
|
|
48194
|
+
async function call2(method, path13, send2, what) {
|
|
48188
48195
|
let out;
|
|
48189
48196
|
try {
|
|
48190
|
-
out = await
|
|
48197
|
+
out = await send2();
|
|
48191
48198
|
} catch (err) {
|
|
48192
48199
|
throw toControlPlaneError(method, path13, err);
|
|
48193
48200
|
}
|
|
@@ -65487,6 +65494,742 @@ async function remove(client, id, options) {
|
|
|
65487
65494
|
await client.deleteJSON(`/api/templates/${id}/calcmodels`, options);
|
|
65488
65495
|
}
|
|
65489
65496
|
|
|
65497
|
+
// src/commands/calcmodelsAgent.ts
|
|
65498
|
+
import { setTimeout as delay } from "timers/promises";
|
|
65499
|
+
|
|
65500
|
+
// src/types/calcmodelsAgent.ts
|
|
65501
|
+
import { z as z3 } from "zod";
|
|
65502
|
+
var optionalText = z3.string().nullish();
|
|
65503
|
+
var amount = z3.number().nullish();
|
|
65504
|
+
var calcAgentOperationStatusSchema = z3.enum([
|
|
65505
|
+
"PENDING",
|
|
65506
|
+
"RUNNING",
|
|
65507
|
+
"SUCCEEDED",
|
|
65508
|
+
"NO_PROPOSAL",
|
|
65509
|
+
"FAILED"
|
|
65510
|
+
]);
|
|
65511
|
+
var calcAgentGateErrorSchema = z3.object({
|
|
65512
|
+
code: z3.string(),
|
|
65513
|
+
phase: optionalText,
|
|
65514
|
+
segment: optionalText,
|
|
65515
|
+
suggestion: optionalText,
|
|
65516
|
+
detail: optionalText
|
|
65517
|
+
}).passthrough();
|
|
65518
|
+
var calcAgentTurnAcceptedSchema = z3.object({ operation_id: z3.string().uuid(), conversation_id: z3.string().optional() }).passthrough();
|
|
65519
|
+
var coverageSchema = z3.object({
|
|
65520
|
+
pct: amount,
|
|
65521
|
+
pool_total: amount,
|
|
65522
|
+
allocated_total: amount,
|
|
65523
|
+
unallocated_amount: amount,
|
|
65524
|
+
uncovered_group_count: z3.number().nullish()
|
|
65525
|
+
}).passthrough();
|
|
65526
|
+
var balanceCheckSchema = z3.object({ group: z3.string(), net: z3.number(), ok: z3.boolean() }).passthrough();
|
|
65527
|
+
var warningSchema = z3.object({ code: optionalText, message: optionalText }).passthrough();
|
|
65528
|
+
var calcAgentPreviewResultSchema = z3.object({
|
|
65529
|
+
total_count: z3.number().nullish(),
|
|
65530
|
+
coverage: coverageSchema.nullish(),
|
|
65531
|
+
balance_checks: z3.array(balanceCheckSchema).nullish(),
|
|
65532
|
+
warnings: z3.array(warningSchema).nullish()
|
|
65533
|
+
}).passthrough();
|
|
65534
|
+
var calcAgentDefinitionSchema = z3.object({
|
|
65535
|
+
model_type: optionalText,
|
|
65536
|
+
name: optionalText,
|
|
65537
|
+
destination_template_id: z3.number().nullish()
|
|
65538
|
+
}).passthrough();
|
|
65539
|
+
var calcAgentPreviewSchema = z3.object({
|
|
65540
|
+
status: calcAgentOperationStatusSchema,
|
|
65541
|
+
phase: optionalText,
|
|
65542
|
+
conversation_id: optionalText,
|
|
65543
|
+
answer_text: optionalText,
|
|
65544
|
+
changes_summary: optionalText,
|
|
65545
|
+
definition: calcAgentDefinitionSchema.nullish(),
|
|
65546
|
+
error: calcAgentGateErrorSchema.nullish(),
|
|
65547
|
+
result: calcAgentPreviewResultSchema.nullish()
|
|
65548
|
+
}).passthrough();
|
|
65549
|
+
var calcAgentSaveResultSchema = z3.object({ id: z3.number(), name: z3.string(), version_id: z3.number() }).passthrough();
|
|
65550
|
+
var calcAgentImpactSchema = z3.object({
|
|
65551
|
+
total_rows: amount,
|
|
65552
|
+
pool_total: amount,
|
|
65553
|
+
allocated_total: amount,
|
|
65554
|
+
coverage_pct: amount
|
|
65555
|
+
}).passthrough();
|
|
65556
|
+
var calcAgentApprovalPlanSchema = z3.object({ impact: calcAgentImpactSchema, version_id: z3.number() }).passthrough();
|
|
65557
|
+
var calcAgentApproveResultSchema = z3.object({
|
|
65558
|
+
id: z3.number(),
|
|
65559
|
+
name: z3.string(),
|
|
65560
|
+
impact: calcAgentImpactSchema.nullish(),
|
|
65561
|
+
run_id: z3.string().nullish()
|
|
65562
|
+
}).passthrough();
|
|
65563
|
+
|
|
65564
|
+
// src/api/_htmlEntities.ts
|
|
65565
|
+
var ENTITIES2 = {
|
|
65566
|
+
"&": "&",
|
|
65567
|
+
"<": "<",
|
|
65568
|
+
">": ">",
|
|
65569
|
+
""": '"',
|
|
65570
|
+
"'": "'",
|
|
65571
|
+
"'": "'"
|
|
65572
|
+
};
|
|
65573
|
+
var ENTITY_RE2 = /&(?:amp|lt|gt|quot|#x27|#39);/g;
|
|
65574
|
+
function decodeHtmlDeep(value) {
|
|
65575
|
+
if (typeof value === "string") return value.replace(ENTITY_RE2, (m) => ENTITIES2[m] ?? m);
|
|
65576
|
+
if (Array.isArray(value)) return value.map(decodeHtmlDeep);
|
|
65577
|
+
if (value !== null && typeof value === "object") {
|
|
65578
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, decodeHtmlDeep(v)]));
|
|
65579
|
+
}
|
|
65580
|
+
return value;
|
|
65581
|
+
}
|
|
65582
|
+
|
|
65583
|
+
// src/api/calcmodelsAgent.ts
|
|
65584
|
+
var BASE3 = "/api/templates/calcmodels/agent";
|
|
65585
|
+
var APPROVAL_ACK_REQUIRED = "CALC_APPROVAL_ACK_REQUIRED";
|
|
65586
|
+
var CalcAgentHttpError = class extends Error {
|
|
65587
|
+
status;
|
|
65588
|
+
body;
|
|
65589
|
+
code;
|
|
65590
|
+
constructor(status2, body, message) {
|
|
65591
|
+
super(message);
|
|
65592
|
+
this.name = "CalcAgentHttpError";
|
|
65593
|
+
this.status = status2;
|
|
65594
|
+
this.body = body;
|
|
65595
|
+
const code = isRecord(body) ? body["code"] : void 0;
|
|
65596
|
+
this.code = typeof code === "string" ? code : void 0;
|
|
65597
|
+
}
|
|
65598
|
+
/** A numeric field of the body (`calc_model_id`, `version_id`), or undefined. */
|
|
65599
|
+
numberField(key) {
|
|
65600
|
+
const v = isRecord(this.body) ? this.body[key] : void 0;
|
|
65601
|
+
return typeof v === "number" ? v : void 0;
|
|
65602
|
+
}
|
|
65603
|
+
};
|
|
65604
|
+
var CONTROL_CHARS2 = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
65605
|
+
function escapeControlChars(text2) {
|
|
65606
|
+
return text2.replace(
|
|
65607
|
+
CONTROL_CHARS2,
|
|
65608
|
+
(c) => c === "\r" ? "\\r" : `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`
|
|
65609
|
+
);
|
|
65610
|
+
}
|
|
65611
|
+
function isRecord(v) {
|
|
65612
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
65613
|
+
}
|
|
65614
|
+
function textField(body, key) {
|
|
65615
|
+
if (!isRecord(body)) return void 0;
|
|
65616
|
+
const v = body[key];
|
|
65617
|
+
return typeof v === "string" && v !== "" ? v : void 0;
|
|
65618
|
+
}
|
|
65619
|
+
async function send(client, method, path13, payload, options) {
|
|
65620
|
+
const res = await client.do(
|
|
65621
|
+
method,
|
|
65622
|
+
path13,
|
|
65623
|
+
payload === void 0 ? void 0 : JSON.stringify(payload),
|
|
65624
|
+
options
|
|
65625
|
+
);
|
|
65626
|
+
const text2 = await res.text();
|
|
65627
|
+
let body = text2;
|
|
65628
|
+
if (text2 !== "") {
|
|
65629
|
+
try {
|
|
65630
|
+
body = JSON.parse(text2);
|
|
65631
|
+
} catch {
|
|
65632
|
+
body = text2;
|
|
65633
|
+
}
|
|
65634
|
+
}
|
|
65635
|
+
return { status: res.status, body: decodeHtmlDeep(body) };
|
|
65636
|
+
}
|
|
65637
|
+
function describeServerError(body) {
|
|
65638
|
+
if (!isRecord(body)) {
|
|
65639
|
+
const raw2 = typeof body === "string" ? body.trim() : "";
|
|
65640
|
+
return raw2.length > 200 ? `${raw2.slice(0, 200)}\u2026` : raw2;
|
|
65641
|
+
}
|
|
65642
|
+
const code = textField(body, "code");
|
|
65643
|
+
const phase = textField(body, "phase");
|
|
65644
|
+
const parts = [
|
|
65645
|
+
textField(body, "detail"),
|
|
65646
|
+
textField(body, "suggestion"),
|
|
65647
|
+
textField(body, "segment") !== void 0 ? `[${textField(body, "segment")}]` : void 0
|
|
65648
|
+
].filter((p) => p !== void 0);
|
|
65649
|
+
const head = code !== void 0 ? `${code}${phase !== void 0 ? ` (${phase})` : ""}` : "";
|
|
65650
|
+
if (head === "") return parts.join(" \u2014 ");
|
|
65651
|
+
return parts.length > 0 ? `${head}: ${parts.join(" \u2014 ")}` : head;
|
|
65652
|
+
}
|
|
65653
|
+
function httpError(method, path13, answer, hint) {
|
|
65654
|
+
const detail = escapeControlChars(hint ?? describeServerError(answer.body));
|
|
65655
|
+
const tail = detail !== "" ? `: ${detail}` : "";
|
|
65656
|
+
return new CalcAgentHttpError(
|
|
65657
|
+
answer.status,
|
|
65658
|
+
answer.body,
|
|
65659
|
+
`${method} ${path13}: HTTP ${answer.status}${tail}`
|
|
65660
|
+
);
|
|
65661
|
+
}
|
|
65662
|
+
function parse2(schema, method, path13, body) {
|
|
65663
|
+
const out = schema.safeParse(body);
|
|
65664
|
+
if (!out.success) {
|
|
65665
|
+
const issues = out.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
65666
|
+
throw new Error(`${method} ${path13}: unexpected response from the server (${issues})`);
|
|
65667
|
+
}
|
|
65668
|
+
return out.data;
|
|
65669
|
+
}
|
|
65670
|
+
async function postTurn(client, input, options) {
|
|
65671
|
+
const path13 = `${BASE3}/turns`;
|
|
65672
|
+
const payload = { message: input.message };
|
|
65673
|
+
if (input.conversationId !== void 0) payload["conversation_id"] = input.conversationId;
|
|
65674
|
+
const answer = await send(client, "POST", path13, payload, options);
|
|
65675
|
+
if (answer.status === 202) return parse2(calcAgentTurnAcceptedSchema, "POST", path13, answer.body);
|
|
65676
|
+
if (answer.status === 404 && input.conversationId !== void 0) {
|
|
65677
|
+
throw httpError(
|
|
65678
|
+
"POST",
|
|
65679
|
+
path13,
|
|
65680
|
+
answer,
|
|
65681
|
+
`conversation ${input.conversationId} not found (only the user who started it can continue it)`
|
|
65682
|
+
);
|
|
65683
|
+
}
|
|
65684
|
+
throw httpError("POST", path13, answer);
|
|
65685
|
+
}
|
|
65686
|
+
async function getPreview2(client, operationId, options) {
|
|
65687
|
+
const path13 = `${BASE3}/preview/${encodeURIComponent(operationId)}`;
|
|
65688
|
+
const answer = await send(client, "GET", path13, void 0, options);
|
|
65689
|
+
if (answer.status === 200) return parse2(calcAgentPreviewSchema, "GET", path13, answer.body);
|
|
65690
|
+
if (answer.status === 404) {
|
|
65691
|
+
throw httpError(
|
|
65692
|
+
"GET",
|
|
65693
|
+
path13,
|
|
65694
|
+
answer,
|
|
65695
|
+
`operation ${operationId} not found (only the user who started the turn can read it)`
|
|
65696
|
+
);
|
|
65697
|
+
}
|
|
65698
|
+
throw httpError("GET", path13, answer);
|
|
65699
|
+
}
|
|
65700
|
+
async function save2(client, input, options) {
|
|
65701
|
+
const payload = {
|
|
65702
|
+
preview_operation_id: input.operationId,
|
|
65703
|
+
name: input.name
|
|
65704
|
+
};
|
|
65705
|
+
if (input.calcModelId !== void 0) payload["calc_model_id"] = input.calcModelId;
|
|
65706
|
+
const answer = await send(client, "POST", BASE3, payload, options);
|
|
65707
|
+
if (answer.status === 201) return parse2(calcAgentSaveResultSchema, "POST", BASE3, answer.body);
|
|
65708
|
+
throw httpError("POST", BASE3, answer);
|
|
65709
|
+
}
|
|
65710
|
+
async function requestApprovalImpact(client, calcModelId, options) {
|
|
65711
|
+
const path13 = `${BASE3}/${calcModelId}/approve`;
|
|
65712
|
+
const answer = await send(client, "POST", path13, {}, options);
|
|
65713
|
+
if (answer.status >= 200 && answer.status < 300) {
|
|
65714
|
+
throw new Error(
|
|
65715
|
+
`POST ${path13}: HTTP ${answer.status} to a request without an acknowledgment; expected ${APPROVAL_ACK_REQUIRED}. Check the model with \`dr calcmodels get ${calcModelId}\`.`
|
|
65716
|
+
);
|
|
65717
|
+
}
|
|
65718
|
+
if (answer.status === 400 && textField(answer.body, "code") === APPROVAL_ACK_REQUIRED) {
|
|
65719
|
+
return parse2(calcAgentApprovalPlanSchema, "POST", path13, answer.body);
|
|
65720
|
+
}
|
|
65721
|
+
throw httpError("POST", path13, answer);
|
|
65722
|
+
}
|
|
65723
|
+
async function confirmApproval(client, calcModelId, versionId, options) {
|
|
65724
|
+
const path13 = `${BASE3}/${calcModelId}/approve`;
|
|
65725
|
+
const answer = await send(
|
|
65726
|
+
client,
|
|
65727
|
+
"POST",
|
|
65728
|
+
path13,
|
|
65729
|
+
{ acknowledged: true, version_id: versionId },
|
|
65730
|
+
options
|
|
65731
|
+
);
|
|
65732
|
+
if (answer.status === 200) return parse2(calcAgentApproveResultSchema, "POST", path13, answer.body);
|
|
65733
|
+
throw httpError("POST", path13, answer);
|
|
65734
|
+
}
|
|
65735
|
+
|
|
65736
|
+
// src/commands/_agentCli.ts
|
|
65737
|
+
import { access } from "fs/promises";
|
|
65738
|
+
var JSON_HELP = "Emit raw JSON to stdout instead of a pretty render.";
|
|
65739
|
+
var CONFIRM_HELP = "Token from a prior plan run \u2014 required to actually write";
|
|
65740
|
+
function withTarget(cmd) {
|
|
65741
|
+
addServerFlag(cmd);
|
|
65742
|
+
addAccountFlag(cmd);
|
|
65743
|
+
return cmd;
|
|
65744
|
+
}
|
|
65745
|
+
function std() {
|
|
65746
|
+
return { out: process.stdout, err: process.stderr };
|
|
65747
|
+
}
|
|
65748
|
+
function requireId(value, placeholder) {
|
|
65749
|
+
const id = (value ?? "").trim();
|
|
65750
|
+
if (id === "") {
|
|
65751
|
+
throw new Error(`missing required option: --id ${placeholder}`);
|
|
65752
|
+
}
|
|
65753
|
+
return id;
|
|
65754
|
+
}
|
|
65755
|
+
function requirePositiveInt(value, flag) {
|
|
65756
|
+
const n = /^\d+$/.test(value) ? Number(value) : NaN;
|
|
65757
|
+
if (!Number.isSafeInteger(n) || n <= 0) {
|
|
65758
|
+
throw new Error(`${flag} ${JSON.stringify(value)}: expected a positive integer`);
|
|
65759
|
+
}
|
|
65760
|
+
return n;
|
|
65761
|
+
}
|
|
65762
|
+
function requireBool(value, flag) {
|
|
65763
|
+
if (value === "true") return true;
|
|
65764
|
+
if (value === "false") return false;
|
|
65765
|
+
throw new Error(`${flag} ${JSON.stringify(value ?? "")}: expected true or false`);
|
|
65766
|
+
}
|
|
65767
|
+
function splitList(value) {
|
|
65768
|
+
return [
|
|
65769
|
+
...new Set(
|
|
65770
|
+
(value ?? "").split(",").map((part) => part.trim()).filter((part) => part !== "")
|
|
65771
|
+
)
|
|
65772
|
+
];
|
|
65773
|
+
}
|
|
65774
|
+
function isNoneSentinel(parts, flag, value, what) {
|
|
65775
|
+
if (!parts.includes("none")) return false;
|
|
65776
|
+
if (parts.length === 1) return true;
|
|
65777
|
+
throw new Error(`${flag} ${JSON.stringify(value)}: "none" cannot be combined with ${what}`);
|
|
65778
|
+
}
|
|
65779
|
+
function shellArg(value) {
|
|
65780
|
+
return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : shellQuote(value);
|
|
65781
|
+
}
|
|
65782
|
+
function proseStream(cfg) {
|
|
65783
|
+
return cfg.asJson ? cfg.err : cfg.out;
|
|
65784
|
+
}
|
|
65785
|
+
async function assertNoOverwrite(paths, force) {
|
|
65786
|
+
if (force) return;
|
|
65787
|
+
const existing = [];
|
|
65788
|
+
for (const path13 of paths) {
|
|
65789
|
+
try {
|
|
65790
|
+
await access(path13);
|
|
65791
|
+
existing.push(path13);
|
|
65792
|
+
} catch {
|
|
65793
|
+
}
|
|
65794
|
+
}
|
|
65795
|
+
if (existing.length > 0) {
|
|
65796
|
+
throw new Error(
|
|
65797
|
+
`refusing to overwrite existing file(s): ${existing.join(", ")}. Pull into another --dir, or pass --force to replace them.`
|
|
65798
|
+
);
|
|
65799
|
+
}
|
|
65800
|
+
}
|
|
65801
|
+
|
|
65802
|
+
// src/commands/calcmodelsAgent.ts
|
|
65803
|
+
var AGENT_POLL_INTERVAL_MS = 3e3;
|
|
65804
|
+
var AGENT_DEFAULT_TIMEOUT_S = 900;
|
|
65805
|
+
var MAX_TRANSIENT_READ_FAILURES = 5;
|
|
65806
|
+
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
65807
|
+
function parseOperationIdArg(raw2) {
|
|
65808
|
+
if (!UUID_RE2.test(raw2)) throw new Error(`invalid operation id: "${raw2}" (expected a UUID)`);
|
|
65809
|
+
return raw2.toLowerCase();
|
|
65810
|
+
}
|
|
65811
|
+
function isLive(p) {
|
|
65812
|
+
return p.status === "PENDING" || p.status === "RUNNING";
|
|
65813
|
+
}
|
|
65814
|
+
function formatNumber(n) {
|
|
65815
|
+
return n === null || n === void 0 ? "n/a" : n.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
|
65816
|
+
}
|
|
65817
|
+
function previewLine(result) {
|
|
65818
|
+
const parts = [`${formatNumber(result.total_count)} rows`];
|
|
65819
|
+
const cov = result.coverage;
|
|
65820
|
+
if (cov) {
|
|
65821
|
+
const pct2 = cov.pct === null || cov.pct === void 0 ? "n/a" : `${formatNumber(cov.pct)}%`;
|
|
65822
|
+
parts.push(
|
|
65823
|
+
`coverage ${pct2} (allocated ${formatNumber(cov.allocated_total)} of pool ${formatNumber(cov.pool_total)})`
|
|
65824
|
+
);
|
|
65825
|
+
}
|
|
65826
|
+
const checks = result.balance_checks ?? [];
|
|
65827
|
+
if (checks.length > 0) {
|
|
65828
|
+
const ok = checks.filter((c) => c.ok).length;
|
|
65829
|
+
parts.push(`balance: ${ok} of ${checks.length} groups net to zero`);
|
|
65830
|
+
}
|
|
65831
|
+
return `preview: ${parts.join(" \xB7 ")}`;
|
|
65832
|
+
}
|
|
65833
|
+
function previewCmd(operationId, serverFlag) {
|
|
65834
|
+
return `dr calcmodels agent preview ${operationId} --server ${serverFlag}`;
|
|
65835
|
+
}
|
|
65836
|
+
function renderPreview(out, p, operationId, serverFlag) {
|
|
65837
|
+
out.write(`status: ${p.status}
|
|
65838
|
+
`);
|
|
65839
|
+
if (p.conversation_id) out.write(`conversation: ${escapeControlChars(p.conversation_id)}
|
|
65840
|
+
`);
|
|
65841
|
+
if (isLive(p)) {
|
|
65842
|
+
out.write(
|
|
65843
|
+
`
|
|
65844
|
+
The turn is still running. Check again with:
|
|
65845
|
+
${previewCmd(operationId, serverFlag)}
|
|
65846
|
+
`
|
|
65847
|
+
);
|
|
65848
|
+
return;
|
|
65849
|
+
}
|
|
65850
|
+
if (p.answer_text) out.write(`
|
|
65851
|
+
${escapeControlChars(p.answer_text.trim())}
|
|
65852
|
+
`);
|
|
65853
|
+
if (p.changes_summary) {
|
|
65854
|
+
out.write("\nchanges:\n");
|
|
65855
|
+
for (const line of p.changes_summary.trim().split("\n"))
|
|
65856
|
+
out.write(` ${escapeControlChars(line)}
|
|
65857
|
+
`);
|
|
65858
|
+
}
|
|
65859
|
+
const savable = p.status === "SUCCEEDED" && p.result !== null && p.result !== void 0;
|
|
65860
|
+
if (p.status === "SUCCEEDED") {
|
|
65861
|
+
const d = p.definition;
|
|
65862
|
+
if (d) {
|
|
65863
|
+
out.write(
|
|
65864
|
+
`
|
|
65865
|
+
proposal: ${escapeControlChars(JSON.stringify(d.name ?? ""))} (${escapeControlChars(d.model_type ?? "unknown")}) \u2192 destination table ${d.destination_template_id ?? "n/a"}
|
|
65866
|
+
`
|
|
65867
|
+
);
|
|
65868
|
+
}
|
|
65869
|
+
if (p.result) {
|
|
65870
|
+
out.write(`${previewLine(p.result)}
|
|
65871
|
+
`);
|
|
65872
|
+
for (const w of p.result.warnings ?? []) {
|
|
65873
|
+
const label = [w.code, w.message].filter((s) => !!s).join(": ");
|
|
65874
|
+
if (label !== "") out.write(`warning: ${escapeControlChars(label)}
|
|
65875
|
+
`);
|
|
65876
|
+
}
|
|
65877
|
+
} else {
|
|
65878
|
+
out.write(
|
|
65879
|
+
"preview: none ran. The server saves only a turn whose preview ran (CALC_PREVIEW_NOT_READY).\n"
|
|
65880
|
+
);
|
|
65881
|
+
}
|
|
65882
|
+
}
|
|
65883
|
+
const next = [];
|
|
65884
|
+
if (savable) {
|
|
65885
|
+
next.push(`save the draft: dr calcmodels agent save ${operationId} --server ${serverFlag}`);
|
|
65886
|
+
}
|
|
65887
|
+
if (p.conversation_id) {
|
|
65888
|
+
next.push(
|
|
65889
|
+
`reply: dr calcmodels agent ask '<reply>' --conversation ${shellQuote(p.conversation_id)} --server ${serverFlag}`
|
|
65890
|
+
);
|
|
65891
|
+
}
|
|
65892
|
+
if (next.length > 0) out.write(`
|
|
65893
|
+
next:
|
|
65894
|
+
${next.map((l) => ` ${l}
|
|
65895
|
+
`).join("")}`);
|
|
65896
|
+
}
|
|
65897
|
+
function writeOutcome(out, p, operationId, serverFlag, asJson) {
|
|
65898
|
+
if (asJson) writeJSONIndented(out, { operation_id: operationId, ...p });
|
|
65899
|
+
else renderPreview(out, p, operationId, serverFlag);
|
|
65900
|
+
if (p.status === "FAILED") {
|
|
65901
|
+
const why = p.error ? escapeControlChars(describeServerError(p.error)) : "the server gave no error detail";
|
|
65902
|
+
throw new Error(`turn ${operationId} failed: ${why}`);
|
|
65903
|
+
}
|
|
65904
|
+
}
|
|
65905
|
+
var ReadTimeoutError = class extends Error {
|
|
65906
|
+
constructor(operationId, ms) {
|
|
65907
|
+
super(`reading operation ${operationId} took longer than ${ms} ms`);
|
|
65908
|
+
this.name = "ReadTimeoutError";
|
|
65909
|
+
}
|
|
65910
|
+
};
|
|
65911
|
+
function isTransient(err) {
|
|
65912
|
+
if (err instanceof ReadTimeoutError) return true;
|
|
65913
|
+
if (err instanceof CalcAgentHttpError) return err.status >= 500;
|
|
65914
|
+
return err instanceof TypeError;
|
|
65915
|
+
}
|
|
65916
|
+
var AGENT_READ_TIMEOUT_MS = 3e4;
|
|
65917
|
+
var AGENT_READ_TIMEOUT_FLOOR_MS = 5e3;
|
|
65918
|
+
var defaultSleep8 = (ms, signal) => delay(ms, void 0, { signal });
|
|
65919
|
+
async function runAgentAsk(cfg) {
|
|
65920
|
+
const accepted = await postTurn(
|
|
65921
|
+
cfg.client,
|
|
65922
|
+
{
|
|
65923
|
+
message: cfg.message,
|
|
65924
|
+
...cfg.conversationId !== void 0 && { conversationId: cfg.conversationId }
|
|
65925
|
+
},
|
|
65926
|
+
{ signal: cfg.signal }
|
|
65927
|
+
);
|
|
65928
|
+
const op = accepted.operation_id;
|
|
65929
|
+
const conv = accepted.conversation_id;
|
|
65930
|
+
cfg.err.write(
|
|
65931
|
+
`turn started: operation ${op}${conv ? `, conversation ${escapeControlChars(conv)}` : ""}
|
|
65932
|
+
`
|
|
65933
|
+
);
|
|
65934
|
+
if (!cfg.wait) {
|
|
65935
|
+
if (cfg.asJson) {
|
|
65936
|
+
writeJSONIndented(cfg.out, accepted);
|
|
65937
|
+
return;
|
|
65938
|
+
}
|
|
65939
|
+
cfg.out.write(`operation: ${op}
|
|
65940
|
+
Read it with:
|
|
65941
|
+
${previewCmd(op, cfg.serverFlag)}
|
|
65942
|
+
`);
|
|
65943
|
+
return;
|
|
65944
|
+
}
|
|
65945
|
+
cfg.err.write(
|
|
65946
|
+
`Waiting for the agent. Ctrl-C stops the wait, not the turn; resume with:
|
|
65947
|
+
${previewCmd(op, cfg.serverFlag)}
|
|
65948
|
+
`
|
|
65949
|
+
);
|
|
65950
|
+
const preview2 = await pollPreview(cfg, op);
|
|
65951
|
+
writeOutcome(cfg.out, preview2, op, cfg.serverFlag, cfg.asJson);
|
|
65952
|
+
}
|
|
65953
|
+
async function pollPreview(cfg, operationId) {
|
|
65954
|
+
const interval = cfg.pollIntervalMs ?? AGENT_POLL_INTERVAL_MS;
|
|
65955
|
+
const sleep3 = cfg.sleep ?? defaultSleep8;
|
|
65956
|
+
const now = cfg.now ?? Date.now;
|
|
65957
|
+
const deadline = now() + cfg.timeoutMs;
|
|
65958
|
+
const readMax = cfg.readTimeoutMs ?? AGENT_READ_TIMEOUT_MS;
|
|
65959
|
+
const readFloor = cfg.readTimeoutFloorMs ?? AGENT_READ_TIMEOUT_FLOOR_MS;
|
|
65960
|
+
let failures = 0;
|
|
65961
|
+
for (; ; ) {
|
|
65962
|
+
await sleep3(Math.max(0, Math.min(interval, deadline - now())), cfg.signal);
|
|
65963
|
+
try {
|
|
65964
|
+
const budget = Math.min(readMax, Math.max(deadline - now(), readFloor));
|
|
65965
|
+
const p = await readWithin(cfg, operationId, budget);
|
|
65966
|
+
failures = 0;
|
|
65967
|
+
if (!isLive(p)) return p;
|
|
65968
|
+
} catch (err) {
|
|
65969
|
+
failures += 1;
|
|
65970
|
+
if (!isTransient(err)) throw err;
|
|
65971
|
+
if (failures > MAX_TRANSIENT_READ_FAILURES && now() < deadline) throw err;
|
|
65972
|
+
}
|
|
65973
|
+
if (now() >= deadline) {
|
|
65974
|
+
throw new Error(
|
|
65975
|
+
`turn ${operationId} is still running after ${Math.round(cfg.timeoutMs / 1e3)}s. It keeps running on the server; read it with: ${previewCmd(operationId, cfg.serverFlag)}`
|
|
65976
|
+
);
|
|
65977
|
+
}
|
|
65978
|
+
}
|
|
65979
|
+
}
|
|
65980
|
+
async function readWithin(cfg, operationId, budgetMs) {
|
|
65981
|
+
const ctl = new AbortController();
|
|
65982
|
+
const relay = () => ctl.abort(cfg.signal.reason);
|
|
65983
|
+
if (cfg.signal.aborted) relay();
|
|
65984
|
+
else cfg.signal.addEventListener("abort", relay, { once: true });
|
|
65985
|
+
const timer = setTimeout(() => ctl.abort(), budgetMs);
|
|
65986
|
+
try {
|
|
65987
|
+
return await getPreview2(cfg.client, operationId, { signal: ctl.signal });
|
|
65988
|
+
} catch (err) {
|
|
65989
|
+
if (ctl.signal.aborted && !cfg.signal.aborted)
|
|
65990
|
+
throw new ReadTimeoutError(operationId, budgetMs);
|
|
65991
|
+
throw err;
|
|
65992
|
+
} finally {
|
|
65993
|
+
clearTimeout(timer);
|
|
65994
|
+
cfg.signal.removeEventListener("abort", relay);
|
|
65995
|
+
}
|
|
65996
|
+
}
|
|
65997
|
+
async function runAgentPreview(cfg) {
|
|
65998
|
+
const p = await getPreview2(cfg.client, cfg.operationId, { signal: cfg.signal });
|
|
65999
|
+
writeOutcome(cfg.out, p, cfg.operationId, cfg.serverFlag, cfg.asJson);
|
|
66000
|
+
}
|
|
66001
|
+
var PREVIEW_ALREADY_SAVED = "CALC_PREVIEW_ALREADY_SAVED";
|
|
66002
|
+
var VERSION_CHANGED = "CALC_APPROVAL_VERSION_CHANGED";
|
|
66003
|
+
async function runAgentSave(cfg) {
|
|
66004
|
+
let name = cfg.name;
|
|
66005
|
+
if (name !== void 0 && name.trim() === "") throw new Error("--name must not be empty");
|
|
66006
|
+
if (name === void 0) {
|
|
66007
|
+
const p = await getPreview2(cfg.client, cfg.operationId, { signal: cfg.signal });
|
|
66008
|
+
name = p.definition?.name ?? void 0;
|
|
66009
|
+
if (name === void 0 || name.trim() === "") {
|
|
66010
|
+
throw new Error(
|
|
66011
|
+
`--name is required: operation ${cfg.operationId} has no proposed name (status ${p.status})`
|
|
66012
|
+
);
|
|
66013
|
+
}
|
|
66014
|
+
}
|
|
66015
|
+
let saved;
|
|
66016
|
+
try {
|
|
66017
|
+
saved = await save2(
|
|
66018
|
+
cfg.client,
|
|
66019
|
+
{
|
|
66020
|
+
operationId: cfg.operationId,
|
|
66021
|
+
name,
|
|
66022
|
+
...cfg.calcModelId !== void 0 && { calcModelId: cfg.calcModelId }
|
|
66023
|
+
},
|
|
66024
|
+
{ signal: cfg.signal }
|
|
66025
|
+
);
|
|
66026
|
+
} catch (err) {
|
|
66027
|
+
if (err instanceof CalcAgentHttpError && err.code === PREVIEW_ALREADY_SAVED) {
|
|
66028
|
+
const modelId = err.numberField("calc_model_id");
|
|
66029
|
+
if (modelId !== void 0) {
|
|
66030
|
+
throw new Error(
|
|
66031
|
+
`${err.message}. To add this turn as a new version of model ${modelId}: dr calcmodels agent save ${cfg.operationId} --model ${modelId} --server ${cfg.serverFlag}`,
|
|
66032
|
+
{ cause: err }
|
|
66033
|
+
);
|
|
66034
|
+
}
|
|
66035
|
+
}
|
|
66036
|
+
throw err;
|
|
66037
|
+
}
|
|
66038
|
+
if (cfg.asJson) {
|
|
66039
|
+
writeJSONIndented(cfg.out, saved);
|
|
66040
|
+
return;
|
|
66041
|
+
}
|
|
66042
|
+
cfg.out.write(
|
|
66043
|
+
`Saved draft calc model ${saved.id} ${escapeControlChars(JSON.stringify(saved.name))} (version ${saved.version_id}). This version does not run until it is approved.
|
|
66044
|
+
|
|
66045
|
+
next:
|
|
66046
|
+
dr calcmodels agent approve ${saved.id} --server ${cfg.serverFlag}
|
|
66047
|
+
`
|
|
66048
|
+
);
|
|
66049
|
+
}
|
|
66050
|
+
function approveAction(calcModelId, versionId) {
|
|
66051
|
+
return { resource: "calcmodel", id: `${calcModelId}@v${versionId}`, verb: "approve" };
|
|
66052
|
+
}
|
|
66053
|
+
function versionChanged(calcModelId, planned, current, cause) {
|
|
66054
|
+
const newer = current !== void 0 ? ` (${current})` : "";
|
|
66055
|
+
return new Error(
|
|
66056
|
+
`calc model ${calcModelId}: a newer version${newer} was saved after the plan for version ${planned}. Review it: re-run without --confirm-token. Nothing was activated.`,
|
|
66057
|
+
cause === void 0 ? void 0 : { cause }
|
|
66058
|
+
);
|
|
66059
|
+
}
|
|
66060
|
+
function impactLines(calcModelId, plan) {
|
|
66061
|
+
const i = plan.impact;
|
|
66062
|
+
const pct2 = i.coverage_pct === null || i.coverage_pct === void 0 ? "n/a" : `${formatNumber(i.coverage_pct)}%`;
|
|
66063
|
+
return [
|
|
66064
|
+
`calc model: ${calcModelId}`,
|
|
66065
|
+
`version: ${plan.version_id}`,
|
|
66066
|
+
`rows the first run writes: ${formatNumber(i.total_rows)}`,
|
|
66067
|
+
`allocated total: ${formatNumber(i.allocated_total)} of pool total ${formatNumber(i.pool_total)} (coverage ${pct2})`,
|
|
66068
|
+
"Approving activates this version and starts its first production run at once."
|
|
66069
|
+
];
|
|
66070
|
+
}
|
|
66071
|
+
async function runAgentApprove(cfg) {
|
|
66072
|
+
const executing = cfg.token !== void 0 && cfg.token.trim() !== "";
|
|
66073
|
+
if (executing && cfg.versionId === void 0) {
|
|
66074
|
+
throw new Error(
|
|
66075
|
+
"--version-id is required with --confirm-token: copy the command the plan printed"
|
|
66076
|
+
);
|
|
66077
|
+
}
|
|
66078
|
+
if (executing) await preflightConfirmToken(approveAction(cfg.id, cfg.versionId), cfg.token);
|
|
66079
|
+
const plan = await requestApprovalImpact(cfg.client, cfg.id, { signal: cfg.signal });
|
|
66080
|
+
if (executing && plan.version_id !== cfg.versionId) {
|
|
66081
|
+
throw versionChanged(cfg.id, cfg.versionId, plan.version_id);
|
|
66082
|
+
}
|
|
66083
|
+
const versionId = executing ? cfg.versionId : plan.version_id;
|
|
66084
|
+
await twoStepConfirm({
|
|
66085
|
+
out: cfg.out,
|
|
66086
|
+
action: approveAction(cfg.id, versionId),
|
|
66087
|
+
token: cfg.token,
|
|
66088
|
+
server: cfg.serverFlag,
|
|
66089
|
+
confirmCmd: `dr calcmodels agent approve ${cfg.id} --version-id ${versionId} --server ${cfg.serverFlag}${cfg.asJson ? " --json" : ""}`,
|
|
66090
|
+
describe: () => Promise.resolve(impactLines(cfg.id, plan)),
|
|
66091
|
+
execute: async () => {
|
|
66092
|
+
let result;
|
|
66093
|
+
try {
|
|
66094
|
+
result = await confirmApproval(cfg.client, cfg.id, versionId, {
|
|
66095
|
+
signal: cfg.signal
|
|
66096
|
+
});
|
|
66097
|
+
} catch (err) {
|
|
66098
|
+
if (err instanceof CalcAgentHttpError && err.code === VERSION_CHANGED) {
|
|
66099
|
+
throw versionChanged(cfg.id, versionId, err.numberField("version_id"), err);
|
|
66100
|
+
}
|
|
66101
|
+
throw err;
|
|
66102
|
+
}
|
|
66103
|
+
if (cfg.asJson) {
|
|
66104
|
+
writeJSONIndented(cfg.out, result);
|
|
66105
|
+
return;
|
|
66106
|
+
}
|
|
66107
|
+
const run2 = result.run_id ? `First run: ${escapeControlChars(result.run_id)}.` : `No run was started: the model is not runnable now. Check it with dr calcmodels get ${cfg.id} --server ${cfg.serverFlag}.`;
|
|
66108
|
+
cfg.out.write(`Activated calc model ${cfg.id} (version ${versionId}). ${run2}
|
|
66109
|
+
`);
|
|
66110
|
+
}
|
|
66111
|
+
});
|
|
66112
|
+
}
|
|
66113
|
+
var ASK_SETUP_HEADROOM_MS = 6e4;
|
|
66114
|
+
function registerCalcmodelsAgentCommand(cm) {
|
|
66115
|
+
const agent = cm.command("agent").description(
|
|
66116
|
+
"Build an AGENT (allocation) calc model with the builder agent \u2014 the web builder endpoints. Super Admin + the calc_model_agent_builder flag."
|
|
66117
|
+
);
|
|
66118
|
+
const askCmd = agent.command("ask").description(
|
|
66119
|
+
"Send one message to the builder agent and wait for its answer. Ctrl-C stops the wait, not the turn."
|
|
66120
|
+
).argument("<message>", "what to build, or a reply to the agent").option("--conversation <id>", "continue this conversation (from a previous answer)").option("--no-wait", "return after the server accepts the turn; read it with `preview`").option(
|
|
66121
|
+
"--timeout <seconds>",
|
|
66122
|
+
`stop waiting after this long (default ${AGENT_DEFAULT_TIMEOUT_S})`
|
|
66123
|
+
).option("--json", "Output raw JSON", false).action(
|
|
66124
|
+
async (message, opts) => {
|
|
66125
|
+
if (message.trim() === "") throw new Error("<message> must not be empty");
|
|
66126
|
+
const timeoutMs = parsePositiveSecondsFlag(
|
|
66127
|
+
opts.timeout,
|
|
66128
|
+
"--timeout",
|
|
66129
|
+
AGENT_DEFAULT_TIMEOUT_S * 1e3
|
|
66130
|
+
);
|
|
66131
|
+
const serverKey = resolveServer(opts.server);
|
|
66132
|
+
const setup = await setupCall(opts.server, opts.account, {
|
|
66133
|
+
timeoutMs: timeoutMs + ASK_SETUP_HEADROOM_MS
|
|
66134
|
+
});
|
|
66135
|
+
try {
|
|
66136
|
+
await runAgentAsk({
|
|
66137
|
+
client: setup.client,
|
|
66138
|
+
message,
|
|
66139
|
+
...opts.conversation !== void 0 && { conversationId: opts.conversation },
|
|
66140
|
+
wait: opts.wait !== false,
|
|
66141
|
+
timeoutMs,
|
|
66142
|
+
asJson: opts.json === true,
|
|
66143
|
+
serverFlag: serverKey,
|
|
66144
|
+
signal: setup.signal,
|
|
66145
|
+
out: process.stdout,
|
|
66146
|
+
err: process.stderr
|
|
66147
|
+
});
|
|
66148
|
+
} finally {
|
|
66149
|
+
setup.cleanup();
|
|
66150
|
+
}
|
|
66151
|
+
}
|
|
66152
|
+
);
|
|
66153
|
+
addServerFlag(askCmd);
|
|
66154
|
+
addAccountFlag(askCmd);
|
|
66155
|
+
const previewCmdDef = agent.command("preview").description("Read one turn: the agent's answer, its proposal, and the preview figures").argument("<operation_id>", "the operation id `ask` printed").option("--json", "Output raw JSON", false).action(async (opArg, opts) => {
|
|
66156
|
+
const operationId = parseOperationIdArg(opArg);
|
|
66157
|
+
const serverKey = resolveServer(opts.server);
|
|
66158
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
66159
|
+
try {
|
|
66160
|
+
await runAgentPreview({
|
|
66161
|
+
client: setup.client,
|
|
66162
|
+
operationId,
|
|
66163
|
+
asJson: opts.json === true,
|
|
66164
|
+
serverFlag: serverKey,
|
|
66165
|
+
signal: setup.signal,
|
|
66166
|
+
out: process.stdout
|
|
66167
|
+
});
|
|
66168
|
+
} finally {
|
|
66169
|
+
setup.cleanup();
|
|
66170
|
+
}
|
|
66171
|
+
});
|
|
66172
|
+
addServerFlag(previewCmdDef);
|
|
66173
|
+
addAccountFlag(previewCmdDef);
|
|
66174
|
+
const saveCmd = agent.command("save").description("Save a succeeded turn as a DRAFT calc model. Nothing runs until `approve`.").argument("<operation_id>", "the operation id of a SUCCEEDED turn").option("--name <name>", "model name (default: the agent's proposed name)").option("--model <calc_model_id>", "add this turn as a new version of an existing AGENT model").option("--json", "Output raw JSON", false).action(async (opArg, opts) => {
|
|
66175
|
+
const operationId = parseOperationIdArg(opArg);
|
|
66176
|
+
const calcModelId = opts.model === void 0 ? void 0 : requirePositiveInt(opts.model, "--model");
|
|
66177
|
+
const serverKey = resolveServer(opts.server);
|
|
66178
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
66179
|
+
try {
|
|
66180
|
+
await runAgentSave({
|
|
66181
|
+
client: setup.client,
|
|
66182
|
+
operationId,
|
|
66183
|
+
...opts.name !== void 0 && { name: opts.name },
|
|
66184
|
+
...calcModelId !== void 0 && { calcModelId },
|
|
66185
|
+
asJson: opts.json === true,
|
|
66186
|
+
serverFlag: serverKey,
|
|
66187
|
+
signal: setup.signal,
|
|
66188
|
+
out: process.stdout
|
|
66189
|
+
});
|
|
66190
|
+
} finally {
|
|
66191
|
+
setup.cleanup();
|
|
66192
|
+
}
|
|
66193
|
+
});
|
|
66194
|
+
addServerFlag(saveCmd);
|
|
66195
|
+
addAccountFlag(saveCmd);
|
|
66196
|
+
const approveCmd = agent.command("approve").description(
|
|
66197
|
+
"Activate an AGENT model's newest version and start its first production run (two-step confirm; a person confirms on production)."
|
|
66198
|
+
).argument("<calc_model_id>", "the model id `save` printed").option("--version-id <n>", "the version the plan showed (required with --confirm-token)").option("--confirm-token <token>", "Token from a prior plan run \u2014 required to activate", "").option("--json", "Output raw JSON of the activation", false).action(
|
|
66199
|
+
async (idArg, opts) => {
|
|
66200
|
+
const id = requirePositiveInt(idArg, "<calc_model_id>");
|
|
66201
|
+
const versionId = opts.versionId === void 0 ? void 0 : requirePositiveInt(opts.versionId, "--version-id");
|
|
66202
|
+
const token = opts.confirmToken;
|
|
66203
|
+
if (token !== void 0 && token.trim() !== "") {
|
|
66204
|
+
if (versionId === void 0) {
|
|
66205
|
+
throw new Error(
|
|
66206
|
+
"--version-id is required with --confirm-token: copy the command the plan printed"
|
|
66207
|
+
);
|
|
66208
|
+
}
|
|
66209
|
+
await preflightConfirmToken(approveAction(id, versionId), token);
|
|
66210
|
+
}
|
|
66211
|
+
const serverKey = resolveServer(opts.server);
|
|
66212
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
66213
|
+
try {
|
|
66214
|
+
await runAgentApprove({
|
|
66215
|
+
client: setup.client,
|
|
66216
|
+
id,
|
|
66217
|
+
...versionId !== void 0 && { versionId },
|
|
66218
|
+
token,
|
|
66219
|
+
asJson: opts.json === true,
|
|
66220
|
+
serverFlag: serverKey,
|
|
66221
|
+
signal: setup.signal,
|
|
66222
|
+
out: process.stdout
|
|
66223
|
+
});
|
|
66224
|
+
} finally {
|
|
66225
|
+
setup.cleanup();
|
|
66226
|
+
}
|
|
66227
|
+
}
|
|
66228
|
+
);
|
|
66229
|
+
addServerFlag(approveCmd);
|
|
66230
|
+
addAccountFlag(approveCmd);
|
|
66231
|
+
}
|
|
66232
|
+
|
|
65490
66233
|
// src/commands/calcmodels.ts
|
|
65491
66234
|
async function runCalcmodelsList(cfg) {
|
|
65492
66235
|
const models = await list25(cfg.client, { signal: cfg.signal });
|
|
@@ -65891,6 +66634,7 @@ function registerCalcmodelsCommand(root) {
|
|
|
65891
66634
|
});
|
|
65892
66635
|
addServerFlag(deleteCmd);
|
|
65893
66636
|
addAccountFlag(deleteCmd);
|
|
66637
|
+
registerCalcmodelsAgentCommand(cm);
|
|
65894
66638
|
}
|
|
65895
66639
|
|
|
65896
66640
|
// src/commands/calcVals.ts
|
|
@@ -68875,13 +69619,13 @@ import { promises as fsPromises3 } from "fs";
|
|
|
68875
69619
|
import * as nodePath3 from "path";
|
|
68876
69620
|
|
|
68877
69621
|
// src/api/spendControl.ts
|
|
68878
|
-
var
|
|
69622
|
+
var BASE4 = "/api/spend-control/v1";
|
|
68879
69623
|
var BASE_V2 = "/api/spend-control/v2";
|
|
68880
69624
|
var MAX_BULK_STATUS_IDS = 50;
|
|
68881
69625
|
var MAX_PAGE_SIZE = 100;
|
|
68882
69626
|
async function listBillingFrequencies(client, options) {
|
|
68883
69627
|
const out = await client.getJSON(
|
|
68884
|
-
`${
|
|
69628
|
+
`${BASE4}/catalogs/billing-frequencies/`,
|
|
68885
69629
|
options
|
|
68886
69630
|
);
|
|
68887
69631
|
return out ?? [];
|
|
@@ -68915,7 +69659,7 @@ function buildContractListQuery(params) {
|
|
|
68915
69659
|
}
|
|
68916
69660
|
async function listContracts(client, params = {}, options) {
|
|
68917
69661
|
const qs = buildContractListQuery(params);
|
|
68918
|
-
const path13 = qs === "" ? `${
|
|
69662
|
+
const path13 = qs === "" ? `${BASE4}/contracts/` : `${BASE4}/contracts/?${qs}`;
|
|
68919
69663
|
const out = await client.getJSON(path13, options);
|
|
68920
69664
|
if (out === null) {
|
|
68921
69665
|
return { items: [], total: 0, page: null, size: null, pages: null };
|
|
@@ -68943,10 +69687,10 @@ async function getContract(client, contractId, params = {}, options) {
|
|
|
68943
69687
|
qs.set("currency_code", params.convertTo);
|
|
68944
69688
|
}
|
|
68945
69689
|
const suffix = qs.toString() === "" ? "" : `?${qs.toString()}`;
|
|
68946
|
-
return client.getJSON(`${
|
|
69690
|
+
return client.getJSON(`${BASE4}/contracts/${contractId}${suffix}`, options);
|
|
68947
69691
|
}
|
|
68948
69692
|
async function deleteContract(client, contractId, options) {
|
|
68949
|
-
await client.deleteJSON(`${
|
|
69693
|
+
await client.deleteJSON(`${BASE4}/contracts/${contractId}`, options);
|
|
68950
69694
|
}
|
|
68951
69695
|
async function uploadFilesWithAi(client, files, options = {}) {
|
|
68952
69696
|
if (files.length === 0) return [];
|
|
@@ -68954,7 +69698,7 @@ async function uploadFilesWithAi(client, files, options = {}) {
|
|
|
68954
69698
|
throw new Error(`uploadFilesWithAi: max 10 files per call, got ${files.length}`);
|
|
68955
69699
|
}
|
|
68956
69700
|
const out = await client.postMultipartFiles(
|
|
68957
|
-
`${
|
|
69701
|
+
`${BASE4}/contracts/files-with-ai`,
|
|
68958
69702
|
{ is_additional: options.isAdditional === true ? "true" : "false" },
|
|
68959
69703
|
files,
|
|
68960
69704
|
"files",
|
|
@@ -68964,7 +69708,7 @@ async function uploadFilesWithAi(client, files, options = {}) {
|
|
|
68964
69708
|
}
|
|
68965
69709
|
async function uploadContractFile(client, file, options = {}) {
|
|
68966
69710
|
return client.postMultipartFiles(
|
|
68967
|
-
`${
|
|
69711
|
+
`${BASE4}/contracts/file`,
|
|
68968
69712
|
{ is_additional: options.isAdditional === true ? "true" : "false" },
|
|
68969
69713
|
[file],
|
|
68970
69714
|
"file",
|
|
@@ -68977,7 +69721,7 @@ async function listContractFiles(client, contractId, params = {}, options) {
|
|
|
68977
69721
|
if (params.size !== void 0) qs.set("size", String(params.size));
|
|
68978
69722
|
const suffix = qs.toString() === "" ? "" : `?${qs.toString()}`;
|
|
68979
69723
|
const out = await client.getJSON(
|
|
68980
|
-
`${
|
|
69724
|
+
`${BASE4}/contracts/${contractId}/files${suffix}`,
|
|
68981
69725
|
options
|
|
68982
69726
|
);
|
|
68983
69727
|
if (out === null) {
|
|
@@ -69003,13 +69747,13 @@ async function listAllContractFiles(client, contractId, options) {
|
|
|
69003
69747
|
}
|
|
69004
69748
|
async function getContractFileDownload(client, fileId, options) {
|
|
69005
69749
|
return client.postJSON(
|
|
69006
|
-
`${
|
|
69750
|
+
`${BASE4}/contracts/files/${fileId}/download`,
|
|
69007
69751
|
{},
|
|
69008
69752
|
options
|
|
69009
69753
|
);
|
|
69010
69754
|
}
|
|
69011
69755
|
async function deleteContractFile(client, fileId, options) {
|
|
69012
|
-
await client.deleteJSON(`${
|
|
69756
|
+
await client.deleteJSON(`${BASE4}/contracts/files/${fileId}`, options);
|
|
69013
69757
|
}
|
|
69014
69758
|
async function uploadFileWithAiAsync(client, file, options = {}) {
|
|
69015
69759
|
return client.postMultipartFiles(
|
|
@@ -69045,7 +69789,7 @@ var EXTRACT_POLL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
|
69045
69789
|
function isTerminalAiStatus(status2) {
|
|
69046
69790
|
return status2 === "PROCESSED" || status2 === "FAILED";
|
|
69047
69791
|
}
|
|
69048
|
-
async function
|
|
69792
|
+
async function defaultSleep9(ms, signal) {
|
|
69049
69793
|
if (signal?.aborted === true) return;
|
|
69050
69794
|
await new Promise((resolve3) => {
|
|
69051
69795
|
const timer = setTimeout(resolve3, ms);
|
|
@@ -69062,7 +69806,7 @@ async function defaultSleep8(ms, signal) {
|
|
|
69062
69806
|
async function pollExtractionStatus(client, contractFileIds, options = {}) {
|
|
69063
69807
|
const interval = options.pollIntervalMs ?? EXTRACT_POLL_INTERVAL_MS;
|
|
69064
69808
|
const timeout = options.timeoutMs ?? EXTRACT_POLL_TIMEOUT_MS;
|
|
69065
|
-
const sleep3 = options.sleep ??
|
|
69809
|
+
const sleep3 = options.sleep ?? defaultSleep9;
|
|
69066
69810
|
const now = options.now ?? Date.now;
|
|
69067
69811
|
const resolved = /* @__PURE__ */ new Map();
|
|
69068
69812
|
if (contractFileIds.length === 0) return { resolved, pending: [] };
|
|
@@ -69129,14 +69873,14 @@ async function bulkCreateContracts(client, body, options) {
|
|
|
69129
69873
|
throw new Error(`bulkCreateContracts: max 10 contracts per call, got ${body.contracts.length}`);
|
|
69130
69874
|
}
|
|
69131
69875
|
const out = await client.postJSON(
|
|
69132
|
-
`${
|
|
69876
|
+
`${BASE4}/contracts/bulk-create`,
|
|
69133
69877
|
body,
|
|
69134
69878
|
options
|
|
69135
69879
|
);
|
|
69136
69880
|
return out ?? { contracts: [] };
|
|
69137
69881
|
}
|
|
69138
69882
|
async function partialUpdateContract(client, contractId, body, options) {
|
|
69139
|
-
return client.patchJSON(`${
|
|
69883
|
+
return client.patchJSON(`${BASE4}/contracts/${contractId}`, body, options);
|
|
69140
69884
|
}
|
|
69141
69885
|
|
|
69142
69886
|
// src/types/spendControl.ts
|
|
@@ -69556,7 +70300,7 @@ function chunkContracts(contracts, size = 10) {
|
|
|
69556
70300
|
}
|
|
69557
70301
|
|
|
69558
70302
|
// src/api/spendControlVendors.ts
|
|
69559
|
-
var
|
|
70303
|
+
var BASE5 = "/api/spend-control/v1/vendors";
|
|
69560
70304
|
var MAX_PAGE_SIZE2 = 100;
|
|
69561
70305
|
function buildVendorListQuery(params) {
|
|
69562
70306
|
const qs = new URLSearchParams();
|
|
@@ -69587,19 +70331,19 @@ function emptyPage() {
|
|
|
69587
70331
|
return { items: [], total: 0, page: null, size: null, pages: null };
|
|
69588
70332
|
}
|
|
69589
70333
|
async function listVendors(client, params = {}, options) {
|
|
69590
|
-
const out = await client.getJSON(withQuery(`${
|
|
70334
|
+
const out = await client.getJSON(withQuery(`${BASE5}/`, params), options);
|
|
69591
70335
|
return out ?? emptyPage();
|
|
69592
70336
|
}
|
|
69593
70337
|
async function listAppStoreVendors(client, params = {}, options) {
|
|
69594
70338
|
const out = await client.getJSON(
|
|
69595
|
-
withQuery(`${
|
|
70339
|
+
withQuery(`${BASE5}/app-store`, params),
|
|
69596
70340
|
options
|
|
69597
70341
|
);
|
|
69598
70342
|
return out ?? emptyPage();
|
|
69599
70343
|
}
|
|
69600
70344
|
async function listVendorsWithContracts(client, params = {}, options) {
|
|
69601
70345
|
const out = await client.getJSON(
|
|
69602
|
-
withQuery(`${
|
|
70346
|
+
withQuery(`${BASE5}/with-contracts`, params),
|
|
69603
70347
|
options
|
|
69604
70348
|
);
|
|
69605
70349
|
return out ?? emptyPage();
|
|
@@ -69616,24 +70360,24 @@ async function listAllVendors(fetchPage, client, params = {}, options) {
|
|
|
69616
70360
|
return collected;
|
|
69617
70361
|
}
|
|
69618
70362
|
async function listVendorCategories(client, options) {
|
|
69619
|
-
const out = await client.getJSON(`${
|
|
70363
|
+
const out = await client.getJSON(`${BASE5}/categories`, options);
|
|
69620
70364
|
return out ?? [];
|
|
69621
70365
|
}
|
|
69622
70366
|
async function listVendorsWithMultipleContracts(client, options) {
|
|
69623
|
-
const out = await client.getJSON(`${
|
|
70367
|
+
const out = await client.getJSON(`${BASE5}/with-multiple-contracts`, options);
|
|
69624
70368
|
return out ?? [];
|
|
69625
70369
|
}
|
|
69626
70370
|
async function getVendorOwners(client, vendorId, options) {
|
|
69627
|
-
return client.getJSON(`${
|
|
70371
|
+
return client.getJSON(`${BASE5}/${encodeURIComponent(vendorId)}/owners/`, options);
|
|
69628
70372
|
}
|
|
69629
70373
|
async function createVendor(client, name, options) {
|
|
69630
|
-
return client.postJSON(`${
|
|
70374
|
+
return client.postJSON(`${BASE5}/`, { name }, options);
|
|
69631
70375
|
}
|
|
69632
70376
|
async function bulkUpdateVendorVisibility(client, vendors, options) {
|
|
69633
70377
|
if (vendors.length === 0) {
|
|
69634
70378
|
throw new Error("bulkUpdateVendorVisibility: at least one vendor is required");
|
|
69635
70379
|
}
|
|
69636
|
-
const out = await client.patchJSON(`${
|
|
70380
|
+
const out = await client.patchJSON(`${BASE5}/visibility`, { vendors }, options);
|
|
69637
70381
|
return out ?? [];
|
|
69638
70382
|
}
|
|
69639
70383
|
|
|
@@ -74746,103 +75490,37 @@ import { promises as fs36 } from "fs";
|
|
|
74746
75490
|
import { Option as Option4 } from "commander";
|
|
74747
75491
|
|
|
74748
75492
|
// src/api/templateLibrary.ts
|
|
74749
|
-
var
|
|
75493
|
+
var BASE6 = "/api/report_library/management";
|
|
74750
75494
|
async function list28(client, options) {
|
|
74751
|
-
return await client.getJSON(
|
|
75495
|
+
return await client.getJSON(BASE6, options) ?? [];
|
|
74752
75496
|
}
|
|
74753
75497
|
async function create19(client, body, options) {
|
|
74754
|
-
const out = await client.postJSON(
|
|
75498
|
+
const out = await client.postJSON(BASE6, body, options);
|
|
74755
75499
|
if (out === null) {
|
|
74756
|
-
throw new Error(`unexpected empty response from POST ${
|
|
75500
|
+
throw new Error(`unexpected empty response from POST ${BASE6}`);
|
|
74757
75501
|
}
|
|
74758
75502
|
return out;
|
|
74759
75503
|
}
|
|
74760
75504
|
async function update12(client, id, body, options) {
|
|
74761
|
-
const out = await client.putJSON(`${
|
|
75505
|
+
const out = await client.putJSON(`${BASE6}/${id}`, body, options);
|
|
74762
75506
|
if (out === null) {
|
|
74763
|
-
throw new Error(`unexpected empty response from PUT ${
|
|
75507
|
+
throw new Error(`unexpected empty response from PUT ${BASE6}/${id}`);
|
|
74764
75508
|
}
|
|
74765
75509
|
return out;
|
|
74766
75510
|
}
|
|
74767
75511
|
async function uploadImage(client, id, png, filename, options) {
|
|
74768
75512
|
const out = await client.putMultipart(
|
|
74769
|
-
`${
|
|
75513
|
+
`${BASE6}/${id}`,
|
|
74770
75514
|
{},
|
|
74771
75515
|
new Blob([png], { type: "image/png" }),
|
|
74772
75516
|
{ ...options, fileFieldName: "image", filename }
|
|
74773
75517
|
);
|
|
74774
75518
|
if (out === null) {
|
|
74775
|
-
throw new Error(`unexpected empty response from PUT ${
|
|
75519
|
+
throw new Error(`unexpected empty response from PUT ${BASE6}/${id} (image)`);
|
|
74776
75520
|
}
|
|
74777
75521
|
return out;
|
|
74778
75522
|
}
|
|
74779
75523
|
|
|
74780
|
-
// src/commands/_agentCli.ts
|
|
74781
|
-
import { access } from "fs/promises";
|
|
74782
|
-
var JSON_HELP = "Emit raw JSON to stdout instead of a pretty render.";
|
|
74783
|
-
var CONFIRM_HELP = "Token from a prior plan run \u2014 required to actually write";
|
|
74784
|
-
function withTarget(cmd) {
|
|
74785
|
-
addServerFlag(cmd);
|
|
74786
|
-
addAccountFlag(cmd);
|
|
74787
|
-
return cmd;
|
|
74788
|
-
}
|
|
74789
|
-
function std() {
|
|
74790
|
-
return { out: process.stdout, err: process.stderr };
|
|
74791
|
-
}
|
|
74792
|
-
function requireId(value, placeholder) {
|
|
74793
|
-
const id = (value ?? "").trim();
|
|
74794
|
-
if (id === "") {
|
|
74795
|
-
throw new Error(`missing required option: --id ${placeholder}`);
|
|
74796
|
-
}
|
|
74797
|
-
return id;
|
|
74798
|
-
}
|
|
74799
|
-
function requirePositiveInt(value, flag) {
|
|
74800
|
-
const n = /^\d+$/.test(value) ? Number(value) : NaN;
|
|
74801
|
-
if (!Number.isSafeInteger(n) || n <= 0) {
|
|
74802
|
-
throw new Error(`${flag} ${JSON.stringify(value)}: expected a positive integer`);
|
|
74803
|
-
}
|
|
74804
|
-
return n;
|
|
74805
|
-
}
|
|
74806
|
-
function requireBool(value, flag) {
|
|
74807
|
-
if (value === "true") return true;
|
|
74808
|
-
if (value === "false") return false;
|
|
74809
|
-
throw new Error(`${flag} ${JSON.stringify(value ?? "")}: expected true or false`);
|
|
74810
|
-
}
|
|
74811
|
-
function splitList(value) {
|
|
74812
|
-
return [
|
|
74813
|
-
...new Set(
|
|
74814
|
-
(value ?? "").split(",").map((part) => part.trim()).filter((part) => part !== "")
|
|
74815
|
-
)
|
|
74816
|
-
];
|
|
74817
|
-
}
|
|
74818
|
-
function isNoneSentinel(parts, flag, value, what) {
|
|
74819
|
-
if (!parts.includes("none")) return false;
|
|
74820
|
-
if (parts.length === 1) return true;
|
|
74821
|
-
throw new Error(`${flag} ${JSON.stringify(value)}: "none" cannot be combined with ${what}`);
|
|
74822
|
-
}
|
|
74823
|
-
function shellArg(value) {
|
|
74824
|
-
return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : shellQuote(value);
|
|
74825
|
-
}
|
|
74826
|
-
function proseStream(cfg) {
|
|
74827
|
-
return cfg.asJson ? cfg.err : cfg.out;
|
|
74828
|
-
}
|
|
74829
|
-
async function assertNoOverwrite(paths, force) {
|
|
74830
|
-
if (force) return;
|
|
74831
|
-
const existing = [];
|
|
74832
|
-
for (const path13 of paths) {
|
|
74833
|
-
try {
|
|
74834
|
-
await access(path13);
|
|
74835
|
-
existing.push(path13);
|
|
74836
|
-
} catch {
|
|
74837
|
-
}
|
|
74838
|
-
}
|
|
74839
|
-
if (existing.length > 0) {
|
|
74840
|
-
throw new Error(
|
|
74841
|
-
`refusing to overwrite existing file(s): ${existing.join(", ")}. Pull into another --dir, or pass --force to replace them.`
|
|
74842
|
-
);
|
|
74843
|
-
}
|
|
74844
|
-
}
|
|
74845
|
-
|
|
74846
75524
|
// src/commands/_templateLibraryImages.ts
|
|
74847
75525
|
var TEMPLATE_LIBRARY_IMAGE_COLORS = ["pink", "purple", "yellow"];
|
|
74848
75526
|
var TEMPLATE_LIBRARY_IMAGE_FILENAMES = {
|
|
@@ -75699,11 +76377,11 @@ import { mkdir as mkdir5, writeFile as writeFile8 } from "fs/promises";
|
|
|
75699
76377
|
import { join as join18 } from "path";
|
|
75700
76378
|
|
|
75701
76379
|
// src/api/aiService.ts
|
|
75702
|
-
import { z as
|
|
76380
|
+
import { z as z4 } from "zod";
|
|
75703
76381
|
var AI_SERVICE_MINT_PATH = "/api/ai-service/v1/Authentication/authenticate";
|
|
75704
76382
|
var ERROR_BODY_MAX_CHARS = 1e3;
|
|
75705
|
-
var MintResponseSchema =
|
|
75706
|
-
var ErrorEnvelopeSchema =
|
|
76383
|
+
var MintResponseSchema = z4.object({ token: z4.string().min(1) });
|
|
76384
|
+
var ErrorEnvelopeSchema = z4.object({ code: z4.string(), message: z4.string() });
|
|
75707
76385
|
function parseErrorEnvelope(text2) {
|
|
75708
76386
|
let parsed2;
|
|
75709
76387
|
try {
|
package/dist/scopes-map.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"cliVersion": "1.6.
|
|
3
|
+
"cliVersion": "1.6.181",
|
|
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": {
|
|
@@ -4089,6 +4089,38 @@
|
|
|
4089
4089
|
}
|
|
4090
4090
|
]
|
|
4091
4091
|
},
|
|
4092
|
+
"calcmodels agent ask": {
|
|
4093
|
+
"requires": [
|
|
4094
|
+
{
|
|
4095
|
+
"resource": "tables",
|
|
4096
|
+
"action": "write"
|
|
4097
|
+
}
|
|
4098
|
+
]
|
|
4099
|
+
},
|
|
4100
|
+
"calcmodels agent preview": {
|
|
4101
|
+
"requires": [
|
|
4102
|
+
{
|
|
4103
|
+
"resource": "tables",
|
|
4104
|
+
"action": "read"
|
|
4105
|
+
}
|
|
4106
|
+
]
|
|
4107
|
+
},
|
|
4108
|
+
"calcmodels agent save": {
|
|
4109
|
+
"requires": [
|
|
4110
|
+
{
|
|
4111
|
+
"resource": "tables",
|
|
4112
|
+
"action": "write"
|
|
4113
|
+
}
|
|
4114
|
+
]
|
|
4115
|
+
},
|
|
4116
|
+
"calcmodels agent approve": {
|
|
4117
|
+
"requires": [
|
|
4118
|
+
{
|
|
4119
|
+
"resource": "tables",
|
|
4120
|
+
"action": "destructive"
|
|
4121
|
+
}
|
|
4122
|
+
]
|
|
4123
|
+
},
|
|
4092
4124
|
"calc-vals list": {
|
|
4093
4125
|
"requires": [
|
|
4094
4126
|
{
|
|
@@ -233,6 +233,7 @@ area's gotchas.
|
|
|
233
233
|
|
|
234
234
|
**Calc models** → [reference/calcmodels.md](reference/calcmodels.md)
|
|
235
235
|
- `calcmodels list|get|create|update|set-flow|recalculate|delete` (alias `calc-models`)
|
|
236
|
+
- `calcmodels agent ask|preview|save|approve` — build an AGENT (allocation) model with the builder agent; `approve` is Tier A
|
|
236
237
|
|
|
237
238
|
**Calculated values (per-table)** → [reference/calc-vals.md](reference/calc-vals.md)
|
|
238
239
|
- `calc-vals list|get|create|update|delete` (alias `calcvals`) — `--table <id>` required on every subcommand
|
|
@@ -69,6 +69,60 @@ names to integer IDs against the tenant — never ask the operator for IDs:
|
|
|
69
69
|
For `update`: `dr calcmodels get <id> --server <S> --json` → splice the requested
|
|
70
70
|
change → write to a tmpfile → `dr calcmodels update <id> --file <tmpfile> --server <S>`.
|
|
71
71
|
|
|
72
|
+
## AGENT models (builder)
|
|
73
|
+
|
|
74
|
+
An AGENT model is an allocation model that the calc model builder's agent writes
|
|
75
|
+
from plain English. `dr calcmodels agent` uses the web builder's endpoints. The
|
|
76
|
+
server allows it only for a Super Admin, on an org with the
|
|
77
|
+
`calc_model_agent_builder` flag, inside a per-org cap on running turns. The CLI
|
|
78
|
+
does not check these; it prints the server's 403 or 429. `create`/`update` above
|
|
79
|
+
are for the three older types only.
|
|
80
|
+
|
|
81
|
+
Flow:
|
|
82
|
+
|
|
83
|
+
1. `dr calcmodels agent ask "<what to build>" --server <S>` — one turn. It
|
|
84
|
+
prints the operation id at once, then waits (3 s polls, default 900 s,
|
|
85
|
+
`--timeout <s>`). Ctrl-C or the timeout stop the wait only; read the turn
|
|
86
|
+
later with `dr calcmodels agent preview <operation_id> --server <S>`.
|
|
87
|
+
2. Read the outcome by `status`:
|
|
88
|
+
- `NO_PROPOSAL` — the agent asks a question. Ask the operator. Do not answer
|
|
89
|
+
it yourself. Then `dr calcmodels agent ask "<reply>" --conversation <id> --server <S>`,
|
|
90
|
+
with the conversation id the answer printed.
|
|
91
|
+
- `SUCCEEDED` — a proposal and its preview: rows, coverage (allocated of
|
|
92
|
+
pool), balance groups, warnings. Show these to the operator. To change it,
|
|
93
|
+
reply in the same conversation. "preview: none ran" means it cannot be saved.
|
|
94
|
+
- `FAILED` — exits non-zero with the gate code, phase and suggestion.
|
|
95
|
+
3. `dr calcmodels agent save <operation_id> [--name <n>] --server <S>` — saves a
|
|
96
|
+
DRAFT. The name defaults to the agent's proposed name. The new version
|
|
97
|
+
does not run until it is approved. `--model <id>` adds the turn as a new
|
|
98
|
+
version of that AGENT model; an active model keeps running its current
|
|
99
|
+
version until then.
|
|
100
|
+
4. `dr calcmodels agent approve <id> --server <S>` — two-step. The plan prints
|
|
101
|
+
the version and its impact (rows the first run writes, allocated total,
|
|
102
|
+
pool total, coverage). Show it to the operator and stop. Run the printed
|
|
103
|
+
command (`--version-id <n> --confirm-token <t>`) only after the operator
|
|
104
|
+
gives a clear in-chat "yes" to that version and impact, on every server.
|
|
105
|
+
Approving activates the version and starts its first production run at once.
|
|
106
|
+
|
|
107
|
+
Gotchas:
|
|
108
|
+
|
|
109
|
+
- `approve` is Tier A (see safety.md). On a production server the token run
|
|
110
|
+
also opens a dialog or terminal prompt where the person types a word; with
|
|
111
|
+
no interactive channel the binary refuses. On a non-production server the
|
|
112
|
+
token alone activates, so the operator's in-chat "yes" is the only check.
|
|
113
|
+
- `save` never activates, and `ask`/`save` write no rows to the destination
|
|
114
|
+
table. Only `approve` starts a run that does.
|
|
115
|
+
- A newer version saved after the plan stops the token run before anything is
|
|
116
|
+
activated ("a newer version … was saved"). Re-run `approve` without the
|
|
117
|
+
token and show the new impact.
|
|
118
|
+
- `CALC_PREVIEW_ALREADY_SAVED`: that turn is already model N. The error
|
|
119
|
+
prints the `save … --model N` command for a new version.
|
|
120
|
+
- `CALC_TURN_RATE_LIMITED` (429): the org has too many running turns. Wait.
|
|
121
|
+
A 409 means a turn already runs on this conversation.
|
|
122
|
+
- Only the user who started a turn can read or continue it (404 otherwise).
|
|
123
|
+
- Out of scope: opening a saved model's definition for edit (no server read
|
|
124
|
+
endpoint), archiving AGENT models.
|
|
125
|
+
|
|
72
126
|
## Customer-facing docs (KB)
|
|
73
127
|
|
|
74
128
|
Help Center → **"Getting Data into Datarails"** → Data Management. Find current articles with `dr kb search "calc model beginning balance"` (or
|
|
@@ -21,6 +21,7 @@ Gated commands include: `dashboards delete`, `datamappers delete`,
|
|
|
21
21
|
`dynamic-ranges delete`, `extables delete`, `functions delete`, `lut delete`,
|
|
22
22
|
`reports delete`, `storyboards delete`, `metrics delete`, `metrics categories
|
|
23
23
|
delete`, `metrics dimensions delete`, `calcmodels delete|set-flow|recalculate`,
|
|
24
|
+
`calcmodels agent approve`,
|
|
24
25
|
`reconciliation filebox delete`, `integrations delete`, `integrations it
|
|
25
26
|
delete`, `devices command-delete`, `validate onboarding session delete`,
|
|
26
27
|
`widgets delete`, `cash rules delete|delete-all|reorder|update`,
|
|
@@ -67,6 +68,9 @@ organization at once);
|
|
|
67
68
|
`agents pin` / `unpin` (decide which agent version customers get on a server; one
|
|
68
69
|
run with `--production` covers every production server, so relay the plan's
|
|
69
70
|
per-server lines — they are also the undo);
|
|
71
|
+
`calcmodels agent approve` (activates an AGENT calc model and starts its first
|
|
72
|
+
production run, which writes new rows; the token is bound to the version whose
|
|
73
|
+
impact the plan printed);
|
|
70
74
|
`error-enrichments import` (replaces every rule on the server, zeroes all
|
|
71
75
|
hits/feedback counters, and reassigns every rule id); and
|
|
72
76
|
`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.
|
|
3
|
+
"version": "1.6.181",
|
|
4
4
|
"description": "Datarails CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@datarails-ai/dr-appspace-cli": "0.12.
|
|
20
|
+
"@datarails-ai/dr-appspace-cli": "0.12.58",
|
|
21
21
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
22
22
|
"@napi-rs/keyring": "^1.3.0",
|
|
23
23
|
"commander": "^12.1.0",
|