@myapihq/cli 1.0.28 → 1.0.37

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.
@@ -1,6 +1,7 @@
1
1
  import * as readline from 'readline';
2
2
  import { loadConfig, saveConfig, addAccount, switchAccount, listAccounts } from '../config.js';
3
3
  import { info, success, error } from '../output.js';
4
+ import { installSkills } from './setup.js';
4
5
  const API_BASE = process.env.MYAPI_API_BASE ?? 'https://api.myapihq.com';
5
6
  function ask(rl, q) {
6
7
  return new Promise(resolve => rl.question(q, resolve));
@@ -64,6 +65,8 @@ export async function signup() {
64
65
  info(`› Sent a code to ${email} · paste it below`);
65
66
  const code = (await ask(rl, '› Code? ')).trim();
66
67
  const data = await post('/hq/account/verify-code', { email, code });
68
+ const skillsAns = (await ask(rl, '› Install the MyAPI skills pack? (Y/n) ')).trim();
69
+ const wantsSkills = yn(skillsAns);
67
70
  if (upgradeOk) {
68
71
  // Upgrade: update current account in place.
69
72
  saveConfig({
@@ -74,6 +77,7 @@ export async function signup() {
74
77
  default_org: data.default_org || config.default_org,
75
78
  default_funnel: data.default_funnel || config.default_funnel,
76
79
  is_anonymous: false,
80
+ skills_installed: wantsSkills,
77
81
  });
78
82
  success(`› Welcome! Account upgraded · ${email}`);
79
83
  }
@@ -87,10 +91,13 @@ export async function signup() {
87
91
  default_org: data.default_org,
88
92
  default_funnel: data.default_funnel,
89
93
  is_anonymous: false,
94
+ skills_installed: wantsSkills,
90
95
  });
91
96
  success(`› Signed in · ${email} (account #${idx + 1})`);
92
97
  info(`› Use "myapi auth switch" to toggle between accounts.`);
93
98
  }
99
+ if (wantsSkills)
100
+ await installSkills();
94
101
  }
95
102
  finally {
96
103
  rl.close();
@@ -1,3 +1,4 @@
1
+ import * as readline from 'readline';
1
2
  import { hq } from '@myapihq/sdk';
2
3
  import { requireConfig } from '../config.js';
3
4
  import { success, error, printTable, info, printJson } from '../output.js';
@@ -8,10 +9,8 @@ export async function balance(flags) {
8
9
  }
9
10
  const config = requireConfig();
10
11
  const result = await hq.getBalance(config.api_key);
11
- const bal = result.balance_display || `$${(result.balance_cents / 100).toFixed(2)}`;
12
- const cred = result.credits_display || `$${((result.credits_cents || 0) / 100).toFixed(2)}`;
13
12
  const pm = result.has_payment_method ? 'yes' : 'no';
14
- info(`Balance: ${bal} | Credits: ${cred} | Payment method: ${pm}`);
13
+ info(`Balance: ${result.balance_display} | Credits: ${result.credits_display} | Payment method: ${pm}`);
15
14
  }
16
15
  export async function history(flags) {
17
16
  if (flags.help) {
@@ -25,40 +24,44 @@ export async function history(flags) {
25
24
  return;
26
25
  }
27
26
  const formattedItems = items.map(item => {
28
- // Attempt to parse the date cleanly (handling Go's "+0000 UTC" suffix)
29
27
  const cleanDate = item.created_at.replace(' +0000 UTC', 'Z');
30
28
  const date = new Date(cleanDate);
31
29
  const dateStr = isNaN(date.getTime()) ? item.created_at : date.toLocaleString();
32
- // Format amount
33
- const isNegative = item.amount_cents < 0;
34
- const absAmount = Math.abs(item.amount_cents) / 100;
35
- const amountStr = `${isNegative ? '-' : ''}$${absAmount.toFixed(2)}`;
36
30
  return {
37
31
  Type: item.type || 'unknown',
38
- Amount: amountStr,
32
+ Amount: item.amount_display,
39
33
  Status: item.status ? item.status.charAt(0).toUpperCase() + item.status.slice(1) : '',
40
- Date: dateStr
34
+ Date: dateStr,
41
35
  };
42
36
  });
43
37
  printTable(formattedItems);
44
38
  }
45
39
  export async function topup(amountStr, flags) {
46
40
  if (flags.help) {
47
- info('Usage: myapi billing topup <amount_in_dollars>\n\nAdds funds to your account balance using your saved payment method.\nExample: myapi billing topup 10.00');
41
+ info('Usage: myapi billing topup <amount>\n\nAmount is in whole dollars (e.g. "10" charges $10).\nUse --yes to skip confirmation.\nExample: myapi billing topup 10');
48
42
  return;
49
43
  }
50
44
  if (!amountStr) {
51
- error("Missing amount. Usage: myapi billing topup <amount_in_dollars>");
45
+ error("Missing amount. Usage: myapi billing topup <amount>\nExample: myapi billing topup 10");
52
46
  return;
53
47
  }
54
- const amount = parseFloat(amountStr);
55
- if (isNaN(amount)) {
56
- error("Invalid amount. Must be a number in dollars (e.g., 10.00)");
48
+ const amount = Math.round(parseFloat(amountStr));
49
+ if (isNaN(amount) || amount <= 0) {
50
+ error("Invalid amount. Must be a positive whole number of dollars (e.g. 10)");
57
51
  return;
58
52
  }
53
+ if (!flags.yes && !flags.y && amount >= 50) {
54
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
55
+ const ans = await new Promise(resolve => rl.question(`› Charge $${amount} to your saved payment method? (y/N) `, resolve));
56
+ rl.close();
57
+ if (ans.trim().toLowerCase() !== 'y' && ans.trim().toLowerCase() !== 'yes') {
58
+ info('Cancelled.');
59
+ return;
60
+ }
61
+ }
59
62
  const config = requireConfig();
60
- const result = await hq.topUp(config.api_key, Math.round(amount * 100));
61
- success(`Top up successful! New balance: $${(result.new_balance_cents / 100).toFixed(2)}`);
63
+ const result = await hq.topUp(config.api_key, amount);
64
+ success(`Top up successful! New balance: ${result.new_balance_display}`);
62
65
  }
63
66
  export async function setup(flags) {
64
67
  if (flags.help) {
@@ -1,2 +1,3 @@
1
1
  export declare function installSkills(): Promise<void>;
2
- export declare function setup(): Promise<void>;
2
+ export declare function importKey(apiKey: string, flags: Record<string, string | boolean>): Promise<void>;
3
+ export declare function setup(flags?: Record<string, string | boolean>): Promise<void>;
@@ -96,11 +96,61 @@ async function anonymousFlow() {
96
96
  // ---------------------------------------------------------------------------
97
97
  // Main setup command
98
98
  // ---------------------------------------------------------------------------
99
- export async function setup() {
99
+ // myapi auth import-key <key> — non-interactively import a raw API key.
100
+ export async function importKey(apiKey, flags) {
101
+ if (!apiKey) {
102
+ info('Usage: myapi auth import-key <api_key> [--install-skills] [--no-skills]');
103
+ return;
104
+ }
105
+ const auth = { Authorization: `Bearer ${apiKey}` };
106
+ let accountId = '';
107
+ let email;
108
+ let defaultOrg = '';
109
+ let defaultFunnel = '';
110
+ try {
111
+ const meRes = await fetch(`${API_BASE}/hq/account/me`, { headers: auth });
112
+ const meJson = await meRes.json();
113
+ if (!meRes.ok)
114
+ throw new Error(meJson.error ?? `HTTP ${meRes.status}`);
115
+ const me = meJson.data ?? meJson;
116
+ accountId = me.account_id ?? '';
117
+ email = me.email || undefined;
118
+ }
119
+ catch (e) {
120
+ throw new Error(`Could not verify API key: ${e.message}`);
121
+ }
122
+ // Fetch org/funnel defaults.
123
+ try {
124
+ const orgRes = await fetch(`${API_BASE}/hq/orgs`, { headers: auth });
125
+ if (orgRes.ok) {
126
+ const orgs = ((await orgRes.json())?.data ?? []);
127
+ if (orgs.length > 0) {
128
+ defaultOrg = orgs[orgs.length - 1].id;
129
+ const fRes = await fetch(`${API_BASE}/funnel/orgs/${defaultOrg}/funnels`, { headers: auth });
130
+ if (fRes.ok) {
131
+ const funnels = ((await fRes.json())?.data ?? []);
132
+ if (funnels.length > 0)
133
+ defaultFunnel = funnels[funnels.length - 1].id;
134
+ }
135
+ }
136
+ }
137
+ }
138
+ catch { /* non-fatal */ }
139
+ const wantsSkills = flags['install-skills'] ? true : flags['no-skills'] ? false : true;
140
+ addAccount({ api_key: apiKey, account_id: accountId, pin: '', email, default_org: defaultOrg, default_funnel: defaultFunnel, skills_installed: wantsSkills });
141
+ success(`› ✓ Key imported${email ? ` · ${email}` : ''}`);
142
+ if (wantsSkills)
143
+ await installSkills();
144
+ }
145
+ export async function setup(flags = {}) {
146
+ if (flags.help) {
147
+ info('Usage: myapi auth setup [--anonymous] [--yes] [--install-skills|--no-skills]\n\nFlags:\n --anonymous Skip registration, create anonymous account\n --yes Skip confirmation prompts\n --install-skills Auto-install skills pack\n --no-skills Skip skills installation');
148
+ return;
149
+ }
100
150
  info('› Configuring MyAPI…');
101
151
  const existing = loadConfig();
102
152
  // Already configured — ask before adding a new account.
103
- if (existing?.api_key) {
153
+ if (existing?.api_key && !flags.yes) {
104
154
  const full = loadFullConfig();
105
155
  const total = full?.accounts.length ?? 1;
106
156
  const activeLabel = existing.email ?? existing.account_id;
@@ -135,8 +185,11 @@ export async function setup() {
135
185
  let subdomainUrl = '';
136
186
  let isAnonymous = false;
137
187
  let email = '';
188
+ // Determine skills preference from flags before any prompts.
189
+ const skillsFromFlag = flags['install-skills'] ? true : flags['no-skills'] ? false : null;
138
190
  try {
139
- const createAns = await ask(rl, '› Create an account? (Y/n) ');
191
+ const useAnon = flags.anonymous || flags.anon;
192
+ const createAns = useAnon ? 'n' : await ask(rl, '› Create an account? (Y/n) ');
140
193
  if (yn(createAns)) {
141
194
  const data = await registeredFlow(rl);
142
195
  apiKey = data.api_key;
@@ -155,8 +208,8 @@ export async function setup() {
155
208
  subdomainUrl = data.subdomain_url;
156
209
  isAnonymous = true;
157
210
  }
158
- const skillsAns = await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
159
- const wantsSkills = yn(skillsAns);
211
+ const skillsAns = skillsFromFlag !== null ? (skillsFromFlag ? 'y' : 'n') : await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
212
+ const wantsSkills = skillsFromFlag !== null ? skillsFromFlag : yn(skillsAns);
160
213
  addAccount({
161
214
  api_key: apiKey,
162
215
  account_id: accountId,
@@ -1,32 +1,24 @@
1
1
  import { execSync } from 'child_process';
2
- import { loadConfig, saveConfig } from '../config.js';
2
+ import { loadConfig } from '../config.js';
3
3
  import { info, success } from '../output.js';
4
4
  import { installSkills } from './setup.js';
5
5
  const REGISTRY_URL = 'https://registry.npmjs.org/@myapihq/cli/latest';
6
- const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
7
6
  // checkForUpdate runs silently in the background on every command.
8
7
  // Auto-installs if a newer version is available.
9
8
  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
9
  try {
16
10
  const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(3000) });
17
11
  if (!res.ok)
18
12
  return;
19
13
  const data = await res.json();
20
14
  const latest = data.version;
21
- // Re-read config at write time to avoid clobbering changes made during setup.
22
- const current = loadConfig();
23
- if (current) {
24
- saveConfig({ ...current, last_update_check: now });
25
- }
26
15
  if (latest && isNewer(latest, currentVersion)) {
27
16
  info(`\n› New version available (${currentVersion} → ${latest}) — installing…`);
28
17
  execSync('npm install -g @myapihq/cli', { stdio: 'pipe' });
29
- await installSkills();
18
+ const config = loadConfig();
19
+ if (config?.skills_installed) {
20
+ await installSkills();
21
+ }
30
22
  success(`› MyAPI CLI updated to ${latest}. Re-run your command.\n`);
31
23
  process.exit(0);
32
24
  }
package/dist/config.d.ts CHANGED
@@ -10,13 +10,11 @@ export interface AccountEntry {
10
10
  skills_installed?: boolean;
11
11
  }
12
12
  export interface Config extends AccountEntry {
13
- last_update_check?: number;
14
13
  autocomplete_setup?: boolean;
15
14
  }
16
15
  export interface FullConfig {
17
16
  active: number;
18
17
  accounts: AccountEntry[];
19
- last_update_check?: number;
20
18
  autocomplete_setup?: boolean;
21
19
  }
22
20
  export declare const CONFIG_DIR: string;
package/dist/config.js CHANGED
@@ -20,8 +20,8 @@ function isFullConfig(raw) {
20
20
  }
21
21
  // Migrate a flat (legacy) config to the new multi-account shape.
22
22
  function migrate(raw) {
23
- const { last_update_check, autocomplete_setup, ...account } = raw;
24
- return { active: 0, accounts: [account], last_update_check, autocomplete_setup };
23
+ const { autocomplete_setup, ...account } = raw;
24
+ return { active: 0, accounts: [account], autocomplete_setup };
25
25
  }
26
26
  export function loadFullConfig() {
27
27
  const raw = readRaw();
@@ -31,7 +31,7 @@ export function loadFullConfig() {
31
31
  }
32
32
  // loadConfig returns the active account merged with globals — unchanged interface for all callers.
33
33
  export function loadConfig() {
34
- const envKey = process.env.MYAPI_KEY;
34
+ const envKey = process.env.MYAPI_API_KEY || process.env.MYAPI_KEY;
35
35
  const full = loadFullConfig();
36
36
  if (!full && envKey)
37
37
  return { api_key: envKey, account_id: '', pin: '' };
@@ -42,7 +42,6 @@ export function loadConfig() {
42
42
  return null;
43
43
  const config = {
44
44
  ...active,
45
- last_update_check: full.last_update_check,
46
45
  autocomplete_setup: full.autocomplete_setup,
47
46
  };
48
47
  if (envKey)
@@ -52,13 +51,11 @@ export function loadConfig() {
52
51
  export function saveConfig(config) {
53
52
  ensureDir();
54
53
  const full = loadFullConfig() ?? { active: 0, accounts: [] };
55
- const { last_update_check, autocomplete_setup, ...account } = config;
54
+ const { autocomplete_setup, ...account } = config;
56
55
  if (full.accounts.length === 0)
57
56
  full.accounts.push(account);
58
57
  else
59
58
  full.accounts[full.active] = account;
60
- if (last_update_check !== undefined)
61
- full.last_update_check = last_update_check;
62
59
  if (autocomplete_setup !== undefined)
63
60
  full.autocomplete_setup = autocomplete_setup;
64
61
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(full, null, 2), { mode: 0o600 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myapihq/cli",
3
- "version": "1.0.28",
3
+ "version": "1.0.37",
4
4
  "description": "MyAPI command-line interface",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,36 +1,13 @@
1
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, rmSync } from 'fs';
2
+ // Skills are not bundled in the CLI package for now.
3
+ import { existsSync, rmSync, mkdirSync } from 'fs';
5
4
  import { join, dirname } from 'path';
6
5
  import { fileURLToPath } from 'url';
7
6
 
8
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
- const repoRoot = join(__dirname, '..', '..', '..');
10
- const skillsRoot = join(repoRoot, 'skills');
11
8
  const dest = join(__dirname, '..', 'src', 'skills');
12
9
 
13
- if (!existsSync(skillsRoot)) {
14
- console.log('copy-skills: no skills/ directory found, skipping.');
15
- process.exit(0);
16
- }
17
-
18
- // Always start clean so unpublished skills don't linger.
19
10
  if (existsSync(dest)) rmSync(dest, { recursive: true, force: true });
20
11
  mkdirSync(dest, { recursive: true });
21
12
 
22
- let copied = 0;
23
- for (const skillDir of readdirSync(skillsRoot)) {
24
- const src = join(skillsRoot, skillDir, 'SKILL.md');
25
- const pluginJson = join(skillsRoot, skillDir, 'claude', '.claude-plugin', 'plugin.json');
26
- if (!existsSync(src)) continue;
27
- // Only bundle skills marked published: true
28
- if (existsSync(pluginJson)) {
29
- const plugin = JSON.parse(readFileSync(pluginJson, 'utf-8'));
30
- if (!plugin.published) continue;
31
- }
32
- copyFileSync(src, join(dest, `${skillDir}.md`));
33
- copied++;
34
- }
35
-
36
- console.log(`copy-skills: copied ${copied} skill(s) to src/skills/`);
13
+ console.log('copy-skills: no skills bundled.');
@@ -2,6 +2,7 @@ import * as readline from 'readline';
2
2
 
3
3
  import { loadConfig, saveConfig, addAccount, switchAccount, listAccounts } from '../config.js';
4
4
  import { info, success, error } from '../output.js';
5
+ import { installSkills } from './setup.js';
5
6
 
6
7
  const API_BASE = process.env.MYAPI_API_BASE ?? 'https://api.myapihq.com';
7
8
 
@@ -72,6 +73,9 @@ export async function signup() {
72
73
  default_funnel: string;
73
74
  };
74
75
 
76
+ const skillsAns = (await ask(rl, '› Install the MyAPI skills pack? (Y/n) ')).trim();
77
+ const wantsSkills = yn(skillsAns);
78
+
75
79
  if (upgradeOk) {
76
80
  // Upgrade: update current account in place.
77
81
  saveConfig({
@@ -82,6 +86,7 @@ export async function signup() {
82
86
  default_org: data.default_org || config!.default_org,
83
87
  default_funnel: data.default_funnel || config!.default_funnel,
84
88
  is_anonymous: false,
89
+ skills_installed: wantsSkills,
85
90
  });
86
91
  success(`› Welcome! Account upgraded · ${email}`);
87
92
  } else {
@@ -94,10 +99,13 @@ export async function signup() {
94
99
  default_org: data.default_org,
95
100
  default_funnel: data.default_funnel,
96
101
  is_anonymous: false,
102
+ skills_installed: wantsSkills,
97
103
  });
98
104
  success(`› Signed in · ${email} (account #${idx + 1})`);
99
105
  info(`› Use "myapi auth switch" to toggle between accounts.`);
100
106
  }
107
+
108
+ if (wantsSkills) await installSkills();
101
109
  } finally {
102
110
  rl.close();
103
111
  }
@@ -1,3 +1,4 @@
1
+ import * as readline from 'readline';
1
2
  import { hq } from '@myapihq/sdk';
2
3
  import { requireConfig } from '../config.js';
3
4
  import { success, error, printTable, info, printJson } from '../output.js';
@@ -10,11 +11,8 @@ export async function balance(flags: Record<string, string | boolean>) {
10
11
  const config = requireConfig();
11
12
  const result = await hq.getBalance(config.api_key);
12
13
 
13
- const bal = result.balance_display || `$${(result.balance_cents / 100).toFixed(2)}`;
14
- const cred = result.credits_display || `$${((result.credits_cents || 0) / 100).toFixed(2)}`;
15
14
  const pm = result.has_payment_method ? 'yes' : 'no';
16
-
17
- info(`Balance: ${bal} | Credits: ${cred} | Payment method: ${pm}`);
15
+ info(`Balance: ${result.balance_display} | Credits: ${result.credits_display} | Payment method: ${pm}`);
18
16
  }
19
17
 
20
18
  export async function history(flags: Record<string, string | boolean>) {
@@ -31,21 +29,14 @@ export async function history(flags: Record<string, string | boolean>) {
31
29
  }
32
30
 
33
31
  const formattedItems = items.map(item => {
34
- // Attempt to parse the date cleanly (handling Go's "+0000 UTC" suffix)
35
32
  const cleanDate = item.created_at.replace(' +0000 UTC', 'Z');
36
33
  const date = new Date(cleanDate);
37
34
  const dateStr = isNaN(date.getTime()) ? item.created_at : date.toLocaleString();
38
-
39
- // Format amount
40
- const isNegative = item.amount_cents < 0;
41
- const absAmount = Math.abs(item.amount_cents) / 100;
42
- const amountStr = `${isNegative ? '-' : ''}$${absAmount.toFixed(2)}`;
43
-
44
35
  return {
45
36
  Type: item.type || 'unknown',
46
- Amount: amountStr,
37
+ Amount: item.amount_display,
47
38
  Status: item.status ? item.status.charAt(0).toUpperCase() + item.status.slice(1) : '',
48
- Date: dateStr
39
+ Date: dateStr,
49
40
  };
50
41
  });
51
42
 
@@ -54,22 +45,32 @@ export async function history(flags: Record<string, string | boolean>) {
54
45
 
55
46
  export async function topup(amountStr: string, flags: Record<string, string | boolean>) {
56
47
  if (flags.help) {
57
- info('Usage: myapi billing topup <amount_in_dollars>\n\nAdds funds to your account balance using your saved payment method.\nExample: myapi billing topup 10.00');
48
+ info('Usage: myapi billing topup <amount>\n\nAmount is in whole dollars (e.g. "10" charges $10).\nUse --yes to skip confirmation.\nExample: myapi billing topup 10');
58
49
  return;
59
50
  }
60
51
  if (!amountStr) {
61
- error("Missing amount. Usage: myapi billing topup <amount_in_dollars>");
52
+ error("Missing amount. Usage: myapi billing topup <amount>\nExample: myapi billing topup 10");
62
53
  return;
63
54
  }
64
- const amount = parseFloat(amountStr);
65
- if (isNaN(amount)) {
66
- error("Invalid amount. Must be a number in dollars (e.g., 10.00)");
55
+ const amount = Math.round(parseFloat(amountStr));
56
+ if (isNaN(amount) || amount <= 0) {
57
+ error("Invalid amount. Must be a positive whole number of dollars (e.g. 10)");
67
58
  return;
68
59
  }
69
-
60
+
61
+ if (!flags.yes && !flags.y && amount >= 50) {
62
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
63
+ const ans = await new Promise<string>(resolve => rl.question(`› Charge $${amount} to your saved payment method? (y/N) `, resolve));
64
+ rl.close();
65
+ if (ans.trim().toLowerCase() !== 'y' && ans.trim().toLowerCase() !== 'yes') {
66
+ info('Cancelled.');
67
+ return;
68
+ }
69
+ }
70
+
70
71
  const config = requireConfig();
71
- const result = await hq.topUp(config.api_key, Math.round(amount * 100));
72
- success(`Top up successful! New balance: $${(result.new_balance_cents / 100).toFixed(2)}`);
72
+ const result = await hq.topUp(config.api_key, amount);
73
+ success(`Top up successful! New balance: ${result.new_balance_display}`);
73
74
  }
74
75
 
75
76
  export async function setup(flags: Record<string, string | boolean>) {
@@ -138,13 +138,64 @@ async function anonymousFlow(): Promise<{
138
138
  // Main setup command
139
139
  // ---------------------------------------------------------------------------
140
140
 
141
- export async function setup() {
141
+ // myapi auth import-key <key> — non-interactively import a raw API key.
142
+ export async function importKey(apiKey: string, flags: Record<string, string | boolean>) {
143
+ if (!apiKey) {
144
+ info('Usage: myapi auth import-key <api_key> [--install-skills] [--no-skills]');
145
+ return;
146
+ }
147
+ const auth = { Authorization: `Bearer ${apiKey}` };
148
+ let accountId = '';
149
+ let email: string | undefined;
150
+ let defaultOrg = '';
151
+ let defaultFunnel = '';
152
+
153
+ try {
154
+ const meRes = await fetch(`${API_BASE}/hq/account/me`, { headers: auth });
155
+ const meJson = await meRes.json() as any;
156
+ if (!meRes.ok) throw new Error(meJson.error ?? `HTTP ${meRes.status}`);
157
+ const me = meJson.data ?? meJson;
158
+ accountId = me.account_id ?? '';
159
+ email = me.email || undefined;
160
+ } catch (e: any) {
161
+ throw new Error(`Could not verify API key: ${e.message}`);
162
+ }
163
+
164
+ // Fetch org/funnel defaults.
165
+ try {
166
+ const orgRes = await fetch(`${API_BASE}/hq/orgs`, { headers: auth });
167
+ if (orgRes.ok) {
168
+ const orgs = ((await orgRes.json() as any)?.data ?? []);
169
+ if (orgs.length > 0) {
170
+ defaultOrg = orgs[orgs.length - 1].id;
171
+ const fRes = await fetch(`${API_BASE}/funnel/orgs/${defaultOrg}/funnels`, { headers: auth });
172
+ if (fRes.ok) {
173
+ const funnels = ((await fRes.json() as any)?.data ?? []);
174
+ if (funnels.length > 0) defaultFunnel = funnels[funnels.length - 1].id;
175
+ }
176
+ }
177
+ }
178
+ } catch { /* non-fatal */ }
179
+
180
+ const wantsSkills = flags['install-skills'] ? true : flags['no-skills'] ? false : true;
181
+
182
+ addAccount({ api_key: apiKey, account_id: accountId, pin: '', email, default_org: defaultOrg, default_funnel: defaultFunnel, skills_installed: wantsSkills });
183
+ success(`› ✓ Key imported${email ? ` · ${email}` : ''}`);
184
+ if (wantsSkills) await installSkills();
185
+ }
186
+
187
+ export async function setup(flags: Record<string, string | boolean> = {}) {
188
+ if (flags.help) {
189
+ info('Usage: myapi auth setup [--anonymous] [--yes] [--install-skills|--no-skills]\n\nFlags:\n --anonymous Skip registration, create anonymous account\n --yes Skip confirmation prompts\n --install-skills Auto-install skills pack\n --no-skills Skip skills installation');
190
+ return;
191
+ }
192
+
142
193
  info('› Configuring MyAPI…');
143
194
 
144
195
  const existing = loadConfig();
145
196
 
146
197
  // Already configured — ask before adding a new account.
147
- if (existing?.api_key) {
198
+ if (existing?.api_key && !flags.yes) {
148
199
  const full = loadFullConfig();
149
200
  const total = full?.accounts.length ?? 1;
150
201
  const activeLabel = existing.email ?? existing.account_id;
@@ -182,8 +233,12 @@ export async function setup() {
182
233
  let isAnonymous = false;
183
234
  let email = '';
184
235
 
236
+ // Determine skills preference from flags before any prompts.
237
+ const skillsFromFlag = flags['install-skills'] ? true : flags['no-skills'] ? false : null;
238
+
185
239
  try {
186
- const createAns = await ask(rl, '› Create an account? (Y/n) ');
240
+ const useAnon = flags.anonymous || flags.anon;
241
+ const createAns = useAnon ? 'n' : await ask(rl, '› Create an account? (Y/n) ');
187
242
 
188
243
  if (yn(createAns)) {
189
244
  const data = await registeredFlow(rl);
@@ -203,8 +258,8 @@ export async function setup() {
203
258
  isAnonymous = true;
204
259
  }
205
260
 
206
- const skillsAns = await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
207
- const wantsSkills = yn(skillsAns);
261
+ const skillsAns = skillsFromFlag !== null ? (skillsFromFlag ? 'y' : 'n') : await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
262
+ const wantsSkills = skillsFromFlag !== null ? skillsFromFlag : yn(skillsAns);
208
263
 
209
264
  addAccount({
210
265
  api_key: apiKey,
@@ -4,34 +4,23 @@ import { info, success } from '../output.js';
4
4
  import { installSkills } from './setup.js';
5
5
 
6
6
  const REGISTRY_URL = 'https://registry.npmjs.org/@myapihq/cli/latest';
7
- const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
8
7
 
9
8
  // checkForUpdate runs silently in the background on every command.
10
9
  // Auto-installs if a newer version is available.
11
10
  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
11
  try {
20
12
  const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(3000) });
21
13
  if (!res.ok) return;
22
14
  const data = await res.json() as { version?: string };
23
15
  const latest = data.version;
24
16
 
25
- // Re-read config at write time to avoid clobbering changes made during setup.
26
- const current = loadConfig();
27
- if (current) {
28
- saveConfig({ ...current, last_update_check: now });
29
- }
30
-
31
17
  if (latest && isNewer(latest, currentVersion)) {
32
18
  info(`\n› New version available (${currentVersion} → ${latest}) — installing…`);
33
19
  execSync('npm install -g @myapihq/cli', { stdio: 'pipe' });
34
- await installSkills();
20
+ const config = loadConfig();
21
+ if (config?.skills_installed) {
22
+ await installSkills();
23
+ }
35
24
  success(`› MyAPI CLI updated to ${latest}. Re-run your command.\n`);
36
25
  process.exit(0);
37
26
  }
package/src/config.ts CHANGED
@@ -16,14 +16,12 @@ export interface AccountEntry {
16
16
 
17
17
  // Flat interface used by all command files — represents the active account.
18
18
  export interface Config extends AccountEntry {
19
- last_update_check?: number;
20
19
  autocomplete_setup?: boolean;
21
20
  }
22
21
 
23
22
  export interface FullConfig {
24
23
  active: number;
25
24
  accounts: AccountEntry[];
26
- last_update_check?: number;
27
25
  autocomplete_setup?: boolean;
28
26
  }
29
27
 
@@ -47,8 +45,8 @@ function isFullConfig(raw: any): raw is FullConfig {
47
45
 
48
46
  // Migrate a flat (legacy) config to the new multi-account shape.
49
47
  function migrate(raw: any): FullConfig {
50
- const { last_update_check, autocomplete_setup, ...account } = raw;
51
- return { active: 0, accounts: [account as AccountEntry], last_update_check, autocomplete_setup };
48
+ const { autocomplete_setup, ...account } = raw;
49
+ return { active: 0, accounts: [account as AccountEntry], autocomplete_setup };
52
50
  }
53
51
 
54
52
  export function loadFullConfig(): FullConfig | null {
@@ -59,7 +57,7 @@ export function loadFullConfig(): FullConfig | null {
59
57
 
60
58
  // loadConfig returns the active account merged with globals — unchanged interface for all callers.
61
59
  export function loadConfig(): Config | null {
62
- const envKey = process.env.MYAPI_KEY;
60
+ const envKey = process.env.MYAPI_API_KEY || process.env.MYAPI_KEY;
63
61
  const full = loadFullConfig();
64
62
 
65
63
  if (!full && envKey) return { api_key: envKey, account_id: '', pin: '' };
@@ -70,7 +68,6 @@ export function loadConfig(): Config | null {
70
68
 
71
69
  const config: Config = {
72
70
  ...active,
73
- last_update_check: full.last_update_check,
74
71
  autocomplete_setup: full.autocomplete_setup,
75
72
  };
76
73
  if (envKey) config.api_key = envKey;
@@ -80,10 +77,9 @@ export function loadConfig(): Config | null {
80
77
  export function saveConfig(config: Config): void {
81
78
  ensureDir();
82
79
  const full = loadFullConfig() ?? { active: 0, accounts: [] };
83
- const { last_update_check, autocomplete_setup, ...account } = config;
80
+ const { autocomplete_setup, ...account } = config;
84
81
  if (full.accounts.length === 0) full.accounts.push(account as AccountEntry);
85
82
  else full.accounts[full.active] = account as AccountEntry;
86
- if (last_update_check !== undefined) full.last_update_check = last_update_check;
87
83
  if (autocomplete_setup !== undefined) full.autocomplete_setup = autocomplete_setup;
88
84
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(full, null, 2), { mode: 0o600 });
89
85
  }
@@ -1,116 +0,0 @@
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.
@@ -1,83 +0,0 @@
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
- ```
@@ -1,35 +0,0 @@
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`
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).
@@ -1,116 +0,0 @@
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.
@@ -1,83 +0,0 @@
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
- ```
@@ -1,35 +0,0 @@
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`
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).