@myapihq/cli 1.0.61 → 1.0.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/commands/auth.js +13 -5
  2. package/dist/commands/config.d.ts +4 -4
  3. package/dist/commands/config.js +13 -13
  4. package/dist/commands/domain.d.ts +0 -1
  5. package/dist/commands/domain.js +10 -24
  6. package/dist/commands/funnel.js +7 -3
  7. package/dist/commands/keys.js +2 -2
  8. package/dist/commands/org.js +8 -4
  9. package/dist/commands/update.js +1 -1
  10. package/dist/index.js +6 -6
  11. package/dist/skills/my-api-hq/README.md +37 -0
  12. package/dist/skills/my-api-hq/SKILL.md +116 -0
  13. package/dist/skills/my-api-hq/claude/.claude-plugin/plugin.json +6 -0
  14. package/dist/skills/my-api-hq/make/.gitkeep +0 -0
  15. package/dist/skills/my-api-hq/n8n/.gitkeep +0 -0
  16. package/dist/skills/my-api-hq/openapi/.gitkeep +0 -0
  17. package/dist/skills/my-domain-api/README.md +37 -0
  18. package/dist/skills/my-domain-api/SKILL.md +83 -0
  19. package/dist/skills/my-domain-api/claude/.claude-plugin/plugin.json +6 -0
  20. package/dist/skills/my-domain-api/make/.gitkeep +0 -0
  21. package/dist/skills/my-domain-api/n8n/.gitkeep +0 -0
  22. package/dist/skills/my-domain-api/openapi/.gitkeep +0 -0
  23. package/dist/skills/my-funnel-api/README.md +39 -0
  24. package/dist/skills/my-funnel-api/SKILL.md +35 -0
  25. package/dist/skills/my-funnel-api/claude/.claude-plugin/plugin.json +6 -0
  26. package/dist/skills/my-funnel-api/make/.gitkeep +0 -0
  27. package/dist/skills/my-funnel-api/n8n/.gitkeep +0 -0
  28. package/dist/skills/my-funnel-api/openapi/.gitkeep +0 -0
  29. package/package.json +4 -1
  30. package/scripts/copy-skills.js +0 -13
  31. package/src/commands/auth.ts +0 -190
  32. package/src/commands/billing.ts +0 -92
  33. package/src/commands/config.ts +0 -71
  34. package/src/commands/domain.ts +0 -146
  35. package/src/commands/email.ts +0 -185
  36. package/src/commands/funnel.ts +0 -122
  37. package/src/commands/image.ts +0 -85
  38. package/src/commands/keys.ts +0 -68
  39. package/src/commands/org.ts +0 -134
  40. package/src/commands/pixel.ts +0 -62
  41. package/src/commands/setup.ts +0 -335
  42. package/src/commands/storage.ts +0 -56
  43. package/src/commands/update.ts +0 -89
  44. package/src/commands/url.ts +0 -28
  45. package/src/commands/webhook.ts +0 -66
  46. package/src/commands/workflow.ts +0 -102
  47. package/src/config.ts +0 -123
  48. package/src/index.ts +0 -200
  49. package/src/output.ts +0 -49
  50. package/src/utils.ts +0 -45
  51. package/thank-you.html +0 -56
  52. package/tsconfig.json +0 -15
@@ -1,134 +0,0 @@
1
- import * as readline from 'readline';
2
- import { hq, funnel as sdkFunnel } from '@myapihq/sdk';
3
- import { requireConfig, saveConfig } from '../config.js';
4
- import { success, error, printTable, printJson, info } from '../output.js';
5
- import { sleep, formatDate } from '../utils.js';
6
-
7
- export async function create(flags: Record<string, string | boolean>) {
8
- if (flags.help) {
9
- info('Usage: myapi org create --name="My Org" [options]\n\nOptions:\n --name Organization name (required)\n --tagline Short tagline\n --description Detailed description\n --business-sector Sector (e.g. Technology)\n --logo-url URL to logo image');
10
- return;
11
- }
12
- if (!flags.name) {
13
- error("Missing --name flag. Run 'myapi org create --help' for details.");
14
- return;
15
- }
16
- const config = requireConfig();
17
- const payload: any = { name: flags.name as string };
18
- if (flags.tagline) payload.tagline = flags.tagline as string;
19
- if (flags.description) payload.description = flags.description as string;
20
- if (flags['business-sector']) payload.business_sector = flags['business-sector'] as string;
21
- if (flags['logo-url']) payload.logo_url = flags['logo-url'] as string;
22
-
23
- const org = await hq.createOrg(config.api_key, payload);
24
- success(`Org created! ID: ${org.id}, Name: ${org.name}`);
25
- if (org.preview_subdomain) {
26
- info(`Preview domain: https://${org.preview_subdomain}.makeautonomous.com`);
27
- }
28
-
29
- const setDefault = flags.yes || await new Promise<boolean>(resolve => {
30
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
31
- rl.question(`› Set as default org and funnel? (Y/n) `, ans => {
32
- rl.close();
33
- resolve(ans.trim().toLowerCase() !== 'n');
34
- });
35
- });
36
-
37
- if (setDefault) {
38
- config.default_org = org.id;
39
- const funnels = await sdkFunnel.listFunnels(config.api_key, org.id);
40
- if (funnels.length > 0) config.default_funnel = funnels[0].id;
41
- saveConfig(config);
42
- success(`Default org${funnels.length > 0 ? ' and funnel' : ''} updated.`);
43
- }
44
- }
45
-
46
- export async function list(flags: Record<string, string | boolean>) {
47
- if (flags.help) {
48
- info('Usage: myapi org list\n\nLists all organizations in your account.');
49
- return;
50
- }
51
- const config = requireConfig();
52
- const orgs = await hq.listOrgs(config.api_key);
53
- const rows = orgs.map((o: any) => ({
54
- id: o.id,
55
- name: o.name,
56
- created_at: o.created_at ? formatDate(o.created_at) : '',
57
- }));
58
- printTable(rows as unknown as Record<string, unknown>[]);
59
- }
60
-
61
- export async function get(id: string, flags: Record<string, string | boolean>) {
62
- if (flags.help) {
63
- info('Usage: myapi org get <id> [--json]\n\nFetches details of a specific organization.\n\nFlags:\n --json Output raw JSON');
64
- return;
65
- }
66
- if (!id) {
67
- error("Missing org id. Usage: myapi org get <id>");
68
- return;
69
- }
70
- const config = requireConfig();
71
- const org = await hq.getOrg(config.api_key, id);
72
- if (flags.json) {
73
- printJson(org);
74
- return;
75
- }
76
- info(`ID: ${org.id}`);
77
- info(`Name: ${org.name ?? '(none)'}`);
78
- if ((org as any).tagline) info(`Tagline: ${(org as any).tagline}`);
79
- if ((org as any).created_at) info(`Created: ${formatDate((org as any).created_at)}`);
80
- if (org.preview_subdomain) info(`Preview: https://${org.preview_subdomain}.makeautonomous.com`);
81
- }
82
-
83
- export async function del(id: string, flags: Record<string, string | boolean>) {
84
- if (flags.help) {
85
- info('Usage: myapi org delete <id>\n\nDeletes an organization.');
86
- return;
87
- }
88
- if (!id) {
89
- error("Missing org id. Usage: myapi org delete <id>");
90
- return;
91
- }
92
- const config = requireConfig();
93
- await hq.deleteOrg(config.api_key, id);
94
- success(`Org ${id} deleted`);
95
- }
96
-
97
- export async function importOrg(args: string[], flags: Record<string, string | boolean>) {
98
- if (flags.help) {
99
- info('Usage: myapi org import <domain> --org <id>\n\nAutomatically extracts brand info from an existing website and updates the organization.\n\nNote: You must create a placeholder organization first using `myapi org create` to get an org_id.');
100
- return;
101
- }
102
- const config = requireConfig();
103
- const domain = args[0] || (config.default_domain as string);
104
- const orgId = (flags.org as string) || config.default_org;
105
-
106
- if (!domain || !orgId) {
107
- error("Missing required arguments.\nUsage: myapi org import <domain> --org <id>\n(Or set defaults via: myapi config set-org <id> / set-domain <domain>)");
108
- return;
109
- }
110
-
111
- const result = await hq.importOrg(config.api_key, orgId, domain);
112
- const importId = result.job_id;
113
-
114
- process.stdout.write("Importing ");
115
- const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
116
- let i = 0;
117
-
118
- while (true) {
119
- const status = await hq.getOrgImportStatus(config.api_key, importId);
120
- if (status.status === 'awaiting_confirm') {
121
- process.stdout.write('\r\x1b[K');
122
- break;
123
- }
124
- if (status.status === 'failed') {
125
- process.stdout.write('\r\x1b[K');
126
- error("Import failed");
127
- }
128
- process.stdout.write(`\rImporting ${chars[i++ % chars.length]}`);
129
- await sleep(3000);
130
- }
131
-
132
- const org = await hq.confirmOrgImport(config.api_key, importId);
133
- success(`Import complete! Org ID: ${org.id}`);
134
- }
@@ -1,62 +0,0 @@
1
- import { pixel as sdkPixel } from '@myapihq/sdk';
2
- import { requireConfig } from '../config.js';
3
- import { success, error, printTable, info, printJson } from '../output.js';
4
-
5
- export async function interactions(flags: Record<string, string | boolean>) {
6
- const config = requireConfig();
7
- const orgId = (flags.org as string) || config.default_org;
8
-
9
- if (!orgId) {
10
- error("Missing required arguments.\nUsage: myapi pixel interactions --org <id> [--website <domain>] [--campaign-id <id>] [--domain <domain>]\n(Or set defaults via: myapi config set-org <id>)");
11
- }
12
-
13
- const params: any = {};
14
- if (flags.website) params.website = flags.website as string;
15
- if (flags['campaign-id']) params.campaign_id = flags['campaign-id'] as string;
16
- if (flags.domain) params.domain = flags.domain as string;
17
- if (flags.from) params.from = flags.from as string;
18
- if (flags.to) params.to = flags.to as string;
19
- if (flags.limit) params.limit = parseInt(flags.limit as string, 10);
20
- if (flags.offset) params.offset = parseInt(flags.offset as string, 10);
21
-
22
- if (!params.website && !params.campaign_id && !params.domain) {
23
- error("You must provide at least one filter: --website, --campaign-id, or --domain");
24
- }
25
-
26
- const res = await sdkPixel.getInteractions(config.api_key, orgId, params);
27
- if (flags.json) {
28
- printJson(res);
29
- } else {
30
- printTable(res.interactions as unknown as Record<string, unknown>[]);
31
- info(`Total Visits: ${res.total_visits} | Total Events: ${res.total_events} | Showing: ${res.limit} | Offset: ${res.offset}`);
32
- }
33
- }
34
-
35
- export async function identity(pixelId: string, flags: Record<string, string | boolean>) {
36
- const config = requireConfig();
37
- const orgId = (flags.org as string) || config.default_org;
38
-
39
- if (!orgId || !pixelId) {
40
- error("Missing required arguments.\nUsage: myapi pixel identity <pixel_id> --org <id>\n(Or set defaults via: myapi config set-org <id>)");
41
- }
42
-
43
- const res = await sdkPixel.getIdentity(config.api_key, orgId, pixelId);
44
- printJson(res);
45
- }
46
-
47
- export async function run(subcommand: string | undefined, args: string[], flags: Record<string, string | boolean>) {
48
- if (!subcommand || (flags.help && !subcommand)) {
49
- info('Usage: myapi pixel <subcommand>\n\nSubcommands:\n interactions Get a unified timeline of visits and events\n identity Resolve the identity graph for a pixel ID\n\nNote: All pixel commands require the --org <id> flag.');
50
- return;
51
- }
52
-
53
- if (flags.help) {
54
- if (subcommand === 'interactions') info('Usage: myapi pixel interactions --org <id> [--website <domain>] [--campaign-id <id>] [--domain <domain>] [--from <iso8601>] [--to <iso8601>] [--limit <num>] [--offset <num>] [--json]\n\nRetrieves a merged timeline of web visits and email events. Requires at least one filter (--website, --campaign-id, or --domain).');
55
- else if (subcommand === 'identity') info('Usage: myapi pixel identity <pixel_id> --org <id>\n\nResolves the full identity graph (emails, IPs, profiles) for a specific pixel tracker ID.');
56
- return;
57
- }
58
-
59
- if (subcommand === 'interactions') await interactions(flags);
60
- else if (subcommand === 'identity') await identity(args[0], flags);
61
- else error(`Unknown subcommand: ${subcommand}. Run "myapi pixel --help" for a list of valid subcommands.`);
62
- }
@@ -1,335 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as os from 'os';
3
- import * as path from 'path';
4
- import * as readline from 'readline';
5
-
6
- import { loadConfig, saveConfig, addAccount, loadFullConfig } 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
-
11
- function ask(rl: readline.Interface, q: string): Promise<string> {
12
- return new Promise(resolve => rl.question(q, resolve));
13
- }
14
-
15
- function yn(answer: string, defaultYes = true): boolean {
16
- const t = answer.trim().toLowerCase();
17
- if (t === '') return defaultYes;
18
- return t === 'y' || t === 'yes';
19
- }
20
-
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;
30
- }
31
-
32
- // ---------------------------------------------------------------------------
33
- // Skills installation
34
- // ---------------------------------------------------------------------------
35
-
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
- // Each bundled file is <skill-name>.md — install as <skill-name>/SKILL.md
58
- const files = fs.readdirSync(BUNDLED_SKILLS_DIR).filter(f => f.endsWith('.md'));
59
- const skills = files.map(f => f.replace(/\.md$/, ''));
60
-
61
- // Write canonical copies: ~/.agents/skills/myapi/<skill-name>/SKILL.md
62
- for (const skill of skills) {
63
- const skillDir = path.join(SKILLS_CANONICAL, skill);
64
- fs.mkdirSync(skillDir, { recursive: true });
65
- fs.copyFileSync(path.join(BUNDLED_SKILLS_DIR, `${skill}.md`), path.join(skillDir, 'SKILL.md'));
66
- }
67
-
68
- // Symlink each skill directory into agent dirs
69
- for (const [agent, dir] of Object.entries(AGENT_DIRS)) {
70
- try {
71
- fs.mkdirSync(dir, { recursive: true });
72
- for (const skill of skills) {
73
- const link = path.join(dir, skill);
74
- const target = path.join(SKILLS_CANONICAL, skill);
75
- try {
76
- try { fs.rmSync(link, { recursive: true, force: true }); } catch { /* ignore */ }
77
- fs.symlinkSync(target, link);
78
- } catch {
79
- // Non-fatal.
80
- }
81
- }
82
- info(` ✓ ${agent}`);
83
- } catch {
84
- // Silently skip agents whose directory can't be created.
85
- }
86
- }
87
- }
88
-
89
- // ---------------------------------------------------------------------------
90
- // Registered flow
91
- // ---------------------------------------------------------------------------
92
-
93
- async function registeredFlow(rl: readline.Interface): Promise<{
94
- api_key: string;
95
- account_id: string;
96
- default_org: string;
97
- default_funnel: string;
98
- email: 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, email };
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
- // 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, 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\nConfigures your account and stores your default org and funnel so you don\'t need to pass --org or --funnel on every command.\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
-
193
- info('› Configuring MyAPI…');
194
-
195
- const existing = loadConfig();
196
-
197
- // Already configured — ask before adding a new account.
198
- if (existing?.api_key && !flags.yes) {
199
- const full = loadFullConfig();
200
- const total = full?.accounts.length ?? 1;
201
- const activeLabel = existing.email ?? existing.account_id;
202
- const activeType = existing.is_anonymous ? 'anonymous' : 'registered';
203
- const othersNote = total > 1 ? ` · ${total - 1} more saved` : '';
204
-
205
- info(`› Connected: ${activeLabel} (${activeType})${othersNote}`);
206
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
207
- const add = await ask(rl, '› Connect a new account? (y/N) ');
208
- if (!yn(add, false)) {
209
- if (!existing.skills_installed) {
210
- const ans = await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
211
- rl.close();
212
- if (yn(ans)) {
213
- await installSkills();
214
- success('› Skills installed.');
215
- saveConfig({ ...existing, skills_installed: true });
216
- }
217
- } else {
218
- rl.close();
219
- }
220
- return;
221
- }
222
- rl.close();
223
- // Fall through to setup flow — new account will be added to the list.
224
- }
225
-
226
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
227
-
228
- let apiKey = '';
229
- let accountId = '';
230
- let defaultOrg = '';
231
- let defaultFunnel = '';
232
- let subdomainUrl = '';
233
- let isAnonymous = false;
234
- let email = '';
235
-
236
- // Determine skills preference from flags before any prompts.
237
- const skillsFromFlag = flags['install-skills'] ? true : flags['no-skills'] ? false : null;
238
-
239
- try {
240
- const useAnon = flags.anonymous || flags.anon;
241
- const createAns = useAnon ? 'n' : await ask(rl, '› Register with email? (Y/n, or N to continue anonymously) ');
242
-
243
- if (yn(createAns)) {
244
- const data = await registeredFlow(rl);
245
- apiKey = data.api_key;
246
- accountId = data.account_id;
247
- defaultOrg = data.default_org;
248
- defaultFunnel = data.default_funnel;
249
- email = data.email;
250
- } else {
251
- info('› Continuing as anonymous — sites ship to *.makeautonomous.com');
252
- const data = await anonymousFlow();
253
- apiKey = data.api_key;
254
- accountId = data.account_id;
255
- defaultOrg = data.default_org;
256
- defaultFunnel = data.default_funnel;
257
- subdomainUrl = data.subdomain_url;
258
- isAnonymous = true;
259
- }
260
-
261
- const wantsSkills = skillsFromFlag !== null ? skillsFromFlag : flags.yes ? true : yn(await ask(rl, '› Install the MyAPI skills pack? (Y/n) '));
262
-
263
- addAccount({
264
- api_key: apiKey,
265
- account_id: accountId,
266
- email: email || undefined,
267
- default_org: defaultOrg,
268
- default_funnel: defaultFunnel,
269
- is_anonymous: isAnonymous,
270
- skills_installed: wantsSkills,
271
- });
272
-
273
- success(`› ✓ saved to ~/.myapi/config.json`);
274
-
275
- if (wantsSkills) {
276
- await installSkills();
277
- }
278
-
279
- // Validate key and ensure org/funnel defaults are correct.
280
- try {
281
- const auth = { Authorization: `Bearer ${apiKey}` };
282
- const meRes = await fetch(`${API_BASE}/hq/account/me`, { headers: auth });
283
- if (!meRes.ok) {
284
- info('› Warning: could not verify API key — check your connection.');
285
- } else {
286
- // Verify default org exists; if not, fetch the most recent one.
287
- let resolvedOrg = defaultOrg;
288
- let resolvedFunnel = defaultFunnel;
289
-
290
- const orgRes = await fetch(`${API_BASE}/hq/orgs`, { headers: auth });
291
- if (orgRes.ok) {
292
- const orgs = (await orgRes.json() as any)?.data ?? [];
293
- if (orgs.length > 0) {
294
- const latest = orgs[orgs.length - 1];
295
- if (!resolvedOrg || !orgs.find((o: any) => o.id === resolvedOrg)) {
296
- resolvedOrg = latest.id;
297
- info(`› Auto-selected org: ${latest.name} (${resolvedOrg})`);
298
- }
299
-
300
- // Verify default funnel exists within the org.
301
- const fRes = await fetch(`${API_BASE}/funnel/orgs/${resolvedOrg}/funnels`, { headers: auth });
302
- if (fRes.ok) {
303
- const funnels = (await fRes.json() as any)?.data ?? [];
304
- if (funnels.length > 0 && (!resolvedFunnel || !funnels.find((f: any) => f.id === resolvedFunnel))) {
305
- resolvedFunnel = funnels[funnels.length - 1].id;
306
- info(`› Auto-selected funnel: ${resolvedFunnel}`);
307
- }
308
- }
309
-
310
- if (resolvedOrg !== defaultOrg || resolvedFunnel !== defaultFunnel) {
311
- const full = loadFullConfig()!;
312
- const idx = full.active;
313
- full.accounts[idx].default_org = resolvedOrg;
314
- full.accounts[idx].default_funnel = resolvedFunnel;
315
- fs.writeFileSync(path.join(os.homedir(), '.myapi', 'config.json'), JSON.stringify(full, null, 2), { mode: 0o600 });
316
- defaultOrg = resolvedOrg;
317
- defaultFunnel = resolvedFunnel;
318
- }
319
- }
320
- }
321
- }
322
- } catch { /* non-fatal */ }
323
-
324
- if (isAnonymous) {
325
- success('› Setup complete. No card, no email, ready to ship.');
326
- if (subdomainUrl) info(`› Your funnel: ${subdomainUrl}`);
327
- info('› Upgrade anytime — myapi auth link');
328
- } else {
329
- success(`› Setup complete. Org ${defaultOrg} ready · $5.00 free credits added.`);
330
- }
331
- } finally {
332
- rl.close();
333
- }
334
- }
335
-
@@ -1,56 +0,0 @@
1
- import { storage as sdkStorage } from '@myapihq/sdk';
2
- import { requireConfig } from '../config.js';
3
- import { success, error, printTable, info, printJson } from '../output.js';
4
-
5
- export async function list(flags: Record<string, string | boolean>) {
6
- const config = requireConfig();
7
- const orgId = (flags.org as string) || config.default_org;
8
- if (!orgId) error("Missing required arguments.\nUsage: myapi storage list --org <id>\n(Or set defaults via: myapi config set-org <id>)");
9
-
10
- const assets = await sdkStorage.listAssets(config.api_key, orgId);
11
- if (flags.json) printJson(assets);
12
- else printTable(assets as unknown as Record<string, unknown>[]);
13
- }
14
-
15
- export async function ingest(url: string, flags: Record<string, string | boolean>) {
16
- const config = requireConfig();
17
- const orgId = (flags.org as string) || config.default_org;
18
-
19
- if (!orgId || !url) {
20
- error("Missing required arguments.\nUsage: myapi storage ingest <url> [--name <name>] --org <id>\n(Or set defaults via: myapi config set-org <id>)");
21
- }
22
-
23
- const res = await sdkStorage.ingestAsset(config.api_key, orgId, url, flags.name as string);
24
- success(`Asset ingested! ID: ${res.asset_id}\nHosted URL: ${res.url}`);
25
- }
26
-
27
- export async function del(id: string, flags: Record<string, string | boolean>) {
28
- const config = requireConfig();
29
- const orgId = (flags.org as string) || config.default_org;
30
-
31
- if (!orgId || !id) {
32
- error("Missing required arguments.\nUsage: myapi storage delete <id> --org <id>\n(Or set defaults via: myapi config set-org <id>)");
33
- }
34
-
35
- await sdkStorage.deleteAsset(config.api_key, orgId, id);
36
- success(`Asset ${id} deleted`);
37
- }
38
-
39
- export async function run(subcommand: string | undefined, args: string[], flags: Record<string, string | boolean>) {
40
- if (!subcommand || (flags.help && !subcommand)) {
41
- info('Usage: myapi storage <subcommand>\n\nSubcommands:\n list List all your uploaded assets\n ingest Ingest a public image URL into your edge storage\n delete Delete a stored asset\n\nNote: All storage commands require the --org <id> flag.');
42
- return;
43
- }
44
-
45
- if (flags.help) {
46
- if (subcommand === 'list') info('Usage: myapi storage list --org <id> [--json]');
47
- else if (subcommand === 'ingest') info('Usage: myapi storage ingest <url> [--name <name>] --org <id>\n\nDownloads a public image (JPEG/PNG) and permanently hosts it on your MyAPI storage. Returns the new URL.');
48
- else if (subcommand === 'delete') info('Usage: myapi storage delete <asset_id> --org <id>');
49
- return;
50
- }
51
-
52
- if (subcommand === 'list') await list(flags);
53
- else if (subcommand === 'ingest') await ingest(args[0], flags);
54
- else if (subcommand === 'delete') await del(args[0], flags);
55
- else error(`Unknown subcommand: ${subcommand}. Run "myapi storage --help" for a list of valid subcommands.`);
56
- }
@@ -1,89 +0,0 @@
1
- import { execSync } from 'child_process';
2
- import { loadConfig } 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
-
8
- // Use the npm binary next to the active node — critical for nvm users.
9
- function npmBin(): string {
10
- return process.execPath.replace(/[/\\]node$/, '/npm');
11
- }
12
-
13
- // checkForUpdate runs silently in the background on every command.
14
- // Auto-installs if a newer version is available.
15
- export async function checkForUpdate(currentVersion: string): Promise<void> {
16
- try {
17
- const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(3000) });
18
- if (!res.ok) return;
19
- const data = await res.json() as { version?: string };
20
- const latest = data.version;
21
-
22
- if (latest && isNewer(latest, currentVersion)) {
23
- info(`\n› New version available (${currentVersion} → ${latest}) — installing…`);
24
- try {
25
- // Pin the exact version so npm can't resolve to a cached older one.
26
- execSync(`"${npmBin()}" install -g @myapihq/cli@${latest}`, { stdio: 'pipe' });
27
- const config = loadConfig();
28
- if (config?.skills_installed) {
29
- await installSkills();
30
- }
31
- info(`› Updated to ${latest} — active after this command completes.\n`);
32
- } catch (installErr: any) {
33
- const msg = installErr?.stderr?.toString?.() || installErr?.message || String(installErr);
34
- info(`› Auto-update failed: ${msg.trim()}`);
35
- info(`› Run manually: npm install -g @myapihq/cli@${latest}`);
36
- }
37
- }
38
- } catch {
39
- // Network errors are silently ignored.
40
- }
41
- }
42
-
43
- // myapi update — explicit update, same logic as auto-update.
44
- export async function update(flags: Record<string, string | boolean> = {}): Promise<void> {
45
- if (flags.help) {
46
- info('Usage: myapi update\n\nUpdates the MyAPI CLI to the latest published version.\nEquivalent to: npm install -g @myapihq/cli@latest');
47
- return;
48
- }
49
- info('› Checking for updates…');
50
- let latest = '';
51
- try {
52
- const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(5000) });
53
- if (!res.ok) throw new Error(`registry returned ${res.status}`);
54
- const data = await res.json() as { version?: string };
55
- latest = data.version ?? '';
56
- if (latest) info(`› Installing @myapihq/cli@${latest}…`);
57
- } catch { /* proceed anyway */ }
58
-
59
- try {
60
- const pkg = latest ? `@myapihq/cli@${latest}` : '@myapihq/cli@latest';
61
- execSync(`"${npmBin()}" install -g ${pkg}`, { stdio: 'inherit' });
62
- } catch {
63
- process.exit(1);
64
- }
65
- info('› Refreshing skills…');
66
- await installSkills();
67
- success('› Up to date.');
68
- }
69
-
70
- // latestVersion fetches the current published version for --version display.
71
- export async function latestVersion(): Promise<string | null> {
72
- try {
73
- const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(3000) });
74
- if (!res.ok) return null;
75
- const data = await res.json() as { version?: string };
76
- return data.version ?? null;
77
- } catch {
78
- return null;
79
- }
80
- }
81
-
82
- export function isNewer(latest: string, current: string): boolean {
83
- const toNum = (v: string) => v.split('.').map(Number);
84
- const [lMaj, lMin, lPat] = toNum(latest);
85
- const [cMaj, cMin, cPat] = toNum(current);
86
- if (lMaj !== cMaj) return lMaj > cMaj;
87
- if (lMin !== cMin) return lMin > cMin;
88
- return lPat > cPat;
89
- }