@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.
- package/dist/commands/{email.d.ts → audience.d.ts} +2 -0
- package/dist/commands/audience.js +245 -0
- package/dist/commands/auth.d.ts +5 -2
- package/dist/commands/auth.js +133 -5
- package/dist/commands/billing.d.ts +2 -0
- package/dist/commands/billing.js +18 -1
- package/dist/commands/company.d.ts +6 -0
- package/dist/commands/company.js +149 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +20 -0
- package/dist/commands/crm/companies.d.ts +6 -0
- package/dist/commands/crm/companies.js +193 -0
- package/dist/commands/crm/contacts.d.ts +6 -0
- package/dist/commands/crm/contacts.js +241 -0
- package/dist/commands/crm/index.d.ts +6 -0
- package/dist/commands/crm/index.js +74 -0
- package/dist/commands/database.d.ts +6 -0
- package/dist/commands/database.js +259 -0
- package/dist/commands/domain.d.ts +3 -0
- package/dist/commands/domain.js +67 -1
- package/dist/commands/email/campaign.d.ts +2 -0
- package/dist/commands/email/campaign.js +34 -1
- package/dist/commands/email/index.d.ts +2 -0
- package/dist/commands/email/index.js +9 -0
- package/dist/commands/email/mailbox.d.ts +2 -0
- package/dist/commands/email/mailbox.js +5 -0
- package/dist/commands/email/message.d.ts +2 -0
- package/dist/commands/email/message.js +8 -0
- package/dist/commands/email/template.d.ts +2 -0
- package/dist/commands/email/template.js +54 -0
- package/dist/commands/email/verify.d.ts +7 -0
- package/dist/commands/email/verify.js +52 -0
- package/dist/commands/email/warmup.d.ts +2 -0
- package/dist/commands/email/warmup.js +7 -0
- package/dist/commands/funnel.d.ts +3 -0
- package/dist/commands/funnel.js +31 -0
- package/dist/commands/image.d.ts +2 -0
- package/dist/commands/image.js +43 -9
- package/dist/commands/keys.d.ts +2 -0
- package/dist/commands/keys.js +5 -0
- package/dist/commands/llm.d.ts +6 -0
- package/dist/commands/llm.js +156 -0
- package/dist/commands/org.d.ts +3 -0
- package/dist/commands/org.js +55 -0
- package/dist/commands/people.d.ts +6 -0
- package/dist/commands/people.js +136 -0
- package/dist/commands/pixel.d.ts +11 -3
- package/dist/commands/pixel.js +93 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +57 -5
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +154 -0
- package/dist/commands/storage.d.ts +2 -0
- package/dist/commands/storage.js +14 -2
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +3 -0
- package/dist/commands/url.d.ts +7 -2
- package/dist/commands/url.js +6 -0
- package/dist/commands/verify.d.ts +7 -0
- package/dist/commands/verify.js +56 -0
- package/dist/commands/webhook.d.ts +2 -0
- package/dist/commands/webhook.js +31 -6
- package/dist/commands/workflow.d.ts +2 -0
- package/dist/commands/workflow.js +19 -1
- package/dist/completion.d.ts +3 -0
- package/dist/completion.js +78 -0
- package/dist/config.d.ts +2 -0
- package/dist/exposes.d.ts +2 -0
- package/dist/exposes.js +13 -0
- package/dist/exposes.test.js +99 -0
- package/dist/flags.test.js +84 -0
- package/dist/index.js +106 -14
- package/dist/prompt.d.ts +15 -0
- package/dist/prompt.js +27 -0
- package/dist/registrant.d.ts +6 -0
- package/dist/registrant.js +155 -0
- package/dist/services-sync.test.d.ts +1 -0
- package/dist/services-sync.test.js +32 -0
- package/dist/skills/my-api-hq/SKILL.md +51 -45
- package/dist/skills/my-crm-api/README.md +42 -0
- package/dist/skills/my-crm-api/SKILL.md +165 -0
- package/dist/skills/my-crm-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-database-api/README.md +41 -0
- package/dist/skills/my-database-api/SKILL.md +107 -0
- package/dist/skills/my-database-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-domain-api/SKILL.md +53 -26
- package/dist/skills/my-email-verify-api/README.md +33 -0
- package/dist/skills/my-email-verify-api/SKILL.md +91 -0
- package/dist/skills/my-email-verify-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-funnel-api/SKILL.md +41 -37
- package/dist/skills/my-image-api/SKILL.md +48 -44
- package/dist/skills/my-llm-api/README.md +35 -0
- package/dist/skills/my-llm-api/SKILL.md +125 -0
- package/dist/skills/my-llm-api/claude/.claude-plugin/plugin.json +6 -0
- package/dist/skills/my-storage-api/README.md +1 -1
- package/dist/skills/my-storage-api/SKILL.md +44 -54
- package/dist/skills/my-webhook-api/README.md +1 -1
- package/dist/skills/my-webhook-api/SKILL.md +48 -92
- package/dist/skills/my-workflow-api/SKILL.md +46 -115
- package/dist/telemetry.d.ts +12 -0
- package/dist/telemetry.js +80 -0
- package/dist/telemetry.test.d.ts +1 -0
- package/dist/telemetry.test.js +101 -0
- package/package.json +13 -8
- package/dist/commands/email.js +0 -586
- package/dist/skills/my-email-api/README.md +0 -45
- package/dist/skills/my-email-api/SKILL.md +0 -104
- package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +0 -6
- package/dist/skills/my-email-api/make/.gitkeep +0 -0
- package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
- package/dist/skills/my-funnel-api/make/.gitkeep +0 -0
- package/dist/skills/my-funnel-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-image-api/make/.gitkeep +0 -0
- package/dist/skills/my-image-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-storage-api/make/.gitkeep +0 -0
- package/dist/skills/my-storage-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
- package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
- package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
- package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
- package/dist/utils.test.js +0 -48
- /package/dist/{utils.test.d.ts → exposes.test.d.ts} +0 -0
- /package/dist/skills/{my-api-hq/make → my-crm-api/openapi}/.gitkeep +0 -0
- /package/dist/skills/{my-api-hq/n8n → my-database-api/openapi}/.gitkeep +0 -0
- /package/dist/skills/{my-domain-api/make → my-email-verify-api/openapi}/.gitkeep +0 -0
- /package/dist/skills/{my-domain-api/n8n → my-llm-api/openapi}/.gitkeep +0 -0
package/dist/commands/storage.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { readFile } from 'fs/promises';
|
|
2
2
|
import { extname, basename } from 'path';
|
|
3
|
-
import { storage as sdkStorage
|
|
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
|
|
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>;
|
package/dist/commands/update.js
CHANGED
|
@@ -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
|
package/dist/commands/url.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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>;
|
package/dist/commands/url.js
CHANGED
|
@@ -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>;
|
package/dist/commands/webhook.js
CHANGED
|
@@ -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> [
|
|
29
|
-
const
|
|
30
|
-
|
|
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> [
|
|
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,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
|
}
|
package/dist/exposes.js
ADDED
|
@@ -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;
|
package/dist/flags.test.js
CHANGED
|
@@ -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
|
});
|