@arbidocs/cli 0.4.75 → 0.4.77
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 +37 -0
- package/SKILL.md +83 -5
- package/dist/index.js +1048 -94
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ 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 yaml = require('yaml');
|
|
15
16
|
var module$1 = require('module');
|
|
16
17
|
|
|
17
18
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -3694,7 +3695,7 @@ function getLatestVersion(skipCache = false) {
|
|
|
3694
3695
|
}
|
|
3695
3696
|
}
|
|
3696
3697
|
function getCurrentVersion() {
|
|
3697
|
-
return "0.4.
|
|
3698
|
+
return "0.4.77";
|
|
3698
3699
|
}
|
|
3699
3700
|
function readChangelog(fromVersion, toVersion) {
|
|
3700
3701
|
try {
|
|
@@ -3747,17 +3748,17 @@ function showChangelog(fromVersion, toVersion) {
|
|
|
3747
3748
|
async function checkForUpdates(autoUpdate) {
|
|
3748
3749
|
try {
|
|
3749
3750
|
const latest = getLatestVersion();
|
|
3750
|
-
if (!latest || latest === "0.4.
|
|
3751
|
+
if (!latest || latest === "0.4.77") return;
|
|
3751
3752
|
if (autoUpdate) {
|
|
3752
3753
|
warn(`
|
|
3753
|
-
Your arbi version is out of date (${"0.4.
|
|
3754
|
+
Your arbi version is out of date (${"0.4.77"} \u2192 ${latest}). Updating...`);
|
|
3754
3755
|
child_process.execSync("npm install -g @arbidocs/cli@latest", { stdio: "inherit" });
|
|
3755
|
-
showChangelog("0.4.
|
|
3756
|
+
showChangelog("0.4.77", latest);
|
|
3756
3757
|
console.log(`Updated to ${latest}.`);
|
|
3757
3758
|
} else {
|
|
3758
3759
|
warn(
|
|
3759
3760
|
`
|
|
3760
|
-
Your arbi version is out of date (${"0.4.
|
|
3761
|
+
Your arbi version is out of date (${"0.4.77"} \u2192 ${latest}).
|
|
3761
3762
|
Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
|
|
3762
3763
|
);
|
|
3763
3764
|
}
|
|
@@ -3789,10 +3790,10 @@ function markNagShown(latest) {
|
|
|
3789
3790
|
function hintUpdateOnError() {
|
|
3790
3791
|
try {
|
|
3791
3792
|
const cached = readCache();
|
|
3792
|
-
if (!cached || cached.latest === "0.4.
|
|
3793
|
+
if (!cached || cached.latest === "0.4.77") return;
|
|
3793
3794
|
if (!shouldShowNag(cached.latest)) return;
|
|
3794
3795
|
warn(
|
|
3795
|
-
`Your arbi version is out of date (${"0.4.
|
|
3796
|
+
`Your arbi version is out of date (${"0.4.77"} \u2192 ${cached.latest}). Run "arbi update".`
|
|
3796
3797
|
);
|
|
3797
3798
|
markNagShown(cached.latest);
|
|
3798
3799
|
} catch {
|
|
@@ -3963,6 +3964,10 @@ function stringifyApiDetail(value) {
|
|
|
3963
3964
|
const rec = value;
|
|
3964
3965
|
if (typeof rec.detail === "string") return rec.detail;
|
|
3965
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
|
+
}
|
|
3966
3971
|
if (typeof rec.message === "string") return rec.message;
|
|
3967
3972
|
if (typeof rec.error === "string") return rec.error;
|
|
3968
3973
|
try {
|
|
@@ -6322,10 +6327,10 @@ function registerDocsCommand(program2) {
|
|
|
6322
6327
|
if (!opts.quiet && statusIntervalMs > 0) {
|
|
6323
6328
|
statusTimer = setInterval(() => printStatus(" "), statusIntervalMs);
|
|
6324
6329
|
}
|
|
6325
|
-
const
|
|
6330
|
+
const sleep2 = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
6326
6331
|
const backoff = async (attempt) => {
|
|
6327
6332
|
const delayMs = Math.min(1e3 * Math.pow(2, attempt), 3e4);
|
|
6328
|
-
await
|
|
6333
|
+
await sleep2(delayMs);
|
|
6329
6334
|
};
|
|
6330
6335
|
const submitBatch = async (batchIds) => {
|
|
6331
6336
|
const updates = batchIds.map((id) => ({
|
|
@@ -6437,12 +6442,12 @@ function registerDocsCommand(program2) {
|
|
|
6437
6442
|
}
|
|
6438
6443
|
submitted += 1;
|
|
6439
6444
|
if (intervalMs > 0 && j < batch.length - 1) {
|
|
6440
|
-
await
|
|
6445
|
+
await sleep2(intervalMs);
|
|
6441
6446
|
}
|
|
6442
6447
|
}
|
|
6443
6448
|
}
|
|
6444
6449
|
if (intervalMs > 0 && i + batchSize < docIds.length) {
|
|
6445
|
-
await
|
|
6450
|
+
await sleep2(intervalMs);
|
|
6446
6451
|
}
|
|
6447
6452
|
}
|
|
6448
6453
|
} finally {
|
|
@@ -6520,19 +6525,19 @@ Processing complete: completed=${procCompleted} failed=0 elapsed=${totalElapsed
|
|
|
6520
6525
|
}
|
|
6521
6526
|
|
|
6522
6527
|
// src/commands/upload-shared.ts
|
|
6523
|
-
function printSummary(
|
|
6528
|
+
function printSummary(summary2) {
|
|
6524
6529
|
console.log("");
|
|
6525
6530
|
console.log("Upload summary:");
|
|
6526
|
-
console.log(` Files found: ${
|
|
6527
|
-
success(` Uploaded: ${
|
|
6528
|
-
if (
|
|
6529
|
-
warn(` Skipped: ${
|
|
6531
|
+
console.log(` Files found: ${summary2.totalFiles}`);
|
|
6532
|
+
success(` Uploaded: ${summary2.uploaded}`);
|
|
6533
|
+
if (summary2.skipped > 0) {
|
|
6534
|
+
warn(` Skipped: ${summary2.skipped}`);
|
|
6530
6535
|
}
|
|
6531
|
-
console.log(` Folders: ${
|
|
6532
|
-
if (
|
|
6536
|
+
console.log(` Folders: ${summary2.folders}`);
|
|
6537
|
+
if (summary2.skippedDetails.length > 0) {
|
|
6533
6538
|
console.log("");
|
|
6534
6539
|
warn("Skipped files:");
|
|
6535
|
-
for (const s of
|
|
6540
|
+
for (const s of summary2.skippedDetails) {
|
|
6536
6541
|
warn(` ${s.file_name}: ${s.reason}`);
|
|
6537
6542
|
}
|
|
6538
6543
|
}
|
|
@@ -6550,10 +6555,10 @@ function countSupportedFiles(dirPath) {
|
|
|
6550
6555
|
}
|
|
6551
6556
|
return count;
|
|
6552
6557
|
}
|
|
6553
|
-
function mergeBatchResult(
|
|
6558
|
+
function mergeBatchResult(summary2, uploadedDocs, result, sourceLabel, opts) {
|
|
6554
6559
|
for (const [folder, info] of result.folders) {
|
|
6555
|
-
|
|
6556
|
-
|
|
6560
|
+
summary2.folders += 1;
|
|
6561
|
+
summary2.totalFiles += info.fileCount;
|
|
6557
6562
|
if (!opts.json) {
|
|
6558
6563
|
success(
|
|
6559
6564
|
` ${folder || "<root>"}: ${info.fileCount} file(s) \u2192 ${info.doc_ext_ids.length} uploaded`
|
|
@@ -6564,10 +6569,10 @@ function mergeBatchResult(summary, uploadedDocs, result, sourceLabel, opts) {
|
|
|
6564
6569
|
}
|
|
6565
6570
|
for (const docId of info.doc_ext_ids) uploadedDocs.set(docId, sourceLabel);
|
|
6566
6571
|
}
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6572
|
+
summary2.uploaded += result.doc_ext_ids.length;
|
|
6573
|
+
summary2.skipped += result.skipped.length;
|
|
6574
|
+
summary2.docIds.push(...result.doc_ext_ids);
|
|
6575
|
+
summary2.skippedDetails.push(...result.skipped);
|
|
6571
6576
|
}
|
|
6572
6577
|
function classifyDirectInputs(inputPaths) {
|
|
6573
6578
|
const out = [];
|
|
@@ -6632,7 +6637,7 @@ async function runDirectUploadMode(inputPaths, opts) {
|
|
|
6632
6637
|
});
|
|
6633
6638
|
const creds = store.requireCredentials();
|
|
6634
6639
|
const workspaceKey = await sdk.getRawWorkspaceKey(arbi, workspaceId, creds.signingPrivateKeyBase64);
|
|
6635
|
-
const
|
|
6640
|
+
const summary2 = {
|
|
6636
6641
|
totalFiles: 0,
|
|
6637
6642
|
uploaded: 0,
|
|
6638
6643
|
skipped: 0,
|
|
@@ -6669,7 +6674,7 @@ async function runDirectUploadMode(inputPaths, opts) {
|
|
|
6669
6674
|
warn(`No supported files found in directory: ${inp.originalPath}`);
|
|
6670
6675
|
continue;
|
|
6671
6676
|
}
|
|
6672
|
-
mergeBatchResult(
|
|
6677
|
+
mergeBatchResult(summary2, uploadedDocs, result2, inp.originalPath, opts);
|
|
6673
6678
|
if (!opts.json) {
|
|
6674
6679
|
success(
|
|
6675
6680
|
`Uploaded directory: ${inp.originalPath} (${result2.doc_ext_ids.length} document(s) total)`
|
|
@@ -6688,7 +6693,7 @@ async function runDirectUploadMode(inputPaths, opts) {
|
|
|
6688
6693
|
warn(`No supported files found in zip: ${inp.originalPath}`);
|
|
6689
6694
|
continue;
|
|
6690
6695
|
}
|
|
6691
|
-
mergeBatchResult(
|
|
6696
|
+
mergeBatchResult(summary2, uploadedDocs, result2, inp.originalPath, opts);
|
|
6692
6697
|
if (!opts.json) {
|
|
6693
6698
|
success(
|
|
6694
6699
|
`Uploaded zip: ${inp.originalPath} (${result2.doc_ext_ids.length} document(s) total)`
|
|
@@ -6706,7 +6711,7 @@ async function runDirectUploadMode(inputPaths, opts) {
|
|
|
6706
6711
|
warn(`No supported files found in archive: ${inp.originalPath}`);
|
|
6707
6712
|
continue;
|
|
6708
6713
|
}
|
|
6709
|
-
mergeBatchResult(
|
|
6714
|
+
mergeBatchResult(summary2, uploadedDocs, result, inp.originalPath, opts);
|
|
6710
6715
|
if (!opts.json) {
|
|
6711
6716
|
success(
|
|
6712
6717
|
`Uploaded archive: ${inp.originalPath} (${result.doc_ext_ids.length} document(s) total)`
|
|
@@ -6723,11 +6728,11 @@ async function runDirectUploadMode(inputPaths, opts) {
|
|
|
6723
6728
|
folder: opts.folder,
|
|
6724
6729
|
configExtId: opts.config
|
|
6725
6730
|
});
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
+
summary2.totalFiles += plainFiles.length;
|
|
6732
|
+
summary2.uploaded += result.doc_ext_ids.length;
|
|
6733
|
+
summary2.skipped += result.skipped.length;
|
|
6734
|
+
summary2.docIds.push(...result.doc_ext_ids);
|
|
6735
|
+
summary2.skippedDetails.push(...result.skipped);
|
|
6731
6736
|
for (let i = 0; i < result.doc_ext_ids.length && i < plainFiles.length; i++) {
|
|
6732
6737
|
uploadedDocs.set(result.doc_ext_ids[i], path6__default.default.basename(plainFiles[i]));
|
|
6733
6738
|
}
|
|
@@ -6738,9 +6743,9 @@ async function runDirectUploadMode(inputPaths, opts) {
|
|
|
6738
6743
|
}
|
|
6739
6744
|
}
|
|
6740
6745
|
if (opts.json) {
|
|
6741
|
-
console.log(JSON.stringify(
|
|
6746
|
+
console.log(JSON.stringify(summary2));
|
|
6742
6747
|
} else {
|
|
6743
|
-
printSummary(
|
|
6748
|
+
printSummary(summary2);
|
|
6744
6749
|
}
|
|
6745
6750
|
if (willWatch && uploadedDocs.size > 0) {
|
|
6746
6751
|
const pending = new Set(uploadedDocs.keys());
|
|
@@ -6849,7 +6854,7 @@ function registerUploadCommand(program2) {
|
|
|
6849
6854
|
});
|
|
6850
6855
|
const uploadedDocs = /* @__PURE__ */ new Map();
|
|
6851
6856
|
const auth = { baseUrl: config.baseUrl, accessToken };
|
|
6852
|
-
const
|
|
6857
|
+
const summary2 = {
|
|
6853
6858
|
totalFiles: 0,
|
|
6854
6859
|
uploaded: 0,
|
|
6855
6860
|
skipped: 0,
|
|
@@ -6883,7 +6888,7 @@ function registerUploadCommand(program2) {
|
|
|
6883
6888
|
if (opts.dryRun) {
|
|
6884
6889
|
const count = countSupportedFiles2(filePath);
|
|
6885
6890
|
console.log(`${filePath}: ${count} supported file(s) would be uploaded`);
|
|
6886
|
-
|
|
6891
|
+
summary2.totalFiles += count;
|
|
6887
6892
|
continue;
|
|
6888
6893
|
}
|
|
6889
6894
|
const result = await sdk.documentsNode.uploadDirectory(auth, filePath, progressCallbacks);
|
|
@@ -6906,7 +6911,7 @@ function registerUploadCommand(program2) {
|
|
|
6906
6911
|
`Uploaded directory: ${filePath} (${result.doc_ext_ids.length} document(s) total)`
|
|
6907
6912
|
);
|
|
6908
6913
|
}
|
|
6909
|
-
updateSummary(
|
|
6914
|
+
updateSummary(summary2, result);
|
|
6910
6915
|
for (const id of result.doc_ext_ids) uploadedDocs.set(id, filePath);
|
|
6911
6916
|
} else if (filePath.toLowerCase().endsWith(".zip")) {
|
|
6912
6917
|
if (opts.dryRun) {
|
|
@@ -6931,7 +6936,7 @@ function registerUploadCommand(program2) {
|
|
|
6931
6936
|
}
|
|
6932
6937
|
success(`Uploaded zip: ${filePath} (${result.doc_ext_ids.length} document(s) total)`);
|
|
6933
6938
|
}
|
|
6934
|
-
updateSummary(
|
|
6939
|
+
updateSummary(summary2, result);
|
|
6935
6940
|
for (const id of result.doc_ext_ids) uploadedDocs.set(id, filePath);
|
|
6936
6941
|
} else if (sdk.documentsNode.ARCHIVE_EXTENSIONS.has(
|
|
6937
6942
|
filePath.slice(filePath.lastIndexOf(".")).toLowerCase()
|
|
@@ -6960,12 +6965,12 @@ function registerUploadCommand(program2) {
|
|
|
6960
6965
|
`Uploaded archive: ${filePath} (${result.doc_ext_ids.length} document(s) total)`
|
|
6961
6966
|
);
|
|
6962
6967
|
}
|
|
6963
|
-
updateSummary(
|
|
6968
|
+
updateSummary(summary2, result);
|
|
6964
6969
|
for (const id of result.doc_ext_ids) uploadedDocs.set(id, filePath);
|
|
6965
6970
|
} else {
|
|
6966
6971
|
if (opts.dryRun) {
|
|
6967
6972
|
console.log(`${filePath}: single file would be uploaded`);
|
|
6968
|
-
|
|
6973
|
+
summary2.totalFiles += 1;
|
|
6969
6974
|
continue;
|
|
6970
6975
|
}
|
|
6971
6976
|
const result = await sdk.documentsNode.uploadLocalFile(auth, filePath, {
|
|
@@ -6981,12 +6986,12 @@ function registerUploadCommand(program2) {
|
|
|
6981
6986
|
);
|
|
6982
6987
|
}
|
|
6983
6988
|
}
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6989
|
+
summary2.totalFiles += 1;
|
|
6990
|
+
summary2.uploaded += result.doc_ext_ids?.length ?? 0;
|
|
6991
|
+
summary2.docIds.push(...result.doc_ext_ids ?? []);
|
|
6987
6992
|
if (result.skipped) {
|
|
6988
|
-
|
|
6989
|
-
|
|
6993
|
+
summary2.skipped += result.skipped.length;
|
|
6994
|
+
summary2.skippedDetails.push(...result.skipped);
|
|
6990
6995
|
}
|
|
6991
6996
|
for (const id of result.doc_ext_ids ?? []) uploadedDocs.set(id, result.fileName);
|
|
6992
6997
|
}
|
|
@@ -6995,9 +7000,9 @@ function registerUploadCommand(program2) {
|
|
|
6995
7000
|
return;
|
|
6996
7001
|
}
|
|
6997
7002
|
if (opts.json) {
|
|
6998
|
-
console.log(JSON.stringify(
|
|
7003
|
+
console.log(JSON.stringify(summary2));
|
|
6999
7004
|
} else {
|
|
7000
|
-
printSummary(
|
|
7005
|
+
printSummary(summary2);
|
|
7001
7006
|
}
|
|
7002
7007
|
if (willWatch && uploadedDocs.size > 0) {
|
|
7003
7008
|
const pending = new Set(uploadedDocs.keys());
|
|
@@ -7059,17 +7064,17 @@ Connection closed. ${pending.size} document(s) still processing.`);
|
|
|
7059
7064
|
})()
|
|
7060
7065
|
);
|
|
7061
7066
|
}
|
|
7062
|
-
function updateSummary(
|
|
7067
|
+
function updateSummary(summary2, result) {
|
|
7063
7068
|
let totalFiles = 0;
|
|
7064
7069
|
for (const [, info] of result.folders) {
|
|
7065
7070
|
totalFiles += info.fileCount;
|
|
7066
|
-
|
|
7071
|
+
summary2.folders += 1;
|
|
7067
7072
|
}
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
+
summary2.totalFiles += totalFiles;
|
|
7074
|
+
summary2.uploaded += result.doc_ext_ids.length;
|
|
7075
|
+
summary2.skipped += result.skipped.length;
|
|
7076
|
+
summary2.docIds.push(...result.doc_ext_ids);
|
|
7077
|
+
summary2.skippedDetails.push(...result.skipped);
|
|
7073
7078
|
}
|
|
7074
7079
|
function countSupportedFiles2(dirPath) {
|
|
7075
7080
|
let count = 0;
|
|
@@ -7752,10 +7757,10 @@ Error: ${message}`))
|
|
|
7752
7757
|
saveLastMetadata(result.metadata);
|
|
7753
7758
|
}
|
|
7754
7759
|
const refs = sdk.countCitations(result.metadata);
|
|
7755
|
-
const
|
|
7756
|
-
if (
|
|
7760
|
+
const summary2 = opts.quiet !== true ? sdk.formatStreamSummary(result, elapsedTime) : "";
|
|
7761
|
+
if (summary2) {
|
|
7757
7762
|
const refSuffix = refs > 0 ? ` \xB7 ${refs} ref${refs === 1 ? "" : "s"}` : "";
|
|
7758
|
-
const text = `[${
|
|
7763
|
+
const text = `[${summary2}${refSuffix}]`;
|
|
7759
7764
|
const cols = process.stderr.columns || 80;
|
|
7760
7765
|
const pad = Math.max(0, cols - text.length);
|
|
7761
7766
|
console.error(chalk2__default.default.dim(" ".repeat(pad) + text));
|
|
@@ -8472,6 +8477,174 @@ function registerTimelineCommand(program2) {
|
|
|
8472
8477
|
);
|
|
8473
8478
|
}
|
|
8474
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
|
+
}
|
|
8475
8648
|
async function fetchTagChoices(arbi) {
|
|
8476
8649
|
const data = await sdk.tags.listTags(arbi);
|
|
8477
8650
|
if (data.length === 0) {
|
|
@@ -8488,7 +8661,7 @@ async function fetchTagChoices(arbi) {
|
|
|
8488
8661
|
};
|
|
8489
8662
|
}
|
|
8490
8663
|
function registerTagsCommand(program2) {
|
|
8491
|
-
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");
|
|
8492
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(
|
|
8493
8666
|
(opts) => runAction(async () => {
|
|
8494
8667
|
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
@@ -8511,7 +8684,8 @@ function registerTagsCommand(program2) {
|
|
|
8511
8684
|
value: (r) => r.tag_type?.type ?? ""
|
|
8512
8685
|
},
|
|
8513
8686
|
{ header: "DOCS", width: 6, value: (r) => String(r.doctag_count) },
|
|
8514
|
-
{ 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 ?? "" }
|
|
8515
8689
|
],
|
|
8516
8690
|
data
|
|
8517
8691
|
);
|
|
@@ -8521,9 +8695,10 @@ function registerTagsCommand(program2) {
|
|
|
8521
8695
|
"-t, --type <type>",
|
|
8522
8696
|
"Tag type (checkbox, text, number, select, folder, search, date, assistant)",
|
|
8523
8697
|
"checkbox"
|
|
8524
|
-
).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(
|
|
8525
8699
|
(name, opts) => runAction(async () => {
|
|
8526
8700
|
const { arbi } = await resolveWorkspace(opts.workspace);
|
|
8701
|
+
const parent = opts.parent ? await resolveTag(arbi, opts.parent) : void 0;
|
|
8527
8702
|
const interactive = !name;
|
|
8528
8703
|
if (!name) {
|
|
8529
8704
|
name = await promptInput("Tag name");
|
|
@@ -8547,9 +8722,16 @@ function registerTagsCommand(program2) {
|
|
|
8547
8722
|
options: []
|
|
8548
8723
|
},
|
|
8549
8724
|
instruction,
|
|
8550
|
-
shared
|
|
8725
|
+
shared,
|
|
8726
|
+
parentId: parent?.external_id ?? null
|
|
8551
8727
|
});
|
|
8552
|
-
|
|
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
|
+
);
|
|
8553
8735
|
})()
|
|
8554
8736
|
);
|
|
8555
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(
|
|
@@ -8614,6 +8796,95 @@ function registerTagsCommand(program2) {
|
|
|
8614
8796
|
success(`Updated: ${data.name} (${ref(data.external_id)})`);
|
|
8615
8797
|
})()
|
|
8616
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
|
+
);
|
|
8617
8888
|
tags.allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
8618
8889
|
const tail = tags.args ?? [];
|
|
8619
8890
|
await tags.commands.find((c) => c.name() === "list").parseAsync(tail, { from: "user" });
|
|
@@ -9427,6 +9698,9 @@ async function runQuickstart(url) {
|
|
|
9427
9698
|
console.log(` Workspace: ${workspaceName} (${ref(workspaceId)})`);
|
|
9428
9699
|
console.log("");
|
|
9429
9700
|
dim('Try: arbi ask "hello"');
|
|
9701
|
+
dim(
|
|
9702
|
+
"Workflows with steps: arbi workflow create --name <n> --steps steps.yaml (see arbi run --help)"
|
|
9703
|
+
);
|
|
9430
9704
|
} catch (err) {
|
|
9431
9705
|
if (isExitPromptError(err)) throw err;
|
|
9432
9706
|
error(`Login failed: ${sdk.getErrorMessage(err)}`);
|
|
@@ -9793,14 +10067,59 @@ function registerTaskCommand(program2) {
|
|
|
9793
10067
|
})()
|
|
9794
10068
|
);
|
|
9795
10069
|
}
|
|
10070
|
+
function signalTrigger(name, eventKind, eventFilter) {
|
|
10071
|
+
if (name === void 0) return void 0;
|
|
10072
|
+
if (!sdk.workflowSteps.SIGNAL_NAME_PATTERN.test(name))
|
|
10073
|
+
throw new Error(
|
|
10074
|
+
`Invalid --on-signal "${name}": lowercase letters, digits, ".", "_" or "-", starting with a letter or digit (e.g. risk-high)`
|
|
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
|
+
}
|
|
10085
|
+
}
|
|
10086
|
+
return { event_kind: "workflow_signal", event_filter: { ...filter, signal: name } };
|
|
10087
|
+
}
|
|
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";
|
|
10096
|
+
}
|
|
9796
10097
|
function registerWorkflowCommand(program2) {
|
|
9797
|
-
const workflow = program2.command("workflow").alias("automation").description(
|
|
9798
|
-
|
|
10098
|
+
const workflow = program2.command("workflow").alias("automation").description(
|
|
10099
|
+
"Workflows (/v1/workflow): create (optionally with steps), list, update, resume, pause, run, delete"
|
|
10100
|
+
);
|
|
10101
|
+
workflow.command("create").description(
|
|
10102
|
+
"Create a workflow: at most one trigger (none = started by hand) and what it runs: --steps, --prompt, --skill or --action"
|
|
10103
|
+
).requiredOption("--name <name>", "Workflow name (required)").option(
|
|
10104
|
+
"--steps <file>",
|
|
10105
|
+
"Named steps (form/upload/agent/review) as a TaskPlanStep list in .json, .yaml or .yml"
|
|
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(
|
|
9799
10110
|
"--auth-mode <mode>",
|
|
9800
10111
|
"scheduled_require_auth or scheduled_permanent",
|
|
9801
10112
|
"scheduled_require_auth"
|
|
9802
10113
|
).option("--event-filter <json>", "Notification attribute filters as a JSON object").option("--prompt <text>", "Instruction the agent runs").option("--skill <ref>", "Skill reference to run").option("--agent <agt-id>", "Agent to run it as (external id)").option("--workspace <wrk-id>", "Workspace to scope the workflow to (default: the current one)").option("--shared", "Share it with the workspace").option("--json", "Output as JSON").action(
|
|
9803
10114
|
(opts) => runAction(async () => {
|
|
10115
|
+
if (opts.steps && (opts.prompt || opts.skill || opts.action))
|
|
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
|
+
}
|
|
10122
|
+
const steps = opts.steps ? readStepsFile(opts.steps) : void 0;
|
|
9804
10123
|
const { arbi, workspaceId } = await resolveWorkspace();
|
|
9805
10124
|
const body = {
|
|
9806
10125
|
name: opts.name,
|
|
@@ -9842,12 +10161,27 @@ function registerWorkflowCommand(program2) {
|
|
|
9842
10161
|
if (opts.after) body.after_conversation_ext_id = opts.after;
|
|
9843
10162
|
if (opts.skill) body.skill_ref = opts.skill;
|
|
9844
10163
|
if (opts.agent) body.agent_ext_id = opts.agent;
|
|
10164
|
+
if (steps) body.plan = sdk.workflowSteps.stepsPlan(steps);
|
|
9845
10165
|
const data = await sdk.workflows.createWorkflow(arbi, body);
|
|
9846
10166
|
if (opts.json) {
|
|
9847
10167
|
printJson(data);
|
|
9848
10168
|
return;
|
|
9849
10169
|
}
|
|
9850
|
-
success(
|
|
10170
|
+
success(
|
|
10171
|
+
`Created workflow: ${data.name} (${ref(data.external_id)})${steps ? ` with ${steps.length} steps` : ""}`
|
|
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
|
+
);
|
|
10184
|
+
if (steps) dim(`Run it: arbi workflow run ${data.external_id}`);
|
|
9851
10185
|
})()
|
|
9852
10186
|
);
|
|
9853
10187
|
workflow.command("list").description("List your workflows").option("--json", "Output as JSON").action(
|
|
@@ -9861,6 +10195,11 @@ function registerWorkflowCommand(program2) {
|
|
|
9861
10195
|
{ header: "NAME", width: 28, value: (r) => r.name ?? "" },
|
|
9862
10196
|
{ header: "ACTIVE", width: 7, value: (r) => r.active ? "yes" : "no" },
|
|
9863
10197
|
{ header: "SHARED", width: 8, value: (r) => r.shared ? "yes" : "no" },
|
|
10198
|
+
{
|
|
10199
|
+
header: "TRIGGER",
|
|
10200
|
+
width: 26,
|
|
10201
|
+
value: (r) => triggerText(r)
|
|
10202
|
+
},
|
|
9864
10203
|
{ header: "NEXT RUN", width: 26, value: (r) => r.next_fire_at ?? "" },
|
|
9865
10204
|
{ header: "LAST RUN", width: 26, value: (r) => r.last_fired_at ?? "" }
|
|
9866
10205
|
],
|
|
@@ -9880,7 +10219,9 @@ function registerWorkflowCommand(program2) {
|
|
|
9880
10219
|
})()
|
|
9881
10220
|
);
|
|
9882
10221
|
}
|
|
9883
|
-
workflow.command("run <id>").description(
|
|
10222
|
+
workflow.command("run <id>").description(
|
|
10223
|
+
"Run a workflow now: a thread (a stepped one waits for its steps), or a document action without one"
|
|
10224
|
+
).option("--json", "Output as JSON").action(
|
|
9884
10225
|
(id, opts) => runAction(async () => {
|
|
9885
10226
|
const { arbi } = await resolveAuth();
|
|
9886
10227
|
const data = await sdk.workflows.runWorkflow(arbi, id);
|
|
@@ -9890,11 +10231,17 @@ function registerWorkflowCommand(program2) {
|
|
|
9890
10231
|
}
|
|
9891
10232
|
if (data.error) throw new Error(data.error);
|
|
9892
10233
|
success(
|
|
9893
|
-
`${data.started ? "Started" : "Submitted"}
|
|
10234
|
+
`${data.started ? "Started" : "Submitted"} workflow ${ref(id)}${data.execution_id ? ` \u2014 execution ${data.execution_id}` : ""}${data.conversation_ext_id ? ` \u2014 thread ${ref(data.conversation_ext_id)}` : ""}`
|
|
9894
10235
|
);
|
|
10236
|
+
if (!data.conversation_ext_id) return;
|
|
10237
|
+
const saved = await sdk.workflows.getWorkflow(arbi, id);
|
|
10238
|
+
if (saved.plan?.steps?.length) {
|
|
10239
|
+
dim(`Its steps: arbi run steps ${data.conversation_ext_id}`);
|
|
10240
|
+
dim(`Follow it: arbi run steps ${data.conversation_ext_id} --watch --answers`);
|
|
10241
|
+
}
|
|
9895
10242
|
})()
|
|
9896
10243
|
);
|
|
9897
|
-
workflow.command("get <id>").description("Read the complete saved
|
|
10244
|
+
workflow.command("get <id>").description("Read the complete saved workflow configuration").action(
|
|
9898
10245
|
(id) => runAction(async () => {
|
|
9899
10246
|
const { arbi } = await resolveAuth();
|
|
9900
10247
|
printJson(await sdk.workflows.getWorkflow(arbi, id));
|
|
@@ -9904,7 +10251,7 @@ function registerWorkflowCommand(program2) {
|
|
|
9904
10251
|
(id, opts) => runAction(async () => {
|
|
9905
10252
|
const { arbi } = await resolveAuth();
|
|
9906
10253
|
const data = await sdk.workflows.listWorkflowRuns(arbi, id);
|
|
9907
|
-
if (!emitListOrTable(data, opts, "This
|
|
10254
|
+
if (!emitListOrTable(data, opts, "This workflow has not run yet.")) return;
|
|
9908
10255
|
printTable(
|
|
9909
10256
|
[
|
|
9910
10257
|
{ header: "RUN", width: 10, value: (r) => String(r.id) },
|
|
@@ -9922,13 +10269,25 @@ function registerWorkflowCommand(program2) {
|
|
|
9922
10269
|
);
|
|
9923
10270
|
})()
|
|
9924
10271
|
);
|
|
9925
|
-
workflow.command("update <id>").description("Update
|
|
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(
|
|
9926
10273
|
(id, opts) => runAction(async () => {
|
|
10274
|
+
if (!opts.file && !opts.steps && !opts.onSignal)
|
|
10275
|
+
throw new Error("Pass --steps <file>, --on-signal <name> and/or --file <path>");
|
|
10276
|
+
const steps = opts.steps ? readStepsFile(opts.steps) : void 0;
|
|
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 });
|
|
10280
|
+
if (steps)
|
|
10281
|
+
Object.assign(body, {
|
|
10282
|
+
plan: sdk.workflowSteps.stepsPlan(steps),
|
|
10283
|
+
prompt: "",
|
|
10284
|
+
skill_ref: null,
|
|
10285
|
+
skill_inputs: null
|
|
10286
|
+
});
|
|
9927
10287
|
const { arbi } = await resolveAuth();
|
|
9928
|
-
const body = JSON.parse(fs7.readFileSync(opts.file, "utf8"));
|
|
9929
10288
|
const data = await sdk.workflows.updateWorkflow(arbi, id, body);
|
|
9930
10289
|
if (opts.json) printJson(data);
|
|
9931
|
-
else success(`Updated
|
|
10290
|
+
else success(`Updated workflow ${ref(id)}${steps ? ` \u2014 ${steps.length} steps` : ""}`);
|
|
9932
10291
|
})()
|
|
9933
10292
|
);
|
|
9934
10293
|
workflow.command("delete <id>").description("Delete a workflow (tasks it already created stay)").action(
|
|
@@ -9939,6 +10298,367 @@ function registerWorkflowCommand(program2) {
|
|
|
9939
10298
|
})()
|
|
9940
10299
|
);
|
|
9941
10300
|
}
|
|
10301
|
+
function runJson(progress, answers = {}) {
|
|
10302
|
+
const c = progress.conversation;
|
|
10303
|
+
return {
|
|
10304
|
+
conversation_ext_id: c.external_id,
|
|
10305
|
+
title: c.title,
|
|
10306
|
+
workflow_ext_id: c.workflow_ext_id ?? null,
|
|
10307
|
+
parent_conversation_ext_id: c.parent_conversation_ext_id ?? null,
|
|
10308
|
+
waiting_on: progress.waitingOn,
|
|
10309
|
+
in_progress: c.in_progress,
|
|
10310
|
+
pending_auth_request_id: c.pending_auth_request_id ?? null,
|
|
10311
|
+
last_error: c.last_error ?? null,
|
|
10312
|
+
steps: progress.steps.map((s) => ({
|
|
10313
|
+
id: s.id,
|
|
10314
|
+
title: s.title,
|
|
10315
|
+
kind: s.kind,
|
|
10316
|
+
state: s.phase,
|
|
10317
|
+
after: s.after,
|
|
10318
|
+
answer_id: s.responseId,
|
|
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),
|
|
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
|
|
10338
|
+
}))
|
|
10339
|
+
};
|
|
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
|
+
}
|
|
10376
|
+
function nextHint(progress, planSteps = []) {
|
|
10377
|
+
const id = progress.conversation.external_id;
|
|
10378
|
+
if (progress.conversation.pending_auth_request_id)
|
|
10379
|
+
return "The run is waiting for access to the workspace: approve the request in ARBI.";
|
|
10380
|
+
switch (progress.waitingOn) {
|
|
10381
|
+
case "finished":
|
|
10382
|
+
return "Every step is done or skipped.";
|
|
10383
|
+
case "agent": {
|
|
10384
|
+
const next = progress.steps.find((s) => s.phase === "running") ?? progress.steps.find((s) => s.kind === "agent" && s.phase === "ready");
|
|
10385
|
+
return `Step ${next ? `"${next.id}" ` : ""}is on its way: arbi run steps ${id} --watch`;
|
|
10386
|
+
}
|
|
10387
|
+
case "person": {
|
|
10388
|
+
const step = progress.steps.find((s) => s.kind !== "agent" && s.phase === "ready");
|
|
10389
|
+
const planned = planSteps.find((p) => p.id === step.id);
|
|
10390
|
+
const have = progress.conversation.skill_inputs ?? {};
|
|
10391
|
+
const fields = (planned ? sdk.workflowSteps.stepFields(planned) : []).filter(
|
|
10392
|
+
(f) => have[f.name] === void 0 || have[f.name] === null || have[f.name] === ""
|
|
10393
|
+
);
|
|
10394
|
+
const inputs = fields.map((f) => ` --input ${f.name}=\u2026`).join("");
|
|
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;
|
|
10399
|
+
}
|
|
10400
|
+
default: {
|
|
10401
|
+
const failed = progress.steps.find((s) => s.phase === "failed");
|
|
10402
|
+
return failed ? `Step "${failed.id}" failed${failed.error ? ` (${failed.error})` : ""}: arbi run step ${id} ${failed.id} skip` : "Nothing can move: a step it waits for has not finished.";
|
|
10403
|
+
}
|
|
10404
|
+
}
|
|
10405
|
+
}
|
|
10406
|
+
function printRun(progress, answers) {
|
|
10407
|
+
const c = progress.conversation;
|
|
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
|
+
);
|
|
10413
|
+
printTable(
|
|
10414
|
+
[
|
|
10415
|
+
{ header: "STEP", width: 16, value: (r) => r.id },
|
|
10416
|
+
{ header: "KIND", width: 8, value: (r) => r.kind },
|
|
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
|
+
},
|
|
10423
|
+
{ header: "ANSWER", width: 16, value: (r) => r.responseId ?? "" },
|
|
10424
|
+
{ header: "NOTES", width: 40, value: (r) => stepNotes(r) },
|
|
10425
|
+
{ header: "TITLE", width: 32, value: (r) => r.title }
|
|
10426
|
+
],
|
|
10427
|
+
progress.steps
|
|
10428
|
+
);
|
|
10429
|
+
for (const line of runEventLines(progress)) dim(line);
|
|
10430
|
+
for (const s of progress.steps) {
|
|
10431
|
+
if (s.phase === "failed" && s.error) warn(`${s.id}: ${s.error}`);
|
|
10432
|
+
if (!s.responseId || answers[s.responseId] === void 0) continue;
|
|
10433
|
+
console.log("");
|
|
10434
|
+
bold(`\u2500\u2500 ${s.id}: ${s.title} (${s.responseId})`);
|
|
10435
|
+
console.log(answers[s.responseId]);
|
|
10436
|
+
}
|
|
10437
|
+
console.log("");
|
|
10438
|
+
dim(nextHint(progress, c.plan?.steps ?? []));
|
|
10439
|
+
}
|
|
10440
|
+
async function answersFor(arbi, progress) {
|
|
10441
|
+
const ids = progress.steps.filter((s) => s.kind === "agent" && s.phase === "done" && s.responseId).map((s) => s.responseId);
|
|
10442
|
+
return sdk.work.getStepAnswers(arbi, progress.conversation.external_id, ids);
|
|
10443
|
+
}
|
|
10444
|
+
var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
10445
|
+
var summary = (p) => p.steps.map((s) => `${s.id}:${s.phase}`).join(" ");
|
|
10446
|
+
async function watchRun(arbi, conversationId, { intervalMs, timeoutMs, quiet }) {
|
|
10447
|
+
const deadline = Date.now() + timeoutMs;
|
|
10448
|
+
let progress = await sdk.work.getRunProgress(arbi, conversationId);
|
|
10449
|
+
let last = "";
|
|
10450
|
+
const report = () => {
|
|
10451
|
+
const now = summary(progress);
|
|
10452
|
+
if (!quiet && now !== last) process.stderr.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${now}
|
|
10453
|
+
`);
|
|
10454
|
+
last = now;
|
|
10455
|
+
};
|
|
10456
|
+
report();
|
|
10457
|
+
while (progress.waitingOn === "agent" && !progress.conversation.pending_auth_request_id) {
|
|
10458
|
+
if (Date.now() >= deadline) return { progress, timedOut: true };
|
|
10459
|
+
await sleep(intervalMs);
|
|
10460
|
+
progress = await sdk.work.getRunProgress(arbi, conversationId);
|
|
10461
|
+
report();
|
|
10462
|
+
}
|
|
10463
|
+
return { progress, timedOut: false };
|
|
10464
|
+
}
|
|
10465
|
+
function seconds(value, flag) {
|
|
10466
|
+
const n = Number(value);
|
|
10467
|
+
if (!Number.isFinite(n) || n <= 0) throw new Error(`${flag} must be a positive number of seconds`);
|
|
10468
|
+
return n * 1e3;
|
|
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
|
+
}
|
|
10512
|
+
function gatherInputs(pairs, file, fields) {
|
|
10513
|
+
return {
|
|
10514
|
+
...file ? readInputsFile(file) : {},
|
|
10515
|
+
...sdk.workflowSteps.parseInputPairs(pairs, fields)
|
|
10516
|
+
};
|
|
10517
|
+
}
|
|
10518
|
+
function registerRunCommand(program2) {
|
|
10519
|
+
const run = program2.command("run").description(
|
|
10520
|
+
"Stepped workflow runs: start one from a steps file, see its steps, complete or skip a step"
|
|
10521
|
+
);
|
|
10522
|
+
run.command("start").description("Start a one-off stepped run as a work thread, without saving a workflow").requiredOption("--steps <file>", "Plan steps (TaskPlanStep list) as .json, .yaml or .yml").requiredOption("--title <title>", "The run thread's title").option("--input <name=value>", "A run input, e.g. a form field (repeatable)", collect, []).option("--inputs-file <file>", "Run inputs as a JSON or YAML object").option("--agent <agt-id>", "Agent to run the agent steps as (external id)").option("--shared", "Share the thread with the workspace").option("--json", "Output as JSON").action(
|
|
10523
|
+
(opts) => runAction(async () => {
|
|
10524
|
+
const steps = readStepsFile(opts.steps);
|
|
10525
|
+
const inputs = gatherInputs(
|
|
10526
|
+
opts.input,
|
|
10527
|
+
opts.inputsFile,
|
|
10528
|
+
steps.flatMap((s) => sdk.workflowSteps.stepFields(s))
|
|
10529
|
+
);
|
|
10530
|
+
const { arbi } = await resolveWorkspace();
|
|
10531
|
+
const created = await sdk.work.createWork(arbi, {
|
|
10532
|
+
title: opts.title,
|
|
10533
|
+
plan: sdk.workflowSteps.stepsPlan(steps),
|
|
10534
|
+
skill_inputs: Object.keys(inputs).length ? inputs : null,
|
|
10535
|
+
agent_ext_id: opts.agent ?? null,
|
|
10536
|
+
shared: opts.shared ?? null,
|
|
10537
|
+
auth_mode: "run_now_temp"
|
|
10538
|
+
});
|
|
10539
|
+
const progress = await sdk.work.getRunProgress(arbi, created.external_id);
|
|
10540
|
+
if (opts.json) {
|
|
10541
|
+
printJson(runJson(progress));
|
|
10542
|
+
return;
|
|
10543
|
+
}
|
|
10544
|
+
success(`Started run ${ref(created.external_id)} \u2014 ${opts.title}`);
|
|
10545
|
+
dim(`Follow it: arbi run steps ${created.external_id} --watch`);
|
|
10546
|
+
dim(nextHint(progress, steps));
|
|
10547
|
+
})()
|
|
10548
|
+
);
|
|
10549
|
+
run.command("steps <con-id>").description(
|
|
10550
|
+
"A run's steps and their state (pending, ready, running, done, skipped, failed), with each done agent step's answer id"
|
|
10551
|
+
).option("--answers", "Print the done agent steps' answers from the thread").option("--watch", "Poll until the run stops waiting on an agent step").option("--interval <seconds>", "Poll interval for --watch", "3").option("--timeout <seconds>", "Stop watching after this long", "600").option("--json", "Output as JSON").action(
|
|
10552
|
+
(conversationId, opts) => runAction(async () => {
|
|
10553
|
+
const intervalMs = seconds(opts.interval, "--interval");
|
|
10554
|
+
const timeoutMs = seconds(opts.timeout, "--timeout");
|
|
10555
|
+
const { arbi } = await resolveWorkspace();
|
|
10556
|
+
let progress;
|
|
10557
|
+
let timedOut = false;
|
|
10558
|
+
if (opts.watch) {
|
|
10559
|
+
;
|
|
10560
|
+
({ progress, timedOut } = await watchRun(arbi, conversationId, {
|
|
10561
|
+
intervalMs,
|
|
10562
|
+
timeoutMs,
|
|
10563
|
+
quiet: !!opts.json
|
|
10564
|
+
}));
|
|
10565
|
+
} else progress = await sdk.work.getRunProgress(arbi, conversationId);
|
|
10566
|
+
const answers = opts.answers ? await answersFor(arbi, progress) : {};
|
|
10567
|
+
if (opts.json) printJson({ ...runJson(progress, answers), timed_out: timedOut });
|
|
10568
|
+
else {
|
|
10569
|
+
printRun(progress, answers);
|
|
10570
|
+
if (timedOut) warn(`Stopped watching after ${opts.timeout}s; the run is still going.`);
|
|
10571
|
+
}
|
|
10572
|
+
})()
|
|
10573
|
+
);
|
|
10574
|
+
run.command("step <con-id> <step-id> <action>").description(
|
|
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");
|
|
10591
|
+
const { arbi } = await resolveWorkspace();
|
|
10592
|
+
const before = await sdk.work.getRunProgress(arbi, conversationId);
|
|
10593
|
+
const plan = before.conversation.plan?.steps ?? [];
|
|
10594
|
+
const step = plan.find((s) => s.id === stepId);
|
|
10595
|
+
if (!step)
|
|
10596
|
+
throw new Error(
|
|
10597
|
+
`No step "${stepId}" in ${conversationId}. Its steps: ${plan.map((s) => s.id).join(", ")}`
|
|
10598
|
+
);
|
|
10599
|
+
const fields = sdk.workflowSteps.stepFields(step);
|
|
10600
|
+
const hasInputs = opts.input.length > 0 || !!opts.inputsFile;
|
|
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") {
|
|
10607
|
+
if (step.kind === "agent")
|
|
10608
|
+
throw new Error(
|
|
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})`
|
|
10610
|
+
);
|
|
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 {
|
|
10626
|
+
const missing = sdk.workflowSteps.missingRequiredFields(step, {
|
|
10627
|
+
...before.conversation.skill_inputs ?? {},
|
|
10628
|
+
...inputs
|
|
10629
|
+
});
|
|
10630
|
+
if (missing.length)
|
|
10631
|
+
throw new Error(
|
|
10632
|
+
`Missing required input${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}. Pass ${missing.map((m) => `--input ${m}=\u2026`).join(" ")}`
|
|
10633
|
+
);
|
|
10634
|
+
}
|
|
10635
|
+
}
|
|
10636
|
+
if (action === "send_back") checkSendBack(plan, stepId, opts.to);
|
|
10637
|
+
await sdk.work.actOnStep(arbi, conversationId, {
|
|
10638
|
+
step: stepId,
|
|
10639
|
+
action,
|
|
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() } : {}
|
|
10643
|
+
});
|
|
10644
|
+
const progress = await sdk.work.getRunProgress(arbi, conversationId);
|
|
10645
|
+
if (opts.json) {
|
|
10646
|
+
printJson(runJson(progress));
|
|
10647
|
+
return;
|
|
10648
|
+
}
|
|
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
|
+
);
|
|
10658
|
+
dim(nextHint(progress, plan));
|
|
10659
|
+
})()
|
|
10660
|
+
);
|
|
10661
|
+
}
|
|
9942
10662
|
var MARKER_START = "# arbi-cli completion start";
|
|
9943
10663
|
var MARKER_END = "# arbi-cli completion end";
|
|
9944
10664
|
var BASH_COMPLETION = `
|
|
@@ -10172,12 +10892,12 @@ function printTree(dir, prefix, maxDepth, depth) {
|
|
|
10172
10892
|
}
|
|
10173
10893
|
}
|
|
10174
10894
|
}
|
|
10175
|
-
function humanizeTtl(
|
|
10176
|
-
if (!Number.isFinite(
|
|
10177
|
-
if (
|
|
10178
|
-
if (
|
|
10179
|
-
if (
|
|
10180
|
-
return `${Math.round(
|
|
10895
|
+
function humanizeTtl(seconds2) {
|
|
10896
|
+
if (!Number.isFinite(seconds2) || seconds2 < 0) return "";
|
|
10897
|
+
if (seconds2 < 60) return `${seconds2}s`;
|
|
10898
|
+
if (seconds2 < 3600) return `${Math.round(seconds2 / 60)}m`;
|
|
10899
|
+
if (seconds2 < 86400) return `${Math.round(seconds2 / 3600)}h`;
|
|
10900
|
+
return `${Math.round(seconds2 / 86400)}d`;
|
|
10181
10901
|
}
|
|
10182
10902
|
function registerSessionCommand(program2) {
|
|
10183
10903
|
const session = program2.command("session").description("List and manage sessions");
|
|
@@ -10239,7 +10959,7 @@ function parseAddress(value) {
|
|
|
10239
10959
|
const name = match?.[1].replace(/^"|"$/g, "");
|
|
10240
10960
|
return name ? { email, name } : { email };
|
|
10241
10961
|
}
|
|
10242
|
-
function
|
|
10962
|
+
function collect2(value, previous = []) {
|
|
10243
10963
|
return [...previous, ...value.split(",").filter((v) => v.trim())];
|
|
10244
10964
|
}
|
|
10245
10965
|
var readTextFile = (path8) => fs7__default.default.readFileSync(path8 === "-" ? 0 : path8, "utf8");
|
|
@@ -10265,7 +10985,7 @@ function buildCompose(options) {
|
|
|
10265
10985
|
};
|
|
10266
10986
|
}
|
|
10267
10987
|
function addComposeOptions(command) {
|
|
10268
|
-
return command.option("--to <addresses>", "Recipient(s), comma-separated or repeated",
|
|
10988
|
+
return command.option("--to <addresses>", "Recipient(s), comma-separated or repeated", collect2).option("--cc <addresses>", "Cc recipient(s)", collect2).option("--bcc <addresses>", "Bcc recipient(s)", collect2).option("-s, --subject <subject>", "Subject").option("--text <text>", "Plain-text body").option("--text-file <path>", "Read the plain-text body from a file (- for stdin)").option("--html <html>", "HTML body (the plain text stays as the fallback)").option("--html-file <path>", "Read the HTML body from a file (- for stdin)").option("-a, --attach <path>", "Attach a file (repeatable)", (v, prev = []) => [
|
|
10269
10989
|
...prev,
|
|
10270
10990
|
v
|
|
10271
10991
|
]).option("--json", "Output as JSON (recommended for scripting / agents)");
|
|
@@ -10532,19 +11252,95 @@ async function resolveProjectId(arbi, explicit) {
|
|
|
10532
11252
|
}
|
|
10533
11253
|
return first;
|
|
10534
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
|
+
}
|
|
10535
11318
|
function registerProjectCommand(program2) {
|
|
10536
|
-
const project = program2.command("project").description(
|
|
10537
|
-
|
|
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(
|
|
10538
11323
|
(opts) => runAction(async () => {
|
|
10539
11324
|
const { arbi } = await resolveAuth();
|
|
10540
|
-
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 });
|
|
10541
11327
|
if (!emitListOrTable(data, opts, "No projects found.")) return;
|
|
10542
11328
|
printTable(
|
|
10543
11329
|
[
|
|
10544
11330
|
{ header: "ID", width: 18, value: (r) => r.external_id },
|
|
10545
|
-
{ header: "NAME", width:
|
|
10546
|
-
{
|
|
10547
|
-
|
|
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 ?? "" }
|
|
10548
11344
|
],
|
|
10549
11345
|
data
|
|
10550
11346
|
);
|
|
@@ -10587,6 +11383,69 @@ function registerProjectCommand(program2) {
|
|
|
10587
11383
|
success(`Updated project ${ref(id)} (${data.name})`);
|
|
10588
11384
|
})()
|
|
10589
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
|
+
);
|
|
10590
11449
|
project.command("delete <id>").description("Delete a project (irreversible; requires an explicit id)").action(
|
|
10591
11450
|
(id) => runAction(async () => {
|
|
10592
11451
|
const { arbi } = await resolveAuth();
|
|
@@ -10718,10 +11577,104 @@ function registerFilesCommand(program2) {
|
|
|
10718
11577
|
await files.commands.find((c) => c.name() === "list").parseAsync(tail, { from: "user" });
|
|
10719
11578
|
});
|
|
10720
11579
|
}
|
|
10721
|
-
|
|
10722
|
-
|
|
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
|
+
}
|
|
10723
11600
|
function registerUsageCommand(program2) {
|
|
10724
|
-
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
|
+
);
|
|
10725
11678
|
usage.command("today").description("Today's tokens used / remaining (for the current user)").option("--json", "Output as JSON").action(
|
|
10726
11679
|
(opts) => runAction(async () => {
|
|
10727
11680
|
const { arbi } = await resolveAuth();
|
|
@@ -10997,7 +11950,7 @@ console.info = (...args) => {
|
|
|
10997
11950
|
_origInfo(...args);
|
|
10998
11951
|
};
|
|
10999
11952
|
var program = new commander.Command();
|
|
11000
|
-
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.77").showHelpAfterError(true).showSuggestionAfterError(true);
|
|
11001
11954
|
registerConfigCommand(program);
|
|
11002
11955
|
registerLoginCommand(program);
|
|
11003
11956
|
registerRegisterCommand(program);
|
|
@@ -11027,6 +11980,7 @@ registerAgentCreateCommand(program);
|
|
|
11027
11980
|
registerAgentCommand(program);
|
|
11028
11981
|
registerTaskCommand(program);
|
|
11029
11982
|
registerWorkflowCommand(program);
|
|
11983
|
+
registerRunCommand(program);
|
|
11030
11984
|
registerCompletionCommand(program);
|
|
11031
11985
|
registerListenCommand(program);
|
|
11032
11986
|
registerSessionCommand(program);
|
|
@@ -11053,7 +12007,7 @@ applyHelpGroups(program, {
|
|
|
11053
12007
|
"Search & ask:": ["ask", "find", "cite", "watch"],
|
|
11054
12008
|
"Tagging:": ["tags", "doctags"],
|
|
11055
12009
|
"Conversations & messaging:": ["convo", "dm", "listen", "notifications", "contacts"],
|
|
11056
|
-
"Agents & tasks:": ["agent", "agent-create", "task", "workflow", "agentconfig", "session"],
|
|
12010
|
+
"Agents & tasks:": ["agent", "agent-create", "task", "workflow", "run", "agentconfig", "session"],
|
|
11057
12011
|
"Account:": ["auth", "usage", "files"],
|
|
11058
12012
|
"System:": ["health", "models", "settings", "tui", "local", "update"]
|
|
11059
12013
|
});
|