@dench.com/cli 0.3.6 → 0.3.8
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/README.md +5 -2
- package/agent.ts +95 -13
- package/chat-spawn.ts +65 -4
- package/chat.ts +134 -1
- package/crm.ts +188 -87
- package/dench.ts +118 -17
- package/lib/cli-args.ts +61 -4
- package/package.json +1 -1
package/crm.ts
CHANGED
|
@@ -29,8 +29,10 @@
|
|
|
29
29
|
import { ConvexHttpClient } from "convex/browser";
|
|
30
30
|
import { makeFunctionReference } from "convex/server";
|
|
31
31
|
import {
|
|
32
|
+
assertNoUnknownFlags as assertNoUnknownFlagsRaw,
|
|
32
33
|
CliArgError,
|
|
33
34
|
getFlag,
|
|
35
|
+
getFlagWithAliases,
|
|
34
36
|
hasFlag,
|
|
35
37
|
parseJson as parseJsonRaw,
|
|
36
38
|
shift as shiftRaw,
|
|
@@ -77,6 +79,24 @@ function parseJson(value: string | undefined): unknown {
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
function assertNoUnknownFlags(args: readonly string[], command: string): void {
|
|
83
|
+
try {
|
|
84
|
+
assertNoUnknownFlagsRaw(args, command);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error instanceof CliArgError) throw new CrmCliError(error.message);
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Pull `--data <JSON>` from args, accepting `--fields` as an alias because
|
|
92
|
+
// the entries.list output exposes the data envelope under `fields` and the
|
|
93
|
+
// rest of the surface uses `crm fields` to manage column schemas — both
|
|
94
|
+
// priors push callers toward `--fields`. We accept either; everywhere
|
|
95
|
+
// downstream still calls the parameter `data` to match the server-side API.
|
|
96
|
+
function getDataFlag(args: string[]): string | undefined {
|
|
97
|
+
return getFlagWithAliases(args, "--data", ["--fields"]);
|
|
98
|
+
}
|
|
99
|
+
|
|
80
100
|
async function callQuery(
|
|
81
101
|
ctx: CrmCliContext,
|
|
82
102
|
fn: Parameters<ConvexHttpClient["query"]>[0],
|
|
@@ -121,26 +141,18 @@ function out(ctx: CrmCliContext, value: unknown): void {
|
|
|
121
141
|
|
|
122
142
|
const api = {
|
|
123
143
|
transaction: {
|
|
124
|
-
begin: makeFunctionReference<"mutation">(
|
|
125
|
-
|
|
126
|
-
),
|
|
127
|
-
addOp: makeFunctionReference<"mutation">(
|
|
128
|
-
"functions/crm/transaction:addOp",
|
|
129
|
-
),
|
|
144
|
+
begin: makeFunctionReference<"mutation">("functions/crm/transaction:begin"),
|
|
145
|
+
addOp: makeFunctionReference<"mutation">("functions/crm/transaction:addOp"),
|
|
130
146
|
commit: makeFunctionReference<"mutation">(
|
|
131
147
|
"functions/crm/transaction:commit",
|
|
132
148
|
),
|
|
133
|
-
abort: makeFunctionReference<"mutation">(
|
|
134
|
-
"functions/crm/transaction:abort",
|
|
135
|
-
),
|
|
149
|
+
abort: makeFunctionReference<"mutation">("functions/crm/transaction:abort"),
|
|
136
150
|
inspect: makeFunctionReference<"query">(
|
|
137
151
|
"functions/crm/transaction:inspect",
|
|
138
152
|
),
|
|
139
153
|
},
|
|
140
154
|
reports: {
|
|
141
|
-
generate: makeFunctionReference<"query">(
|
|
142
|
-
"functions/crm/reports:generate",
|
|
143
|
-
),
|
|
155
|
+
generate: makeFunctionReference<"query">("functions/crm/reports:generate"),
|
|
144
156
|
},
|
|
145
157
|
enrich: {
|
|
146
158
|
requestCellEnrichment: makeFunctionReference<"mutation">(
|
|
@@ -163,22 +175,14 @@ const api = {
|
|
|
163
175
|
create: makeFunctionReference<"mutation">("functions/crm/fields:create"),
|
|
164
176
|
update: makeFunctionReference<"mutation">("functions/crm/fields:update"),
|
|
165
177
|
remove: makeFunctionReference<"mutation">("functions/crm/fields:remove"),
|
|
166
|
-
reorder: makeFunctionReference<"mutation">(
|
|
167
|
-
"functions/crm/fields:reorder",
|
|
168
|
-
),
|
|
178
|
+
reorder: makeFunctionReference<"mutation">("functions/crm/fields:reorder"),
|
|
169
179
|
},
|
|
170
180
|
entries: {
|
|
171
181
|
list: makeFunctionReference<"query">("functions/crm/entries:list"),
|
|
172
182
|
get: makeFunctionReference<"query">("functions/crm/entries:get"),
|
|
173
|
-
create: makeFunctionReference<"mutation">(
|
|
174
|
-
|
|
175
|
-
),
|
|
176
|
-
update: makeFunctionReference<"mutation">(
|
|
177
|
-
"functions/crm/entries:update",
|
|
178
|
-
),
|
|
179
|
-
remove: makeFunctionReference<"mutation">(
|
|
180
|
-
"functions/crm/entries:remove",
|
|
181
|
-
),
|
|
183
|
+
create: makeFunctionReference<"mutation">("functions/crm/entries:create"),
|
|
184
|
+
update: makeFunctionReference<"mutation">("functions/crm/entries:update"),
|
|
185
|
+
remove: makeFunctionReference<"mutation">("functions/crm/entries:remove"),
|
|
182
186
|
bulkDelete: makeFunctionReference<"mutation">(
|
|
183
187
|
"functions/crm/entries:bulkDelete",
|
|
184
188
|
),
|
|
@@ -186,9 +190,7 @@ const api = {
|
|
|
186
190
|
cells: {
|
|
187
191
|
get: makeFunctionReference<"query">("functions/crm/cells:get"),
|
|
188
192
|
set: makeFunctionReference<"mutation">("functions/crm/cells:set"),
|
|
189
|
-
append: makeFunctionReference<"mutation">(
|
|
190
|
-
"functions/crm/cells:append",
|
|
191
|
-
),
|
|
193
|
+
append: makeFunctionReference<"mutation">("functions/crm/cells:append"),
|
|
192
194
|
},
|
|
193
195
|
query: {
|
|
194
196
|
queryEntries: makeFunctionReference<"query">(
|
|
@@ -208,9 +210,7 @@ const api = {
|
|
|
208
210
|
},
|
|
209
211
|
documents: {
|
|
210
212
|
list: makeFunctionReference<"query">("functions/crm/documents:list"),
|
|
211
|
-
create: makeFunctionReference<"mutation">(
|
|
212
|
-
"functions/crm/documents:create",
|
|
213
|
-
),
|
|
213
|
+
create: makeFunctionReference<"mutation">("functions/crm/documents:create"),
|
|
214
214
|
link: makeFunctionReference<"mutation">("functions/crm/documents:link"),
|
|
215
215
|
},
|
|
216
216
|
actions: {
|
|
@@ -288,11 +288,13 @@ async function runObjectsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
288
288
|
const verb = ctx.args.shift();
|
|
289
289
|
switch (verb) {
|
|
290
290
|
case "list": {
|
|
291
|
+
assertNoUnknownFlags(ctx.args, "crm objects list");
|
|
291
292
|
out(ctx, await callQuery(ctx, api.objects.list, {}));
|
|
292
293
|
return;
|
|
293
294
|
}
|
|
294
295
|
case "get": {
|
|
295
296
|
const name = shift(ctx.args, "object name");
|
|
297
|
+
assertNoUnknownFlags(ctx.args, "crm objects get");
|
|
296
298
|
out(ctx, await callQuery(ctx, api.objects.get, { name }));
|
|
297
299
|
return;
|
|
298
300
|
}
|
|
@@ -301,6 +303,7 @@ async function runObjectsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
301
303
|
const description = getFlag(ctx.args, "--description");
|
|
302
304
|
const defaultView = getFlag(ctx.args, "--default-view") as any;
|
|
303
305
|
const icon = getFlag(ctx.args, "--icon");
|
|
306
|
+
assertNoUnknownFlags(ctx.args, "crm objects create");
|
|
304
307
|
out(
|
|
305
308
|
ctx,
|
|
306
309
|
await callMutation(ctx, api.objects.create, {
|
|
@@ -317,6 +320,7 @@ async function runObjectsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
317
320
|
const description = getFlag(ctx.args, "--description");
|
|
318
321
|
const defaultView = getFlag(ctx.args, "--default-view") as any;
|
|
319
322
|
const icon = getFlag(ctx.args, "--icon");
|
|
323
|
+
assertNoUnknownFlags(ctx.args, "crm objects update");
|
|
320
324
|
out(
|
|
321
325
|
ctx,
|
|
322
326
|
await callMutation(ctx, api.objects.update, {
|
|
@@ -331,12 +335,14 @@ async function runObjectsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
331
335
|
case "rename": {
|
|
332
336
|
const from = shift(ctx.args, "current name");
|
|
333
337
|
const to = shift(ctx.args, "new name");
|
|
338
|
+
assertNoUnknownFlags(ctx.args, "crm objects rename");
|
|
334
339
|
out(ctx, await callMutation(ctx, api.objects.rename, { from, to }));
|
|
335
340
|
return;
|
|
336
341
|
}
|
|
337
342
|
case "delete":
|
|
338
343
|
case "remove": {
|
|
339
344
|
const name = shift(ctx.args, "object name");
|
|
345
|
+
assertNoUnknownFlags(ctx.args, "crm objects delete");
|
|
340
346
|
out(ctx, await callMutation(ctx, api.objects.remove, { name }));
|
|
341
347
|
return;
|
|
342
348
|
}
|
|
@@ -350,6 +356,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
350
356
|
switch (verb) {
|
|
351
357
|
case "list": {
|
|
352
358
|
const objectName = shift(ctx.args, "object name");
|
|
359
|
+
assertNoUnknownFlags(ctx.args, "crm fields list");
|
|
353
360
|
out(ctx, await callQuery(ctx, api.fields.list, { objectName }));
|
|
354
361
|
return;
|
|
355
362
|
}
|
|
@@ -373,6 +380,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
373
380
|
: hasFlag(ctx.args, "--no-indexed")
|
|
374
381
|
? false
|
|
375
382
|
: undefined;
|
|
383
|
+
assertNoUnknownFlags(ctx.args, "crm fields create");
|
|
376
384
|
out(
|
|
377
385
|
ctx,
|
|
378
386
|
await callMutation(ctx, api.fields.create, {
|
|
@@ -404,6 +412,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
404
412
|
: hasFlag(ctx.args, "--no-indexed")
|
|
405
413
|
? false
|
|
406
414
|
: undefined;
|
|
415
|
+
assertNoUnknownFlags(ctx.args, "crm fields update");
|
|
407
416
|
out(
|
|
408
417
|
ctx,
|
|
409
418
|
await callMutation(ctx, api.fields.update, {
|
|
@@ -421,6 +430,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
421
430
|
case "remove": {
|
|
422
431
|
const objectName = shift(ctx.args, "object name");
|
|
423
432
|
const fieldName = shift(ctx.args, "field name");
|
|
433
|
+
assertNoUnknownFlags(ctx.args, "crm fields delete");
|
|
424
434
|
out(
|
|
425
435
|
ctx,
|
|
426
436
|
await callMutation(ctx, api.fields.remove, {
|
|
@@ -432,7 +442,10 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
432
442
|
}
|
|
433
443
|
case "reorder": {
|
|
434
444
|
const objectName = shift(ctx.args, "object name");
|
|
435
|
-
|
|
445
|
+
// Assert before consuming the rest as positional field names so a
|
|
446
|
+
// typo'd flag fails loudly rather than getting silently filtered.
|
|
447
|
+
assertNoUnknownFlags(ctx.args, "crm fields reorder");
|
|
448
|
+
const orderedFieldNames = [...ctx.args];
|
|
436
449
|
out(
|
|
437
450
|
ctx,
|
|
438
451
|
await callMutation(ctx, api.fields.reorder, {
|
|
@@ -453,15 +466,14 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
453
466
|
case "list": {
|
|
454
467
|
const objectName = shift(ctx.args, "object name");
|
|
455
468
|
const limit = parseInt(getFlag(ctx.args, "--limit") ?? "100", 10);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
await callQuery(ctx, api.entries.list, { objectName, limit }),
|
|
459
|
-
);
|
|
469
|
+
assertNoUnknownFlags(ctx.args, "crm entries list");
|
|
470
|
+
out(ctx, await callQuery(ctx, api.entries.list, { objectName, limit }));
|
|
460
471
|
return;
|
|
461
472
|
}
|
|
462
473
|
case "get": {
|
|
463
474
|
const objectName = shift(ctx.args, "object name");
|
|
464
475
|
const entryId = shift(ctx.args, "entry id");
|
|
476
|
+
assertNoUnknownFlags(ctx.args, "crm entries get");
|
|
465
477
|
out(
|
|
466
478
|
ctx,
|
|
467
479
|
await callQuery(ctx, api.entries.get, {
|
|
@@ -473,9 +485,24 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
473
485
|
}
|
|
474
486
|
case "create": {
|
|
475
487
|
const objectName = shift(ctx.args, "object name");
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
488
|
+
const dataRaw = getDataFlag(ctx.args);
|
|
489
|
+
assertNoUnknownFlags(ctx.args, "crm entries create");
|
|
490
|
+
// Refuse silent blank-row creation. The agent that triggered this
|
|
491
|
+
// refactor passed `--fields` (which used to be silently dropped),
|
|
492
|
+
// got an empty data object, and only realized after creating 15
|
|
493
|
+
// blank rows. Be explicit: callers must pass --data (or --fields)
|
|
494
|
+
// with at least one key.
|
|
495
|
+
if (dataRaw === undefined) {
|
|
496
|
+
throw new CrmCliError(
|
|
497
|
+
'dench crm entries create requires --data \'{"Field":"Value",...}\' (alias: --fields)',
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
const data = (parseJson(dataRaw) as JsonRecord | undefined) ?? {};
|
|
501
|
+
if (!data || Object.keys(data).length === 0) {
|
|
502
|
+
throw new CrmCliError(
|
|
503
|
+
'dench crm entries create: --data must include at least one field. Pass --data \'{"Name":"…"}\' or use cells set after creation.',
|
|
504
|
+
);
|
|
505
|
+
}
|
|
479
506
|
out(
|
|
480
507
|
ctx,
|
|
481
508
|
await callMutation(ctx, api.entries.create, { objectName, data }),
|
|
@@ -485,9 +512,19 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
485
512
|
case "update": {
|
|
486
513
|
const objectName = shift(ctx.args, "object name");
|
|
487
514
|
const entryId = shift(ctx.args, "entry id");
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
515
|
+
const dataRaw = getDataFlag(ctx.args);
|
|
516
|
+
assertNoUnknownFlags(ctx.args, "crm entries update");
|
|
517
|
+
if (dataRaw === undefined) {
|
|
518
|
+
throw new CrmCliError(
|
|
519
|
+
'dench crm entries update requires --data \'{"Field":"Value",...}\' (alias: --fields)',
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
const data = (parseJson(dataRaw) as JsonRecord | undefined) ?? {};
|
|
523
|
+
if (!data || Object.keys(data).length === 0) {
|
|
524
|
+
throw new CrmCliError(
|
|
525
|
+
'dench crm entries update: --data must include at least one field to change. Pass --data \'{"Field":"…"}\'.',
|
|
526
|
+
);
|
|
527
|
+
}
|
|
491
528
|
out(
|
|
492
529
|
ctx,
|
|
493
530
|
await callMutation(ctx, api.entries.update, {
|
|
@@ -502,6 +539,7 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
502
539
|
case "remove": {
|
|
503
540
|
const _objectName = shift(ctx.args, "object name");
|
|
504
541
|
const entryId = shift(ctx.args, "entry id");
|
|
542
|
+
assertNoUnknownFlags(ctx.args, "crm entries delete");
|
|
505
543
|
out(
|
|
506
544
|
ctx,
|
|
507
545
|
await callMutation(ctx, api.entries.remove, {
|
|
@@ -514,6 +552,7 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
514
552
|
case "bulkDelete": {
|
|
515
553
|
const _objectName = shift(ctx.args, "object name");
|
|
516
554
|
const idsCsv = getFlag(ctx.args, "--ids") ?? "";
|
|
555
|
+
assertNoUnknownFlags(ctx.args, "crm entries bulk-delete");
|
|
517
556
|
const entryIds = idsCsv.split(",").filter(Boolean);
|
|
518
557
|
out(
|
|
519
558
|
ctx,
|
|
@@ -535,6 +574,7 @@ async function runCellsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
535
574
|
const fieldName = shift(ctx.args, "field name");
|
|
536
575
|
switch (verb) {
|
|
537
576
|
case "get": {
|
|
577
|
+
assertNoUnknownFlags(ctx.args, "crm cells get");
|
|
538
578
|
out(
|
|
539
579
|
ctx,
|
|
540
580
|
await callQuery(ctx, api.cells.get, {
|
|
@@ -547,6 +587,7 @@ async function runCellsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
547
587
|
}
|
|
548
588
|
case "set": {
|
|
549
589
|
const value = parseJson(ctx.args.shift()) ?? null;
|
|
590
|
+
assertNoUnknownFlags(ctx.args, "crm cells set");
|
|
550
591
|
out(
|
|
551
592
|
ctx,
|
|
552
593
|
await callMutation(ctx, api.cells.set, {
|
|
@@ -560,6 +601,7 @@ async function runCellsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
560
601
|
}
|
|
561
602
|
case "append": {
|
|
562
603
|
const value = parseJson(ctx.args.shift()) ?? null;
|
|
604
|
+
assertNoUnknownFlags(ctx.args, "crm cells append");
|
|
563
605
|
out(
|
|
564
606
|
ctx,
|
|
565
607
|
await callMutation(ctx, api.cells.append, {
|
|
@@ -584,6 +626,7 @@ async function runQueryCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
584
626
|
const select = getFlag(ctx.args, "--select")?.split(",").filter(Boolean);
|
|
585
627
|
const sort = getFlag(ctx.args, "--sort");
|
|
586
628
|
const limit = parseInt(getFlag(ctx.args, "--limit") ?? "100", 10);
|
|
629
|
+
assertNoUnknownFlags(ctx.args, "crm query");
|
|
587
630
|
// Convex's wire serializer rejects $-prefixed object keys, so we
|
|
588
631
|
// route the DSL through the dslJson string field whenever the filter
|
|
589
632
|
// has any operator-shaped clause.
|
|
@@ -591,7 +634,8 @@ async function runQueryCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
591
634
|
const hasDollarKey = filter ? containsDollarKey(filter) : false;
|
|
592
635
|
out(
|
|
593
636
|
ctx,
|
|
594
|
-
await callQuery(
|
|
637
|
+
await callQuery(
|
|
638
|
+
ctx,
|
|
595
639
|
api.query.queryEntries,
|
|
596
640
|
hasDollarKey
|
|
597
641
|
? { objectName, dslJson: JSON.stringify(dsl) }
|
|
@@ -616,6 +660,7 @@ async function runAggregateCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
616
660
|
const objectName = shift(ctx.args, "object name");
|
|
617
661
|
const fieldName = getFlag(ctx.args, "--field");
|
|
618
662
|
if (!fieldName) throw new CrmCliError("--field is required");
|
|
663
|
+
assertNoUnknownFlags(ctx.args, "crm aggregate");
|
|
619
664
|
out(
|
|
620
665
|
ctx,
|
|
621
666
|
await callQuery(ctx, api.query.aggregate, { objectName, fieldName }),
|
|
@@ -624,13 +669,14 @@ async function runAggregateCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
624
669
|
|
|
625
670
|
async function runSearchCommand(ctx: CrmCliContext): Promise<void> {
|
|
626
671
|
const objectName = getFlag(ctx.args, "--object");
|
|
672
|
+
const limit = parseInt(getFlag(ctx.args, "--limit") ?? "50", 10);
|
|
673
|
+
// Drain known flags before joining the rest into the search text so a
|
|
674
|
+
// typo like `--objct` surfaces instead of being concatenated into the
|
|
675
|
+
// query string.
|
|
676
|
+
assertNoUnknownFlags(ctx.args, "crm search");
|
|
627
677
|
const text = ctx.args.join(" ").trim();
|
|
628
678
|
if (!text) throw new CrmCliError("Search text required");
|
|
629
|
-
|
|
630
|
-
out(
|
|
631
|
-
ctx,
|
|
632
|
-
await callQuery(ctx, api.query.search, { text, objectName, limit }),
|
|
633
|
-
);
|
|
679
|
+
out(ctx, await callQuery(ctx, api.query.search, { text, objectName, limit }));
|
|
634
680
|
}
|
|
635
681
|
|
|
636
682
|
async function runStatusesCommand(ctx: CrmCliContext): Promise<void> {
|
|
@@ -638,12 +684,14 @@ async function runStatusesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
638
684
|
switch (verb) {
|
|
639
685
|
case "list": {
|
|
640
686
|
const objectName = shift(ctx.args, "object name");
|
|
687
|
+
assertNoUnknownFlags(ctx.args, "crm statuses list");
|
|
641
688
|
out(ctx, await callQuery(ctx, api.statuses.list, { objectName }));
|
|
642
689
|
return;
|
|
643
690
|
}
|
|
644
691
|
case "set": {
|
|
645
692
|
const objectName = shift(ctx.args, "object name");
|
|
646
693
|
const statuses = parseJson(getFlag(ctx.args, "--statuses")) as any[];
|
|
694
|
+
assertNoUnknownFlags(ctx.args, "crm statuses set");
|
|
647
695
|
if (!Array.isArray(statuses)) {
|
|
648
696
|
throw new CrmCliError("--statuses (JSON array) is required");
|
|
649
697
|
}
|
|
@@ -663,6 +711,10 @@ async function runStatusesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
663
711
|
|
|
664
712
|
async function runBatchCommand(ctx: CrmCliContext): Promise<void> {
|
|
665
713
|
const filePath = getFlag(ctx.args, "--file");
|
|
714
|
+
// The help text advertises --idempotency-key; thread it through so the
|
|
715
|
+
// doc and the implementation agree.
|
|
716
|
+
const idempotencyKey = getFlag(ctx.args, "--idempotency-key");
|
|
717
|
+
assertNoUnknownFlags(ctx.args, "crm batch");
|
|
666
718
|
if (!filePath) {
|
|
667
719
|
throw new CrmCliError(
|
|
668
720
|
"dench crm batch requires --file <ops.jsonl> (one op per line)",
|
|
@@ -683,7 +735,8 @@ async function runBatchCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
683
735
|
ctx,
|
|
684
736
|
await callMutation(ctx, api.batch.apply, {
|
|
685
737
|
ops: ops as any,
|
|
686
|
-
|
|
738
|
+
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
739
|
+
} as any),
|
|
687
740
|
);
|
|
688
741
|
}
|
|
689
742
|
|
|
@@ -691,6 +744,7 @@ async function runDocsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
691
744
|
const verb = ctx.args.shift();
|
|
692
745
|
switch (verb) {
|
|
693
746
|
case "list": {
|
|
747
|
+
assertNoUnknownFlags(ctx.args, "crm docs list");
|
|
694
748
|
out(ctx, await callQuery(ctx, api.documents.list, {}));
|
|
695
749
|
return;
|
|
696
750
|
}
|
|
@@ -699,6 +753,7 @@ async function runDocsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
699
753
|
const title = getFlag(ctx.args, "--title") ?? filePath.split("/").pop()!;
|
|
700
754
|
const icon = getFlag(ctx.args, "--icon");
|
|
701
755
|
const linkEntryId = getFlag(ctx.args, "--link-entry");
|
|
756
|
+
assertNoUnknownFlags(ctx.args, "crm docs create");
|
|
702
757
|
out(
|
|
703
758
|
ctx,
|
|
704
759
|
await callMutation(ctx, api.documents.create, {
|
|
@@ -714,6 +769,7 @@ async function runDocsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
714
769
|
const filePath = shift(ctx.args, "file path");
|
|
715
770
|
const objectName = getFlag(ctx.args, "--object");
|
|
716
771
|
const entryId = getFlag(ctx.args, "--entry");
|
|
772
|
+
assertNoUnknownFlags(ctx.args, "crm docs link");
|
|
717
773
|
out(
|
|
718
774
|
ctx,
|
|
719
775
|
await callMutation(ctx, api.documents.link, {
|
|
@@ -734,6 +790,7 @@ async function runActionsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
734
790
|
switch (verb) {
|
|
735
791
|
case "list": {
|
|
736
792
|
const entryId = shift(ctx.args, "entry id");
|
|
793
|
+
assertNoUnknownFlags(ctx.args, "crm actions list");
|
|
737
794
|
out(
|
|
738
795
|
ctx,
|
|
739
796
|
await callQuery(ctx, api.actions.list, {
|
|
@@ -750,6 +807,7 @@ async function runActionsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
750
807
|
const entryId = getFlag(ctx.args, "--entry-id");
|
|
751
808
|
const goal = getFlag(ctx.args, "--goal") ?? "";
|
|
752
809
|
const wait = hasFlag(ctx.args, "--wait");
|
|
810
|
+
assertNoUnknownFlags(ctx.args, "crm actions run");
|
|
753
811
|
if (!fieldId || !entryId) {
|
|
754
812
|
throw new CrmCliError(
|
|
755
813
|
"dench crm actions run requires --field-id and --entry-id",
|
|
@@ -804,35 +862,50 @@ async function runTransactionCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
804
862
|
const verb = ctx.args.shift();
|
|
805
863
|
switch (verb) {
|
|
806
864
|
case "begin": {
|
|
865
|
+
assertNoUnknownFlags(ctx.args, "crm transaction begin");
|
|
807
866
|
out(ctx, await callMutation(ctx, api.transaction.begin, {}));
|
|
808
867
|
return;
|
|
809
868
|
}
|
|
810
869
|
case "add": {
|
|
811
870
|
const txnId = shift(ctx.args, "txn id");
|
|
812
871
|
const opType = shift(ctx.args, "op type (entries.create|update|delete)");
|
|
813
|
-
const objectName = ctx.args[0];
|
|
814
872
|
let op: any;
|
|
815
873
|
if (opType === "entries.create") {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
874
|
+
const opObjectName = shift(ctx.args, "object name");
|
|
875
|
+
const dataRaw = getDataFlag(ctx.args);
|
|
876
|
+
if (dataRaw === undefined) {
|
|
877
|
+
throw new CrmCliError(
|
|
878
|
+
"dench crm transaction add <txn> entries.create requires --data '{...}' (alias: --fields)",
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
const data = (parseJson(dataRaw) as JsonRecord | undefined) ?? {};
|
|
882
|
+
if (!data || Object.keys(data).length === 0) {
|
|
883
|
+
throw new CrmCliError(
|
|
884
|
+
"dench crm transaction add entries.create: --data must include at least one field.",
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
op = { type: "entries.create", objectName: opObjectName, data };
|
|
823
888
|
} else if (opType === "entries.update") {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
}
|
|
889
|
+
const opEntryId = shift(ctx.args, "entry id");
|
|
890
|
+
const dataRaw = getDataFlag(ctx.args);
|
|
891
|
+
if (dataRaw === undefined) {
|
|
892
|
+
throw new CrmCliError(
|
|
893
|
+
"dench crm transaction add <txn> entries.update requires --data '{...}' (alias: --fields)",
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
const data = (parseJson(dataRaw) as JsonRecord | undefined) ?? {};
|
|
897
|
+
if (!data || Object.keys(data).length === 0) {
|
|
898
|
+
throw new CrmCliError(
|
|
899
|
+
"dench crm transaction add entries.update: --data must include at least one field.",
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
op = { type: "entries.update", entryId: opEntryId, data };
|
|
831
903
|
} else if (opType === "entries.delete") {
|
|
832
904
|
op = { type: "entries.delete", entryId: shift(ctx.args, "entry id") };
|
|
833
905
|
} else {
|
|
834
906
|
throw new CrmCliError(`Unknown txn op type: ${opType}`);
|
|
835
907
|
}
|
|
908
|
+
assertNoUnknownFlags(ctx.args, "crm transaction add");
|
|
836
909
|
out(
|
|
837
910
|
ctx,
|
|
838
911
|
await callMutation(ctx, api.transaction.addOp, {
|
|
@@ -844,6 +917,7 @@ async function runTransactionCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
844
917
|
}
|
|
845
918
|
case "commit": {
|
|
846
919
|
const txnId = shift(ctx.args, "txn id");
|
|
920
|
+
assertNoUnknownFlags(ctx.args, "crm transaction commit");
|
|
847
921
|
out(
|
|
848
922
|
ctx,
|
|
849
923
|
await callMutation(ctx, api.transaction.commit, {
|
|
@@ -854,6 +928,7 @@ async function runTransactionCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
854
928
|
}
|
|
855
929
|
case "abort": {
|
|
856
930
|
const txnId = shift(ctx.args, "txn id");
|
|
931
|
+
assertNoUnknownFlags(ctx.args, "crm transaction abort");
|
|
857
932
|
out(
|
|
858
933
|
ctx,
|
|
859
934
|
await callMutation(ctx, api.transaction.abort, {
|
|
@@ -864,6 +939,7 @@ async function runTransactionCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
864
939
|
}
|
|
865
940
|
case "inspect": {
|
|
866
941
|
const txnId = shift(ctx.args, "txn id");
|
|
942
|
+
assertNoUnknownFlags(ctx.args, "crm transaction inspect");
|
|
867
943
|
out(
|
|
868
944
|
ctx,
|
|
869
945
|
await callQuery(ctx, api.transaction.inspect, {
|
|
@@ -878,6 +954,7 @@ async function runTransactionCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
878
954
|
ctx.args.unshift(verb ?? "");
|
|
879
955
|
const filePath = getFlag(ctx.args, "--file");
|
|
880
956
|
const commit = hasFlag(ctx.args, "--commit");
|
|
957
|
+
assertNoUnknownFlags(ctx.args, "crm transaction --file");
|
|
881
958
|
if (!filePath) {
|
|
882
959
|
throw new CrmCliError(
|
|
883
960
|
"dench crm transaction --file <ops.json> [--commit]",
|
|
@@ -886,10 +963,9 @@ async function runTransactionCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
886
963
|
const fs = await import("node:fs/promises");
|
|
887
964
|
const text = await fs.readFile(filePath, "utf-8");
|
|
888
965
|
const ops = JSON.parse(text) as unknown[];
|
|
889
|
-
const begun = (await callMutation(ctx,
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
)) as { txnId: string };
|
|
966
|
+
const begun = (await callMutation(ctx, api.transaction.begin, {})) as {
|
|
967
|
+
txnId: string;
|
|
968
|
+
};
|
|
893
969
|
for (const op of ops) {
|
|
894
970
|
await callMutation(ctx, api.transaction.addOp, {
|
|
895
971
|
txnId: begun.txnId as any,
|
|
@@ -924,6 +1000,7 @@ async function runImportCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
924
1000
|
| "skip"
|
|
925
1001
|
| "update"
|
|
926
1002
|
| "error";
|
|
1003
|
+
assertNoUnknownFlags(ctx.args, "crm import");
|
|
927
1004
|
if (!csvPath && !jsonlPath) {
|
|
928
1005
|
throw new CrmCliError(
|
|
929
1006
|
"dench crm import <object> requires --csv <path> or --jsonl <path>",
|
|
@@ -965,10 +1042,9 @@ async function runImportCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
965
1042
|
|
|
966
1043
|
async function runExportCommand(ctx: CrmCliContext): Promise<void> {
|
|
967
1044
|
const objectName = shift(ctx.args, "object name");
|
|
968
|
-
const format = (getFlag(ctx.args, "--format") ?? "jsonl") as
|
|
969
|
-
| "csv"
|
|
970
|
-
| "jsonl";
|
|
1045
|
+
const format = (getFlag(ctx.args, "--format") ?? "jsonl") as "csv" | "jsonl";
|
|
971
1046
|
const limit = parseInt(getFlag(ctx.args, "--limit") ?? "10000", 10);
|
|
1047
|
+
assertNoUnknownFlags(ctx.args, "crm export");
|
|
972
1048
|
// Page through entries.list in 1000-row chunks.
|
|
973
1049
|
const all: any[] = [];
|
|
974
1050
|
let cursor = 0;
|
|
@@ -1012,9 +1088,7 @@ export function parseCsv(
|
|
|
1012
1088
|
const rows: Record<string, unknown>[] = [];
|
|
1013
1089
|
const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
1014
1090
|
if (lines.length === 0) return rows;
|
|
1015
|
-
const header = parseCsvLine(lines[0]).map(
|
|
1016
|
-
(col) => colMap[col] ?? col,
|
|
1017
|
-
);
|
|
1091
|
+
const header = parseCsvLine(lines[0]).map((col) => colMap[col] ?? col);
|
|
1018
1092
|
for (let i = 1; i < lines.length; i++) {
|
|
1019
1093
|
const cols = parseCsvLine(lines[i]);
|
|
1020
1094
|
const row: Record<string, unknown> = {};
|
|
@@ -1109,7 +1183,10 @@ async function runSqlCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1109
1183
|
.split(",")
|
|
1110
1184
|
.map((s) => {
|
|
1111
1185
|
const desc = /\s+DESC\s*$/i.test(s);
|
|
1112
|
-
return `${desc ? "-" : ""}${s
|
|
1186
|
+
return `${desc ? "-" : ""}${s
|
|
1187
|
+
.replace(/\s+(asc|desc)\s*$/i, "")
|
|
1188
|
+
.trim()
|
|
1189
|
+
.replace(/"/g, "")}`;
|
|
1113
1190
|
})
|
|
1114
1191
|
.join(",")
|
|
1115
1192
|
: undefined;
|
|
@@ -1131,6 +1208,7 @@ async function runPeopleCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1131
1208
|
const verb = ctx.args.shift();
|
|
1132
1209
|
switch (verb) {
|
|
1133
1210
|
case "search": {
|
|
1211
|
+
assertNoUnknownFlags(ctx.args, "crm people search");
|
|
1134
1212
|
const text = ctx.args.join(" ").trim();
|
|
1135
1213
|
if (!text) throw new CrmCliError("Search text required");
|
|
1136
1214
|
out(
|
|
@@ -1144,9 +1222,19 @@ async function runPeopleCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1144
1222
|
return;
|
|
1145
1223
|
}
|
|
1146
1224
|
case "upsert": {
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1225
|
+
const dataRaw = getDataFlag(ctx.args);
|
|
1226
|
+
assertNoUnknownFlags(ctx.args, "crm people upsert");
|
|
1227
|
+
if (dataRaw === undefined) {
|
|
1228
|
+
throw new CrmCliError(
|
|
1229
|
+
'dench crm people upsert requires --data \'{"Email Address":"…",...}\' (alias: --fields)',
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
const data = (parseJson(dataRaw) as JsonRecord | undefined) ?? {};
|
|
1233
|
+
if (!data || Object.keys(data).length === 0) {
|
|
1234
|
+
throw new CrmCliError(
|
|
1235
|
+
"dench crm people upsert: --data must include at least one field.",
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1150
1238
|
out(
|
|
1151
1239
|
ctx,
|
|
1152
1240
|
await callMutation(ctx, api.entries.create, {
|
|
@@ -1159,11 +1247,13 @@ async function runPeopleCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1159
1247
|
case "enrich": {
|
|
1160
1248
|
const entryId = shift(ctx.args, "person entry id");
|
|
1161
1249
|
const provider = getFlag(ctx.args, "--provider");
|
|
1250
|
+
const fieldName = getFlag(ctx.args, "--field") ?? "Email Address";
|
|
1251
|
+
assertNoUnknownFlags(ctx.args, "crm people enrich");
|
|
1162
1252
|
out(
|
|
1163
1253
|
ctx,
|
|
1164
1254
|
await callMutation(ctx, api.enrich.requestObjectEnrichment, {
|
|
1165
1255
|
objectName: "people",
|
|
1166
|
-
fieldName
|
|
1256
|
+
fieldName,
|
|
1167
1257
|
provider,
|
|
1168
1258
|
}),
|
|
1169
1259
|
);
|
|
@@ -1182,6 +1272,7 @@ async function runCompaniesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1182
1272
|
const verb = ctx.args.shift();
|
|
1183
1273
|
switch (verb) {
|
|
1184
1274
|
case "search": {
|
|
1275
|
+
assertNoUnknownFlags(ctx.args, "crm companies search");
|
|
1185
1276
|
const text = ctx.args.join(" ").trim();
|
|
1186
1277
|
if (!text) throw new CrmCliError("Search text required");
|
|
1187
1278
|
out(
|
|
@@ -1197,9 +1288,15 @@ async function runCompaniesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1197
1288
|
case "upsert": {
|
|
1198
1289
|
const domain = getFlag(ctx.args, "--domain");
|
|
1199
1290
|
const name = getFlag(ctx.args, "--name");
|
|
1291
|
+
assertNoUnknownFlags(ctx.args, "crm companies upsert");
|
|
1200
1292
|
const data: Record<string, unknown> = {};
|
|
1201
1293
|
if (domain) data["Domain"] = domain;
|
|
1202
1294
|
if (name) data["Name"] = name;
|
|
1295
|
+
if (Object.keys(data).length === 0) {
|
|
1296
|
+
throw new CrmCliError(
|
|
1297
|
+
"dench crm companies upsert requires at least --domain or --name. For arbitrary fields, use `dench crm entries create company --data '{...}'`.",
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1203
1300
|
out(
|
|
1204
1301
|
ctx,
|
|
1205
1302
|
await callMutation(ctx, api.entries.create, {
|
|
@@ -1212,11 +1309,13 @@ async function runCompaniesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1212
1309
|
case "enrich": {
|
|
1213
1310
|
const entryId = shift(ctx.args, "company entry id");
|
|
1214
1311
|
const provider = getFlag(ctx.args, "--provider");
|
|
1312
|
+
const fieldName = getFlag(ctx.args, "--field") ?? "Domain";
|
|
1313
|
+
assertNoUnknownFlags(ctx.args, "crm companies enrich");
|
|
1215
1314
|
out(
|
|
1216
1315
|
ctx,
|
|
1217
1316
|
await callMutation(ctx, api.enrich.requestObjectEnrichment, {
|
|
1218
1317
|
objectName: "company",
|
|
1219
|
-
fieldName
|
|
1318
|
+
fieldName,
|
|
1220
1319
|
provider,
|
|
1221
1320
|
}),
|
|
1222
1321
|
);
|
|
@@ -1238,6 +1337,7 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1238
1337
|
const entryId = shift(ctx.args, "entry id");
|
|
1239
1338
|
const fieldName = shift(ctx.args, "field name");
|
|
1240
1339
|
const provider = getFlag(ctx.args, "--provider");
|
|
1340
|
+
assertNoUnknownFlags(ctx.args, "crm enrich cell");
|
|
1241
1341
|
out(
|
|
1242
1342
|
ctx,
|
|
1243
1343
|
await callMutation(ctx, api.enrich.requestCellEnrichment, {
|
|
@@ -1254,6 +1354,7 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1254
1354
|
const fieldName = getFlag(ctx.args, "--field");
|
|
1255
1355
|
const missingOnly = !hasFlag(ctx.args, "--all");
|
|
1256
1356
|
const provider = getFlag(ctx.args, "--provider");
|
|
1357
|
+
assertNoUnknownFlags(ctx.args, "crm enrich object");
|
|
1257
1358
|
if (!fieldName) {
|
|
1258
1359
|
throw new CrmCliError(
|
|
1259
1360
|
"dench crm enrich object <name> --field <field> [--provider ...] [--all]",
|
|
@@ -1286,11 +1387,7 @@ async function runReportsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1286
1387
|
}
|
|
1287
1388
|
const _name = shift(ctx.args, "report name");
|
|
1288
1389
|
const objectName = getFlag(ctx.args, "--object");
|
|
1289
|
-
const type = getFlag(ctx.args, "--type") as
|
|
1290
|
-
| "pie"
|
|
1291
|
-
| "bar"
|
|
1292
|
-
| "line"
|
|
1293
|
-
| "table";
|
|
1390
|
+
const type = getFlag(ctx.args, "--type") as "pie" | "bar" | "line" | "table";
|
|
1294
1391
|
const groupBy = getFlag(ctx.args, "--group-by");
|
|
1295
1392
|
const metric = getFlag(ctx.args, "--metric") as
|
|
1296
1393
|
| "count"
|
|
@@ -1300,6 +1397,7 @@ async function runReportsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1300
1397
|
| "max"
|
|
1301
1398
|
| undefined;
|
|
1302
1399
|
const metricField = getFlag(ctx.args, "--metric-field");
|
|
1400
|
+
assertNoUnknownFlags(ctx.args, "crm reports generate");
|
|
1303
1401
|
if (!objectName || !type || !groupBy) {
|
|
1304
1402
|
throw new CrmCliError(
|
|
1305
1403
|
"dench crm reports generate requires --object, --type, --group-by",
|
|
@@ -1341,10 +1439,13 @@ Fields (columns):
|
|
|
1341
1439
|
Entries (rows):
|
|
1342
1440
|
dench crm entries list <object> [--limit N] [--json]
|
|
1343
1441
|
dench crm entries get <object> <entryId>
|
|
1344
|
-
dench crm entries create <object> --data '{"Field":"Value",...}'
|
|
1345
|
-
dench crm entries update <object> <entryId> --data '{...}'
|
|
1442
|
+
dench crm entries create <object> --data '{"Field":"Value",...}' (alias: --fields)
|
|
1443
|
+
dench crm entries update <object> <entryId> --data '{...}' (alias: --fields)
|
|
1346
1444
|
dench crm entries delete <object> <entryId>
|
|
1347
1445
|
dench crm entries bulk-delete <object> --ids id1,id2,id3
|
|
1446
|
+
Note: --data is REQUIRED on create/update and must include >=1 field.
|
|
1447
|
+
An empty --data '{}' is rejected — use 'crm cells set' for
|
|
1448
|
+
single-field tweaks instead of creating a placeholder blank row.
|
|
1348
1449
|
|
|
1349
1450
|
Cells (single-field updates):
|
|
1350
1451
|
dench crm cells get <object> <entryId> <field>
|
|
@@ -1385,7 +1486,7 @@ SQL escape hatch (subset; flat SELECTs only):
|
|
|
1385
1486
|
|
|
1386
1487
|
People + companies shortcuts:
|
|
1387
1488
|
dench crm people search '<query>'
|
|
1388
|
-
dench crm people upsert --data '{"Email Address":"jane@example.com",...}'
|
|
1489
|
+
dench crm people upsert --data '{"Email Address":"jane@example.com",...}' (alias: --fields)
|
|
1389
1490
|
dench crm people enrich <entryId> [--provider apollo]
|
|
1390
1491
|
dench crm companies search '<query>'
|
|
1391
1492
|
dench crm companies upsert --domain acme.com [--name Acme]
|