@arbidocs/cli 0.4.74 → 0.4.76

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/dist/index.js CHANGED
@@ -13,6 +13,7 @@ var client = require('@arbidocs/client');
13
13
  var url = require('url');
14
14
  var crypto = require('crypto');
15
15
  var module$1 = require('module');
16
+ var yaml = require('yaml');
16
17
 
17
18
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
18
19
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -3694,7 +3695,7 @@ function getLatestVersion(skipCache = false) {
3694
3695
  }
3695
3696
  }
3696
3697
  function getCurrentVersion() {
3697
- return "0.4.74";
3698
+ return "0.4.76";
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.74") return;
3751
+ if (!latest || latest === "0.4.76") return;
3751
3752
  if (autoUpdate) {
3752
3753
  warn(`
3753
- Your arbi version is out of date (${"0.4.74"} \u2192 ${latest}). Updating...`);
3754
+ Your arbi version is out of date (${"0.4.76"} \u2192 ${latest}). Updating...`);
3754
3755
  child_process.execSync("npm install -g @arbidocs/cli@latest", { stdio: "inherit" });
3755
- showChangelog("0.4.74", latest);
3756
+ showChangelog("0.4.76", 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.74"} \u2192 ${latest}).
3761
+ Your arbi version is out of date (${"0.4.76"} \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.74") return;
3793
+ if (!cached || cached.latest === "0.4.76") return;
3793
3794
  if (!shouldShowNag(cached.latest)) return;
3794
3795
  warn(
3795
- `Your arbi version is out of date (${"0.4.74"} \u2192 ${cached.latest}). Run "arbi update".`
3796
+ `Your arbi version is out of date (${"0.4.76"} \u2192 ${cached.latest}). Run "arbi update".`
3796
3797
  );
3797
3798
  markNagShown(cached.latest);
3798
3799
  } catch {
@@ -6322,10 +6323,10 @@ function registerDocsCommand(program2) {
6322
6323
  if (!opts.quiet && statusIntervalMs > 0) {
6323
6324
  statusTimer = setInterval(() => printStatus(" "), statusIntervalMs);
6324
6325
  }
6325
- const sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
6326
+ const sleep2 = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
6326
6327
  const backoff = async (attempt) => {
6327
6328
  const delayMs = Math.min(1e3 * Math.pow(2, attempt), 3e4);
6328
- await sleep(delayMs);
6329
+ await sleep2(delayMs);
6329
6330
  };
6330
6331
  const submitBatch = async (batchIds) => {
6331
6332
  const updates = batchIds.map((id) => ({
@@ -6437,12 +6438,12 @@ function registerDocsCommand(program2) {
6437
6438
  }
6438
6439
  submitted += 1;
6439
6440
  if (intervalMs > 0 && j < batch.length - 1) {
6440
- await sleep(intervalMs);
6441
+ await sleep2(intervalMs);
6441
6442
  }
6442
6443
  }
6443
6444
  }
6444
6445
  if (intervalMs > 0 && i + batchSize < docIds.length) {
6445
- await sleep(intervalMs);
6446
+ await sleep2(intervalMs);
6446
6447
  }
6447
6448
  }
6448
6449
  } finally {
@@ -6520,19 +6521,19 @@ Processing complete: completed=${procCompleted} failed=0 elapsed=${totalElapsed
6520
6521
  }
6521
6522
 
6522
6523
  // src/commands/upload-shared.ts
6523
- function printSummary(summary) {
6524
+ function printSummary(summary2) {
6524
6525
  console.log("");
6525
6526
  console.log("Upload summary:");
6526
- console.log(` Files found: ${summary.totalFiles}`);
6527
- success(` Uploaded: ${summary.uploaded}`);
6528
- if (summary.skipped > 0) {
6529
- warn(` Skipped: ${summary.skipped}`);
6527
+ console.log(` Files found: ${summary2.totalFiles}`);
6528
+ success(` Uploaded: ${summary2.uploaded}`);
6529
+ if (summary2.skipped > 0) {
6530
+ warn(` Skipped: ${summary2.skipped}`);
6530
6531
  }
6531
- console.log(` Folders: ${summary.folders}`);
6532
- if (summary.skippedDetails.length > 0) {
6532
+ console.log(` Folders: ${summary2.folders}`);
6533
+ if (summary2.skippedDetails.length > 0) {
6533
6534
  console.log("");
6534
6535
  warn("Skipped files:");
6535
- for (const s of summary.skippedDetails) {
6536
+ for (const s of summary2.skippedDetails) {
6536
6537
  warn(` ${s.file_name}: ${s.reason}`);
6537
6538
  }
6538
6539
  }
@@ -6550,10 +6551,10 @@ function countSupportedFiles(dirPath) {
6550
6551
  }
6551
6552
  return count;
6552
6553
  }
6553
- function mergeBatchResult(summary, uploadedDocs, result, sourceLabel, opts) {
6554
+ function mergeBatchResult(summary2, uploadedDocs, result, sourceLabel, opts) {
6554
6555
  for (const [folder, info] of result.folders) {
6555
- summary.folders += 1;
6556
- summary.totalFiles += info.fileCount;
6556
+ summary2.folders += 1;
6557
+ summary2.totalFiles += info.fileCount;
6557
6558
  if (!opts.json) {
6558
6559
  success(
6559
6560
  ` ${folder || "<root>"}: ${info.fileCount} file(s) \u2192 ${info.doc_ext_ids.length} uploaded`
@@ -6564,10 +6565,10 @@ function mergeBatchResult(summary, uploadedDocs, result, sourceLabel, opts) {
6564
6565
  }
6565
6566
  for (const docId of info.doc_ext_ids) uploadedDocs.set(docId, sourceLabel);
6566
6567
  }
6567
- summary.uploaded += result.doc_ext_ids.length;
6568
- summary.skipped += result.skipped.length;
6569
- summary.docIds.push(...result.doc_ext_ids);
6570
- summary.skippedDetails.push(...result.skipped);
6568
+ summary2.uploaded += result.doc_ext_ids.length;
6569
+ summary2.skipped += result.skipped.length;
6570
+ summary2.docIds.push(...result.doc_ext_ids);
6571
+ summary2.skippedDetails.push(...result.skipped);
6571
6572
  }
6572
6573
  function classifyDirectInputs(inputPaths) {
6573
6574
  const out = [];
@@ -6632,7 +6633,7 @@ async function runDirectUploadMode(inputPaths, opts) {
6632
6633
  });
6633
6634
  const creds = store.requireCredentials();
6634
6635
  const workspaceKey = await sdk.getRawWorkspaceKey(arbi, workspaceId, creds.signingPrivateKeyBase64);
6635
- const summary = {
6636
+ const summary2 = {
6636
6637
  totalFiles: 0,
6637
6638
  uploaded: 0,
6638
6639
  skipped: 0,
@@ -6669,7 +6670,7 @@ async function runDirectUploadMode(inputPaths, opts) {
6669
6670
  warn(`No supported files found in directory: ${inp.originalPath}`);
6670
6671
  continue;
6671
6672
  }
6672
- mergeBatchResult(summary, uploadedDocs, result2, inp.originalPath, opts);
6673
+ mergeBatchResult(summary2, uploadedDocs, result2, inp.originalPath, opts);
6673
6674
  if (!opts.json) {
6674
6675
  success(
6675
6676
  `Uploaded directory: ${inp.originalPath} (${result2.doc_ext_ids.length} document(s) total)`
@@ -6688,7 +6689,7 @@ async function runDirectUploadMode(inputPaths, opts) {
6688
6689
  warn(`No supported files found in zip: ${inp.originalPath}`);
6689
6690
  continue;
6690
6691
  }
6691
- mergeBatchResult(summary, uploadedDocs, result2, inp.originalPath, opts);
6692
+ mergeBatchResult(summary2, uploadedDocs, result2, inp.originalPath, opts);
6692
6693
  if (!opts.json) {
6693
6694
  success(
6694
6695
  `Uploaded zip: ${inp.originalPath} (${result2.doc_ext_ids.length} document(s) total)`
@@ -6706,7 +6707,7 @@ async function runDirectUploadMode(inputPaths, opts) {
6706
6707
  warn(`No supported files found in archive: ${inp.originalPath}`);
6707
6708
  continue;
6708
6709
  }
6709
- mergeBatchResult(summary, uploadedDocs, result, inp.originalPath, opts);
6710
+ mergeBatchResult(summary2, uploadedDocs, result, inp.originalPath, opts);
6710
6711
  if (!opts.json) {
6711
6712
  success(
6712
6713
  `Uploaded archive: ${inp.originalPath} (${result.doc_ext_ids.length} document(s) total)`
@@ -6723,11 +6724,11 @@ async function runDirectUploadMode(inputPaths, opts) {
6723
6724
  folder: opts.folder,
6724
6725
  configExtId: opts.config
6725
6726
  });
6726
- summary.totalFiles += plainFiles.length;
6727
- summary.uploaded += result.doc_ext_ids.length;
6728
- summary.skipped += result.skipped.length;
6729
- summary.docIds.push(...result.doc_ext_ids);
6730
- summary.skippedDetails.push(...result.skipped);
6727
+ summary2.totalFiles += plainFiles.length;
6728
+ summary2.uploaded += result.doc_ext_ids.length;
6729
+ summary2.skipped += result.skipped.length;
6730
+ summary2.docIds.push(...result.doc_ext_ids);
6731
+ summary2.skippedDetails.push(...result.skipped);
6731
6732
  for (let i = 0; i < result.doc_ext_ids.length && i < plainFiles.length; i++) {
6732
6733
  uploadedDocs.set(result.doc_ext_ids[i], path6__default.default.basename(plainFiles[i]));
6733
6734
  }
@@ -6738,9 +6739,9 @@ async function runDirectUploadMode(inputPaths, opts) {
6738
6739
  }
6739
6740
  }
6740
6741
  if (opts.json) {
6741
- console.log(JSON.stringify(summary));
6742
+ console.log(JSON.stringify(summary2));
6742
6743
  } else {
6743
- printSummary(summary);
6744
+ printSummary(summary2);
6744
6745
  }
6745
6746
  if (willWatch && uploadedDocs.size > 0) {
6746
6747
  const pending = new Set(uploadedDocs.keys());
@@ -6849,7 +6850,7 @@ function registerUploadCommand(program2) {
6849
6850
  });
6850
6851
  const uploadedDocs = /* @__PURE__ */ new Map();
6851
6852
  const auth = { baseUrl: config.baseUrl, accessToken };
6852
- const summary = {
6853
+ const summary2 = {
6853
6854
  totalFiles: 0,
6854
6855
  uploaded: 0,
6855
6856
  skipped: 0,
@@ -6883,7 +6884,7 @@ function registerUploadCommand(program2) {
6883
6884
  if (opts.dryRun) {
6884
6885
  const count = countSupportedFiles2(filePath);
6885
6886
  console.log(`${filePath}: ${count} supported file(s) would be uploaded`);
6886
- summary.totalFiles += count;
6887
+ summary2.totalFiles += count;
6887
6888
  continue;
6888
6889
  }
6889
6890
  const result = await sdk.documentsNode.uploadDirectory(auth, filePath, progressCallbacks);
@@ -6906,7 +6907,7 @@ function registerUploadCommand(program2) {
6906
6907
  `Uploaded directory: ${filePath} (${result.doc_ext_ids.length} document(s) total)`
6907
6908
  );
6908
6909
  }
6909
- updateSummary(summary, result);
6910
+ updateSummary(summary2, result);
6910
6911
  for (const id of result.doc_ext_ids) uploadedDocs.set(id, filePath);
6911
6912
  } else if (filePath.toLowerCase().endsWith(".zip")) {
6912
6913
  if (opts.dryRun) {
@@ -6931,7 +6932,7 @@ function registerUploadCommand(program2) {
6931
6932
  }
6932
6933
  success(`Uploaded zip: ${filePath} (${result.doc_ext_ids.length} document(s) total)`);
6933
6934
  }
6934
- updateSummary(summary, result);
6935
+ updateSummary(summary2, result);
6935
6936
  for (const id of result.doc_ext_ids) uploadedDocs.set(id, filePath);
6936
6937
  } else if (sdk.documentsNode.ARCHIVE_EXTENSIONS.has(
6937
6938
  filePath.slice(filePath.lastIndexOf(".")).toLowerCase()
@@ -6960,12 +6961,12 @@ function registerUploadCommand(program2) {
6960
6961
  `Uploaded archive: ${filePath} (${result.doc_ext_ids.length} document(s) total)`
6961
6962
  );
6962
6963
  }
6963
- updateSummary(summary, result);
6964
+ updateSummary(summary2, result);
6964
6965
  for (const id of result.doc_ext_ids) uploadedDocs.set(id, filePath);
6965
6966
  } else {
6966
6967
  if (opts.dryRun) {
6967
6968
  console.log(`${filePath}: single file would be uploaded`);
6968
- summary.totalFiles += 1;
6969
+ summary2.totalFiles += 1;
6969
6970
  continue;
6970
6971
  }
6971
6972
  const result = await sdk.documentsNode.uploadLocalFile(auth, filePath, {
@@ -6981,12 +6982,12 @@ function registerUploadCommand(program2) {
6981
6982
  );
6982
6983
  }
6983
6984
  }
6984
- summary.totalFiles += 1;
6985
- summary.uploaded += result.doc_ext_ids?.length ?? 0;
6986
- summary.docIds.push(...result.doc_ext_ids ?? []);
6985
+ summary2.totalFiles += 1;
6986
+ summary2.uploaded += result.doc_ext_ids?.length ?? 0;
6987
+ summary2.docIds.push(...result.doc_ext_ids ?? []);
6987
6988
  if (result.skipped) {
6988
- summary.skipped += result.skipped.length;
6989
- summary.skippedDetails.push(...result.skipped);
6989
+ summary2.skipped += result.skipped.length;
6990
+ summary2.skippedDetails.push(...result.skipped);
6990
6991
  }
6991
6992
  for (const id of result.doc_ext_ids ?? []) uploadedDocs.set(id, result.fileName);
6992
6993
  }
@@ -6995,9 +6996,9 @@ function registerUploadCommand(program2) {
6995
6996
  return;
6996
6997
  }
6997
6998
  if (opts.json) {
6998
- console.log(JSON.stringify(summary));
6999
+ console.log(JSON.stringify(summary2));
6999
7000
  } else {
7000
- printSummary(summary);
7001
+ printSummary(summary2);
7001
7002
  }
7002
7003
  if (willWatch && uploadedDocs.size > 0) {
7003
7004
  const pending = new Set(uploadedDocs.keys());
@@ -7059,17 +7060,17 @@ Connection closed. ${pending.size} document(s) still processing.`);
7059
7060
  })()
7060
7061
  );
7061
7062
  }
7062
- function updateSummary(summary, result) {
7063
+ function updateSummary(summary2, result) {
7063
7064
  let totalFiles = 0;
7064
7065
  for (const [, info] of result.folders) {
7065
7066
  totalFiles += info.fileCount;
7066
- summary.folders += 1;
7067
+ summary2.folders += 1;
7067
7068
  }
7068
- summary.totalFiles += totalFiles;
7069
- summary.uploaded += result.doc_ext_ids.length;
7070
- summary.skipped += result.skipped.length;
7071
- summary.docIds.push(...result.doc_ext_ids);
7072
- summary.skippedDetails.push(...result.skipped);
7069
+ summary2.totalFiles += totalFiles;
7070
+ summary2.uploaded += result.doc_ext_ids.length;
7071
+ summary2.skipped += result.skipped.length;
7072
+ summary2.docIds.push(...result.doc_ext_ids);
7073
+ summary2.skippedDetails.push(...result.skipped);
7073
7074
  }
7074
7075
  function countSupportedFiles2(dirPath) {
7075
7076
  let count = 0;
@@ -7752,10 +7753,10 @@ Error: ${message}`))
7752
7753
  saveLastMetadata(result.metadata);
7753
7754
  }
7754
7755
  const refs = sdk.countCitations(result.metadata);
7755
- const summary = opts.quiet !== true ? sdk.formatStreamSummary(result, elapsedTime) : "";
7756
- if (summary) {
7756
+ const summary2 = opts.quiet !== true ? sdk.formatStreamSummary(result, elapsedTime) : "";
7757
+ if (summary2) {
7757
7758
  const refSuffix = refs > 0 ? ` \xB7 ${refs} ref${refs === 1 ? "" : "s"}` : "";
7758
- const text = `[${summary}${refSuffix}]`;
7759
+ const text = `[${summary2}${refSuffix}]`;
7759
7760
  const cols = process.stderr.columns || 80;
7760
7761
  const pad = Math.max(0, cols - text.length);
7761
7762
  console.error(chalk2__default.default.dim(" ".repeat(pad) + text));
@@ -9427,6 +9428,9 @@ async function runQuickstart(url) {
9427
9428
  console.log(` Workspace: ${workspaceName} (${ref(workspaceId)})`);
9428
9429
  console.log("");
9429
9430
  dim('Try: arbi ask "hello"');
9431
+ dim(
9432
+ "Workflows with steps: arbi workflow create --name <n> --steps steps.yaml (see arbi run --help)"
9433
+ );
9430
9434
  } catch (err) {
9431
9435
  if (isExitPromptError(err)) throw err;
9432
9436
  error(`Login failed: ${sdk.getErrorMessage(err)}`);
@@ -9793,14 +9797,59 @@ function registerTaskCommand(program2) {
9793
9797
  })()
9794
9798
  );
9795
9799
  }
9800
+ function readStructured(path8) {
9801
+ let text;
9802
+ try {
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) {
9811
+ throw new Error(
9812
+ `${path8} is not valid ${ext === ".json" ? "JSON" : "YAML"}: ${err.message}`
9813
+ );
9814
+ }
9815
+ }
9816
+ function readStepsFile(path8) {
9817
+ try {
9818
+ return sdk.workflowSteps.parsePlanSteps(readStructured(path8));
9819
+ } catch (err) {
9820
+ if (err instanceof sdk.PlanStepsError) throw new Error(`${path8}: ${err.message}`);
9821
+ throw err;
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];
9832
+ }
9833
+
9834
+ // src/commands/workflow.ts
9796
9835
  function registerWorkflowCommand(program2) {
9797
- const workflow = program2.command("workflow").alias("automation").description("Workflows (/v1/workflow): create, list, resume, pause, run, delete");
9798
- workflow.command("create").description("Create a workflow with exactly one trigger").requiredOption("--name <name>", "Workflow name (required)").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("--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(
9836
+ const workflow = program2.command("workflow").alias("automation").description(
9837
+ "Workflows (/v1/workflow): create (optionally with steps), list, update, resume, pause, run, delete"
9838
+ );
9839
+ workflow.command("create").description(
9840
+ "Create a workflow: at most one trigger (none = started by hand) and what it runs: --steps, --prompt, --skill or --action"
9841
+ ).requiredOption("--name <name>", "Workflow name (required)").option(
9842
+ "--steps <file>",
9843
+ "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("--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
9845
  "--auth-mode <mode>",
9800
9846
  "scheduled_require_auth or scheduled_permanent",
9801
9847
  "scheduled_require_auth"
9802
9848
  ).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
9849
  (opts) => runAction(async () => {
9850
+ if (opts.steps && (opts.prompt || opts.skill || opts.action))
9851
+ throw new Error("Use --steps, or --prompt / --skill / --action, not both");
9852
+ const steps = opts.steps ? readStepsFile(opts.steps) : void 0;
9804
9853
  const { arbi, workspaceId } = await resolveWorkspace();
9805
9854
  const body = {
9806
9855
  name: opts.name,
@@ -9842,12 +9891,16 @@ function registerWorkflowCommand(program2) {
9842
9891
  if (opts.after) body.after_conversation_ext_id = opts.after;
9843
9892
  if (opts.skill) body.skill_ref = opts.skill;
9844
9893
  if (opts.agent) body.agent_ext_id = opts.agent;
9894
+ if (steps) body.plan = sdk.workflowSteps.stepsPlan(steps);
9845
9895
  const data = await sdk.workflows.createWorkflow(arbi, body);
9846
9896
  if (opts.json) {
9847
9897
  printJson(data);
9848
9898
  return;
9849
9899
  }
9850
- success(`Created workflow: ${data.name} (${ref(data.external_id)})`);
9900
+ success(
9901
+ `Created workflow: ${data.name} (${ref(data.external_id)})${steps ? ` with ${steps.length} steps` : ""}`
9902
+ );
9903
+ if (steps) dim(`Run it: arbi workflow run ${data.external_id}`);
9851
9904
  })()
9852
9905
  );
9853
9906
  workflow.command("list").description("List your workflows").option("--json", "Output as JSON").action(
@@ -9880,7 +9933,9 @@ function registerWorkflowCommand(program2) {
9880
9933
  })()
9881
9934
  );
9882
9935
  }
9883
- workflow.command("run <id>").description("Start the saved action now (document actions do not create a thread)").option("--json", "Output as JSON").action(
9936
+ workflow.command("run <id>").description(
9937
+ "Run a workflow now: a thread (a stepped one waits for its steps), or a document action without one"
9938
+ ).option("--json", "Output as JSON").action(
9884
9939
  (id, opts) => runAction(async () => {
9885
9940
  const { arbi } = await resolveAuth();
9886
9941
  const data = await sdk.workflows.runWorkflow(arbi, id);
@@ -9890,11 +9945,17 @@ function registerWorkflowCommand(program2) {
9890
9945
  }
9891
9946
  if (data.error) throw new Error(data.error);
9892
9947
  success(
9893
- `${data.started ? "Started" : "Submitted"} automation ${ref(id)}${data.execution_id ? ` \u2014 execution ${data.execution_id}` : ""}${data.conversation_ext_id ? ` \u2014 thread ${ref(data.conversation_ext_id)}` : ""}`
9948
+ `${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
9949
  );
9950
+ if (!data.conversation_ext_id) return;
9951
+ const saved = await sdk.workflows.getWorkflow(arbi, id);
9952
+ if (saved.plan?.steps?.length) {
9953
+ dim(`Its steps: arbi run steps ${data.conversation_ext_id}`);
9954
+ dim(`Follow it: arbi run steps ${data.conversation_ext_id} --watch --answers`);
9955
+ }
9895
9956
  })()
9896
9957
  );
9897
- workflow.command("get <id>").description("Read the complete saved automation configuration").action(
9958
+ workflow.command("get <id>").description("Read the complete saved workflow configuration").action(
9898
9959
  (id) => runAction(async () => {
9899
9960
  const { arbi } = await resolveAuth();
9900
9961
  printJson(await sdk.workflows.getWorkflow(arbi, id));
@@ -9904,7 +9965,7 @@ function registerWorkflowCommand(program2) {
9904
9965
  (id, opts) => runAction(async () => {
9905
9966
  const { arbi } = await resolveAuth();
9906
9967
  const data = await sdk.workflows.listWorkflowRuns(arbi, id);
9907
- if (!emitListOrTable(data, opts, "This automation has not run yet.")) return;
9968
+ if (!emitListOrTable(data, opts, "This workflow has not run yet.")) return;
9908
9969
  printTable(
9909
9970
  [
9910
9971
  { header: "RUN", width: 10, value: (r) => String(r.id) },
@@ -9922,13 +9983,22 @@ function registerWorkflowCommand(program2) {
9922
9983
  );
9923
9984
  })()
9924
9985
  );
9925
- workflow.command("update <id>").description("Update any saved automation settings from a JSON file").requiredOption("--file <path>", "JSON UpdateWorkflowRequest file").option("--json", "Output as JSON").action(
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(
9926
9987
  (id, opts) => runAction(async () => {
9988
+ if (!opts.file && !opts.steps) throw new Error("Pass --steps <file> and/or --file <path>");
9989
+ const steps = opts.steps ? readStepsFile(opts.steps) : void 0;
9990
+ const body = opts.file ? JSON.parse(fs7.readFileSync(opts.file, "utf8")) : {};
9991
+ if (steps)
9992
+ Object.assign(body, {
9993
+ plan: sdk.workflowSteps.stepsPlan(steps),
9994
+ prompt: "",
9995
+ skill_ref: null,
9996
+ skill_inputs: null
9997
+ });
9927
9998
  const { arbi } = await resolveAuth();
9928
- const body = JSON.parse(fs7.readFileSync(opts.file, "utf8"));
9929
9999
  const data = await sdk.workflows.updateWorkflow(arbi, id, body);
9930
10000
  if (opts.json) printJson(data);
9931
- else success(`Updated automation ${ref(id)}`);
10001
+ else success(`Updated workflow ${ref(id)}${steps ? ` \u2014 ${steps.length} steps` : ""}`);
9932
10002
  })()
9933
10003
  );
9934
10004
  workflow.command("delete <id>").description("Delete a workflow (tasks it already created stay)").action(
@@ -9939,6 +10009,226 @@ function registerWorkflowCommand(program2) {
9939
10009
  })()
9940
10010
  );
9941
10011
  }
10012
+ function runJson(progress, answers = {}) {
10013
+ const c = progress.conversation;
10014
+ return {
10015
+ conversation_ext_id: c.external_id,
10016
+ title: c.title,
10017
+ waiting_on: progress.waitingOn,
10018
+ in_progress: c.in_progress,
10019
+ pending_auth_request_id: c.pending_auth_request_id ?? null,
10020
+ last_error: c.last_error ?? null,
10021
+ steps: progress.steps.map((s) => ({
10022
+ id: s.id,
10023
+ title: s.title,
10024
+ kind: s.kind,
10025
+ state: s.phase,
10026
+ after: s.after,
10027
+ answer_id: s.responseId,
10028
+ error: s.error,
10029
+ ...s.responseId && answers[s.responseId] !== void 0 ? { answer: answers[s.responseId] } : {}
10030
+ }))
10031
+ };
10032
+ }
10033
+ function nextHint(progress, planSteps = []) {
10034
+ const id = progress.conversation.external_id;
10035
+ if (progress.conversation.pending_auth_request_id)
10036
+ return "The run is waiting for access to the workspace: approve the request in ARBI.";
10037
+ switch (progress.waitingOn) {
10038
+ case "finished":
10039
+ return "Every step is done or skipped.";
10040
+ case "agent": {
10041
+ const next = progress.steps.find((s) => s.phase === "running") ?? progress.steps.find((s) => s.kind === "agent" && s.phase === "ready");
10042
+ return `Step ${next ? `"${next.id}" ` : ""}is on its way: arbi run steps ${id} --watch`;
10043
+ }
10044
+ case "person": {
10045
+ const step = progress.steps.find((s) => s.kind !== "agent" && s.phase === "ready");
10046
+ const planned = planSteps.find((p) => p.id === step.id);
10047
+ const have = progress.conversation.skill_inputs ?? {};
10048
+ const fields = (planned ? sdk.workflowSteps.stepFields(planned) : []).filter(
10049
+ (f) => have[f.name] === void 0 || have[f.name] === null || have[f.name] === ""
10050
+ );
10051
+ const inputs = fields.map((f) => ` --input ${f.name}=\u2026`).join("");
10052
+ return `Waiting on ${step.kind} step "${step.id}": arbi run step ${id} ${step.id} complete${inputs}`;
10053
+ }
10054
+ default: {
10055
+ const failed = progress.steps.find((s) => s.phase === "failed");
10056
+ 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.";
10057
+ }
10058
+ }
10059
+ }
10060
+ function printRun(progress, answers) {
10061
+ const c = progress.conversation;
10062
+ bold(`Run ${c.external_id}${c.title ? ` \u2014 ${c.title}` : ""}`);
10063
+ printTable(
10064
+ [
10065
+ { header: "STEP", width: 16, value: (r) => r.id },
10066
+ { header: "KIND", width: 8, value: (r) => r.kind },
10067
+ { header: "STATE", width: 9, value: (r) => r.phase },
10068
+ { header: "ANSWER", width: 16, value: (r) => r.responseId ?? "" },
10069
+ { header: "TITLE", width: 40, value: (r) => r.title }
10070
+ ],
10071
+ progress.steps
10072
+ );
10073
+ for (const s of progress.steps) {
10074
+ if (s.phase === "failed" && s.error) warn(`${s.id}: ${s.error}`);
10075
+ if (!s.responseId || answers[s.responseId] === void 0) continue;
10076
+ console.log("");
10077
+ bold(`\u2500\u2500 ${s.id}: ${s.title} (${s.responseId})`);
10078
+ console.log(answers[s.responseId]);
10079
+ }
10080
+ console.log("");
10081
+ dim(nextHint(progress, c.plan?.steps ?? []));
10082
+ }
10083
+ async function answersFor(arbi, progress) {
10084
+ const ids = progress.steps.filter((s) => s.kind === "agent" && s.phase === "done" && s.responseId).map((s) => s.responseId);
10085
+ return sdk.work.getStepAnswers(arbi, progress.conversation.external_id, ids);
10086
+ }
10087
+ var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
10088
+ var summary = (p) => p.steps.map((s) => `${s.id}:${s.phase}`).join(" ");
10089
+ async function watchRun(arbi, conversationId, { intervalMs, timeoutMs, quiet }) {
10090
+ const deadline = Date.now() + timeoutMs;
10091
+ let progress = await sdk.work.getRunProgress(arbi, conversationId);
10092
+ let last = "";
10093
+ const report = () => {
10094
+ const now = summary(progress);
10095
+ if (!quiet && now !== last) process.stderr.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${now}
10096
+ `);
10097
+ last = now;
10098
+ };
10099
+ report();
10100
+ while (progress.waitingOn === "agent" && !progress.conversation.pending_auth_request_id) {
10101
+ if (Date.now() >= deadline) return { progress, timedOut: true };
10102
+ await sleep(intervalMs);
10103
+ progress = await sdk.work.getRunProgress(arbi, conversationId);
10104
+ report();
10105
+ }
10106
+ return { progress, timedOut: false };
10107
+ }
10108
+ function seconds(value, flag) {
10109
+ const n = Number(value);
10110
+ if (!Number.isFinite(n) || n <= 0) throw new Error(`${flag} must be a positive number of seconds`);
10111
+ return n * 1e3;
10112
+ }
10113
+ function gatherInputs(pairs, file, fields) {
10114
+ return {
10115
+ ...file ? readInputsFile(file) : {},
10116
+ ...sdk.workflowSteps.parseInputPairs(pairs, fields)
10117
+ };
10118
+ }
10119
+ function registerRunCommand(program2) {
10120
+ const run = program2.command("run").description(
10121
+ "Stepped workflow runs: start one from a steps file, see its steps, complete or skip a step"
10122
+ );
10123
+ 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(
10124
+ (opts) => runAction(async () => {
10125
+ const steps = readStepsFile(opts.steps);
10126
+ const inputs = gatherInputs(
10127
+ opts.input,
10128
+ opts.inputsFile,
10129
+ steps.flatMap((s) => sdk.workflowSteps.stepFields(s))
10130
+ );
10131
+ const { arbi } = await resolveWorkspace();
10132
+ const created = await sdk.work.createWork(arbi, {
10133
+ title: opts.title,
10134
+ plan: sdk.workflowSteps.stepsPlan(steps),
10135
+ skill_inputs: Object.keys(inputs).length ? inputs : null,
10136
+ agent_ext_id: opts.agent ?? null,
10137
+ shared: opts.shared ?? null,
10138
+ auth_mode: "run_now_temp"
10139
+ });
10140
+ const progress = await sdk.work.getRunProgress(arbi, created.external_id);
10141
+ if (opts.json) {
10142
+ printJson(runJson(progress));
10143
+ return;
10144
+ }
10145
+ success(`Started run ${ref(created.external_id)} \u2014 ${opts.title}`);
10146
+ dim(`Follow it: arbi run steps ${created.external_id} --watch`);
10147
+ dim(nextHint(progress, steps));
10148
+ })()
10149
+ );
10150
+ run.command("steps <con-id>").description(
10151
+ "A run's steps and their state (pending, ready, running, done, skipped, failed), with each done agent step's answer id"
10152
+ ).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(
10153
+ (conversationId, opts) => runAction(async () => {
10154
+ const intervalMs = seconds(opts.interval, "--interval");
10155
+ const timeoutMs = seconds(opts.timeout, "--timeout");
10156
+ const { arbi } = await resolveWorkspace();
10157
+ let progress;
10158
+ let timedOut = false;
10159
+ if (opts.watch) {
10160
+ ;
10161
+ ({ progress, timedOut } = await watchRun(arbi, conversationId, {
10162
+ intervalMs,
10163
+ timeoutMs,
10164
+ quiet: !!opts.json
10165
+ }));
10166
+ } else progress = await sdk.work.getRunProgress(arbi, conversationId);
10167
+ const answers = opts.answers ? await answersFor(arbi, progress) : {};
10168
+ if (opts.json) printJson({ ...runJson(progress, answers), timed_out: timedOut });
10169
+ else {
10170
+ printRun(progress, answers);
10171
+ if (timedOut) warn(`Stopped watching after ${opts.timeout}s; the run is still going.`);
10172
+ }
10173
+ })()
10174
+ );
10175
+ run.command("step <con-id> <step-id> <action>").description(
10176
+ "Act on a step: complete a form, upload or review step (a form takes its inputs), or skip any step"
10177
+ ).option("--input <name=value>", "A form input (repeatable)", collect, []).option("--inputs-file <file>", "Form inputs as a JSON or YAML object").option("--json", "Output as JSON").action(
10178
+ (conversationId, stepId, action, opts) => runAction(async () => {
10179
+ if (action !== "complete" && action !== "skip")
10180
+ throw new Error(`Unknown action "${action}": use complete or skip`);
10181
+ const { arbi } = await resolveWorkspace();
10182
+ const before = await sdk.work.getRunProgress(arbi, conversationId);
10183
+ const plan = before.conversation.plan?.steps ?? [];
10184
+ const step = plan.find((s) => s.id === stepId);
10185
+ if (!step)
10186
+ throw new Error(
10187
+ `No step "${stepId}" in ${conversationId}. Its steps: ${plan.map((s) => s.id).join(", ")}`
10188
+ );
10189
+ const hasInputs = opts.input.length > 0 || !!opts.inputsFile;
10190
+ if (hasInputs && (action === "skip" || step.kind !== "form"))
10191
+ throw new Error("Only completing a form step takes --input / --inputs-file");
10192
+ let inputs;
10193
+ if (action === "complete") {
10194
+ if (step.kind === "agent")
10195
+ throw new Error(
10196
+ `"${stepId}" is an agent step: it runs itself. Skip it with: arbi run step ${conversationId} ${stepId} skip`
10197
+ );
10198
+ const phase = before.steps.find((s) => s.id === stepId)?.phase;
10199
+ if (phase === "pending") {
10200
+ const waits = before.steps.find((s) => s.id === stepId)?.after ?? [];
10201
+ throw new Error(`"${stepId}" waits for ${waits.join(", ")} to finish first`);
10202
+ }
10203
+ if (step.kind === "form") {
10204
+ inputs = gatherInputs(opts.input, opts.inputsFile, sdk.workflowSteps.stepFields(step));
10205
+ const missing = sdk.workflowSteps.missingRequiredFields(step, {
10206
+ ...before.conversation.skill_inputs ?? {},
10207
+ ...inputs
10208
+ });
10209
+ if (missing.length)
10210
+ throw new Error(
10211
+ `Missing required input${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}. Pass ${missing.map((m) => `--input ${m}=\u2026`).join(" ")}`
10212
+ );
10213
+ }
10214
+ }
10215
+ await sdk.work.actOnStep(arbi, conversationId, {
10216
+ step: stepId,
10217
+ action,
10218
+ ...inputs && Object.keys(inputs).length ? { inputs } : {}
10219
+ });
10220
+ const progress = await sdk.work.getRunProgress(arbi, conversationId);
10221
+ if (opts.json) {
10222
+ printJson(runJson(progress));
10223
+ return;
10224
+ }
10225
+ success(
10226
+ `${action === "complete" ? "Completed" : "Skipped"} step "${stepId}" of ${ref(conversationId)}`
10227
+ );
10228
+ dim(nextHint(progress, plan));
10229
+ })()
10230
+ );
10231
+ }
9942
10232
  var MARKER_START = "# arbi-cli completion start";
9943
10233
  var MARKER_END = "# arbi-cli completion end";
9944
10234
  var BASH_COMPLETION = `
@@ -10172,12 +10462,12 @@ function printTree(dir, prefix, maxDepth, depth) {
10172
10462
  }
10173
10463
  }
10174
10464
  }
10175
- function humanizeTtl(seconds) {
10176
- if (!Number.isFinite(seconds) || seconds < 0) return "";
10177
- if (seconds < 60) return `${seconds}s`;
10178
- if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
10179
- if (seconds < 86400) return `${Math.round(seconds / 3600)}h`;
10180
- return `${Math.round(seconds / 86400)}d`;
10465
+ function humanizeTtl(seconds2) {
10466
+ if (!Number.isFinite(seconds2) || seconds2 < 0) return "";
10467
+ if (seconds2 < 60) return `${seconds2}s`;
10468
+ if (seconds2 < 3600) return `${Math.round(seconds2 / 60)}m`;
10469
+ if (seconds2 < 86400) return `${Math.round(seconds2 / 3600)}h`;
10470
+ return `${Math.round(seconds2 / 86400)}d`;
10181
10471
  }
10182
10472
  function registerSessionCommand(program2) {
10183
10473
  const session = program2.command("session").description("List and manage sessions");
@@ -10239,7 +10529,7 @@ function parseAddress(value) {
10239
10529
  const name = match?.[1].replace(/^"|"$/g, "");
10240
10530
  return name ? { email, name } : { email };
10241
10531
  }
10242
- function collect(value, previous = []) {
10532
+ function collect2(value, previous = []) {
10243
10533
  return [...previous, ...value.split(",").filter((v) => v.trim())];
10244
10534
  }
10245
10535
  var readTextFile = (path8) => fs7__default.default.readFileSync(path8 === "-" ? 0 : path8, "utf8");
@@ -10265,7 +10555,7 @@ function buildCompose(options) {
10265
10555
  };
10266
10556
  }
10267
10557
  function addComposeOptions(command) {
10268
- return command.option("--to <addresses>", "Recipient(s), comma-separated or repeated", collect).option("--cc <addresses>", "Cc recipient(s)", collect).option("--bcc <addresses>", "Bcc recipient(s)", collect).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 = []) => [
10558
+ 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
10559
  ...prev,
10270
10560
  v
10271
10561
  ]).option("--json", "Output as JSON (recommended for scripting / agents)");
@@ -10997,7 +11287,7 @@ console.info = (...args) => {
10997
11287
  _origInfo(...args);
10998
11288
  };
10999
11289
  var program = new commander.Command();
11000
- program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.4.74").showHelpAfterError(true).showSuggestionAfterError(true);
11290
+ program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.4.76").showHelpAfterError(true).showSuggestionAfterError(true);
11001
11291
  registerConfigCommand(program);
11002
11292
  registerLoginCommand(program);
11003
11293
  registerRegisterCommand(program);
@@ -11027,6 +11317,7 @@ registerAgentCreateCommand(program);
11027
11317
  registerAgentCommand(program);
11028
11318
  registerTaskCommand(program);
11029
11319
  registerWorkflowCommand(program);
11320
+ registerRunCommand(program);
11030
11321
  registerCompletionCommand(program);
11031
11322
  registerListenCommand(program);
11032
11323
  registerSessionCommand(program);
@@ -11053,7 +11344,7 @@ applyHelpGroups(program, {
11053
11344
  "Search & ask:": ["ask", "find", "cite", "watch"],
11054
11345
  "Tagging:": ["tags", "doctags"],
11055
11346
  "Conversations & messaging:": ["convo", "dm", "listen", "notifications", "contacts"],
11056
- "Agents & tasks:": ["agent", "agent-create", "task", "workflow", "agentconfig", "session"],
11347
+ "Agents & tasks:": ["agent", "agent-create", "task", "workflow", "run", "agentconfig", "session"],
11057
11348
  "Account:": ["auth", "usage", "files"],
11058
11349
  "System:": ["health", "models", "settings", "tui", "local", "update"]
11059
11350
  });