@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseFlags } from './flags.js';
|
|
3
|
+
const SCHEMA = {
|
|
4
|
+
org: 'string',
|
|
5
|
+
name: 'string',
|
|
6
|
+
years: 'number',
|
|
7
|
+
'no-enable': 'boolean',
|
|
8
|
+
};
|
|
9
|
+
describe('parseFlags', () => {
|
|
10
|
+
it('treats unknown flags as boolean by default (forward-compatible)', () => {
|
|
11
|
+
const { args, flags } = parseFlags(['funnel', 'get', '--json', 'abc-123'], SCHEMA);
|
|
12
|
+
expect(flags.json).toBe(true);
|
|
13
|
+
expect(args).toEqual(['funnel', 'get', 'abc-123']);
|
|
14
|
+
});
|
|
15
|
+
it('treats global booleans (--yes, --help, --verbose) as boolean', () => {
|
|
16
|
+
const { flags } = parseFlags(['--yes', '--help', '--verbose'], SCHEMA);
|
|
17
|
+
expect(flags.yes).toBe(true);
|
|
18
|
+
expect(flags.help).toBe(true);
|
|
19
|
+
expect(flags.verbose).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
it('consumes the next token for declared string flags', () => {
|
|
22
|
+
const { args, flags } = parseFlags(['org', 'create', '--name', 'Acme Inc', '--yes'], SCHEMA);
|
|
23
|
+
expect(flags.name).toBe('Acme Inc');
|
|
24
|
+
expect(flags.yes).toBe(true);
|
|
25
|
+
expect(args).toEqual(['org', 'create']);
|
|
26
|
+
});
|
|
27
|
+
it('supports --key=value for any flag', () => {
|
|
28
|
+
const { flags } = parseFlags(['--org=abc-123', '--json'], SCHEMA);
|
|
29
|
+
expect(flags.org).toBe('abc-123');
|
|
30
|
+
expect(flags.json).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
it('declared string flag with no next token becomes boolean true', () => {
|
|
33
|
+
const { flags } = parseFlags(['--name'], SCHEMA);
|
|
34
|
+
expect(flags.name).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it('declared string flag followed by another flag does not consume it', () => {
|
|
37
|
+
const { flags } = parseFlags(['--org', '--json'], SCHEMA);
|
|
38
|
+
expect(flags.org).toBe(true);
|
|
39
|
+
expect(flags.json).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
it('-h maps to help', () => {
|
|
42
|
+
const { flags } = parseFlags(['-h'], SCHEMA);
|
|
43
|
+
expect(flags.help).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
it('-v maps to version', () => {
|
|
46
|
+
const { flags } = parseFlags(['-v'], SCHEMA);
|
|
47
|
+
expect(flags.version).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
it('collects positional args correctly', () => {
|
|
50
|
+
const { args, flags } = parseFlags(['domain', 'register', 'example.com', '--org', 'uuid-here'], SCHEMA);
|
|
51
|
+
expect(args).toEqual(['domain', 'register', 'example.com']);
|
|
52
|
+
expect(flags.org).toBe('uuid-here');
|
|
53
|
+
});
|
|
54
|
+
it('parses declared number flags as numbers', () => {
|
|
55
|
+
const { flags } = parseFlags(['--years', '3'], SCHEMA);
|
|
56
|
+
expect(flags.years).toBe(3);
|
|
57
|
+
expect(typeof flags.years).toBe('number');
|
|
58
|
+
});
|
|
59
|
+
it('declared boolean flag does not consume next token', () => {
|
|
60
|
+
const { args, flags } = parseFlags(['--no-enable', 'abc'], SCHEMA);
|
|
61
|
+
expect(flags['no-enable']).toBe(true);
|
|
62
|
+
expect(args).toEqual(['abc']);
|
|
63
|
+
});
|
|
64
|
+
it('-- terminator: everything after is positional', () => {
|
|
65
|
+
const { args, flags } = parseFlags(['--name', 'X', '--', '--not-a-flag', 'y'], SCHEMA);
|
|
66
|
+
expect(flags.name).toBe('X');
|
|
67
|
+
expect(args).toEqual(['--not-a-flag', 'y']);
|
|
68
|
+
});
|
|
69
|
+
it('unknown --key=value preserved as a string (not silently lost)', () => {
|
|
70
|
+
const { flags } = parseFlags(['--mystery=42'], SCHEMA);
|
|
71
|
+
expect(flags.mystery).toBe('42');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Config } from './config.js';
|
|
2
|
+
export type Flags = Record<string, string | boolean | number>;
|
|
3
|
+
export declare function requireOrg(flags: Flags, config: Config, usage: string): string;
|
|
4
|
+
export declare function requireDomain(arg: string | undefined, flags: Flags, config: Config, usage: string): string;
|
|
5
|
+
export declare function requireArg(value: string | undefined, name: string, usage: string): string;
|
|
6
|
+
export declare function requireFlag(flags: Flags, name: string, usage: string): string;
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { error } from './output.js';
|
|
2
|
+
// error() returns `never`, so after `if (!x) error(...)` TS narrows x to a
|
|
3
|
+
// non-falsy value and the casts disappear.
|
|
4
|
+
export function requireOrg(flags, config, usage) {
|
|
5
|
+
const orgId = (typeof flags.org === 'string' ? flags.org : '') || config.default_org;
|
|
6
|
+
if (!orgId) {
|
|
7
|
+
error(`Missing required arguments.\nUsage: ${usage}\n(Or set default: myapi config set-org <id>)`);
|
|
8
|
+
}
|
|
9
|
+
return orgId;
|
|
10
|
+
}
|
|
11
|
+
export function requireDomain(arg, flags, config, usage) {
|
|
12
|
+
const fromFlag = typeof flags.domain === 'string' ? flags.domain : '';
|
|
13
|
+
const fromConfig = typeof config.default_domain === 'string' ? config.default_domain : '';
|
|
14
|
+
const domain = arg || fromFlag || fromConfig;
|
|
15
|
+
if (!domain) {
|
|
16
|
+
error(`Missing required arguments.\nUsage: ${usage}\n(Or set default: myapi config set-domain <domain>)`);
|
|
17
|
+
}
|
|
18
|
+
return domain;
|
|
19
|
+
}
|
|
20
|
+
export function requireArg(value, name, usage) {
|
|
21
|
+
if (!value)
|
|
22
|
+
error(`Missing required argument: ${name}.\nUsage: ${usage}`);
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
export function requireFlag(flags, name, usage) {
|
|
26
|
+
const v = flags[name];
|
|
27
|
+
if (v === undefined || v === true || v === false || v === '') {
|
|
28
|
+
error(`Missing required flag: --${name}.\nUsage: ${usage}`);
|
|
29
|
+
}
|
|
30
|
+
return String(v);
|
|
31
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// manually maintained — do not regenerate from scripts/generate-indexes.js
|
|
3
|
-
import { parseArgs } from './utils.js';
|
|
4
2
|
import { error, info, success } from './output.js';
|
|
5
3
|
import { loadConfig } from './config.js';
|
|
6
4
|
import { MyApiError } from '@myapihq/sdk';
|
|
7
5
|
import * as fs from 'fs';
|
|
6
|
+
import { parseFlags } from './flags.js';
|
|
8
7
|
const pkgPath = new URL('../package.json', import.meta.url);
|
|
9
8
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
10
9
|
import * as keysCmd from './commands/keys.js';
|
|
@@ -14,8 +13,29 @@ import * as setupCmd from './commands/setup.js';
|
|
|
14
13
|
import * as updateCmd from './commands/update.js';
|
|
15
14
|
import * as domainCmd from './commands/domain.js';
|
|
16
15
|
import * as funnelCmd from './commands/funnel.js';
|
|
16
|
+
import * as webhookCmd from './commands/webhook.js';
|
|
17
|
+
import * as workflowCmd from './commands/workflow.js';
|
|
18
|
+
import * as emailCmd from './commands/email/index.js';
|
|
17
19
|
import * as authCmd from './commands/auth.js';
|
|
18
20
|
import * as configCmd from './commands/config.js';
|
|
21
|
+
// Each command file declares the value flags it understands. We union them
|
|
22
|
+
// into a single schema for the upfront parse, so adding a new value flag in
|
|
23
|
+
// one command means editing one file (its SCHEMA), not a global allowlist.
|
|
24
|
+
const COMBINED_SCHEMA = {
|
|
25
|
+
...authCmd.SCHEMA,
|
|
26
|
+
...billingCmd.SCHEMA,
|
|
27
|
+
...configCmd.SCHEMA,
|
|
28
|
+
...domainCmd.SCHEMA,
|
|
29
|
+
...emailCmd.SCHEMA,
|
|
30
|
+
...funnelCmd.SCHEMA,
|
|
31
|
+
...keysCmd.SCHEMA,
|
|
32
|
+
...orgCmd.SCHEMA,
|
|
33
|
+
...webhookCmd.SCHEMA,
|
|
34
|
+
...workflowCmd.SCHEMA,
|
|
35
|
+
// Top-level flags
|
|
36
|
+
version: 'boolean',
|
|
37
|
+
V: 'boolean',
|
|
38
|
+
};
|
|
19
39
|
const ERROR_MESSAGES = {
|
|
20
40
|
DOMAIN_NOT_FOUND: 'Domain not found.',
|
|
21
41
|
INVALID_DOMAIN: 'Invalid domain name.',
|
|
@@ -43,7 +63,7 @@ function friendlyError(code) {
|
|
|
43
63
|
return ERROR_MESSAGES[code] || code;
|
|
44
64
|
}
|
|
45
65
|
async function main() {
|
|
46
|
-
const { args, flags } =
|
|
66
|
+
const { args, flags } = parseFlags(process.argv.slice(2), COMBINED_SCHEMA);
|
|
47
67
|
if (flags.version || flags.v || flags.V) {
|
|
48
68
|
const latest = await updateCmd.latestVersion();
|
|
49
69
|
const updateNote = latest && updateCmd.isNewer(latest, pkg.version)
|
|
@@ -71,89 +91,16 @@ async function main() {
|
|
|
71
91
|
try {
|
|
72
92
|
switch (command) {
|
|
73
93
|
case 'auth':
|
|
74
|
-
|
|
75
|
-
info('Usage: myapi auth <subcommand>\n\nSubcommands:\n setup Configure your account\n import-key Import an existing API key non-interactively\n whoami Show current account · supports --json\n link [email] Upgrade anonymous account to registered (or add a second session)\n Use myapi auth setup to create a completely new account\n switch [index] Switch active account by index or email\n config Manage CLI defaults (org, funnel, domain) · supports set-org / set-funnel / set-domain\n install-skills Install or update the MyAPI skills pack for AI agents\n api-keys Manage API keys · list / create / revoke\n keys Alias for api-keys');
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
if (subcommand === 'setup')
|
|
79
|
-
await setupCmd.setup(flags);
|
|
80
|
-
else if (subcommand === 'import-key')
|
|
81
|
-
await setupCmd.importKey(restArgs[0], flags);
|
|
82
|
-
else if (subcommand === 'whoami')
|
|
83
|
-
await authCmd.whoami(flags);
|
|
84
|
-
else if (subcommand === 'link')
|
|
85
|
-
await authCmd.link(flags, restArgs[0]);
|
|
86
|
-
else if (subcommand === 'switch')
|
|
87
|
-
await authCmd.switchCmd(flags, restArgs[0]);
|
|
88
|
-
else if (subcommand === 'install-skills') {
|
|
89
|
-
if (flags.help) {
|
|
90
|
-
info('Usage: myapi auth install-skills\n\nInstalls the MyAPI skills pack for AI coding agents (Claude, Gemini, Cursor).\n\nThis command writes skill definition files to:\n ~/.agents/skills/myapi/\n\nAnd creates symlinks in the appropriate agent config directories:\n ~/.claude/ (Claude)\n ~/.gemini/ (Gemini)\n ~/.cursor/ (Cursor, if detected)\n\nThese files teach agents how to use the MyAPI CLI and API directly.\nRun this command again to update existing skills to the latest version.');
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
await setupCmd.installSkills();
|
|
94
|
-
success('› Skills installed.');
|
|
95
|
-
}
|
|
96
|
-
else if (subcommand === 'config')
|
|
97
|
-
await configCmd.run(restArgs[0], restArgs.slice(1), flags);
|
|
98
|
-
else if (subcommand === 'api-keys') {
|
|
99
|
-
if (flags.help && !restArgs[0]) {
|
|
100
|
-
info('Usage: myapi auth api-keys <subcommand>\n\nManage programmatic API keys for your account. API keys are used to authenticate\nrequests to the MyAPI SDK and REST API.\n\nSubcommands:\n list List all API keys with their IDs and creation dates\n create Create a new API key (the key value is shown once)\n revoke <id> Permanently revoke an API key by ID\n\nExamples:\n myapi auth api-keys list\n myapi auth api-keys create\n myapi auth api-keys revoke hq_live_xxxxxxxxxxxxxxxxxxxx');
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
if (!restArgs[0]) {
|
|
104
|
-
info('Run: myapi auth api-keys --help');
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
if (restArgs[0] === 'create')
|
|
108
|
-
await keysCmd.createNew(flags);
|
|
109
|
-
else if (restArgs[0] === 'list')
|
|
110
|
-
await keysCmd.list(flags);
|
|
111
|
-
else if (restArgs[0] === 'revoke')
|
|
112
|
-
await keysCmd.revoke(restArgs[1], flags);
|
|
113
|
-
}
|
|
114
|
-
else
|
|
115
|
-
info('Unknown subcommand. Run: myapi auth --help');
|
|
94
|
+
await dispatchAuth(subcommand, restArgs, flags);
|
|
116
95
|
break;
|
|
117
96
|
case 'update':
|
|
118
97
|
await updateCmd.update(flags);
|
|
119
98
|
break;
|
|
120
99
|
case 'org':
|
|
121
|
-
|
|
122
|
-
info('Usage: myapi org <subcommand>\n\nSubcommands:\n list List organizations\n create Create an organization · myapi org create "Name" --yes\n get Get details of an organization\n delete Delete an organization\n sync-brand Sync brand info (name, logo, description) from an existing website into an org');
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
if (subcommand === 'list')
|
|
126
|
-
await orgCmd.list(flags);
|
|
127
|
-
else if (subcommand === 'create')
|
|
128
|
-
await orgCmd.create(restArgs, flags);
|
|
129
|
-
else if (subcommand === 'get')
|
|
130
|
-
await orgCmd.get(restArgs[0], flags);
|
|
131
|
-
else if (subcommand === 'delete')
|
|
132
|
-
await orgCmd.del(restArgs[0], flags);
|
|
133
|
-
else if (subcommand === 'sync-brand')
|
|
134
|
-
await orgCmd.importOrg(restArgs, flags);
|
|
135
|
-
else if (subcommand === 'import') {
|
|
136
|
-
process.stderr.write('› Note: "org import" is deprecated — use "org sync-brand" instead.\n');
|
|
137
|
-
await orgCmd.importOrg(restArgs, flags);
|
|
138
|
-
}
|
|
139
|
-
else
|
|
140
|
-
printHelp();
|
|
100
|
+
await orgCmd.run(subcommand, restArgs, flags);
|
|
141
101
|
break;
|
|
142
102
|
case 'billing':
|
|
143
|
-
|
|
144
|
-
info('Usage: myapi billing <subcommand>\n\nSubcommands:\n balance Check balance\n history View billing history\n topup Top up your balance\n setup Setup a payment method');
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
if (subcommand === 'balance')
|
|
148
|
-
await billingCmd.balance(flags);
|
|
149
|
-
else if (subcommand === 'history')
|
|
150
|
-
await billingCmd.history(flags);
|
|
151
|
-
else if (subcommand === 'topup')
|
|
152
|
-
await billingCmd.topup(restArgs[0], flags);
|
|
153
|
-
else if (subcommand === 'setup')
|
|
154
|
-
await billingCmd.setup(flags);
|
|
155
|
-
else
|
|
156
|
-
printHelp();
|
|
103
|
+
await billingCmd.run(subcommand, restArgs, flags);
|
|
157
104
|
break;
|
|
158
105
|
case 'domain':
|
|
159
106
|
await domainCmd.run(subcommand, restArgs, flags);
|
|
@@ -161,6 +108,15 @@ async function main() {
|
|
|
161
108
|
case 'funnel':
|
|
162
109
|
await funnelCmd.run(subcommand, restArgs, flags);
|
|
163
110
|
break;
|
|
111
|
+
case 'webhook':
|
|
112
|
+
await webhookCmd.run(subcommand, restArgs, flags);
|
|
113
|
+
break;
|
|
114
|
+
case 'workflow':
|
|
115
|
+
await workflowCmd.run(subcommand, restArgs, flags);
|
|
116
|
+
break;
|
|
117
|
+
case 'email':
|
|
118
|
+
await emailCmd.run(subcommand, restArgs, flags);
|
|
119
|
+
break;
|
|
164
120
|
// Convenience aliases
|
|
165
121
|
case 'setup':
|
|
166
122
|
await setupCmd.setup(flags);
|
|
@@ -169,45 +125,21 @@ async function main() {
|
|
|
169
125
|
await authCmd.whoami(flags);
|
|
170
126
|
break;
|
|
171
127
|
case 'keys':
|
|
172
|
-
|
|
173
|
-
info('Usage: myapi keys <subcommand>\n\nManage programmatic API keys for your account. API keys are used to authenticate\nrequests to the MyAPI SDK and REST API.\n\nSubcommands:\n list List all API keys with their IDs and creation dates\n create Create a new API key (the key value is shown once)\n revoke <id> Permanently revoke an API key by ID\n\nExamples:\n myapi keys list\n myapi keys create\n myapi keys revoke hq_live_xxxxxxxxxxxxxxxxxxxx\n\nAlias for: myapi auth api-keys');
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
if (subcommand === 'create')
|
|
177
|
-
await keysCmd.createNew(flags);
|
|
178
|
-
else if (subcommand === 'list')
|
|
179
|
-
await keysCmd.list(flags);
|
|
180
|
-
else if (subcommand === 'revoke')
|
|
181
|
-
await keysCmd.revoke(restArgs[0], flags);
|
|
128
|
+
await keysCmd.run(subcommand, restArgs, flags);
|
|
182
129
|
break;
|
|
183
130
|
case 'config':
|
|
184
131
|
await configCmd.run(subcommand, restArgs, { ...flags, _via: 'config' });
|
|
185
132
|
break;
|
|
186
133
|
case 'install-skills':
|
|
187
134
|
if (flags.help) {
|
|
188
|
-
info(
|
|
135
|
+
info(authCmd.INSTALL_SKILLS_HELP);
|
|
189
136
|
break;
|
|
190
137
|
}
|
|
191
138
|
await setupCmd.installSkills();
|
|
192
139
|
success('› Skills installed.');
|
|
193
140
|
break;
|
|
194
141
|
case 'help':
|
|
195
|
-
|
|
196
|
-
await funnelCmd.run(undefined, [], { help: true });
|
|
197
|
-
else if (subcommand === 'domain')
|
|
198
|
-
await domainCmd.run(undefined, [], { help: true });
|
|
199
|
-
else if (subcommand === 'auth')
|
|
200
|
-
info('Usage: myapi auth <subcommand>\n\nSubcommands:\n setup Configure your account\n import-key Import an existing API key non-interactively\n whoami Show current account · supports --json\n link [email] Upgrade anonymous account to registered (or add a second session)\n switch [index] Switch active account by index or email\n config Manage CLI defaults (org, funnel, domain)\n install-skills Install or update the MyAPI skills pack for AI agents\n api-keys Manage API keys · list / create / revoke');
|
|
201
|
-
else if (subcommand === 'org')
|
|
202
|
-
info('Usage: myapi org <subcommand>\n\nSubcommands:\n list List organizations\n create Create an organization · myapi org create "Name" --yes\n get Get details of an organization\n delete Delete an organization\n sync-brand Sync brand info from an existing website into an org');
|
|
203
|
-
else if (subcommand === 'billing')
|
|
204
|
-
info('Usage: myapi billing <subcommand>\n\nSubcommands:\n balance Check balance\n history View billing history\n topup Top up your balance\n setup Setup a payment method');
|
|
205
|
-
else if (subcommand === 'keys')
|
|
206
|
-
info('Usage: myapi keys <subcommand>\n\nSubcommands:\n list List all API keys\n create Create a new API key\n revoke <id> Revoke an API key\n\nAlias for: myapi auth api-keys');
|
|
207
|
-
else if (subcommand)
|
|
208
|
-
info(`Run: myapi ${subcommand} --help`);
|
|
209
|
-
else
|
|
210
|
-
printHelp();
|
|
142
|
+
await dispatchHelp(subcommand);
|
|
211
143
|
break;
|
|
212
144
|
default:
|
|
213
145
|
error(`Unknown command: ${command}. Run "myapi" for available commands.`);
|
|
@@ -223,16 +155,70 @@ async function main() {
|
|
|
223
155
|
else if (err.code === 'NO_PAYMENT_METHOD')
|
|
224
156
|
error('No payment method on file. Run: myapi billing setup');
|
|
225
157
|
else
|
|
226
|
-
error(
|
|
158
|
+
error('Insufficient balance. Run: myapi billing topup <amount>');
|
|
227
159
|
}
|
|
228
160
|
else
|
|
229
161
|
error(friendlyError(err.code) || err.message);
|
|
230
162
|
}
|
|
231
163
|
else {
|
|
232
|
-
|
|
164
|
+
// Don't JSON.stringify Error instances — that returns "{}" because
|
|
165
|
+
// Error's enumerable surface is empty. Prefer .message; fall back to
|
|
166
|
+
// String() which Errors stringify reasonably ("Error: ...").
|
|
167
|
+
error(err?.message ?? String(err));
|
|
233
168
|
}
|
|
234
169
|
}
|
|
235
170
|
}
|
|
171
|
+
async function dispatchAuth(subcommand, restArgs, flags) {
|
|
172
|
+
if (!subcommand || (flags.help && !subcommand)) {
|
|
173
|
+
info(authCmd.HELP);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
switch (subcommand) {
|
|
177
|
+
case 'setup': return setupCmd.setup(flags);
|
|
178
|
+
case 'import-key': return setupCmd.importKey(restArgs[0], flags);
|
|
179
|
+
case 'whoami': return authCmd.whoami(flags);
|
|
180
|
+
case 'link': return authCmd.link(flags, restArgs[0]);
|
|
181
|
+
case 'switch': return authCmd.switchCmd(flags, restArgs[0]);
|
|
182
|
+
case 'install-skills':
|
|
183
|
+
if (flags.help) {
|
|
184
|
+
info(authCmd.INSTALL_SKILLS_HELP);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
await setupCmd.installSkills();
|
|
188
|
+
success('› Skills installed.');
|
|
189
|
+
return;
|
|
190
|
+
case 'config': return configCmd.run(restArgs[0], restArgs.slice(1), flags);
|
|
191
|
+
case 'api-keys':
|
|
192
|
+
case 'keys': return keysCmd.runApiKeys(restArgs[0], restArgs.slice(1), flags);
|
|
193
|
+
default: info('Unknown subcommand. Run: myapi auth --help');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const HELP_TARGETS = {
|
|
197
|
+
funnel: f => funnelCmd.run(undefined, [], f),
|
|
198
|
+
domain: f => domainCmd.run(undefined, [], f),
|
|
199
|
+
webhook: f => webhookCmd.run(undefined, [], f),
|
|
200
|
+
workflow: f => workflowCmd.run(undefined, [], f),
|
|
201
|
+
email: f => emailCmd.run(undefined, [], f),
|
|
202
|
+
org: f => orgCmd.run(undefined, [], f),
|
|
203
|
+
billing: f => billingCmd.run(undefined, [], f),
|
|
204
|
+
keys: f => keysCmd.run(undefined, [], f),
|
|
205
|
+
config: f => configCmd.run(undefined, [], f),
|
|
206
|
+
};
|
|
207
|
+
async function dispatchHelp(target) {
|
|
208
|
+
if (!target) {
|
|
209
|
+
printHelp();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (target === 'auth') {
|
|
213
|
+
info(authCmd.HELP);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const handler = HELP_TARGETS[target];
|
|
217
|
+
if (handler)
|
|
218
|
+
await handler({ help: true });
|
|
219
|
+
else
|
|
220
|
+
info(`Run: myapi ${target} --help`);
|
|
221
|
+
}
|
|
236
222
|
function printHelp() {
|
|
237
223
|
const config = loadConfig();
|
|
238
224
|
const quickStart = config?.api_key
|
|
@@ -252,6 +238,9 @@ Commands:
|
|
|
252
238
|
update Update CLI and skills to the latest version
|
|
253
239
|
domain Manage domain configurations
|
|
254
240
|
funnel Manage websites (publish pages, custom domains, funnels)
|
|
241
|
+
webhook Manage inbound webhook endpoints and inspect deliveries
|
|
242
|
+
email Manage mailboxes, send/read email, templates, and campaigns
|
|
243
|
+
workflow Run actions (send email, post to Slack) when a webhook fires
|
|
255
244
|
|
|
256
245
|
Aliases:
|
|
257
246
|
whoami → myapi auth whoami
|
|
@@ -264,4 +253,4 @@ Run "myapi <command> --help" for subcommand help.
|
|
|
264
253
|
|
|
265
254
|
${quickStart}`);
|
|
266
255
|
}
|
|
267
|
-
main().catch(err => { error(err
|
|
256
|
+
main().catch(err => { error(err?.message ?? String(err)); });
|
package/dist/output.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
export type TableFlags = {
|
|
2
|
+
json?: boolean | string | number;
|
|
3
|
+
} & Record<string, unknown>;
|
|
1
4
|
export declare function success(message: string): void;
|
|
2
5
|
export declare function error(message: string): never;
|
|
3
6
|
export declare function info(message: string): void;
|
|
4
7
|
export declare function banner(message: string): void;
|
|
5
8
|
export declare function printJson(data: unknown): void;
|
|
6
|
-
export declare function
|
|
9
|
+
export declare function spinnerFrame(i: number): string;
|
|
10
|
+
export declare function clearLine(): void;
|
|
11
|
+
export interface PrintTableOptions {
|
|
12
|
+
/** Pass `flags` so `--json` (any truthy form) routes to JSON output. */
|
|
13
|
+
flags?: TableFlags;
|
|
14
|
+
/** Hint shown when the table is empty (instead of "No data found."). */
|
|
15
|
+
empty?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function printTable<T extends object>(rows: T[], opts?: PrintTableOptions): void;
|
package/dist/output.js
CHANGED
|
@@ -15,19 +15,29 @@ export function banner(message) {
|
|
|
15
15
|
export function printJson(data) {
|
|
16
16
|
console.log(JSON.stringify(data, null, 2));
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// Spinner / line-clear primitives. Used by polling helpers (utils.pollJob)
|
|
19
|
+
// and any handler that wants its own progress UI.
|
|
20
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
21
|
+
export function spinnerFrame(i) {
|
|
22
|
+
return SPINNER_FRAMES[i % SPINNER_FRAMES.length];
|
|
23
|
+
}
|
|
24
|
+
export function clearLine() {
|
|
25
|
+
process.stdout.write('\r\x1b[K');
|
|
26
|
+
}
|
|
27
|
+
// Generic so callers don't need `as unknown as Record<string, unknown>[]`.
|
|
28
|
+
// Field names come from the first row; if SDK renames a field, the projector
|
|
29
|
+
// (passed in by the caller) is where it'll fail at compile time.
|
|
30
|
+
export function printTable(rows, opts = {}) {
|
|
31
|
+
if (opts.flags && opts.flags.json) {
|
|
20
32
|
printJson(rows);
|
|
21
33
|
return;
|
|
22
34
|
}
|
|
23
35
|
if (rows.length === 0) {
|
|
24
|
-
console.log(
|
|
36
|
+
console.log(opts.empty ?? 'No data found.');
|
|
25
37
|
return;
|
|
26
38
|
}
|
|
27
39
|
const columns = Object.keys(rows[0]);
|
|
28
|
-
const colWidths = columns.map(col =>
|
|
29
|
-
return Math.max(col.length, ...rows.map(row => String(row[col] ?? '').length));
|
|
30
|
-
});
|
|
40
|
+
const colWidths = columns.map(col => Math.max(col.length, ...rows.map(row => String(row[col] ?? '').length)));
|
|
31
41
|
const printRow = (row) => {
|
|
32
42
|
console.log(row.map((cell, i) => cell.padEnd(colWidths[i] + 2)).join(''));
|
|
33
43
|
};
|
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as readline from 'readline';
|
|
2
|
+
/**
|
|
3
|
+
* Ask a free-form question on stdin. Returns the trimmed answer.
|
|
4
|
+
*
|
|
5
|
+
* Handles the readline lifecycle so callers don't open/close interfaces
|
|
6
|
+
* inline. Replaces five copies of the same boilerplate across commands.
|
|
7
|
+
*/
|
|
8
|
+
export declare function ask(question: string): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* Yes/No confirm. `defaultYes` controls how Enter (no input) is interpreted.
|
|
11
|
+
*
|
|
12
|
+
* - `confirm('› Continue? (Y/n) ', true)` — Enter means yes.
|
|
13
|
+
* - `confirm('› Delete? (y/N) ', false)` — Enter means no.
|
|
14
|
+
*
|
|
15
|
+
* Accepted yes inputs: 'y', 'yes' (case-insensitive). Anything else is no
|
|
16
|
+
* when `defaultYes=false`; when `defaultYes=true`, only an explicit 'n' /
|
|
17
|
+
* 'no' counts as no, matching the existing Y/n convention in the CLI.
|
|
18
|
+
*/
|
|
19
|
+
export declare function confirm(question: string, defaultYes: boolean): Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Lower-level escape hatch: get a readline.Interface, do something with it,
|
|
22
|
+
* close it on the way out (even if your callback throws).
|
|
23
|
+
*/
|
|
24
|
+
export declare function withReadline<T>(fn: (rl: readline.Interface) => Promise<T>): Promise<T>;
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as readline from 'readline';
|
|
2
|
+
/**
|
|
3
|
+
* Ask a free-form question on stdin. Returns the trimmed answer.
|
|
4
|
+
*
|
|
5
|
+
* Handles the readline lifecycle so callers don't open/close interfaces
|
|
6
|
+
* inline. Replaces five copies of the same boilerplate across commands.
|
|
7
|
+
*/
|
|
8
|
+
export async function ask(question) {
|
|
9
|
+
return withReadline(rl => new Promise(resolve => {
|
|
10
|
+
rl.question(question, ans => resolve(ans.trim()));
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Yes/No confirm. `defaultYes` controls how Enter (no input) is interpreted.
|
|
15
|
+
*
|
|
16
|
+
* - `confirm('› Continue? (Y/n) ', true)` — Enter means yes.
|
|
17
|
+
* - `confirm('› Delete? (y/N) ', false)` — Enter means no.
|
|
18
|
+
*
|
|
19
|
+
* Accepted yes inputs: 'y', 'yes' (case-insensitive). Anything else is no
|
|
20
|
+
* when `defaultYes=false`; when `defaultYes=true`, only an explicit 'n' /
|
|
21
|
+
* 'no' counts as no, matching the existing Y/n convention in the CLI.
|
|
22
|
+
*/
|
|
23
|
+
export async function confirm(question, defaultYes) {
|
|
24
|
+
const ans = (await ask(question)).toLowerCase();
|
|
25
|
+
if (defaultYes)
|
|
26
|
+
return ans !== 'n' && ans !== 'no';
|
|
27
|
+
return ans === 'y' || ans === 'yes';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Lower-level escape hatch: get a readline.Interface, do something with it,
|
|
31
|
+
* close it on the way out (even if your callback throws).
|
|
32
|
+
*/
|
|
33
|
+
export async function withReadline(fn) {
|
|
34
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
35
|
+
try {
|
|
36
|
+
return await fn(rl);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
rl.close();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
# my-email-api
|
|
3
|
+
|
|
4
|
+
Send transactional email and run drip campaigns from mailboxes on your own registered domains. Includes AI template generation, warmup, and inbox/outbox reading.
|
|
5
|
+
|
|
6
|
+
## What it does
|
|
7
|
+
|
|
8
|
+
- Create mailboxes on your registered domains
|
|
9
|
+
- Send transactional emails (one-shot or templated)
|
|
10
|
+
- Read inbox, outbox, sent history, and per-message status
|
|
11
|
+
- Generate HTML email templates with AI from a prompt
|
|
12
|
+
- Run paced drip campaigns against uploaded contact lists
|
|
13
|
+
- Manage IP/domain warmup for sender reputation
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Create a mailbox + activate sending
|
|
19
|
+
myapi email mailbox create hello@yourdomain.com
|
|
20
|
+
myapi email mailbox activate-sending --address hello@yourdomain.com
|
|
21
|
+
|
|
22
|
+
# Send
|
|
23
|
+
myapi email message send \
|
|
24
|
+
--from hello@yourdomain.com \
|
|
25
|
+
--to recipient@example.com \
|
|
26
|
+
--subject "Hi" \
|
|
27
|
+
--body "Test"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Authentication
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export MYAPI_KEY=mak_...
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Requires:
|
|
37
|
+
- An `api_key` from **myapihq**
|
|
38
|
+
- A registered domain via **mydomainapi**, assigned to your org
|
|
39
|
+
- Default `org_id` (for templates/campaigns) — set with `myapi auth config set-org <id>`
|
|
40
|
+
|
|
41
|
+
## Documentation
|
|
42
|
+
|
|
43
|
+
Full command reference and flow diagrams: see `SKILL.md`.
|
|
44
|
+
|
|
45
|
+
Run `myapi email --help` for inline reference.
|