@arbidocs/cli 0.4.32 → 0.4.37

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
@@ -3694,7 +3694,7 @@ function getLatestVersion(skipCache = false) {
3694
3694
  }
3695
3695
  }
3696
3696
  function getCurrentVersion() {
3697
- return "0.4.32";
3697
+ return "0.4.37";
3698
3698
  }
3699
3699
  function readChangelog(fromVersion, toVersion) {
3700
3700
  try {
@@ -3747,17 +3747,17 @@ function showChangelog(fromVersion, toVersion) {
3747
3747
  async function checkForUpdates(autoUpdate) {
3748
3748
  try {
3749
3749
  const latest = getLatestVersion();
3750
- if (!latest || latest === "0.4.32") return;
3750
+ if (!latest || latest === "0.4.37") return;
3751
3751
  if (autoUpdate) {
3752
3752
  warn(`
3753
- Your arbi version is out of date (${"0.4.32"} \u2192 ${latest}). Updating...`);
3753
+ Your arbi version is out of date (${"0.4.37"} \u2192 ${latest}). Updating...`);
3754
3754
  child_process.execSync("npm install -g @arbidocs/cli@latest", { stdio: "inherit" });
3755
- showChangelog("0.4.32", latest);
3755
+ showChangelog("0.4.37", latest);
3756
3756
  console.log(`Updated to ${latest}.`);
3757
3757
  } else {
3758
3758
  warn(
3759
3759
  `
3760
- Your arbi version is out of date (${"0.4.32"} \u2192 ${latest}).
3760
+ Your arbi version is out of date (${"0.4.37"} \u2192 ${latest}).
3761
3761
  Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3762
3762
  );
3763
3763
  }
@@ -3789,10 +3789,10 @@ function markNagShown(latest) {
3789
3789
  function hintUpdateOnError() {
3790
3790
  try {
3791
3791
  const cached = readCache();
3792
- if (!cached || cached.latest === "0.4.32") return;
3792
+ if (!cached || cached.latest === "0.4.37") return;
3793
3793
  if (!shouldShowNag(cached.latest)) return;
3794
3794
  warn(
3795
- `Your arbi version is out of date (${"0.4.32"} \u2192 ${cached.latest}). Run "arbi update".`
3795
+ `Your arbi version is out of date (${"0.4.37"} \u2192 ${cached.latest}). Run "arbi update".`
3796
3796
  );
3797
3797
  markNagShown(cached.latest);
3798
3798
  } catch {
@@ -9583,7 +9583,7 @@ function resolveTaskId(taskIdArg) {
9583
9583
  }
9584
9584
  function statusColor2(s) {
9585
9585
  if (s === "completed") return chalk2__default.default.green(s);
9586
- if (s === "failed") return chalk2__default.default.red(s);
9586
+ if (s === "failed" || s === "cancelled") return chalk2__default.default.red(s);
9587
9587
  if (s === "queued" || s === "in_progress") return chalk2__default.default.yellow(s);
9588
9588
  return s;
9589
9589
  }
@@ -9633,7 +9633,7 @@ function registerTaskCommand(program2) {
9633
9633
  const id = resolveTaskId(taskId);
9634
9634
  const { accessToken, config } = await resolveWorkspace(opts?.workspace);
9635
9635
  const result = await sdk.responses.getResponse({ baseUrl: config.baseUrl, accessToken }, id);
9636
- if (result.status === "completed" || result.status === "failed") {
9636
+ if (result.status === "completed" || result.status === "failed" || result.status === "cancelled") {
9637
9637
  updateTaskStatus(id, result.status);
9638
9638
  }
9639
9639
  if (opts?.json) {
@@ -9653,7 +9653,7 @@ function registerTaskCommand(program2) {
9653
9653
  const id = resolveTaskId(taskId);
9654
9654
  const { accessToken, config } = await resolveWorkspace(opts?.workspace);
9655
9655
  const result = await sdk.responses.getResponse({ baseUrl: config.baseUrl, accessToken }, id);
9656
- if (result.status === "completed" || result.status === "failed") {
9656
+ if (result.status === "completed" || result.status === "failed" || result.status === "cancelled") {
9657
9657
  updateTaskStatus(id, result.status);
9658
9658
  }
9659
9659
  if (opts?.json) {
@@ -9675,8 +9675,12 @@ function registerTaskCommand(program2) {
9675
9675
  );
9676
9676
  }
9677
9677
  function registerWorkflowCommand(program2) {
9678
- const workflow = program2.command("workflow").description("Workflows (/v1/workflow): create, list, resume, pause, run, delete");
9679
- 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("--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(
9678
+ const workflow = program2.command("workflow").alias("automation").description("Workflows (/v1/workflow): create, list, resume, pause, run, delete");
9679
+ 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(
9680
+ "--auth-mode <mode>",
9681
+ "scheduled_require_auth or scheduled_permanent",
9682
+ "scheduled_require_auth"
9683
+ ).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(
9680
9684
  (opts) => runAction(async () => {
9681
9685
  const { arbi, workspaceId } = await resolveWorkspace();
9682
9686
  const body = {
@@ -9688,9 +9692,31 @@ function registerWorkflowCommand(program2) {
9688
9692
  timezone: opts.timezone,
9689
9693
  prompt: opts.prompt ?? "",
9690
9694
  // Unattended runs ask the owner for access each time unless the workflow is made permanent.
9691
- auth_mode: "scheduled_require_auth",
9695
+ auth_mode: opts.authMode,
9692
9696
  workspace_ext_id: opts.workspace ?? workspaceId
9693
9697
  };
9698
+ if (!["scheduled_require_auth", "scheduled_permanent"].includes(opts.authMode)) {
9699
+ throw new Error(
9700
+ "Invalid --auth-mode: use scheduled_require_auth or scheduled_permanent"
9701
+ );
9702
+ }
9703
+ if (opts.action) {
9704
+ if (!["annotate_documents", "email_documents"].includes(opts.action)) {
9705
+ throw new Error("Invalid --action: use annotate_documents or email_documents");
9706
+ }
9707
+ const ids = (value) => value?.split(",").map((id) => id.trim()).filter(Boolean) ?? [];
9708
+ body.action = {
9709
+ type: opts.action,
9710
+ documents: {
9711
+ doc_ext_ids: ids(opts.documents),
9712
+ label_ext_ids: ids(opts.labels),
9713
+ latest: !!opts.latest
9714
+ },
9715
+ tag_ext_ids: ids(opts.tags),
9716
+ overwrite: !!opts.overwrite
9717
+ };
9718
+ }
9719
+ if (opts.eventFilter) body.event_filter = JSON.parse(opts.eventFilter);
9694
9720
  if (opts.rrule) body.rrule = opts.rrule;
9695
9721
  if (opts.runAt) body.run_at = opts.runAt;
9696
9722
  if (opts.eventKind) body.event_kind = opts.eventKind;
@@ -9735,7 +9761,7 @@ function registerWorkflowCommand(program2) {
9735
9761
  })()
9736
9762
  );
9737
9763
  }
9738
- workflow.command("run <id>").description("Run a workflow now (creates a task like its trigger would)").option("--json", "Output as JSON").action(
9764
+ workflow.command("run <id>").description("Start the saved action now (document actions do not create a thread)").option("--json", "Output as JSON").action(
9739
9765
  (id, opts) => runAction(async () => {
9740
9766
  const { arbi } = await resolveAuth();
9741
9767
  const data = await sdk.workflows.runWorkflow(arbi, id);
@@ -9743,7 +9769,47 @@ function registerWorkflowCommand(program2) {
9743
9769
  printJson(data);
9744
9770
  return;
9745
9771
  }
9746
- success(`Started workflow ${ref(id)}`);
9772
+ if (data.error) throw new Error(data.error);
9773
+ success(
9774
+ `${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)}` : ""}`
9775
+ );
9776
+ })()
9777
+ );
9778
+ workflow.command("get <id>").description("Read the complete saved automation configuration").action(
9779
+ (id) => runAction(async () => {
9780
+ const { arbi } = await resolveAuth();
9781
+ printJson(await sdk.workflows.getWorkflow(arbi, id));
9782
+ })()
9783
+ );
9784
+ workflow.command("runs <id>").alias("executions").description("Show persisted document-action runs, results and failures").option("--json", "Output as JSON").action(
9785
+ (id, opts) => runAction(async () => {
9786
+ const { arbi } = await resolveAuth();
9787
+ const data = await sdk.workflows.listWorkflowRuns(arbi, id);
9788
+ if (!emitListOrTable(data, opts, "This automation has not run yet.")) return;
9789
+ printTable(
9790
+ [
9791
+ { header: "RUN", width: 10, value: (r) => String(r.id) },
9792
+ { header: "STATUS", width: 14, value: (r) => String(r.status) },
9793
+ { header: "ACTION", width: 22, value: (r) => String(r.action_type) },
9794
+ {
9795
+ header: "DOCUMENTS",
9796
+ width: 12,
9797
+ value: (r) => String(r.document_ext_ids.length)
9798
+ },
9799
+ { header: "CREATED", width: 26, value: (r) => String(r.created_at) },
9800
+ { header: "ERROR", width: 40, value: (r) => String(r.error ?? "") }
9801
+ ],
9802
+ data
9803
+ );
9804
+ })()
9805
+ );
9806
+ 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(
9807
+ (id, opts) => runAction(async () => {
9808
+ const { arbi } = await resolveAuth();
9809
+ const body = JSON.parse(fs5.readFileSync(opts.file, "utf8"));
9810
+ const data = await sdk.workflows.updateWorkflow(arbi, id, body);
9811
+ if (opts.json) printJson(data);
9812
+ else success(`Updated automation ${ref(id)}`);
9747
9813
  })()
9748
9814
  );
9749
9815
  workflow.command("delete <id>").description("Delete a workflow (tasks it already created stay)").action(
@@ -10045,6 +10111,204 @@ function registerSessionCommand(program2) {
10045
10111
  await session.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
10046
10112
  });
10047
10113
  }
10114
+ function parseAddress(value) {
10115
+ const match = /^\s*(.*?)\s*<([^<>\s]+)>\s*$/.exec(value);
10116
+ const email = (match ? match[2] : value).trim();
10117
+ if (!/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(email)) {
10118
+ throw new Error(`Not an email address: ${value}`);
10119
+ }
10120
+ const name = match?.[1].replace(/^"|"$/g, "");
10121
+ return name ? { email, name } : { email };
10122
+ }
10123
+ function collect(value, previous = []) {
10124
+ return [...previous, ...value.split(",").filter((v) => v.trim())];
10125
+ }
10126
+ var readTextFile = (path7) => fs5__default.default.readFileSync(path7 === "-" ? 0 : path7, "utf8");
10127
+ function buildCompose(options) {
10128
+ const read = options.readText ?? readTextFile;
10129
+ if (options.text !== void 0 && options.textFile) {
10130
+ throw new Error("Use either --text or --text-file, not both");
10131
+ }
10132
+ if (options.html !== void 0 && options.htmlFile) {
10133
+ throw new Error("Use either --html or --html-file, not both");
10134
+ }
10135
+ const html = options.html ?? (options.htmlFile ? read(options.htmlFile) : void 0);
10136
+ return {
10137
+ to: (options.to ?? []).map(parseAddress),
10138
+ cc: (options.cc ?? []).map(parseAddress),
10139
+ bcc: (options.bcc ?? []).map(parseAddress),
10140
+ subject: options.subject ?? "",
10141
+ text: options.text ?? (options.textFile ? read(options.textFile) : ""),
10142
+ ...html ? { html } : {},
10143
+ secure_delivery: false,
10144
+ attachments: (options.attach ?? []).map((file) => sdk.mailNode.attachmentFromFile(file))
10145
+ };
10146
+ }
10147
+ function addComposeOptions(command) {
10148
+ 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 = []) => [
10149
+ ...prev,
10150
+ v
10151
+ ]).option("--json", "Output as JSON (recommended for scripting / agents)");
10152
+ }
10153
+ function registerMailCommand(program2) {
10154
+ const mail = program2.command("mail").enablePositionalOptions().description("Your ARBI mailbox: read, draft and send (an agent can only draft)");
10155
+ async function connect() {
10156
+ const { loginResult, config } = await resolveAuth();
10157
+ return {
10158
+ fetch: sdk.mail.createMailFetch({
10159
+ baseUrl: config.baseUrl,
10160
+ accessToken: loginResult.accessToken
10161
+ }),
10162
+ login: loginResult,
10163
+ isAgent: Boolean(loginResult.parentExtId)
10164
+ };
10165
+ }
10166
+ async function connectToRead() {
10167
+ const ctx = await connect();
10168
+ if (ctx.isAgent) {
10169
+ error("Agents can only create drafts. Use: arbi mail draft --to <address> ...");
10170
+ process.exit(1);
10171
+ }
10172
+ await sdk.mail.unlockNativeMail(ctx.fetch, {
10173
+ signingPrivateKey: ctx.login.signingPrivateKey,
10174
+ serverSessionKey: ctx.login.serverSessionKey
10175
+ });
10176
+ return ctx;
10177
+ }
10178
+ mail.command("alias <name>").description("Claim your ARBI address, name@<deployment domain> (one per user)").action(
10179
+ (name) => runAction(async () => {
10180
+ const { fetch: fetch2 } = await connect();
10181
+ success(`Your ARBI email is ${await sdk.mail.claimNativeAlias(fetch2, name.toLowerCase())}`);
10182
+ })()
10183
+ );
10184
+ mail.command("folders").description("List folders with unread counts").option("--json", "Output as JSON").action(
10185
+ (opts) => runAction(async () => {
10186
+ const { fetch: fetch2 } = await connectToRead();
10187
+ const folders = await sdk.mail.listFolders(fetch2);
10188
+ if (opts.json) return printJson(folders);
10189
+ printTable(
10190
+ [
10191
+ { header: "ID", width: 14, value: (f) => f.id },
10192
+ { header: "NAME", width: 20, value: (f) => f.name },
10193
+ { header: "UNREAD", width: 8, value: (f) => String(f.unread ?? 0) }
10194
+ ],
10195
+ folders
10196
+ );
10197
+ })()
10198
+ );
10199
+ mail.command("list").description("List messages in a folder (default: inbox)").option("-f, --folder <id>", "Folder id: inbox, sent, drafts, archive, trash", "inbox").option("-q, --query <text>", "Filter by subject, sender or recipient").option("-l, --limit <n>", "Messages per page", (v) => parseInt(v, 10), 30).option("--cursor <cursor>", "Continue from a previous page (its next_cursor)").option("--json", "Output as JSON (includes next_cursor)").action(
10200
+ (opts) => runAction(async () => {
10201
+ const { fetch: fetch2 } = await connectToRead();
10202
+ const page = await sdk.mail.listMessages(fetch2, {
10203
+ folderId: opts.folder,
10204
+ query: opts.query,
10205
+ limit: opts.limit,
10206
+ cursor: opts.cursor
10207
+ });
10208
+ if (opts.json) return printJson(page);
10209
+ if (page.messages.length === 0) {
10210
+ process.stderr.write("No messages.\n");
10211
+ return;
10212
+ }
10213
+ const people = (m) => (["sent", "drafts"].includes(opts.folder) ? m.to : m.sender).map((a) => a.name || a.email).join(", ");
10214
+ printTable(
10215
+ [
10216
+ { header: "ID", width: 38, value: (m) => m.id },
10217
+ { header: "WHO", width: 24, value: (m) => truncate(people(m), 23) },
10218
+ {
10219
+ header: "SUBJECT",
10220
+ width: 40,
10221
+ value: (m) => truncate(m.subject || "(no subject)", 39)
10222
+ },
10223
+ {
10224
+ header: "FLAGS",
10225
+ width: 14,
10226
+ value: (m) => [
10227
+ m.unread ? "unread" : "",
10228
+ m.attachments.length ? "attach" : "",
10229
+ m.drafted_by ? `by ${m.drafted_by}` : ""
10230
+ ].filter(Boolean).join(" ")
10231
+ }
10232
+ ],
10233
+ page.messages
10234
+ );
10235
+ if (page.next_cursor) dim(`More: --cursor ${page.next_cursor}`);
10236
+ })()
10237
+ );
10238
+ mail.command("read <id>").description("Read a message, or save one of its attachments with --attachment").option("--attachment <id>", "Save this attachment (id from the attachment list) instead").option("-o, --output <path>", "Where to save the attachment (default: its file name)").option("--json", "Output the full message as JSON").action(
10239
+ (id, opts) => runAction(async () => {
10240
+ const { fetch: fetch2 } = await connectToRead();
10241
+ const message = await sdk.mail.getMessage(fetch2, id);
10242
+ if (opts.attachment !== void 0) {
10243
+ const meta = message.attachments.find((a) => a.id === opts.attachment);
10244
+ if (!meta) throw new Error(`No attachment ${opts.attachment} on this message`);
10245
+ const bytes = await sdk.mail.downloadAttachment(fetch2, id, meta.id);
10246
+ const target = opts.output ?? meta.name;
10247
+ fs5__default.default.writeFileSync(target, bytes);
10248
+ success(`Saved ${target} (${bytes.length} bytes)`);
10249
+ return;
10250
+ }
10251
+ if (opts.json) return printJson(message);
10252
+ const who = (list) => list.map((a) => a.name ? `${a.name} <${a.email}>` : a.email).join(", ");
10253
+ console.log(`From: ${who(message.sender)}`);
10254
+ console.log(`To: ${who(message.to)}`);
10255
+ if (message.cc.length) console.log(`Cc: ${who(message.cc)}`);
10256
+ console.log(`Subject: ${message.subject}`);
10257
+ if (message.received_at) console.log(`Date: ${message.received_at}`);
10258
+ if (message.drafted_by) console.log(`Drafted by: ${message.drafted_by}`);
10259
+ if (message.attachments.length) {
10260
+ console.log(
10261
+ `Attachments: ${message.attachments.map((a) => `${a.name} (id ${a.id}, ${a.size} bytes)`).join("; ")}`
10262
+ );
10263
+ }
10264
+ console.log(`
10265
+ ${message.text}`);
10266
+ })()
10267
+ );
10268
+ addComposeOptions(
10269
+ mail.command("draft").description(
10270
+ "Save a draft. Run by an agent, it goes to its owner's Drafts, from their address, for them to review and send"
10271
+ )
10272
+ ).action(
10273
+ (opts) => runAction(async () => {
10274
+ const compose = buildCompose(opts);
10275
+ const { fetch: fetch2, isAgent } = await connect();
10276
+ const saved = await sdk.mail.saveDraft(fetch2, compose);
10277
+ if (opts.json) return printJson(saved);
10278
+ success(
10279
+ isAgent ? `Draft ${saved.id} saved to your owner's Drafts. They review it and send.` : `Draft ${saved.id} saved to Drafts.`
10280
+ );
10281
+ })()
10282
+ );
10283
+ addComposeOptions(
10284
+ mail.command("send").description("Send a message from your ARBI address (not available to agents)")
10285
+ ).option("--request-id <id>", "Idempotency key: retrying with the same id never sends twice").action(
10286
+ (opts) => runAction(async () => {
10287
+ const compose = buildCompose(opts);
10288
+ if (compose.to?.length === 0 && compose.cc?.length === 0 && compose.bcc?.length === 0) {
10289
+ throw new Error("Add at least one recipient with --to, --cc or --bcc");
10290
+ }
10291
+ const { fetch: fetch2, isAgent } = await connect();
10292
+ if (isAgent) {
10293
+ error("Agents cannot send mail. Save a draft instead: arbi mail draft ...");
10294
+ process.exit(1);
10295
+ }
10296
+ const result = await sdk.mail.sendMessage(fetch2, { ...compose, request_id: opts.requestId });
10297
+ if (opts.json) return printJson(result);
10298
+ success(`Sent (${result.id}).`);
10299
+ })()
10300
+ );
10301
+ mail.arguments("[maybeSubcommand]").action((maybe) => {
10302
+ if (maybe) {
10303
+ suggestSubcommandAndExit(
10304
+ "mail",
10305
+ maybe,
10306
+ mail.commands.map((c) => c.name())
10307
+ );
10308
+ }
10309
+ mail.outputHelp();
10310
+ });
10311
+ }
10048
10312
  function registerNotificationsCommand(program2) {
10049
10313
  const notif = program2.command("notifications").enablePositionalOptions().description("Non-DM notifications");
10050
10314
  notif.command("list").description("List non-DM notifications (workspace invites, contact accepts, \u2026)").option("--unread", "Only show unread notifications").option("--type <comma-list>", "Filter to specific type(s), e.g. workspaceuser_invited").option("-l, --limit <n>", "Cap to N most-recent", (v) => parseInt(v, 10)).option("--json", "Output as JSON (recommended for scripting / agents)").action(
@@ -10610,7 +10874,7 @@ console.info = (...args) => {
10610
10874
  _origInfo(...args);
10611
10875
  };
10612
10876
  var program = new commander.Command();
10613
- program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.4.32").showHelpAfterError(true).showSuggestionAfterError(true);
10877
+ program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.4.37").showHelpAfterError(true).showSuggestionAfterError(true);
10614
10878
  registerConfigCommand(program);
10615
10879
  registerLoginCommand(program);
10616
10880
  registerRegisterCommand(program);
@@ -10645,6 +10909,7 @@ registerListenCommand(program);
10645
10909
  registerSessionCommand(program);
10646
10910
  registerLocalCommand(program);
10647
10911
  registerNotificationsCommand(program);
10912
+ registerMailCommand(program);
10648
10913
  registerProjectCommand(program);
10649
10914
  registerFilesCommand(program);
10650
10915
  registerUsageCommand(program);