@signaliz/cli 1.0.56 → 1.0.58

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 +40 -13
  2. package/dist/bin.js +510 -55
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -40,6 +40,9 @@ signaliz signal-to-copy --company-domain example.com --person-name "Jane Doe" \
40
40
  # Signals Everything returns distinct companies with a source-backed signal.
41
41
  # It is asynchronous by default; add --wait or resume the returned run ID.
42
42
  signaliz signals "companies that just got funded" --limit 100 --sources web,news
43
+ signaliz signals "companies showing signals they need account-signal automation" \
44
+ --icp-context "B2B SaaS with outbound teams and manual account research" \
45
+ --limit 100 --sources web,news
43
46
  signaliz signals --signal-search-run-id run_... --wait --json
44
47
  ```
45
48
 
@@ -52,8 +55,20 @@ signaliz find-email --input contacts.jsonl --concurrency 20 --json
52
55
  signaliz verify-email --input emails.json --concurrency 20 --json
53
56
  signaliz company-signals --input companies.jsonl --concurrency 5 --json
54
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
55
65
  ```
56
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
+
57
72
  The CLI compacts exact duplicate successes by default: the first row contains
58
73
  the full result and repeats contain `duplicateOf` pointing to its zero-based
59
74
  input index. Use `--expand-duplicates` only when every repeated row must contain
@@ -94,26 +109,38 @@ they reduce the ceiling.
94
109
 
95
110
  Batch input is capped at 5,000 records, concurrency is bounded to `1-50`, input
96
111
  order is preserved, and a failed item does not abort successful items.
97
- Exact duplicate tasks are single-flighted across the full input before
98
- transport. The four row-oriented core products use one recoverable REST job for inputs larger
99
- than 25; the CLI waits for completion and retrieves every result page before it
100
- emits complete results or terminal per-row errors. Find Email, Verify Email, and
101
- Signal to Copy use 500-row pages, while Company Signal Enrichment uses 25-row
102
- pages. Durable Signal to Copy jobs use available company-signal data only. If a
103
- Company Signal row contains `signal_run_id`, the CLI preserves the recovery-read
104
- contract with direct batches of at most 25. If a
105
- copy row explicitly requests fresh or deep research, the CLI preserves that
106
- behavior with synchronous chunks of at most 25. Signal to Copy also shares
107
- 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. Durable Signal to Copy jobs use
119
+ persisted, eligible company-signal data only and never start live providers or
120
+ deep research. Large Copy inputs with fresh, deep, skip-cache, per-row
121
+ idempotency, or synchronous-only controls fail locally before HTTP; split those
122
+ requests into batches of at most 25. Company Signal `signal_run_id` recovery
123
+ rows likewise stay in direct batches of at most 25. Large Company Signal jobs
124
+ persist bounded result pages and exclude rejected candidate-evidence
125
+ diagnostics; use `--include-candidate-evidence` only with batches of at most 25.
126
+
127
+ Every command uses the authenticated workspace. Find Email and Verify Email
128
+ honor that workspace's configured cache window; eligible hits cost 0 credits,
129
+ while fresh discovery or verification is charged on every plan. Builder, Team,
130
+ Agency, and Pay-As-You-Go include fresh Company Signals and Signal to Copy. Those plans
131
+ still check eligible workspace signal data first even when Company Signals is
132
+ set to No cache. `--lookback-days` remains a hard evidence-age bound for both
133
+ cached and fresh Company Signals and Signal to Copy results.
108
134
 
109
135
  Signals Everything is a query-first company discovery command rather than a
110
136
  row batch. It returns only dated, source-linked signals that contain both a
111
137
  company name and company domain. The default request is 100 companies and the
112
- maximum is 1,000. It is included on Team and Agency plans, is resumable with
138
+ maximum is 1,000. It is included on Builder, Team, and Agency plans, is resumable with
113
139
  `signal_search_run_id`, and reports a strict source-cost guardrail: results are
114
140
  only returned when source cost is below $0.01 per qualified company. A request
115
141
  is a ceiling, not a promise: it can return fewer results when public evidence
116
- cannot verify both a date and company identity.
142
+ cannot verify both a date and company identity. Use `--icp-context` to qualify
143
+ an open-ended signal request against a target market.
117
144
 
118
145
  Connection commands are limited to access infrastructure:
119
146
 
package/dist/bin.js CHANGED
@@ -13,14 +13,21 @@ var HELP = `Signaliz CLI
13
13
 
14
14
  Five product commands:
15
15
  signaliz find-email --company-domain example.com --full-name "Jane Doe" [--skip-cache]
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
16
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
17
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
18
23
  signaliz company-signals --signal-run-id run_... Read a completed run without duplicate spend
19
24
  signaliz signal-to-copy --company-domain example.com --person-name "Jane Doe" \\
20
25
  --title "VP Sales" --campaign-offer "pipeline intelligence" [--signal-run-id run_...] \\
21
- [--skip-cache] [--deep-search] [--max-wait-ms 120000]
26
+ [--lookback-days 90] [--skip-cache] [--deep-search] [--max-wait-ms 120000]
22
27
  signaliz signal-to-copy --signal-run-id copy_... Reuse a completed signal run
23
- signaliz signals "companies that just got funded" [--limit 100] [--sources web,news] [--wait]
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
30
+ signaliz signals "companies that just got funded" [--icp-context "..."] [--limit 100] [--sources web,news] [--wait]
24
31
  signaliz signals --signal-search-run-id run_... Resume an existing company-attributed signal search
25
32
 
26
33
  Access and connection:
@@ -42,9 +49,12 @@ Common flags:
42
49
  --input - Read a JSON array or JSONL batch from stdin
43
50
  --concurrency <1-50> Simultaneous API requests for batch input (default: 10)
44
51
  --idempotency-key <key> Reuse after an ambiguous response to recover the same request or job
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
54
+ --icp-context <text> Optional ICP, product, or pain context that returned signals must support
45
55
  --dry-run Return a no-spend plan without provider calls, jobs, or writes
46
56
 
47
- 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.
48
58
  `;
49
59
  function loadConfig() {
50
60
  if (!(0, import_node_fs.existsSync)(CONFIG_FILE)) return {};
@@ -124,37 +134,203 @@ function readBatchInput() {
124
134
  function record(value) {
125
135
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
126
136
  }
127
- function customerEmailResult(value) {
137
+ function safeNonNegativeNumber(value) {
138
+ if (value === null || value === void 0 || value === "") return null;
139
+ const numeric = Number(value);
140
+ return Number.isFinite(numeric) && numeric >= 0 ? numeric : null;
141
+ }
142
+ function customerEmailBillingMetadata(value) {
143
+ const source = record(value);
144
+ const safe = {};
145
+ if (typeof source.result_returned_from === "string") safe.result_returned_from = source.result_returned_from;
146
+ for (const field of ["cache_hit", "fresh_enrichment_used", "live_provider_called", "openrouter_called", "byo_openrouter_used"]) {
147
+ if (typeof source[field] === "boolean") safe[field] = source[field];
148
+ }
149
+ for (const field of ["credits_used", "credits_charged", "estimated_external_cost_usd"]) {
150
+ const numeric = safeNonNegativeNumber(source[field]);
151
+ if (numeric !== null) safe[field] = numeric;
152
+ }
153
+ return safe;
154
+ }
155
+ function customerEmailResult(value, kind) {
128
156
  const result = record(value);
129
- if (String(result.status || "").toLowerCase() === "processing") return result;
130
157
  const raw = record(result.raw);
131
- const verifiedForSending = typeof raw.verified_for_sending === "boolean" ? raw.verified_for_sending : typeof raw._verified_for_sending === "boolean" ? raw._verified_for_sending : result.raw === void 0 && typeof result.verifiedForSending === "boolean" ? result.verifiedForSending : result.raw === void 0 && typeof result.isVerifiedForSending === "boolean" ? result.isVerifiedForSending : void 0;
132
- const isCatchAll = typeof result.isCatchAll === "boolean" ? result.isCatchAll : void 0;
133
- const isMalformed = result.isMalformed === true || raw.is_malformed === true;
134
- const verificationStatus = result.verificationStatus || result.verificationVerdict;
135
- return {
136
- ...result,
158
+ if (String(result.status || "").toLowerCase() === "processing") {
159
+ const verificationRunId2 = result.verificationRunId ?? raw.verification_run_id;
160
+ const runId2 = result.runId ?? raw.run_id ?? verificationRunId2;
161
+ return {
162
+ success: true,
163
+ status: "processing",
164
+ email: null,
165
+ run_id: runId2,
166
+ ...verificationRunId2 ? { verification_run_id: verificationRunId2 } : {},
167
+ next_poll_after_seconds: result.nextPollAfterSeconds ?? raw.next_poll_after_seconds,
168
+ suggested_action: result.suggestedAction ?? raw.suggested_action ?? (verificationRunId2 ? "retry_with_verification_run_id" : "retry_with_run_id")
169
+ };
170
+ }
171
+ const creditsUsed = Number(result.creditsUsed ?? raw.credits_used ?? 0);
172
+ const base = {
137
173
  email: typeof result.email === "string" && result.email.trim() ? result.email.trim() : null,
138
174
  status: "completed",
139
175
  success: result.success === true,
140
- is_valid: verifiedForSending ?? result.isValid ?? result.isVerified ?? false,
141
- credits_used: Number(result.creditsUsed || 0),
142
- ...verifiedForSending !== void 0 ? { verified_for_sending: verifiedForSending } : {},
143
- ...isCatchAll !== void 0 ? { email_is_catch_all: isCatchAll, is_catch_all: isCatchAll } : {},
144
- ...typeof result.isMalformed === "boolean" || typeof raw.is_malformed === "boolean" ? { is_malformed: isMalformed } : {},
145
- ...verificationStatus ? { verification_status: verificationStatus, verification_verdict: verificationStatus } : {},
146
- ...result.billingMetadata ? { billing_metadata: result.billingMetadata } : {}
176
+ is_valid: typeof result.isValid === "boolean" ? result.isValid : typeof raw.is_valid === "boolean" ? raw.is_valid : result.isVerified ?? false,
177
+ credits_used: Number.isFinite(creditsUsed) && creditsUsed >= 0 ? creditsUsed : 0
178
+ };
179
+ if (kind === "find") {
180
+ const verifiedForSending2 = typeof result.isVerifiedForSending === "boolean" ? result.isVerifiedForSending : raw.verified_for_sending === true;
181
+ const isCatchAll2 = typeof result.isCatchAll === "boolean" ? result.isCatchAll : raw.email_is_catch_all === true || raw.is_catch_all === true;
182
+ const verificationStatus2 = String(
183
+ result.verificationStatus ?? raw.verification_status ?? raw.deliverability_status ?? (verifiedForSending2 ? "valid" : "unknown")
184
+ ).toLowerCase();
185
+ const deliverabilityStatus2 = String(
186
+ result.deliverabilityStatus ?? raw.deliverability_status ?? verificationStatus2
187
+ ).toLowerCase();
188
+ const verificationVerdict2 = String(
189
+ result.verificationVerdict ?? raw.verification_verdict ?? verificationStatus2
190
+ ).toLowerCase();
191
+ const billingMetadata2 = customerEmailBillingMetadata(
192
+ Object.keys(record(result.billingMetadata)).length > 0 ? result.billingMetadata : raw.billing_metadata
193
+ );
194
+ const historicalBillingMetadata2 = customerEmailBillingMetadata(
195
+ Object.keys(record(result.historicalBillingMetadata)).length > 0 ? result.historicalBillingMetadata : raw.historical_billing_metadata
196
+ );
197
+ const cacheHit2 = typeof raw.cache_hit === "boolean" ? raw.cache_hit : result.cacheHit === true;
198
+ const negativeCacheHit2 = typeof raw.negative_cache_hit === "boolean" ? raw.negative_cache_hit : result.negativeCacheHit === true;
199
+ const resultReturnedFrom = result.resultReturnedFrom ?? raw.result_returned_from ?? null;
200
+ const freshEnrichmentUsed = typeof raw.fresh_enrichment_used === "boolean" ? raw.fresh_enrichment_used : result.freshEnrichmentUsed === true;
201
+ const liveProviderCalled = typeof raw.live_provider_called === "boolean" ? raw.live_provider_called : result.liveProviderCalled === true;
202
+ const normalizedBillingMetadata = {
203
+ ...billingMetadata2,
204
+ ...resultReturnedFrom ? { result_returned_from: resultReturnedFrom } : {},
205
+ cache_hit: cacheHit2,
206
+ fresh_enrichment_used: freshEnrichmentUsed,
207
+ credits_used: base.credits_used,
208
+ live_provider_called: liveProviderCalled
209
+ };
210
+ const estimatedExternalCostUsd2 = safeNonNegativeNumber(
211
+ result.estimatedExternalCostUsd ?? raw.estimated_external_cost_usd
212
+ );
213
+ return {
214
+ ...base,
215
+ is_valid: verifiedForSending2,
216
+ verification_status: verificationStatus2,
217
+ deliverability_status: deliverabilityStatus2,
218
+ verification_verdict: verificationVerdict2,
219
+ verified_for_sending: verifiedForSending2,
220
+ is_deliverable: typeof result.isDeliverable === "boolean" ? result.isDeliverable && verifiedForSending2 : verifiedForSending2,
221
+ email_is_catch_all: isCatchAll2,
222
+ is_catch_all: isCatchAll2,
223
+ verification_source: result.verificationSource ?? raw.verification_source ?? result.providerUsed ?? "email_finder",
224
+ ...result.verificationObservedAt ?? raw.verification_observed_at ? { verification_observed_at: result.verificationObservedAt ?? raw.verification_observed_at } : {},
225
+ failure_reason: result.failureReason ?? raw.failure_reason ?? (verifiedForSending2 ? "none" : "no_verified_email_found"),
226
+ cache_hit: cacheHit2,
227
+ negative_cache_hit: negativeCacheHit2,
228
+ negative_cache_ttl_seconds: safeNonNegativeNumber(
229
+ result.negativeCacheTtlSeconds ?? raw.negative_cache_ttl_seconds
230
+ ),
231
+ cache_tier: result.cacheTier ?? raw.cache_tier ?? null,
232
+ result_returned_from: resultReturnedFrom,
233
+ fresh_enrichment_used: freshEnrichmentUsed,
234
+ live_provider_called: liveProviderCalled,
235
+ billing_metadata: normalizedBillingMetadata,
236
+ ...Object.keys(historicalBillingMetadata2).length > 0 ? { historical_billing_metadata: historicalBillingMetadata2 } : {},
237
+ ...estimatedExternalCostUsd2 !== null ? { estimated_external_cost_usd: estimatedExternalCostUsd2 } : {},
238
+ ...result.runId ?? raw.run_id ? { run_id: result.runId ?? raw.run_id } : {},
239
+ ...raw.cache_publication_status ? {
240
+ cache_publication_status: raw.cache_publication_status,
241
+ cache_publication_retry_eligible: raw.cache_publication_retry_eligible === true,
242
+ ...raw.suggested_action ? { suggested_action: raw.suggested_action } : {}
243
+ } : {}
244
+ };
245
+ }
246
+ const verificationStatus = String(
247
+ result.verificationStatus ?? raw.verification_status ?? result.verificationVerdict ?? raw.verification_verdict ?? raw.deliverability_status ?? "unknown"
248
+ ).toLowerCase();
249
+ const deliverabilityStatus = String(
250
+ result.deliverabilityStatus ?? raw.deliverability_status ?? verificationStatus
251
+ ).toLowerCase();
252
+ const verificationVerdict = String(
253
+ result.verificationVerdict ?? raw.verification_verdict ?? verificationStatus
254
+ ).toLowerCase();
255
+ const verifiedForSending = typeof result.verifiedForSending === "boolean" ? result.verifiedForSending : raw.verified_for_sending === true;
256
+ const isDeliverable = typeof result.isDeliverable === "boolean" ? result.isDeliverable : raw.is_deliverable === true;
257
+ const isCatchAll = typeof result.isCatchAll === "boolean" ? result.isCatchAll : raw.email_is_catch_all === true || raw.is_catch_all === true;
258
+ const isRoleAccount = typeof result.isRoleAccount === "boolean" ? result.isRoleAccount : raw.is_role_account === true;
259
+ const isMalformed = typeof result.isMalformed === "boolean" ? result.isMalformed : raw.is_malformed === true;
260
+ const quality = typeof result.quality === "string" ? result.quality : typeof raw.quality === "string" ? raw.quality : null;
261
+ 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";
262
+ const cacheHit = typeof raw.cache_hit === "boolean" ? raw.cache_hit : result.cacheHit === true;
263
+ const negativeCacheHit = typeof raw.negative_cache_hit === "boolean" ? raw.negative_cache_hit : result.negativeCacheHit === true;
264
+ const negativeCacheTtlSeconds = safeNonNegativeNumber(
265
+ result.negativeCacheTtlSeconds ?? raw.negative_cache_ttl_seconds
266
+ );
267
+ const billingMetadata = customerEmailBillingMetadata(
268
+ Object.keys(record(result.billingMetadata)).length > 0 ? result.billingMetadata : raw.billing_metadata
269
+ );
270
+ const historicalBillingMetadata = customerEmailBillingMetadata(
271
+ Object.keys(record(result.historicalBillingMetadata)).length > 0 ? result.historicalBillingMetadata : raw.historical_billing_metadata
272
+ );
273
+ const verificationRunId = result.verificationRunId ?? raw.verification_run_id ?? raw.run_id;
274
+ const runId = result.runId ?? raw.run_id ?? verificationRunId;
275
+ const estimatedExternalCostUsd = safeNonNegativeNumber(
276
+ result.estimatedExternalCostUsd ?? raw.estimated_external_cost_usd
277
+ );
278
+ return {
279
+ ...base,
280
+ verification_status: verificationStatus,
281
+ deliverability_status: deliverabilityStatus,
282
+ verification_verdict: verificationVerdict,
283
+ verified_for_sending: verifiedForSending,
284
+ is_deliverable: isDeliverable,
285
+ email_is_catch_all: isCatchAll,
286
+ is_catch_all: isCatchAll,
287
+ is_role_account: isRoleAccount,
288
+ is_malformed: isMalformed,
289
+ quality,
290
+ recommendation,
291
+ smtp_status: result.smtpStatus ?? raw.smtp_status ?? (isCatchAll ? "accept_all" : verifiedForSending ? "accepted" : "not_checked"),
292
+ provider_status: result.providerStatus ?? raw.provider_status ?? verificationStatus,
293
+ verification_source: result.verificationSource ?? raw.verification_source ?? "email_verification",
294
+ ...result.verificationObservedAt ?? raw.verification_observed_at ? { verification_observed_at: result.verificationObservedAt ?? raw.verification_observed_at } : {},
295
+ failure_reason: result.failureReason ?? raw.failure_reason ?? (verifiedForSending ? "none" : isCatchAll ? "catch_all_domain_not_send_safe" : "verification_not_send_safe"),
296
+ cache_hit: cacheHit,
297
+ negative_cache_hit: negativeCacheHit,
298
+ negative_cache_ttl_seconds: negativeCacheTtlSeconds,
299
+ cache_tier: result.cacheTier ?? raw.cache_tier ?? null,
300
+ result_returned_from: result.resultReturnedFrom ?? raw.result_returned_from ?? null,
301
+ fresh_enrichment_used: typeof raw.fresh_enrichment_used === "boolean" ? raw.fresh_enrichment_used : result.freshEnrichmentUsed === true,
302
+ live_provider_called: typeof raw.live_provider_called === "boolean" ? raw.live_provider_called : result.liveProviderCalled === true,
303
+ billing_replayed: typeof raw.billing_replayed === "boolean" ? raw.billing_replayed : result.billingReplayed === true,
304
+ billing_metadata: billingMetadata,
305
+ ...Object.keys(historicalBillingMetadata).length > 0 ? { historical_billing_metadata: historicalBillingMetadata } : {},
306
+ ...estimatedExternalCostUsd !== null ? { estimated_external_cost_usd: estimatedExternalCostUsd } : {},
307
+ ...verificationRunId ? { verification_run_id: verificationRunId } : {},
308
+ ...runId ? { run_id: runId } : {},
309
+ ...raw.cache_publication_status ? {
310
+ cache_publication_status: raw.cache_publication_status,
311
+ cache_publication_retry_eligible: raw.cache_publication_retry_eligible === true,
312
+ ...raw.suggested_action ? { suggested_action: raw.suggested_action } : {}
313
+ } : {}
147
314
  };
148
315
  }
149
- function customerEmailBatch(value) {
316
+ function customerEmailBatch(value, kind) {
150
317
  return {
151
318
  ...value,
152
319
  results: value.results.map((item) => {
153
320
  const row = record(item);
154
- return row.data ? { ...row, data: customerEmailResult(row.data) } : row;
321
+ return promoteBatchRecoveryMetadata(row.data ? { ...row, data: customerEmailResult(row.data, kind) } : row);
155
322
  })
156
323
  };
157
324
  }
325
+ function promoteBatchRecoveryMetadata(row) {
326
+ return {
327
+ ...row,
328
+ ...typeof row.runId === "string" ? { run_id: row.runId } : {},
329
+ ...typeof row.verificationRunId === "string" ? { verification_run_id: row.verificationRunId } : {},
330
+ ...typeof row.jobId === "string" ? { job_id: row.jobId } : {},
331
+ ...typeof row.suggestedAction === "string" ? { suggested_action: row.suggestedAction } : {}
332
+ };
333
+ }
158
334
  function customerSignalResult(value) {
159
335
  const result = record(value);
160
336
  const raw = record(result.raw);
@@ -176,7 +352,7 @@ function customerSignalBatch(value) {
176
352
  ...value,
177
353
  results: value.results.map((item) => {
178
354
  const row = record(item);
179
- return row.data ? { ...row, data: customerSignalResult(row.data) } : row;
355
+ return promoteBatchRecoveryMetadata(row.data ? { ...row, data: customerSignalResult(row.data) } : row);
180
356
  })
181
357
  };
182
358
  }
@@ -184,14 +360,40 @@ function batchOptions() {
184
360
  return {
185
361
  concurrency: numberFlag("concurrency"),
186
362
  idempotencyKey: flag("idempotency-key"),
187
- compactDuplicates: !hasFlag("expand-duplicates")
363
+ compactDuplicates: !hasFlag("expand-duplicates"),
364
+ maxWaitMs: numberFlag("max-wait-ms"),
365
+ pollIntervalMs: numberFlag("poll-interval-ms")
188
366
  };
189
367
  }
190
- function rejectAsyncSignalFlags() {
191
- if (hasFlag("no-wait") || hasFlag("poll-interval-ms")) {
192
- fail("Company Signals and Signal to Copy are always synchronous; --no-wait and --poll-interval-ms are not supported.");
368
+ function rejectDurableNoWaitPollingFlags(label) {
369
+ if (!hasFlag("no-wait")) return;
370
+ const conflicts = ["max-wait-ms", "poll-interval-ms", "concurrency"].filter(hasFlag);
371
+ if (conflicts.length > 0) {
372
+ fail(`${label} --no-wait cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`);
193
373
  }
194
374
  }
375
+ function recoverableJobOutput(value, command) {
376
+ const resumeCommand = `signaliz ${command} --job-id ${value.jobId} --idempotency-key ${value.idempotencyKey}`;
377
+ const payload = {
378
+ success: true,
379
+ status: value.status,
380
+ capability: value.capability,
381
+ job_id: value.jobId,
382
+ idempotency_key: value.idempotencyKey,
383
+ total: value.total,
384
+ next_poll_after_seconds: value.nextPollAfterSeconds,
385
+ suggested_action: "check_job_status",
386
+ resume_command: resumeCommand
387
+ };
388
+ output(payload, () => {
389
+ process.stdout.write(`Durable ${command} batch submitted: ${value.jobId}
390
+ `);
391
+ process.stdout.write(`Idempotency key: ${value.idempotencyKey}
392
+ `);
393
+ process.stdout.write(`Resume with: ${resumeCommand}
394
+ `);
395
+ });
396
+ }
195
397
  function batchOutput(value) {
196
398
  if (hasFlag("jsonl")) {
197
399
  for (const result of value.results) {
@@ -256,6 +458,9 @@ function numberValue(value) {
256
458
  const number = Number(value);
257
459
  return Number.isFinite(number) ? number : void 0;
258
460
  }
461
+ function nullableNumberValue(value) {
462
+ return value === null ? null : numberValue(value);
463
+ }
259
464
  function stringValue(value) {
260
465
  return typeof value === "string" ? value.trim() : "";
261
466
  }
@@ -311,6 +516,7 @@ function normalizeSignalsEverythingResult(request, value) {
311
516
  const status = statuses.has(statusValue) ? statusValue : signals2.length > 0 ? "completed" : "failed";
312
517
  const guardrail = record(value.cost_guardrail);
313
518
  const maxCost = numberValue(guardrail.max_cost_per_qualified_signal_usd);
519
+ const identityCostSource = stringValue(guardrail.identity_cost_source);
314
520
  const warnings = Array.isArray(value.warnings) ? value.warnings.filter((warning) => typeof warning === "string") : void 0;
315
521
  return {
316
522
  success: value.success !== false,
@@ -327,7 +533,15 @@ function normalizeSignalsEverythingResult(request, value) {
327
533
  ...maxCost === void 0 ? {} : {
328
534
  costGuardrail: {
329
535
  maxCostPerQualifiedSignalUsd: maxCost,
330
- costPerQualifiedSignalUsd: value.cost_guardrail && typeof value.cost_guardrail === "object" ? numberValue(guardrail.cost_per_qualified_signal_usd) ?? (guardrail.cost_per_qualified_signal_usd === null ? null : void 0) : void 0
536
+ costPerQualifiedSignalUsd: nullableNumberValue(guardrail.cost_per_qualified_signal_usd),
537
+ identityResolutionCostUsd: nullableNumberValue(guardrail.identity_resolution_cost_usd),
538
+ identityCostSource: [
539
+ "observed_zero_credit_receipts",
540
+ "contractual_zero_cost",
541
+ "not_used",
542
+ "unavailable"
543
+ ].includes(identityCostSource) ? identityCostSource : void 0,
544
+ identityProviderRequests: numberValue(guardrail.identity_provider_requests)
331
545
  }
332
546
  },
333
547
  ...warnings?.length ? { warnings } : {},
@@ -342,6 +556,7 @@ async function discoverSignalsEverything(request) {
342
556
  ...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {}
343
557
  } : {
344
558
  query: request.query,
559
+ ...request.icpContext ? { icp_context: request.icpContext } : {},
345
560
  limit: request.limit ?? 100,
346
561
  ...request.sources ? { sources: request.sources } : {},
347
562
  ...request.lookbackDays === void 0 ? {} : { lookback_days: request.lookbackDays },
@@ -465,10 +680,41 @@ async function auth(args) {
465
680
  fail("Usage: signaliz auth <login|set-key|workspace|status>");
466
681
  }
467
682
  async function findEmail() {
683
+ const jobId = flag("job-id");
684
+ if (jobId) {
685
+ const conflicts = [
686
+ "input",
687
+ "dry-run",
688
+ "company-domain",
689
+ "domain",
690
+ "full-name",
691
+ "first-name",
692
+ "last-name",
693
+ "linkedin-url",
694
+ "company-name",
695
+ "run-id",
696
+ "skip-cache",
697
+ "idempotency-key"
698
+ ].filter(hasFlag);
699
+ if (conflicts.length > 0) {
700
+ fail(`Find Email --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`);
701
+ }
702
+ const result2 = await createClient().resumeFindEmailBatch(jobId, {
703
+ pageSize: numberFlag("page-size"),
704
+ maxWaitMs: numberFlag("max-wait-ms"),
705
+ pollIntervalMs: numberFlag("poll-interval-ms"),
706
+ compactDuplicates: !hasFlag("expand-duplicates")
707
+ });
708
+ batchOutput(customerEmailBatch(result2, "find"));
709
+ return;
710
+ }
468
711
  const rows = readBatchInput();
469
712
  if (rows) {
470
713
  const requests = rows.map((value) => {
471
714
  const row = record(value);
715
+ const runId2 = row.runId || row.run_id;
716
+ const idempotencyKey = row.idempotencyKey || row.idempotency_key;
717
+ if (runId2) return { runId: runId2, idempotencyKey };
472
718
  return {
473
719
  companyDomain: row.companyDomain || row.company_domain || row.domain,
474
720
  fullName: row.fullName || row.full_name || row.name,
@@ -476,7 +722,8 @@ async function findEmail() {
476
722
  lastName: row.lastName || row.last_name,
477
723
  linkedinUrl: row.linkedinUrl || row.linkedin_url || row.linkedin,
478
724
  companyName: row.companyName || row.company_name,
479
- skipCache: row.skipCache ?? row.skip_cache ?? hasFlag("skip-cache")
725
+ skipCache: row.skipCache ?? row.skip_cache ?? hasFlag("skip-cache"),
726
+ idempotencyKey
480
727
  };
481
728
  });
482
729
  if (hasFlag("dry-run")) {
@@ -485,18 +732,19 @@ async function findEmail() {
485
732
  return;
486
733
  }
487
734
  const result2 = await createClient().findEmails(requests, batchOptions());
488
- batchOutput(customerEmailBatch(result2));
735
+ batchOutput(customerEmailBatch(result2, "find"));
489
736
  return;
490
737
  }
491
738
  const companyDomain = flag("company-domain") || flag("domain");
739
+ const runId = flag("run-id");
492
740
  const fullName = flag("full-name");
493
741
  const firstName = flag("first-name");
494
742
  const lastName = flag("last-name");
495
743
  const linkedinUrl = flag("linkedin-url");
496
- if (!companyDomain || !fullName && !(firstName && lastName)) {
497
- fail('Usage: signaliz find-email --company-domain example.com --full-name "Jane Doe"');
744
+ if (!runId && (!companyDomain || !fullName && !(firstName && lastName))) {
745
+ fail('Usage: signaliz find-email --company-domain example.com --full-name "Jane Doe", or signaliz find-email --run-id run_...');
498
746
  }
499
- const request = {
747
+ const request = runId ? { runId } : {
500
748
  companyDomain,
501
749
  fullName,
502
750
  firstName,
@@ -512,30 +760,68 @@ async function findEmail() {
512
760
  return;
513
761
  }
514
762
  const result = await createClient().findEmail(request);
515
- const publicResult = customerEmailResult(result);
763
+ const publicResult = customerEmailResult(result, "find");
516
764
  if (outputFailedCoreProduct(publicResult, "No verified email found")) return;
517
765
  output(publicResult, () => {
518
- process.stdout.write(`${result.email || "No email found"} (${result.verificationStatus})
766
+ if (result.status === "processing" && result.runId) {
767
+ process.stdout.write(`Find Email processing: ${result.runId}
519
768
  `);
520
- process.stdout.write(`Verified for sending: ${result.isVerifiedForSending}
769
+ process.stdout.write(`Retry after ${result.nextPollAfterSeconds || 3} seconds with --run-id ${result.runId}
521
770
  `);
522
- process.stdout.write(`Catch-all: ${result.isCatchAll}
771
+ return;
772
+ }
773
+ process.stdout.write(`Email: ${publicResult.email ?? "Not found"}
523
774
  `);
524
- process.stdout.write(`Confidence: ${result.confidence}
775
+ process.stdout.write(`Status: ${publicResult.status}
525
776
  `);
526
- process.stdout.write(`Provider: ${result.providerUsed}
777
+ process.stdout.write(`Success: ${publicResult.success}
527
778
  `);
528
- process.stdout.write(`Credits used: ${result.creditsUsed || 0}
779
+ process.stdout.write(`Valid: ${publicResult.is_valid}
780
+ `);
781
+ process.stdout.write(`Credits used: ${publicResult.credits_used}
529
782
  `);
530
- if (result.needsReverification) {
531
- process.stdout.write("Send safety: stale verification; rerun with --skip-cache before sending\n");
532
- }
533
783
  });
534
784
  }
535
785
  async function verifyEmail(args) {
786
+ const jobId = flag("job-id");
787
+ if (jobId) {
788
+ const positionalEmail = args[0] && !args[0].startsWith("--");
789
+ const conflicts = [
790
+ "input",
791
+ "dry-run",
792
+ "email",
793
+ "verification-run-id",
794
+ "skip-cache",
795
+ "idempotency-key",
796
+ "no-wait"
797
+ ].filter(hasFlag);
798
+ if (positionalEmail || conflicts.length > 0) {
799
+ const conflictList = [
800
+ ...positionalEmail ? ["email argument"] : [],
801
+ ...conflicts.map((name) => `--${name}`)
802
+ ];
803
+ fail(`Verify Email --job-id cannot be combined with ${conflictList.join(", ")}.`);
804
+ }
805
+ const result2 = await createClient().resumeVerifyEmailBatch(jobId, {
806
+ pageSize: numberFlag("page-size"),
807
+ maxWaitMs: numberFlag("max-wait-ms"),
808
+ pollIntervalMs: numberFlag("poll-interval-ms"),
809
+ compactDuplicates: !hasFlag("expand-duplicates")
810
+ });
811
+ batchOutput(customerEmailBatch(result2, "verify"));
812
+ return;
813
+ }
536
814
  const rows = readBatchInput();
537
815
  if (rows) {
538
- const emails = rows.map((value) => typeof value === "string" ? value : String(record(value).email || ""));
816
+ const emails = rows.map((value) => {
817
+ if (typeof value === "string") return value;
818
+ const row = record(value);
819
+ return {
820
+ email: String(row.email || ""),
821
+ skipCache: row.skipCache ?? row.skip_cache ?? hasFlag("skip-cache"),
822
+ idempotencyKey: row.idempotencyKey || row.idempotency_key
823
+ };
824
+ });
539
825
  if (hasFlag("dry-run")) {
540
826
  const result3 = await createClient().verifyEmails(emails, {
541
827
  ...batchOptions(),
@@ -549,7 +835,7 @@ async function verifyEmail(args) {
549
835
  ...batchOptions(),
550
836
  skipCache: hasFlag("skip-cache")
551
837
  });
552
- batchOutput(customerEmailBatch(result2));
838
+ batchOutput(customerEmailBatch(result2, "verify"));
553
839
  return;
554
840
  }
555
841
  const email = args[0] && !args[0].startsWith("--") ? args[0] : flag("email");
@@ -569,7 +855,7 @@ async function verifyEmail(args) {
569
855
  return;
570
856
  }
571
857
  const result = await createClient().verifyEmail(email, options);
572
- const publicResult = customerEmailResult(result);
858
+ const publicResult = customerEmailResult(result, "verify");
573
859
  if (outputFailedCoreProduct(publicResult, "Email verification failed")) return;
574
860
  output(publicResult, () => {
575
861
  if (result.status === "processing") {
@@ -579,22 +865,69 @@ async function verifyEmail(args) {
579
865
  `);
580
866
  return;
581
867
  }
582
- process.stdout.write(`${result.email}: ${result.verifiedForSending ? "verified for sending" : "not verified for sending"}
868
+ process.stdout.write(`Email: ${publicResult.email ?? "Not found"}
869
+ `);
870
+ process.stdout.write(`Status: ${publicResult.status}
871
+ `);
872
+ process.stdout.write(`Success: ${publicResult.success}
873
+ `);
874
+ process.stdout.write(`Valid: ${publicResult.is_valid}
583
875
  `);
584
- process.stdout.write(`Valid: ${result.isValid}
585
- Deliverable: ${result.isDeliverable}
586
- Catch-all: ${result.isCatchAll}
587
- Verdict: ${result.verificationVerdict}
876
+ process.stdout.write(`Verdict: ${publicResult.verification_status}
588
877
  `);
589
- if (result.isMalformed) process.stdout.write("Malformed: true (rejected before provider call)\n");
590
- process.stdout.write(`Credits used: ${result.creditsUsed || 0}
878
+ process.stdout.write(`Recommendation: ${publicResult.recommendation}
879
+ `);
880
+ process.stdout.write(`Source: ${publicResult.verification_source}${publicResult.cache_hit === true ? " (cache)" : ""}
881
+ `);
882
+ if (publicResult.run_id) process.stdout.write(`Run ID: ${publicResult.run_id}
883
+ `);
884
+ process.stdout.write(`Credits used: ${publicResult.credits_used}
591
885
  `);
592
886
  });
593
887
  }
594
888
  async function companySignals() {
595
- rejectAsyncSignalFlags();
889
+ const jobId = flag("job-id");
890
+ if (jobId) {
891
+ const conflicts = [
892
+ "input",
893
+ "dry-run",
894
+ "domain",
895
+ "company-domain",
896
+ "company-name",
897
+ "signal-run-id",
898
+ "research-prompt",
899
+ "prompt",
900
+ "signal-types",
901
+ "target-signal-count",
902
+ "lookback-days",
903
+ "offline",
904
+ "shallow",
905
+ "search-mode",
906
+ "no-summary",
907
+ "outreach-intelligence",
908
+ "predictive-intelligence",
909
+ "skip-cache",
910
+ "include-candidate-evidence",
911
+ "no-wait",
912
+ "concurrency"
913
+ ].filter(hasFlag);
914
+ if (conflicts.length > 0) {
915
+ fail(`Company Signals --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`);
916
+ }
917
+ const result2 = await createClient().resumeCompanySignalBatch(jobId, {
918
+ pageSize: numberFlag("page-size"),
919
+ maxWaitMs: numberFlag("max-wait-ms"),
920
+ pollIntervalMs: numberFlag("poll-interval-ms"),
921
+ idempotencyKey: flag("idempotency-key"),
922
+ compactDuplicates: !hasFlag("expand-duplicates")
923
+ });
924
+ batchOutput(customerSignalBatch(result2));
925
+ return;
926
+ }
927
+ rejectDurableNoWaitPollingFlags("Company Signals");
596
928
  const rows = readBatchInput();
597
929
  if (rows) {
930
+ const durableBatch = rows.length > 25;
598
931
  const requests = rows.map((value) => {
599
932
  const row = record(value);
600
933
  const online = row.online ?? !hasFlag("offline");
@@ -614,18 +947,30 @@ async function companySignals() {
614
947
  enablePredictiveIntelligence: row.enablePredictiveIntelligence ?? row.enable_predictive_intelligence,
615
948
  skipCache: row.skipCache ?? row.skip_cache ?? hasFlag("skip-cache"),
616
949
  includeCandidateEvidence: row.includeCandidateEvidence ?? row.include_candidate_evidence ?? hasFlag("include-candidate-evidence"),
617
- maxWaitMs: row.maxWaitMs || row.max_wait_ms || numberFlag("max-wait-ms")
950
+ maxWaitMs: row.maxWaitMs || row.max_wait_ms || (durableBatch ? void 0 : numberFlag("max-wait-ms")),
951
+ idempotencyKey: row.idempotencyKey || row.idempotency_key
618
952
  };
619
953
  });
620
954
  if (hasFlag("dry-run")) {
955
+ if (hasFlag("no-wait")) fail("Company Signals --no-wait cannot be combined with --dry-run.");
621
956
  const result3 = await createClient().enrichCompanies(requests, { ...batchOptions(), dryRun: true });
622
957
  dryRunOutput(result3);
623
958
  return;
624
959
  }
960
+ if (hasFlag("no-wait")) {
961
+ const job = await createClient().enrichCompanies(requests, {
962
+ ...batchOptions(),
963
+ waitForResult: false
964
+ });
965
+ recoverableJobOutput(job, "company-signals");
966
+ return;
967
+ }
625
968
  const result2 = await createClient().enrichCompanies(requests, batchOptions());
626
969
  batchOutput(customerSignalBatch(result2));
627
970
  return;
628
971
  }
972
+ if (hasFlag("no-wait")) fail("Company Signals --no-wait requires --input with more than 25 homogeneous rows.");
973
+ if (hasFlag("poll-interval-ms")) fail("Company Signals --poll-interval-ms requires --input or --job-id.");
629
974
  const companyDomain = flag("domain") || flag("company-domain");
630
975
  const companyName = flag("company-name");
631
976
  const signalRunId = flag("signal-run-id");
@@ -676,6 +1021,7 @@ function signalQueryFromArgs(args) {
676
1021
  "--limit",
677
1022
  "--sources",
678
1023
  "--lookback-days",
1024
+ "--icp-context",
679
1025
  "--signal-search-run-id",
680
1026
  "--idempotency-key",
681
1027
  "--max-wait-ms",
@@ -709,8 +1055,13 @@ async function signals(args) {
709
1055
  if (sourceValues?.some((source) => !["web", "news"].includes(source))) {
710
1056
  fail("--sources must contain only web or news");
711
1057
  }
1058
+ const icpContext = flag("icp-context");
1059
+ if (hasFlag("icp-context") && (typeof icpContext !== "string" || icpContext.trim().length < 2 || icpContext.trim().length > 4e3)) {
1060
+ fail("--icp-context must contain between 2 and 4000 characters");
1061
+ }
712
1062
  const request = {
713
1063
  query,
1064
+ icpContext: icpContext?.trim(),
714
1065
  signalSearchRunId,
715
1066
  limit: numberFlag("limit"),
716
1067
  sources: sourceValues,
@@ -764,6 +1115,14 @@ async function signals(args) {
764
1115
  `Source cost per qualified company: ${guardrail.costPerQualifiedSignalUsd ?? "n/a"} (must be < $${guardrail.maxCostPerQualifiedSignalUsd})
765
1116
  `
766
1117
  );
1118
+ if (guardrail.identityResolutionCostUsd !== void 0) {
1119
+ const identityCost = guardrail.identityResolutionCostUsd === null ? "unavailable" : `$${guardrail.identityResolutionCostUsd}`;
1120
+ const identityRequests = guardrail.identityProviderRequests;
1121
+ process.stdout.write(
1122
+ `Identity resolution cost: ${identityCost}${identityRequests === void 0 ? "" : ` across ${identityRequests} identity lookup request${identityRequests === 1 ? "" : "s"}`}${guardrail.identityCostSource ? ` (${guardrail.identityCostSource})` : ""}.
1123
+ `
1124
+ );
1125
+ }
767
1126
  }
768
1127
  for (const signal of result.signals.slice(0, 20)) {
769
1128
  process.stdout.write(`- ${signal.company.name} (${signal.company.domain}) \u2014 ${signal.eventDate} \u2014 ${signal.title}
@@ -777,9 +1136,43 @@ async function signals(args) {
777
1136
  });
778
1137
  }
779
1138
  async function signalToCopy() {
780
- rejectAsyncSignalFlags();
1139
+ const jobId = flag("job-id");
1140
+ if (jobId) {
1141
+ const conflicts = [
1142
+ "input",
1143
+ "dry-run",
1144
+ "company-domain",
1145
+ "domain",
1146
+ "person-name",
1147
+ "title",
1148
+ "campaign-offer",
1149
+ "offer",
1150
+ "research-prompt",
1151
+ "prompt",
1152
+ "lookback-days",
1153
+ "signal-run-id",
1154
+ "skip-cache",
1155
+ "deep-search",
1156
+ "no-wait",
1157
+ "concurrency"
1158
+ ].filter(hasFlag);
1159
+ if (conflicts.length > 0) {
1160
+ fail(`Signal to Copy --job-id cannot be combined with ${conflicts.map((name) => `--${name}`).join(", ")}.`);
1161
+ }
1162
+ const result2 = await createClient().resumeSignalCopyBatch(jobId, {
1163
+ pageSize: numberFlag("page-size"),
1164
+ maxWaitMs: numberFlag("max-wait-ms"),
1165
+ pollIntervalMs: numberFlag("poll-interval-ms"),
1166
+ idempotencyKey: flag("idempotency-key"),
1167
+ compactDuplicates: !hasFlag("expand-duplicates")
1168
+ });
1169
+ batchOutput(customerSignalBatch(result2));
1170
+ return;
1171
+ }
1172
+ rejectDurableNoWaitPollingFlags("Signal to Copy");
781
1173
  const rows = readBatchInput();
782
1174
  if (rows) {
1175
+ const durableBatch = rows.length > 25;
783
1176
  const requests = rows.map((value) => {
784
1177
  const row = record(value);
785
1178
  return {
@@ -788,21 +1181,34 @@ async function signalToCopy() {
788
1181
  title: row.title,
789
1182
  campaignOffer: row.campaignOffer || row.campaign_offer || row.offer,
790
1183
  researchPrompt: row.researchPrompt || row.research_prompt || flag("research-prompt") || flag("prompt"),
1184
+ lookbackDays: row.lookbackDays || row.lookback_days || numberFlag("lookback-days"),
791
1185
  signalRunId: row.signalRunId || row.signal_run_id,
792
1186
  skipCache: row.skipCache ?? row.skip_cache ?? hasFlag("skip-cache"),
793
1187
  enableDeepSearch: row.enableDeepSearch ?? row.enable_deep_search ?? hasFlag("deep-search"),
794
- maxWaitMs: row.maxWaitMs || row.max_wait_ms || numberFlag("max-wait-ms")
1188
+ maxWaitMs: row.maxWaitMs || row.max_wait_ms || (durableBatch ? void 0 : numberFlag("max-wait-ms")),
1189
+ idempotencyKey: row.idempotencyKey || row.idempotency_key
795
1190
  };
796
1191
  });
797
1192
  if (hasFlag("dry-run")) {
1193
+ if (hasFlag("no-wait")) fail("Signal to Copy --no-wait cannot be combined with --dry-run.");
798
1194
  const result3 = await createClient().createSignalCopyBatch(requests, { ...batchOptions(), dryRun: true });
799
1195
  dryRunOutput(result3);
800
1196
  return;
801
1197
  }
1198
+ if (hasFlag("no-wait")) {
1199
+ const job = await createClient().createSignalCopyBatch(requests, {
1200
+ ...batchOptions(),
1201
+ waitForResult: false
1202
+ });
1203
+ recoverableJobOutput(job, "signal-to-copy");
1204
+ return;
1205
+ }
802
1206
  const result2 = await createClient().createSignalCopyBatch(requests, batchOptions());
803
1207
  batchOutput(customerSignalBatch(result2));
804
1208
  return;
805
1209
  }
1210
+ if (hasFlag("no-wait")) fail("Signal to Copy --no-wait requires --input with more than 25 available-data-only rows.");
1211
+ if (hasFlag("poll-interval-ms")) fail("Signal to Copy --poll-interval-ms requires --input or --job-id.");
806
1212
  const companyDomain = flag("company-domain") || flag("domain");
807
1213
  const personName = flag("person-name");
808
1214
  const title = flag("title");
@@ -814,6 +1220,7 @@ async function signalToCopy() {
814
1220
  title,
815
1221
  campaignOffer,
816
1222
  researchPrompt: flag("research-prompt") || flag("prompt"),
1223
+ lookbackDays: numberFlag("lookback-days"),
817
1224
  signalRunId,
818
1225
  skipCache: hasFlag("skip-cache"),
819
1226
  enableDeepSearch: hasFlag("deep-search"),
@@ -933,6 +1340,14 @@ main().catch((error) => {
933
1340
  retry_after: signalizError.retryAfter,
934
1341
  retry_after_seconds: signalizError.retryAfter
935
1342
  } : {},
1343
+ ...signalizError?.details?.retry_strategy === "new_idempotency_key" ? { retry_strategy: "new_idempotency_key" } : {},
1344
+ ...signalizError?.details?.credits_used === 0 ? { credits_used: 0 } : {},
1345
+ ...signalizError?.details?.credits_charged === 0 ? { credits_charged: 0 } : {},
1346
+ ...typeof signalizError?.details?.run_id === "string" ? { run_id: signalizError.details.run_id } : {},
1347
+ ...typeof signalizError?.details?.verification_run_id === "string" ? { verification_run_id: signalizError.details.verification_run_id } : {},
1348
+ ...typeof signalizError?.details?.job_id === "string" ? { job_id: signalizError.details.job_id } : {},
1349
+ ...typeof signalizError?.details?.idempotency_key === "string" ? { idempotency_key: signalizError.details.idempotency_key } : {},
1350
+ ...typeof signalizError?.details?.suggested_action === "string" ? { suggested_action: signalizError.details.suggested_action } : {},
936
1351
  ...signalizError?.errorType ? { error_type: signalizError.errorType } : {},
937
1352
  ...signalizError?.details ? { details: signalizError.details } : {}
938
1353
  }, hasFlag("json") ? 2 : void 0)}
@@ -946,6 +1361,46 @@ main().catch((error) => {
946
1361
  fail(`${message}
947
1362
  Recoverable signal run: ${signalRunId}
948
1363
  Retry the same signaliz ${command} command with --signal-run-id ${signalRunId}, or use --json for structured recovery metadata.`);
1364
+ }
1365
+ if (error instanceof import_sdk.SignalizError && typeof error.details?.verification_run_id === "string") {
1366
+ const verificationRunId = error.details.verification_run_id;
1367
+ if (error.details.suggested_action === "retry_with_verification_run_id") {
1368
+ fail(`${message}
1369
+ Recoverable verification run: ${verificationRunId}
1370
+ Retry with: signaliz verify-email --verification-run-id ${verificationRunId}`);
1371
+ }
1372
+ fail(`${message}
1373
+ Terminal verification run: ${verificationRunId}
1374
+ No automatic retry is recommended; review the provider outcome or contact support.`);
1375
+ }
1376
+ if (error instanceof import_sdk.SignalizError && typeof error.details?.run_id === "string" && process.argv[2] === "find-email") {
1377
+ const runId = error.details.run_id;
1378
+ if (error.details.suggested_action === "retry_with_run_id") {
1379
+ fail(`${message}
1380
+ Recoverable Find Email run: ${runId}
1381
+ Retry with: signaliz find-email --run-id ${runId}`);
1382
+ }
1383
+ fail(`${message}
1384
+ Terminal Find Email run: ${runId}
1385
+ No automatic retry is recommended; review the provider outcome or contact support.`);
1386
+ }
1387
+ if (error instanceof import_sdk.SignalizError && typeof error.details?.job_id === "string") {
1388
+ const jobId = error.details.job_id;
1389
+ if (error.details.suggested_action === "check_job_status") {
1390
+ if (["find-email", "verify-email", "company-signals", "signal-to-copy"].includes(process.argv[2])) {
1391
+ const idempotencyKey = typeof error.details.idempotency_key === "string" ? error.details.idempotency_key : "";
1392
+ const keyFlag = idempotencyKey ? ` --idempotency-key ${idempotencyKey}` : "";
1393
+ fail(`${message}
1394
+ Recoverable job: ${jobId}
1395
+ Retry with: signaliz ${process.argv[2]} --job-id ${jobId}${keyFlag}`);
1396
+ }
1397
+ fail(`${message}
1398
+ Recoverable job: ${jobId}
1399
+ Resume this job through the REST API or SDK using job_id ${jobId}, or use --json for structured recovery metadata.`);
1400
+ }
1401
+ fail(`${message}
1402
+ Terminal job: ${jobId}
1403
+ No automatic retry is recommended; review the provider outcome or contact support.`);
949
1404
  }
950
1405
  fail(message);
951
1406
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaliz/cli",
3
- "version": "1.0.56",
3
+ "version": "1.0.58",
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.60"
32
+ "@signaliz/sdk": "^1.0.62"
33
33
  },
34
34
  "devDependencies": {
35
35
  "tsup": "^8.0.0",