@myapihq/cli 1.1.0-wip.0 → 1.1.0-wip.2
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.js +75 -33
- package/dist/commands/billing.js +3 -5
- package/dist/commands/domain.js +8 -1
- 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.js +71 -51
- package/dist/commands/funnel.d.ts +1 -1
- package/dist/commands/funnel.js +31 -9
- package/dist/commands/keys.d.ts +1 -1
- package/dist/commands/keys.js +6 -9
- package/dist/commands/org.js +17 -34
- package/dist/commands/setup.js +143 -115
- package/dist/commands/update.js +5 -0
- package/dist/commands/webhook.d.ts +1 -1
- package/dist/commands/webhook.js +23 -7
- package/dist/commands/workflow.d.ts +1 -1
- package/dist/commands/workflow.js +45 -6
- package/dist/config.js +10 -5
- package/dist/helpers.js +8 -6
- package/dist/index.js +9 -3
- package/dist/output.d.ts +3 -1
- package/dist/output.js +12 -0
- package/dist/prompt.d.ts +24 -0
- package/dist/prompt.js +41 -0
- package/dist/skills/my-email-api/README.md +1 -1
- package/dist/skills/my-email-api/SKILL.md +4 -6
- package/dist/skills/my-webhook-api/README.md +1 -1
- package/dist/skills/my-webhook-api/SKILL.md +77 -4
- package/dist/skills/my-workflow-api/README.md +3 -4
- package/dist/skills/my-workflow-api/SKILL.md +79 -10
- package/dist/utils.d.ts +26 -0
- package/dist/utils.js +32 -0
- package/package.json +1 -1
package/dist/commands/auth.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as readline from 'readline';
|
|
2
1
|
import { loadConfig, saveConfig, addAccount, switchAccount, listAccounts } from '../config.js';
|
|
3
2
|
import { info, success, error, printJson } from '../output.js';
|
|
3
|
+
import { ask, confirm, withReadline } from '../prompt.js';
|
|
4
4
|
import { installSkills } from './setup.js';
|
|
5
5
|
import { hq } from '@myapihq/sdk';
|
|
6
6
|
export const SCHEMA = {
|
|
@@ -9,21 +9,75 @@ export const SCHEMA = {
|
|
|
9
9
|
anonymous: 'boolean',
|
|
10
10
|
anon: 'boolean',
|
|
11
11
|
};
|
|
12
|
-
export const HELP =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
export const HELP = `Usage: myapi auth <subcommand>
|
|
13
|
+
|
|
14
|
+
Subcommands:
|
|
15
|
+
setup Configure your account
|
|
16
|
+
import-key Import an existing API key non-interactively
|
|
17
|
+
whoami Show current account · supports --json
|
|
18
|
+
link [email] Upgrade anonymous account to registered (or add a second session)
|
|
19
|
+
Use myapi auth setup to create a completely new account
|
|
20
|
+
switch [index] Switch active account by index or email
|
|
21
|
+
config Manage CLI defaults (org, funnel, domain) · supports set-org / set-funnel / set-domain
|
|
22
|
+
install-skills Install or update the MyAPI skills pack for AI agents
|
|
23
|
+
api-keys Manage API keys · list / create / revoke
|
|
24
|
+
keys Alias for api-keys`;
|
|
25
|
+
export const INSTALL_SKILLS_HELP = `Usage: myapi auth install-skills
|
|
26
|
+
|
|
27
|
+
Installs the MyAPI skills pack for AI coding agents (Claude, Gemini, Cursor).
|
|
28
|
+
|
|
29
|
+
This command writes skill definition files to:
|
|
30
|
+
~/.agents/skills/myapi/
|
|
31
|
+
|
|
32
|
+
And creates symlinks in the appropriate agent config directories:
|
|
33
|
+
~/.claude/ (Claude)
|
|
34
|
+
~/.gemini/ (Gemini)
|
|
35
|
+
~/.cursor/ (Cursor, if detected)
|
|
36
|
+
|
|
37
|
+
These files teach agents how to use the MyAPI CLI and API directly.
|
|
38
|
+
Run this command again to update existing skills to the latest version.`;
|
|
39
|
+
const LINK_HELP = `Usage: myapi auth link [email]
|
|
40
|
+
|
|
41
|
+
Behavior depends on whether the email is new or already registered:
|
|
42
|
+
|
|
43
|
+
New email: Attaches it to your current anonymous account, upgrading it
|
|
44
|
+
to a registered account. A one-time code is sent to confirm.
|
|
45
|
+
|
|
46
|
+
Existing email: Does NOT merge accounts. The existing account is added as a
|
|
47
|
+
second active session — your anonymous account stays separate.
|
|
48
|
+
Switch between sessions with: myapi auth switch
|
|
49
|
+
|
|
50
|
+
To create a completely new account from scratch, use: myapi auth setup`;
|
|
51
|
+
const WHOAMI_HELP = `Usage: myapi auth whoami [--json]
|
|
52
|
+
|
|
53
|
+
Displays the active account details: email, account ID, default org,
|
|
54
|
+
default funnel, account type, and current balance.
|
|
55
|
+
|
|
56
|
+
Flags:
|
|
57
|
+
--json Output raw JSON`;
|
|
58
|
+
const SWITCH_HELP = `Usage: myapi auth switch [index|email]
|
|
59
|
+
|
|
60
|
+
Switches the active account. Run without arguments to see a numbered list
|
|
61
|
+
of accounts and choose interactively.
|
|
62
|
+
|
|
63
|
+
Pass an index number or email address to switch non-interactively:
|
|
64
|
+
myapi auth switch 2
|
|
65
|
+
myapi auth switch you@example.com
|
|
66
|
+
|
|
67
|
+
Note: index numbers can shift as accounts are added or removed.
|
|
68
|
+
Use email as a stable identifier in scripts.`;
|
|
69
|
+
// Resolve the user's "install skills?" preference: explicit flag wins, else prompt.
|
|
70
|
+
async function resolveSkillsPreference(flags) {
|
|
71
|
+
if (flags['install-skills'])
|
|
72
|
+
return true;
|
|
73
|
+
if (flags['no-skills'])
|
|
74
|
+
return false;
|
|
75
|
+
return confirm('› Install the MyAPI skills pack? (Y/n) ', true);
|
|
22
76
|
}
|
|
23
77
|
// myapi auth link — upgrade anonymous session to registered account.
|
|
24
78
|
export async function link(flags = {}, emailArg) {
|
|
25
79
|
if (flags.help) {
|
|
26
|
-
info(
|
|
80
|
+
info(LINK_HELP);
|
|
27
81
|
return;
|
|
28
82
|
}
|
|
29
83
|
const config = loadConfig();
|
|
@@ -31,9 +85,8 @@ export async function link(flags = {}, emailArg) {
|
|
|
31
85
|
error('Not configured. Run: myapi auth setup');
|
|
32
86
|
if (!config.is_anonymous)
|
|
33
87
|
error('Already registered. Use your existing account.');
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const email = emailArg || (await ask(rl, '› Email? ')).trim();
|
|
88
|
+
await withReadline(async () => {
|
|
89
|
+
const email = emailArg || await ask('› Email? ');
|
|
37
90
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
|
|
38
91
|
error(`Invalid email address: "${email}".`);
|
|
39
92
|
let upgradeOk = true;
|
|
@@ -46,16 +99,14 @@ export async function link(flags = {}, emailArg) {
|
|
|
46
99
|
upgradeOk = false;
|
|
47
100
|
info(`› That email already has an account — accounts cannot be merged.`);
|
|
48
101
|
info(`› Your anonymous account will be kept and you can switch back to it anytime.`);
|
|
49
|
-
|
|
50
|
-
if (!yn(ans))
|
|
102
|
+
if (!await confirm('› Sign in and add it as a second account? (Y/n) ', true))
|
|
51
103
|
return;
|
|
52
104
|
await hq.sendCode(email);
|
|
53
105
|
}
|
|
54
106
|
info(`› Sent a code to ${email} · paste it below`);
|
|
55
|
-
const code =
|
|
107
|
+
const code = await ask('› Code? ');
|
|
56
108
|
const data = await hq.verifyCode(email, code);
|
|
57
|
-
const
|
|
58
|
-
const wantsSkills = skillsFromFlag !== null ? skillsFromFlag : yn((await ask(rl, '› Install the MyAPI skills pack? (Y/n) ')).trim());
|
|
109
|
+
const wantsSkills = await resolveSkillsPreference(flags);
|
|
59
110
|
if (upgradeOk) {
|
|
60
111
|
saveConfig({
|
|
61
112
|
...config,
|
|
@@ -84,15 +135,12 @@ export async function link(flags = {}, emailArg) {
|
|
|
84
135
|
}
|
|
85
136
|
if (wantsSkills)
|
|
86
137
|
await installSkills();
|
|
87
|
-
}
|
|
88
|
-
finally {
|
|
89
|
-
rl.close();
|
|
90
|
-
}
|
|
138
|
+
});
|
|
91
139
|
}
|
|
92
140
|
// myapi auth whoami — show current session info.
|
|
93
141
|
export async function whoami(flags = {}) {
|
|
94
142
|
if (flags.help) {
|
|
95
|
-
info(
|
|
143
|
+
info(WHOAMI_HELP);
|
|
96
144
|
return;
|
|
97
145
|
}
|
|
98
146
|
const config = loadConfig();
|
|
@@ -126,7 +174,7 @@ export async function whoami(flags = {}) {
|
|
|
126
174
|
// myapi auth switch [index] — switch between saved accounts.
|
|
127
175
|
export async function switchCmd(flags = {}, indexArg) {
|
|
128
176
|
if (flags.help) {
|
|
129
|
-
info(
|
|
177
|
+
info(SWITCH_HELP);
|
|
130
178
|
return;
|
|
131
179
|
}
|
|
132
180
|
const accounts = listAccounts();
|
|
@@ -145,13 +193,7 @@ export async function switchCmd(flags = {}, indexArg) {
|
|
|
145
193
|
}
|
|
146
194
|
let idxStr = indexArg ?? '';
|
|
147
195
|
if (!idxStr) {
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
idxStr = (await ask(rl, `› Switch to account (1-${accounts.length})? `)).trim();
|
|
151
|
-
}
|
|
152
|
-
finally {
|
|
153
|
-
rl.close();
|
|
154
|
-
}
|
|
196
|
+
idxStr = await ask(`› Switch to account (1-${accounts.length})? `);
|
|
155
197
|
}
|
|
156
198
|
// Support email as a stable identifier
|
|
157
199
|
let idx;
|
package/dist/commands/billing.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as readline from 'readline';
|
|
2
1
|
import { hq } from '@myapihq/sdk';
|
|
3
2
|
import { requireConfig } from '../config.js';
|
|
4
3
|
import { success, error, printTable, info, printJson } from '../output.js';
|
|
4
|
+
import { confirm } from '../prompt.js';
|
|
5
5
|
import { formatDate } from '../utils.js';
|
|
6
6
|
export const SCHEMA = {};
|
|
7
7
|
const SUBCOMMAND_USAGE = {
|
|
@@ -82,10 +82,8 @@ export async function topup(amountStr, flags) {
|
|
|
82
82
|
error('Amount must be a positive whole number of dollars (e.g. myapi billing topup 10)');
|
|
83
83
|
}
|
|
84
84
|
if (!flags.yes && !flags.y && amount >= 50) {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
rl.close();
|
|
88
|
-
if (ans.trim().toLowerCase() !== 'y' && ans.trim().toLowerCase() !== 'yes') {
|
|
85
|
+
const ok = await confirm(`› Charge $${amount} to your saved payment method? (y/N) `, false);
|
|
86
|
+
if (!ok) {
|
|
89
87
|
info('Cancelled.');
|
|
90
88
|
return;
|
|
91
89
|
}
|
package/dist/commands/domain.js
CHANGED
|
@@ -57,11 +57,18 @@ export async function list(flags) {
|
|
|
57
57
|
printJson(domains);
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
|
-
printTable(domains, {
|
|
60
|
+
printTable(domains.map(summarizeDomain), {
|
|
61
61
|
flags,
|
|
62
62
|
empty: 'No domains found in this organization. Register one with: myapi domain register <name>',
|
|
63
63
|
});
|
|
64
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)' };
|
|
71
|
+
}
|
|
65
72
|
export async function assign(domainArg, flags) {
|
|
66
73
|
const config = requireConfig();
|
|
67
74
|
const orgId = requireOrg(flags, config, 'myapi domain assign <domain> [--org <id>]');
|
|
@@ -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} (${res.emails_quota_remaining} emails/day quota)`);
|
|
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
|
+
}
|