@myapihq/cli 1.2.0 → 1.2.1

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.
@@ -5,6 +5,7 @@ export declare const EXPOSES: Exposes;
5
5
  export declare const SCHEMA: FlagSchema;
6
6
  export declare function check(domainArg: string, flags: Flags): Promise<void>;
7
7
  export declare function register(domainArg: string, flags: Flags): Promise<void>;
8
+ export declare function importCmd(domainArg: string, flags: Flags): Promise<void>;
8
9
  export declare function renew(domainArg: string, flags: Flags): Promise<void>;
9
10
  export declare function list(flags: Flags): Promise<void>;
10
11
  export declare function assign(domainArg: string, flags: Flags): Promise<void>;
@@ -23,6 +23,8 @@ export const SCHEMA = {
23
23
  security: 'string',
24
24
  'browser-check': 'string',
25
25
  'purge-cache': 'boolean',
26
+ // Poll `status` until the domain reaches a terminal state.
27
+ 'watch': 'boolean',
26
28
  // WHOIS registrant — required by ICANN at register time. Resolution
27
29
  // order is JSON > per-field flags > stored config > TTY prompt > error.
28
30
  // See packages/cli/src/registrant.ts for the full flow.
@@ -78,6 +80,41 @@ export async function register(domainArg, flags) {
78
80
  }
79
81
  // Renew a registered domain for one more registration period (typically 1 year).
80
82
  // Charged against the org's billing balance — surface a confirmation unless --yes.
83
+ // BYOD: snapshot existing DNS records, create a CF zone, hand back the NS
84
+ // values the customer needs to set at their current registrar. No registrar
85
+ // credentials needed — backend is registrar-agnostic now.
86
+ export async function importCmd(domainArg, flags) {
87
+ const config = requireConfig();
88
+ const orgId = requireOrg(flags, config, 'myapi domain import <domain> [--org <id>]');
89
+ if (!domainArg)
90
+ error('Missing required arguments.\nUsage: myapi domain import <domain> [--org <id>]');
91
+ const res = await sdkDomain.importDomain(config.api_key, orgId, domainArg);
92
+ if (flags.json) {
93
+ printJson(res);
94
+ return;
95
+ }
96
+ success(`Imported ${res.domain} (status: ${res.status})`);
97
+ info('');
98
+ info(`Nameservers (set these at your current registrar):`);
99
+ for (const ns of res.nameservers)
100
+ info(` ${ns}`);
101
+ info('');
102
+ if (res.preserved_records.length > 0) {
103
+ info(`Preserved DNS records (${res.preserved_count} found — verify before the NS change):`);
104
+ printTable(res.preserved_records.map(r => ({ type: r.type, name: r.name, content: r.content, ttl: r.ttl })), { flags });
105
+ info('');
106
+ }
107
+ else {
108
+ info(`No DNS records detected via public probe — verify directly with your registrar before the NS change.`);
109
+ info('');
110
+ }
111
+ info(res.probe_warning);
112
+ info('');
113
+ info(res.next_step);
114
+ info('');
115
+ info(`After changing nameservers, watch activation with:`);
116
+ info(` myapi domain status ${res.domain} --watch`);
117
+ }
81
118
  export async function renew(domainArg, flags) {
82
119
  const config = requireConfig();
83
120
  const orgId = requireOrg(flags, config, 'myapi domain renew <domain> [--yes] [--org <id>]');
@@ -124,21 +161,55 @@ export async function unassign(domainArg, flags) {
124
161
  await sdkDomain.unassignDomain(config.api_key, orgId, domainArg);
125
162
  success(`Unassigned ${domainArg} from org ${orgId}`);
126
163
  }
164
+ // Terminal states — stop polling under --watch.
165
+ const TERMINAL_STATUSES = new Set(['active', 'failed', 'error', 'expired']);
127
166
  export async function status(domainArg, flags) {
128
167
  const config = requireConfig();
129
- const orgId = requireOrg(flags, config, 'myapi domain status <domain> [--org <id>]');
130
- const domain = requireDomain(domainArg, flags, config, 'myapi domain status <domain> [--org <id>]');
131
- const res = await sdkDomain.getDomainStatus(config.api_key, orgId, domain);
132
- if (flags.json) {
133
- printJson(res);
168
+ const orgId = requireOrg(flags, config, 'myapi domain status <domain> [--org <id>] [--watch]');
169
+ const domain = requireDomain(domainArg, flags, config, 'myapi domain status <domain> [--org <id>] [--watch]');
170
+ if (!flags.watch) {
171
+ const res = await sdkDomain.getDomainStatus(config.api_key, orgId, domain);
172
+ if (flags.json) {
173
+ printJson(res);
174
+ return;
175
+ }
176
+ renderStatus(res);
134
177
  return;
135
178
  }
179
+ // Watch mode: 10s × 30 (5 min), then 30s × 60 (30 min). Total ~35 min budget.
180
+ // Backend live-checks CF on each poll for `pending_ns_change` rows, so polling
181
+ // both reports state AND triggers the flip to `provisioning`.
182
+ let lastStatus = '';
183
+ const schedule = [
184
+ { count: 30, intervalMs: 10_000 },
185
+ { count: 60, intervalMs: 30_000 },
186
+ ];
187
+ info(`Watching ${domain} — press Ctrl-C to stop.`);
188
+ for (const phase of schedule) {
189
+ for (let i = 0; i < phase.count; i++) {
190
+ const res = await sdkDomain.getDomainStatus(config.api_key, orgId, domain);
191
+ if (res.status !== lastStatus) {
192
+ renderStatus(res);
193
+ lastStatus = res.status;
194
+ }
195
+ if (TERMINAL_STATUSES.has(res.status))
196
+ return;
197
+ await new Promise(r => setTimeout(r, phase.intervalMs));
198
+ }
199
+ }
200
+ info(`Watch budget exhausted (~35 min). Rerun "myapi domain status ${domain} --watch" to keep waiting.`);
201
+ }
202
+ function renderStatus(res) {
136
203
  info(`Domain: ${res.domain}`);
137
204
  info(`Status: ${res.status}`);
138
205
  if (res.expires_at)
139
206
  info(`Expires: ${formatDate(res.expires_at)}`);
140
- if (res.status === 'active')
207
+ if (res.status === 'pending_ns_change') {
208
+ info('Waiting for you to change nameservers at your current registrar. Each poll re-checks Cloudflare.');
209
+ }
210
+ if (res.status === 'active') {
141
211
  info('Note: if recently activated, the SSL certificate may still be provisioning — allow a few minutes before the site is reachable over HTTPS.');
212
+ }
142
213
  }
143
214
  export async function settings(domainArg, flags) {
144
215
  const config = requireConfig();
@@ -203,6 +274,20 @@ Registrant (required at every register call):
203
274
 
204
275
  After registering: myapi domain assign <domain>
205
276
  DNS propagation takes a few minutes — track it with: myapi domain status <domain>`,
277
+ 'import': `myapi domain import <domain> [--org <id>] [--json]
278
+
279
+ Bring your own domain (BYOD). Snapshots existing DNS records via a best-effort
280
+ public probe and creates a Cloudflare zone for the domain. Returns the
281
+ nameservers you need to set at your current registrar.
282
+
283
+ No registrar credentials needed — works with any registrar (Namecheap, GoDaddy,
284
+ Cloudflare Registrar, etc.).
285
+
286
+ After running:
287
+ 1. Verify the preserved-records table covers your MX/SPF/DKIM/DMARC etc.
288
+ 2. At your current registrar, change the nameservers to the values returned.
289
+ 3. Wait for propagation. Use "myapi domain status <domain> --watch" to track
290
+ activation — the backend live-checks Cloudflare each time you poll.`,
206
291
  'renew': `myapi domain renew <domain> [--org <id>]
207
292
 
208
293
  Renews a registered domain for one more registration period (typically 1 year).
@@ -217,7 +302,12 @@ https://<domain> after DNS propagation completes.
217
302
 
218
303
  To transfer to a different org: unassign first, then assign to the new org.`,
219
304
  'unassign': 'myapi domain unassign <domain> [--org <id>]',
220
- 'status': 'myapi domain status <domain> [--org <id>] [--json]',
305
+ 'status': `myapi domain status <domain> [--org <id>] [--watch] [--json]
306
+
307
+ --watch Poll until the domain reaches a terminal state (active / failed /
308
+ expired). Backoff: 10s × 30 then 30s × 60 (~35 min budget). Useful
309
+ after "myapi domain import" — each poll also live-checks Cloudflare,
310
+ so polling drives the flip from pending_ns_change → provisioning.`,
221
311
  'settings': `myapi domain settings <domain> [--org <id>]
222
312
 
223
313
  Gets current edge/CDN settings (security level, browser check, cache).
@@ -239,6 +329,7 @@ Subcommands:
239
329
  list List domains
240
330
  check Check domain availability
241
331
  register Register a domain
332
+ import Bring your own domain (BYOD) — registrar-agnostic
242
333
  renew Renew a registered domain for another period
243
334
  assign Assign domain to an org
244
335
  unassign Unassign domain from its current org
@@ -261,6 +352,7 @@ Tip: Set defaults with "myapi config set-org <id>" / "set-domain <domain>" to sk
261
352
  case 'list': return list(flags);
262
353
  case 'check': return check(args[0], flags);
263
354
  case 'register': return register(args[0], flags);
355
+ case 'import': return importCmd(args[0], flags);
264
356
  case 'renew': return renew(args[0], flags);
265
357
  case 'assign': return assign(args[0], flags);
266
358
  case 'unassign': return unassign(args[0], flags);
package/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/cli",
3
3
  "license": "Apache-2.0",
4
- "version": "1.2.0",
4
+ "version": "1.2.1",
5
5
  "description": "MyAPI command-line interface",
6
6
  "type": "module",
7
7
  "files": [
@@ -27,7 +27,7 @@
27
27
  "lint:changelog": "node ../../scripts/lint-changelog.js"
28
28
  },
29
29
  "dependencies": {
30
- "@myapihq/sdk": "^1.2.0",
30
+ "@myapihq/sdk": "^1.2.1",
31
31
  "omelette": "^0.4.17"
32
32
  },
33
33
  "devDependencies": {
@@ -1,7 +0,0 @@
1
- import type { FlagSchema } from '../flags.js';
2
- import { type Flags } from '../helpers.js';
3
- import type { Exposes } from '../exposes.js';
4
- export declare const EXPOSES: Exposes;
5
- export declare const SCHEMA: FlagSchema;
6
- export declare const VERIFY_HELP = "Usage: myapi verify <email> [--org <id>] [--json]\n\nSync single-address email verification. Cheap layer only: syntax + DNS +\nMicrosoft GetCredentialType. Returns a verdict in <1s for ~50% of inputs;\nthe rest get verdict='unknown' with smtp_recommended=true.\n\nVerdicts:\n deliverable high-confidence \u2014 the address accepts mail\n undeliverable high-confidence \u2014 syntax bad, DNS missing, or Microsoft rejects\n unknown not enough signal; consider an SMTP probe (not in this API)\n\nUse --json for the full check breakdown (syntax / DNS / Microsoft probes).\n";
7
- export declare function run(arg: string | undefined, _rest: string[], flags?: Flags): Promise<void>;
@@ -1,56 +0,0 @@
1
- import { email as sdkEmail } from '@myapihq/sdk';
2
- import { requireConfig } from '../config.js';
3
- import { error, info, printJson } from '../output.js';
4
- import { requireOrg } from '../helpers.js';
5
- export const EXPOSES = [
6
- 'POST /email/orgs/{org_id}/verify',
7
- ];
8
- export const SCHEMA = {
9
- org: 'string',
10
- };
11
- export const VERIFY_HELP = `Usage: myapi verify <email> [--org <id>] [--json]
12
-
13
- Sync single-address email verification. Cheap layer only: syntax + DNS +
14
- Microsoft GetCredentialType. Returns a verdict in <1s for ~50% of inputs;
15
- the rest get verdict='unknown' with smtp_recommended=true.
16
-
17
- Verdicts:
18
- deliverable high-confidence — the address accepts mail
19
- undeliverable high-confidence — syntax bad, DNS missing, or Microsoft rejects
20
- unknown not enough signal; consider an SMTP probe (not in this API)
21
-
22
- Use --json for the full check breakdown (syntax / DNS / Microsoft probes).
23
- `;
24
- export async function run(arg, _rest, flags = {}) {
25
- if (flags.help) {
26
- info(VERIFY_HELP);
27
- return;
28
- }
29
- if (!arg)
30
- error('Missing required argument <email>.\nUsage: myapi verify <email> [--org <id>] [--json]');
31
- const config = requireConfig();
32
- const orgId = requireOrg(flags, config, 'myapi verify <email> [--org <id>]');
33
- const res = await sdkEmail.verifyEmail(config.api_key, orgId, arg);
34
- if (flags.json) {
35
- printJson(res);
36
- return;
37
- }
38
- // Compact human render. The verdict + confidence is the headline; the
39
- // checks block is verbose enough that we hide it behind --json.
40
- const verdictLabel = res.verdict === 'deliverable' ? '✓ deliverable'
41
- : res.verdict === 'undeliverable' ? '✗ undeliverable'
42
- : '? unknown';
43
- info(`Email: ${res.email}`);
44
- info(`Verdict: ${verdictLabel} (confidence ${res.confidence.toFixed(2)})`);
45
- info(`SMTP next: ${res.smtp_recommended ? 'yes — consider an SMTP probe' : 'no — verdict is definitive'}`);
46
- info(`Took: ${res.elapsed_ms}ms`);
47
- if (res.checks.syntax.detail)
48
- info(`Syntax: ${res.checks.syntax.detail}`);
49
- if (res.checks.dns && res.checks.dns.mx_records && res.checks.dns.mx_records.length > 0) {
50
- const mx = res.checks.dns.mx_records.filter(Boolean);
51
- if (mx.length > 0)
52
- info(`MX: ${mx.slice(0, 3).join(', ')}${mx.length > 3 ? ` (+${mx.length - 3} more)` : ''}`);
53
- }
54
- if (res.checks.microsoft)
55
- info(`Microsoft: ${res.checks.microsoft.verdict}`);
56
- }