@myapihq/cli 1.1.0-wip.4 → 1.2.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 (127) 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 +3 -0
  20. package/dist/commands/domain.js +67 -1
  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 +14 -2
  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/verify.d.ts +7 -0
  60. package/dist/commands/verify.js +56 -0
  61. package/dist/commands/webhook.d.ts +2 -0
  62. package/dist/commands/webhook.js +31 -6
  63. package/dist/commands/workflow.d.ts +2 -0
  64. package/dist/commands/workflow.js +19 -1
  65. package/dist/completion.d.ts +3 -0
  66. package/dist/completion.js +78 -0
  67. package/dist/config.d.ts +2 -0
  68. package/dist/exposes.d.ts +2 -0
  69. package/dist/exposes.js +13 -0
  70. package/dist/exposes.test.js +99 -0
  71. package/dist/flags.test.js +84 -0
  72. package/dist/index.js +106 -14
  73. package/dist/prompt.d.ts +15 -0
  74. package/dist/prompt.js +27 -0
  75. package/dist/registrant.d.ts +6 -0
  76. package/dist/registrant.js +155 -0
  77. package/dist/services-sync.test.d.ts +1 -0
  78. package/dist/services-sync.test.js +32 -0
  79. package/dist/skills/my-api-hq/SKILL.md +51 -45
  80. package/dist/skills/my-crm-api/README.md +42 -0
  81. package/dist/skills/my-crm-api/SKILL.md +165 -0
  82. package/dist/skills/my-crm-api/claude/.claude-plugin/plugin.json +6 -0
  83. package/dist/skills/my-database-api/README.md +41 -0
  84. package/dist/skills/my-database-api/SKILL.md +107 -0
  85. package/dist/skills/my-database-api/claude/.claude-plugin/plugin.json +6 -0
  86. package/dist/skills/my-domain-api/SKILL.md +53 -26
  87. package/dist/skills/my-email-verify-api/README.md +33 -0
  88. package/dist/skills/my-email-verify-api/SKILL.md +91 -0
  89. package/dist/skills/my-email-verify-api/claude/.claude-plugin/plugin.json +6 -0
  90. package/dist/skills/my-funnel-api/SKILL.md +41 -37
  91. package/dist/skills/my-image-api/SKILL.md +48 -44
  92. package/dist/skills/my-llm-api/README.md +35 -0
  93. package/dist/skills/my-llm-api/SKILL.md +125 -0
  94. package/dist/skills/my-llm-api/claude/.claude-plugin/plugin.json +6 -0
  95. package/dist/skills/my-storage-api/README.md +1 -1
  96. package/dist/skills/my-storage-api/SKILL.md +44 -54
  97. package/dist/skills/my-webhook-api/README.md +1 -1
  98. package/dist/skills/my-webhook-api/SKILL.md +48 -92
  99. package/dist/skills/my-workflow-api/SKILL.md +46 -115
  100. package/dist/telemetry.d.ts +12 -0
  101. package/dist/telemetry.js +80 -0
  102. package/dist/telemetry.test.d.ts +1 -0
  103. package/dist/telemetry.test.js +101 -0
  104. package/package.json +13 -8
  105. package/dist/commands/email.js +0 -586
  106. package/dist/skills/my-email-api/README.md +0 -45
  107. package/dist/skills/my-email-api/SKILL.md +0 -104
  108. package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +0 -6
  109. package/dist/skills/my-email-api/make/.gitkeep +0 -0
  110. package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
  111. package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
  112. package/dist/skills/my-funnel-api/make/.gitkeep +0 -0
  113. package/dist/skills/my-funnel-api/n8n/.gitkeep +0 -0
  114. package/dist/skills/my-image-api/make/.gitkeep +0 -0
  115. package/dist/skills/my-image-api/n8n/.gitkeep +0 -0
  116. package/dist/skills/my-storage-api/make/.gitkeep +0 -0
  117. package/dist/skills/my-storage-api/n8n/.gitkeep +0 -0
  118. package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
  119. package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
  120. package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
  121. package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
  122. package/dist/utils.test.js +0 -48
  123. /package/dist/{utils.test.d.ts → exposes.test.d.ts} +0 -0
  124. /package/dist/skills/{my-api-hq/make → my-crm-api/openapi}/.gitkeep +0 -0
  125. /package/dist/skills/{my-api-hq/n8n → my-database-api/openapi}/.gitkeep +0 -0
  126. /package/dist/skills/{my-domain-api/make → my-email-verify-api/openapi}/.gitkeep +0 -0
  127. /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,11 @@
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 renew(domainArg: string, flags: Flags): Promise<void>;
6
9
  export declare function list(flags: Flags): Promise<void>;
7
10
  export declare function assign(domainArg: string, flags: Flags): Promise<void>;
8
11
  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,19 @@ export const SCHEMA = {
11
23
  security: 'string',
12
24
  'browser-check': 'string',
13
25
  'purge-cache': 'boolean',
26
+ // WHOIS registrant — required by ICANN at register time. Resolution
27
+ // order is JSON > per-field flags > stored config > TTY prompt > error.
28
+ // See packages/cli/src/registrant.ts for the full flow.
29
+ 'registrant-json': 'string',
30
+ 'registrant-name': 'string',
31
+ 'registrant-email': 'string',
32
+ 'registrant-phone': 'string',
33
+ 'registrant-street': 'string',
34
+ 'registrant-city': 'string',
35
+ 'registrant-state': 'string',
36
+ 'registrant-postal-code': 'string',
37
+ 'registrant-country': 'string',
38
+ 'registrant-organization': 'string',
14
39
  };
15
40
  export async function check(domainArg, flags) {
16
41
  const config = requireConfig();
@@ -42,12 +67,26 @@ export async function register(domainArg, flags) {
42
67
  if (!domainArg)
43
68
  error('Missing required arguments.\nUsage: myapi domain register <domain> [--years <n>] [--org <id>]');
44
69
  const years = flags.years || 1;
45
- await sdkDomain.registerDomain(config.api_key, orgId, domainArg, years);
70
+ // Required by ICANN; resolver throws a useful error if nothing's set up
71
+ // in non-interactive contexts, or runs an interactive prompt for humans.
72
+ const registrant = await resolveRegistrantForRegister(flags, config);
73
+ await sdkDomain.registerDomain(config.api_key, orgId, domainArg, { years, registrant });
46
74
  success(`Registered ${domainArg}!`);
47
75
  info(`Assign it to your org now:\n myapi domain assign ${domainArg} --org ${orgId}`);
48
76
  info(`DNS propagation can take a few minutes — track it with: myapi domain status ${domainArg}`);
49
77
  info(`Your funnel will be live on https://${domainArg} once propagation completes.`);
50
78
  }
79
+ // Renew a registered domain for one more registration period (typically 1 year).
80
+ // Charged against the org's billing balance — surface a confirmation unless --yes.
81
+ export async function renew(domainArg, flags) {
82
+ const config = requireConfig();
83
+ const orgId = requireOrg(flags, config, 'myapi domain renew <domain> [--yes] [--org <id>]');
84
+ if (!domainArg)
85
+ error('Missing required arguments.\nUsage: myapi domain renew <domain> [--yes] [--org <id>]');
86
+ await sdkDomain.renewDomain(config.api_key, orgId, domainArg);
87
+ success(`Renewed ${domainArg}.`);
88
+ info(`Track expiry with: myapi domain status ${domainArg}`);
89
+ }
51
90
  export async function list(flags) {
52
91
  const config = requireConfig();
53
92
  const orgId = requireOrg(flags, config, 'myapi domain list [--filter=all|unassigned|org] [--org <id>]');
@@ -137,15 +176,40 @@ Note: --filter=all and --filter=unassigned are account-wide; --org is only used
137
176
  to authenticate the request.`,
138
177
  'check': 'myapi domain check <domain> [--org <id>] [--json]',
139
178
  'register': `myapi domain register <domain> [--years <num>] [--org <id>]
179
+ <registrant flags — see below>
140
180
 
141
181
  --years <num> Number of years to register for (default: 1)
142
182
 
143
183
  Prerequisites:
144
184
  - Sufficient balance (myapi billing balance)
145
185
  - Domain available (myapi domain check <domain>)
186
+ - WHOIS registrant info (ICANN requirement — see below)
187
+
188
+ Registrant (required at every register call):
189
+ Cheapest path: run \`myapi auth registrant set\` once. The CLI stores
190
+ your name/email/phone/address and auto-injects it on every register.
191
+
192
+ Per-call override (agent-friendly):
193
+ --registrant-json '{"name":"...", "email":"...", "phone":"+33...", "street":"...",
194
+ "city":"...", "postal_code":"...", "country_code":"DE"}'
195
+ or per-field:
196
+ --registrant-name --registrant-email --registrant-phone --registrant-street
197
+ --registrant-city --registrant-postal-code --registrant-country
198
+ --registrant-state (required for US/CA only)
199
+ --registrant-organization (optional)
200
+
201
+ If no source provides it and the terminal is interactive, you'll be
202
+ prompted for the missing fields and offered to save them.
146
203
 
147
204
  After registering: myapi domain assign <domain>
148
205
  DNS propagation takes a few minutes — track it with: myapi domain status <domain>`,
206
+ 'renew': `myapi domain renew <domain> [--org <id>]
207
+
208
+ Renews a registered domain for one more registration period (typically 1 year).
209
+ Charged against the org's billing balance — confirm with "myapi billing balance"
210
+ before running.
211
+
212
+ Track expiry afterward with: myapi domain status <domain>`,
149
213
  'assign': `myapi domain assign <domain> [--org <id>]
150
214
 
151
215
  Assigns a registered domain to an org. Once assigned, your funnel is served at
@@ -175,6 +239,7 @@ Subcommands:
175
239
  list List domains
176
240
  check Check domain availability
177
241
  register Register a domain
242
+ renew Renew a registered domain for another period
178
243
  assign Assign domain to an org
179
244
  unassign Unassign domain from its current org
180
245
  status Get domain status
@@ -196,6 +261,7 @@ Tip: Set defaults with "myapi config set-org <id>" / "set-domain <domain>" to sk
196
261
  case 'list': return list(flags);
197
262
  case 'check': return check(args[0], flags);
198
263
  case 'register': return register(args[0], flags);
264
+ case 'renew': return renew(args[0], flags);
199
265
  case 'assign': return assign(args[0], flags);
200
266
  case 'unassign': return unassign(args[0], flags);
201
267
  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>;
@@ -2,6 +2,19 @@ import { email as sdkEmail } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../../config.js';
3
3
  import { success, error, printTable, printJson, info } from '../../output.js';
4
4
  import { requireOrg } from '../../helpers.js';
5
+ export const EXPOSES = [
6
+ 'POST /email/orgs/{org_id}/campaigns',
7
+ 'GET /email/orgs/{org_id}/campaigns',
8
+ 'GET /email/orgs/{org_id}/campaigns/{campaign_id}',
9
+ 'PATCH /email/orgs/{org_id}/campaigns/{campaign_id}',
10
+ 'POST /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/upload-list',
11
+ 'POST /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/upload-file',
12
+ 'GET /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/status',
13
+ 'POST /email/orgs/{org_id}/campaigns/{campaign_id}/start',
14
+ 'POST /email/orgs/{org_id}/campaigns/{campaign_id}/pause',
15
+ 'POST /email/orgs/{org_id}/campaigns/{campaign_id}/resume',
16
+ 'GET /email/orgs/{org_id}/campaigns/{campaign_id}/stats',
17
+ ];
5
18
  async function create(nameArg, flags) {
6
19
  const config = requireConfig();
7
20
  const orgId = requireOrg(flags, config, 'myapi email campaign create <name> --template-id <id> --from <email> [--per-day <n>] [--org <id>]');
@@ -61,6 +74,23 @@ async function uploadContacts(id, flags) {
61
74
  const res = await sdkEmail.uploadContactList(config.api_key, orgId, id, emails);
62
75
  success(`Uploaded ${res.total} contacts (status: ${res.status})`);
63
76
  }
77
+ async function uploadContactsFile(id, flags) {
78
+ const config = requireConfig();
79
+ const orgId = requireOrg(flags, config, 'myapi email campaign upload-contacts-file <campaign_id> --file <path> [--org <id>]');
80
+ if (!id || !flags.file) {
81
+ error('Missing required arguments.\nUsage: myapi email campaign upload-contacts-file <campaign_id> --file <path> [--org <id>]');
82
+ }
83
+ // Defer fs require to keep the unimported-cost low for users who never
84
+ // touch this command. Plain JS readFile fits — the SDK accepts a Buffer.
85
+ const fs = await import('node:fs');
86
+ const path = await import('node:path');
87
+ const filePath = flags.file;
88
+ if (!fs.existsSync(filePath))
89
+ error(`File not found: ${filePath}`);
90
+ const buf = fs.readFileSync(filePath);
91
+ const res = await sdkEmail.uploadContactsFile(config.api_key, orgId, id, buf, path.basename(filePath));
92
+ success(`Uploaded ${res.total} contacts from ${filePath} (status: ${res.status})`);
93
+ }
64
94
  async function start(id, flags) {
65
95
  const config = requireConfig();
66
96
  const orgId = requireOrg(flags, config, 'myapi email campaign start <id> [--org <id>]');
@@ -106,6 +136,7 @@ Either form works; the positional name is the recommended shape.`,
106
136
  'get': 'myapi email campaign get <id> [--org <id>]',
107
137
  'update': 'myapi email campaign update <id> [--name <str>] [--per-day <n>] [--org <id>]',
108
138
  'upload-contacts': 'myapi email campaign upload-contacts <campaign_id> --emails <a@x,b@y> [--org <id>]',
139
+ 'upload-contacts-file': 'myapi email campaign upload-contacts-file <campaign_id> --file <path> [--org <id>]',
109
140
  'start': 'myapi email campaign start <id> [--org <id>]',
110
141
  'pause': 'myapi email campaign pause <id> [--org <id>]',
111
142
  'resume': 'myapi email campaign resume <id> [--org <id>]',
@@ -120,7 +151,8 @@ Subcommands:
120
151
  list List campaigns
121
152
  get <id> Get a single campaign
122
153
  update <id> Patch name / per-day-limit
123
- upload-contacts <id> Upload contact list (--emails a@x,b@y)
154
+ upload-contacts <id> Upload contact list (--emails a@x,b@y)
155
+ upload-contacts-file <id> Upload contacts from a CSV file (--file path/to/contacts.csv)
124
156
  start <id> Start a campaign
125
157
  pause <id> Pause a campaign
126
158
  resume <id> Resume a campaign
@@ -143,6 +175,7 @@ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
143
175
  case 'get': return get(args[0], flags);
144
176
  case 'update': return update(args[0], flags);
145
177
  case 'upload-contacts': return uploadContacts(args[0], flags);
178
+ case 'upload-contacts-file': return uploadContactsFile(args[0], flags);
146
179
  case 'start': return start(args[0], flags);
147
180
  case 'pause': return pause(args[0], flags);
148
181
  case 'resume': return resume(args[0], flags);
@@ -1,4 +1,6 @@
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 run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
@@ -2,11 +2,16 @@
2
2
  // namespace lives in its own file in this directory; this index just
3
3
  // routes to the right one and handles the deprecated flat aliases.
4
4
  import { error, info } from '../../output.js';
5
+ // The email index is a dispatcher only; each subcommand file declares
6
+ // its own EXPOSES. Empty here so the coverage tool can still introspect
7
+ // every file uniformly.
8
+ export const EXPOSES = [];
5
9
  import * as mailbox from './mailbox.js';
6
10
  import * as message from './message.js';
7
11
  import * as warmup from './warmup.js';
8
12
  import * as template from './template.js';
9
13
  import * as campaign from './campaign.js';
14
+ import * as verify from './verify.js';
10
15
  export const SCHEMA = {
11
16
  org: 'string',
12
17
  domain: 'string',
@@ -27,6 +32,7 @@ export const SCHEMA = {
27
32
  name: 'string',
28
33
  emails: 'string',
29
34
  'per-day': 'number',
35
+ file: 'string',
30
36
  };
31
37
  // Old flat subcommand → [namespace, sub]. Kept so existing scripts/skills
32
38
  // don't break. Emits a one-line stderr deprecation note then runs the new
@@ -63,6 +69,7 @@ async function dispatchNamespace(ns, sub, restArgs, flags) {
63
69
  case 'warmup': return warmup.run(sub, restArgs, flags);
64
70
  case 'template': return template.run(sub, restArgs, flags);
65
71
  case 'campaign': return campaign.run(sub, restArgs, flags);
72
+ case 'verify': return verify.run(sub, restArgs, flags);
66
73
  default: error(`Unknown namespace: ${ns}. Run "myapi email --help" for the list.`);
67
74
  }
68
75
  }
@@ -76,12 +83,14 @@ Namespaces:
76
83
  warmup Warmup controls (account-scoped) — start, pause, resume, stop, stats
77
84
  template Email templates (org-scoped) — generate, list, edit, send-test, delete
78
85
  campaign Email campaigns (org-scoped) — create, list, get, update, upload-contacts, start, pause, resume, stats
86
+ verify Sync single-address email verification (org-scoped) — syntax + DNS + Microsoft probe
79
87
 
80
88
  Examples:
81
89
  myapi email mailbox create hello@example.com
82
90
  myapi email message send --from a@x --to b@y --subject hi --body "hello"
83
91
  myapi email template generate welcome --prompt "Welcome email"
84
92
  myapi email campaign create "Q2 Launch" --template-id <id> --from a@x
93
+ myapi email verify alice@example.com
85
94
 
86
95
  Org-scoped commands accept --org <id> (or set default: myapi config set-org <id>).
87
96
  Run "myapi email <namespace> --help" for namespace-specific commands.`);
@@ -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>;
@@ -1,6 +1,11 @@
1
1
  import { email as sdkEmail } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../../config.js';
3
3
  import { success, error, printTable, info } from '../../output.js';
4
+ export const EXPOSES = [
5
+ 'POST /email/mailboxes/create',
6
+ 'GET /email/mailboxes',
7
+ 'POST /email/sending/activate',
8
+ ];
4
9
  async function create(addressArg, flags) {
5
10
  const config = requireConfig();
6
11
  // Convention: first required arg is positional. For mailboxes that's the
@@ -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>;
@@ -1,6 +1,14 @@
1
1
  import { email as sdkEmail } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../../config.js';
3
3
  import { success, error, printTable, printJson, info } from '../../output.js';
4
+ export const EXPOSES = [
5
+ 'POST /email/send',
6
+ 'GET /email/status/{message_id}',
7
+ 'GET /email/sent',
8
+ 'GET /email/inbox/{address}',
9
+ 'GET /email/outbox/{address}',
10
+ 'GET /email/message/{message_id}',
11
+ ];
4
12
  async function send(flags) {
5
13
  const config = requireConfig();
6
14
  if (!flags.from || !flags.to || !flags.subject) {
@@ -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>;