@dench.com/cli 3.6.5-staging.384 → 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/agent-config.ts +17 -3
- package/app-sdk.d.ts +248 -10
- package/crm.ts +197 -43
- package/dench.ts +135 -22
- package/email-inbox.ts +169 -0
- package/email.ts +103 -21
- package/lib/api-schemas.ts +156 -14
- package/lib/app-sdk-catalog.generated.ts +9 -3
- package/lib/bundled-references.ts +17 -0
- package/lib/cli-args.ts +30 -3
- package/lib/command-args.ts +237 -0
- package/lib/command-help.ts +78 -0
- package/lib/command-registry.ts +75 -3
- package/lib/native-email-wait.ts +75 -0
- package/lib/public-error.ts +82 -0
- package/lib/references.generated.json +66 -0
- package/package.json +3 -1
- package/reference.ts +47 -0
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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"),
|
|
@@ -1063,6 +1090,15 @@ const api = {
|
|
|
1063
1090
|
},
|
|
1064
1091
|
};
|
|
1065
1092
|
|
|
1093
|
+
/** These read commands have local, command-specific help (no runtime needed). */
|
|
1094
|
+
export function isCrmReadHelpRequest(args: readonly string[]): boolean {
|
|
1095
|
+
const command = args.find((arg) => arg !== "--json");
|
|
1096
|
+
return (
|
|
1097
|
+
(command === "query" || command === "aggregate") &&
|
|
1098
|
+
(args.includes("--help") || args.includes("-h"))
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1066
1102
|
export async function runCrmCommand(opts: {
|
|
1067
1103
|
convex: ConvexHttpClient;
|
|
1068
1104
|
args: string[];
|
|
@@ -1088,6 +1124,21 @@ export async function runCrmCommand(opts: {
|
|
|
1088
1124
|
crmHelp();
|
|
1089
1125
|
return;
|
|
1090
1126
|
}
|
|
1127
|
+
if (isCrmReadHelpRequest(opts.args)) {
|
|
1128
|
+
if (subcommand === "query") {
|
|
1129
|
+
console.log(`Usage: dench crm query <object> [--where '<JSON>'] [--select Name,Email] [--sort -Score] [--limit N] [--json]
|
|
1130
|
+
--page [--cursor '<continueCursor>'] Return {page,isDone,continueCursor} in index order; omit --sort.
|
|
1131
|
+
--assigned-to me|unassigned|<userId|email> (or --mine) [--assignee-field <Field>]
|
|
1132
|
+
Default limit: 100. Maximum: 1000 for a one-shot array, 100 for a cursor page.
|
|
1133
|
+
Continue cursor pages until isDone, including empty filtered pages.
|
|
1134
|
+
Example: dench crm query company --where '{"Status":"New"}' --select Name --limit 25 --json`);
|
|
1135
|
+
} else {
|
|
1136
|
+
console.log(`Usage: dench crm aggregate <object> --field <Field> [--sum <NumberField>] [--json]
|
|
1137
|
+
Count rows per value of --field. --sum also totals a number field within each bucket.
|
|
1138
|
+
Example: dench crm aggregate deal --field Stage --sum Amount --json`);
|
|
1139
|
+
}
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1091
1142
|
switch (subcommand) {
|
|
1092
1143
|
case "objects":
|
|
1093
1144
|
return await runObjectsCommand(ctx);
|
|
@@ -1435,8 +1486,16 @@ async function runObjectsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1435
1486
|
const verb = ctx.args.shift();
|
|
1436
1487
|
switch (verb) {
|
|
1437
1488
|
case "list": {
|
|
1489
|
+
const compact = hasFlag(ctx.args, "--compact");
|
|
1438
1490
|
assertNoUnknownFlags(ctx.args, "crm objects list");
|
|
1439
|
-
out(
|
|
1491
|
+
out(
|
|
1492
|
+
ctx,
|
|
1493
|
+
await callQuery(
|
|
1494
|
+
ctx,
|
|
1495
|
+
compact ? api.objects.listCompact : api.objects.list,
|
|
1496
|
+
{},
|
|
1497
|
+
),
|
|
1498
|
+
);
|
|
1440
1499
|
return;
|
|
1441
1500
|
}
|
|
1442
1501
|
case "get": {
|
|
@@ -2135,8 +2194,16 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2135
2194
|
switch (verb) {
|
|
2136
2195
|
case "list": {
|
|
2137
2196
|
const objectName = shift(ctx.args, "object name");
|
|
2197
|
+
const compact = hasFlag(ctx.args, "--compact");
|
|
2138
2198
|
assertNoUnknownFlags(ctx.args, "crm fields list");
|
|
2139
|
-
out(
|
|
2199
|
+
out(
|
|
2200
|
+
ctx,
|
|
2201
|
+
await callQuery(
|
|
2202
|
+
ctx,
|
|
2203
|
+
compact ? api.fields.listCompact : api.fields.list,
|
|
2204
|
+
{ objectName },
|
|
2205
|
+
),
|
|
2206
|
+
);
|
|
2140
2207
|
return;
|
|
2141
2208
|
}
|
|
2142
2209
|
case "create": {
|
|
@@ -2270,7 +2337,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2270
2337
|
// Assert before consuming the rest as positional field names so a
|
|
2271
2338
|
// typo'd flag fails loudly rather than getting silently filtered.
|
|
2272
2339
|
assertNoUnknownFlags(ctx.args, "crm fields reorder");
|
|
2273
|
-
const orderedFieldNames =
|
|
2340
|
+
const orderedFieldNames = ctx.args.splice(0);
|
|
2274
2341
|
out(
|
|
2275
2342
|
ctx,
|
|
2276
2343
|
await callMutation(ctx, api.fields.reorder, {
|
|
@@ -2287,7 +2354,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2287
2354
|
// there are no stray flags so a typo'd `--limit` fails loud
|
|
2288
2355
|
// instead of being silently consumed as an enum value.
|
|
2289
2356
|
assertNoUnknownFlags(ctx.args, "crm fields enum-reorder");
|
|
2290
|
-
const orderedValues =
|
|
2357
|
+
const orderedValues = ctx.args.splice(0);
|
|
2291
2358
|
if (orderedValues.length === 0) {
|
|
2292
2359
|
throw new CrmCliError(
|
|
2293
2360
|
"dench crm fields enum-reorder requires at least one value. " +
|
|
@@ -2391,37 +2458,100 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2391
2458
|
switch (verb) {
|
|
2392
2459
|
case "list": {
|
|
2393
2460
|
const objectName = shift(ctx.args, "object name");
|
|
2461
|
+
const withMeta = hasFlag(ctx.args, "--with-meta");
|
|
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
|
+
);
|
|
2472
|
+
const cursor = getFlag(ctx.args, "--cursor");
|
|
2473
|
+
if (cursorSpecified && (!cursor?.trim() || cursor.startsWith("--")))
|
|
2474
|
+
throw new CrmCliError(
|
|
2475
|
+
"--cursor requires a non-empty continuation cursor.",
|
|
2476
|
+
);
|
|
2477
|
+
const assigneeScopeArgs = resolveAssigneeScopeFlags(ctx.args);
|
|
2394
2478
|
const limit = resolveLimitFlag(ctx.args, {
|
|
2395
2479
|
defaultValue: 100,
|
|
2396
|
-
cap:
|
|
2480
|
+
cap:
|
|
2481
|
+
withMeta && assigneeScopeArgs.assignedTo !== undefined ? 100 : 1000,
|
|
2397
2482
|
command: "crm entries list",
|
|
2483
|
+
...(withMeta
|
|
2484
|
+
? {
|
|
2485
|
+
fullDatasetHint:
|
|
2486
|
+
"Continue with --with-meta --cursor '<continueCursor>' when a cursor is returned.",
|
|
2487
|
+
}
|
|
2488
|
+
: {}),
|
|
2398
2489
|
});
|
|
2399
|
-
|
|
2400
|
-
|
|
2490
|
+
if (cursor !== undefined && !withMeta) {
|
|
2491
|
+
throw new CrmCliError(
|
|
2492
|
+
"--cursor requires --with-meta on crm entries list.",
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2401
2495
|
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
2496
|
if (!withMeta) {
|
|
2408
2497
|
// Bare-array output: the clamp warning already went to stderr
|
|
2409
2498
|
// (stdout must stay a valid JSON array in --json mode).
|
|
2410
|
-
out(
|
|
2499
|
+
out(
|
|
2500
|
+
ctx,
|
|
2501
|
+
await callQuery(ctx, api.entries.list, {
|
|
2502
|
+
objectName,
|
|
2503
|
+
limit: limit.effective,
|
|
2504
|
+
...assigneeScopeArgs,
|
|
2505
|
+
}),
|
|
2506
|
+
);
|
|
2411
2507
|
return;
|
|
2412
2508
|
}
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2509
|
+
// Catalog counts include hidden and unassigned records. Only the
|
|
2510
|
+
// readable page can tell us whether this caller has a continuation.
|
|
2511
|
+
const [result, object] = (await Promise.all([
|
|
2512
|
+
assigneeScopeArgs.assignedTo !== undefined
|
|
2513
|
+
? callQuery(ctx, api.query.scanEntries, {
|
|
2514
|
+
objectName,
|
|
2515
|
+
dsl: { limit: limit.effective },
|
|
2516
|
+
paginationOpts: {
|
|
2517
|
+
cursor: cursor ?? null,
|
|
2518
|
+
numItems: limit.effective,
|
|
2519
|
+
},
|
|
2520
|
+
...assigneeScopeArgs,
|
|
2521
|
+
})
|
|
2522
|
+
: callQuery(ctx, api.entries.listPage, {
|
|
2523
|
+
objectName,
|
|
2524
|
+
limit: limit.effective,
|
|
2525
|
+
...(cursor !== undefined ? { cursor } : {}),
|
|
2526
|
+
}),
|
|
2527
|
+
callQuery(ctx, api.objects.get, { name: objectName }),
|
|
2528
|
+
])) as [
|
|
2529
|
+
{
|
|
2530
|
+
page?: unknown[];
|
|
2531
|
+
isDone?: boolean;
|
|
2532
|
+
continueCursor?: string | null;
|
|
2533
|
+
truncated?: boolean;
|
|
2534
|
+
} | null,
|
|
2535
|
+
{ entryCount?: number; visibility?: string } | null,
|
|
2536
|
+
];
|
|
2537
|
+
const entries = Array.isArray(result?.page) ? result.page : [];
|
|
2538
|
+
const isDone = typeof result?.isDone === "boolean" ? result.isDone : null;
|
|
2539
|
+
const truncated = result?.truncated === true;
|
|
2540
|
+
const continueCursor =
|
|
2541
|
+
isDone === false ? result?.continueCursor || null : null;
|
|
2542
|
+
const hasMore = continueCursor
|
|
2543
|
+
? true
|
|
2544
|
+
: isDone === true && !truncated
|
|
2545
|
+
? false
|
|
2546
|
+
: null;
|
|
2421
2547
|
out(ctx, {
|
|
2422
2548
|
objectName,
|
|
2423
|
-
totalCount
|
|
2424
|
-
|
|
2549
|
+
totalCount:
|
|
2550
|
+
typeof object?.entryCount === "number" ? object.entryCount : null,
|
|
2551
|
+
totalCountScope: "object",
|
|
2552
|
+
visibility: object?.visibility ?? null,
|
|
2553
|
+
returnedCount: entries.length,
|
|
2554
|
+
returnedCountScope: "readable",
|
|
2425
2555
|
limit: limit.effective,
|
|
2426
2556
|
...(limit.clamped
|
|
2427
2557
|
? {
|
|
@@ -2431,7 +2561,10 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2431
2561
|
},
|
|
2432
2562
|
}
|
|
2433
2563
|
: {}),
|
|
2434
|
-
hasMore
|
|
2564
|
+
hasMore,
|
|
2565
|
+
isDone,
|
|
2566
|
+
truncated,
|
|
2567
|
+
continueCursor,
|
|
2435
2568
|
entries,
|
|
2436
2569
|
});
|
|
2437
2570
|
return;
|
|
@@ -3152,25 +3285,31 @@ async function runAggregateCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
3152
3285
|
|
|
3153
3286
|
async function runSearchCommand(ctx: CrmCliContext): Promise<void> {
|
|
3154
3287
|
const objectName = getFlag(ctx.args, "--object");
|
|
3155
|
-
|
|
3156
|
-
|
|
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;
|
|
3157
3298
|
const limit = resolveLimitFlag(ctx.args, {
|
|
3158
|
-
defaultValue: 50,
|
|
3299
|
+
defaultValue: projected ? 20 : 50,
|
|
3159
3300
|
cap: 100,
|
|
3160
3301
|
command: "crm search",
|
|
3161
3302
|
});
|
|
3162
|
-
// Drain known flags before joining the rest into the search text so a
|
|
3163
|
-
// typo like `--objct` surfaces instead of being concatenated into the
|
|
3164
|
-
// query string.
|
|
3165
3303
|
assertNoUnknownFlags(ctx.args, "crm search");
|
|
3166
3304
|
const text = ctx.args.join(" ").trim();
|
|
3167
3305
|
if (!text) throw new CrmCliError("Search text required");
|
|
3168
3306
|
out(
|
|
3169
3307
|
ctx,
|
|
3170
|
-
await callQuery(ctx, api.query.search, {
|
|
3308
|
+
await callQuery(ctx, projected ? api.query.searchPage : api.query.search, {
|
|
3171
3309
|
text,
|
|
3172
3310
|
objectName,
|
|
3173
3311
|
limit: limit.effective,
|
|
3312
|
+
...(projected ? { select, cursor } : {}),
|
|
3174
3313
|
}),
|
|
3175
3314
|
);
|
|
3176
3315
|
}
|
|
@@ -3300,7 +3439,8 @@ async function runActionsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
3300
3439
|
const objectName = shift(ctx.args, "object name");
|
|
3301
3440
|
const fieldId = shift(ctx.args, "field id");
|
|
3302
3441
|
const actionId = shift(ctx.args, "action id");
|
|
3303
|
-
|
|
3442
|
+
assertNoUnknownFlags(ctx.args, "crm actions run");
|
|
3443
|
+
const entryIds = ctx.args.splice(0);
|
|
3304
3444
|
if (entryIds.length === 0) {
|
|
3305
3445
|
throw new CrmCliError("Provide at least one entry id");
|
|
3306
3446
|
}
|
|
@@ -3840,7 +3980,7 @@ export function remapKeys(
|
|
|
3840
3980
|
// ── SQL escape hatch ─────────────────────────────────────────────────────
|
|
3841
3981
|
|
|
3842
3982
|
async function runSqlCommand(ctx: CrmCliContext): Promise<void> {
|
|
3843
|
-
const sql = ctx.args.join(" ").trim();
|
|
3983
|
+
const sql = ctx.args.splice(0).join(" ").trim();
|
|
3844
3984
|
if (!sql) {
|
|
3845
3985
|
throw new CrmCliError("dench crm sql '<SELECT ... FROM ...>'");
|
|
3846
3986
|
}
|
|
@@ -4329,6 +4469,7 @@ async function enrichCellThroughGateway(
|
|
|
4329
4469
|
ctx: CrmCliContext,
|
|
4330
4470
|
args: CellEnrichmentArgs,
|
|
4331
4471
|
): Promise<Record<string, unknown>> {
|
|
4472
|
+
assertWriteArguments(ctx);
|
|
4332
4473
|
const target = await resolveEnrichmentTarget(ctx, args);
|
|
4333
4474
|
const entry = asEntry(
|
|
4334
4475
|
await callQuery(ctx, api.entries.get, {
|
|
@@ -4352,6 +4493,7 @@ async function enrichObjectThroughGateway(
|
|
|
4352
4493
|
ctx: CrmCliContext,
|
|
4353
4494
|
args: ObjectEnrichmentArgs,
|
|
4354
4495
|
): Promise<Record<string, unknown>> {
|
|
4496
|
+
assertWriteArguments(ctx);
|
|
4355
4497
|
const target = await resolveEnrichmentTarget(ctx, args);
|
|
4356
4498
|
const entries = asEntries(
|
|
4357
4499
|
await callQuery(ctx, api.entries.list, {
|
|
@@ -4533,6 +4675,7 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
4533
4675
|
const lookups = input.map((value, index) =>
|
|
4534
4676
|
requireNonEmptyJsonRecord(value, `lookup ${index + 1}`),
|
|
4535
4677
|
);
|
|
4678
|
+
assertWriteArguments(ctx);
|
|
4536
4679
|
out(ctx, await enrichProfiles({ kind, lookups }, ctx.enrichmentGateway));
|
|
4537
4680
|
return;
|
|
4538
4681
|
}
|
|
@@ -4638,10 +4781,14 @@ async function runReportsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
4638
4781
|
}
|
|
4639
4782
|
|
|
4640
4783
|
function crmHelp(): void {
|
|
4641
|
-
console.log(
|
|
4784
|
+
console.log(crmHelpText());
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4787
|
+
export function crmHelpText(): string {
|
|
4788
|
+
return `Usage: dench crm <subcommand>
|
|
4642
4789
|
|
|
4643
4790
|
Objects (CRM tables):
|
|
4644
|
-
dench crm objects list [--json]
|
|
4791
|
+
dench crm objects list [--json] [--compact]
|
|
4645
4792
|
dench crm objects get <name>
|
|
4646
4793
|
dench crm objects create <name> [--description ...] [--default-view kanban] [--icon ...]
|
|
4647
4794
|
dench crm objects update <name> [--default-view ...] [--icon ...] [--display-name "Clients"] [--display-name-singular "Client"] [--visibility workspace|owner_only|owner_plus_members]
|
|
@@ -4728,7 +4875,7 @@ Sidebar (progressive disclosure — Library + home Pinned strip; layout is WORKS
|
|
|
4728
4875
|
shared shortcut, not a Library move; open to any member.
|
|
4729
4876
|
|
|
4730
4877
|
Fields (columns):
|
|
4731
|
-
dench crm fields list <object> [--json]
|
|
4878
|
+
dench crm fields list <object> [--compact] [--json]
|
|
4732
4879
|
dench crm fields create <object> <name> --type text|email|phone|url|number|boolean|date|enum|relation|user|tags|action|file|richtext \\
|
|
4733
4880
|
[--required] [--description ...] [--enum-values JSON] [--enum-colors JSON] \\
|
|
4734
4881
|
[--enum-multiple] [--related-object <name>] [--relationship many_to_one|many_to_many] \\
|
|
@@ -4785,8 +4932,12 @@ Fields (columns):
|
|
|
4785
4932
|
Update the color (e.g. "#3b82f6") for an existing enum option.
|
|
4786
4933
|
|
|
4787
4934
|
Entries (rows):
|
|
4788
|
-
dench crm entries list <object> [--limit N] [--with-meta] [--json]
|
|
4935
|
+
dench crm entries list <object> [--limit N] [--with-meta [--cursor '<continueCursor>']] [--json]
|
|
4789
4936
|
[--assigned-to me|unassigned|<userId|email> | --mine] [--assignee-field <Field>]
|
|
4937
|
+
--with-meta includes readable pagination; totalCount is the entire object's catalog count,
|
|
4938
|
+
not a readable or assigned total. Continue only when continueCursor is returned, preserving filters.
|
|
4939
|
+
Scoped reads may be truncated with no cursor (hasMore: null); repeating the same call will not advance.
|
|
4940
|
+
Assigned metadata pages cap at 100 rows; other list reads cap at 1000.
|
|
4790
4941
|
dench crm entries get <object> <entryId> [--with-context] (--with-context appends the newest activity + notes)
|
|
4791
4942
|
dench crm entries sync-history <object> <entryId> [--status]
|
|
4792
4943
|
Queue a full, paged pull of the record's email history from every
|
|
@@ -4945,7 +5096,10 @@ Aggregates:
|
|
|
4945
5096
|
dench crm aggregate <object> --field Status [--sum Amount]
|
|
4946
5097
|
|
|
4947
5098
|
Full-text search:
|
|
4948
|
-
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.
|
|
4949
5103
|
|
|
4950
5104
|
Statuses (kanban columns):
|
|
4951
5105
|
dench crm statuses list <object>
|
|
@@ -5129,5 +5283,5 @@ unambiguous, but prefer canonical names from 'crm objects list'.
|
|
|
5129
5283
|
|
|
5130
5284
|
Global flags:
|
|
5131
5285
|
--json Output raw JSON instead of pretty-printed text.
|
|
5132
|
-
|
|
5286
|
+
`;
|
|
5133
5287
|
}
|