@dench.com/cli 2.8.1-staging.110 → 2.8.2-staging.112

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/crm.ts CHANGED
@@ -3093,6 +3093,13 @@ async function resolveEnrichmentTarget(
3093
3093
  * Email (no first/last name reached the gateway, so the validator
3094
3094
  * rejected the request before any lookup ran).
3095
3095
  */
3096
+ const CONTACT_REQUIRED_FIELDS = new Set([
3097
+ "email",
3098
+ "work_email",
3099
+ "personal_email",
3100
+ "phone",
3101
+ ]);
3102
+
3096
3103
  async function callEnrichmentGatewayForEntry(args: {
3097
3104
  target: EnrichmentTarget;
3098
3105
  entry: CrmEntryForEnrichment;
@@ -3118,7 +3125,11 @@ async function callEnrichmentGatewayForEntry(args: {
3118
3125
  // Contact cells stay on /person/contact. A LinkedIn URL for a profile
3119
3126
  // field (name, title, headline) uses /person so the gateway waterfall
3120
3127
  // can fill graph data without a contact job.
3121
- const isContactField = Boolean(target.column.enrichFields?.length);
3128
+ const isContactField =
3129
+ Boolean(target.column.enrichFields?.length) ||
3130
+ target.column.requiredFields.some((field) =>
3131
+ CONTACT_REQUIRED_FIELDS.has(field),
3132
+ );
3122
3133
  if (!isContactField && personBody.linkedinUrl) {
3123
3134
  return aviatoEnrichPerson(
3124
3135
  {
package/dench.ts CHANGED
@@ -44,7 +44,11 @@ import {
44
44
  withCurrentSessionSelection,
45
45
  withSavedSession,
46
46
  } from "./session";
47
- import { fetchConnectedAppsSummary, runToolCommand } from "./tools";
47
+ import {
48
+ fetchConnectedAppsSummary,
49
+ fetchUnipileConnections,
50
+ runToolCommand,
51
+ } from "./tools";
48
52
 
49
53
  type JsonRecord = Record<string, unknown>;
50
54
 
@@ -2879,11 +2883,14 @@ async function connectedAppsContext(runtime: Runtime) {
2879
2883
  ...unipileQueryAuth(runtime),
2880
2884
  });
2881
2885
  } catch (error) {
2886
+ const unipile = await fetchUnipileConnections(unipileQueryAuth(runtime));
2882
2887
  return {
2883
2888
  available: false,
2884
2889
  reason: errorMessage(error),
2885
- count: 0,
2886
- connections: [],
2890
+ count: unipile.connections.length,
2891
+ connections: unipile.connections,
2892
+ unipileAvailable: unipile.available,
2893
+ ...(unipile.error ? { unipileError: unipile.error } : {}),
2887
2894
  };
2888
2895
  }
2889
2896
  }
@@ -4979,6 +4986,7 @@ async function main() {
4979
4986
  args.slice(args.indexOf("artifacts") + 1),
4980
4987
  );
4981
4988
  if (
4989
+ runtime.mode === "dev" ||
4982
4990
  runtime.mode === "session" ||
4983
4991
  runtime.mode === "apiKey"
4984
4992
  ) {
@@ -4987,7 +4995,10 @@ async function main() {
4987
4995
  convex: runtime.client,
4988
4996
  args: artifactArgs,
4989
4997
  jsonOutput: json,
4990
- sessionToken: runtime.sessionToken,
4998
+ sessionToken:
4999
+ runtime.mode === "dev"
5000
+ ? encodeDevCrmSessionToken(runtime.workspaceArgs)
5001
+ : runtime.sessionToken,
4991
5002
  });
4992
5003
  if (handled) return;
4993
5004
  }
package/leads.ts ADDED
@@ -0,0 +1,585 @@
1
+ /**
2
+ * `dench leads find`, `dench jobs find`, and workspace artifact
3
+ * list/show/import. Find writes a durable table artifact; import is the
4
+ * same path as the chat card.
5
+ */
6
+ import type { ConvexHttpClient } from "convex/browser";
7
+ import { makeFunctionReference } from "convex/server";
8
+ import {
9
+ assertNoUnknownFlags as assertNoUnknownFlagsRaw,
10
+ CliArgError,
11
+ getFlag,
12
+ hasFlag,
13
+ shift as shiftRaw,
14
+ } from "./lib/cli-args";
15
+ import {
16
+ EnrichmentGatewayError,
17
+ type EnrichmentGatewayOptions,
18
+ searchCompanies,
19
+ searchJobs,
20
+ searchPeople,
21
+ } from "./lib/enrichment-gateway";
22
+
23
+ class FindCliError extends Error {
24
+ constructor(message: string) {
25
+ super(message);
26
+ this.name = "FindCliError";
27
+ }
28
+ }
29
+
30
+ type FindCliContext = {
31
+ convex: ConvexHttpClient;
32
+ args: string[];
33
+ jsonOutput: boolean;
34
+ sessionToken?: string;
35
+ enrichmentGateway?: Pick<EnrichmentGatewayOptions, "apiKey" | "baseUrl">;
36
+ };
37
+
38
+ const api = {
39
+ saveFind: makeFunctionReference<"action">(
40
+ "functions/workspaceArtifacts:saveFind",
41
+ ),
42
+ list: makeFunctionReference<"query">("functions/workspaceArtifacts:list"),
43
+ get: makeFunctionReference<"query">("functions/workspaceArtifacts:get"),
44
+ importArtifact: makeFunctionReference<"action">(
45
+ "functions/workspaceArtifacts:importArtifact",
46
+ ),
47
+ };
48
+
49
+ function shift(args: string[], expected: string): string {
50
+ try {
51
+ return shiftRaw(args, expected);
52
+ } catch (error) {
53
+ if (error instanceof CliArgError) throw new FindCliError(error.message);
54
+ throw error;
55
+ }
56
+ }
57
+
58
+ function assertNoUnknownFlags(args: readonly string[], command: string): void {
59
+ try {
60
+ assertNoUnknownFlagsRaw(args, command);
61
+ } catch (error) {
62
+ if (error instanceof CliArgError) throw new FindCliError(error.message);
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ function parseLeadsFindFlags(args: string[]): {
68
+ titles?: string[];
69
+ locations?: string[];
70
+ seniorities?: string[];
71
+ industries?: string[];
72
+ functions?: string[];
73
+ companyDomain?: string;
74
+ keywords?: string;
75
+ limit?: number;
76
+ pageToken?: string;
77
+ } {
78
+ const titles = csvFlag(args, "--titles");
79
+ const locations = csvFlag(args, "--locations");
80
+ const seniorities = csvFlag(args, "--seniorities");
81
+ const industries = csvFlag(args, "--industries");
82
+ const functions = csvFlag(args, "--functions");
83
+ const companyDomain = getFlag(args, "--company-domain");
84
+ const keywords = getFlag(args, "--keywords");
85
+ const limit = parseLimit(args);
86
+ const pageToken = getFlag(args, "--page-token");
87
+ assertNoUnknownFlags(args, "leads find");
88
+ if (
89
+ !titles &&
90
+ !locations &&
91
+ !companyDomain &&
92
+ !keywords &&
93
+ !industries &&
94
+ !seniorities
95
+ ) {
96
+ throw new FindCliError(
97
+ "dench leads find requires at least one of --titles, --locations, --company-domain, --keywords, --industries, --seniorities",
98
+ );
99
+ }
100
+ return {
101
+ titles,
102
+ locations,
103
+ seniorities,
104
+ industries,
105
+ functions,
106
+ companyDomain,
107
+ keywords,
108
+ limit,
109
+ pageToken,
110
+ };
111
+ }
112
+
113
+ export function assertLeadsFindArgs(args: string[]): void {
114
+ const copy = [...args];
115
+ hasFlag(copy, "--json");
116
+ const verb = copy[0];
117
+ if (verb !== "find") return;
118
+ parseLeadsFindFlags(copy.slice(1));
119
+ }
120
+
121
+ function csvFlag(args: string[], name: string): string[] | undefined {
122
+ const raw = getFlag(args, name);
123
+ if (!raw) return undefined;
124
+ return raw
125
+ .split(",")
126
+ .map((part) => part.trim())
127
+ .filter(Boolean);
128
+ }
129
+
130
+ function parseLimit(args: string[]): number | undefined {
131
+ const raw = getFlag(args, "--limit");
132
+ if (!raw) return undefined;
133
+ const parsed = Number(raw);
134
+ if (!Number.isInteger(parsed) || parsed < 1) {
135
+ throw new FindCliError("--limit must be a positive integer");
136
+ }
137
+ return Math.min(parsed, 5000);
138
+ }
139
+
140
+ function out(ctx: FindCliContext, value: unknown): void {
141
+ console.log(JSON.stringify(value, null, ctx.jsonOutput ? 2 : 0));
142
+ }
143
+
144
+ async function callQuery(
145
+ ctx: FindCliContext,
146
+ fn: Parameters<ConvexHttpClient["query"]>[0],
147
+ args: Record<string, unknown> = {},
148
+ ): Promise<unknown> {
149
+ return await ctx.convex.query(fn, {
150
+ ...args,
151
+ ...(ctx.sessionToken ? { sessionToken: ctx.sessionToken } : {}),
152
+ } as never);
153
+ }
154
+
155
+ async function callAction(
156
+ ctx: FindCliContext,
157
+ fn: Parameters<ConvexHttpClient["action"]>[0],
158
+ args: Record<string, unknown> = {},
159
+ ): Promise<unknown> {
160
+ return await ctx.convex.action(fn, {
161
+ ...args,
162
+ ...(ctx.sessionToken ? { sessionToken: ctx.sessionToken } : {}),
163
+ } as never);
164
+ }
165
+
166
+ function asArray(value: unknown): unknown[] {
167
+ return Array.isArray(value) ? value : [];
168
+ }
169
+
170
+ function findTitle(
171
+ kind: "people" | "companies" | "jobs",
172
+ parts: string[],
173
+ ): string {
174
+ const first = parts.find((part) => part.length > 0);
175
+ if (kind === "people") return first ? `People: ${first}` : "People search";
176
+ if (kind === "companies") {
177
+ return first ? `Companies: ${first}` : "Company search";
178
+ }
179
+ return first ? `Jobs: ${first}` : "Job search";
180
+ }
181
+
182
+ async function persist(
183
+ ctx: FindCliContext,
184
+ args: {
185
+ createdFrom: "find_people" | "find_companies" | "find_jobs";
186
+ title: string;
187
+ filters: Record<string, unknown>;
188
+ people?: unknown[];
189
+ companies?: unknown[];
190
+ jobs?: unknown[];
191
+ },
192
+ ): Promise<unknown> {
193
+ return await callAction(ctx, api.saveFind, args);
194
+ }
195
+
196
+ export function leadsHelp(): void {
197
+ console.log(`Dench leads
198
+
199
+ Usage:
200
+ dench leads find --titles "VP Sales" --locations "San Francisco" [--company-domain acme.com] [--limit 500] [--page-token <token>] [--json]
201
+ dench leads find --keywords "fintech" [--industries SaaS] [--seniorities director,vp] [--json]
202
+
203
+ Finds people through Dench search and writes a workspace artifact.
204
+ Import with \`dench artifacts import <id> --to lead\`.
205
+ `);
206
+ }
207
+
208
+ export function companiesHelp(): void {
209
+ console.log(`Dench companies
210
+
211
+ Usage:
212
+ dench companies find --name "Acme" [--domain acme.com] [--keywords fintech] [--industries SaaS] [--locations "San Francisco"] [--linkedin https://linkedin.com/company/acme] [--limit 500] [--page-token <token>] [--json]
213
+
214
+ Finds companies through Dench search and writes a workspace artifact.
215
+ Import with \`dench artifacts import <id> --to lead\`.
216
+ `);
217
+ }
218
+
219
+ export function jobsHelp(): void {
220
+ console.log(`Dench jobs
221
+
222
+ Usage:
223
+ dench jobs find --titles "Account Executive" --locations "San Francisco" [--company-domain acme.com] [--keywords remote] [--limit 500] [--page-token <token>] [--json]
224
+
225
+ Finds job postings through Dench search and writes a workspace artifact.
226
+ Import with \`dench artifacts import <id> --to job_posting\`.
227
+ `);
228
+ }
229
+
230
+ export function workspaceArtifactsHelp(): void {
231
+ console.log(`Dench artifacts
232
+
233
+ Usage:
234
+ dench artifacts [--limit 25] [--json]
235
+ dench artifacts list [--limit 25] [--json]
236
+ dench artifacts show <id> [--json]
237
+ dench artifacts import <id> --to lead|company|job_posting [--view "Q3 outbound"] [--pin] [--json]
238
+
239
+ \`list\` / \`show\` / \`import\` operate on find results (lead lists and job
240
+ searches). Bare \`dench artifacts\` still lists agent suggested-work artifacts.
241
+ `);
242
+ }
243
+
244
+ export function assertJobsFindArgs(args: string[]): void {
245
+ const copy = [...args];
246
+ hasFlag(copy, "--json");
247
+ if (copy[0] !== "find") return;
248
+ if (
249
+ !copy.includes("--titles") &&
250
+ !copy.includes("--locations") &&
251
+ !copy.includes("--company-domain") &&
252
+ !copy.includes("--company") &&
253
+ !copy.includes("--keywords")
254
+ ) {
255
+ throw new FindCliError(
256
+ "dench jobs find requires at least one of --titles, --locations, --company-domain, --company, --keywords",
257
+ );
258
+ }
259
+ }
260
+
261
+ export function assertCompaniesFindArgs(args: string[]): void {
262
+ const copy = [...args];
263
+ hasFlag(copy, "--json");
264
+ if (copy[0] !== "find") return;
265
+ if (
266
+ !copy.includes("--domain") &&
267
+ !copy.includes("--name") &&
268
+ !copy.includes("--keywords") &&
269
+ !copy.includes("--industries") &&
270
+ !copy.includes("--locations") &&
271
+ !copy.includes("--linkedin") &&
272
+ !copy.includes("--headcount")
273
+ ) {
274
+ throw new FindCliError(
275
+ "dench companies find requires at least one of --name, --domain, --keywords, --industries, --locations, --linkedin, --headcount",
276
+ );
277
+ }
278
+ }
279
+
280
+ export async function runLeadsCommand(opts: {
281
+ convex: ConvexHttpClient;
282
+ args: string[];
283
+ jsonOutput?: boolean;
284
+ sessionToken?: string;
285
+ enrichmentGateway?: Pick<EnrichmentGatewayOptions, "apiKey" | "baseUrl">;
286
+ }): Promise<void> {
287
+ const args = [...opts.args];
288
+ const jsonFromArgs = hasFlag(args, "--json");
289
+ const ctx: FindCliContext = {
290
+ convex: opts.convex,
291
+ args,
292
+ jsonOutput: opts.jsonOutput === true || jsonFromArgs,
293
+ sessionToken: opts.sessionToken,
294
+ enrichmentGateway: opts.enrichmentGateway,
295
+ };
296
+ const verb = args.shift();
297
+ if (!verb || verb === "help" || verb === "--help") {
298
+ leadsHelp();
299
+ return;
300
+ }
301
+ if (verb !== "find") {
302
+ throw new FindCliError(`Unknown leads verb: ${verb}`);
303
+ }
304
+ const {
305
+ titles,
306
+ locations,
307
+ seniorities,
308
+ industries,
309
+ functions,
310
+ companyDomain,
311
+ keywords,
312
+ limit,
313
+ pageToken,
314
+ } = parseLeadsFindFlags(args);
315
+ try {
316
+ const result = await searchPeople(
317
+ {
318
+ personTitles: titles,
319
+ personLocations: locations,
320
+ personSeniorities: seniorities,
321
+ personFunctions: functions,
322
+ organizationDomains: companyDomain ? [companyDomain] : undefined,
323
+ organizationIndustries: industries,
324
+ keywords,
325
+ limit,
326
+ pageToken,
327
+ },
328
+ ctx.enrichmentGateway,
329
+ );
330
+ const people = asArray(result.people);
331
+ const artifact = await persist(ctx, {
332
+ createdFrom: "find_people",
333
+ title: findTitle("people", [
334
+ titles?.[0] ?? "",
335
+ keywords ?? "",
336
+ companyDomain ?? "",
337
+ ]),
338
+ filters: {
339
+ titles,
340
+ locations,
341
+ seniorities,
342
+ industries,
343
+ companyDomain,
344
+ keywords,
345
+ limit,
346
+ },
347
+ people,
348
+ });
349
+ // metadata carries total / nextPageToken — pass the token back via
350
+ // --page-token to continue where this page stopped.
351
+ out(ctx, {
352
+ ...(artifact as Record<string, unknown>),
353
+ metadata: result.metadata,
354
+ });
355
+ } catch (error) {
356
+ throw new FindCliError(
357
+ error instanceof EnrichmentGatewayError || error instanceof Error
358
+ ? error.message
359
+ : String(error),
360
+ );
361
+ }
362
+ }
363
+
364
+ export async function runJobsCommand(opts: {
365
+ convex: ConvexHttpClient;
366
+ args: string[];
367
+ jsonOutput?: boolean;
368
+ sessionToken?: string;
369
+ enrichmentGateway?: Pick<EnrichmentGatewayOptions, "apiKey" | "baseUrl">;
370
+ }): Promise<void> {
371
+ const args = [...opts.args];
372
+ const jsonFromArgs = hasFlag(args, "--json");
373
+ const ctx: FindCliContext = {
374
+ convex: opts.convex,
375
+ args,
376
+ jsonOutput: opts.jsonOutput === true || jsonFromArgs,
377
+ sessionToken: opts.sessionToken,
378
+ enrichmentGateway: opts.enrichmentGateway,
379
+ };
380
+ const verb = args.shift();
381
+ if (!verb || verb === "help" || verb === "--help") {
382
+ jobsHelp();
383
+ return;
384
+ }
385
+ if (verb !== "find") {
386
+ throw new FindCliError(`Unknown jobs verb: ${verb}`);
387
+ }
388
+ const titles = csvFlag(args, "--titles");
389
+ const locations = csvFlag(args, "--locations");
390
+ const companyDomain = getFlag(args, "--company-domain");
391
+ const companyName = getFlag(args, "--company");
392
+ const keywords = getFlag(args, "--keywords");
393
+ const postedAfter = getFlag(args, "--posted-after");
394
+ const limit = parseLimit(args);
395
+ const pageToken = getFlag(args, "--page-token");
396
+ assertNoUnknownFlags(args, "jobs find");
397
+ if (!titles && !locations && !companyDomain && !companyName && !keywords) {
398
+ throw new FindCliError(
399
+ "dench jobs find requires at least one of --titles, --locations, --company-domain, --company, --keywords",
400
+ );
401
+ }
402
+ try {
403
+ const result = await searchJobs(
404
+ {
405
+ titles,
406
+ locations,
407
+ companyDomain,
408
+ companyName,
409
+ keywords,
410
+ postedAfter,
411
+ limit,
412
+ pageToken,
413
+ },
414
+ ctx.enrichmentGateway,
415
+ );
416
+ const jobs = asArray(result.jobs);
417
+ const artifact = await persist(ctx, {
418
+ createdFrom: "find_jobs",
419
+ title: findTitle("jobs", [titles?.[0] ?? "", companyName ?? "", keywords ?? ""]),
420
+ filters: {
421
+ titles,
422
+ locations,
423
+ companyDomain,
424
+ companyName,
425
+ keywords,
426
+ postedAfter,
427
+ limit,
428
+ },
429
+ jobs,
430
+ });
431
+ out(ctx, {
432
+ ...(artifact as Record<string, unknown>),
433
+ metadata: result.metadata,
434
+ });
435
+ } catch (error) {
436
+ throw new FindCliError(
437
+ error instanceof EnrichmentGatewayError || error instanceof Error
438
+ ? error.message
439
+ : String(error),
440
+ );
441
+ }
442
+ }
443
+
444
+ export async function runCompaniesCommand(opts: {
445
+ convex: ConvexHttpClient;
446
+ args: string[];
447
+ jsonOutput?: boolean;
448
+ sessionToken?: string;
449
+ enrichmentGateway?: Pick<EnrichmentGatewayOptions, "apiKey" | "baseUrl">;
450
+ }): Promise<void> {
451
+ const args = [...opts.args];
452
+ const jsonFromArgs = hasFlag(args, "--json");
453
+ const ctx: FindCliContext = {
454
+ convex: opts.convex,
455
+ args,
456
+ jsonOutput: opts.jsonOutput === true || jsonFromArgs,
457
+ sessionToken: opts.sessionToken,
458
+ enrichmentGateway: opts.enrichmentGateway,
459
+ };
460
+ const verb = args.shift();
461
+ if (!verb || verb === "help" || verb === "--help") {
462
+ companiesHelp();
463
+ return;
464
+ }
465
+ if (verb !== "find") {
466
+ throw new FindCliError(`Unknown companies verb: ${verb}`);
467
+ }
468
+ const domain = getFlag(args, "--domain");
469
+ const name = getFlag(args, "--name");
470
+ const keywords = getFlag(args, "--keywords");
471
+ const industries = csvFlag(args, "--industries");
472
+ const locations = csvFlag(args, "--locations");
473
+ const linkedinUrl = getFlag(args, "--linkedin");
474
+ const headcountRanges = csvFlag(args, "--headcount");
475
+ const limit = parseLimit(args);
476
+ const pageToken = getFlag(args, "--page-token");
477
+ assertNoUnknownFlags(args, "companies find");
478
+ if (
479
+ !domain &&
480
+ !name &&
481
+ !keywords &&
482
+ !industries &&
483
+ !locations &&
484
+ !linkedinUrl &&
485
+ !headcountRanges
486
+ ) {
487
+ throw new FindCliError(
488
+ "dench companies find requires at least one of --name, --domain, --keywords, --industries, --locations, --linkedin, --headcount",
489
+ );
490
+ }
491
+ try {
492
+ const result = await searchCompanies(
493
+ {
494
+ domain,
495
+ name,
496
+ keywords,
497
+ industries,
498
+ locations,
499
+ linkedinUrl,
500
+ headcountRanges,
501
+ limit,
502
+ pageToken,
503
+ },
504
+ ctx.enrichmentGateway,
505
+ );
506
+ const artifact = await persist(ctx, {
507
+ createdFrom: "find_companies",
508
+ title: findTitle("companies", [name ?? "", domain ?? "", keywords ?? ""]),
509
+ filters: {
510
+ domain,
511
+ name,
512
+ keywords,
513
+ industries,
514
+ locations,
515
+ linkedinUrl,
516
+ headcountRanges,
517
+ limit,
518
+ },
519
+ companies: asArray(result.companies),
520
+ });
521
+ out(ctx, {
522
+ ...(artifact as Record<string, unknown>),
523
+ metadata: result.metadata,
524
+ });
525
+ } catch (error) {
526
+ throw new FindCliError(
527
+ error instanceof EnrichmentGatewayError || error instanceof Error
528
+ ? error.message
529
+ : String(error),
530
+ );
531
+ }
532
+ }
533
+
534
+ export async function runWorkspaceArtifactsCommand(opts: {
535
+ convex: ConvexHttpClient;
536
+ args: string[];
537
+ jsonOutput?: boolean;
538
+ sessionToken?: string;
539
+ }): Promise<boolean> {
540
+ const args = [...opts.args];
541
+ const verb = args[0];
542
+ if (verb !== "list" && verb !== "show" && verb !== "import") {
543
+ return false;
544
+ }
545
+ args.shift();
546
+ const ctx: FindCliContext = {
547
+ convex: opts.convex,
548
+ args,
549
+ jsonOutput: opts.jsonOutput === true || hasFlag(args, "--json"),
550
+ sessionToken: opts.sessionToken,
551
+ };
552
+ if (verb === "list") {
553
+ const limitRaw = getFlag(args, "--limit");
554
+ const limit = limitRaw ? Number(limitRaw) : 25;
555
+ assertNoUnknownFlags(args, "artifacts list");
556
+ out(ctx, await callQuery(ctx, api.list, { limit }));
557
+ return true;
558
+ }
559
+ if (verb === "show") {
560
+ const artifactId = shift(args, "artifact id");
561
+ assertNoUnknownFlags(args, "artifacts show");
562
+ out(ctx, await callQuery(ctx, api.get, { artifactId }));
563
+ return true;
564
+ }
565
+ const artifactId = shift(args, "artifact id");
566
+ const to = getFlag(args, "--to");
567
+ if (to !== "lead" && to !== "job_posting" && to !== "company") {
568
+ throw new FindCliError(
569
+ "dench artifacts import <id> --to lead|company|job_posting [--view ...] [--pin]",
570
+ );
571
+ }
572
+ const viewName = getFlag(args, "--view");
573
+ const pin = hasFlag(args, "--pin");
574
+ assertNoUnknownFlags(args, "artifacts import");
575
+ out(
576
+ ctx,
577
+ await callAction(ctx, api.importArtifact, {
578
+ artifactId,
579
+ to,
580
+ viewName,
581
+ pin,
582
+ }),
583
+ );
584
+ return true;
585
+ }
@@ -91,6 +91,22 @@ export const PEOPLE_ENRICHMENT_COLUMNS: readonly EnrichmentColumnDef[] = [
91
91
  extractionFallbacks: ["email"],
92
92
  enrichFields: ["work_emails"],
93
93
  },
94
+ {
95
+ label: "Work Email",
96
+ key: "person.work_email",
97
+ fieldType: "email",
98
+ apolloPath: "person.work_email",
99
+ requiredFields: ["work_email"],
100
+ enrichFields: ["work_emails"],
101
+ },
102
+ {
103
+ label: "Personal Email",
104
+ key: "person.personal_email",
105
+ fieldType: "email",
106
+ apolloPath: "person.personal_email",
107
+ requiredFields: ["personal_email"],
108
+ enrichFields: ["personal_emails"],
109
+ },
94
110
  {
95
111
  label: "Headline",
96
112
  key: "person.headline",
@@ -336,7 +352,14 @@ export function getRequiredFieldsForApolloPath(
336
352
  const column = getEnrichmentColumns(category).find(
337
353
  (candidate) => candidate.apolloPath === apolloPath,
338
354
  );
339
- return column?.requiredFields ?? [];
355
+ if (column) return column.requiredFields;
356
+ if (category !== "people") return [];
357
+ if (apolloPath.includes("personal_email")) return ["personal_email"];
358
+ if (apolloPath.includes("phone")) return ["phone"];
359
+ if (apolloPath.includes("work_email") || /(^|\.)email$/.test(apolloPath)) {
360
+ return ["work_email"];
361
+ }
362
+ return [];
340
363
  }
341
364
 
342
365
  export function buildEnrichmentMeta(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "2.8.1-staging.110",
3
+ "version": "2.8.2-staging.112",
4
4
  "description": "Dench agent workspace CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,7 @@
32
32
  "search.ts",
33
33
  "web.ts",
34
34
  "image.ts",
35
+ "leads.ts",
35
36
  "tools.ts",
36
37
  "apps.ts",
37
38
  "agentKind.ts",
package/tools.ts CHANGED
@@ -169,7 +169,9 @@ function withoutShadowedComposioTools(
169
169
  * whole command over that would take `dench integrations` from "missing the
170
170
  * calendar" to "broken", which is strictly worse.
171
171
  */
172
- async function fetchUnipileConnections(ctx: UnipileAuthContext): Promise<{
172
+ export async function fetchUnipileConnections(
173
+ ctx: UnipileAuthContext,
174
+ ): Promise<{
173
175
  available: boolean;
174
176
  connections: CompactConnection[];
175
177
  error?: string;
@@ -257,12 +259,9 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
257
259
  const filterToolkit = ctx.args[0]?.startsWith("--") ? undefined : ctx.args[0];
258
260
  if (filterToolkit) ctx.args.shift();
259
261
 
260
- const [connectionsPayload, unipile] = await Promise.all([
261
- callGateway(ctx, "GET", "/v1/composio/connections"),
262
- fetchUnipileConnections(ctx),
263
- ]);
262
+ const connectionsSummary = await fetchConnectedAppsSummary(ctx);
264
263
  let toolkitsPayload: unknown = null;
265
- if (filterToolkit) {
264
+ if (filterToolkit && connectionsSummary.available) {
266
265
  const search = encodeURIComponent(filterToolkit);
267
266
  toolkitsPayload = await callGateway(
268
267
  ctx,
@@ -271,10 +270,7 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
271
270
  );
272
271
  }
273
272
 
274
- const allConnections = [
275
- ...extractConnections(connectionsPayload).map(compactConnection),
276
- ...unipile.connections,
277
- ];
273
+ const allConnections = connectionsSummary.connections;
278
274
  // Filtering after the merge is what makes `dench tool status gmail` answer
279
275
  // with the Unipile mailbox instead of nothing.
280
276
  const filteredConnections = filterToolkit
@@ -285,12 +281,16 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
285
281
 
286
282
  const summary = {
287
283
  ok: true,
288
- available: true,
284
+ available: connectionsSummary.available,
289
285
  toolkit: filterToolkit ?? null,
290
286
  count: filteredConnections.length,
291
287
  connections: filteredConnections,
292
- unipileAvailable: unipile.available,
293
- ...(unipile.error ? { unipileError: unipile.error } : {}),
288
+ unipileAvailable: connectionsSummary.unipileAvailable,
289
+ ...(connectionsSummary.unipileError
290
+ ? { unipileError: connectionsSummary.unipileError }
291
+ : {}),
292
+ ...(connectionsSummary.error ? { error: connectionsSummary.error } : {}),
293
+ ...(connectionsSummary.reason ? { reason: connectionsSummary.reason } : {}),
294
294
  ...(toolkitsPayload
295
295
  ? { toolkit_info: extractToolkits(toolkitsPayload) }
296
296
  : {}),