@signaliz/cli 1.0.49 → 1.0.51

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 +18 -3
  2. package/dist/bin.js +239 -4
  3. package/package.json +4 -3
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,7 +82,7 @@ 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
@@ -87,6 +93,15 @@ copy row explicitly requests fresh or deep research, the CLI preserves that
87
93
  behavior with synchronous chunks of at most 25. Signal to Copy also shares
88
94
  company research across recipients.
89
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
+
90
105
  Connection commands are limited to access infrastructure:
91
106
 
92
107
  ```bash
package/dist/bin.js CHANGED
@@ -11,7 +11,7 @@ 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
17
  signaliz company-signals --domain example.com [--research-prompt "..."] [--search-mode fast|balanced|coverage] [--include-candidate-evidence] [--no-summary] [--max-wait-ms 120000]
@@ -20,6 +20,8 @@ Four product commands:
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,13 +172,137 @@ 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()
178
183
  });
179
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
+ const eventDate = stringValue(signal.event_date || signal.date) || evidence[0]?.publishedAt || "";
220
+ const sourceUrl = stringValue(signal.source_url || signal.url) || evidence[0]?.url || null;
221
+ if (!eventDate || !sourceUrl || !evidence.some((item) => Boolean(item.publishedAt))) {
222
+ throw new Error("Signals Everything response violated the dated-evidence and source-URL contract.");
223
+ }
224
+ return {
225
+ company: { name, domain },
226
+ title: stringValue(signal.title || signal.summary || signal.content),
227
+ eventDate,
228
+ sourceUrl,
229
+ evidence
230
+ };
231
+ });
232
+ const statusValue = stringValue(value.status).toLowerCase();
233
+ const statuses = /* @__PURE__ */ new Set([
234
+ "planned",
235
+ "queued",
236
+ "processing",
237
+ "completed",
238
+ "completed_with_warnings",
239
+ "failed"
240
+ ]);
241
+ const status = statuses.has(statusValue) ? statusValue : signals2.length > 0 ? "completed" : "failed";
242
+ const guardrail = record(value.cost_guardrail);
243
+ const maxCost = numberValue(guardrail.max_cost_per_qualified_signal_usd);
244
+ const warnings = Array.isArray(value.warnings) ? value.warnings.filter((warning) => typeof warning === "string") : void 0;
245
+ return {
246
+ success: value.success !== false,
247
+ status,
248
+ signalSearchRunId: stringValue(value.signal_search_run_id || request.signalSearchRunId),
249
+ query: stringValue(value.query || request.query),
250
+ requestedCount: numberValue(value.requested_count) ?? request.limit ?? 100,
251
+ nextPollAfterSeconds: numberValue(value.next_poll_after_seconds),
252
+ signals: signals2,
253
+ signalCount: signals2.length,
254
+ ...numberValue(value.available_company_signal_count) === void 0 ? {} : {
255
+ availableCompanySignalCount: numberValue(value.available_company_signal_count)
256
+ },
257
+ ...maxCost === void 0 ? {} : {
258
+ costGuardrail: {
259
+ maxCostPerQualifiedSignalUsd: maxCost,
260
+ 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
261
+ }
262
+ },
263
+ ...warnings?.length ? { warnings } : {},
264
+ raw: value
265
+ };
266
+ }
267
+ async function discoverSignalsEverything(request) {
268
+ const body = request.signalSearchRunId ? {
269
+ signal_search_run_id: request.signalSearchRunId,
270
+ ...request.limit === void 0 ? {} : { limit: request.limit },
271
+ ...request.dryRun === void 0 ? {} : { dry_run: request.dryRun },
272
+ ...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {}
273
+ } : {
274
+ query: request.query,
275
+ limit: request.limit ?? 100,
276
+ ...request.sources ? { sources: request.sources } : {},
277
+ ...request.lookbackDays === void 0 ? {} : { lookback_days: request.lookbackDays },
278
+ ...request.dryRun === void 0 ? {} : { dry_run: request.dryRun },
279
+ ...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {}
280
+ };
281
+ const response = await fetch(`${resolveBaseUrl()}/api/v1/signals`, {
282
+ method: "POST",
283
+ headers: {
284
+ "Content-Type": "application/json",
285
+ Authorization: `Bearer ${resolveApiKey()}`,
286
+ ...request.idempotencyKey ? { "Idempotency-Key": request.idempotencyKey } : {}
287
+ },
288
+ body: JSON.stringify(body)
289
+ });
290
+ const responseValue = await response.json().catch(() => ({}));
291
+ const responseBody = record(responseValue);
292
+ if (!response.ok) {
293
+ const error = record(responseBody.error);
294
+ const code = stringValue(error.code || responseBody.error_code) || `HTTP_${response.status}`;
295
+ const message = stringValue(error.message || responseBody.message || responseBody.error) || `Signals Everything request failed with status ${response.status}`;
296
+ throw new import_sdk.SignalizError({
297
+ code,
298
+ message,
299
+ errorType: signalsEverythingErrorType(response.status),
300
+ retryAfter: numberValue(responseBody.retry_after_seconds || responseBody.retry_after),
301
+ details: responseBody
302
+ });
303
+ }
304
+ return normalizeSignalsEverythingResult(request, responseBody);
305
+ }
180
306
  function output(value, human) {
181
307
  if (hasFlag("json") || hasFlag("jsonl")) {
182
308
  process.stdout.write(`${stringifyMachineJson(value, hasFlag("json") ? 2 : void 0)}
@@ -445,6 +571,113 @@ Signals: ${result.signalCount}
445
571
  }
446
572
  });
447
573
  }
574
+ function signalQueryFromArgs(args) {
575
+ const explicitQuery = flag("query");
576
+ if (explicitQuery) return explicitQuery;
577
+ const valueFlags = /* @__PURE__ */ new Set([
578
+ "--limit",
579
+ "--sources",
580
+ "--lookback-days",
581
+ "--signal-search-run-id",
582
+ "--idempotency-key",
583
+ "--max-wait-ms",
584
+ "--poll-interval-ms",
585
+ "--base-url"
586
+ ]);
587
+ for (let index = 0; index < args.length; index += 1) {
588
+ const value = args[index];
589
+ if (value.startsWith("--")) {
590
+ if (!value.includes("=") && valueFlags.has(value)) index += 1;
591
+ continue;
592
+ }
593
+ return value;
594
+ }
595
+ return void 0;
596
+ }
597
+ function signalDiscoveryIsPending(status) {
598
+ return ["queued", "pending", "processing", "running"].includes(status.toLowerCase());
599
+ }
600
+ function sleep(milliseconds) {
601
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
602
+ }
603
+ async function signals(args) {
604
+ if (flag("input")) fail("Signals Everything accepts one natural-language query at a time; do not pass --input.");
605
+ const signalSearchRunId = flag("signal-search-run-id");
606
+ const query = signalQueryFromArgs(args);
607
+ if (!query && !signalSearchRunId) {
608
+ fail('Usage: signaliz signals "companies that just got funded" [--limit 100] OR signaliz signals --signal-search-run-id run_...');
609
+ }
610
+ const sourceValues = csvFlag("sources");
611
+ if (sourceValues?.some((source) => !["web", "news"].includes(source))) {
612
+ fail("--sources must contain only web or news");
613
+ }
614
+ const request = {
615
+ query,
616
+ signalSearchRunId,
617
+ limit: numberFlag("limit"),
618
+ sources: sourceValues,
619
+ lookbackDays: numberFlag("lookback-days"),
620
+ idempotencyKey: flag("idempotency-key"),
621
+ dryRun: hasFlag("dry-run")
622
+ };
623
+ let result = await discoverSignalsEverything(request);
624
+ if (hasFlag("wait") && !request.dryRun) {
625
+ const maxWaitMs = numberFlag("max-wait-ms") ?? 15 * 6e4;
626
+ const pollIntervalMs = numberFlag("poll-interval-ms");
627
+ const startedAt = Date.now();
628
+ while (signalDiscoveryIsPending(result.status) && result.signalSearchRunId && Date.now() - startedAt < maxWaitMs) {
629
+ await sleep(Math.max(250, pollIntervalMs ?? (result.nextPollAfterSeconds || 5) * 1e3));
630
+ result = await discoverSignalsEverything({
631
+ signalSearchRunId: result.signalSearchRunId,
632
+ limit: request.limit
633
+ });
634
+ }
635
+ }
636
+ output(result, () => {
637
+ if (result.status === "planned") {
638
+ process.stdout.write(`Signals Everything plan: ${result.requestedCount} distinct companies. No source acquisition was launched.
639
+ `);
640
+ return;
641
+ }
642
+ if (signalDiscoveryIsPending(result.status)) {
643
+ process.stdout.write(`Signals Everything ${result.status}: ${result.signalSearchRunId}
644
+ `);
645
+ process.stdout.write(`Resume with --signal-search-run-id ${result.signalSearchRunId}${hasFlag("wait") ? " after the current wait window" : ""}.
646
+ `);
647
+ return;
648
+ }
649
+ if (!result.success || result.status === "failed") {
650
+ process.stdout.write(`${String(result.raw.error || result.raw.message || "Signals Everything failed.")}
651
+ `);
652
+ return;
653
+ }
654
+ const available = result.availableCompanySignalCount ?? result.signalCount;
655
+ process.stdout.write(`Qualified companies returned: ${result.signalCount}
656
+ `);
657
+ if (available > result.signalCount) {
658
+ process.stdout.write(
659
+ `Retained cohort: ${available}. Expand without new acquisition: signaliz signals --signal-search-run-id ${result.signalSearchRunId} --limit ${available}
660
+ `
661
+ );
662
+ }
663
+ const guardrail = result.costGuardrail;
664
+ if (guardrail) {
665
+ process.stdout.write(
666
+ `Source cost per qualified company: ${guardrail.costPerQualifiedSignalUsd ?? "n/a"} (must be < $${guardrail.maxCostPerQualifiedSignalUsd})
667
+ `
668
+ );
669
+ }
670
+ for (const signal of result.signals.slice(0, 20)) {
671
+ process.stdout.write(`- ${signal.company.name} (${signal.company.domain}) \u2014 ${signal.eventDate} \u2014 ${signal.title}
672
+ ${signal.sourceUrl}
673
+ `);
674
+ }
675
+ if (result.signalCount > 20) process.stdout.write(`Showing 20 of ${result.signalCount}; use --json for the full result.
676
+ `);
677
+ for (const warning of result.warnings || []) process.stdout.write(`Warning: ${warning}
678
+ `);
679
+ });
680
+ }
448
681
  async function signalToCopy() {
449
682
  rejectAsyncSignalFlags();
450
683
  const rows = readBatchInput();
@@ -571,6 +804,8 @@ async function main() {
571
804
  return verifyEmail(args);
572
805
  case "company-signals":
573
806
  return companySignals();
807
+ case "signals":
808
+ return signals(args);
574
809
  case "signal-to-copy":
575
810
  return signalToCopy();
576
811
  case "health":
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@signaliz/cli",
3
- "version": "1.0.49",
4
- "description": "Signaliz CLI for Find Email, Verify Email, Company Signal Enrichment, and Signal to Copy AI.",
3
+ "version": "1.0.51",
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,7 +21,8 @@
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": {