@signaliz/cli 1.0.48 → 1.0.50

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 +20 -3
  2. package/dist/bin.js +229 -5
  3. package/package.json +5 -4
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @signaliz/cli
2
2
 
3
- The Signaliz CLI exposes four product commands:
3
+ The Signaliz CLI exposes all five core products:
4
4
 
5
5
  ```bash
6
6
  npm install -g @signaliz/cli
@@ -11,6 +11,7 @@ signaliz auth login
11
11
  # https://api.signaliz.com/functions/v1/api/v1/verify-email
12
12
  # https://api.signaliz.com/functions/v1/api/v1/company-signals
13
13
  # https://api.signaliz.com/functions/v1/api/v1/signal-to-copy
14
+ # https://api.signaliz.com/functions/v1/api/v1/signals
14
15
 
15
16
  signaliz find-email --company-domain example.com --full-name "Jane Doe"
16
17
  # Force a fresh-provider quality check instead of reusing cached email or verification data
@@ -33,11 +34,16 @@ signaliz signal-to-copy \
33
34
  # Force fresh signals or opt into slower deep research only when needed
34
35
  signaliz signal-to-copy --company-domain example.com --person-name "Jane Doe" \
35
36
  --title "VP Sales" --campaign-offer "pipeline intelligence" --skip-cache --deep-search
37
+
38
+ # Signals Everything returns distinct companies with a source-backed signal.
39
+ # It is asynchronous by default; add --wait or resume the returned run ID.
40
+ signaliz signals "companies that just got funded" --limit 100 --sources web,news
41
+ signaliz signals --signal-search-run-id run_... --wait --json
36
42
  ```
37
43
 
38
44
  Use `--json` with any product command for structured output.
39
45
 
40
- Every product command also accepts a JSON array or JSONL file for scale:
46
+ The four row-oriented products accept a JSON array or JSONL file for scale:
41
47
 
42
48
  ```bash
43
49
  signaliz find-email --input contacts.jsonl --concurrency 20 --json
@@ -76,15 +82,26 @@ calls, jobs, writes, or spend.
76
82
  Batch input is capped at 5,000 records, concurrency is bounded to `1-50`, input
77
83
  order is preserved, and a failed item does not abort successful items.
78
84
  Exact duplicate tasks are single-flighted across the full input before
79
- transport. All four core products use one recoverable REST job for inputs larger
85
+ transport. The four row-oriented core products use one recoverable REST job for inputs larger
80
86
  than 25; the CLI waits for completion and retrieves every result page before it
81
87
  emits complete results or terminal per-row errors. Find Email, Verify Email, and
82
88
  Signal to Copy use 500-row pages, while Company Signal Enrichment uses 25-row
83
89
  pages. Durable Signal to Copy jobs use available company-signal data only. If a
90
+ Company Signal row contains `signal_run_id`, the CLI preserves the recovery-read
91
+ contract with direct batches of at most 25. If a
84
92
  copy row explicitly requests fresh or deep research, the CLI preserves that
85
93
  behavior with synchronous chunks of at most 25. Signal to Copy also shares
86
94
  company research across recipients.
87
95
 
96
+ Signals Everything is a query-first company discovery command rather than a
97
+ row batch. It returns only dated, source-linked signals that contain both a
98
+ company name and company domain. The default request is 100 companies and the
99
+ maximum is 1,000. It is included on Team and Agency plans, is resumable with
100
+ `signal_search_run_id`, and reports a strict source-cost guardrail: results are
101
+ only returned when source cost is below $0.01 per qualified company. A request
102
+ is a ceiling, not a promise: it can return fewer results when public evidence
103
+ cannot verify both a date and company identity.
104
+
88
105
  Connection commands are limited to access infrastructure:
89
106
 
90
107
  ```bash
package/dist/bin.js CHANGED
@@ -11,15 +11,17 @@ var CONFIG_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".sig
11
11
  var CONFIG_FILE = (0, import_node_path.join)(CONFIG_DIR, "config.json");
12
12
  var HELP = `Signaliz CLI
13
13
 
14
- Four product commands:
14
+ Five product commands:
15
15
  signaliz find-email --company-domain example.com --full-name "Jane Doe" [--skip-cache]
16
16
  signaliz verify-email [jane@example.com] [--verification-run-id run_...] [--no-wait] [--max-wait-ms 600000]
17
- signaliz company-signals --domain example.com [--research-prompt "..."] [--search-mode fast|balanced|coverage] [--no-summary] [--max-wait-ms 120000]
17
+ signaliz company-signals --domain example.com [--research-prompt "..."] [--search-mode fast|balanced|coverage] [--include-candidate-evidence] [--no-summary] [--max-wait-ms 120000]
18
18
  signaliz company-signals --signal-run-id run_... Read a completed run without duplicate spend
19
19
  signaliz signal-to-copy --company-domain example.com --person-name "Jane Doe" \\
20
20
  --title "VP Sales" --campaign-offer "pipeline intelligence" [--signal-run-id run_...] \\
21
21
  [--skip-cache] [--deep-search] [--max-wait-ms 120000]
22
22
  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]
24
+ signaliz signals --signal-search-run-id run_... Resume an existing company-attributed signal search
23
25
 
24
26
  Access and connection:
25
27
  signaliz auth login
@@ -42,7 +44,7 @@ Common flags:
42
44
  --idempotency-key <key> Reuse after an ambiguous response to recover the same request or job
43
45
  --dry-run Return a no-spend plan without provider calls, jobs, or writes
44
46
 
45
- Company Signals and Signal to Copy always wait for complete results or terminal errors.
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.
46
48
  `;
47
49
  function loadConfig() {
48
50
  if (!(0, import_node_fs.existsSync)(CONFIG_FILE)) return {};
@@ -170,12 +172,126 @@ function resolveApiKey() {
170
172
  if (!key) fail("No API key configured. Run: signaliz auth login");
171
173
  return key;
172
174
  }
173
- function createClient() {
175
+ function resolveBaseUrl() {
174
176
  const config = loadConfig();
177
+ return (flag("base-url") || process.env.SIGNALIZ_BASE_URL || process.env.SIGNALIZ_API_URL || config.baseUrl || "https://api.signaliz.com/functions/v1").replace(/\/$/, "");
178
+ }
179
+ function createClient() {
175
180
  return new import_sdk.Signaliz({
176
181
  apiKey: resolveApiKey(),
177
- baseUrl: flag("base-url") || process.env.SIGNALIZ_BASE_URL || process.env.SIGNALIZ_API_URL || config.baseUrl
182
+ baseUrl: resolveBaseUrl()
183
+ });
184
+ }
185
+ function numberValue(value) {
186
+ const number = Number(value);
187
+ return Number.isFinite(number) ? number : void 0;
188
+ }
189
+ function stringValue(value) {
190
+ return typeof value === "string" ? value.trim() : "";
191
+ }
192
+ function signalsEverythingErrorType(status) {
193
+ if (status === 400 || status === 422) return "validation";
194
+ if (status === 401) return "auth_expired";
195
+ if (status === 402) return "insufficient_credits";
196
+ if (status === 404) return "not_found";
197
+ if (status === 429) return "rate_limited";
198
+ if (status === 504) return "timeout";
199
+ return status >= 500 ? "provider_error" : "unknown";
200
+ }
201
+ function normalizeSignalsEverythingResult(request, value) {
202
+ const rawSignals = Array.isArray(value.signals) ? value.signals : [];
203
+ const signals2 = rawSignals.map((rawSignal) => {
204
+ const signal = record(rawSignal);
205
+ const company = record(signal.company);
206
+ const name = stringValue(company.name || signal.company_name);
207
+ const domain = stringValue(company.domain || signal.company_domain);
208
+ if (!name || !domain) {
209
+ throw new Error("Signals Everything response violated the company attribution contract.");
210
+ }
211
+ const evidence = Array.isArray(signal.evidence) ? signal.evidence.map((rawEvidence) => {
212
+ const item = record(rawEvidence);
213
+ return {
214
+ url: stringValue(item.url),
215
+ publishedAt: stringValue(item.published_at || item.publishedAt),
216
+ source: stringValue(item.source)
217
+ };
218
+ }).filter((item) => Boolean(item.url)) : [];
219
+ return {
220
+ company: { name, domain },
221
+ title: stringValue(signal.title || signal.summary || signal.content),
222
+ sourceUrl: stringValue(signal.source_url || signal.url) || null,
223
+ evidence
224
+ };
225
+ });
226
+ const statusValue = stringValue(value.status).toLowerCase();
227
+ const statuses = /* @__PURE__ */ new Set([
228
+ "planned",
229
+ "queued",
230
+ "processing",
231
+ "completed",
232
+ "completed_with_warnings",
233
+ "failed"
234
+ ]);
235
+ const status = statuses.has(statusValue) ? statusValue : signals2.length > 0 ? "completed" : "failed";
236
+ const guardrail = record(value.cost_guardrail);
237
+ const maxCost = numberValue(guardrail.max_cost_per_qualified_signal_usd);
238
+ const warnings = Array.isArray(value.warnings) ? value.warnings.filter((warning) => typeof warning === "string") : void 0;
239
+ return {
240
+ success: value.success !== false,
241
+ status,
242
+ signalSearchRunId: stringValue(value.signal_search_run_id || request.signalSearchRunId),
243
+ query: stringValue(value.query || request.query),
244
+ requestedCount: numberValue(value.requested_count) ?? request.limit ?? 100,
245
+ nextPollAfterSeconds: numberValue(value.next_poll_after_seconds),
246
+ signals: signals2,
247
+ signalCount: signals2.length,
248
+ ...maxCost === void 0 ? {} : {
249
+ costGuardrail: {
250
+ maxCostPerQualifiedSignalUsd: maxCost,
251
+ 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
252
+ }
253
+ },
254
+ ...warnings?.length ? { warnings } : {},
255
+ raw: value
256
+ };
257
+ }
258
+ async function discoverSignalsEverything(request) {
259
+ const body = request.signalSearchRunId ? {
260
+ signal_search_run_id: request.signalSearchRunId,
261
+ ...request.dryRun === void 0 ? {} : { dry_run: request.dryRun },
262
+ ...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {}
263
+ } : {
264
+ query: request.query,
265
+ limit: request.limit ?? 100,
266
+ ...request.sources ? { sources: request.sources } : {},
267
+ ...request.lookbackDays === void 0 ? {} : { lookback_days: request.lookbackDays },
268
+ ...request.dryRun === void 0 ? {} : { dry_run: request.dryRun },
269
+ ...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {}
270
+ };
271
+ const response = await fetch(`${resolveBaseUrl()}/api/v1/signals`, {
272
+ method: "POST",
273
+ headers: {
274
+ "Content-Type": "application/json",
275
+ Authorization: `Bearer ${resolveApiKey()}`,
276
+ ...request.idempotencyKey ? { "Idempotency-Key": request.idempotencyKey } : {}
277
+ },
278
+ body: JSON.stringify(body)
178
279
  });
280
+ const responseValue = await response.json().catch(() => ({}));
281
+ const responseBody = record(responseValue);
282
+ if (!response.ok) {
283
+ const error = record(responseBody.error);
284
+ const code = stringValue(error.code || responseBody.error_code) || `HTTP_${response.status}`;
285
+ const message = stringValue(error.message || responseBody.message || responseBody.error) || `Signals Everything request failed with status ${response.status}`;
286
+ throw new import_sdk.SignalizError({
287
+ code,
288
+ message,
289
+ errorType: signalsEverythingErrorType(response.status),
290
+ retryAfter: numberValue(responseBody.retry_after_seconds || responseBody.retry_after),
291
+ details: responseBody
292
+ });
293
+ }
294
+ return normalizeSignalsEverythingResult(request, responseBody);
179
295
  }
180
296
  function output(value, human) {
181
297
  if (hasFlag("json") || hasFlag("jsonl")) {
@@ -393,6 +509,7 @@ async function companySignals() {
393
509
  enableOutreachIntelligence: row.enableOutreachIntelligence ?? row.enable_outreach_intelligence,
394
510
  enablePredictiveIntelligence: row.enablePredictiveIntelligence ?? row.enable_predictive_intelligence,
395
511
  skipCache: row.skipCache ?? row.skip_cache ?? hasFlag("skip-cache"),
512
+ includeCandidateEvidence: row.includeCandidateEvidence ?? row.include_candidate_evidence ?? hasFlag("include-candidate-evidence"),
396
513
  maxWaitMs: row.maxWaitMs || row.max_wait_ms || numberFlag("max-wait-ms")
397
514
  };
398
515
  });
@@ -424,6 +541,7 @@ async function companySignals() {
424
541
  enableOutreachIntelligence: hasFlag("outreach-intelligence") || void 0,
425
542
  enablePredictiveIntelligence: hasFlag("predictive-intelligence") || void 0,
426
543
  skipCache: hasFlag("skip-cache"),
544
+ includeCandidateEvidence: hasFlag("include-candidate-evidence"),
427
545
  maxWaitMs: numberFlag("max-wait-ms"),
428
546
  idempotencyKey: flag("idempotency-key")
429
547
  };
@@ -443,6 +561,103 @@ Signals: ${result.signalCount}
443
561
  }
444
562
  });
445
563
  }
564
+ function signalQueryFromArgs(args) {
565
+ const explicitQuery = flag("query");
566
+ if (explicitQuery) return explicitQuery;
567
+ const valueFlags = /* @__PURE__ */ new Set([
568
+ "--limit",
569
+ "--sources",
570
+ "--lookback-days",
571
+ "--signal-search-run-id",
572
+ "--idempotency-key",
573
+ "--max-wait-ms",
574
+ "--poll-interval-ms",
575
+ "--base-url"
576
+ ]);
577
+ for (let index = 0; index < args.length; index += 1) {
578
+ const value = args[index];
579
+ if (value.startsWith("--")) {
580
+ if (!value.includes("=") && valueFlags.has(value)) index += 1;
581
+ continue;
582
+ }
583
+ return value;
584
+ }
585
+ return void 0;
586
+ }
587
+ function signalDiscoveryIsPending(status) {
588
+ return ["queued", "pending", "processing", "running"].includes(status.toLowerCase());
589
+ }
590
+ function sleep(milliseconds) {
591
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
592
+ }
593
+ async function signals(args) {
594
+ if (flag("input")) fail("Signals Everything accepts one natural-language query at a time; do not pass --input.");
595
+ const signalSearchRunId = flag("signal-search-run-id");
596
+ const query = signalQueryFromArgs(args);
597
+ if (!query && !signalSearchRunId) {
598
+ fail('Usage: signaliz signals "companies that just got funded" [--limit 100] OR signaliz signals --signal-search-run-id run_...');
599
+ }
600
+ const sourceValues = csvFlag("sources");
601
+ if (sourceValues?.some((source) => !["web", "news"].includes(source))) {
602
+ fail("--sources must contain only web or news");
603
+ }
604
+ const request = {
605
+ query,
606
+ signalSearchRunId,
607
+ limit: numberFlag("limit"),
608
+ sources: sourceValues,
609
+ lookbackDays: numberFlag("lookback-days"),
610
+ idempotencyKey: flag("idempotency-key"),
611
+ dryRun: hasFlag("dry-run")
612
+ };
613
+ let result = await discoverSignalsEverything(request);
614
+ if (hasFlag("wait") && !request.dryRun) {
615
+ const maxWaitMs = numberFlag("max-wait-ms") ?? 15 * 6e4;
616
+ const pollIntervalMs = numberFlag("poll-interval-ms");
617
+ const startedAt = Date.now();
618
+ while (signalDiscoveryIsPending(result.status) && result.signalSearchRunId && Date.now() - startedAt < maxWaitMs) {
619
+ await sleep(Math.max(250, pollIntervalMs ?? (result.nextPollAfterSeconds || 5) * 1e3));
620
+ result = await discoverSignalsEverything({ signalSearchRunId: result.signalSearchRunId });
621
+ }
622
+ }
623
+ output(result, () => {
624
+ if (result.status === "planned") {
625
+ process.stdout.write(`Signals Everything plan: ${result.requestedCount} distinct companies. No source acquisition was launched.
626
+ `);
627
+ return;
628
+ }
629
+ if (signalDiscoveryIsPending(result.status)) {
630
+ process.stdout.write(`Signals Everything ${result.status}: ${result.signalSearchRunId}
631
+ `);
632
+ process.stdout.write(`Resume with --signal-search-run-id ${result.signalSearchRunId}${hasFlag("wait") ? " after the current wait window" : ""}.
633
+ `);
634
+ return;
635
+ }
636
+ if (!result.success || result.status === "failed") {
637
+ process.stdout.write(`${String(result.raw.error || result.raw.message || "Signals Everything failed.")}
638
+ `);
639
+ return;
640
+ }
641
+ process.stdout.write(`Qualified companies: ${result.signalCount}
642
+ `);
643
+ const guardrail = result.costGuardrail;
644
+ if (guardrail) {
645
+ process.stdout.write(
646
+ `Source cost per qualified company: ${guardrail.costPerQualifiedSignalUsd ?? "n/a"} (must be < $${guardrail.maxCostPerQualifiedSignalUsd})
647
+ `
648
+ );
649
+ }
650
+ for (const signal of result.signals.slice(0, 20)) {
651
+ process.stdout.write(`- ${signal.company.name} (${signal.company.domain}) \u2014 ${signal.title}
652
+ ${signal.sourceUrl || signal.evidence[0]?.url || ""}
653
+ `);
654
+ }
655
+ if (result.signalCount > 20) process.stdout.write(`Showing 20 of ${result.signalCount}; use --json for the full result.
656
+ `);
657
+ for (const warning of result.warnings || []) process.stdout.write(`Warning: ${warning}
658
+ `);
659
+ });
660
+ }
446
661
  async function signalToCopy() {
447
662
  rejectAsyncSignalFlags();
448
663
  const rows = readBatchInput();
@@ -569,6 +784,8 @@ async function main() {
569
784
  return verifyEmail(args);
570
785
  case "company-signals":
571
786
  return companySignals();
787
+ case "signals":
788
+ return signals(args);
572
789
  case "signal-to-copy":
573
790
  return signalToCopy();
574
791
  case "health":
@@ -604,5 +821,12 @@ main().catch((error) => {
604
821
  process.exitCode = 1;
605
822
  return;
606
823
  }
824
+ if (error instanceof import_sdk.SignalizError && typeof error.details?.signal_run_id === "string") {
825
+ const signalRunId = error.details.signal_run_id;
826
+ const command = process.argv[2] === "signal-to-copy" ? "signal-to-copy" : "company-signals";
827
+ fail(`${message}
828
+ Recoverable signal run: ${signalRunId}
829
+ Retry the same signaliz ${command} command with --signal-run-id ${signalRunId}, or use --json for structured recovery metadata.`);
830
+ }
607
831
  fail(message);
608
832
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@signaliz/cli",
3
- "version": "1.0.48",
4
- "description": "Signaliz CLI for Find Email, Verify Email, Company Signal Enrichment, and Signal to Copy AI.",
3
+ "version": "1.0.50",
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"
7
7
  },
@@ -21,14 +21,15 @@
21
21
  "email-finder",
22
22
  "email-verification",
23
23
  "company-signals",
24
- "signal-to-copy"
24
+ "signal-to-copy",
25
+ "signals-everything"
25
26
  ],
26
27
  "license": "MIT",
27
28
  "engines": {
28
29
  "node": ">=18"
29
30
  },
30
31
  "dependencies": {
31
- "@signaliz/sdk": "^1.0.50"
32
+ "@signaliz/sdk": "^1.0.53"
32
33
  },
33
34
  "devDependencies": {
34
35
  "tsup": "^8.0.0",