@dench.com/cli 2.1.2 → 2.1.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * `dench identity / organisation / user / heartbeat / bootstrap /
3
- * model / daily / mem aggregate` — CLI surface for the OpenClaw-
2
+ * `dench identity / organisation / user / bootstrap / model / daily /
3
+ * mem aggregate` — CLI surface for the OpenClaw-
4
4
  * parity self-updating harness.
5
5
  *
6
6
  * Mirrors the auth pattern in `cli/cron.ts`:
@@ -17,14 +17,12 @@
17
17
  * user show | edit
18
18
  * tools show | notes-edit
19
19
  * memory show | append <text> | set (MEMORY.md aggregate)
20
- * heartbeat status | enable | disable | interval <duration> |
21
- * instructions
22
20
  * bootstrap status | complete | reopen | template
23
21
  * model default <id> | model default clear |
24
22
  * model thread <threadId> <id> | model thread <threadId> clear
25
23
  * daily today | daily show <YYYY-MM-DD> | daily append <text>
26
24
  *
27
- * `edit` / `notes-edit` / `instructions` / `template` open the
25
+ * `edit` / `notes-edit` / `template` open the
28
26
  * user's `$EDITOR` (default `vi`) to capture the new body. The
29
27
  * stdin path is also supported via `--stdin`, useful in scripts.
30
28
  */
@@ -60,9 +58,6 @@ const api = {
60
58
  updateUserProfile: makeFunctionReference<"mutation">(
61
59
  "functions/agentConfig:updateUserProfile",
62
60
  ),
63
- updateHeartbeat: makeFunctionReference<"mutation">(
64
- "functions/agentConfig:updateHeartbeat",
65
- ),
66
61
  updateBootstrapTemplate: makeFunctionReference<"mutation">(
67
62
  "functions/agentConfig:updateBootstrapTemplate",
68
63
  ),
@@ -182,12 +177,6 @@ async function loadAgentConfig(ctx: CliCtx): Promise<{
182
177
  completedAt?: number;
183
178
  template?: string;
184
179
  };
185
- heartbeat: {
186
- enabled: boolean;
187
- intervalMs: number;
188
- instructions: string;
189
- lastFiredAt?: number;
190
- };
191
180
  };
192
181
  userProfile: { content: string; updatedAt: number } | null;
193
182
  organizationId: string;
@@ -206,12 +195,6 @@ async function loadAgentConfig(ctx: CliCtx): Promise<{
206
195
  completedAt?: number;
207
196
  template?: string;
208
197
  };
209
- heartbeat: {
210
- enabled: boolean;
211
- intervalMs: number;
212
- instructions: string;
213
- lastFiredAt?: number;
214
- };
215
198
  };
216
199
  userProfile: { content: string; updatedAt: number } | null;
217
200
  organizationId: string;
@@ -306,106 +289,6 @@ async function handleSimpleMagicFile(
306
289
  );
307
290
  }
308
291
 
309
- function formatInterval(ms: number): string {
310
- if (ms < 60_000) return `${Math.round(ms / 1000)}s`;
311
- if (ms < 60 * 60_000) return `${Math.round(ms / 60_000)}m`;
312
- if (ms < 24 * 60 * 60_000) {
313
- const hours = ms / (60 * 60_000);
314
- return Number.isInteger(hours) ? `${hours}h` : `${hours.toFixed(1)}h`;
315
- }
316
- return `${(ms / (24 * 60 * 60_000)).toFixed(1)}d`;
317
- }
318
-
319
- const DURATION_RE = /^(\d+)\s*(ms|s|m|h|d)$/i;
320
- function parseDurationFlagToMs(input: string): number | null {
321
- const m = DURATION_RE.exec(input.trim());
322
- if (!m) return null;
323
- const n = parseInt(m[1], 10);
324
- if (!Number.isFinite(n) || n <= 0) return null;
325
- switch (m[2].toLowerCase()) {
326
- case "ms":
327
- return n;
328
- case "s":
329
- return n * 1000;
330
- case "m":
331
- return n * 60_000;
332
- case "h":
333
- return n * 3_600_000;
334
- case "d":
335
- return n * 86_400_000;
336
- }
337
- return null;
338
- }
339
-
340
- async function handleHeartbeat(ctx: CliCtx, args: string[]): Promise<void> {
341
- const sub = args[0];
342
- if (!sub || sub === "status" || sub === "show") {
343
- const config = await loadAgentConfig(ctx);
344
- const hb = config.agentConfig.heartbeat;
345
- if (ctx.jsonOutput) {
346
- out(ctx, hb);
347
- return;
348
- }
349
- out(
350
- ctx,
351
- `enabled=${hb.enabled} interval=${formatInterval(hb.intervalMs)}\n\n${hb.instructions}`,
352
- );
353
- return;
354
- }
355
- if (sub === "enable") {
356
- await runMutation(ctx, api.functions.agentConfig.updateHeartbeat, {
357
- enabled: true,
358
- });
359
- out(ctx, { ok: true, enabled: true });
360
- return;
361
- }
362
- if (sub === "disable") {
363
- await runMutation(ctx, api.functions.agentConfig.updateHeartbeat, {
364
- enabled: false,
365
- });
366
- out(ctx, { ok: true, enabled: false });
367
- return;
368
- }
369
- if (sub === "interval") {
370
- const raw = args[1];
371
- if (!raw) {
372
- throw new AgentConfigCliError(
373
- "Usage: dench heartbeat interval <duration> (e.g. 30m, 2h, 1d)",
374
- );
375
- }
376
- const ms = parseDurationFlagToMs(raw);
377
- if (!ms) throw new AgentConfigCliError(`Invalid duration: ${raw}`);
378
- await runMutation(ctx, api.functions.agentConfig.updateHeartbeat, {
379
- intervalMs: ms,
380
- });
381
- out(ctx, { ok: true, intervalMs: ms, label: formatInterval(ms) });
382
- return;
383
- }
384
- if (sub === "instructions" || sub === "edit") {
385
- let content: string;
386
- if (hasFlag(args, "--stdin") || getFlag(args, "--content")) {
387
- content = await resolveContent(args);
388
- } else {
389
- const config = await loadAgentConfig(ctx);
390
- content = openEditor(
391
- config.agentConfig.heartbeat.instructions,
392
- "heartbeat",
393
- );
394
- }
395
- if (!content.trim().length) {
396
- throw new AgentConfigCliError("heartbeat instructions cannot be empty");
397
- }
398
- await runMutation(ctx, api.functions.agentConfig.updateHeartbeat, {
399
- instructions: content,
400
- });
401
- out(ctx, { ok: true, kind: "heartbeat" });
402
- return;
403
- }
404
- throw new AgentConfigCliError(
405
- `Unknown heartbeat subcommand: "${sub}". Try "status", "enable", "disable", "interval <duration>", "instructions".`,
406
- );
407
- }
408
-
409
292
  async function handleBootstrap(ctx: CliCtx, args: string[]): Promise<void> {
410
293
  const sub = args[0];
411
294
  if (!sub || sub === "status" || sub === "show") {
@@ -598,10 +481,6 @@ export async function runMemoryAggregateCommand(ctx: CliCtx): Promise<void> {
598
481
  );
599
482
  }
600
483
 
601
- export async function runHeartbeatCommand(ctx: CliCtx): Promise<void> {
602
- await handleHeartbeat(ctx, ctx.args);
603
- }
604
-
605
484
  export async function runBootstrapCommand(ctx: CliCtx): Promise<void> {
606
485
  await handleBootstrap(ctx, ctx.args);
607
486
  }
@@ -666,8 +545,6 @@ export function printAgentConfigCliHelp(): void {
666
545
  dench user show | edit USER.md (per-member)
667
546
  dench tools show | notes-edit TOOLS.md notes section
668
547
  dench mem show | append <text> | set MEMORY.md aggregate
669
- dench heartbeat status | enable | disable Heartbeat cron
670
- | interval <30m|1h|…> | instructions
671
548
  dench bootstrap status | complete | reopen | template
672
549
  dench model default <id|clear> | model thread <threadId> <id|clear>
673
550
  dench daily today | show <YYYY-MM-DD> Activity log paths
package/chat.ts CHANGED
@@ -433,7 +433,7 @@ async function runChatDeleteCommand(ctx: ChatCliContext): Promise<void> {
433
433
  );
434
434
  }
435
435
  const automatedSuffix = includeAutomated
436
- ? " (INCLUDING ROUTINES / HEARTBEATS — automated thread protection bypassed)"
436
+ ? " (INCLUDING AUTOMATED ROUTINES — automated thread protection bypassed)"
437
437
  : "";
438
438
  const ok = await promptYesNo(
439
439
  `PERMANENTLY queue delete of ${threadIds.length} chat${
package/dench.ts CHANGED
@@ -490,6 +490,15 @@ LinkedIn outreach (Browserbase, verified before CRM update):
490
490
  dench linkedin connect <profileUrl> [--entry people:<entryId>] [--json]
491
491
  Help: dench linkedin help
492
492
 
493
+ Managed email campaigns (Dench Emailing Service):
494
+ dench email identity verify --from founder@example.com [--json]
495
+ dench email template create --name "Intro" --subject "Hi {{firstName}}" --html-file body.html [--json]
496
+ dench email campaign create --name "Q2 outreach" --identity <id> --template <id> [--json]
497
+ dench email campaign recipients add --campaign <id> --file recipients.jsonl [--json]
498
+ dench email campaign submit --campaign <id> [--json]
499
+ dench email campaign status --campaign <id> [--json]
500
+ Help: dench email help
501
+
493
502
  Image generation / editing (via the Dench Gateway):
494
503
  dench image generate "<prompt>" [--size 1024x1024] [--quality auto|low|medium|high] [--format png|jpeg|webp] [--save-path /workspace/path/file.png] [--output <local file>] [--no-write] [--json]
495
504
  dench image edit "<prompt>" --input <path> [--input <path> ...] [--mask <path>] [--size ...] [--quality ...] [--format ...] [--save-path ...] [--output ...] [--json]
@@ -552,8 +561,6 @@ Self-updating agent harness:
552
561
  dench user show | edit USER.md (per-member)
553
562
  dench tools show | edit TOOLS.md notes section
554
563
  dench mem show | append "<text>" | set MEMORY.md aggregate
555
- dench heartbeat status | enable | disable
556
- | interval <30m|1h|…> | instructions
557
564
  dench bootstrap status | complete | reopen | template
558
565
  dench model default <id|clear>
559
566
  dench model thread <threadId> <id|clear>
@@ -3666,6 +3673,20 @@ async function main() {
3666
3673
  return;
3667
3674
  }
3668
3675
 
3676
+ if (
3677
+ command === "email" &&
3678
+ (!subcommand || subcommand === "help" || hasFlag("--help"))
3679
+ ) {
3680
+ const { runEmailCommand } = await import("./email");
3681
+ await runEmailCommand({
3682
+ // biome-ignore lint/suspicious/noExplicitAny: harmless help-only stub
3683
+ convex: {} as any,
3684
+ args: subcommand ? [subcommand] : ["help"],
3685
+ jsonOutput: json,
3686
+ });
3687
+ return;
3688
+ }
3689
+
3669
3690
  if (
3670
3691
  command === "billing" &&
3671
3692
  (!subcommand || subcommand === "help" || hasFlag("--help"))
@@ -3897,6 +3918,31 @@ async function main() {
3897
3918
  return;
3898
3919
  }
3899
3920
 
3921
+ if (command === "email") {
3922
+ const subArgs = args.slice(args.indexOf("email") + 1);
3923
+ const { runEmailCommand } = await import("./email");
3924
+ const runtime = await getRuntime();
3925
+ const authedRuntime = requireAuthenticatedRuntime(runtime, "dench email");
3926
+ let gatewayAuth:
3927
+ | Awaited<ReturnType<typeof resolveGatewayCommandAuth>>
3928
+ | undefined;
3929
+ try {
3930
+ gatewayAuth = await resolveGatewayCommandAuth(runtime, "dench email");
3931
+ } catch {
3932
+ gatewayAuth = undefined;
3933
+ }
3934
+ await runEmailCommand({
3935
+ convex: authedRuntime.client,
3936
+ args: subArgs.filter((arg) => arg !== "--json"),
3937
+ jsonOutput: json,
3938
+ sessionToken: authedRuntime.sessionToken,
3939
+ bearerToken: gatewayAuth?.bearerToken ?? authedRuntime.sessionToken,
3940
+ gatewayBaseUrl: gatewayAuth?.gatewayBaseUrl,
3941
+ gatewayHeaders: gatewayAuth?.gatewayHeaders,
3942
+ });
3943
+ return;
3944
+ }
3945
+
3900
3946
  if (command === "apps") {
3901
3947
  const runtime = await getRuntime();
3902
3948
  const gatewayAuth = await resolveGatewayCommandAuth(runtime, "dench apps");
@@ -4093,7 +4139,7 @@ async function main() {
4093
4139
  // ── OpenClaw-parity self-update CLI surface ──────────────────────────
4094
4140
  //
4095
4141
  // Dispatch all `dench identity / organisation / user / tools / mem /
4096
- // heartbeat / bootstrap / model / daily` traffic through the shared
4142
+ // bootstrap / model / daily` traffic through the shared
4097
4143
  // agent-config CLI module. Each command requires the same auth shape
4098
4144
  // as `dench cron` (Bearer DENCH_API_KEY or `dench signin` session
4099
4145
  // token) so server-side `requireCronAccess` accepts both.
@@ -4103,7 +4149,6 @@ async function main() {
4103
4149
  "user",
4104
4150
  "tools",
4105
4151
  "mem",
4106
- "heartbeat",
4107
4152
  "bootstrap",
4108
4153
  "model",
4109
4154
  "daily",
@@ -4151,10 +4196,6 @@ async function main() {
4151
4196
  await mod.runMemoryAggregateCommand(ctxBase);
4152
4197
  return;
4153
4198
  }
4154
- if (command === "heartbeat") {
4155
- await mod.runHeartbeatCommand(ctxBase);
4156
- return;
4157
- }
4158
4199
  if (command === "bootstrap") {
4159
4200
  await mod.runBootstrapCommand(ctxBase);
4160
4201
  return;
@@ -888,11 +888,6 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
888
888
  content: z.string(),
889
889
  append: z.boolean().optional(),
890
890
  }),
891
- "agentConfig.heartbeat.update": z.object({
892
- enabled: z.boolean().optional(),
893
- intervalMs: z.number().int().optional(),
894
- instructions: z.string().optional(),
895
- }),
896
891
  "agentConfig.bootstrap.complete": z.object({
897
892
  completed: z.boolean().optional(),
898
893
  }),
@@ -1095,6 +1095,180 @@ const rawApiOperations = [
1095
1095
  ),
1096
1096
  }),
1097
1097
 
1098
+ // Managed email campaigns.
1099
+ op({
1100
+ id: "email.identity.verify",
1101
+ group: "email",
1102
+ summary: "Start Dench Emailing Service sender verification.",
1103
+ cli: "dench email identity verify",
1104
+ method: "POST",
1105
+ path: "/email/identities/verify",
1106
+ requestSchema: anyObject,
1107
+ responseSchema: anyResponse,
1108
+ backend: gateway("POST", "/email/identities/verify"),
1109
+ }),
1110
+ op({
1111
+ id: "email.identity.create",
1112
+ group: "email",
1113
+ summary: "Create a durable Dench email sending identity record.",
1114
+ cli: "dench email identity verify",
1115
+ method: "POST",
1116
+ path: "/email/sending-identities",
1117
+ requestSchema: anyObject,
1118
+ responseSchema: anyResponse,
1119
+ backend: convex("mutation", "functions/emailCampaigns:createIdentity"),
1120
+ }),
1121
+ op({
1122
+ id: "email.identity.refresh",
1123
+ group: "email",
1124
+ summary: "Refresh a Dench Emailing Service sender verification status.",
1125
+ cli: "dench email identity status --identity-id",
1126
+ method: "POST",
1127
+ path: "/email/sending-identities/{identityId}/status",
1128
+ requestSchema: anyObject,
1129
+ responseSchema: anyResponse,
1130
+ backend: convex(
1131
+ "action",
1132
+ "functions/emailCampaignsNode:refreshIdentityStatus",
1133
+ ),
1134
+ }),
1135
+ op({
1136
+ id: "email.identity.beginVerification",
1137
+ group: "email",
1138
+ summary:
1139
+ "Begin the full Dench sender verification flow for a sending identity.",
1140
+ cli: "dench email identity verify",
1141
+ method: "POST",
1142
+ path: "/email/sending-identities/{identityId}/verify",
1143
+ requestSchema: anyObject,
1144
+ responseSchema: anyResponse,
1145
+ backend: convex(
1146
+ "action",
1147
+ "functions/emailCampaignsNode:beginSenderVerification",
1148
+ ),
1149
+ }),
1150
+ op({
1151
+ id: "email.identity.status",
1152
+ group: "email",
1153
+ summary: "Read Dench Emailing Service sender verification status.",
1154
+ cli: "dench email identity status",
1155
+ method: "GET",
1156
+ path: "/email/identities/{identity}/status",
1157
+ requestSchema: anyObject,
1158
+ responseSchema: anyResponse,
1159
+ backend: gateway("GET", "/email/identities/{identity}/status"),
1160
+ }),
1161
+ op({
1162
+ id: "email.template.create",
1163
+ group: "email",
1164
+ summary: "Create a managed email campaign template.",
1165
+ cli: "dench email template create",
1166
+ method: "POST",
1167
+ path: "/email/templates",
1168
+ requestSchema: anyObject,
1169
+ responseSchema: anyResponse,
1170
+ backend: convex("mutation", "functions/emailCampaigns:createTemplate"),
1171
+ }),
1172
+ op({
1173
+ id: "email.template.preview",
1174
+ group: "email",
1175
+ summary: "Preview a managed email campaign template.",
1176
+ cli: "dench email template preview",
1177
+ method: "POST",
1178
+ path: "/email/templates/{templateId}/preview",
1179
+ requestSchema: anyObject,
1180
+ responseSchema: anyResponse,
1181
+ backend: convex("query", "functions/emailCampaigns:previewTemplate"),
1182
+ }),
1183
+ op({
1184
+ id: "email.campaign.create",
1185
+ group: "email",
1186
+ summary: "Create a managed email campaign draft.",
1187
+ cli: "dench email campaign create",
1188
+ method: "POST",
1189
+ path: "/email/campaigns",
1190
+ requestSchema: anyObject,
1191
+ responseSchema: anyResponse,
1192
+ backend: convex("mutation", "functions/emailCampaigns:createCampaign"),
1193
+ }),
1194
+ op({
1195
+ id: "email.campaign.get",
1196
+ group: "email",
1197
+ summary: "Read campaign status and counters.",
1198
+ cli: "dench email campaign status",
1199
+ method: "GET",
1200
+ path: "/email/campaigns/{campaignId}",
1201
+ requestSchema: anyObject,
1202
+ responseSchema: anyResponse,
1203
+ backend: convex("query", "functions/emailCampaigns:getCampaign"),
1204
+ }),
1205
+ op({
1206
+ id: "email.campaign.recipients.add",
1207
+ group: "email",
1208
+ summary: "Add recipients to a managed email campaign.",
1209
+ cli: "dench email campaign recipients add",
1210
+ method: "POST",
1211
+ path: "/email/campaigns/{campaignId}/recipients",
1212
+ requestSchema: anyObject,
1213
+ responseSchema: anyResponse,
1214
+ backend: convex("mutation", "functions/emailCampaigns:addRecipients"),
1215
+ }),
1216
+ op({
1217
+ id: "email.campaign.preview",
1218
+ group: "email",
1219
+ summary: "Preview rendered messages for a campaign sample.",
1220
+ cli: "dench email campaign preview",
1221
+ method: "POST",
1222
+ path: "/email/campaigns/{campaignId}/preview",
1223
+ requestSchema: anyObject,
1224
+ responseSchema: anyResponse,
1225
+ backend: convex("query", "functions/emailCampaigns:previewCampaign"),
1226
+ }),
1227
+ op({
1228
+ id: "email.campaign.submit",
1229
+ group: "email",
1230
+ summary: "Submit a campaign for approval or sending.",
1231
+ cli: "dench email campaign submit",
1232
+ method: "POST",
1233
+ path: "/email/campaigns/{campaignId}/submit",
1234
+ requestSchema: anyObject,
1235
+ responseSchema: anyResponse,
1236
+ backend: convex("mutation", "functions/emailCampaigns:submitCampaign"),
1237
+ }),
1238
+ op({
1239
+ id: "email.campaign.pause",
1240
+ group: "email",
1241
+ summary: "Pause a sending email campaign.",
1242
+ cli: "dench email campaign pause",
1243
+ method: "POST",
1244
+ path: "/email/campaigns/{campaignId}/pause",
1245
+ requestSchema: anyObject,
1246
+ responseSchema: anyResponse,
1247
+ backend: convex("mutation", "functions/emailCampaigns:pauseCampaign"),
1248
+ }),
1249
+ op({
1250
+ id: "email.campaign.resume",
1251
+ group: "email",
1252
+ summary: "Resume a paused email campaign.",
1253
+ cli: "dench email campaign resume",
1254
+ method: "POST",
1255
+ path: "/email/campaigns/{campaignId}/resume",
1256
+ requestSchema: anyObject,
1257
+ responseSchema: anyResponse,
1258
+ backend: convex("mutation", "functions/emailCampaigns:resumeCampaign"),
1259
+ }),
1260
+ op({
1261
+ id: "email.campaign.cancel",
1262
+ group: "email",
1263
+ summary: "Cancel an email campaign.",
1264
+ cli: "dench email campaign cancel",
1265
+ method: "POST",
1266
+ path: "/email/campaigns/{campaignId}/cancel",
1267
+ requestSchema: anyObject,
1268
+ responseSchema: anyResponse,
1269
+ backend: convex("mutation", "functions/emailCampaigns:cancelCampaign"),
1270
+ }),
1271
+
1098
1272
  // Routines.
1099
1273
  op({
1100
1274
  id: "cron.list",
@@ -1521,28 +1695,6 @@ const rawApiOperations = [
1521
1695
  responseSchema: anyResponse,
1522
1696
  backend: convex("mutation", "functions/agentConfig:updateMemory"),
1523
1697
  }),
1524
- op({
1525
- id: "agentConfig.heartbeat.show",
1526
- group: "agent-config",
1527
- summary: "Read heartbeat config.",
1528
- cli: "dench heartbeat status",
1529
- method: "GET",
1530
- path: "/agent-config/heartbeat",
1531
- requestSchema: noBody,
1532
- responseSchema: anyResponse,
1533
- backend: convex("query", "functions/agentConfig:getAgentConfig"),
1534
- }),
1535
- op({
1536
- id: "agentConfig.heartbeat.update",
1537
- group: "agent-config",
1538
- summary: "Update heartbeat config.",
1539
- cli: "dench heartbeat enable|disable|interval|instructions",
1540
- method: "PATCH",
1541
- path: "/agent-config/heartbeat",
1542
- requestSchema: anyObject,
1543
- responseSchema: anyResponse,
1544
- backend: convex("mutation", "functions/agentConfig:updateHeartbeat"),
1545
- }),
1546
1698
  op({
1547
1699
  id: "agentConfig.bootstrap.show",
1548
1700
  group: "agent-config",
@@ -1055,12 +1055,13 @@ export async function aviatoEnrichPerson(
1055
1055
  body: AviatoEnrichPersonBody,
1056
1056
  options: EnrichmentGatewayOptions = {},
1057
1057
  ): Promise<Record<string, unknown>> {
1058
+ const linkedinUrl = body.linkedinUrl?.trim();
1059
+ const linkedinID = body.linkedinID?.trim();
1060
+ const linkedinNumID = body.linkedinNumID?.trim();
1061
+ const linkedinEntityId = body.linkedinEntityId?.trim();
1062
+ const email = body.email?.trim();
1058
1063
  const hasIdentifier =
1059
- body.linkedinUrl ||
1060
- body.linkedinID ||
1061
- body.linkedinNumID ||
1062
- body.linkedinEntityId ||
1063
- body.email;
1064
+ linkedinUrl || linkedinID || linkedinNumID || linkedinEntityId || email;
1064
1065
  if (!hasIdentifier) {
1065
1066
  throw new EnrichmentGatewayError(
1066
1067
  "Aviato person enrichment requires at least one identifier: linkedinUrl, linkedinID, linkedinNumID, linkedinEntityId, or email.",
@@ -1068,14 +1069,24 @@ export async function aviatoEnrichPerson(
1068
1069
  );
1069
1070
  }
1070
1071
 
1072
+ // Aviato rejects email-only lookups in preview mode ("Email lookups are
1073
+ // not allowed in preview mode"), so when email is the sole identifier we
1074
+ // upgrade to a full (charged) lookup rather than surfacing a hard 400.
1075
+ const emailOnly =
1076
+ Boolean(email) &&
1077
+ !linkedinUrl &&
1078
+ !linkedinID &&
1079
+ !linkedinNumID &&
1080
+ !linkedinEntityId;
1081
+
1071
1082
  const requestBody: Record<string, unknown> = {};
1072
- if (body.linkedinUrl) requestBody.linkedinURL = body.linkedinUrl.trim();
1073
- if (body.linkedinID) requestBody.linkedinID = body.linkedinID.trim();
1074
- if (body.linkedinNumID) requestBody.linkedinNumID = body.linkedinNumID.trim();
1075
- if (body.linkedinEntityId)
1076
- requestBody.linkedinEntityId = body.linkedinEntityId.trim();
1077
- if (body.email) requestBody.email = body.email.trim();
1078
- if (body.preview !== undefined) requestBody.preview = body.preview;
1083
+ if (linkedinUrl) requestBody.linkedinURL = linkedinUrl;
1084
+ if (linkedinID) requestBody.linkedinID = linkedinID;
1085
+ if (linkedinNumID) requestBody.linkedinNumID = linkedinNumID;
1086
+ if (linkedinEntityId) requestBody.linkedinEntityId = linkedinEntityId;
1087
+ if (email) requestBody.email = email;
1088
+ if (body.preview !== undefined && !(body.preview && emailOnly))
1089
+ requestBody.preview = body.preview;
1079
1090
 
1080
1091
  const raw = await callGatewayJson("/v1/enrichment/aviato/person", {
1081
1092
  method: "POST",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "Dench agent workspace CLI. v2 unifies auth behind `dench signin`; the legacy `dench login` / `dench onboard` / `dench setup` / `dench what-can-i-do` / `dench register` commands now error with a redirect.",
5
5
  "type": "module",
6
6
  "bin": {