@signaliz/cli 1.0.61 → 1.0.63
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 +2 -0
- package/dist/bin.js +237 -35
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ signaliz auth login
|
|
|
14
14
|
# https://api.signaliz.com/functions/v1/api/v1/signals
|
|
15
15
|
|
|
16
16
|
signaliz find-email --company-domain example.com --full-name "Jane Doe"
|
|
17
|
+
# An HTTPS LinkedIn person profile URL (/in/<slug>) is sufficient without a name or company domain
|
|
18
|
+
signaliz find-email --linkedin-url "https://www.linkedin.com/in/jane-doe"
|
|
17
19
|
# Force a fresh-provider quality check instead of reusing cached email or verification data
|
|
18
20
|
signaliz find-email --company-domain example.com --full-name "Jane Doe" --skip-cache
|
|
19
21
|
signaliz verify-email jane@example.com
|
package/dist/bin.js
CHANGED
|
@@ -7,11 +7,18 @@ var import_node_os = require("os");
|
|
|
7
7
|
var import_node_path = require("path");
|
|
8
8
|
var import_node_readline = require("readline");
|
|
9
9
|
var import_sdk = require("@signaliz/sdk");
|
|
10
|
+
var CliInputError = class extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "CliInputError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
10
16
|
var CONFIG_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".signaliz");
|
|
11
17
|
var CONFIG_FILE = (0, import_node_path.join)(CONFIG_DIR, "config.json");
|
|
12
18
|
var HELP = `Signaliz CLI
|
|
13
19
|
|
|
14
20
|
Five product commands:
|
|
21
|
+
signaliz find-email --linkedin-url https://www.linkedin.com/in/jane-doe [--skip-cache]
|
|
15
22
|
signaliz find-email --company-domain example.com --full-name "Jane Doe" [--skip-cache]
|
|
16
23
|
signaliz find-email --run-id run_... Resume an existing Find Email run without duplicate spend
|
|
17
24
|
signaliz find-email --job-id job_... Resume an existing Find Email batch without redispatch
|
|
@@ -92,13 +99,25 @@ function numberFlag(name) {
|
|
|
92
99
|
const value = flag(name);
|
|
93
100
|
if (value === void 0) return void 0;
|
|
94
101
|
const parsed = Number(value);
|
|
95
|
-
if (!Number.isFinite(parsed))
|
|
102
|
+
if (!Number.isFinite(parsed)) throw new CliInputError(`--${name} must be a number`);
|
|
96
103
|
return parsed;
|
|
97
104
|
}
|
|
98
105
|
function requireIntegerInRange(value, name, minimum, maximum) {
|
|
99
106
|
if (value === void 0) return;
|
|
100
107
|
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
101
|
-
throw new
|
|
108
|
+
throw new CliInputError(`${name} must be an integer between ${minimum} and ${maximum}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function requireSignalToCopyIdentity(request) {
|
|
112
|
+
if (stringValue(request.signalRunId)) return;
|
|
113
|
+
const missing = [
|
|
114
|
+
["companyDomain", request.companyDomain],
|
|
115
|
+
["personName", request.personName],
|
|
116
|
+
["title", request.title],
|
|
117
|
+
["campaignOffer", request.campaignOffer]
|
|
118
|
+
].filter(([, value]) => !stringValue(value)).map(([name]) => name);
|
|
119
|
+
if (missing.length > 0) {
|
|
120
|
+
throw new CliInputError(`Signal to Copy requires signalRunId or: ${missing.join(", ")}`);
|
|
102
121
|
}
|
|
103
122
|
}
|
|
104
123
|
function csvFlag(name) {
|
|
@@ -108,7 +127,7 @@ function csvFlag(name) {
|
|
|
108
127
|
function companySignalSearchMode(value) {
|
|
109
128
|
if (value === void 0 || value === null || value === "") return void 0;
|
|
110
129
|
if (value === "fast" || value === "balanced" || value === "coverage") return value;
|
|
111
|
-
|
|
130
|
+
throw new CliInputError("--search-mode must be fast, balanced, or coverage");
|
|
112
131
|
}
|
|
113
132
|
function readBatchInput() {
|
|
114
133
|
const path = flag("input");
|
|
@@ -118,23 +137,28 @@ function readBatchInput() {
|
|
|
118
137
|
try {
|
|
119
138
|
raw = (0, import_node_fs.readFileSync)(path === "-" ? 0 : path, "utf8").trim();
|
|
120
139
|
} catch (error) {
|
|
121
|
-
|
|
140
|
+
throw new CliInputError(`Unable to read --input ${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
122
141
|
}
|
|
123
|
-
if (!raw)
|
|
142
|
+
if (!raw) throw new CliInputError(`--input ${source} must contain at least one record`);
|
|
124
143
|
let rows;
|
|
125
144
|
try {
|
|
126
145
|
rows = raw.startsWith("[") ? JSON.parse(raw) : raw.split(/\r?\n/).filter(Boolean).map((line, index) => {
|
|
127
146
|
try {
|
|
128
147
|
return JSON.parse(line);
|
|
129
148
|
} catch {
|
|
130
|
-
|
|
149
|
+
throw new CliInputError(`Invalid JSON on line ${index + 1} of ${source}`);
|
|
131
150
|
}
|
|
132
151
|
});
|
|
133
152
|
} catch (error) {
|
|
134
|
-
|
|
153
|
+
if (error instanceof CliInputError) throw error;
|
|
154
|
+
throw new CliInputError(`Invalid JSON in --input ${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
155
|
+
}
|
|
156
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
157
|
+
throw new CliInputError("--input must be a non-empty JSON array or JSONL file");
|
|
158
|
+
}
|
|
159
|
+
if (rows.length > 5e3) {
|
|
160
|
+
throw new CliInputError(`--input supports at most 5,000 records; received ${rows.length}`);
|
|
135
161
|
}
|
|
136
|
-
if (!Array.isArray(rows) || rows.length === 0) fail("--input must be a non-empty JSON array or JSONL file");
|
|
137
|
-
if (rows.length > 5e3) fail(`--input supports at most 5,000 records; received ${rows.length}`);
|
|
138
162
|
return rows;
|
|
139
163
|
}
|
|
140
164
|
function record(value) {
|
|
@@ -164,6 +188,11 @@ function customerEmailResult(value, kind) {
|
|
|
164
188
|
if (String(result.status || "").toLowerCase() === "processing") {
|
|
165
189
|
const verificationRunId2 = result.verificationRunId ?? raw.verification_run_id;
|
|
166
190
|
const runId2 = result.runId ?? raw.run_id ?? verificationRunId2;
|
|
191
|
+
const creditsUsed2 = safeNonNegativeNumber(result.creditsUsed ?? raw.credits_used);
|
|
192
|
+
const creditsCharged2 = safeNonNegativeNumber(result.creditsCharged ?? raw.credits_charged);
|
|
193
|
+
const estimatedExternalCostUsd2 = safeNonNegativeNumber(
|
|
194
|
+
result.estimatedExternalCostUsd ?? raw.estimated_external_cost_usd
|
|
195
|
+
);
|
|
167
196
|
return {
|
|
168
197
|
success: true,
|
|
169
198
|
status: "processing",
|
|
@@ -171,16 +200,28 @@ function customerEmailResult(value, kind) {
|
|
|
171
200
|
run_id: runId2,
|
|
172
201
|
...verificationRunId2 ? { verification_run_id: verificationRunId2 } : {},
|
|
173
202
|
next_poll_after_seconds: result.nextPollAfterSeconds ?? raw.next_poll_after_seconds,
|
|
174
|
-
suggested_action: result.suggestedAction ?? raw.suggested_action ?? (verificationRunId2 ? "retry_with_verification_run_id" : "retry_with_run_id")
|
|
203
|
+
suggested_action: result.suggestedAction ?? raw.suggested_action ?? (verificationRunId2 ? "retry_with_verification_run_id" : "retry_with_run_id"),
|
|
204
|
+
retry_eligible: raw.retry_eligible ?? true,
|
|
205
|
+
retry_strategy: raw.retry_strategy ?? "same_run_recovery",
|
|
206
|
+
do_not_auto_resubmit: raw.do_not_auto_resubmit ?? true,
|
|
207
|
+
recovery_mode: raw.recovery_mode ?? "same_run",
|
|
208
|
+
recovery_status: raw.recovery_status ?? "pending",
|
|
209
|
+
...creditsUsed2 !== null ? { credits_used: creditsUsed2 } : {},
|
|
210
|
+
...creditsCharged2 !== null ? { credits_charged: creditsCharged2 } : {},
|
|
211
|
+
...typeof raw.live_provider_called === "boolean" ? { live_provider_called: raw.live_provider_called } : {},
|
|
212
|
+
...estimatedExternalCostUsd2 !== null ? { estimated_external_cost_usd: estimatedExternalCostUsd2 } : {},
|
|
213
|
+
...Object.keys(customerEmailBillingMetadata(raw.billing_metadata)).length > 0 ? { billing_metadata: customerEmailBillingMetadata(raw.billing_metadata) } : {}
|
|
175
214
|
};
|
|
176
215
|
}
|
|
177
216
|
const creditsUsed = Number(result.creditsUsed ?? raw.credits_used ?? 0);
|
|
217
|
+
const creditsCharged = Number(result.creditsCharged ?? raw.credits_charged ?? creditsUsed);
|
|
178
218
|
const base = {
|
|
179
219
|
email: typeof result.email === "string" && result.email.trim() ? result.email.trim() : null,
|
|
180
220
|
status: "completed",
|
|
181
221
|
success: result.success === true,
|
|
182
222
|
is_valid: typeof result.isValid === "boolean" ? result.isValid : typeof raw.is_valid === "boolean" ? raw.is_valid : result.isVerified ?? false,
|
|
183
|
-
credits_used: Number.isFinite(creditsUsed) && creditsUsed >= 0 ? creditsUsed : 0
|
|
223
|
+
credits_used: Number.isFinite(creditsUsed) && creditsUsed >= 0 ? creditsUsed : 0,
|
|
224
|
+
credits_charged: Number.isFinite(creditsCharged) && creditsCharged >= 0 ? creditsCharged : 0
|
|
184
225
|
};
|
|
185
226
|
if (kind === "find") {
|
|
186
227
|
const verifiedForSending2 = typeof result.isVerifiedForSending === "boolean" ? result.isVerifiedForSending : raw.verified_for_sending === true;
|
|
@@ -211,11 +252,29 @@ function customerEmailResult(value, kind) {
|
|
|
211
252
|
cache_hit: cacheHit2,
|
|
212
253
|
fresh_enrichment_used: freshEnrichmentUsed,
|
|
213
254
|
credits_used: base.credits_used,
|
|
255
|
+
credits_charged: base.credits_charged,
|
|
214
256
|
live_provider_called: liveProviderCalled
|
|
215
257
|
};
|
|
216
258
|
const estimatedExternalCostUsd2 = safeNonNegativeNumber(
|
|
217
259
|
result.estimatedExternalCostUsd ?? raw.estimated_external_cost_usd
|
|
218
260
|
);
|
|
261
|
+
const rawErrorCode = String(result.errorCode ?? raw.error_code ?? "").trim().toUpperCase();
|
|
262
|
+
const terminalErrorCode = ["EMAIL_NOT_FOUND", "PROVIDER_001", "PROVIDER_002"].includes(rawErrorCode) ? rawErrorCode : null;
|
|
263
|
+
const directRunId = stringValue(result.runId ?? raw.run_id);
|
|
264
|
+
const verificationRunId2 = stringValue(raw.verification_run_id);
|
|
265
|
+
const jobId = stringValue(raw.job_id);
|
|
266
|
+
const rawRetryStrategy = result.retryStrategy ?? raw.retry_strategy;
|
|
267
|
+
const retryStrategy = ["new_idempotency_key", "same_run_recovery"].includes(String(rawRetryStrategy)) ? String(rawRetryStrategy) : null;
|
|
268
|
+
const recoveryStatus = String(raw.recovery_status ?? "").trim().toLowerCase();
|
|
269
|
+
const nestedRunActive = Boolean(verificationRunId2 || jobId) && !["completed", "failed"].includes(recoveryStatus);
|
|
270
|
+
const manualReviewRequested = (result.suggestedAction ?? raw.suggested_action) === "manual_review";
|
|
271
|
+
const sameRunRetryProven = retryStrategy === "same_run_recovery" && !manualReviewRequested && nestedRunActive;
|
|
272
|
+
const newRequestRetryProven = retryStrategy === "new_idempotency_key" && raw.credits_used === 0 && raw.credits_charged === 0;
|
|
273
|
+
const retryEligible = terminalErrorCode === "PROVIDER_002" && !manualReviewRequested && (result.retryEligible ?? raw.retry_eligible) === true && (sameRunRetryProven || newRequestRetryProven);
|
|
274
|
+
const suggestedAction = retryEligible && retryStrategy === "same_run_recovery" ? verificationRunId2 ? "retry_with_verification_run_id" : jobId ? "check_job_status" : null : terminalErrorCode === "PROVIDER_002" && !retryEligible ? "manual_review" : null;
|
|
275
|
+
const terminalError = terminalErrorCode ? String(
|
|
276
|
+
result.error ?? raw.error ?? raw.message ?? (terminalErrorCode === "EMAIL_NOT_FOUND" ? "No verified email found" : "")
|
|
277
|
+
).trim().slice(0, 300) || null : null;
|
|
219
278
|
return {
|
|
220
279
|
...base,
|
|
221
280
|
is_valid: verifiedForSending2,
|
|
@@ -238,14 +297,38 @@ function customerEmailResult(value, kind) {
|
|
|
238
297
|
result_returned_from: resultReturnedFrom,
|
|
239
298
|
fresh_enrichment_used: freshEnrichmentUsed,
|
|
240
299
|
live_provider_called: liveProviderCalled,
|
|
300
|
+
billing_replayed: typeof raw.billing_replayed === "boolean" ? raw.billing_replayed : result.billingReplayed === true,
|
|
301
|
+
...typeof raw.idempotency_replayed === "boolean" || typeof result.idempotencyReplayed === "boolean" ? {
|
|
302
|
+
idempotency_replayed: typeof raw.idempotency_replayed === "boolean" ? raw.idempotency_replayed : result.idempotencyReplayed === true
|
|
303
|
+
} : {},
|
|
241
304
|
billing_metadata: normalizedBillingMetadata,
|
|
242
305
|
...Object.keys(historicalBillingMetadata2).length > 0 ? { historical_billing_metadata: historicalBillingMetadata2 } : {},
|
|
243
306
|
...estimatedExternalCostUsd2 !== null ? { estimated_external_cost_usd: estimatedExternalCostUsd2 } : {},
|
|
244
|
-
...
|
|
307
|
+
...directRunId ? { run_id: directRunId } : {},
|
|
308
|
+
...raw.recovery_mode ? {
|
|
309
|
+
recovery_mode: raw.recovery_mode,
|
|
310
|
+
recovery_status: raw.recovery_status ?? "completed"
|
|
311
|
+
} : {},
|
|
245
312
|
...raw.cache_publication_status ? {
|
|
246
313
|
cache_publication_status: raw.cache_publication_status,
|
|
247
314
|
cache_publication_retry_eligible: raw.cache_publication_retry_eligible === true,
|
|
248
315
|
...raw.suggested_action ? { suggested_action: raw.suggested_action } : {}
|
|
316
|
+
} : {},
|
|
317
|
+
...terminalErrorCode ? {
|
|
318
|
+
email: null,
|
|
319
|
+
success: false,
|
|
320
|
+
found: false,
|
|
321
|
+
is_valid: false,
|
|
322
|
+
verified_for_sending: false,
|
|
323
|
+
is_deliverable: false,
|
|
324
|
+
error_code: terminalErrorCode,
|
|
325
|
+
retry_eligible: retryEligible,
|
|
326
|
+
...terminalError ? { error: terminalError } : {},
|
|
327
|
+
...retryEligible && retryStrategy ? { retry_strategy: retryStrategy } : {},
|
|
328
|
+
...suggestedAction ? { suggested_action: suggestedAction } : {},
|
|
329
|
+
...result.doNotAutoResubmit === true || raw.do_not_auto_resubmit === true ? { do_not_auto_resubmit: true } : {},
|
|
330
|
+
...verificationRunId2 ? { verification_run_id: verificationRunId2 } : {},
|
|
331
|
+
...jobId ? { job_id: jobId } : {}
|
|
249
332
|
} : {}
|
|
250
333
|
};
|
|
251
334
|
}
|
|
@@ -307,6 +390,9 @@ function customerEmailResult(value, kind) {
|
|
|
307
390
|
fresh_enrichment_used: typeof raw.fresh_enrichment_used === "boolean" ? raw.fresh_enrichment_used : result.freshEnrichmentUsed === true,
|
|
308
391
|
live_provider_called: typeof raw.live_provider_called === "boolean" ? raw.live_provider_called : result.liveProviderCalled === true,
|
|
309
392
|
billing_replayed: typeof raw.billing_replayed === "boolean" ? raw.billing_replayed : result.billingReplayed === true,
|
|
393
|
+
...typeof raw.idempotency_replayed === "boolean" || typeof result.idempotencyReplayed === "boolean" ? {
|
|
394
|
+
idempotency_replayed: typeof raw.idempotency_replayed === "boolean" ? raw.idempotency_replayed : result.idempotencyReplayed === true
|
|
395
|
+
} : {},
|
|
310
396
|
billing_metadata: billingMetadata,
|
|
311
397
|
...Object.keys(historicalBillingMetadata).length > 0 ? { historical_billing_metadata: historicalBillingMetadata } : {},
|
|
312
398
|
...estimatedExternalCostUsd !== null ? { estimated_external_cost_usd: estimatedExternalCostUsd } : {},
|
|
@@ -375,7 +461,9 @@ function rejectDurableNoWaitPollingFlags(label) {
|
|
|
375
461
|
if (!hasFlag("no-wait")) return;
|
|
376
462
|
const conflicts = ["max-wait-ms", "poll-interval-ms", "concurrency"].filter(hasFlag);
|
|
377
463
|
if (conflicts.length > 0) {
|
|
378
|
-
|
|
464
|
+
throw new CliInputError(
|
|
465
|
+
`${label} --no-wait cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`
|
|
466
|
+
);
|
|
379
467
|
}
|
|
380
468
|
}
|
|
381
469
|
function recoverableJobOutput(value, command) {
|
|
@@ -454,10 +542,11 @@ function resolveBaseUrl() {
|
|
|
454
542
|
const config = loadConfig();
|
|
455
543
|
return (flag("base-url") || process.env.SIGNALIZ_BASE_URL || process.env.SIGNALIZ_API_URL || config.baseUrl || "https://api.signaliz.com/functions/v1").replace(/\/$/, "");
|
|
456
544
|
}
|
|
457
|
-
function createClient() {
|
|
545
|
+
function createClient(maxRetries) {
|
|
458
546
|
return new import_sdk.Signaliz({
|
|
459
547
|
apiKey: resolveApiKey(),
|
|
460
|
-
baseUrl: resolveBaseUrl()
|
|
548
|
+
baseUrl: resolveBaseUrl(),
|
|
549
|
+
...maxRetries !== void 0 ? { maxRetries } : {}
|
|
461
550
|
});
|
|
462
551
|
}
|
|
463
552
|
function numberValue(value) {
|
|
@@ -470,6 +559,18 @@ function nullableNumberValue(value) {
|
|
|
470
559
|
function stringValue(value) {
|
|
471
560
|
return typeof value === "string" ? value.trim() : "";
|
|
472
561
|
}
|
|
562
|
+
function hasFindEmailLinkedInIdentity(value) {
|
|
563
|
+
const linkedinUrl = stringValue(value);
|
|
564
|
+
if (!linkedinUrl) return false;
|
|
565
|
+
if (!/^[hH][tT][tT][pP][sS]:\/\/(?:[A-Za-z0-9-]+\.)*[lL][iI][nN][kK][eE][dD][iI][nN]\.[cC][oO][mM]\/[iI][nN]\/[^/?#]+\/?(?:[?#].*)?$/.test(linkedinUrl)) return false;
|
|
566
|
+
try {
|
|
567
|
+
const url = new URL(linkedinUrl);
|
|
568
|
+
const hostname = url.hostname.toLowerCase();
|
|
569
|
+
return url.protocol === "https:" && !url.username && !url.password && !url.port && (hostname === "linkedin.com" || hostname.endsWith(".linkedin.com")) && /^\/in\/[^/]+\/?$/i.test(url.pathname);
|
|
570
|
+
} catch {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
473
574
|
function signalsEverythingErrorType(status) {
|
|
474
575
|
if (status === 400 || status === 422) return "validation";
|
|
475
576
|
if (status === 401) return "auth_expired";
|
|
@@ -716,7 +817,9 @@ async function findEmail() {
|
|
|
716
817
|
"skip-cache"
|
|
717
818
|
].filter(hasFlag);
|
|
718
819
|
if (conflicts.length > 0) {
|
|
719
|
-
|
|
820
|
+
throw new CliInputError(
|
|
821
|
+
`Find Email --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`
|
|
822
|
+
);
|
|
720
823
|
}
|
|
721
824
|
const result2 = await createClient().resumeFindEmailBatch(jobId, {
|
|
722
825
|
pageSize: numberFlag("page-size"),
|
|
@@ -746,6 +849,15 @@ async function findEmail() {
|
|
|
746
849
|
idempotencyKey
|
|
747
850
|
};
|
|
748
851
|
});
|
|
852
|
+
const invalidIndexes = requests.flatMap((request2, index) => {
|
|
853
|
+
if (stringValue(request2.runId)) return [];
|
|
854
|
+
if (hasFindEmailLinkedInIdentity(request2.linkedinUrl)) return [];
|
|
855
|
+
const hasName = stringValue(request2.fullName) || stringValue(request2.firstName) && stringValue(request2.lastName);
|
|
856
|
+
return !stringValue(request2.companyDomain) || !hasName ? [index] : [];
|
|
857
|
+
});
|
|
858
|
+
if (invalidIndexes.length === requests.length) {
|
|
859
|
+
throw new CliInputError("Every Find Email row must provide an HTTPS LinkedIn person profile URL (/in/<slug>), or company_domain with full_name or first_name and last_name.");
|
|
860
|
+
}
|
|
749
861
|
if (hasFlag("dry-run")) {
|
|
750
862
|
const result3 = await createClient().findEmails(requests, { ...batchOptions(), dryRun: true });
|
|
751
863
|
dryRunOutput(result3);
|
|
@@ -761,8 +873,9 @@ async function findEmail() {
|
|
|
761
873
|
const firstName = flag("first-name");
|
|
762
874
|
const lastName = flag("last-name");
|
|
763
875
|
const linkedinUrl = flag("linkedin-url");
|
|
764
|
-
|
|
765
|
-
|
|
876
|
+
const hasNameAndDomain = Boolean(companyDomain && (fullName || firstName && lastName));
|
|
877
|
+
if (!runId && !hasFindEmailLinkedInIdentity(linkedinUrl) && !hasNameAndDomain) {
|
|
878
|
+
throw new CliInputError("Find Email must provide an HTTPS --linkedin-url person profile (/in/<slug>), or --company-domain with --full-name or --first-name and --last-name, or --run-id.");
|
|
766
879
|
}
|
|
767
880
|
const request = runId ? { runId } : {
|
|
768
881
|
companyDomain,
|
|
@@ -775,11 +888,11 @@ async function findEmail() {
|
|
|
775
888
|
idempotencyKey: flag("idempotency-key")
|
|
776
889
|
};
|
|
777
890
|
if (hasFlag("dry-run")) {
|
|
778
|
-
const result2 = await createClient().findEmail({ ...request, dryRun: true });
|
|
891
|
+
const result2 = await createClient(runId ? 0 : void 0).findEmail({ ...request, dryRun: true });
|
|
779
892
|
dryRunOutput(result2);
|
|
780
893
|
return;
|
|
781
894
|
}
|
|
782
|
-
const result = await createClient().findEmail(request);
|
|
895
|
+
const result = await createClient(runId ? 0 : void 0).findEmail(request);
|
|
783
896
|
const publicResult = customerEmailResult(result, "find");
|
|
784
897
|
if (outputFailedCoreProduct(publicResult, "No verified email found")) return;
|
|
785
898
|
output(publicResult, () => {
|
|
@@ -819,7 +932,7 @@ async function verifyEmail(args) {
|
|
|
819
932
|
...positionalEmail ? ["email argument"] : [],
|
|
820
933
|
...conflicts.map((name) => `--${name}`)
|
|
821
934
|
];
|
|
822
|
-
|
|
935
|
+
throw new CliInputError(`Verify Email --job-id cannot be combined with ${conflictList.join(", ")}.`);
|
|
823
936
|
}
|
|
824
937
|
const result2 = await createClient().resumeVerifyEmailBatch(jobId, {
|
|
825
938
|
pageSize: numberFlag("page-size"),
|
|
@@ -860,7 +973,9 @@ async function verifyEmail(args) {
|
|
|
860
973
|
}
|
|
861
974
|
const email = args[0] && !args[0].startsWith("--") ? args[0] : flag("email");
|
|
862
975
|
const verificationRunId = flag("verification-run-id");
|
|
863
|
-
if (!email && !verificationRunId)
|
|
976
|
+
if (!email && !verificationRunId) {
|
|
977
|
+
throw new CliInputError("Usage: signaliz verify-email [jane@example.com] [--verification-run-id run_...]");
|
|
978
|
+
}
|
|
864
979
|
const options = {
|
|
865
980
|
skipCache: hasFlag("skip-cache"),
|
|
866
981
|
verificationRunId,
|
|
@@ -870,11 +985,11 @@ async function verifyEmail(args) {
|
|
|
870
985
|
idempotencyKey: flag("idempotency-key")
|
|
871
986
|
};
|
|
872
987
|
if (hasFlag("dry-run")) {
|
|
873
|
-
const result2 = await createClient().verifyEmail(email, { ...options, dryRun: true });
|
|
988
|
+
const result2 = await createClient(verificationRunId ? 0 : void 0).verifyEmail(email, { ...options, dryRun: true });
|
|
874
989
|
dryRunOutput(result2);
|
|
875
990
|
return;
|
|
876
991
|
}
|
|
877
|
-
const result = await createClient().verifyEmail(email, options);
|
|
992
|
+
const result = await createClient(verificationRunId ? 0 : void 0).verifyEmail(email, options);
|
|
878
993
|
const publicResult = customerEmailResult(result, "verify");
|
|
879
994
|
if (outputFailedCoreProduct(publicResult, "Email verification failed")) return;
|
|
880
995
|
output(publicResult, () => {
|
|
@@ -932,7 +1047,9 @@ async function companySignals() {
|
|
|
932
1047
|
"concurrency"
|
|
933
1048
|
].filter(hasFlag);
|
|
934
1049
|
if (conflicts.length > 0) {
|
|
935
|
-
|
|
1050
|
+
throw new CliInputError(
|
|
1051
|
+
`Company Signals --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`
|
|
1052
|
+
);
|
|
936
1053
|
}
|
|
937
1054
|
const result2 = await createClient().resumeCompanySignalBatch(jobId, {
|
|
938
1055
|
pageSize: numberFlag("page-size"),
|
|
@@ -974,9 +1091,46 @@ async function companySignals() {
|
|
|
974
1091
|
for (const request2 of requests) {
|
|
975
1092
|
requireIntegerInRange(request2.targetSignalCount, "targetSignalCount", 1, 15);
|
|
976
1093
|
requireIntegerInRange(request2.lookbackDays, "lookbackDays", 1, 365);
|
|
1094
|
+
requireIntegerInRange(request2.maxWaitMs, "maxWaitMs", 1e4, 12e4);
|
|
1095
|
+
}
|
|
1096
|
+
if (durableBatch && requests.some((request2) => request2.includeCandidateEvidence === true)) {
|
|
1097
|
+
throw new CliInputError(
|
|
1098
|
+
"includeCandidateEvidence is not supported in Company Signals batches larger than 25 because rejected evidence diagnostics are intentionally excluded from durable scale storage; split diagnostic requests into batches of at most 25."
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
if (durableBatch) {
|
|
1102
|
+
const controlKey = (request2) => JSON.stringify({
|
|
1103
|
+
researchPrompt: request2.researchPrompt,
|
|
1104
|
+
signalTypes: request2.signalTypes,
|
|
1105
|
+
targetSignalCount: request2.targetSignalCount,
|
|
1106
|
+
lookbackDays: request2.lookbackDays,
|
|
1107
|
+
online: request2.online,
|
|
1108
|
+
enableDeepSearch: request2.enableDeepSearch,
|
|
1109
|
+
searchMode: request2.searchMode ?? "balanced",
|
|
1110
|
+
includeSummary: request2.includeSummary,
|
|
1111
|
+
enableOutreachIntelligence: request2.enableOutreachIntelligence,
|
|
1112
|
+
enablePredictiveIntelligence: request2.enablePredictiveIntelligence,
|
|
1113
|
+
skipCache: request2.skipCache,
|
|
1114
|
+
includeCandidateEvidence: request2.includeCandidateEvidence
|
|
1115
|
+
});
|
|
1116
|
+
const firstControlKey = controlKey(requests[0]);
|
|
1117
|
+
const incompatible = requests.some(
|
|
1118
|
+
(request2) => Boolean(stringValue(request2.signalRunId)) || Boolean(stringValue(request2.idempotencyKey)) || !stringValue(request2.companyDomain) && !stringValue(request2.companyName) || controlKey(request2) !== firstControlKey
|
|
1119
|
+
);
|
|
1120
|
+
if (incompatible) {
|
|
1121
|
+
throw new CliInputError(
|
|
1122
|
+
"Company Signals batches larger than 25 require homogeneous new-enrichment rows with company identity only and uniform controls; split recovery or mixed-control rows into batches of at most 25."
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
} else if (hasFlag("no-wait") && !hasFlag("dry-run")) {
|
|
1126
|
+
throw new CliInputError(
|
|
1127
|
+
"Company Signals --no-wait requires more than 25 homogeneous new-enrichment rows; recovery reads and mixed controls must use batches of at most 25."
|
|
1128
|
+
);
|
|
977
1129
|
}
|
|
978
1130
|
if (hasFlag("dry-run")) {
|
|
979
|
-
if (hasFlag("no-wait"))
|
|
1131
|
+
if (hasFlag("no-wait")) {
|
|
1132
|
+
throw new CliInputError("Company Signals --no-wait cannot be combined with --dry-run.");
|
|
1133
|
+
}
|
|
980
1134
|
const result3 = await createClient().enrichCompanies(requests, { ...batchOptions(), dryRun: true });
|
|
981
1135
|
dryRunOutput(result3);
|
|
982
1136
|
return;
|
|
@@ -993,12 +1147,18 @@ async function companySignals() {
|
|
|
993
1147
|
batchOutput(customerSignalBatch(result2));
|
|
994
1148
|
return;
|
|
995
1149
|
}
|
|
996
|
-
if (hasFlag("no-wait"))
|
|
997
|
-
|
|
1150
|
+
if (hasFlag("no-wait")) {
|
|
1151
|
+
throw new CliInputError("Company Signals --no-wait requires --input with more than 25 homogeneous rows.");
|
|
1152
|
+
}
|
|
1153
|
+
if (hasFlag("poll-interval-ms")) {
|
|
1154
|
+
throw new CliInputError("Company Signals --poll-interval-ms requires --input or --job-id.");
|
|
1155
|
+
}
|
|
998
1156
|
const companyDomain = flag("domain") || flag("company-domain");
|
|
999
1157
|
const companyName = flag("company-name");
|
|
1000
1158
|
const signalRunId = flag("signal-run-id");
|
|
1001
|
-
if (!companyDomain && !companyName && !signalRunId)
|
|
1159
|
+
if (!companyDomain && !companyName && !signalRunId) {
|
|
1160
|
+
throw new CliInputError("Usage: signaliz company-signals --domain example.com");
|
|
1161
|
+
}
|
|
1002
1162
|
const request = {
|
|
1003
1163
|
signalRunId,
|
|
1004
1164
|
companyDomain,
|
|
@@ -1018,6 +1178,9 @@ async function companySignals() {
|
|
|
1018
1178
|
maxWaitMs: numberFlag("max-wait-ms"),
|
|
1019
1179
|
idempotencyKey: flag("idempotency-key")
|
|
1020
1180
|
};
|
|
1181
|
+
requireIntegerInRange(request.targetSignalCount, "targetSignalCount", 1, 15);
|
|
1182
|
+
requireIntegerInRange(request.lookbackDays, "lookbackDays", 1, 365);
|
|
1183
|
+
requireIntegerInRange(request.maxWaitMs, "maxWaitMs", 1e4, 12e4);
|
|
1021
1184
|
if (hasFlag("dry-run")) {
|
|
1022
1185
|
const result2 = await createClient().enrichCompanySignals({ ...request, dryRun: true });
|
|
1023
1186
|
dryRunOutput(result2);
|
|
@@ -1181,7 +1344,9 @@ async function signalToCopy() {
|
|
|
1181
1344
|
"concurrency"
|
|
1182
1345
|
].filter(hasFlag);
|
|
1183
1346
|
if (conflicts.length > 0) {
|
|
1184
|
-
|
|
1347
|
+
throw new CliInputError(
|
|
1348
|
+
`Signal to Copy --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`
|
|
1349
|
+
);
|
|
1185
1350
|
}
|
|
1186
1351
|
const result2 = await createClient().resumeSignalCopyBatch(jobId, {
|
|
1187
1352
|
pageSize: numberFlag("page-size"),
|
|
@@ -1213,8 +1378,27 @@ async function signalToCopy() {
|
|
|
1213
1378
|
idempotencyKey: row.idempotencyKey || row.idempotency_key
|
|
1214
1379
|
};
|
|
1215
1380
|
});
|
|
1381
|
+
for (const request2 of requests) {
|
|
1382
|
+
requireIntegerInRange(request2.lookbackDays, "lookbackDays", 1, 365);
|
|
1383
|
+
requireIntegerInRange(request2.maxWaitMs, "maxWaitMs", 1e4, 12e4);
|
|
1384
|
+
requireSignalToCopyIdentity(request2);
|
|
1385
|
+
}
|
|
1386
|
+
if (durableBatch) {
|
|
1387
|
+
const invalidIndex = requests.findIndex(
|
|
1388
|
+
(request2) => request2.skipCache === true || request2.enableDeepSearch === true || Boolean(stringValue(request2.idempotencyKey)) || request2.maxWaitMs !== void 0 || !stringValue(request2.companyDomain) || !stringValue(request2.personName) || !stringValue(request2.title) || !stringValue(request2.campaignOffer)
|
|
1389
|
+
);
|
|
1390
|
+
if (invalidIndex >= 0) {
|
|
1391
|
+
throw new CliInputError(
|
|
1392
|
+
`Signal to Copy batches larger than 25 are available-data-only; row ${invalidIndex} requests fresh/deep/synchronous controls or lacks required recipient fields. Split fresh requests into batches of at most 25.`
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
} else if (hasFlag("no-wait") && !hasFlag("dry-run")) {
|
|
1396
|
+
throw new CliInputError("Signal to Copy --no-wait requires a durable batch of more than 25 rows.");
|
|
1397
|
+
}
|
|
1216
1398
|
if (hasFlag("dry-run")) {
|
|
1217
|
-
if (hasFlag("no-wait"))
|
|
1399
|
+
if (hasFlag("no-wait")) {
|
|
1400
|
+
throw new CliInputError("Signal to Copy --no-wait cannot be combined with --dry-run.");
|
|
1401
|
+
}
|
|
1218
1402
|
const result3 = await createClient().createSignalCopyBatch(requests, { ...batchOptions(), dryRun: true });
|
|
1219
1403
|
dryRunOutput(result3);
|
|
1220
1404
|
return;
|
|
@@ -1231,8 +1415,12 @@ async function signalToCopy() {
|
|
|
1231
1415
|
batchOutput(customerSignalBatch(result2));
|
|
1232
1416
|
return;
|
|
1233
1417
|
}
|
|
1234
|
-
if (hasFlag("no-wait"))
|
|
1235
|
-
|
|
1418
|
+
if (hasFlag("no-wait")) {
|
|
1419
|
+
throw new CliInputError("Signal to Copy --no-wait requires --input with more than 25 available-data-only rows.");
|
|
1420
|
+
}
|
|
1421
|
+
if (hasFlag("poll-interval-ms")) {
|
|
1422
|
+
throw new CliInputError("Signal to Copy --poll-interval-ms requires --input or --job-id.");
|
|
1423
|
+
}
|
|
1236
1424
|
const companyDomain = flag("company-domain") || flag("domain");
|
|
1237
1425
|
const personName = flag("person-name");
|
|
1238
1426
|
const title = flag("title");
|
|
@@ -1251,6 +1439,9 @@ async function signalToCopy() {
|
|
|
1251
1439
|
maxWaitMs: numberFlag("max-wait-ms"),
|
|
1252
1440
|
idempotencyKey: flag("idempotency-key")
|
|
1253
1441
|
};
|
|
1442
|
+
requireIntegerInRange(request.lookbackDays, "lookbackDays", 1, 365);
|
|
1443
|
+
requireIntegerInRange(request.maxWaitMs, "maxWaitMs", 1e4, 12e4);
|
|
1444
|
+
requireSignalToCopyIdentity(request);
|
|
1254
1445
|
if (hasFlag("dry-run")) {
|
|
1255
1446
|
const result2 = await createClient().signalToCopy({ ...request, dryRun: true });
|
|
1256
1447
|
dryRunOutput(result2);
|
|
@@ -1354,19 +1545,30 @@ main().catch((error) => {
|
|
|
1354
1545
|
const message = error instanceof Error ? error.message : String(error);
|
|
1355
1546
|
if (hasFlag("json") || hasFlag("jsonl")) {
|
|
1356
1547
|
const signalizError = error instanceof import_sdk.SignalizError ? error : null;
|
|
1548
|
+
const cliInputError = error instanceof CliInputError;
|
|
1549
|
+
const recoveryReadZeroSpend = signalizError?.details?.retry_strategy === "same_run_recovery" && signalizError.details.credits_used === 0 && signalizError.details.credits_charged === 0 && (process.argv[2] === "find-email" && hasFlag("run-id") || process.argv[2] === "verify-email" && hasFlag("verification-run-id"));
|
|
1357
1550
|
process.stdout.write(`${stringifyMachineJson({
|
|
1358
1551
|
success: false,
|
|
1359
1552
|
error: "product_request_failed",
|
|
1360
|
-
error_code: signalizError?.code || (
|
|
1553
|
+
error_code: signalizError?.code || (cliInputError ? "INPUT_001" : "CLI_ERROR"),
|
|
1361
1554
|
message,
|
|
1362
1555
|
retry_eligible: signalizError?.isRetryable ?? false,
|
|
1363
1556
|
...signalizError?.retryAfter !== void 0 ? {
|
|
1364
1557
|
retry_after: signalizError.retryAfter,
|
|
1365
1558
|
retry_after_seconds: signalizError.retryAfter
|
|
1366
1559
|
} : {},
|
|
1367
|
-
...signalizError?.details?.retry_strategy
|
|
1560
|
+
...["new_idempotency_key", "same_run_recovery"].includes(String(signalizError?.details?.retry_strategy || "")) ? { retry_strategy: signalizError?.details?.retry_strategy } : {},
|
|
1368
1561
|
...signalizError?.details?.credits_used === 0 ? { credits_used: 0 } : {},
|
|
1369
1562
|
...signalizError?.details?.credits_charged === 0 ? { credits_charged: 0 } : {},
|
|
1563
|
+
...signalizError?.details?.live_provider_called === false || recoveryReadZeroSpend ? { live_provider_called: false } : {},
|
|
1564
|
+
...signalizError?.details?.estimated_external_cost_usd === 0 || recoveryReadZeroSpend ? { estimated_external_cost_usd: 0 } : {},
|
|
1565
|
+
...signalizError?.details?.do_not_auto_resubmit === true ? { do_not_auto_resubmit: true } : {},
|
|
1566
|
+
...cliInputError ? {
|
|
1567
|
+
credits_used: 0,
|
|
1568
|
+
credits_charged: 0,
|
|
1569
|
+
live_provider_called: false,
|
|
1570
|
+
estimated_external_cost_usd: 0
|
|
1571
|
+
} : {},
|
|
1370
1572
|
...typeof signalizError?.details?.run_id === "string" ? { run_id: signalizError.details.run_id } : {},
|
|
1371
1573
|
...typeof signalizError?.details?.verification_run_id === "string" ? { verification_run_id: signalizError.details.verification_run_id } : {},
|
|
1372
1574
|
...typeof signalizError?.details?.job_id === "string" ? { job_id: signalizError.details.job_id } : {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaliz/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.63",
|
|
4
4
|
"description": "Signaliz CLI for Signals Everything, Find Email, Verify Email, Company Signal Enrichment, and Signal to Copy AI.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"signaliz": "dist/bin.js"
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"node": ">=18"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@signaliz/sdk": "^1.0.
|
|
32
|
+
"@signaliz/sdk": "^1.0.67"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"tsup": "^8.0.0",
|