argorant 0.8.0 → 0.9.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/README.md +13 -0
- package/bin/argorant.js +139 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,6 +152,19 @@ argorant inboxes connect-google --admin admin@yourdomain.com # lists mailb
|
|
|
152
152
|
argorant inboxes connect-google --admin admin@yourdomain.com --emails a@x.com,b@x.com # or --all
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
+
New mailboxes on their own new domains, ordered from the terminal. The quote shows every domain with its
|
|
156
|
+
first-year and renewal price, the mailboxes and the total; the order returns a checkout link that you pay
|
|
157
|
+
yourself. Nothing is bought before that payment, and setup starts once it is confirmed. Warm-up is included
|
|
158
|
+
in every plan; switch it on per mailbox with `argorant inboxes set <mailbox> --warmup`.
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
argorant inboxes suggest acme.com # free domain names with prices
|
|
162
|
+
argorant inboxes quote --mailboxes 100 --company acme.com # or --domains getacme.com,tryacme.co
|
|
163
|
+
argorant inboxes order <quote-id> --senders "Elena Vaughn" # asks first; prints the checkout link
|
|
164
|
+
argorant inboxes status [<order-id>] # steps, domains, renewal prices, bundles
|
|
165
|
+
argorant inboxes cancel-bundle <order-id> <bundle-id> [--keep-domains] | cancel-domain <order-id> <domain>
|
|
166
|
+
```
|
|
167
|
+
|
|
155
168
|
`<campaign>` accepts an id or an unambiguous case-insensitive name prefix. Every address is verified before
|
|
156
169
|
it is enrolled; only valid ones send unless `--include-catch-all`. Variables: `{{first_name}}`, `{{last_name}}`,
|
|
157
170
|
`{{company}}`, `{{title}}`; spintax `{a|b}` varies phrasing. `--query "..."` filter enrollment stays available
|
package/bin/argorant.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
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
|
+
const crypto = require("crypto");
|
|
8
9
|
const https = require("https");
|
|
9
10
|
const http = require("http");
|
|
10
11
|
const fs = require("fs");
|
|
@@ -178,7 +179,7 @@ function parseArgs(argv) {
|
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
// ---- HTTP ----
|
|
181
|
-
function request(method, base, urlPath, { key, query, body } = {}) {
|
|
182
|
+
function request(method, base, urlPath, { key, query, body, headers: extraHeaders } = {}) {
|
|
182
183
|
return new Promise((resolve, reject) => {
|
|
183
184
|
let u;
|
|
184
185
|
try {
|
|
@@ -192,7 +193,7 @@ function request(method, base, urlPath, { key, query, body } = {}) {
|
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
195
|
const payload = body ? Buffer.from(JSON.stringify(body)) : null;
|
|
195
|
-
const headers = { Accept: "application/json", "User-Agent": `argorant-cli/${VERSION}
|
|
196
|
+
const headers = { Accept: "application/json", "User-Agent": `argorant-cli/${VERSION}`, ...(extraHeaders || {}) };
|
|
196
197
|
if (key) headers["Authorization"] = `Bearer ${key}`;
|
|
197
198
|
if (payload) {
|
|
198
199
|
headers["Content-Type"] = "application/json";
|
|
@@ -912,27 +913,28 @@ async function cmdVerifyFile(args, key) {
|
|
|
912
913
|
const out = ensureWritable(args.output || "argorant-verified.csv");
|
|
913
914
|
// Interactive-only by design — see reveal/export.
|
|
914
915
|
if (!args.yes && !args.json && process.stdin.isTTY) {
|
|
915
|
-
const ans = await prompt(`Verify ${bold(emails.length.toLocaleString())} emails?
|
|
916
|
+
const ans = await prompt(`Verify ${bold(emails.length.toLocaleString())} emails? Fresh checks cost half a credit each; recent re-checks are free. [y/N] `);
|
|
916
917
|
if (!/^y(es)?$/i.test(ans)) return console.log(dim("aborted."));
|
|
917
918
|
}
|
|
918
919
|
const all = [];
|
|
919
|
-
let charged = 0, cached = 0;
|
|
920
|
+
let charged = 0, credits = 0, cached = 0;
|
|
920
921
|
for (let i = 0; i < emails.length; i += 500) {
|
|
921
922
|
const chunk = emails.slice(i, i + 500);
|
|
922
923
|
const res = await request("POST", args.base, "/api/mcp/email/verify/batch", { key, body: { emails: chunk } });
|
|
923
924
|
const r = need(res, "verify");
|
|
924
925
|
charged += r.checks_charged || 0;
|
|
926
|
+
credits += r.credits_charged || 0;
|
|
925
927
|
cached += r.cached || 0;
|
|
926
928
|
for (const row of r.results || []) all.push(row);
|
|
927
929
|
if (!args.json) process.stdout.write(`\r${dim(`verified ${Math.min(i + 500, emails.length).toLocaleString()}/${emails.length.toLocaleString()}`)}`);
|
|
928
930
|
}
|
|
929
931
|
if (!args.json) process.stdout.write("\n");
|
|
930
|
-
if (args.json) return console.log(JSON.stringify({ ok: true, total: all.length, checks_charged: charged, cached, results: all }, null, 2));
|
|
932
|
+
if (args.json) return console.log(JSON.stringify({ ok: true, total: all.length, checks_charged: charged, credits_charged: credits, cached, results: all }, null, 2));
|
|
931
933
|
const q = (v) => `"${String(v == null ? "" : v).replace(/"/g, '""')}"`;
|
|
932
934
|
const csv = ["email,status,deliverable", ...all.map((r) => [r.email, r.status, r.deliverable].map(q).join(","))].join("\n") + "\n";
|
|
933
935
|
fs.writeFileSync(out, csv);
|
|
934
936
|
const deliverable = all.filter((r) => r.deliverable).length;
|
|
935
|
-
console.log(green("✓") + ` ${bold(all.length.toLocaleString())} verified → ${bold(out)} ${dim(`(${deliverable.toLocaleString()} deliverable · ${charged.toLocaleString()} checks
|
|
937
|
+
console.log(green("✓") + ` ${bold(all.length.toLocaleString())} verified → ${bold(out)} ${dim(`(${deliverable.toLocaleString()} deliverable · ${charged.toLocaleString()} fresh checks, ${credits.toLocaleString()} credits · ${cached.toLocaleString()} free)`)}`);
|
|
936
938
|
}
|
|
937
939
|
|
|
938
940
|
// ---- list: save & inspect reusable lead lists (parity with MCP/app). Creating a
|
|
@@ -1565,7 +1567,17 @@ async function cmdInboxThreads(argv) {
|
|
|
1565
1567
|
console.log(green("✓") + ` ${r.classification}`);
|
|
1566
1568
|
return;
|
|
1567
1569
|
}
|
|
1568
|
-
|
|
1570
|
+
if (sub === "forward") {
|
|
1571
|
+
const args = readFlags(argv.slice(1), { "--to": "to", "--note": "note" });
|
|
1572
|
+
const id = args._[0]; if (!id) die("usage: argorant inbox forward <thread-id> --to someone@company.com[,other@company.com] [--note \"...\"]");
|
|
1573
|
+
const to = String(args.to || "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
1574
|
+
if (!to.length) die("give at least one address with --to");
|
|
1575
|
+
const r = need(await request("POST", args.base, `/api/v1/inbox/${encodeURIComponent(id)}/forward`, { key, body: { to, note: args.note || "" } }), "inbox forward");
|
|
1576
|
+
if (args.json) return console.log(JSON.stringify(r, null, 2));
|
|
1577
|
+
console.log(green("✓") + ` Forwarded to ${to.join(", ")} from ${r.from || "the mailbox"}`);
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
die(`unknown inbox subcommand: ${sub} (list, read, reply, forward, classify)`);
|
|
1569
1581
|
}
|
|
1570
1582
|
|
|
1571
1583
|
async function campaignsAnalytics(argv) {
|
|
@@ -1642,6 +1654,11 @@ ${bold("USAGE")}
|
|
|
1642
1654
|
${bold("argorant inboxes")} set <mailbox> [--daily-limit 40] [--warmup|--no-warmup] [--active|--inactive] [--timezone <tz>]
|
|
1643
1655
|
${bold("argorant inboxes")} disconnect <mailbox>
|
|
1644
1656
|
${bold("argorant inboxes")} connect-google --admin <admin@yourdomain.com> [--emails a,b | --all]
|
|
1657
|
+
${bold("argorant inboxes")} suggest <website|brand> [-n 20] ${dim("free domain names with prices")}
|
|
1658
|
+
${bold("argorant inboxes")} quote --mailboxes 100 [--company acme.com] [--domains a.com,b.com] [--currency usd|eur] [--billing month|quarter]
|
|
1659
|
+
${bold("argorant inboxes")} order <quote-id> --senders "Elena Vaughn[,Mark Hill]" [--website acme.com] [-y] ${dim("returns a checkout link; nothing is bought before you pay it")}
|
|
1660
|
+
${bold("argorant inboxes")} status [<order-id>]
|
|
1661
|
+
${bold("argorant inboxes")} cancel-bundle <order-id> <bundle-id> [--keep-domains] [-y] | cancel-domain <order-id> <domain> [-y]
|
|
1645
1662
|
${bold("argorant blocklist")} list | add <email|domain> | remove <id>
|
|
1646
1663
|
|
|
1647
1664
|
${bold("argorant inbox")} list [--folder replies|interested|not_interested|out_of_office|bounces|unsubscribes|all] [--campaign <c>] [--q text]
|
|
@@ -1763,7 +1780,119 @@ async function cmdInboxes(argv) {
|
|
|
1763
1780
|
for (const c of r.connected || []) console.log(dim(` ${c.email} · ${c.health || "ok"}`));
|
|
1764
1781
|
return;
|
|
1765
1782
|
}
|
|
1766
|
-
|
|
1783
|
+
if (sub === "suggest") {
|
|
1784
|
+
const args = readFlags(argv.slice(1), { "-n": "limit", "--limit": "limit" });
|
|
1785
|
+
const company = args._.join(" ").trim();
|
|
1786
|
+
if (!company) die("usage: argorant inboxes suggest <website|brand> [-n 20]");
|
|
1787
|
+
const query = { company, limit: args.limit ? parseLimit(args.limit, "--limit") : 20 };
|
|
1788
|
+
const r = need(await request("GET", args.base, "/api/v1/inboxes/domains/suggest", { key, query }), "inboxes suggest");
|
|
1789
|
+
if (args.json) return console.log(JSON.stringify(r, null, 2));
|
|
1790
|
+
for (const d of r.results || []) {
|
|
1791
|
+
const price = d.first_year ? `${money(d.first_year.usd, "usd")} first year, ${money(d.renewal.usd, "usd")}/year after` : "";
|
|
1792
|
+
console.log(`${d.available ? green("✓") : dim("x")} ${d.available ? bold(d.name) : dim(d.name)} ${dim(d.available ? price : "taken")}`);
|
|
1793
|
+
}
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
if (sub === "quote") {
|
|
1797
|
+
const args = readFlags(argv.slice(1), { "--mailboxes": "mailboxes", "--daily-volume": "dailyVolume", "--domains": "domains",
|
|
1798
|
+
"--company": "company", "--currency": "currency", "--billing": "billing" });
|
|
1799
|
+
if (!args.mailboxes && !args.dailyVolume) die("usage: argorant inboxes quote --mailboxes 100 [--company acme.com] [--domains a.com,b.com] [--currency usd|eur] [--billing month|quarter] (or --daily-volume 500)");
|
|
1800
|
+
const body = {
|
|
1801
|
+
mailboxes: args.mailboxes ? parseLimit(args.mailboxes, "--mailboxes") : null,
|
|
1802
|
+
daily_volume: args.dailyVolume ? parseLimit(args.dailyVolume, "--daily-volume") : null,
|
|
1803
|
+
domains: args.domains ? String(args.domains).split(",").map((x) => x.trim()).filter(Boolean) : [],
|
|
1804
|
+
company: args.company || "",
|
|
1805
|
+
currency: (args.currency || "usd").toLowerCase(),
|
|
1806
|
+
billing: (args.billing || "month").toLowerCase(),
|
|
1807
|
+
};
|
|
1808
|
+
const q = need(await request("POST", args.base, "/api/v1/inboxes/quote", { key, body }), "inboxes quote");
|
|
1809
|
+
if (args.json) return console.log(JSON.stringify(q, null, 2));
|
|
1810
|
+
const cur = q.currency;
|
|
1811
|
+
console.log(bold(`${q.plan.mailboxes} mailboxes on ${q.plan.domains} new domains`) + dim(` (${q.plan.daily_capacity} cold emails a day)`));
|
|
1812
|
+
for (const d of q.domains || []) {
|
|
1813
|
+
console.log(` ${d.available ? green("✓") : red("x")} ${d.name} ${money(d.first_year_cents, cur)} first year, renews at ${money(d.renewal_per_year_cents, cur)}/year${d.available ? "" : red(" not available")}${d.suggested ? dim(" suggested") : ""}`);
|
|
1814
|
+
}
|
|
1815
|
+
console.log(` Mailboxes: ${money(q.mailboxes.price_cents, cur)} per ${q.mailboxes.billing}`);
|
|
1816
|
+
console.log(bold(`Due today: ${money(q.due_today_cents, cur)}`) + ` then ${money(q.recurring_cents, cur)} per ${q.mailboxes.billing}; domains renew at ${money(q.renewal_per_year_cents, cur)} a year in total`);
|
|
1817
|
+
console.log(dim(q.currency_rule));
|
|
1818
|
+
for (const line of q.included || []) console.log(dim(` · ${line}`));
|
|
1819
|
+
console.log(`\nQuote ${bold(q.quote_id)} ${dim(`(valid until ${String(q.expires_at).slice(0, 16).replace("T", " ")} UTC)`)}`);
|
|
1820
|
+
if (q.can_order) console.log(dim(`Order it: argorant inboxes order ${q.quote_id} --senders "First Last"`));
|
|
1821
|
+
else console.log(yellow(q.next_step));
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
if (sub === "order") {
|
|
1825
|
+
const args = readFlags(argv.slice(1), { "--senders": "senders", "--sender": "senders", "--website": "website" });
|
|
1826
|
+
const quoteId = args._[0];
|
|
1827
|
+
if (!quoteId || !args.senders) die("usage: argorant inboxes order <quote-id> --senders \"Elena Vaughn[,Mark Hill]\" [--website acme.com] [-y]");
|
|
1828
|
+
const senders = String(args.senders).split(",").map((x) => x.trim()).filter(Boolean).map((full) => {
|
|
1829
|
+
const parts = full.split(/\s+/);
|
|
1830
|
+
if (parts.length < 2) die(`sender "${full}" needs a first and a last name`);
|
|
1831
|
+
return { first_name: parts[0], last_name: parts.slice(1).join(" ") };
|
|
1832
|
+
});
|
|
1833
|
+
if (!args.yes) {
|
|
1834
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1835
|
+
const ok = await new Promise((r) => rl.question(`Order the inboxes in quote ${quoteId}? You get a checkout link to pay; nothing is bought before that. [y/N] `, (a) => { rl.close(); r(/^y/i.test(a)); }));
|
|
1836
|
+
if (!ok) return console.log(dim("aborted"));
|
|
1837
|
+
}
|
|
1838
|
+
const body = { quote_id: quoteId, senders, website: args.website || "", confirmed: true };
|
|
1839
|
+
// Same command, same key: running it again returns the same order and link instead of a second order.
|
|
1840
|
+
const idem = "cli-" + crypto.createHash("sha256").update(JSON.stringify(body)).digest("hex").slice(0, 40);
|
|
1841
|
+
const r = need(await request("POST", args.base, "/api/v1/inboxes/orders", { key, body, headers: { "Idempotency-Key": idem } }), "inboxes order");
|
|
1842
|
+
if (args.json) return console.log(JSON.stringify(r, null, 2));
|
|
1843
|
+
if (!r.checkout_url) return console.log(`Order ${r.order_id}: ${r.status}. ${dim(r.message || "")}`);
|
|
1844
|
+
console.log(green("✓") + ` Order ${bold(r.order_id)}${r.existing || r.replayed ? dim(" (already placed)") : ""}`);
|
|
1845
|
+
console.log(`Pay here: ${cyan(r.checkout_url)}`);
|
|
1846
|
+
console.log(dim("Nothing is bought until you pay it; setup starts after the payment is confirmed."));
|
|
1847
|
+
console.log(dim(`Track it: argorant inboxes status ${r.order_id}`));
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
if (sub === "status" || sub === "orders") {
|
|
1851
|
+
const args = readFlags(argv.slice(1));
|
|
1852
|
+
const orderId = sub === "status" ? args._[0] : undefined;
|
|
1853
|
+
const path_ = orderId ? `/api/v1/inboxes/orders/${encodeURIComponent(orderId)}` : "/api/v1/inboxes/orders";
|
|
1854
|
+
const r = need(await request("GET", args.base, path_, { key }), "inboxes status");
|
|
1855
|
+
if (args.json) return console.log(JSON.stringify(r, null, 2));
|
|
1856
|
+
const rows = orderId ? [r] : r.orders || [];
|
|
1857
|
+
if (!rows.length) return console.log(dim("No inbox orders yet. Start with: argorant inboxes quote --mailboxes 100 --company <your website>"));
|
|
1858
|
+
for (const o of rows) {
|
|
1859
|
+
const mb = o.mailboxes || {};
|
|
1860
|
+
console.log(`${bold(o.order_id)} ${o.status}${o.step ? dim(` · ${o.step}`) : ""} ${dim(`${mb.created || 0}/${mb.total || 0} mailboxes`)}${o.aging_ends_at ? dim(` · aging ends ${o.aging_ends_at}`) : ""}`);
|
|
1861
|
+
for (const d of o.domains || []) {
|
|
1862
|
+
console.log(dim(` ${d.name}${d.expires_at ? ` · expires ${String(d.expires_at).slice(0, 10)}` : ""} · renews at ${money(d.renewal_per_year_cents, o.currency)}/year${d.auto_renew === false ? " · renewal off" : ""}`));
|
|
1863
|
+
}
|
|
1864
|
+
for (const b of (o.billing && o.billing.bundles) || []) {
|
|
1865
|
+
console.log(dim(` bundle ${b.bundle_id}: ${b.mailboxes} mailboxes on ${(b.domains || []).join(", ")} · ${b.status}${b.ends_at ? ` · ends ${String(b.ends_at).slice(0, 10)}` : ""}`));
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
if (sub === "cancel-bundle" || sub === "cancel-domain") {
|
|
1871
|
+
const args = readFlags(argv.slice(1), {}, { "--keep-domains": "keepDomains" });
|
|
1872
|
+
const [orderId, target] = args._;
|
|
1873
|
+
if (!orderId || !target) die(sub === "cancel-bundle" ? "usage: argorant inboxes cancel-bundle <order-id> <bundle-id> [--keep-domains] [-y]" : "usage: argorant inboxes cancel-domain <order-id> <domain> [-y]");
|
|
1874
|
+
const question = sub === "cancel-bundle"
|
|
1875
|
+
? `Cancel bundle ${target}? Billing for its mailboxes stops now (no refund); they work until the paid period ends, then they are deleted${args.keepDomains ? "" : " and its domains stop renewing"}. [y/N] `
|
|
1876
|
+
: `Stop renewing ${target}? It works until its expiry date and is not renewed. [y/N] `;
|
|
1877
|
+
if (!args.yes) {
|
|
1878
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1879
|
+
const ok = await new Promise((r) => rl.question(question, (a) => { rl.close(); r(/^y/i.test(a)); }));
|
|
1880
|
+
if (!ok) return console.log(dim("aborted"));
|
|
1881
|
+
}
|
|
1882
|
+
const base = `/api/v1/inboxes/orders/${encodeURIComponent(orderId)}`;
|
|
1883
|
+
const r = sub === "cancel-bundle"
|
|
1884
|
+
? need(await request("POST", args.base, `${base}/bundles/${encodeURIComponent(target)}/cancel`, { key, body: { keep_domains: !!args.keepDomains } }), "inboxes cancel-bundle")
|
|
1885
|
+
: need(await request("POST", args.base, `${base}/domains/${encodeURIComponent(target)}/cancel`, { key, body: {} }), "inboxes cancel-domain");
|
|
1886
|
+
if (args.json) return console.log(JSON.stringify(r, null, 2));
|
|
1887
|
+
return console.log(green("✓") + (sub === "cancel-bundle" ? ` Bundle ${target} cancelled${r.existing ? dim(" (already)") : ""}` : ` ${target} will not renew`));
|
|
1888
|
+
}
|
|
1889
|
+
die(`unknown inboxes subcommand: ${sub} (list, set, disconnect, connect-google, suggest, quote, order, status, cancel-bundle, cancel-domain)`);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
function money(cents, currency) {
|
|
1893
|
+
if (cents === null || cents === undefined) return "?";
|
|
1894
|
+
const sign = String(currency || "usd").toLowerCase() === "eur" ? "€" : "$";
|
|
1895
|
+
return sign + (Number(cents) / 100).toFixed(2);
|
|
1767
1896
|
}
|
|
1768
1897
|
|
|
1769
1898
|
function help() {
|
|
@@ -1795,7 +1924,8 @@ ${bold("COMMANDS")}
|
|
|
1795
1924
|
${cyan("verify")} --file emails.csv -o out.csv Bulk-verify your own list ${dim("(recent re-checks free)")}
|
|
1796
1925
|
${cyan("campaigns")} ... Email outreach: create, write, enroll, launch ${dim("(argorant campaigns help)")}
|
|
1797
1926
|
${cyan("inboxes")} list | connect-google Connected mailboxes; connect a Google Workspace
|
|
1798
|
-
${cyan("
|
|
1927
|
+
${cyan("inboxes")} quote | order | status New mailboxes on new domains ${dim("(you pay each order at checkout)")}
|
|
1928
|
+
${cyan("inbox")} list | read | reply | forward | classify Replies across campaigns; answer in-thread or forward
|
|
1799
1929
|
${cyan("blocklist")} list | add | remove Addresses and domains never contacted
|
|
1800
1930
|
|
|
1801
1931
|
${bold("FILTERS")}
|
package/package.json
CHANGED