@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.
- package/README.md +18 -3
- package/dist/bin.js +239 -4
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @signaliz/cli
|
|
2
2
|
|
|
3
|
-
The Signaliz CLI exposes
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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:
|
|
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.
|
|
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": {
|