@botuyo/mcp 0.3.13 → 0.3.21
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/LICENSE +21 -0
- package/README.md +135 -30
- package/dist/__tests__/client.spec.d.ts +5 -0
- package/dist/__tests__/client.spec.js +139 -0
- package/dist/__tests__/client.spec.js.map +1 -0
- package/dist/client.d.ts +6 -1
- package/dist/client.js +30 -10
- package/dist/client.js.map +1 -1
- package/dist/commands/__tests__/credentials.spec.d.ts +5 -0
- package/dist/commands/__tests__/credentials.spec.js +174 -0
- package/dist/commands/__tests__/credentials.spec.js.map +1 -0
- package/dist/commands/auth.js +34 -50
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/credentials.d.ts +38 -4
- package/dist/commands/credentials.js +80 -12
- package/dist/commands/credentials.js.map +1 -1
- package/dist/commands/login.js +26 -56
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/switch_tenant.js +11 -36
- package/dist/commands/switch_tenant.js.map +1 -1
- package/dist/commands/tenants.js +10 -19
- package/dist/commands/tenants.js.map +1 -1
- package/dist/index.js +88 -11
- package/dist/index.js.map +1 -1
- package/dist/tools/__tests__/configure_avatar.spec.js +2 -3
- package/dist/tools/__tests__/configure_avatar.spec.js.map +1 -1
- package/dist/tools/configure_agent_tool.js +4 -4
- package/dist/tools/configure_avatar.js +2 -3
- package/dist/tools/configure_avatar.js.map +1 -1
- package/dist/tools/example_agent.d.ts +5 -3
- package/dist/tools/example_agent.js +10 -8
- package/dist/tools/example_agent.js.map +1 -1
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/ingest_knowledge_url.d.ts +4 -0
- package/dist/tools/ingest_knowledge_url.js +35 -0
- package/dist/tools/ingest_knowledge_url.js.map +1 -0
- package/dist/tools/switch_tenant.js +1 -1
- package/dist/tools/switch_tenant.js.map +1 -1
- package/package.json +2 -3
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* credentials.ts — Unit tests
|
|
3
|
+
* Tests for apiUrl persistence, resolveApiUrl, and checkExistingSession
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
// We'll mock fs/promises to avoid touching the real filesystem
|
|
9
|
+
vi.mock('fs/promises', () => ({
|
|
10
|
+
readFile: vi.fn(),
|
|
11
|
+
writeFile: vi.fn(),
|
|
12
|
+
mkdir: vi.fn(),
|
|
13
|
+
unlink: vi.fn()
|
|
14
|
+
}));
|
|
15
|
+
// Import after mocking
|
|
16
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
17
|
+
import { readCredentials, saveCredentials, resolveToken, resolveApiUrl, checkExistingSession } from '../credentials.js';
|
|
18
|
+
const CREDS_PATH = join(homedir(), '.botuyo', 'credentials.json');
|
|
19
|
+
function makeCreds(overrides = {}) {
|
|
20
|
+
return {
|
|
21
|
+
token: 'test-jwt-token',
|
|
22
|
+
tenantId: 'tenant-123',
|
|
23
|
+
tenantName: 'My Tenant',
|
|
24
|
+
role: 'owner',
|
|
25
|
+
email: 'user@test.com',
|
|
26
|
+
savedAt: new Date().toISOString(),
|
|
27
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
28
|
+
apiUrl: 'http://localhost:8080',
|
|
29
|
+
...overrides
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
describe('credentials', () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
vi.resetAllMocks();
|
|
35
|
+
// Clean env vars between tests
|
|
36
|
+
delete process.env.BOTUYO_TOKEN;
|
|
37
|
+
delete process.env.BOTUYO_API_URL;
|
|
38
|
+
});
|
|
39
|
+
// ── apiUrl persistence ──────────────────────────────────────────────
|
|
40
|
+
describe('apiUrl in credentials', () => {
|
|
41
|
+
it('saveCredentials includes apiUrl in stored JSON', async () => {
|
|
42
|
+
const creds = makeCreds({ apiUrl: 'http://localhost:8080' });
|
|
43
|
+
await saveCredentials(creds);
|
|
44
|
+
expect(writeFile).toHaveBeenCalledWith(CREDS_PATH, expect.stringContaining('"apiUrl": "http://localhost:8080"'), 'utf8');
|
|
45
|
+
});
|
|
46
|
+
it('readCredentials returns apiUrl from stored JSON', async () => {
|
|
47
|
+
const creds = makeCreds({ apiUrl: 'https://api.botuyo.com' });
|
|
48
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(creds));
|
|
49
|
+
const result = await readCredentials();
|
|
50
|
+
expect(result?.apiUrl).toBe('https://api.botuyo.com');
|
|
51
|
+
});
|
|
52
|
+
it('readCredentials returns null apiUrl for legacy credentials without apiUrl', async () => {
|
|
53
|
+
// Simulate old credentials.json that doesn't have apiUrl
|
|
54
|
+
const legacy = {
|
|
55
|
+
token: 'old-token',
|
|
56
|
+
tenantId: 't1',
|
|
57
|
+
tenantName: 'T1',
|
|
58
|
+
role: 'owner',
|
|
59
|
+
email: 'a@b.com',
|
|
60
|
+
savedAt: new Date().toISOString(),
|
|
61
|
+
expiresAt: new Date(Date.now() + 86400000).toISOString()
|
|
62
|
+
};
|
|
63
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(legacy));
|
|
64
|
+
const result = await readCredentials();
|
|
65
|
+
expect(result).not.toBeNull();
|
|
66
|
+
expect(result?.apiUrl).toBeUndefined();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
// ── resolveApiUrl ───────────────────────────────────────────────────
|
|
70
|
+
describe('resolveApiUrl', () => {
|
|
71
|
+
it('returns BOTUYO_API_URL env var when set (highest priority)', async () => {
|
|
72
|
+
process.env.BOTUYO_API_URL = 'https://env-override.com';
|
|
73
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(makeCreds({ apiUrl: 'http://localhost:8080' })));
|
|
74
|
+
const url = await resolveApiUrl();
|
|
75
|
+
expect(url).toBe('https://env-override.com');
|
|
76
|
+
});
|
|
77
|
+
it('returns apiUrl from credentials when env var is not set', async () => {
|
|
78
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(makeCreds({ apiUrl: 'http://localhost:8080' })));
|
|
79
|
+
const url = await resolveApiUrl();
|
|
80
|
+
expect(url).toBe('http://localhost:8080');
|
|
81
|
+
});
|
|
82
|
+
it('returns default https://api.botuyo.com when no env var and no credentials', async () => {
|
|
83
|
+
vi.mocked(readFile).mockRejectedValue(new Error('ENOENT'));
|
|
84
|
+
const url = await resolveApiUrl();
|
|
85
|
+
expect(url).toBe('https://api.botuyo.com');
|
|
86
|
+
});
|
|
87
|
+
it('returns default when credentials exist but apiUrl is missing (legacy)', async () => {
|
|
88
|
+
const legacy = { token: 'x', tenantId: 'y', tenantName: 'z', role: 'a', email: 'b', savedAt: 'c', expiresAt: 'd' };
|
|
89
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(legacy));
|
|
90
|
+
const url = await resolveApiUrl();
|
|
91
|
+
expect(url).toBe('https://api.botuyo.com');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
// ── checkExistingSession ────────────────────────────────────────────
|
|
95
|
+
describe('checkExistingSession', () => {
|
|
96
|
+
it('returns null when no credentials exist', async () => {
|
|
97
|
+
vi.mocked(readFile).mockRejectedValue(new Error('ENOENT'));
|
|
98
|
+
const result = await checkExistingSession('http://localhost:8080', false);
|
|
99
|
+
expect(result).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
it('returns null and clears when credentials are locally expired', async () => {
|
|
102
|
+
const expired = makeCreds({ expiresAt: new Date(Date.now() - 1000).toISOString() });
|
|
103
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(expired));
|
|
104
|
+
const result = await checkExistingSession('http://localhost:8080', false);
|
|
105
|
+
expect(result).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
it('returns credentials when valid and not forced', async () => {
|
|
108
|
+
const valid = makeCreds();
|
|
109
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(valid));
|
|
110
|
+
// Mock global fetch for server verification
|
|
111
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
112
|
+
ok: true,
|
|
113
|
+
json: async () => ({ success: true })
|
|
114
|
+
});
|
|
115
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
116
|
+
const result = await checkExistingSession('http://localhost:8080', false);
|
|
117
|
+
expect(result).not.toBeNull();
|
|
118
|
+
expect(result?.token).toBe('test-jwt-token');
|
|
119
|
+
vi.unstubAllGlobals();
|
|
120
|
+
});
|
|
121
|
+
it('returns null when --force is true even if credentials are valid', async () => {
|
|
122
|
+
const valid = makeCreds();
|
|
123
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(valid));
|
|
124
|
+
const result = await checkExistingSession('http://localhost:8080', true);
|
|
125
|
+
expect(result).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
it('returns null when server rejects the token (401)', async () => {
|
|
128
|
+
const valid = makeCreds();
|
|
129
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(valid));
|
|
130
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
131
|
+
ok: false,
|
|
132
|
+
json: async () => ({ success: false, error: 'Invalid token' })
|
|
133
|
+
});
|
|
134
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
135
|
+
const result = await checkExistingSession('http://localhost:8080', false);
|
|
136
|
+
expect(result).toBeNull();
|
|
137
|
+
vi.unstubAllGlobals();
|
|
138
|
+
});
|
|
139
|
+
it('returns credentials when server is unreachable (network error = assume valid)', async () => {
|
|
140
|
+
const valid = makeCreds();
|
|
141
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(valid));
|
|
142
|
+
const mockFetch = vi.fn().mockRejectedValue(new Error('ECONNREFUSED'));
|
|
143
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
144
|
+
const result = await checkExistingSession('http://localhost:8080', false);
|
|
145
|
+
expect(result).not.toBeNull();
|
|
146
|
+
vi.unstubAllGlobals();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
// ── resolveToken (existing, ensure no regression) ──────────────────
|
|
150
|
+
describe('resolveToken', () => {
|
|
151
|
+
it('returns BOTUYO_TOKEN env var when set', async () => {
|
|
152
|
+
process.env.BOTUYO_TOKEN = 'env-token';
|
|
153
|
+
const token = await resolveToken();
|
|
154
|
+
expect(token).toBe('env-token');
|
|
155
|
+
});
|
|
156
|
+
it('returns token from credentials when env var is not set', async () => {
|
|
157
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(makeCreds()));
|
|
158
|
+
const token = await resolveToken();
|
|
159
|
+
expect(token).toBe('test-jwt-token');
|
|
160
|
+
});
|
|
161
|
+
it('returns null when no credentials and no env var', async () => {
|
|
162
|
+
vi.mocked(readFile).mockRejectedValue(new Error('ENOENT'));
|
|
163
|
+
const token = await resolveToken();
|
|
164
|
+
expect(token).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
it('returns null and clears when credentials are expired', async () => {
|
|
167
|
+
const expired = makeCreds({ expiresAt: new Date(Date.now() - 1000).toISOString() });
|
|
168
|
+
vi.mocked(readFile).mockResolvedValue(JSON.stringify(expired));
|
|
169
|
+
const token = await resolveToken();
|
|
170
|
+
expect(token).toBeNull();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
//# sourceMappingURL=credentials.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.spec.js","sourceRoot":"","sources":["../../../src/commands/__tests__/credentials.spec.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/auth.js
CHANGED
|
@@ -17,31 +17,23 @@
|
|
|
17
17
|
import http from 'http';
|
|
18
18
|
import { URL } from 'url';
|
|
19
19
|
import { execSync } from 'child_process';
|
|
20
|
-
import {
|
|
20
|
+
import { saveCredentials, checkExistingSession, resolveApiUrl, resolveTenantName } from './credentials.js';
|
|
21
21
|
import os from 'os';
|
|
22
22
|
const ADMIN_URL = process.env.BOTUYO_ADMIN_URL || 'https://botuyo.com';
|
|
23
|
-
const API_URL = process.env.BOTUYO_API_URL || 'https://api.botuyo.com';
|
|
24
23
|
export async function runAuth(args) {
|
|
25
24
|
console.log('\n🤖 BotUyo MCP — Autenticación por Browser\n');
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.log(` Tenant: ${existing.tenantName} (${existing.role})`);
|
|
39
|
-
console.log('\n Usá --force para re-autenticarte.');
|
|
40
|
-
console.log(' O usá `npx @botuyo/mcp login` para login por terminal.\n');
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
console.log('⚠️ Tu sesión ya no es válida en el servidor. Vamos a re-autenticarte.\n');
|
|
44
|
-
}
|
|
25
|
+
const API_URL = process.env.BOTUYO_API_URL || await resolveApiUrl();
|
|
26
|
+
const force = args.includes('--force');
|
|
27
|
+
const existing = await checkExistingSession(API_URL, force);
|
|
28
|
+
if (existing) {
|
|
29
|
+
console.log(`✅ Ya estás autenticado como: ${existing.email || 'usuario'}`);
|
|
30
|
+
console.log(` Tenant: ${existing.tenantName} (${existing.role})`);
|
|
31
|
+
console.log('\n Usá --force para re-autenticarte.');
|
|
32
|
+
console.log(' O usá `npx @botuyo/mcp login` para login por terminal.\n');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!force) {
|
|
36
|
+
console.log('⚠️ Sesión expirada o inválida. Vamos a re-autenticarte.\n');
|
|
45
37
|
}
|
|
46
38
|
// Start local callback server
|
|
47
39
|
const { port, waitForCode } = await startCallbackServer();
|
|
@@ -75,20 +67,12 @@ export async function runAuth(args) {
|
|
|
75
67
|
role: tokenData.data.role || 'viewer',
|
|
76
68
|
email: tokenData.data.callerEmail || '',
|
|
77
69
|
savedAt: new Date().toISOString(),
|
|
78
|
-
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString() // 7 days
|
|
70
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days
|
|
71
|
+
apiUrl: API_URL
|
|
79
72
|
};
|
|
80
|
-
// Resolve the real tenant name via
|
|
73
|
+
// Resolve the real tenant name via shared helper
|
|
81
74
|
if (creds.tenantName === creds.tenantId) {
|
|
82
|
-
|
|
83
|
-
const tRes = await fetch(`${API_URL}/api/tenants/${creds.tenantId}`, {
|
|
84
|
-
headers: { Authorization: `Bearer ${creds.token}` }
|
|
85
|
-
});
|
|
86
|
-
const tData = (await tRes.json());
|
|
87
|
-
const name = tData.data?.name || tData.data?.tenant?.name;
|
|
88
|
-
if (name)
|
|
89
|
-
creds.tenantName = name;
|
|
90
|
-
}
|
|
91
|
-
catch { /* keep ID as fallback */ }
|
|
75
|
+
creds.tenantName = await resolveTenantName(API_URL, creds.token, creds.tenantId);
|
|
92
76
|
}
|
|
93
77
|
await saveCredentials(creds);
|
|
94
78
|
console.log(`\n✅ ¡Autenticado exitosamente!`);
|
|
@@ -111,24 +95,24 @@ function startCallbackServer() {
|
|
|
111
95
|
if (url.pathname === '/callback') {
|
|
112
96
|
const code = url.searchParams.get('code');
|
|
113
97
|
const html = code
|
|
114
|
-
? `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
115
|
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap" rel="stylesheet">
|
|
116
|
-
<style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Plus Jakarta Sans',sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#f8fafc,#eef2ff,#f8fafc);color:#0f172a}
|
|
117
|
-
.card{text-align:center;max-width:420px;padding:48px 32px;background:#fff;border-radius:20px;border:1px solid #e2e8f0;box-shadow:0 25px 50px rgba(0,0,0,.08)}
|
|
118
|
-
.icon{width:64px;height:64px;margin:0 auto 20px;background:#f0fdf4;border-radius:16px;display:flex;align-items:center;justify-content:center;font-size:28px}
|
|
119
|
-
h2{font-size:22px;font-weight:700;margin-bottom:8px}p{color:#64748b;font-size:15px;line-height:1.6}
|
|
120
|
-
.badge{display:inline-block;margin-top:16px;background:#eef2ff;color:#4f46e5;padding:6px 16px;border-radius:8px;font-size:13px;font-weight:600}</style></head>
|
|
121
|
-
<body><div class="card"><div class="icon">✅</div><h2>BotUyo CLI Autorizado</h2>
|
|
122
|
-
<p>Podés cerrar esta pestaña y volver a la terminal.</p>
|
|
98
|
+
? `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
99
|
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap" rel="stylesheet">
|
|
100
|
+
<style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Plus Jakarta Sans',sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#f8fafc,#eef2ff,#f8fafc);color:#0f172a}
|
|
101
|
+
.card{text-align:center;max-width:420px;padding:48px 32px;background:#fff;border-radius:20px;border:1px solid #e2e8f0;box-shadow:0 25px 50px rgba(0,0,0,.08)}
|
|
102
|
+
.icon{width:64px;height:64px;margin:0 auto 20px;background:#f0fdf4;border-radius:16px;display:flex;align-items:center;justify-content:center;font-size:28px}
|
|
103
|
+
h2{font-size:22px;font-weight:700;margin-bottom:8px}p{color:#64748b;font-size:15px;line-height:1.6}
|
|
104
|
+
.badge{display:inline-block;margin-top:16px;background:#eef2ff;color:#4f46e5;padding:6px 16px;border-radius:8px;font-size:13px;font-weight:600}</style></head>
|
|
105
|
+
<body><div class="card"><div class="icon">✅</div><h2>BotUyo CLI Autorizado</h2>
|
|
106
|
+
<p>Podés cerrar esta pestaña y volver a la terminal.</p>
|
|
123
107
|
<span class="badge">Sesión activa</span></div></body></html>`
|
|
124
|
-
: `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
125
|
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap" rel="stylesheet">
|
|
126
|
-
<style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Plus Jakarta Sans',sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#f8fafc,#fff1f2,#f8fafc);color:#0f172a}
|
|
127
|
-
.card{text-align:center;max-width:420px;padding:48px 32px;background:#fff;border-radius:20px;border:1px solid #e2e8f0;box-shadow:0 25px 50px rgba(0,0,0,.08)}
|
|
128
|
-
.icon{width:64px;height:64px;margin:0 auto 20px;background:#fef2f2;border-radius:16px;display:flex;align-items:center;justify-content:center;font-size:28px}
|
|
129
|
-
h2{font-size:22px;font-weight:700;margin-bottom:8px}p{color:#64748b;font-size:15px;line-height:1.6}
|
|
130
|
-
code{background:#f1f5f9;padding:2px 8px;border-radius:4px;font-size:13px}</style></head>
|
|
131
|
-
<body><div class="card"><div class="icon">❌</div><h2>Autorización fallida</h2>
|
|
108
|
+
: `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
109
|
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap" rel="stylesheet">
|
|
110
|
+
<style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Plus Jakarta Sans',sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#f8fafc,#fff1f2,#f8fafc);color:#0f172a}
|
|
111
|
+
.card{text-align:center;max-width:420px;padding:48px 32px;background:#fff;border-radius:20px;border:1px solid #e2e8f0;box-shadow:0 25px 50px rgba(0,0,0,.08)}
|
|
112
|
+
.icon{width:64px;height:64px;margin:0 auto 20px;background:#fef2f2;border-radius:16px;display:flex;align-items:center;justify-content:center;font-size:28px}
|
|
113
|
+
h2{font-size:22px;font-weight:700;margin-bottom:8px}p{color:#64748b;font-size:15px;line-height:1.6}
|
|
114
|
+
code{background:#f1f5f9;padding:2px 8px;border-radius:4px;font-size:13px}</style></head>
|
|
115
|
+
<body><div class="card"><div class="icon">❌</div><h2>Autorización fallida</h2>
|
|
132
116
|
<p>No se recibió el código. Ejecutá <code>npx @botuyo/mcp auth</code> de nuevo.</p></div></body></html>`;
|
|
133
117
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
134
118
|
res.end(html);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@ export interface BotuyoCredentials {
|
|
|
10
10
|
email: string;
|
|
11
11
|
savedAt: string;
|
|
12
12
|
expiresAt: string;
|
|
13
|
+
apiUrl?: string;
|
|
13
14
|
}
|
|
14
15
|
export declare function readCredentials(): Promise<BotuyoCredentials | null>;
|
|
15
16
|
export declare function saveCredentials(creds: BotuyoCredentials): Promise<void>;
|
|
@@ -27,8 +28,41 @@ export declare function resolveToken(): Promise<string | null>;
|
|
|
27
28
|
*/
|
|
28
29
|
export declare function isTokenExpired(): Promise<boolean>;
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* Resolve the API URL to use, in priority order:
|
|
32
|
+
* 1. BOTUYO_API_URL env var
|
|
33
|
+
* 2. apiUrl from ~/.botuyo/credentials.json
|
|
34
|
+
* 3. Default: https://api.botuyo.com
|
|
33
35
|
*/
|
|
34
|
-
export declare
|
|
36
|
+
export declare const DEFAULT_API_URL = "https://api.botuyo.com";
|
|
37
|
+
export declare function resolveApiUrl(): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Check if there is a valid existing session.
|
|
40
|
+
* Returns the credentials if valid, null if re-auth is needed.
|
|
41
|
+
*
|
|
42
|
+
* Logic:
|
|
43
|
+
* 1. No credentials on disk → null
|
|
44
|
+
* 2. Locally expired → clear + null
|
|
45
|
+
* 3. force=true → null (skip validation)
|
|
46
|
+
* 4. Server rejects token → clear + null
|
|
47
|
+
* 5. Network error → assume valid (return creds)
|
|
48
|
+
* 6. Valid → return creds
|
|
49
|
+
*/
|
|
50
|
+
export declare function checkExistingSession(apiUrl: string, force: boolean): Promise<BotuyoCredentials | null>;
|
|
51
|
+
export interface UserInfo {
|
|
52
|
+
tenantIds: string[];
|
|
53
|
+
roles: Array<{
|
|
54
|
+
tenantId: string;
|
|
55
|
+
role: string;
|
|
56
|
+
}>;
|
|
57
|
+
email: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Fetch the current user's info from GET /api/auth/me.
|
|
61
|
+
* Returns parsed UserInfo or throws on failure.
|
|
62
|
+
*/
|
|
63
|
+
export declare function fetchUserInfo(apiUrl: string, token: string): Promise<UserInfo>;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve a tenant's display name via GET /api/tenants/:id.
|
|
66
|
+
* Returns the name or the tenantId as fallback (never throws).
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveTenantName(apiUrl: string, token: string, tenantId: string): Promise<string>;
|
|
@@ -58,30 +58,98 @@ export async function isTokenExpired() {
|
|
|
58
58
|
return new Date(creds.expiresAt) < new Date();
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
61
|
+
* Resolve the API URL to use, in priority order:
|
|
62
|
+
* 1. BOTUYO_API_URL env var
|
|
63
|
+
* 2. apiUrl from ~/.botuyo/credentials.json
|
|
64
|
+
* 3. Default: https://api.botuyo.com
|
|
64
65
|
*/
|
|
65
|
-
export
|
|
66
|
-
|
|
66
|
+
export const DEFAULT_API_URL = 'https://api.botuyo.com';
|
|
67
|
+
export async function resolveApiUrl() {
|
|
68
|
+
if (process.env.BOTUYO_API_URL)
|
|
69
|
+
return process.env.BOTUYO_API_URL;
|
|
70
|
+
const creds = await readCredentials();
|
|
71
|
+
if (creds?.apiUrl)
|
|
72
|
+
return creds.apiUrl;
|
|
73
|
+
return DEFAULT_API_URL;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if there is a valid existing session.
|
|
77
|
+
* Returns the credentials if valid, null if re-auth is needed.
|
|
78
|
+
*
|
|
79
|
+
* Logic:
|
|
80
|
+
* 1. No credentials on disk → null
|
|
81
|
+
* 2. Locally expired → clear + null
|
|
82
|
+
* 3. force=true → null (skip validation)
|
|
83
|
+
* 4. Server rejects token → clear + null
|
|
84
|
+
* 5. Network error → assume valid (return creds)
|
|
85
|
+
* 6. Valid → return creds
|
|
86
|
+
*/
|
|
87
|
+
export async function checkExistingSession(apiUrl, force) {
|
|
88
|
+
const creds = await readCredentials();
|
|
89
|
+
if (!creds)
|
|
90
|
+
return null;
|
|
91
|
+
// Locally expired
|
|
92
|
+
if (creds.expiresAt && new Date(creds.expiresAt) < new Date()) {
|
|
93
|
+
await clearCredentials();
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
// Force re-auth
|
|
97
|
+
if (force)
|
|
98
|
+
return null;
|
|
99
|
+
// Verify with server
|
|
67
100
|
try {
|
|
68
|
-
const res = await fetch(`${
|
|
69
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
101
|
+
const res = await fetch(`${apiUrl}/api/auth/me`, {
|
|
102
|
+
headers: { Authorization: `Bearer ${creds.token}` }
|
|
70
103
|
});
|
|
71
104
|
if (!res.ok) {
|
|
72
105
|
await clearCredentials();
|
|
73
|
-
return
|
|
106
|
+
return null;
|
|
74
107
|
}
|
|
75
108
|
const body = await res.json();
|
|
76
109
|
if (!body.success) {
|
|
77
110
|
await clearCredentials();
|
|
78
|
-
return
|
|
111
|
+
return null;
|
|
79
112
|
}
|
|
80
|
-
return
|
|
113
|
+
return creds;
|
|
81
114
|
}
|
|
82
115
|
catch {
|
|
83
|
-
// Network error — don't clear
|
|
84
|
-
return
|
|
116
|
+
// Network error — don't clear, assume valid
|
|
117
|
+
return creds;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Fetch the current user's info from GET /api/auth/me.
|
|
122
|
+
* Returns parsed UserInfo or throws on failure.
|
|
123
|
+
*/
|
|
124
|
+
export async function fetchUserInfo(apiUrl, token) {
|
|
125
|
+
const res = await fetch(`${apiUrl}/api/auth/me`, {
|
|
126
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
127
|
+
});
|
|
128
|
+
const data = (await res.json());
|
|
129
|
+
if (!data.success) {
|
|
130
|
+
throw new Error(data.error || 'Failed to fetch user info');
|
|
131
|
+
}
|
|
132
|
+
const user = data.data.user;
|
|
133
|
+
return {
|
|
134
|
+
tenantIds: user.tenantIds || [],
|
|
135
|
+
roles: user.roles || [],
|
|
136
|
+
email: user.email
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Resolve a tenant's display name via GET /api/tenants/:id.
|
|
141
|
+
* Returns the name or the tenantId as fallback (never throws).
|
|
142
|
+
*/
|
|
143
|
+
export async function resolveTenantName(apiUrl, token, tenantId) {
|
|
144
|
+
try {
|
|
145
|
+
const res = await fetch(`${apiUrl}/api/tenants/${tenantId}`, {
|
|
146
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
147
|
+
});
|
|
148
|
+
const data = (await res.json());
|
|
149
|
+
return data.data?.name || data.data?.tenant?.name || tenantId;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return tenantId;
|
|
85
153
|
}
|
|
86
154
|
}
|
|
87
155
|
//# sourceMappingURL=credentials.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/commands/credentials.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAA;AAE5B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAA;AAC7C,MAAM,gBAAgB,GAAG,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/commands/credentials.ts"],"names":[],"mappings":"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"}
|