@signaliz/cli 1.0.60 → 1.0.62
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 +248 -39
- 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,
|
|
@@ -241,11 +300,31 @@ function customerEmailResult(value, kind) {
|
|
|
241
300
|
billing_metadata: normalizedBillingMetadata,
|
|
242
301
|
...Object.keys(historicalBillingMetadata2).length > 0 ? { historical_billing_metadata: historicalBillingMetadata2 } : {},
|
|
243
302
|
...estimatedExternalCostUsd2 !== null ? { estimated_external_cost_usd: estimatedExternalCostUsd2 } : {},
|
|
244
|
-
...
|
|
303
|
+
...directRunId ? { run_id: directRunId } : {},
|
|
304
|
+
...raw.recovery_mode ? {
|
|
305
|
+
recovery_mode: raw.recovery_mode,
|
|
306
|
+
recovery_status: raw.recovery_status ?? "completed"
|
|
307
|
+
} : {},
|
|
245
308
|
...raw.cache_publication_status ? {
|
|
246
309
|
cache_publication_status: raw.cache_publication_status,
|
|
247
310
|
cache_publication_retry_eligible: raw.cache_publication_retry_eligible === true,
|
|
248
311
|
...raw.suggested_action ? { suggested_action: raw.suggested_action } : {}
|
|
312
|
+
} : {},
|
|
313
|
+
...terminalErrorCode ? {
|
|
314
|
+
email: null,
|
|
315
|
+
success: false,
|
|
316
|
+
found: false,
|
|
317
|
+
is_valid: false,
|
|
318
|
+
verified_for_sending: false,
|
|
319
|
+
is_deliverable: false,
|
|
320
|
+
error_code: terminalErrorCode,
|
|
321
|
+
retry_eligible: retryEligible,
|
|
322
|
+
...terminalError ? { error: terminalError } : {},
|
|
323
|
+
...retryEligible && retryStrategy ? { retry_strategy: retryStrategy } : {},
|
|
324
|
+
...suggestedAction ? { suggested_action: suggestedAction } : {},
|
|
325
|
+
...result.doNotAutoResubmit === true || raw.do_not_auto_resubmit === true ? { do_not_auto_resubmit: true } : {},
|
|
326
|
+
...verificationRunId2 ? { verification_run_id: verificationRunId2 } : {},
|
|
327
|
+
...jobId ? { job_id: jobId } : {}
|
|
249
328
|
} : {}
|
|
250
329
|
};
|
|
251
330
|
}
|
|
@@ -375,7 +454,9 @@ function rejectDurableNoWaitPollingFlags(label) {
|
|
|
375
454
|
if (!hasFlag("no-wait")) return;
|
|
376
455
|
const conflicts = ["max-wait-ms", "poll-interval-ms", "concurrency"].filter(hasFlag);
|
|
377
456
|
if (conflicts.length > 0) {
|
|
378
|
-
|
|
457
|
+
throw new CliInputError(
|
|
458
|
+
`${label} --no-wait cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`
|
|
459
|
+
);
|
|
379
460
|
}
|
|
380
461
|
}
|
|
381
462
|
function recoverableJobOutput(value, command) {
|
|
@@ -454,10 +535,11 @@ function resolveBaseUrl() {
|
|
|
454
535
|
const config = loadConfig();
|
|
455
536
|
return (flag("base-url") || process.env.SIGNALIZ_BASE_URL || process.env.SIGNALIZ_API_URL || config.baseUrl || "https://api.signaliz.com/functions/v1").replace(/\/$/, "");
|
|
456
537
|
}
|
|
457
|
-
function createClient() {
|
|
538
|
+
function createClient(maxRetries) {
|
|
458
539
|
return new import_sdk.Signaliz({
|
|
459
540
|
apiKey: resolveApiKey(),
|
|
460
|
-
baseUrl: resolveBaseUrl()
|
|
541
|
+
baseUrl: resolveBaseUrl(),
|
|
542
|
+
...maxRetries !== void 0 ? { maxRetries } : {}
|
|
461
543
|
});
|
|
462
544
|
}
|
|
463
545
|
function numberValue(value) {
|
|
@@ -470,6 +552,18 @@ function nullableNumberValue(value) {
|
|
|
470
552
|
function stringValue(value) {
|
|
471
553
|
return typeof value === "string" ? value.trim() : "";
|
|
472
554
|
}
|
|
555
|
+
function hasFindEmailLinkedInIdentity(value) {
|
|
556
|
+
const linkedinUrl = stringValue(value);
|
|
557
|
+
if (!linkedinUrl) return false;
|
|
558
|
+
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;
|
|
559
|
+
try {
|
|
560
|
+
const url = new URL(linkedinUrl);
|
|
561
|
+
const hostname = url.hostname.toLowerCase();
|
|
562
|
+
return url.protocol === "https:" && !url.username && !url.password && !url.port && (hostname === "linkedin.com" || hostname.endsWith(".linkedin.com")) && /^\/in\/[^/]+\/?$/i.test(url.pathname);
|
|
563
|
+
} catch {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
473
567
|
function signalsEverythingErrorType(status) {
|
|
474
568
|
if (status === 400 || status === 422) return "validation";
|
|
475
569
|
if (status === 401) return "auth_expired";
|
|
@@ -497,9 +591,9 @@ function normalizeSignalsEverythingResult(request, value) {
|
|
|
497
591
|
source: stringValue(item.source)
|
|
498
592
|
};
|
|
499
593
|
}).filter((item) => Boolean(item.url)) : [];
|
|
500
|
-
const eventDate = stringValue(signal.event_date || signal.date)
|
|
501
|
-
const sourceUrl = stringValue(signal.source_url || signal.url)
|
|
502
|
-
if (!eventDate || !sourceUrl || !evidence.some((item) =>
|
|
594
|
+
const eventDate = stringValue(signal.event_date || signal.date);
|
|
595
|
+
const sourceUrl = stringValue(signal.source_url || signal.url);
|
|
596
|
+
if (!eventDate || !sourceUrl || !evidence.some((item) => item.url === sourceUrl && item.publishedAt === eventDate)) {
|
|
503
597
|
throw new Error("Signals Everything response violated the dated-evidence and source-URL contract.");
|
|
504
598
|
}
|
|
505
599
|
return {
|
|
@@ -522,6 +616,7 @@ function normalizeSignalsEverythingResult(request, value) {
|
|
|
522
616
|
const status = statuses.has(statusValue) ? statusValue : signals2.length > 0 ? "completed" : "failed";
|
|
523
617
|
const guardrail = record(value.cost_guardrail);
|
|
524
618
|
const maxCost = numberValue(guardrail.max_cost_per_qualified_signal_usd);
|
|
619
|
+
const costSource = stringValue(guardrail.cost_source);
|
|
525
620
|
const identityCostSource = stringValue(guardrail.identity_cost_source);
|
|
526
621
|
const warnings = Array.isArray(value.warnings) ? value.warnings.filter((warning) => typeof warning === "string") : void 0;
|
|
527
622
|
return {
|
|
@@ -539,6 +634,13 @@ function normalizeSignalsEverythingResult(request, value) {
|
|
|
539
634
|
...maxCost === void 0 ? {} : {
|
|
540
635
|
costGuardrail: {
|
|
541
636
|
maxCostPerQualifiedSignalUsd: maxCost,
|
|
637
|
+
projectedSourceCostUsd: nullableNumberValue(guardrail.projected_source_cost_usd),
|
|
638
|
+
maxSourceCostUsd: nullableNumberValue(guardrail.max_source_cost_usd),
|
|
639
|
+
projectedCostPerRequestedCompanySignalUsd: nullableNumberValue(
|
|
640
|
+
guardrail.projected_cost_per_requested_company_signal_usd
|
|
641
|
+
),
|
|
642
|
+
sourceCostUsd: nullableNumberValue(guardrail.source_cost_usd),
|
|
643
|
+
costSource: ["observed", "catalog_preflight", "catalog_bounded", "mixed", "unavailable"].includes(costSource) ? costSource : void 0,
|
|
542
644
|
costPerQualifiedSignalUsd: nullableNumberValue(guardrail.cost_per_qualified_signal_usd),
|
|
543
645
|
identityResolutionCostUsd: nullableNumberValue(guardrail.identity_resolution_cost_usd),
|
|
544
646
|
identityCostSource: [
|
|
@@ -547,7 +649,13 @@ function normalizeSignalsEverythingResult(request, value) {
|
|
|
547
649
|
"not_used",
|
|
548
650
|
"unavailable"
|
|
549
651
|
].includes(identityCostSource) ? identityCostSource : void 0,
|
|
550
|
-
identityProviderRequests: numberValue(guardrail.identity_provider_requests)
|
|
652
|
+
identityProviderRequests: numberValue(guardrail.identity_provider_requests),
|
|
653
|
+
qualifiedCompanySignalCount: numberValue(guardrail.qualified_company_signal_count),
|
|
654
|
+
returnedCompanySignalCount: numberValue(guardrail.returned_company_signal_count),
|
|
655
|
+
availableCompanySignalCount: numberValue(guardrail.available_company_signal_count),
|
|
656
|
+
acquisitionCandidateLimit: numberValue(guardrail.acquisition_candidate_limit),
|
|
657
|
+
minimumQualifiedCompanySignals: numberValue(guardrail.minimum_qualified_company_signals),
|
|
658
|
+
withinLimit: guardrail.within_limit === true
|
|
551
659
|
}
|
|
552
660
|
},
|
|
553
661
|
...warnings?.length ? { warnings } : {},
|
|
@@ -702,7 +810,9 @@ async function findEmail() {
|
|
|
702
810
|
"skip-cache"
|
|
703
811
|
].filter(hasFlag);
|
|
704
812
|
if (conflicts.length > 0) {
|
|
705
|
-
|
|
813
|
+
throw new CliInputError(
|
|
814
|
+
`Find Email --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`
|
|
815
|
+
);
|
|
706
816
|
}
|
|
707
817
|
const result2 = await createClient().resumeFindEmailBatch(jobId, {
|
|
708
818
|
pageSize: numberFlag("page-size"),
|
|
@@ -732,6 +842,15 @@ async function findEmail() {
|
|
|
732
842
|
idempotencyKey
|
|
733
843
|
};
|
|
734
844
|
});
|
|
845
|
+
const invalidIndexes = requests.flatMap((request2, index) => {
|
|
846
|
+
if (stringValue(request2.runId)) return [];
|
|
847
|
+
if (hasFindEmailLinkedInIdentity(request2.linkedinUrl)) return [];
|
|
848
|
+
const hasName = stringValue(request2.fullName) || stringValue(request2.firstName) && stringValue(request2.lastName);
|
|
849
|
+
return !stringValue(request2.companyDomain) || !hasName ? [index] : [];
|
|
850
|
+
});
|
|
851
|
+
if (invalidIndexes.length === requests.length) {
|
|
852
|
+
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.");
|
|
853
|
+
}
|
|
735
854
|
if (hasFlag("dry-run")) {
|
|
736
855
|
const result3 = await createClient().findEmails(requests, { ...batchOptions(), dryRun: true });
|
|
737
856
|
dryRunOutput(result3);
|
|
@@ -747,8 +866,9 @@ async function findEmail() {
|
|
|
747
866
|
const firstName = flag("first-name");
|
|
748
867
|
const lastName = flag("last-name");
|
|
749
868
|
const linkedinUrl = flag("linkedin-url");
|
|
750
|
-
|
|
751
|
-
|
|
869
|
+
const hasNameAndDomain = Boolean(companyDomain && (fullName || firstName && lastName));
|
|
870
|
+
if (!runId && !hasFindEmailLinkedInIdentity(linkedinUrl) && !hasNameAndDomain) {
|
|
871
|
+
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.");
|
|
752
872
|
}
|
|
753
873
|
const request = runId ? { runId } : {
|
|
754
874
|
companyDomain,
|
|
@@ -761,11 +881,11 @@ async function findEmail() {
|
|
|
761
881
|
idempotencyKey: flag("idempotency-key")
|
|
762
882
|
};
|
|
763
883
|
if (hasFlag("dry-run")) {
|
|
764
|
-
const result2 = await createClient().findEmail({ ...request, dryRun: true });
|
|
884
|
+
const result2 = await createClient(runId ? 0 : void 0).findEmail({ ...request, dryRun: true });
|
|
765
885
|
dryRunOutput(result2);
|
|
766
886
|
return;
|
|
767
887
|
}
|
|
768
|
-
const result = await createClient().findEmail(request);
|
|
888
|
+
const result = await createClient(runId ? 0 : void 0).findEmail(request);
|
|
769
889
|
const publicResult = customerEmailResult(result, "find");
|
|
770
890
|
if (outputFailedCoreProduct(publicResult, "No verified email found")) return;
|
|
771
891
|
output(publicResult, () => {
|
|
@@ -805,7 +925,7 @@ async function verifyEmail(args) {
|
|
|
805
925
|
...positionalEmail ? ["email argument"] : [],
|
|
806
926
|
...conflicts.map((name) => `--${name}`)
|
|
807
927
|
];
|
|
808
|
-
|
|
928
|
+
throw new CliInputError(`Verify Email --job-id cannot be combined with ${conflictList.join(", ")}.`);
|
|
809
929
|
}
|
|
810
930
|
const result2 = await createClient().resumeVerifyEmailBatch(jobId, {
|
|
811
931
|
pageSize: numberFlag("page-size"),
|
|
@@ -846,7 +966,9 @@ async function verifyEmail(args) {
|
|
|
846
966
|
}
|
|
847
967
|
const email = args[0] && !args[0].startsWith("--") ? args[0] : flag("email");
|
|
848
968
|
const verificationRunId = flag("verification-run-id");
|
|
849
|
-
if (!email && !verificationRunId)
|
|
969
|
+
if (!email && !verificationRunId) {
|
|
970
|
+
throw new CliInputError("Usage: signaliz verify-email [jane@example.com] [--verification-run-id run_...]");
|
|
971
|
+
}
|
|
850
972
|
const options = {
|
|
851
973
|
skipCache: hasFlag("skip-cache"),
|
|
852
974
|
verificationRunId,
|
|
@@ -856,11 +978,11 @@ async function verifyEmail(args) {
|
|
|
856
978
|
idempotencyKey: flag("idempotency-key")
|
|
857
979
|
};
|
|
858
980
|
if (hasFlag("dry-run")) {
|
|
859
|
-
const result2 = await createClient().verifyEmail(email, { ...options, dryRun: true });
|
|
981
|
+
const result2 = await createClient(verificationRunId ? 0 : void 0).verifyEmail(email, { ...options, dryRun: true });
|
|
860
982
|
dryRunOutput(result2);
|
|
861
983
|
return;
|
|
862
984
|
}
|
|
863
|
-
const result = await createClient().verifyEmail(email, options);
|
|
985
|
+
const result = await createClient(verificationRunId ? 0 : void 0).verifyEmail(email, options);
|
|
864
986
|
const publicResult = customerEmailResult(result, "verify");
|
|
865
987
|
if (outputFailedCoreProduct(publicResult, "Email verification failed")) return;
|
|
866
988
|
output(publicResult, () => {
|
|
@@ -918,7 +1040,9 @@ async function companySignals() {
|
|
|
918
1040
|
"concurrency"
|
|
919
1041
|
].filter(hasFlag);
|
|
920
1042
|
if (conflicts.length > 0) {
|
|
921
|
-
|
|
1043
|
+
throw new CliInputError(
|
|
1044
|
+
`Company Signals --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`
|
|
1045
|
+
);
|
|
922
1046
|
}
|
|
923
1047
|
const result2 = await createClient().resumeCompanySignalBatch(jobId, {
|
|
924
1048
|
pageSize: numberFlag("page-size"),
|
|
@@ -960,9 +1084,46 @@ async function companySignals() {
|
|
|
960
1084
|
for (const request2 of requests) {
|
|
961
1085
|
requireIntegerInRange(request2.targetSignalCount, "targetSignalCount", 1, 15);
|
|
962
1086
|
requireIntegerInRange(request2.lookbackDays, "lookbackDays", 1, 365);
|
|
1087
|
+
requireIntegerInRange(request2.maxWaitMs, "maxWaitMs", 1e4, 12e4);
|
|
1088
|
+
}
|
|
1089
|
+
if (durableBatch && requests.some((request2) => request2.includeCandidateEvidence === true)) {
|
|
1090
|
+
throw new CliInputError(
|
|
1091
|
+
"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."
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
if (durableBatch) {
|
|
1095
|
+
const controlKey = (request2) => JSON.stringify({
|
|
1096
|
+
researchPrompt: request2.researchPrompt,
|
|
1097
|
+
signalTypes: request2.signalTypes,
|
|
1098
|
+
targetSignalCount: request2.targetSignalCount,
|
|
1099
|
+
lookbackDays: request2.lookbackDays,
|
|
1100
|
+
online: request2.online,
|
|
1101
|
+
enableDeepSearch: request2.enableDeepSearch,
|
|
1102
|
+
searchMode: request2.searchMode ?? "balanced",
|
|
1103
|
+
includeSummary: request2.includeSummary,
|
|
1104
|
+
enableOutreachIntelligence: request2.enableOutreachIntelligence,
|
|
1105
|
+
enablePredictiveIntelligence: request2.enablePredictiveIntelligence,
|
|
1106
|
+
skipCache: request2.skipCache,
|
|
1107
|
+
includeCandidateEvidence: request2.includeCandidateEvidence
|
|
1108
|
+
});
|
|
1109
|
+
const firstControlKey = controlKey(requests[0]);
|
|
1110
|
+
const incompatible = requests.some(
|
|
1111
|
+
(request2) => Boolean(stringValue(request2.signalRunId)) || Boolean(stringValue(request2.idempotencyKey)) || !stringValue(request2.companyDomain) && !stringValue(request2.companyName) || controlKey(request2) !== firstControlKey
|
|
1112
|
+
);
|
|
1113
|
+
if (incompatible) {
|
|
1114
|
+
throw new CliInputError(
|
|
1115
|
+
"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."
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
} else if (hasFlag("no-wait") && !hasFlag("dry-run")) {
|
|
1119
|
+
throw new CliInputError(
|
|
1120
|
+
"Company Signals --no-wait requires more than 25 homogeneous new-enrichment rows; recovery reads and mixed controls must use batches of at most 25."
|
|
1121
|
+
);
|
|
963
1122
|
}
|
|
964
1123
|
if (hasFlag("dry-run")) {
|
|
965
|
-
if (hasFlag("no-wait"))
|
|
1124
|
+
if (hasFlag("no-wait")) {
|
|
1125
|
+
throw new CliInputError("Company Signals --no-wait cannot be combined with --dry-run.");
|
|
1126
|
+
}
|
|
966
1127
|
const result3 = await createClient().enrichCompanies(requests, { ...batchOptions(), dryRun: true });
|
|
967
1128
|
dryRunOutput(result3);
|
|
968
1129
|
return;
|
|
@@ -979,12 +1140,18 @@ async function companySignals() {
|
|
|
979
1140
|
batchOutput(customerSignalBatch(result2));
|
|
980
1141
|
return;
|
|
981
1142
|
}
|
|
982
|
-
if (hasFlag("no-wait"))
|
|
983
|
-
|
|
1143
|
+
if (hasFlag("no-wait")) {
|
|
1144
|
+
throw new CliInputError("Company Signals --no-wait requires --input with more than 25 homogeneous rows.");
|
|
1145
|
+
}
|
|
1146
|
+
if (hasFlag("poll-interval-ms")) {
|
|
1147
|
+
throw new CliInputError("Company Signals --poll-interval-ms requires --input or --job-id.");
|
|
1148
|
+
}
|
|
984
1149
|
const companyDomain = flag("domain") || flag("company-domain");
|
|
985
1150
|
const companyName = flag("company-name");
|
|
986
1151
|
const signalRunId = flag("signal-run-id");
|
|
987
|
-
if (!companyDomain && !companyName && !signalRunId)
|
|
1152
|
+
if (!companyDomain && !companyName && !signalRunId) {
|
|
1153
|
+
throw new CliInputError("Usage: signaliz company-signals --domain example.com");
|
|
1154
|
+
}
|
|
988
1155
|
const request = {
|
|
989
1156
|
signalRunId,
|
|
990
1157
|
companyDomain,
|
|
@@ -1004,6 +1171,9 @@ async function companySignals() {
|
|
|
1004
1171
|
maxWaitMs: numberFlag("max-wait-ms"),
|
|
1005
1172
|
idempotencyKey: flag("idempotency-key")
|
|
1006
1173
|
};
|
|
1174
|
+
requireIntegerInRange(request.targetSignalCount, "targetSignalCount", 1, 15);
|
|
1175
|
+
requireIntegerInRange(request.lookbackDays, "lookbackDays", 1, 365);
|
|
1176
|
+
requireIntegerInRange(request.maxWaitMs, "maxWaitMs", 1e4, 12e4);
|
|
1007
1177
|
if (hasFlag("dry-run")) {
|
|
1008
1178
|
const result2 = await createClient().enrichCompanySignals({ ...request, dryRun: true });
|
|
1009
1179
|
dryRunOutput(result2);
|
|
@@ -1167,7 +1337,9 @@ async function signalToCopy() {
|
|
|
1167
1337
|
"concurrency"
|
|
1168
1338
|
].filter(hasFlag);
|
|
1169
1339
|
if (conflicts.length > 0) {
|
|
1170
|
-
|
|
1340
|
+
throw new CliInputError(
|
|
1341
|
+
`Signal to Copy --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`
|
|
1342
|
+
);
|
|
1171
1343
|
}
|
|
1172
1344
|
const result2 = await createClient().resumeSignalCopyBatch(jobId, {
|
|
1173
1345
|
pageSize: numberFlag("page-size"),
|
|
@@ -1199,8 +1371,27 @@ async function signalToCopy() {
|
|
|
1199
1371
|
idempotencyKey: row.idempotencyKey || row.idempotency_key
|
|
1200
1372
|
};
|
|
1201
1373
|
});
|
|
1374
|
+
for (const request2 of requests) {
|
|
1375
|
+
requireIntegerInRange(request2.lookbackDays, "lookbackDays", 1, 365);
|
|
1376
|
+
requireIntegerInRange(request2.maxWaitMs, "maxWaitMs", 1e4, 12e4);
|
|
1377
|
+
requireSignalToCopyIdentity(request2);
|
|
1378
|
+
}
|
|
1379
|
+
if (durableBatch) {
|
|
1380
|
+
const invalidIndex = requests.findIndex(
|
|
1381
|
+
(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)
|
|
1382
|
+
);
|
|
1383
|
+
if (invalidIndex >= 0) {
|
|
1384
|
+
throw new CliInputError(
|
|
1385
|
+
`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.`
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
} else if (hasFlag("no-wait") && !hasFlag("dry-run")) {
|
|
1389
|
+
throw new CliInputError("Signal to Copy --no-wait requires a durable batch of more than 25 rows.");
|
|
1390
|
+
}
|
|
1202
1391
|
if (hasFlag("dry-run")) {
|
|
1203
|
-
if (hasFlag("no-wait"))
|
|
1392
|
+
if (hasFlag("no-wait")) {
|
|
1393
|
+
throw new CliInputError("Signal to Copy --no-wait cannot be combined with --dry-run.");
|
|
1394
|
+
}
|
|
1204
1395
|
const result3 = await createClient().createSignalCopyBatch(requests, { ...batchOptions(), dryRun: true });
|
|
1205
1396
|
dryRunOutput(result3);
|
|
1206
1397
|
return;
|
|
@@ -1217,8 +1408,12 @@ async function signalToCopy() {
|
|
|
1217
1408
|
batchOutput(customerSignalBatch(result2));
|
|
1218
1409
|
return;
|
|
1219
1410
|
}
|
|
1220
|
-
if (hasFlag("no-wait"))
|
|
1221
|
-
|
|
1411
|
+
if (hasFlag("no-wait")) {
|
|
1412
|
+
throw new CliInputError("Signal to Copy --no-wait requires --input with more than 25 available-data-only rows.");
|
|
1413
|
+
}
|
|
1414
|
+
if (hasFlag("poll-interval-ms")) {
|
|
1415
|
+
throw new CliInputError("Signal to Copy --poll-interval-ms requires --input or --job-id.");
|
|
1416
|
+
}
|
|
1222
1417
|
const companyDomain = flag("company-domain") || flag("domain");
|
|
1223
1418
|
const personName = flag("person-name");
|
|
1224
1419
|
const title = flag("title");
|
|
@@ -1237,6 +1432,9 @@ async function signalToCopy() {
|
|
|
1237
1432
|
maxWaitMs: numberFlag("max-wait-ms"),
|
|
1238
1433
|
idempotencyKey: flag("idempotency-key")
|
|
1239
1434
|
};
|
|
1435
|
+
requireIntegerInRange(request.lookbackDays, "lookbackDays", 1, 365);
|
|
1436
|
+
requireIntegerInRange(request.maxWaitMs, "maxWaitMs", 1e4, 12e4);
|
|
1437
|
+
requireSignalToCopyIdentity(request);
|
|
1240
1438
|
if (hasFlag("dry-run")) {
|
|
1241
1439
|
const result2 = await createClient().signalToCopy({ ...request, dryRun: true });
|
|
1242
1440
|
dryRunOutput(result2);
|
|
@@ -1340,19 +1538,30 @@ main().catch((error) => {
|
|
|
1340
1538
|
const message = error instanceof Error ? error.message : String(error);
|
|
1341
1539
|
if (hasFlag("json") || hasFlag("jsonl")) {
|
|
1342
1540
|
const signalizError = error instanceof import_sdk.SignalizError ? error : null;
|
|
1541
|
+
const cliInputError = error instanceof CliInputError;
|
|
1542
|
+
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"));
|
|
1343
1543
|
process.stdout.write(`${stringifyMachineJson({
|
|
1344
1544
|
success: false,
|
|
1345
1545
|
error: "product_request_failed",
|
|
1346
|
-
error_code: signalizError?.code || (
|
|
1546
|
+
error_code: signalizError?.code || (cliInputError ? "INPUT_001" : "CLI_ERROR"),
|
|
1347
1547
|
message,
|
|
1348
1548
|
retry_eligible: signalizError?.isRetryable ?? false,
|
|
1349
1549
|
...signalizError?.retryAfter !== void 0 ? {
|
|
1350
1550
|
retry_after: signalizError.retryAfter,
|
|
1351
1551
|
retry_after_seconds: signalizError.retryAfter
|
|
1352
1552
|
} : {},
|
|
1353
|
-
...signalizError?.details?.retry_strategy
|
|
1553
|
+
...["new_idempotency_key", "same_run_recovery"].includes(String(signalizError?.details?.retry_strategy || "")) ? { retry_strategy: signalizError?.details?.retry_strategy } : {},
|
|
1354
1554
|
...signalizError?.details?.credits_used === 0 ? { credits_used: 0 } : {},
|
|
1355
1555
|
...signalizError?.details?.credits_charged === 0 ? { credits_charged: 0 } : {},
|
|
1556
|
+
...signalizError?.details?.live_provider_called === false || recoveryReadZeroSpend ? { live_provider_called: false } : {},
|
|
1557
|
+
...signalizError?.details?.estimated_external_cost_usd === 0 || recoveryReadZeroSpend ? { estimated_external_cost_usd: 0 } : {},
|
|
1558
|
+
...signalizError?.details?.do_not_auto_resubmit === true ? { do_not_auto_resubmit: true } : {},
|
|
1559
|
+
...cliInputError ? {
|
|
1560
|
+
credits_used: 0,
|
|
1561
|
+
credits_charged: 0,
|
|
1562
|
+
live_provider_called: false,
|
|
1563
|
+
estimated_external_cost_usd: 0
|
|
1564
|
+
} : {},
|
|
1356
1565
|
...typeof signalizError?.details?.run_id === "string" ? { run_id: signalizError.details.run_id } : {},
|
|
1357
1566
|
...typeof signalizError?.details?.verification_run_id === "string" ? { verification_run_id: signalizError.details.verification_run_id } : {},
|
|
1358
1567
|
...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.62",
|
|
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.66"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"tsup": "^8.0.0",
|