@myapihq/cli 1.0.27 → 1.0.28

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,2 +1,3 @@
1
1
  export declare function signup(): Promise<void>;
2
2
  export declare function whoami(): Promise<void>;
3
+ export declare function switchCmd(): Promise<void>;
@@ -1,10 +1,16 @@
1
1
  import * as readline from 'readline';
2
- import { loadConfig, saveConfig } from '../config.js';
2
+ import { loadConfig, saveConfig, addAccount, switchAccount, listAccounts } from '../config.js';
3
3
  import { info, success, error } from '../output.js';
4
4
  const API_BASE = process.env.MYAPI_API_BASE ?? 'https://api.myapihq.com';
5
5
  function ask(rl, q) {
6
6
  return new Promise(resolve => rl.question(q, resolve));
7
7
  }
8
+ function yn(answer, defaultYes = true) {
9
+ const t = answer.trim().toLowerCase();
10
+ if (t === '')
11
+ return defaultYes;
12
+ return t === 'y' || t === 'yes';
13
+ }
8
14
  async function post(path, body, apiKey) {
9
15
  const headers = { 'Content-Type': 'application/json' };
10
16
  if (apiKey)
@@ -34,25 +40,57 @@ async function patch(path, body, apiKey) {
34
40
  export async function signup() {
35
41
  const config = loadConfig();
36
42
  if (!config?.api_key)
37
- error('Not configured. Run: myapi setup');
43
+ error('Not configured. Run: myapi auth setup');
38
44
  if (!config.is_anonymous)
39
45
  error('Already registered. Use your existing account.');
40
46
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
41
47
  try {
42
48
  const email = (await ask(rl, '› Email? ')).trim();
43
- await patch('/hq/account/upgrade', { email }, config.api_key);
49
+ let upgradeOk = true;
50
+ try {
51
+ await patch('/hq/account/upgrade', { email }, config.api_key);
52
+ }
53
+ catch (err) {
54
+ if (err.message !== 'EMAIL_TAKEN')
55
+ throw err;
56
+ upgradeOk = false;
57
+ info(`› That email already has an account — accounts cannot be merged.`);
58
+ info(`› Your anonymous account will be kept and you can switch back to it anytime.`);
59
+ const ans = (await ask(rl, '› Sign in and add it as a second account? (Y/n) ')).trim();
60
+ if (!yn(ans))
61
+ return;
62
+ await post('/hq/account/send-code', { email });
63
+ }
44
64
  info(`› Sent a code to ${email} · paste it below`);
45
65
  const code = (await ask(rl, '› Code? ')).trim();
46
66
  const data = await post('/hq/account/verify-code', { email, code });
47
- saveConfig({
48
- ...config,
49
- api_key: data.api_key,
50
- account_id: data.account_id,
51
- default_org: data.default_org || config.default_org,
52
- default_funnel: data.default_funnel || config.default_funnel,
53
- is_anonymous: false,
54
- });
55
- success(`› Welcome! Account upgraded · ${email}`);
67
+ if (upgradeOk) {
68
+ // Upgrade: update current account in place.
69
+ saveConfig({
70
+ ...config,
71
+ api_key: data.api_key,
72
+ account_id: data.account_id,
73
+ email,
74
+ default_org: data.default_org || config.default_org,
75
+ default_funnel: data.default_funnel || config.default_funnel,
76
+ is_anonymous: false,
77
+ });
78
+ success(`› Welcome! Account upgraded · ${email}`);
79
+ }
80
+ else {
81
+ // Add as new account and switch to it.
82
+ const idx = addAccount({
83
+ api_key: data.api_key,
84
+ account_id: data.account_id,
85
+ email,
86
+ pin: '',
87
+ default_org: data.default_org,
88
+ default_funnel: data.default_funnel,
89
+ is_anonymous: false,
90
+ });
91
+ success(`› Signed in · ${email} (account #${idx + 1})`);
92
+ info(`› Use "myapi auth switch" to toggle between accounts.`);
93
+ }
56
94
  }
57
95
  finally {
58
96
  rl.close();
@@ -62,9 +100,42 @@ export async function signup() {
62
100
  export async function whoami() {
63
101
  const config = loadConfig();
64
102
  if (!config?.api_key)
65
- error('Not configured. Run: myapi setup');
103
+ error('Not configured. Run: myapi auth setup');
104
+ if (config.email)
105
+ info(`Email: ${config.email}`);
66
106
  info(`Account: ${config.account_id}`);
67
107
  info(`Org: ${config.default_org ?? '(none)'}`);
68
108
  info(`Funnel: ${config.default_funnel ?? '(none)'}`);
69
109
  info(`Type: ${config.is_anonymous ? 'anonymous' : 'registered'}`);
70
110
  }
111
+ // myapi auth switch — switch between saved accounts.
112
+ export async function switchCmd() {
113
+ const accounts = listAccounts();
114
+ if (accounts.length === 0)
115
+ error('No accounts configured. Run: myapi auth setup');
116
+ if (accounts.length === 1) {
117
+ info(`Only one account configured: ${accounts[0].email ?? accounts[0].account_id}`);
118
+ return;
119
+ }
120
+ info('Accounts:');
121
+ for (const a of accounts) {
122
+ const label = a.email ?? (a.is_anonymous ? `anonymous · ${a.account_id.slice(0, 8)}` : a.account_id.slice(0, 8));
123
+ const marker = a.active ? ' ◀ active' : '';
124
+ info(` ${a.index + 1}. ${label}${marker}`);
125
+ }
126
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
127
+ try {
128
+ const ans = (await ask(rl, `› Switch to account (1-${accounts.length})? `)).trim();
129
+ const idx = parseInt(ans, 10) - 1;
130
+ if (isNaN(idx) || idx < 0 || idx >= accounts.length) {
131
+ error('Invalid selection.');
132
+ }
133
+ if (switchAccount(idx)) {
134
+ const a = accounts[idx];
135
+ success(`› Switched to ${a.email ?? a.account_id}`);
136
+ }
137
+ }
138
+ finally {
139
+ rl.close();
140
+ }
141
+ }
@@ -1,4 +1,5 @@
1
1
  export declare function setOrg(id: string, flags: Record<string, string | boolean>): Promise<void>;
2
+ export declare function setFunnel(id: string, flags: Record<string, string | boolean>): Promise<void>;
2
3
  export declare function setDomain(domain: string, flags: Record<string, string | boolean>): Promise<void>;
3
4
  export declare function view(flags: Record<string, string | boolean>): Promise<void>;
4
5
  export declare function run(subcommand: string | undefined, args: string[], flags: Record<string, string | boolean>): Promise<void>;
@@ -10,6 +10,16 @@ export async function setOrg(id, flags) {
10
10
  saveConfig(config);
11
11
  success(`Default organization set to: ${id}`);
12
12
  }
13
+ export async function setFunnel(id, flags) {
14
+ if (!id || flags.help) {
15
+ info("Usage: myapi config set-funnel <id>\n\nSets a default funnel ID for subsequent commands.");
16
+ return;
17
+ }
18
+ const config = requireConfig();
19
+ config.default_funnel = id;
20
+ saveConfig(config);
21
+ success(`Default funnel set to: ${id}`);
22
+ }
13
23
  export async function setDomain(domain, flags) {
14
24
  if (!domain || flags.help) {
15
25
  info("Usage: myapi config set-domain <domain>\n\nSets a default domain for subsequent commands.");
@@ -26,18 +36,21 @@ export async function view(flags) {
26
36
  return;
27
37
  }
28
38
  const config = requireConfig();
29
- info(`Default Org: ${config.default_org || 'Not set'}`);
39
+ info(`Default Org: ${config.default_org || 'Not set'}`);
40
+ info(`Default Funnel: ${config.default_funnel || 'Not set'}`);
30
41
  info(`Default Domain: ${config.default_domain || 'Not set'}`);
31
42
  }
32
43
  export async function run(subcommand, args, flags) {
33
44
  if (!subcommand || (flags.help && !subcommand)) {
34
- info('Usage: myapi config <subcommand>\n\nSubcommands:\n view View current config\n set-org Set default organization\n set-domain Set default domain');
45
+ info('Usage: myapi config <subcommand>\n\nSubcommands:\n view View current config\n set-org Set default organization\n set-funnel Set default funnel\n set-domain Set default domain');
35
46
  return;
36
47
  }
37
48
  if (subcommand === 'view')
38
49
  await view(flags);
39
50
  else if (subcommand === 'set-org')
40
51
  await setOrg(args[0], flags);
52
+ else if (subcommand === 'set-funnel')
53
+ await setFunnel(args[0], flags);
41
54
  else if (subcommand === 'set-domain')
42
55
  await setDomain(args[0], flags);
43
56
  else
@@ -41,8 +41,10 @@ export async function del(id, flags) {
41
41
  export async function push(id, slug, flags) {
42
42
  const config = requireConfig();
43
43
  const orgId = flags.org || config.default_org;
44
- if (!orgId || !id || !slug) {
45
- error("Missing required arguments.\nUsage: myapi funnel push <funnel_id> <slug> --org <id> < index.html\n(Or set defaults via: myapi config set-org <id>)");
44
+ const funnelId = id || config.default_funnel;
45
+ const finalSlug = slug || flags.slug || '/';
46
+ if (!orgId || !funnelId) {
47
+ error("Missing required arguments.\nUsage: myapi funnel push [funnel_id] [slug] < index.html\n(Defaults to your configured funnel and slug '/' when omitted)");
46
48
  }
47
49
  const html = await new Promise((resolve, reject) => {
48
50
  let data = '';
@@ -53,15 +55,15 @@ export async function push(id, slug, flags) {
53
55
  });
54
56
  if (!html)
55
57
  error("No HTML provided via stdin");
56
- const result = await sdkFunnel.pushFunnelPage(config.api_key, orgId, id, { slug, html });
57
- success(`Pushed page to ${slug}`);
58
+ const result = await sdkFunnel.pushFunnelPage(config.api_key, orgId, funnelId, { slug: finalSlug, html });
59
+ success(`Pushed page to ${finalSlug}`);
58
60
  if (result?.url) {
59
61
  info(`Preview: ${result.url}`);
60
62
  }
61
63
  else {
62
64
  const org = await hq.getOrg(config.api_key, orgId);
63
65
  if (org.preview_subdomain) {
64
- info(`Preview: https://${org.preview_subdomain}.makeautonomous.com/${slug}`);
66
+ info(`Preview: https://${org.preview_subdomain}.makeautonomous.com${finalSlug}`);
65
67
  }
66
68
  }
67
69
  }
@@ -91,7 +93,7 @@ export async function run(subcommand, args, flags) {
91
93
  else if (subcommand === 'delete')
92
94
  info('Usage: myapi funnel delete <id> --org <id>');
93
95
  else if (subcommand === 'push')
94
- info('Usage: myapi funnel push <funnel_id> <slug> --org <id> < index.html\n\nPushes HTML content from stdin to the specified funnel.');
96
+ info('Usage: myapi funnel push [funnel_id] [slug] < index.html\n\nPushes HTML from stdin. Uses your default funnel and slug "/" when omitted.');
95
97
  else if (subcommand === 'verify')
96
98
  info('Usage: myapi funnel verify <id> --org <id> [--slug <slug>]\n\nVerifies syntax and structure before pushing.');
97
99
  return;
@@ -2,7 +2,7 @@ import * as fs from 'fs';
2
2
  import * as os from 'os';
3
3
  import * as path from 'path';
4
4
  import * as readline from 'readline';
5
- import { loadConfig, saveConfig } from '../config.js';
5
+ import { loadConfig, saveConfig, addAccount, loadFullConfig } from '../config.js';
6
6
  import { info, success } from '../output.js';
7
7
  const API_BASE = process.env.MYAPI_API_BASE ?? 'https://api.myapihq.com';
8
8
  function ask(rl, q) {
@@ -41,30 +41,31 @@ export async function installSkills() {
41
41
  info('No bundled skills found — skipping skills install.');
42
42
  return;
43
43
  }
44
- fs.mkdirSync(SKILLS_CANONICAL, { recursive: true });
44
+ // Each bundled file is <skill-name>.md — install as <skill-name>/SKILL.md
45
45
  const files = fs.readdirSync(BUNDLED_SKILLS_DIR).filter(f => f.endsWith('.md'));
46
- for (const file of files) {
47
- const src = path.join(BUNDLED_SKILLS_DIR, file);
48
- const dst = path.join(SKILLS_CANONICAL, file);
49
- fs.copyFileSync(src, dst);
46
+ const skills = files.map(f => f.replace(/\.md$/, ''));
47
+ // Write canonical copies: ~/.agents/skills/myapi/<skill-name>/SKILL.md
48
+ for (const skill of skills) {
49
+ const skillDir = path.join(SKILLS_CANONICAL, skill);
50
+ fs.mkdirSync(skillDir, { recursive: true });
51
+ fs.copyFileSync(path.join(BUNDLED_SKILLS_DIR, `${skill}.md`), path.join(skillDir, 'SKILL.md'));
50
52
  }
53
+ // Symlink each skill directory into agent dirs
51
54
  for (const [agent, dir] of Object.entries(AGENT_DIRS)) {
52
55
  try {
53
56
  fs.mkdirSync(dir, { recursive: true });
54
- for (const file of files) {
55
- const link = path.join(dir, file);
56
- const target = path.join(SKILLS_CANONICAL, file);
57
+ for (const skill of skills) {
58
+ const link = path.join(dir, skill);
59
+ const target = path.join(SKILLS_CANONICAL, skill);
57
60
  try {
58
- if (fs.existsSync(link)) {
59
- const stat = fs.lstatSync(link);
60
- if (stat.isSymbolicLink() && fs.readlinkSync(link) === target)
61
- continue;
62
- fs.unlinkSync(link);
61
+ try {
62
+ fs.rmSync(link, { recursive: true, force: true });
63
63
  }
64
+ catch { /* ignore */ }
64
65
  fs.symlinkSync(target, link);
65
66
  }
66
67
  catch {
67
- // Non-fatal: skip this agent dir if permissions are wrong.
68
+ // Non-fatal.
68
69
  }
69
70
  }
70
71
  info(` ✓ ${agent}`);
@@ -83,7 +84,7 @@ async function registeredFlow(rl) {
83
84
  info(`› Sent a code to ${email} · paste it below`);
84
85
  const code = (await ask(rl, '› Code? ')).trim();
85
86
  const data = await post('/hq/account/verify-code', { email, code });
86
- return data;
87
+ return { ...data, email };
87
88
  }
88
89
  // ---------------------------------------------------------------------------
89
90
  // Anonymous flow
@@ -98,29 +99,33 @@ async function anonymousFlow() {
98
99
  export async function setup() {
99
100
  info('› Configuring MyAPI…');
100
101
  const existing = loadConfig();
101
- // Already configured — just verify the key is still valid.
102
+ // Already configured — ask before adding a new account.
102
103
  if (existing?.api_key) {
103
- try {
104
- const res = await fetch(`${API_BASE}/hq/account/me`, {
105
- headers: { Authorization: `Bearer ${existing.api_key}` },
106
- });
107
- if (res.ok) {
108
- info(`› Already configured · account ${existing.account_id}`);
109
- // Re-ask skills only if preference was never recorded.
110
- if (existing.skills_installed === undefined) {
111
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
112
- const ans = await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
113
- rl.close();
114
- if (yn(ans)) {
115
- await installSkills();
116
- success('› Skills installed.');
117
- }
118
- saveConfig({ ...existing, skills_installed: yn(ans) });
104
+ const full = loadFullConfig();
105
+ const total = full?.accounts.length ?? 1;
106
+ const activeLabel = existing.email ?? existing.account_id;
107
+ const activeType = existing.is_anonymous ? 'anonymous' : 'registered';
108
+ const othersNote = total > 1 ? ` · ${total - 1} more saved` : '';
109
+ info(`› Connected: ${activeLabel} (${activeType})${othersNote}`);
110
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
111
+ const add = await ask(rl, '› Connect a new account? (y/N) ');
112
+ if (!yn(add, false)) {
113
+ if (!existing.skills_installed) {
114
+ const ans = await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
115
+ rl.close();
116
+ if (yn(ans)) {
117
+ await installSkills();
118
+ success('› Skills installed.');
119
+ saveConfig({ ...existing, skills_installed: true });
119
120
  }
120
- return;
121
121
  }
122
+ else {
123
+ rl.close();
124
+ }
125
+ return;
122
126
  }
123
- catch { /* fall through to full setup */ }
127
+ rl.close();
128
+ // Fall through to setup flow — new account will be added to the list.
124
129
  }
125
130
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
126
131
  let apiKey = '';
@@ -129,6 +134,7 @@ export async function setup() {
129
134
  let defaultFunnel = '';
130
135
  let subdomainUrl = '';
131
136
  let isAnonymous = false;
137
+ let email = '';
132
138
  try {
133
139
  const createAns = await ask(rl, '› Create an account? (Y/n) ');
134
140
  if (yn(createAns)) {
@@ -137,6 +143,7 @@ export async function setup() {
137
143
  accountId = data.account_id;
138
144
  defaultOrg = data.default_org;
139
145
  defaultFunnel = data.default_funnel;
146
+ email = data.email;
140
147
  }
141
148
  else {
142
149
  info('› Continuing as anonymous — sites ship to *.makeautonomous.com');
@@ -150,10 +157,11 @@ export async function setup() {
150
157
  }
151
158
  const skillsAns = await ask(rl, '› Install the MyAPI skills pack? (Y/n) ');
152
159
  const wantsSkills = yn(skillsAns);
153
- saveConfig({
160
+ addAccount({
154
161
  api_key: apiKey,
155
162
  account_id: accountId,
156
163
  pin: '',
164
+ email: email || undefined,
157
165
  default_org: defaultOrg,
158
166
  default_funnel: defaultFunnel,
159
167
  is_anonymous: isAnonymous,
@@ -163,6 +171,49 @@ export async function setup() {
163
171
  if (wantsSkills) {
164
172
  await installSkills();
165
173
  }
174
+ // Validate key and ensure org/funnel defaults are correct.
175
+ try {
176
+ const auth = { Authorization: `Bearer ${apiKey}` };
177
+ const meRes = await fetch(`${API_BASE}/hq/account/me`, { headers: auth });
178
+ if (!meRes.ok) {
179
+ info('› Warning: could not verify API key — check your connection.');
180
+ }
181
+ else {
182
+ // Verify default org exists; if not, fetch the most recent one.
183
+ let resolvedOrg = defaultOrg;
184
+ let resolvedFunnel = defaultFunnel;
185
+ const orgRes = await fetch(`${API_BASE}/hq/orgs`, { headers: auth });
186
+ if (orgRes.ok) {
187
+ const orgs = (await orgRes.json())?.data ?? [];
188
+ if (orgs.length > 0) {
189
+ const latest = orgs[orgs.length - 1];
190
+ if (!resolvedOrg || !orgs.find((o) => o.id === resolvedOrg)) {
191
+ resolvedOrg = latest.id;
192
+ info(`› Auto-selected org: ${latest.name} (${resolvedOrg})`);
193
+ }
194
+ // Verify default funnel exists within the org.
195
+ const fRes = await fetch(`${API_BASE}/funnel/orgs/${resolvedOrg}/funnels`, { headers: auth });
196
+ if (fRes.ok) {
197
+ const funnels = (await fRes.json())?.data ?? [];
198
+ if (funnels.length > 0 && (!resolvedFunnel || !funnels.find((f) => f.id === resolvedFunnel))) {
199
+ resolvedFunnel = funnels[funnels.length - 1].id;
200
+ info(`› Auto-selected funnel: ${resolvedFunnel}`);
201
+ }
202
+ }
203
+ if (resolvedOrg !== defaultOrg || resolvedFunnel !== defaultFunnel) {
204
+ const full = loadFullConfig();
205
+ const idx = full.active;
206
+ full.accounts[idx].default_org = resolvedOrg;
207
+ full.accounts[idx].default_funnel = resolvedFunnel;
208
+ fs.writeFileSync(path.join(os.homedir(), '.myapi', 'config.json'), JSON.stringify(full, null, 2), { mode: 0o600 });
209
+ defaultOrg = resolvedOrg;
210
+ defaultFunnel = resolvedFunnel;
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+ catch { /* non-fatal */ }
166
217
  if (isAnonymous) {
167
218
  success('› Setup complete. No card, no email, ready to ship.');
168
219
  if (subdomainUrl)
@@ -18,8 +18,10 @@ export async function checkForUpdate(currentVersion) {
18
18
  return;
19
19
  const data = await res.json();
20
20
  const latest = data.version;
21
- if (config) {
22
- saveConfig({ ...config, last_update_check: now });
21
+ // Re-read config at write time to avoid clobbering changes made during setup.
22
+ const current = loadConfig();
23
+ if (current) {
24
+ saveConfig({ ...current, last_update_check: now });
23
25
  }
24
26
  if (latest && isNewer(latest, currentVersion)) {
25
27
  info(`\n› New version available (${currentVersion} → ${latest}) — installing…`);
package/dist/config.d.ts CHANGED
@@ -1,15 +1,33 @@
1
- export interface Config {
1
+ export interface AccountEntry {
2
2
  api_key: string;
3
3
  account_id: string;
4
4
  pin: string;
5
+ email?: string;
5
6
  default_org?: string;
6
7
  default_funnel?: string;
7
8
  default_domain?: string;
8
9
  is_anonymous?: boolean;
9
10
  skills_installed?: boolean;
11
+ }
12
+ export interface Config extends AccountEntry {
13
+ last_update_check?: number;
14
+ autocomplete_setup?: boolean;
15
+ }
16
+ export interface FullConfig {
17
+ active: number;
18
+ accounts: AccountEntry[];
10
19
  last_update_check?: number;
11
20
  autocomplete_setup?: boolean;
12
21
  }
22
+ export declare const CONFIG_DIR: string;
23
+ export declare const CONFIG_FILE: string;
24
+ export declare function loadFullConfig(): FullConfig | null;
13
25
  export declare function loadConfig(): Config | null;
14
26
  export declare function saveConfig(config: Config): void;
27
+ export declare function addAccount(account: AccountEntry): number;
28
+ export declare function switchAccount(index: number): boolean;
29
+ export declare function listAccounts(): Array<AccountEntry & {
30
+ index: number;
31
+ active: boolean;
32
+ }>;
15
33
  export declare function requireConfig(): Config;
package/dist/config.js CHANGED
@@ -1,38 +1,102 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import * as os from 'os';
4
- const CONFIG_DIR = path.join(os.homedir(), '.myapi');
5
- const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
6
- export function loadConfig() {
7
- let fileConfig = {};
4
+ export const CONFIG_DIR = path.join(os.homedir(), '.myapi');
5
+ export const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
6
+ function ensureDir() {
7
+ if (!fs.existsSync(CONFIG_DIR))
8
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
9
+ }
10
+ function readRaw() {
8
11
  try {
9
- if (fs.existsSync(CONFIG_FILE)) {
10
- const data = fs.readFileSync(CONFIG_FILE, 'utf-8');
11
- fileConfig = JSON.parse(data);
12
- }
13
- }
14
- catch (err) {
15
- // Ignore read errors
12
+ if (fs.existsSync(CONFIG_FILE))
13
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
16
14
  }
15
+ catch { /* ignore */ }
16
+ return null;
17
+ }
18
+ function isFullConfig(raw) {
19
+ return raw && Array.isArray(raw.accounts);
20
+ }
21
+ // Migrate a flat (legacy) config to the new multi-account shape.
22
+ function migrate(raw) {
23
+ const { last_update_check, autocomplete_setup, ...account } = raw;
24
+ return { active: 0, accounts: [account], last_update_check, autocomplete_setup };
25
+ }
26
+ export function loadFullConfig() {
27
+ const raw = readRaw();
28
+ if (!raw)
29
+ return null;
30
+ return isFullConfig(raw) ? raw : migrate(raw);
31
+ }
32
+ // loadConfig returns the active account merged with globals — unchanged interface for all callers.
33
+ export function loadConfig() {
17
34
  const envKey = process.env.MYAPI_KEY;
18
- if (envKey) {
19
- fileConfig.api_key = envKey;
20
- }
21
- if (Object.keys(fileConfig).length === 0) {
35
+ const full = loadFullConfig();
36
+ if (!full && envKey)
37
+ return { api_key: envKey, account_id: '', pin: '' };
38
+ if (!full)
22
39
  return null;
23
- }
24
- return fileConfig;
40
+ const active = full.accounts[full.active] ?? full.accounts[0];
41
+ if (!active)
42
+ return null;
43
+ const config = {
44
+ ...active,
45
+ last_update_check: full.last_update_check,
46
+ autocomplete_setup: full.autocomplete_setup,
47
+ };
48
+ if (envKey)
49
+ config.api_key = envKey;
50
+ return config;
25
51
  }
26
52
  export function saveConfig(config) {
27
- if (!fs.existsSync(CONFIG_DIR)) {
28
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
53
+ ensureDir();
54
+ const full = loadFullConfig() ?? { active: 0, accounts: [] };
55
+ const { last_update_check, autocomplete_setup, ...account } = config;
56
+ if (full.accounts.length === 0)
57
+ full.accounts.push(account);
58
+ else
59
+ full.accounts[full.active] = account;
60
+ if (last_update_check !== undefined)
61
+ full.last_update_check = last_update_check;
62
+ if (autocomplete_setup !== undefined)
63
+ full.autocomplete_setup = autocomplete_setup;
64
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(full, null, 2), { mode: 0o600 });
65
+ }
66
+ export function addAccount(account) {
67
+ ensureDir();
68
+ const full = loadFullConfig() ?? { active: 0, accounts: [] };
69
+ // Replace if same account_id already exists.
70
+ const existing = full.accounts.findIndex(a => a.account_id === account.account_id);
71
+ if (existing >= 0) {
72
+ full.accounts[existing] = account;
73
+ full.active = existing;
74
+ }
75
+ else {
76
+ full.accounts.push(account);
77
+ full.active = full.accounts.length - 1;
29
78
  }
30
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
79
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(full, null, 2), { mode: 0o600 });
80
+ return full.active;
81
+ }
82
+ export function switchAccount(index) {
83
+ const full = loadFullConfig();
84
+ if (!full || index < 0 || index >= full.accounts.length)
85
+ return false;
86
+ full.active = index;
87
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(full, null, 2), { mode: 0o600 });
88
+ return true;
89
+ }
90
+ export function listAccounts() {
91
+ const full = loadFullConfig();
92
+ if (!full)
93
+ return [];
94
+ return full.accounts.map((a, i) => ({ ...a, index: i, active: i === full.active }));
31
95
  }
32
96
  export function requireConfig() {
33
97
  const config = loadConfig();
34
98
  if (!config || !config.api_key) {
35
- console.error("No API key found. Provide MYAPI_KEY env var or run: myapi setup");
99
+ console.error("No API key found. Provide MYAPI_KEY env var or run: myapi auth setup");
36
100
  process.exit(1);
37
101
  }
38
102
  return config;