argorant 0.2.0 → 0.4.0
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/bin/argorant.js +131 -10
- package/package.json +2 -2
package/bin/argorant.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
// Argorant CLI — a thin, dependency-free wrapper over the Argorant REST API.
|
|
5
|
-
// Search, count, reveal, and
|
|
5
|
+
// Search, count, reveal, export, and verify B2B contacts from the terminal.
|
|
6
6
|
// Auth: an Argorant API key (ag_live_*) via `argorant login`, or ARGORANT_API_KEY.
|
|
7
7
|
|
|
8
8
|
const https = require("https");
|
|
@@ -66,24 +66,30 @@ const VALUE_FLAGS = {
|
|
|
66
66
|
"--city": "city",
|
|
67
67
|
"--company": "company_name",
|
|
68
68
|
"--domain": "company_domain",
|
|
69
|
-
"--verify-status": "verify_status",
|
|
70
69
|
};
|
|
71
|
-
// Boolean filter flags (presence => "true").
|
|
70
|
+
// Boolean filter flags (presence => "true"). --verified-only is positive intent
|
|
71
|
+
// only (deliverable contacts); there is deliberately NO flag to query invalid or
|
|
72
|
+
// any raw verification status — that is never exposed on any surface.
|
|
72
73
|
const BOOL_FLAGS = {
|
|
73
74
|
"--has-phone": "has_phone",
|
|
74
75
|
"--has-linkedin": "has_linkedin",
|
|
75
76
|
"--has-email": "has_email",
|
|
77
|
+
"--verified-only": "verified_only",
|
|
76
78
|
};
|
|
77
79
|
|
|
78
80
|
function parseArgs(argv) {
|
|
79
|
-
const out = { _: [], filters: {}, limit: null, output: null, json: false, yes: false, base: DEFAULT_BASE };
|
|
81
|
+
const out = { _: [], filters: {}, limit: null, output: null, file: null, column: null, json: false, yes: false, base: DEFAULT_BASE, name: null, includeExported: false };
|
|
80
82
|
for (let i = 0; i < argv.length; i++) {
|
|
81
83
|
const a = argv[i];
|
|
82
84
|
if (a === "--json") out.json = true;
|
|
83
85
|
else if (a === "--yes" || a === "-y") out.yes = true;
|
|
84
86
|
else if (a === "-n" || a === "--limit") out.limit = parseInt(argv[++i], 10);
|
|
85
87
|
else if (a === "-o" || a === "--output") out.output = argv[++i];
|
|
88
|
+
else if (a === "-f" || a === "--file") out.file = argv[++i];
|
|
89
|
+
else if (a === "--column") out.column = argv[++i];
|
|
86
90
|
else if (a === "--base") out.base = argv[++i];
|
|
91
|
+
else if (a === "--name") out.name = argv[++i];
|
|
92
|
+
else if (a === "--include-exported") out.includeExported = true;
|
|
87
93
|
else if (a in VALUE_FLAGS) out.filters[VALUE_FLAGS[a]] = argv[++i];
|
|
88
94
|
else if (a in BOOL_FLAGS) out.filters[BOOL_FLAGS[a]] = "true";
|
|
89
95
|
else if (a.startsWith("--") && a.includes("=")) {
|
|
@@ -301,7 +307,16 @@ async function cmdExport(args) {
|
|
|
301
307
|
const ans = await prompt(`Export up to ${bold(limit)} verified contacts to ${bold(dest)}? Uses quota/credits. [y/N] `);
|
|
302
308
|
if (!/^y(es)?$/i.test(ans)) return console.log(dim("aborted."));
|
|
303
309
|
}
|
|
304
|
-
|
|
310
|
+
// Match the MCP/app defaults so the CLI yields the same rows: business email
|
|
311
|
+
// present by default, and skip rows already exported (override with
|
|
312
|
+
// --include-exported). Verification stays live at export time — only deliverable
|
|
313
|
+
// rows are billed; no verification-status filter is exposed.
|
|
314
|
+
const exportFilters = { ...args.filters };
|
|
315
|
+
if (exportFilters.has_email === undefined) exportFilters.has_email = "true";
|
|
316
|
+
const create = await request("POST", args.base, "/api/mcp/exports/create", {
|
|
317
|
+
key,
|
|
318
|
+
body: { limit, filters: exportFilters, exclude_previously_exported: !args.includeExported },
|
|
319
|
+
});
|
|
305
320
|
const job = need(create, "export");
|
|
306
321
|
const statusPath = job.status_api_path || (job.job_id ? `/api/mcp/exports/${job.job_id}` : null);
|
|
307
322
|
if (!statusPath) {
|
|
@@ -330,7 +345,7 @@ async function cmdExport(args) {
|
|
|
330
345
|
if (!args.json) process.stdout.write("\n");
|
|
331
346
|
if (!downloadPath) {
|
|
332
347
|
if (args.json) return console.log(JSON.stringify(job, null, 2));
|
|
333
|
-
return console.log("Export ready but no download path returned.
|
|
348
|
+
return console.log("Export ready but no download path returned yet. Re-run with --json to inspect the job, or check the Exports page in the app.");
|
|
334
349
|
}
|
|
335
350
|
await downloadTo(args.base, downloadPath, key, dest);
|
|
336
351
|
if (args.json) return console.log(JSON.stringify({ ok: true, file: dest, job_id: job.job_id }, null, 2));
|
|
@@ -344,6 +359,103 @@ async function cmdExport(args) {
|
|
|
344
359
|
console.log(green("✓") + ` Saved ${rows != null ? bold(rows.toLocaleString()) + " rows → " : ""}${bold(dest)}`);
|
|
345
360
|
}
|
|
346
361
|
|
|
362
|
+
// ---- verify: external email verification (own lists) — the verification pool,
|
|
363
|
+
// separate from contact credits. 60-day re-checks are free. ----
|
|
364
|
+
const EMAIL_RE = /[^\s,;"']+@[^\s,;"']+\.[^\s,;"']+/;
|
|
365
|
+
|
|
366
|
+
async function cmdVerify(args) {
|
|
367
|
+
const key = requireKey();
|
|
368
|
+
if (args.file) return cmdVerifyFile(args, key);
|
|
369
|
+
const email = (args._[0] || args.filters.q || "").trim().toLowerCase();
|
|
370
|
+
if (!email || !email.includes("@")) {
|
|
371
|
+
die('usage: argorant verify <email> | argorant verify --file emails.csv [-o out.csv]');
|
|
372
|
+
}
|
|
373
|
+
const res = await request("POST", args.base, "/api/mcp/email/verify", { key, body: { email } });
|
|
374
|
+
const r = need(res, "verify");
|
|
375
|
+
if (args.json) return console.log(JSON.stringify(r, null, 2));
|
|
376
|
+
const tag = r.deliverable ? green(r.status) : dim(r.status);
|
|
377
|
+
console.log(` ${bold(email)} → ${tag}${r.deliverable ? " " + green("✓ deliverable") : ""}`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function cmdVerifyFile(args, key) {
|
|
381
|
+
let text;
|
|
382
|
+
try { text = fs.readFileSync(args.file, "utf8"); } catch { die(`cannot read file: ${args.file}`); }
|
|
383
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim());
|
|
384
|
+
if (!lines.length) die("file is empty");
|
|
385
|
+
// Use the named/auto-detected email column if the file looks like a CSV with a
|
|
386
|
+
// header; otherwise scan every line for an address.
|
|
387
|
+
const header = lines[0].split(",").map((h) => h.trim().toLowerCase().replace(/^["']|["']$/g, ""));
|
|
388
|
+
const colIdx = args.column
|
|
389
|
+
? header.indexOf(args.column.toLowerCase())
|
|
390
|
+
: header.findIndex((h) => h === "email" || h.includes("email"));
|
|
391
|
+
let emails = [];
|
|
392
|
+
if (colIdx >= 0) {
|
|
393
|
+
for (let i = 1; i < lines.length; i++) {
|
|
394
|
+
const m = (lines[i].split(",")[colIdx] || "").match(EMAIL_RE);
|
|
395
|
+
if (m) emails.push(m[0].toLowerCase());
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
for (const l of lines) { const m = l.match(EMAIL_RE); if (m) emails.push(m[0].toLowerCase()); }
|
|
399
|
+
}
|
|
400
|
+
emails = [...new Set(emails)];
|
|
401
|
+
if (!emails.length) die("no email addresses found in file (try --column <name>)");
|
|
402
|
+
const out = args.output || "argorant-verified.csv";
|
|
403
|
+
if (!args.yes && !args.json && process.stdin.isTTY) {
|
|
404
|
+
const ans = await prompt(`Verify ${bold(emails.length.toLocaleString())} emails? You're billed only for fresh checks from your verification-check pool; recent re-checks are free. [y/N] `);
|
|
405
|
+
if (!/^y(es)?$/i.test(ans)) return console.log(dim("aborted."));
|
|
406
|
+
}
|
|
407
|
+
const all = [];
|
|
408
|
+
let charged = 0, cached = 0;
|
|
409
|
+
for (let i = 0; i < emails.length; i += 500) {
|
|
410
|
+
const chunk = emails.slice(i, i + 500);
|
|
411
|
+
const res = await request("POST", args.base, "/api/mcp/email/verify/batch", { key, body: { emails: chunk } });
|
|
412
|
+
const r = need(res, "verify");
|
|
413
|
+
charged += r.checks_charged || 0;
|
|
414
|
+
cached += r.cached || 0;
|
|
415
|
+
for (const row of r.results || []) all.push(row);
|
|
416
|
+
if (!args.json) process.stdout.write(`\r${dim(`verified ${Math.min(i + 500, emails.length).toLocaleString()}/${emails.length.toLocaleString()}`)}`);
|
|
417
|
+
}
|
|
418
|
+
if (!args.json) process.stdout.write("\n");
|
|
419
|
+
if (args.json) return console.log(JSON.stringify({ ok: true, total: all.length, checks_charged: charged, cached, results: all }, null, 2));
|
|
420
|
+
const csv = ["email,status,deliverable", ...all.map((r) => `${r.email},${r.status},${r.deliverable}`)].join("\n") + "\n";
|
|
421
|
+
fs.writeFileSync(out, csv);
|
|
422
|
+
const deliverable = all.filter((r) => r.deliverable).length;
|
|
423
|
+
console.log(green("✓") + ` ${bold(all.length.toLocaleString())} verified → ${bold(out)} ${dim(`(${deliverable.toLocaleString()} deliverable · ${charged.toLocaleString()} checks billed · ${cached.toLocaleString()} free)`)}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ---- list: save & inspect reusable lead lists (parity with MCP/app). Creating a
|
|
427
|
+
// filtered list is free and does NOT reveal contacts — the server counts the
|
|
428
|
+
// matches itself, so the list reports its real size right away. ----
|
|
429
|
+
async function cmdList(args) {
|
|
430
|
+
const key = requireKey();
|
|
431
|
+
const sub = (args._[0] || "").toLowerCase();
|
|
432
|
+
if (sub === "create") {
|
|
433
|
+
const name = (args.name || "").trim();
|
|
434
|
+
if (!name) die('usage: argorant list create --name "My list" [filters] (e.g. --title CEO --country Germany)');
|
|
435
|
+
delete args.filters.q; // the "create" subcommand word leaks into q via parseArgs
|
|
436
|
+
const body = { name, filters: args.filters, record_type: "person", selection_mode: "filtered" };
|
|
437
|
+
const res = await request("POST", args.base, "/api/mcp/lists/create", { key, body });
|
|
438
|
+
const r = need(res, "list create");
|
|
439
|
+
if (args.json) return console.log(JSON.stringify(r, null, 2));
|
|
440
|
+
const total = Number(r.snapshot_total || 0);
|
|
441
|
+
console.log(green("✓") + ` Created list ${bold("#" + r.list_id)} ${dim("“" + r.name + "”")} — ${bold(total.toLocaleString())} matching contacts`);
|
|
442
|
+
console.log(dim(`Export it with: argorant export ${Object.entries(args.filters).filter(([, v]) => v).map(([k, v]) => `--${k.replace(/_/g, "-")} ${/\s/.test(String(v)) ? `"${v}"` : v}`).join(" ")} -o leads.csv`));
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (sub === "status" || sub === "show" || sub === "get") {
|
|
446
|
+
const id = args._[1] || args.name;
|
|
447
|
+
if (!id) die("usage: argorant list status <list_id>");
|
|
448
|
+
const res = await request("GET", args.base, `/api/mcp/lists/${encodeURIComponent(id)}`, { key });
|
|
449
|
+
const r = need(res, "list status");
|
|
450
|
+
if (args.json) return console.log(JSON.stringify(r, null, 2));
|
|
451
|
+
const total = Number(r.snapshot_total ?? r.item_count ?? 0);
|
|
452
|
+
console.log(`${bold("List #" + (r.list_id ?? id))} ${dim("“" + (r.name || "—") + "”")}`);
|
|
453
|
+
console.log(` ${bold(total.toLocaleString())} contacts · ${dim((r.selection_mode || "filtered") + " · " + (r.record_type || "person"))}`);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
die("usage: argorant list create --name \"…\" [filters] | argorant list status <id>");
|
|
457
|
+
}
|
|
458
|
+
|
|
347
459
|
function help() {
|
|
348
460
|
const p = bold("argorant");
|
|
349
461
|
console.log(`
|
|
@@ -357,15 +469,20 @@ ${bold("COMMANDS")}
|
|
|
357
469
|
${cyan("whoami")} Account, scopes, and daily quota
|
|
358
470
|
${cyan("count")} "<query>" Count matching contacts ${dim("(free)")}
|
|
359
471
|
${cyan("search")} "<query>" -n 10 Preview matches, details redacted ${dim("(free)")}
|
|
360
|
-
${cyan("reveal")} "<query>" -n 25 Reveal full contact details ${dim("(uses
|
|
361
|
-
${cyan("export")} "<query>" -n 1000 -o leads.csv Verified CSV export ${dim("(uses
|
|
472
|
+
${cyan("reveal")} "<query>" -n 25 Reveal full contact details ${dim("(uses credits; live-verified, pay only for deliverable)")}
|
|
473
|
+
${cyan("export")} "<query>" -n 1000 -o leads.csv Verified CSV export ${dim("(uses credits)")}
|
|
474
|
+
${cyan("list create")} --name "<n>" [filters] Save a reusable list ${dim("(free)")}
|
|
475
|
+
${cyan("list status")} <id> Show a saved list's size ${dim("(free)")}
|
|
476
|
+
${cyan("verify")} <email> Verify one of your own emails ${dim("(verification pool)")}
|
|
477
|
+
${cyan("verify")} --file emails.csv -o out.csv Bulk-verify your own list ${dim("(recent re-checks free)")}
|
|
362
478
|
|
|
363
479
|
${bold("FILTERS")}
|
|
364
480
|
--title <t> --exclude-title <t> --seniority <s> --department <d>
|
|
365
481
|
--industry <i> --country <c> --geography <r> --state <s>
|
|
366
|
-
--city <c> --company <name> --domain <domain>
|
|
367
|
-
--has-phone --has-linkedin --has-email
|
|
482
|
+
--city <c> --company <name> --domain <domain>
|
|
483
|
+
--has-phone --has-linkedin --has-email --verified-only
|
|
368
484
|
${dim("--title is abbreviation-aware (CFO ↔ Chief Financial Officer).")}
|
|
485
|
+
${dim("--verified-only keeps deliverable contacts; export verifies live & bills only valid.")}
|
|
369
486
|
${dim("--country / --geography accept regions: Europe, EMEA, DACH, Nordics, APAC, LATAM, GCC…")}
|
|
370
487
|
|
|
371
488
|
${bold("OPTIONS")}
|
|
@@ -377,6 +494,8 @@ ${bold("EXAMPLES")}
|
|
|
377
494
|
${p} count "fintech CFOs in germany"
|
|
378
495
|
${p} search "heads of procurement" --country Germany -n 10
|
|
379
496
|
${p} export --industry fintech --title CFO --country Germany -n 500 -o cfos.csv
|
|
497
|
+
${p} verify ceo@stripe.com
|
|
498
|
+
${p} verify --file my-list.csv -o verified.csv
|
|
380
499
|
|
|
381
500
|
Docs: ${cyan("https://argorant.com/docs/cli")}
|
|
382
501
|
`);
|
|
@@ -400,6 +519,8 @@ async function main() {
|
|
|
400
519
|
search: cmdSearch,
|
|
401
520
|
reveal: cmdReveal,
|
|
402
521
|
export: cmdExport,
|
|
522
|
+
list: cmdList,
|
|
523
|
+
verify: cmdVerify,
|
|
403
524
|
};
|
|
404
525
|
const fn = table[cmd];
|
|
405
526
|
if (!fn) die(`unknown command: ${cmd}\nRun \`argorant help\` for usage.`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "argorant",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Search, count, reveal, and
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Search, count, reveal, export, and verify B2B contacts from the Argorant database \u2014 from your terminal, scripts, or coding agent.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"argorant": "bin/argorant.js"
|
|
7
7
|
},
|