@myapihq/cli 1.1.0-wip.4 → 1.2.0

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 (127) 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 +3 -0
  20. package/dist/commands/domain.js +67 -1
  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 +14 -2
  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/verify.d.ts +7 -0
  60. package/dist/commands/verify.js +56 -0
  61. package/dist/commands/webhook.d.ts +2 -0
  62. package/dist/commands/webhook.js +31 -6
  63. package/dist/commands/workflow.d.ts +2 -0
  64. package/dist/commands/workflow.js +19 -1
  65. package/dist/completion.d.ts +3 -0
  66. package/dist/completion.js +78 -0
  67. package/dist/config.d.ts +2 -0
  68. package/dist/exposes.d.ts +2 -0
  69. package/dist/exposes.js +13 -0
  70. package/dist/exposes.test.js +99 -0
  71. package/dist/flags.test.js +84 -0
  72. package/dist/index.js +106 -14
  73. package/dist/prompt.d.ts +15 -0
  74. package/dist/prompt.js +27 -0
  75. package/dist/registrant.d.ts +6 -0
  76. package/dist/registrant.js +155 -0
  77. package/dist/services-sync.test.d.ts +1 -0
  78. package/dist/services-sync.test.js +32 -0
  79. package/dist/skills/my-api-hq/SKILL.md +51 -45
  80. package/dist/skills/my-crm-api/README.md +42 -0
  81. package/dist/skills/my-crm-api/SKILL.md +165 -0
  82. package/dist/skills/my-crm-api/claude/.claude-plugin/plugin.json +6 -0
  83. package/dist/skills/my-database-api/README.md +41 -0
  84. package/dist/skills/my-database-api/SKILL.md +107 -0
  85. package/dist/skills/my-database-api/claude/.claude-plugin/plugin.json +6 -0
  86. package/dist/skills/my-domain-api/SKILL.md +53 -26
  87. package/dist/skills/my-email-verify-api/README.md +33 -0
  88. package/dist/skills/my-email-verify-api/SKILL.md +91 -0
  89. package/dist/skills/my-email-verify-api/claude/.claude-plugin/plugin.json +6 -0
  90. package/dist/skills/my-funnel-api/SKILL.md +41 -37
  91. package/dist/skills/my-image-api/SKILL.md +48 -44
  92. package/dist/skills/my-llm-api/README.md +35 -0
  93. package/dist/skills/my-llm-api/SKILL.md +125 -0
  94. package/dist/skills/my-llm-api/claude/.claude-plugin/plugin.json +6 -0
  95. package/dist/skills/my-storage-api/README.md +1 -1
  96. package/dist/skills/my-storage-api/SKILL.md +44 -54
  97. package/dist/skills/my-webhook-api/README.md +1 -1
  98. package/dist/skills/my-webhook-api/SKILL.md +48 -92
  99. package/dist/skills/my-workflow-api/SKILL.md +46 -115
  100. package/dist/telemetry.d.ts +12 -0
  101. package/dist/telemetry.js +80 -0
  102. package/dist/telemetry.test.d.ts +1 -0
  103. package/dist/telemetry.test.js +101 -0
  104. package/package.json +13 -8
  105. package/dist/commands/email.js +0 -586
  106. package/dist/skills/my-email-api/README.md +0 -45
  107. package/dist/skills/my-email-api/SKILL.md +0 -104
  108. package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +0 -6
  109. package/dist/skills/my-email-api/make/.gitkeep +0 -0
  110. package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
  111. package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
  112. package/dist/skills/my-funnel-api/make/.gitkeep +0 -0
  113. package/dist/skills/my-funnel-api/n8n/.gitkeep +0 -0
  114. package/dist/skills/my-image-api/make/.gitkeep +0 -0
  115. package/dist/skills/my-image-api/n8n/.gitkeep +0 -0
  116. package/dist/skills/my-storage-api/make/.gitkeep +0 -0
  117. package/dist/skills/my-storage-api/n8n/.gitkeep +0 -0
  118. package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
  119. package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
  120. package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
  121. package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
  122. package/dist/utils.test.js +0 -48
  123. /package/dist/{utils.test.d.ts → exposes.test.d.ts} +0 -0
  124. /package/dist/skills/{my-api-hq/make → my-crm-api/openapi}/.gitkeep +0 -0
  125. /package/dist/skills/{my-api-hq/n8n → my-database-api/openapi}/.gitkeep +0 -0
  126. /package/dist/skills/{my-domain-api/make → my-email-verify-api/openapi}/.gitkeep +0 -0
  127. /package/dist/skills/{my-domain-api/n8n → my-llm-api/openapi}/.gitkeep +0 -0
@@ -1,10 +1,20 @@
1
1
  import { readFile } from 'fs/promises';
2
2
  import { extname, basename } from 'path';
3
- import { storage as sdkStorage, STORAGE_BASE } from '@myapihq/sdk';
3
+ import { storage as sdkStorage } from '@myapihq/sdk';
4
4
  import { requireConfig } from '../config.js';
5
+ // Mirrors STORAGE_BASE in @myapihq/sdk's config (intentionally NOT
6
+ // re-exported from the SDK barrel — see comment in sdk/src/index.ts).
7
+ // Used here so `myapi storage get-url` is a pure local URL constructor.
8
+ const STORAGE_BASE = process.env.MYAPI_STORAGE_URL ?? 'https://api.mystorageapi.com';
5
9
  import { success, error, printTable, info, printJson } from '../output.js';
6
10
  import { formatDate } from '../utils.js';
7
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
+ ];
8
18
  export const SCHEMA = {
9
19
  org: 'string',
10
20
  name: 'string',
@@ -16,6 +26,8 @@ const EXT_TO_CT = {
16
26
  '.jpeg': 'image/jpeg',
17
27
  '.gif': 'image/gif',
18
28
  '.webp': 'image/webp',
29
+ '.mp4': 'video/mp4',
30
+ '.webm': 'video/webm',
19
31
  };
20
32
  function summarizeAsset(a) {
21
33
  return {
@@ -52,7 +64,7 @@ async function upload(filePath, flags) {
52
64
  const ext = extname(filePath).toLowerCase();
53
65
  const contentType = EXT_TO_CT[ext];
54
66
  if (!contentType) {
55
- 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.)`);
56
68
  }
57
69
  let data;
58
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;
@@ -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 VERIFY_HELP = "Usage: myapi verify <email> [--org <id>] [--json]\n\nSync single-address email verification. Cheap layer only: syntax + DNS +\nMicrosoft GetCredentialType. Returns a verdict in <1s for ~50% of inputs;\nthe rest get verdict='unknown' with smtp_recommended=true.\n\nVerdicts:\n deliverable high-confidence \u2014 the address accepts mail\n undeliverable high-confidence \u2014 syntax bad, DNS missing, or Microsoft rejects\n unknown not enough signal; consider an SMTP probe (not in this API)\n\nUse --json for the full check breakdown (syntax / DNS / Microsoft probes).\n";
7
+ export declare function run(arg: string | undefined, _rest: string[], flags?: Flags): Promise<void>;
@@ -0,0 +1,56 @@
1
+ import { email as sdkEmail } from '@myapihq/sdk';
2
+ import { requireConfig } from '../config.js';
3
+ import { error, info, printJson } from '../output.js';
4
+ import { requireOrg } from '../helpers.js';
5
+ export const EXPOSES = [
6
+ 'POST /email/orgs/{org_id}/verify',
7
+ ];
8
+ export const SCHEMA = {
9
+ org: 'string',
10
+ };
11
+ export const VERIFY_HELP = `Usage: myapi verify <email> [--org <id>] [--json]
12
+
13
+ Sync single-address email verification. Cheap layer only: syntax + DNS +
14
+ Microsoft GetCredentialType. Returns a verdict in <1s for ~50% of inputs;
15
+ the rest get verdict='unknown' with smtp_recommended=true.
16
+
17
+ Verdicts:
18
+ deliverable high-confidence — the address accepts mail
19
+ undeliverable high-confidence — syntax bad, DNS missing, or Microsoft rejects
20
+ unknown not enough signal; consider an SMTP probe (not in this API)
21
+
22
+ Use --json for the full check breakdown (syntax / DNS / Microsoft probes).
23
+ `;
24
+ export async function run(arg, _rest, flags = {}) {
25
+ if (flags.help) {
26
+ info(VERIFY_HELP);
27
+ return;
28
+ }
29
+ if (!arg)
30
+ error('Missing required argument <email>.\nUsage: myapi verify <email> [--org <id>] [--json]');
31
+ const config = requireConfig();
32
+ const orgId = requireOrg(flags, config, 'myapi verify <email> [--org <id>]');
33
+ const res = await sdkEmail.verifyEmail(config.api_key, orgId, arg);
34
+ if (flags.json) {
35
+ printJson(res);
36
+ return;
37
+ }
38
+ // Compact human render. The verdict + confidence is the headline; the
39
+ // checks block is verbose enough that we hide it behind --json.
40
+ const verdictLabel = res.verdict === 'deliverable' ? '✓ deliverable'
41
+ : res.verdict === 'undeliverable' ? '✗ undeliverable'
42
+ : '? unknown';
43
+ info(`Email: ${res.email}`);
44
+ info(`Verdict: ${verdictLabel} (confidence ${res.confidence.toFixed(2)})`);
45
+ info(`SMTP next: ${res.smtp_recommended ? 'yes — consider an SMTP probe' : 'no — verdict is definitive'}`);
46
+ info(`Took: ${res.elapsed_ms}ms`);
47
+ if (res.checks.syntax.detail)
48
+ info(`Syntax: ${res.checks.syntax.detail}`);
49
+ if (res.checks.dns && res.checks.dns.mx_records && res.checks.dns.mx_records.length > 0) {
50
+ const mx = res.checks.dns.mx_records.filter(Boolean);
51
+ if (mx.length > 0)
52
+ info(`MX: ${mx.slice(0, 3).join(', ')}${mx.length > 3 ? ` (+${mx.length - 3} more)` : ''}`);
53
+ }
54
+ if (res.checks.microsoft)
55
+ info(`Microsoft: ${res.checks.microsoft.verdict}`);
56
+ }
@@ -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;
@@ -70,4 +70,88 @@ describe('parseFlags', () => {
70
70
  const { flags } = parseFlags(['--mystery=42'], SCHEMA);
71
71
  expect(flags.mystery).toBe('42');
72
72
  });
73
+ // ── S-405: Strict flag validation behavior ────────────────────────────────
74
+ //
75
+ // The CLI is intentionally LENIENT on unknown flags: they're tolerated as
76
+ // boolean/string values, with a stderr warning. This is by design — agents
77
+ // hallucinate flags occasionally, and breaking their script with a hard
78
+ // error on `--unknown-flag` is worse UX than warning + continuing.
79
+ //
80
+ // These tests lock in the documented behavior so it can't drift.
81
+ it('unknown flag with a following value-shaped token consumes that token', () => {
82
+ const { args, flags } = parseFlags(['funnel', 'create', '--on-error', 'simon@x.com'], SCHEMA);
83
+ expect(flags['on-error']).toBe('simon@x.com');
84
+ expect(args).toEqual(['funnel', 'create']);
85
+ });
86
+ it('unknown flag with no following token defaults to boolean true', () => {
87
+ const { flags } = parseFlags(['--mystery'], SCHEMA);
88
+ expect(flags.mystery).toBe(true);
89
+ });
90
+ it('unknown flag does not consume a token that looks like another flag', () => {
91
+ const { flags } = parseFlags(['--mystery', '--json'], SCHEMA);
92
+ expect(flags.mystery).toBe(true);
93
+ expect(flags.json).toBe(true);
94
+ });
95
+ it('emits a stderr warning when unknown flags are present', () => {
96
+ const stderrChunks = [];
97
+ const origWrite = process.stderr.write.bind(process.stderr);
98
+ process.stderr.write = (chunk) => { stderrChunks.push(String(chunk)); return true; };
99
+ try {
100
+ parseFlags(['--mystery', '--also-unknown'], SCHEMA);
101
+ }
102
+ finally {
103
+ process.stderr.write = origWrite;
104
+ }
105
+ const out = stderrChunks.join('');
106
+ expect(out).toMatch(/ignoring unknown flag/i);
107
+ expect(out).toMatch(/--mystery/);
108
+ expect(out).toMatch(/--also-unknown/);
109
+ });
110
+ it('respects MYAPI_QUIET_UNKNOWN_FLAGS to suppress the warning', () => {
111
+ const stderrChunks = [];
112
+ const origWrite = process.stderr.write.bind(process.stderr);
113
+ const origEnv = process.env.MYAPI_QUIET_UNKNOWN_FLAGS;
114
+ process.env.MYAPI_QUIET_UNKNOWN_FLAGS = '1';
115
+ process.stderr.write = (chunk) => { stderrChunks.push(String(chunk)); return true; };
116
+ try {
117
+ parseFlags(['--mystery'], SCHEMA);
118
+ }
119
+ finally {
120
+ process.stderr.write = origWrite;
121
+ if (origEnv === undefined)
122
+ delete process.env.MYAPI_QUIET_UNKNOWN_FLAGS;
123
+ else
124
+ process.env.MYAPI_QUIET_UNKNOWN_FLAGS = origEnv;
125
+ }
126
+ const out = stderrChunks.join('');
127
+ expect(out).not.toMatch(/ignoring unknown flag/);
128
+ });
129
+ it('emits no warning when there are no unknown flags', () => {
130
+ const stderrChunks = [];
131
+ const origWrite = process.stderr.write.bind(process.stderr);
132
+ process.stderr.write = (chunk) => { stderrChunks.push(String(chunk)); return true; };
133
+ try {
134
+ parseFlags(['--name', 'Acme', '--yes'], SCHEMA);
135
+ }
136
+ finally {
137
+ process.stderr.write = origWrite;
138
+ }
139
+ expect(stderrChunks.join('')).toBe('');
140
+ });
141
+ it('declared number flag with non-numeric value falls back to the raw string', () => {
142
+ const { flags } = parseFlags(['--years', 'not-a-number'], SCHEMA);
143
+ expect(flags.years).toBe('not-a-number');
144
+ });
145
+ it('declared boolean flag with --key=false sets it to false', () => {
146
+ const { flags } = parseFlags(['--no-enable=false'], SCHEMA);
147
+ expect(flags['no-enable']).toBe(false);
148
+ });
149
+ it('declared boolean flag with --key=0 sets it to false', () => {
150
+ const { flags } = parseFlags(['--no-enable=0'], SCHEMA);
151
+ expect(flags['no-enable']).toBe(false);
152
+ });
153
+ it('declared boolean flag with --key=true is truthy', () => {
154
+ const { flags } = parseFlags(['--no-enable=true'], SCHEMA);
155
+ expect(flags['no-enable']).toBe(true);
156
+ });
73
157
  });