@agent-pattern-labs/leads-rig 0.1.3 → 0.1.4
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/.opencode/skills/lead-harness.md +2 -2
- package/.opencode/skills/public-leads.md +2 -2
- package/.pi/iso-route.md +15 -0
- package/.pi/prompts/lead-harness.md +2 -2
- package/.pi/prompts/public-leads.md +2 -2
- package/.pi/settings.json +9 -0
- package/README.md +20 -5
- package/bin/create-leads-harness.mjs +2 -2
- package/config/profile.example.yml +8 -5
- package/docs/CONSTRUCTION.md +1 -1
- package/docs/SETUP.md +23 -5
- package/iso/commands/lead-harness.md +2 -2
- package/iso/commands/public-leads.md +2 -2
- package/lib/leadharness-crawler.mjs +16 -12
- package/lib/leadharness-ingest.mjs +23 -6
- package/lib/leadharness-leads.mjs +216 -4
- package/modes/_shared.md +2 -1
- package/modes/ingest.md +6 -3
- package/modes/pipeline.md +1 -1
- package/modes/setup.md +3 -2
- package/package.json +2 -2
- package/scripts/ingest.mjs +2 -2
- package/scripts/pipeline.mjs +2 -2
|
@@ -41,10 +41,10 @@ Available commands:
|
|
|
41
41
|
|
|
42
42
|
Local commands:
|
|
43
43
|
npx public-leads crawl --input data/domains.tsv --out data/lead-results.json
|
|
44
|
-
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
44
|
+
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
45
45
|
npx public-leads validate --input data/lead-results.json
|
|
46
46
|
npx public-leads manifest --input data/lead-results.json
|
|
47
|
-
npx public-leads ingest --input data/lead-results.json
|
|
47
|
+
npx public-leads ingest --input data/lead-results.json
|
|
48
48
|
npx public-leads verify
|
|
49
49
|
```
|
|
50
50
|
|
|
@@ -41,10 +41,10 @@ Available commands:
|
|
|
41
41
|
|
|
42
42
|
Local commands:
|
|
43
43
|
npx public-leads crawl --input data/domains.tsv --out data/lead-results.json
|
|
44
|
-
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
44
|
+
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
45
45
|
npx public-leads validate --input data/lead-results.json
|
|
46
46
|
npx public-leads manifest --input data/lead-results.json
|
|
47
|
-
npx public-leads ingest --input data/lead-results.json
|
|
47
|
+
npx public-leads ingest --input data/lead-results.json
|
|
48
48
|
npx public-leads verify
|
|
49
49
|
```
|
|
50
50
|
|
package/.pi/iso-route.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# iso-route — Pi notes
|
|
2
|
+
|
|
3
|
+
Pi reads `.pi/settings.json` for the default provider/model and model cycling. It does not have native role-specific subagent binding, so role entries below are advisory for manual model switching or extension/package workflows.
|
|
4
|
+
|
|
5
|
+
## Default
|
|
6
|
+
|
|
7
|
+
- **anthropic / claude-sonnet-4-6**
|
|
8
|
+
|
|
9
|
+
## Roles
|
|
10
|
+
|
|
11
|
+
| Role | Provider | Model | Thinking | Fallback |
|
|
12
|
+
| ---- | -------- | ----- | -------- | -------- |
|
|
13
|
+
| `fast` | anthropic | `claude-haiku-4-5` | - | - |
|
|
14
|
+
| `quality` | anthropic | `claude-opus-4-7` | high | - |
|
|
15
|
+
| `minimal` | anthropic | `claude-haiku-4-5` | - | - |
|
|
@@ -39,10 +39,10 @@ Available commands:
|
|
|
39
39
|
|
|
40
40
|
Local commands:
|
|
41
41
|
npx public-leads crawl --input data/domains.tsv --out data/lead-results.json
|
|
42
|
-
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
42
|
+
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
43
43
|
npx public-leads validate --input data/lead-results.json
|
|
44
44
|
npx public-leads manifest --input data/lead-results.json
|
|
45
|
-
npx public-leads ingest --input data/lead-results.json
|
|
45
|
+
npx public-leads ingest --input data/lead-results.json
|
|
46
46
|
npx public-leads verify
|
|
47
47
|
```
|
|
48
48
|
|
|
@@ -39,10 +39,10 @@ Available commands:
|
|
|
39
39
|
|
|
40
40
|
Local commands:
|
|
41
41
|
npx public-leads crawl --input data/domains.tsv --out data/lead-results.json
|
|
42
|
-
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
42
|
+
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
43
43
|
npx public-leads validate --input data/lead-results.json
|
|
44
44
|
npx public-leads manifest --input data/lead-results.json
|
|
45
|
-
npx public-leads ingest --input data/lead-results.json
|
|
45
|
+
npx public-leads ingest --input data/lead-results.json
|
|
46
46
|
npx public-leads verify
|
|
47
47
|
```
|
|
48
48
|
|
package/README.md
CHANGED
|
@@ -20,9 +20,9 @@ npm run smoke:iso
|
|
|
20
20
|
|
|
21
21
|
npx -p @agent-pattern-labs/leads-rig create-public-leads-harness my-lead-project
|
|
22
22
|
npx public-leads crawl --domains example.com --out data/lead-results.json
|
|
23
|
-
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
23
|
+
PUBLIC_LEADS_API=https://cold-agent-leads.example.com npx public-leads pipeline --input data/domains.tsv --ingest
|
|
24
24
|
npx public-leads validate --input examples/sample-leads.json
|
|
25
|
-
npx public-leads ingest --input examples/sample-leads.json --dry-run --out output/sample-ingest.json
|
|
25
|
+
PUBLIC_LEADS_API=https://cold-agent-leads.example.com npx public-leads ingest --input examples/sample-leads.json --dry-run --out output/sample-ingest.json
|
|
26
26
|
batch/batch-runner.sh --help
|
|
27
27
|
```
|
|
28
28
|
|
|
@@ -32,12 +32,27 @@ The default reference client posts to the Cold Agent Leads ingest API:
|
|
|
32
32
|
|
|
33
33
|
```http
|
|
34
34
|
POST /api/lead-ingests
|
|
35
|
-
Authorization: Bearer $
|
|
35
|
+
Authorization: Bearer $PUBLIC_LEADS_API_TOKEN
|
|
36
36
|
X-Admin-Email: admin@example.com
|
|
37
37
|
Content-Type: application/json
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
For upstream ingest, set `PUBLIC_LEADS_API`, `PUBLIC_LEADS_API_TOKEN`, and `PUBLIC_LEADS_OPERATOR_EMAIL`; no local Postgres or local Cold Agent Leads server is required. Endpoint details are configurable through environment variables, `config/profile.yml`, or CLI flags. `--target-project /path/to/cold-agent-leads` remains available only as compatibility for reading a local app `.env`. Payloads contain `jobId`, `domains`, `leads`, `results`, and `errors`, with the schema defined in `templates/lead-schema.json`.
|
|
41
|
+
|
|
42
|
+
Run from a Cold Agent Leads checkout without installing local package dependencies:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cd /Users/charlie/Razroo/cold-agent-leads
|
|
46
|
+
export PUBLIC_LEADS_API=https://cold-agent-leads.example.com
|
|
47
|
+
export PUBLIC_LEADS_API_TOKEN=...
|
|
48
|
+
export PUBLIC_LEADS_OPERATOR_EMAIL=admin@example.com
|
|
49
|
+
npx -p @agent-pattern-labs/leads-rig public-leads pipeline \
|
|
50
|
+
--domains example.com \
|
|
51
|
+
--ingest \
|
|
52
|
+
--out data/lead-results.json \
|
|
53
|
+
--manifest data/lead-manifest.json \
|
|
54
|
+
--ingest-out data/ingest-response.json
|
|
55
|
+
```
|
|
41
56
|
|
|
42
57
|
Legacy aliases `lead-harness` and `create-leads-harness` remain available for compatibility.
|
|
43
58
|
|
|
@@ -53,7 +68,7 @@ gh run watch
|
|
|
53
68
|
You can also publish by creating a GitHub release for the package version:
|
|
54
69
|
|
|
55
70
|
```bash
|
|
56
|
-
gh release create v0.1.
|
|
71
|
+
gh release create v0.1.4 --title v0.1.4 --generate-notes
|
|
57
72
|
```
|
|
58
73
|
|
|
59
74
|
The package version must not already exist on npm.
|
|
@@ -51,7 +51,7 @@ write('package.json', JSON.stringify({
|
|
|
51
51
|
'update-harness': 'npm install @agent-pattern-labs/leads-rig@latest && public-leads sync',
|
|
52
52
|
},
|
|
53
53
|
dependencies: {
|
|
54
|
-
'@agent-pattern-labs/leads-rig': '^0.1.
|
|
54
|
+
'@agent-pattern-labs/leads-rig': '^0.1.4',
|
|
55
55
|
},
|
|
56
56
|
engines: {
|
|
57
57
|
node: '>=20.6.0',
|
|
@@ -143,7 +143,7 @@ npm run verify
|
|
|
143
143
|
opencode
|
|
144
144
|
\`\`\`
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
Set \`PUBLIC_LEADS_API\`, \`PUBLIC_LEADS_API_TOKEN\`, and \`PUBLIC_LEADS_OPERATOR_EMAIL\` for upstream ingest. Add domains to \`data/domains.tsv\` or \`data/pipeline.md\`.
|
|
147
147
|
`);
|
|
148
148
|
|
|
149
149
|
console.log('\nNext: npm install, then edit config/profile.yml and data/domains.tsv.\n');
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
# Prefer environment variables for secrets in shared terminals.
|
|
3
3
|
|
|
4
4
|
api:
|
|
5
|
-
# Cold Agent Leads
|
|
6
|
-
#
|
|
7
|
-
base_url: "
|
|
5
|
+
# Set the deployed Cold Agent Leads API base URL through PUBLIC_LEADS_API,
|
|
6
|
+
# or put it here for a non-shared local profile. Example:
|
|
7
|
+
# base_url: "https://cold-agent-leads.example.com"
|
|
8
|
+
base_url: ""
|
|
8
9
|
ingest_path: "/api/lead-ingests"
|
|
10
|
+
# Optional compatibility only: set when reading ADMIN_API_TOKEN/ADMIN_EMAILS
|
|
11
|
+
# from a local cold-agent-leads checkout. Not needed for upstream ingest.
|
|
9
12
|
target_project: ""
|
|
10
|
-
operator_email: "
|
|
13
|
+
operator_email: ""
|
|
11
14
|
operator_email_header: "X-Admin-Email"
|
|
12
15
|
auth_header: "Authorization"
|
|
13
16
|
auth_scheme: "Bearer"
|
|
14
|
-
auth_token_env: "
|
|
17
|
+
auth_token_env: "PUBLIC_LEADS_API_TOKEN"
|
|
15
18
|
|
|
16
19
|
crawl:
|
|
17
20
|
max_pages: 10
|
package/docs/CONSTRUCTION.md
CHANGED
|
@@ -37,4 +37,4 @@
|
|
|
37
37
|
6. Run `npm audit`.
|
|
38
38
|
7. Run `npm pack` and confirm generated runtime surfaces are included.
|
|
39
39
|
8. Scaffold a clean consumer project and test install-time sync from the packed package or source tree.
|
|
40
|
-
9. Publish through GitHub Actions with `gh workflow run publish.yml --ref main`, or create a published release with `gh release create v0.1.
|
|
40
|
+
9. Publish through GitHub Actions with `gh workflow run publish.yml --ref main`, or create a published release with `gh release create v0.1.4 --title v0.1.4 --generate-notes`.
|
package/docs/SETUP.md
CHANGED
|
@@ -32,7 +32,9 @@ Then edit:
|
|
|
32
32
|
Use environment variables for secrets:
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
export
|
|
35
|
+
export PUBLIC_LEADS_API=https://cold-agent-leads.example.com
|
|
36
|
+
export PUBLIC_LEADS_API_TOKEN=...
|
|
37
|
+
export PUBLIC_LEADS_OPERATOR_EMAIL=admin@example.com
|
|
36
38
|
```
|
|
37
39
|
|
|
38
40
|
## Validate A Payload
|
|
@@ -51,27 +53,43 @@ npx public-leads pipeline --input data/domains.tsv --out data/lead-results.json
|
|
|
51
53
|
## Dry-Run Ingest
|
|
52
54
|
|
|
53
55
|
```bash
|
|
54
|
-
npx public-leads ingest --input examples/sample-leads.json --dry-run --out output/sample-ingest.json
|
|
56
|
+
PUBLIC_LEADS_API=https://cold-agent-leads.example.com npx public-leads ingest --input examples/sample-leads.json --dry-run --out output/sample-ingest.json
|
|
55
57
|
```
|
|
56
58
|
|
|
57
59
|
## Live Ingest
|
|
58
60
|
|
|
59
61
|
```bash
|
|
60
|
-
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
61
|
-
npx public-leads ingest --input data/lead-results.json
|
|
62
|
+
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
63
|
+
npx public-leads ingest --input data/lead-results.json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Run from `/Users/charlie/Razroo/cold-agent-leads` without local Postgres or a local API:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cd /Users/charlie/Razroo/cold-agent-leads
|
|
70
|
+
export PUBLIC_LEADS_API=https://cold-agent-leads.example.com
|
|
71
|
+
export PUBLIC_LEADS_API_TOKEN=...
|
|
72
|
+
export PUBLIC_LEADS_OPERATOR_EMAIL=admin@example.com
|
|
73
|
+
npx -p @agent-pattern-labs/leads-rig public-leads pipeline \
|
|
74
|
+
--domains example.com \
|
|
75
|
+
--ingest \
|
|
76
|
+
--out data/lead-results.json \
|
|
77
|
+
--manifest data/lead-manifest.json \
|
|
78
|
+
--ingest-out data/ingest-response.json
|
|
62
79
|
```
|
|
63
80
|
|
|
64
81
|
Required request settings come from flags, environment, or `config/profile.yml`:
|
|
65
82
|
|
|
66
83
|
- `api.base_url`
|
|
67
84
|
- `api.ingest_path`
|
|
68
|
-
- `api.target_project`
|
|
69
85
|
- `api.operator_email`
|
|
70
86
|
- `api.operator_email_header`
|
|
71
87
|
- `api.auth_header`
|
|
72
88
|
- `api.auth_scheme`
|
|
73
89
|
- `api.auth_token_env`
|
|
74
90
|
|
|
91
|
+
Use `api.target_project` only when intentionally reading credentials from a local Cold Agent Leads checkout. Upstream ingest should use `PUBLIC_LEADS_API`, `PUBLIC_LEADS_API_TOKEN`, and `PUBLIC_LEADS_OPERATOR_EMAIL`.
|
|
92
|
+
|
|
75
93
|
## Batch
|
|
76
94
|
|
|
77
95
|
Create `batch/batch-input.tsv`:
|
|
@@ -44,10 +44,10 @@ Available commands:
|
|
|
44
44
|
|
|
45
45
|
Local commands:
|
|
46
46
|
npx public-leads crawl --input data/domains.tsv --out data/lead-results.json
|
|
47
|
-
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
47
|
+
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
48
48
|
npx public-leads validate --input data/lead-results.json
|
|
49
49
|
npx public-leads manifest --input data/lead-results.json
|
|
50
|
-
npx public-leads ingest --input data/lead-results.json
|
|
50
|
+
npx public-leads ingest --input data/lead-results.json
|
|
51
51
|
npx public-leads verify
|
|
52
52
|
```
|
|
53
53
|
|
|
@@ -44,10 +44,10 @@ Available commands:
|
|
|
44
44
|
|
|
45
45
|
Local commands:
|
|
46
46
|
npx public-leads crawl --input data/domains.tsv --out data/lead-results.json
|
|
47
|
-
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
47
|
+
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
48
48
|
npx public-leads validate --input data/lead-results.json
|
|
49
49
|
npx public-leads manifest --input data/lead-results.json
|
|
50
|
-
npx public-leads ingest --input data/lead-results.json
|
|
50
|
+
npx public-leads ingest --input data/lead-results.json
|
|
51
51
|
npx public-leads verify
|
|
52
52
|
```
|
|
53
53
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
2
|
import { lookup, resolveMx } from 'dns/promises';
|
|
3
3
|
import { setTimeout as sleep } from 'timers/promises';
|
|
4
|
-
import { cleanDomains, normalizeDomain } from './leadharness-leads.mjs';
|
|
4
|
+
import { cleanDomains, dedupeLeadRecords, mergeLeadRecords, normalizeDomain } from './leadharness-leads.mjs';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_MAX_PAGES = 10;
|
|
7
7
|
const HARD_MAX_PAGES = 25;
|
|
@@ -51,16 +51,14 @@ export async function crawlDomains(inputs, options = {}) {
|
|
|
51
51
|
for (const lead of result.leads) {
|
|
52
52
|
const key = leadKey(lead);
|
|
53
53
|
const current = leadsByKey.get(key);
|
|
54
|
-
|
|
55
|
-
leadsByKey.set(key, lead);
|
|
56
|
-
}
|
|
54
|
+
leadsByKey.set(key, current ? mergeLeadRecords(current, lead) : lead);
|
|
57
55
|
}
|
|
58
56
|
} catch (error) {
|
|
59
57
|
errors.push(`${target.domain}: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
const leads = [...leadsByKey.values()].sort(sortLead);
|
|
61
|
+
const leads = dedupeLeadRecords([...leadsByKey.values()]).sort(sortLead);
|
|
64
62
|
const domains = cleanDomains([
|
|
65
63
|
...targets.map((target) => target.domain),
|
|
66
64
|
...results.map((result) => result.domain),
|
|
@@ -173,9 +171,7 @@ async function crawlDomainTarget(target, options) {
|
|
|
173
171
|
if (!isRelevantLead(lead, { minConfidence, includeBlocked: options.includeBlocked })) continue;
|
|
174
172
|
const key = leadKey(lead);
|
|
175
173
|
const current = leadsByKey.get(key);
|
|
176
|
-
|
|
177
|
-
leadsByKey.set(key, lead);
|
|
178
|
-
}
|
|
174
|
+
leadsByKey.set(key, current ? mergeLeadRecords(current, lead) : lead);
|
|
179
175
|
}
|
|
180
176
|
|
|
181
177
|
if (page.hasForm && isContactLikeURL(page.url)) {
|
|
@@ -183,9 +179,7 @@ async function crawlDomainTarget(target, options) {
|
|
|
183
179
|
if (isRelevantLead(lead, { minConfidence, includeBlocked: options.includeBlocked })) {
|
|
184
180
|
const key = leadKey(lead);
|
|
185
181
|
const current = leadsByKey.get(key);
|
|
186
|
-
|
|
187
|
-
leadsByKey.set(key, lead);
|
|
188
|
-
}
|
|
182
|
+
leadsByKey.set(key, current ? mergeLeadRecords(current, lead) : lead);
|
|
189
183
|
}
|
|
190
184
|
}
|
|
191
185
|
|
|
@@ -199,7 +193,7 @@ async function crawlDomainTarget(target, options) {
|
|
|
199
193
|
await sleep(boundedInteger(options.delayMs, DEFAULT_DELAY_MS, 0, 10_000));
|
|
200
194
|
}
|
|
201
195
|
|
|
202
|
-
const leads = [...leadsByKey.values()].sort(sortLead);
|
|
196
|
+
const leads = dedupeLeadRecords([...leadsByKey.values()]).sort(sortLead);
|
|
203
197
|
const completedAt = new Date().toISOString();
|
|
204
198
|
if (pages.length === 0) {
|
|
205
199
|
warnings.push(`no pages could be crawled for ${target.domain}`);
|
|
@@ -502,6 +496,11 @@ async function buildEmailLead(email, companyDomain, websiteUrl, page) {
|
|
|
502
496
|
emailType,
|
|
503
497
|
sourceUrl,
|
|
504
498
|
sourceLabel: sourceLabel(sourceUrl),
|
|
499
|
+
sources: [{
|
|
500
|
+
url: sourceUrl,
|
|
501
|
+
label: sourceLabel(sourceUrl),
|
|
502
|
+
evidence: snippet,
|
|
503
|
+
}],
|
|
505
504
|
evidence: snippet,
|
|
506
505
|
extractionMethod: 'agentic_harness_public_page',
|
|
507
506
|
verificationStatus: emailType === 'blocked' ? 'blocked' : verificationStatus,
|
|
@@ -533,6 +532,11 @@ function buildContactPath(domain, websiteUrl, page) {
|
|
|
533
532
|
emailType: 'contact_path',
|
|
534
533
|
sourceUrl,
|
|
535
534
|
sourceLabel: sourceLabel(sourceUrl),
|
|
535
|
+
sources: [{
|
|
536
|
+
url: sourceUrl,
|
|
537
|
+
label: sourceLabel(sourceUrl),
|
|
538
|
+
evidence: contactPathEvidence(page),
|
|
539
|
+
}],
|
|
536
540
|
evidence: contactPathEvidence(page),
|
|
537
541
|
extractionMethod: 'agentic_harness_contact_form',
|
|
538
542
|
verificationStatus: 'not_applicable',
|
|
@@ -14,10 +14,18 @@ export function resolveIngestConfig(opts = {}, profile = loadProfileConfig()) {
|
|
|
14
14
|
const configuredOperatorEmail = opts.operatorEmail || env.PUBLIC_LEADS_OPERATOR_EMAIL || env.LEAD_HARNESS_OPERATOR_EMAIL || profile.operatorEmail;
|
|
15
15
|
const adminEmails = splitList(env.ADMIN_EMAILS);
|
|
16
16
|
const operatorEmail = configuredOperatorEmail || legacyOperatorEmail || adminEmails[0] || '';
|
|
17
|
-
const tokenEnv = opts.tokenEnv
|
|
17
|
+
const tokenEnv = opts.tokenEnv
|
|
18
|
+
|| profile.authTokenEnv
|
|
19
|
+
|| profile.adminTokenEnv
|
|
20
|
+
|| (env.PUBLIC_LEADS_API_TOKEN ? 'PUBLIC_LEADS_API_TOKEN' : '')
|
|
21
|
+
|| (env.LEAD_HARNESS_API_TOKEN ? 'LEAD_HARNESS_API_TOKEN' : '')
|
|
22
|
+
|| 'PUBLIC_LEADS_API_TOKEN';
|
|
23
|
+
const baseUrl = opts.api || env.PUBLIC_LEADS_API || env.LEAD_HARNESS_API || profile.baseUrl || '';
|
|
18
24
|
|
|
19
25
|
return {
|
|
20
|
-
endpoint:
|
|
26
|
+
endpoint: baseUrl
|
|
27
|
+
? `${stripTrailingSlash(baseUrl)}${normalizeEndpointPath(opts.ingestPath || env.PUBLIC_LEADS_INGEST_PATH || env.LEAD_HARNESS_INGEST_PATH || profile.ingestPath || '/api/lead-ingests')}`
|
|
28
|
+
: '',
|
|
21
29
|
operatorEmail,
|
|
22
30
|
operatorEmailHeader: opts.operatorEmailHeader
|
|
23
31
|
|| opts.adminEmailHeader
|
|
@@ -25,8 +33,7 @@ export function resolveIngestConfig(opts = {}, profile = loadProfileConfig()) {
|
|
|
25
33
|
|| env.LEAD_HARNESS_OPERATOR_EMAIL_HEADER
|
|
26
34
|
|| profile.operatorEmailHeader
|
|
27
35
|
|| profile.adminEmailHeader
|
|
28
|
-
||
|
|
29
|
-
|| (operatorEmail ? 'X-Operator-Email' : ''),
|
|
36
|
+
|| 'X-Admin-Email',
|
|
30
37
|
authHeader: opts.authHeader || env.PUBLIC_LEADS_AUTH_HEADER || env.LEAD_HARNESS_AUTH_HEADER || profile.authHeader || 'Authorization',
|
|
31
38
|
authScheme: opts.authScheme || env.PUBLIC_LEADS_AUTH_SCHEME || env.LEAD_HARNESS_AUTH_SCHEME || profile.authScheme || 'Bearer',
|
|
32
39
|
tokenEnv,
|
|
@@ -56,6 +63,10 @@ export async function ingestPayload(payload, opts = {}) {
|
|
|
56
63
|
errors: payload.errors,
|
|
57
64
|
};
|
|
58
65
|
|
|
66
|
+
if (!config.endpoint) {
|
|
67
|
+
throw new Error('ingest API base URL is required (--api, $PUBLIC_LEADS_API, $LEAD_HARNESS_API, or api.base_url)');
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
if (opts.dryRun) {
|
|
60
71
|
const output = {
|
|
61
72
|
status: 'DRY RUN',
|
|
@@ -69,10 +80,16 @@ export async function ingestPayload(payload, opts = {}) {
|
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
if (config.operatorEmailHeader && !config.operatorEmail) {
|
|
72
|
-
throw new Error('operator email is required when an operator email header is configured');
|
|
83
|
+
throw new Error('operator email is required when an operator email header is configured (--operator-email, $PUBLIC_LEADS_OPERATOR_EMAIL, or api.operator_email)');
|
|
73
84
|
}
|
|
74
85
|
if (!config.token) {
|
|
75
|
-
|
|
86
|
+
const tokenHints = [...new Set([
|
|
87
|
+
`$${config.tokenEnv}`,
|
|
88
|
+
'$PUBLIC_LEADS_API_TOKEN',
|
|
89
|
+
'$LEAD_HARNESS_API_TOKEN',
|
|
90
|
+
'$ADMIN_API_TOKEN',
|
|
91
|
+
])];
|
|
92
|
+
throw new Error(`API token is required (--token, ${tokenHints.slice(0, -1).join(', ')}, or ${tokenHints.at(-1)})`);
|
|
76
93
|
}
|
|
77
94
|
|
|
78
95
|
const headers = {
|
|
@@ -94,7 +94,7 @@ export function normalizePayload(value, { now = new Date().toISOString(), source
|
|
|
94
94
|
throw new Error('lead artifact must be a JSON array, JSONL records, or an object with a leads array');
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
const leads = payload.leads.map((lead) => normalizeLead(lead, { now }));
|
|
97
|
+
const leads = dedupeLeadRecords(payload.leads.map((lead) => normalizeLead(lead, { now })));
|
|
98
98
|
const results = Array.isArray(payload.results)
|
|
99
99
|
? payload.results.map((result) => normalizeResult(result, { now }))
|
|
100
100
|
: [];
|
|
@@ -146,6 +146,11 @@ export function normalizeLead(input, { now = new Date().toISOString() } = {}) {
|
|
|
146
146
|
emailType,
|
|
147
147
|
sourceUrl,
|
|
148
148
|
sourceLabel: stringValue(input.sourceLabel) || sourceUrl,
|
|
149
|
+
sources: normalizeLeadSources(input.sources, {
|
|
150
|
+
sourceUrl,
|
|
151
|
+
sourceLabel: stringValue(input.sourceLabel) || sourceUrl,
|
|
152
|
+
evidence: compactWhitespace(stringValue(input.evidence)),
|
|
153
|
+
}),
|
|
149
154
|
evidence: compactWhitespace(stringValue(input.evidence)),
|
|
150
155
|
extractionMethod: stringValue(input.extractionMethod) || 'agentic_harness',
|
|
151
156
|
verificationStatus,
|
|
@@ -158,7 +163,7 @@ export function normalizeLead(input, { now = new Date().toISOString() } = {}) {
|
|
|
158
163
|
};
|
|
159
164
|
|
|
160
165
|
if (!lead.id) lead.id = stableLeadID(lead);
|
|
161
|
-
return lead;
|
|
166
|
+
return normalizeLeadRecord(lead);
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
export function normalizeResult(input, { now = new Date().toISOString() } = {}) {
|
|
@@ -169,7 +174,9 @@ export function normalizeResult(input, { now = new Date().toISOString() } = {})
|
|
|
169
174
|
return {
|
|
170
175
|
domain,
|
|
171
176
|
websiteUrl: stringValue(input.websiteUrl) || (domain ? `https://${domain}/` : ''),
|
|
172
|
-
leads: Array.isArray(input.leads)
|
|
177
|
+
leads: Array.isArray(input.leads)
|
|
178
|
+
? dedupeLeadRecords(input.leads.map((lead) => normalizeLead(lead, { now })))
|
|
179
|
+
: [],
|
|
173
180
|
pages: Array.isArray(input.pages) ? input.pages.map(normalizePageVisit) : [],
|
|
174
181
|
warnings: normalizeWarnings(input.warnings),
|
|
175
182
|
completedAt: normalizeDateTime(input.completedAt, now),
|
|
@@ -299,6 +306,19 @@ export function validateLead(lead, path, issues) {
|
|
|
299
306
|
if (!Array.isArray(lead.warnings)) {
|
|
300
307
|
issues.push(issue('error', `${path}.warnings`, 'invalid_warnings', 'warnings must be an array'));
|
|
301
308
|
}
|
|
309
|
+
if (!Array.isArray(lead.sources)) {
|
|
310
|
+
issues.push(issue('error', `${path}.sources`, 'invalid_sources', 'sources must be an array'));
|
|
311
|
+
} else {
|
|
312
|
+
lead.sources.forEach((source, index) => {
|
|
313
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
314
|
+
issues.push(issue('error', `${path}.sources[${index}]`, 'invalid_source', 'sources entries must be objects'));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (!isHttpUrl(source.url)) {
|
|
318
|
+
issues.push(issue('error', `${path}.sources[${index}].url`, 'invalid_source_url', 'sources entries require an http(s) URL'));
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
302
322
|
if (lead.foundAt && Number.isNaN(Date.parse(lead.foundAt))) {
|
|
303
323
|
issues.push(issue('error', `${path}.foundAt`, 'invalid_found_at', 'foundAt must be ISO datetime'));
|
|
304
324
|
}
|
|
@@ -447,6 +467,127 @@ export function domainFromUrl(value) {
|
|
|
447
467
|
}
|
|
448
468
|
}
|
|
449
469
|
|
|
470
|
+
export function dedupeLeadRecords(leads) {
|
|
471
|
+
const byKey = new Map();
|
|
472
|
+
for (const rawLead of leads || []) {
|
|
473
|
+
const lead = normalizeLeadRecord(rawLead);
|
|
474
|
+
const key = stableLeadKey(lead);
|
|
475
|
+
const current = byKey.get(key);
|
|
476
|
+
byKey.set(key, current ? mergeLeadRecords(current, lead) : lead);
|
|
477
|
+
}
|
|
478
|
+
return [...byKey.values()];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function normalizeLeadRecord(lead) {
|
|
482
|
+
const sources = normalizeLeadSources(lead?.sources, {
|
|
483
|
+
sourceUrl: lead?.sourceUrl,
|
|
484
|
+
sourceLabel: lead?.sourceLabel,
|
|
485
|
+
evidence: lead?.evidence,
|
|
486
|
+
});
|
|
487
|
+
const normalized = {
|
|
488
|
+
...lead,
|
|
489
|
+
sourceUrl: sources[0]?.url || stringValue(lead?.sourceUrl),
|
|
490
|
+
sourceLabel: sources[0]?.label || stringValue(lead?.sourceLabel),
|
|
491
|
+
sources,
|
|
492
|
+
evidence: sources[0]?.evidence || compactWhitespace(lead?.evidence),
|
|
493
|
+
warnings: normalizeWarnings(lead?.warnings),
|
|
494
|
+
socialUrls: normalizeUrlList(lead?.socialUrls),
|
|
495
|
+
contactUrls: normalizeUrlList(lead?.contactUrls),
|
|
496
|
+
};
|
|
497
|
+
normalized.id = stableLeadID(normalized);
|
|
498
|
+
return normalized;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export function mergeLeadRecords(current, candidate) {
|
|
502
|
+
const left = normalizeLeadRecord(current);
|
|
503
|
+
const right = normalizeLeadRecord(candidate);
|
|
504
|
+
const primary = leadQualityScore(right) > leadQualityScore(left) ? right : left;
|
|
505
|
+
const secondary = primary === right ? left : right;
|
|
506
|
+
|
|
507
|
+
const merged = {
|
|
508
|
+
...primary,
|
|
509
|
+
company: primary.company || secondary.company,
|
|
510
|
+
domain: primary.domain || secondary.domain,
|
|
511
|
+
websiteUrl: primary.websiteUrl || secondary.websiteUrl,
|
|
512
|
+
contactName: primary.contactName || secondary.contactName,
|
|
513
|
+
title: primary.title || secondary.title,
|
|
514
|
+
email: primary.email || secondary.email,
|
|
515
|
+
emailType: primary.emailType || secondary.emailType,
|
|
516
|
+
extractionMethod: primary.extractionMethod || secondary.extractionMethod,
|
|
517
|
+
verificationStatus: verificationRank(secondary.verificationStatus) > verificationRank(primary.verificationStatus)
|
|
518
|
+
? secondary.verificationStatus
|
|
519
|
+
: primary.verificationStatus,
|
|
520
|
+
confidence: Math.max(primary.confidence || 0, secondary.confidence || 0),
|
|
521
|
+
phone: primary.phone || secondary.phone,
|
|
522
|
+
socialUrls: normalizeUrlList([...(primary.socialUrls || []), ...(secondary.socialUrls || [])]),
|
|
523
|
+
contactUrls: normalizeUrlList([...(primary.contactUrls || []), ...(secondary.contactUrls || [])]),
|
|
524
|
+
warnings: normalizeWarnings([...(primary.warnings || []), ...(secondary.warnings || [])]),
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
merged.foundAt = earliestDateTime(primary.foundAt, secondary.foundAt);
|
|
528
|
+
if ((secondary.evidence || '').length > (merged.evidence || '').length) {
|
|
529
|
+
merged.evidence = secondary.evidence;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
merged.sources = mergeLeadSources(merged.sourceUrl, left.sources, right.sources);
|
|
533
|
+
merged.sourceUrl = merged.sources[0]?.url || merged.sourceUrl;
|
|
534
|
+
merged.sourceLabel = merged.sources[0]?.label || merged.sourceLabel;
|
|
535
|
+
if (!merged.evidence) {
|
|
536
|
+
merged.evidence = merged.sources[0]?.evidence || '';
|
|
537
|
+
}
|
|
538
|
+
merged.id = stableLeadID(merged);
|
|
539
|
+
return normalizeLeadRecord(merged);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function normalizeLeadSources(value, fallback = {}) {
|
|
543
|
+
const byUrl = new Map();
|
|
544
|
+
const ordered = [];
|
|
545
|
+
const addSource = (source) => {
|
|
546
|
+
const url = stringValue(source?.url);
|
|
547
|
+
if (!url) return;
|
|
548
|
+
|
|
549
|
+
const entry = {
|
|
550
|
+
url,
|
|
551
|
+
label: stringValue(source?.label) || defaultSourceLabel(url),
|
|
552
|
+
evidence: compactWhitespace(source?.evidence),
|
|
553
|
+
};
|
|
554
|
+
const current = byUrl.get(url);
|
|
555
|
+
if (!current) {
|
|
556
|
+
byUrl.set(url, entry);
|
|
557
|
+
ordered.push(url);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
byUrl.set(url, {
|
|
561
|
+
url,
|
|
562
|
+
label: current.label || entry.label,
|
|
563
|
+
evidence: entry.evidence.length > current.evidence.length ? entry.evidence : current.evidence,
|
|
564
|
+
});
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
if (Array.isArray(value)) {
|
|
568
|
+
value.forEach(addSource);
|
|
569
|
+
}
|
|
570
|
+
addSource({
|
|
571
|
+
url: fallback.sourceUrl,
|
|
572
|
+
label: fallback.sourceLabel,
|
|
573
|
+
evidence: fallback.evidence,
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
const preferredUrl = stringValue(fallback.sourceUrl);
|
|
577
|
+
return ordered
|
|
578
|
+
.map((url) => byUrl.get(url))
|
|
579
|
+
.sort((left, right) => sourceSortValue(right, preferredUrl) - sourceSortValue(left, preferredUrl) || left.label.localeCompare(right.label) || left.url.localeCompare(right.url));
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function mergeLeadSources(preferredUrl, ...lists) {
|
|
583
|
+
return normalizeLeadSources(lists.flat(), { sourceUrl: preferredUrl });
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function sourceSortValue(source, preferredUrl) {
|
|
587
|
+
if (preferredUrl && source.url === preferredUrl) return 10_000;
|
|
588
|
+
return pagePriority(source.url || '') * 100 + Math.min((source.evidence || '').length, 99);
|
|
589
|
+
}
|
|
590
|
+
|
|
450
591
|
export function stableLeadID(lead) {
|
|
451
592
|
const key = stableLeadKey(lead);
|
|
452
593
|
if (!key.replace(/\|/g, '').trim()) return `agentic-${Date.now()}`;
|
|
@@ -469,6 +610,77 @@ function stableLeadKey(lead) {
|
|
|
469
610
|
].join('|').toLowerCase();
|
|
470
611
|
}
|
|
471
612
|
|
|
613
|
+
function leadQualityScore(lead) {
|
|
614
|
+
return (lead?.confidence || 0) * 1000
|
|
615
|
+
+ verificationRank(lead?.verificationStatus) * 100
|
|
616
|
+
+ pagePriority(lead?.sourceUrl || '') * 10
|
|
617
|
+
+ (Array.isArray(lead?.sources) ? lead.sources.length : 0) * 5
|
|
618
|
+
+ (lead?.phone ? 25 : 0)
|
|
619
|
+
+ (Array.isArray(lead?.socialUrls) ? lead.socialUrls.length : 0) * 5
|
|
620
|
+
+ (Array.isArray(lead?.contactUrls) ? lead.contactUrls.length : 0) * 5
|
|
621
|
+
+ (lead?.contactName ? 20 : 0)
|
|
622
|
+
+ (lead?.title ? 10 : 0);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function verificationRank(value) {
|
|
626
|
+
const status = stringValue(value).toLowerCase();
|
|
627
|
+
if (status === 'verified' || status === 'mx_verified') return 4;
|
|
628
|
+
if (status === 'unverified') return 3;
|
|
629
|
+
if (status === 'not_applicable') return 2;
|
|
630
|
+
if (status === 'unknown') return 1;
|
|
631
|
+
return 0;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function earliestDateTime(...values) {
|
|
635
|
+
const dates = values
|
|
636
|
+
.map((value) => stringValue(value))
|
|
637
|
+
.filter(Boolean)
|
|
638
|
+
.map((value) => new Date(value))
|
|
639
|
+
.filter((value) => !Number.isNaN(value.getTime()))
|
|
640
|
+
.sort((left, right) => left.getTime() - right.getTime());
|
|
641
|
+
return dates[0]?.toISOString() || new Date().toISOString();
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function defaultSourceLabel(sourceUrl) {
|
|
645
|
+
const path = safePath(sourceUrl).replace(/^\/+|\/+$/g, '');
|
|
646
|
+
if (!path) return 'Home';
|
|
647
|
+
const last = path.split('/').filter(Boolean).pop() || path;
|
|
648
|
+
return last.replace(/[-_]+/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase());
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function safePath(value) {
|
|
652
|
+
try {
|
|
653
|
+
return new URL(value).pathname.toLowerCase();
|
|
654
|
+
} catch {
|
|
655
|
+
return '';
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function pagePriority(link) {
|
|
660
|
+
const path = safePath(link);
|
|
661
|
+
const weighted = [
|
|
662
|
+
['contact', 100],
|
|
663
|
+
['about', 90],
|
|
664
|
+
['team', 85],
|
|
665
|
+
['people', 85],
|
|
666
|
+
['leadership', 85],
|
|
667
|
+
['staff', 80],
|
|
668
|
+
['founder', 80],
|
|
669
|
+
['press', 65],
|
|
670
|
+
['media', 65],
|
|
671
|
+
['blog', 55],
|
|
672
|
+
['author', 55],
|
|
673
|
+
['career', 45],
|
|
674
|
+
['privacy', 25],
|
|
675
|
+
['legal', 20],
|
|
676
|
+
['impressum', 20],
|
|
677
|
+
];
|
|
678
|
+
for (const [needle, score] of weighted) {
|
|
679
|
+
if (path.includes(needle)) return score;
|
|
680
|
+
}
|
|
681
|
+
return path === '/' ? 50 : 0;
|
|
682
|
+
}
|
|
683
|
+
|
|
472
684
|
export function fileHash(path) {
|
|
473
685
|
return createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
474
686
|
}
|
|
@@ -507,7 +719,7 @@ function normalizeVerificationStatus(value, emailType) {
|
|
|
507
719
|
|
|
508
720
|
function normalizeWarnings(value) {
|
|
509
721
|
if (!Array.isArray(value)) return [];
|
|
510
|
-
return value.map((item) => stringValue(item)).filter(Boolean);
|
|
722
|
+
return [...new Set(value.map((item) => stringValue(item)).filter(Boolean))];
|
|
511
723
|
}
|
|
512
724
|
|
|
513
725
|
function normalizeUrlList(value) {
|
package/modes/_shared.md
CHANGED
|
@@ -45,6 +45,7 @@ Confidence should reflect source quality, person specificity, domain match, and
|
|
|
45
45
|
```bash
|
|
46
46
|
npx public-leads validate --input data/lead-results.json
|
|
47
47
|
npx public-leads manifest --input data/lead-results.json
|
|
48
|
-
npx public-leads ingest --input data/lead-results.json --dry-run
|
|
48
|
+
PUBLIC_LEADS_API=https://cold-agent-leads.example.com npx public-leads ingest --input data/lead-results.json --dry-run
|
|
49
|
+
npx public-leads pipeline --input data/domains.tsv --ingest
|
|
49
50
|
npx public-leads verify
|
|
50
51
|
```
|
package/modes/ingest.md
CHANGED
|
@@ -12,20 +12,23 @@ Use this when the user asks to store validated leads in a configured ingest API.
|
|
|
12
12
|
```bash
|
|
13
13
|
npx public-leads manifest --input <artifact>
|
|
14
14
|
```
|
|
15
|
-
3. Confirm ingest
|
|
15
|
+
3. Confirm upstream ingest settings are available through environment variables, flags, or `config/profile.yml`. Do not print token values.
|
|
16
|
+
- `PUBLIC_LEADS_API`
|
|
17
|
+
- `PUBLIC_LEADS_API_TOKEN`
|
|
18
|
+
- `PUBLIC_LEADS_OPERATOR_EMAIL`
|
|
16
19
|
|
|
17
20
|
## Dry Run
|
|
18
21
|
|
|
19
22
|
Use dry-run before live ingest unless the user explicitly requested live submission:
|
|
20
23
|
|
|
21
24
|
```bash
|
|
22
|
-
npx public-leads ingest --input <artifact> --dry-run --out data/ingest-response.json
|
|
25
|
+
PUBLIC_LEADS_API=https://cold-agent-leads.example.com npx public-leads ingest --input <artifact> --dry-run --out data/ingest-response.json
|
|
23
26
|
```
|
|
24
27
|
|
|
25
28
|
## Live Ingest
|
|
26
29
|
|
|
27
30
|
```bash
|
|
28
|
-
npx public-leads ingest --input <artifact> --
|
|
31
|
+
npx public-leads ingest --input <artifact> --out data/ingest-response.json
|
|
29
32
|
```
|
|
30
33
|
|
|
31
34
|
## Output
|
package/modes/pipeline.md
CHANGED
|
@@ -20,7 +20,7 @@ Read domains from the first available source:
|
|
|
20
20
|
5. For browser-heavy batch work, use `batch/batch-runner.sh --parallel 2`.
|
|
21
21
|
6. Validate every artifact with `npx public-leads validate`.
|
|
22
22
|
7. Merge validated artifacts into a manifest with `npx public-leads manifest --input <artifact>`.
|
|
23
|
-
8. Ingest with `npx public-leads pipeline --ingest
|
|
23
|
+
8. Ingest with `npx public-leads pipeline --ingest` only when upload is requested and upstream API env vars are configured.
|
|
24
24
|
|
|
25
25
|
## Output
|
|
26
26
|
|
package/modes/setup.md
CHANGED
|
@@ -9,7 +9,7 @@ Use this when the user asks to configure or inspect the lead harness.
|
|
|
9
9
|
- `data/domains.tsv`
|
|
10
10
|
- `data/pipeline.md`
|
|
11
11
|
- direct domains in the user request
|
|
12
|
-
3. Confirm the ingest API settings:
|
|
12
|
+
3. Confirm the upstream ingest API settings:
|
|
13
13
|
- `api.base_url`
|
|
14
14
|
- `api.ingest_path`
|
|
15
15
|
- `api.operator_email`
|
|
@@ -17,7 +17,8 @@ Use this when the user asks to configure or inspect the lead harness.
|
|
|
17
17
|
- `api.auth_header`
|
|
18
18
|
- `api.auth_scheme`
|
|
19
19
|
- `api.auth_token_env`
|
|
20
|
-
- `
|
|
20
|
+
- or `PUBLIC_LEADS_API`, `PUBLIC_LEADS_API_TOKEN`, and `PUBLIC_LEADS_OPERATOR_EMAIL`
|
|
21
|
+
- `api.target_project` only when intentionally reading credentials from a local Cold Agent Leads checkout
|
|
21
22
|
4. Run `npx public-leads verify`.
|
|
22
23
|
|
|
23
24
|
## Output
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-pattern-labs/leads-rig",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Agentic public-web lead discovery harness with portable ingest artifacts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Razroo",
|
|
@@ -181,6 +181,6 @@
|
|
|
181
181
|
"@razroo/iso": "^0.3.1",
|
|
182
182
|
"@razroo/iso-eval": "^0.4.0",
|
|
183
183
|
"@razroo/iso-harness": "^0.8.0",
|
|
184
|
-
"@razroo/iso-route": "^0.
|
|
184
|
+
"@razroo/iso-route": "^0.6.0"
|
|
185
185
|
}
|
|
186
186
|
}
|
package/scripts/ingest.mjs
CHANGED
|
@@ -9,7 +9,7 @@ Usage:
|
|
|
9
9
|
public-leads ingest --input <file> [--api <base-url>] [--ingest-path </path>]
|
|
10
10
|
[--operator-email <email>] [--operator-email-header <header>]
|
|
11
11
|
[--auth-header <header>] [--auth-scheme <scheme>]
|
|
12
|
-
[--token <token>] [--token-env
|
|
12
|
+
[--token <token>] [--token-env PUBLIC_LEADS_API_TOKEN]
|
|
13
13
|
[--target-project /path/to/cold-agent-leads]
|
|
14
14
|
[--job-id <id>] [--out data/ingest-response.json] [--dry-run]
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ Defaults are read from config/profile.yml when present:
|
|
|
18
18
|
api.auth_header, api.auth_scheme, api.auth_token_env, api.target_project
|
|
19
19
|
|
|
20
20
|
Compatibility fallbacks are still accepted:
|
|
21
|
-
--admin-email, api.admin_email, api.admin_token_env, $
|
|
21
|
+
--admin-email, api.admin_email, api.admin_token_env, $ADMIN_API_TOKEN, --target-project
|
|
22
22
|
`;
|
|
23
23
|
|
|
24
24
|
const opts = parseArgs(process.argv.slice(2));
|
package/scripts/pipeline.mjs
CHANGED
|
@@ -23,11 +23,11 @@ Usage:
|
|
|
23
23
|
[--manifest data/lead-manifest.json]
|
|
24
24
|
[--max-pages 10] [--min-confidence 30]
|
|
25
25
|
[--ingest | --upload] [--dry-run]
|
|
26
|
-
[--
|
|
26
|
+
[--api https://cold-agent-leads.example.com]
|
|
27
27
|
|
|
28
28
|
Examples:
|
|
29
29
|
public-leads pipeline --domains example.com,example.org
|
|
30
|
-
public-leads pipeline --input data/domains.tsv --ingest
|
|
30
|
+
PUBLIC_LEADS_API=https://cold-agent-leads.example.com public-leads pipeline --input data/domains.tsv --ingest
|
|
31
31
|
`;
|
|
32
32
|
|
|
33
33
|
const opts = parseArgs(process.argv.slice(2));
|