@arbidocs/cli 0.4.76 → 0.4.78
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/CHANGELOG.md +28 -0
- package/SKILL.md +46 -5
- package/dist/index.js +742 -79
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -12,8 +12,8 @@ var child_process = require('child_process');
|
|
|
12
12
|
var client = require('@arbidocs/client');
|
|
13
13
|
var url = require('url');
|
|
14
14
|
var crypto = require('crypto');
|
|
15
|
-
var module$1 = require('module');
|
|
16
15
|
var yaml = require('yaml');
|
|
16
|
+
var module$1 = require('module');
|
|
17
17
|
|
|
18
18
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
19
19
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -3695,7 +3695,7 @@ function getLatestVersion(skipCache = false) {
|
|
|
3695
3695
|
}
|
|
3696
3696
|
}
|
|
3697
3697
|
function getCurrentVersion() {
|
|
3698
|
-
return "0.4.
|
|
3698
|
+
return "0.4.78";
|
|
3699
3699
|
}
|
|
3700
3700
|
function readChangelog(fromVersion, toVersion) {
|
|
3701
3701
|
try {
|
|
@@ -3748,17 +3748,17 @@ function showChangelog(fromVersion, toVersion) {
|
|
|
3748
3748
|
async function checkForUpdates(autoUpdate) {
|
|
3749
3749
|
try {
|
|
3750
3750
|
const latest = getLatestVersion();
|
|
3751
|
-
if (!latest || latest === "0.4.
|
|
3751
|
+
if (!latest || latest === "0.4.78") return;
|
|
3752
3752
|
if (autoUpdate) {
|
|
3753
3753
|
warn(`
|
|
3754
|
-
Your arbi version is out of date (${"0.4.
|
|
3754
|
+
Your arbi version is out of date (${"0.4.78"} \u2192 ${latest}). Updating...`);
|
|
3755
3755
|
child_process.execSync("npm install -g @arbidocs/cli@latest", { stdio: "inherit" });
|
|
3756
|
-
showChangelog("0.4.
|
|
3756
|
+
showChangelog("0.4.78", latest);
|
|
3757
3757
|
console.log(`Updated to ${latest}.`);
|
|
3758
3758
|
} else {
|
|
3759
3759
|
warn(
|
|
3760
3760
|
`
|
|
3761
|
-
Your arbi version is out of date (${"0.4.
|
|
3761
|
+
Your arbi version is out of date (${"0.4.78"} \u2192 ${latest}).
|
|
3762
3762
|
Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
|
|
3763
3763
|
);
|
|
3764
3764
|
}
|
|
@@ -3790,10 +3790,10 @@ function markNagShown(latest) {
|
|
|
3790
3790
|
function hintUpdateOnError() {
|
|
3791
3791
|
try {
|
|
3792
3792
|
const cached = readCache();
|
|
3793
|
-
if (!cached || cached.latest === "0.4.
|
|
3793
|
+
if (!cached || cached.latest === "0.4.78") return;
|
|
3794
3794
|
if (!shouldShowNag(cached.latest)) return;
|
|
3795
3795
|
warn(
|
|
3796
|
-
`Your arbi version is out of date (${"0.4.
|
|
3796
|
+
`Your arbi version is out of date (${"0.4.78"} \u2192 ${cached.latest}). Run "arbi update".`
|
|
3797
3797
|
);
|
|
3798
3798
|
markNagShown(cached.latest);
|
|
3799
3799
|
} catch {
|
|
@@ -3964,6 +3964,10 @@ function stringifyApiDetail(value) {
|
|
|
3964
3964
|
const rec = value;
|
|
3965
3965
|
if (typeof rec.detail === "string") return rec.detail;
|
|
3966
3966
|
if (Array.isArray(rec.detail)) return stringifyApiDetail(rec.detail);
|
|
3967
|
+
if (rec.fields && typeof rec.fields === "object" && !Array.isArray(rec.fields)) {
|
|
3968
|
+
const fields = Object.entries(rec.fields).map(([name, problem]) => `${name}: ${String(problem)}`).join("; ");
|
|
3969
|
+
return typeof rec.message === "string" ? `${rec.message} (${fields})` : fields;
|
|
3970
|
+
}
|
|
3967
3971
|
if (typeof rec.message === "string") return rec.message;
|
|
3968
3972
|
if (typeof rec.error === "string") return rec.error;
|
|
3969
3973
|
try {
|
|
@@ -8473,6 +8477,174 @@ function registerTimelineCommand(program2) {
|
|
|
8473
8477
|
);
|
|
8474
8478
|
}
|
|
8475
8479
|
init_prompts();
|
|
8480
|
+
function readStructured(path8) {
|
|
8481
|
+
let text;
|
|
8482
|
+
try {
|
|
8483
|
+
text = fs7.readFileSync(path8, "utf8");
|
|
8484
|
+
} catch (err) {
|
|
8485
|
+
throw new Error(`Cannot read ${path8}: ${err.message}`);
|
|
8486
|
+
}
|
|
8487
|
+
const ext = path6.extname(path8).toLowerCase();
|
|
8488
|
+
try {
|
|
8489
|
+
return ext === ".json" ? JSON.parse(text) : yaml.parse(text);
|
|
8490
|
+
} catch (err) {
|
|
8491
|
+
throw new Error(
|
|
8492
|
+
`${path8} is not valid ${ext === ".json" ? "JSON" : "YAML"}: ${err.message}`
|
|
8493
|
+
);
|
|
8494
|
+
}
|
|
8495
|
+
}
|
|
8496
|
+
function readStepsFile(path8) {
|
|
8497
|
+
try {
|
|
8498
|
+
return sdk.workflowSteps.parsePlanSteps(readStructured(path8));
|
|
8499
|
+
} catch (err) {
|
|
8500
|
+
if (err instanceof sdk.PlanStepsError) throw new Error(`${path8}: ${err.message}`);
|
|
8501
|
+
throw err;
|
|
8502
|
+
}
|
|
8503
|
+
}
|
|
8504
|
+
function readInputsFile(path8) {
|
|
8505
|
+
const value = readStructured(path8);
|
|
8506
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
8507
|
+
throw new Error(`${path8}: expected an object of input values, e.g. {"client": "Acme"}`);
|
|
8508
|
+
return value;
|
|
8509
|
+
}
|
|
8510
|
+
function collect(value, previous = []) {
|
|
8511
|
+
return [...previous, value];
|
|
8512
|
+
}
|
|
8513
|
+
var GAP = "\u2014";
|
|
8514
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
8515
|
+
function parseFieldDefinitions(arg) {
|
|
8516
|
+
let value;
|
|
8517
|
+
const trimmed = arg.trim();
|
|
8518
|
+
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
|
|
8519
|
+
try {
|
|
8520
|
+
value = JSON.parse(trimmed);
|
|
8521
|
+
} catch (err) {
|
|
8522
|
+
throw new Error(`--define is not valid JSON: ${err.message}`);
|
|
8523
|
+
}
|
|
8524
|
+
} else if (fs7.existsSync(arg)) value = readStructured(arg);
|
|
8525
|
+
else
|
|
8526
|
+
throw new Error(
|
|
8527
|
+
`--define takes JSON (e.g. '[{"name":"budget","type":"number"}]') or a .json/.yaml file; no file "${arg}"`
|
|
8528
|
+
);
|
|
8529
|
+
const list = isRecord(value) ? value.fields : value;
|
|
8530
|
+
if (!Array.isArray(list))
|
|
8531
|
+
throw new Error('--define: expected a list of fields, or an object with a "fields" list');
|
|
8532
|
+
const problems = [];
|
|
8533
|
+
const names = /* @__PURE__ */ new Set();
|
|
8534
|
+
const out = list.map((raw, i) => {
|
|
8535
|
+
const at = `fields[${i}]`;
|
|
8536
|
+
if (!isRecord(raw)) {
|
|
8537
|
+
problems.push(`${at}: must be an object with a name and a type`);
|
|
8538
|
+
return { name: "", type: "text" };
|
|
8539
|
+
}
|
|
8540
|
+
for (const key of Object.keys(raw))
|
|
8541
|
+
if (!["id", "name", "type", "options"].includes(key))
|
|
8542
|
+
problems.push(`${at}.${key}: unknown field (allowed: name, type, options, id)`);
|
|
8543
|
+
const name = typeof raw.name === "string" ? raw.name.trim() : "";
|
|
8544
|
+
if (!name || name.length > 200)
|
|
8545
|
+
problems.push(`${at}.name: required text (up to 200 characters)`);
|
|
8546
|
+
else if (names.has(name.toLowerCase())) problems.push(`${at}.name: "${name}" is used twice`);
|
|
8547
|
+
names.add(name.toLowerCase());
|
|
8548
|
+
const type = raw.type ?? "text";
|
|
8549
|
+
if (!sdk.tags.TAG_FIELD_TYPES.includes(type))
|
|
8550
|
+
problems.push(
|
|
8551
|
+
`${at}.type: must be one of ${sdk.tags.TAG_FIELD_TYPES.join(", ")} (got ${JSON.stringify(type)})`
|
|
8552
|
+
);
|
|
8553
|
+
const options = raw.options;
|
|
8554
|
+
if (options !== void 0 && options !== null) {
|
|
8555
|
+
if (!Array.isArray(options) || options.some((o) => typeof o !== "string"))
|
|
8556
|
+
problems.push(`${at}.options: a list of text, e.g. ["low", "high"]`);
|
|
8557
|
+
else if (type !== "select") problems.push(`${at}.options: only a select field has options`);
|
|
8558
|
+
} else if (type === "select") problems.push(`${at}.options: a select field needs its options`);
|
|
8559
|
+
if (raw.id !== void 0 && (typeof raw.id !== "string" || !/^fld-[a-f0-9]{6}$/.test(raw.id)))
|
|
8560
|
+
problems.push(`${at}.id: an existing field id (fld-\u2026), or leave it out`);
|
|
8561
|
+
return {
|
|
8562
|
+
...typeof raw.id === "string" ? { id: raw.id } : {},
|
|
8563
|
+
name,
|
|
8564
|
+
type,
|
|
8565
|
+
...Array.isArray(options) ? { options } : {}
|
|
8566
|
+
};
|
|
8567
|
+
});
|
|
8568
|
+
if (problems.length)
|
|
8569
|
+
throw new Error(`Invalid field definitions:
|
|
8570
|
+
${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
8571
|
+
return out;
|
|
8572
|
+
}
|
|
8573
|
+
function parseFieldValues(pairs, fields) {
|
|
8574
|
+
const out = {};
|
|
8575
|
+
for (const pair of pairs) {
|
|
8576
|
+
const eq = pair.indexOf("=");
|
|
8577
|
+
if (eq <= 0) throw new Error(`Invalid --set "${pair}": use name=value (name= clears it)`);
|
|
8578
|
+
const name = pair.slice(0, eq).trim();
|
|
8579
|
+
const value = pair.slice(eq + 1);
|
|
8580
|
+
const field = fields.find((f) => f.name.toLowerCase() === name.toLowerCase() || f.id === name);
|
|
8581
|
+
if (!field)
|
|
8582
|
+
throw new Error(
|
|
8583
|
+
`No field "${name}" on this tag. Its fields: ${fields.map((f) => f.name).join(", ") || "(none: add some with --define)"}`
|
|
8584
|
+
);
|
|
8585
|
+
if (value.trim() === "") out[field.name] = null;
|
|
8586
|
+
else if (field.type === "select" && field.options?.length) {
|
|
8587
|
+
const option = field.options.find((o) => o.toLowerCase() === value.trim().toLowerCase());
|
|
8588
|
+
if (!option)
|
|
8589
|
+
throw new Error(`"${field.name}" is one of ${field.options.join(", ")}; got "${value}"`);
|
|
8590
|
+
out[field.name] = option;
|
|
8591
|
+
} else out[field.name] = value;
|
|
8592
|
+
}
|
|
8593
|
+
return out;
|
|
8594
|
+
}
|
|
8595
|
+
function formatFieldValue(value) {
|
|
8596
|
+
if (value === void 0 || value === null || value === "") return GAP;
|
|
8597
|
+
if (typeof value === "boolean") return value ? "yes" : "no";
|
|
8598
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
8599
|
+
return String(value);
|
|
8600
|
+
}
|
|
8601
|
+
function fieldRows(tag) {
|
|
8602
|
+
return (tag.fields ?? []).map((f) => ({
|
|
8603
|
+
id: f.id,
|
|
8604
|
+
name: f.name,
|
|
8605
|
+
type: f.type === "select" && f.options?.length ? `select: ${f.options.join("|")}` : f.type,
|
|
8606
|
+
value: formatFieldValue(tag.field_values?.[f.id]),
|
|
8607
|
+
from: f.tag_ext_id === tag.external_id ? "own" : `inherited (${f.tag_ext_id})`
|
|
8608
|
+
}));
|
|
8609
|
+
}
|
|
8610
|
+
function matrixTable(matrix) {
|
|
8611
|
+
const columns = sdk.tags.tagMatrixColumns(matrix);
|
|
8612
|
+
const headers = ["TAG", ...columns.map((c) => c.name.toUpperCase()), "DOCS", "FACTS"];
|
|
8613
|
+
const rows = [];
|
|
8614
|
+
const gaps = [];
|
|
8615
|
+
for (const row of matrix.rows ?? []) {
|
|
8616
|
+
const has = new Set((row.tag.fields ?? []).map((f) => f.id));
|
|
8617
|
+
const cells = columns.map(
|
|
8618
|
+
(c) => has.has(c.id) ? formatFieldValue(row.tag.field_values?.[c.id]) : "n/a"
|
|
8619
|
+
);
|
|
8620
|
+
rows.push([
|
|
8621
|
+
row.tag.name,
|
|
8622
|
+
...cells,
|
|
8623
|
+
row.doc_count ? String(row.doc_count) : GAP,
|
|
8624
|
+
row.fact_count ? String(row.fact_count) : GAP
|
|
8625
|
+
]);
|
|
8626
|
+
const missing = columns.filter((c, i) => has.has(c.id) && cells[i] === GAP).map((c) => c.name);
|
|
8627
|
+
if (!row.doc_count) missing.push("documents");
|
|
8628
|
+
if (!row.fact_count) missing.push("facts");
|
|
8629
|
+
if (missing.length) gaps.push(`${row.tag.name}: no ${missing.join(", no ")}`);
|
|
8630
|
+
}
|
|
8631
|
+
return { headers, rows, gaps };
|
|
8632
|
+
}
|
|
8633
|
+
function renderTable(headers, rows, max = 28) {
|
|
8634
|
+
const clip = (s) => s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
8635
|
+
const widths = headers.map(
|
|
8636
|
+
(h, i) => Math.min(max, Math.max(h.length, ...rows.map((r) => clip(r[i] ?? "").length)))
|
|
8637
|
+
);
|
|
8638
|
+
const line = (cells) => cells.map((c, i) => clip(c ?? "").padEnd(widths[i])).join(" ").trimEnd();
|
|
8639
|
+
return [line(headers), ...rows.map(line)];
|
|
8640
|
+
}
|
|
8641
|
+
|
|
8642
|
+
// src/commands/tags.ts
|
|
8643
|
+
async function resolveTag(arbi, selector) {
|
|
8644
|
+
const res = resolveByIdOrName(await sdk.tags.listTags(arbi), selector);
|
|
8645
|
+
if (!res.ok) failResolveAndExit("Tag", selector, res);
|
|
8646
|
+
return res.item;
|
|
8647
|
+
}
|
|
8476
8648
|
async function fetchTagChoices(arbi) {
|
|
8477
8649
|
const data = await sdk.tags.listTags(arbi);
|
|
8478
8650
|
if (data.length === 0) {
|
|
@@ -8489,7 +8661,7 @@ async function fetchTagChoices(arbi) {
|
|
|
8489
8661
|
};
|
|
8490
8662
|
}
|
|
8491
8663
|
function registerTagsCommand(program2) {
|
|
8492
|
-
const tags = program2.command("tags").description("Tags: list, create, delete, update, share, unshare, rename");
|
|
8664
|
+
const tags = program2.command("tags").description("Tags: list, create, delete, update, share, unshare, rename, fields, matrix");
|
|
8493
8665
|
tags.command("list").description("List tags in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--json", "Output as JSON").action(
|
|
8494
8666
|
(opts) => runAction(async () => {
|
|
8495
8667
|
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
@@ -8512,7 +8684,8 @@ function registerTagsCommand(program2) {
|
|
|
8512
8684
|
value: (r) => r.tag_type?.type ?? ""
|
|
8513
8685
|
},
|
|
8514
8686
|
{ header: "DOCS", width: 6, value: (r) => String(r.doctag_count) },
|
|
8515
|
-
{ header: "SHARED", width: 8, value: (r) => r.shared ? "yes" : "no" }
|
|
8687
|
+
{ header: "SHARED", width: 8, value: (r) => r.shared ? "yes" : "no" },
|
|
8688
|
+
{ header: "PARENT", width: 24, value: (r) => r.parent_ext_id ?? "" }
|
|
8516
8689
|
],
|
|
8517
8690
|
data
|
|
8518
8691
|
);
|
|
@@ -8522,9 +8695,10 @@ function registerTagsCommand(program2) {
|
|
|
8522
8695
|
"-t, --type <type>",
|
|
8523
8696
|
"Tag type (checkbox, text, number, select, folder, search, date, assistant)",
|
|
8524
8697
|
"checkbox"
|
|
8525
|
-
).option("-i, --instruction <text>", "Tag instruction").option("--shared", "Make tag shared", false).action(
|
|
8698
|
+
).option("-i, --instruction <text>", "Tag instruction").option("--shared", "Make tag shared", false).option("--parent <tag>", "Make it a child of this tag (id or name): it inherits its fields").option("--json", "Output as JSON").action(
|
|
8526
8699
|
(name, opts) => runAction(async () => {
|
|
8527
8700
|
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
8701
|
+
const parent = opts.parent ? await resolveTag(arbi, opts.parent) : void 0;
|
|
8528
8702
|
const interactive = !name;
|
|
8529
8703
|
if (!name) {
|
|
8530
8704
|
name = await promptInput("Tag name");
|
|
@@ -8548,9 +8722,16 @@ function registerTagsCommand(program2) {
|
|
|
8548
8722
|
options: []
|
|
8549
8723
|
},
|
|
8550
8724
|
instruction,
|
|
8551
|
-
shared
|
|
8725
|
+
shared,
|
|
8726
|
+
parentId: parent?.external_id ?? null
|
|
8552
8727
|
});
|
|
8553
|
-
|
|
8728
|
+
if (opts.json) {
|
|
8729
|
+
printJson(data);
|
|
8730
|
+
return;
|
|
8731
|
+
}
|
|
8732
|
+
success(
|
|
8733
|
+
`Created: ${data.name} (${ref(data.external_id)})${parent ? ` under ${parent.name}` : ""}`
|
|
8734
|
+
);
|
|
8554
8735
|
})()
|
|
8555
8736
|
);
|
|
8556
8737
|
tags.command("delete [id]").description("Delete a tag (interactive picker if no ID given)").option("--dry-run", "Preview which tag would be deleted (no SDK call)").action(
|
|
@@ -8615,6 +8796,95 @@ function registerTagsCommand(program2) {
|
|
|
8615
8796
|
success(`Updated: ${data.name} (${ref(data.external_id)})`);
|
|
8616
8797
|
})()
|
|
8617
8798
|
);
|
|
8799
|
+
tags.command("fields <tag>").description(
|
|
8800
|
+
"Show a tag's fields and values; --define its own fields (a child inherits its parent's), --set values"
|
|
8801
|
+
).option(
|
|
8802
|
+
"--define <json|file>",
|
|
8803
|
+
`Its own field definitions: JSON or a .json/.yaml file, e.g. '[{"name":"budget","type":"number"},{"name":"risk","type":"select","options":["low","high"]}]'. A field defined again by name keeps its values; ones left out are dropped unless --keep-others`
|
|
8804
|
+
).option("--keep-others", "With --define: keep its other own fields").option("--set <name=value>", "Set a field value (repeatable; name= clears it)", collect, []).option("--json", "Output as JSON").action(
|
|
8805
|
+
(selector, opts) => runAction(async () => {
|
|
8806
|
+
const defs = opts.define ? parseFieldDefinitions(opts.define) : void 0;
|
|
8807
|
+
const { arbi } = await resolveWorkspace();
|
|
8808
|
+
let tag = await resolveTag(arbi, selector);
|
|
8809
|
+
if (defs || opts.set.length) {
|
|
8810
|
+
const fields = defs ? sdk.tags.mergeTagFieldDefinitions(tag, defs, { keepOthers: !!opts.keepOthers }) : void 0;
|
|
8811
|
+
const known = fields ? [
|
|
8812
|
+
...(tag.fields ?? []).filter((f) => f.tag_ext_id !== tag.external_id),
|
|
8813
|
+
...fields.map((f) => ({
|
|
8814
|
+
id: f.id ?? "",
|
|
8815
|
+
name: f.name,
|
|
8816
|
+
type: f.type,
|
|
8817
|
+
options: f.options ?? null,
|
|
8818
|
+
tag_ext_id: tag.external_id
|
|
8819
|
+
}))
|
|
8820
|
+
] : tag.fields ?? [];
|
|
8821
|
+
const values = opts.set.length ? parseFieldValues(opts.set, known) : void 0;
|
|
8822
|
+
tag = await sdk.tags.updateTagFields(arbi, tag.external_id, { fields, values });
|
|
8823
|
+
}
|
|
8824
|
+
if (opts.json) {
|
|
8825
|
+
printJson(tag);
|
|
8826
|
+
return;
|
|
8827
|
+
}
|
|
8828
|
+
if (defs || opts.set.length)
|
|
8829
|
+
success(`Updated the fields of ${tag.name} (${ref(tag.external_id)})`);
|
|
8830
|
+
else bold(`${tag.name} (${tag.external_id})`);
|
|
8831
|
+
const rows = fieldRows(tag);
|
|
8832
|
+
if (!rows.length) {
|
|
8833
|
+
dim(
|
|
8834
|
+
`No fields. Define some: arbi tags fields ${tag.external_id} --define '[{"name":"budget","type":"number"}]'`
|
|
8835
|
+
);
|
|
8836
|
+
return;
|
|
8837
|
+
}
|
|
8838
|
+
printTable(
|
|
8839
|
+
[
|
|
8840
|
+
{ header: "FIELD", width: 22, value: (r) => r.name },
|
|
8841
|
+
{ header: "TYPE", width: 24, value: (r) => r.type },
|
|
8842
|
+
{ header: "VALUE", width: 28, value: (r) => r.value },
|
|
8843
|
+
{ header: "DEFINED", width: 30, value: (r) => r.from }
|
|
8844
|
+
],
|
|
8845
|
+
rows
|
|
8846
|
+
);
|
|
8847
|
+
})()
|
|
8848
|
+
);
|
|
8849
|
+
tags.command("matrix").description(
|
|
8850
|
+
"Tags as a table: rows are a parent's children (--parent) or the given tags (--tag), columns their fields, then document and fact counts; gaps marked \u2014"
|
|
8851
|
+
).option("--parent <tag>", "Rows are this tag\u2019s children (id or name)").option("--tag <tag>", "A row (id or name; repeatable, in this order)", collect, []).option("--links <n>", "Most documents and facts listed per row (JSON)").option("--json", "Output as JSON").action(
|
|
8852
|
+
(opts) => runAction(async () => {
|
|
8853
|
+
if (!opts.parent === !opts.tag.length)
|
|
8854
|
+
throw new Error("Pass --parent <tag>, or one or more --tag <tag>");
|
|
8855
|
+
const links = opts.links === void 0 ? void 0 : Number(opts.links);
|
|
8856
|
+
if (links !== void 0 && (!Number.isInteger(links) || links < 0))
|
|
8857
|
+
throw new Error("--links must be a whole number");
|
|
8858
|
+
const { arbi } = await resolveWorkspace();
|
|
8859
|
+
const all = await sdk.tags.listTags(arbi);
|
|
8860
|
+
const pick = (selector) => {
|
|
8861
|
+
const res = resolveByIdOrName(all, selector);
|
|
8862
|
+
if (!res.ok) failResolveAndExit("Tag", selector, res);
|
|
8863
|
+
return res.item;
|
|
8864
|
+
};
|
|
8865
|
+
const selection = opts.parent ? { parentId: pick(opts.parent).external_id } : { tagIds: opts.tag.map((t) => pick(t).external_id) };
|
|
8866
|
+
const matrix = await sdk.tags.getTagMatrix(arbi, selection, links);
|
|
8867
|
+
if (opts.json) {
|
|
8868
|
+
printJson(matrix);
|
|
8869
|
+
return;
|
|
8870
|
+
}
|
|
8871
|
+
const table = matrixTable(matrix);
|
|
8872
|
+
if (matrix.parent) bold(`${matrix.parent.name} (${matrix.parent.external_id})`);
|
|
8873
|
+
if (!table.rows.length) {
|
|
8874
|
+
process.stderr.write(
|
|
8875
|
+
opts.parent ? `No child tags. Add one: arbi tags create <name> --parent ${opts.parent}
|
|
8876
|
+
` : "No tags.\n"
|
|
8877
|
+
);
|
|
8878
|
+
return;
|
|
8879
|
+
}
|
|
8880
|
+
for (const line of renderTable(table.headers, table.rows)) console.log(line);
|
|
8881
|
+
if (table.gaps.length) {
|
|
8882
|
+
console.log("");
|
|
8883
|
+
dim("Gaps:");
|
|
8884
|
+
for (const g of table.gaps) dim(` ${g}`);
|
|
8885
|
+
}
|
|
8886
|
+
})()
|
|
8887
|
+
);
|
|
8618
8888
|
tags.allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
8619
8889
|
const tail = tags.args ?? [];
|
|
8620
8890
|
await tags.commands.find((c) => c.name() === "list").parseAsync(tail, { from: "user" });
|
|
@@ -9797,41 +10067,33 @@ function registerTaskCommand(program2) {
|
|
|
9797
10067
|
})()
|
|
9798
10068
|
);
|
|
9799
10069
|
}
|
|
9800
|
-
function
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
text = fs7.readFileSync(path8, "utf8");
|
|
9804
|
-
} catch (err) {
|
|
9805
|
-
throw new Error(`Cannot read ${path8}: ${err.message}`);
|
|
9806
|
-
}
|
|
9807
|
-
const ext = path6.extname(path8).toLowerCase();
|
|
9808
|
-
try {
|
|
9809
|
-
return ext === ".json" ? JSON.parse(text) : yaml.parse(text);
|
|
9810
|
-
} catch (err) {
|
|
10070
|
+
function signalTrigger(name, eventKind, eventFilter) {
|
|
10071
|
+
if (name === void 0) return void 0;
|
|
10072
|
+
if (!sdk.workflowSteps.SIGNAL_NAME_PATTERN.test(name))
|
|
9811
10073
|
throw new Error(
|
|
9812
|
-
|
|
10074
|
+
`Invalid --on-signal "${name}": lowercase letters, digits, ".", "_" or "-", starting with a letter or digit (e.g. risk-high)`
|
|
9813
10075
|
);
|
|
10076
|
+
if (eventKind && eventKind !== "workflow_signal")
|
|
10077
|
+
throw new Error(`--on-signal is an event trigger: use it instead of --event-kind ${eventKind}`);
|
|
10078
|
+
let filter = {};
|
|
10079
|
+
if (eventFilter) {
|
|
10080
|
+
try {
|
|
10081
|
+
filter = JSON.parse(eventFilter);
|
|
10082
|
+
} catch {
|
|
10083
|
+
throw new Error("--event-filter must be a JSON object");
|
|
10084
|
+
}
|
|
9814
10085
|
}
|
|
10086
|
+
return { event_kind: "workflow_signal", event_filter: { ...filter, signal: name } };
|
|
9815
10087
|
}
|
|
9816
|
-
function
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
}
|
|
9823
|
-
|
|
9824
|
-
function readInputsFile(path8) {
|
|
9825
|
-
const value = readStructured(path8);
|
|
9826
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
9827
|
-
throw new Error(`${path8}: expected an object of input values, e.g. {"client": "Acme"}`);
|
|
9828
|
-
return value;
|
|
9829
|
-
}
|
|
9830
|
-
function collect(value, previous = []) {
|
|
9831
|
-
return [...previous, value];
|
|
10088
|
+
function triggerText(w) {
|
|
10089
|
+
const signal = w.event_filter?.signal;
|
|
10090
|
+
if (w.event_kind === "workflow_signal" && typeof signal === "string") return `signal ${signal}`;
|
|
10091
|
+
if (w.event_kind) return `event ${w.event_kind}`;
|
|
10092
|
+
if (w.rrule) return w.rrule;
|
|
10093
|
+
if (w.run_at) return `once ${w.run_at}`;
|
|
10094
|
+
if (w.after_conversation_ext_id) return `after ${w.after_conversation_ext_id}`;
|
|
10095
|
+
return "by hand";
|
|
9832
10096
|
}
|
|
9833
|
-
|
|
9834
|
-
// src/commands/workflow.ts
|
|
9835
10097
|
function registerWorkflowCommand(program2) {
|
|
9836
10098
|
const workflow = program2.command("workflow").alias("automation").description(
|
|
9837
10099
|
"Workflows (/v1/workflow): create (optionally with steps), list, update, resume, pause, run, delete"
|
|
@@ -9841,7 +10103,10 @@ function registerWorkflowCommand(program2) {
|
|
|
9841
10103
|
).requiredOption("--name <name>", "Workflow name (required)").option(
|
|
9842
10104
|
"--steps <file>",
|
|
9843
10105
|
"Named steps (form/upload/agent/review) as a TaskPlanStep list in .json, .yaml or .yml"
|
|
9844
|
-
).option("--rrule <RRULE>", "Trigger: RFC-5545 recurrence rule").option("--run-at <ISO>", "Trigger: run once at this time (ISO-8601)").option("--event-kind <kind>", "Trigger: a notification type to react to").option(
|
|
10106
|
+
).option("--rrule <RRULE>", "Trigger: RFC-5545 recurrence rule").option("--run-at <ISO>", "Trigger: run once at this time (ISO-8601)").option("--event-kind <kind>", "Trigger: a notification type to react to").option(
|
|
10107
|
+
"--on-signal <name>",
|
|
10108
|
+
"Trigger: when a step of another run raises this signal (event_kind=workflow_signal, filter {signal})"
|
|
10109
|
+
).option("--after <con-id>", "Trigger: when this task (conversation) is done").option("--timezone <tz>", "IANA timezone for --rrule", "UTC").option("--action <type>", "Action: annotate_documents or email_documents").option("--documents <ids>", "Comma-separated document IDs").option("--labels <ids>", "Select documents matching these label IDs at execution time").option("--latest", "Select documents uploaded since the previous successful run").option("--tags <ids>", "Annotation field IDs for AI review").option("--overwrite", "Regenerate existing annotation answers").option(
|
|
9845
10110
|
"--auth-mode <mode>",
|
|
9846
10111
|
"scheduled_require_auth or scheduled_permanent",
|
|
9847
10112
|
"scheduled_require_auth"
|
|
@@ -9849,6 +10114,11 @@ function registerWorkflowCommand(program2) {
|
|
|
9849
10114
|
(opts) => runAction(async () => {
|
|
9850
10115
|
if (opts.steps && (opts.prompt || opts.skill || opts.action))
|
|
9851
10116
|
throw new Error("Use --steps, or --prompt / --skill / --action, not both");
|
|
10117
|
+
const signal = signalTrigger(opts.onSignal, opts.eventKind, opts.eventFilter);
|
|
10118
|
+
if (signal) {
|
|
10119
|
+
opts.eventKind = signal.event_kind;
|
|
10120
|
+
opts.eventFilter = JSON.stringify(signal.event_filter);
|
|
10121
|
+
}
|
|
9852
10122
|
const steps = opts.steps ? readStepsFile(opts.steps) : void 0;
|
|
9853
10123
|
const { arbi, workspaceId } = await resolveWorkspace();
|
|
9854
10124
|
const body = {
|
|
@@ -9900,6 +10170,17 @@ function registerWorkflowCommand(program2) {
|
|
|
9900
10170
|
success(
|
|
9901
10171
|
`Created workflow: ${data.name} (${ref(data.external_id)})${steps ? ` with ${steps.length} steps` : ""}`
|
|
9902
10172
|
);
|
|
10173
|
+
if (opts.onSignal)
|
|
10174
|
+
dim(
|
|
10175
|
+
`It starts when a step of a run raises "${opts.onSignal}" (a step's signals: [{name: ${opts.onSignal}, when: \u2026}]).`
|
|
10176
|
+
);
|
|
10177
|
+
const raised = [
|
|
10178
|
+
...new Set((steps ?? []).flatMap((s) => (s.signals ?? []).map((x) => x.name)))
|
|
10179
|
+
];
|
|
10180
|
+
if (raised.length)
|
|
10181
|
+
dim(
|
|
10182
|
+
`Its steps raise ${raised.map((n) => `"${n}"`).join(", ")}: a workflow made with --on-signal <name> starts on each.`
|
|
10183
|
+
);
|
|
9903
10184
|
if (steps) dim(`Run it: arbi workflow run ${data.external_id}`);
|
|
9904
10185
|
})()
|
|
9905
10186
|
);
|
|
@@ -9914,6 +10195,11 @@ function registerWorkflowCommand(program2) {
|
|
|
9914
10195
|
{ header: "NAME", width: 28, value: (r) => r.name ?? "" },
|
|
9915
10196
|
{ header: "ACTIVE", width: 7, value: (r) => r.active ? "yes" : "no" },
|
|
9916
10197
|
{ header: "SHARED", width: 8, value: (r) => r.shared ? "yes" : "no" },
|
|
10198
|
+
{
|
|
10199
|
+
header: "TRIGGER",
|
|
10200
|
+
width: 26,
|
|
10201
|
+
value: (r) => triggerText(r)
|
|
10202
|
+
},
|
|
9917
10203
|
{ header: "NEXT RUN", width: 26, value: (r) => r.next_fire_at ?? "" },
|
|
9918
10204
|
{ header: "LAST RUN", width: 26, value: (r) => r.last_fired_at ?? "" }
|
|
9919
10205
|
],
|
|
@@ -9983,11 +10269,14 @@ function registerWorkflowCommand(program2) {
|
|
|
9983
10269
|
);
|
|
9984
10270
|
})()
|
|
9985
10271
|
);
|
|
9986
|
-
workflow.command("update <id>").description("Update a saved workflow: its steps (--steps), and any setting from a JSON file").option("--steps <file>", "Replace its steps (TaskPlanStep list in .json, .yaml or .yml)").option("--file <path>", "JSON UpdateWorkflowRequest file").option("--json", "Output as JSON").action(
|
|
10272
|
+
workflow.command("update <id>").description("Update a saved workflow: its steps (--steps), and any setting from a JSON file").option("--steps <file>", "Replace its steps (TaskPlanStep list in .json, .yaml or .yml)").option("--file <path>", "JSON UpdateWorkflowRequest file").option("--on-signal <name>", "Start it when a run raises this signal (replaces its trigger)").option("--json", "Output as JSON").action(
|
|
9987
10273
|
(id, opts) => runAction(async () => {
|
|
9988
|
-
if (!opts.file && !opts.steps
|
|
10274
|
+
if (!opts.file && !opts.steps && !opts.onSignal)
|
|
10275
|
+
throw new Error("Pass --steps <file>, --on-signal <name> and/or --file <path>");
|
|
9989
10276
|
const steps = opts.steps ? readStepsFile(opts.steps) : void 0;
|
|
9990
10277
|
const body = opts.file ? JSON.parse(fs7.readFileSync(opts.file, "utf8")) : {};
|
|
10278
|
+
const signal = signalTrigger(opts.onSignal, void 0, void 0);
|
|
10279
|
+
if (signal) Object.assign(body, signal, { on_demand: false });
|
|
9991
10280
|
if (steps)
|
|
9992
10281
|
Object.assign(body, {
|
|
9993
10282
|
plan: sdk.workflowSteps.stepsPlan(steps),
|
|
@@ -10014,6 +10303,8 @@ function runJson(progress, answers = {}) {
|
|
|
10014
10303
|
return {
|
|
10015
10304
|
conversation_ext_id: c.external_id,
|
|
10016
10305
|
title: c.title,
|
|
10306
|
+
workflow_ext_id: c.workflow_ext_id ?? null,
|
|
10307
|
+
parent_conversation_ext_id: c.parent_conversation_ext_id ?? null,
|
|
10017
10308
|
waiting_on: progress.waitingOn,
|
|
10018
10309
|
in_progress: c.in_progress,
|
|
10019
10310
|
pending_auth_request_id: c.pending_auth_request_id ?? null,
|
|
@@ -10026,10 +10317,62 @@ function runJson(progress, answers = {}) {
|
|
|
10026
10317
|
after: s.after,
|
|
10027
10318
|
answer_id: s.responseId,
|
|
10028
10319
|
error: s.error,
|
|
10320
|
+
assignee: s.assignee,
|
|
10321
|
+
announced_to: s.announcedTo,
|
|
10322
|
+
sent_back: s.sentBack,
|
|
10323
|
+
sent_back_note: s.hasNote,
|
|
10324
|
+
times_sent_back: s.timesSentBack,
|
|
10325
|
+
signals: s.signals,
|
|
10326
|
+
linked_runs: s.linkedRuns.map(linkedJson),
|
|
10029
10327
|
...s.responseId && answers[s.responseId] !== void 0 ? { answer: answers[s.responseId] } : {}
|
|
10328
|
+
})),
|
|
10329
|
+
signals: (progress.signals ?? []).map((x) => ({ step: x.step, signal: x.signal, at: x.at })),
|
|
10330
|
+
linked_runs: (progress.linkedRuns ?? []).map(linkedJson),
|
|
10331
|
+
send_backs: (progress.sendBacks ?? []).map((b) => ({
|
|
10332
|
+
step: b.step,
|
|
10333
|
+
to: b.to,
|
|
10334
|
+
reopened: b.reopened,
|
|
10335
|
+
note: b.hasNote,
|
|
10336
|
+
by: b.by,
|
|
10337
|
+
at: b.at
|
|
10030
10338
|
}))
|
|
10031
10339
|
};
|
|
10032
10340
|
}
|
|
10341
|
+
var linkedJson = (l) => ({
|
|
10342
|
+
step: l.step,
|
|
10343
|
+
signal: l.signal,
|
|
10344
|
+
conversation_ext_id: l.conversationId,
|
|
10345
|
+
workflow_ext_id: l.workflowId,
|
|
10346
|
+
at: l.at
|
|
10347
|
+
});
|
|
10348
|
+
function stepNotes(s) {
|
|
10349
|
+
const notes = [];
|
|
10350
|
+
if (s.sentBack) notes.push(`sent back${s.hasNote ? " (note)" : ""}`);
|
|
10351
|
+
else if (s.timesSentBack) notes.push(`sent back \xD7${s.timesSentBack}`);
|
|
10352
|
+
if (s.signals.length) notes.push(`\u2691 ${s.signals.join(", ")}`);
|
|
10353
|
+
if (s.linkedRuns.length)
|
|
10354
|
+
notes.push(
|
|
10355
|
+
s.linkedRuns.length === 1 ? `\u2192 ${s.linkedRuns[0].conversationId}` : `\u2192 ${s.linkedRuns.length} linked runs`
|
|
10356
|
+
);
|
|
10357
|
+
return notes.join("; ");
|
|
10358
|
+
}
|
|
10359
|
+
function runEventLines(progress) {
|
|
10360
|
+
const lines = [
|
|
10361
|
+
...(progress.signals ?? []).map((x) => ({
|
|
10362
|
+
at: x.at ?? "",
|
|
10363
|
+
line: `\u2691 ${x.step} raised "${x.signal}"`
|
|
10364
|
+
})),
|
|
10365
|
+
...(progress.linkedRuns ?? []).map((l) => ({
|
|
10366
|
+
at: l.at ?? "",
|
|
10367
|
+
line: `\u2192 "${l.signal ?? "?"}" started ${l.conversationId}${l.workflowId ? ` (workflow ${l.workflowId})` : ""}: arbi run steps ${l.conversationId}`
|
|
10368
|
+
})),
|
|
10369
|
+
...(progress.sendBacks ?? []).map((b) => ({
|
|
10370
|
+
at: b.at ?? "",
|
|
10371
|
+
line: `\u21A9 ${b.step} sent the work back to ${b.to}${b.hasNote ? " with a note" : ""}${b.by ? ` (${b.by})` : ""}; reopened ${b.reopened.join(", ")}`
|
|
10372
|
+
}))
|
|
10373
|
+
];
|
|
10374
|
+
return lines.sort((a, b) => a.at && b.at ? a.at.localeCompare(b.at) : 0).map((l) => l.line);
|
|
10375
|
+
}
|
|
10033
10376
|
function nextHint(progress, planSteps = []) {
|
|
10034
10377
|
const id = progress.conversation.external_id;
|
|
10035
10378
|
if (progress.conversation.pending_auth_request_id)
|
|
@@ -10049,7 +10392,10 @@ function nextHint(progress, planSteps = []) {
|
|
|
10049
10392
|
(f) => have[f.name] === void 0 || have[f.name] === null || have[f.name] === ""
|
|
10050
10393
|
);
|
|
10051
10394
|
const inputs = fields.map((f) => ` --input ${f.name}=\u2026`).join("");
|
|
10052
|
-
|
|
10395
|
+
const forWhom = step.assignee ? ` (for ${sdk.workflowSteps.describeAssignee(step.assignee)})` : "";
|
|
10396
|
+
const hint = `Waiting on ${step.kind} step "${step.id}"${forWhom}: arbi run step ${id} ${step.id} complete${inputs}`;
|
|
10397
|
+
const back = step.kind === "review" && step.after.length > 0;
|
|
10398
|
+
return back ? `${hint} (or: arbi run step ${id} ${step.id} send-back${step.after.length > 1 ? " --to <step>" : ""} --note "\u2026")` : hint;
|
|
10053
10399
|
}
|
|
10054
10400
|
default: {
|
|
10055
10401
|
const failed = progress.steps.find((s) => s.phase === "failed");
|
|
@@ -10060,16 +10406,27 @@ function nextHint(progress, planSteps = []) {
|
|
|
10060
10406
|
function printRun(progress, answers) {
|
|
10061
10407
|
const c = progress.conversation;
|
|
10062
10408
|
bold(`Run ${c.external_id}${c.title ? ` \u2014 ${c.title}` : ""}`);
|
|
10409
|
+
if (c.parent_conversation_ext_id)
|
|
10410
|
+
dim(
|
|
10411
|
+
`Started from ${c.parent_conversation_ext_id}${c.workflow_ext_id ? ` by workflow ${c.workflow_ext_id}` : ""}: arbi run steps ${c.parent_conversation_ext_id}`
|
|
10412
|
+
);
|
|
10063
10413
|
printTable(
|
|
10064
10414
|
[
|
|
10065
10415
|
{ header: "STEP", width: 16, value: (r) => r.id },
|
|
10066
10416
|
{ header: "KIND", width: 8, value: (r) => r.kind },
|
|
10067
10417
|
{ header: "STATE", width: 9, value: (r) => r.phase },
|
|
10418
|
+
{
|
|
10419
|
+
header: "FOR",
|
|
10420
|
+
width: 15,
|
|
10421
|
+
value: (r) => sdk.workflowSteps.describeAssignee(r.assignee)
|
|
10422
|
+
},
|
|
10068
10423
|
{ header: "ANSWER", width: 16, value: (r) => r.responseId ?? "" },
|
|
10069
|
-
{ header: "
|
|
10424
|
+
{ header: "NOTES", width: 40, value: (r) => stepNotes(r) },
|
|
10425
|
+
{ header: "TITLE", width: 32, value: (r) => r.title }
|
|
10070
10426
|
],
|
|
10071
10427
|
progress.steps
|
|
10072
10428
|
);
|
|
10429
|
+
for (const line of runEventLines(progress)) dim(line);
|
|
10073
10430
|
for (const s of progress.steps) {
|
|
10074
10431
|
if (s.phase === "failed" && s.error) warn(`${s.id}: ${s.error}`);
|
|
10075
10432
|
if (!s.responseId || answers[s.responseId] === void 0) continue;
|
|
@@ -10110,6 +10467,48 @@ function seconds(value, flag) {
|
|
|
10110
10467
|
if (!Number.isFinite(n) || n <= 0) throw new Error(`${flag} must be a positive number of seconds`);
|
|
10111
10468
|
return n * 1e3;
|
|
10112
10469
|
}
|
|
10470
|
+
var STEP_ACTIONS = {
|
|
10471
|
+
complete: "complete",
|
|
10472
|
+
skip: "skip",
|
|
10473
|
+
"send-back": "send_back",
|
|
10474
|
+
send_back: "send_back"
|
|
10475
|
+
};
|
|
10476
|
+
function fieldsHint(fields) {
|
|
10477
|
+
const one = (f) => {
|
|
10478
|
+
const options = f.options?.length ? `: ${f.options.map((o) => o.value).join("|")}` : "";
|
|
10479
|
+
const range = f.min !== null && f.min !== void 0 && f.max !== null && f.max !== void 0 ? ` ${f.min}\u2013${f.max}` : "";
|
|
10480
|
+
return `${f.name} (${f.type ?? "text"}${options}${range}${f.required ? ", required" : ""})`;
|
|
10481
|
+
};
|
|
10482
|
+
return `Its fields: ${fields.map(one).join(", ")}`;
|
|
10483
|
+
}
|
|
10484
|
+
function upstreamOf(plan, id) {
|
|
10485
|
+
const found = /* @__PURE__ */ new Set();
|
|
10486
|
+
const stack = sdk.workflowSteps.stepDependencies(plan, id);
|
|
10487
|
+
while (stack.length) {
|
|
10488
|
+
const d = stack.pop();
|
|
10489
|
+
if (found.has(d)) continue;
|
|
10490
|
+
found.add(d);
|
|
10491
|
+
stack.push(...sdk.workflowSteps.stepDependencies(plan, d));
|
|
10492
|
+
}
|
|
10493
|
+
return found;
|
|
10494
|
+
}
|
|
10495
|
+
function checkSendBack(plan, stepId, to) {
|
|
10496
|
+
const upstream = upstreamOf(plan, stepId);
|
|
10497
|
+
if (to === void 0) {
|
|
10498
|
+
const direct = sdk.workflowSteps.stepDependencies(plan, stepId);
|
|
10499
|
+
if (direct.length !== 1)
|
|
10500
|
+
throw new Error(
|
|
10501
|
+
`"${stepId}" waits for ${direct.length ? direct.join(", ") : "no step"}: say which to send it back to with --to <step>`
|
|
10502
|
+
);
|
|
10503
|
+
return;
|
|
10504
|
+
}
|
|
10505
|
+
if (!plan.some((s) => s.id === to))
|
|
10506
|
+
throw new Error(`No step "${to}". Its steps: ${plan.map((s) => s.id).join(", ")}`);
|
|
10507
|
+
if (!upstream.has(to))
|
|
10508
|
+
throw new Error(
|
|
10509
|
+
`"${to}" is not a step "${stepId}" waits for: send it back to one of ${[...upstream].join(", ") || "(none)"}`
|
|
10510
|
+
);
|
|
10511
|
+
}
|
|
10113
10512
|
function gatherInputs(pairs, file, fields) {
|
|
10114
10513
|
return {
|
|
10115
10514
|
...file ? readInputsFile(file) : {},
|
|
@@ -10173,11 +10572,22 @@ function registerRunCommand(program2) {
|
|
|
10173
10572
|
})()
|
|
10174
10573
|
);
|
|
10175
10574
|
run.command("step <con-id> <step-id> <action>").description(
|
|
10176
|
-
"Act on a step: complete a form,
|
|
10177
|
-
).option(
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10575
|
+
"Act on a step: complete a person step (a form, or a step with fields, takes --input), skip any step, or send-back the work to an earlier step (--to, --note)"
|
|
10576
|
+
).option(
|
|
10577
|
+
"--input <name=value>",
|
|
10578
|
+
"An input: a form field or a step field (repeatable)",
|
|
10579
|
+
collect,
|
|
10580
|
+
[]
|
|
10581
|
+
).option("--inputs-file <file>", "Inputs as a JSON or YAML object").option(
|
|
10582
|
+
"--to <step-id>",
|
|
10583
|
+
"send-back: the step to reopen (default: the one this step waits for)"
|
|
10584
|
+
).option("--note <text>", "send-back: what to change (an agent step runs again with it)").option("--json", "Output as JSON").action(
|
|
10585
|
+
(conversationId, stepId, rawAction, opts) => runAction(async () => {
|
|
10586
|
+
const action = STEP_ACTIONS[rawAction];
|
|
10587
|
+
if (!action)
|
|
10588
|
+
throw new Error(`Unknown action "${rawAction}": use complete, skip or send-back`);
|
|
10589
|
+
if (action !== "send_back" && (opts.to !== void 0 || opts.note !== void 0))
|
|
10590
|
+
throw new Error("--to and --note go with send-back only");
|
|
10181
10591
|
const { arbi } = await resolveWorkspace();
|
|
10182
10592
|
const before = await sdk.work.getRunProgress(arbi, conversationId);
|
|
10183
10593
|
const plan = before.conversation.plan?.steps ?? [];
|
|
@@ -10186,22 +10596,33 @@ function registerRunCommand(program2) {
|
|
|
10186
10596
|
throw new Error(
|
|
10187
10597
|
`No step "${stepId}" in ${conversationId}. Its steps: ${plan.map((s) => s.id).join(", ")}`
|
|
10188
10598
|
);
|
|
10599
|
+
const fields = sdk.workflowSteps.stepFields(step);
|
|
10189
10600
|
const hasInputs = opts.input.length > 0 || !!opts.inputsFile;
|
|
10190
|
-
if (hasInputs && (action
|
|
10191
|
-
throw new Error(
|
|
10192
|
-
|
|
10193
|
-
|
|
10601
|
+
if (hasInputs && (action !== "complete" || fields.length === 0))
|
|
10602
|
+
throw new Error(
|
|
10603
|
+
"Only completing a form step, or a step with fields, takes --input / --inputs-file"
|
|
10604
|
+
);
|
|
10605
|
+
const here = before.steps.find((s) => s.id === stepId);
|
|
10606
|
+
if (action !== "skip") {
|
|
10194
10607
|
if (step.kind === "agent")
|
|
10195
10608
|
throw new Error(
|
|
10196
|
-
`"${stepId}" is an agent step: it runs itself. Skip it with: arbi run step ${conversationId} ${stepId} skip`
|
|
10609
|
+
action === "complete" ? `"${stepId}" is an agent step: it runs itself. Skip it with: arbi run step ${conversationId} ${stepId} skip` : `"${stepId}" is an agent step: send the work back to it from a person step after it (--to ${stepId})`
|
|
10197
10610
|
);
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
throw new Error(`"${stepId}"
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10611
|
+
if (here.phase === "pending")
|
|
10612
|
+
throw new Error(`"${stepId}" waits for ${here.after.join(", ")} to finish first`);
|
|
10613
|
+
if (here.phase === "done" || here.phase === "skipped")
|
|
10614
|
+
throw new Error(`"${stepId}" is already ${here.phase}`);
|
|
10615
|
+
}
|
|
10616
|
+
let inputs;
|
|
10617
|
+
if (action === "complete" && fields.length) {
|
|
10618
|
+
inputs = gatherInputs(opts.input, opts.inputsFile, fields);
|
|
10619
|
+
if (Array.isArray(step.fields)) {
|
|
10620
|
+
const problems = sdk.workflowSteps.checkStepInputs(step, inputs);
|
|
10621
|
+
if (Object.keys(problems).length)
|
|
10622
|
+
throw new Error(
|
|
10623
|
+
`Cannot complete "${stepId}": ${sdk.workflowSteps.describeInputProblems(step, problems)}. ${fieldsHint(fields)}`
|
|
10624
|
+
);
|
|
10625
|
+
} else {
|
|
10205
10626
|
const missing = sdk.workflowSteps.missingRequiredFields(step, {
|
|
10206
10627
|
...before.conversation.skill_inputs ?? {},
|
|
10207
10628
|
...inputs
|
|
@@ -10212,19 +10633,28 @@ function registerRunCommand(program2) {
|
|
|
10212
10633
|
);
|
|
10213
10634
|
}
|
|
10214
10635
|
}
|
|
10636
|
+
if (action === "send_back") checkSendBack(plan, stepId, opts.to);
|
|
10215
10637
|
await sdk.work.actOnStep(arbi, conversationId, {
|
|
10216
10638
|
step: stepId,
|
|
10217
10639
|
action,
|
|
10218
|
-
...inputs && Object.keys(inputs).length ? { inputs } : {}
|
|
10640
|
+
...inputs && Object.keys(inputs).length ? { inputs } : {},
|
|
10641
|
+
...action === "send_back" && opts.to ? { to: opts.to } : {},
|
|
10642
|
+
...action === "send_back" && opts.note?.trim() ? { note: opts.note.trim() } : {}
|
|
10219
10643
|
});
|
|
10220
10644
|
const progress = await sdk.work.getRunProgress(arbi, conversationId);
|
|
10221
10645
|
if (opts.json) {
|
|
10222
10646
|
printJson(runJson(progress));
|
|
10223
10647
|
return;
|
|
10224
10648
|
}
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10649
|
+
if (action === "send_back") {
|
|
10650
|
+
const back = progress.sendBacks.at(-1);
|
|
10651
|
+
success(
|
|
10652
|
+
`Sent "${stepId}" of ${ref(conversationId)} back to "${back?.to ?? opts.to ?? "?"}"${opts.note?.trim() ? " with a note" : ""}; reopened ${back?.reopened.join(", ") ?? ""}`
|
|
10653
|
+
);
|
|
10654
|
+
} else
|
|
10655
|
+
success(
|
|
10656
|
+
`${action === "complete" ? "Completed" : "Skipped"} step "${stepId}" of ${ref(conversationId)}`
|
|
10657
|
+
);
|
|
10228
10658
|
dim(nextHint(progress, plan));
|
|
10229
10659
|
})()
|
|
10230
10660
|
);
|
|
@@ -10822,19 +11252,95 @@ async function resolveProjectId(arbi, explicit) {
|
|
|
10822
11252
|
}
|
|
10823
11253
|
return first;
|
|
10824
11254
|
}
|
|
11255
|
+
var orNull = (value) => value.trim().toLowerCase() === "none" ? null : value;
|
|
11256
|
+
function projectPatch(opts) {
|
|
11257
|
+
const body = {};
|
|
11258
|
+
if (opts.budget !== void 0)
|
|
11259
|
+
body.budget = orNull(opts.budget) === null ? null : sdk.projects.parseBudget(opts.budget);
|
|
11260
|
+
if (opts.client !== void 0) body.client = orNull(opts.client);
|
|
11261
|
+
if (opts.clientRef !== void 0) body.client_ref = orNull(opts.clientRef);
|
|
11262
|
+
if (opts.stage !== void 0) body.stage = orNull(opts.stage);
|
|
11263
|
+
if (opts.field?.length) {
|
|
11264
|
+
const fields = {};
|
|
11265
|
+
for (const pair of opts.field) {
|
|
11266
|
+
const eq = pair.indexOf("=");
|
|
11267
|
+
const key = eq > 0 ? pair.slice(0, eq).trim() : "";
|
|
11268
|
+
if (!/^[a-z][a-z0-9_]{0,63}$/.test(key))
|
|
11269
|
+
throw new Error(
|
|
11270
|
+
`Invalid --field "${pair}": use key=value with a key like "matter_type" (lowercase, digits, "_")`
|
|
11271
|
+
);
|
|
11272
|
+
const value = pair.slice(eq + 1);
|
|
11273
|
+
fields[key] = value.trim() === "" ? null : value;
|
|
11274
|
+
}
|
|
11275
|
+
body.fields = fields;
|
|
11276
|
+
}
|
|
11277
|
+
return body;
|
|
11278
|
+
}
|
|
11279
|
+
async function resolveProject(arbi, selector) {
|
|
11280
|
+
const res = resolveByIdOrName(await sdk.projects.listProjects(arbi), selector);
|
|
11281
|
+
if (!res.ok) failResolveAndExit("Project", selector, res);
|
|
11282
|
+
return res.item;
|
|
11283
|
+
}
|
|
11284
|
+
var PERSON_ID = /^(?:usr|agt)-[a-zA-Z0-9]{8}$/;
|
|
11285
|
+
async function resolveLead(value) {
|
|
11286
|
+
if (orNull(value) === null) return null;
|
|
11287
|
+
if (PERSON_ID.test(value)) return value;
|
|
11288
|
+
if (!value.includes("@"))
|
|
11289
|
+
throw new Error(`--lead takes a person id (usr-\u2026), an email, or none; got "${value}"`);
|
|
11290
|
+
const { arbi } = await resolveWorkspace();
|
|
11291
|
+
const users = await sdk.workspaces.listWorkspaceUsers(arbi);
|
|
11292
|
+
const found = users.find((u) => u.user.email.toLowerCase() === value.toLowerCase());
|
|
11293
|
+
if (!found) {
|
|
11294
|
+
error(
|
|
11295
|
+
`No one with the email ${value} in the open workspace. Pass their id (usr-\u2026), or open a workspace they are in.`
|
|
11296
|
+
);
|
|
11297
|
+
process.exit(3);
|
|
11298
|
+
}
|
|
11299
|
+
return found.user.external_id;
|
|
11300
|
+
}
|
|
11301
|
+
function eventValue(event, side) {
|
|
11302
|
+
const detail = event.detail ?? {};
|
|
11303
|
+
const value = detail[side === "old" ? "from" : "to"];
|
|
11304
|
+
if (value === null || value === void 0) return "\u2014";
|
|
11305
|
+
if (typeof value === "object")
|
|
11306
|
+
return sdk.projects.formatBudget(value) || JSON.stringify(value);
|
|
11307
|
+
return String(value);
|
|
11308
|
+
}
|
|
11309
|
+
function printProjectDetails(p) {
|
|
11310
|
+
label("Budget:", sdk.projects.formatBudget(p.budget) || "\u2014");
|
|
11311
|
+
label("Client:", p.client ?? "\u2014");
|
|
11312
|
+
label("Client ref:", p.client_ref ?? "\u2014");
|
|
11313
|
+
label("Stage:", `${p.stage ?? "\u2014"}${p.stage_changed_at ? ` (since ${p.stage_changed_at})` : ""}`);
|
|
11314
|
+
label("Lead:", p.lead_user ?? "\u2014");
|
|
11315
|
+
const fields = Object.entries(p.fields ?? {});
|
|
11316
|
+
label("Fields:", fields.length ? fields.map(([k, v]) => `${k}=${String(v)}`).join(", ") : "\u2014");
|
|
11317
|
+
}
|
|
10825
11318
|
function registerProjectCommand(program2) {
|
|
10826
|
-
const project = program2.command("project").description(
|
|
10827
|
-
|
|
11319
|
+
const project = program2.command("project").description(
|
|
11320
|
+
"Project tenancy: list, create, rename, update, set (budget, client, stage, lead, fields), show, events, delete, refresh"
|
|
11321
|
+
);
|
|
11322
|
+
project.command("list").description("List your projects (--stage, --lead to filter)").option("--stage <label>", "Only projects at this stage").option("--lead <usr-id|email>", "Only projects led by this person").option("--json", "Output as JSON").action(
|
|
10828
11323
|
(opts) => runAction(async () => {
|
|
10829
11324
|
const { arbi } = await resolveAuth();
|
|
10830
|
-
const
|
|
11325
|
+
const leadUser = opts.lead ? await resolveLead(opts.lead) ?? void 0 : void 0;
|
|
11326
|
+
const data = await sdk.projects.listProjects(arbi, { stage: opts.stage, leadUser });
|
|
10831
11327
|
if (!emitListOrTable(data, opts, "No projects found.")) return;
|
|
10832
11328
|
printTable(
|
|
10833
11329
|
[
|
|
10834
11330
|
{ header: "ID", width: 18, value: (r) => r.external_id },
|
|
10835
|
-
{ header: "NAME", width:
|
|
10836
|
-
{
|
|
10837
|
-
|
|
11331
|
+
{ header: "NAME", width: 26, value: (r) => r.name ?? "" },
|
|
11332
|
+
{
|
|
11333
|
+
header: "PLAN",
|
|
11334
|
+
width: 10,
|
|
11335
|
+
value: (r) => r.plan ?? r.subscription ?? ""
|
|
11336
|
+
},
|
|
11337
|
+
{ header: "STAGE", width: 16, value: (r) => r.stage ?? "" },
|
|
11338
|
+
{
|
|
11339
|
+
header: "BUDGET",
|
|
11340
|
+
width: 16,
|
|
11341
|
+
value: (r) => sdk.projects.formatBudget(r.budget)
|
|
11342
|
+
},
|
|
11343
|
+
{ header: "LEAD", width: 14, value: (r) => r.lead_user ?? "" }
|
|
10838
11344
|
],
|
|
10839
11345
|
data
|
|
10840
11346
|
);
|
|
@@ -10877,6 +11383,69 @@ function registerProjectCommand(program2) {
|
|
|
10877
11383
|
success(`Updated project ${ref(id)} (${data.name})`);
|
|
10878
11384
|
})()
|
|
10879
11385
|
);
|
|
11386
|
+
project.command("set <project>").description(
|
|
11387
|
+
"Set a project's details: budget, client, client ref, stage, lead and fields (owner only; stage and budget changes become project events)"
|
|
11388
|
+
).option("--budget <amount+currency>", "Budget, e.g. 6000GBP (none to clear)").option("--client <name>", "Client name (none to clear)").option("--client-ref <id>", "Client record id, e.g. a CRM id (none to clear)").option("--stage <label>", "Stage, e.g. Discovery (none to clear)").option(
|
|
11389
|
+
"--lead <usr-id|email>",
|
|
11390
|
+
"Lead: a member of one of its workspaces, by id or email (none to clear)"
|
|
11391
|
+
).option("--field <key=value>", "A field (repeatable; key= removes it)", collect, []).option("--json", "Output as JSON").action(
|
|
11392
|
+
(selector, opts) => runAction(async () => {
|
|
11393
|
+
const body = projectPatch(opts);
|
|
11394
|
+
if (Object.keys(body).length === 0 && !opts.lead)
|
|
11395
|
+
throw new Error(
|
|
11396
|
+
"Nothing to set. Pass --budget, --client, --client-ref, --stage, --lead and/or --field"
|
|
11397
|
+
);
|
|
11398
|
+
const { arbi } = await resolveAuth();
|
|
11399
|
+
const target = await resolveProject(arbi, selector);
|
|
11400
|
+
if (opts.lead !== void 0) body.lead_user = await resolveLead(opts.lead);
|
|
11401
|
+
const data = await sdk.projects.updateProject(arbi, target.external_id, body);
|
|
11402
|
+
if (opts.json) {
|
|
11403
|
+
printJson(data);
|
|
11404
|
+
return;
|
|
11405
|
+
}
|
|
11406
|
+
success(`Updated project ${data.name} (${ref(data.external_id)})`);
|
|
11407
|
+
printProjectDetails(data);
|
|
11408
|
+
})()
|
|
11409
|
+
);
|
|
11410
|
+
project.command("show <project>").description("A project's details: budget, client, stage, lead and fields").option("--json", "Output as JSON").action(
|
|
11411
|
+
(selector, opts) => runAction(async () => {
|
|
11412
|
+
const { arbi } = await resolveAuth();
|
|
11413
|
+
const data = await resolveProject(arbi, selector);
|
|
11414
|
+
if (opts.json) {
|
|
11415
|
+
printJson(data);
|
|
11416
|
+
return;
|
|
11417
|
+
}
|
|
11418
|
+
bold(`${data.name} (${data.external_id})`);
|
|
11419
|
+
printProjectDetails(data);
|
|
11420
|
+
})()
|
|
11421
|
+
);
|
|
11422
|
+
project.command("events <project>").description("A project's stage and budget changes, newest first").option("--kind <kind>", "Only stage_changed or budget_changed").option("--limit <n>", "At most this many (default 100)").option("--json", "Output as JSON").action(
|
|
11423
|
+
(selector, opts) => runAction(async () => {
|
|
11424
|
+
if (opts.kind && !["stage_changed", "budget_changed"].includes(opts.kind))
|
|
11425
|
+
throw new Error("--kind must be stage_changed or budget_changed");
|
|
11426
|
+
const limit = opts.limit === void 0 ? void 0 : Number(opts.limit);
|
|
11427
|
+
if (limit !== void 0 && (!Number.isInteger(limit) || limit < 1 || limit > 1e3))
|
|
11428
|
+
throw new Error("--limit must be a whole number from 1 to 1000");
|
|
11429
|
+
const { arbi } = await resolveAuth();
|
|
11430
|
+
const target = await resolveProject(arbi, selector);
|
|
11431
|
+
const events = await sdk.projects.listProjectEvents(arbi, target.external_id, {
|
|
11432
|
+
kind: opts.kind,
|
|
11433
|
+
limit
|
|
11434
|
+
});
|
|
11435
|
+
if (!emitListOrTable(events, opts, `No stage or budget changes on ${target.name} yet.`))
|
|
11436
|
+
return;
|
|
11437
|
+
printTable(
|
|
11438
|
+
[
|
|
11439
|
+
{ header: "WHEN", width: 26, value: (r) => r.created_at },
|
|
11440
|
+
{ header: "CHANGE", width: 16, value: (r) => r.kind },
|
|
11441
|
+
{ header: "FROM", width: 22, value: (r) => eventValue(r, "old") },
|
|
11442
|
+
{ header: "TO", width: 22, value: (r) => eventValue(r, "new") },
|
|
11443
|
+
{ header: "BY", width: 14, value: (r) => r.actor ?? "" }
|
|
11444
|
+
],
|
|
11445
|
+
events
|
|
11446
|
+
);
|
|
11447
|
+
})()
|
|
11448
|
+
);
|
|
10880
11449
|
project.command("delete <id>").description("Delete a project (irreversible; requires an explicit id)").action(
|
|
10881
11450
|
(id) => runAction(async () => {
|
|
10882
11451
|
const { arbi } = await resolveAuth();
|
|
@@ -11008,10 +11577,104 @@ function registerFilesCommand(program2) {
|
|
|
11008
11577
|
await files.commands.find((c) => c.name() === "list").parseAsync(tail, { from: "user" });
|
|
11009
11578
|
});
|
|
11010
11579
|
}
|
|
11011
|
-
|
|
11012
|
-
|
|
11580
|
+
var credits = (n) => `${n.toLocaleString("en", { maximumFractionDigits: 2 })} credits`;
|
|
11581
|
+
function breakdownRows(breakdown, name = (k) => k) {
|
|
11582
|
+
return Object.entries(breakdown).sort(([, a], [, b]) => b.spend - a.spend).map(([key, b]) => [
|
|
11583
|
+
name(key),
|
|
11584
|
+
credits(b.spend),
|
|
11585
|
+
b.total_tokens.toLocaleString("en"),
|
|
11586
|
+
String(b.requests)
|
|
11587
|
+
]);
|
|
11588
|
+
}
|
|
11589
|
+
function printBreakdown(title, breakdown, name) {
|
|
11590
|
+
console.log("");
|
|
11591
|
+
bold(title);
|
|
11592
|
+
const rows = breakdownRows(breakdown, name);
|
|
11593
|
+
if (!rows.length) {
|
|
11594
|
+
dim(" (nothing recorded)");
|
|
11595
|
+
return;
|
|
11596
|
+
}
|
|
11597
|
+
for (const line of renderTable(["", "SPEND", "TOKENS", "REQUESTS"], rows, 40))
|
|
11598
|
+
console.log(` ${line}`);
|
|
11599
|
+
}
|
|
11013
11600
|
function registerUsageCommand(program2) {
|
|
11014
|
-
const usage = program2.command("usage").description(
|
|
11601
|
+
const usage = program2.command("usage").description(
|
|
11602
|
+
"Usage and spend: today's against the daily cap, a conversation's by step, model and agent, a project's by conversation"
|
|
11603
|
+
);
|
|
11604
|
+
usage.command("conversation <con-id>").description(
|
|
11605
|
+
"What a conversation's AI work cost this billing period: by step, model and agent (also by run and feature in --json)"
|
|
11606
|
+
).option("--json", "Output as JSON").action(
|
|
11607
|
+
(conversationId, opts) => runAction(async () => {
|
|
11608
|
+
const { arbi } = await resolveWorkspace();
|
|
11609
|
+
const data = await sdk.work.getConversationUsage(arbi, conversationId);
|
|
11610
|
+
if (opts.json) {
|
|
11611
|
+
printJson(data);
|
|
11612
|
+
return;
|
|
11613
|
+
}
|
|
11614
|
+
bold(`Conversation ${data.conversation_ext_id} (project ${data.project_ext_id})`);
|
|
11615
|
+
label("Period:", `${data.period_start} \u2192 ${data.period_end}`);
|
|
11616
|
+
label("Spend:", credits(data.total_spend));
|
|
11617
|
+
label("Tokens:", data.total_tokens.toLocaleString("en"));
|
|
11618
|
+
label("Requests:", String(data.total_requests));
|
|
11619
|
+
printBreakdown("By step", data.by_step);
|
|
11620
|
+
printBreakdown(
|
|
11621
|
+
"By run (each run of the thread: a send-back runs a step again)",
|
|
11622
|
+
data.by_run
|
|
11623
|
+
);
|
|
11624
|
+
printBreakdown("By model", data.by_model);
|
|
11625
|
+
printBreakdown("By agent", data.by_agent);
|
|
11626
|
+
})()
|
|
11627
|
+
);
|
|
11628
|
+
usage.command("project <project>").description(
|
|
11629
|
+
"A project's spend this billing period (--by-conversation: per conversation, plus what none accounts for)"
|
|
11630
|
+
).option("--by-conversation", "Break the spend down by conversation").option("--months-back <n>", "0 = this billing period (default), 1 = the one before, \u2026").option("--json", "Output as JSON").action(
|
|
11631
|
+
(selector, opts) => runAction(async () => {
|
|
11632
|
+
const monthsBack = opts.monthsBack === void 0 ? void 0 : Number(opts.monthsBack);
|
|
11633
|
+
if (monthsBack !== void 0 && (!Number.isInteger(monthsBack) || monthsBack < 0))
|
|
11634
|
+
throw new Error("--months-back must be a whole number, 0 or more");
|
|
11635
|
+
const { arbi } = await resolveAuth();
|
|
11636
|
+
const res = resolveByIdOrName(await sdk.projects.listProjects(arbi), selector);
|
|
11637
|
+
if (!res.ok) failResolveAndExit("Project", selector, res);
|
|
11638
|
+
const projectId = res.item.external_id;
|
|
11639
|
+
if (!opts.byConversation) {
|
|
11640
|
+
const data2 = await sdk.projects.getProjectUsage(arbi, projectId, monthsBack);
|
|
11641
|
+
if (opts.json) {
|
|
11642
|
+
printJson(data2);
|
|
11643
|
+
return;
|
|
11644
|
+
}
|
|
11645
|
+
bold(`Project ${res.item.name} (${projectId})`);
|
|
11646
|
+
label("Period:", `${data2.period_start} \u2192 ${data2.period_end}`);
|
|
11647
|
+
label("Spend:", credits(data2.total_spend));
|
|
11648
|
+
label("Tokens:", data2.total_tokens.toLocaleString("en"));
|
|
11649
|
+
dim(`By conversation: arbi usage project ${projectId} --by-conversation`);
|
|
11650
|
+
return;
|
|
11651
|
+
}
|
|
11652
|
+
const data = await sdk.projects.getProjectConversationUsage(arbi, projectId, monthsBack);
|
|
11653
|
+
if (opts.json) {
|
|
11654
|
+
printJson(data);
|
|
11655
|
+
return;
|
|
11656
|
+
}
|
|
11657
|
+
const titles = /* @__PURE__ */ new Map();
|
|
11658
|
+
if (getConfig()?.selectedWorkspaceId)
|
|
11659
|
+
try {
|
|
11660
|
+
const { arbi: ws } = await resolveWorkspace();
|
|
11661
|
+
for (const c of await sdk.conversations.listConversations(ws))
|
|
11662
|
+
if (c.title) titles.set(c.external_id, c.title);
|
|
11663
|
+
} catch {
|
|
11664
|
+
}
|
|
11665
|
+
bold(`Project ${res.item.name} (${projectId})`);
|
|
11666
|
+
label("Period:", `${data.period_start} \u2192 ${data.period_end}`);
|
|
11667
|
+
label("Spend:", credits(data.total_spend));
|
|
11668
|
+
printBreakdown(
|
|
11669
|
+
"By conversation",
|
|
11670
|
+
data.by_conversation,
|
|
11671
|
+
(id) => titles.has(id) ? `${id} ${titles.get(id)}` : id
|
|
11672
|
+
);
|
|
11673
|
+
const u = data.unattributed;
|
|
11674
|
+
if (u.spend || u.requests)
|
|
11675
|
+
dim(`Not in any conversation: ${credits(u.spend)}, ${u.requests} requests`);
|
|
11676
|
+
})()
|
|
11677
|
+
);
|
|
11015
11678
|
usage.command("today").description("Today's tokens used / remaining (for the current user)").option("--json", "Output as JSON").action(
|
|
11016
11679
|
(opts) => runAction(async () => {
|
|
11017
11680
|
const { arbi } = await resolveAuth();
|
|
@@ -11287,7 +11950,7 @@ console.info = (...args) => {
|
|
|
11287
11950
|
_origInfo(...args);
|
|
11288
11951
|
};
|
|
11289
11952
|
var program = new commander.Command();
|
|
11290
|
-
program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.4.
|
|
11953
|
+
program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.4.78").showHelpAfterError(true).showSuggestionAfterError(true);
|
|
11291
11954
|
registerConfigCommand(program);
|
|
11292
11955
|
registerLoginCommand(program);
|
|
11293
11956
|
registerRegisterCommand(program);
|