argorant 0.6.0 → 0.7.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.
Files changed (3) hide show
  1. package/README.md +10 -1
  2. package/bin/argorant.js +199 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -136,7 +136,16 @@ argorant campaigns leads add <campaign> --list <list-id> # a saved lis
136
136
  argorant campaigns leads add <campaign> --csv leads.csv # email, first_name, last_name, company, title
137
137
  argorant campaigns senders set <campaign> --emails a@x.com,b@y.com # or --all
138
138
  argorant campaigns launch <campaign> [-y] # asks for confirmation
139
- argorant campaigns pause | stop | status <campaign>
139
+ argorant campaigns update <campaign> --daily-limit 40 --window 08:00-17:00 --timezone Europe/Berlin --timezone-mode lead --gap 60-120 --tags q4
140
+ argorant campaigns leads list <campaign> [--status replied] | leads remove <campaign> <email>
141
+ argorant campaigns senders remove <campaign> <mailbox>
142
+ argorant campaigns replies <campaign>
143
+ argorant campaigns pause | stop | status | delete <campaign>
144
+
145
+ argorant inbox list [--folder replies|interested|not_interested|out_of_office|bounces|unsubscribes] [--campaign <c>] [--q text]
146
+ argorant inbox read <thread-id>
147
+ argorant inbox reply <thread-id> --body "..." [-y]
148
+ argorant inbox classify <thread-id> meeting_booked
140
149
 
141
150
  argorant inboxes list
142
151
  argorant inboxes connect-google --admin admin@yourdomain.com # lists mailboxes (or the delegation setup to do first)
package/bin/argorant.js CHANGED
@@ -1119,35 +1119,19 @@ async function campaignsList(argv) {
1119
1119
 
1120
1120
  async function campaignsCreate(argv) {
1121
1121
  const key = requireKey();
1122
- const args = readFlags(
1123
- argv,
1124
- { "--name": "name", "--timezone": "timezone", "--daily-limit": "dailyLimit", "--gap": "gap", "--start": "start" },
1125
- { "--html": "html" }
1126
- );
1122
+ const args = readFlags(argv, CAMPAIGN_SETTING_FLAGS, CAMPAIGN_SETTING_BOOLS);
1127
1123
  const name = (args.name || "").trim();
1128
1124
  if (!name) {
1129
- die('usage: argorant campaigns create --name "<name>" [--daily-limit 100] [--timezone America/New_York] [--gap 60-120] [--start YYYY-MM-DD] [--html]');
1130
- }
1131
- const body = { name };
1132
- if (args.timezone) body.timezone = args.timezone;
1133
- if (args.dailyLimit) body.daily_limit = parseLimit(args.dailyLimit, "--daily-limit");
1134
- if (args.gap) {
1135
- const m = /^(\d+)(?:-(\d+))?$/.exec(args.gap);
1136
- if (!m) die("--gap must look like 60-120 (minutes between two sends from one mailbox)");
1137
- body.gap_minutes_min = Number(m[1]);
1138
- body.gap_minutes_max = Number(m[2] || m[1]);
1139
- }
1140
- if (args.start) {
1141
- if (!/^\d{4}-\d{2}-\d{2}$/.test(args.start)) die("--start must be YYYY-MM-DD");
1142
- body.planned_start_date = args.start;
1125
+ die('usage: argorant campaigns create --name "<name>" [--daily-limit 100] [--per-mailbox-limit 20] [--timezone America/New_York] [--timezone-mode fixed|lead] [--window 08:00-17:00] [--no-skip-weekends] [--gap 60-120] [--no-unsubscribe] [--start YYYY-MM-DD] [--tags a,b]');
1143
1126
  }
1144
- if (args.html) body.plain_text = false;
1127
+ const body = settingsFromFlags(args);
1128
+ body.name = name;
1145
1129
  const res = await request("POST", args.base, "/api/v1/campaigns", { key, body });
1146
1130
  const r = need(res, "campaigns create");
1147
1131
  const c = r.campaign || r;
1148
1132
  if (args.json) return console.log(JSON.stringify(r, null, 2));
1149
1133
  console.log(green("✓") + ` Created campaign ${bold(c.name || name)} ${dim(c.id)}`);
1150
- console.log(dim(` ${c.status || "draft"} · ${c.default_timezone || body.timezone || "America/New_York"} · ${c.daily_limit || body.daily_limit || 100} a day`));
1134
+ printSettings(c);
1151
1135
  console.log(dim(`Next: argorant campaigns emails set ${c.id} --step 1 --subject "..." --body-file ./copy.txt`));
1152
1136
  }
1153
1137
 
@@ -1205,8 +1189,17 @@ async function campaignsEmails(argv) {
1205
1189
  // senders: which connected mailboxes send this campaign.
1206
1190
  async function campaignsSenders(argv) {
1207
1191
  const sub = argv[0];
1208
- if (sub !== "set") die("usage: argorant campaigns senders set <campaign> --emails a@x.com,b@y.com | --all");
1209
1192
  const key = requireKey();
1193
+ if (sub === "remove") {
1194
+ const args = readFlags(argv.slice(1));
1195
+ const [identifier, email] = args._;
1196
+ if (!identifier || !email) die("usage: argorant campaigns senders remove <campaign> <mailbox@domain>");
1197
+ const campaignId = await resolveCampaign(args.base, key, identifier);
1198
+ const r = need(await request("DELETE", args.base, `/api/v1/campaigns/${campaignId}/senders/${encodeURIComponent(email)}`, { key }), "campaigns senders remove");
1199
+ if (args.json) return console.log(JSON.stringify(r, null, 2));
1200
+ return console.log(green("✓") + ` Detached ${r.detached}`);
1201
+ }
1202
+ if (sub !== "set") die("usage: argorant campaigns senders set <campaign> --emails a@x.com,b@y.com | --all\n or: argorant campaigns senders remove <campaign> <mailbox>");
1210
1203
  const args = readFlags(argv.slice(1), { "--emails": "emails" }, { "--all": "all" });
1211
1204
  const identifier = args._[0];
1212
1205
  if (!identifier) die("usage: argorant campaigns senders set <campaign> --emails a@x.com,b@y.com | --all");
@@ -1330,8 +1323,14 @@ async function campaignsLeads(argv) {
1330
1323
  'usage: argorant campaigns leads add <campaign> --list <list-id>\n' +
1331
1324
  ' or: argorant campaigns leads add <campaign> --csv <file> (columns: email, first_name, last_name, company, title)\n' +
1332
1325
  ' or: argorant campaigns leads add <campaign> --query "..." [filters] -n <n> (operator keys)';
1333
- if (sub !== "add") die(usage);
1334
1326
  const key = requireKey();
1327
+ if (sub === "list" || sub === "remove") {
1328
+ const args = readFlags(argv.slice(1), { "--status": "status", "--q": "q", "-n": "limit", "--limit": "limit", "--offset": "offset" });
1329
+ if (!args._[0]) die(`usage: argorant campaigns leads ${sub} <campaign>${sub === "remove" ? " <email>" : " [--status queued|replied|bounced] [--q text] [-n 50]"}`);
1330
+ const campaignId = await resolveCampaign(args.base, key, args._[0]);
1331
+ return sub === "list" ? campaignsLeadsList(args, campaignId, key) : campaignsLeadsRemove(args, campaignId, key);
1332
+ }
1333
+ if (sub !== "add") die(usage);
1335
1334
  const valueFlags = { ...CAMPAIGN_FILTER_VALUE_FLAGS, "--csv": "csv", "--file": "csv", "--list": "list", "-n": "limit", "--limit": "limit" };
1336
1335
  const args = readFlags(argv.slice(1), valueFlags, { ...CAMPAIGN_FILTER_BOOL_FLAGS, "--include-catch-all": "includeCatchAll" });
1337
1336
  const identifier = args._[0];
@@ -1406,7 +1405,8 @@ async function campaignsStatus(argv) {
1406
1405
  const c = r.campaign || {};
1407
1406
  console.log(`${bold(c.name || "—")} ${dim(c.id)}`);
1408
1407
  console.log(` status ${bold(c.status)} · leads ${Number(c.lead_count || 0).toLocaleString()} · sent ${Number(c.sent_count || 0).toLocaleString()} · replies ${Number(c.replied_count || 0).toLocaleString()} · bounced ${Number(c.bounced_count || 0).toLocaleString()}`);
1409
- console.log(` emails ${(r.emails || []).length} · senders ${(r.senders || []).length} · ${c.daily_limit || "?"} a day · ${c.default_timezone || ""}`);
1408
+ console.log(` emails ${(r.emails || []).length} · senders ${(r.senders || []).length}`);
1409
+ printSettings({ settings: r.settings });
1410
1410
  for (const e of r.emails || []) console.log(dim(` ${e.step}. ${e.subject || "(same thread)"}${e.step > 1 ? ` · +${e.delay_days || 0} days` : ""}`));
1411
1411
  if (r.launch_blockers && r.launch_blockers.length) {
1412
1412
  console.log(yellow(" before launch:"));
@@ -1415,6 +1415,159 @@ async function campaignsStatus(argv) {
1415
1415
  if (c.url) console.log(dim(` ${c.url}`));
1416
1416
  }
1417
1417
 
1418
+ const CAMPAIGN_SETTING_FLAGS = {
1419
+ "--name": "name", "--daily-limit": "daily_limit", "--per-mailbox-limit": "per_mailbox_daily_limit", "--max-mailboxes": "max_active_mailboxes",
1420
+ "--timezone": "timezone", "--timezone-mode": "timezone_mode", "--window": "window", "--gap": "gap", "--start": "planned_start_date",
1421
+ "--days-ahead": "schedule_days_ahead", "--tags": "tags",
1422
+ };
1423
+ const CAMPAIGN_SETTING_BOOLS = { "--skip-weekends": "skip_weekends", "--no-skip-weekends": "no_skip_weekends", "--unsubscribe": "include_unsubscribe", "--no-unsubscribe": "no_unsubscribe" };
1424
+
1425
+ function settingsFromFlags(args) {
1426
+ const body = {};
1427
+ for (const key of ["name", "timezone", "timezone_mode", "planned_start_date"]) if (args[key] !== undefined) body[key] = args[key];
1428
+ for (const key of ["daily_limit", "per_mailbox_daily_limit", "max_active_mailboxes", "schedule_days_ahead"]) if (args[key] !== undefined) body[key] = parseLimit(args[key], key);
1429
+ if (args.window) {
1430
+ const m = /^(\d{1,2}:\d{2})-(\d{1,2}:\d{2})$/.exec(args.window);
1431
+ if (!m) die("--window must look like 08:00-17:00");
1432
+ body.window_start = m[1].padStart(5, "0"); body.window_end = m[2].padStart(5, "0");
1433
+ }
1434
+ if (args.gap) {
1435
+ const m = /^(\d+)(?:-(\d+))?$/.exec(args.gap);
1436
+ if (!m) die("--gap must look like 60-120 (minutes between two sends from one mailbox)");
1437
+ body.gap_minutes_min = Number(m[1]); body.gap_minutes_max = Number(m[2] || m[1]);
1438
+ }
1439
+ if (args.tags !== undefined) body.tags = String(args.tags).split(",").map((t) => t.trim()).filter(Boolean);
1440
+ if (args.skip_weekends) body.skip_weekends = true;
1441
+ if (args.no_skip_weekends) body.skip_weekends = false;
1442
+ if (args.include_unsubscribe) body.include_unsubscribe = true;
1443
+ if (args.no_unsubscribe) body.include_unsubscribe = false;
1444
+ return body;
1445
+ }
1446
+
1447
+ function printSettings(c) {
1448
+ const s = c.settings || {};
1449
+ console.log(dim(` ${s.daily_limit || "?"} a day` + (s.per_mailbox_daily_limit ? ` · ${s.per_mailbox_daily_limit} per mailbox` : "") + ` · ${s.window_start || "?"}-${s.window_end || "?"} ${s.timezone || ""}${s.timezone_mode === "lead" ? " (recipient local time)" : ""}` +
1450
+ ` · ${s.skip_weekends ? "weekdays" : "every day"} · gap ${s.gap_minutes_min || "?"}-${s.gap_minutes_max || "?"} min` + (s.planned_start_date ? ` · starts ${s.planned_start_date}` : "") + (s.include_unsubscribe === false ? " · no opt-out line" : "") + (s.tags && s.tags.length ? ` · ${s.tags.join(", ")}` : "")));
1451
+ }
1452
+
1453
+ async function campaignsUpdate(argv) {
1454
+ const key = requireKey();
1455
+ const args = readFlags(argv, CAMPAIGN_SETTING_FLAGS, CAMPAIGN_SETTING_BOOLS);
1456
+ const identifier = args._[0];
1457
+ if (!identifier) die('usage: argorant campaigns update <campaign> [--name "<n>"] [--daily-limit 40] [--per-mailbox-limit 20] [--max-mailboxes 10] [--timezone Europe/Berlin] [--timezone-mode fixed|lead] [--window 08:00-17:00] [--skip-weekends|--no-skip-weekends] [--gap 60-120] [--unsubscribe|--no-unsubscribe] [--start YYYY-MM-DD] [--tags a,b]');
1458
+ const campaignId = await resolveCampaign(args.base, key, identifier);
1459
+ const body = settingsFromFlags(args);
1460
+ if (!Object.keys(body).length) die("nothing to change (see: argorant campaigns help)");
1461
+ const r = need(await request("PATCH", args.base, `/api/v1/campaigns/${campaignId}`, { key, body }), "campaigns update");
1462
+ if (args.json) return console.log(JSON.stringify(r, null, 2));
1463
+ console.log(green("✓") + ` Updated ${(r.changed || []).join(", ")}`);
1464
+ printSettings(r.campaign || {});
1465
+ }
1466
+
1467
+ async function campaignsDelete(argv) {
1468
+ const key = requireKey();
1469
+ const args = readFlags(argv);
1470
+ const identifier = args._[0];
1471
+ if (!identifier) die("usage: argorant campaigns delete <campaign> [-y]");
1472
+ const campaignId = await resolveCampaign(args.base, key, identifier);
1473
+ if (!args.yes) {
1474
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1475
+ const ok = await new Promise((r) => rl.question(`Delete campaign ${campaignId} with all its leads and results? [y/N] `, (a) => { rl.close(); r(/^y/i.test(a)); }));
1476
+ if (!ok) return console.log(dim("aborted"));
1477
+ }
1478
+ const r = need(await request("DELETE", args.base, `/api/v1/campaigns/${campaignId}`, { key }), "campaigns delete");
1479
+ if (args.json) return console.log(JSON.stringify(r, null, 2));
1480
+ console.log(green("✓") + ` Deleted ${campaignId}`);
1481
+ }
1482
+
1483
+ async function campaignsLeadsList(args, campaignId, key) {
1484
+ const query = { limit: args.limit || 50, offset: args.offset || 0 };
1485
+ if (args.status) query.status = args.status;
1486
+ if (args.q) query.q = args.q;
1487
+ const r = need(await request("GET", args.base, `/api/v1/campaigns/${campaignId}/leads`, { key, query }), "campaigns leads");
1488
+ if (args.json) return console.log(JSON.stringify(r, null, 2));
1489
+ console.log(dim(`${r.total} lead(s)`));
1490
+ for (const l of r.leads || []) console.log(`${l.email} ${dim([l.first_name, l.last_name].filter(Boolean).join(" ") || "")} ${l.status}${l.current_step ? dim(` step ${l.current_step}`) : ""}${l.replied_at ? green(" replied") : ""}${l.bounced_at ? red(" bounced") : ""}`);
1491
+ }
1492
+
1493
+ async function campaignsLeadsRemove(args, campaignId, key) {
1494
+ const email = args._[1];
1495
+ if (!email) die("usage: argorant campaigns leads remove <campaign> <email>");
1496
+ const r = need(await request("DELETE", args.base, `/api/v1/campaigns/${campaignId}/leads/${encodeURIComponent(email)}`, { key }), "campaigns leads remove");
1497
+ if (args.json) return console.log(JSON.stringify(r, null, 2));
1498
+ console.log(green("✓") + ` Removed ${r.removed}`);
1499
+ }
1500
+
1501
+ async function campaignsReplies(argv) {
1502
+ const key = requireKey();
1503
+ const args = readFlags(argv, { "-n": "limit", "--limit": "limit" });
1504
+ const identifier = args._[0];
1505
+ if (!identifier) die("usage: argorant campaigns replies <campaign> [-n 50]");
1506
+ const campaignId = await resolveCampaign(args.base, key, identifier);
1507
+ const r = need(await request("GET", args.base, `/api/v1/campaigns/${campaignId}/replies`, { key, query: { limit: args.limit || 50 } }), "campaigns replies");
1508
+ if (args.json) return console.log(JSON.stringify(r, null, 2));
1509
+ if (!(r.replies || []).length) return console.log(dim("No replies yet."));
1510
+ for (const e of r.replies) {
1511
+ console.log(`${bold(e.from_email || "")} ${dim([e.first_name, e.last_name].filter(Boolean).join(" "))} ${e.classification || ""} ${dim(e.created_at || "")}`);
1512
+ console.log(dim(` ${(e.subject || "").slice(0, 80)} — ${(e.body || "").replace(/\s+/g, " ").slice(0, 140)}`));
1513
+ }
1514
+ }
1515
+
1516
+ async function cmdInboxThreads(argv) {
1517
+ const sub = argv[0] || "list";
1518
+ const key = requireKey();
1519
+ if (sub === "list") {
1520
+ const args = readFlags(argv.slice(1), { "--folder": "folder", "--campaign": "campaign", "--classification": "classification", "--q": "q", "-n": "limit", "--limit": "limit" });
1521
+ const query = { folder: args.folder || "replies", limit: args.limit || 50 };
1522
+ if (args.classification) query.classification = args.classification;
1523
+ if (args.q) query.q = args.q;
1524
+ if (args.campaign) query.campaign_id = await resolveCampaign(args.base, key, args.campaign);
1525
+ const r = need(await request("GET", args.base, "/api/v1/inbox", { key, query }), "inbox");
1526
+ if (args.json) return console.log(JSON.stringify(r, null, 2));
1527
+ if (!(r.events || []).length) return console.log(dim(`No conversations in ${query.folder}.`));
1528
+ for (const e of r.events) {
1529
+ console.log(`${dim(e.id)}`);
1530
+ console.log(` ${bold(e.from_email || e.lead_email || "")} ${e.classification || ""} ${dim(e.campaign_name || "")} ${dim(e.created_at || "")}`);
1531
+ console.log(dim(` ${(e.subject || "").slice(0, 80)} — ${(e.body || e.snippet || "").replace(/\s+/g, " ").slice(0, 140)}`));
1532
+ }
1533
+ return;
1534
+ }
1535
+ if (sub === "read") {
1536
+ const args = readFlags(argv.slice(1));
1537
+ const id = args._[0]; if (!id) die("usage: argorant inbox read <thread-id>");
1538
+ const r = need(await request("GET", args.base, `/api/v1/inbox/${encodeURIComponent(id)}`, { key }), "inbox read");
1539
+ if (args.json) return console.log(JSON.stringify(r, null, 2));
1540
+ const e = r.event || {};
1541
+ console.log(`${bold(e.from_email || e.lead_email || "")} ${e.classification || ""} ${dim(e.campaign_name || "")}\n${dim(e.subject || "")}\n\n${e.body || ""}`);
1542
+ return;
1543
+ }
1544
+ if (sub === "reply") {
1545
+ const args = readFlags(argv.slice(1), { "--body": "body", "--body-file": "bodyFile" });
1546
+ const id = args._[0]; if (!id) die("usage: argorant inbox reply <thread-id> --body \"...\" | --body-file reply.txt [-y]");
1547
+ let body = args.body;
1548
+ if (args.bodyFile) { try { body = fs.readFileSync(args.bodyFile, "utf8"); } catch { die(`cannot read file: ${args.bodyFile}`); } }
1549
+ if (!body || !body.trim()) die("give the reply text with --body or --body-file");
1550
+ if (!args.yes) {
1551
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1552
+ const ok = await new Promise((r) => rl.question(`Send this reply now? [y/N] `, (a) => { rl.close(); r(/^y/i.test(a)); }));
1553
+ if (!ok) return console.log(dim("aborted"));
1554
+ }
1555
+ const r = need(await request("POST", args.base, `/api/v1/inbox/${encodeURIComponent(id)}/reply`, { key, body: { body } }), "inbox reply");
1556
+ if (args.json) return console.log(JSON.stringify(r, null, 2));
1557
+ console.log(green("✓") + " Reply sent");
1558
+ return;
1559
+ }
1560
+ if (sub === "classify") {
1561
+ const args = readFlags(argv.slice(1));
1562
+ const [id, cl] = args._; if (!id || !cl) die("usage: argorant inbox classify <thread-id> interested|not_interested|out_of_office|meeting_booked|referral|other");
1563
+ const r = need(await request("POST", args.base, `/api/v1/inbox/${encodeURIComponent(id)}/classify`, { key, body: { classification: cl } }), "inbox classify");
1564
+ if (args.json) return console.log(JSON.stringify(r, null, 2));
1565
+ console.log(green("✓") + ` ${r.classification}`);
1566
+ return;
1567
+ }
1568
+ die(`unknown inbox subcommand: ${sub} (list, read, reply, classify)`);
1569
+ }
1570
+
1418
1571
  function campaignsHelp() {
1419
1572
  const p = bold("argorant campaigns");
1420
1573
  console.log(`
@@ -1424,11 +1577,20 @@ Works with any Argorant API key. Nothing is sent until you launch.
1424
1577
 
1425
1578
  ${bold("USAGE")}
1426
1579
  ${p} list
1427
- ${p} create --name "<name>" [--daily-limit 100] [--timezone <tz>] [--gap 60-120] [--start YYYY-MM-DD]
1580
+ ${p} create --name "<name>" [settings]
1581
+ ${p} update <campaign> [settings] ${dim("only the flags you pass change")}
1582
+ settings: --daily-limit 40 --per-mailbox-limit 20 --max-mailboxes 10 --timezone Europe/Berlin
1583
+ --timezone-mode fixed|lead --window 08:00-17:00 --skip-weekends|--no-skip-weekends
1584
+ --gap 60-120 --unsubscribe|--no-unsubscribe --start YYYY-MM-DD --tags a,b --name "<n>"
1428
1585
  ${p} emails set <campaign> --file emails.json
1429
1586
  ${p} emails set <campaign> --step <n> --subject "..." (--body-file <path> | --body <text|->) [--delay-days <d>]
1430
1587
  ${p} leads add <campaign> --list <list-id> | --csv <file> [--include-catch-all]
1588
+ ${p} leads list <campaign> [--status replied] [--q text] [-n 50]
1589
+ ${p} leads remove <campaign> <email>
1431
1590
  ${p} senders set <campaign> --emails a@x.com,b@y.com | --all
1591
+ ${p} senders remove <campaign> <mailbox>
1592
+ ${p} replies <campaign> [-n 50]
1593
+ ${p} delete <campaign> [-y]
1432
1594
  ${p} launch <campaign> [-y] ${dim("asks for confirmation; sends real email")}
1433
1595
  ${p} pause <campaign>
1434
1596
  ${p} stop <campaign>
@@ -1437,6 +1599,11 @@ ${bold("USAGE")}
1437
1599
  ${bold("argorant inboxes")} list
1438
1600
  ${bold("argorant inboxes")} connect-google --admin <admin@yourdomain.com> [--emails a,b | --all]
1439
1601
 
1602
+ ${bold("argorant inbox")} list [--folder replies|interested|not_interested|out_of_office|bounces|unsubscribes|all] [--campaign <c>] [--q text]
1603
+ ${bold("argorant inbox")} read <thread-id>
1604
+ ${bold("argorant inbox")} reply <thread-id> --body "..." | --body-file reply.txt [-y]
1605
+ ${bold("argorant inbox")} classify <thread-id> interested|not_interested|out_of_office|meeting_booked|referral|other
1606
+
1440
1607
  ${dim("<campaign> accepts an id or an unambiguous case-insensitive name prefix.")}
1441
1608
  ${dim("Variables in emails: {{first_name}} {{last_name}} {{company}} {{title}}. Spintax {a|b} varies phrasing.")}
1442
1609
  ${dim("Every address is verified before it is enrolled; only valid ones send unless --include-catch-all.")}
@@ -1463,6 +1630,10 @@ async function cmdCampaigns(argv) {
1463
1630
  const table = {
1464
1631
  list: campaignsList,
1465
1632
  create: campaignsCreate,
1633
+ update: campaignsUpdate,
1634
+ set: campaignsUpdate,
1635
+ delete: campaignsDelete,
1636
+ replies: campaignsReplies,
1466
1637
  emails: campaignsEmails,
1467
1638
  steps: campaignsSteps,
1468
1639
  senders: campaignsSenders,
@@ -1551,6 +1722,7 @@ ${bold("COMMANDS")}
1551
1722
  ${cyan("verify")} --file emails.csv -o out.csv Bulk-verify your own list ${dim("(recent re-checks free)")}
1552
1723
  ${cyan("campaigns")} ... Email outreach: create, write, enroll, launch ${dim("(argorant campaigns help)")}
1553
1724
  ${cyan("inboxes")} list | connect-google Connected mailboxes; connect a Google Workspace
1725
+ ${cyan("inbox")} list | read | reply | classify Replies across campaigns; answer in-thread
1554
1726
 
1555
1727
  ${bold("FILTERS")}
1556
1728
  --keywords <k> Comma = OR. The widest, most reliable filter - prefer it
@@ -1608,9 +1780,10 @@ async function main() {
1608
1780
  // `campaigns` has its own flag vocabulary (--step, --count, --pool, --csv, ...)
1609
1781
  // handled by readFlags — it never goes through the generic filter parser
1610
1782
  // below, which would reject those flags as unknown.
1611
- if (cmd === "campaigns" || cmd === "campaign" || cmd === "inboxes" || cmd === "inbox") {
1783
+ if (cmd === "campaigns" || cmd === "campaign" || cmd === "inboxes" || cmd === "mailboxes" || cmd === "inbox" || cmd === "unibox") {
1612
1784
  try {
1613
1785
  if (cmd.startsWith("campaign")) await cmdCampaigns(argv.slice(1));
1786
+ else if (cmd === "inbox" || cmd === "unibox") await cmdInboxThreads(argv.slice(1));
1614
1787
  else await cmdInboxes(argv.slice(1));
1615
1788
  } catch (e) {
1616
1789
  die(e && e.message ? e.message : String(e));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argorant",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Search, count, reveal, export, and verify B2B contacts from the Argorant database — from your terminal, scripts, or coding agent.",
5
5
  "bin": {
6
6
  "argorant": "bin/argorant.js"