@dench.com/cli 2.7.11-staging.102 → 2.8.1-staging.110

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
@@ -49,9 +49,6 @@ import {
49
49
  getRequiredFieldsForApolloPath,
50
50
  } from "./lib/crm-enrichment";
51
51
  import {
52
- type AviatoEnrichCompanyBody,
53
- type AviatoEnrichPersonBody,
54
- aviatoEnrichCompany,
55
52
  aviatoEnrichPerson,
56
53
  type EnrichCompanyBody,
57
54
  EnrichmentGatewayError,
@@ -61,14 +58,6 @@ import {
61
58
  enrichPerson,
62
59
  } from "./lib/enrichment-gateway";
63
60
 
64
- // Contact-info fields that Aviato does not return.
65
- const CONTACT_REQUIRED_FIELDS = ["phone", "email", "personal_email"] as const;
66
- type ContactRequiredField = (typeof CONTACT_REQUIRED_FIELDS)[number];
67
-
68
- function isContactField(field: string): field is ContactRequiredField {
69
- return (CONTACT_REQUIRED_FIELDS as readonly string[]).includes(field);
70
- }
71
-
72
61
  type JsonRecord = Record<string, unknown>;
73
62
 
74
63
  const CRM_BATCH_CHUNK_SIZE = 200;
@@ -2892,8 +2881,6 @@ type CrmEntryForEnrichment = {
2892
2881
  fields: Record<string, unknown>;
2893
2882
  };
2894
2883
 
2895
- type CliEnrichmentProvider = "fullenrich" | "aviato";
2896
-
2897
2884
  type EnrichmentTarget = {
2898
2885
  category: EnrichmentCategory;
2899
2886
  column: EnrichmentColumnDef;
@@ -2905,7 +2892,6 @@ type CellEnrichmentArgs = {
2905
2892
  objectName: string;
2906
2893
  entryId: string;
2907
2894
  fieldName: string;
2908
- provider?: CliEnrichmentProvider;
2909
2895
  category?: EnrichmentCategory;
2910
2896
  inputFieldName?: string;
2911
2897
  apolloPath?: string;
@@ -2925,16 +2911,6 @@ function parseCategoryFlag(
2925
2911
  throw new CrmCliError("--category must be people or company");
2926
2912
  }
2927
2913
 
2928
- function parseProviderFlag(
2929
- value: string | undefined,
2930
- ): CliEnrichmentProvider | undefined {
2931
- if (value === undefined) return undefined;
2932
- if (value === "fullenrich" || value === "aviato") return value;
2933
- throw new CrmCliError(
2934
- `--provider must be fullenrich or aviato; received "${value}"`,
2935
- );
2936
- }
2937
-
2938
2914
  function parsePositiveInt(
2939
2915
  flag: string,
2940
2916
  value: string | undefined,
@@ -3107,21 +3083,19 @@ async function resolveEnrichmentTarget(
3107
3083
  * chat-turn `enrich_person` / `enrich_company` / `enrich_person_basic` /
3108
3084
  * `enrich_company_basic` tools do — pass every structured identifier the
3109
3085
  * row has (email, firstName, lastName, linkedinUrl, domain, …) and let the
3110
- * shared gateway client (`enrichPerson`, `enrichCompany`,
3111
- * `aviatoEnrichPerson`, `aviatoEnrichCompany`) validate + dispatch.
3086
+ * shared gateway client (`enrichPerson`, `enrichCompany`) validate +
3087
+ * dispatch. The gateway owns the waterfall; the CLI never names a vendor.
3112
3088
  *
3113
3089
  * The previous implementation collapsed the entry to a single
3114
3090
  * `--input-field` value and called `enrichPersonByIdentifier`, which
3115
- * threw away the rest of the row and made FullEnrich fail with
3091
+ * threw away the rest of the row and made enrichment fail with
3116
3092
  * `missing_people_identifier` whenever the input field was just an
3117
3093
  * Email (no first/last name reached the gateway, so the validator
3118
- * rejected the request before any provider was called).
3094
+ * rejected the request before any lookup ran).
3119
3095
  */
3120
3096
  async function callEnrichmentGatewayForEntry(args: {
3121
3097
  target: EnrichmentTarget;
3122
3098
  entry: CrmEntryForEnrichment;
3123
- /** Explicit provider override. Defaults: people=fullenrich, company=aviato. */
3124
- provider?: CliEnrichmentProvider;
3125
3099
  gateway?: Pick<EnrichmentGatewayOptions, "apiKey" | "baseUrl">;
3126
3100
  }): Promise<Record<string, unknown>> {
3127
3101
  const { entry, target } = args;
@@ -3134,43 +3108,27 @@ async function callEnrichmentGatewayForEntry(args: {
3134
3108
  );
3135
3109
  }
3136
3110
  const isCompany = target.category === "company";
3137
- const resolvedProvider =
3138
- args.provider ?? (isCompany ? "aviato" : "fullenrich");
3139
-
3140
- if (resolvedProvider === "aviato") {
3141
- // Aviato does not return phone or email — surface a clear error
3142
- // before we waste a gateway round-trip.
3143
- const contactFields = target.requiredFields.filter(isContactField);
3144
- if (contactFields.length > 0) {
3145
- throw new CrmCliError(
3146
- `Aviato does not return phone or email; use --provider fullenrich for contact fields (${contactFields.join(", ")}).`,
3147
- );
3148
- }
3149
- if (isCompany) {
3150
- return aviatoEnrichCompany(
3151
- buildAviatoCompanyBodyFromEntry(entry),
3152
- args.gateway,
3153
- );
3154
- }
3155
- return aviatoEnrichPerson(
3156
- buildAviatoPersonBodyFromEntry(entry),
3157
- args.gateway,
3158
- );
3159
- }
3160
-
3161
- // FullEnrich path. Mirrors `enrichPersonStep` / `enrichCompanyStep` in
3162
- // src/workflows/enrichment-steps.ts: build a structured body, hand it
3163
- // to the same shared gateway client.
3164
3111
  if (isCompany) {
3165
3112
  return enrichCompany(
3166
3113
  buildCompanyBodyFromEntry(entry, target.requiredFields),
3167
3114
  args.gateway,
3168
3115
  );
3169
3116
  }
3170
- return enrichPerson(
3171
- buildPersonBodyFromEntry(entry, target.requiredFields),
3172
- args.gateway,
3173
- );
3117
+ const personBody = buildPersonBodyFromEntry(entry, target.requiredFields);
3118
+ // Contact cells stay on /person/contact. A LinkedIn URL for a profile
3119
+ // field (name, title, headline) uses /person so the gateway waterfall
3120
+ // can fill graph data without a contact job.
3121
+ const isContactField = Boolean(target.column.enrichFields?.length);
3122
+ if (!isContactField && personBody.linkedinUrl) {
3123
+ return aviatoEnrichPerson(
3124
+ {
3125
+ linkedinUrl: personBody.linkedinUrl,
3126
+ email: personBody.email,
3127
+ },
3128
+ args.gateway,
3129
+ );
3130
+ }
3131
+ return enrichPerson(personBody, args.gateway);
3174
3132
  }
3175
3133
 
3176
3134
  // ── Body builders ────────────────────────────────────────────────────────
@@ -3275,24 +3233,6 @@ export function buildCompanyBodyFromEntry(
3275
3233
  };
3276
3234
  }
3277
3235
 
3278
- export function buildAviatoPersonBodyFromEntry(
3279
- entry: CrmEntryForEnrichment,
3280
- ): AviatoEnrichPersonBody {
3281
- return {
3282
- linkedinUrl: readEntryField(entry, PEOPLE_LINKEDIN_FIELDS),
3283
- email: readEntryField(entry, PEOPLE_EMAIL_FIELDS),
3284
- };
3285
- }
3286
-
3287
- export function buildAviatoCompanyBodyFromEntry(
3288
- entry: CrmEntryForEnrichment,
3289
- ): AviatoEnrichCompanyBody {
3290
- return {
3291
- website: readEntryField(entry, COMPANY_WEBSITE_FIELDS),
3292
- linkedinUrl: readEntryField(entry, COMPANY_LINKEDIN_FIELDS),
3293
- };
3294
- }
3295
-
3296
3236
  async function enrichOneEntry(
3297
3237
  ctx: CrmCliContext,
3298
3238
  args: Omit<CellEnrichmentArgs, "entryId"> & {
@@ -3305,7 +3245,6 @@ async function enrichOneEntry(
3305
3245
  const payload = await callEnrichmentGatewayForEntry({
3306
3246
  target: args.target,
3307
3247
  entry: args.entry,
3308
- provider: args.provider,
3309
3248
  gateway: ctx.enrichmentGateway,
3310
3249
  });
3311
3250
  value = extractEnrichmentValue(payload, args.target.column);
@@ -3341,7 +3280,7 @@ async function enrichOneEntry(
3341
3280
  entryId: args.entry.id,
3342
3281
  fieldName: args.fieldName,
3343
3282
  value,
3344
- provider: args.provider ?? "gateway",
3283
+ provider: "gateway",
3345
3284
  overwrite: args.overwrite,
3346
3285
  },
3347
3286
  )) as Record<string, unknown>;
@@ -3458,7 +3397,6 @@ async function runPeopleCommand(ctx: CrmCliContext): Promise<void> {
3458
3397
  }
3459
3398
  case "enrich": {
3460
3399
  const entryId = shift(ctx.args, "person entry id");
3461
- const provider = parseProviderFlag(getFlag(ctx.args, "--provider"));
3462
3400
  const fieldName = getFlag(ctx.args, "--field") ?? "LinkedIn URL";
3463
3401
  const inputFieldName = getFlag(ctx.args, "--input-field");
3464
3402
  const overwrite = hasFlag(ctx.args, "--overwrite");
@@ -3469,7 +3407,6 @@ async function runPeopleCommand(ctx: CrmCliContext): Promise<void> {
3469
3407
  objectName: "people",
3470
3408
  entryId,
3471
3409
  fieldName,
3472
- provider,
3473
3410
  category: "people",
3474
3411
  inputFieldName,
3475
3412
  overwrite,
@@ -3523,7 +3460,6 @@ async function runCompaniesCommand(ctx: CrmCliContext): Promise<void> {
3523
3460
  }
3524
3461
  case "enrich": {
3525
3462
  const entryId = shift(ctx.args, "company entry id");
3526
- const provider = parseProviderFlag(getFlag(ctx.args, "--provider"));
3527
3463
  const fieldName = getFlag(ctx.args, "--field") ?? "Company Name";
3528
3464
  const inputFieldName = getFlag(ctx.args, "--input-field") ?? "Domain";
3529
3465
  const overwrite = hasFlag(ctx.args, "--overwrite");
@@ -3534,7 +3470,6 @@ async function runCompaniesCommand(ctx: CrmCliContext): Promise<void> {
3534
3470
  objectName: "company",
3535
3471
  entryId,
3536
3472
  fieldName,
3537
- provider,
3538
3473
  category: "company",
3539
3474
  inputFieldName,
3540
3475
  overwrite,
@@ -3556,7 +3491,6 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
3556
3491
  const objectName = shift(ctx.args, "object name");
3557
3492
  const entryId = shift(ctx.args, "entry id");
3558
3493
  const fieldName = shift(ctx.args, "field name");
3559
- const provider = parseProviderFlag(getFlag(ctx.args, "--provider"));
3560
3494
  const inputFieldName = getFlag(ctx.args, "--input-field");
3561
3495
  const category = parseCategoryFlag(getFlag(ctx.args, "--category"));
3562
3496
  const apolloPath = getFlag(ctx.args, "--apollo-path");
@@ -3568,7 +3502,6 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
3568
3502
  objectName,
3569
3503
  entryId,
3570
3504
  fieldName,
3571
- provider,
3572
3505
  category,
3573
3506
  inputFieldName,
3574
3507
  apolloPath,
@@ -3581,7 +3514,6 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
3581
3514
  const objectName = shift(ctx.args, "object name");
3582
3515
  const fieldName = getFlag(ctx.args, "--field");
3583
3516
  const missingOnly = !hasFlag(ctx.args, "--all");
3584
- const provider = parseProviderFlag(getFlag(ctx.args, "--provider"));
3585
3517
  const inputFieldName = getFlag(ctx.args, "--input-field");
3586
3518
  const category = parseCategoryFlag(getFlag(ctx.args, "--category"));
3587
3519
  const apolloPath = getFlag(ctx.args, "--apollo-path");
@@ -3590,7 +3522,7 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
3590
3522
  assertNoUnknownFlags(ctx.args, "crm enrich object");
3591
3523
  if (!fieldName) {
3592
3524
  throw new CrmCliError(
3593
- "dench crm enrich object <name> --field <field> [--input-field <field>] [--category people|company] [--apollo-path <path>] [--limit N] [--provider ...] [--all] [--overwrite]",
3525
+ "dench crm enrich object <name> --field <field> [--input-field <field>] [--category people|company] [--apollo-path <path>] [--limit N] [--all] [--overwrite]",
3594
3526
  );
3595
3527
  }
3596
3528
  out(
@@ -3598,7 +3530,6 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
3598
3530
  await enrichObjectThroughGateway(ctx, {
3599
3531
  objectName,
3600
3532
  fieldName,
3601
- provider,
3602
3533
  category,
3603
3534
  inputFieldName,
3604
3535
  apolloPath,
package/dench.ts CHANGED
@@ -471,6 +471,12 @@ Usage:
471
471
  dench memory search "query" [--limit 8] [--json]
472
472
  dench memory save <key> "text" [--kind fact|decision|preference|goal|tool_note|other] [--tags a,b] [--sensitivity normal|broad|sensitive] [--scope user|org] [--json]
473
473
  dench artifacts [--limit 25] [--json]
474
+ dench artifacts list [--limit 25] [--json]
475
+ dench artifacts show <id> [--json]
476
+ dench artifacts import <id> --to lead|company|job_posting [--view "Q3 outbound"] [--pin] [--json]
477
+ dench leads find --titles "VP Sales" [--locations "San Francisco"] [--limit 500] [--page-token <token>] [--json]
478
+ dench companies find --name "Acme" [--domain acme.com] [--keywords fintech] [--limit 500] [--page-token <token>] [--json]
479
+ dench jobs find --titles "Account Executive" [--locations "San Francisco"] [--limit 500] [--page-token <token>] [--json]
474
480
  dench suggested-work [--json]
475
481
  dench approval request "message" [--json]
476
482
  dench approval approve <approvalId> [--evidence "User said yes in chat"] [--json]
@@ -737,9 +743,37 @@ function artifactsHelp() {
737
743
 
738
744
  Usage:
739
745
  dench artifacts [--limit 25] [--json]
746
+ dench artifacts list [--limit 25] [--json]
747
+ dench artifacts show <id> [--json]
748
+ dench artifacts import <id> --to lead|company|job_posting [--view "Q3 outbound"] [--pin] [--json]
740
749
  dench suggested-work [--json]
741
750
 
742
- Artifacts are durable outputs for human or agent review.
751
+ \`list\` / \`show\` / \`import\` operate on find results. Bare \`dench artifacts\`
752
+ lists agent suggested-work artifacts.
753
+ `);
754
+ }
755
+
756
+ function leadsHelp() {
757
+ console.log(`Dench leads
758
+
759
+ Usage:
760
+ dench leads find --titles "VP Sales" --locations "San Francisco" [--company-domain acme.com] [--keywords fintech] [--limit 500] [--page-token <token>] [--json]
761
+ `);
762
+ }
763
+
764
+ function companiesHelp() {
765
+ console.log(`Dench companies
766
+
767
+ Usage:
768
+ dench companies find --name "Acme" [--domain acme.com] [--keywords fintech] [--industries SaaS] [--locations "San Francisco"] [--limit 500] [--page-token <token>] [--json]
769
+ `);
770
+ }
771
+
772
+ function jobsHelp() {
773
+ console.log(`Dench jobs
774
+
775
+ Usage:
776
+ dench jobs find --titles "Account Executive" --locations "San Francisco" [--company-domain acme.com] [--keywords remote] [--limit 500] [--page-token <token>] [--json]
743
777
  `);
744
778
  }
745
779
 
@@ -2528,6 +2562,7 @@ function stripRuntimeFlags(args: string[]) {
2528
2562
  "--prod",
2529
2563
  "--staging",
2530
2564
  "--use-dev-env",
2565
+ "--json",
2531
2566
  ]);
2532
2567
  const valueFlags = new Set(["--convex-url", "--host", "--backend"]);
2533
2568
  const stripped: string[] = [];
@@ -2559,12 +2594,50 @@ function requireAuthenticatedRuntime(
2559
2594
  throw loginRequiredError(command);
2560
2595
  }
2561
2596
 
2597
+ async function resolveFindCommandAuth(command: string) {
2598
+ const runtime = await getRuntime();
2599
+ if (runtime.mode === "dev") {
2600
+ const gatewayAuth = await resolveGatewayCommandAuth(
2601
+ runtime,
2602
+ `${command} find`,
2603
+ );
2604
+ return {
2605
+ convex: runtime.client,
2606
+ sessionToken: encodeDevCrmSessionToken(runtime.workspaceArgs),
2607
+ enrichmentGateway: {
2608
+ apiKey: gatewayAuth.bearerToken,
2609
+ baseUrl: gatewayAuth.gatewayBaseUrl,
2610
+ },
2611
+ };
2612
+ }
2613
+ const authedRuntime = requireAuthenticatedRuntime(runtime, command);
2614
+ const gatewayAuth = await resolveGatewayCommandAuth(
2615
+ authedRuntime,
2616
+ `${command} find`,
2617
+ );
2618
+ return {
2619
+ convex: authedRuntime.client,
2620
+ sessionToken: authedRuntime.sessionToken,
2621
+ enrichmentGateway: {
2622
+ apiKey: gatewayAuth.bearerToken,
2623
+ baseUrl: gatewayAuth.gatewayBaseUrl,
2624
+ },
2625
+ };
2626
+ }
2627
+
2562
2628
  type GatewayCommandAuth = {
2563
2629
  bearerToken: string;
2564
2630
  gatewayBaseUrl?: string;
2565
2631
  gatewayHeaders?: Record<string, string>;
2566
2632
  };
2567
2633
 
2634
+ function envGatewayBaseUrlOverride(): string | undefined {
2635
+ const explicit =
2636
+ process.env.DENCH_GATEWAY_URL?.trim() || process.env.GATEWAY_URL?.trim();
2637
+ if (!explicit) return undefined;
2638
+ return explicit.replace(/\/+$/, "").replace(/\/v1$/, "");
2639
+ }
2640
+
2568
2641
  function gatewayBaseUrlForCli(value: unknown): string | undefined {
2569
2642
  if (typeof value !== "string") return undefined;
2570
2643
  const trimmed = value.trim().replace(/\/+$/, "");
@@ -2632,7 +2705,10 @@ async function resolveGatewayCommandAuth(
2632
2705
  command: string,
2633
2706
  ): Promise<GatewayCommandAuth> {
2634
2707
  if (runtime.mode === "apiKey") {
2635
- return { bearerToken: runtime.sessionToken };
2708
+ return {
2709
+ bearerToken: runtime.sessionToken,
2710
+ gatewayBaseUrl: envGatewayBaseUrlOverride(),
2711
+ };
2636
2712
  }
2637
2713
 
2638
2714
  if (runtime.mode === "session") {
@@ -2658,7 +2734,8 @@ async function resolveGatewayCommandAuth(
2658
2734
  }
2659
2735
  return {
2660
2736
  bearerToken: gatewayKey,
2661
- gatewayBaseUrl: gatewayBaseUrlForCli(body.gatewayBaseUrl),
2737
+ gatewayBaseUrl:
2738
+ envGatewayBaseUrlOverride() ?? gatewayBaseUrlForCli(body.gatewayBaseUrl),
2662
2739
  gatewayHeaders:
2663
2740
  body.gatewayHeaders &&
2664
2741
  typeof body.gatewayHeaders === "object" &&
@@ -2669,7 +2746,12 @@ async function resolveGatewayCommandAuth(
2669
2746
  }
2670
2747
 
2671
2748
  const envApiKey = process.env.DENCH_API_KEY?.trim();
2672
- if (envApiKey) return { bearerToken: envApiKey };
2749
+ if (envApiKey) {
2750
+ return {
2751
+ bearerToken: envApiKey,
2752
+ gatewayBaseUrl: envGatewayBaseUrlOverride(),
2753
+ };
2754
+ }
2673
2755
  throw loginRequiredError(command);
2674
2756
  }
2675
2757
 
@@ -4169,6 +4251,21 @@ async function main() {
4169
4251
  return;
4170
4252
  }
4171
4253
 
4254
+ if (command === "leads" && (subcommand === "help" || hasFlag("--help"))) {
4255
+ leadsHelp();
4256
+ return;
4257
+ }
4258
+
4259
+ if (command === "companies" && (subcommand === "help" || hasFlag("--help"))) {
4260
+ companiesHelp();
4261
+ return;
4262
+ }
4263
+
4264
+ if (command === "jobs" && (subcommand === "help" || hasFlag("--help"))) {
4265
+ jobsHelp();
4266
+ return;
4267
+ }
4268
+
4172
4269
  if (command === "status" && hasFlag("--help")) {
4173
4270
  statusHelp();
4174
4271
  return;
@@ -4249,6 +4346,53 @@ async function main() {
4249
4346
  return;
4250
4347
  }
4251
4348
 
4349
+ if (command === "leads") {
4350
+ const { runLeadsCommand, assertLeadsFindArgs } = await import("./leads");
4351
+ const leadArgs = stripRuntimeFlags(args.slice(args.indexOf("leads") + 1));
4352
+ assertLeadsFindArgs(leadArgs);
4353
+ const auth = await resolveFindCommandAuth("dench leads");
4354
+ await runLeadsCommand({
4355
+ convex: auth.convex,
4356
+ args: leadArgs,
4357
+ jsonOutput: json,
4358
+ sessionToken: auth.sessionToken,
4359
+ enrichmentGateway: auth.enrichmentGateway,
4360
+ });
4361
+ return;
4362
+ }
4363
+
4364
+ if (command === "companies") {
4365
+ const { runCompaniesCommand, assertCompaniesFindArgs } = await import("./leads");
4366
+ const companyArgs = stripRuntimeFlags(
4367
+ args.slice(args.indexOf("companies") + 1),
4368
+ );
4369
+ assertCompaniesFindArgs(companyArgs);
4370
+ const auth = await resolveFindCommandAuth("dench companies");
4371
+ await runCompaniesCommand({
4372
+ convex: auth.convex,
4373
+ args: companyArgs,
4374
+ jsonOutput: json,
4375
+ sessionToken: auth.sessionToken,
4376
+ enrichmentGateway: auth.enrichmentGateway,
4377
+ });
4378
+ return;
4379
+ }
4380
+
4381
+ if (command === "jobs") {
4382
+ const { runJobsCommand, assertJobsFindArgs } = await import("./leads");
4383
+ const jobArgs = stripRuntimeFlags(args.slice(args.indexOf("jobs") + 1));
4384
+ assertJobsFindArgs(jobArgs);
4385
+ const auth = await resolveFindCommandAuth("dench jobs");
4386
+ await runJobsCommand({
4387
+ convex: auth.convex,
4388
+ args: jobArgs,
4389
+ jsonOutput: json,
4390
+ sessionToken: auth.sessionToken,
4391
+ enrichmentGateway: auth.enrichmentGateway,
4392
+ });
4393
+ return;
4394
+ }
4395
+
4252
4396
  if (command === "members") {
4253
4397
  // Top-level convenience surface for the same Convex query that
4254
4398
  // `dench crm members list` calls. Lives here (rather than only
@@ -4830,6 +4974,23 @@ async function main() {
4830
4974
  }
4831
4975
 
4832
4976
  if (command === "artifacts") {
4977
+ const runtime = await getRuntime();
4978
+ const artifactArgs = stripRuntimeFlags(
4979
+ args.slice(args.indexOf("artifacts") + 1),
4980
+ );
4981
+ if (
4982
+ runtime.mode === "session" ||
4983
+ runtime.mode === "apiKey"
4984
+ ) {
4985
+ const { runWorkspaceArtifactsCommand } = await import("./leads");
4986
+ const handled = await runWorkspaceArtifactsCommand({
4987
+ convex: runtime.client,
4988
+ args: artifactArgs,
4989
+ jsonOutput: json,
4990
+ sessionToken: runtime.sessionToken,
4991
+ });
4992
+ if (handled) return;
4993
+ }
4833
4994
  print(await listArtifacts(runtime, parseNumberOption("--limit")));
4834
4995
  return;
4835
4996
  }
@@ -949,15 +949,30 @@ const toolRunResponse = z
949
949
 
950
950
  const enrichmentMetadata = z
951
951
  .object({
952
- total: z.number().optional(),
952
+ total: z
953
+ .number()
954
+ .optional()
955
+ .describe(
956
+ "Total matches for the search. Stable across pages; absent when only an estimate exists.",
957
+ ),
958
+ totalIsEstimate: z
959
+ .boolean()
960
+ .optional()
961
+ .describe("True when the total is a lower bound rather than exact."),
953
962
  offset: z.number().optional(),
963
+ nextPageToken: z
964
+ .string()
965
+ .optional()
966
+ .describe(
967
+ "Opaque token for the next page. Pass it back as pageToken; absent on the last page.",
968
+ ),
954
969
  })
955
970
  .passthrough()
956
971
  .optional();
957
972
 
958
973
  const enrichedPeopleResponse = z
959
974
  .object({
960
- source: z.enum(["fullenrich", "cache"]),
975
+ source: z.enum(["live", "cache"]),
961
976
  people: z
962
977
  .array(
963
978
  z
@@ -985,7 +1000,7 @@ const enrichedPeopleResponse = z
985
1000
 
986
1001
  const enrichedCompaniesResponse = z
987
1002
  .object({
988
- source: z.enum(["fullenrich", "cache"]),
1003
+ source: z.enum(["live", "cache"]),
989
1004
  companies: z
990
1005
  .array(
991
1006
  z
@@ -1187,8 +1202,12 @@ const peopleSearch = z.object({
1187
1202
  locations: z.array(z.string()).max(50).optional(),
1188
1203
  headcountRanges: z.array(z.string()).max(20).optional(),
1189
1204
  exactMatch: z.boolean().optional(),
1190
- limit: z.number().int().positive().max(100).optional(),
1205
+ limit: z.number().int().positive().max(5000).optional(),
1191
1206
  offset: z.number().int().nonnegative().optional(),
1207
+ pageToken: z
1208
+ .string()
1209
+ .optional()
1210
+ .describe("Opaque token from a previous response's metadata.nextPageToken."),
1192
1211
  preferCache: z.boolean().optional(),
1193
1212
  });
1194
1213
 
@@ -1197,11 +1216,52 @@ const companySearch = z.object({
1197
1216
  name: z.string().optional(),
1198
1217
  linkedinUrl: z.string().optional(),
1199
1218
  exactMatch: z.boolean().optional(),
1200
- limit: z.number().int().positive().max(100).optional(),
1219
+ limit: z.number().int().positive().max(5000).optional(),
1201
1220
  offset: z.number().int().nonnegative().optional(),
1221
+ pageToken: z
1222
+ .string()
1223
+ .optional()
1224
+ .describe("Opaque token from a previous response's metadata.nextPageToken."),
1202
1225
  preferCache: z.boolean().optional(),
1203
1226
  });
1204
1227
 
1228
+ const jobsSearch = z.object({
1229
+ titles: z.array(z.string()).max(50).optional(),
1230
+ locations: z.array(z.string()).max(50).optional(),
1231
+ companyDomain: z.string().optional(),
1232
+ companyName: z.string().optional(),
1233
+ companyLinkedinUrl: z.string().optional(),
1234
+ keywords: z.string().optional(),
1235
+ postedAfter: z.string().optional(),
1236
+ limit: z.number().int().positive().max(5000).optional(),
1237
+ pageToken: z
1238
+ .string()
1239
+ .optional()
1240
+ .describe("Opaque token from a previous response's metadata.nextPageToken."),
1241
+ preferCache: z.boolean().optional(),
1242
+ });
1243
+
1244
+ const jobsSearchResponse = z
1245
+ .object({
1246
+ source: z.enum(["live", "cache"]),
1247
+ jobs: z
1248
+ .array(
1249
+ z
1250
+ .object({
1251
+ title: z.string().optional(),
1252
+ company: z.string().optional(),
1253
+ location: z.string().optional(),
1254
+ url: z.string().optional(),
1255
+ workplaceType: z.string().optional(),
1256
+ postedAt: z.string().optional(),
1257
+ })
1258
+ .passthrough(),
1259
+ )
1260
+ .describe("Job postings in the canonical job_posting shape."),
1261
+ metadata: enrichmentMetadata,
1262
+ })
1263
+ .passthrough();
1264
+
1205
1265
  // ---------------------------------------------------------------------------
1206
1266
  // Request schemas keyed by operation id
1207
1267
  // ---------------------------------------------------------------------------
@@ -1764,12 +1824,10 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
1764
1824
  objectName: z.string(),
1765
1825
  entryId: z.string(),
1766
1826
  fieldName: z.string(),
1767
- provider: z.string().optional().describe("Defaults to auto."),
1768
1827
  }),
1769
1828
  "crm.enrich.object": z.object({
1770
1829
  objectName: z.string(),
1771
1830
  fieldName: z.string(),
1772
- provider: z.string().optional(),
1773
1831
  missingOnly: z
1774
1832
  .boolean()
1775
1833
  .optional()
@@ -1777,6 +1835,31 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
1777
1835
  }),
1778
1836
  "crm.people.search": peopleSearch,
1779
1837
  "crm.companies.search": companySearch,
1838
+ "enrichment.jobs.search": jobsSearch,
1839
+ "enrichment.search.preview": z
1840
+ .object({
1841
+ kind: z.enum(["people", "companies", "jobs"]).optional(),
1842
+ })
1843
+ .passthrough(),
1844
+ "enrichment.person": z
1845
+ .object({
1846
+ linkedinURL: z.string().optional(),
1847
+ linkedinID: z.string().optional(),
1848
+ email: z.string().optional(),
1849
+ })
1850
+ .passthrough(),
1851
+ "enrichment.company": z
1852
+ .object({
1853
+ website: z.string().optional(),
1854
+ linkedinURL: z.string().optional(),
1855
+ linkedinID: z.string().optional(),
1856
+ })
1857
+ .passthrough(),
1858
+ "enrichment.person.contact": z
1859
+ .object({
1860
+ contacts: z.array(z.record(z.string(), z.unknown())).min(1),
1861
+ })
1862
+ .passthrough(),
1780
1863
 
1781
1864
  // Email (sender identities, raw sends, templates, campaigns)
1782
1865
  "email.identity.verify": z.object({
@@ -2485,15 +2568,31 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
2485
2568
  }),
2486
2569
  "crm.enrich.cell": z.object({
2487
2570
  enqueued: z.boolean(),
2488
- provider: z.string(),
2489
2571
  hint: z.string(),
2490
2572
  }),
2491
2573
  "crm.enrich.object": z.object({
2492
2574
  enqueued: z.number().describe("Number of cells queued for enrichment."),
2493
- provider: z.string(),
2494
2575
  }),
2495
2576
  "crm.people.search": enrichedPeopleResponse,
2496
2577
  "crm.companies.search": enrichedCompaniesResponse,
2578
+ "enrichment.jobs.search": jobsSearchResponse,
2579
+ "enrichment.search.preview": z.object({
2580
+ count: z.number(),
2581
+ source: z.enum(["live", "cache"]).optional(),
2582
+ }),
2583
+ "enrichment.person": z
2584
+ .object({
2585
+ source: z.enum(["live", "cache"]),
2586
+ data: z.unknown().nullable(),
2587
+ })
2588
+ .passthrough(),
2589
+ "enrichment.company": z
2590
+ .object({
2591
+ source: z.enum(["live", "cache"]),
2592
+ data: z.unknown().nullable(),
2593
+ })
2594
+ .passthrough(),
2595
+ "enrichment.person.contact": z.object({}).passthrough(),
2497
2596
 
2498
2597
  // Email
2499
2598
  "email.identity.verify": z
@@ -3007,6 +3106,19 @@ export const requestExamples: Record<string, unknown> = {
3007
3106
  limit: 10,
3008
3107
  },
3009
3108
  "crm.companies.search": { domain: "example.com" },
3109
+ "enrichment.jobs.search": {
3110
+ titles: ["Account Executive"],
3111
+ locations: ["San Francisco"],
3112
+ limit: 50,
3113
+ },
3114
+ "enrichment.search.preview": { kind: "people", titles: ["VP Sales"] },
3115
+ "enrichment.person": {
3116
+ linkedinURL: "https://www.linkedin.com/in/janedoe",
3117
+ },
3118
+ "enrichment.company": { website: "acme.com" },
3119
+ "enrichment.person.contact": {
3120
+ contacts: [{ linkedinUrl: "https://www.linkedin.com/in/janedoe" }],
3121
+ },
3010
3122
  "email.identity.verify": { type: "email", identity: "founder@example.com" },
3011
3123
  "email.identity.create": {
3012
3124
  type: "domain",
@@ -3624,12 +3736,11 @@ export const responseExamples: Record<string, unknown> = {
3624
3736
  },
3625
3737
  "crm.enrich.cell": {
3626
3738
  enqueued: true,
3627
- provider: "auto",
3628
- hint: "Enrichment queued; the cell fills in once the provider responds.",
3739
+ hint: "Enrichment queued; the cell fills in once search responds.",
3629
3740
  },
3630
- "crm.enrich.object": { enqueued: 37, provider: "auto" },
3741
+ "crm.enrich.object": { enqueued: 37 },
3631
3742
  "crm.people.search": {
3632
- source: "fullenrich",
3743
+ source: "live",
3633
3744
  people: [
3634
3745
  {
3635
3746
  fullName: "Jane Doe",
@@ -3646,7 +3757,7 @@ export const responseExamples: Record<string, unknown> = {
3646
3757
  metadata: { total: 1, offset: 0 },
3647
3758
  },
3648
3759
  "crm.companies.search": {
3649
- source: "fullenrich",
3760
+ source: "live",
3650
3761
  companies: [
3651
3762
  {
3652
3763
  name: "Acme",
@@ -3660,6 +3771,28 @@ export const responseExamples: Record<string, unknown> = {
3660
3771
  creditsCharged: 1,
3661
3772
  metadata: { total: 1, offset: 0 },
3662
3773
  },
3774
+ "enrichment.jobs.search": {
3775
+ source: "live",
3776
+ jobs: [
3777
+ {
3778
+ title: "Account Executive",
3779
+ company: "Acme",
3780
+ location: "San Francisco",
3781
+ url: "https://example.com/jobs/ae",
3782
+ workplaceType: "hybrid",
3783
+ },
3784
+ ],
3785
+ },
3786
+ "enrichment.search.preview": { count: 247, source: "live" },
3787
+ "enrichment.person": {
3788
+ source: "live",
3789
+ data: { fullName: "Jane Doe", linkedinID: "janedoe" },
3790
+ },
3791
+ "enrichment.company": {
3792
+ source: "live",
3793
+ data: { name: "Acme", website: "acme.com" },
3794
+ },
3795
+ "enrichment.person.contact": { enrichmentId: "job_123", status: "queued" },
3663
3796
  "email.identity.verify": {
3664
3797
  ok: true,
3665
3798
  identity: "example.com",
@@ -1360,7 +1360,7 @@ const rawApiOperations = [
1360
1360
  summary: "Search for people through the enrichment gateway.",
1361
1361
  cli: "dench crm people search",
1362
1362
  method: "POST",
1363
- path: "/crm/people/search",
1363
+ path: "/enrichment/people/search",
1364
1364
  requestSchema: anyObject,
1365
1365
  responseSchema: anyResponse,
1366
1366
  backend: gateway("POST", "/enrichment/people/search"),
@@ -1371,11 +1371,77 @@ const rawApiOperations = [
1371
1371
  summary: "Search for companies through the enrichment gateway.",
1372
1372
  cli: "dench crm companies search",
1373
1373
  method: "POST",
1374
- path: "/crm/companies/search",
1374
+ path: "/enrichment/company/search",
1375
1375
  requestSchema: anyObject,
1376
1376
  responseSchema: anyResponse,
1377
1377
  backend: gateway("POST", "/enrichment/company/search"),
1378
1378
  }),
1379
+ op({
1380
+ id: "enrichment.companies.search",
1381
+ group: "crm",
1382
+ summary: "Search companies through Dench find.",
1383
+ cli: "dench companies find",
1384
+ method: "POST",
1385
+ path: "/enrichment/company/search",
1386
+ requestSchema: anyObject,
1387
+ responseSchema: anyResponse,
1388
+ backend: gateway("POST", "/enrichment/company/search"),
1389
+ }),
1390
+ op({
1391
+ id: "enrichment.jobs.search",
1392
+ group: "crm",
1393
+ summary: "Search job postings through Dench find.",
1394
+ cli: "dench jobs find",
1395
+ method: "POST",
1396
+ path: "/enrichment/jobs/search",
1397
+ requestSchema: anyObject,
1398
+ responseSchema: anyResponse,
1399
+ backend: gateway("POST", "/enrichment/jobs/search"),
1400
+ }),
1401
+ op({
1402
+ id: "enrichment.person",
1403
+ group: "crm",
1404
+ summary: "Look up one person profile.",
1405
+ cli: "dench crm enrich cell",
1406
+ method: "POST",
1407
+ path: "/enrichment/person",
1408
+ requestSchema: anyObject,
1409
+ responseSchema: anyResponse,
1410
+ backend: gateway("POST", "/enrichment/person"),
1411
+ }),
1412
+ op({
1413
+ id: "enrichment.company",
1414
+ group: "crm",
1415
+ summary: "Look up one company profile.",
1416
+ cli: "dench crm enrich cell",
1417
+ method: "POST",
1418
+ path: "/enrichment/company",
1419
+ requestSchema: anyObject,
1420
+ responseSchema: anyResponse,
1421
+ backend: gateway("POST", "/enrichment/company"),
1422
+ }),
1423
+ op({
1424
+ id: "enrichment.person.contact",
1425
+ group: "crm",
1426
+ summary: "Look up verified emails and phones for people.",
1427
+ cli: "dench crm enrich cell",
1428
+ method: "POST",
1429
+ path: "/enrichment/person/contact",
1430
+ requestSchema: anyObject,
1431
+ responseSchema: anyResponse,
1432
+ backend: gateway("POST", "/enrichment/person/contact"),
1433
+ }),
1434
+ op({
1435
+ id: "enrichment.search.preview",
1436
+ group: "crm",
1437
+ summary: "Preview how many records a find would return.",
1438
+ cli: "dench leads find --limit 1",
1439
+ method: "POST",
1440
+ path: "/enrichment/search/preview",
1441
+ requestSchema: anyObject,
1442
+ responseSchema: anyResponse,
1443
+ backend: gateway("POST", "/enrichment/search/preview"),
1444
+ }),
1379
1445
  op({
1380
1446
  id: "crm.templates.list",
1381
1447
  group: "crm",
@@ -27,6 +27,10 @@ export type EnrichmentFieldType =
27
27
  | "tags"
28
28
  | "action";
29
29
 
30
+ /** Contact lookups the gateway can run for a column (person/contact only).
31
+ * Mirrors `EnrichField` in src/lib/workspace/enrichment-columns.ts. */
32
+ export type EnrichField = "work_emails" | "personal_emails" | "phones";
33
+
30
34
  export type EnrichmentColumnDef = {
31
35
  label: string;
32
36
  key: string;
@@ -40,6 +44,9 @@ export type EnrichmentColumnDef = {
40
44
  requiredFields: string[];
41
45
  /** Additional dot-paths to try when apolloPath does not resolve. */
42
46
  extractionFallbacks?: string[];
47
+ /** Contact enrich_fields to request — present only on contact columns
48
+ * (email / phone), which must stay on the /person/contact lane. */
49
+ enrichFields?: EnrichField[];
43
50
  };
44
51
 
45
52
  export type EnrichmentInputKind = "email" | "linkedin" | "domain";
@@ -82,6 +89,7 @@ export const PEOPLE_ENRICHMENT_COLUMNS: readonly EnrichmentColumnDef[] = [
82
89
  apolloPath: "person.email",
83
90
  requiredFields: ["email"],
84
91
  extractionFallbacks: ["email"],
92
+ enrichFields: ["work_emails"],
85
93
  },
86
94
  {
87
95
  label: "Headline",
@@ -114,6 +122,7 @@ export const PEOPLE_ENRICHMENT_COLUMNS: readonly EnrichmentColumnDef[] = [
114
122
  apolloPath: "person.contact.phone_numbers.0.sanitized_number",
115
123
  requiredFields: ["phone"],
116
124
  extractionFallbacks: ["phone", "person.phone"],
125
+ enrichFields: ["phones"],
117
126
  },
118
127
  {
119
128
  label: "Title",
@@ -46,15 +46,81 @@ export type ReverseEmailBody = {
46
46
  email: string;
47
47
  };
48
48
 
49
+ /** One list-filter entry: plain include string or `{value, exclude, exactMatch}`. */
50
+ export type SearchFilterItem =
51
+ | string
52
+ | { value: string; exclude?: boolean; exactMatch?: boolean };
53
+
54
+ export type SearchRangeFilter = { min?: number; max?: number };
55
+
49
56
  export type SearchPeopleBody = {
50
57
  personTitles?: string[];
51
58
  personLocations?: string[];
59
+ personSeniorities?: string[];
60
+ personFunctions?: string[];
52
61
  organizationDomains?: string[];
53
62
  organizationLocations?: string[];
63
+ organizationIndustries?: string[];
54
64
  organizationNumEmployeesRanges?: string[];
55
65
  qKeywords?: string;
66
+ keywords?: string;
56
67
  page?: number;
57
68
  perPage?: number;
69
+ limit?: number;
70
+ offset?: number;
71
+ searchAfter?: unknown;
72
+ /** Opaque token from a previous response's `metadata.nextPageToken`. */
73
+ pageToken?: string;
74
+ // Rich filters, gateway-named. Merged with the legacy person*/organization*
75
+ // aliases above; entries may carry per-value exclude / exactMatch.
76
+ titles?: SearchFilterItem[];
77
+ pastTitles?: SearchFilterItem[];
78
+ personNames?: SearchFilterItem[];
79
+ seniorities?: SearchFilterItem[];
80
+ jobFunctions?: SearchFilterItem[];
81
+ subFunctions?: SearchFilterItem[];
82
+ industries?: SearchFilterItem[];
83
+ locations?: SearchFilterItem[];
84
+ headcountRanges?: SearchFilterItem[];
85
+ companyNames?: SearchFilterItem[];
86
+ pastCompanyNames?: SearchFilterItem[];
87
+ companyDomains?: SearchFilterItem[];
88
+ companyHeadquarters?: SearchFilterItem[];
89
+ companyTypes?: SearchFilterItem[];
90
+ universities?: SearchFilterItem[];
91
+ skills?: SearchFilterItem[];
92
+ languages?: SearchFilterItem[];
93
+ yearsInRole?: SearchRangeFilter;
94
+ yearsAtCompany?: SearchRangeFilter;
95
+ daysSinceJobChange?: SearchRangeFilter;
96
+ foundedYears?: SearchRangeFilter;
97
+ };
98
+
99
+ export type SearchCompaniesBody = {
100
+ domain?: string;
101
+ name?: string;
102
+ linkedinUrl?: string;
103
+ industries?: string[];
104
+ locations?: string[];
105
+ headcountRanges?: string[];
106
+ keywords?: string;
107
+ limit?: number;
108
+ offset?: number;
109
+ /** Opaque token from a previous response's `metadata.nextPageToken`. */
110
+ pageToken?: string;
111
+ };
112
+
113
+ export type SearchJobsBody = {
114
+ titles?: string[];
115
+ locations?: string[];
116
+ companyDomain?: string;
117
+ companyName?: string;
118
+ companyLinkedinUrl?: string;
119
+ keywords?: string;
120
+ postedAfter?: string;
121
+ limit?: number;
122
+ /** Opaque token from a previous response's `metadata.nextPageToken`. */
123
+ pageToken?: string;
58
124
  };
59
125
 
60
126
  export type EnrichmentGatewayOptions = {
@@ -1031,7 +1097,7 @@ export async function enrichCompany(
1031
1097
  }
1032
1098
 
1033
1099
  // ---------------------------------------------------------------------------
1034
- // aviatoEnrichPerson — calls POST /v1/enrichment/aviato/person (synchronous)
1100
+ // Person profile — POST /v1/enrichment/person (synchronous waterfall)
1035
1101
  // ---------------------------------------------------------------------------
1036
1102
 
1037
1103
  export type AviatoEnrichPersonBody = {
@@ -1064,7 +1130,7 @@ export async function aviatoEnrichPerson(
1064
1130
  linkedinUrl || linkedinID || linkedinNumID || linkedinEntityId || email;
1065
1131
  if (!hasIdentifier) {
1066
1132
  throw new EnrichmentGatewayError(
1067
- "Aviato person enrichment requires at least one identifier: linkedinUrl, linkedinID, linkedinNumID, linkedinEntityId, or email.",
1133
+ "Person enrichment requires at least one identifier: linkedinUrl, linkedinID, linkedinNumID, linkedinEntityId, or email.",
1068
1134
  { code: "empty_identifier" },
1069
1135
  );
1070
1136
  }
@@ -1088,7 +1154,7 @@ export async function aviatoEnrichPerson(
1088
1154
  if (body.preview !== undefined && !(body.preview && emailOnly))
1089
1155
  requestBody.preview = body.preview;
1090
1156
 
1091
- const raw = await callGatewayJson("/v1/enrichment/aviato/person", {
1157
+ const raw = await callGatewayJson("/v1/enrichment/person", {
1092
1158
  method: "POST",
1093
1159
  body: requestBody,
1094
1160
  options,
@@ -1110,7 +1176,6 @@ export async function aviatoEnrichPerson(
1110
1176
 
1111
1177
  return {
1112
1178
  _meta: {
1113
- provider: "aviato",
1114
1179
  cacheHit: resp.billing?.cache_hit ?? false,
1115
1180
  chargedCents: resp.billing?.charged_cents ?? 0,
1116
1181
  },
@@ -1119,7 +1184,7 @@ export async function aviatoEnrichPerson(
1119
1184
  }
1120
1185
 
1121
1186
  // ---------------------------------------------------------------------------
1122
- // aviatoEnrichCompany — calls POST /v1/enrichment/aviato/company (synchronous)
1187
+ // Company profile — POST /v1/enrichment/company (synchronous waterfall)
1123
1188
  // ---------------------------------------------------------------------------
1124
1189
 
1125
1190
  export async function aviatoEnrichCompany(
@@ -1130,7 +1195,7 @@ export async function aviatoEnrichCompany(
1130
1195
  body.website || body.linkedinUrl || body.linkedinID || body.linkedinNumID;
1131
1196
  if (!hasIdentifier) {
1132
1197
  throw new EnrichmentGatewayError(
1133
- "Aviato company enrichment requires at least one identifier: website, linkedinUrl, linkedinID, or linkedinNumID.",
1198
+ "Company enrichment requires at least one identifier: website, linkedinUrl, linkedinID, or linkedinNumID.",
1134
1199
  { code: "empty_identifier" },
1135
1200
  );
1136
1201
  }
@@ -1142,7 +1207,7 @@ export async function aviatoEnrichCompany(
1142
1207
  if (body.linkedinNumID) requestBody.linkedinNumID = body.linkedinNumID.trim();
1143
1208
  if (body.preview !== undefined) requestBody.preview = body.preview;
1144
1209
 
1145
- const raw = await callGatewayJson("/v1/enrichment/aviato/company", {
1210
+ const raw = await callGatewayJson("/v1/enrichment/company", {
1146
1211
  method: "POST",
1147
1212
  body: requestBody,
1148
1213
  options,
@@ -1164,7 +1229,6 @@ export async function aviatoEnrichCompany(
1164
1229
 
1165
1230
  return {
1166
1231
  _meta: {
1167
- provider: "aviato",
1168
1232
  cacheHit: resp.billing?.cache_hit ?? false,
1169
1233
  chargedCents: resp.billing?.charged_cents ?? 0,
1170
1234
  },
@@ -1184,29 +1248,80 @@ export async function searchPeople(
1184
1248
  // `locations`, `headcountRanges`, `titles`, `companyDomain`, etc.
1185
1249
  const payload: Record<string, unknown> = { preferCache: true };
1186
1250
 
1187
- if (body.personTitles?.length) payload.titles = body.personTitles;
1251
+ const mergeItems = (
1252
+ legacy: string[] | undefined,
1253
+ rich: SearchFilterItem[] | undefined,
1254
+ ): SearchFilterItem[] => {
1255
+ const legacyItems = (legacy ?? [])
1256
+ .map((s) => s.trim())
1257
+ .filter(Boolean) as SearchFilterItem[];
1258
+ return [...legacyItems, ...(rich ?? [])];
1259
+ };
1260
+
1261
+ const titles = mergeItems(body.personTitles, body.titles);
1262
+ if (titles.length > 0) payload.titles = titles;
1188
1263
 
1189
- const locations = [
1190
- ...(body.personLocations ?? []),
1191
- ...(body.organizationLocations ?? []),
1192
- ]
1193
- .map((s) => s.trim())
1194
- .filter(Boolean);
1264
+ const locations = mergeItems(
1265
+ [...(body.personLocations ?? []), ...(body.organizationLocations ?? [])],
1266
+ body.locations,
1267
+ );
1195
1268
  if (locations.length > 0) payload.locations = locations;
1196
1269
 
1197
1270
  if (body.organizationDomains && body.organizationDomains.length > 0) {
1198
1271
  payload.companyDomain = body.organizationDomains[0];
1199
1272
  }
1200
1273
 
1201
- const headcountRanges = (body.organizationNumEmployeesRanges ?? [])
1202
- .map((s) => s.trim())
1203
- .filter(Boolean);
1204
- if (headcountRanges.length > 0) {
1205
- payload.headcountRanges = headcountRanges;
1274
+ const headcountRanges = mergeItems(
1275
+ body.organizationNumEmployeesRanges,
1276
+ body.headcountRanges,
1277
+ );
1278
+ if (headcountRanges.length > 0) payload.headcountRanges = headcountRanges;
1279
+
1280
+ const seniorities = mergeItems(body.personSeniorities, body.seniorities);
1281
+ if (seniorities.length > 0) payload.seniorities = seniorities;
1282
+ const jobFunctions = mergeItems(body.personFunctions, body.jobFunctions);
1283
+ if (jobFunctions.length > 0) payload.jobFunctions = jobFunctions;
1284
+ const industries = mergeItems(body.organizationIndustries, body.industries);
1285
+ if (industries.length > 0) payload.industries = industries;
1286
+
1287
+ // Rich list filters with no legacy alias — pass straight through.
1288
+ const listPassthrough = [
1289
+ "pastTitles",
1290
+ "personNames",
1291
+ "subFunctions",
1292
+ "companyNames",
1293
+ "pastCompanyNames",
1294
+ "companyDomains",
1295
+ "companyHeadquarters",
1296
+ "companyTypes",
1297
+ "universities",
1298
+ "skills",
1299
+ "languages",
1300
+ ] as const;
1301
+ for (const key of listPassthrough) {
1302
+ const items = body[key];
1303
+ if (items?.length) payload[key] = items;
1304
+ }
1305
+ const rangePassthrough = [
1306
+ "yearsInRole",
1307
+ "yearsAtCompany",
1308
+ "daysSinceJobChange",
1309
+ "foundedYears",
1310
+ ] as const;
1311
+ for (const key of rangePassthrough) {
1312
+ const range = body[key];
1313
+ if (range && (range.min !== undefined || range.max !== undefined)) {
1314
+ payload[key] = range;
1315
+ }
1206
1316
  }
1207
1317
 
1208
- if (body.qKeywords) payload.keywords = body.qKeywords;
1209
- if (body.perPage !== undefined) payload.limit = body.perPage;
1318
+ const keywords = body.keywords ?? body.qKeywords;
1319
+ if (keywords) payload.keywords = keywords;
1320
+ if (body.limit !== undefined) payload.limit = body.limit;
1321
+ else if (body.perPage !== undefined) payload.limit = body.perPage;
1322
+ if (body.offset !== undefined) payload.offset = body.offset;
1323
+ if (body.searchAfter !== undefined) payload.searchAfter = body.searchAfter;
1324
+ if (body.pageToken) payload.pageToken = body.pageToken;
1210
1325
 
1211
1326
  return callGatewayJson("/v1/enrichment/people/search", {
1212
1327
  method: "POST",
@@ -1215,6 +1330,51 @@ export async function searchPeople(
1215
1330
  });
1216
1331
  }
1217
1332
 
1333
+ export async function searchCompanies(
1334
+ body: SearchCompaniesBody,
1335
+ options: EnrichmentGatewayOptions = {},
1336
+ ): Promise<Record<string, unknown>> {
1337
+ const payload: Record<string, unknown> = { preferCache: true };
1338
+ if (body.domain) payload.domain = body.domain;
1339
+ if (body.name) payload.name = body.name;
1340
+ if (body.linkedinUrl) payload.linkedinUrl = body.linkedinUrl;
1341
+ if (body.industries?.length) payload.industries = body.industries;
1342
+ if (body.locations?.length) payload.locations = body.locations;
1343
+ if (body.headcountRanges?.length)
1344
+ payload.headcountRanges = body.headcountRanges;
1345
+ if (body.keywords) payload.keywords = body.keywords;
1346
+ if (body.limit !== undefined) payload.limit = body.limit;
1347
+ if (body.offset !== undefined) payload.offset = body.offset;
1348
+ if (body.pageToken) payload.pageToken = body.pageToken;
1349
+ return callGatewayJson("/v1/enrichment/company/search", {
1350
+ method: "POST",
1351
+ body: payload,
1352
+ options,
1353
+ });
1354
+ }
1355
+
1356
+ export async function searchJobs(
1357
+ body: SearchJobsBody,
1358
+ options: EnrichmentGatewayOptions = {},
1359
+ ): Promise<Record<string, unknown>> {
1360
+ const payload: Record<string, unknown> = {};
1361
+ if (body.titles?.length) payload.titles = body.titles;
1362
+ if (body.locations?.length) payload.locations = body.locations;
1363
+ if (body.companyDomain) payload.companyDomain = body.companyDomain;
1364
+ if (body.companyName) payload.companyName = body.companyName;
1365
+ if (body.companyLinkedinUrl)
1366
+ payload.companyLinkedinUrl = body.companyLinkedinUrl;
1367
+ if (body.keywords) payload.keywords = body.keywords;
1368
+ if (body.postedAfter) payload.postedAfter = body.postedAfter;
1369
+ if (body.limit !== undefined) payload.limit = body.limit;
1370
+ if (body.pageToken) payload.pageToken = body.pageToken;
1371
+ return callGatewayJson("/v1/enrichment/jobs/search", {
1372
+ method: "POST",
1373
+ body: payload,
1374
+ options,
1375
+ });
1376
+ }
1377
+
1218
1378
  /**
1219
1379
  * Number of Exa candidates to pull when resolving ONE person for enrichment.
1220
1380
  *
@@ -1355,7 +1515,8 @@ async function buildGatewayError(
1355
1515
  if (
1356
1516
  response.status === 401 ||
1357
1517
  code === "invalid_api_key" ||
1358
- code === "unauthorized"
1518
+ code === "unauthorized" ||
1519
+ code === "aviato_unauthorized"
1359
1520
  ) {
1360
1521
  return new EnrichmentGatewayError(
1361
1522
  upstreamMessage ?? "Unauthorized gateway request",
@@ -1368,12 +1529,6 @@ async function buildGatewayError(
1368
1529
  { status: response.status, code: "forbidden" },
1369
1530
  );
1370
1531
  }
1371
- if (code === "aviato_unauthorized") {
1372
- return new EnrichmentGatewayError(
1373
- upstreamMessage ?? "Aviato provider unauthorized",
1374
- { status: response.status || 401, code: "aviato_unauthorized" },
1375
- );
1376
- }
1377
1532
  if (response.status === 503 || code === "provider_unavailable") {
1378
1533
  return new EnrichmentGatewayError("Gateway providers unavailable", {
1379
1534
  status: response.status,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "2.7.11-staging.102",
3
+ "version": "2.8.1-staging.110",
4
4
  "description": "Dench agent workspace CLI.",
5
5
  "type": "module",
6
6
  "bin": {