@myapihq/cli 1.0.84 → 1.1.0-wip.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/commands/auth.d.ts +8 -3
  2. package/dist/commands/auth.js +84 -60
  3. package/dist/commands/billing.d.ts +8 -4
  4. package/dist/commands/billing.js +46 -27
  5. package/dist/commands/config.d.ts +8 -5
  6. package/dist/commands/config.js +52 -27
  7. package/dist/commands/domain.d.ts +12 -9
  8. package/dist/commands/domain.js +124 -86
  9. package/dist/commands/email/campaign.d.ts +2 -0
  10. package/dist/commands/email/campaign.js +152 -0
  11. package/dist/commands/email/index.d.ts +4 -0
  12. package/dist/commands/email/index.js +98 -0
  13. package/dist/commands/email/mailbox.d.ts +2 -0
  14. package/dist/commands/email/mailbox.js +88 -0
  15. package/dist/commands/email/message.d.ts +2 -0
  16. package/dist/commands/email/message.js +115 -0
  17. package/dist/commands/email/template.d.ts +2 -0
  18. package/dist/commands/email/template.js +106 -0
  19. package/dist/commands/email/warmup.d.ts +2 -0
  20. package/dist/commands/email/warmup.js +43 -0
  21. package/dist/commands/email.d.ts +4 -12
  22. package/dist/commands/email.js +528 -146
  23. package/dist/commands/funnel.d.ts +10 -7
  24. package/dist/commands/funnel.js +79 -55
  25. package/dist/commands/image.js +25 -15
  26. package/dist/commands/keys.d.ts +8 -3
  27. package/dist/commands/keys.js +71 -35
  28. package/dist/commands/org.d.ts +9 -5
  29. package/dist/commands/org.js +100 -64
  30. package/dist/commands/pixel.js +23 -11
  31. package/dist/commands/setup.d.ts +3 -2
  32. package/dist/commands/setup.js +160 -165
  33. package/dist/commands/storage.js +25 -15
  34. package/dist/commands/update.d.ts +2 -1
  35. package/dist/commands/update.js +5 -0
  36. package/dist/commands/url.js +19 -7
  37. package/dist/commands/webhook.d.ts +8 -5
  38. package/dist/commands/webhook.js +70 -38
  39. package/dist/commands/workflow.d.ts +13 -7
  40. package/dist/commands/workflow.js +179 -58
  41. package/dist/config.js +10 -5
  42. package/dist/flags.d.ts +8 -0
  43. package/dist/flags.js +88 -0
  44. package/dist/flags.test.d.ts +1 -0
  45. package/dist/flags.test.js +73 -0
  46. package/dist/helpers.d.ts +6 -0
  47. package/dist/helpers.js +31 -0
  48. package/dist/index.js +98 -109
  49. package/dist/output.d.ts +12 -1
  50. package/dist/output.js +16 -6
  51. package/dist/prompt.d.ts +24 -0
  52. package/dist/prompt.js +41 -0
  53. package/dist/skills/my-email-api/README.md +45 -0
  54. package/dist/skills/my-email-api/SKILL.md +104 -0
  55. package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +6 -0
  56. package/dist/skills/my-email-api/make/.gitkeep +0 -0
  57. package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
  58. package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
  59. package/dist/skills/my-webhook-api/README.md +40 -0
  60. package/dist/skills/my-webhook-api/SKILL.md +138 -0
  61. package/dist/skills/my-webhook-api/claude/.claude-plugin/plugin.json +6 -0
  62. package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
  63. package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
  64. package/dist/skills/my-webhook-api/openapi/.gitkeep +0 -0
  65. package/dist/skills/my-workflow-api/README.md +36 -0
  66. package/dist/skills/my-workflow-api/SKILL.md +156 -0
  67. package/dist/skills/my-workflow-api/claude/.claude-plugin/plugin.json +6 -0
  68. package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
  69. package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
  70. package/dist/skills/my-workflow-api/openapi/.gitkeep +0 -0
  71. package/dist/utils.d.ts +26 -4
  72. package/dist/utils.js +32 -33
  73. package/dist/utils.test.d.ts +1 -0
  74. package/dist/utils.test.js +48 -0
  75. package/package.json +9 -4
  76. package/dist/commands/account.d.ts +0 -4
  77. package/dist/commands/account.js +0 -80
@@ -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;
@@ -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 } = parseArgs(process.argv.slice(2));
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
- if (!subcommand || (flags.help && !subcommand)) {
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
- if (!subcommand || (flags.help && !subcommand)) {
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
- if (!subcommand || (flags.help && !subcommand)) {
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
- if (!subcommand || (flags.help && !subcommand)) {
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('Usage: myapi install-skills\n\nAlias for: myapi auth install-skills');
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
- if (subcommand === 'funnel')
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(`Insufficient balance. Run: myapi billing topup <amount>`);
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
- error(err.message || (typeof err === 'object' ? JSON.stringify(err) : String(err)));
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.message || (typeof err === 'object' ? JSON.stringify(err) : String(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 printTable(rows: Record<string, unknown>[]): void;
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
- export function printTable(rows) {
19
- if (process.argv.includes('--json')) {
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("No data found.");
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
  };
@@ -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.