@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.
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +84 -13
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +15 -2
- package/dist/commands/funnel.js +8 -6
- package/dist/commands/setup.js +87 -36
- package/dist/commands/update.js +4 -2
- package/dist/config.d.ts +19 -1
- package/dist/config.js +85 -21
- package/dist/index.js +43 -21
- package/dist/skills/my-api-hq.md +116 -0
- package/dist/skills/my-domain-api.md +83 -0
- package/dist/skills/my-funnel-api.md +35 -0
- package/package.json +2 -2
- package/scripts/copy-skills.js +3 -1
- package/src/commands/auth.ts +81 -14
- package/src/commands/config.ts +15 -2
- package/src/commands/funnel.ts +9 -7
- package/src/commands/setup.ts +90 -37
- package/src/commands/update.ts +4 -2
- package/src/config.ts +93 -23
- package/src/index.ts +30 -19
- package/src/skills/my-funnel-api.md +1 -1
package/dist/commands/auth.d.ts
CHANGED
package/dist/commands/auth.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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>;
|
package/dist/commands/config.js
CHANGED
|
@@ -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:
|
|
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
|
package/dist/commands/funnel.js
CHANGED
|
@@ -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
|
-
|
|
45
|
-
|
|
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,
|
|
57
|
-
success(`Pushed page to ${
|
|
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
|
|
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
|
|
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;
|
package/dist/commands/setup.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
55
|
-
const link = path.join(dir,
|
|
56
|
-
const target = path.join(SKILLS_CANONICAL,
|
|
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
|
-
|
|
59
|
-
|
|
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
|
|
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 —
|
|
102
|
+
// Already configured — ask before adding a new account.
|
|
102
103
|
if (existing?.api_key) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
saveConfig({ ...existing, skills_installed:
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
package/dist/commands/update.js
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
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
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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(
|
|
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;
|