@dench.com/cli 2.8.1-staging.110 → 2.8.3-staging.117

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.
@@ -1187,27 +1187,136 @@ const composioToolRun = z.object({
1187
1187
  account: z.string().optional(),
1188
1188
  });
1189
1189
 
1190
+ /**
1191
+ * One list-filter entry: a plain string includes the value; the object
1192
+ * form supports per-value `exclude` / `exactMatch`.
1193
+ */
1194
+ const searchFilterItem = z.union([
1195
+ z.string(),
1196
+ z.object({
1197
+ value: z.string(),
1198
+ exclude: z.boolean().optional(),
1199
+ exactMatch: z.boolean().optional(),
1200
+ }),
1201
+ ]);
1202
+
1203
+ const FILTER_OPTIONS_NOTE =
1204
+ "Accepted values: see `dench leads filters` or https://dench.com/references/find-filter-options.json.";
1205
+
1206
+ const searchRange = z.object({
1207
+ min: z.number().int().nonnegative().optional(),
1208
+ max: z.number().int().nonnegative().optional(),
1209
+ });
1210
+
1190
1211
  const peopleSearch = z.object({
1191
1212
  linkedinUrl: z.string().optional(),
1192
1213
  fullName: z.string().optional(),
1193
1214
  firstName: z.string().optional(),
1194
1215
  lastName: z.string().optional(),
1195
- keywords: z.string().optional(),
1216
+ keywords: z
1217
+ .string()
1218
+ .optional()
1219
+ .describe(`Free-text query, e.g. "fintech, AI". ${FILTER_OPTIONS_NOTE}`),
1196
1220
  companyDomain: z.string().optional(),
1197
1221
  companyName: z.string().optional(),
1198
- titles: z.array(z.string()).max(50).optional(),
1199
- seniorities: z.array(z.string()).max(20).optional(),
1200
- jobFunctions: z.array(z.string()).max(20).optional(),
1201
- industries: z.array(z.string()).max(50).optional(),
1202
- locations: z.array(z.string()).max(50).optional(),
1203
- headcountRanges: z.array(z.string()).max(20).optional(),
1222
+ titles: z
1223
+ .array(searchFilterItem)
1224
+ .max(50)
1225
+ .optional()
1226
+ .describe(`Current job titles (free text). ${FILTER_OPTIONS_NOTE}`),
1227
+ pastTitles: z
1228
+ .array(searchFilterItem)
1229
+ .max(50)
1230
+ .optional()
1231
+ .describe("Past job titles (free text)."),
1232
+ personNames: z.array(searchFilterItem).max(20).optional(),
1233
+ seniorities: z
1234
+ .array(searchFilterItem)
1235
+ .max(20)
1236
+ .optional()
1237
+ .describe(
1238
+ "Exact enum: Owner, Founder, C-level, Partner, VP, Head, Director, Manager, Senior.",
1239
+ ),
1240
+ jobFunctions: z
1241
+ .array(searchFilterItem)
1242
+ .max(20)
1243
+ .optional()
1244
+ .describe(
1245
+ `Exact enum, e.g. Sales, Marketing, Software. ${FILTER_OPTIONS_NOTE}`,
1246
+ ),
1247
+ subFunctions: z
1248
+ .array(searchFilterItem)
1249
+ .max(50)
1250
+ .optional()
1251
+ .describe(
1252
+ `Exact enum, e.g. SDR/BDR, Demand Generation. ${FILTER_OPTIONS_NOTE}`,
1253
+ ),
1254
+ industries: z
1255
+ .array(searchFilterItem)
1256
+ .max(50)
1257
+ .optional()
1258
+ .describe(`Exact enum, e.g. Software Development. ${FILTER_OPTIONS_NOTE}`),
1259
+ locations: z
1260
+ .array(searchFilterItem)
1261
+ .max(50)
1262
+ .optional()
1263
+ .describe(
1264
+ `Person locations — cities, states, countries, or metros (free text). ${FILTER_OPTIONS_NOTE}`,
1265
+ ),
1266
+ headcountRanges: z
1267
+ .array(searchFilterItem)
1268
+ .max(20)
1269
+ .optional()
1270
+ .describe(
1271
+ "Exact buckets: 1-10, 11-50, 51-200, 201-500, 501-1000, 1001-5000, 5001-10000, 10001+.",
1272
+ ),
1273
+ companyNames: z.array(searchFilterItem).max(50).optional(),
1274
+ pastCompanyNames: z.array(searchFilterItem).max(50).optional(),
1275
+ companyDomains: z.array(searchFilterItem).max(50).optional(),
1276
+ companyHeadquarters: z
1277
+ .array(searchFilterItem)
1278
+ .max(50)
1279
+ .optional()
1280
+ .describe(
1281
+ `Company HQ locations — cities, states, countries, or metros (free text). ${FILTER_OPTIONS_NOTE}`,
1282
+ ),
1283
+ companyTypes: z
1284
+ .array(searchFilterItem)
1285
+ .max(10)
1286
+ .optional()
1287
+ .describe(
1288
+ "Exact enum: Partnership, Nonprofit, Educational, Privately Held, Public Company, Self-Owned, Self-Employed, Government Agency.",
1289
+ ),
1290
+ universities: z
1291
+ .array(searchFilterItem)
1292
+ .max(50)
1293
+ .optional()
1294
+ .describe(`Universities attended (free text). ${FILTER_OPTIONS_NOTE}`),
1295
+ skills: z
1296
+ .array(searchFilterItem)
1297
+ .max(50)
1298
+ .optional()
1299
+ .describe(`Profile skills (free text). ${FILTER_OPTIONS_NOTE}`),
1300
+ languages: z
1301
+ .array(searchFilterItem)
1302
+ .max(20)
1303
+ .optional()
1304
+ .describe(`Spoken languages, e.g. English, German. ${FILTER_OPTIONS_NOTE}`),
1305
+ yearsInRole: searchRange.optional(),
1306
+ yearsAtCompany: searchRange.optional(),
1307
+ daysSinceJobChange: searchRange.optional(),
1308
+ foundedYears: searchRange
1309
+ .optional()
1310
+ .describe("Company founded-year range, e.g. {min: 2015, max: 2024}."),
1204
1311
  exactMatch: z.boolean().optional(),
1205
1312
  limit: z.number().int().positive().max(5000).optional(),
1206
1313
  offset: z.number().int().nonnegative().optional(),
1207
1314
  pageToken: z
1208
1315
  .string()
1209
1316
  .optional()
1210
- .describe("Opaque token from a previous response's metadata.nextPageToken."),
1317
+ .describe(
1318
+ "Opaque token from a previous response's metadata.nextPageToken.",
1319
+ ),
1211
1320
  preferCache: z.boolean().optional(),
1212
1321
  });
1213
1322
 
@@ -1215,19 +1324,51 @@ const companySearch = z.object({
1215
1324
  domain: z.string().optional(),
1216
1325
  name: z.string().optional(),
1217
1326
  linkedinUrl: z.string().optional(),
1327
+ keywords: z.string().optional(),
1328
+ industries: z
1329
+ .array(z.string())
1330
+ .max(50)
1331
+ .optional()
1332
+ .describe(`Exact enum, e.g. Software Development. ${FILTER_OPTIONS_NOTE}`),
1333
+ locations: z
1334
+ .array(z.string())
1335
+ .max(50)
1336
+ .optional()
1337
+ .describe(
1338
+ `Cities, states, countries, or metros (free text). ${FILTER_OPTIONS_NOTE}`,
1339
+ ),
1340
+ headcountRanges: z
1341
+ .array(z.string())
1342
+ .max(20)
1343
+ .optional()
1344
+ .describe(
1345
+ "Exact buckets: 1-10, 11-50, 51-200, 201-500, 501-1000, 1001-5000, 5001-10000, 10001+.",
1346
+ ),
1218
1347
  exactMatch: z.boolean().optional(),
1219
1348
  limit: z.number().int().positive().max(5000).optional(),
1220
1349
  offset: z.number().int().nonnegative().optional(),
1221
1350
  pageToken: z
1222
1351
  .string()
1223
1352
  .optional()
1224
- .describe("Opaque token from a previous response's metadata.nextPageToken."),
1353
+ .describe(
1354
+ "Opaque token from a previous response's metadata.nextPageToken.",
1355
+ ),
1225
1356
  preferCache: z.boolean().optional(),
1226
1357
  });
1227
1358
 
1228
1359
  const jobsSearch = z.object({
1229
- titles: z.array(z.string()).max(50).optional(),
1230
- locations: z.array(z.string()).max(50).optional(),
1360
+ titles: z
1361
+ .array(z.string())
1362
+ .max(50)
1363
+ .optional()
1364
+ .describe(`Job titles (free text). ${FILTER_OPTIONS_NOTE}`),
1365
+ locations: z
1366
+ .array(z.string())
1367
+ .max(50)
1368
+ .optional()
1369
+ .describe(
1370
+ `Cities, states, countries, or metros (free text). ${FILTER_OPTIONS_NOTE}`,
1371
+ ),
1231
1372
  companyDomain: z.string().optional(),
1232
1373
  companyName: z.string().optional(),
1233
1374
  companyLinkedinUrl: z.string().optional(),
@@ -1237,7 +1378,9 @@ const jobsSearch = z.object({
1237
1378
  pageToken: z
1238
1379
  .string()
1239
1380
  .optional()
1240
- .describe("Opaque token from a previous response's metadata.nextPageToken."),
1381
+ .describe(
1382
+ "Opaque token from a previous response's metadata.nextPageToken.",
1383
+ ),
1241
1384
  preferCache: z.boolean().optional(),
1242
1385
  });
1243
1386
 
@@ -1450,7 +1593,7 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
1450
1593
  icon: z
1451
1594
  .string()
1452
1595
  .optional()
1453
- .describe("Lucide icon name, e.g. users, target."),
1596
+ .describe("Hugeicons (or legacy Lucide) icon name, e.g. user-multiple, target-01."),
1454
1597
  }),
1455
1598
  "crm.objects.update": z.object({
1456
1599
  name: z.string(),
@@ -3068,7 +3211,11 @@ export const requestExamples: Record<string, unknown> = {
3068
3211
  "web.brand": { input: { domain: "stripe.com" } },
3069
3212
  "web.sitemap": { input: { domain: "example.com", maxLinks: 1000 } },
3070
3213
  "web.crawl": {
3071
- input: { url: "https://example.com", maxPages: 15, useMainContentOnly: true },
3214
+ input: {
3215
+ url: "https://example.com",
3216
+ maxPages: 15,
3217
+ useMainContentOnly: true,
3218
+ },
3072
3219
  },
3073
3220
  "web.screenshot": {
3074
3221
  input: { directUrl: "https://example.com", fullScreenshot: false },
@@ -3103,12 +3250,14 @@ export const requestExamples: Record<string, unknown> = {
3103
3250
  "crm.people.search": {
3104
3251
  titles: ["Head of Sales"],
3105
3252
  industries: ["SaaS"],
3253
+ companyDomains: ["acme.com", "globex.com"],
3106
3254
  limit: 10,
3107
3255
  },
3108
3256
  "crm.companies.search": { domain: "example.com" },
3109
3257
  "enrichment.jobs.search": {
3110
3258
  titles: ["Account Executive"],
3111
3259
  locations: ["San Francisco"],
3260
+ companyDomains: ["acme.com", "globex.com"],
3112
3261
  limit: 50,
3113
3262
  },
3114
3263
  "enrichment.search.preview": { kind: "people", titles: ["VP Sales"] },
@@ -316,6 +316,81 @@ const rawApiOperations = [
316
316
  backend: convex("query", "functions/agentWorkspace:agentListArtifacts"),
317
317
  }),
318
318
 
319
+ // Lists (saved searches). Find results persist as named list artifacts;
320
+ // these ops give agents the same full management the sidebar has.
321
+ // CRM lists (saved views) are managed through the `crm.views.*` ops.
322
+ op({
323
+ id: "lists.list",
324
+ group: "lists",
325
+ summary:
326
+ "List saved searches (find-result lists) in the workspace, newest first.",
327
+ cli: "dench lists",
328
+ aliases: ["dench lists list"],
329
+ method: "GET",
330
+ path: "/lists",
331
+ requestSchema: noBody,
332
+ responseSchema: anyResponse,
333
+ backend: convex("query", "functions/workspaceArtifacts:list"),
334
+ }),
335
+ op({
336
+ id: "lists.get",
337
+ group: "lists",
338
+ summary: "Get one saved search / list by artifact id.",
339
+ cli: "dench lists show",
340
+ method: "GET",
341
+ path: "/lists/{artifactId}",
342
+ requestSchema: noBody,
343
+ responseSchema: anyResponse,
344
+ backend: convex("query", "functions/workspaceArtifacts:get"),
345
+ }),
346
+ op({
347
+ id: "lists.save",
348
+ group: "lists",
349
+ summary:
350
+ "Save find results as a named list (rows + filters become a workspace artifact).",
351
+ cli: 'dench leads find --save-as "Name"',
352
+ method: "POST",
353
+ path: "/lists",
354
+ requestSchema: anyObject,
355
+ responseSchema: anyResponse,
356
+ backend: convex("action", "functions/workspaceArtifacts:saveFind"),
357
+ }),
358
+ op({
359
+ id: "lists.update",
360
+ group: "lists",
361
+ summary:
362
+ "Update a saved list in place (title, filters, and/or replacement rows).",
363
+ cli: "dench lists update",
364
+ method: "PATCH",
365
+ path: "/lists/{artifactId}",
366
+ requestSchema: anyObject,
367
+ responseSchema: anyResponse,
368
+ backend: convex("action", "functions/workspaceArtifacts:updateFind"),
369
+ }),
370
+ op({
371
+ id: "lists.rename",
372
+ group: "lists",
373
+ summary: "Rename a saved search / list.",
374
+ cli: "dench lists rename",
375
+ method: "POST",
376
+ path: "/lists/{artifactId}/rename",
377
+ requestSchema: anyObject,
378
+ responseSchema: anyResponse,
379
+ backend: convex("mutation", "functions/workspaceArtifacts:rename"),
380
+ }),
381
+ op({
382
+ id: "lists.delete",
383
+ group: "lists",
384
+ summary:
385
+ "Delete a saved search / list, its exported CSV/JSONL files, and any linked watch.",
386
+ cli: "dench lists delete",
387
+ method: "DELETE",
388
+ path: "/lists/{artifactId}",
389
+ requestSchema: anyObject,
390
+ responseSchema: anyResponse,
391
+ backend: convex("mutation", "functions/workspaceArtifacts:remove"),
392
+ }),
393
+
319
394
  // Memory and approvals.
320
395
  op({
321
396
  id: "memory.search",
@@ -666,6 +741,18 @@ const rawApiOperations = [
666
741
  responseSchema: anyResponse,
667
742
  backend: convex("mutation", "functions/crm/objects:saveView"),
668
743
  }),
744
+ op({
745
+ id: "crm.views.rename",
746
+ group: "crm",
747
+ summary:
748
+ "Rename a saved view in place — keeps its filters, pins, and sidebar slot.",
749
+ cli: "dench crm views rename",
750
+ method: "POST",
751
+ path: "/crm/objects/{objectName}/views/{viewName}/rename",
752
+ requestSchema: anyObject,
753
+ responseSchema: anyResponse,
754
+ backend: convex("mutation", "functions/crm/objects:renameView"),
755
+ }),
669
756
  op({
670
757
  id: "crm.views.delete",
671
758
  group: "crm",
@@ -1357,7 +1444,8 @@ const rawApiOperations = [
1357
1444
  op({
1358
1445
  id: "crm.people.search",
1359
1446
  group: "crm",
1360
- summary: "Search for people through the enrichment gateway.",
1447
+ summary:
1448
+ "Search for people through the enrichment gateway. `companyDomains` takes up to 1000 domains (e.g. a resolved company list; `dench leads find --company-list <id|view>` resolves one for you) — the gateway fans large lists out as sequential vendor-safe chunks behind one `nextPageToken` stream.",
1361
1449
  cli: "dench crm people search",
1362
1450
  method: "POST",
1363
1451
  path: "/enrichment/people/search",
@@ -1390,7 +1478,8 @@ const rawApiOperations = [
1390
1478
  op({
1391
1479
  id: "enrichment.jobs.search",
1392
1480
  group: "crm",
1393
- summary: "Search job postings through Dench find.",
1481
+ summary:
1482
+ "Search job postings through Dench find. `companyDomains` takes up to 1000 domains (chunked automatically); `dench jobs find --company-list <id|view>` resolves a saved company list into it.",
1394
1483
  cli: "dench jobs find",
1395
1484
  method: "POST",
1396
1485
  path: "/enrichment/jobs/search",
@@ -1442,6 +1531,20 @@ const rawApiOperations = [
1442
1531
  responseSchema: anyResponse,
1443
1532
  backend: gateway("POST", "/enrichment/search/preview"),
1444
1533
  }),
1534
+ op({
1535
+ id: "enrichment.filters.options",
1536
+ group: "crm",
1537
+ summary:
1538
+ "List Lead Finder filter vocabularies: exact enum values (seniorities, functions, industries, headcount, company types, languages) and curated suggestions (titles, locations, company HQ, universities, skills, keywords).",
1539
+ cli: "dench leads filters",
1540
+ method: "GET",
1541
+ path: "/local/enrichment/filter-options",
1542
+ requestSchema: noBody,
1543
+ responseSchema: anyResponse,
1544
+ backend: local(
1545
+ "Catalogs are bundled with the CLI. Over HTTPS, fetch https://dench.com/references/find-filter-options.json instead.",
1546
+ ),
1547
+ }),
1445
1548
  op({
1446
1549
  id: "crm.templates.list",
1447
1550
  group: "crm",
@@ -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(
@@ -114,7 +114,11 @@ export type SearchJobsBody = {
114
114
  titles?: string[];
115
115
  locations?: string[];
116
116
  companyDomain?: string;
117
+ /** Multiple company domains (e.g. a resolved company list); the gateway
118
+ * fans lists past ~50 domains out as sequential vendor-safe chunks. */
119
+ companyDomains?: string[];
117
120
  companyName?: string;
121
+ companyNames?: string[];
118
122
  companyLinkedinUrl?: string;
119
123
  keywords?: string;
120
124
  postedAfter?: string;
@@ -1361,7 +1365,9 @@ export async function searchJobs(
1361
1365
  if (body.titles?.length) payload.titles = body.titles;
1362
1366
  if (body.locations?.length) payload.locations = body.locations;
1363
1367
  if (body.companyDomain) payload.companyDomain = body.companyDomain;
1368
+ if (body.companyDomains?.length) payload.companyDomains = body.companyDomains;
1364
1369
  if (body.companyName) payload.companyName = body.companyName;
1370
+ if (body.companyNames?.length) payload.companyNames = body.companyNames;
1365
1371
  if (body.companyLinkedinUrl)
1366
1372
  payload.companyLinkedinUrl = body.companyLinkedinUrl;
1367
1373
  if (body.keywords) payload.keywords = body.keywords;