@myapihq/cli 1.0.23 → 1.0.24

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.
@@ -0,0 +1,2 @@
1
+ export declare function signup(): Promise<void>;
2
+ export declare function whoami(): Promise<void>;
@@ -0,0 +1,70 @@
1
+ import * as readline from 'readline';
2
+ import { loadConfig, saveConfig } from '../config.js';
3
+ import { info, success, error } from '../output.js';
4
+ const API_BASE = process.env.MYAPI_API_BASE ?? 'https://api.myapihq.com';
5
+ function ask(rl, q) {
6
+ return new Promise(resolve => rl.question(q, resolve));
7
+ }
8
+ async function post(path, body, apiKey) {
9
+ const headers = { 'Content-Type': 'application/json' };
10
+ if (apiKey)
11
+ headers['Authorization'] = `Bearer ${apiKey}`;
12
+ const res = await fetch(`${API_BASE}${path}`, {
13
+ method: 'POST',
14
+ headers,
15
+ body: JSON.stringify(body),
16
+ });
17
+ const json = await res.json();
18
+ if (!res.ok)
19
+ throw new Error(json.error ?? `HTTP ${res.status}`);
20
+ return json.data ?? json;
21
+ }
22
+ async function patch(path, body, apiKey) {
23
+ const res = await fetch(`${API_BASE}${path}`, {
24
+ method: 'PATCH',
25
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
26
+ body: JSON.stringify(body),
27
+ });
28
+ const json = await res.json();
29
+ if (!res.ok)
30
+ throw new Error(json.error ?? `HTTP ${res.status}`);
31
+ return json.data ?? json;
32
+ }
33
+ // myapi auth signup — upgrade anonymous session to registered account.
34
+ export async function signup() {
35
+ const config = loadConfig();
36
+ if (!config?.api_key)
37
+ error('Not configured. Run: myapi setup');
38
+ if (!config.is_anonymous)
39
+ error('Already registered. Use your existing account.');
40
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
41
+ try {
42
+ const email = (await ask(rl, '› Email? ')).trim();
43
+ await patch('/hq/account/upgrade', { email }, config.api_key);
44
+ info(`› Sent a code to ${email} · paste it below`);
45
+ const code = (await ask(rl, '› Code? ')).trim();
46
+ const data = await post('/hq/account/verify-code', { email, code });
47
+ saveConfig({
48
+ ...config,
49
+ api_key: data.api_key,
50
+ account_id: data.account_id,
51
+ default_org: data.default_org || config.default_org,
52
+ default_funnel: data.default_funnel || config.default_funnel,
53
+ is_anonymous: false,
54
+ });
55
+ success(`› Welcome! Account upgraded · ${email}`);
56
+ }
57
+ finally {
58
+ rl.close();
59
+ }
60
+ }
61
+ // myapi auth whoami — show current session info.
62
+ export async function whoami() {
63
+ const config = loadConfig();
64
+ if (!config?.api_key)
65
+ error('Not configured. Run: myapi setup');
66
+ info(`Account: ${config.account_id}`);
67
+ info(`Org: ${config.default_org ?? '(none)'}`);
68
+ info(`Funnel: ${config.default_funnel ?? '(none)'}`);
69
+ info(`Type: ${config.is_anonymous ? 'anonymous' : 'registered'}`);
70
+ }
@@ -15,12 +15,12 @@ export async function create(flags) {
15
15
  if (!orgId) {
16
16
  error("Missing required arguments.\nUsage: myapi funnel create --org <id>\n(Or set defaults via: myapi config set-org <id>)");
17
17
  }
18
- const funnel = await sdkFunnel.createFunnel(config.api_key, orgId);
19
- success(`Funnel created! ID: ${funnel.id}`);
20
- const org = await hq.getOrg(config.api_key, orgId);
21
- if (org.preview_subdomain) {
22
- info(`Preview: https://${org.preview_subdomain}.makeautonomous.com`);
23
- }
18
+ const result = await sdkFunnel.createFunnel(config.api_key, orgId);
19
+ success(`Funnel created! ID: ${result.funnel.id}`);
20
+ if (result.domain_url)
21
+ info(`Live: ${result.domain_url}`);
22
+ else if (result.subdomain_url)
23
+ info(`Preview: ${result.subdomain_url}`);
24
24
  }
25
25
  export async function get(id, flags) {
26
26
  const config = requireConfig();
@@ -1 +1,2 @@
1
+ export declare function installSkills(): Promise<void>;
1
2
  export declare function setup(): Promise<void>;
@@ -1,6 +1,10 @@
1
- import { loadConfig, saveConfig } from '../config.js';
2
- import { success, info } from '../output.js';
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
3
4
  import * as readline from 'readline';
5
+ import { loadConfig, saveConfig } from '../config.js';
6
+ import { info, success } from '../output.js';
7
+ const API_BASE = process.env.MYAPI_API_BASE ?? 'https://api.myapihq.com';
4
8
  function ask(rl, q) {
5
9
  return new Promise(resolve => rl.question(q, resolve));
6
10
  }
@@ -10,31 +14,166 @@ function yn(answer, defaultYes = true) {
10
14
  return defaultYes;
11
15
  return t === 'y' || t === 'yes';
12
16
  }
13
- async function getKey(rl) {
14
- const key = await ask(rl, 'Paste your API key (get it from https://myapihq.com): ');
15
- saveConfig({ api_key: key.trim(), account_id: '', pin: '' });
16
- success('API key saved to ~/.myapi/config.json');
17
- return key.trim();
17
+ async function post(path, body) {
18
+ const res = await fetch(`${API_BASE}${path}`, {
19
+ method: 'POST',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify(body),
22
+ });
23
+ const json = await res.json();
24
+ if (!res.ok)
25
+ throw new Error(json.error ?? `HTTP ${res.status}`);
26
+ return json.data ?? json;
27
+ }
28
+ // ---------------------------------------------------------------------------
29
+ // Skills installation
30
+ // ---------------------------------------------------------------------------
31
+ const AGENT_DIRS = {
32
+ claude: path.join(os.homedir(), '.claude', 'skills'),
33
+ gemini: path.join(os.homedir(), '.gemini', 'skills'),
34
+ cursor: path.join(os.homedir(), '.cursor', 'rules'),
35
+ };
36
+ const SKILLS_CANONICAL = path.join(os.homedir(), '.agents', 'skills', 'myapi');
37
+ // Skills are bundled inside the npm package at build time from skills/*/SKILL.md
38
+ const BUNDLED_SKILLS_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'skills');
39
+ export async function installSkills() {
40
+ if (!fs.existsSync(BUNDLED_SKILLS_DIR)) {
41
+ info('No bundled skills found — skipping skills install.');
42
+ return;
43
+ }
44
+ fs.mkdirSync(SKILLS_CANONICAL, { recursive: true });
45
+ const files = fs.readdirSync(BUNDLED_SKILLS_DIR).filter(f => f.endsWith('.md'));
46
+ for (const file of files) {
47
+ const src = path.join(BUNDLED_SKILLS_DIR, file);
48
+ const dst = path.join(SKILLS_CANONICAL, file);
49
+ fs.copyFileSync(src, dst);
50
+ }
51
+ for (const [agent, dir] of Object.entries(AGENT_DIRS)) {
52
+ try {
53
+ fs.mkdirSync(dir, { recursive: true });
54
+ for (const file of files) {
55
+ const link = path.join(dir, file);
56
+ const target = path.join(SKILLS_CANONICAL, file);
57
+ try {
58
+ if (fs.existsSync(link)) {
59
+ const stat = fs.lstatSync(link);
60
+ if (stat.isSymbolicLink() && fs.readlinkSync(link) === target)
61
+ continue;
62
+ fs.unlinkSync(link);
63
+ }
64
+ fs.symlinkSync(target, link);
65
+ }
66
+ catch {
67
+ // Non-fatal: skip this agent dir if permissions are wrong.
68
+ }
69
+ }
70
+ info(` ✓ ${agent}`);
71
+ }
72
+ catch {
73
+ // Silently skip agents whose directory can't be created.
74
+ }
75
+ }
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // Registered flow
79
+ // ---------------------------------------------------------------------------
80
+ async function registeredFlow(rl) {
81
+ const email = (await ask(rl, '› Email? ')).trim();
82
+ await post('/hq/account/send-code', { email });
83
+ info(`› Sent a code to ${email} · paste it below`);
84
+ const code = (await ask(rl, '› Code? ')).trim();
85
+ const data = await post('/hq/account/verify-code', { email, code });
86
+ return data;
18
87
  }
88
+ // ---------------------------------------------------------------------------
89
+ // Anonymous flow
90
+ // ---------------------------------------------------------------------------
91
+ async function anonymousFlow() {
92
+ const data = await post('/hq/account/anonymous', {});
93
+ return data;
94
+ }
95
+ // ---------------------------------------------------------------------------
96
+ // Main setup command
97
+ // ---------------------------------------------------------------------------
19
98
  export async function setup() {
20
- info('MyAPI Setup\n');
21
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
99
+ info('› Configuring MyAPI');
22
100
  const existing = loadConfig();
101
+ // Already configured — just verify the key is still valid.
23
102
  if (existing?.api_key) {
24
- const reuse = await ask(rl, `Found existing account (${existing.account_id || existing.api_key.slice(0, 20) + '...'}). Use it? [Y/n]: `);
25
- if (!yn(reuse, true)) {
26
- await getKey(rl);
103
+ try {
104
+ const res = await fetch(`${API_BASE}/hq/account/me`, {
105
+ headers: { Authorization: `Bearer ${existing.api_key}` },
106
+ });
107
+ if (res.ok) {
108
+ info(`› Already configured · account ${existing.account_id}`);
109
+ // Re-ask skills only if preference was never recorded.
110
+ if (existing.skills_installed === undefined) {
111
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
112
+ const ans = await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
113
+ rl.close();
114
+ if (yn(ans)) {
115
+ await installSkills();
116
+ success('› Skills installed.');
117
+ }
118
+ saveConfig({ ...existing, skills_installed: yn(ans) });
119
+ }
120
+ return;
121
+ }
122
+ }
123
+ catch { /* fall through to full setup */ }
124
+ }
125
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
126
+ let apiKey = '';
127
+ let accountId = '';
128
+ let defaultOrg = '';
129
+ let defaultFunnel = '';
130
+ let subdomainUrl = '';
131
+ let isAnonymous = false;
132
+ try {
133
+ const createAns = await ask(rl, '› Create an account? (Y/n) ');
134
+ if (yn(createAns)) {
135
+ const data = await registeredFlow(rl);
136
+ apiKey = data.api_key;
137
+ accountId = data.account_id;
138
+ defaultOrg = data.default_org;
139
+ defaultFunnel = data.default_funnel;
140
+ }
141
+ else {
142
+ info('› Continuing as anonymous — sites ship to *.makeautonomous.com');
143
+ const data = await anonymousFlow();
144
+ apiKey = data.api_key;
145
+ accountId = data.account_id;
146
+ defaultOrg = data.default_org;
147
+ defaultFunnel = data.default_funnel;
148
+ subdomainUrl = data.subdomain_url;
149
+ isAnonymous = true;
150
+ }
151
+ const skillsAns = await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
152
+ const wantsSkills = yn(skillsAns);
153
+ saveConfig({
154
+ api_key: apiKey,
155
+ account_id: accountId,
156
+ pin: '',
157
+ default_org: defaultOrg,
158
+ default_funnel: defaultFunnel,
159
+ is_anonymous: isAnonymous,
160
+ skills_installed: wantsSkills,
161
+ });
162
+ success(`› ✓ saved to ~/.myapi/config.json`);
163
+ if (wantsSkills) {
164
+ await installSkills();
165
+ }
166
+ if (isAnonymous) {
167
+ success('› Setup complete. No card, no email, ready to ship.');
168
+ if (subdomainUrl)
169
+ info(`› Your funnel: ${subdomainUrl}`);
170
+ info('› Upgrade anytime — myapi auth signup');
27
171
  }
28
172
  else {
29
- info('Using existing credentials.');
173
+ success(`› Setup complete. Org ${defaultOrg} ready · $5.00 free credits added.`);
30
174
  }
31
175
  }
32
- else {
33
- await getKey(rl);
176
+ finally {
177
+ rl.close();
34
178
  }
35
- rl.close();
36
- info('\nTo integrate MyAPI with AI agents (Claude, Cursor, Copilot, etc.),');
37
- info('please follow the instructions in our skills repository:');
38
- info('https://github.com/myapihq/agent-skills\n');
39
- success('Setup complete! Try: myapi billing balance');
40
179
  }
@@ -0,0 +1,2 @@
1
+ export declare function checkForUpdate(currentVersion: string): Promise<void>;
2
+ export declare function update(): Promise<void>;
@@ -0,0 +1,58 @@
1
+ import { execSync } from 'child_process';
2
+ import { loadConfig, saveConfig } from '../config.js';
3
+ import { info, success } from '../output.js';
4
+ import { installSkills } from './setup.js';
5
+ const REGISTRY_URL = 'https://registry.npmjs.org/@myapihq/cli/latest';
6
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
7
+ // checkForUpdate runs silently in the background on every command.
8
+ // Prints a one-liner if a newer version is available.
9
+ export async function checkForUpdate(currentVersion) {
10
+ const config = loadConfig();
11
+ const now = Date.now();
12
+ if (config?.last_update_check && now - config.last_update_check < CHECK_INTERVAL_MS) {
13
+ return;
14
+ }
15
+ try {
16
+ const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(3000) });
17
+ if (!res.ok)
18
+ return;
19
+ const data = await res.json();
20
+ const latest = data.version;
21
+ if (config) {
22
+ saveConfig({ ...config, last_update_check: now });
23
+ }
24
+ if (latest && latest !== currentVersion && isNewer(latest, currentVersion)) {
25
+ // Print after a tiny delay so it appears below command output.
26
+ setTimeout(() => {
27
+ info(`\n› Update available: ${currentVersion} → ${latest} · run: myapi update`);
28
+ }, 50);
29
+ }
30
+ }
31
+ catch {
32
+ // Network errors are silently ignored.
33
+ }
34
+ }
35
+ // myapi update — installs the latest CLI version then re-installs skills.
36
+ export async function update() {
37
+ info('› Updating MyAPI CLI…');
38
+ try {
39
+ execSync('npm install -g @myapihq/cli', { stdio: 'inherit' });
40
+ }
41
+ catch {
42
+ // npm printed its own error; just exit.
43
+ process.exit(1);
44
+ }
45
+ info('› Refreshing skills…');
46
+ await installSkills();
47
+ success('› Up to date.');
48
+ }
49
+ function isNewer(latest, current) {
50
+ const toNum = (v) => v.split('.').map(Number);
51
+ const [lMaj, lMin, lPat] = toNum(latest);
52
+ const [cMaj, cMin, cPat] = toNum(current);
53
+ if (lMaj !== cMaj)
54
+ return lMaj > cMaj;
55
+ if (lMin !== cMin)
56
+ return lMin > cMin;
57
+ return lPat > cPat;
58
+ }
package/dist/config.d.ts CHANGED
@@ -3,7 +3,11 @@ export interface Config {
3
3
  account_id: string;
4
4
  pin: string;
5
5
  default_org?: string;
6
+ default_funnel?: string;
6
7
  default_domain?: string;
8
+ is_anonymous?: boolean;
9
+ skills_installed?: boolean;
10
+ last_update_check?: number;
7
11
  autocomplete_setup?: boolean;
8
12
  }
9
13
  export declare function loadConfig(): Config | null;
package/dist/index.js CHANGED
@@ -14,11 +14,6 @@ import * as setupCmd from './commands/setup.js';
14
14
  import * as configCmd from './commands/config.js';
15
15
  import * as domainCmd from './commands/domain.js';
16
16
  import * as funnelCmd from './commands/funnel.js';
17
- import * as imageCmd from './commands/image.js';
18
- import * as storageCmd from './commands/storage.js';
19
- import * as urlCmd from './commands/url.js';
20
- import * as webhookCmd from './commands/webhook.js';
21
- import * as workflowCmd from './commands/workflow.js';
22
17
  async function main() {
23
18
  try {
24
19
  updateNotifier({ pkg }).notify();
@@ -96,21 +91,6 @@ async function main() {
96
91
  case 'funnel':
97
92
  await funnelCmd.run(subcommand, restArgs, flags);
98
93
  break;
99
- case 'image':
100
- await imageCmd.run(subcommand, restArgs, flags);
101
- break;
102
- case 'storage':
103
- await storageCmd.run(subcommand, restArgs, flags);
104
- break;
105
- case 'url':
106
- await urlCmd.run(subcommand, restArgs, flags);
107
- break;
108
- case 'webhook':
109
- await webhookCmd.run(subcommand, restArgs, flags);
110
- break;
111
- case 'workflow':
112
- await workflowCmd.run(subcommand, restArgs, flags);
113
- break;
114
94
  default:
115
95
  printHelp();
116
96
  process.exit(0);
@@ -139,11 +119,6 @@ Commands:
139
119
  config Manage CLI defaults like org_id and domain
140
120
  domain Manage domain configurations
141
121
  funnel Manage headless funnels and pages
142
- image Generate AI images
143
- storage Manage static assets
144
- url Shorten URLs and manage links
145
- webhook Manage inbound webhooks
146
- workflow Manage workflow automations
147
122
 
148
123
  Run "myapi <command> --help" for subcommand help.
149
124
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myapihq/cli",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
4
4
  "description": "MyAPI command-line interface",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,6 +8,7 @@
8
8
  "myapi": "dist/index.js"
9
9
  },
10
10
  "scripts": {
11
+ "prebuild": "node scripts/copy-skills.js",
11
12
  "build": "tsc",
12
13
  "dev": "tsc --watch"
13
14
  },
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ // Copies skills/*/SKILL.md from the repo root into src/skills/ so they get
3
+ // bundled in the npm package. Runs as prebuild.
4
+ import { readdirSync, mkdirSync, copyFileSync, existsSync, readFileSync } from 'fs';
5
+ import { join, dirname } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const repoRoot = join(__dirname, '..', '..', '..');
10
+ const skillsRoot = join(repoRoot, 'skills');
11
+ const dest = join(__dirname, '..', 'src', 'skills');
12
+
13
+ if (!existsSync(skillsRoot)) {
14
+ console.log('copy-skills: no skills/ directory found, skipping.');
15
+ process.exit(0);
16
+ }
17
+
18
+ mkdirSync(dest, { recursive: true });
19
+
20
+ let copied = 0;
21
+ for (const skillDir of readdirSync(skillsRoot)) {
22
+ const src = join(skillsRoot, skillDir, 'SKILL.md');
23
+ const pluginJson = join(skillsRoot, skillDir, 'claude', '.claude-plugin', 'plugin.json');
24
+ if (!existsSync(src)) continue;
25
+ // Only bundle skills marked published: true
26
+ if (existsSync(pluginJson)) {
27
+ const plugin = JSON.parse(readFileSync(pluginJson, 'utf-8'));
28
+ if (!plugin.published) continue;
29
+ }
30
+ copyFileSync(src, join(dest, `${skillDir}.md`));
31
+ copied++;
32
+ }
33
+
34
+ console.log(`copy-skills: copied ${copied} skill(s) to src/skills/`);
@@ -0,0 +1,81 @@
1
+ import * as readline from 'readline';
2
+
3
+ import { loadConfig, saveConfig } from '../config.js';
4
+ import { info, success, error } from '../output.js';
5
+
6
+ const API_BASE = process.env.MYAPI_API_BASE ?? 'https://api.myapihq.com';
7
+
8
+ function ask(rl: readline.Interface, q: string): Promise<string> {
9
+ return new Promise(resolve => rl.question(q, resolve));
10
+ }
11
+
12
+ async function post(path: string, body: unknown, apiKey?: string): Promise<unknown> {
13
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' };
14
+ if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
15
+ const res = await fetch(`${API_BASE}${path}`, {
16
+ method: 'POST',
17
+ headers,
18
+ body: JSON.stringify(body),
19
+ });
20
+ const json = await res.json() as { data?: unknown; error?: string };
21
+ if (!res.ok) throw new Error(json.error ?? `HTTP ${res.status}`);
22
+ return json.data ?? json;
23
+ }
24
+
25
+ async function patch(path: string, body: unknown, apiKey: string): Promise<unknown> {
26
+ const res = await fetch(`${API_BASE}${path}`, {
27
+ method: 'PATCH',
28
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
29
+ body: JSON.stringify(body),
30
+ });
31
+ const json = await res.json() as { data?: unknown; error?: string };
32
+ if (!res.ok) throw new Error(json.error ?? `HTTP ${res.status}`);
33
+ return json.data ?? json;
34
+ }
35
+
36
+ // myapi auth signup — upgrade anonymous session to registered account.
37
+ export async function signup() {
38
+ const config = loadConfig();
39
+ if (!config?.api_key) error('Not configured. Run: myapi setup');
40
+ if (!config!.is_anonymous) error('Already registered. Use your existing account.');
41
+
42
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
43
+ try {
44
+ const email = (await ask(rl, '› Email? ')).trim();
45
+
46
+ await patch('/hq/account/upgrade', { email }, config!.api_key);
47
+ info(`› Sent a code to ${email} · paste it below`);
48
+
49
+ const code = (await ask(rl, '› Code? ')).trim();
50
+ const data = await post('/hq/account/verify-code', { email, code }) as {
51
+ api_key: string;
52
+ account_id: string;
53
+ default_org: string;
54
+ default_funnel: string;
55
+ };
56
+
57
+ saveConfig({
58
+ ...config!,
59
+ api_key: data.api_key,
60
+ account_id: data.account_id,
61
+ default_org: data.default_org || config!.default_org,
62
+ default_funnel: data.default_funnel || config!.default_funnel,
63
+ is_anonymous: false,
64
+ });
65
+
66
+ success(`› Welcome! Account upgraded · ${email}`);
67
+ } finally {
68
+ rl.close();
69
+ }
70
+ }
71
+
72
+ // myapi auth whoami — show current session info.
73
+ export async function whoami() {
74
+ const config = loadConfig();
75
+ if (!config?.api_key) error('Not configured. Run: myapi setup');
76
+
77
+ info(`Account: ${config!.account_id}`);
78
+ info(`Org: ${config!.default_org ?? '(none)'}`);
79
+ info(`Funnel: ${config!.default_funnel ?? '(none)'}`);
80
+ info(`Type: ${config!.is_anonymous ? 'anonymous' : 'registered'}`);
81
+ }
@@ -19,12 +19,10 @@ export async function create(flags: Record<string, string | boolean>) {
19
19
  error("Missing required arguments.\nUsage: myapi funnel create --org <id>\n(Or set defaults via: myapi config set-org <id>)");
20
20
  }
21
21
 
22
- const funnel = await sdkFunnel.createFunnel(config.api_key, orgId);
23
- success(`Funnel created! ID: ${funnel.id}`);
24
- const org = await hq.getOrg(config.api_key, orgId);
25
- if (org.preview_subdomain) {
26
- info(`Preview: https://${org.preview_subdomain}.makeautonomous.com`);
27
- }
22
+ const result = await sdkFunnel.createFunnel(config.api_key, orgId);
23
+ success(`Funnel created! ID: ${result.funnel.id}`);
24
+ if (result.domain_url) info(`Live: ${result.domain_url}`);
25
+ else if (result.subdomain_url) info(`Preview: ${result.subdomain_url}`);
28
26
  }
29
27
 
30
28
  export async function get(id: string, flags: Record<string, string | boolean>) {
@@ -1,7 +1,13 @@
1
- import { loadConfig, saveConfig } from '../config.js';
2
- import { success, info } from '../output.js';
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
3
4
  import * as readline from 'readline';
4
5
 
6
+ import { loadConfig, saveConfig } from '../config.js';
7
+ import { info, success } from '../output.js';
8
+
9
+ const API_BASE = process.env.MYAPI_API_BASE ?? 'https://api.myapihq.com';
10
+
5
11
  function ask(rl: readline.Interface, q: string): Promise<string> {
6
12
  return new Promise(resolve => rl.question(q, resolve));
7
13
  }
@@ -12,35 +18,212 @@ function yn(answer: string, defaultYes = true): boolean {
12
18
  return t === 'y' || t === 'yes';
13
19
  }
14
20
 
15
- async function getKey(rl: readline.Interface): Promise<string> {
16
- const key = await ask(rl, 'Paste your API key (get it from https://myapihq.com): ');
17
- saveConfig({ api_key: key.trim(), account_id: '', pin: '' });
18
- success('API key saved to ~/.myapi/config.json');
19
- return key.trim();
21
+ async function post(path: string, body: unknown): Promise<unknown> {
22
+ const res = await fetch(`${API_BASE}${path}`, {
23
+ method: 'POST',
24
+ headers: { 'Content-Type': 'application/json' },
25
+ body: JSON.stringify(body),
26
+ });
27
+ const json = await res.json() as { data?: unknown; error?: string };
28
+ if (!res.ok) throw new Error(json.error ?? `HTTP ${res.status}`);
29
+ return json.data ?? json;
20
30
  }
21
31
 
22
- export async function setup() {
23
- info('MyAPI Setup\n');
32
+ // ---------------------------------------------------------------------------
33
+ // Skills installation
34
+ // ---------------------------------------------------------------------------
24
35
 
25
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
36
+ const AGENT_DIRS: Record<string, string> = {
37
+ claude: path.join(os.homedir(), '.claude', 'skills'),
38
+ gemini: path.join(os.homedir(), '.gemini', 'skills'),
39
+ cursor: path.join(os.homedir(), '.cursor', 'rules'),
40
+ };
41
+
42
+ const SKILLS_CANONICAL = path.join(os.homedir(), '.agents', 'skills', 'myapi');
43
+
44
+ // Skills are bundled inside the npm package at build time from skills/*/SKILL.md
45
+ const BUNDLED_SKILLS_DIR = path.join(
46
+ path.dirname(new URL(import.meta.url).pathname),
47
+ '..',
48
+ 'skills',
49
+ );
50
+
51
+ export async function installSkills(): Promise<void> {
52
+ if (!fs.existsSync(BUNDLED_SKILLS_DIR)) {
53
+ info('No bundled skills found — skipping skills install.');
54
+ return;
55
+ }
56
+
57
+ fs.mkdirSync(SKILLS_CANONICAL, { recursive: true });
58
+
59
+ const files = fs.readdirSync(BUNDLED_SKILLS_DIR).filter(f => f.endsWith('.md'));
60
+ for (const file of files) {
61
+ const src = path.join(BUNDLED_SKILLS_DIR, file);
62
+ const dst = path.join(SKILLS_CANONICAL, file);
63
+ fs.copyFileSync(src, dst);
64
+ }
65
+
66
+ for (const [agent, dir] of Object.entries(AGENT_DIRS)) {
67
+ try {
68
+ fs.mkdirSync(dir, { recursive: true });
69
+ for (const file of files) {
70
+ const link = path.join(dir, file);
71
+ const target = path.join(SKILLS_CANONICAL, file);
72
+ try {
73
+ if (fs.existsSync(link)) {
74
+ const stat = fs.lstatSync(link);
75
+ if (stat.isSymbolicLink() && fs.readlinkSync(link) === target) continue;
76
+ fs.unlinkSync(link);
77
+ }
78
+ fs.symlinkSync(target, link);
79
+ } catch {
80
+ // Non-fatal: skip this agent dir if permissions are wrong.
81
+ }
82
+ }
83
+ info(` ✓ ${agent}`);
84
+ } catch {
85
+ // Silently skip agents whose directory can't be created.
86
+ }
87
+ }
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Registered flow
92
+ // ---------------------------------------------------------------------------
93
+
94
+ async function registeredFlow(rl: readline.Interface): Promise<{
95
+ api_key: string;
96
+ account_id: string;
97
+ default_org: string;
98
+ default_funnel: string;
99
+ }> {
100
+ const email = (await ask(rl, '› Email? ')).trim();
101
+
102
+ await post('/hq/account/send-code', { email });
103
+ info(`› Sent a code to ${email} · paste it below`);
104
+
105
+ const code = (await ask(rl, '› Code? ')).trim();
106
+ const data = await post('/hq/account/verify-code', { email, code }) as {
107
+ api_key: string;
108
+ account_id: string;
109
+ default_org: string;
110
+ default_funnel: string;
111
+ };
112
+
113
+ return data;
114
+ }
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // Anonymous flow
118
+ // ---------------------------------------------------------------------------
119
+
120
+ async function anonymousFlow(): Promise<{
121
+ api_key: string;
122
+ account_id: string;
123
+ default_org: string;
124
+ default_funnel: string;
125
+ subdomain_url: string;
126
+ }> {
127
+ const data = await post('/hq/account/anonymous', {}) as {
128
+ api_key: string;
129
+ account_id: string;
130
+ default_org: string;
131
+ default_funnel: string;
132
+ subdomain_url: string;
133
+ };
134
+ return data;
135
+ }
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // Main setup command
139
+ // ---------------------------------------------------------------------------
140
+
141
+ export async function setup() {
142
+ info('› Configuring MyAPI…');
26
143
 
27
144
  const existing = loadConfig();
145
+
146
+ // Already configured — just verify the key is still valid.
28
147
  if (existing?.api_key) {
29
- const reuse = await ask(rl, `Found existing account (${existing.account_id || existing.api_key.slice(0, 20) + '...'}). Use it? [Y/n]: `);
30
- if (!yn(reuse, true)) {
31
- await getKey(rl);
148
+ try {
149
+ const res = await fetch(`${API_BASE}/hq/account/me`, {
150
+ headers: { Authorization: `Bearer ${existing.api_key}` },
151
+ });
152
+ if (res.ok) {
153
+ info(`› Already configured · account ${existing.account_id}`);
154
+ // Re-ask skills only if preference was never recorded.
155
+ if (existing.skills_installed === undefined) {
156
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
157
+ const ans = await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
158
+ rl.close();
159
+ if (yn(ans)) {
160
+ await installSkills();
161
+ success('› Skills installed.');
162
+ }
163
+ saveConfig({ ...existing, skills_installed: yn(ans) });
164
+ }
165
+ return;
166
+ }
167
+ } catch { /* fall through to full setup */ }
168
+ }
169
+
170
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
171
+
172
+ let apiKey = '';
173
+ let accountId = '';
174
+ let defaultOrg = '';
175
+ let defaultFunnel = '';
176
+ let subdomainUrl = '';
177
+ let isAnonymous = false;
178
+
179
+ try {
180
+ const createAns = await ask(rl, '› Create an account? (Y/n) ');
181
+
182
+ if (yn(createAns)) {
183
+ const data = await registeredFlow(rl);
184
+ apiKey = data.api_key;
185
+ accountId = data.account_id;
186
+ defaultOrg = data.default_org;
187
+ defaultFunnel = data.default_funnel;
32
188
  } else {
33
- info('Using existing credentials.');
189
+ info(' Continuing as anonymous — sites ship to *.makeautonomous.com');
190
+ const data = await anonymousFlow();
191
+ apiKey = data.api_key;
192
+ accountId = data.account_id;
193
+ defaultOrg = data.default_org;
194
+ defaultFunnel = data.default_funnel;
195
+ subdomainUrl = data.subdomain_url;
196
+ isAnonymous = true;
34
197
  }
35
- } else {
36
- await getKey(rl);
37
- }
38
198
 
39
- rl.close();
199
+ const skillsAns = await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
200
+ const wantsSkills = yn(skillsAns);
201
+
202
+ saveConfig({
203
+ api_key: apiKey,
204
+ account_id: accountId,
205
+ pin: '',
206
+ default_org: defaultOrg,
207
+ default_funnel: defaultFunnel,
208
+ is_anonymous: isAnonymous,
209
+ skills_installed: wantsSkills,
210
+ });
40
211
 
41
- info('\nTo integrate MyAPI with AI agents (Claude, Cursor, Copilot, etc.),');
42
- info('please follow the instructions in our skills repository:');
43
- info('https://github.com/myapihq/agent-skills\n');
212
+ success(`› saved to ~/.myapi/config.json`);
44
213
 
45
- success('Setup complete! Try: myapi billing balance');
214
+ if (wantsSkills) {
215
+ await installSkills();
216
+ }
217
+
218
+ if (isAnonymous) {
219
+ success('› Setup complete. No card, no email, ready to ship.');
220
+ if (subdomainUrl) info(`› Your funnel: ${subdomainUrl}`);
221
+ info('› Upgrade anytime — myapi auth signup');
222
+ } else {
223
+ success(`› Setup complete. Org ${defaultOrg} ready · $5.00 free credits added.`);
224
+ }
225
+ } finally {
226
+ rl.close();
227
+ }
46
228
  }
229
+
@@ -0,0 +1,61 @@
1
+ import { execSync } from 'child_process';
2
+ import { loadConfig, saveConfig } from '../config.js';
3
+ import { info, success } from '../output.js';
4
+ import { installSkills } from './setup.js';
5
+
6
+ const REGISTRY_URL = 'https://registry.npmjs.org/@myapihq/cli/latest';
7
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
8
+
9
+ // checkForUpdate runs silently in the background on every command.
10
+ // Prints a one-liner if a newer version is available.
11
+ export async function checkForUpdate(currentVersion: string): Promise<void> {
12
+ const config = loadConfig();
13
+ const now = Date.now();
14
+
15
+ if (config?.last_update_check && now - config.last_update_check < CHECK_INTERVAL_MS) {
16
+ return;
17
+ }
18
+
19
+ try {
20
+ const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(3000) });
21
+ if (!res.ok) return;
22
+ const data = await res.json() as { version?: string };
23
+ const latest = data.version;
24
+
25
+ if (config) {
26
+ saveConfig({ ...config, last_update_check: now });
27
+ }
28
+
29
+ if (latest && latest !== currentVersion && isNewer(latest, currentVersion)) {
30
+ // Print after a tiny delay so it appears below command output.
31
+ setTimeout(() => {
32
+ info(`\n› Update available: ${currentVersion} → ${latest} · run: myapi update`);
33
+ }, 50);
34
+ }
35
+ } catch {
36
+ // Network errors are silently ignored.
37
+ }
38
+ }
39
+
40
+ // myapi update — installs the latest CLI version then re-installs skills.
41
+ export async function update(): Promise<void> {
42
+ info('› Updating MyAPI CLI…');
43
+ try {
44
+ execSync('npm install -g @myapihq/cli', { stdio: 'inherit' });
45
+ } catch {
46
+ // npm printed its own error; just exit.
47
+ process.exit(1);
48
+ }
49
+ info('› Refreshing skills…');
50
+ await installSkills();
51
+ success('› Up to date.');
52
+ }
53
+
54
+ function isNewer(latest: string, current: string): boolean {
55
+ const toNum = (v: string) => v.split('.').map(Number);
56
+ const [lMaj, lMin, lPat] = toNum(latest);
57
+ const [cMaj, cMin, cPat] = toNum(current);
58
+ if (lMaj !== cMaj) return lMaj > cMaj;
59
+ if (lMin !== cMin) return lMin > cMin;
60
+ return lPat > cPat;
61
+ }
package/src/config.ts CHANGED
@@ -7,7 +7,11 @@ export interface Config {
7
7
  account_id: string;
8
8
  pin: string;
9
9
  default_org?: string;
10
+ default_funnel?: string;
10
11
  default_domain?: string;
12
+ is_anonymous?: boolean;
13
+ skills_installed?: boolean;
14
+ last_update_check?: number;
11
15
  autocomplete_setup?: boolean;
12
16
  }
13
17
 
package/src/index.ts CHANGED
@@ -16,11 +16,6 @@ import * as setupCmd from './commands/setup.js';
16
16
  import * as configCmd from './commands/config.js';
17
17
  import * as domainCmd from './commands/domain.js';
18
18
  import * as funnelCmd from './commands/funnel.js';
19
- import * as imageCmd from './commands/image.js';
20
- import * as storageCmd from './commands/storage.js';
21
- import * as urlCmd from './commands/url.js';
22
- import * as webhookCmd from './commands/webhook.js';
23
- import * as workflowCmd from './commands/workflow.js';
24
19
 
25
20
  async function main() {
26
21
  try {
@@ -81,21 +76,6 @@ async function main() {
81
76
  case 'funnel':
82
77
  await funnelCmd.run(subcommand, restArgs, flags);
83
78
  break;
84
- case 'image':
85
- await imageCmd.run(subcommand, restArgs, flags);
86
- break;
87
- case 'storage':
88
- await storageCmd.run(subcommand, restArgs, flags);
89
- break;
90
- case 'url':
91
- await urlCmd.run(subcommand, restArgs, flags);
92
- break;
93
- case 'webhook':
94
- await webhookCmd.run(subcommand, restArgs, flags);
95
- break;
96
- case 'workflow':
97
- await workflowCmd.run(subcommand, restArgs, flags);
98
- break;
99
79
  default:
100
80
  printHelp();
101
81
  process.exit(0);
@@ -122,11 +102,6 @@ Commands:
122
102
  config Manage CLI defaults like org_id and domain
123
103
  domain Manage domain configurations
124
104
  funnel Manage headless funnels and pages
125
- image Generate AI images
126
- storage Manage static assets
127
- url Shorten URLs and manage links
128
- webhook Manage inbound webhooks
129
- workflow Manage workflow automations
130
105
 
131
106
  Run "myapi <command> --help" for subcommand help.
132
107
 
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: my-api-hq
3
+ description: >
4
+ Core Identity and Billing hub. Manage auth, organizations (get org_id), and billing (checkout/topup).
5
+ ---
6
+
7
+ # MyApiHQ Skill
8
+ Root entry point for the ecosystem. All other skills require an `api_key` and often an `org_id` from here.
9
+
10
+ ## Platform Conventions
11
+
12
+ ### Response Envelope
13
+ Every response across all services is wrapped in:
14
+ ```json
15
+ {
16
+ "success": true,
17
+ "data": { ... },
18
+ "error": null,
19
+ "meta": { "request_id": "...", "latency_ms": 12, "service": "...", "version": "v1" }
20
+ }
21
+ ```
22
+ On error, `success` is `false`, `data` is `null`, and `error` contains a string error code or object. Always check `success` before reading `data`.
23
+
24
+ ### Pagination
25
+ List endpoints accept `?limit=` and `?offset=` and return `total`, `limit`, `offset` in the body.
26
+
27
+ ## Authentication & Key Management
28
+
29
+ You need to go to myapihq.com and register an account. Generate an api key and export it to pass it to the agent (as env).
30
+
31
+ ### Account Login
32
+ ```
33
+ POST /hq/account/login
34
+ { "email": "...", "password": "..." }
35
+ → { "data": { "token": "<JWT>" } }
36
+ ```
37
+ Errors: `401` wrong password.
38
+
39
+ **Refresh Token:**
40
+ ```
41
+ POST /hq/account/refresh
42
+ ```
43
+
44
+ **Google OAuth:**
45
+ - `GET /hq/auth/google/redirect` — Initiates Google OAuth flow.
46
+ - `GET /hq/auth/google/callback` — OAuth callback.
47
+
48
+ ### API Key Management
49
+ **Generate Persistent Key:**
50
+ ```
51
+ POST /hq/account/create/key
52
+ Authorization: Bearer <JWT from login>
53
+ { "name": "MyKey" }
54
+ → { "data": { "api_key": "hq_live_...", "id": "...", "prefix": "..." } }
55
+ ```
56
+ Errors: `401` no auth.
57
+
58
+ Use `Authorization: Bearer <api_key>` for all subsequent requests across the entire ecosystem.
59
+
60
+ **List Keys:** `GET /hq/account/keys`
61
+
62
+ **Revoke Key:** `DELETE /hq/account/delete/key/{id}`
63
+
64
+ ### Cross-Session Storage
65
+ **Always persist credentials immediately.** Write a `.env` file:
66
+ ```
67
+ MYAPI_API_KEY=hq_live_...
68
+ MYAPI_ACCOUNT_ID=...
69
+ ```
70
+ Also save to agent memory if your runtime supports it (e.g., Claude Code memory). On every new session, check for credentials before creating a new account.
71
+
72
+ ## Organization Management
73
+ **You MUST create an org to get an `org_id` for other APIs.**
74
+
75
+ ### Create Org (sync)
76
+ ```
77
+ POST /hq/orgs
78
+ { "name": "Acme Inc" (required), "tagline", "description", "business_sector",
79
+ "logo_url", "favicon_url", "og_image_url",
80
+ "color_palette": { "primary": "#hex", ... },
81
+ "font_family", "imagery_style", "headline", "subheadline", "cta_text",
82
+ "value_propositions": ["..."],
83
+ "social_links": { "twitter": "url", ... },
84
+ "canonical_url", "privacy_policy_url", "cookie_policy_url", "terms_url",
85
+ "gdpr_enabled": false, "default_language": "en", "tracking": {} }
86
+ → { "data": { "id": "<org_id>", ... } }
87
+ ```
88
+ Errors: `400` invalid_json · `422` name_required, invalid_field:color_palette, invalid_field:value_propositions, invalid_field:social_links, invalid_field:tracking · `402` insufficient balance (org creation has a cost on paid plan).
89
+
90
+ ### Async Brand Import
91
+ ```
92
+ POST /hq/org-imports
93
+ { "org_id": "<id>" (required), "domain": "example.com" (required), "auto_accept": false }
94
+ → { "data": { "job_id": "...", "status": "pending" } }
95
+
96
+ GET /hq/org-imports/{job_id}
97
+ → Poll until status = "awaiting_confirm". Returns brand_preview.
98
+
99
+ POST /hq/org-imports/{job_id}/confirm
100
+ { ...optional overrides matching POST /hq/orgs payload... }
101
+ → { "data": { "id": "<org_id>", ... } }
102
+ ```
103
+
104
+ ### Manage Orgs
105
+ - `GET /hq/orgs` — list all orgs.
106
+ - `GET /hq/orgs/{id}` — get org details. Errors: `404` org_not_found.
107
+ - `PATCH /hq/orgs/{id}` — partial update, same fields as create. Errors: `400` invalid_json · `404` org_not_found · `422` invalid_field:*.
108
+ - `DELETE /hq/orgs/{id}` — delete org and cascade. Errors: `404` org_not_found.
109
+
110
+ ## Billing
111
+ - **Setup Payment Method:** You need to do this from the myapihq dashboard directly.
112
+ - **Check Balance:** `GET /hq/billing/balance` → `{ "data": { "balance_cents": 1000, "balance_display": "$10.00", "credits_cents": 500, "credits_display": "$5.00", "has_payment_method": true } }`.
113
+ - **Billing History:** `GET /hq/billing/history`
114
+ - **Top Up:** `POST /hq/billing/topup` — `{ "amount_cents": 1000 }` → `{ "data": { "new_balance_cents": 2000, "new_balance_display": "$20.00" } }`.
115
+
116
+ **On 402 from any service:** check balance and top up here before retrying.
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: my-domain-api
3
+ description: >
4
+ Register new domains, check availability and pricing, import existing domains, and manage edge settings. Use this before creating mailboxes or funnels — both require an owned domain.
5
+ ---
6
+
7
+ # MyDomainAPI Skill
8
+
9
+ ## Quick Start
10
+ 1. `GET /domain/orgs/{org_id}/list?filter=all` — lists domains owned by your account. `filter` can be `all`, `unassigned`, or `org` (default).
11
+ 2. `GET /domain/orgs/{org_id}/check/available/{domain}` — confirm availability and price.
12
+ 3. `POST /domain/orgs/{org_id}/register` with `domain` and optional `years`.
13
+ 4. Proceed to `my-email-api` for mailboxes or `my-funnel-api` for a website.
14
+
15
+ DNS is fully managed by the platform — enabling seamless email deliverability, tracking pixel, and edge delivery integration. Manual DNS record management is not exposed.
16
+
17
+ ## Dependencies & Backlinks
18
+ - **Auth & Billing:** 401/402 → fall back to `my-api-hq`.
19
+ - **Next Steps:** After registration → `my-email-api` for mailboxes or `my-funnel-api` for a website.
20
+
21
+ ## Authentication
22
+ `Authorization: Bearer <api_key>` (from `my-api-hq`).
23
+
24
+ ## Endpoints
25
+
26
+ ### Check Availability
27
+ ```
28
+ GET /domain/orgs/{org_id}/check/available/{domain}
29
+ → { "available": true, "price_cents": 1200 }
30
+ ```
31
+ Errors: `400` INVALID_DOMAIN, TLD_NOT_SUPPORTED.
32
+
33
+ ### Register Domain
34
+ ```
35
+ POST /domain/orgs/{org_id}/register
36
+ { "domain": "example.com", "years": 1 }
37
+ → { "domain": "...", "status": "provisioning", "domain_id": "..." }
38
+ ```
39
+ Errors: `400` invalid request, INVALID_DOMAIN, TLD_NOT_SUPPORTED · `409` DOMAIN_ALREADY_OWNED, DOMAIN_UNAVAILABLE · `402` INSUFFICIENT_BALANCE (includes `required_cents`) or UPGRADE_REQUIRED (free account) · `403` `already_owned` flag is not permitted.
40
+
41
+ ### Import Existing Domain
42
+ ```
43
+ POST /domain/orgs/{org_id}/import
44
+ { "domain": "example.com", "namecheap_api_user": "optional", "namecheap_api_key": "optional" }
45
+ ```
46
+ Sets up DNS and email infrastructure automatically. Optionally updates Namecheap NS if credentials are provided.
47
+ Errors: `402` insufficient balance.
48
+
49
+ To use Namecheap automation: go to **Profile > Tools > Namecheap API Access**, generate an API Key, and whitelist the MyAPI-HQ server IP — otherwise the API calls will be rejected.
50
+
51
+ ### List & Status
52
+ ```
53
+ GET /domain/orgs/{org_id}/list
54
+ GET /domain/orgs/{org_id}/{domain}/status
55
+ ```
56
+ Errors (status): `404` DOMAIN_NOT_FOUND.
57
+
58
+ ### Assign / Unassign Domain
59
+ ```
60
+ POST /domain/orgs/{org_id}/{domain}/assign
61
+ { "org_id": "<target_org_id>" } // Pass null to unassign
62
+ ```
63
+ Associates a domain already in the account with a specific organization, or removes it from its current organization if `org_id` is null.
64
+ Errors: `404` DOMAIN_NOT_FOUND · `422` ORG_NOT_FOUND.
65
+
66
+ ### Edge Settings
67
+
68
+ **Update:**
69
+ ```
70
+ POST /domain/orgs/{org_id}/{domain}/settings
71
+ {
72
+ "security_level": "essentially_off", // essentially_off | medium | high | under_attack
73
+ "browser_check": "off", // on | off
74
+ "purge_cache": true
75
+ }
76
+ ```
77
+ *To allow AI training bots and crawlers: set `security_level: "essentially_off"` and `browser_check: "off"`.*
78
+
79
+ **Get:**
80
+ ```
81
+ GET /domain/orgs/{org_id}/{domain}/settings
82
+ → { "domain": "...", "security_level": "...", "browser_check": "...", "ai_bots_protection": "disabled", "is_robots_txt_managed": false }
83
+ ```
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: my-funnel-api:funnel
3
+ description: >
4
+ A lean CRUD and CDN Publishing API. Manage funnel configurations, push raw HTML pages, and deploy static assets to the edge KV.
5
+ ---
6
+
7
+ # MyFunnelAPI Skill
8
+
9
+ ## 1. Funnel Management (Authenticated)
10
+ These endpoints manage the database records and structural configuration of funnels.
11
+
12
+ - `GET /funnel/orgs/{org_id}/funnels`
13
+ Lists all funnels for the specified organization.
14
+ - `POST /funnel/orgs/{org_id}/funnels/create-raw`
15
+ Creates a new funnel entry. Expects basic configuration metadata (name, domain, etc.). Body: `{ "domain": "example.com" }`
16
+ - `GET /funnel/orgs/{org_id}/funnels/{id}`
17
+ Retrieves the metadata and configuration details of a specific funnel.
18
+ - `DELETE /funnel/orgs/{org_id}/funnels/{id}`
19
+ Deletes a funnel from the database and automatically purges all of its preview and published pages from the edge KV cache.
20
+
21
+ ## 2. Publishing & Edge Deployment (Authenticated)
22
+ These endpoints interact with the edge KV cache to push HTML/JS content to the edge domains. As soon as you push a page, it is live.
23
+
24
+ - `POST /funnel/orgs/{org_id}/funnels/{id}/push-page`
25
+ Deploys raw HTML to a specific slug on the live funnel (e.g., pushing custom HTML to /contact). Body: `{"slug": "/route", "html": "..."}`.
26
+ - `POST /funnel/orgs/{org_id}/funnels/{id}/verify`
27
+ Pre-publish verification. Validates syntax and structure of raw HTML or an existing page slug.
28
+
29
+ ## 3. Public Proxies (Unauthenticated)
30
+ These endpoints are called directly by the end-users' browsers (via the deployed static HTML). They do not require API keys. They are stateless and act as routing proxies to the Webhook API.
31
+
32
+ - `POST /funnel/funnels/{id}/submit/{slug...}`
33
+ The endpoint for HTML form submissions. Validates the JSON payload, returns a 200 OK to the browser, and asynchronously POSTs the data to the organization's matching webhook (or fallback webhook).
34
+ - `POST /funnel/funnels/{id}/event`
35
+ The endpoint for analytics and tracking scripts. Proxies click events, pageviews, and pixel tracking data to the configured webhook endpoints. Includes built-in rate limiting (max 60 req/min per funnel).