@dench.com/cli 3.6.6-staging.386 → 3.7.0-staging.387

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/crm.ts CHANGED
@@ -239,6 +239,7 @@ function normalizeRpcError(error: unknown): Error {
239
239
  userFacingConvexErrorMessage(error) ??
240
240
  (error instanceof Error ? error.message : String(error)),
241
241
  ),
242
+ { cause: error },
242
243
  );
243
244
  }
244
245
 
@@ -246,7 +247,8 @@ export function shift(args: string[], expected: string): string {
246
247
  try {
247
248
  return shiftRaw(args, expected);
248
249
  } catch (error) {
249
- if (error instanceof CliArgError) throw new CrmCliError(error.message);
250
+ if (error instanceof CliArgError)
251
+ throw new CrmCliError(error.message, { cause: error });
250
252
  throw error;
251
253
  }
252
254
  }
@@ -290,7 +292,8 @@ function resolveLimitFlag(
290
292
  try {
291
293
  parsed = parseLimitRaw(raw, opts);
292
294
  } catch (error) {
293
- if (error instanceof CliArgError) throw new CrmCliError(error.message);
295
+ if (error instanceof CliArgError)
296
+ throw new CrmCliError(error.message, { cause: error });
294
297
  throw error;
295
298
  }
296
299
  if (parsed.clamped) {
@@ -365,7 +368,8 @@ function parseJson(value: string | undefined): unknown {
365
368
  try {
366
369
  return parseJsonRaw(value);
367
370
  } catch (error) {
368
- if (error instanceof CliArgError) throw new CrmCliError(error.message);
371
+ if (error instanceof CliArgError)
372
+ throw new CrmCliError(error.message, { cause: error });
369
373
  throw error;
370
374
  }
371
375
  }
@@ -377,7 +381,8 @@ export function assertNoUnknownFlags(
377
381
  try {
378
382
  assertNoUnknownFlagsRaw(args, command);
379
383
  } catch (error) {
380
- if (error instanceof CliArgError) throw new CrmCliError(error.message);
384
+ if (error instanceof CliArgError)
385
+ throw new CrmCliError(error.message, { cause: error });
381
386
  throw error;
382
387
  }
383
388
  }
@@ -780,11 +785,23 @@ export async function callQuery(
780
785
  }
781
786
  }
782
787
 
788
+ /** Every parsed token must be accounted for before an external effect. */
789
+ function assertWriteArguments(ctx: CrmCliContext): void {
790
+ assertNoUnknownFlags(ctx.args, "crm");
791
+ if (ctx.args.length) {
792
+ const error = new CliArgError(
793
+ `Unexpected argument: ${ctx.args[0]}. Run the command with --help for usage.`,
794
+ );
795
+ throw new CrmCliError(error.message, { cause: error });
796
+ }
797
+ }
798
+
783
799
  export async function callMutation(
784
800
  ctx: CrmCliContext,
785
801
  fn: Parameters<ConvexHttpClient["mutation"]>[0],
786
802
  args: Record<string, unknown> = {},
787
803
  ): Promise<unknown> {
804
+ assertWriteArguments(ctx);
788
805
  try {
789
806
  return await ctx.convex.mutation(fn, {
790
807
  ...args,
@@ -800,6 +817,7 @@ export async function callAction(
800
817
  fn: Parameters<ConvexHttpClient["action"]>[0],
801
818
  args: Record<string, unknown> = {},
802
819
  ): Promise<unknown> {
820
+ assertWriteArguments(ctx);
803
821
  try {
804
822
  return await ctx.convex.action(fn, {
805
823
  ...args,
@@ -914,6 +932,9 @@ const api = {
914
932
  },
915
933
  objects: {
916
934
  list: makeFunctionReference<"query">("functions/crm/objects:list"),
935
+ listCompact: makeFunctionReference<"query">(
936
+ "functions/crm/objects:listCompact",
937
+ ),
917
938
  get: makeFunctionReference<"query">("functions/crm/objects:get"),
918
939
  materializeView: makeFunctionReference<"action">(
919
940
  "functions/crm/objects:completeView",
@@ -963,6 +984,9 @@ const api = {
963
984
  },
964
985
  fields: {
965
986
  list: makeFunctionReference<"query">("functions/crm/fields:list"),
987
+ listCompact: makeFunctionReference<"query">(
988
+ "functions/crm/fields:listCompact",
989
+ ),
966
990
  create: makeFunctionReference<"mutation">("functions/crm/fields:create"),
967
991
  update: makeFunctionReference<"mutation">("functions/crm/fields:update"),
968
992
  remove: makeFunctionReference<"mutation">("functions/crm/fields:remove"),
@@ -1013,6 +1037,9 @@ const api = {
1013
1037
  ),
1014
1038
  aggregate: makeFunctionReference<"query">("functions/crm/query:aggregate"),
1015
1039
  search: makeFunctionReference<"query">("functions/crm/query:search"),
1040
+ searchPage: makeFunctionReference<"query">(
1041
+ "functions/crm/query:searchPage",
1042
+ ),
1016
1043
  },
1017
1044
  statuses: {
1018
1045
  list: makeFunctionReference<"query">("functions/crm/statuses:list"),
@@ -1459,8 +1486,16 @@ async function runObjectsCommand(ctx: CrmCliContext): Promise<void> {
1459
1486
  const verb = ctx.args.shift();
1460
1487
  switch (verb) {
1461
1488
  case "list": {
1489
+ const compact = hasFlag(ctx.args, "--compact");
1462
1490
  assertNoUnknownFlags(ctx.args, "crm objects list");
1463
- out(ctx, await callQuery(ctx, api.objects.list, {}));
1491
+ out(
1492
+ ctx,
1493
+ await callQuery(
1494
+ ctx,
1495
+ compact ? api.objects.listCompact : api.objects.list,
1496
+ {},
1497
+ ),
1498
+ );
1464
1499
  return;
1465
1500
  }
1466
1501
  case "get": {
@@ -2159,8 +2194,16 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
2159
2194
  switch (verb) {
2160
2195
  case "list": {
2161
2196
  const objectName = shift(ctx.args, "object name");
2197
+ const compact = hasFlag(ctx.args, "--compact");
2162
2198
  assertNoUnknownFlags(ctx.args, "crm fields list");
2163
- out(ctx, await callQuery(ctx, api.fields.list, { objectName }));
2199
+ out(
2200
+ ctx,
2201
+ await callQuery(
2202
+ ctx,
2203
+ compact ? api.fields.listCompact : api.fields.list,
2204
+ { objectName },
2205
+ ),
2206
+ );
2164
2207
  return;
2165
2208
  }
2166
2209
  case "create": {
@@ -2294,7 +2337,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
2294
2337
  // Assert before consuming the rest as positional field names so a
2295
2338
  // typo'd flag fails loudly rather than getting silently filtered.
2296
2339
  assertNoUnknownFlags(ctx.args, "crm fields reorder");
2297
- const orderedFieldNames = [...ctx.args];
2340
+ const orderedFieldNames = ctx.args.splice(0);
2298
2341
  out(
2299
2342
  ctx,
2300
2343
  await callMutation(ctx, api.fields.reorder, {
@@ -2311,7 +2354,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
2311
2354
  // there are no stray flags so a typo'd `--limit` fails loud
2312
2355
  // instead of being silently consumed as an enum value.
2313
2356
  assertNoUnknownFlags(ctx.args, "crm fields enum-reorder");
2314
- const orderedValues = [...ctx.args];
2357
+ const orderedValues = ctx.args.splice(0);
2315
2358
  if (orderedValues.length === 0) {
2316
2359
  throw new CrmCliError(
2317
2360
  "dench crm fields enum-reorder requires at least one value. " +
@@ -2417,6 +2460,15 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
2417
2460
  const objectName = shift(ctx.args, "object name");
2418
2461
  const withMeta = hasFlag(ctx.args, "--with-meta");
2419
2462
  const cursorSpecified = ctx.args.includes("--cursor");
2463
+ const cursorValue = ctx.args[ctx.args.indexOf("--cursor") + 1];
2464
+ if (
2465
+ cursorSpecified &&
2466
+ (!cursorValue?.trim() || cursorValue.startsWith("--"))
2467
+ )
2468
+ throw new CrmCliError(
2469
+ "--cursor requires a non-empty continuation cursor.",
2470
+ { cause: new CliArgError("Invalid --cursor") },
2471
+ );
2420
2472
  const cursor = getFlag(ctx.args, "--cursor");
2421
2473
  if (cursorSpecified && (!cursor?.trim() || cursor.startsWith("--")))
2422
2474
  throw new CrmCliError(
@@ -2483,8 +2535,7 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
2483
2535
  { entryCount?: number; visibility?: string } | null,
2484
2536
  ];
2485
2537
  const entries = Array.isArray(result?.page) ? result.page : [];
2486
- const isDone =
2487
- typeof result?.isDone === "boolean" ? result.isDone : null;
2538
+ const isDone = typeof result?.isDone === "boolean" ? result.isDone : null;
2488
2539
  const truncated = result?.truncated === true;
2489
2540
  const continueCursor =
2490
2541
  isDone === false ? result?.continueCursor || null : null;
@@ -3234,25 +3285,31 @@ async function runAggregateCommand(ctx: CrmCliContext): Promise<void> {
3234
3285
 
3235
3286
  async function runSearchCommand(ctx: CrmCliContext): Promise<void> {
3236
3287
  const objectName = getFlag(ctx.args, "--object");
3237
- // Search caps at 100 server-side. Warn on stderr when clamped (the
3238
- // output is a bare JSON array, so stdout must stay untouched).
3288
+ const selectRaw = getFlag(ctx.args, "--select");
3289
+ const select = selectRaw?.split(",").map((name) => name.trim());
3290
+ if (select && (select.length > 30 || select.some((name) => !name))) {
3291
+ throw new CrmCliError(
3292
+ "--select requires between 1 and 30 nonempty field names.",
3293
+ );
3294
+ }
3295
+ const page = hasFlag(ctx.args, "--page");
3296
+ const cursor = getFlag(ctx.args, "--cursor");
3297
+ const projected = select !== undefined || page || cursor !== undefined;
3239
3298
  const limit = resolveLimitFlag(ctx.args, {
3240
- defaultValue: 50,
3299
+ defaultValue: projected ? 20 : 50,
3241
3300
  cap: 100,
3242
3301
  command: "crm search",
3243
3302
  });
3244
- // Drain known flags before joining the rest into the search text so a
3245
- // typo like `--objct` surfaces instead of being concatenated into the
3246
- // query string.
3247
3303
  assertNoUnknownFlags(ctx.args, "crm search");
3248
3304
  const text = ctx.args.join(" ").trim();
3249
3305
  if (!text) throw new CrmCliError("Search text required");
3250
3306
  out(
3251
3307
  ctx,
3252
- await callQuery(ctx, api.query.search, {
3308
+ await callQuery(ctx, projected ? api.query.searchPage : api.query.search, {
3253
3309
  text,
3254
3310
  objectName,
3255
3311
  limit: limit.effective,
3312
+ ...(projected ? { select, cursor } : {}),
3256
3313
  }),
3257
3314
  );
3258
3315
  }
@@ -3382,7 +3439,8 @@ async function runActionsCommand(ctx: CrmCliContext): Promise<void> {
3382
3439
  const objectName = shift(ctx.args, "object name");
3383
3440
  const fieldId = shift(ctx.args, "field id");
3384
3441
  const actionId = shift(ctx.args, "action id");
3385
- const entryIds = ctx.args.filter((a) => !a.startsWith("--"));
3442
+ assertNoUnknownFlags(ctx.args, "crm actions run");
3443
+ const entryIds = ctx.args.splice(0);
3386
3444
  if (entryIds.length === 0) {
3387
3445
  throw new CrmCliError("Provide at least one entry id");
3388
3446
  }
@@ -3922,7 +3980,7 @@ export function remapKeys(
3922
3980
  // ── SQL escape hatch ─────────────────────────────────────────────────────
3923
3981
 
3924
3982
  async function runSqlCommand(ctx: CrmCliContext): Promise<void> {
3925
- const sql = ctx.args.join(" ").trim();
3983
+ const sql = ctx.args.splice(0).join(" ").trim();
3926
3984
  if (!sql) {
3927
3985
  throw new CrmCliError("dench crm sql '<SELECT ... FROM ...>'");
3928
3986
  }
@@ -4411,6 +4469,7 @@ async function enrichCellThroughGateway(
4411
4469
  ctx: CrmCliContext,
4412
4470
  args: CellEnrichmentArgs,
4413
4471
  ): Promise<Record<string, unknown>> {
4472
+ assertWriteArguments(ctx);
4414
4473
  const target = await resolveEnrichmentTarget(ctx, args);
4415
4474
  const entry = asEntry(
4416
4475
  await callQuery(ctx, api.entries.get, {
@@ -4434,6 +4493,7 @@ async function enrichObjectThroughGateway(
4434
4493
  ctx: CrmCliContext,
4435
4494
  args: ObjectEnrichmentArgs,
4436
4495
  ): Promise<Record<string, unknown>> {
4496
+ assertWriteArguments(ctx);
4437
4497
  const target = await resolveEnrichmentTarget(ctx, args);
4438
4498
  const entries = asEntries(
4439
4499
  await callQuery(ctx, api.entries.list, {
@@ -4615,6 +4675,7 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
4615
4675
  const lookups = input.map((value, index) =>
4616
4676
  requireNonEmptyJsonRecord(value, `lookup ${index + 1}`),
4617
4677
  );
4678
+ assertWriteArguments(ctx);
4618
4679
  out(ctx, await enrichProfiles({ kind, lookups }, ctx.enrichmentGateway));
4619
4680
  return;
4620
4681
  }
@@ -4720,10 +4781,14 @@ async function runReportsCommand(ctx: CrmCliContext): Promise<void> {
4720
4781
  }
4721
4782
 
4722
4783
  function crmHelp(): void {
4723
- console.log(`Usage: dench crm <subcommand>
4784
+ console.log(crmHelpText());
4785
+ }
4786
+
4787
+ export function crmHelpText(): string {
4788
+ return `Usage: dench crm <subcommand>
4724
4789
 
4725
4790
  Objects (CRM tables):
4726
- dench crm objects list [--json]
4791
+ dench crm objects list [--json] [--compact]
4727
4792
  dench crm objects get <name>
4728
4793
  dench crm objects create <name> [--description ...] [--default-view kanban] [--icon ...]
4729
4794
  dench crm objects update <name> [--default-view ...] [--icon ...] [--display-name "Clients"] [--display-name-singular "Client"] [--visibility workspace|owner_only|owner_plus_members]
@@ -4810,7 +4875,7 @@ Sidebar (progressive disclosure — Library + home Pinned strip; layout is WORKS
4810
4875
  shared shortcut, not a Library move; open to any member.
4811
4876
 
4812
4877
  Fields (columns):
4813
- dench crm fields list <object> [--json]
4878
+ dench crm fields list <object> [--compact] [--json]
4814
4879
  dench crm fields create <object> <name> --type text|email|phone|url|number|boolean|date|enum|relation|user|tags|action|file|richtext \\
4815
4880
  [--required] [--description ...] [--enum-values JSON] [--enum-colors JSON] \\
4816
4881
  [--enum-multiple] [--related-object <name>] [--relationship many_to_one|many_to_many] \\
@@ -5031,7 +5096,10 @@ Aggregates:
5031
5096
  dench crm aggregate <object> --field Status [--sum Amount]
5032
5097
 
5033
5098
  Full-text search:
5034
- dench crm search [--object <name>] [--limit N] <text>
5099
+ dench crm search [--object <name>] [--select "Name,Email"] [--limit N] [--page] [--cursor <cursor>] <text>
5100
+ --select returns a compact page with stable IDs, selected cells, isDone
5101
+ and continueCursor. Follow cursors until isDone for complete results.
5102
+ Without --select/--page/--cursor the legacy full-row array is unchanged.
5035
5103
 
5036
5104
  Statuses (kanban columns):
5037
5105
  dench crm statuses list <object>
@@ -5215,5 +5283,5 @@ unambiguous, but prefer canonical names from 'crm objects list'.
5215
5283
 
5216
5284
  Global flags:
5217
5285
  --json Output raw JSON instead of pretty-printed text.
5218
- `);
5286
+ `;
5219
5287
  }
package/dench.ts CHANGED
@@ -28,10 +28,12 @@ import {
28
28
  resolveHostFromArgs,
29
29
  STAGING_HOST,
30
30
  } from "./host";
31
+ import { CliArgError, isHelpRequest } from "./lib/cli-args";
31
32
  import { cliVersion } from "./lib/cli-version";
33
+ import { assertDeclaredCommandFlags } from "./lib/command-args";
32
34
  import { denchConfigDirectory } from "./lib/config-path.mjs";
33
35
  import { resolveOrganizationBySelector } from "./lib/organizationSelector";
34
- import { backendErrorText } from "./lib/public-error";
36
+ import { backendErrorText, publicErrorDetails } from "./lib/public-error";
35
37
  import { formatApprovalOpenMessage, openUrl } from "./openUrl";
36
38
  import {
37
39
  explicitSessionKeyInput,
@@ -215,7 +217,10 @@ const api = {
215
217
  };
216
218
  const CONFIG_DIR = denchConfigDirectory();
217
219
  const CONFIG_PATH = join(CONFIG_DIR, "config.json");
218
- const args = process.argv.slice(2);
220
+ const args = process.argv.slice(2).flatMap((arg) => {
221
+ const split = arg.startsWith("--") ? arg.indexOf("=") : -1;
222
+ return split > 2 ? [arg.slice(0, split), arg.slice(split + 1)] : [arg];
223
+ });
219
224
  const json = args.includes("--json");
220
225
  const filteredArgs = args.filter((arg) => arg !== "--json");
221
226
  const scopedStatus = args.includes("--mine") || args.includes("--self");
@@ -321,13 +326,18 @@ function throwIfCliError(value: unknown) {
321
326
  }
322
327
  const record = value as JsonRecord;
323
328
  if (record.ok === false && typeof record.error === "string") {
324
- throw new Error(record.error);
329
+ throw new CliError(record.error, publicErrorDetails(record));
325
330
  }
326
331
  }
327
332
 
328
333
  function normalizeCliError(error: unknown): CliError {
329
- if (error instanceof CliError) return error;
334
+ if (error instanceof CliError) {
335
+ error.payload = { ...error.payload, ...publicErrorDetails(error.payload) };
336
+ return error;
337
+ }
330
338
  const message = backendErrorText(error);
339
+ if (error instanceof CliArgError)
340
+ return new CliError(message, { code: error.code, retryable: false });
331
341
  if (message.includes("Invalid dev agent key")) {
332
342
  return new CliError("Invalid dev agent key", {
333
343
  code: "invalid_dev_agent_key",
@@ -369,7 +379,10 @@ function normalizeCliError(error: unknown): CliError {
369
379
  ],
370
380
  });
371
381
  }
372
- return new CliError(message);
382
+ return new CliError(message, {
383
+ code: "cli_error",
384
+ ...publicErrorDetails(error),
385
+ });
373
386
  }
374
387
 
375
388
  function _parseJsonObjectOption(name: string) {
@@ -673,6 +686,8 @@ Subagents:
673
686
  Help: dench agent help
674
687
 
675
688
  dench --version
689
+ dench reference <name> [--json] Offline CLI-version-matched skill/reference
690
+ dench reference --list [--json] Bundled reference index and revision
676
691
 
677
692
  Self-updating agent harness:
678
693
  dench identity show | edit IDENTITY.md (org-wide)
@@ -2701,12 +2716,21 @@ function stripRuntimeFlags(args: string[]) {
2701
2716
  "--staging",
2702
2717
  "--use-dev-env",
2703
2718
  ]);
2704
- const valueFlags = new Set(["--convex-url", "--host", "--backend"]);
2719
+ const valueFlags = new Set([
2720
+ "--convex-url",
2721
+ "--host",
2722
+ "--backend",
2723
+ "--session-key",
2724
+ "--session",
2725
+ ]);
2705
2726
  const stripped: string[] = [];
2706
2727
  for (let i = 0; i < args.length; i++) {
2707
2728
  const arg = args[i];
2708
2729
  if (booleanFlags.has(arg)) continue;
2709
2730
  if (valueFlags.has(arg)) {
2731
+ if (!args[i + 1] || args[i + 1].startsWith("--")) {
2732
+ throw new CliArgError(`Missing value for ${arg}`);
2733
+ }
2710
2734
  i++;
2711
2735
  continue;
2712
2736
  }
@@ -3392,7 +3416,7 @@ async function resolveFsDaemonEnv(): Promise<NodeJS.ProcessEnv> {
3392
3416
  }
3393
3417
 
3394
3418
  async function runFsCommand() {
3395
- const subArgs = args.slice(args.indexOf("fs") + 1);
3419
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf("fs") + 1));
3396
3420
  const configuredBinary = process.env.DENCH_FS_DAEMON_BIN?.trim();
3397
3421
  const binary = configuredBinary || "dench-fs-daemon";
3398
3422
  const daemonEnv = await resolveFsDaemonEnv();
@@ -4303,10 +4327,95 @@ function isCommandNotFound(error: unknown) {
4303
4327
  }
4304
4328
 
4305
4329
  async function main() {
4330
+ if (isHelpRequest(args)) {
4331
+ const helpArgs = stripRuntimeFlags(args).filter((arg) => arg !== "--json");
4332
+ if (helpArgs[0] === "help") helpArgs.shift();
4333
+ if (!helpArgs.length || ["--help", "-h"].includes(helpArgs[0])) {
4334
+ help();
4335
+ return;
4336
+ }
4337
+ const path = helpArgs.filter(
4338
+ (arg) => !["help", "--help", "-h"].includes(arg),
4339
+ );
4340
+ // Root help retains domain-specific setup, account and pagination guidance.
4341
+ // These are explicit help-only paths; never pass mutation arguments to a handler.
4342
+ if (path.length === 1) {
4343
+ const roots: Record<string, () => void | Promise<void>> = {
4344
+ reference: async () =>
4345
+ console.log((await import("./reference")).REFERENCE_HELP),
4346
+ tool: toolHelp,
4347
+ integrations: integrationsHelp,
4348
+ backend: backendHelp,
4349
+ signin: signinHelp,
4350
+ login: signinHelp,
4351
+ onboard: signinHelp,
4352
+ setup: signinHelp,
4353
+ "what-can-i-do": signinHelp,
4354
+ register: signinHelp,
4355
+ memory: memoryHelp,
4356
+ billing: billingHelp,
4357
+ logout: logoutHelp,
4358
+ context: contextHelp,
4359
+ artifacts: artifactsHelp,
4360
+ "suggested-work": artifactsHelp,
4361
+ lists: listsCliHelp,
4362
+ leads: leadsHelp,
4363
+ companies: companiesHelp,
4364
+ jobs: jobsHelp,
4365
+ status: statusHelp,
4366
+ web: async () => (await import("./web")).webHelp(),
4367
+ crm: async () =>
4368
+ (await import("./crm")).runCrmCommand({
4369
+ convex: {} as ConvexHttpClient,
4370
+ args: ["help"],
4371
+ }),
4372
+ apps: async () =>
4373
+ (await import("./apps")).runAppsCommand({
4374
+ convex: {} as ConvexHttpClient,
4375
+ args: ["help"],
4376
+ }),
4377
+ };
4378
+ if (roots[path[0]]) {
4379
+ await roots[path[0]]();
4380
+ return;
4381
+ }
4382
+ }
4383
+ if (helpArgs[0] === "reference") {
4384
+ console.log((await import("./reference")).REFERENCE_HELP);
4385
+ return;
4386
+ }
4387
+ // Preserve the small, established CRM read help including its cursor contract.
4388
+ if (helpArgs[0] === "crm" && ["query", "aggregate"].includes(helpArgs[1])) {
4389
+ const { runCrmCommand } = await import("./crm");
4390
+ await runCrmCommand({
4391
+ convex: {} as ConvexHttpClient,
4392
+ args: [...helpArgs.slice(1), "--help"],
4393
+ });
4394
+ return;
4395
+ }
4396
+ const { commandHelp } = await import("./lib/command-help");
4397
+ const usage =
4398
+ helpArgs[0] === "email"
4399
+ ? (await import("./email")).emailHelpText()
4400
+ : helpArgs[0] === "crm"
4401
+ ? (await import("./crm")).crmHelpText()
4402
+ : "";
4403
+ console.log(commandHelp(helpArgs, usage));
4404
+ return;
4405
+ }
4306
4406
  const command = positional(0);
4307
4407
  const subcommand = positional(1);
4308
4408
 
4309
- if (command === "version" || hasFlag("--version")) {
4409
+ assertDeclaredCommandFlags(stripRuntimeFlags(args));
4410
+
4411
+ if (command === "reference") {
4412
+ await (await import("./reference")).runReferenceCommand(
4413
+ stripRuntimeFlags(args.slice(args.indexOf("reference") + 1)),
4414
+ );
4415
+ return;
4416
+ }
4417
+
4418
+ if (command === "version" || stripRuntimeFlags(args)[0] === "--version") {
4310
4419
  console.log(await cliVersion());
4311
4420
  return;
4312
4421
  }
@@ -4741,7 +4850,7 @@ async function main() {
4741
4850
  }
4742
4851
 
4743
4852
  if (command === "web") {
4744
- const subArgs = args.slice(args.indexOf("web") + 1);
4853
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf("web") + 1));
4745
4854
  const filteredSubArgs = subArgs.filter((a) => a !== "--json");
4746
4855
  const { runWebCommand, webHelp } = await import("./web");
4747
4856
  const sub = filteredSubArgs[0];
@@ -4761,7 +4870,7 @@ async function main() {
4761
4870
  }
4762
4871
 
4763
4872
  if (command === "search") {
4764
- const subArgs = args.slice(args.indexOf("search") + 1);
4873
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf("search") + 1));
4765
4874
  const filteredSubArgs = subArgs.filter((a) => a !== "--json");
4766
4875
  const { runSearchCommand } = await import("./search");
4767
4876
  const sub = filteredSubArgs[0];
@@ -4784,7 +4893,7 @@ async function main() {
4784
4893
  }
4785
4894
 
4786
4895
  if (command === "image") {
4787
- const subArgs = args.slice(args.indexOf("image") + 1);
4896
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf("image") + 1));
4788
4897
  const filteredSubArgs = subArgs.filter((a) => a !== "--json");
4789
4898
  const { runImageCommand } = await import("./image");
4790
4899
  const sub = filteredSubArgs[0];
@@ -4804,7 +4913,7 @@ async function main() {
4804
4913
  }
4805
4914
 
4806
4915
  if (command === "email") {
4807
- const subArgs = args.slice(args.indexOf("email") + 1);
4916
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf("email") + 1));
4808
4917
  const { runEmailCommand } = await import("./email");
4809
4918
  const runtime = await getRuntime();
4810
4919
  const authedRuntime = requireAuthenticatedRuntime(runtime, "dench email");
@@ -4813,7 +4922,7 @@ async function main() {
4813
4922
  | undefined;
4814
4923
  try {
4815
4924
  if (
4816
- subArgs[0] !== "draft" &&
4925
+ !["draft", "inbox", "send"].includes(subArgs[0]) &&
4817
4926
  !subArgs.some(
4818
4927
  (arg) => arg === "--mailbox" || arg.startsWith("--mailbox="),
4819
4928
  )
@@ -4922,7 +5031,7 @@ async function main() {
4922
5031
  }
4923
5032
 
4924
5033
  if (command === "browser") {
4925
- const subArgs = args.slice(args.indexOf("browser") + 1);
5034
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf("browser") + 1));
4926
5035
  const { runBrowserCommand } = await import("./browser");
4927
5036
  const runtime = await getRuntime();
4928
5037
  const authedRuntime = requireAuthenticatedRuntime(runtime, "dench browser");
@@ -4947,7 +5056,7 @@ async function main() {
4947
5056
  // `dench ads` is canonical; `dench ad` and `dench adline` are aliases for
4948
5057
  // the same command (one code path in ./ads).
4949
5058
  if (command === "ads" || command === "ad" || command === "adline") {
4950
- const subArgs = args.slice(args.indexOf(command) + 1);
5059
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf(command) + 1));
4951
5060
  const { runAdsCommand } = await import("./ads");
4952
5061
  // Earning is opt-in: resolve the agent session token best-effort so a
4953
5062
  // signed-in developer gets credited on flush, but never require login —
@@ -4983,7 +5092,7 @@ async function main() {
4983
5092
  }
4984
5093
 
4985
5094
  if (command === "chat") {
4986
- const subArgs = args.slice(args.indexOf("chat") + 1);
5095
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf("chat") + 1));
4987
5096
  const sub = subArgs[0];
4988
5097
  if (!sub || sub === "help" || sub === "--help") {
4989
5098
  const { runChatCommand } = await import("./chat");
@@ -5033,7 +5142,7 @@ async function main() {
5033
5142
  }
5034
5143
 
5035
5144
  if (command === "agent") {
5036
- const subArgs = args.slice(args.indexOf("agent") + 1);
5145
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf("agent") + 1));
5037
5146
  const sub = subArgs[0];
5038
5147
  if (!sub || sub === "help" || sub === "--help") {
5039
5148
  const { runAgentCommand } = await import("./agent");
@@ -5110,7 +5219,7 @@ async function main() {
5110
5219
  // `dench cron`. Same dispatch, same auth, same handler — only the
5111
5220
  // top-level command name differs to match the workspace UI rename.
5112
5221
  if (command === "cron" || command === "routine" || command === "routines") {
5113
- const subArgs = args.slice(args.indexOf(command) + 1);
5222
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf(command) + 1));
5114
5223
  const sub = subArgs[0];
5115
5224
  // Help is a no-auth path so users can read the surface without a
5116
5225
  // session — mirrors `dench crm help`.
@@ -5159,7 +5268,7 @@ async function main() {
5159
5268
  "daily",
5160
5269
  ]);
5161
5270
  if (AGENT_CONFIG_COMMANDS.has(command)) {
5162
- const subArgs = args.slice(args.indexOf(command) + 1);
5271
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf(command) + 1));
5163
5272
  if (
5164
5273
  subArgs[0] === "help" ||
5165
5274
  subArgs.includes("--help") ||