@dench.com/cli 3.6.3-staging.372 → 3.6.5-staging.384
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 +16 -0
- package/agent-config.ts +10 -16
- package/app-sdk.d.ts +234 -6
- package/crm.ts +62 -16
- package/dench.ts +70 -36
- package/email.ts +207 -2
- package/lib/api-schemas.ts +127 -9
- package/lib/app-sdk-catalog.generated.ts +17 -0
- package/lib/app-sdk-surface.ts +49 -0
- package/lib/command-registry.ts +115 -3
- package/lib/enrichment-gateway.ts +12 -182
- package/lib/enrichment-shapes.ts +182 -0
- package/lib/native-providers.ts +44 -0
- package/package.json +1 -1
- package/tools.ts +106 -61
package/README.md
CHANGED
|
@@ -465,3 +465,19 @@ Commands that need to impersonate a specific agent (`dench memory`,
|
|
|
465
465
|
(`dench signin`) — the unified API key is org-scoped and cannot speak
|
|
466
466
|
as a single agent. Those commands return a clear "agent session
|
|
467
467
|
required" error in API-key mode.
|
|
468
|
+
|
|
469
|
+
### Focused workspace context
|
|
470
|
+
|
|
471
|
+
`dench context --instructions` reads explicit IDENTITY, RULES and TOOLS notes,
|
|
472
|
+
plus document availability/revisions. It does not load business profiles,
|
|
473
|
+
personal profiles or aggregate memory. Fetch only what the task needs:
|
|
474
|
+
|
|
475
|
+
```sh
|
|
476
|
+
dench context --sections organisation,user,memory --json
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Section names are `identity`, `organisation`, `user`, `memory`, `rules`, and
|
|
480
|
+
`tools`. Multiple names use one query. The existing `organisation show`,
|
|
481
|
+
`user show`, and `mem show` commands use the same saved documents; reading never
|
|
482
|
+
fills profiles or writes memory. Personal USER context is unavailable to shared
|
|
483
|
+
channel sessions. `dench context --json` keeps its existing workspace overview.
|
package/agent-config.ts
CHANGED
|
@@ -215,22 +215,16 @@ async function handleSimpleMagicFile(
|
|
|
215
215
|
): Promise<void> {
|
|
216
216
|
const sub = args[0];
|
|
217
217
|
if (!sub || sub === "show") {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
: null;
|
|
229
|
-
if (opts.perMember) {
|
|
230
|
-
out(ctx, config.userProfile?.content ?? "(empty)");
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
out(ctx, value?.content ?? "(empty)");
|
|
218
|
+
const context = (await runQuery(
|
|
219
|
+
ctx,
|
|
220
|
+
makeFunctionReference<"query">("functions/agentConfig:workspaceContext"),
|
|
221
|
+
{ sections: [opts.label] },
|
|
222
|
+
)) as { documents: Array<{ name: string; content?: string }> };
|
|
223
|
+
out(
|
|
224
|
+
ctx,
|
|
225
|
+
context.documents.find((document) => document.name === opts.label)
|
|
226
|
+
?.content || "(empty)",
|
|
227
|
+
);
|
|
234
228
|
return;
|
|
235
229
|
}
|
|
236
230
|
if (sub === "edit" || sub === "set") {
|
package/app-sdk.d.ts
CHANGED
|
@@ -497,10 +497,17 @@ export interface ApiRequests {
|
|
|
497
497
|
organizationId?: string;
|
|
498
498
|
newOrganizationName?: string;
|
|
499
499
|
};
|
|
500
|
+
/** Read selected scoped workspace documents and a compact context index without writes. — `dench context --sections` — GET /api/v1/context/sections */
|
|
501
|
+
"workspace.context.sections": {
|
|
502
|
+
organizationId?: string;
|
|
503
|
+
sections?: Array<"identity" | "organisation" | "user" | "memory" | "rules" | "tools">;
|
|
504
|
+
};
|
|
500
505
|
/** Return the current Dench workspace context for the bearer. — `dench context` — GET /api/v1/context */
|
|
501
506
|
"workspace.context": Record<string, unknown>;
|
|
502
507
|
/** Return the current workspace's standing instructions for external agents. — `dench context --instructions` — GET /api/v1/context/instructions */
|
|
503
|
-
"workspace.instructions":
|
|
508
|
+
"workspace.instructions": {
|
|
509
|
+
organizationId?: string;
|
|
510
|
+
};
|
|
504
511
|
/** Return workspace and agent status. — `dench status` — GET /api/v1/status */
|
|
505
512
|
"workspace.status": Record<string, unknown>;
|
|
506
513
|
/** List agents visible in the workspace status payload. — `dench agents` — GET /api/v1/agents */
|
|
@@ -2172,6 +2179,116 @@ export interface ApiRequests {
|
|
|
2172
2179
|
/** Identity id, sender email, or domain (deletes every sender on the domain). Permanent: re-adding requires re-verification. */
|
|
2173
2180
|
identity: string;
|
|
2174
2181
|
};
|
|
2182
|
+
/** Create an editable Dench email draft and synchronize its selected native mailbox asynchronously. Use a stable requestId for retries. — `dench email draft create` — POST /api/v1/email/drafts */
|
|
2183
|
+
"email.drafts.create": {
|
|
2184
|
+
organizationId?: string;
|
|
2185
|
+
threadId?: string;
|
|
2186
|
+
mailbox?: string;
|
|
2187
|
+
requestId: string;
|
|
2188
|
+
to?: {
|
|
2189
|
+
email: string;
|
|
2190
|
+
name?: string;
|
|
2191
|
+
}[];
|
|
2192
|
+
cc?: {
|
|
2193
|
+
email: string;
|
|
2194
|
+
name?: string;
|
|
2195
|
+
}[];
|
|
2196
|
+
bcc?: {
|
|
2197
|
+
email: string;
|
|
2198
|
+
name?: string;
|
|
2199
|
+
}[];
|
|
2200
|
+
subject?: string;
|
|
2201
|
+
htmlBody?: string;
|
|
2202
|
+
textBody?: string;
|
|
2203
|
+
attachments?: {
|
|
2204
|
+
storageId: string;
|
|
2205
|
+
name: string;
|
|
2206
|
+
contentType: string;
|
|
2207
|
+
size: number;
|
|
2208
|
+
}[];
|
|
2209
|
+
};
|
|
2210
|
+
/** Read an email draft, eligible senders and provider sync status. — `dench email draft show` — GET /api/v1/email/drafts/{draftId} */
|
|
2211
|
+
"email.drafts.get": {
|
|
2212
|
+
draftId: string;
|
|
2213
|
+
};
|
|
2214
|
+
/** List native mailboxes this caller may use. — `dench email draft senders` — GET /api/v1/email/drafts/senders */
|
|
2215
|
+
"email.drafts.senders": {
|
|
2216
|
+
organizationId?: string;
|
|
2217
|
+
};
|
|
2218
|
+
/** Save draft content using expectedRevision; sender changes require a new draft after provider binding. — `dench email draft update` — PATCH /api/v1/email/drafts/{draftId} */
|
|
2219
|
+
"email.drafts.update": {
|
|
2220
|
+
draftId: string;
|
|
2221
|
+
expectedRevision: number;
|
|
2222
|
+
patch: {
|
|
2223
|
+
mailboxAccountId?: string;
|
|
2224
|
+
to?: {
|
|
2225
|
+
email: string;
|
|
2226
|
+
name?: string;
|
|
2227
|
+
}[];
|
|
2228
|
+
cc?: {
|
|
2229
|
+
email: string;
|
|
2230
|
+
name?: string;
|
|
2231
|
+
}[];
|
|
2232
|
+
bcc?: {
|
|
2233
|
+
email: string;
|
|
2234
|
+
name?: string;
|
|
2235
|
+
}[];
|
|
2236
|
+
subject?: string;
|
|
2237
|
+
htmlBody?: string;
|
|
2238
|
+
textBody?: string;
|
|
2239
|
+
attachments?: {
|
|
2240
|
+
storageId: string;
|
|
2241
|
+
name: string;
|
|
2242
|
+
contentType: string;
|
|
2243
|
+
size: number;
|
|
2244
|
+
}[];
|
|
2245
|
+
};
|
|
2246
|
+
};
|
|
2247
|
+
/** Send the current Dench draft revision through its native mailbox. Repeated requests share a durable receipt. — `dench email draft send` — POST /api/v1/email/drafts/{draftId}/send */
|
|
2248
|
+
"email.drafts.send": {
|
|
2249
|
+
draftId: string;
|
|
2250
|
+
expectedRevision: number;
|
|
2251
|
+
requestId?: string;
|
|
2252
|
+
};
|
|
2253
|
+
/** Upload attachment bytes authorized against this draft before adding its storage reference. — `dench email draft attach` — POST /api/v1/email/drafts/{draftId}/attachments */
|
|
2254
|
+
"email.drafts.upload": {
|
|
2255
|
+
draftId: string;
|
|
2256
|
+
name: string;
|
|
2257
|
+
contentType: string;
|
|
2258
|
+
contentBase64: string;
|
|
2259
|
+
};
|
|
2260
|
+
/** Load the selected mailbox signature for explicit visible insertion. — `dench email draft signature` — GET /api/v1/email/drafts/{draftId}/signature */
|
|
2261
|
+
"email.drafts.signature": {
|
|
2262
|
+
draftId: string;
|
|
2263
|
+
};
|
|
2264
|
+
/** Send through a native Gmail or Outlook mailbox with a durable request receipt. Only use for an explicit send request. — `dench email send --mailbox` — POST /api/v1/email/native/send */
|
|
2265
|
+
"email.native.send": {
|
|
2266
|
+
organizationId?: string;
|
|
2267
|
+
threadId?: string;
|
|
2268
|
+
mailbox?: string;
|
|
2269
|
+
requestId: string;
|
|
2270
|
+
to?: {
|
|
2271
|
+
email: string;
|
|
2272
|
+
name?: string;
|
|
2273
|
+
}[];
|
|
2274
|
+
cc?: {
|
|
2275
|
+
email: string;
|
|
2276
|
+
name?: string;
|
|
2277
|
+
}[];
|
|
2278
|
+
bcc?: {
|
|
2279
|
+
email: string;
|
|
2280
|
+
name?: string;
|
|
2281
|
+
}[];
|
|
2282
|
+
subject?: string;
|
|
2283
|
+
htmlBody?: string;
|
|
2284
|
+
textBody?: string;
|
|
2285
|
+
attachments?: {
|
|
2286
|
+
storageId: string;
|
|
2287
|
+
name: string;
|
|
2288
|
+
contentType: string;
|
|
2289
|
+
size: number;
|
|
2290
|
+
}[];
|
|
2291
|
+
};
|
|
2175
2292
|
/** Send one raw email (no campaign): to/cc/bcc, custom from-name and reply-to, optional reply threading via replyToMessageId. — `dench email send` — POST /api/v1/email/send */
|
|
2176
2293
|
"email.send": {
|
|
2177
2294
|
/** Verified sender mailbox. */
|
|
@@ -3017,17 +3134,25 @@ export interface ApiRequests {
|
|
|
3017
3134
|
/** Create a tool OAuth connection link. Slack is in-house, never a third-party connection: for toolkit slack the CLI prints the first-party install URL (/api/slack/install?slug=<workspace>, org admin) instead of calling this endpoint. — `dench tool connect` — POST /api/v1/tools/connections */
|
|
3018
3135
|
"tool.connect": {
|
|
3019
3136
|
/** Toolkit slug, e.g. github, gmail, stripe. */
|
|
3020
|
-
toolkit
|
|
3137
|
+
toolkit?: string;
|
|
3138
|
+
toolkit_slug?: string;
|
|
3021
3139
|
/** Optional OAuth callback URL. */
|
|
3022
3140
|
callback_url?: string;
|
|
3141
|
+
/** Native OAuth return path within the workspace. */
|
|
3142
|
+
returnTo?: string;
|
|
3023
3143
|
};
|
|
3024
3144
|
/** Search for external tool actions. — `dench tool search` — POST /api/v1/tools/search */
|
|
3025
3145
|
"tool.search": {
|
|
3026
|
-
queries
|
|
3146
|
+
queries?: {
|
|
3027
3147
|
/** Natural-language use case. */
|
|
3028
3148
|
use_case: string;
|
|
3029
3149
|
known_fields?: string;
|
|
3030
3150
|
}[];
|
|
3151
|
+
query?: string;
|
|
3152
|
+
toolkit?: string;
|
|
3153
|
+
toolkit_slug?: string;
|
|
3154
|
+
limit?: number;
|
|
3155
|
+
important?: boolean;
|
|
3031
3156
|
session?: {
|
|
3032
3157
|
id?: string;
|
|
3033
3158
|
generate_id?: boolean;
|
|
@@ -3045,6 +3170,8 @@ export interface ApiRequests {
|
|
|
3045
3170
|
/** Tool arguments object. */
|
|
3046
3171
|
arguments?: Record<string, unknown>;
|
|
3047
3172
|
account?: string;
|
|
3173
|
+
connected_account_id?: string;
|
|
3174
|
+
approvalId?: string;
|
|
3048
3175
|
};
|
|
3049
3176
|
/** Disconnect an external tool account. — `dench tool disconnect` — DELETE /api/v1/tools/connections/{connectionId} */
|
|
3050
3177
|
"tool.disconnect": {
|
|
@@ -3374,6 +3501,7 @@ export interface ApiResponses {
|
|
|
3374
3501
|
sessionExpiresAt?: number;
|
|
3375
3502
|
};
|
|
3376
3503
|
};
|
|
3504
|
+
"workspace.context.sections": Record<string, unknown>;
|
|
3377
3505
|
"workspace.context": {
|
|
3378
3506
|
workspace: {
|
|
3379
3507
|
id: string;
|
|
@@ -5319,6 +5447,54 @@ export interface ApiResponses {
|
|
|
5319
5447
|
disabled: boolean;
|
|
5320
5448
|
}[];
|
|
5321
5449
|
};
|
|
5450
|
+
"email.drafts.create": {
|
|
5451
|
+
ok: boolean;
|
|
5452
|
+
kind: "email";
|
|
5453
|
+
draftId: string;
|
|
5454
|
+
syncStatus: string;
|
|
5455
|
+
revision: number;
|
|
5456
|
+
[key: string]: unknown;
|
|
5457
|
+
};
|
|
5458
|
+
"email.drafts.get": {
|
|
5459
|
+
ok: boolean;
|
|
5460
|
+
kind: "email";
|
|
5461
|
+
draftId: string;
|
|
5462
|
+
syncStatus: string;
|
|
5463
|
+
revision: number;
|
|
5464
|
+
[key: string]: unknown;
|
|
5465
|
+
};
|
|
5466
|
+
"email.drafts.senders": Record<string, unknown>[];
|
|
5467
|
+
"email.drafts.update": {
|
|
5468
|
+
ok: boolean;
|
|
5469
|
+
kind: "email";
|
|
5470
|
+
draftId: string;
|
|
5471
|
+
syncStatus: string;
|
|
5472
|
+
revision: number;
|
|
5473
|
+
[key: string]: unknown;
|
|
5474
|
+
};
|
|
5475
|
+
"email.drafts.send": {
|
|
5476
|
+
ok: boolean;
|
|
5477
|
+
kind: "email";
|
|
5478
|
+
draftId: string;
|
|
5479
|
+
syncStatus: string;
|
|
5480
|
+
revision: number;
|
|
5481
|
+
[key: string]: unknown;
|
|
5482
|
+
};
|
|
5483
|
+
"email.drafts.upload": {
|
|
5484
|
+
storageId: string;
|
|
5485
|
+
name: string;
|
|
5486
|
+
contentType: string;
|
|
5487
|
+
size: number;
|
|
5488
|
+
};
|
|
5489
|
+
"email.drafts.signature": Record<string, unknown>;
|
|
5490
|
+
"email.native.send": {
|
|
5491
|
+
ok: boolean;
|
|
5492
|
+
kind: "email";
|
|
5493
|
+
draftId: string;
|
|
5494
|
+
syncStatus: string;
|
|
5495
|
+
revision: number;
|
|
5496
|
+
[key: string]: unknown;
|
|
5497
|
+
};
|
|
5322
5498
|
"email.send": {
|
|
5323
5499
|
ok: boolean;
|
|
5324
5500
|
/** Dench message id for `dench email message status`. */
|
|
@@ -6567,6 +6743,14 @@ export interface ApiResponses {
|
|
|
6567
6743
|
/** OAuth URL the user opens to finish connecting. */
|
|
6568
6744
|
redirect_url?: string;
|
|
6569
6745
|
connection_id?: string;
|
|
6746
|
+
redirectUrl?: string;
|
|
6747
|
+
link?: string;
|
|
6748
|
+
artifact?: {
|
|
6749
|
+
kind: "connect";
|
|
6750
|
+
data: {
|
|
6751
|
+
toolkit: string;
|
|
6752
|
+
};
|
|
6753
|
+
};
|
|
6570
6754
|
[key: string]: unknown;
|
|
6571
6755
|
};
|
|
6572
6756
|
"tool.search": {
|
|
@@ -6592,10 +6776,13 @@ export interface ApiResponses {
|
|
|
6592
6776
|
/** Tool slug -> schema with gateway-added execution_ref for tool.run. */
|
|
6593
6777
|
tool_schemas?: Record<string, Record<string, unknown>>;
|
|
6594
6778
|
/** Tool-router session; reuse session.id for follow-up calls. */
|
|
6595
|
-
session
|
|
6779
|
+
session?: {
|
|
6596
6780
|
id: string;
|
|
6597
6781
|
[key: string]: unknown;
|
|
6598
6782
|
};
|
|
6783
|
+
items?: Record<string, unknown>[];
|
|
6784
|
+
nativeAvailable?: boolean;
|
|
6785
|
+
available?: boolean;
|
|
6599
6786
|
execution_ref_version?: number;
|
|
6600
6787
|
[key: string]: unknown;
|
|
6601
6788
|
};
|
|
@@ -7810,6 +7997,22 @@ export type DenchApiTree = {
|
|
|
7810
7997
|
/** Submit a campaign for approval or sending. (`dench email campaign submit`) */
|
|
7811
7998
|
submit(args: Req<"email.campaign.submit">): Promise<Res<"email.campaign.submit">>;
|
|
7812
7999
|
};
|
|
8000
|
+
drafts: {
|
|
8001
|
+
/** Create an editable Dench email draft and synchronize its selected native mailbox asynchronously. Use a stable requestId for retries. (`dench email draft create`) */
|
|
8002
|
+
create(args: Req<"email.drafts.create">): Promise<Res<"email.drafts.create">>;
|
|
8003
|
+
/** Read an email draft, eligible senders and provider sync status. (`dench email draft show`) */
|
|
8004
|
+
get(args: Req<"email.drafts.get">): Promise<Res<"email.drafts.get">>;
|
|
8005
|
+
/** Send the current Dench draft revision through its native mailbox. Repeated requests share a durable receipt. (`dench email draft send`) */
|
|
8006
|
+
send(args: Req<"email.drafts.send">): Promise<Res<"email.drafts.send">>;
|
|
8007
|
+
/** List native mailboxes this caller may use. (`dench email draft senders`) */
|
|
8008
|
+
senders(args?: Req<"email.drafts.senders">): Promise<Res<"email.drafts.senders">>;
|
|
8009
|
+
/** Load the selected mailbox signature for explicit visible insertion. (`dench email draft signature`) */
|
|
8010
|
+
signature(args: Req<"email.drafts.signature">): Promise<Res<"email.drafts.signature">>;
|
|
8011
|
+
/** Save draft content using expectedRevision; sender changes require a new draft after provider binding. (`dench email draft update`) */
|
|
8012
|
+
update(args: Req<"email.drafts.update">): Promise<Res<"email.drafts.update">>;
|
|
8013
|
+
/** Upload attachment bytes authorized against this draft before adding its storage reference. (`dench email draft attach`) */
|
|
8014
|
+
upload(args: Req<"email.drafts.upload">): Promise<Res<"email.drafts.upload">>;
|
|
8015
|
+
};
|
|
7813
8016
|
identity: {
|
|
7814
8017
|
/** Begin the full Dench sender verification flow for a sending identity. (`dench email identity verify`) */
|
|
7815
8018
|
beginVerification(args: Req<"email.identity.beginVerification">): Promise<Res<"email.identity.beginVerification">>;
|
|
@@ -7838,6 +8041,10 @@ export type DenchApiTree = {
|
|
|
7838
8041
|
/** List recent raw email messages for the workspace. (`dench email message list`) */
|
|
7839
8042
|
list(args?: Req<"email.message.list">): Promise<Res<"email.message.list">>;
|
|
7840
8043
|
};
|
|
8044
|
+
native: {
|
|
8045
|
+
/** Send through a native Gmail or Outlook mailbox with a durable request receipt. Only use for an explicit send request. (`dench email send --mailbox`) */
|
|
8046
|
+
send(args: Req<"email.native.send">): Promise<Res<"email.native.send">>;
|
|
8047
|
+
};
|
|
7841
8048
|
/** Send one raw email (no campaign): to/cc/bcc, custom from-name and reply-to, optional reply threading via replyToMessageId. (`dench email send`) */
|
|
7842
8049
|
send(args: Req<"email.send">): Promise<Res<"email.send">>;
|
|
7843
8050
|
sequence: {
|
|
@@ -8126,8 +8333,13 @@ export type DenchApiTree = {
|
|
|
8126
8333
|
/** List artifacts for the authenticated workspace. (`dench artifacts`) */
|
|
8127
8334
|
list(args?: Req<"workspace.artifacts.list">): Promise<Res<"workspace.artifacts.list">>;
|
|
8128
8335
|
};
|
|
8129
|
-
|
|
8130
|
-
|
|
8336
|
+
context: {
|
|
8337
|
+
/** Return the current Dench workspace context for the bearer. (`dench context`) */
|
|
8338
|
+
(args?: Req<"workspace.context">): Promise<Res<"workspace.context">>;
|
|
8339
|
+
$call(args?: Req<"workspace.context">): Promise<Res<"workspace.context">>;
|
|
8340
|
+
/** Read selected scoped workspace documents and a compact context index without writes. (`dench context --sections`) */
|
|
8341
|
+
sections(args?: Req<"workspace.context.sections">): Promise<Res<"workspace.context.sections">>;
|
|
8342
|
+
};
|
|
8131
8343
|
/** Return the current workspace's standing instructions for external agents. (`dench context --instructions`) */
|
|
8132
8344
|
instructions(args?: Req<"workspace.instructions">): Promise<Res<"workspace.instructions">>;
|
|
8133
8345
|
/** Return workspace and agent status. (`dench status`) */
|
|
@@ -8555,6 +8767,22 @@ export interface DenchCampaigns {
|
|
|
8555
8767
|
}
|
|
8556
8768
|
|
|
8557
8769
|
export interface DenchEmail {
|
|
8770
|
+
/** Create a native mailbox draft with a stable request receipt. Permission: `email:write`. Wraps `email.drafts.create`. */
|
|
8771
|
+
createDraft(draft: Req<"email.drafts.create">): Promise<Res<"email.drafts.create">>;
|
|
8772
|
+
/** Read a native email draft and sync status. Permission: `email:read`. Wraps `email.drafts.get`. */
|
|
8773
|
+
draft(draftId: string): Promise<Res<"email.drafts.get">>;
|
|
8774
|
+
/** Save the next email draft revision. Permission: `email:write`. Wraps `email.drafts.update`. */
|
|
8775
|
+
updateDraft(input: Req<"email.drafts.update">): Promise<Res<"email.drafts.update">>;
|
|
8776
|
+
/** Send the frozen native email draft revision. Permission: `email:write`. Wraps `email.drafts.send`. */
|
|
8777
|
+
sendDraft(input: Req<"email.drafts.send">): Promise<Res<"email.drafts.send">>;
|
|
8778
|
+
/** List eligible native mailboxes. Permission: `email:read`. Wraps `email.drafts.senders`. */
|
|
8779
|
+
senders(opts?: Req<"email.drafts.senders">): Promise<Res<"email.drafts.senders">>;
|
|
8780
|
+
/** Upload attachment bytes for a native draft. Permission: `email:write`. Wraps `email.drafts.upload`. */
|
|
8781
|
+
uploadAttachment(input: Req<"email.drafts.upload">): Promise<Res<"email.drafts.upload">>;
|
|
8782
|
+
/** Load the selected mailbox signature for visible insertion. Permission: `email:read`. Wraps `email.drafts.signature`. */
|
|
8783
|
+
signature(draftId: string): Promise<Res<"email.drafts.signature">>;
|
|
8784
|
+
/** Explicitly send through a native Gmail or Outlook mailbox. Permission: `email:write`. Wraps `email.native.send`. */
|
|
8785
|
+
sendFromMailbox(message: Req<"email.native.send">): Promise<Res<"email.native.send">>;
|
|
8558
8786
|
/** Send one email from a verified identity. Permission: `email:write`. Wraps `email.send`. */
|
|
8559
8787
|
send(message: Req<"email.send">): Promise<Res<"email.send">>;
|
|
8560
8788
|
/** Sent messages. Permission: `email:read`. Wraps `email.message.list`. */
|
package/crm.ts
CHANGED
|
@@ -450,6 +450,13 @@ function parseCreateManyRows(items: unknown[]): JsonRecord[] {
|
|
|
450
450
|
);
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
function parseDedupeFieldFlag(raw: string | undefined): string | undefined {
|
|
454
|
+
if (raw !== undefined && !raw.trim()) {
|
|
455
|
+
throw new CrmCliError("--dedupe-key must name a non-empty CRM field");
|
|
456
|
+
}
|
|
457
|
+
return raw;
|
|
458
|
+
}
|
|
459
|
+
|
|
453
460
|
/**
|
|
454
461
|
* Stable per-row dedupe key derived from a named column. Values are
|
|
455
462
|
* normalized (String → trim → lowercase) so `" Jane@X.com"` and
|
|
@@ -464,6 +471,16 @@ function dedupeKeyFromColumn(opts: {
|
|
|
464
471
|
command: string;
|
|
465
472
|
}): string {
|
|
466
473
|
const raw = opts.row[opts.column];
|
|
474
|
+
if (
|
|
475
|
+
raw !== null &&
|
|
476
|
+
raw !== undefined &&
|
|
477
|
+
typeof raw !== "string" &&
|
|
478
|
+
(typeof raw !== "number" || !Number.isFinite(raw))
|
|
479
|
+
) {
|
|
480
|
+
throw new CrmCliError(
|
|
481
|
+
`${opts.command}: row ${opts.rowIndex + 1} must have a string or finite number for --dedupe-key column "${opts.column}"`,
|
|
482
|
+
);
|
|
483
|
+
}
|
|
467
484
|
const normalized =
|
|
468
485
|
raw === null || raw === undefined ? "" : String(raw).trim().toLowerCase();
|
|
469
486
|
if (!normalized) {
|
|
@@ -571,6 +588,8 @@ export type CrmBatchOp =
|
|
|
571
588
|
data: JsonRecord;
|
|
572
589
|
/** Stable per-row dedupe key (server upserts on conflict). */
|
|
573
590
|
seedKey?: string;
|
|
591
|
+
/** Match existing CRM entries by this scalar field before writing. */
|
|
592
|
+
matchField?: string;
|
|
574
593
|
/** 1-based source row for server-side error messages. */
|
|
575
594
|
rowIndex?: number;
|
|
576
595
|
}
|
|
@@ -2638,10 +2657,18 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2638
2657
|
const objectName = shift(ctx.args, "object name");
|
|
2639
2658
|
const dataRaw = getDataFlag(ctx.args);
|
|
2640
2659
|
const idempotencyKey = getFlag(ctx.args, "--idempotency-key");
|
|
2641
|
-
//
|
|
2642
|
-
//
|
|
2643
|
-
|
|
2644
|
-
|
|
2660
|
+
// Match ordinary existing records by this field, retaining the
|
|
2661
|
+
// stable seedKey for retry compatibility with earlier CLI versions.
|
|
2662
|
+
const dedupeColumn = parseDedupeFieldFlag(
|
|
2663
|
+
getFlag(ctx.args, "--dedupe-key"),
|
|
2664
|
+
);
|
|
2665
|
+
const onConflictFlag = getFlag(ctx.args, "--on-conflict");
|
|
2666
|
+
const onConflict = parseOnConflictFlag(onConflictFlag);
|
|
2667
|
+
if (onConflictFlag !== undefined && !dedupeColumn) {
|
|
2668
|
+
throw new CrmCliError(
|
|
2669
|
+
"crm entries create-many: --on-conflict requires --dedupe-key <field>",
|
|
2670
|
+
);
|
|
2671
|
+
}
|
|
2645
2672
|
const allowUnknown = hasFlag(ctx.args, "--allow-unknown");
|
|
2646
2673
|
const rows = parseCreateManyRows(
|
|
2647
2674
|
await readJsonArrayInput({
|
|
@@ -2670,12 +2697,16 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2670
2697
|
objectName,
|
|
2671
2698
|
data,
|
|
2672
2699
|
...(seedKeys
|
|
2673
|
-
? {
|
|
2700
|
+
? {
|
|
2701
|
+
seedKey: seedKeys[index],
|
|
2702
|
+
matchField: dedupeColumn,
|
|
2703
|
+
rowIndex: index + 1,
|
|
2704
|
+
}
|
|
2674
2705
|
: {}),
|
|
2675
2706
|
})),
|
|
2676
2707
|
idempotencyKey,
|
|
2677
2708
|
allowUnknown,
|
|
2678
|
-
seedKeys ?
|
|
2709
|
+
seedKeys ? onConflict : undefined,
|
|
2679
2710
|
);
|
|
2680
2711
|
// Only claim per-row links when we actually got a result per row. A
|
|
2681
2712
|
// silently-empty `results` is indistinguishable from "this object has
|
|
@@ -2687,7 +2718,10 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2687
2718
|
out(ctx, {
|
|
2688
2719
|
created: statusCounts ? statusCounts.created : rows.length,
|
|
2689
2720
|
...(statusCounts
|
|
2690
|
-
? {
|
|
2721
|
+
? {
|
|
2722
|
+
alreadyExisted: statusCounts.alreadyExisted,
|
|
2723
|
+
updated: statusCounts.updated,
|
|
2724
|
+
}
|
|
2691
2725
|
: {}),
|
|
2692
2726
|
chunks,
|
|
2693
2727
|
...(linked > 0
|
|
@@ -2788,6 +2822,11 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2788
2822
|
const _objectName = shift(ctx.args, "object name");
|
|
2789
2823
|
const entryId = shift(ctx.args, "entry id");
|
|
2790
2824
|
assertNoUnknownFlags(ctx.args, "crm entries delete");
|
|
2825
|
+
if (ctx.args.length > 0) {
|
|
2826
|
+
throw new CrmCliError(
|
|
2827
|
+
"crm entries delete accepts exactly one entry ID. For multiple entries, use dench crm entries bulk-delete <object> --ids id1,id2,... or crm batch with entries.delete operations.",
|
|
2828
|
+
);
|
|
2829
|
+
}
|
|
2791
2830
|
out(
|
|
2792
2831
|
ctx,
|
|
2793
2832
|
await callMutation(ctx, api.entries.remove, {
|
|
@@ -3519,7 +3558,7 @@ async function runImportCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
3519
3558
|
const csvPath = getFlag(ctx.args, "--csv");
|
|
3520
3559
|
const jsonlPath = getFlag(ctx.args, "--jsonl");
|
|
3521
3560
|
const mapJson = getFlag(ctx.args, "--map");
|
|
3522
|
-
const dedupeColumn = getFlag(ctx.args, "--dedupe-key");
|
|
3561
|
+
const dedupeColumn = parseDedupeFieldFlag(getFlag(ctx.args, "--dedupe-key"));
|
|
3523
3562
|
const onConflict = parseOnConflictFlag(getFlag(ctx.args, "--on-conflict"));
|
|
3524
3563
|
assertNoUnknownFlags(ctx.args, "crm import");
|
|
3525
3564
|
if (!csvPath && !jsonlPath) {
|
|
@@ -3557,10 +3596,11 @@ async function runImportCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
3557
3596
|
dedupeColumn,
|
|
3558
3597
|
sha256,
|
|
3559
3598
|
}),
|
|
3599
|
+
...(dedupeColumn ? { matchField: dedupeColumn } : {}),
|
|
3560
3600
|
rowIndex: index + 1,
|
|
3561
3601
|
}));
|
|
3562
|
-
// batch.apply
|
|
3563
|
-
//
|
|
3602
|
+
// batch.apply matches the selected CRM field when supplied; otherwise
|
|
3603
|
+
// seedKey protects exact reruns. The shared helper handles chunking.
|
|
3564
3604
|
const { results } = await applyBatchOpsInChunks(
|
|
3565
3605
|
ctx,
|
|
3566
3606
|
ops,
|
|
@@ -4840,9 +4880,10 @@ Tasks (shortcuts over the protected 'task' object):
|
|
|
4840
4880
|
dench crm entries create <object> --data '{"Field":"Value",...}' (alias: --fields) [--allow-unknown] [--idempotency-key <key>]
|
|
4841
4881
|
--idempotency-key upserts on (object, key): a retried create returns
|
|
4842
4882
|
the existing row with alreadyExisted: true instead of duplicating it.
|
|
4843
|
-
dench crm entries create-many <object> --data '[{"Field":"Value",...}]' [--file rows.json] [--idempotency-key <key>] [--dedupe-key <
|
|
4844
|
-
--dedupe-key <
|
|
4845
|
-
|
|
4883
|
+
dench crm entries create-many <object> --data '[{"Field":"Value",...}]' [--file rows.json] [--idempotency-key <key>] [--dedupe-key <field>] [--on-conflict skip|update|error] [--allow-unknown]
|
|
4884
|
+
--dedupe-key <field> matches existing CRM rows by field value,
|
|
4885
|
+
including rows created outside imports. --on-conflict requires it;
|
|
4886
|
+
default skip leaves matches untouched, update merges, error aborts.
|
|
4846
4887
|
dench crm entries update <object> <entryId> --data '{...}' (alias: --fields) [--allow-unknown]
|
|
4847
4888
|
dench crm entries update-many <object> --updates '[{"id":"...","data":{...}}]' [--file updates.json] [--idempotency-key <key>] [--allow-unknown]
|
|
4848
4889
|
dench crm entries delete <object> <entryId>
|
|
@@ -4934,9 +4975,14 @@ Batch:
|
|
|
4934
4975
|
Import / export:
|
|
4935
4976
|
dench crm import <object> --csv leads.csv [--map '{"Email":"Email Address"}'] [--on-conflict skip|update|error] [--dedupe-key <column>]
|
|
4936
4977
|
dench crm import <object> --jsonl rows.jsonl
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4978
|
+
--dedupe-key names the CRM field AFTER --map. Values match existing
|
|
4979
|
+
rows after trim + lowercase; supported fields are text, email, phone,
|
|
4980
|
+
URL, single-select enum, or number. Blank/non-scalar values fail.
|
|
4981
|
+
Number fields compare numeric values ("01" matches 1).
|
|
4982
|
+
Multiple matches or an incomplete bounded lookup abort the current
|
|
4983
|
+
batch without writes; earlier batches may already have committed.
|
|
4984
|
+
Without --dedupe-key, a content-hash key protects exact reimports
|
|
4985
|
+
only; it does not match ordinary existing records by field value.
|
|
4940
4986
|
dench crm export <object> --format csv|jsonl [--limit N]
|
|
4941
4987
|
Pages with a real cursor and verifies no duplicate ids before
|
|
4942
4988
|
emitting; a summary (exportedCount/uniqueIds) is printed to stderr.
|