salesprompter-cli 0.1.24 → 0.1.26
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/README.md +10 -5
- package/dist/cli.js +1031 -34
- package/dist/hunter-emailfinder.js +252 -0
- package/package.json +2 -2
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const nullableNumber = z.union([z.string(), z.number()]).transform((value) => Number(value)).nullable().optional()
|
|
3
|
+
.transform((value) => (value == null || Number.isNaN(value) ? null : value));
|
|
4
|
+
const stringId = z.union([z.string(), z.number()]).transform((value) => String(value).trim()).refine((value) => value.length > 0);
|
|
5
|
+
export const hunterEmailfinderQueueRowSchema = z.object({
|
|
6
|
+
clientId: stringId,
|
|
7
|
+
contactId: stringId,
|
|
8
|
+
companyId: stringId,
|
|
9
|
+
firstName_cleaned: z.string().trim().min(1),
|
|
10
|
+
lastName_cleaned: z.string().trim().min(1),
|
|
11
|
+
domain: z.string().trim().min(1),
|
|
12
|
+
companyScore: nullableNumber,
|
|
13
|
+
seniorityId: nullableNumber
|
|
14
|
+
});
|
|
15
|
+
export const hunterEmailfinderQueueRowArraySchema = z.array(hunterEmailfinderQueueRowSchema);
|
|
16
|
+
export const directEmailEnrichmentInputRowArraySchema = z.array(z.object({
|
|
17
|
+
clientId: z.string().nullable(),
|
|
18
|
+
companyName: z.string().trim(),
|
|
19
|
+
fullName: z.string().trim()
|
|
20
|
+
}));
|
|
21
|
+
function detectLooseDelimiter(value) {
|
|
22
|
+
if (value.includes("\t")) {
|
|
23
|
+
return "\t";
|
|
24
|
+
}
|
|
25
|
+
if (value.includes(";")) {
|
|
26
|
+
return ";";
|
|
27
|
+
}
|
|
28
|
+
return ",";
|
|
29
|
+
}
|
|
30
|
+
function splitLooseDelimitedLine(value, delimiter) {
|
|
31
|
+
if (delimiter === "\t") {
|
|
32
|
+
return value.split("\t");
|
|
33
|
+
}
|
|
34
|
+
const parts = [];
|
|
35
|
+
let current = "";
|
|
36
|
+
let inQuotes = false;
|
|
37
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
38
|
+
const char = value[index] ?? "";
|
|
39
|
+
if (char === "\"") {
|
|
40
|
+
inQuotes = !inQuotes;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (char === delimiter && !inQuotes) {
|
|
44
|
+
parts.push(current);
|
|
45
|
+
current = "";
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
current += char;
|
|
49
|
+
}
|
|
50
|
+
parts.push(current);
|
|
51
|
+
return parts;
|
|
52
|
+
}
|
|
53
|
+
export function parseDirectEmailEnrichmentInput(content) {
|
|
54
|
+
const trimmed = content.trim();
|
|
55
|
+
if (!trimmed) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
if (trimmed.startsWith("[")) {
|
|
59
|
+
const parsed = z.array(z.object({
|
|
60
|
+
clientId: z.union([z.string(), z.number()]).nullish(),
|
|
61
|
+
companyName: z.string().nullish(),
|
|
62
|
+
fullName: z.string().nullish()
|
|
63
|
+
})).parse(JSON.parse(trimmed));
|
|
64
|
+
return parsed.map((row) => ({
|
|
65
|
+
clientId: row.clientId == null ? null : String(row.clientId).trim() || null,
|
|
66
|
+
companyName: row.companyName?.trim() ?? "",
|
|
67
|
+
fullName: row.fullName?.trim() ?? ""
|
|
68
|
+
})).filter((row) => row.companyName.length > 0 || row.fullName.length > 0);
|
|
69
|
+
}
|
|
70
|
+
const lines = trimmed
|
|
71
|
+
.split(/\r?\n/)
|
|
72
|
+
.map((line) => line.trim())
|
|
73
|
+
.filter((line) => line.length > 0);
|
|
74
|
+
if (lines.length === 0) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const delimiter = detectLooseDelimiter(lines[0] ?? "");
|
|
78
|
+
const headerValues = splitLooseDelimitedLine(lines[0] ?? "", delimiter).map((value) => value.trim().toLowerCase());
|
|
79
|
+
const hasHeader = headerValues.includes("fullname") ||
|
|
80
|
+
headerValues.includes("full_name") ||
|
|
81
|
+
headerValues.includes("companyname") ||
|
|
82
|
+
headerValues.includes("company_name");
|
|
83
|
+
if (hasHeader) {
|
|
84
|
+
const companyNameIndex = headerValues.findIndex((value) => ["companyname", "company_name"].includes(value));
|
|
85
|
+
const fullNameIndex = headerValues.findIndex((value) => ["fullname", "full_name", "contact_name", "name"].includes(value));
|
|
86
|
+
const clientIdIndex = headerValues.findIndex((value) => ["clientid", "client_id"].includes(value));
|
|
87
|
+
return lines.slice(1)
|
|
88
|
+
.map((line) => splitLooseDelimitedLine(line, delimiter).map((value) => value.trim()))
|
|
89
|
+
.map((columns) => ({
|
|
90
|
+
clientId: clientIdIndex >= 0 ? columns[clientIdIndex] || null : null,
|
|
91
|
+
companyName: companyNameIndex >= 0 ? columns[companyNameIndex] || "" : "",
|
|
92
|
+
fullName: fullNameIndex >= 0 ? columns[fullNameIndex] || "" : ""
|
|
93
|
+
}))
|
|
94
|
+
.filter((row) => row.companyName.length > 0 || row.fullName.length > 0);
|
|
95
|
+
}
|
|
96
|
+
return lines
|
|
97
|
+
.map((line) => splitLooseDelimitedLine(line, delimiter).map((value) => value.trim()))
|
|
98
|
+
.map((columns) => {
|
|
99
|
+
if (columns.length >= 3) {
|
|
100
|
+
return {
|
|
101
|
+
clientId: columns[0] || null,
|
|
102
|
+
fullName: columns[1] || "",
|
|
103
|
+
companyName: columns[2] || ""
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
clientId: null,
|
|
108
|
+
companyName: columns[0] || "",
|
|
109
|
+
fullName: columns[1] || ""
|
|
110
|
+
};
|
|
111
|
+
})
|
|
112
|
+
.filter((row) => row.companyName.length > 0 || row.fullName.length > 0);
|
|
113
|
+
}
|
|
114
|
+
export function resolveDirectEmailEnrichmentClientId(inputRows, clientIdOption) {
|
|
115
|
+
const explicitClientId = clientIdOption == null || String(clientIdOption).trim().length === 0
|
|
116
|
+
? null
|
|
117
|
+
: z.coerce.number().int().positive().parse(clientIdOption);
|
|
118
|
+
const rowClientIds = Array.from(new Set(inputRows
|
|
119
|
+
.map((row) => row.clientId?.trim() ?? "")
|
|
120
|
+
.filter((value) => value.length > 0)));
|
|
121
|
+
if (explicitClientId != null && rowClientIds.length > 0) {
|
|
122
|
+
const mismatched = rowClientIds.some((value) => Number(value) !== explicitClientId);
|
|
123
|
+
if (mismatched) {
|
|
124
|
+
throw new Error("Input rows contain multiple or mismatched clientId values. Pass one consistent --client-id or align the input.");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (explicitClientId != null) {
|
|
128
|
+
return explicitClientId;
|
|
129
|
+
}
|
|
130
|
+
if (rowClientIds.length === 1) {
|
|
131
|
+
return z.coerce.number().int().positive().parse(rowClientIds[0]);
|
|
132
|
+
}
|
|
133
|
+
if (rowClientIds.length > 1) {
|
|
134
|
+
throw new Error("Input rows contain multiple clientId values. Pass --client-id to choose one batch client.");
|
|
135
|
+
}
|
|
136
|
+
return Math.max(1, Math.trunc(Date.now() / 1000));
|
|
137
|
+
}
|
|
138
|
+
export function buildHunterEmailfinderQueueSql(clientId, limit) {
|
|
139
|
+
return [
|
|
140
|
+
"SELECT",
|
|
141
|
+
" clientId,",
|
|
142
|
+
" contactId,",
|
|
143
|
+
" companyId,",
|
|
144
|
+
" firstName_cleaned,",
|
|
145
|
+
" lastName_cleaned,",
|
|
146
|
+
" domain,",
|
|
147
|
+
" companyScore,",
|
|
148
|
+
" seniorityId",
|
|
149
|
+
"FROM `icpidentifier.SalesPrompter.hunter_emailFinder_input`",
|
|
150
|
+
`WHERE clientId = ${clientId}`,
|
|
151
|
+
"ORDER BY companyScore DESC NULLS LAST, seniorityId DESC NULLS LAST, contactId",
|
|
152
|
+
`LIMIT ${limit}`
|
|
153
|
+
].join("\n");
|
|
154
|
+
}
|
|
155
|
+
export function buildHunterEmailfinderTriggerPayload(params) {
|
|
156
|
+
return {
|
|
157
|
+
action: "run_hunter_emailfinder",
|
|
158
|
+
workflow_target: "hunter_emailFinder",
|
|
159
|
+
app_source: "salesprompter_cli",
|
|
160
|
+
integration: "hunter",
|
|
161
|
+
integration_type: "hunter",
|
|
162
|
+
trigger_source: "cli_hunter_emailfinder_run_bq",
|
|
163
|
+
trace_id: params.traceId,
|
|
164
|
+
queue_source: "icpidentifier.SalesPrompter.hunter_emailFinder_input",
|
|
165
|
+
output_table: "icpidentifier.SalesPrompter.hunter_emailFinder_output",
|
|
166
|
+
queue_client_id: params.clientId,
|
|
167
|
+
batch_count: params.queueRows.length,
|
|
168
|
+
inputs: params.queueRows,
|
|
169
|
+
payload: {
|
|
170
|
+
trace_id: params.traceId,
|
|
171
|
+
queue_source: "icpidentifier.SalesPrompter.hunter_emailFinder_input",
|
|
172
|
+
output_table: "icpidentifier.SalesPrompter.hunter_emailFinder_output",
|
|
173
|
+
clientId: params.clientId,
|
|
174
|
+
inputs: params.queueRows
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
export function readHunterEmailfinderConfig(env = process.env, endpointUrlOverride) {
|
|
179
|
+
const endpointId = env.PIPEDREAM_HUNTER_EMAILFINDER_ENDPOINT_ID?.trim() || "";
|
|
180
|
+
const endpointUrl = endpointUrlOverride?.trim() ||
|
|
181
|
+
env.SALESPROMPTER_HUNTER_EMAILFINDER_ENDPOINT_URL?.trim() ||
|
|
182
|
+
env.HUNTER_EMAILFINDER_ENDPOINT_URL?.trim() ||
|
|
183
|
+
(endpointId ? `https://${endpointId}.m.pipedream.net` : "");
|
|
184
|
+
if (!endpointUrl) {
|
|
185
|
+
throw new Error("Missing email enrichment endpoint. Set --endpoint-url, SALESPROMPTER_HUNTER_EMAILFINDER_ENDPOINT_URL, HUNTER_EMAILFINDER_ENDPOINT_URL, or PIPEDREAM_HUNTER_EMAILFINDER_ENDPOINT_ID.");
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
endpointUrl,
|
|
189
|
+
secret: env.PIPEDREAM_SECRET_KEY?.trim() || "",
|
|
190
|
+
clientId: env.PIPEDREAM_CLIENT_ID?.trim() || "",
|
|
191
|
+
projectId: env.PIPEDREAM_PROJECT_ID?.trim() || "",
|
|
192
|
+
projectEnvironment: env.PIPEDREAM_PROJECT_ENVIRONMENT?.trim() || ""
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export async function triggerHunterEmailfinderWorkflow(params) {
|
|
196
|
+
const endpoint = new URL(params.config.endpointUrl);
|
|
197
|
+
if (params.config.secret) {
|
|
198
|
+
endpoint.searchParams.set("secret", params.config.secret);
|
|
199
|
+
}
|
|
200
|
+
const headers = {
|
|
201
|
+
"Content-Type": "application/json",
|
|
202
|
+
"x-pd-external-user-id": params.externalUserId
|
|
203
|
+
};
|
|
204
|
+
if (params.config.secret) {
|
|
205
|
+
headers.Authorization = `Bearer ${params.config.secret}`;
|
|
206
|
+
headers["x-pd-secret"] = params.config.secret;
|
|
207
|
+
headers["x-secret-key"] = params.config.secret;
|
|
208
|
+
}
|
|
209
|
+
if (params.config.clientId) {
|
|
210
|
+
headers["X-Client-ID"] = params.config.clientId;
|
|
211
|
+
}
|
|
212
|
+
if (params.config.projectId) {
|
|
213
|
+
headers["X-Project-ID"] = params.config.projectId;
|
|
214
|
+
}
|
|
215
|
+
if (params.config.projectEnvironment) {
|
|
216
|
+
headers["X-Environment"] = params.config.projectEnvironment;
|
|
217
|
+
headers["x-pd-environment"] = params.config.projectEnvironment;
|
|
218
|
+
}
|
|
219
|
+
const controller = new AbortController();
|
|
220
|
+
const timeout = setTimeout(() => controller.abort(), params.timeoutMs);
|
|
221
|
+
try {
|
|
222
|
+
const response = await fetch(endpoint, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers,
|
|
225
|
+
body: JSON.stringify(params.payload),
|
|
226
|
+
signal: controller.signal
|
|
227
|
+
});
|
|
228
|
+
const bodyText = await response.text();
|
|
229
|
+
let parsedBody = null;
|
|
230
|
+
try {
|
|
231
|
+
parsedBody = bodyText ? JSON.parse(bodyText) : null;
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
parsedBody = bodyText;
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
endpoint: endpoint.toString(),
|
|
238
|
+
bodyText,
|
|
239
|
+
parsedBody,
|
|
240
|
+
response
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
if (error.name === "AbortError") {
|
|
245
|
+
throw new Error(`Email enrichment workflow timed out after ${params.timeoutMs}ms.`);
|
|
246
|
+
}
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
clearTimeout(timeout);
|
|
251
|
+
}
|
|
252
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "salesprompter-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"description": "Sales workflow CLI for guided lead generation, enrichment, scoring, and sync.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"json",
|
|
42
42
|
"tooling"
|
|
43
43
|
],
|
|
44
|
-
"homepage": "https://salesprompter
|
|
44
|
+
"homepage": "https://salesprompter.ai/docs",
|
|
45
45
|
"repository": {
|
|
46
46
|
"type": "git",
|
|
47
47
|
"url": "git+https://github.com/danielsinewe/salesprompter-cli.git"
|