@myapihq/cli 1.1.0-wip.5 → 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.
Files changed (125) hide show
  1. package/dist/commands/{email.d.ts → audience.d.ts} +2 -0
  2. package/dist/commands/audience.js +245 -0
  3. package/dist/commands/auth.d.ts +5 -2
  4. package/dist/commands/auth.js +133 -5
  5. package/dist/commands/billing.d.ts +2 -0
  6. package/dist/commands/billing.js +18 -1
  7. package/dist/commands/company.d.ts +6 -0
  8. package/dist/commands/company.js +149 -0
  9. package/dist/commands/config.d.ts +2 -0
  10. package/dist/commands/config.js +20 -0
  11. package/dist/commands/crm/companies.d.ts +6 -0
  12. package/dist/commands/crm/companies.js +193 -0
  13. package/dist/commands/crm/contacts.d.ts +6 -0
  14. package/dist/commands/crm/contacts.js +241 -0
  15. package/dist/commands/crm/index.d.ts +6 -0
  16. package/dist/commands/crm/index.js +74 -0
  17. package/dist/commands/database.d.ts +6 -0
  18. package/dist/commands/database.js +259 -0
  19. package/dist/commands/domain.d.ts +4 -0
  20. package/dist/commands/domain.js +166 -8
  21. package/dist/commands/email/campaign.d.ts +2 -0
  22. package/dist/commands/email/campaign.js +34 -1
  23. package/dist/commands/email/index.d.ts +2 -0
  24. package/dist/commands/email/index.js +9 -0
  25. package/dist/commands/email/mailbox.d.ts +2 -0
  26. package/dist/commands/email/mailbox.js +5 -0
  27. package/dist/commands/email/message.d.ts +2 -0
  28. package/dist/commands/email/message.js +8 -0
  29. package/dist/commands/email/template.d.ts +2 -0
  30. package/dist/commands/email/template.js +54 -0
  31. package/dist/commands/email/verify.d.ts +7 -0
  32. package/dist/commands/email/verify.js +52 -0
  33. package/dist/commands/email/warmup.d.ts +2 -0
  34. package/dist/commands/email/warmup.js +7 -0
  35. package/dist/commands/funnel.d.ts +3 -0
  36. package/dist/commands/funnel.js +31 -0
  37. package/dist/commands/image.d.ts +2 -0
  38. package/dist/commands/image.js +43 -9
  39. package/dist/commands/keys.d.ts +2 -0
  40. package/dist/commands/keys.js +5 -0
  41. package/dist/commands/llm.d.ts +6 -0
  42. package/dist/commands/llm.js +156 -0
  43. package/dist/commands/org.d.ts +3 -0
  44. package/dist/commands/org.js +55 -0
  45. package/dist/commands/people.d.ts +6 -0
  46. package/dist/commands/people.js +136 -0
  47. package/dist/commands/pixel.d.ts +11 -3
  48. package/dist/commands/pixel.js +93 -0
  49. package/dist/commands/setup.d.ts +2 -0
  50. package/dist/commands/setup.js +57 -5
  51. package/dist/commands/status.d.ts +7 -0
  52. package/dist/commands/status.js +154 -0
  53. package/dist/commands/storage.d.ts +2 -0
  54. package/dist/commands/storage.js +9 -1
  55. package/dist/commands/update.d.ts +2 -0
  56. package/dist/commands/update.js +3 -0
  57. package/dist/commands/url.d.ts +7 -2
  58. package/dist/commands/url.js +6 -0
  59. package/dist/commands/webhook.d.ts +2 -0
  60. package/dist/commands/webhook.js +31 -6
  61. package/dist/commands/workflow.d.ts +2 -0
  62. package/dist/commands/workflow.js +19 -1
  63. package/dist/completion.d.ts +3 -0
  64. package/dist/completion.js +78 -0
  65. package/dist/config.d.ts +2 -0
  66. package/dist/exposes.d.ts +2 -0
  67. package/dist/exposes.js +13 -0
  68. package/dist/exposes.test.js +99 -0
  69. package/dist/flags.test.js +84 -0
  70. package/dist/index.js +106 -14
  71. package/dist/prompt.d.ts +15 -0
  72. package/dist/prompt.js +27 -0
  73. package/dist/registrant.d.ts +6 -0
  74. package/dist/registrant.js +155 -0
  75. package/dist/services-sync.test.d.ts +1 -0
  76. package/dist/services-sync.test.js +32 -0
  77. package/dist/skills/my-api-hq/SKILL.md +51 -45
  78. package/dist/skills/my-crm-api/README.md +42 -0
  79. package/dist/skills/my-crm-api/SKILL.md +165 -0
  80. package/dist/skills/my-crm-api/claude/.claude-plugin/plugin.json +6 -0
  81. package/dist/skills/my-database-api/README.md +41 -0
  82. package/dist/skills/my-database-api/SKILL.md +107 -0
  83. package/dist/skills/my-database-api/claude/.claude-plugin/plugin.json +6 -0
  84. package/dist/skills/my-domain-api/SKILL.md +53 -26
  85. package/dist/skills/my-email-verify-api/README.md +33 -0
  86. package/dist/skills/my-email-verify-api/SKILL.md +91 -0
  87. package/dist/skills/my-email-verify-api/claude/.claude-plugin/plugin.json +6 -0
  88. package/dist/skills/my-funnel-api/SKILL.md +41 -37
  89. package/dist/skills/my-image-api/SKILL.md +48 -44
  90. package/dist/skills/my-llm-api/README.md +35 -0
  91. package/dist/skills/my-llm-api/SKILL.md +125 -0
  92. package/dist/skills/my-llm-api/claude/.claude-plugin/plugin.json +6 -0
  93. package/dist/skills/my-storage-api/README.md +1 -1
  94. package/dist/skills/my-storage-api/SKILL.md +44 -54
  95. package/dist/skills/my-webhook-api/README.md +1 -1
  96. package/dist/skills/my-webhook-api/SKILL.md +48 -92
  97. package/dist/skills/my-workflow-api/SKILL.md +46 -115
  98. package/dist/telemetry.d.ts +12 -0
  99. package/dist/telemetry.js +80 -0
  100. package/dist/telemetry.test.d.ts +1 -0
  101. package/dist/telemetry.test.js +101 -0
  102. package/package.json +13 -8
  103. package/dist/commands/email.js +0 -586
  104. package/dist/skills/my-email-api/README.md +0 -45
  105. package/dist/skills/my-email-api/SKILL.md +0 -104
  106. package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +0 -6
  107. package/dist/skills/my-email-api/make/.gitkeep +0 -0
  108. package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
  109. package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
  110. package/dist/skills/my-funnel-api/make/.gitkeep +0 -0
  111. package/dist/skills/my-funnel-api/n8n/.gitkeep +0 -0
  112. package/dist/skills/my-image-api/make/.gitkeep +0 -0
  113. package/dist/skills/my-image-api/n8n/.gitkeep +0 -0
  114. package/dist/skills/my-storage-api/make/.gitkeep +0 -0
  115. package/dist/skills/my-storage-api/n8n/.gitkeep +0 -0
  116. package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
  117. package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
  118. package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
  119. package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
  120. package/dist/utils.test.js +0 -48
  121. /package/dist/{utils.test.d.ts → exposes.test.d.ts} +0 -0
  122. /package/dist/skills/{my-api-hq/make → my-crm-api/openapi}/.gitkeep +0 -0
  123. /package/dist/skills/{my-api-hq/n8n → my-database-api/openapi}/.gitkeep +0 -0
  124. /package/dist/skills/{my-domain-api/make → my-email-verify-api/openapi}/.gitkeep +0 -0
  125. /package/dist/skills/{my-domain-api/n8n → my-llm-api/openapi}/.gitkeep +0 -0
@@ -0,0 +1,74 @@
1
+ // `myapi crm <namespace> <subcommand>` — Two namespaces (contacts +
2
+ // companies) each with the same shape: list / search / get / create /
3
+ // update / delete / restore / promote. Contacts also have `events` to
4
+ // inspect the timeline.
5
+ import { error, info } from '../../output.js';
6
+ import * as contacts from './contacts.js';
7
+ import * as companies from './companies.js';
8
+ // Dispatcher exports an empty EXPOSES; each subcommand file declares its own.
9
+ export const EXPOSES = [];
10
+ export const SCHEMA = {
11
+ org: 'string',
12
+ // Shared filter/payload flags across contacts + companies
13
+ email: 'string',
14
+ domain: 'string',
15
+ 'first-name': 'string',
16
+ 'last-name': 'string',
17
+ name: 'string',
18
+ stage: 'string',
19
+ source: 'string',
20
+ 'company-id': 'string',
21
+ 'custom-json': 'string',
22
+ 'include-deleted': 'boolean',
23
+ 'min-last-engagement-days': 'number',
24
+ 'max-last-engagement-days': 'number',
25
+ limit: 'number',
26
+ offset: 'number',
27
+ cursor: 'string',
28
+ kind: 'string',
29
+ };
30
+ async function dispatchNamespace(ns, sub, rest, flags) {
31
+ switch (ns) {
32
+ case 'contacts': return contacts.run(sub, rest, flags);
33
+ case 'companies': return companies.run(sub, rest, flags);
34
+ default: error(`Unknown namespace: ${ns}. Run "myapi crm --help" for the list.`);
35
+ }
36
+ }
37
+ export async function run(subcommand, args, flags) {
38
+ if (!subcommand || (flags.help && !subcommand)) {
39
+ info(`Usage: myapi crm <namespace> <subcommand>
40
+
41
+ Namespaces:
42
+ contacts The people you've engaged with (private to org)
43
+ companies The companies you've engaged with (private to org)
44
+
45
+ Each namespace shares the same subcommands:
46
+ list List with no filter (alias for search)
47
+ search Filter by stage / source / email / domain / engagement
48
+ get <id> Fetch one
49
+ create <email|domain> ... Manually create
50
+ update <id> ... Patch fields (stage, names, custom JSON)
51
+ delete <id> Soft delete (events retained)
52
+ restore <id> Restore a soft-deleted row
53
+ promote <goldfox_id> Promote a Goldfox lead into your CRM
54
+
55
+ Contacts also have:
56
+ events <id> [--kind <k>] Inspect the contact's engagement timeline
57
+
58
+ Examples:
59
+ myapi crm contacts list
60
+ myapi crm contacts search --stage qualified --source goldfox
61
+ myapi crm contacts promote p_MC0xMDUuY29t.0
62
+ myapi crm contacts update <id> --stage qualified
63
+ myapi crm contacts events <id> --kind webhook_received
64
+
65
+ Auto-ingest: webhooks with a configurable email path land here as
66
+ \`webhook_received\` events on a contact automatically. Email and pixel
67
+ ingest are coming — see "myapi crm --help" later for the wiring.
68
+
69
+ Org-scoped: all commands accept --org <id> (or set default: myapi config set-org <id>).
70
+ Run "myapi crm <namespace> --help" for namespace-specific commands.`);
71
+ return;
72
+ }
73
+ return dispatchNamespace(subcommand, args[0], args.slice(1), flags);
74
+ }
@@ -0,0 +1,6 @@
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 function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
@@ -0,0 +1,259 @@
1
+ // `myapi database <subcommand>` — KV primitive with per-org namespaces.
2
+ // Two resource groups: namespaces (container) and keys (the values).
3
+ // Surface kept flat (not nested under `database namespaces ... keys ...`)
4
+ // because most calls are key-level and we want tab-completion to land in
5
+ // one hop.
6
+ import * as fs from 'fs';
7
+ import { database as sdk, MyApiError } from '@myapihq/sdk';
8
+ import { requireConfig } from '../config.js';
9
+ import { success, error, info, printTable, printJson } from '../output.js';
10
+ import { requireOrg, requireArg } from '../helpers.js';
11
+ export const EXPOSES = [
12
+ 'GET /database/orgs/{org_id}/namespaces',
13
+ 'POST /database/orgs/{org_id}/namespaces',
14
+ 'GET /database/orgs/{org_id}/namespaces/{ns}',
15
+ 'DELETE /database/orgs/{org_id}/namespaces/{ns}',
16
+ 'GET /database/orgs/{org_id}/namespaces/{ns}/keys',
17
+ 'GET /database/orgs/{org_id}/namespaces/{ns}/keys/{key}',
18
+ 'PUT /database/orgs/{org_id}/namespaces/{ns}/keys/{key}',
19
+ 'DELETE /database/orgs/{org_id}/namespaces/{ns}/keys/{key}',
20
+ ];
21
+ export const SCHEMA = {
22
+ org: 'string',
23
+ ns: 'string',
24
+ prefix: 'string',
25
+ cursor: 'string',
26
+ limit: 'number',
27
+ values: 'boolean',
28
+ file: 'string',
29
+ 'if-match': 'string',
30
+ };
31
+ // `--values` for list-keys requires both `--values` and (for now) `=true`. We
32
+ // accept the bare flag too because that's how every other boolean works.
33
+ function wantValues(flags) {
34
+ return flags.values === true || flags.values === 'true';
35
+ }
36
+ // Resolve --ns flag. Required for every key-level op.
37
+ function requireNs(flags, usage) {
38
+ const ns = typeof flags.ns === 'string' ? flags.ns : '';
39
+ if (!ns)
40
+ error(`Missing required flag: --ns <namespace>.\nUsage: ${usage}`);
41
+ return ns;
42
+ }
43
+ // Read a JSON value from one of: positional arg, --file, or stdin ('-').
44
+ function readJsonValue(arg, flags, usage) {
45
+ let raw;
46
+ if (typeof flags.file === 'string' && flags.file) {
47
+ raw = fs.readFileSync(flags.file, 'utf-8');
48
+ }
49
+ else if (arg === '-') {
50
+ raw = fs.readFileSync(0, 'utf-8');
51
+ }
52
+ else if (arg !== undefined) {
53
+ raw = arg;
54
+ }
55
+ else {
56
+ error(`Missing required argument <value>.\nUsage: ${usage}`);
57
+ }
58
+ raw = raw.trim();
59
+ if (!raw)
60
+ error('Empty value.');
61
+ // Pre-flight size check — server caps at 256 KB.
62
+ if (Buffer.byteLength(raw, 'utf-8') > sdk.MAX_VALUE_BYTES) {
63
+ error(`Value exceeds ${sdk.MAX_VALUE_BYTES / 1024} KB limit. Use myapi storage for blobs.`);
64
+ }
65
+ try {
66
+ return JSON.parse(raw);
67
+ }
68
+ catch (e) {
69
+ error(`Value is not valid JSON: ${e.message}`);
70
+ }
71
+ }
72
+ // ── Namespaces ───────────────────────────────────────────────────────────
73
+ async function nsList(flags) {
74
+ const config = requireConfig();
75
+ const orgId = requireOrg(flags, config, 'myapi database namespaces [--org <id>]');
76
+ const res = await sdk.listNamespaces(config.api_key, orgId);
77
+ if (flags.json) {
78
+ printJson(res);
79
+ return;
80
+ }
81
+ printTable(res.namespaces.map(n => ({
82
+ name: n.name,
83
+ keys: n.key_count,
84
+ created: n.created_at,
85
+ })), { flags, empty: 'No namespaces yet. Create one: myapi database create <name>' });
86
+ }
87
+ async function nsCreate(name, flags) {
88
+ const config = requireConfig();
89
+ const orgId = requireOrg(flags, config, 'myapi database create <name> [--org <id>]');
90
+ requireArg(name, 'name', 'myapi database create <name>');
91
+ const ns = await sdk.createNamespace(config.api_key, orgId, name);
92
+ if (flags.json) {
93
+ printJson(ns);
94
+ return;
95
+ }
96
+ success(`Namespace "${ns.name}" created.`);
97
+ }
98
+ async function nsDelete(name, flags) {
99
+ const config = requireConfig();
100
+ const orgId = requireOrg(flags, config, 'myapi database delete-namespace <name> [--org <id>]');
101
+ requireArg(name, 'name', 'myapi database delete-namespace <name>');
102
+ await sdk.deleteNamespace(config.api_key, orgId, name);
103
+ success(`Namespace "${name}" and all its keys deleted.`);
104
+ }
105
+ // ── Keys ─────────────────────────────────────────────────────────────────
106
+ async function keysList(flags) {
107
+ const config = requireConfig();
108
+ const orgId = requireOrg(flags, config, 'myapi database keys --ns <ns> [--prefix <p>] [--limit N] [--values] [--cursor <c>] [--org <id>]');
109
+ const ns = requireNs(flags, 'myapi database keys --ns <ns> [--prefix <p>] [--limit N] [--values]');
110
+ const opts = {};
111
+ if (typeof flags.prefix === 'string')
112
+ opts.prefix = flags.prefix;
113
+ if (typeof flags.limit === 'number')
114
+ opts.limit = flags.limit;
115
+ if (typeof flags.cursor === 'string')
116
+ opts.cursor = flags.cursor;
117
+ if (wantValues(flags))
118
+ opts.values = true;
119
+ const res = await sdk.listKeys(config.api_key, orgId, ns, opts);
120
+ if (flags.json) {
121
+ printJson(res);
122
+ return;
123
+ }
124
+ if (res.keys.length === 0) {
125
+ info('No keys.');
126
+ return;
127
+ }
128
+ if (wantValues(flags)) {
129
+ for (const k of res.keys) {
130
+ info(`${k.key} (etag=${k.etag})`);
131
+ info(` ${JSON.stringify(k.value)}`);
132
+ }
133
+ }
134
+ else {
135
+ for (const k of res.keys)
136
+ info(k.key);
137
+ }
138
+ if (res.next_cursor)
139
+ info(`\n— More results — pass --cursor "${res.next_cursor}" to continue`);
140
+ }
141
+ async function keyGet(key, flags) {
142
+ const config = requireConfig();
143
+ const orgId = requireOrg(flags, config, 'myapi database get <key> --ns <ns> [--org <id>]');
144
+ const ns = requireNs(flags, 'myapi database get <key> --ns <ns>');
145
+ requireArg(key, 'key', 'myapi database get <key> --ns <ns>');
146
+ const entry = await sdk.getKey(config.api_key, orgId, ns, key);
147
+ if (flags.json) {
148
+ printJson(entry);
149
+ return;
150
+ }
151
+ info(JSON.stringify(entry.value));
152
+ process.stderr.write(`\n— etag=${entry.etag} · updated=${entry.updated_at}\n`);
153
+ }
154
+ async function keySet(key, valueArg, flags) {
155
+ const config = requireConfig();
156
+ const orgId = requireOrg(flags, config, 'myapi database set <key> <value-json> --ns <ns> [--if-match <etag>] [--file <path>] [--org <id>]');
157
+ const ns = requireNs(flags, 'myapi database set <key> <value-json> --ns <ns>');
158
+ requireArg(key, 'key', 'myapi database set <key> <value-json>');
159
+ const value = readJsonValue(valueArg, flags, 'myapi database set <key> <value-json> --ns <ns>');
160
+ const ifMatch = typeof flags['if-match'] === 'string' ? flags['if-match'] : undefined;
161
+ try {
162
+ const entry = await sdk.putKey(config.api_key, orgId, ns, key, value, ifMatch);
163
+ if (flags.json) {
164
+ printJson(entry);
165
+ return;
166
+ }
167
+ success(`Set ${ns}:${key} (etag=${entry.etag})`);
168
+ }
169
+ catch (err) {
170
+ if (err instanceof MyApiError && err.code === 'ETAG_MISMATCH') {
171
+ error('etag mismatch — another writer updated this key. Re-read with: myapi database get');
172
+ }
173
+ throw err;
174
+ }
175
+ }
176
+ async function keyDel(key, flags) {
177
+ const config = requireConfig();
178
+ const orgId = requireOrg(flags, config, 'myapi database del <key> --ns <ns> [--if-match <etag>] [--org <id>]');
179
+ const ns = requireNs(flags, 'myapi database del <key> --ns <ns>');
180
+ requireArg(key, 'key', 'myapi database del <key>');
181
+ const ifMatch = typeof flags['if-match'] === 'string' ? flags['if-match'] : undefined;
182
+ try {
183
+ await sdk.deleteKey(config.api_key, orgId, ns, key, ifMatch);
184
+ success(`Deleted ${ns}:${key}`);
185
+ }
186
+ catch (err) {
187
+ if (err instanceof MyApiError && err.code === 'ETAG_MISMATCH') {
188
+ error('etag mismatch — another writer updated this key. Re-read with: myapi database get');
189
+ }
190
+ throw err;
191
+ }
192
+ }
193
+ // ── Dispatcher ───────────────────────────────────────────────────────────
194
+ const SUBCOMMAND_USAGE = {
195
+ 'namespaces': 'myapi database namespaces [--org <id>] [--json]',
196
+ 'create': 'myapi database create <name> [--org <id>]',
197
+ 'delete-namespace': `myapi database delete-namespace <name> [--org <id>]
198
+
199
+ Deletes the namespace AND all keys in it. Irreversible.`,
200
+ 'keys': `myapi database keys --ns <namespace> [--prefix <p>] [--limit N] [--values] [--cursor <c>] [--org <id>] [--json]
201
+
202
+ Default render is one key per line. --values inlines each value alongside
203
+ its key + etag. --cursor takes the next_cursor printed at the end of a
204
+ previous page.`,
205
+ 'get': `myapi database get <key> --ns <namespace> [--org <id>] [--json]
206
+
207
+ Prints the value as compact JSON on stdout; etag + updated_at go to
208
+ stderr so pipelines like \`myapi database get foo --ns x | jq\` work.`,
209
+ 'set': `myapi database set <key> <value-json> --ns <namespace> [--if-match <etag>] [--file <path>] [--org <id>]
210
+
211
+ <value-json> The value as a JSON literal (e.g. '{"foo":1}', '"bar"', 'true', '42')
212
+ --file <path> Read the value from a file instead of the positional arg
213
+ Pass "-" as <value-json> to read from stdin
214
+ --if-match Compare-and-swap. Pass the etag from a previous get; returns
215
+ error if another writer updated the key in the meantime.
216
+
217
+ Value must be valid JSON and ≤ 256 KB serialized. For blobs use myapi storage.`,
218
+ 'del': `myapi database del <key> --ns <namespace> [--if-match <etag>] [--org <id>]
219
+
220
+ Delete a single key. CAS-protected via --if-match like set.`,
221
+ };
222
+ export async function run(subcommand, args, flags) {
223
+ if (!subcommand || (flags.help && !subcommand)) {
224
+ info(`Usage: myapi database <subcommand>
225
+
226
+ Namespaces:
227
+ namespaces List namespaces in the org
228
+ create <name> Create a namespace
229
+ delete-namespace <name> Delete a namespace AND all its keys (irreversible)
230
+
231
+ Keys (require --ns <namespace>):
232
+ keys List keys in a namespace
233
+ get <key> Get a key's value + etag
234
+ set <key> <value-json> Set a key. Value is JSON; CAS via --if-match
235
+ del <key> Delete a key (CAS via --if-match)
236
+
237
+ All commands accept --org <id> (or set default: myapi config set-org <id>).
238
+ Values are JSON; per-key size cap is 256 KB. Use myapi storage for binary.`);
239
+ return;
240
+ }
241
+ if (flags.help) {
242
+ const usage = SUBCOMMAND_USAGE[subcommand];
243
+ if (usage)
244
+ info(`Usage: ${usage}`);
245
+ else
246
+ info(`Unknown subcommand: ${subcommand}. Run "myapi database --help" for the list.`);
247
+ return;
248
+ }
249
+ switch (subcommand) {
250
+ case 'namespaces': return nsList(flags);
251
+ case 'create': return nsCreate(args[0], flags);
252
+ case 'delete-namespace': return nsDelete(args[0], flags);
253
+ case 'keys': return keysList(flags);
254
+ case 'get': return keyGet(args[0], flags);
255
+ case 'set': return keySet(args[0], args[1], flags);
256
+ case 'del': return keyDel(args[0], flags);
257
+ default: error(`Unknown subcommand: ${subcommand}. Run "myapi database --help" for valid subcommands.`);
258
+ }
259
+ }
@@ -1,8 +1,12 @@
1
1
  import type { FlagSchema } from '../flags.js';
2
2
  import { type Flags } from '../helpers.js';
3
+ import type { Exposes } from '../exposes.js';
4
+ export declare const EXPOSES: Exposes;
3
5
  export declare const SCHEMA: FlagSchema;
4
6
  export declare function check(domainArg: string, flags: Flags): Promise<void>;
5
7
  export declare function register(domainArg: string, flags: Flags): Promise<void>;
8
+ export declare function importCmd(domainArg: string, flags: Flags): Promise<void>;
9
+ export declare function renew(domainArg: string, flags: Flags): Promise<void>;
6
10
  export declare function list(flags: Flags): Promise<void>;
7
11
  export declare function assign(domainArg: string, flags: Flags): Promise<void>;
8
12
  export declare function unassign(domainArg: string, flags: Flags): Promise<void>;
@@ -2,7 +2,19 @@ import { domain as sdkDomain } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../config.js';
3
3
  import { success, error, info, printTable, printJson } from '../output.js';
4
4
  import { formatDate } from '../utils.js';
5
+ import { resolveRegistrantForRegister } from '../registrant.js';
5
6
  import { requireOrg, requireDomain } from '../helpers.js';
7
+ export const EXPOSES = [
8
+ 'GET /domain/orgs/{org_id}/list',
9
+ 'GET /domain/orgs/{org_id}/check/available/{name}',
10
+ 'POST /domain/orgs/{org_id}/register',
11
+ 'POST /domain/orgs/{org_id}/import',
12
+ 'POST /domain/orgs/{org_id}/{domain}/assign',
13
+ 'POST /domain/orgs/{org_id}/{domain}/renew',
14
+ 'GET /domain/orgs/{org_id}/{domain}/settings',
15
+ 'POST /domain/orgs/{org_id}/{domain}/settings',
16
+ 'GET /domain/orgs/{org_id}/{domain}/status',
17
+ ];
6
18
  export const SCHEMA = {
7
19
  org: 'string',
8
20
  domain: 'string',
@@ -11,6 +23,21 @@ export const SCHEMA = {
11
23
  security: 'string',
12
24
  'browser-check': 'string',
13
25
  'purge-cache': 'boolean',
26
+ // Poll `status` until the domain reaches a terminal state.
27
+ 'watch': 'boolean',
28
+ // WHOIS registrant — required by ICANN at register time. Resolution
29
+ // order is JSON > per-field flags > stored config > TTY prompt > error.
30
+ // See packages/cli/src/registrant.ts for the full flow.
31
+ 'registrant-json': 'string',
32
+ 'registrant-name': 'string',
33
+ 'registrant-email': 'string',
34
+ 'registrant-phone': 'string',
35
+ 'registrant-street': 'string',
36
+ 'registrant-city': 'string',
37
+ 'registrant-state': 'string',
38
+ 'registrant-postal-code': 'string',
39
+ 'registrant-country': 'string',
40
+ 'registrant-organization': 'string',
14
41
  };
15
42
  export async function check(domainArg, flags) {
16
43
  const config = requireConfig();
@@ -42,12 +69,61 @@ export async function register(domainArg, flags) {
42
69
  if (!domainArg)
43
70
  error('Missing required arguments.\nUsage: myapi domain register <domain> [--years <n>] [--org <id>]');
44
71
  const years = flags.years || 1;
45
- await sdkDomain.registerDomain(config.api_key, orgId, domainArg, years);
72
+ // Required by ICANN; resolver throws a useful error if nothing's set up
73
+ // in non-interactive contexts, or runs an interactive prompt for humans.
74
+ const registrant = await resolveRegistrantForRegister(flags, config);
75
+ await sdkDomain.registerDomain(config.api_key, orgId, domainArg, { years, registrant });
46
76
  success(`Registered ${domainArg}!`);
47
77
  info(`Assign it to your org now:\n myapi domain assign ${domainArg} --org ${orgId}`);
48
78
  info(`DNS propagation can take a few minutes — track it with: myapi domain status ${domainArg}`);
49
79
  info(`Your funnel will be live on https://${domainArg} once propagation completes.`);
50
80
  }
81
+ // Renew a registered domain for one more registration period (typically 1 year).
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
+ }
118
+ export async function renew(domainArg, flags) {
119
+ const config = requireConfig();
120
+ const orgId = requireOrg(flags, config, 'myapi domain renew <domain> [--yes] [--org <id>]');
121
+ if (!domainArg)
122
+ error('Missing required arguments.\nUsage: myapi domain renew <domain> [--yes] [--org <id>]');
123
+ await sdkDomain.renewDomain(config.api_key, orgId, domainArg);
124
+ success(`Renewed ${domainArg}.`);
125
+ info(`Track expiry with: myapi domain status ${domainArg}`);
126
+ }
51
127
  export async function list(flags) {
52
128
  const config = requireConfig();
53
129
  const orgId = requireOrg(flags, config, 'myapi domain list [--filter=all|unassigned|org] [--org <id>]');
@@ -85,21 +161,55 @@ export async function unassign(domainArg, flags) {
85
161
  await sdkDomain.unassignDomain(config.api_key, orgId, domainArg);
86
162
  success(`Unassigned ${domainArg} from org ${orgId}`);
87
163
  }
164
+ // Terminal states — stop polling under --watch.
165
+ const TERMINAL_STATUSES = new Set(['active', 'failed', 'error', 'expired']);
88
166
  export async function status(domainArg, flags) {
89
167
  const config = requireConfig();
90
- const orgId = requireOrg(flags, config, 'myapi domain status <domain> [--org <id>]');
91
- const domain = requireDomain(domainArg, flags, config, 'myapi domain status <domain> [--org <id>]');
92
- const res = await sdkDomain.getDomainStatus(config.api_key, orgId, domain);
93
- if (flags.json) {
94
- 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);
95
177
  return;
96
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) {
97
203
  info(`Domain: ${res.domain}`);
98
204
  info(`Status: ${res.status}`);
99
205
  if (res.expires_at)
100
206
  info(`Expires: ${formatDate(res.expires_at)}`);
101
- 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') {
102
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
+ }
103
213
  }
104
214
  export async function settings(domainArg, flags) {
105
215
  const config = requireConfig();
@@ -137,15 +247,54 @@ Note: --filter=all and --filter=unassigned are account-wide; --org is only used
137
247
  to authenticate the request.`,
138
248
  'check': 'myapi domain check <domain> [--org <id>] [--json]',
139
249
  'register': `myapi domain register <domain> [--years <num>] [--org <id>]
250
+ <registrant flags — see below>
140
251
 
141
252
  --years <num> Number of years to register for (default: 1)
142
253
 
143
254
  Prerequisites:
144
255
  - Sufficient balance (myapi billing balance)
145
256
  - Domain available (myapi domain check <domain>)
257
+ - WHOIS registrant info (ICANN requirement — see below)
258
+
259
+ Registrant (required at every register call):
260
+ Cheapest path: run \`myapi auth registrant set\` once. The CLI stores
261
+ your name/email/phone/address and auto-injects it on every register.
262
+
263
+ Per-call override (agent-friendly):
264
+ --registrant-json '{"name":"...", "email":"...", "phone":"+33...", "street":"...",
265
+ "city":"...", "postal_code":"...", "country_code":"DE"}'
266
+ or per-field:
267
+ --registrant-name --registrant-email --registrant-phone --registrant-street
268
+ --registrant-city --registrant-postal-code --registrant-country
269
+ --registrant-state (required for US/CA only)
270
+ --registrant-organization (optional)
271
+
272
+ If no source provides it and the terminal is interactive, you'll be
273
+ prompted for the missing fields and offered to save them.
146
274
 
147
275
  After registering: myapi domain assign <domain>
148
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.`,
291
+ 'renew': `myapi domain renew <domain> [--org <id>]
292
+
293
+ Renews a registered domain for one more registration period (typically 1 year).
294
+ Charged against the org's billing balance — confirm with "myapi billing balance"
295
+ before running.
296
+
297
+ Track expiry afterward with: myapi domain status <domain>`,
149
298
  'assign': `myapi domain assign <domain> [--org <id>]
150
299
 
151
300
  Assigns a registered domain to an org. Once assigned, your funnel is served at
@@ -153,7 +302,12 @@ https://<domain> after DNS propagation completes.
153
302
 
154
303
  To transfer to a different org: unassign first, then assign to the new org.`,
155
304
  'unassign': 'myapi domain unassign <domain> [--org <id>]',
156
- '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.`,
157
311
  'settings': `myapi domain settings <domain> [--org <id>]
158
312
 
159
313
  Gets current edge/CDN settings (security level, browser check, cache).
@@ -175,6 +329,8 @@ Subcommands:
175
329
  list List domains
176
330
  check Check domain availability
177
331
  register Register a domain
332
+ import Bring your own domain (BYOD) — registrar-agnostic
333
+ renew Renew a registered domain for another period
178
334
  assign Assign domain to an org
179
335
  unassign Unassign domain from its current org
180
336
  status Get domain status
@@ -196,6 +352,8 @@ Tip: Set defaults with "myapi config set-org <id>" / "set-domain <domain>" to sk
196
352
  case 'list': return list(flags);
197
353
  case 'check': return check(args[0], flags);
198
354
  case 'register': return register(args[0], flags);
355
+ case 'import': return importCmd(args[0], flags);
356
+ case 'renew': return renew(args[0], flags);
199
357
  case 'assign': return assign(args[0], flags);
200
358
  case 'unassign': return unassign(args[0], flags);
201
359
  case 'status': return status(args[0], flags);
@@ -1,2 +1,4 @@
1
1
  import { type Flags } from '../../helpers.js';
2
+ import type { Exposes } from '../../exposes.js';
3
+ export declare const EXPOSES: Exposes;
2
4
  export declare function run(sub: string | undefined, args: string[], flags: Flags): Promise<void>;