@myapihq/cli 1.0.84 → 1.1.0-wip.1

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 (77) hide show
  1. package/dist/commands/auth.d.ts +8 -3
  2. package/dist/commands/auth.js +84 -60
  3. package/dist/commands/billing.d.ts +8 -4
  4. package/dist/commands/billing.js +46 -27
  5. package/dist/commands/config.d.ts +8 -5
  6. package/dist/commands/config.js +52 -27
  7. package/dist/commands/domain.d.ts +12 -9
  8. package/dist/commands/domain.js +124 -86
  9. package/dist/commands/email/campaign.d.ts +2 -0
  10. package/dist/commands/email/campaign.js +152 -0
  11. package/dist/commands/email/index.d.ts +4 -0
  12. package/dist/commands/email/index.js +98 -0
  13. package/dist/commands/email/mailbox.d.ts +2 -0
  14. package/dist/commands/email/mailbox.js +88 -0
  15. package/dist/commands/email/message.d.ts +2 -0
  16. package/dist/commands/email/message.js +115 -0
  17. package/dist/commands/email/template.d.ts +2 -0
  18. package/dist/commands/email/template.js +106 -0
  19. package/dist/commands/email/warmup.d.ts +2 -0
  20. package/dist/commands/email/warmup.js +43 -0
  21. package/dist/commands/email.d.ts +4 -12
  22. package/dist/commands/email.js +528 -146
  23. package/dist/commands/funnel.d.ts +10 -7
  24. package/dist/commands/funnel.js +79 -55
  25. package/dist/commands/image.js +25 -15
  26. package/dist/commands/keys.d.ts +8 -3
  27. package/dist/commands/keys.js +71 -35
  28. package/dist/commands/org.d.ts +9 -5
  29. package/dist/commands/org.js +100 -64
  30. package/dist/commands/pixel.js +23 -11
  31. package/dist/commands/setup.d.ts +3 -2
  32. package/dist/commands/setup.js +160 -165
  33. package/dist/commands/storage.js +25 -15
  34. package/dist/commands/update.d.ts +2 -1
  35. package/dist/commands/update.js +5 -0
  36. package/dist/commands/url.js +19 -7
  37. package/dist/commands/webhook.d.ts +8 -5
  38. package/dist/commands/webhook.js +70 -38
  39. package/dist/commands/workflow.d.ts +13 -7
  40. package/dist/commands/workflow.js +179 -58
  41. package/dist/config.js +10 -5
  42. package/dist/flags.d.ts +8 -0
  43. package/dist/flags.js +88 -0
  44. package/dist/flags.test.d.ts +1 -0
  45. package/dist/flags.test.js +73 -0
  46. package/dist/helpers.d.ts +6 -0
  47. package/dist/helpers.js +31 -0
  48. package/dist/index.js +98 -109
  49. package/dist/output.d.ts +12 -1
  50. package/dist/output.js +16 -6
  51. package/dist/prompt.d.ts +24 -0
  52. package/dist/prompt.js +41 -0
  53. package/dist/skills/my-email-api/README.md +45 -0
  54. package/dist/skills/my-email-api/SKILL.md +104 -0
  55. package/dist/skills/my-email-api/claude/.claude-plugin/plugin.json +6 -0
  56. package/dist/skills/my-email-api/make/.gitkeep +0 -0
  57. package/dist/skills/my-email-api/n8n/.gitkeep +0 -0
  58. package/dist/skills/my-email-api/openapi/.gitkeep +0 -0
  59. package/dist/skills/my-webhook-api/README.md +40 -0
  60. package/dist/skills/my-webhook-api/SKILL.md +138 -0
  61. package/dist/skills/my-webhook-api/claude/.claude-plugin/plugin.json +6 -0
  62. package/dist/skills/my-webhook-api/make/.gitkeep +0 -0
  63. package/dist/skills/my-webhook-api/n8n/.gitkeep +0 -0
  64. package/dist/skills/my-webhook-api/openapi/.gitkeep +0 -0
  65. package/dist/skills/my-workflow-api/README.md +36 -0
  66. package/dist/skills/my-workflow-api/SKILL.md +156 -0
  67. package/dist/skills/my-workflow-api/claude/.claude-plugin/plugin.json +6 -0
  68. package/dist/skills/my-workflow-api/make/.gitkeep +0 -0
  69. package/dist/skills/my-workflow-api/n8n/.gitkeep +0 -0
  70. package/dist/skills/my-workflow-api/openapi/.gitkeep +0 -0
  71. package/dist/utils.d.ts +26 -4
  72. package/dist/utils.js +32 -33
  73. package/dist/utils.test.d.ts +1 -0
  74. package/dist/utils.test.js +48 -0
  75. package/package.json +9 -4
  76. package/dist/commands/account.d.ts +0 -4
  77. package/dist/commands/account.js +0 -80
@@ -1,30 +1,10 @@
1
1
  import * as fs from 'fs';
2
2
  import * as os from 'os';
3
3
  import * as path from 'path';
4
- import * as readline from 'readline';
5
4
  import { loadConfig, saveConfig, addAccount, loadFullConfig } from '../config.js';
6
5
  import { info, success } from '../output.js';
7
- const API_BASE = process.env.MYAPI_API_BASE ?? 'https://api.myapihq.com';
8
- function ask(rl, q) {
9
- return new Promise(resolve => rl.question(q, resolve));
10
- }
11
- function yn(answer, defaultYes = true) {
12
- const t = answer.trim().toLowerCase();
13
- if (t === '')
14
- return defaultYes;
15
- return t === 'y' || t === 'yes';
16
- }
17
- async function post(path, body) {
18
- const res = await fetch(`${API_BASE}${path}`, {
19
- method: 'POST',
20
- headers: { 'Content-Type': 'application/json' },
21
- body: JSON.stringify(body),
22
- });
23
- const json = await res.json();
24
- if (!res.ok)
25
- throw new Error(json.error ?? `HTTP ${res.status}`);
26
- return json.data ?? json;
27
- }
6
+ import { ask, confirm } from '../prompt.js';
7
+ import { hq, funnel as sdkFunnel } from '@myapihq/sdk';
28
8
  // ---------------------------------------------------------------------------
29
9
  // Skills installation
30
10
  // ---------------------------------------------------------------------------
@@ -55,96 +35,153 @@ export async function installSkills() {
55
35
  fs.mkdirSync(skillDir, { recursive: true });
56
36
  fs.copyFileSync(path.join(BUNDLED_SKILLS_DIR, skill, 'SKILL.md'), path.join(skillDir, 'SKILL.md'));
57
37
  }
58
- // Symlink each skill into agent config directories
38
+ // Symlink each skill into agent config directories. Surface failures so
39
+ // users can fix permission issues — skills that didn't install will not
40
+ // be available to the agent and "skills installed" would otherwise lie.
59
41
  for (const [agent, dir] of Object.entries(AGENT_DIRS)) {
60
42
  try {
61
43
  fs.mkdirSync(dir, { recursive: true });
62
44
  for (const skill of skills) {
63
45
  const link = path.join(dir, skill);
64
46
  const target = path.join(SKILLS_CANONICAL, skill);
47
+ // Only remove if it's already a symlink we own. Avoids clobbering a
48
+ // user-authored skill that happens to share the same `my-X-api/`
49
+ // name. ENOENT here just means "nothing to clean up".
65
50
  try {
66
- fs.rmSync(link, { recursive: true, force: true });
51
+ const stat = fs.lstatSync(link);
52
+ if (stat.isSymbolicLink())
53
+ fs.rmSync(link, { force: true });
67
54
  }
68
- catch { /* ignore */ }
55
+ catch { /* ENOENT: nothing there */ }
69
56
  fs.symlinkSync(target, link);
70
57
  }
71
58
  info(` ✓ ${agent}`);
72
59
  }
73
- catch {
74
- // Silently skip agents whose directory can't be created.
60
+ catch (err) {
61
+ // Skip agents not installed on this machine (ENOENT on parent dir is OK).
62
+ // Surface anything else so the user knows skills aren't fully installed.
63
+ const code = err?.code;
64
+ if (code === 'ENOENT' || code === 'ENOTDIR') {
65
+ // Agent likely not installed — quietly skip.
66
+ continue;
67
+ }
68
+ info(` ✗ ${agent}: ${err.message || code} (${dir})`);
75
69
  }
76
70
  }
77
71
  }
78
72
  // ---------------------------------------------------------------------------
73
+ // Shared helpers
74
+ // ---------------------------------------------------------------------------
75
+ // Resolve the user's "install skills?" preference: explicit flag wins, else
76
+ // prompt (defaulting to yes). `--yes` short-circuits to true.
77
+ async function resolveSkillsPreference(flags) {
78
+ if (flags['install-skills'])
79
+ return true;
80
+ if (flags['no-skills'])
81
+ return false;
82
+ if (flags.yes)
83
+ return true;
84
+ return confirm('› Install the MyAPI skills pack? (Y/n) ', true);
85
+ }
86
+ // Resolve default org and funnel for a key by inspecting the account's orgs.
87
+ // Used by both `setup` and `import-key`. Picks the most recently-created
88
+ // org/funnel as the default; preserves the caller's hint if it still exists.
89
+ async function resolveDefaults(apiKey, hint = {}) {
90
+ let org = hint.org ?? '';
91
+ let funnel = hint.funnel ?? '';
92
+ try {
93
+ const orgs = await hq.listOrgs(apiKey);
94
+ if (orgs.length > 0) {
95
+ if (!org || !orgs.find(o => o.id === org)) {
96
+ org = orgs[orgs.length - 1].id;
97
+ }
98
+ const funnels = await sdkFunnel.listFunnels(apiKey, org);
99
+ if (funnels.length > 0 && (!funnel || !funnels.find((f) => f.id === funnel))) {
100
+ funnel = funnels[funnels.length - 1].id;
101
+ }
102
+ }
103
+ }
104
+ catch { /* non-fatal */ }
105
+ return { org, funnel };
106
+ }
107
+ // ---------------------------------------------------------------------------
79
108
  // Registered flow
80
109
  // ---------------------------------------------------------------------------
81
- async function registeredFlow(rl) {
82
- const email = (await ask(rl, '› Email? ')).trim();
83
- await post('/hq/account/send-code', { email });
110
+ async function registeredFlow() {
111
+ const email = await ask('› Email? ');
112
+ await hq.sendCode(email);
84
113
  info(`› Sent a code to ${email} · paste it below`);
85
- const code = (await ask(rl, '› Code? ')).trim();
86
- const data = await post('/hq/account/verify-code', { email, code });
114
+ const code = await ask('› Code? ');
115
+ const data = await hq.verifyCode(email, code);
87
116
  return { ...data, email };
88
117
  }
89
- // ---------------------------------------------------------------------------
90
- // Anonymous flow
91
- // ---------------------------------------------------------------------------
92
118
  async function anonymousFlow() {
93
- const data = await post('/hq/account/anonymous', {});
94
- return data;
119
+ return hq.createAnonymousAccount();
95
120
  }
96
121
  // ---------------------------------------------------------------------------
97
122
  // Main setup command
98
123
  // ---------------------------------------------------------------------------
124
+ const IMPORT_KEY_HELP = `Usage: myapi auth import-key <api_key> [--install-skills] [--no-skills]
125
+
126
+ Imports an existing API key non-interactively. Use this in CI, Docker, or any
127
+ environment where the interactive "myapi auth setup" flow is not practical.
128
+
129
+ The key is validated against the API before being saved. Your default org and
130
+ funnel are auto-detected from the account and written to the local config.
131
+
132
+ Flags:
133
+ --install-skills Also install the MyAPI skills pack for AI agents after importing
134
+ --no-skills Skip skills installation even if previously installed
135
+
136
+ Examples:
137
+ myapi auth import-key hq_live_xxxxxxxxxxxxxxxxxxxx
138
+ myapi auth import-key hq_live_xxxxxxxxxxxxxxxxxxxx --install-skills`;
139
+ const SETUP_HELP = `Usage: myapi auth setup [--anonymous] [--yes] [--install-skills|--no-skills]
140
+
141
+ Configures your account and stores your default org and funnel so you don't
142
+ need to pass --org or --funnel on every command.
143
+
144
+ Flags:
145
+ --anonymous Skip registration, create anonymous account
146
+ --yes Skip confirmation prompts
147
+ --install-skills Auto-install skills pack
148
+ --no-skills Skip skills installation`;
99
149
  // myapi auth import-key <key> — non-interactively import a raw API key.
100
150
  export async function importKey(apiKey, flags) {
101
151
  if (flags.help || !apiKey) {
102
- info('Usage: myapi auth import-key <api_key> [--install-skills] [--no-skills]\n\nImports an existing API key non-interactively. Use this in CI, Docker, or any\nenvironment where the interactive "myapi auth setup" flow is not practical.\n\nThe key is validated against the API before being saved. Your default org and\nfunnel are auto-detected from the account and written to the local config.\n\nFlags:\n --install-skills Also install the MyAPI skills pack for AI agents after importing\n --no-skills Skip skills installation even if previously installed\n\nExamples:\n myapi auth import-key hq_live_xxxxxxxxxxxxxxxxxxxx\n myapi auth import-key hq_live_xxxxxxxxxxxxxxxxxxxx --install-skills');
152
+ info(IMPORT_KEY_HELP);
103
153
  return;
104
154
  }
105
- const auth = { Authorization: `Bearer ${apiKey}` };
106
155
  let accountId = '';
107
156
  let email;
108
- let defaultOrg = '';
109
- let defaultFunnel = '';
110
157
  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;
158
+ const me = await hq.getAccount(apiKey);
116
159
  accountId = me.account_id ?? '';
117
160
  email = me.email || undefined;
118
161
  }
119
162
  catch (e) {
120
163
  throw new Error(`Could not verify API key: ${e.message}`);
121
164
  }
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, email, default_org: defaultOrg, default_funnel: defaultFunnel, skills_installed: wantsSkills });
165
+ const { org: defaultOrg, funnel: defaultFunnel } = await resolveDefaults(apiKey);
166
+ const wantsSkills = flags['no-skills'] ? false : true;
167
+ addAccount({
168
+ api_key: apiKey, account_id: accountId, email,
169
+ default_org: defaultOrg, default_funnel: defaultFunnel,
170
+ skills_installed: wantsSkills,
171
+ });
141
172
  success(`› ✓ Key imported${email ? ` · ${email}` : ''}`);
142
173
  if (wantsSkills)
143
174
  await installSkills();
144
175
  }
176
+ // Persist a new account + (optionally) install skills.
177
+ async function installAndPersistSkills(account, wantsSkills) {
178
+ addAccount({ ...account, skills_installed: wantsSkills });
179
+ if (wantsSkills)
180
+ await installSkills();
181
+ }
145
182
  export async function setup(flags = {}) {
146
183
  if (flags.help) {
147
- 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');
184
+ info(SETUP_HELP);
148
185
  return;
149
186
  }
150
187
  info('› Configuring MyAPI…');
@@ -157,27 +194,20 @@ export async function setup(flags = {}) {
157
194
  const activeType = existing.is_anonymous ? 'anonymous' : 'registered';
158
195
  const othersNote = total > 1 ? ` · ${total - 1} more saved` : '';
159
196
  info(`› Connected: ${activeLabel} (${activeType})${othersNote}`);
160
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
161
- const add = await ask(rl, '› Connect a new account? (y/N) ');
162
- if (!yn(add, false)) {
197
+ const add = await confirm('› Connect a new account? (y/N) ', false);
198
+ if (!add) {
163
199
  if (!existing.skills_installed) {
164
- const ans = await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
165
- rl.close();
166
- if (yn(ans)) {
200
+ const wants = await resolveSkillsPreference(flags);
201
+ if (wants) {
167
202
  await installSkills();
168
203
  success('› Skills installed.');
169
204
  saveConfig({ ...existing, skills_installed: true });
170
205
  }
171
206
  }
172
- else {
173
- rl.close();
174
- }
175
207
  return;
176
208
  }
177
- rl.close();
178
209
  // Fall through to setup flow — new account will be added to the list.
179
210
  }
180
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
181
211
  let apiKey = '';
182
212
  let accountId = '';
183
213
  let defaultOrg = '';
@@ -185,97 +215,62 @@ export async function setup(flags = {}) {
185
215
  let subdomainUrl = '';
186
216
  let isAnonymous = false;
187
217
  let email = '';
188
- // Determine skills preference from flags before any prompts.
189
- const skillsFromFlag = flags['install-skills'] ? true : flags['no-skills'] ? false : null;
218
+ const useAnon = flags.anonymous || flags.anon;
219
+ const wantsRegister = useAnon ? false : await confirm(' Register with email? (Y/n, or N to continue anonymously) ', true);
220
+ if (wantsRegister) {
221
+ const data = await registeredFlow();
222
+ apiKey = data.api_key;
223
+ accountId = data.account_id;
224
+ defaultOrg = data.default_org;
225
+ defaultFunnel = data.default_funnel;
226
+ email = data.email;
227
+ }
228
+ else {
229
+ info('› Continuing as anonymous — sites ship to *.makeautonomous.com');
230
+ const data = await anonymousFlow();
231
+ apiKey = data.api_key;
232
+ accountId = data.account_id;
233
+ defaultOrg = data.default_org;
234
+ defaultFunnel = data.default_funnel;
235
+ subdomainUrl = data.subdomain_url;
236
+ isAnonymous = true;
237
+ }
238
+ const wantsSkills = await resolveSkillsPreference(flags);
239
+ await installAndPersistSkills({
240
+ api_key: apiKey,
241
+ account_id: accountId,
242
+ email: email || undefined,
243
+ default_org: defaultOrg,
244
+ default_funnel: defaultFunnel,
245
+ is_anonymous: isAnonymous,
246
+ }, wantsSkills);
247
+ success(`› ✓ saved to ~/.myapi/config.json`);
248
+ // Validate key and ensure org/funnel defaults are still correct.
249
+ // (resolveDefaults is non-fatal if the API call fails.)
190
250
  try {
191
- const useAnon = flags.anonymous || flags.anon;
192
- const createAns = useAnon ? 'n' : await ask(rl, '› Register with email? (Y/n, or N to continue anonymously) ');
193
- if (yn(createAns)) {
194
- const data = await registeredFlow(rl);
195
- apiKey = data.api_key;
196
- accountId = data.account_id;
197
- defaultOrg = data.default_org;
198
- defaultFunnel = data.default_funnel;
199
- email = data.email;
200
- }
201
- else {
202
- info('› Continuing as anonymous — sites ship to *.makeautonomous.com');
203
- const data = await anonymousFlow();
204
- apiKey = data.api_key;
205
- accountId = data.account_id;
206
- defaultOrg = data.default_org;
207
- defaultFunnel = data.default_funnel;
208
- subdomainUrl = data.subdomain_url;
209
- isAnonymous = true;
210
- }
211
- const wantsSkills = skillsFromFlag !== null ? skillsFromFlag : flags.yes ? true : yn(await ask(rl, '› Install the MyAPI skills pack? (Y/n) '));
212
- addAccount({
213
- api_key: apiKey,
214
- account_id: accountId,
215
- email: email || undefined,
216
- default_org: defaultOrg,
217
- default_funnel: defaultFunnel,
218
- is_anonymous: isAnonymous,
219
- skills_installed: wantsSkills,
220
- });
221
- success(`› ✓ saved to ~/.myapi/config.json`);
222
- if (wantsSkills) {
223
- await installSkills();
224
- }
225
- // Validate key and ensure org/funnel defaults are correct.
226
- try {
227
- const auth = { Authorization: `Bearer ${apiKey}` };
228
- const meRes = await fetch(`${API_BASE}/hq/account/me`, { headers: auth });
229
- if (!meRes.ok) {
230
- info('› Warning: could not verify API key — check your connection.');
231
- }
232
- else {
233
- // Verify default org exists; if not, fetch the most recent one.
234
- let resolvedOrg = defaultOrg;
235
- let resolvedFunnel = defaultFunnel;
236
- const orgRes = await fetch(`${API_BASE}/hq/orgs`, { headers: auth });
237
- if (orgRes.ok) {
238
- const orgs = (await orgRes.json())?.data ?? [];
239
- if (orgs.length > 0) {
240
- const latest = orgs[orgs.length - 1];
241
- if (!resolvedOrg || !orgs.find((o) => o.id === resolvedOrg)) {
242
- resolvedOrg = latest.id;
243
- info(`› Auto-selected org: ${latest.name} (${resolvedOrg})`);
244
- }
245
- // Verify default funnel exists within the org.
246
- const fRes = await fetch(`${API_BASE}/funnel/orgs/${resolvedOrg}/funnels`, { headers: auth });
247
- if (fRes.ok) {
248
- const funnels = (await fRes.json())?.data ?? [];
249
- if (funnels.length > 0 && (!resolvedFunnel || !funnels.find((f) => f.id === resolvedFunnel))) {
250
- resolvedFunnel = funnels[funnels.length - 1].id;
251
- info(`› Auto-selected funnel: ${resolvedFunnel}`);
252
- }
253
- }
254
- if (resolvedOrg !== defaultOrg || resolvedFunnel !== defaultFunnel) {
255
- const full = loadFullConfig();
256
- const idx = full.active;
257
- full.accounts[idx].default_org = resolvedOrg;
258
- full.accounts[idx].default_funnel = resolvedFunnel;
259
- fs.writeFileSync(path.join(os.homedir(), '.myapi', 'config.json'), JSON.stringify(full, null, 2), { mode: 0o600 });
260
- defaultOrg = resolvedOrg;
261
- defaultFunnel = resolvedFunnel;
262
- }
263
- }
264
- }
251
+ await hq.getAccount(apiKey);
252
+ const resolved = await resolveDefaults(apiKey, { org: defaultOrg, funnel: defaultFunnel });
253
+ if (resolved.org !== defaultOrg || resolved.funnel !== defaultFunnel) {
254
+ const current = loadConfig();
255
+ if (current) {
256
+ saveConfig({ ...current, default_org: resolved.org, default_funnel: resolved.funnel });
257
+ if (resolved.org !== defaultOrg)
258
+ info(`› Auto-selected org: ${resolved.org}`);
259
+ if (resolved.funnel !== defaultFunnel)
260
+ info(`› Auto-selected funnel: ${resolved.funnel}`);
261
+ defaultOrg = resolved.org;
262
+ defaultFunnel = resolved.funnel;
265
263
  }
266
264
  }
267
- catch { /* non-fatal */ }
268
- if (isAnonymous) {
269
- success('› Setup complete. No card, no email, ready to ship.');
270
- if (subdomainUrl)
271
- info(`› Your funnel: ${subdomainUrl}`);
272
- info('› Upgrade anytime — myapi auth link');
273
- }
274
- else {
275
- success(`› Setup complete. Org ${defaultOrg} ready · $5.00 free credits added.`);
276
- }
277
265
  }
278
- finally {
279
- rl.close();
266
+ catch { /* non-fatal */ }
267
+ if (isAnonymous) {
268
+ success('› Setup complete. No card, no email, ready to ship.');
269
+ if (subdomainUrl)
270
+ info(`› Your funnel: ${subdomainUrl}`);
271
+ info('› Upgrade anytime — myapi auth link');
272
+ }
273
+ else {
274
+ success(`› Setup complete. Org ${defaultOrg} ready · $5.00 free credits added.`);
280
275
  }
281
276
  }
@@ -30,26 +30,36 @@ export async function del(id, flags) {
30
30
  await sdkStorage.deleteAsset(config.api_key, orgId, id);
31
31
  success(`Asset ${id} deleted`);
32
32
  }
33
+ // ── Dispatcher ───────────────────────────────────────────────────────────────
34
+ const SUBCOMMAND_USAGE = {
35
+ 'list': 'myapi storage list [--org <id>] [--json]',
36
+ 'ingest': 'myapi storage ingest <url> [--name <name>] [--org <id>]',
37
+ 'delete': 'myapi storage delete <asset_id> [--org <id>]',
38
+ };
33
39
  export async function run(subcommand, args, flags) {
34
40
  if (!subcommand || (flags.help && !subcommand)) {
35
- 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.');
41
+ info(`Usage: myapi storage <subcommand>
42
+
43
+ Subcommands:
44
+ list List all your uploaded assets
45
+ ingest Ingest a public image URL into your edge storage
46
+ delete Delete a stored asset
47
+
48
+ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
36
49
  return;
37
50
  }
38
51
  if (flags.help) {
39
- if (subcommand === 'list')
40
- info('Usage: myapi storage list --org <id> [--json]');
41
- else if (subcommand === 'ingest')
42
- 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.');
43
- else if (subcommand === 'delete')
44
- info('Usage: myapi storage delete <asset_id> --org <id>');
52
+ const usage = SUBCOMMAND_USAGE[subcommand];
53
+ if (usage)
54
+ info(`Usage: ${usage}`);
55
+ else
56
+ info(`Unknown subcommand: ${subcommand}. Run "myapi storage --help" for the list.`);
45
57
  return;
46
58
  }
47
- if (subcommand === 'list')
48
- await list(flags);
49
- else if (subcommand === 'ingest')
50
- await ingest(args[0], flags);
51
- else if (subcommand === 'delete')
52
- await del(args[0], flags);
53
- else
54
- error(`Unknown subcommand: ${subcommand}. Run "myapi storage --help" for a list of valid subcommands.`);
59
+ switch (subcommand) {
60
+ case 'list': return list(flags);
61
+ case 'ingest': return ingest(args[0], flags);
62
+ case 'delete': return del(args[0], flags);
63
+ default: error(`Unknown subcommand: ${subcommand}. Run "myapi storage --help" for a list of valid subcommands.`);
64
+ }
55
65
  }
@@ -1,4 +1,5 @@
1
+ import type { Flags } from '../helpers.js';
1
2
  export declare function checkForUpdate(currentVersion: string): Promise<void>;
2
- export declare function update(flags?: Record<string, string | boolean>): Promise<void>;
3
+ export declare function update(flags?: Flags): Promise<void>;
3
4
  export declare function latestVersion(): Promise<string | null>;
4
5
  export declare function isNewer(latest: string, current: string): boolean;
@@ -126,6 +126,11 @@ export async function latestVersion() {
126
126
  return null;
127
127
  }
128
128
  }
129
+ // Stable-only semver comparison (1.2.3). Prerelease versions (1.2.3-wip.0)
130
+ // produce NaN in the patch slot, and `n > NaN` is always false — which is
131
+ // what we want: a prerelease user should not be auto-rolled back to stable.
132
+ // Auto-update is opt-in via the `latest` dist-tag (always stable); see
133
+ // checkForUpdate() above.
129
134
  export function isNewer(latest, current) {
130
135
  const toNum = (v) => v.split('.').map(Number);
131
136
  const [lMaj, lMin, lPat] = toNum(latest);
@@ -12,18 +12,30 @@ export async function shorten(targetUrl, flags) {
12
12
  else
13
13
  success(`Shortened URL: ${res.short_url}\nCode: ${res.short_code}`);
14
14
  }
15
+ // ── Dispatcher ───────────────────────────────────────────────────────────────
16
+ const SUBCOMMAND_USAGE = {
17
+ 'shorten': 'myapi url shorten <url> [--org <id>] [--json]',
18
+ };
15
19
  export async function run(subcommand, args, flags) {
16
20
  if (!subcommand || (flags.help && !subcommand)) {
17
- info('Usage: myapi url <subcommand>\n\nSubcommands:\n shorten Shorten a long URL\n\nNote: All url commands require the --org <id> flag.');
21
+ info(`Usage: myapi url <subcommand>
22
+
23
+ Subcommands:
24
+ shorten Shorten a long URL (returns a myurlto.com link)
25
+
26
+ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
18
27
  return;
19
28
  }
20
29
  if (flags.help) {
21
- if (subcommand === 'shorten')
22
- info('Usage: myapi url shorten <url> --org <id> [--json]\n\nShortens a long URL and returns the compact myurlto.com link.');
30
+ const usage = SUBCOMMAND_USAGE[subcommand];
31
+ if (usage)
32
+ info(`Usage: ${usage}`);
33
+ else
34
+ info(`Unknown subcommand: ${subcommand}. Run "myapi url --help" for the list.`);
23
35
  return;
24
36
  }
25
- if (subcommand === 'shorten')
26
- await shorten(args[0], flags);
27
- else
28
- error(`Unknown subcommand: ${subcommand}. Run "myapi url --help" for a list of valid subcommands.`);
37
+ switch (subcommand) {
38
+ case 'shorten': return shorten(args[0], flags);
39
+ default: error(`Unknown subcommand: ${subcommand}. Run "myapi url --help" for a list of valid subcommands.`);
40
+ }
29
41
  }
@@ -1,5 +1,8 @@
1
- export declare function list(flags: Record<string, string | boolean>): Promise<void>;
2
- export declare function create(flags: Record<string, string | boolean>): Promise<void>;
3
- export declare function del(id: string, flags: Record<string, string | boolean>): Promise<void>;
4
- export declare function delivery(id: string, flags: Record<string, string | boolean>): Promise<void>;
5
- export declare function run(subcommand: string | undefined, args: string[], flags: Record<string, string | boolean>): Promise<void>;
1
+ import type { FlagSchema } from '../flags.js';
2
+ import { type Flags } from '../helpers.js';
3
+ export declare const SCHEMA: FlagSchema;
4
+ export declare function list(flags: Flags): Promise<void>;
5
+ export declare function create(nameArg: string | undefined, flags: Flags): Promise<void>;
6
+ export declare function del(id: string, flags: Flags): Promise<void>;
7
+ export declare function delivery(id: string, flags: Flags): Promise<void>;
8
+ export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;