@myapihq/cli 1.0.84 → 1.1.0-wip.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 (77) hide show
  1. package/dist/commands/auth.d.ts +8 -3
  2. package/dist/commands/auth.js +84 -60
  3. package/dist/commands/billing.d.ts +8 -4
  4. package/dist/commands/billing.js +46 -27
  5. package/dist/commands/config.d.ts +8 -5
  6. package/dist/commands/config.js +52 -27
  7. package/dist/commands/domain.d.ts +12 -9
  8. package/dist/commands/domain.js +124 -86
  9. package/dist/commands/email/campaign.d.ts +2 -0
  10. package/dist/commands/email/campaign.js +152 -0
  11. package/dist/commands/email/index.d.ts +4 -0
  12. package/dist/commands/email/index.js +98 -0
  13. package/dist/commands/email/mailbox.d.ts +2 -0
  14. package/dist/commands/email/mailbox.js +88 -0
  15. package/dist/commands/email/message.d.ts +2 -0
  16. package/dist/commands/email/message.js +115 -0
  17. package/dist/commands/email/template.d.ts +2 -0
  18. package/dist/commands/email/template.js +106 -0
  19. package/dist/commands/email/warmup.d.ts +2 -0
  20. package/dist/commands/email/warmup.js +43 -0
  21. package/dist/commands/email.d.ts +4 -12
  22. package/dist/commands/email.js +528 -146
  23. package/dist/commands/funnel.d.ts +10 -7
  24. package/dist/commands/funnel.js +79 -55
  25. package/dist/commands/image.js +25 -15
  26. package/dist/commands/keys.d.ts +8 -3
  27. package/dist/commands/keys.js +71 -35
  28. package/dist/commands/org.d.ts +9 -5
  29. package/dist/commands/org.js +100 -64
  30. package/dist/commands/pixel.js +23 -11
  31. package/dist/commands/setup.d.ts +3 -2
  32. package/dist/commands/setup.js +160 -165
  33. package/dist/commands/storage.js +25 -15
  34. package/dist/commands/update.d.ts +2 -1
  35. package/dist/commands/update.js +5 -0
  36. package/dist/commands/url.js +19 -7
  37. package/dist/commands/webhook.d.ts +8 -5
  38. package/dist/commands/webhook.js +70 -38
  39. package/dist/commands/workflow.d.ts +13 -7
  40. package/dist/commands/workflow.js +179 -58
  41. package/dist/config.js +10 -5
  42. package/dist/flags.d.ts +8 -0
  43. package/dist/flags.js +88 -0
  44. package/dist/flags.test.d.ts +1 -0
  45. package/dist/flags.test.js +73 -0
  46. package/dist/helpers.d.ts +6 -0
  47. package/dist/helpers.js +31 -0
  48. package/dist/index.js +98 -109
  49. package/dist/output.d.ts +12 -1
  50. package/dist/output.js +16 -6
  51. package/dist/prompt.d.ts +24 -0
  52. package/dist/prompt.js +41 -0
  53. package/dist/skills/my-email-api/README.md +45 -0
  54. package/dist/skills/my-email-api/SKILL.md +104 -0
  55. package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +6 -0
  56. package/dist/skills/my-email-api/make/.gitkeep +0 -0
  57. package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
  58. package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
  59. package/dist/skills/my-webhook-api/README.md +40 -0
  60. package/dist/skills/my-webhook-api/SKILL.md +138 -0
  61. package/dist/skills/my-webhook-api/claude/.claude-plugin/plugin.json +6 -0
  62. package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
  63. package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
  64. package/dist/skills/my-webhook-api/openapi/.gitkeep +0 -0
  65. package/dist/skills/my-workflow-api/README.md +36 -0
  66. package/dist/skills/my-workflow-api/SKILL.md +156 -0
  67. package/dist/skills/my-workflow-api/claude/.claude-plugin/plugin.json +6 -0
  68. package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
  69. package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
  70. package/dist/skills/my-workflow-api/openapi/.gitkeep +0 -0
  71. package/dist/utils.d.ts +26 -4
  72. package/dist/utils.js +32 -33
  73. package/dist/utils.test.d.ts +1 -0
  74. package/dist/utils.test.js +48 -0
  75. package/package.json +9 -4
  76. package/dist/commands/account.d.ts +0 -4
  77. package/dist/commands/account.js +0 -80
@@ -1,204 +1,586 @@
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
- import { sleep } from '../utils.js';
5
- export async function createMailbox(flags) {
4
+ import { pollJob } from '../utils.js';
5
+ import { requireOrg } from '../helpers.js';
6
+ export const SCHEMA = {
7
+ org: 'string',
8
+ domain: 'string',
9
+ username: 'string',
10
+ 'display-name': 'string',
11
+ filter: 'string',
12
+ address: 'string',
13
+ from: 'string',
14
+ to: 'string',
15
+ subject: 'string',
16
+ body: 'string',
17
+ html: 'string',
18
+ 'template-id': 'string',
19
+ 'template-vars': 'string',
20
+ limit: 'number',
21
+ offset: 'number',
22
+ prompt: 'string',
23
+ name: 'string',
24
+ emails: 'string',
25
+ 'per-day': 'number',
26
+ };
27
+ // ─── Mailbox ─────────────────────────────────────────────────────────────────
28
+ async function mailboxCreate(addressArg, flags) {
6
29
  const config = requireConfig();
7
- const orgId = flags.org || config.default_org;
8
- const domain = flags.domain || config.default_domain;
9
- if (!orgId || !domain || !flags.username) {
10
- error("Missing required arguments.\nUsage: myapi email create-mailbox --org <id> --domain <domain> --username <user>\n(Or set defaults via: myapi config set-org <id> / set-domain <domain>)");
30
+ // Convention: first required arg is positional. For mailboxes that's the
31
+ // full address `user@domain`. The flag form (--username + --domain) is
32
+ // still accepted for back-compat / scripts that built up parts separately.
33
+ let username = flags.username;
34
+ let domain = flags.domain || config.default_domain;
35
+ if (addressArg) {
36
+ const at = addressArg.indexOf('@');
37
+ if (at > 0 && at < addressArg.length - 1) {
38
+ username = addressArg.slice(0, at);
39
+ domain = addressArg.slice(at + 1);
40
+ }
41
+ else {
42
+ error(`"${addressArg}" is not a valid email address. Expected format: user@domain.com`);
43
+ }
11
44
  }
12
- const address = await sdkEmail.createMailbox(config.api_key, orgId, domain, flags.username);
13
- success(`Mailbox created: ${address.address}`);
45
+ if (!username || !domain) {
46
+ error('Missing required arguments.\nUsage: myapi email mailbox create <user@domain> [--display-name <name>]\n or: myapi email mailbox create --username <u> --domain <d> [--display-name <name>]\n(Or set default: myapi config set-domain <domain>)');
47
+ }
48
+ const res = await sdkEmail.createMailbox(config.api_key, domain, username, flags['display-name']);
49
+ // Backend response sometimes omits address; we know it from the inputs.
50
+ success(`Mailbox created: ${res.address || `${username}@${domain}`}`);
14
51
  }
15
- export async function listMailboxes(flags) {
52
+ async function mailboxList(flags) {
16
53
  const config = requireConfig();
17
- const orgId = flags.org || config.default_org;
18
- if (!orgId)
19
- error("Missing required arguments.\nUsage: myapi email list-mailboxes --org <id> [--domain <domain> | --filter unassigned]\n(Or set a default org via: myapi config set-org <id>)");
20
- if (!flags.domain && flags.filter !== 'unassigned')
21
- error("Must provide either --domain <domain> or --filter unassigned");
22
- const mailboxes = await sdkEmail.listMailboxes(config.api_key, orgId, {
54
+ if (!flags.domain && flags.filter !== 'unassigned') {
55
+ error('Must provide either --domain <domain> (mailboxes on that domain) or --filter unassigned (mailboxes whose domain is not assigned to any org).');
56
+ }
57
+ const mailboxes = await sdkEmail.listMailboxes(config.api_key, {
23
58
  domain: flags.domain,
24
- filter: flags.filter === 'unassigned' ? 'unassigned' : undefined
59
+ filter: flags.filter === 'unassigned' ? 'unassigned' : undefined,
60
+ });
61
+ printTable(mailboxes, {
62
+ flags,
63
+ empty: 'No mailboxes found.',
25
64
  });
26
- printTable(mailboxes);
27
65
  }
28
- export async function sent(flags) {
66
+ async function mailboxActivateSending(flags) {
29
67
  const config = requireConfig();
30
- const orgId = flags.org || config.default_org;
31
- if (!orgId)
32
- error("Missing required arguments.\nUsage: myapi email sent --org <id> [--limit <num>] [--offset <num>]\n(Or set defaults via: myapi config set-org <id>)");
33
- const limit = parseInt(flags.limit) || 50;
34
- const offset = parseInt(flags.offset) || 0;
35
- const emails = await sdkEmail.getSentEmails(config.api_key, orgId, limit, offset);
36
- printTable(emails);
68
+ const address = flags.address;
69
+ if (!address)
70
+ error('Missing required arguments.\nUsage: myapi email mailbox activate-sending --address <email>');
71
+ const res = await sdkEmail.activateSending(config.api_key, address);
72
+ success(`Sending activated: ${address} (status: ${res.status})`);
37
73
  }
38
- export async function send(flags) {
74
+ const MAILBOX_USAGE = {
75
+ 'create': `myapi email mailbox create <user@domain> [--display-name <name>]
76
+ myapi email mailbox create --username <u> --domain <d> [--display-name <name>]
77
+
78
+ Either form works. The positional address is the recommended shape and
79
+ matches the rest of the CLI ("first required arg is positional").`,
80
+ 'list': `myapi email mailbox list (--domain <domain> | --filter unassigned)
81
+
82
+ --domain <domain> List mailboxes on the given domain.
83
+ --filter unassigned List mailboxes whose domain is NOT assigned to any
84
+ org (orphaned mailboxes — domain was unassigned
85
+ without first deleting them).`,
86
+ 'activate-sending': 'myapi email mailbox activate-sending --address <email>',
87
+ };
88
+ async function mailboxRun(sub, args, flags) {
89
+ if (!sub || (flags.help && !sub)) {
90
+ info(`Usage: myapi email mailbox <subcommand>
91
+
92
+ Subcommands:
93
+ create Create a mailbox (positional <user@domain> or --username/--domain)
94
+ list List mailboxes on a domain (--domain) or orphaned ones (--filter unassigned)
95
+ activate-sending Activate outbound sending for a mailbox`);
96
+ return;
97
+ }
98
+ if (flags.help) {
99
+ const usage = MAILBOX_USAGE[sub];
100
+ if (usage)
101
+ info(`Usage: ${usage}`);
102
+ else
103
+ info(`Unknown subcommand: ${sub}. Run "myapi email mailbox --help" for the list.`);
104
+ return;
105
+ }
106
+ switch (sub) {
107
+ case 'create': return mailboxCreate(args[0], flags);
108
+ case 'list': return mailboxList(flags);
109
+ case 'activate-sending': return mailboxActivateSending(flags);
110
+ default: error(`Unknown subcommand: ${sub}. Run "myapi email mailbox --help" for the list.`);
111
+ }
112
+ }
113
+ // ─── Message ─────────────────────────────────────────────────────────────────
114
+ async function messageSend(flags) {
39
115
  const config = requireConfig();
40
- const orgId = flags.org || config.default_org;
41
- if (!orgId || !flags.from || !flags.to || !flags.subject) {
42
- error("Missing required arguments.\nUsage: myapi email send --org <id> --from <email> --to <email> --subject <str> [--body <str> | --template-id <id> [--template-vars <json_str>]]");
116
+ if (!flags.from || !flags.to || !flags.subject) {
117
+ error('Missing required arguments.\nUsage: myapi email message send --from <email> --to <email> --subject <str> [--body <str> | --html <str> | --template-id <id> [--template-vars <json>]]');
43
118
  }
44
- if (!flags.body && !flags['template-id'])
45
- error("Must provide either --body or --template-id");
46
- if (flags.body && flags['template-id'])
47
- error("Cannot provide both --body and --template-id");
119
+ if (!flags.body && !flags.html && !flags['template-id'])
120
+ error('Must provide one of --body, --html, or --template-id');
121
+ if ((flags.body || flags.html) && flags['template-id'])
122
+ error('Cannot combine --body/--html with --template-id');
48
123
  let templateVars;
49
124
  if (flags['template-vars']) {
50
125
  try {
51
126
  templateVars = JSON.parse(flags['template-vars']);
52
127
  }
53
- catch (e) {
54
- error("--template-vars must be a valid JSON string");
128
+ catch {
129
+ error('--template-vars must be valid JSON');
55
130
  }
56
131
  }
57
- const res = await sdkEmail.sendEmail(config.api_key, orgId, {
132
+ const res = await sdkEmail.sendEmail(config.api_key, {
58
133
  from: flags.from,
59
134
  to: [flags.to],
60
135
  subject: flags.subject,
61
136
  text: flags.body,
137
+ html: flags.html,
62
138
  template_id: flags['template-id'],
63
- template_vars: templateVars
139
+ template_vars: templateVars,
64
140
  });
65
141
  success(`Email sent! Message ID: ${res.message_id}`);
66
142
  }
67
- export async function inbox(address, flags) {
143
+ async function messageStatus(messageId, _flags) {
68
144
  const config = requireConfig();
69
- const orgId = flags.org || config.default_org;
70
- if (!orgId || !address)
71
- error("Missing required arguments.\nUsage: myapi email inbox <address> --org <id>");
72
- const messages = await sdkEmail.getInbox(config.api_key, orgId, address);
73
- printTable(messages);
145
+ if (!messageId)
146
+ error('Missing required arguments.\nUsage: myapi email message status <message_id>');
147
+ const res = await sdkEmail.getEmailStatus(config.api_key, messageId);
148
+ printJson(res);
74
149
  }
75
- export async function outbox(address, flags) {
150
+ async function messageSent(flags) {
76
151
  const config = requireConfig();
77
- const orgId = flags.org || config.default_org;
78
- if (!orgId || !address)
79
- error("Missing required arguments.\nUsage: myapi email outbox <address> --org <id>");
80
- const messages = await sdkEmail.getOutbox(config.api_key, orgId, address);
81
- printTable(messages);
152
+ const limit = flags.limit || 50;
153
+ const offset = flags.offset || 0;
154
+ const emails = await sdkEmail.getSentEmails(config.api_key, limit, offset);
155
+ printTable(emails, {
156
+ flags,
157
+ empty: 'No sent emails yet.',
158
+ });
159
+ }
160
+ async function messageInbox(address, flags) {
161
+ const config = requireConfig();
162
+ if (!address)
163
+ error('Missing required arguments.\nUsage: myapi email message inbox <address>');
164
+ const messages = await sdkEmail.getInbox(config.api_key, address);
165
+ printTable(messages, {
166
+ flags,
167
+ empty: `No messages in ${address}.`,
168
+ });
169
+ }
170
+ async function messageOutbox(address, flags) {
171
+ const config = requireConfig();
172
+ if (!address)
173
+ error('Missing required arguments.\nUsage: myapi email message outbox <address>');
174
+ const messages = await sdkEmail.getOutbox(config.api_key, address);
175
+ printTable(messages, {
176
+ flags,
177
+ empty: `No outbound messages from ${address}.`,
178
+ });
179
+ }
180
+ async function messageGet(messageId, flags) {
181
+ const config = requireConfig();
182
+ if (!messageId || !flags.address)
183
+ error('Missing required arguments.\nUsage: myapi email message get <message_id> --address <email>');
184
+ const res = await sdkEmail.getMessage(config.api_key, messageId, flags.address);
185
+ printJson(res);
186
+ }
187
+ const MESSAGE_USAGE = {
188
+ 'send': 'myapi email message send --from <email> --to <email> --subject <str> [--body <str> | --html <str> | --template-id <id> [--template-vars <json>]]',
189
+ 'status': 'myapi email message status <message_id>',
190
+ 'sent': 'myapi email message sent [--limit <n>] [--offset <n>]',
191
+ 'inbox': 'myapi email message inbox <address>',
192
+ 'outbox': 'myapi email message outbox <address>',
193
+ 'get': 'myapi email message get <message_id> --address <email>',
194
+ };
195
+ async function messageRun(sub, args, flags) {
196
+ if (!sub || (flags.help && !sub)) {
197
+ info(`Usage: myapi email message <subcommand>
198
+
199
+ Subcommands:
200
+ send Send an email (transactional or templated)
201
+ status <id> Get delivery status of a sent message
202
+ sent List sent emails
203
+ inbox <addr> Read inbox for a mailbox address
204
+ outbox <addr> Read outbox for a mailbox address
205
+ get <id> Get a single message (--address <email>)`);
206
+ return;
207
+ }
208
+ if (flags.help) {
209
+ const usage = MESSAGE_USAGE[sub];
210
+ if (usage)
211
+ info(`Usage: ${usage}`);
212
+ else
213
+ info(`Unknown subcommand: ${sub}. Run "myapi email message --help" for the list.`);
214
+ return;
215
+ }
216
+ switch (sub) {
217
+ case 'send': return messageSend(flags);
218
+ case 'status': return messageStatus(args[0], flags);
219
+ case 'sent': return messageSent(flags);
220
+ case 'inbox': return messageInbox(args[0], flags);
221
+ case 'outbox': return messageOutbox(args[0], flags);
222
+ case 'get': return messageGet(args[0], flags);
223
+ default: error(`Unknown subcommand: ${sub}. Run "myapi email message --help" for the list.`);
224
+ }
225
+ }
226
+ // ─── Warmup ──────────────────────────────────────────────────────────────────
227
+ async function warmupRun(sub, args, flags) {
228
+ if (!sub || (flags.help && !sub)) {
229
+ info('Usage: myapi email warmup <start|pause|resume|stop|stats> --address <email>');
230
+ return;
231
+ }
232
+ if (flags.help) {
233
+ info(`Usage: myapi email warmup ${sub} --address <email>`);
234
+ return;
235
+ }
236
+ const config = requireConfig();
237
+ const address = flags.address || args[0];
238
+ if (!address)
239
+ error('Missing required argument: --address <email>');
240
+ switch (sub) {
241
+ case 'start': {
242
+ const res = await sdkEmail.startWarmup(config.api_key, address);
243
+ success(`Warmup started: ${address} (${res.warmup_status})`);
244
+ return;
245
+ }
246
+ case 'pause':
247
+ await sdkEmail.pauseWarmup(config.api_key, address);
248
+ success(`Warmup paused: ${address}`);
249
+ return;
250
+ case 'resume':
251
+ await sdkEmail.resumeWarmup(config.api_key, address);
252
+ success(`Warmup resumed: ${address}`);
253
+ return;
254
+ case 'stop':
255
+ await sdkEmail.stopWarmup(config.api_key, address);
256
+ success(`Warmup stopped: ${address}`);
257
+ return;
258
+ case 'stats': {
259
+ const res = await sdkEmail.getWarmupStats(config.api_key, address);
260
+ printJson(res);
261
+ return;
262
+ }
263
+ default:
264
+ error(`Unknown warmup subcommand: ${sub}. Use start|pause|resume|stop|stats`);
265
+ }
266
+ }
267
+ // ─── Templates ───────────────────────────────────────────────────────────────
268
+ async function templateGenerate(nameArg, flags) {
269
+ const config = requireConfig();
270
+ const orgId = requireOrg(flags, config, 'myapi email template generate <name> --prompt <str> [--org <id>]');
271
+ const name = nameArg || flags.name;
272
+ if (!name || !flags.prompt) {
273
+ error('Missing required arguments.\nUsage: myapi email template generate <name> --prompt <str> [--org <id>]\n or: myapi email template generate --name <name> --prompt <str> [--org <id>]');
274
+ }
275
+ const job = await sdkEmail.generateTemplate(config.api_key, orgId, {
276
+ prompt: flags.prompt,
277
+ name,
278
+ });
279
+ const status = await pollJob({
280
+ label: 'Generating template',
281
+ timeoutMs: 90_000,
282
+ intervalMs: 3000,
283
+ check: () => sdkEmail.getTemplateJobStatus(config.api_key, orgId, job.job_id),
284
+ isDone: s => s.status === 'completed',
285
+ isFailed: s => s.status === 'failed',
286
+ failedMessage: 'Template generation failed',
287
+ timeoutMessage: 'Template generation timed out',
288
+ });
289
+ success(`Template generated! ID: ${status.template_id}\nPreview: ${status.result?.preview_url}`);
82
290
  }
83
- export async function listTemplates(flags) {
291
+ async function templateList(flags) {
84
292
  const config = requireConfig();
85
- const orgId = flags.org || config.default_org;
86
- if (!orgId)
87
- error("Missing required arguments.\nUsage: myapi email list-templates --org <id>\n(Or set defaults via: myapi config set-org <id>)");
293
+ const orgId = requireOrg(flags, config, 'myapi email template list [--org <id>]');
88
294
  const templates = await sdkEmail.listTemplates(config.api_key, orgId);
89
- if (flags.json)
295
+ if (flags.json) {
90
296
  printJson(templates);
91
- else
92
- printTable(templates);
297
+ return;
298
+ }
299
+ printTable(templates, {
300
+ flags,
301
+ empty: 'No templates yet. Generate one with: myapi email template generate --prompt <p> --name <n>',
302
+ });
93
303
  }
94
- export async function deleteTemplate(id, flags) {
304
+ async function templateEdit(id, flags) {
95
305
  const config = requireConfig();
96
- const orgId = flags.org || config.default_org;
97
- if (!orgId || !id)
98
- error("Missing required arguments.\nUsage: myapi email delete-template <id> --org <id>\n(Or set defaults via: myapi config set-org <id>)");
306
+ const orgId = requireOrg(flags, config, 'myapi email template edit <id> --prompt <str> [--org <id>]');
307
+ if (!id || !flags.prompt)
308
+ error('Missing required arguments.\nUsage: myapi email template edit <id> --prompt <str> [--org <id>]');
309
+ const res = await sdkEmail.editTemplate(config.api_key, orgId, id, flags.prompt);
310
+ success(`Template ${res.template_id} edited\nPreview: ${res.preview_url}`);
311
+ }
312
+ async function templateSendTest(id, flags) {
313
+ const config = requireConfig();
314
+ const orgId = requireOrg(flags, config, 'myapi email template send-test <template_id> --to <email> [--org <id>]');
315
+ if (!id || !flags.to)
316
+ error('Missing required arguments.\nUsage: myapi email template send-test <template_id> --to <email> [--org <id>]');
317
+ const res = await sdkEmail.sendTestEmail(config.api_key, orgId, id, flags.to);
318
+ success(`Test sent! Message ID: ${res.message_id}`);
319
+ }
320
+ async function templateDelete(id, flags) {
321
+ const config = requireConfig();
322
+ const orgId = requireOrg(flags, config, 'myapi email template delete <id> [--org <id>]');
323
+ if (!id)
324
+ error('Missing required arguments.\nUsage: myapi email template delete <id> [--org <id>]');
99
325
  await sdkEmail.deleteTemplate(config.api_key, orgId, id);
100
326
  success(`Deleted template ${id}`);
101
327
  }
102
- export async function generateTemplate(flags) {
328
+ const TEMPLATE_USAGE = {
329
+ 'generate': `myapi email template generate <name> --prompt <str> [--org <id>]
330
+ myapi email template generate --name <name> --prompt <str> [--org <id>]
331
+
332
+ Either form works; the positional name is the recommended shape.`,
333
+ 'list': 'myapi email template list [--org <id>] [--json]',
334
+ 'edit': 'myapi email template edit <id> --prompt <str> [--org <id>]',
335
+ 'send-test': 'myapi email template send-test <template_id> --to <email> [--org <id>]',
336
+ 'delete': 'myapi email template delete <id> [--org <id>]',
337
+ };
338
+ async function templateRun(sub, args, flags) {
339
+ if (!sub || (flags.help && !sub)) {
340
+ info(`Usage: myapi email template <subcommand>
341
+
342
+ Subcommands:
343
+ generate <name> Generate AI template (--prompt)
344
+ list List templates
345
+ edit <id> Edit a template via prompt
346
+ send-test <id> Send a test email for a template (--to)
347
+ delete <id> Delete a template
348
+
349
+ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
350
+ return;
351
+ }
352
+ if (flags.help) {
353
+ const usage = TEMPLATE_USAGE[sub];
354
+ if (usage)
355
+ info(`Usage: ${usage}`);
356
+ else
357
+ info(`Unknown subcommand: ${sub}. Run "myapi email template --help" for the list.`);
358
+ return;
359
+ }
360
+ switch (sub) {
361
+ case 'generate': return templateGenerate(args[0], flags);
362
+ case 'list': return templateList(flags);
363
+ case 'edit': return templateEdit(args[0], flags);
364
+ case 'send-test': return templateSendTest(args[0], flags);
365
+ case 'delete': return templateDelete(args[0], flags);
366
+ default: error(`Unknown subcommand: ${sub}. Run "myapi email template --help" for the list.`);
367
+ }
368
+ }
369
+ // ─── Campaigns ───────────────────────────────────────────────────────────────
370
+ async function campaignCreate(nameArg, flags) {
103
371
  const config = requireConfig();
104
- const orgId = flags.org || config.default_org;
105
- if (!orgId || !flags.prompt || !flags.name) {
106
- error("Missing required arguments.\nUsage: myapi email generate-template --org <id> --prompt <str> --name <str>");
372
+ const orgId = requireOrg(flags, config, 'myapi email campaign create <name> --template-id <id> --from <email> [--per-day <n>] [--org <id>]');
373
+ const name = nameArg || flags.name;
374
+ if (!name || !flags['template-id'] || !flags.from) {
375
+ error('Missing required arguments.\nUsage: myapi email campaign create <name> --template-id <id> --from <email> [--per-day <n>] [--org <id>]\n or: myapi email campaign create --name <name> --template-id <id> --from <email> [--per-day <n>] [--org <id>]');
107
376
  }
108
- const job = await sdkEmail.generateTemplate(config.api_key, orgId, {
109
- prompt: flags.prompt,
110
- name: flags.name
377
+ const perDayLimit = flags['per-day'];
378
+ const res = await sdkEmail.createCampaign(config.api_key, orgId, {
379
+ name,
380
+ template_id: flags['template-id'],
381
+ from_address: flags.from,
382
+ per_day_limit: perDayLimit,
111
383
  });
112
- process.stdout.write("Generating template ");
113
- const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
114
- let i = 0;
115
- let elapsed = 0;
116
- while (elapsed < 90000) {
117
- const status = await sdkEmail.getTemplateJobStatus(config.api_key, orgId, job.job_id);
118
- if (status.status === 'completed') {
119
- process.stdout.write('\r\x1b[K');
120
- success(`Template generated! ID: ${status.template_id}\nPreview: ${status.result?.preview_url}`);
121
- return;
122
- }
123
- if (status.status === 'failed') {
124
- process.stdout.write('\r\x1b[K');
125
- error("Template generation failed");
126
- }
127
- process.stdout.write(`\rGenerating template ${chars[i++ % chars.length]}`);
128
- await sleep(3000);
129
- elapsed += 3000;
130
- }
131
- process.stdout.write('\r\x1b[K');
132
- error("Template generation timed out");
384
+ success(`Campaign created! ID: ${res.id}`);
133
385
  }
134
- export async function listCampaigns(flags) {
386
+ async function campaignList(flags) {
135
387
  const config = requireConfig();
136
- const orgId = flags.org || config.default_org;
137
- if (!orgId)
138
- error("Missing required arguments.\nUsage: myapi email list-campaigns --org <id>\n(Or set a default org via: myapi config set-org <id>)");
388
+ const orgId = requireOrg(flags, config, 'myapi email campaign list [--org <id>]');
139
389
  const campaigns = await sdkEmail.listCampaigns(config.api_key, orgId);
140
- printTable(campaigns);
390
+ printTable(campaigns, {
391
+ flags,
392
+ empty: 'No campaigns yet. Create one with: myapi email campaign create',
393
+ });
394
+ }
395
+ async function campaignGet(id, flags) {
396
+ const config = requireConfig();
397
+ const orgId = requireOrg(flags, config, 'myapi email campaign get <id> [--org <id>]');
398
+ if (!id)
399
+ error('Missing required arguments.\nUsage: myapi email campaign get <id> [--org <id>]');
400
+ const c = await sdkEmail.getCampaign(config.api_key, orgId, id);
401
+ printJson(c);
141
402
  }
142
- export async function campaignStats(id, flags) {
403
+ async function campaignUpdate(id, flags) {
143
404
  const config = requireConfig();
144
- const orgId = flags.org || config.default_org;
145
- if (!orgId || !id)
146
- error("Missing required arguments.\nUsage: myapi email campaign-stats <id> --org <id>");
405
+ const orgId = requireOrg(flags, config, 'myapi email campaign update <id> [--name <str>] [--per-day <n>] [--org <id>]');
406
+ if (!id)
407
+ error('Missing required arguments.\nUsage: myapi email campaign update <id> [--name <str>] [--per-day <n>] [--org <id>]');
408
+ const payload = {};
409
+ if (flags.name)
410
+ payload.name = flags.name;
411
+ if (flags['per-day'])
412
+ payload.per_day_limit = flags['per-day'];
413
+ if (!payload.name && payload.per_day_limit === undefined) {
414
+ error('Nothing to update. Pass at least one of --name, --per-day.');
415
+ }
416
+ const res = await sdkEmail.updateCampaign(config.api_key, orgId, id, payload);
417
+ success(`Campaign ${res.id} updated`);
418
+ }
419
+ async function campaignUploadContacts(id, flags) {
420
+ const config = requireConfig();
421
+ const orgId = requireOrg(flags, config, 'myapi email campaign upload-contacts <campaign_id> --emails <a@x,b@y> [--org <id>]');
422
+ if (!id || !flags.emails) {
423
+ error('Missing required arguments.\nUsage: myapi email campaign upload-contacts <campaign_id> --emails <a@x,b@y> [--org <id>]');
424
+ }
425
+ const emails = flags.emails.split(',').map(s => s.trim()).filter(Boolean);
426
+ const res = await sdkEmail.uploadContactList(config.api_key, orgId, id, emails);
427
+ success(`Uploaded ${res.total} contacts (status: ${res.status})`);
428
+ }
429
+ async function campaignStart(id, flags) {
430
+ const config = requireConfig();
431
+ const orgId = requireOrg(flags, config, 'myapi email campaign start <id> [--org <id>]');
432
+ if (!id)
433
+ error('Missing required arguments.\nUsage: myapi email campaign start <id> [--org <id>]');
434
+ const status = await sdkEmail.getContactUploadStatus(config.api_key, orgId, id);
435
+ if (status.upload_status !== 'ready') {
436
+ error(`Contacts not ready (current status: ${status.upload_status})`);
437
+ }
438
+ const res = await sdkEmail.startCampaign(config.api_key, orgId, id);
439
+ success(`Campaign started (status: ${res.status})`);
440
+ }
441
+ async function campaignPause(id, flags) {
442
+ const config = requireConfig();
443
+ const orgId = requireOrg(flags, config, 'myapi email campaign pause <id> [--org <id>]');
444
+ if (!id)
445
+ error('Missing required arguments.\nUsage: myapi email campaign pause <id> [--org <id>]');
446
+ await sdkEmail.pauseCampaign(config.api_key, orgId, id);
447
+ success(`Campaign ${id} paused`);
448
+ }
449
+ async function campaignResume(id, flags) {
450
+ const config = requireConfig();
451
+ const orgId = requireOrg(flags, config, 'myapi email campaign resume <id> [--org <id>]');
452
+ if (!id)
453
+ error('Missing required arguments.\nUsage: myapi email campaign resume <id> [--org <id>]');
454
+ await sdkEmail.resumeCampaign(config.api_key, orgId, id);
455
+ success(`Campaign ${id} resumed`);
456
+ }
457
+ async function campaignStats(id, flags) {
458
+ const config = requireConfig();
459
+ const orgId = requireOrg(flags, config, 'myapi email campaign stats <id> [--org <id>]');
460
+ if (!id)
461
+ error('Missing required arguments.\nUsage: myapi email campaign stats <id> [--org <id>]');
147
462
  const stats = await sdkEmail.getCampaignStats(config.api_key, orgId, id);
148
463
  printJson(stats);
149
464
  }
150
- export async function run(subcommand, args, flags) {
151
- if (!subcommand || (flags.help && !subcommand)) {
152
- info('Usage: myapi email <subcommand>\n\nSubcommands:\n create-mailbox Create a new mailbox\n list-mailboxes List mailboxes\n send Send an email\n sent List sent emails\n inbox Read received emails\n outbox Read sent emails for address\n list-templates List email templates\n generate-template Generate AI template\n delete-template Delete a template\n list-campaigns List campaigns\n campaign-stats Get campaign stats\n\nNote: All email commands require the --org <id> flag.');
465
+ const CAMPAIGN_USAGE = {
466
+ 'create': `myapi email campaign create <name> --template-id <id> --from <email> [--per-day <n>] [--org <id>]
467
+ myapi email campaign create --name <name> --template-id <id> --from <email> [--per-day <n>] [--org <id>]
468
+
469
+ Either form works; the positional name is the recommended shape.`,
470
+ 'list': 'myapi email campaign list [--org <id>]',
471
+ 'get': 'myapi email campaign get <id> [--org <id>]',
472
+ 'update': 'myapi email campaign update <id> [--name <str>] [--per-day <n>] [--org <id>]',
473
+ 'upload-contacts': 'myapi email campaign upload-contacts <campaign_id> --emails <a@x,b@y> [--org <id>]',
474
+ 'start': 'myapi email campaign start <id> [--org <id>]',
475
+ 'pause': 'myapi email campaign pause <id> [--org <id>]',
476
+ 'resume': 'myapi email campaign resume <id> [--org <id>]',
477
+ 'stats': 'myapi email campaign stats <id> [--org <id>]',
478
+ };
479
+ async function campaignRun(sub, args, flags) {
480
+ if (!sub || (flags.help && !sub)) {
481
+ info(`Usage: myapi email campaign <subcommand>
482
+
483
+ Subcommands:
484
+ create <name> Create a campaign (--template-id --from)
485
+ list List campaigns
486
+ get <id> Get a single campaign
487
+ update <id> Patch name / per-day-limit
488
+ upload-contacts <id> Upload contact list (--emails a@x,b@y)
489
+ start <id> Start a campaign
490
+ pause <id> Pause a campaign
491
+ resume <id> Resume a campaign
492
+ stats <id> Get campaign stats
493
+
494
+ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
153
495
  return;
154
496
  }
155
497
  if (flags.help) {
156
- if (subcommand === 'create-mailbox')
157
- info('Usage: myapi email create-mailbox --org <id> --domain <domain> --username <user>');
158
- else if (subcommand === 'list-mailboxes')
159
- info('Usage: myapi email list-mailboxes --org <id> [--domain <domain> | --filter unassigned]');
160
- else if (subcommand === 'send')
161
- info('Usage: myapi email send --org <id> --from <email> --to <email> --subject <str> [--body <str> | --template-id <id> [--template-vars <json_str>]]');
162
- else if (subcommand === 'sent')
163
- info('Usage: myapi email sent --org <id> [--limit <num>] [--offset <num>]');
164
- else if (subcommand === 'inbox')
165
- info('Usage: myapi email inbox <address> --org <id>');
166
- else if (subcommand === 'outbox')
167
- info('Usage: myapi email outbox <address> --org <id>');
168
- else if (subcommand === 'list-templates')
169
- info('Usage: myapi email list-templates --org <id>');
170
- else if (subcommand === 'generate-template')
171
- info('Usage: myapi email generate-template --org <id> --prompt <str> --name <str>');
172
- else if (subcommand === 'delete-template')
173
- info('Usage: myapi email delete-template <id> --org <id>');
174
- else if (subcommand === 'list-campaigns')
175
- info('Usage: myapi email list-campaigns --org <id>');
176
- else if (subcommand === 'campaign-stats')
177
- info('Usage: myapi email campaign-stats <campaign_id> --org <id>');
498
+ const usage = CAMPAIGN_USAGE[sub];
499
+ if (usage)
500
+ info(`Usage: ${usage}`);
501
+ else
502
+ info(`Unknown subcommand: ${sub}. Run "myapi email campaign --help" for the list.`);
503
+ return;
504
+ }
505
+ switch (sub) {
506
+ case 'create': return campaignCreate(args[0], flags);
507
+ case 'list': return campaignList(flags);
508
+ case 'get': return campaignGet(args[0], flags);
509
+ case 'update': return campaignUpdate(args[0], flags);
510
+ case 'upload-contacts': return campaignUploadContacts(args[0], flags);
511
+ case 'start': return campaignStart(args[0], flags);
512
+ case 'pause': return campaignPause(args[0], flags);
513
+ case 'resume': return campaignResume(args[0], flags);
514
+ case 'stats': return campaignStats(args[0], flags);
515
+ default: error(`Unknown subcommand: ${sub}. Run "myapi email campaign --help" for the list.`);
516
+ }
517
+ }
518
+ // ─── Deprecated flat aliases ─────────────────────────────────────────────────
519
+ // Old subcommand new namespace + sub. Kept so existing scripts/skills don't
520
+ // break. Emits a one-line stderr deprecation note then runs the new path.
521
+ const DEPRECATED_ALIASES = {
522
+ 'create-mailbox': ['mailbox', 'create'],
523
+ 'list-mailboxes': ['mailbox', 'list'],
524
+ 'activate-sending': ['mailbox', 'activate-sending'],
525
+ 'send': ['message', 'send'],
526
+ 'get-status': ['message', 'status'],
527
+ 'sent': ['message', 'sent'],
528
+ 'inbox': ['message', 'inbox'],
529
+ 'outbox': ['message', 'outbox'],
530
+ 'get-message': ['message', 'get'],
531
+ 'generate-template': ['template', 'generate'],
532
+ 'list-templates': ['template', 'list'],
533
+ 'edit-template': ['template', 'edit'],
534
+ 'send-test': ['template', 'send-test'],
535
+ 'delete-template': ['template', 'delete'],
536
+ 'create-campaign': ['campaign', 'create'],
537
+ 'list-campaigns': ['campaign', 'list'],
538
+ 'get-campaign': ['campaign', 'get'],
539
+ 'update-campaign': ['campaign', 'update'],
540
+ 'upload-contacts': ['campaign', 'upload-contacts'],
541
+ 'start-campaign': ['campaign', 'start'],
542
+ 'pause-campaign': ['campaign', 'pause'],
543
+ 'resume-campaign': ['campaign', 'resume'],
544
+ 'campaign-stats': ['campaign', 'stats'],
545
+ };
546
+ // ─── Top-level dispatcher ────────────────────────────────────────────────────
547
+ export async function run(subcommand, args, flags) {
548
+ if (!subcommand || (flags.help && !subcommand)) {
549
+ info(`Usage: myapi email <namespace> <subcommand>
550
+
551
+ Namespaces:
552
+ mailbox Mailboxes (account-scoped) — create, list, activate-sending
553
+ message Send & read mail (account-scoped) — send, status, sent, inbox, outbox, get
554
+ warmup Warmup controls (account-scoped) — start, pause, resume, stop, stats
555
+ template Email templates (org-scoped) — generate, list, edit, send-test, delete
556
+ campaign Email campaigns (org-scoped) — create, list, get, update, upload-contacts, start, pause, resume, stats
557
+
558
+ Examples:
559
+ myapi email mailbox create --domain example.com --username hello
560
+ myapi email message send --from a@x --to b@y --subject hi --body "hello"
561
+ myapi email template generate --prompt "Welcome email" --name welcome
562
+ myapi email campaign create --name "Q2 Launch" --template-id <id> --from a@x
563
+
564
+ Org-scoped commands accept --org <id> (or set default: myapi config set-org <id>).
565
+ Run "myapi email <namespace> --help" for namespace-specific commands.`);
178
566
  return;
179
567
  }
180
- if (subcommand === 'create-mailbox')
181
- await createMailbox(flags);
182
- else if (subcommand === 'list-mailboxes')
183
- await listMailboxes(flags);
184
- else if (subcommand === 'send')
185
- await send(flags);
186
- else if (subcommand === 'sent')
187
- await sent(flags);
188
- else if (subcommand === 'inbox')
189
- await inbox(args[0], flags);
190
- else if (subcommand === 'outbox')
191
- await outbox(args[0], flags);
192
- else if (subcommand === 'list-templates')
193
- await listTemplates(flags);
194
- else if (subcommand === 'generate-template')
195
- await generateTemplate(flags);
196
- else if (subcommand === 'delete-template')
197
- await deleteTemplate(args[0], flags);
198
- else if (subcommand === 'list-campaigns')
199
- await listCampaigns(flags);
200
- else if (subcommand === 'campaign-stats')
201
- await campaignStats(args[0], flags);
202
- else
203
- error(`Unknown subcommand: ${subcommand}. Run "myapi email --help" for a list of valid subcommands.`);
568
+ // Deprecated flat command? Forward to the new path with a one-line note.
569
+ const aliased = DEPRECATED_ALIASES[subcommand];
570
+ if (aliased) {
571
+ const [ns, sub] = aliased;
572
+ process.stderr.write(`› Note: "email ${subcommand}" is deprecated — use "email ${ns} ${sub}" instead.\n`);
573
+ return dispatchNamespace(ns, sub, args, flags);
574
+ }
575
+ return dispatchNamespace(subcommand, args[0], args.slice(1), flags);
576
+ }
577
+ async function dispatchNamespace(ns, sub, restArgs, flags) {
578
+ switch (ns) {
579
+ case 'mailbox': return mailboxRun(sub, restArgs, flags);
580
+ case 'message': return messageRun(sub, restArgs, flags);
581
+ case 'warmup': return warmupRun(sub, restArgs, flags);
582
+ case 'template': return templateRun(sub, restArgs, flags);
583
+ case 'campaign': return campaignRun(sub, restArgs, flags);
584
+ default: error(`Unknown namespace: ${ns}. Run "myapi email --help" for the list.`);
585
+ }
204
586
  }