@dench.com/cli 0.4.0 → 0.4.2
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/chat-spawn.ts +10 -4
- package/crm.ts +278 -7
- package/dench.ts +63 -477
- package/fs-daemon.ts +386 -71
- package/package.json +2 -1
- package/tools.ts +808 -0
package/chat-spawn.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* calls until the workflow finishes.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import { getCurrentRuntimeTimeZone } from "@/lib/timezone";
|
|
20
21
|
import { hasFlag } from "./lib/cli-args";
|
|
21
22
|
|
|
22
23
|
class ChatSpawnError extends Error {}
|
|
@@ -64,12 +65,14 @@ async function postJson(
|
|
|
64
65
|
bearer: string,
|
|
65
66
|
body: unknown,
|
|
66
67
|
): Promise<{ ok: boolean; status: number; payload: unknown }> {
|
|
68
|
+
const timezone = getCurrentRuntimeTimeZone();
|
|
67
69
|
const response = await fetch(url, {
|
|
68
70
|
method: "POST",
|
|
69
71
|
headers: {
|
|
70
72
|
accept: "application/json",
|
|
71
73
|
"content-type": "application/json",
|
|
72
74
|
authorization: `Bearer ${bearer}`,
|
|
75
|
+
...(timezone ? { "x-dench-user-timezone": timezone } : {}),
|
|
73
76
|
},
|
|
74
77
|
body: JSON.stringify(body),
|
|
75
78
|
});
|
|
@@ -258,7 +261,10 @@ async function followStream(args: {
|
|
|
258
261
|
}
|
|
259
262
|
|
|
260
263
|
function joinPositionalsExceptFlags(args: string[]): string {
|
|
261
|
-
return args
|
|
264
|
+
return args
|
|
265
|
+
.filter((arg) => !arg.startsWith("--"))
|
|
266
|
+
.join(" ")
|
|
267
|
+
.trim();
|
|
262
268
|
}
|
|
263
269
|
|
|
264
270
|
async function runChatNew(ctx: ChatSpawnContext): Promise<void> {
|
|
@@ -286,6 +292,7 @@ async function runChatNew(ctx: ChatSpawnContext): Promise<void> {
|
|
|
286
292
|
visibility,
|
|
287
293
|
title,
|
|
288
294
|
yolo,
|
|
295
|
+
timezone: getCurrentRuntimeTimeZone(),
|
|
289
296
|
},
|
|
290
297
|
);
|
|
291
298
|
if (!result.ok) {
|
|
@@ -341,6 +348,7 @@ async function runChatSend(ctx: ChatSpawnContext): Promise<void> {
|
|
|
341
348
|
prompt,
|
|
342
349
|
model,
|
|
343
350
|
yolo,
|
|
351
|
+
timezone: getCurrentRuntimeTimeZone(),
|
|
344
352
|
},
|
|
345
353
|
);
|
|
346
354
|
if (!result.ok) {
|
|
@@ -385,9 +393,7 @@ async function runChatFollow(ctx: ChatSpawnContext): Promise<void> {
|
|
|
385
393
|
const positionals = ctx.args.filter((arg) => !arg.startsWith("--"));
|
|
386
394
|
const threadId = positionals.shift();
|
|
387
395
|
if (!threadId) {
|
|
388
|
-
throw new ChatSpawnError(
|
|
389
|
-
"Usage: dench chat follow <threadId> [--json]",
|
|
390
|
-
);
|
|
396
|
+
throw new ChatSpawnError("Usage: dench chat follow <threadId> [--json]");
|
|
391
397
|
}
|
|
392
398
|
if (!ctx.jsonOutput) {
|
|
393
399
|
console.error(`--- following stream for thread ${threadId} ---`);
|
package/crm.ts
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* The full surface mirrors the plan's "dench-cli CRM surface" section:
|
|
18
18
|
* crm objects list / get / create / update / delete / rename
|
|
19
19
|
* crm fields list / create / update / delete / reorder
|
|
20
|
-
* crm entries list / get / create / update / delete / bulk-delete
|
|
21
|
-
* crm cells get / set / append
|
|
20
|
+
* crm entries list / get / create / create-many / update / update-many / delete / bulk-delete
|
|
21
|
+
* crm cells get / set / set-many / append
|
|
22
22
|
* crm query <object> --where … --select … --sort … --limit …
|
|
23
23
|
* crm aggregate <object> --field …
|
|
24
24
|
* crm batch --file ops.jsonl
|
|
@@ -40,6 +40,8 @@ import {
|
|
|
40
40
|
|
|
41
41
|
type JsonRecord = Record<string, unknown>;
|
|
42
42
|
|
|
43
|
+
const CRM_BATCH_CHUNK_SIZE = 200;
|
|
44
|
+
|
|
43
45
|
type CrmCliContext = {
|
|
44
46
|
convex: ConvexHttpClient;
|
|
45
47
|
args: string[];
|
|
@@ -97,6 +99,150 @@ function getDataFlag(args: string[]): string | undefined {
|
|
|
97
99
|
return getFlagWithAliases(args, "--data", ["--fields"]);
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
function getUpdatesFlag(args: string[]): string | undefined {
|
|
103
|
+
return getFlagWithAliases(args, "--updates", ["--data"]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isJsonRecord(value: unknown): value is JsonRecord {
|
|
107
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function requireNonEmptyJsonRecord(value: unknown, label: string): JsonRecord {
|
|
111
|
+
if (!isJsonRecord(value) || Object.keys(value).length === 0) {
|
|
112
|
+
throw new CrmCliError(`${label} must be a non-empty JSON object`);
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function readJsonArrayInput(opts: {
|
|
118
|
+
args: string[];
|
|
119
|
+
command: string;
|
|
120
|
+
inlineFlag: string;
|
|
121
|
+
inlineValue: string | undefined;
|
|
122
|
+
}): Promise<unknown[]> {
|
|
123
|
+
const filePath = getFlag(opts.args, "--file");
|
|
124
|
+
assertNoUnknownFlags(opts.args, opts.command);
|
|
125
|
+
if (opts.inlineValue !== undefined && filePath) {
|
|
126
|
+
throw new CrmCliError(
|
|
127
|
+
`${opts.command} accepts either ${opts.inlineFlag} or --file, not both`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (opts.inlineValue === undefined && !filePath) {
|
|
131
|
+
throw new CrmCliError(
|
|
132
|
+
`${opts.command} requires ${opts.inlineFlag} '<json-array>' or --file <path>`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let raw: string;
|
|
137
|
+
if (filePath) {
|
|
138
|
+
const fs = await import("node:fs/promises");
|
|
139
|
+
raw = await fs.readFile(filePath, "utf-8");
|
|
140
|
+
} else {
|
|
141
|
+
raw = opts.inlineValue ?? "";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const parsed = parseJson(raw);
|
|
145
|
+
if (!Array.isArray(parsed)) {
|
|
146
|
+
throw new CrmCliError(`${opts.inlineFlag} must be a JSON array`);
|
|
147
|
+
}
|
|
148
|
+
if (parsed.length === 0) {
|
|
149
|
+
throw new CrmCliError(`${opts.inlineFlag} must include at least one item`);
|
|
150
|
+
}
|
|
151
|
+
return parsed;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function parseCreateManyRows(items: unknown[]): JsonRecord[] {
|
|
155
|
+
return items.map((item, index) =>
|
|
156
|
+
requireNonEmptyJsonRecord(item, `create-many item ${index + 1}`),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function parseUpdateManyRows(items: unknown[]): Array<{
|
|
161
|
+
id: string;
|
|
162
|
+
data: JsonRecord;
|
|
163
|
+
}> {
|
|
164
|
+
return items.map((item, index) => {
|
|
165
|
+
if (!isJsonRecord(item) || typeof item.id !== "string" || !item.id) {
|
|
166
|
+
throw new CrmCliError(
|
|
167
|
+
`update-many item ${index + 1} must include a string id`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
id: item.id,
|
|
172
|
+
data: requireNonEmptyJsonRecord(
|
|
173
|
+
item.data,
|
|
174
|
+
`update-many item ${index + 1} data`,
|
|
175
|
+
),
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function parseCellSetManyRows(items: unknown[]): Array<{
|
|
181
|
+
entryId: string;
|
|
182
|
+
field: string;
|
|
183
|
+
value: unknown;
|
|
184
|
+
}> {
|
|
185
|
+
return items.map((item, index) => {
|
|
186
|
+
if (!isJsonRecord(item)) {
|
|
187
|
+
throw new CrmCliError(
|
|
188
|
+
`cells set-many item ${index + 1} must be a JSON object`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (typeof item.entryId !== "string" || !item.entryId) {
|
|
192
|
+
throw new CrmCliError(
|
|
193
|
+
`cells set-many item ${index + 1} must include a string entryId`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (typeof item.field !== "string" || !item.field) {
|
|
197
|
+
throw new CrmCliError(
|
|
198
|
+
`cells set-many item ${index + 1} must include a string field`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
entryId: item.entryId,
|
|
203
|
+
field: item.field,
|
|
204
|
+
value: item.value ?? null,
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
type CrmBatchOp =
|
|
210
|
+
| { type: "entries.create"; objectName: string; data: JsonRecord }
|
|
211
|
+
| { type: "entries.update"; entryId: string; data: JsonRecord };
|
|
212
|
+
|
|
213
|
+
function chunkArray<T>(items: T[], size: number): T[][] {
|
|
214
|
+
const chunks: T[][] = [];
|
|
215
|
+
for (let index = 0; index < items.length; index += size) {
|
|
216
|
+
chunks.push(items.slice(index, index + size));
|
|
217
|
+
}
|
|
218
|
+
return chunks;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function chunkIdempotencyKey(
|
|
222
|
+
idempotencyKey: string | undefined,
|
|
223
|
+
chunkIndex: number,
|
|
224
|
+
): string | undefined {
|
|
225
|
+
return idempotencyKey
|
|
226
|
+
? `${idempotencyKey}:chunk-${chunkIndex + 1}`
|
|
227
|
+
: undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function applyBatchOpsInChunks(
|
|
231
|
+
ctx: CrmCliContext,
|
|
232
|
+
ops: CrmBatchOp[],
|
|
233
|
+
idempotencyKey: string | undefined,
|
|
234
|
+
): Promise<number> {
|
|
235
|
+
const chunks = chunkArray(ops, CRM_BATCH_CHUNK_SIZE);
|
|
236
|
+
for (const [chunkIndex, chunk] of chunks.entries()) {
|
|
237
|
+
const chunkKey = chunkIdempotencyKey(idempotencyKey, chunkIndex);
|
|
238
|
+
await callMutation(ctx, api.batch.apply, {
|
|
239
|
+
ops: chunk,
|
|
240
|
+
...(chunkKey ? { idempotencyKey: chunkKey } : {}),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return chunks.length;
|
|
244
|
+
}
|
|
245
|
+
|
|
100
246
|
async function callQuery(
|
|
101
247
|
ctx: CrmCliContext,
|
|
102
248
|
fn: Parameters<ConvexHttpClient["query"]>[0],
|
|
@@ -139,6 +285,35 @@ function out(ctx: CrmCliContext, value: unknown): void {
|
|
|
139
285
|
console.log(JSON.stringify(value, null, 2));
|
|
140
286
|
}
|
|
141
287
|
|
|
288
|
+
function getFieldEnrichmentFlag(args: string[]): {
|
|
289
|
+
category: EnrichmentCategory;
|
|
290
|
+
key: string;
|
|
291
|
+
apolloPath: string;
|
|
292
|
+
inputFieldName: string;
|
|
293
|
+
} | undefined {
|
|
294
|
+
const category = parseCategoryFlag(getFlag(args, "--enrichment-category"));
|
|
295
|
+
const key = getFlag(args, "--enrichment-key");
|
|
296
|
+
const apolloPath = getFlag(args, "--apollo-path");
|
|
297
|
+
const inputFieldName = getFlag(args, "--input-field");
|
|
298
|
+
const sawAny =
|
|
299
|
+
category !== undefined ||
|
|
300
|
+
key !== undefined ||
|
|
301
|
+
apolloPath !== undefined ||
|
|
302
|
+
inputFieldName !== undefined;
|
|
303
|
+
if (!sawAny) return undefined;
|
|
304
|
+
if (!category || !apolloPath || !inputFieldName) {
|
|
305
|
+
throw new CrmCliError(
|
|
306
|
+
"Enrichment fields require --enrichment-category people|company, --apollo-path <path>, and --input-field <field>.",
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
category,
|
|
311
|
+
key: key ?? apolloPath,
|
|
312
|
+
apolloPath,
|
|
313
|
+
inputFieldName,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
142
317
|
const api = {
|
|
143
318
|
transaction: {
|
|
144
319
|
begin: makeFunctionReference<"mutation">("functions/crm/transaction:begin"),
|
|
@@ -380,6 +555,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
380
555
|
: hasFlag(ctx.args, "--no-indexed")
|
|
381
556
|
? false
|
|
382
557
|
: undefined;
|
|
558
|
+
const enrichment = getFieldEnrichmentFlag(ctx.args);
|
|
383
559
|
assertNoUnknownFlags(ctx.args, "crm fields create");
|
|
384
560
|
out(
|
|
385
561
|
ctx,
|
|
@@ -395,6 +571,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
395
571
|
relatedObjectName,
|
|
396
572
|
relationshipType,
|
|
397
573
|
indexed,
|
|
574
|
+
enrichment,
|
|
398
575
|
}),
|
|
399
576
|
);
|
|
400
577
|
return;
|
|
@@ -412,6 +589,8 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
412
589
|
: hasFlag(ctx.args, "--no-indexed")
|
|
413
590
|
? false
|
|
414
591
|
: undefined;
|
|
592
|
+
const enrichment = getFieldEnrichmentFlag(ctx.args);
|
|
593
|
+
const clearEnrichment = hasFlag(ctx.args, "--clear-enrichment");
|
|
415
594
|
assertNoUnknownFlags(ctx.args, "crm fields update");
|
|
416
595
|
out(
|
|
417
596
|
ctx,
|
|
@@ -422,6 +601,8 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
422
601
|
required,
|
|
423
602
|
enumValues,
|
|
424
603
|
indexed,
|
|
604
|
+
enrichment,
|
|
605
|
+
clearEnrichment,
|
|
425
606
|
}),
|
|
426
607
|
);
|
|
427
608
|
return;
|
|
@@ -509,6 +690,30 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
509
690
|
);
|
|
510
691
|
return;
|
|
511
692
|
}
|
|
693
|
+
case "create-many": {
|
|
694
|
+
const objectName = shift(ctx.args, "object name");
|
|
695
|
+
const dataRaw = getDataFlag(ctx.args);
|
|
696
|
+
const idempotencyKey = getFlag(ctx.args, "--idempotency-key");
|
|
697
|
+
const rows = parseCreateManyRows(
|
|
698
|
+
await readJsonArrayInput({
|
|
699
|
+
args: ctx.args,
|
|
700
|
+
command: "crm entries create-many",
|
|
701
|
+
inlineFlag: "--data",
|
|
702
|
+
inlineValue: dataRaw,
|
|
703
|
+
}),
|
|
704
|
+
);
|
|
705
|
+
const chunks = await applyBatchOpsInChunks(
|
|
706
|
+
ctx,
|
|
707
|
+
rows.map((data) => ({
|
|
708
|
+
type: "entries.create",
|
|
709
|
+
objectName,
|
|
710
|
+
data,
|
|
711
|
+
})),
|
|
712
|
+
idempotencyKey,
|
|
713
|
+
);
|
|
714
|
+
out(ctx, { created: rows.length, chunks });
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
512
717
|
case "update": {
|
|
513
718
|
const objectName = shift(ctx.args, "object name");
|
|
514
719
|
const entryId = shift(ctx.args, "entry id");
|
|
@@ -535,6 +740,30 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
535
740
|
);
|
|
536
741
|
return;
|
|
537
742
|
}
|
|
743
|
+
case "update-many": {
|
|
744
|
+
const _objectName = shift(ctx.args, "object name");
|
|
745
|
+
const updatesRaw = getUpdatesFlag(ctx.args);
|
|
746
|
+
const idempotencyKey = getFlag(ctx.args, "--idempotency-key");
|
|
747
|
+
const updates = parseUpdateManyRows(
|
|
748
|
+
await readJsonArrayInput({
|
|
749
|
+
args: ctx.args,
|
|
750
|
+
command: "crm entries update-many",
|
|
751
|
+
inlineFlag: "--updates",
|
|
752
|
+
inlineValue: updatesRaw,
|
|
753
|
+
}),
|
|
754
|
+
);
|
|
755
|
+
const chunks = await applyBatchOpsInChunks(
|
|
756
|
+
ctx,
|
|
757
|
+
updates.map((update) => ({
|
|
758
|
+
type: "entries.update",
|
|
759
|
+
entryId: update.id,
|
|
760
|
+
data: update.data,
|
|
761
|
+
})),
|
|
762
|
+
idempotencyKey,
|
|
763
|
+
);
|
|
764
|
+
out(ctx, { updated: updates.length, chunks });
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
538
767
|
case "delete":
|
|
539
768
|
case "remove": {
|
|
540
769
|
const _objectName = shift(ctx.args, "object name");
|
|
@@ -569,11 +798,11 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
569
798
|
|
|
570
799
|
async function runCellsCommand(ctx: CrmCliContext): Promise<void> {
|
|
571
800
|
const verb = ctx.args.shift();
|
|
572
|
-
const objectName = shift(ctx.args, "object name");
|
|
573
|
-
const entryId = shift(ctx.args, "entry id");
|
|
574
|
-
const fieldName = shift(ctx.args, "field name");
|
|
575
801
|
switch (verb) {
|
|
576
802
|
case "get": {
|
|
803
|
+
const objectName = shift(ctx.args, "object name");
|
|
804
|
+
const entryId = shift(ctx.args, "entry id");
|
|
805
|
+
const fieldName = shift(ctx.args, "field name");
|
|
577
806
|
assertNoUnknownFlags(ctx.args, "crm cells get");
|
|
578
807
|
out(
|
|
579
808
|
ctx,
|
|
@@ -586,6 +815,9 @@ async function runCellsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
586
815
|
return;
|
|
587
816
|
}
|
|
588
817
|
case "set": {
|
|
818
|
+
const objectName = shift(ctx.args, "object name");
|
|
819
|
+
const entryId = shift(ctx.args, "entry id");
|
|
820
|
+
const fieldName = shift(ctx.args, "field name");
|
|
589
821
|
const value = parseJson(ctx.args.shift()) ?? null;
|
|
590
822
|
assertNoUnknownFlags(ctx.args, "crm cells set");
|
|
591
823
|
out(
|
|
@@ -599,7 +831,39 @@ async function runCellsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
599
831
|
);
|
|
600
832
|
return;
|
|
601
833
|
}
|
|
834
|
+
case "set-many": {
|
|
835
|
+
const _objectName = shift(ctx.args, "object name");
|
|
836
|
+
const updatesRaw = getUpdatesFlag(ctx.args);
|
|
837
|
+
const idempotencyKey = getFlag(ctx.args, "--idempotency-key");
|
|
838
|
+
const updates = parseCellSetManyRows(
|
|
839
|
+
await readJsonArrayInput({
|
|
840
|
+
args: ctx.args,
|
|
841
|
+
command: "crm cells set-many",
|
|
842
|
+
inlineFlag: "--updates",
|
|
843
|
+
inlineValue: updatesRaw,
|
|
844
|
+
}),
|
|
845
|
+
);
|
|
846
|
+
const grouped = new Map<string, JsonRecord>();
|
|
847
|
+
for (const update of updates) {
|
|
848
|
+
const data = grouped.get(update.entryId) ?? {};
|
|
849
|
+
data[update.field] = update.value;
|
|
850
|
+
grouped.set(update.entryId, data);
|
|
851
|
+
}
|
|
852
|
+
const ops: CrmBatchOp[] = Array.from(grouped.entries()).map(
|
|
853
|
+
([entryId, data]) => ({
|
|
854
|
+
type: "entries.update",
|
|
855
|
+
entryId,
|
|
856
|
+
data,
|
|
857
|
+
}),
|
|
858
|
+
);
|
|
859
|
+
const chunks = await applyBatchOpsInChunks(ctx, ops, idempotencyKey);
|
|
860
|
+
out(ctx, { updated: updates.length, entriesUpdated: ops.length, chunks });
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
602
863
|
case "append": {
|
|
864
|
+
const objectName = shift(ctx.args, "object name");
|
|
865
|
+
const entryId = shift(ctx.args, "entry id");
|
|
866
|
+
const fieldName = shift(ctx.args, "field name");
|
|
603
867
|
const value = parseJson(ctx.args.shift()) ?? null;
|
|
604
868
|
assertNoUnknownFlags(ctx.args, "crm cells append");
|
|
605
869
|
out(
|
|
@@ -1431,8 +1695,9 @@ Fields (columns):
|
|
|
1431
1695
|
dench crm fields create <object> <name> --type text|email|phone|url|number|boolean|date|enum|relation|user|tags|action|file|richtext \\
|
|
1432
1696
|
[--required] [--description ...] [--enum-values JSON] [--enum-colors JSON] \\
|
|
1433
1697
|
[--enum-multiple] [--related-object <name>] [--relationship many_to_one|many_to_many] \\
|
|
1434
|
-
[--indexed | --no-indexed]
|
|
1435
|
-
dench crm fields update <object> <field> [--description ...] [--required] [--enum-values JSON] [--indexed | --no-indexed]
|
|
1698
|
+
[--indexed | --no-indexed] [--enrichment-category people|company --apollo-path <path> --input-field <field>]
|
|
1699
|
+
dench crm fields update <object> <field> [--description ...] [--required] [--enum-values JSON] [--indexed | --no-indexed] \\
|
|
1700
|
+
[--enrichment-category people|company --apollo-path <path> --input-field <field> | --clear-enrichment]
|
|
1436
1701
|
dench crm fields delete <object> <field>
|
|
1437
1702
|
dench crm fields reorder <object> <field1> <field2> <field3> ...
|
|
1438
1703
|
|
|
@@ -1440,7 +1705,9 @@ Entries (rows):
|
|
|
1440
1705
|
dench crm entries list <object> [--limit N] [--json]
|
|
1441
1706
|
dench crm entries get <object> <entryId>
|
|
1442
1707
|
dench crm entries create <object> --data '{"Field":"Value",...}' (alias: --fields)
|
|
1708
|
+
dench crm entries create-many <object> --data '[{"Field":"Value",...}]' [--file rows.json] [--idempotency-key <key>]
|
|
1443
1709
|
dench crm entries update <object> <entryId> --data '{...}' (alias: --fields)
|
|
1710
|
+
dench crm entries update-many <object> --updates '[{"id":"...","data":{...}}]' [--file updates.json] [--idempotency-key <key>]
|
|
1444
1711
|
dench crm entries delete <object> <entryId>
|
|
1445
1712
|
dench crm entries bulk-delete <object> --ids id1,id2,id3
|
|
1446
1713
|
Note: --data is REQUIRED on create/update and must include >=1 field.
|
|
@@ -1450,6 +1717,7 @@ Entries (rows):
|
|
|
1450
1717
|
Cells (single-field updates):
|
|
1451
1718
|
dench crm cells get <object> <entryId> <field>
|
|
1452
1719
|
dench crm cells set <object> <entryId> <field> '<JSON value>'
|
|
1720
|
+
dench crm cells set-many <object> --updates '[{"entryId":"...","field":"Status","value":"Done"}]' [--file updates.json] [--idempotency-key <key>]
|
|
1453
1721
|
dench crm cells append <object> <entryId> <field> '<JSON value>'
|
|
1454
1722
|
|
|
1455
1723
|
Query DSL:
|
|
@@ -1467,6 +1735,9 @@ Statuses (kanban columns):
|
|
|
1467
1735
|
|
|
1468
1736
|
Batch:
|
|
1469
1737
|
dench crm batch --file ops.jsonl [--idempotency-key <key>]
|
|
1738
|
+
For repeated writes, prefer entries create-many/update-many or cells set-many;
|
|
1739
|
+
for transformations, write a script in /tmp/work/<runId>/ that loops locally
|
|
1740
|
+
and calls a batch surface in chunks.
|
|
1470
1741
|
|
|
1471
1742
|
Import / export:
|
|
1472
1743
|
dench crm import <object> --csv leads.csv [--map '{"Email":"Email Address"}'] [--on-conflict skip|update|error]
|