@myapihq/cli 1.1.0-wip.5 → 1.2.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 (125) hide show
  1. package/dist/commands/{email.d.ts → audience.d.ts} +2 -0
  2. package/dist/commands/audience.js +245 -0
  3. package/dist/commands/auth.d.ts +5 -2
  4. package/dist/commands/auth.js +133 -5
  5. package/dist/commands/billing.d.ts +2 -0
  6. package/dist/commands/billing.js +18 -1
  7. package/dist/commands/company.d.ts +6 -0
  8. package/dist/commands/company.js +149 -0
  9. package/dist/commands/config.d.ts +2 -0
  10. package/dist/commands/config.js +20 -0
  11. package/dist/commands/crm/companies.d.ts +6 -0
  12. package/dist/commands/crm/companies.js +193 -0
  13. package/dist/commands/crm/contacts.d.ts +6 -0
  14. package/dist/commands/crm/contacts.js +241 -0
  15. package/dist/commands/crm/index.d.ts +6 -0
  16. package/dist/commands/crm/index.js +74 -0
  17. package/dist/commands/database.d.ts +6 -0
  18. package/dist/commands/database.js +259 -0
  19. package/dist/commands/domain.d.ts +4 -0
  20. package/dist/commands/domain.js +166 -8
  21. package/dist/commands/email/campaign.d.ts +2 -0
  22. package/dist/commands/email/campaign.js +34 -1
  23. package/dist/commands/email/index.d.ts +2 -0
  24. package/dist/commands/email/index.js +9 -0
  25. package/dist/commands/email/mailbox.d.ts +2 -0
  26. package/dist/commands/email/mailbox.js +5 -0
  27. package/dist/commands/email/message.d.ts +2 -0
  28. package/dist/commands/email/message.js +8 -0
  29. package/dist/commands/email/template.d.ts +2 -0
  30. package/dist/commands/email/template.js +54 -0
  31. package/dist/commands/email/verify.d.ts +7 -0
  32. package/dist/commands/email/verify.js +52 -0
  33. package/dist/commands/email/warmup.d.ts +2 -0
  34. package/dist/commands/email/warmup.js +7 -0
  35. package/dist/commands/funnel.d.ts +3 -0
  36. package/dist/commands/funnel.js +31 -0
  37. package/dist/commands/image.d.ts +2 -0
  38. package/dist/commands/image.js +43 -9
  39. package/dist/commands/keys.d.ts +2 -0
  40. package/dist/commands/keys.js +5 -0
  41. package/dist/commands/llm.d.ts +6 -0
  42. package/dist/commands/llm.js +156 -0
  43. package/dist/commands/org.d.ts +3 -0
  44. package/dist/commands/org.js +55 -0
  45. package/dist/commands/people.d.ts +6 -0
  46. package/dist/commands/people.js +136 -0
  47. package/dist/commands/pixel.d.ts +11 -3
  48. package/dist/commands/pixel.js +93 -0
  49. package/dist/commands/setup.d.ts +2 -0
  50. package/dist/commands/setup.js +57 -5
  51. package/dist/commands/status.d.ts +7 -0
  52. package/dist/commands/status.js +154 -0
  53. package/dist/commands/storage.d.ts +2 -0
  54. package/dist/commands/storage.js +9 -1
  55. package/dist/commands/update.d.ts +2 -0
  56. package/dist/commands/update.js +3 -0
  57. package/dist/commands/url.d.ts +7 -2
  58. package/dist/commands/url.js +6 -0
  59. package/dist/commands/webhook.d.ts +2 -0
  60. package/dist/commands/webhook.js +31 -6
  61. package/dist/commands/workflow.d.ts +2 -0
  62. package/dist/commands/workflow.js +19 -1
  63. package/dist/completion.d.ts +3 -0
  64. package/dist/completion.js +78 -0
  65. package/dist/config.d.ts +2 -0
  66. package/dist/exposes.d.ts +2 -0
  67. package/dist/exposes.js +13 -0
  68. package/dist/exposes.test.js +99 -0
  69. package/dist/flags.test.js +84 -0
  70. package/dist/index.js +106 -14
  71. package/dist/prompt.d.ts +15 -0
  72. package/dist/prompt.js +27 -0
  73. package/dist/registrant.d.ts +6 -0
  74. package/dist/registrant.js +155 -0
  75. package/dist/services-sync.test.d.ts +1 -0
  76. package/dist/services-sync.test.js +32 -0
  77. package/dist/skills/my-api-hq/SKILL.md +51 -45
  78. package/dist/skills/my-crm-api/README.md +42 -0
  79. package/dist/skills/my-crm-api/SKILL.md +165 -0
  80. package/dist/skills/my-crm-api/claude/.claude-plugin/plugin.json +6 -0
  81. package/dist/skills/my-database-api/README.md +41 -0
  82. package/dist/skills/my-database-api/SKILL.md +107 -0
  83. package/dist/skills/my-database-api/claude/.claude-plugin/plugin.json +6 -0
  84. package/dist/skills/my-domain-api/SKILL.md +53 -26
  85. package/dist/skills/my-email-verify-api/README.md +33 -0
  86. package/dist/skills/my-email-verify-api/SKILL.md +91 -0
  87. package/dist/skills/my-email-verify-api/claude/.claude-plugin/plugin.json +6 -0
  88. package/dist/skills/my-funnel-api/SKILL.md +41 -37
  89. package/dist/skills/my-image-api/SKILL.md +48 -44
  90. package/dist/skills/my-llm-api/README.md +35 -0
  91. package/dist/skills/my-llm-api/SKILL.md +125 -0
  92. package/dist/skills/my-llm-api/claude/.claude-plugin/plugin.json +6 -0
  93. package/dist/skills/my-storage-api/README.md +1 -1
  94. package/dist/skills/my-storage-api/SKILL.md +44 -54
  95. package/dist/skills/my-webhook-api/README.md +1 -1
  96. package/dist/skills/my-webhook-api/SKILL.md +48 -92
  97. package/dist/skills/my-workflow-api/SKILL.md +46 -115
  98. package/dist/telemetry.d.ts +12 -0
  99. package/dist/telemetry.js +80 -0
  100. package/dist/telemetry.test.d.ts +1 -0
  101. package/dist/telemetry.test.js +101 -0
  102. package/package.json +13 -8
  103. package/dist/commands/email.js +0 -586
  104. package/dist/skills/my-email-api/README.md +0 -45
  105. package/dist/skills/my-email-api/SKILL.md +0 -104
  106. package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +0 -6
  107. package/dist/skills/my-email-api/make/.gitkeep +0 -0
  108. package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
  109. package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
  110. package/dist/skills/my-funnel-api/make/.gitkeep +0 -0
  111. package/dist/skills/my-funnel-api/n8n/.gitkeep +0 -0
  112. package/dist/skills/my-image-api/make/.gitkeep +0 -0
  113. package/dist/skills/my-image-api/n8n/.gitkeep +0 -0
  114. package/dist/skills/my-storage-api/make/.gitkeep +0 -0
  115. package/dist/skills/my-storage-api/n8n/.gitkeep +0 -0
  116. package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
  117. package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
  118. package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
  119. package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
  120. package/dist/utils.test.js +0 -48
  121. /package/dist/{utils.test.d.ts → exposes.test.d.ts} +0 -0
  122. /package/dist/skills/{my-api-hq/make → my-crm-api/openapi}/.gitkeep +0 -0
  123. /package/dist/skills/{my-api-hq/n8n → my-database-api/openapi}/.gitkeep +0 -0
  124. /package/dist/skills/{my-domain-api/make → my-email-verify-api/openapi}/.gitkeep +0 -0
  125. /package/dist/skills/{my-domain-api/n8n → my-llm-api/openapi}/.gitkeep +0 -0
@@ -0,0 +1,7 @@
1
+ import type { FlagSchema } from '../flags.js';
2
+ import { type Flags } from '../helpers.js';
3
+ import type { Exposes } from '../exposes.js';
4
+ export declare const EXPOSES: Exposes;
5
+ export declare const SCHEMA: FlagSchema;
6
+ export declare const STATUS_HELP = "Usage: myapi status [--org <id>] [--json]\n\nSingle-screen view of your account and what's running in the default org:\nidentity, balance, free-tier usage, plus resource counts across domains,\nfunnels, campaigns, webhooks, and workflows. Useful before a\nco-build session or when checking \"what's mid-flight\" without running six\nlist commands.\n\nEach section is best-effort \u2014 a failing service is shown as \"\u2014\", not an\nerror, so the rest of the snapshot still prints.\n";
7
+ export declare function run(_subcommand: string | undefined, _args: string[], flags?: Flags): Promise<void>;
@@ -0,0 +1,154 @@
1
+ import { hq, domain, funnel, email, webhook, workflow, MyApiError } from '@myapihq/sdk';
2
+ // listMailboxes is omitted from this view: the backend requires a domain or
3
+ // filter, so there is no global "all mailboxes in this org" call. Use
4
+ // `myapi email mailbox list --domain <d>` for per-domain detail.
5
+ import { loadConfig } from '../config.js';
6
+ import { info, error, printJson } from '../output.js';
7
+ export const EXPOSES = [
8
+ 'GET /hq/billing/balance',
9
+ 'GET /hq/account/free-tier',
10
+ 'GET /domain/orgs/{org_id}/domains',
11
+ 'GET /funnel/orgs/{org_id}/funnels',
12
+ 'GET /email/orgs/{org_id}/campaigns',
13
+ 'GET /webhook/orgs/{org_id}/endpoints',
14
+ 'GET /workflow/orgs/{org_id}/workflows',
15
+ ];
16
+ export const SCHEMA = {
17
+ org: 'string',
18
+ };
19
+ export const STATUS_HELP = `Usage: myapi status [--org <id>] [--json]
20
+
21
+ Single-screen view of your account and what's running in the default org:
22
+ identity, balance, free-tier usage, plus resource counts across domains,
23
+ funnels, campaigns, webhooks, and workflows. Useful before a
24
+ co-build session or when checking "what's mid-flight" without running six
25
+ list commands.
26
+
27
+ Each section is best-effort — a failing service is shown as "—", not an
28
+ error, so the rest of the snapshot still prints.
29
+ `;
30
+ function unwrap(r) {
31
+ return r.status === 'fulfilled' ? r.value : null;
32
+ }
33
+ export async function run(_subcommand, _args, flags = {}) {
34
+ if (flags.help) {
35
+ info(STATUS_HELP);
36
+ return;
37
+ }
38
+ const config = loadConfig();
39
+ if (!config?.api_key)
40
+ error('Not configured. Run: myapi auth setup');
41
+ const orgId = flags.org ?? config.default_org;
42
+ // Account-level info works without an org; org-scoped resources are skipped if none set.
43
+ const orgKnown = Boolean(orgId);
44
+ // Account-level (always fetched).
45
+ const accountResults = await Promise.allSettled([
46
+ hq.getBalance(config.api_key),
47
+ hq.getFreeTier(config.api_key),
48
+ ]);
49
+ const balance = unwrap(accountResults[0]);
50
+ // The backend currently returns an array of { service, used, allowance }
51
+ // per-service entries. The SDK type lies about this (claims scalar) — same
52
+ // bug surfaces in `whoami`. Treat as unknown here and narrow at render time.
53
+ const freeTier = unwrap(accountResults[1]);
54
+ // Catch a server-side rejection across any best-effort probe so we can flag
55
+ // the snapshot as untrustworthy rather than silently printing local config.
56
+ const keyRejected = accountResults.some(r => r.status === 'rejected' && r.reason instanceof MyApiError && r.reason.status === 401);
57
+ // Org-scoped (skipped if no default org set).
58
+ let domains = null, funnels = null, campaigns = null, webhooks = null, workflows = null;
59
+ if (orgKnown) {
60
+ const orgResults = await Promise.allSettled([
61
+ domain.listDomains(config.api_key, orgId),
62
+ funnel.listFunnels(config.api_key, orgId),
63
+ email.listCampaigns(config.api_key, orgId),
64
+ webhook.listEndpoints(config.api_key, orgId),
65
+ workflow.listWorkflows(config.api_key, orgId),
66
+ ]);
67
+ domains = unwrap(orgResults[0]);
68
+ funnels = unwrap(orgResults[1]);
69
+ campaigns = unwrap(orgResults[2]);
70
+ webhooks = unwrap(orgResults[3]);
71
+ workflows = unwrap(orgResults[4]);
72
+ }
73
+ if (flags.json) {
74
+ printJson({
75
+ account: {
76
+ email: config.email ?? null,
77
+ account_id: config.account_id,
78
+ type: config.is_anonymous ? 'anonymous' : 'registered',
79
+ default_org: orgId ?? null,
80
+ default_funnel: config.default_funnel ?? null,
81
+ key_rejected: keyRejected,
82
+ },
83
+ balance,
84
+ free_tier: freeTier,
85
+ org_resources: orgKnown ? {
86
+ domains, funnels, campaigns, webhooks, workflows,
87
+ } : null,
88
+ });
89
+ return;
90
+ }
91
+ if (keyRejected) {
92
+ info('⚠ The backend rejected your API key. Values may be stale — run: myapi auth setup');
93
+ info('');
94
+ }
95
+ // ── Identity ──────────────────────────────────────────────────────────────
96
+ if (config.email)
97
+ info(`Email: ${config.email}`);
98
+ info(`Account: ${config.account_id}`);
99
+ info(`Org: ${orgId ?? '(none — run: myapi config set-org <id>)'}`);
100
+ info(`Funnel: ${config.default_funnel ?? '(none)'}`);
101
+ info(`Type: ${config.is_anonymous ? 'anonymous' : 'registered'}`);
102
+ info(`Balance: ${balance ? `${balance.balance_display} | Credits: ${balance.credits_display}` : '—'}`);
103
+ if (config.is_anonymous) {
104
+ info(' → Link an email to unlock $5 free credit + paid actions: myapi auth link <email>');
105
+ }
106
+ if (Array.isArray(freeTier) && freeTier.length > 0) {
107
+ const parts = freeTier
108
+ .filter((e) => e && typeof e.service === 'string')
109
+ .map((e) => `${e.service} ${e.used ?? 0}/${e.allowance ?? '?'}`);
110
+ if (parts.length > 0)
111
+ info(`FreeTier: ${parts.join(' · ')}`);
112
+ }
113
+ // ── Resources ─────────────────────────────────────────────────────────────
114
+ info('');
115
+ info('Resources:');
116
+ if (!orgKnown) {
117
+ info(' (set a default org to see domains/funnels/campaigns/webhooks/workflows)');
118
+ return;
119
+ }
120
+ if (domains) {
121
+ const verified = domains.filter(d => /verified|active|ready/i.test(d.status)).length;
122
+ const pending = domains.length - verified;
123
+ const pendingStr = pending > 0 ? `, ${pending} pending` : '';
124
+ info(` Domains: ${domains.length} (${verified} verified${pendingStr})`);
125
+ }
126
+ else
127
+ info(' Domains: —');
128
+ info(` Funnels: ${funnels ? funnels.length : '—'}`);
129
+ if (campaigns) {
130
+ // Campaigns: backend statuses include 'draft', 'active', 'paused', 'completed'.
131
+ // Surface counts for the two states that matter mid-flight.
132
+ const active = campaigns.filter(c => /active|running/i.test(c.status)).length;
133
+ const paused = campaigns.filter(c => /paused/i.test(c.status)).length;
134
+ const extras = [
135
+ active > 0 ? `${active} active` : null,
136
+ paused > 0 ? `${paused} paused` : null,
137
+ ].filter(Boolean).join(', ');
138
+ info(` Campaigns: ${campaigns.length}${extras ? ` (${extras})` : ''}`);
139
+ }
140
+ else
141
+ info(' Campaigns: —');
142
+ info(` Webhooks: ${webhooks ? webhooks.length : '—'}`);
143
+ if (workflows) {
144
+ const enabled = workflows.filter(w => w.enabled).length;
145
+ const disabled = workflows.length - enabled;
146
+ const extras = [
147
+ enabled > 0 ? `${enabled} enabled` : null,
148
+ disabled > 0 ? `${disabled} disabled` : null,
149
+ ].filter(Boolean).join(', ');
150
+ info(` Workflows: ${workflows.length}${extras ? ` (${extras})` : ''}`);
151
+ }
152
+ else
153
+ info(' Workflows: —');
154
+ }
@@ -1,4 +1,6 @@
1
1
  import type { FlagSchema } from '../flags.js';
2
2
  import { type Flags } from '../helpers.js';
3
+ import type { Exposes } from '../exposes.js';
4
+ export declare const EXPOSES: Exposes;
3
5
  export declare const SCHEMA: FlagSchema;
4
6
  export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
@@ -9,6 +9,12 @@ const STORAGE_BASE = process.env.MYAPI_STORAGE_URL ?? 'https://api.mystorageapi.
9
9
  import { success, error, printTable, info, printJson } from '../output.js';
10
10
  import { formatDate } from '../utils.js';
11
11
  import { requireOrg, requireArg } from '../helpers.js';
12
+ export const EXPOSES = [
13
+ 'POST /storage/orgs/{org_id}/assets/ingest',
14
+ 'POST /storage/orgs/{org_id}/assets/upload',
15
+ 'GET /storage/orgs/{org_id}/assets',
16
+ 'DELETE /storage/orgs/{org_id}/assets/{asset_id}',
17
+ ];
12
18
  export const SCHEMA = {
13
19
  org: 'string',
14
20
  name: 'string',
@@ -20,6 +26,8 @@ const EXT_TO_CT = {
20
26
  '.jpeg': 'image/jpeg',
21
27
  '.gif': 'image/gif',
22
28
  '.webp': 'image/webp',
29
+ '.mp4': 'video/mp4',
30
+ '.webm': 'video/webm',
23
31
  };
24
32
  function summarizeAsset(a) {
25
33
  return {
@@ -56,7 +64,7 @@ async function upload(filePath, flags) {
56
64
  const ext = extname(filePath).toLowerCase();
57
65
  const contentType = EXT_TO_CT[ext];
58
66
  if (!contentType) {
59
- error(`Unsupported file extension "${ext}". Supported: ${Object.keys(EXT_TO_CT).join(', ')}.\n(SVG and non-image types — pdf/mp4/webm — go through "myapi storage ingest <url>" today.)`);
67
+ error(`Unsupported file extension "${ext}". Supported: ${Object.keys(EXT_TO_CT).join(', ')}.\n(SVG and PDF still go through "myapi storage ingest <url>" today.)`);
60
68
  }
61
69
  let data;
62
70
  try {
@@ -1,4 +1,6 @@
1
1
  import type { Flags } from '../helpers.js';
2
+ import type { Exposes } from '../exposes.js';
3
+ export declare const EXPOSES: Exposes;
2
4
  export declare function checkForUpdate(currentVersion: string): Promise<void>;
3
5
  export declare function update(flags?: Flags): Promise<void>;
4
6
  export declare function latestVersion(): Promise<string | null>;
@@ -3,6 +3,9 @@ import { existsSync } from 'fs';
3
3
  import { loadConfig } from '../config.js';
4
4
  import { info, success, banner } from '../output.js';
5
5
  import { installSkills } from './setup.js';
6
+ // `myapi update` only talks to the npm registry to check for new CLI versions
7
+ // and to ./setup.ts for the skills install. No MyAPI backend endpoints.
8
+ export const EXPOSES = [];
6
9
  const REGISTRY_URL = 'https://registry.npmjs.org/@myapihq/cli/latest';
7
10
  const PACKUMENT_URL = 'https://registry.npmjs.org/@myapihq/cli';
8
11
  // Verify the version is listed in the full packument — the same data source
@@ -1,2 +1,7 @@
1
- export declare function shorten(targetUrl: string, flags: Record<string, string | boolean>): Promise<void>;
2
- export declare function run(subcommand: string | undefined, args: string[], flags: Record<string, string | boolean>): Promise<void>;
1
+ import type { Exposes } from '../exposes.js';
2
+ import type { Flags } from '../helpers.js';
3
+ import type { FlagSchema } from '../flags.js';
4
+ export declare const SCHEMA: FlagSchema;
5
+ export declare const EXPOSES: Exposes;
6
+ export declare function shorten(targetUrl: string, flags: Flags): Promise<void>;
7
+ export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
@@ -1,6 +1,12 @@
1
1
  import { url as sdkUrl } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../config.js';
3
3
  import { success, error, info, printJson } from '../output.js';
4
+ export const SCHEMA = {
5
+ org: 'string',
6
+ };
7
+ export const EXPOSES = [
8
+ 'POST /url/orgs/{org_id}/shorten',
9
+ ];
4
10
  export async function shorten(targetUrl, flags) {
5
11
  const config = requireConfig();
6
12
  const orgId = flags.org || config.default_org;
@@ -1,5 +1,7 @@
1
1
  import type { FlagSchema } from '../flags.js';
2
2
  import { type Flags } from '../helpers.js';
3
+ import type { Exposes } from '../exposes.js';
4
+ export declare const EXPOSES: Exposes;
3
5
  export declare const SCHEMA: FlagSchema;
4
6
  export declare function list(flags: Flags): Promise<void>;
5
7
  export declare function create(nameArg: string | undefined, flags: Flags): Promise<void>;
@@ -2,10 +2,17 @@ import { webhook as sdkWebhook } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../config.js';
3
3
  import { success, error, printTable, info, printJson } from '../output.js';
4
4
  import { requireOrg } from '../helpers.js';
5
+ export const EXPOSES = [
6
+ 'POST /webhook/orgs/{org_id}/endpoints',
7
+ 'GET /webhook/orgs/{org_id}/endpoints',
8
+ 'DELETE /webhook/orgs/{org_id}/endpoints/{endpoint_id}',
9
+ 'GET /webhook/orgs/{org_id}/deliveries/{delivery_id}',
10
+ ];
5
11
  export const SCHEMA = {
6
12
  org: 'string',
7
13
  name: 'string',
8
14
  description: 'string',
15
+ 'crm-email-path': 'string',
9
16
  };
10
17
  export async function list(flags) {
11
18
  const config = requireConfig();
@@ -25,9 +32,17 @@ export async function create(nameArg, flags) {
25
32
  const orgId = requireOrg(flags, config, 'myapi webhook create <name> [--description <desc>] [--org <id>]');
26
33
  const name = nameArg || flags.name;
27
34
  if (!name)
28
- error('Missing required arguments.\nUsage: myapi webhook create <name> [--description <desc>] [--org <id>]\n or: myapi webhook create --name <name> [--description <desc>] [--org <id>]');
29
- const description = flags.description;
30
- const res = await sdkWebhook.createEndpoint(config.api_key, orgId, name, description);
35
+ error('Missing required arguments.\nUsage: myapi webhook create <name> [--description <desc>] [--crm-email-path <path>] [--org <id>]\n or: myapi webhook create --name <name> [...]');
36
+ const opts = {};
37
+ if (typeof flags.description === 'string')
38
+ opts.description = flags.description;
39
+ if (typeof flags['crm-email-path'] === 'string')
40
+ opts.crm_email_path = flags['crm-email-path'];
41
+ const res = await sdkWebhook.createEndpoint(config.api_key, orgId, name, opts);
42
+ if (flags.json) {
43
+ printJson(res);
44
+ return;
45
+ }
31
46
  success(`Webhook created! ID: ${res.id}\nInbound URL: ${res.url}`);
32
47
  }
33
48
  export async function del(id, flags) {
@@ -49,10 +64,16 @@ export async function delivery(id, flags) {
49
64
  // ── Dispatcher ───────────────────────────────────────────────────────────────
50
65
  const SUBCOMMAND_USAGE = {
51
66
  'list': 'myapi webhook list [--org <id>] [--json]',
52
- 'create': `myapi webhook create <name> [--description <desc>] [--org <id>]
53
- myapi webhook create --name <name> [--description <desc>] [--org <id>]
67
+ 'create': `myapi webhook create <name> [--description <desc>] [--crm-email-path <path>] [--org <id>]
68
+ myapi webhook create --name <name> [...]
54
69
 
55
- Either form works; the positional name is the recommended shape.`,
70
+ Either form works; the positional name is the recommended shape.
71
+
72
+ CRM auto-ingest (optional):
73
+ --crm-email-path <dot-path> Where to find the email in incoming payloads.
74
+ Default 'email' (top-level). For Stripe:
75
+ 'data.object.customer_email'. For GitHub:
76
+ 'sender.email'. Empty string disables ingest.`,
56
77
  'delete': 'myapi webhook delete <id> [--org <id>]',
57
78
  'delivery': 'myapi webhook delivery <delivery_id> [--org <id>]',
58
79
  };
@@ -73,6 +94,10 @@ Payload contract:
73
94
  Whatever you POST is stored verbatim alongside headers + timestamp.
74
95
  Workflows reference fields with {{ payload.field }} templating.
75
96
 
97
+ Discovering delivery IDs:
98
+ Each POST to an inbound URL responds with {"delivery_id":"<uuid>"}.
99
+ Capture it from the response, then pass to: myapi webhook delivery <id>
100
+
76
101
  Example — wire a contact form to email + Slack:
77
102
  WID=$(myapi webhook create "contact-form" --json | jq -r .id)
78
103
  myapi workflow create "Contact handler" --endpoint-id $WID --steps '[
@@ -1,5 +1,7 @@
1
1
  import type { FlagSchema } from '../flags.js';
2
2
  import { type Flags } from '../helpers.js';
3
+ import type { Exposes } from '../exposes.js';
4
+ export declare const EXPOSES: Exposes;
3
5
  export declare const SCHEMA: FlagSchema;
4
6
  export declare function list(flags: Flags): Promise<void>;
5
7
  export declare function get(id: string, flags: Flags): Promise<void>;
@@ -2,6 +2,17 @@ import { workflow as sdkWorkflow } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../config.js';
3
3
  import { success, error, printTable, info, printJson } from '../output.js';
4
4
  import { requireOrg } from '../helpers.js';
5
+ export const EXPOSES = [
6
+ 'POST /workflow/orgs/{org_id}/workflows',
7
+ 'GET /workflow/orgs/{org_id}/workflows',
8
+ 'GET /workflow/orgs/{org_id}/workflows/{workflow_id}',
9
+ 'PATCH /workflow/orgs/{org_id}/workflows/{workflow_id}',
10
+ 'DELETE /workflow/orgs/{org_id}/workflows/{workflow_id}',
11
+ 'POST /workflow/orgs/{org_id}/workflows/{workflow_id}/enable',
12
+ 'POST /workflow/orgs/{org_id}/workflows/{workflow_id}/disable',
13
+ 'GET /workflow/orgs/{org_id}/workflows/{workflow_id}/runs',
14
+ 'GET /workflow/orgs/{org_id}/runs/{run_id}',
15
+ ];
5
16
  export const SCHEMA = {
6
17
  org: 'string',
7
18
  name: 'string',
@@ -115,7 +126,7 @@ export async function create(nameArg, flags) {
115
126
  const name = nameArg || flags.name;
116
127
  const endpointId = flags['endpoint-id'];
117
128
  if (!name || !endpointId || !flags.steps) {
118
- error('Missing required arguments.\nUsage: myapi workflow create <name> --endpoint-id <id> --steps <json> [--no-enable] [--org <id>]\n or: myapi workflow create --name <name> --endpoint-id <id> --steps <json> [--no-enable] [--org <id>]');
129
+ error('Missing required arguments.\nUsage: myapi workflow create <name> --endpoint-id <id> --steps <json> [--no-enable] [--org <id>]\n or: myapi workflow create --name <name> --endpoint-id <id> --steps <json> [--no-enable] [--org <id>]\n\n→ Step JSON shape is non-obvious. Run "myapi workflow create --help" or activate the my-workflow-api skill for examples.');
119
130
  }
120
131
  if (!UUID_RE.test(endpointId)) {
121
132
  error(`Invalid --endpoint-id "${endpointId}". Expected a webhook endpoint UUID.\nList your endpoints with: myapi webhook list`);
@@ -226,6 +237,8 @@ const SUBCOMMAND_USAGE = {
226
237
  'create': `myapi workflow create <name> --endpoint-id <id> --steps <json> [--no-enable] [--org <id>]
227
238
  myapi workflow create --name <name> --endpoint-id <id> --steps <json> [--no-enable] [--org <id>]
228
239
 
240
+ → For ready-to-use step recipes, activate the my-workflow-api skill.
241
+
229
242
  Either form works; the positional name is the recommended shape.
230
243
 
231
244
  --steps is a JSON array of step objects. Each step has a "type" field
@@ -284,6 +297,11 @@ export async function run(subcommand, args, flags) {
284
297
  if (!subcommand || (flags.help && !subcommand)) {
285
298
  info(`Usage: myapi workflow <subcommand>
286
299
 
300
+ → For ready-to-use examples and JSON step recipes, activate the my-workflow-api skill.
301
+ myapi workflow create requires --endpoint-id <webhook_id> and --steps <json_array>.
302
+ The step JSON shape is non-obvious — don't guess. Read the skill, or run:
303
+ myapi workflow create --help
304
+
287
305
  Subcommands:
288
306
  list List all workflows
289
307
  get <id> Show a single workflow
@@ -0,0 +1,3 @@
1
+ export declare function initCompletion(): void;
2
+ export declare function installCompletion(): void;
3
+ export declare function uninstallCompletion(): void;
@@ -0,0 +1,78 @@
1
+ // Shell autocomplete via omelette. Quietly active on every CLI run — when
2
+ // the shell invokes us with the magic completion env vars, omelette's
3
+ // init() handles the request and exits before we hit normal command
4
+ // dispatch. On any other run, init() returns immediately.
5
+ //
6
+ // User installs the shell hook with `myapi completion install` (or
7
+ // uninstalls with `myapi completion uninstall`). Supports bash, zsh, and
8
+ // fish (handled internally by omelette).
9
+ //
10
+ // The tree below mirrors the dispatcher in index.ts and the per-command
11
+ // switch statements. When you add a new top-level command or subcommand,
12
+ // extend it here too — there's no automatic discovery.
13
+ import omelette from 'omelette';
14
+ const TREE = {
15
+ auth: ['link', 'switch', 'whoami', 'config', 'registrant'],
16
+ registrant: ['set', 'get', 'clear'],
17
+ org: ['create', 'delete', 'get', 'import', 'list', 'sync-brand', 'update'],
18
+ billing: ['balance', 'history', 'setup', 'topup'],
19
+ domain: ['assign', 'check', 'list', 'register', 'renew', 'settings', 'status', 'unassign', 'update-settings'],
20
+ funnel: ['create', 'delete', 'get', 'list', 'pages', 'push', 'verify'],
21
+ webhook: ['create', 'delete', 'delivery', 'list'],
22
+ workflow: ['create', 'delete', 'disable', 'enable', 'get', 'get-run', 'list', 'runs', 'update'],
23
+ email: ['mailbox', 'message', 'warmup', 'template', 'campaign', 'verify'],
24
+ image: ['delete', 'generate', 'get', 'get-url', 'list', 'models'],
25
+ storage: ['delete', 'get', 'get-url', 'ingest', 'list', 'upload'],
26
+ pixel: ['audience', 'events', 'identity', 'interactions', 'visits'],
27
+ people: ['search', 'get'],
28
+ company: ['search', 'get'],
29
+ audience: ['create', 'list', 'get', 'update', 'delete', 'members', 'refresh'],
30
+ llm: ['complete', 'embed', 'models'],
31
+ database: ['namespaces', 'create', 'delete-namespace', 'keys', 'get', 'set', 'del'],
32
+ crm: ['contacts', 'companies'],
33
+ contacts: ['list', 'search', 'create', 'get', 'update', 'delete', 'restore', 'promote', 'events'],
34
+ companies: ['list', 'search', 'create', 'get', 'update', 'delete', 'restore', 'promote'],
35
+ url: ['shorten'],
36
+ keys: ['create', 'list', 'revoke'],
37
+ 'api-keys': ['create', 'list', 'revoke'],
38
+ config: ['view', 'set-org', 'set-funnel', 'set-domain'],
39
+ setup: [],
40
+ 'import-key': [],
41
+ whoami: [],
42
+ status: [],
43
+ 'install-skills': [],
44
+ update: [],
45
+ completion: ['install', 'uninstall'],
46
+ help: [],
47
+ };
48
+ const TOP_LEVEL = Object.keys(TREE).sort();
49
+ const completion = omelette('myapi <command> <subcommand>');
50
+ completion.on('command', ({ reply }) => reply(TOP_LEVEL));
51
+ completion.on('subcommand', ({ before, reply, line }) => {
52
+ // omelette's `before` is the LAST word of the line, not the previous
53
+ // word. When the user has started typing a partial subcommand (e.g.
54
+ // "myapi audience l"), `before` is "l", not "audience" — so a naive
55
+ // `TREE[before]` returns undefined and tab completion silently fails.
56
+ // Detect the real parent command by checking whether the line ends in
57
+ // whitespace (no partial in flight → use `before`) vs. mid-word (partial
58
+ // in flight → use the previous word from the line).
59
+ const endsInSpace = /\s$/.test(line);
60
+ const parent = endsInSpace
61
+ ? before
62
+ : (line.trim().split(/\s+/).slice(-2, -1)[0] ?? before);
63
+ reply(TREE[parent] ?? []);
64
+ });
65
+ // Quiet no-op when shell isn't asking for completion. The first thing
66
+ // index.ts should do is call this — omelette will short-circuit and
67
+ // process.exit if a completion request is in flight.
68
+ export function initCompletion() {
69
+ completion.init();
70
+ }
71
+ // Wire/unwire the shell rc snippet. Omelette writes a single eval line
72
+ // into ~/.bashrc / ~/.zshrc / ~/.config/fish/config.fish.
73
+ export function installCompletion() {
74
+ completion.setupShellInitFile();
75
+ }
76
+ export function uninstallCompletion() {
77
+ completion.cleanupShellInitFile();
78
+ }
package/dist/config.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { domain as sdkDomain } from '@myapihq/sdk';
1
2
  export interface AccountEntry {
2
3
  api_key: string;
3
4
  account_id: string;
@@ -5,6 +6,7 @@ export interface AccountEntry {
5
6
  default_org?: string;
6
7
  default_funnel?: string;
7
8
  default_domain?: string;
9
+ registrant?: sdkDomain.Registrant;
8
10
  is_anonymous?: boolean;
9
11
  skills_installed?: boolean;
10
12
  }
@@ -0,0 +1,2 @@
1
+ export type Endpoint = `${'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'} /${string}`;
2
+ export type Exposes = readonly Endpoint[];
@@ -0,0 +1,13 @@
1
+ // OpenAPI-templated endpoint declaration shared across CLI command modules.
2
+ //
3
+ // Each commands/<service>.ts file exports an `EXPOSES: Exposes` array listing
4
+ // the backend endpoints that command (and its subcommands) cover. The
5
+ // coverage check tool (S-102) walks these arrays and verifies every endpoint
6
+ // in the published OpenAPI schema is reachable from at least one CLI command.
7
+ //
8
+ // Form: `<METHOD> <PATH>` where path uses `{name}` for variable segments
9
+ // matching OpenAPI 3.x style. Examples:
10
+ // 'POST /funnel/orgs/{org_id}/funnels'
11
+ // 'GET /hq/billing/balance'
12
+ // 'DELETE /domain/orgs/{org_id}/{domain}'
13
+ export {};
@@ -0,0 +1,99 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as url from 'url';
5
+ // Discover every command module the dispatcher imports. Each must export an
6
+ // EXPOSES array so the coverage tool (S-102) can introspect the full CLI
7
+ // surface.
8
+ const COMMAND_MODULES = [
9
+ './commands/auth.js',
10
+ './commands/billing.js',
11
+ './commands/config.js',
12
+ './commands/crm/index.js',
13
+ './commands/crm/contacts.js',
14
+ './commands/crm/companies.js',
15
+ './commands/database.js',
16
+ './commands/domain.js',
17
+ './commands/email/index.js',
18
+ './commands/email/mailbox.js',
19
+ './commands/email/message.js',
20
+ './commands/email/template.js',
21
+ './commands/email/campaign.js',
22
+ './commands/email/warmup.js',
23
+ './commands/email/verify.js',
24
+ './commands/funnel.js',
25
+ './commands/image.js',
26
+ './commands/keys.js',
27
+ './commands/llm.js',
28
+ './commands/org.js',
29
+ './commands/pixel.js',
30
+ './commands/audience.js',
31
+ './commands/company.js',
32
+ './commands/people.js',
33
+ './commands/setup.js',
34
+ './commands/status.js',
35
+ './commands/storage.js',
36
+ './commands/update.js',
37
+ './commands/url.js',
38
+ './commands/webhook.js',
39
+ './commands/workflow.js',
40
+ ];
41
+ const ENDPOINT_PATTERN = /^(GET|POST|PATCH|PUT|DELETE) \/[A-Za-z0-9_\-./{}]*$/;
42
+ describe('every CLI command exports a typed EXPOSES array (S-101)', () => {
43
+ for (const modPath of COMMAND_MODULES) {
44
+ it(`${modPath} exports EXPOSES`, async () => {
45
+ const mod = await import(modPath);
46
+ expect(mod, `${modPath} did not import`).toBeDefined();
47
+ expect(mod.EXPOSES, `${modPath} missing EXPOSES export`).toBeDefined();
48
+ expect(Array.isArray(mod.EXPOSES), `${modPath} EXPOSES is not an array`).toBe(true);
49
+ });
50
+ it(`${modPath} EXPOSES entries are well-formed`, async () => {
51
+ const mod = await import(modPath);
52
+ const entries = mod.EXPOSES;
53
+ for (const entry of entries) {
54
+ expect(typeof entry, `${modPath}: non-string entry`).toBe('string');
55
+ expect(entry, `${modPath}: malformed entry "${entry}"`).toMatch(ENDPOINT_PATTERN);
56
+ // No double slashes, no trailing slash (except root), no whitespace
57
+ // beyond the single METHOD/PATH separator.
58
+ expect(entry.includes('//'), `${modPath}: double slash in "${entry}"`).toBe(false);
59
+ }
60
+ });
61
+ }
62
+ });
63
+ describe('EXPOSES coverage of CLI commands directory', () => {
64
+ it('every .ts file under commands/ is in COMMAND_MODULES (no orphans)', () => {
65
+ // Walk the source directory to catch any new command files that someone
66
+ // added without registering here. Keeps the test list in sync with reality.
67
+ const __filename = url.fileURLToPath(import.meta.url);
68
+ const commandsDir = path.resolve(path.dirname(__filename), 'commands');
69
+ const found = walkTs(commandsDir);
70
+ const declared = new Set(COMMAND_MODULES.map((m) => m.replace(/^\.\//, '').replace(/\.js$/, '.ts')));
71
+ for (const file of found) {
72
+ const rel = path.relative(path.dirname(__filename), file).replace(/\\/g, '/');
73
+ expect(declared.has(rel), `${rel} exists under commands/ but is not in COMMAND_MODULES — add it (and its EXPOSES) to keep coverage honest`).toBe(true);
74
+ }
75
+ });
76
+ });
77
+ function walkTs(dir) {
78
+ const out = [];
79
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
80
+ const p = path.join(dir, entry.name);
81
+ if (entry.isDirectory()) {
82
+ out.push(...walkTs(p));
83
+ }
84
+ else if (entry.isFile() && entry.name.endsWith('.ts') && !entry.name.endsWith('.test.ts')) {
85
+ out.push(p);
86
+ }
87
+ }
88
+ return out;
89
+ }
90
+ // Compile-time check: ensure the Endpoint template literal type accepts
91
+ // representative entries. If the type narrows in a way that breaks any of
92
+ // these, the file won't compile.
93
+ const _typeCheck = [
94
+ 'GET /hq/billing/balance',
95
+ 'POST /funnel/orgs/{org_id}/funnels',
96
+ 'DELETE /domain/orgs/{org_id}/{domain}',
97
+ 'PATCH /workflow/orgs/{org_id}/workflows/{id}',
98
+ ];
99
+ void _typeCheck;