@myapihq/cli 1.0.29 → 1.0.39

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,8 @@
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';
5
+ import { formatDate } from '../utils.js';
4
6
 
5
7
  export async function balance(flags: Record<string, string | boolean>) {
6
8
  if (flags.help) {
@@ -9,12 +11,11 @@ export async function balance(flags: Record<string, string | boolean>) {
9
11
  }
10
12
  const config = requireConfig();
11
13
  const result = await hq.getBalance(config.api_key);
12
-
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)}`;
14
+
15
15
  const pm = result.has_payment_method ? 'yes' : 'no';
16
-
17
- info(`Balance: ${bal} | Credits: ${cred} | Payment method: ${pm}`);
16
+ const accountType = config.is_anonymous ? 'anonymous' : `registered (${config.email ?? ''})`;
17
+ info(`Account: ${accountType}`);
18
+ info(`Balance: ${result.balance_display} | Credits: ${result.credits_display} | Payment method: ${pm}`);
18
19
  }
19
20
 
20
21
  export async function history(flags: Record<string, string | boolean>) {
@@ -24,52 +25,51 @@ export async function history(flags: Record<string, string | boolean>) {
24
25
  }
25
26
  const config = requireConfig();
26
27
  const items = await hq.getBillingHistory(config.api_key);
27
-
28
+
28
29
  if (flags.json) {
29
30
  printJson(items);
30
31
  return;
31
32
  }
32
-
33
- const formattedItems = items.map(item => {
34
- // Attempt to parse the date cleanly (handling Go's "+0000 UTC" suffix)
35
- const cleanDate = item.created_at.replace(' +0000 UTC', 'Z');
36
- const date = new Date(cleanDate);
37
- 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
- return {
45
- Type: item.type || 'unknown',
46
- Amount: amountStr,
47
- Status: item.status ? item.status.charAt(0).toUpperCase() + item.status.slice(1) : '',
48
- Date: dateStr
49
- };
50
- });
51
-
33
+
34
+ if (items.length === 0) {
35
+ info('No transactions yet.');
36
+ return;
37
+ }
38
+
39
+ const formattedItems = items.map(item => ({
40
+ Type: item.type || 'unknown',
41
+ Amount: item.amount_display,
42
+ Status: item.status ? item.status.charAt(0).toUpperCase() + item.status.slice(1) : '',
43
+ Date: formatDate(item.created_at),
44
+ }));
45
+
52
46
  printTable(formattedItems as unknown as Record<string, unknown>[]);
53
47
  }
54
48
 
55
49
  export async function topup(amountStr: string, flags: Record<string, string | boolean>) {
56
50
  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');
51
+ 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
52
  return;
59
53
  }
60
- if (!amountStr) {
61
- error("Missing amount. Usage: myapi billing topup <amount_in_dollars>");
54
+ const amount = Math.round(parseFloat(amountStr));
55
+ if (!amountStr || isNaN(amount) || amount <= 0) {
56
+ error("Amount must be a positive whole number of dollars (e.g. myapi billing topup 10)");
62
57
  return;
63
58
  }
64
- const amount = parseFloat(amountStr);
65
- if (isNaN(amount)) {
66
- error("Invalid amount. Must be a number in dollars (e.g., 10.00)");
67
- return;
59
+
60
+ if (!flags.yes && !flags.y && amount >= 50) {
61
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
62
+ const ans = await new Promise<string>(resolve => rl.question(`› Charge $${amount} to your saved payment method? (y/N) `, resolve));
63
+ rl.close();
64
+ if (ans.trim().toLowerCase() !== 'y' && ans.trim().toLowerCase() !== 'yes') {
65
+ info('Cancelled.');
66
+ return;
67
+ }
68
68
  }
69
-
69
+
70
70
  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)}`);
71
+ const result = await hq.topUp(config.api_key, amount);
72
+ success(`Top up successful! New balance: ${result.new_balance_display}`);
73
73
  }
74
74
 
75
75
  export async function setup(flags: Record<string, string | boolean>) {
@@ -6,12 +6,13 @@ export async function check(domainArg: string, flags: Record<string, string | bo
6
6
  const config = requireConfig();
7
7
  const orgId = (flags.org as string) || config.default_org;
8
8
  const domain = domainArg || (config.default_domain as string);
9
- if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain check <domain> --org <id>\n(Or set defaults via: myapi config set-org <id> / set-domain <domain>)");
10
- const res = await sdkDomain.checkDomain(config.api_key, orgId, domain);
9
+ if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain check <domain> --org <id>\n(Or set defaults via: myapi auth config set-org <id> / set-domain <domain>)");
10
+ const res = await sdkDomain.checkDomain(config.api_key, orgId, domain) as { available: boolean; price_cents: number; price_display?: string; message?: string };
11
11
  if (res.available) {
12
- info(`${domain} Available ✓ Price: $${(res.price_cents / 100).toFixed(2)}/yr`);
12
+ const price = res.price_display || `$${(res.price_cents / 100).toFixed(2)}`;
13
+ info(`${domain} — Available ✓ ${price}/yr`);
13
14
  } else {
14
- info(`Not available ✗`);
15
+ info(`${domain} — ${res.message || 'Not available'} ✗`);
15
16
  }
16
17
  }
17
18
 
@@ -19,7 +20,7 @@ export async function register(domainArg: string, flags: Record<string, string |
19
20
  const config = requireConfig();
20
21
  const orgId = (flags.org as string) || config.default_org;
21
22
  const domain = domainArg || (config.default_domain as string);
22
- if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain register <domain> --org <id> [--years <num>]\n(Or set defaults via: myapi config set-org <id> / set-domain <domain>)");
23
+ if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain register <domain> --org <id> [--years <num>]\n(Or set defaults via: myapi auth config set-org <id> / set-domain <domain>)");
23
24
  const years = parseInt(flags.years as string) || 1;
24
25
  const res = await sdkDomain.registerDomain(config.api_key, orgId, domain, years);
25
26
  success(`Registered ${domain}!\n${JSON.stringify(res, null, 2)}`);
@@ -29,7 +30,7 @@ export async function importDomain(domainArg: string, flags: Record<string, stri
29
30
  const config = requireConfig();
30
31
  const orgId = (flags.org as string) || config.default_org;
31
32
  const domain = domainArg || (config.default_domain as string);
32
- if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain import <domain> --org <id> [--namecheap-user <user> --namecheap-key <key>]\n(Or set defaults via: myapi config set-org <id> / set-domain <domain>)");
33
+ if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain import <domain> --org <id> [--namecheap-user <user> --namecheap-key <key>]\n(Or set defaults via: myapi auth config set-org <id> / set-domain <domain>)");
33
34
  const payload: { domain: string; namecheap_api_user?: string; namecheap_api_key?: string } = { domain };
34
35
  if (flags['namecheap-user']) payload.namecheap_api_user = flags['namecheap-user'] as string;
35
36
  if (flags['namecheap-key']) payload.namecheap_api_key = flags['namecheap-key'] as string;
@@ -40,7 +41,7 @@ export async function importDomain(domainArg: string, flags: Record<string, stri
40
41
  export async function list(flags: Record<string, string | boolean>) {
41
42
  const config = requireConfig();
42
43
  const orgId = (flags.org as string) || config.default_org;
43
- if (!orgId) error("Missing required arguments.\nUsage: myapi domain list --org <id> [--filter=all|unassigned|org]\n(Or set a default org via: myapi config set-org <id>)");
44
+ if (!orgId) error("Missing required arguments.\nUsage: myapi domain list --org <id> [--filter=all|unassigned|org]\n(Or set a default org via: myapi auth config set-org <id>)");
44
45
  const filter = flags.filter as 'all' | 'unassigned' | 'org' | undefined;
45
46
  const domains = await sdkDomain.listDomains(config.api_key, orgId, filter);
46
47
  printTable(domains as unknown as Record<string, unknown>[]);
@@ -51,8 +52,8 @@ export async function assign(domainArg: string, flags: Record<string, string | b
51
52
  const orgId = (flags.org as string) || config.default_org;
52
53
  const domain = domainArg || (config.default_domain as string);
53
54
  const targetOrgId = flags.target as string;
54
- if (!orgId || !domain || !targetOrgId) error("Missing required arguments.\nUsage: myapi domain assign <domain> --org <id> --target <target_org_id>\n(Or set defaults via: myapi config set-org <id> / set-domain <domain>)");
55
- const res = await sdkDomain.assignDomain(config.api_key, orgId, domain, targetOrgId);
55
+ if (!orgId || !domain || !targetOrgId) error("Missing required arguments.\nUsage: myapi domain assign <domain> --org <id> --target <target_org_id>\n(Or set defaults via: myapi auth config set-org <id> / set-domain <domain>)");
56
+ await sdkDomain.assignDomain(config.api_key, orgId, domain, targetOrgId);
56
57
  success(`Assigned ${domain} to org ${targetOrgId}`);
57
58
  }
58
59
 
@@ -60,16 +61,30 @@ export async function unassign(domainArg: string, flags: Record<string, string |
60
61
  const config = requireConfig();
61
62
  const orgId = (flags.org as string) || config.default_org;
62
63
  const domain = domainArg || (config.default_domain as string);
63
- if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain unassign <domain> --org <id>\n(Or set defaults via: myapi config set-org <id> / set-domain <domain>)");
64
- const res = await sdkDomain.unassignDomain(config.api_key, orgId, domain);
64
+ if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain unassign <domain> --org <id>\n(Or set defaults via: myapi auth config set-org <id> / set-domain <domain>)");
65
+ await sdkDomain.unassignDomain(config.api_key, orgId, domain);
65
66
  success(`Unassigned ${domain} from org ${orgId}`);
66
67
  }
67
68
 
69
+ export async function status(domainArg: string, flags: Record<string, string | boolean>) {
70
+ const config = requireConfig();
71
+ const orgId = (flags.org as string) || config.default_org;
72
+ const domain = domainArg || (config.default_domain as string);
73
+ if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain status <domain> --org <id>\n(Or set defaults via: myapi auth config set-org <id> / set-domain <domain>)");
74
+ const res = await sdkDomain.getDomainStatus(config.api_key, orgId, domain);
75
+ const display: Record<string, unknown> = {
76
+ domain: res.domain,
77
+ status: res.status,
78
+ };
79
+ if (res.expires_at) display.expires_at = res.expires_at;
80
+ printJson(display);
81
+ }
82
+
68
83
  export async function settings(domainArg: string, flags: Record<string, string | boolean>) {
69
84
  const config = requireConfig();
70
85
  const orgId = (flags.org as string) || config.default_org;
71
86
  const domain = domainArg || (config.default_domain as string);
72
- if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain settings <domain> --org <id>\n(Or set defaults via: myapi config set-org <id> / set-domain <domain>)");
87
+ if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain settings <domain> --org <id>\n(Or set defaults via: myapi auth config set-org <id> / set-domain <domain>)");
73
88
  const res = await sdkDomain.getDomainSettings(config.api_key, orgId, domain);
74
89
  printJson(res);
75
90
  }
@@ -78,22 +93,22 @@ export async function updateSettings(domainArg: string, flags: Record<string, st
78
93
  const config = requireConfig();
79
94
  const orgId = (flags.org as string) || config.default_org;
80
95
  const domain = domainArg || (config.default_domain as string);
81
- if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain update-settings <domain> --org <id> [--security=...] [--browser-check=...] [--purge-cache]\n(Or set defaults via: myapi config set-org <id> / set-domain <domain>)");
96
+ if (!orgId || !domain) error("Missing required arguments.\nUsage: myapi domain update-settings <domain> --org <id> [--security=...] [--browser-check=...] [--purge-cache]\n(Or set defaults via: myapi auth config set-org <id> / set-domain <domain>)");
82
97
  const payload: any = {};
83
98
  if (flags.security) payload.security_level = flags.security as string;
84
99
  if (flags['browser-check']) payload.browser_check = flags['browser-check'] as string;
85
100
  if (flags['purge-cache']) payload.purge_cache = true;
86
-
101
+
87
102
  const res = await sdkDomain.updateDomainSettings(config.api_key, orgId, domain, payload);
88
103
  success(`Updated settings for ${domain}!\n${JSON.stringify(res, null, 2)}`);
89
104
  }
90
105
 
91
106
  export async function run(subcommand: string | undefined, args: string[], flags: Record<string, string | boolean>) {
92
107
  if (!subcommand || (flags.help && !subcommand)) {
93
- info('Usage: myapi domain <subcommand>\n\nSubcommands:\n list List domains\n check Check domain availability\n register Register a domain\n import Import an existing domain\n assign Assign domain to org\n unassign Unassign domain from its current org\n settings Get domain settings\n update-settings Update domain settings\n\nNote: All domain commands require the --org <id> flag.');
108
+ info('Usage: myapi domain <subcommand>\n\nSubcommands:\n list List domains\n check Check domain availability\n register Register a domain\n import Import an existing domain\n assign Assign domain to org\n unassign Unassign domain from its current org\n status Get domain status\n settings Get domain settings\n update-settings Update domain settings\n\nNote: All domain commands require the --org <id> flag.');
94
109
  return;
95
110
  }
96
-
111
+
97
112
  if (flags.help) {
98
113
  if (subcommand === 'list') info('Usage: myapi domain list --org <id> [--filter=all|unassigned|org]\n\nLists all registered domains in your account.\n --filter=all Lists all domains owned by the account.\n --filter=unassigned Lists domains not assigned to an org.\n --filter=org (Default) Lists domains assigned to the current org.');
99
114
  else if (subcommand === 'check') info('Usage: myapi domain check <domain> --org <id>\n\nChecks if a domain name is available for registration.');
@@ -101,6 +116,7 @@ export async function run(subcommand: string | undefined, args: string[], flags:
101
116
  else if (subcommand === 'import') info('Usage: myapi domain import <domain> --org <id> --namecheap-user <user> --namecheap-key <key>\n\nImports an existing domain from Namecheap.');
102
117
  else if (subcommand === 'assign') info('Usage: myapi domain assign <domain> --org <id> --target <target_org_id>\n\nAssigns a domain to a different organization.');
103
118
  else if (subcommand === 'unassign') info('Usage: myapi domain unassign <domain> --org <id>\n\nUnassigns a domain from its current organization.');
119
+ else if (subcommand === 'status') info('Usage: myapi domain status <domain> --org <id>\n\nGets the registration status of a domain.');
104
120
  else if (subcommand === 'settings') info('Usage: myapi domain settings <domain> --org <id>\n\nGets the DNS settings and configuration for a domain.');
105
121
  else if (subcommand === 'update-settings') info('Usage: myapi domain update-settings <domain> --org <id> [--security=essentially_off|medium|high|under_attack] [--browser-check=on|off] [--purge-cache]\n\nUpdates edge settings for a domain.');
106
122
  return;
@@ -112,6 +128,7 @@ export async function run(subcommand: string | undefined, args: string[], flags:
112
128
  else if (subcommand === 'import') await importDomain(args[0], flags);
113
129
  else if (subcommand === 'assign') await assign(args[0], flags);
114
130
  else if (subcommand === 'unassign') await unassign(args[0], flags);
131
+ else if (subcommand === 'status') await status(args[0], flags);
115
132
  else if (subcommand === 'settings') await settings(args[0], flags);
116
133
  else if (subcommand === 'update-settings') await updateSettings(args[0], flags);
117
134
  else error(`Unknown subcommand: ${subcommand}. Run "myapi domain --help" for a list of valid subcommands.`);
@@ -1,7 +1,7 @@
1
1
  import { hq } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../config.js';
3
3
  import { success, error, printTable, printJson, info } from '../output.js';
4
- import { sleep } from '../utils.js';
4
+ import { sleep, formatDate } from '../utils.js';
5
5
 
6
6
  export async function create(flags: Record<string, string | boolean>) {
7
7
  if (flags.help) {
@@ -18,7 +18,7 @@ export async function create(flags: Record<string, string | boolean>) {
18
18
  if (flags.description) payload.description = flags.description as string;
19
19
  if (flags['business-sector']) payload.business_sector = flags['business-sector'] as string;
20
20
  if (flags['logo-url']) payload.logo_url = flags['logo-url'] as string;
21
-
21
+
22
22
  const org = await hq.createOrg(config.api_key, payload);
23
23
  success(`Org created! ID: ${org.id}, Name: ${org.name}`);
24
24
  if (org.preview_subdomain) {
@@ -28,12 +28,17 @@ export async function create(flags: Record<string, string | boolean>) {
28
28
 
29
29
  export async function list(flags: Record<string, string | boolean>) {
30
30
  if (flags.help) {
31
- info('Usage: myapi org list\n\nLists all organizations in your account as JSON.');
31
+ info('Usage: myapi org list\n\nLists all organizations in your account.');
32
32
  return;
33
33
  }
34
34
  const config = requireConfig();
35
35
  const orgs = await hq.listOrgs(config.api_key);
36
- printJson(orgs);
36
+ const rows = orgs.map((o: any) => ({
37
+ id: o.id,
38
+ name: o.name,
39
+ created_at: o.created_at ? formatDate(o.created_at) : '',
40
+ }));
41
+ printTable(rows as unknown as Record<string, unknown>[]);
37
42
  }
38
43
 
39
44
  export async function get(id: string, flags: Record<string, string | boolean>) {
@@ -75,19 +80,19 @@ export async function importOrg(args: string[], flags: Record<string, string | b
75
80
  const config = requireConfig();
76
81
  const domain = args[0] || (config.default_domain as string);
77
82
  const orgId = (flags.org as string) || config.default_org;
78
-
83
+
79
84
  if (!domain || !orgId) {
80
85
  error("Missing required arguments.\nUsage: myapi org import <domain> --org <id>\n(Or set defaults via: myapi config set-org <id> / set-domain <domain>)");
81
86
  return;
82
87
  }
83
-
88
+
84
89
  const result = await hq.importOrg(config.api_key, orgId, domain);
85
90
  const importId = result.job_id;
86
-
91
+
87
92
  process.stdout.write("Importing ");
88
93
  const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
89
94
  let i = 0;
90
-
95
+
91
96
  while (true) {
92
97
  const status = await hq.getOrgImportStatus(config.api_key, importId);
93
98
  if (status.status === 'awaiting_confirm') {
@@ -101,7 +106,7 @@ export async function importOrg(args: string[], flags: Record<string, string | b
101
106
  process.stdout.write(`\rImporting ${chars[i++ % chars.length]}`);
102
107
  await sleep(3000);
103
108
  }
104
-
109
+
105
110
  const org = await hq.confirmOrgImport(config.api_key, importId);
106
111
  success(`Import complete! Org ID: ${org.id}`);
107
112
  }
@@ -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,
@@ -1,4 +1,5 @@
1
1
  import { execSync } from 'child_process';
2
+ import { loadConfig, saveConfig } from '../config.js';
2
3
  import { info, success } from '../output.js';
3
4
  import { installSkills } from './setup.js';
4
5
 
@@ -16,7 +17,10 @@ export async function checkForUpdate(currentVersion: string): Promise<void> {
16
17
  if (latest && isNewer(latest, currentVersion)) {
17
18
  info(`\n› New version available (${currentVersion} → ${latest}) — installing…`);
18
19
  execSync('npm install -g @myapihq/cli', { stdio: 'pipe' });
19
- await installSkills();
20
+ const config = loadConfig();
21
+ if (config?.skills_installed) {
22
+ await installSkills();
23
+ }
20
24
  success(`› MyAPI CLI updated to ${latest}. Re-run your command.\n`);
21
25
  process.exit(0);
22
26
  }
package/src/config.ts CHANGED
@@ -57,7 +57,7 @@ export function loadFullConfig(): FullConfig | null {
57
57
 
58
58
  // loadConfig returns the active account merged with globals — unchanged interface for all callers.
59
59
  export function loadConfig(): Config | null {
60
- const envKey = process.env.MYAPI_KEY;
60
+ const envKey = process.env.MYAPI_API_KEY || process.env.MYAPI_KEY;
61
61
  const full = loadFullConfig();
62
62
 
63
63
  if (!full && envKey) return { api_key: envKey, account_id: '', pin: '' };
package/src/utils.ts CHANGED
@@ -28,3 +28,14 @@ export function parseArgs(argv: string[]) {
28
28
  export function sleep(ms: number) {
29
29
  return new Promise(resolve => setTimeout(resolve, ms));
30
30
  }
31
+
32
+ /**
33
+ * Formats an ISO/Go timestamp string to "YYYY-MM-DD HH:mm" (UTC).
34
+ * Strips Go's " +0000 UTC" suffix before parsing.
35
+ */
36
+ export function formatDate(str: string): string {
37
+ const clean = str.replace(' +0000 UTC', 'Z');
38
+ const date = new Date(clean);
39
+ if (isNaN(date.getTime())) return str;
40
+ return date.toISOString().replace('T', ' ').slice(0, 16);
41
+ }
@@ -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
- ```