@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.
- package/dist/commands/auth.d.ts +8 -3
- package/dist/commands/auth.js +84 -60
- package/dist/commands/billing.d.ts +8 -4
- package/dist/commands/billing.js +46 -27
- package/dist/commands/config.d.ts +8 -5
- package/dist/commands/config.js +52 -27
- package/dist/commands/domain.d.ts +12 -9
- package/dist/commands/domain.js +124 -86
- package/dist/commands/email/campaign.d.ts +2 -0
- package/dist/commands/email/campaign.js +152 -0
- package/dist/commands/email/index.d.ts +4 -0
- package/dist/commands/email/index.js +98 -0
- package/dist/commands/email/mailbox.d.ts +2 -0
- package/dist/commands/email/mailbox.js +88 -0
- package/dist/commands/email/message.d.ts +2 -0
- package/dist/commands/email/message.js +115 -0
- package/dist/commands/email/template.d.ts +2 -0
- package/dist/commands/email/template.js +106 -0
- package/dist/commands/email/warmup.d.ts +2 -0
- package/dist/commands/email/warmup.js +43 -0
- package/dist/commands/email.d.ts +4 -12
- package/dist/commands/email.js +528 -146
- package/dist/commands/funnel.d.ts +10 -7
- package/dist/commands/funnel.js +79 -55
- package/dist/commands/image.js +25 -15
- package/dist/commands/keys.d.ts +8 -3
- package/dist/commands/keys.js +71 -35
- package/dist/commands/org.d.ts +9 -5
- package/dist/commands/org.js +100 -64
- package/dist/commands/pixel.js +23 -11
- package/dist/commands/setup.d.ts +3 -2
- package/dist/commands/setup.js +160 -165
- package/dist/commands/storage.js +25 -15
- package/dist/commands/update.d.ts +2 -1
- package/dist/commands/update.js +5 -0
- package/dist/commands/url.js +19 -7
- package/dist/commands/webhook.d.ts +8 -5
- package/dist/commands/webhook.js +70 -38
- package/dist/commands/workflow.d.ts +13 -7
- package/dist/commands/workflow.js +179 -58
- package/dist/config.js +10 -5
- package/dist/flags.d.ts +8 -0
- package/dist/flags.js +88 -0
- package/dist/flags.test.d.ts +1 -0
- package/dist/flags.test.js +73 -0
- package/dist/helpers.d.ts +6 -0
- package/dist/helpers.js +31 -0
- package/dist/index.js +98 -109
- package/dist/output.d.ts +12 -1
- package/dist/output.js +16 -6
- package/dist/prompt.d.ts +24 -0
- package/dist/prompt.js +41 -0
- package/dist/skills/my-email-api/README.md +45 -0
- package/dist/skills/my-email-api/SKILL.md +104 -0
- package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-email-api/make/.gitkeep +0 -0
- package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
- package/dist/skills/my-webhook-api/README.md +40 -0
- package/dist/skills/my-webhook-api/SKILL.md +138 -0
- package/dist/skills/my-webhook-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
- package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-webhook-api/openapi/.gitkeep +0 -0
- package/dist/skills/my-workflow-api/README.md +36 -0
- package/dist/skills/my-workflow-api/SKILL.md +156 -0
- package/dist/skills/my-workflow-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
- package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-workflow-api/openapi/.gitkeep +0 -0
- package/dist/utils.d.ts +26 -4
- package/dist/utils.js +32 -33
- package/dist/utils.test.d.ts +1 -0
- package/dist/utils.test.js +48 -0
- package/package.json +9 -4
- package/dist/commands/account.d.ts +0 -4
- package/dist/commands/account.js +0 -80
package/dist/commands/domain.js
CHANGED
|
@@ -2,16 +2,20 @@ 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 { requireOrg, requireDomain } from '../helpers.js';
|
|
6
|
+
export const SCHEMA = {
|
|
7
|
+
org: 'string',
|
|
8
|
+
domain: 'string',
|
|
9
|
+
filter: 'string',
|
|
10
|
+
years: 'number',
|
|
11
|
+
security: 'string',
|
|
12
|
+
'browser-check': 'string',
|
|
13
|
+
'purge-cache': 'boolean',
|
|
14
|
+
};
|
|
5
15
|
export async function check(domainArg, flags) {
|
|
6
16
|
const config = requireConfig();
|
|
7
|
-
const orgId = flags
|
|
8
|
-
const domain = domainArg
|
|
9
|
-
if (!orgId || !domain) {
|
|
10
|
-
const hint = config.default_org
|
|
11
|
-
? `(current default org: ${config.default_org})`
|
|
12
|
-
: '(Set defaults: myapi config set-org <id> / myapi config set-domain <domain>)';
|
|
13
|
-
error(`Missing required arguments.\nUsage: myapi domain check <domain>${config.default_org ? '' : ' --org <id>'}\n${hint}`);
|
|
14
|
-
}
|
|
17
|
+
const orgId = requireOrg(flags, config, 'myapi domain check <domain> [--org <id>]');
|
|
18
|
+
const domain = requireDomain(domainArg, flags, config, 'myapi domain check <domain> [--org <id>]');
|
|
15
19
|
const res = await sdkDomain.checkDomain(config.api_key, orgId, domain);
|
|
16
20
|
if (flags.json) {
|
|
17
21
|
const out = { domain, available: res.available };
|
|
@@ -34,58 +38,57 @@ export async function check(domainArg, flags) {
|
|
|
34
38
|
}
|
|
35
39
|
export async function register(domainArg, flags) {
|
|
36
40
|
const config = requireConfig();
|
|
37
|
-
const orgId = flags
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
info(`
|
|
45
|
-
info(`
|
|
46
|
-
info(`Your funnel will be live on https://${domain} once propagation completes.`);
|
|
41
|
+
const orgId = requireOrg(flags, config, 'myapi domain register <domain> [--years <n>] [--org <id>]');
|
|
42
|
+
if (!domainArg)
|
|
43
|
+
error('Missing required arguments.\nUsage: myapi domain register <domain> [--years <n>] [--org <id>]');
|
|
44
|
+
const years = flags.years || 1;
|
|
45
|
+
await sdkDomain.registerDomain(config.api_key, orgId, domainArg, years);
|
|
46
|
+
success(`Registered ${domainArg}!`);
|
|
47
|
+
info(`Assign it to your org now:\n myapi domain assign ${domainArg} --org ${orgId}`);
|
|
48
|
+
info(`DNS propagation can take a few minutes — track it with: myapi domain status ${domainArg}`);
|
|
49
|
+
info(`Your funnel will be live on https://${domainArg} once propagation completes.`);
|
|
47
50
|
}
|
|
48
51
|
export async function list(flags) {
|
|
49
52
|
const config = requireConfig();
|
|
50
|
-
const orgId = flags
|
|
51
|
-
if (!orgId)
|
|
52
|
-
error("Missing required arguments.\nUsage: myapi domain list --org <id> [--filter=all|unassigned|org]\n(Set default: myapi auth config set-org <id>)");
|
|
53
|
+
const orgId = requireOrg(flags, config, 'myapi domain list [--filter=all|unassigned|org] [--org <id>]');
|
|
53
54
|
const filter = flags.filter;
|
|
54
55
|
const domains = await sdkDomain.listDomains(config.api_key, orgId, filter);
|
|
55
56
|
if (flags.json) {
|
|
56
57
|
printJson(domains);
|
|
57
58
|
return;
|
|
58
59
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
60
|
+
printTable(domains.map(summarizeDomain), {
|
|
61
|
+
flags,
|
|
62
|
+
empty: 'No domains found in this organization. Register one with: myapi domain register <name>',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// Column projection: rename `org_id` → `assigned_to_org` (clearer header)
|
|
66
|
+
// and replace empty/null with "(unassigned)". Lives next to the SDK type
|
|
67
|
+
// so it'll fail at compile time if a field is removed.
|
|
68
|
+
function summarizeDomain(d) {
|
|
69
|
+
const { org_id, ...rest } = d;
|
|
70
|
+
return { ...rest, assigned_to_org: org_id || '(unassigned)' };
|
|
64
71
|
}
|
|
65
72
|
export async function assign(domainArg, flags) {
|
|
66
73
|
const config = requireConfig();
|
|
67
|
-
const orgId = flags
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
success(`Assigned ${domain} to org ${orgId}`);
|
|
74
|
+
const orgId = requireOrg(flags, config, 'myapi domain assign <domain> [--org <id>]');
|
|
75
|
+
if (!domainArg)
|
|
76
|
+
error('Missing required arguments.\nUsage: myapi domain assign <domain> [--org <id>]');
|
|
77
|
+
await sdkDomain.assignDomain(config.api_key, orgId, domainArg);
|
|
78
|
+
success(`Assigned ${domainArg} to org ${orgId}`);
|
|
73
79
|
}
|
|
74
80
|
export async function unassign(domainArg, flags) {
|
|
75
81
|
const config = requireConfig();
|
|
76
|
-
const orgId = flags
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
success(`Unassigned ${domain} from org ${orgId}`);
|
|
82
|
+
const orgId = requireOrg(flags, config, 'myapi domain unassign <domain> [--org <id>]');
|
|
83
|
+
if (!domainArg)
|
|
84
|
+
error('Missing required arguments.\nUsage: myapi domain unassign <domain> [--org <id>]');
|
|
85
|
+
await sdkDomain.unassignDomain(config.api_key, orgId, domainArg);
|
|
86
|
+
success(`Unassigned ${domainArg} from org ${orgId}`);
|
|
82
87
|
}
|
|
83
88
|
export async function status(domainArg, flags) {
|
|
84
89
|
const config = requireConfig();
|
|
85
|
-
const orgId = flags
|
|
86
|
-
const domain = domainArg
|
|
87
|
-
if (!orgId || !domain)
|
|
88
|
-
error("Missing required arguments.\nUsage: myapi domain status <domain> --org <id>\n(Set defaults: myapi auth config set-org <id> / myapi auth config set-domain <domain>)");
|
|
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>]');
|
|
89
92
|
const res = await sdkDomain.getDomainStatus(config.api_key, orgId, domain);
|
|
90
93
|
if (flags.json) {
|
|
91
94
|
printJson(res);
|
|
@@ -96,23 +99,19 @@ export async function status(domainArg, flags) {
|
|
|
96
99
|
if (res.expires_at)
|
|
97
100
|
info(`Expires: ${formatDate(res.expires_at)}`);
|
|
98
101
|
if (res.status === 'active')
|
|
99
|
-
info(
|
|
102
|
+
info('Note: if recently activated, the SSL certificate may still be provisioning — allow a few minutes before the site is reachable over HTTPS.');
|
|
100
103
|
}
|
|
101
104
|
export async function settings(domainArg, flags) {
|
|
102
105
|
const config = requireConfig();
|
|
103
|
-
const orgId = flags
|
|
104
|
-
const domain = domainArg
|
|
105
|
-
if (!orgId || !domain)
|
|
106
|
-
error("Missing required arguments.\nUsage: myapi domain settings <domain> --org <id>\n(Set defaults: myapi auth config set-org <id> / myapi auth config set-domain <domain>)");
|
|
106
|
+
const orgId = requireOrg(flags, config, 'myapi domain settings <domain> [--org <id>]');
|
|
107
|
+
const domain = requireDomain(domainArg, flags, config, 'myapi domain settings <domain> [--org <id>]');
|
|
107
108
|
const res = await sdkDomain.getDomainSettings(config.api_key, orgId, domain);
|
|
108
109
|
printJson(res);
|
|
109
110
|
}
|
|
110
111
|
export async function updateSettings(domainArg, flags) {
|
|
111
112
|
const config = requireConfig();
|
|
112
|
-
const orgId = flags
|
|
113
|
-
const domain = domainArg
|
|
114
|
-
if (!orgId || !domain)
|
|
115
|
-
error("Missing required arguments.\nUsage: myapi domain update-settings <domain> --org <id> [--security=...] [--browser-check=...] [--purge-cache]\n(Set defaults: myapi auth config set-org <id> / myapi auth config set-domain <domain>)");
|
|
113
|
+
const orgId = requireOrg(flags, config, 'myapi domain update-settings <domain> [--org <id>]');
|
|
114
|
+
const domain = requireDomain(domainArg, flags, config, 'myapi domain update-settings <domain> [--org <id>]');
|
|
116
115
|
const payload = {};
|
|
117
116
|
if (flags.security)
|
|
118
117
|
payload.security_level = flags.security;
|
|
@@ -120,49 +119,88 @@ export async function updateSettings(domainArg, flags) {
|
|
|
120
119
|
payload.browser_check = flags['browser-check'];
|
|
121
120
|
if (flags['purge-cache'])
|
|
122
121
|
payload.purge_cache = true;
|
|
122
|
+
if (Object.keys(payload).length === 0) {
|
|
123
|
+
error('Nothing to update. Pass at least one of --security=<level>, --browser-check=<on|off>, --purge-cache.');
|
|
124
|
+
}
|
|
123
125
|
const res = await sdkDomain.updateDomainSettings(config.api_key, orgId, domain, payload);
|
|
124
126
|
success(`Updated settings for ${domain}!\n${JSON.stringify(res, null, 2)}`);
|
|
125
127
|
}
|
|
128
|
+
// ── Dispatcher ───────────────────────────────────────────────────────────────
|
|
129
|
+
const SUBCOMMAND_USAGE = {
|
|
130
|
+
'list': `myapi domain list [--filter=all|unassigned|org] [--org <id>] [--json]
|
|
131
|
+
|
|
132
|
+
--filter=all All domains owned by the account.
|
|
133
|
+
--filter=unassigned Domains not yet assigned to any org.
|
|
134
|
+
--filter=org (Default) Domains assigned to the specified org.
|
|
135
|
+
|
|
136
|
+
Note: --filter=all and --filter=unassigned are account-wide; --org is only used
|
|
137
|
+
to authenticate the request.`,
|
|
138
|
+
'check': 'myapi domain check <domain> [--org <id>] [--json]',
|
|
139
|
+
'register': `myapi domain register <domain> [--years <num>] [--org <id>]
|
|
140
|
+
|
|
141
|
+
--years <num> Number of years to register for (default: 1)
|
|
142
|
+
|
|
143
|
+
Prerequisites:
|
|
144
|
+
- Sufficient balance (myapi billing balance)
|
|
145
|
+
- Domain available (myapi domain check <domain>)
|
|
146
|
+
|
|
147
|
+
After registering: myapi domain assign <domain>
|
|
148
|
+
DNS propagation takes a few minutes — track it with: myapi domain status <domain>`,
|
|
149
|
+
'assign': `myapi domain assign <domain> [--org <id>]
|
|
150
|
+
|
|
151
|
+
Assigns a registered domain to an org. Once assigned, your funnel is served at
|
|
152
|
+
https://<domain> after DNS propagation completes.
|
|
153
|
+
|
|
154
|
+
To transfer to a different org: unassign first, then assign to the new org.`,
|
|
155
|
+
'unassign': 'myapi domain unassign <domain> [--org <id>]',
|
|
156
|
+
'status': 'myapi domain status <domain> [--org <id>] [--json]',
|
|
157
|
+
'settings': `myapi domain settings <domain> [--org <id>]
|
|
158
|
+
|
|
159
|
+
Gets current edge/CDN settings (security level, browser check, cache).
|
|
160
|
+
Use "myapi domain update-settings" to change them.`,
|
|
161
|
+
'update-settings': `myapi domain update-settings <domain> [--security=<level>] [--browser-check=on|off] [--purge-cache] [--org <id>]
|
|
162
|
+
|
|
163
|
+
--security=<level> essentially_off | low | medium | high | under_attack (default: medium)
|
|
164
|
+
--browser-check=<on|off> Browser integrity challenge for suspicious traffic (default: on)
|
|
165
|
+
--purge-cache Immediately clear the CDN cache for this domain
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
myapi domain update-settings example.com --security=high --purge-cache`,
|
|
169
|
+
};
|
|
126
170
|
export async function run(subcommand, args, flags) {
|
|
127
171
|
if (!subcommand || (flags.help && !subcommand)) {
|
|
128
|
-
info(
|
|
172
|
+
info(`Usage: myapi domain <subcommand>
|
|
173
|
+
|
|
174
|
+
Subcommands:
|
|
175
|
+
list List domains
|
|
176
|
+
check Check domain availability
|
|
177
|
+
register Register a domain
|
|
178
|
+
assign Assign domain to an org
|
|
179
|
+
unassign Unassign domain from its current org
|
|
180
|
+
status Get domain status
|
|
181
|
+
settings Get edge/CDN settings
|
|
182
|
+
update-settings Update edge/CDN settings
|
|
183
|
+
|
|
184
|
+
Tip: Set defaults with "myapi config set-org <id>" / "set-domain <domain>" to skip flags on every command.`);
|
|
129
185
|
return;
|
|
130
186
|
}
|
|
131
187
|
if (flags.help) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
info('Usage: myapi domain register <domain> --org <id> [--years <num>]\n\nRegisters a new domain name under your account.\n\nFlags:\n --years <num> Number of years to register for (default: 1)\n\nPrerequisites:\n - You must have sufficient balance (myapi billing balance)\n - The domain must be available (myapi domain check <domain>)\n\nAfter registering, assign the domain to your org:\n myapi domain assign <domain> --org <id>\n\nDNS propagation takes a few minutes. Track it with:\n myapi domain status <domain>');
|
|
138
|
-
else if (subcommand === 'assign')
|
|
139
|
-
info('Usage: myapi domain assign <domain> --org <id>\n\nAssigns a registered domain to an organization. The domain must already be\nregistered under your account. Once assigned, your funnel will be served at\nhttps://<domain> after DNS propagation completes.\n\nTo transfer a domain to a different org: unassign it first, then assign to the new org.');
|
|
140
|
-
else if (subcommand === 'unassign')
|
|
141
|
-
info('Usage: myapi domain unassign <domain> --org <id>\n\nUnassigns a domain from its current organization. The domain remains registered\nunder your account and can be reassigned to a different org at any time.');
|
|
142
|
-
else if (subcommand === 'status')
|
|
143
|
-
info('Usage: myapi domain status <domain> --org <id> [--json]\n\nGets the registration status of a domain.\n\nFlags:\n --json Output raw JSON');
|
|
144
|
-
else if (subcommand === 'settings')
|
|
145
|
-
info('Usage: myapi domain settings <domain> --org <id>\n\nGets the current edge/CDN settings for a domain (security level, browser check, cache).\nUse "myapi domain update-settings" to change them.');
|
|
146
|
-
else if (subcommand === 'update-settings')
|
|
147
|
-
info('Usage: myapi domain update-settings <domain> --org <id> [flags]\n\nUpdates edge/CDN settings for a domain.\n\nFlags:\n --security=<level> CDN security level. Controls bot/threat filtering.\n Values: essentially_off | low | medium | high | under_attack\n Default (if unset): medium\n --browser-check=<on|off> Enable or disable browser integrity challenge for suspicious traffic.\n Default: on\n --purge-cache Immediately clears the CDN cache for this domain.\n Use after deploying changes that are not yet visible.\n\nExample:\n myapi domain update-settings example.com --org <id> --security=high --purge-cache');
|
|
188
|
+
const usage = SUBCOMMAND_USAGE[subcommand];
|
|
189
|
+
if (usage)
|
|
190
|
+
info(`Usage: ${usage}`);
|
|
191
|
+
else
|
|
192
|
+
info(`Unknown subcommand: ${subcommand}. Run "myapi domain --help" for the list.`);
|
|
148
193
|
return;
|
|
149
194
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
await status(args[0], flags);
|
|
162
|
-
else if (subcommand === 'settings')
|
|
163
|
-
await settings(args[0], flags);
|
|
164
|
-
else if (subcommand === 'update-settings')
|
|
165
|
-
await updateSettings(args[0], flags);
|
|
166
|
-
else
|
|
167
|
-
error(`Unknown subcommand: ${subcommand}. Run "myapi domain --help" for a list of valid subcommands.`);
|
|
195
|
+
switch (subcommand) {
|
|
196
|
+
case 'list': return list(flags);
|
|
197
|
+
case 'check': return check(args[0], flags);
|
|
198
|
+
case 'register': return register(args[0], flags);
|
|
199
|
+
case 'assign': return assign(args[0], flags);
|
|
200
|
+
case 'unassign': return unassign(args[0], flags);
|
|
201
|
+
case 'status': return status(args[0], flags);
|
|
202
|
+
case 'settings': return settings(args[0], flags);
|
|
203
|
+
case 'update-settings': return updateSettings(args[0], flags);
|
|
204
|
+
default: error(`Unknown subcommand: ${subcommand}. Run "myapi domain --help" for a list of valid subcommands.`);
|
|
205
|
+
}
|
|
168
206
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { email as sdkEmail } from '@myapihq/sdk';
|
|
2
|
+
import { requireConfig } from '../../config.js';
|
|
3
|
+
import { success, error, printTable, printJson, info } from '../../output.js';
|
|
4
|
+
import { requireOrg } from '../../helpers.js';
|
|
5
|
+
async function create(nameArg, flags) {
|
|
6
|
+
const config = requireConfig();
|
|
7
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign create <name> --template-id <id> --from <email> [--per-day <n>] [--org <id>]');
|
|
8
|
+
const name = nameArg || flags.name;
|
|
9
|
+
if (!name || !flags['template-id'] || !flags.from) {
|
|
10
|
+
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>]');
|
|
11
|
+
}
|
|
12
|
+
const perDayLimit = flags['per-day'];
|
|
13
|
+
const res = await sdkEmail.createCampaign(config.api_key, orgId, {
|
|
14
|
+
name,
|
|
15
|
+
template_id: flags['template-id'],
|
|
16
|
+
from_address: flags.from,
|
|
17
|
+
per_day_limit: perDayLimit,
|
|
18
|
+
});
|
|
19
|
+
success(`Campaign created! ID: ${res.id}`);
|
|
20
|
+
}
|
|
21
|
+
async function list(flags) {
|
|
22
|
+
const config = requireConfig();
|
|
23
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign list [--org <id>]');
|
|
24
|
+
const campaigns = await sdkEmail.listCampaigns(config.api_key, orgId);
|
|
25
|
+
printTable(campaigns, {
|
|
26
|
+
flags,
|
|
27
|
+
empty: 'No campaigns yet. Create one with: myapi email campaign create',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function get(id, flags) {
|
|
31
|
+
const config = requireConfig();
|
|
32
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign get <id> [--org <id>]');
|
|
33
|
+
if (!id)
|
|
34
|
+
error('Missing required arguments.\nUsage: myapi email campaign get <id> [--org <id>]');
|
|
35
|
+
const c = await sdkEmail.getCampaign(config.api_key, orgId, id);
|
|
36
|
+
printJson(c);
|
|
37
|
+
}
|
|
38
|
+
async function update(id, flags) {
|
|
39
|
+
const config = requireConfig();
|
|
40
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign update <id> [--name <str>] [--per-day <n>] [--org <id>]');
|
|
41
|
+
if (!id)
|
|
42
|
+
error('Missing required arguments.\nUsage: myapi email campaign update <id> [--name <str>] [--per-day <n>] [--org <id>]');
|
|
43
|
+
const payload = {};
|
|
44
|
+
if (flags.name)
|
|
45
|
+
payload.name = flags.name;
|
|
46
|
+
if (flags['per-day'])
|
|
47
|
+
payload.per_day_limit = flags['per-day'];
|
|
48
|
+
if (!payload.name && payload.per_day_limit === undefined) {
|
|
49
|
+
error('Nothing to update. Pass at least one of --name, --per-day.');
|
|
50
|
+
}
|
|
51
|
+
const res = await sdkEmail.updateCampaign(config.api_key, orgId, id, payload);
|
|
52
|
+
success(`Campaign ${res.id} updated`);
|
|
53
|
+
}
|
|
54
|
+
async function uploadContacts(id, flags) {
|
|
55
|
+
const config = requireConfig();
|
|
56
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign upload-contacts <campaign_id> --emails <a@x,b@y> [--org <id>]');
|
|
57
|
+
if (!id || !flags.emails) {
|
|
58
|
+
error('Missing required arguments.\nUsage: myapi email campaign upload-contacts <campaign_id> --emails <a@x,b@y> [--org <id>]');
|
|
59
|
+
}
|
|
60
|
+
const emails = flags.emails.split(',').map(s => s.trim()).filter(Boolean);
|
|
61
|
+
const res = await sdkEmail.uploadContactList(config.api_key, orgId, id, emails);
|
|
62
|
+
success(`Uploaded ${res.total} contacts (status: ${res.status})`);
|
|
63
|
+
}
|
|
64
|
+
async function start(id, flags) {
|
|
65
|
+
const config = requireConfig();
|
|
66
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign start <id> [--org <id>]');
|
|
67
|
+
if (!id)
|
|
68
|
+
error('Missing required arguments.\nUsage: myapi email campaign start <id> [--org <id>]');
|
|
69
|
+
const status = await sdkEmail.getContactUploadStatus(config.api_key, orgId, id);
|
|
70
|
+
if (status.upload_status !== 'ready') {
|
|
71
|
+
error(`Contacts not ready (current status: ${status.upload_status})`);
|
|
72
|
+
}
|
|
73
|
+
const res = await sdkEmail.startCampaign(config.api_key, orgId, id);
|
|
74
|
+
success(`Campaign started (status: ${res.status})`);
|
|
75
|
+
}
|
|
76
|
+
async function pause(id, flags) {
|
|
77
|
+
const config = requireConfig();
|
|
78
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign pause <id> [--org <id>]');
|
|
79
|
+
if (!id)
|
|
80
|
+
error('Missing required arguments.\nUsage: myapi email campaign pause <id> [--org <id>]');
|
|
81
|
+
await sdkEmail.pauseCampaign(config.api_key, orgId, id);
|
|
82
|
+
success(`Campaign ${id} paused`);
|
|
83
|
+
}
|
|
84
|
+
async function resume(id, flags) {
|
|
85
|
+
const config = requireConfig();
|
|
86
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign resume <id> [--org <id>]');
|
|
87
|
+
if (!id)
|
|
88
|
+
error('Missing required arguments.\nUsage: myapi email campaign resume <id> [--org <id>]');
|
|
89
|
+
await sdkEmail.resumeCampaign(config.api_key, orgId, id);
|
|
90
|
+
success(`Campaign ${id} resumed`);
|
|
91
|
+
}
|
|
92
|
+
async function stats(id, flags) {
|
|
93
|
+
const config = requireConfig();
|
|
94
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign stats <id> [--org <id>]');
|
|
95
|
+
if (!id)
|
|
96
|
+
error('Missing required arguments.\nUsage: myapi email campaign stats <id> [--org <id>]');
|
|
97
|
+
const res = await sdkEmail.getCampaignStats(config.api_key, orgId, id);
|
|
98
|
+
printJson(res);
|
|
99
|
+
}
|
|
100
|
+
const USAGE = {
|
|
101
|
+
'create': `myapi email campaign create <name> --template-id <id> --from <email> [--per-day <n>] [--org <id>]
|
|
102
|
+
myapi email campaign create --name <name> --template-id <id> --from <email> [--per-day <n>] [--org <id>]
|
|
103
|
+
|
|
104
|
+
Either form works; the positional name is the recommended shape.`,
|
|
105
|
+
'list': 'myapi email campaign list [--org <id>]',
|
|
106
|
+
'get': 'myapi email campaign get <id> [--org <id>]',
|
|
107
|
+
'update': 'myapi email campaign update <id> [--name <str>] [--per-day <n>] [--org <id>]',
|
|
108
|
+
'upload-contacts': 'myapi email campaign upload-contacts <campaign_id> --emails <a@x,b@y> [--org <id>]',
|
|
109
|
+
'start': 'myapi email campaign start <id> [--org <id>]',
|
|
110
|
+
'pause': 'myapi email campaign pause <id> [--org <id>]',
|
|
111
|
+
'resume': 'myapi email campaign resume <id> [--org <id>]',
|
|
112
|
+
'stats': 'myapi email campaign stats <id> [--org <id>]',
|
|
113
|
+
};
|
|
114
|
+
export async function run(sub, args, flags) {
|
|
115
|
+
if (!sub || (flags.help && !sub)) {
|
|
116
|
+
info(`Usage: myapi email campaign <subcommand>
|
|
117
|
+
|
|
118
|
+
Subcommands:
|
|
119
|
+
create <name> Create a campaign (--template-id --from)
|
|
120
|
+
list List campaigns
|
|
121
|
+
get <id> Get a single campaign
|
|
122
|
+
update <id> Patch name / per-day-limit
|
|
123
|
+
upload-contacts <id> Upload contact list (--emails a@x,b@y)
|
|
124
|
+
start <id> Start a campaign
|
|
125
|
+
pause <id> Pause a campaign
|
|
126
|
+
resume <id> Resume a campaign
|
|
127
|
+
stats <id> Get campaign stats
|
|
128
|
+
|
|
129
|
+
All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (flags.help) {
|
|
133
|
+
const usage = USAGE[sub];
|
|
134
|
+
if (usage)
|
|
135
|
+
info(`Usage: ${usage}`);
|
|
136
|
+
else
|
|
137
|
+
info(`Unknown subcommand: ${sub}. Run "myapi email campaign --help" for the list.`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
switch (sub) {
|
|
141
|
+
case 'create': return create(args[0], flags);
|
|
142
|
+
case 'list': return list(flags);
|
|
143
|
+
case 'get': return get(args[0], flags);
|
|
144
|
+
case 'update': return update(args[0], flags);
|
|
145
|
+
case 'upload-contacts': return uploadContacts(args[0], flags);
|
|
146
|
+
case 'start': return start(args[0], flags);
|
|
147
|
+
case 'pause': return pause(args[0], flags);
|
|
148
|
+
case 'resume': return resume(args[0], flags);
|
|
149
|
+
case 'stats': return stats(args[0], flags);
|
|
150
|
+
default: error(`Unknown subcommand: ${sub}. Run "myapi email campaign --help" for the list.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Top-level router for `myapi email <namespace> <subcommand>`. Each
|
|
2
|
+
// namespace lives in its own file in this directory; this index just
|
|
3
|
+
// routes to the right one and handles the deprecated flat aliases.
|
|
4
|
+
import { error, info } from '../../output.js';
|
|
5
|
+
import * as mailbox from './mailbox.js';
|
|
6
|
+
import * as message from './message.js';
|
|
7
|
+
import * as warmup from './warmup.js';
|
|
8
|
+
import * as template from './template.js';
|
|
9
|
+
import * as campaign from './campaign.js';
|
|
10
|
+
export const SCHEMA = {
|
|
11
|
+
org: 'string',
|
|
12
|
+
domain: 'string',
|
|
13
|
+
username: 'string',
|
|
14
|
+
'display-name': 'string',
|
|
15
|
+
filter: 'string',
|
|
16
|
+
address: 'string',
|
|
17
|
+
from: 'string',
|
|
18
|
+
to: 'string',
|
|
19
|
+
subject: 'string',
|
|
20
|
+
body: 'string',
|
|
21
|
+
html: 'string',
|
|
22
|
+
'template-id': 'string',
|
|
23
|
+
'template-vars': 'string',
|
|
24
|
+
limit: 'number',
|
|
25
|
+
offset: 'number',
|
|
26
|
+
prompt: 'string',
|
|
27
|
+
name: 'string',
|
|
28
|
+
emails: 'string',
|
|
29
|
+
'per-day': 'number',
|
|
30
|
+
};
|
|
31
|
+
// Old flat subcommand → [namespace, sub]. Kept so existing scripts/skills
|
|
32
|
+
// don't break. Emits a one-line stderr deprecation note then runs the new
|
|
33
|
+
// path.
|
|
34
|
+
const DEPRECATED_ALIASES = {
|
|
35
|
+
'create-mailbox': ['mailbox', 'create'],
|
|
36
|
+
'list-mailboxes': ['mailbox', 'list'],
|
|
37
|
+
'activate-sending': ['mailbox', 'activate-sending'],
|
|
38
|
+
'send': ['message', 'send'],
|
|
39
|
+
'get-status': ['message', 'status'],
|
|
40
|
+
'sent': ['message', 'sent'],
|
|
41
|
+
'inbox': ['message', 'inbox'],
|
|
42
|
+
'outbox': ['message', 'outbox'],
|
|
43
|
+
'get-message': ['message', 'get'],
|
|
44
|
+
'generate-template': ['template', 'generate'],
|
|
45
|
+
'list-templates': ['template', 'list'],
|
|
46
|
+
'edit-template': ['template', 'edit'],
|
|
47
|
+
'send-test': ['template', 'send-test'],
|
|
48
|
+
'delete-template': ['template', 'delete'],
|
|
49
|
+
'create-campaign': ['campaign', 'create'],
|
|
50
|
+
'list-campaigns': ['campaign', 'list'],
|
|
51
|
+
'get-campaign': ['campaign', 'get'],
|
|
52
|
+
'update-campaign': ['campaign', 'update'],
|
|
53
|
+
'upload-contacts': ['campaign', 'upload-contacts'],
|
|
54
|
+
'start-campaign': ['campaign', 'start'],
|
|
55
|
+
'pause-campaign': ['campaign', 'pause'],
|
|
56
|
+
'resume-campaign': ['campaign', 'resume'],
|
|
57
|
+
'campaign-stats': ['campaign', 'stats'],
|
|
58
|
+
};
|
|
59
|
+
async function dispatchNamespace(ns, sub, restArgs, flags) {
|
|
60
|
+
switch (ns) {
|
|
61
|
+
case 'mailbox': return mailbox.run(sub, restArgs, flags);
|
|
62
|
+
case 'message': return message.run(sub, restArgs, flags);
|
|
63
|
+
case 'warmup': return warmup.run(sub, restArgs, flags);
|
|
64
|
+
case 'template': return template.run(sub, restArgs, flags);
|
|
65
|
+
case 'campaign': return campaign.run(sub, restArgs, flags);
|
|
66
|
+
default: error(`Unknown namespace: ${ns}. Run "myapi email --help" for the list.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export async function run(subcommand, args, flags) {
|
|
70
|
+
if (!subcommand || (flags.help && !subcommand)) {
|
|
71
|
+
info(`Usage: myapi email <namespace> <subcommand>
|
|
72
|
+
|
|
73
|
+
Namespaces:
|
|
74
|
+
mailbox Mailboxes (account-scoped) — create, list, activate-sending
|
|
75
|
+
message Send & read mail (account-scoped) — send, status, sent, inbox, outbox, get
|
|
76
|
+
warmup Warmup controls (account-scoped) — start, pause, resume, stop, stats
|
|
77
|
+
template Email templates (org-scoped) — generate, list, edit, send-test, delete
|
|
78
|
+
campaign Email campaigns (org-scoped) — create, list, get, update, upload-contacts, start, pause, resume, stats
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
myapi email mailbox create hello@example.com
|
|
82
|
+
myapi email message send --from a@x --to b@y --subject hi --body "hello"
|
|
83
|
+
myapi email template generate welcome --prompt "Welcome email"
|
|
84
|
+
myapi email campaign create "Q2 Launch" --template-id <id> --from a@x
|
|
85
|
+
|
|
86
|
+
Org-scoped commands accept --org <id> (or set default: myapi config set-org <id>).
|
|
87
|
+
Run "myapi email <namespace> --help" for namespace-specific commands.`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Deprecated flat command? Forward to the new path with a one-line note.
|
|
91
|
+
const aliased = DEPRECATED_ALIASES[subcommand];
|
|
92
|
+
if (aliased) {
|
|
93
|
+
const [ns, sub] = aliased;
|
|
94
|
+
process.stderr.write(`› Note: "email ${subcommand}" is deprecated — use "email ${ns} ${sub}" instead.\n`);
|
|
95
|
+
return dispatchNamespace(ns, sub, args, flags);
|
|
96
|
+
}
|
|
97
|
+
return dispatchNamespace(subcommand, args[0], args.slice(1), flags);
|
|
98
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { email as sdkEmail } from '@myapihq/sdk';
|
|
2
|
+
import { requireConfig } from '../../config.js';
|
|
3
|
+
import { success, error, printTable, info } from '../../output.js';
|
|
4
|
+
async function create(addressArg, flags) {
|
|
5
|
+
const config = requireConfig();
|
|
6
|
+
// Convention: first required arg is positional. For mailboxes that's the
|
|
7
|
+
// full address `user@domain`. The flag form (--username + --domain) is
|
|
8
|
+
// still accepted for back-compat / scripts that built up parts separately.
|
|
9
|
+
let username = flags.username;
|
|
10
|
+
let domain = flags.domain || config.default_domain;
|
|
11
|
+
if (addressArg) {
|
|
12
|
+
const at = addressArg.indexOf('@');
|
|
13
|
+
if (at > 0 && at < addressArg.length - 1) {
|
|
14
|
+
username = addressArg.slice(0, at);
|
|
15
|
+
domain = addressArg.slice(at + 1);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
error(`"${addressArg}" is not a valid email address. Expected format: user@domain.com`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (!username || !domain) {
|
|
22
|
+
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>)');
|
|
23
|
+
}
|
|
24
|
+
const res = await sdkEmail.createMailbox(config.api_key, domain, username, flags['display-name']);
|
|
25
|
+
// Backend response sometimes omits address; we know it from the inputs.
|
|
26
|
+
success(`Mailbox created: ${res.address || `${username}@${domain}`}`);
|
|
27
|
+
}
|
|
28
|
+
async function list(flags) {
|
|
29
|
+
const config = requireConfig();
|
|
30
|
+
if (!flags.domain && flags.filter !== 'unassigned') {
|
|
31
|
+
error('Must provide either --domain <domain> (mailboxes on that domain) or --filter unassigned (mailboxes whose domain is not assigned to any org).');
|
|
32
|
+
}
|
|
33
|
+
const mailboxes = await sdkEmail.listMailboxes(config.api_key, {
|
|
34
|
+
domain: flags.domain,
|
|
35
|
+
filter: flags.filter === 'unassigned' ? 'unassigned' : undefined,
|
|
36
|
+
});
|
|
37
|
+
printTable(mailboxes, {
|
|
38
|
+
flags,
|
|
39
|
+
empty: 'No mailboxes found.',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function activateSending(flags) {
|
|
43
|
+
const config = requireConfig();
|
|
44
|
+
const address = flags.address;
|
|
45
|
+
if (!address)
|
|
46
|
+
error('Missing required arguments.\nUsage: myapi email mailbox activate-sending --address <email>');
|
|
47
|
+
const res = await sdkEmail.activateSending(config.api_key, address);
|
|
48
|
+
success(`Sending activated: ${address} (status: ${res.status})`);
|
|
49
|
+
}
|
|
50
|
+
const USAGE = {
|
|
51
|
+
'create': `myapi email mailbox create <user@domain> [--display-name <name>]
|
|
52
|
+
myapi email mailbox create --username <u> --domain <d> [--display-name <name>]
|
|
53
|
+
|
|
54
|
+
Either form works. The positional address is the recommended shape and
|
|
55
|
+
matches the rest of the CLI ("first required arg is positional").`,
|
|
56
|
+
'list': `myapi email mailbox list (--domain <domain> | --filter unassigned)
|
|
57
|
+
|
|
58
|
+
--domain <domain> List mailboxes on the given domain.
|
|
59
|
+
--filter unassigned List mailboxes whose domain is NOT assigned to any
|
|
60
|
+
org (orphaned mailboxes — domain was unassigned
|
|
61
|
+
without first deleting them).`,
|
|
62
|
+
'activate-sending': 'myapi email mailbox activate-sending --address <email>',
|
|
63
|
+
};
|
|
64
|
+
export async function run(sub, args, flags) {
|
|
65
|
+
if (!sub || (flags.help && !sub)) {
|
|
66
|
+
info(`Usage: myapi email mailbox <subcommand>
|
|
67
|
+
|
|
68
|
+
Subcommands:
|
|
69
|
+
create Create a mailbox (positional <user@domain> or --username/--domain)
|
|
70
|
+
list List mailboxes on a domain (--domain) or orphaned ones (--filter unassigned)
|
|
71
|
+
activate-sending Activate outbound sending for a mailbox`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (flags.help) {
|
|
75
|
+
const usage = USAGE[sub];
|
|
76
|
+
if (usage)
|
|
77
|
+
info(`Usage: ${usage}`);
|
|
78
|
+
else
|
|
79
|
+
info(`Unknown subcommand: ${sub}. Run "myapi email mailbox --help" for the list.`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
switch (sub) {
|
|
83
|
+
case 'create': return create(args[0], flags);
|
|
84
|
+
case 'list': return list(flags);
|
|
85
|
+
case 'activate-sending': return activateSending(flags);
|
|
86
|
+
default: error(`Unknown subcommand: ${sub}. Run "myapi email mailbox --help" for the list.`);
|
|
87
|
+
}
|
|
88
|
+
}
|