@dench.com/cli 3.5.3-staging.333 → 3.5.5-staging.342
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/app-sdk.d.ts +2 -2
- package/campaign.ts +1 -1
- package/crm.ts +6 -6
- package/lib/api-schemas.ts +1 -1
- package/lib/enrichment-gateway.ts +178 -0
- package/package.json +1 -1
package/app-sdk.d.ts
CHANGED
|
@@ -1941,7 +1941,7 @@ export interface ApiRequests {
|
|
|
1941
1941
|
onlyIf?: "always" | "connected" | "not_connected";
|
|
1942
1942
|
/** Legacy LinkedIn step sender. Pool campaigns use the campaign's linkedinAccountIds instead. */
|
|
1943
1943
|
linkedinAccountId?: string;
|
|
1944
|
-
/** linkedin_connection: the invite note (optional); linkedin_message: the message (up to 3000 chars). */
|
|
1944
|
+
/** linkedin_connection: the invite note (optional, up to 300 chars; sent without it if LinkedIn refuses the note); linkedin_message: the message (up to 3000 chars). */
|
|
1945
1945
|
linkedinMessageBody?: string;
|
|
1946
1946
|
/** linkedin_message: what to do when the invite is still pending. */
|
|
1947
1947
|
linkedinMessageMode?: "skip_if_not_connected" | "wait_until_connected";
|
|
@@ -4862,7 +4862,7 @@ export interface ApiResponses {
|
|
|
4862
4862
|
onlyIf?: "always" | "connected" | "not_connected";
|
|
4863
4863
|
/** Legacy LinkedIn step sender. Pool campaigns use the campaign's linkedinAccountIds instead. */
|
|
4864
4864
|
linkedinAccountId?: string;
|
|
4865
|
-
/** linkedin_connection: the invite note (optional); linkedin_message: the message (up to 3000 chars). */
|
|
4865
|
+
/** linkedin_connection: the invite note (optional, up to 300 chars; sent without it if LinkedIn refuses the note); linkedin_message: the message (up to 3000 chars). */
|
|
4866
4866
|
linkedinMessageBody?: string;
|
|
4867
4867
|
/** linkedin_message: what to do when the invite is still pending. */
|
|
4868
4868
|
linkedinMessageMode?: "skip_if_not_connected" | "wait_until_connected";
|
package/campaign.ts
CHANGED
|
@@ -1342,7 +1342,7 @@ async function runGet(ctx: CampaignCliContext): Promise<void> {
|
|
|
1342
1342
|
);
|
|
1343
1343
|
}
|
|
1344
1344
|
lines.push(
|
|
1345
|
-
` Replied ${a.replied ?? 0} (${pct(a.replyRate)} of
|
|
1345
|
+
` Replied ${a.replied ?? 0} (${pct(a.replyRate)} of contacted)`,
|
|
1346
1346
|
);
|
|
1347
1347
|
lines.push(
|
|
1348
1348
|
` Done finished ${a.finished ?? 0} · stopped ${a.stopped ?? 0} · failed ${a.failed ?? 0}`,
|
package/crm.ts
CHANGED
|
@@ -4654,10 +4654,10 @@ Sidebar (progressive disclosure — Library + home Pinned strip; layout is WORKS
|
|
|
4654
4654
|
and a row inside a group is pulled out to the top level. This
|
|
4655
4655
|
does NOT add a home-level tab. Nav ids come from 'sidebar list'
|
|
4656
4656
|
— e.g. 'crm-object:task', 'crm-view:task:Due%20today',
|
|
4657
|
-
'crm-people', 'group:abc123'. To-Do(s), Automations,
|
|
4658
|
-
|
|
4659
|
-
Library rows. ADMIN-ONLY: the Library order is shared by
|
|
4660
|
-
member.
|
|
4657
|
+
'crm-people', 'group:abc123'. To-Do(s), Automations, Meetings,
|
|
4658
|
+
Outreach (with Inbox inside it), and Library are fixed home-level
|
|
4659
|
+
tabs, not Library rows. ADMIN-ONLY: the Library order is shared by
|
|
4660
|
+
every member.
|
|
4661
4661
|
dench crm sidebar group create <name>
|
|
4662
4662
|
New named group (e.g. "Hiring") at the bottom of the Library.
|
|
4663
4663
|
Open to any member; prints the group id.
|
|
@@ -4670,8 +4670,8 @@ Sidebar (progressive disclosure — Library + home Pinned strip; layout is WORKS
|
|
|
4670
4670
|
Move a row out of its group, back to the top level.
|
|
4671
4671
|
dench crm sidebar pin <nav-id> | unpin <nav-id>
|
|
4672
4672
|
Pin/unpin a Library row — or a whole group (group id) — on the
|
|
4673
|
-
home Pinned strip (under To-Do(s)/Automations/
|
|
4674
|
-
|
|
4673
|
+
home Pinned strip (under To-Do(s)/Automations/Meetings/Outreach/
|
|
4674
|
+
Library). This is "add a tab on the left". A pin is a
|
|
4675
4675
|
shared shortcut, not a Library move; open to any member.
|
|
4676
4676
|
|
|
4677
4677
|
Fields (columns):
|
package/lib/api-schemas.ts
CHANGED
|
@@ -449,7 +449,7 @@ const campaignStepInput = z
|
|
|
449
449
|
.string()
|
|
450
450
|
.optional()
|
|
451
451
|
.describe(
|
|
452
|
-
"linkedin_connection: the invite note (optional); linkedin_message: the message (up to 3000 chars).",
|
|
452
|
+
"linkedin_connection: the invite note (optional, up to 300 chars; sent without it if LinkedIn refuses the note); linkedin_message: the message (up to 3000 chars).",
|
|
453
453
|
),
|
|
454
454
|
linkedinMessageMode: z
|
|
455
455
|
.enum(["skip_if_not_connected", "wait_until_connected"])
|
|
@@ -143,6 +143,62 @@ export type SearchJobsBody = {
|
|
|
143
143
|
pageToken?: string;
|
|
144
144
|
};
|
|
145
145
|
|
|
146
|
+
export type SignalKind =
|
|
147
|
+
| "post_engagement"
|
|
148
|
+
| "keyword"
|
|
149
|
+
| "hiring"
|
|
150
|
+
| "funding"
|
|
151
|
+
| "launch"
|
|
152
|
+
| "new_leader";
|
|
153
|
+
|
|
154
|
+
export type SearchSignalsBody = {
|
|
155
|
+
signal: SignalKind;
|
|
156
|
+
titles?: string[];
|
|
157
|
+
market?: string;
|
|
158
|
+
locations?: string[];
|
|
159
|
+
headcountRanges?: string[];
|
|
160
|
+
excludeDomains?: string[];
|
|
161
|
+
excludeCompanies?: string[];
|
|
162
|
+
postedAfter: string;
|
|
163
|
+
limit?: number;
|
|
164
|
+
terms?: string[];
|
|
165
|
+
recency?: "week" | "month";
|
|
166
|
+
mode?: "search" | "volume";
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export type SignalRow = {
|
|
170
|
+
signal: SignalKind;
|
|
171
|
+
companyName: string;
|
|
172
|
+
companyDomain: string;
|
|
173
|
+
companyLinkedinUrl?: string;
|
|
174
|
+
headline: string;
|
|
175
|
+
occurredAt?: string;
|
|
176
|
+
detail: {
|
|
177
|
+
jobTitles?: string[];
|
|
178
|
+
jobCount?: number;
|
|
179
|
+
jobUrl?: string;
|
|
180
|
+
role?: string;
|
|
181
|
+
roundType?: string;
|
|
182
|
+
amount?: string;
|
|
183
|
+
product?: string;
|
|
184
|
+
sourceUrl?: string;
|
|
185
|
+
personName?: string;
|
|
186
|
+
personTitle?: string;
|
|
187
|
+
personLinkedinUrl?: string;
|
|
188
|
+
term?: string;
|
|
189
|
+
postUrl?: string;
|
|
190
|
+
postText?: string;
|
|
191
|
+
engagement?: "reaction" | "comment" | "post";
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export type SearchSignalsResponse = {
|
|
196
|
+
signals: SignalRow[];
|
|
197
|
+
creditsCharged: number;
|
|
198
|
+
volume?: { term: string; count: number; saturated?: true }[];
|
|
199
|
+
metadata?: { warnings?: string[] };
|
|
200
|
+
};
|
|
201
|
+
|
|
146
202
|
/**
|
|
147
203
|
* FullEnrich hard cases run 40–120s end to end; a 90s budget produced
|
|
148
204
|
* spurious "No data returned" failures for jobs that later completed (and
|
|
@@ -256,6 +312,7 @@ export type ApolloPersonEnvelope = {
|
|
|
256
312
|
email?: string;
|
|
257
313
|
work_email?: string;
|
|
258
314
|
personal_email?: string;
|
|
315
|
+
photo_url?: string;
|
|
259
316
|
linkedin_url?: string;
|
|
260
317
|
title?: string;
|
|
261
318
|
headline?: string;
|
|
@@ -276,6 +333,7 @@ export type ApolloPersonEnvelope = {
|
|
|
276
333
|
};
|
|
277
334
|
|
|
278
335
|
type EnrichedPersonUpsertLike = {
|
|
336
|
+
avatarUrl?: string;
|
|
279
337
|
searchMatch?: {
|
|
280
338
|
derivedFields: string[];
|
|
281
339
|
basis: "current_title" | "employment_title";
|
|
@@ -316,6 +374,7 @@ function shapePersonToApolloEnvelope(
|
|
|
316
374
|
email: workEmail ?? anyEmail,
|
|
317
375
|
work_email: workEmail,
|
|
318
376
|
personal_email: personalEmail,
|
|
377
|
+
photo_url: u.avatarUrl,
|
|
319
378
|
linkedin_url: u.linkedinID
|
|
320
379
|
? `https://www.linkedin.com/in/${u.linkedinID}`
|
|
321
380
|
: undefined,
|
|
@@ -1541,6 +1600,125 @@ export async function searchJobs(
|
|
|
1541
1600
|
});
|
|
1542
1601
|
}
|
|
1543
1602
|
|
|
1603
|
+
export async function searchSignals(
|
|
1604
|
+
body: SearchSignalsBody,
|
|
1605
|
+
options: EnrichmentGatewayOptions = {},
|
|
1606
|
+
): Promise<SearchSignalsResponse> {
|
|
1607
|
+
const payload: Record<string, unknown> = {
|
|
1608
|
+
signal: body.signal,
|
|
1609
|
+
postedAfter: body.postedAfter,
|
|
1610
|
+
};
|
|
1611
|
+
if (body.titles?.length) payload.titles = body.titles;
|
|
1612
|
+
if (body.locations?.length) payload.locations = body.locations;
|
|
1613
|
+
if (body.market) payload.market = body.market;
|
|
1614
|
+
if (body.headcountRanges?.length)
|
|
1615
|
+
payload.headcountRanges = body.headcountRanges;
|
|
1616
|
+
if (body.excludeDomains?.length) payload.excludeDomains = body.excludeDomains;
|
|
1617
|
+
if (body.excludeCompanies?.length)
|
|
1618
|
+
payload.excludeCompanies = body.excludeCompanies;
|
|
1619
|
+
if (body.limit !== undefined) payload.limit = body.limit;
|
|
1620
|
+
if (body.terms?.length) payload.terms = body.terms;
|
|
1621
|
+
if (body.recency) payload.recency = body.recency;
|
|
1622
|
+
if (body.mode) payload.mode = body.mode;
|
|
1623
|
+
return (await callGatewayJson("/v1/enrichment/signals/search", {
|
|
1624
|
+
method: "POST",
|
|
1625
|
+
body: payload,
|
|
1626
|
+
options: {
|
|
1627
|
+
...options,
|
|
1628
|
+
signal: withTimeout(
|
|
1629
|
+
options.signal,
|
|
1630
|
+
body.mode === "volume"
|
|
1631
|
+
? SIGNAL_GATEWAY_TIMEOUTS_MS.volume
|
|
1632
|
+
: SIGNAL_GATEWAY_TIMEOUTS_MS.search,
|
|
1633
|
+
),
|
|
1634
|
+
},
|
|
1635
|
+
})) as SearchSignalsResponse;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
export const SIGNAL_GATEWAY_TIMEOUTS_MS = {
|
|
1639
|
+
search: 140_000,
|
|
1640
|
+
volume: 75_000,
|
|
1641
|
+
buyer: 75_000,
|
|
1642
|
+
reveal: 75_000,
|
|
1643
|
+
} as const;
|
|
1644
|
+
|
|
1645
|
+
function withTimeout(signal: AbortSignal | undefined, ms: number): AbortSignal {
|
|
1646
|
+
const timeout = AbortSignal.timeout(ms);
|
|
1647
|
+
return signal && typeof AbortSignal.any === "function"
|
|
1648
|
+
? AbortSignal.any([signal, timeout])
|
|
1649
|
+
: (signal ?? timeout);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
export type SignalBuyerBody = {
|
|
1653
|
+
companyDomain: string;
|
|
1654
|
+
titles?: string[];
|
|
1655
|
+
withEmail?: boolean;
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
export type SignalBuyer = {
|
|
1659
|
+
name: string;
|
|
1660
|
+
firstName?: string;
|
|
1661
|
+
title?: string;
|
|
1662
|
+
linkedinUrl?: string;
|
|
1663
|
+
email?: string;
|
|
1664
|
+
};
|
|
1665
|
+
|
|
1666
|
+
export type SignalBuyerResponse = {
|
|
1667
|
+
buyer: SignalBuyer | null;
|
|
1668
|
+
reveal?: "found" | "not_found" | "failed";
|
|
1669
|
+
};
|
|
1670
|
+
|
|
1671
|
+
export type SignalPersonRevealBody = {
|
|
1672
|
+
companyDomain: string;
|
|
1673
|
+
person: {
|
|
1674
|
+
name: string;
|
|
1675
|
+
linkedinUrl: string;
|
|
1676
|
+
title?: string;
|
|
1677
|
+
firstName?: string;
|
|
1678
|
+
};
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
export async function findSignalBuyer(
|
|
1682
|
+
body: SignalBuyerBody,
|
|
1683
|
+
options: EnrichmentGatewayOptions = {},
|
|
1684
|
+
): Promise<SignalBuyerResponse> {
|
|
1685
|
+
const payload: Record<string, unknown> = {
|
|
1686
|
+
companyDomain: body.companyDomain,
|
|
1687
|
+
};
|
|
1688
|
+
if (body.titles?.length) payload.titles = body.titles;
|
|
1689
|
+
if (body.withEmail !== undefined) payload.withEmail = body.withEmail;
|
|
1690
|
+
return (await callGatewayJson("/v1/enrichment/signals/buyer", {
|
|
1691
|
+
method: "POST",
|
|
1692
|
+
body: payload,
|
|
1693
|
+
options: {
|
|
1694
|
+
...options,
|
|
1695
|
+
signal: withTimeout(options.signal, SIGNAL_GATEWAY_TIMEOUTS_MS.buyer),
|
|
1696
|
+
},
|
|
1697
|
+
})) as SignalBuyerResponse;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
export async function revealSignalPersonEmail(
|
|
1701
|
+
body: SignalPersonRevealBody,
|
|
1702
|
+
options: EnrichmentGatewayOptions = {},
|
|
1703
|
+
): Promise<SignalBuyerResponse> {
|
|
1704
|
+
return (await callGatewayJson("/v1/enrichment/signals/buyer", {
|
|
1705
|
+
method: "POST",
|
|
1706
|
+
body: {
|
|
1707
|
+
companyDomain: body.companyDomain,
|
|
1708
|
+
person: {
|
|
1709
|
+
name: body.person.name,
|
|
1710
|
+
linkedinUrl: body.person.linkedinUrl,
|
|
1711
|
+
...(body.person.title ? { title: body.person.title } : {}),
|
|
1712
|
+
...(body.person.firstName ? { firstName: body.person.firstName } : {}),
|
|
1713
|
+
},
|
|
1714
|
+
},
|
|
1715
|
+
options: {
|
|
1716
|
+
...options,
|
|
1717
|
+
signal: withTimeout(options.signal, SIGNAL_GATEWAY_TIMEOUTS_MS.reveal),
|
|
1718
|
+
},
|
|
1719
|
+
})) as SignalBuyerResponse;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1544
1722
|
/**
|
|
1545
1723
|
* Number of Exa candidates to pull when resolving ONE person for enrichment.
|
|
1546
1724
|
*
|