@signaliz/cli 1.0.61 → 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 +230 -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,
|
|
@@ -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";
|
|
@@ -716,7 +810,9 @@ async function findEmail() {
|
|
|
716
810
|
"skip-cache"
|
|
717
811
|
].filter(hasFlag);
|
|
718
812
|
if (conflicts.length > 0) {
|
|
719
|
-
|
|
813
|
+
throw new CliInputError(
|
|
814
|
+
`Find Email --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`
|
|
815
|
+
);
|
|
720
816
|
}
|
|
721
817
|
const result2 = await createClient().resumeFindEmailBatch(jobId, {
|
|
722
818
|
pageSize: numberFlag("page-size"),
|
|
@@ -746,6 +842,15 @@ async function findEmail() {
|
|
|
746
842
|
idempotencyKey
|
|
747
843
|
};
|
|
748
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
|
+
}
|
|
749
854
|
if (hasFlag("dry-run")) {
|
|
750
855
|
const result3 = await createClient().findEmails(requests, { ...batchOptions(), dryRun: true });
|
|
751
856
|
dryRunOutput(result3);
|
|
@@ -761,8 +866,9 @@ async function findEmail() {
|
|
|
761
866
|
const firstName = flag("first-name");
|
|
762
867
|
const lastName = flag("last-name");
|
|
763
868
|
const linkedinUrl = flag("linkedin-url");
|
|
764
|
-
|
|
765
|
-
|
|
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.");
|
|
766
872
|
}
|
|
767
873
|
const request = runId ? { runId } : {
|
|
768
874
|
companyDomain,
|
|
@@ -775,11 +881,11 @@ async function findEmail() {
|
|
|
775
881
|
idempotencyKey: flag("idempotency-key")
|
|
776
882
|
};
|
|
777
883
|
if (hasFlag("dry-run")) {
|
|
778
|
-
const result2 = await createClient().findEmail({ ...request, dryRun: true });
|
|
884
|
+
const result2 = await createClient(runId ? 0 : void 0).findEmail({ ...request, dryRun: true });
|
|
779
885
|
dryRunOutput(result2);
|
|
780
886
|
return;
|
|
781
887
|
}
|
|
782
|
-
const result = await createClient().findEmail(request);
|
|
888
|
+
const result = await createClient(runId ? 0 : void 0).findEmail(request);
|
|
783
889
|
const publicResult = customerEmailResult(result, "find");
|
|
784
890
|
if (outputFailedCoreProduct(publicResult, "No verified email found")) return;
|
|
785
891
|
output(publicResult, () => {
|
|
@@ -819,7 +925,7 @@ async function verifyEmail(args) {
|
|
|
819
925
|
...positionalEmail ? ["email argument"] : [],
|
|
820
926
|
...conflicts.map((name) => `--${name}`)
|
|
821
927
|
];
|
|
822
|
-
|
|
928
|
+
throw new CliInputError(`Verify Email --job-id cannot be combined with ${conflictList.join(", ")}.`);
|
|
823
929
|
}
|
|
824
930
|
const result2 = await createClient().resumeVerifyEmailBatch(jobId, {
|
|
825
931
|
pageSize: numberFlag("page-size"),
|
|
@@ -860,7 +966,9 @@ async function verifyEmail(args) {
|
|
|
860
966
|
}
|
|
861
967
|
const email = args[0] && !args[0].startsWith("--") ? args[0] : flag("email");
|
|
862
968
|
const verificationRunId = flag("verification-run-id");
|
|
863
|
-
if (!email && !verificationRunId)
|
|
969
|
+
if (!email && !verificationRunId) {
|
|
970
|
+
throw new CliInputError("Usage: signaliz verify-email [jane@example.com] [--verification-run-id run_...]");
|
|
971
|
+
}
|
|
864
972
|
const options = {
|
|
865
973
|
skipCache: hasFlag("skip-cache"),
|
|
866
974
|
verificationRunId,
|
|
@@ -870,11 +978,11 @@ async function verifyEmail(args) {
|
|
|
870
978
|
idempotencyKey: flag("idempotency-key")
|
|
871
979
|
};
|
|
872
980
|
if (hasFlag("dry-run")) {
|
|
873
|
-
const result2 = await createClient().verifyEmail(email, { ...options, dryRun: true });
|
|
981
|
+
const result2 = await createClient(verificationRunId ? 0 : void 0).verifyEmail(email, { ...options, dryRun: true });
|
|
874
982
|
dryRunOutput(result2);
|
|
875
983
|
return;
|
|
876
984
|
}
|
|
877
|
-
const result = await createClient().verifyEmail(email, options);
|
|
985
|
+
const result = await createClient(verificationRunId ? 0 : void 0).verifyEmail(email, options);
|
|
878
986
|
const publicResult = customerEmailResult(result, "verify");
|
|
879
987
|
if (outputFailedCoreProduct(publicResult, "Email verification failed")) return;
|
|
880
988
|
output(publicResult, () => {
|
|
@@ -932,7 +1040,9 @@ async function companySignals() {
|
|
|
932
1040
|
"concurrency"
|
|
933
1041
|
].filter(hasFlag);
|
|
934
1042
|
if (conflicts.length > 0) {
|
|
935
|
-
|
|
1043
|
+
throw new CliInputError(
|
|
1044
|
+
`Company Signals --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`
|
|
1045
|
+
);
|
|
936
1046
|
}
|
|
937
1047
|
const result2 = await createClient().resumeCompanySignalBatch(jobId, {
|
|
938
1048
|
pageSize: numberFlag("page-size"),
|
|
@@ -974,9 +1084,46 @@ async function companySignals() {
|
|
|
974
1084
|
for (const request2 of requests) {
|
|
975
1085
|
requireIntegerInRange(request2.targetSignalCount, "targetSignalCount", 1, 15);
|
|
976
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
|
+
);
|
|
977
1122
|
}
|
|
978
1123
|
if (hasFlag("dry-run")) {
|
|
979
|
-
if (hasFlag("no-wait"))
|
|
1124
|
+
if (hasFlag("no-wait")) {
|
|
1125
|
+
throw new CliInputError("Company Signals --no-wait cannot be combined with --dry-run.");
|
|
1126
|
+
}
|
|
980
1127
|
const result3 = await createClient().enrichCompanies(requests, { ...batchOptions(), dryRun: true });
|
|
981
1128
|
dryRunOutput(result3);
|
|
982
1129
|
return;
|
|
@@ -993,12 +1140,18 @@ async function companySignals() {
|
|
|
993
1140
|
batchOutput(customerSignalBatch(result2));
|
|
994
1141
|
return;
|
|
995
1142
|
}
|
|
996
|
-
if (hasFlag("no-wait"))
|
|
997
|
-
|
|
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
|
+
}
|
|
998
1149
|
const companyDomain = flag("domain") || flag("company-domain");
|
|
999
1150
|
const companyName = flag("company-name");
|
|
1000
1151
|
const signalRunId = flag("signal-run-id");
|
|
1001
|
-
if (!companyDomain && !companyName && !signalRunId)
|
|
1152
|
+
if (!companyDomain && !companyName && !signalRunId) {
|
|
1153
|
+
throw new CliInputError("Usage: signaliz company-signals --domain example.com");
|
|
1154
|
+
}
|
|
1002
1155
|
const request = {
|
|
1003
1156
|
signalRunId,
|
|
1004
1157
|
companyDomain,
|
|
@@ -1018,6 +1171,9 @@ async function companySignals() {
|
|
|
1018
1171
|
maxWaitMs: numberFlag("max-wait-ms"),
|
|
1019
1172
|
idempotencyKey: flag("idempotency-key")
|
|
1020
1173
|
};
|
|
1174
|
+
requireIntegerInRange(request.targetSignalCount, "targetSignalCount", 1, 15);
|
|
1175
|
+
requireIntegerInRange(request.lookbackDays, "lookbackDays", 1, 365);
|
|
1176
|
+
requireIntegerInRange(request.maxWaitMs, "maxWaitMs", 1e4, 12e4);
|
|
1021
1177
|
if (hasFlag("dry-run")) {
|
|
1022
1178
|
const result2 = await createClient().enrichCompanySignals({ ...request, dryRun: true });
|
|
1023
1179
|
dryRunOutput(result2);
|
|
@@ -1181,7 +1337,9 @@ async function signalToCopy() {
|
|
|
1181
1337
|
"concurrency"
|
|
1182
1338
|
].filter(hasFlag);
|
|
1183
1339
|
if (conflicts.length > 0) {
|
|
1184
|
-
|
|
1340
|
+
throw new CliInputError(
|
|
1341
|
+
`Signal to Copy --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`
|
|
1342
|
+
);
|
|
1185
1343
|
}
|
|
1186
1344
|
const result2 = await createClient().resumeSignalCopyBatch(jobId, {
|
|
1187
1345
|
pageSize: numberFlag("page-size"),
|
|
@@ -1213,8 +1371,27 @@ async function signalToCopy() {
|
|
|
1213
1371
|
idempotencyKey: row.idempotencyKey || row.idempotency_key
|
|
1214
1372
|
};
|
|
1215
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
|
+
}
|
|
1216
1391
|
if (hasFlag("dry-run")) {
|
|
1217
|
-
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
|
+
}
|
|
1218
1395
|
const result3 = await createClient().createSignalCopyBatch(requests, { ...batchOptions(), dryRun: true });
|
|
1219
1396
|
dryRunOutput(result3);
|
|
1220
1397
|
return;
|
|
@@ -1231,8 +1408,12 @@ async function signalToCopy() {
|
|
|
1231
1408
|
batchOutput(customerSignalBatch(result2));
|
|
1232
1409
|
return;
|
|
1233
1410
|
}
|
|
1234
|
-
if (hasFlag("no-wait"))
|
|
1235
|
-
|
|
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
|
+
}
|
|
1236
1417
|
const companyDomain = flag("company-domain") || flag("domain");
|
|
1237
1418
|
const personName = flag("person-name");
|
|
1238
1419
|
const title = flag("title");
|
|
@@ -1251,6 +1432,9 @@ async function signalToCopy() {
|
|
|
1251
1432
|
maxWaitMs: numberFlag("max-wait-ms"),
|
|
1252
1433
|
idempotencyKey: flag("idempotency-key")
|
|
1253
1434
|
};
|
|
1435
|
+
requireIntegerInRange(request.lookbackDays, "lookbackDays", 1, 365);
|
|
1436
|
+
requireIntegerInRange(request.maxWaitMs, "maxWaitMs", 1e4, 12e4);
|
|
1437
|
+
requireSignalToCopyIdentity(request);
|
|
1254
1438
|
if (hasFlag("dry-run")) {
|
|
1255
1439
|
const result2 = await createClient().signalToCopy({ ...request, dryRun: true });
|
|
1256
1440
|
dryRunOutput(result2);
|
|
@@ -1354,19 +1538,30 @@ main().catch((error) => {
|
|
|
1354
1538
|
const message = error instanceof Error ? error.message : String(error);
|
|
1355
1539
|
if (hasFlag("json") || hasFlag("jsonl")) {
|
|
1356
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"));
|
|
1357
1543
|
process.stdout.write(`${stringifyMachineJson({
|
|
1358
1544
|
success: false,
|
|
1359
1545
|
error: "product_request_failed",
|
|
1360
|
-
error_code: signalizError?.code || (
|
|
1546
|
+
error_code: signalizError?.code || (cliInputError ? "INPUT_001" : "CLI_ERROR"),
|
|
1361
1547
|
message,
|
|
1362
1548
|
retry_eligible: signalizError?.isRetryable ?? false,
|
|
1363
1549
|
...signalizError?.retryAfter !== void 0 ? {
|
|
1364
1550
|
retry_after: signalizError.retryAfter,
|
|
1365
1551
|
retry_after_seconds: signalizError.retryAfter
|
|
1366
1552
|
} : {},
|
|
1367
|
-
...signalizError?.details?.retry_strategy
|
|
1553
|
+
...["new_idempotency_key", "same_run_recovery"].includes(String(signalizError?.details?.retry_strategy || "")) ? { retry_strategy: signalizError?.details?.retry_strategy } : {},
|
|
1368
1554
|
...signalizError?.details?.credits_used === 0 ? { credits_used: 0 } : {},
|
|
1369
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
|
+
} : {},
|
|
1370
1565
|
...typeof signalizError?.details?.run_id === "string" ? { run_id: signalizError.details.run_id } : {},
|
|
1371
1566
|
...typeof signalizError?.details?.verification_run_id === "string" ? { verification_run_id: signalizError.details.verification_run_id } : {},
|
|
1372
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",
|