@dench.com/cli 3.6.5-staging.384 → 3.6.6-staging.386

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.
Files changed (3) hide show
  1. package/crm.ts +107 -21
  2. package/dench.ts +7 -3
  3. package/package.json +1 -1
package/crm.ts CHANGED
@@ -1063,6 +1063,15 @@ const api = {
1063
1063
  },
1064
1064
  };
1065
1065
 
1066
+ /** These read commands have local, command-specific help (no runtime needed). */
1067
+ export function isCrmReadHelpRequest(args: readonly string[]): boolean {
1068
+ const command = args.find((arg) => arg !== "--json");
1069
+ return (
1070
+ (command === "query" || command === "aggregate") &&
1071
+ (args.includes("--help") || args.includes("-h"))
1072
+ );
1073
+ }
1074
+
1066
1075
  export async function runCrmCommand(opts: {
1067
1076
  convex: ConvexHttpClient;
1068
1077
  args: string[];
@@ -1088,6 +1097,21 @@ export async function runCrmCommand(opts: {
1088
1097
  crmHelp();
1089
1098
  return;
1090
1099
  }
1100
+ if (isCrmReadHelpRequest(opts.args)) {
1101
+ if (subcommand === "query") {
1102
+ console.log(`Usage: dench crm query <object> [--where '<JSON>'] [--select Name,Email] [--sort -Score] [--limit N] [--json]
1103
+ --page [--cursor '<continueCursor>'] Return {page,isDone,continueCursor} in index order; omit --sort.
1104
+ --assigned-to me|unassigned|<userId|email> (or --mine) [--assignee-field <Field>]
1105
+ Default limit: 100. Maximum: 1000 for a one-shot array, 100 for a cursor page.
1106
+ Continue cursor pages until isDone, including empty filtered pages.
1107
+ Example: dench crm query company --where '{"Status":"New"}' --select Name --limit 25 --json`);
1108
+ } else {
1109
+ console.log(`Usage: dench crm aggregate <object> --field <Field> [--sum <NumberField>] [--json]
1110
+ Count rows per value of --field. --sum also totals a number field within each bucket.
1111
+ Example: dench crm aggregate deal --field Stage --sum Amount --json`);
1112
+ }
1113
+ return;
1114
+ }
1091
1115
  switch (subcommand) {
1092
1116
  case "objects":
1093
1117
  return await runObjectsCommand(ctx);
@@ -2391,37 +2415,92 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
2391
2415
  switch (verb) {
2392
2416
  case "list": {
2393
2417
  const objectName = shift(ctx.args, "object name");
2418
+ const withMeta = hasFlag(ctx.args, "--with-meta");
2419
+ const cursorSpecified = ctx.args.includes("--cursor");
2420
+ const cursor = getFlag(ctx.args, "--cursor");
2421
+ if (cursorSpecified && (!cursor?.trim() || cursor.startsWith("--")))
2422
+ throw new CrmCliError(
2423
+ "--cursor requires a non-empty continuation cursor.",
2424
+ );
2425
+ const assigneeScopeArgs = resolveAssigneeScopeFlags(ctx.args);
2394
2426
  const limit = resolveLimitFlag(ctx.args, {
2395
2427
  defaultValue: 100,
2396
- cap: 1000,
2428
+ cap:
2429
+ withMeta && assigneeScopeArgs.assignedTo !== undefined ? 100 : 1000,
2397
2430
  command: "crm entries list",
2431
+ ...(withMeta
2432
+ ? {
2433
+ fullDatasetHint:
2434
+ "Continue with --with-meta --cursor '<continueCursor>' when a cursor is returned.",
2435
+ }
2436
+ : {}),
2398
2437
  });
2399
- const withMeta = hasFlag(ctx.args, "--with-meta");
2400
- const assigneeScopeArgs = resolveAssigneeScopeFlags(ctx.args);
2438
+ if (cursor !== undefined && !withMeta) {
2439
+ throw new CrmCliError(
2440
+ "--cursor requires --with-meta on crm entries list.",
2441
+ );
2442
+ }
2401
2443
  assertNoUnknownFlags(ctx.args, "crm entries list");
2402
- const entries = await callQuery(ctx, api.entries.list, {
2403
- objectName,
2404
- limit: limit.effective,
2405
- ...assigneeScopeArgs,
2406
- });
2407
2444
  if (!withMeta) {
2408
2445
  // Bare-array output: the clamp warning already went to stderr
2409
2446
  // (stdout must stay a valid JSON array in --json mode).
2410
- out(ctx, entries);
2447
+ out(
2448
+ ctx,
2449
+ await callQuery(ctx, api.entries.list, {
2450
+ objectName,
2451
+ limit: limit.effective,
2452
+ ...assigneeScopeArgs,
2453
+ }),
2454
+ );
2411
2455
  return;
2412
2456
  }
2413
- const object = (await callQuery(ctx, api.objects.get, {
2414
- name: objectName,
2415
- })) as { entryCount?: number } | null;
2416
- const returnedCount = Array.isArray(entries) ? entries.length : 0;
2417
- const totalCount =
2418
- typeof object?.entryCount === "number"
2419
- ? object.entryCount
2420
- : returnedCount;
2457
+ // Catalog counts include hidden and unassigned records. Only the
2458
+ // readable page can tell us whether this caller has a continuation.
2459
+ const [result, object] = (await Promise.all([
2460
+ assigneeScopeArgs.assignedTo !== undefined
2461
+ ? callQuery(ctx, api.query.scanEntries, {
2462
+ objectName,
2463
+ dsl: { limit: limit.effective },
2464
+ paginationOpts: {
2465
+ cursor: cursor ?? null,
2466
+ numItems: limit.effective,
2467
+ },
2468
+ ...assigneeScopeArgs,
2469
+ })
2470
+ : callQuery(ctx, api.entries.listPage, {
2471
+ objectName,
2472
+ limit: limit.effective,
2473
+ ...(cursor !== undefined ? { cursor } : {}),
2474
+ }),
2475
+ callQuery(ctx, api.objects.get, { name: objectName }),
2476
+ ])) as [
2477
+ {
2478
+ page?: unknown[];
2479
+ isDone?: boolean;
2480
+ continueCursor?: string | null;
2481
+ truncated?: boolean;
2482
+ } | null,
2483
+ { entryCount?: number; visibility?: string } | null,
2484
+ ];
2485
+ const entries = Array.isArray(result?.page) ? result.page : [];
2486
+ const isDone =
2487
+ typeof result?.isDone === "boolean" ? result.isDone : null;
2488
+ const truncated = result?.truncated === true;
2489
+ const continueCursor =
2490
+ isDone === false ? result?.continueCursor || null : null;
2491
+ const hasMore = continueCursor
2492
+ ? true
2493
+ : isDone === true && !truncated
2494
+ ? false
2495
+ : null;
2421
2496
  out(ctx, {
2422
2497
  objectName,
2423
- totalCount,
2424
- returnedCount,
2498
+ totalCount:
2499
+ typeof object?.entryCount === "number" ? object.entryCount : null,
2500
+ totalCountScope: "object",
2501
+ visibility: object?.visibility ?? null,
2502
+ returnedCount: entries.length,
2503
+ returnedCountScope: "readable",
2425
2504
  limit: limit.effective,
2426
2505
  ...(limit.clamped
2427
2506
  ? {
@@ -2431,7 +2510,10 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
2431
2510
  },
2432
2511
  }
2433
2512
  : {}),
2434
- hasMore: totalCount > returnedCount,
2513
+ hasMore,
2514
+ isDone,
2515
+ truncated,
2516
+ continueCursor,
2435
2517
  entries,
2436
2518
  });
2437
2519
  return;
@@ -4785,8 +4867,12 @@ Fields (columns):
4785
4867
  Update the color (e.g. "#3b82f6") for an existing enum option.
4786
4868
 
4787
4869
  Entries (rows):
4788
- dench crm entries list <object> [--limit N] [--with-meta] [--json]
4870
+ dench crm entries list <object> [--limit N] [--with-meta [--cursor '<continueCursor>']] [--json]
4789
4871
  [--assigned-to me|unassigned|<userId|email> | --mine] [--assignee-field <Field>]
4872
+ --with-meta includes readable pagination; totalCount is the entire object's catalog count,
4873
+ not a readable or assigned total. Continue only when continueCursor is returned, preserving filters.
4874
+ Scoped reads may be truncated with no cursor (hasMore: null); repeating the same call will not advance.
4875
+ Assigned metadata pages cap at 100 rows; other list reads cap at 1000.
4790
4876
  dench crm entries get <object> <entryId> [--with-context] (--with-context appends the newest activity + notes)
4791
4877
  dench crm entries sync-history <object> <entryId> [--status]
4792
4878
  Queue a full, paged pull of the record's email history from every
package/dench.ts CHANGED
@@ -4459,18 +4459,22 @@ async function main() {
4459
4459
  if (command === "crm") {
4460
4460
  const subArgs = stripRuntimeFlags(args.slice(args.indexOf("crm") + 1));
4461
4461
  const sub = subArgs[0];
4462
+ const { runCrmCommand, isCrmReadHelpRequest } = await import("./crm");
4462
4463
  // Short-circuit help so users don't need a session to read the
4463
4464
  // surface. The crm dispatcher's own help resolves --json + the
4464
4465
  // subcommand list inline.
4465
- if (!sub || sub === "help" || sub === "--help") {
4466
- const { runCrmCommand } = await import("./crm");
4466
+ if (
4467
+ !sub ||
4468
+ sub === "help" ||
4469
+ sub === "--help" ||
4470
+ isCrmReadHelpRequest(subArgs)
4471
+ ) {
4467
4472
  // runCrmCommand only needs `convex` for actual queries; help
4468
4473
  // doesn't touch it. Pass a minimal stub.
4469
4474
  // biome-ignore lint/suspicious/noExplicitAny: harmless help-only stub
4470
4475
  await runCrmCommand({ convex: {} as any, args: subArgs });
4471
4476
  return;
4472
4477
  }
4473
- const { runCrmCommand } = await import("./crm");
4474
4478
  const runtime = await getRuntime();
4475
4479
  // CRM is org-scoped — accept either an agent session token
4476
4480
  // (`dch_agent_*` from `dench signin`) or a unified Dench API key
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "3.6.5-staging.384",
3
+ "version": "3.6.6-staging.386",
4
4
  "description": "Dench agent workspace CLI.",
5
5
  "type": "module",
6
6
  "bin": {