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.
@@ -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.24",
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-cli.vercel.app",
44
+ "homepage": "https://salesprompter.ai/docs",
45
45
  "repository": {
46
46
  "type": "git",
47
47
  "url": "git+https://github.com/danielsinewe/salesprompter-cli.git"