@signaliz/cli 1.0.57 → 1.0.60

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.
Files changed (3) hide show
  1. package/README.md +34 -16
  2. package/dist/bin.js +406 -30
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -55,8 +55,20 @@ signaliz find-email --input contacts.jsonl --concurrency 20 --json
55
55
  signaliz verify-email --input emails.json --concurrency 20 --json
56
56
  signaliz company-signals --input companies.jsonl --concurrency 5 --json
57
57
  signaliz signal-to-copy --input copy-requests.jsonl --concurrency 5 --json
58
+
59
+ # Yield an agent turn immediately for a durable >25-row batch.
60
+ # The response includes both job_id and the actual idempotency_key.
61
+ signaliz company-signals --input companies.jsonl --no-wait --json
62
+ signaliz company-signals --job-id job_... --idempotency-key key_... --json
63
+ signaliz signal-to-copy --input copy-requests.jsonl --no-wait --json
64
+ signaliz signal-to-copy --job-id job_... --idempotency-key key_... --json
58
65
  ```
59
66
 
67
+ Retrieve completed Company/Copy result pages within seven days. If retention
68
+ expires, the CLI returns non-retryable `BATCH_RESULTS_EXPIRED`; do not
69
+ automatically rerun the submission because provider work may already have been
70
+ performed.
71
+
60
72
  The CLI compacts exact duplicate successes by default: the first row contains
61
73
  the full result and repeats contain `duplicateOf` pointing to its zero-based
62
74
  input index. Use `--expand-duplicates` only when every repeated row must contain
@@ -97,30 +109,36 @@ they reduce the ceiling.
97
109
 
98
110
  Batch input is capped at 5,000 records, concurrency is bounded to `1-50`, input
99
111
  order is preserved, and a failed item does not abort successful items.
100
- Exact duplicate tasks are single-flighted across the full input before
101
- transport. The four row-oriented core products use one recoverable REST job for inputs larger
102
- than 25; the CLI waits for completion and retrieves every result page before it
103
- emits complete results or terminal per-row errors. Find Email, Verify Email, and
104
- Signal to Copy use 500-row pages, while Company Signal Enrichment uses 25-row
105
- pages. Durable Signal to Copy jobs use available company-signal data only. If a
106
- Company Signal row contains `signal_run_id`, the CLI preserves the recovery-read
107
- contract with direct batches of at most 25. If a
108
- copy row explicitly requests fresh or deep research, the CLI preserves that
109
- behavior with synchronous chunks of at most 25. Signal to Copy also shares
110
- company research across recipients.
112
+ Synchronous exact duplicates are single-flighted. Durable Company/Copy
113
+ submissions preserve every row and index so idempotency stays stable across
114
+ REST, MCP, SDK, and CLI recovery. The four row-oriented core products use one
115
+ recoverable REST job for inputs larger than 25; the CLI waits by default or
116
+ returns `job_id`, `idempotency_key`, and an exact resume command with
117
+ `--no-wait`. Find Email, Verify Email, and Company Signal Enrichment use 25-row
118
+ pages; Signal to Copy uses 100-row pages. Large Find Email and Verify Email
119
+ batches honor `--max-wait-ms` and `--poll-interval-ms`. Durable Signal to Copy jobs use
120
+ persisted, eligible company-signal data only and never start live providers or
121
+ deep research. Large Copy inputs with fresh, deep, skip-cache, per-row
122
+ idempotency, or synchronous-only controls fail locally before HTTP; split those
123
+ requests into batches of at most 25. Company Signal `signal_run_id` recovery
124
+ rows likewise stay in direct batches of at most 25. Large Company Signal jobs
125
+ persist bounded result pages and exclude rejected candidate-evidence
126
+ diagnostics; use `--include-candidate-evidence` only with batches of at most 25.
111
127
 
112
128
  Every command uses the authenticated workspace. Find Email and Verify Email
113
129
  honor that workspace's configured cache window; eligible hits cost 0 credits,
114
- while fresh discovery or verification is charged on every plan. Team, Agency,
115
- and Pay-As-You-Go include fresh Company Signals and Signal to Copy. Those plans
116
- still check eligible workspace signal data first even when Company Signals is
117
- set to No cache. `--lookback-days` remains a hard evidence-age bound for both
130
+ while fresh discovery or verification is charged on every plan. Builder, Team,
131
+ Agency, and Pay-As-You-Go include fresh Company Signals. Team and Agency also
132
+ include Unlimited Signal to Copy AI; Signal to Copy remains callable on other
133
+ plans under the standard product contract. These plans still check eligible
134
+ workspace signal data first even when Company Signals is set to No cache.
135
+ `--lookback-days` remains a hard evidence-age bound for both
118
136
  cached and fresh Company Signals and Signal to Copy results.
119
137
 
120
138
  Signals Everything is a query-first company discovery command rather than a
121
139
  row batch. It returns only dated, source-linked signals that contain both a
122
140
  company name and company domain. The default request is 100 companies and the
123
- maximum is 1,000. It is included on Team and Agency plans, is resumable with
141
+ maximum is 1,000. It is included on Builder, Team, and Agency plans, is resumable with
124
142
  `signal_search_run_id`, and reports a strict source-cost guardrail: results are
125
143
  only returned when source cost is below $0.01 per qualified company. A request
126
144
  is a ceiling, not a promise: it can return fewer results when public evidence
package/dist/bin.js CHANGED
@@ -14,13 +14,19 @@ var HELP = `Signaliz CLI
14
14
  Five product commands:
15
15
  signaliz find-email --company-domain example.com --full-name "Jane Doe" [--skip-cache]
16
16
  signaliz find-email --run-id run_... Resume an existing Find Email run without duplicate spend
17
+ signaliz find-email --job-id job_... Resume an existing Find Email batch without redispatch
17
18
  signaliz verify-email [jane@example.com] [--verification-run-id run_...] [--no-wait] [--max-wait-ms 600000]
19
+ signaliz verify-email --job-id job_... Resume an existing Verify Email batch without redispatch
18
20
  signaliz company-signals --domain example.com [--research-prompt "..."] [--search-mode fast|balanced|coverage] [--include-candidate-evidence] [--no-summary] [--max-wait-ms 120000]
21
+ signaliz company-signals --input companies.json --no-wait Submit a durable >25-row batch and return its job
22
+ signaliz company-signals --job-id job_... Resume an existing Company Signals batch without redispatch
19
23
  signaliz company-signals --signal-run-id run_... Read a completed run without duplicate spend
20
24
  signaliz signal-to-copy --company-domain example.com --person-name "Jane Doe" \\
21
25
  --title "VP Sales" --campaign-offer "pipeline intelligence" [--signal-run-id run_...] \\
22
26
  [--lookback-days 90] [--skip-cache] [--deep-search] [--max-wait-ms 120000]
23
27
  signaliz signal-to-copy --signal-run-id copy_... Reuse a completed signal run
28
+ signaliz signal-to-copy --input recipients.json --no-wait Submit an available-data-only >25-row batch
29
+ signaliz signal-to-copy --job-id job_... Resume an existing Signal to Copy batch without redispatch
24
30
  signaliz signals "companies that just got funded" [--icp-context "..."] [--limit 100] [--sources web,news] [--wait]
25
31
  signaliz signals --signal-search-run-id run_... Resume an existing company-attributed signal search
26
32
 
@@ -44,10 +50,11 @@ Common flags:
44
50
  --concurrency <1-50> Simultaneous API requests for batch input (default: 10)
45
51
  --idempotency-key <key> Reuse after an ambiguous response to recover the same request or job
46
52
  --run-id <run_...> Resume one existing Find Email run without dispatching new work
53
+ --job-id <job_...> Resume an existing core-product batch without dispatching new work
47
54
  --icp-context <text> Optional ICP, product, or pain context that returned signals must support
48
55
  --dry-run Return a no-spend plan without provider calls, jobs, or writes
49
56
 
50
- Company Signals and Signal to Copy always wait for complete results or terminal errors. Signals Everything is asynchronous by default; use --wait to poll until it completes.
57
+ Company Signals and Signal to Copy wait by default. For a durable batch larger than 25 rows, use --no-wait to return job_id + idempotency_key immediately, then resume with --job-id. Large Company Signal batches exclude candidate-evidence diagnostics; split --include-candidate-evidence requests into batches of at most 25. Large Signal to Copy batches are available-data-only. Signals Everything is asynchronous by default; use --wait to poll until it completes.
51
58
  `;
52
59
  function loadConfig() {
53
60
  if (!(0, import_node_fs.existsSync)(CONFIG_FILE)) return {};
@@ -88,6 +95,12 @@ function numberFlag(name) {
88
95
  if (!Number.isFinite(parsed)) fail(`--${name} must be a number`);
89
96
  return parsed;
90
97
  }
98
+ function requireIntegerInRange(value, name, minimum, maximum) {
99
+ if (value === void 0) return;
100
+ if (!Number.isInteger(value) || value < minimum || value > maximum) {
101
+ throw new RangeError(`${name} must be an integer between ${minimum} and ${maximum}`);
102
+ }
103
+ }
91
104
  function csvFlag(name) {
92
105
  const value = flag(name);
93
106
  return value?.split(",").map((item) => item.trim()).filter(Boolean);
@@ -127,37 +140,191 @@ function readBatchInput() {
127
140
  function record(value) {
128
141
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
129
142
  }
130
- function customerEmailResult(value) {
143
+ function safeNonNegativeNumber(value) {
144
+ if (value === null || value === void 0 || value === "") return null;
145
+ const numeric = Number(value);
146
+ return Number.isFinite(numeric) && numeric >= 0 ? numeric : null;
147
+ }
148
+ function customerEmailBillingMetadata(value) {
149
+ const source = record(value);
150
+ const safe = {};
151
+ if (typeof source.result_returned_from === "string") safe.result_returned_from = source.result_returned_from;
152
+ for (const field of ["cache_hit", "fresh_enrichment_used", "live_provider_called", "openrouter_called", "byo_openrouter_used"]) {
153
+ if (typeof source[field] === "boolean") safe[field] = source[field];
154
+ }
155
+ for (const field of ["credits_used", "credits_charged", "estimated_external_cost_usd"]) {
156
+ const numeric = safeNonNegativeNumber(source[field]);
157
+ if (numeric !== null) safe[field] = numeric;
158
+ }
159
+ return safe;
160
+ }
161
+ function customerEmailResult(value, kind) {
131
162
  const result = record(value);
132
163
  const raw = record(result.raw);
133
164
  if (String(result.status || "").toLowerCase() === "processing") {
134
- const verificationRunId = result.verificationRunId ?? raw.verification_run_id;
135
- const runId = result.runId ?? raw.run_id ?? verificationRunId;
165
+ const verificationRunId2 = result.verificationRunId ?? raw.verification_run_id;
166
+ const runId2 = result.runId ?? raw.run_id ?? verificationRunId2;
136
167
  return {
137
168
  success: true,
138
169
  status: "processing",
139
170
  email: null,
140
- run_id: runId,
141
- ...verificationRunId ? { verification_run_id: verificationRunId } : {},
171
+ run_id: runId2,
172
+ ...verificationRunId2 ? { verification_run_id: verificationRunId2 } : {},
142
173
  next_poll_after_seconds: result.nextPollAfterSeconds ?? raw.next_poll_after_seconds,
143
- suggested_action: result.suggestedAction ?? raw.suggested_action ?? (verificationRunId ? "retry_with_verification_run_id" : "retry_with_run_id")
174
+ suggested_action: result.suggestedAction ?? raw.suggested_action ?? (verificationRunId2 ? "retry_with_verification_run_id" : "retry_with_run_id")
144
175
  };
145
176
  }
146
177
  const creditsUsed = Number(result.creditsUsed ?? raw.credits_used ?? 0);
147
- return {
178
+ const base = {
148
179
  email: typeof result.email === "string" && result.email.trim() ? result.email.trim() : null,
149
180
  status: "completed",
150
181
  success: result.success === true,
151
- is_valid: typeof raw.is_valid === "boolean" ? raw.is_valid : result.isValid ?? result.isVerified ?? false,
182
+ is_valid: typeof result.isValid === "boolean" ? result.isValid : typeof raw.is_valid === "boolean" ? raw.is_valid : result.isVerified ?? false,
152
183
  credits_used: Number.isFinite(creditsUsed) && creditsUsed >= 0 ? creditsUsed : 0
153
184
  };
185
+ if (kind === "find") {
186
+ const verifiedForSending2 = typeof result.isVerifiedForSending === "boolean" ? result.isVerifiedForSending : raw.verified_for_sending === true;
187
+ const isCatchAll2 = typeof result.isCatchAll === "boolean" ? result.isCatchAll : raw.email_is_catch_all === true || raw.is_catch_all === true;
188
+ const verificationStatus2 = String(
189
+ result.verificationStatus ?? raw.verification_status ?? raw.deliverability_status ?? (verifiedForSending2 ? "valid" : "unknown")
190
+ ).toLowerCase();
191
+ const deliverabilityStatus2 = String(
192
+ result.deliverabilityStatus ?? raw.deliverability_status ?? verificationStatus2
193
+ ).toLowerCase();
194
+ const verificationVerdict2 = String(
195
+ result.verificationVerdict ?? raw.verification_verdict ?? verificationStatus2
196
+ ).toLowerCase();
197
+ const billingMetadata2 = customerEmailBillingMetadata(
198
+ Object.keys(record(result.billingMetadata)).length > 0 ? result.billingMetadata : raw.billing_metadata
199
+ );
200
+ const historicalBillingMetadata2 = customerEmailBillingMetadata(
201
+ Object.keys(record(result.historicalBillingMetadata)).length > 0 ? result.historicalBillingMetadata : raw.historical_billing_metadata
202
+ );
203
+ const cacheHit2 = typeof raw.cache_hit === "boolean" ? raw.cache_hit : result.cacheHit === true;
204
+ const negativeCacheHit2 = typeof raw.negative_cache_hit === "boolean" ? raw.negative_cache_hit : result.negativeCacheHit === true;
205
+ const resultReturnedFrom = result.resultReturnedFrom ?? raw.result_returned_from ?? null;
206
+ const freshEnrichmentUsed = typeof raw.fresh_enrichment_used === "boolean" ? raw.fresh_enrichment_used : result.freshEnrichmentUsed === true;
207
+ const liveProviderCalled = typeof raw.live_provider_called === "boolean" ? raw.live_provider_called : result.liveProviderCalled === true;
208
+ const normalizedBillingMetadata = {
209
+ ...billingMetadata2,
210
+ ...resultReturnedFrom ? { result_returned_from: resultReturnedFrom } : {},
211
+ cache_hit: cacheHit2,
212
+ fresh_enrichment_used: freshEnrichmentUsed,
213
+ credits_used: base.credits_used,
214
+ live_provider_called: liveProviderCalled
215
+ };
216
+ const estimatedExternalCostUsd2 = safeNonNegativeNumber(
217
+ result.estimatedExternalCostUsd ?? raw.estimated_external_cost_usd
218
+ );
219
+ return {
220
+ ...base,
221
+ is_valid: verifiedForSending2,
222
+ verification_status: verificationStatus2,
223
+ deliverability_status: deliverabilityStatus2,
224
+ verification_verdict: verificationVerdict2,
225
+ verified_for_sending: verifiedForSending2,
226
+ is_deliverable: typeof result.isDeliverable === "boolean" ? result.isDeliverable && verifiedForSending2 : verifiedForSending2,
227
+ email_is_catch_all: isCatchAll2,
228
+ is_catch_all: isCatchAll2,
229
+ verification_source: result.verificationSource ?? raw.verification_source ?? result.providerUsed ?? "email_finder",
230
+ ...result.verificationObservedAt ?? raw.verification_observed_at ? { verification_observed_at: result.verificationObservedAt ?? raw.verification_observed_at } : {},
231
+ failure_reason: result.failureReason ?? raw.failure_reason ?? (verifiedForSending2 ? "none" : "no_verified_email_found"),
232
+ cache_hit: cacheHit2,
233
+ negative_cache_hit: negativeCacheHit2,
234
+ negative_cache_ttl_seconds: safeNonNegativeNumber(
235
+ result.negativeCacheTtlSeconds ?? raw.negative_cache_ttl_seconds
236
+ ),
237
+ cache_tier: result.cacheTier ?? raw.cache_tier ?? null,
238
+ result_returned_from: resultReturnedFrom,
239
+ fresh_enrichment_used: freshEnrichmentUsed,
240
+ live_provider_called: liveProviderCalled,
241
+ billing_metadata: normalizedBillingMetadata,
242
+ ...Object.keys(historicalBillingMetadata2).length > 0 ? { historical_billing_metadata: historicalBillingMetadata2 } : {},
243
+ ...estimatedExternalCostUsd2 !== null ? { estimated_external_cost_usd: estimatedExternalCostUsd2 } : {},
244
+ ...result.runId ?? raw.run_id ? { run_id: result.runId ?? raw.run_id } : {},
245
+ ...raw.cache_publication_status ? {
246
+ cache_publication_status: raw.cache_publication_status,
247
+ cache_publication_retry_eligible: raw.cache_publication_retry_eligible === true,
248
+ ...raw.suggested_action ? { suggested_action: raw.suggested_action } : {}
249
+ } : {}
250
+ };
251
+ }
252
+ const verificationStatus = String(
253
+ result.verificationStatus ?? raw.verification_status ?? result.verificationVerdict ?? raw.verification_verdict ?? raw.deliverability_status ?? "unknown"
254
+ ).toLowerCase();
255
+ const deliverabilityStatus = String(
256
+ result.deliverabilityStatus ?? raw.deliverability_status ?? verificationStatus
257
+ ).toLowerCase();
258
+ const verificationVerdict = String(
259
+ result.verificationVerdict ?? raw.verification_verdict ?? verificationStatus
260
+ ).toLowerCase();
261
+ const verifiedForSending = typeof result.verifiedForSending === "boolean" ? result.verifiedForSending : raw.verified_for_sending === true;
262
+ const isDeliverable = typeof result.isDeliverable === "boolean" ? result.isDeliverable : raw.is_deliverable === true;
263
+ const isCatchAll = typeof result.isCatchAll === "boolean" ? result.isCatchAll : raw.email_is_catch_all === true || raw.is_catch_all === true;
264
+ const isRoleAccount = typeof result.isRoleAccount === "boolean" ? result.isRoleAccount : raw.is_role_account === true;
265
+ const isMalformed = typeof result.isMalformed === "boolean" ? result.isMalformed : raw.is_malformed === true;
266
+ const quality = typeof result.quality === "string" ? result.quality : typeof raw.quality === "string" ? raw.quality : null;
267
+ const recommendation = typeof result.recommendation === "string" ? result.recommendation : typeof raw.recommendation === "string" ? raw.recommendation : verifiedForSending ? "send" : ["unknown", "verifier_error"].includes(verificationStatus) ? "manual_review" : "do_not_send";
268
+ const cacheHit = typeof raw.cache_hit === "boolean" ? raw.cache_hit : result.cacheHit === true;
269
+ const negativeCacheHit = typeof raw.negative_cache_hit === "boolean" ? raw.negative_cache_hit : result.negativeCacheHit === true;
270
+ const negativeCacheTtlSeconds = safeNonNegativeNumber(
271
+ result.negativeCacheTtlSeconds ?? raw.negative_cache_ttl_seconds
272
+ );
273
+ const billingMetadata = customerEmailBillingMetadata(
274
+ Object.keys(record(result.billingMetadata)).length > 0 ? result.billingMetadata : raw.billing_metadata
275
+ );
276
+ const historicalBillingMetadata = customerEmailBillingMetadata(
277
+ Object.keys(record(result.historicalBillingMetadata)).length > 0 ? result.historicalBillingMetadata : raw.historical_billing_metadata
278
+ );
279
+ const verificationRunId = result.verificationRunId ?? raw.verification_run_id ?? raw.run_id;
280
+ const runId = result.runId ?? raw.run_id ?? verificationRunId;
281
+ const estimatedExternalCostUsd = safeNonNegativeNumber(
282
+ result.estimatedExternalCostUsd ?? raw.estimated_external_cost_usd
283
+ );
284
+ return {
285
+ ...base,
286
+ verification_status: verificationStatus,
287
+ deliverability_status: deliverabilityStatus,
288
+ verification_verdict: verificationVerdict,
289
+ verified_for_sending: verifiedForSending,
290
+ is_deliverable: isDeliverable,
291
+ email_is_catch_all: isCatchAll,
292
+ is_catch_all: isCatchAll,
293
+ is_role_account: isRoleAccount,
294
+ is_malformed: isMalformed,
295
+ quality,
296
+ recommendation,
297
+ smtp_status: result.smtpStatus ?? raw.smtp_status ?? (isCatchAll ? "accept_all" : verifiedForSending ? "accepted" : "not_checked"),
298
+ provider_status: result.providerStatus ?? raw.provider_status ?? verificationStatus,
299
+ verification_source: result.verificationSource ?? raw.verification_source ?? "email_verification",
300
+ ...result.verificationObservedAt ?? raw.verification_observed_at ? { verification_observed_at: result.verificationObservedAt ?? raw.verification_observed_at } : {},
301
+ failure_reason: result.failureReason ?? raw.failure_reason ?? (verifiedForSending ? "none" : isCatchAll ? "catch_all_domain_not_send_safe" : "verification_not_send_safe"),
302
+ cache_hit: cacheHit,
303
+ negative_cache_hit: negativeCacheHit,
304
+ negative_cache_ttl_seconds: negativeCacheTtlSeconds,
305
+ cache_tier: result.cacheTier ?? raw.cache_tier ?? null,
306
+ result_returned_from: result.resultReturnedFrom ?? raw.result_returned_from ?? null,
307
+ fresh_enrichment_used: typeof raw.fresh_enrichment_used === "boolean" ? raw.fresh_enrichment_used : result.freshEnrichmentUsed === true,
308
+ live_provider_called: typeof raw.live_provider_called === "boolean" ? raw.live_provider_called : result.liveProviderCalled === true,
309
+ billing_replayed: typeof raw.billing_replayed === "boolean" ? raw.billing_replayed : result.billingReplayed === true,
310
+ billing_metadata: billingMetadata,
311
+ ...Object.keys(historicalBillingMetadata).length > 0 ? { historical_billing_metadata: historicalBillingMetadata } : {},
312
+ ...estimatedExternalCostUsd !== null ? { estimated_external_cost_usd: estimatedExternalCostUsd } : {},
313
+ ...verificationRunId ? { verification_run_id: verificationRunId } : {},
314
+ ...runId ? { run_id: runId } : {},
315
+ ...raw.cache_publication_status ? {
316
+ cache_publication_status: raw.cache_publication_status,
317
+ cache_publication_retry_eligible: raw.cache_publication_retry_eligible === true,
318
+ ...raw.suggested_action ? { suggested_action: raw.suggested_action } : {}
319
+ } : {}
320
+ };
154
321
  }
155
- function customerEmailBatch(value) {
322
+ function customerEmailBatch(value, kind) {
156
323
  return {
157
324
  ...value,
158
325
  results: value.results.map((item) => {
159
326
  const row = record(item);
160
- return promoteBatchRecoveryMetadata(row.data ? { ...row, data: customerEmailResult(row.data) } : row);
327
+ return promoteBatchRecoveryMetadata(row.data ? { ...row, data: customerEmailResult(row.data, kind) } : row);
161
328
  })
162
329
  };
163
330
  }
@@ -199,14 +366,40 @@ function batchOptions() {
199
366
  return {
200
367
  concurrency: numberFlag("concurrency"),
201
368
  idempotencyKey: flag("idempotency-key"),
202
- compactDuplicates: !hasFlag("expand-duplicates")
369
+ compactDuplicates: !hasFlag("expand-duplicates"),
370
+ maxWaitMs: numberFlag("max-wait-ms"),
371
+ pollIntervalMs: numberFlag("poll-interval-ms")
203
372
  };
204
373
  }
205
- function rejectAsyncSignalFlags() {
206
- if (hasFlag("no-wait") || hasFlag("poll-interval-ms")) {
207
- fail("Company Signals and Signal to Copy are always synchronous; --no-wait and --poll-interval-ms are not supported.");
374
+ function rejectDurableNoWaitPollingFlags(label) {
375
+ if (!hasFlag("no-wait")) return;
376
+ const conflicts = ["max-wait-ms", "poll-interval-ms", "concurrency"].filter(hasFlag);
377
+ if (conflicts.length > 0) {
378
+ fail(`${label} --no-wait cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`);
208
379
  }
209
380
  }
381
+ function recoverableJobOutput(value, command) {
382
+ const resumeCommand = `signaliz ${command} --job-id ${value.jobId} --idempotency-key ${value.idempotencyKey}`;
383
+ const payload = {
384
+ success: true,
385
+ status: value.status,
386
+ capability: value.capability,
387
+ job_id: value.jobId,
388
+ idempotency_key: value.idempotencyKey,
389
+ total: value.total,
390
+ next_poll_after_seconds: value.nextPollAfterSeconds,
391
+ suggested_action: "check_job_status",
392
+ resume_command: resumeCommand
393
+ };
394
+ output(payload, () => {
395
+ process.stdout.write(`Durable ${command} batch submitted: ${value.jobId}
396
+ `);
397
+ process.stdout.write(`Idempotency key: ${value.idempotencyKey}
398
+ `);
399
+ process.stdout.write(`Resume with: ${resumeCommand}
400
+ `);
401
+ });
402
+ }
210
403
  function batchOutput(value) {
211
404
  if (hasFlag("jsonl")) {
212
405
  for (const result of value.results) {
@@ -493,12 +686,41 @@ async function auth(args) {
493
686
  fail("Usage: signaliz auth <login|set-key|workspace|status>");
494
687
  }
495
688
  async function findEmail() {
689
+ const jobId = flag("job-id");
690
+ if (jobId) {
691
+ const conflicts = [
692
+ "input",
693
+ "dry-run",
694
+ "company-domain",
695
+ "domain",
696
+ "full-name",
697
+ "first-name",
698
+ "last-name",
699
+ "linkedin-url",
700
+ "company-name",
701
+ "run-id",
702
+ "skip-cache"
703
+ ].filter(hasFlag);
704
+ if (conflicts.length > 0) {
705
+ fail(`Find Email --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`);
706
+ }
707
+ const result2 = await createClient().resumeFindEmailBatch(jobId, {
708
+ pageSize: numberFlag("page-size"),
709
+ maxWaitMs: numberFlag("max-wait-ms"),
710
+ pollIntervalMs: numberFlag("poll-interval-ms"),
711
+ compactDuplicates: !hasFlag("expand-duplicates"),
712
+ idempotencyKey: flag("idempotency-key")
713
+ });
714
+ batchOutput(customerEmailBatch(result2, "find"));
715
+ return;
716
+ }
496
717
  const rows = readBatchInput();
497
718
  if (rows) {
498
719
  const requests = rows.map((value) => {
499
720
  const row = record(value);
500
721
  const runId2 = row.runId || row.run_id;
501
- if (runId2) return { runId: runId2 };
722
+ const idempotencyKey = row.idempotencyKey || row.idempotency_key;
723
+ if (runId2) return { runId: runId2, idempotencyKey };
502
724
  return {
503
725
  companyDomain: row.companyDomain || row.company_domain || row.domain,
504
726
  fullName: row.fullName || row.full_name || row.name,
@@ -506,7 +728,8 @@ async function findEmail() {
506
728
  lastName: row.lastName || row.last_name,
507
729
  linkedinUrl: row.linkedinUrl || row.linkedin_url || row.linkedin,
508
730
  companyName: row.companyName || row.company_name,
509
- skipCache: row.skipCache ?? row.skip_cache ?? hasFlag("skip-cache")
731
+ skipCache: row.skipCache ?? row.skip_cache ?? hasFlag("skip-cache"),
732
+ idempotencyKey
510
733
  };
511
734
  });
512
735
  if (hasFlag("dry-run")) {
@@ -515,7 +738,7 @@ async function findEmail() {
515
738
  return;
516
739
  }
517
740
  const result2 = await createClient().findEmails(requests, batchOptions());
518
- batchOutput(customerEmailBatch(result2));
741
+ batchOutput(customerEmailBatch(result2, "find"));
519
742
  return;
520
743
  }
521
744
  const companyDomain = flag("company-domain") || flag("domain");
@@ -543,7 +766,7 @@ async function findEmail() {
543
766
  return;
544
767
  }
545
768
  const result = await createClient().findEmail(request);
546
- const publicResult = customerEmailResult(result);
769
+ const publicResult = customerEmailResult(result, "find");
547
770
  if (outputFailedCoreProduct(publicResult, "No verified email found")) return;
548
771
  output(publicResult, () => {
549
772
  if (result.status === "processing" && result.runId) {
@@ -566,9 +789,45 @@ async function findEmail() {
566
789
  });
567
790
  }
568
791
  async function verifyEmail(args) {
792
+ const jobId = flag("job-id");
793
+ if (jobId) {
794
+ const positionalEmail = args[0] && !args[0].startsWith("--");
795
+ const conflicts = [
796
+ "input",
797
+ "dry-run",
798
+ "email",
799
+ "verification-run-id",
800
+ "skip-cache",
801
+ "no-wait"
802
+ ].filter(hasFlag);
803
+ if (positionalEmail || conflicts.length > 0) {
804
+ const conflictList = [
805
+ ...positionalEmail ? ["email argument"] : [],
806
+ ...conflicts.map((name) => `--${name}`)
807
+ ];
808
+ fail(`Verify Email --job-id cannot be combined with ${conflictList.join(", ")}.`);
809
+ }
810
+ const result2 = await createClient().resumeVerifyEmailBatch(jobId, {
811
+ pageSize: numberFlag("page-size"),
812
+ maxWaitMs: numberFlag("max-wait-ms"),
813
+ pollIntervalMs: numberFlag("poll-interval-ms"),
814
+ compactDuplicates: !hasFlag("expand-duplicates"),
815
+ idempotencyKey: flag("idempotency-key")
816
+ });
817
+ batchOutput(customerEmailBatch(result2, "verify"));
818
+ return;
819
+ }
569
820
  const rows = readBatchInput();
570
821
  if (rows) {
571
- const emails = rows.map((value) => typeof value === "string" ? value : String(record(value).email || ""));
822
+ const emails = rows.map((value) => {
823
+ if (typeof value === "string") return value;
824
+ const row = record(value);
825
+ return {
826
+ email: String(row.email || ""),
827
+ skipCache: row.skipCache ?? row.skip_cache ?? hasFlag("skip-cache"),
828
+ idempotencyKey: row.idempotencyKey || row.idempotency_key
829
+ };
830
+ });
572
831
  if (hasFlag("dry-run")) {
573
832
  const result3 = await createClient().verifyEmails(emails, {
574
833
  ...batchOptions(),
@@ -582,7 +841,7 @@ async function verifyEmail(args) {
582
841
  ...batchOptions(),
583
842
  skipCache: hasFlag("skip-cache")
584
843
  });
585
- batchOutput(customerEmailBatch(result2));
844
+ batchOutput(customerEmailBatch(result2, "verify"));
586
845
  return;
587
846
  }
588
847
  const email = args[0] && !args[0].startsWith("--") ? args[0] : flag("email");
@@ -602,7 +861,7 @@ async function verifyEmail(args) {
602
861
  return;
603
862
  }
604
863
  const result = await createClient().verifyEmail(email, options);
605
- const publicResult = customerEmailResult(result);
864
+ const publicResult = customerEmailResult(result, "verify");
606
865
  if (outputFailedCoreProduct(publicResult, "Email verification failed")) return;
607
866
  output(publicResult, () => {
608
867
  if (result.status === "processing") {
@@ -619,15 +878,62 @@ async function verifyEmail(args) {
619
878
  process.stdout.write(`Success: ${publicResult.success}
620
879
  `);
621
880
  process.stdout.write(`Valid: ${publicResult.is_valid}
881
+ `);
882
+ process.stdout.write(`Verdict: ${publicResult.verification_status}
883
+ `);
884
+ process.stdout.write(`Recommendation: ${publicResult.recommendation}
885
+ `);
886
+ process.stdout.write(`Source: ${publicResult.verification_source}${publicResult.cache_hit === true ? " (cache)" : ""}
887
+ `);
888
+ if (publicResult.run_id) process.stdout.write(`Run ID: ${publicResult.run_id}
622
889
  `);
623
890
  process.stdout.write(`Credits used: ${publicResult.credits_used}
624
891
  `);
625
892
  });
626
893
  }
627
894
  async function companySignals() {
628
- rejectAsyncSignalFlags();
895
+ const jobId = flag("job-id");
896
+ if (jobId) {
897
+ const conflicts = [
898
+ "input",
899
+ "dry-run",
900
+ "domain",
901
+ "company-domain",
902
+ "company-name",
903
+ "signal-run-id",
904
+ "research-prompt",
905
+ "prompt",
906
+ "signal-types",
907
+ "target-signal-count",
908
+ "lookback-days",
909
+ "offline",
910
+ "shallow",
911
+ "search-mode",
912
+ "no-summary",
913
+ "outreach-intelligence",
914
+ "predictive-intelligence",
915
+ "skip-cache",
916
+ "include-candidate-evidence",
917
+ "no-wait",
918
+ "concurrency"
919
+ ].filter(hasFlag);
920
+ if (conflicts.length > 0) {
921
+ fail(`Company Signals --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`);
922
+ }
923
+ const result2 = await createClient().resumeCompanySignalBatch(jobId, {
924
+ pageSize: numberFlag("page-size"),
925
+ maxWaitMs: numberFlag("max-wait-ms"),
926
+ pollIntervalMs: numberFlag("poll-interval-ms"),
927
+ idempotencyKey: flag("idempotency-key"),
928
+ compactDuplicates: !hasFlag("expand-duplicates")
929
+ });
930
+ batchOutput(customerSignalBatch(result2));
931
+ return;
932
+ }
933
+ rejectDurableNoWaitPollingFlags("Company Signals");
629
934
  const rows = readBatchInput();
630
935
  if (rows) {
936
+ const durableBatch = rows.length > 25;
631
937
  const requests = rows.map((value) => {
632
938
  const row = record(value);
633
939
  const online = row.online ?? !hasFlag("offline");
@@ -637,8 +943,8 @@ async function companySignals() {
637
943
  companyName: row.companyName || row.company_name || row.name,
638
944
  researchPrompt: row.researchPrompt || row.research_prompt || flag("research-prompt") || flag("prompt"),
639
945
  signalTypes: row.signalTypes || row.signal_types || csvFlag("signal-types"),
640
- targetSignalCount: row.targetSignalCount || row.target_signal_count || numberFlag("target-signal-count"),
641
- lookbackDays: row.lookbackDays || row.lookback_days || numberFlag("lookback-days"),
946
+ targetSignalCount: row.targetSignalCount ?? row.target_signal_count ?? numberFlag("target-signal-count"),
947
+ lookbackDays: row.lookbackDays ?? row.lookback_days ?? numberFlag("lookback-days"),
642
948
  online,
643
949
  enableDeepSearch: row.enableDeepSearch ?? row.enable_deep_search ?? (online && !hasFlag("shallow")),
644
950
  searchMode: companySignalSearchMode(row.searchMode ?? row.search_mode ?? flag("search-mode")),
@@ -647,18 +953,34 @@ async function companySignals() {
647
953
  enablePredictiveIntelligence: row.enablePredictiveIntelligence ?? row.enable_predictive_intelligence,
648
954
  skipCache: row.skipCache ?? row.skip_cache ?? hasFlag("skip-cache"),
649
955
  includeCandidateEvidence: row.includeCandidateEvidence ?? row.include_candidate_evidence ?? hasFlag("include-candidate-evidence"),
650
- maxWaitMs: row.maxWaitMs || row.max_wait_ms || numberFlag("max-wait-ms")
956
+ maxWaitMs: row.maxWaitMs ?? row.max_wait_ms ?? (durableBatch ? void 0 : numberFlag("max-wait-ms")),
957
+ idempotencyKey: row.idempotencyKey || row.idempotency_key
651
958
  };
652
959
  });
960
+ for (const request2 of requests) {
961
+ requireIntegerInRange(request2.targetSignalCount, "targetSignalCount", 1, 15);
962
+ requireIntegerInRange(request2.lookbackDays, "lookbackDays", 1, 365);
963
+ }
653
964
  if (hasFlag("dry-run")) {
965
+ if (hasFlag("no-wait")) fail("Company Signals --no-wait cannot be combined with --dry-run.");
654
966
  const result3 = await createClient().enrichCompanies(requests, { ...batchOptions(), dryRun: true });
655
967
  dryRunOutput(result3);
656
968
  return;
657
969
  }
970
+ if (hasFlag("no-wait")) {
971
+ const job = await createClient().enrichCompanies(requests, {
972
+ ...batchOptions(),
973
+ waitForResult: false
974
+ });
975
+ recoverableJobOutput(job, "company-signals");
976
+ return;
977
+ }
658
978
  const result2 = await createClient().enrichCompanies(requests, batchOptions());
659
979
  batchOutput(customerSignalBatch(result2));
660
980
  return;
661
981
  }
982
+ if (hasFlag("no-wait")) fail("Company Signals --no-wait requires --input with more than 25 homogeneous rows.");
983
+ if (hasFlag("poll-interval-ms")) fail("Company Signals --poll-interval-ms requires --input or --job-id.");
662
984
  const companyDomain = flag("domain") || flag("company-domain");
663
985
  const companyName = flag("company-name");
664
986
  const signalRunId = flag("signal-run-id");
@@ -807,7 +1129,7 @@ async function signals(args) {
807
1129
  const identityCost = guardrail.identityResolutionCostUsd === null ? "unavailable" : `$${guardrail.identityResolutionCostUsd}`;
808
1130
  const identityRequests = guardrail.identityProviderRequests;
809
1131
  process.stdout.write(
810
- `Identity resolution cost: ${identityCost}${identityRequests === void 0 ? "" : ` across ${identityRequests} fallback provider request${identityRequests === 1 ? "" : "s"}`}${guardrail.identityCostSource ? ` (${guardrail.identityCostSource})` : ""}.
1132
+ `Identity resolution cost: ${identityCost}${identityRequests === void 0 ? "" : ` across ${identityRequests} identity lookup request${identityRequests === 1 ? "" : "s"}`}${guardrail.identityCostSource ? ` (${guardrail.identityCostSource})` : ""}.
811
1133
  `
812
1134
  );
813
1135
  }
@@ -824,9 +1146,43 @@ async function signals(args) {
824
1146
  });
825
1147
  }
826
1148
  async function signalToCopy() {
827
- rejectAsyncSignalFlags();
1149
+ const jobId = flag("job-id");
1150
+ if (jobId) {
1151
+ const conflicts = [
1152
+ "input",
1153
+ "dry-run",
1154
+ "company-domain",
1155
+ "domain",
1156
+ "person-name",
1157
+ "title",
1158
+ "campaign-offer",
1159
+ "offer",
1160
+ "research-prompt",
1161
+ "prompt",
1162
+ "lookback-days",
1163
+ "signal-run-id",
1164
+ "skip-cache",
1165
+ "deep-search",
1166
+ "no-wait",
1167
+ "concurrency"
1168
+ ].filter(hasFlag);
1169
+ if (conflicts.length > 0) {
1170
+ fail(`Signal to Copy --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`);
1171
+ }
1172
+ const result2 = await createClient().resumeSignalCopyBatch(jobId, {
1173
+ pageSize: numberFlag("page-size"),
1174
+ maxWaitMs: numberFlag("max-wait-ms"),
1175
+ pollIntervalMs: numberFlag("poll-interval-ms"),
1176
+ idempotencyKey: flag("idempotency-key"),
1177
+ compactDuplicates: !hasFlag("expand-duplicates")
1178
+ });
1179
+ batchOutput(customerSignalBatch(result2));
1180
+ return;
1181
+ }
1182
+ rejectDurableNoWaitPollingFlags("Signal to Copy");
828
1183
  const rows = readBatchInput();
829
1184
  if (rows) {
1185
+ const durableBatch = rows.length > 25;
830
1186
  const requests = rows.map((value) => {
831
1187
  const row = record(value);
832
1188
  return {
@@ -835,22 +1191,34 @@ async function signalToCopy() {
835
1191
  title: row.title,
836
1192
  campaignOffer: row.campaignOffer || row.campaign_offer || row.offer,
837
1193
  researchPrompt: row.researchPrompt || row.research_prompt || flag("research-prompt") || flag("prompt"),
838
- lookbackDays: row.lookbackDays || row.lookback_days || numberFlag("lookback-days"),
1194
+ lookbackDays: row.lookbackDays ?? row.lookback_days ?? numberFlag("lookback-days"),
839
1195
  signalRunId: row.signalRunId || row.signal_run_id,
840
1196
  skipCache: row.skipCache ?? row.skip_cache ?? hasFlag("skip-cache"),
841
1197
  enableDeepSearch: row.enableDeepSearch ?? row.enable_deep_search ?? hasFlag("deep-search"),
842
- maxWaitMs: row.maxWaitMs || row.max_wait_ms || numberFlag("max-wait-ms")
1198
+ maxWaitMs: row.maxWaitMs ?? row.max_wait_ms ?? (durableBatch ? void 0 : numberFlag("max-wait-ms")),
1199
+ idempotencyKey: row.idempotencyKey || row.idempotency_key
843
1200
  };
844
1201
  });
845
1202
  if (hasFlag("dry-run")) {
1203
+ if (hasFlag("no-wait")) fail("Signal to Copy --no-wait cannot be combined with --dry-run.");
846
1204
  const result3 = await createClient().createSignalCopyBatch(requests, { ...batchOptions(), dryRun: true });
847
1205
  dryRunOutput(result3);
848
1206
  return;
849
1207
  }
1208
+ if (hasFlag("no-wait")) {
1209
+ const job = await createClient().createSignalCopyBatch(requests, {
1210
+ ...batchOptions(),
1211
+ waitForResult: false
1212
+ });
1213
+ recoverableJobOutput(job, "signal-to-copy");
1214
+ return;
1215
+ }
850
1216
  const result2 = await createClient().createSignalCopyBatch(requests, batchOptions());
851
1217
  batchOutput(customerSignalBatch(result2));
852
1218
  return;
853
1219
  }
1220
+ if (hasFlag("no-wait")) fail("Signal to Copy --no-wait requires --input with more than 25 available-data-only rows.");
1221
+ if (hasFlag("poll-interval-ms")) fail("Signal to Copy --poll-interval-ms requires --input or --job-id.");
854
1222
  const companyDomain = flag("company-domain") || flag("domain");
855
1223
  const personName = flag("person-name");
856
1224
  const title = flag("title");
@@ -988,6 +1356,7 @@ main().catch((error) => {
988
1356
  ...typeof signalizError?.details?.run_id === "string" ? { run_id: signalizError.details.run_id } : {},
989
1357
  ...typeof signalizError?.details?.verification_run_id === "string" ? { verification_run_id: signalizError.details.verification_run_id } : {},
990
1358
  ...typeof signalizError?.details?.job_id === "string" ? { job_id: signalizError.details.job_id } : {},
1359
+ ...typeof signalizError?.details?.idempotency_key === "string" ? { idempotency_key: signalizError.details.idempotency_key } : {},
991
1360
  ...typeof signalizError?.details?.suggested_action === "string" ? { suggested_action: signalizError.details.suggested_action } : {},
992
1361
  ...signalizError?.errorType ? { error_type: signalizError.errorType } : {},
993
1362
  ...signalizError?.details ? { details: signalizError.details } : {}
@@ -1028,6 +1397,13 @@ No automatic retry is recommended; review the provider outcome or contact suppor
1028
1397
  if (error instanceof import_sdk.SignalizError && typeof error.details?.job_id === "string") {
1029
1398
  const jobId = error.details.job_id;
1030
1399
  if (error.details.suggested_action === "check_job_status") {
1400
+ if (["find-email", "verify-email", "company-signals", "signal-to-copy"].includes(process.argv[2])) {
1401
+ const idempotencyKey = typeof error.details.idempotency_key === "string" ? error.details.idempotency_key : "";
1402
+ const keyFlag = idempotencyKey ? ` --idempotency-key ${idempotencyKey}` : "";
1403
+ fail(`${message}
1404
+ Recoverable job: ${jobId}
1405
+ Retry with: signaliz ${process.argv[2]} --job-id ${jobId}${keyFlag}`);
1406
+ }
1031
1407
  fail(`${message}
1032
1408
  Recoverable job: ${jobId}
1033
1409
  Resume this job through the REST API or SDK using job_id ${jobId}, or use --json for structured recovery metadata.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaliz/cli",
3
- "version": "1.0.57",
3
+ "version": "1.0.60",
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.61"
32
+ "@signaliz/sdk": "^1.0.63"
33
33
  },
34
34
  "devDependencies": {
35
35
  "tsup": "^8.0.0",