posthorn 0.2.0 → 0.2.3
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 +9 -3
- package/dist/index.js +60 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,7 +106,12 @@ PRINCIPLE: domain purchase ALWAYS happens on the user's own Cloudflare account
|
|
|
106
106
|
and card. Posthorn never owns or bills for domains.
|
|
107
107
|
|
|
108
108
|
1. NEW domain → requires the user's own Cloudflare account (+ payment method).
|
|
109
|
-
- `posthorn domains check name1.com name2.io --cloudflare <
|
|
109
|
+
- `posthorn domains check name1.com name2.io --cloudflare <id>` — fast availability + price
|
|
110
|
+
- `posthorn domains reputation <domain>` — VET it before buying (Spamhaus
|
|
111
|
+
blocklist/score + archive.org prior-use). Verdicts: clean / caution (used
|
|
112
|
+
before) / avoid (blocklisted or poor reputation). Run this on the candidate
|
|
113
|
+
before `buy` — you can't tell a tainted domain from its name. (Slower + uses
|
|
114
|
+
a quota, so it's a separate explicit call, not part of `check`.)
|
|
110
115
|
- `posthorn domains buy <domain> --cloudflare <id> --contact '{...}'`
|
|
111
116
|
|
|
112
117
|
2. EXISTING domain, recommended → user's own Cloudflare account.
|
|
@@ -149,8 +154,9 @@ Anonymous tier can do setup (accounts, domains). To send/warmup, verify:
|
|
|
149
154
|
|
|
150
155
|
## Tips for agents
|
|
151
156
|
|
|
152
|
-
-
|
|
153
|
-
|
|
157
|
+
- Read commands auto-detect output: when stdout isn't a TTY (i.e. you're an agent
|
|
158
|
+
capturing output), they emit JSON automatically — no flag needed. Humans in a
|
|
159
|
+
terminal get formatted text. Force either way with `--json` / `--pretty`.
|
|
154
160
|
- Async steps (domain provisioning, nameserver propagation) need polling, not blocking.
|
|
155
161
|
- Every command stores state locally (~/.config), so the user doesn't re-enter keys.
|
|
156
162
|
- Translate everything into plain English for the user. Never show them raw JSON
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,13 @@ function getConfigPath() {
|
|
|
46
46
|
return conf.path;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// src/output.ts
|
|
50
|
+
function useJson(options = {}) {
|
|
51
|
+
if (options.json) return true;
|
|
52
|
+
if (options.pretty) return false;
|
|
53
|
+
return !process.stdout.isTTY;
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
// src/commands/auth.ts
|
|
50
57
|
async function register(options) {
|
|
51
58
|
const apiUrl = options.url ?? "https://api-production-08f2.up.railway.app";
|
|
@@ -80,7 +87,7 @@ async function register(options) {
|
|
|
80
87
|
}
|
|
81
88
|
async function status(options = {}) {
|
|
82
89
|
const config = getConfig();
|
|
83
|
-
if (options
|
|
90
|
+
if (useJson(options)) {
|
|
84
91
|
console.log(JSON.stringify({
|
|
85
92
|
loggedIn: !!config.apiKey,
|
|
86
93
|
apiUrl: config.apiUrl,
|
|
@@ -128,7 +135,7 @@ var ApiError = class extends Error {
|
|
|
128
135
|
async function api(path, options = {}) {
|
|
129
136
|
const config = getConfig();
|
|
130
137
|
if (!config.apiKey) {
|
|
131
|
-
throw new Error("Not logged in. Run: posthorn
|
|
138
|
+
throw new Error("Not logged in. Run: posthorn auth register");
|
|
132
139
|
}
|
|
133
140
|
const url = `${config.apiUrl}${path}`;
|
|
134
141
|
const res = await fetch(url, {
|
|
@@ -206,7 +213,7 @@ import chalk3 from "chalk";
|
|
|
206
213
|
import ora3 from "ora";
|
|
207
214
|
async function listDomains(options = {}) {
|
|
208
215
|
const data = await api("/api/domains");
|
|
209
|
-
if (options
|
|
216
|
+
if (useJson(options)) {
|
|
210
217
|
console.log(JSON.stringify(data.domains, null, 2));
|
|
211
218
|
return;
|
|
212
219
|
}
|
|
@@ -291,7 +298,7 @@ async function activateDomain(domainId) {
|
|
|
291
298
|
async function getDomain(domainId, options = {}) {
|
|
292
299
|
const data = await api(`/api/domains/${domainId}`);
|
|
293
300
|
const d = data.domain;
|
|
294
|
-
if (options
|
|
301
|
+
if (useJson(options)) {
|
|
295
302
|
console.log(JSON.stringify(d, null, 2));
|
|
296
303
|
return;
|
|
297
304
|
}
|
|
@@ -302,6 +309,18 @@ async function getDomain(domainId, options = {}) {
|
|
|
302
309
|
console.log(` DNS: ${d.dns_mode === "managed" ? "managed by Posthorn" : "your Cloudflare"}`);
|
|
303
310
|
if (d.cloudflare_zone_id) console.log(` Zone: ${chalk3.dim(d.cloudflare_zone_id)}`);
|
|
304
311
|
}
|
|
312
|
+
function verdictText(rep) {
|
|
313
|
+
switch (rep.verdict) {
|
|
314
|
+
case "avoid":
|
|
315
|
+
return chalk3.red(`\u26A0 ${rep.verdictLabel} \u2014 avoid`);
|
|
316
|
+
case "caution":
|
|
317
|
+
return chalk3.yellow(rep.verdictLabel);
|
|
318
|
+
case "clean":
|
|
319
|
+
return chalk3.green(rep.verdictLabel);
|
|
320
|
+
default:
|
|
321
|
+
return chalk3.dim(rep.verdictLabel);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
305
324
|
async function checkDomains(domains2, options) {
|
|
306
325
|
const spinner = ora3("Checking availability...").start();
|
|
307
326
|
const data = await api("/api/domains/check", {
|
|
@@ -309,11 +328,35 @@ async function checkDomains(domains2, options) {
|
|
|
309
328
|
body: { domains: domains2, cloudflareAccountId: options.cloudflare }
|
|
310
329
|
});
|
|
311
330
|
spinner.stop();
|
|
331
|
+
if (useJson(options)) {
|
|
332
|
+
console.log(JSON.stringify(data.results, null, 2));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
312
335
|
console.log(chalk3.bold("Domain Availability\n"));
|
|
313
336
|
for (const r of data.results) {
|
|
314
337
|
const icon = r.available ? chalk3.green(" \u2713") : chalk3.red(" \u2717");
|
|
315
338
|
const price = r.price ? chalk3.dim(`$${r.price}/yr`) : "";
|
|
316
|
-
console.log(`${icon} ${r.domain}
|
|
339
|
+
console.log(`${icon} ${String(r.domain).padEnd(30)}${price}`);
|
|
340
|
+
}
|
|
341
|
+
console.log(chalk3.dim("\n Vet a domain before buying: posthorn domains reputation <domain>"));
|
|
342
|
+
}
|
|
343
|
+
async function domainReputation(domains2, options = {}) {
|
|
344
|
+
const spinner = ora3("Checking reputation (blocklist + history)...").start();
|
|
345
|
+
const data = await api("/api/domains/reputation", {
|
|
346
|
+
method: "POST",
|
|
347
|
+
body: { domains: domains2 }
|
|
348
|
+
});
|
|
349
|
+
spinner.stop();
|
|
350
|
+
if (useJson(options)) {
|
|
351
|
+
console.log(JSON.stringify(data.results, null, 2));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
console.log(chalk3.bold("Domain Reputation\n"));
|
|
355
|
+
for (const rep of data.results) {
|
|
356
|
+
console.log(` ${chalk3.cyan(rep.domain.padEnd(28))}${verdictText(rep)}`);
|
|
357
|
+
for (const w of rep.warnings) {
|
|
358
|
+
console.log(chalk3.dim(` \u2022 ${w}`));
|
|
359
|
+
}
|
|
317
360
|
}
|
|
318
361
|
}
|
|
319
362
|
|
|
@@ -377,8 +420,8 @@ async function provisionCredentials(mailboxId, options) {
|
|
|
377
420
|
method: "POST",
|
|
378
421
|
body: { authType: options.type ?? "xoauth2" }
|
|
379
422
|
});
|
|
380
|
-
if (data.connectivity.
|
|
381
|
-
spinner.succeed(
|
|
423
|
+
if (data.connectivity.send && data.connectivity.imap) {
|
|
424
|
+
spinner.succeed(`Credentials provisioned \u2014 sending (${data.connectivity.sendMethod}) and IMAP connected!`);
|
|
382
425
|
} else {
|
|
383
426
|
spinner.warn("Credentials provisioned but connectivity issues:");
|
|
384
427
|
for (const err of data.connectivity.errors) {
|
|
@@ -410,7 +453,7 @@ async function pauseWarmup(campaignId) {
|
|
|
410
453
|
}
|
|
411
454
|
async function warmupStats(campaignId, options = {}) {
|
|
412
455
|
const data = await api(`/api/warmup/campaigns/${campaignId}`);
|
|
413
|
-
if (options
|
|
456
|
+
if (useJson(options)) {
|
|
414
457
|
console.log(JSON.stringify(data, null, 2));
|
|
415
458
|
return;
|
|
416
459
|
}
|
|
@@ -483,21 +526,23 @@ Typical flow:
|
|
|
483
526
|
posthorn auth verify unlock sending + warmup
|
|
484
527
|
posthorn warmup start <mailbox-id>
|
|
485
528
|
|
|
486
|
-
|
|
529
|
+
Output: read commands auto-detect \u2014 agents (non-TTY) get JSON, humans get
|
|
530
|
+
formatted text. Force either with --json or --pretty.
|
|
487
531
|
`);
|
|
488
532
|
program.command("guide").description("Print the full agent playbook \u2014 workflow, external steps, and how to drive the CLI. Run this first.").action(guide);
|
|
489
533
|
var auth = program.command("auth").description("Account management");
|
|
490
534
|
auth.command("register").description("Create a new account and get an API key").option("--url <url>", "API server URL", "https://api-production-08f2.up.railway.app").action(register);
|
|
491
|
-
auth.command("status").description("Show current account info and setup progress").option("--json", "
|
|
535
|
+
auth.command("status").description("Show current account info and setup progress").option("--json", "Force JSON output").option("--pretty", "Force human-readable output").action(status);
|
|
492
536
|
auth.command("logout").description("Clear stored credentials").action(logout);
|
|
493
537
|
var accounts = program.command("accounts").description("Connected accounts (Cloudflare, Workspace)");
|
|
494
538
|
accounts.command("list").description("List connected accounts").action(listAccounts);
|
|
495
539
|
accounts.command("cloudflare <token>").description("Connect a Cloudflare account").option("--label <label>", "Account label", "main").action(connectCloudflare);
|
|
496
540
|
accounts.command("workspace <admin-email>").description("Connect Google Workspace via domain-wide delegation").action(connectWorkspace);
|
|
497
541
|
var domains = program.command("domains").description("Domain management");
|
|
498
|
-
domains.command("list").description("List all domains").option("--json", "
|
|
499
|
-
domains.command("get <domain-id>").description("Get domain details (poll this until status is 'ready')").option("--json", "
|
|
500
|
-
domains.command("check <domains...>").description("Check domain availability").requiredOption("--cloudflare <account-id>", "Cloudflare account ID").action(checkDomains);
|
|
542
|
+
domains.command("list").description("List all domains").option("--json", "Force JSON output").option("--pretty", "Force human-readable output").action(listDomains);
|
|
543
|
+
domains.command("get <domain-id>").description("Get domain details (poll this until status is 'ready')").option("--json", "Force JSON output").option("--pretty", "Force human-readable output").action(getDomain);
|
|
544
|
+
domains.command("check <domains...>").description("Check domain availability + pricing (fast)").requiredOption("--cloudflare <account-id>", "Cloudflare account ID").option("--json", "Force JSON output").option("--pretty", "Force human-readable output").action(checkDomains);
|
|
545
|
+
domains.command("reputation <domains...>").description("Vet a domain before buying \u2014 blocklist + reputation + prior-use history").option("--json", "Force JSON output").option("--pretty", "Force human-readable output").action(domainReputation);
|
|
501
546
|
domains.command("buy <domain>").description("Purchase a domain").requiredOption("--cloudflare <account-id>", "Cloudflare account ID").option("--contact <json>", "Registrant contact info as JSON").action(buyDomain);
|
|
502
547
|
domains.command("connect <domain>").description("Connect an existing domain on your own Cloudflare account").requiredOption("--cloudflare <account-id>", "Cloudflare account ID").option("--workspace <account-id>", "Workspace account ID").action(connectDomain);
|
|
503
548
|
domains.command("managed <domain>").description("Add an existing domain to Posthorn-managed DNS (returns nameservers to set at your registrar)").option("--workspace <account-id>", "Workspace account ID").action(managedDomain);
|
|
@@ -511,14 +556,14 @@ var warmup = program.command("warmup").description("Email warmup management");
|
|
|
511
556
|
warmup.command("list").description("List warmup campaigns").action(listCampaigns);
|
|
512
557
|
warmup.command("start <mailbox-id>").description("Start warming a mailbox").action(startWarmup);
|
|
513
558
|
warmup.command("pause <campaign-id>").description("Pause a warmup campaign").action(pauseWarmup);
|
|
514
|
-
warmup.command("stats <campaign-id>").description("Show warmup stats and daily breakdown").option("--json", "
|
|
559
|
+
warmup.command("stats <campaign-id>").description("Show warmup stats and daily breakdown").option("--json", "Force JSON output").option("--pretty", "Force human-readable output").action(warmupStats);
|
|
515
560
|
program.hook("preAction", () => {
|
|
516
561
|
});
|
|
517
562
|
program.parseAsync(process.argv).catch((err) => {
|
|
518
563
|
if (err.statusCode === 401) {
|
|
519
564
|
console.log(chalk6.red("\n Authentication failed. Run: posthorn auth register\n"));
|
|
520
565
|
} else if (err.statusCode === 403) {
|
|
521
|
-
console.log(chalk6.red("\n Account not verified.
|
|
566
|
+
console.log(chalk6.red("\n Account not verified. Run: posthorn auth verify\n"));
|
|
522
567
|
} else if (err.statusCode === 429) {
|
|
523
568
|
console.log(chalk6.yellow("\n Rate limit exceeded. Try again in a minute.\n"));
|
|
524
569
|
} else {
|