@botuyo/mcp 0.1.0 → 0.2.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.
- package/dist/client.d.ts +9 -14
- package/dist/client.js +34 -29
- package/dist/client.js.map +1 -1
- package/dist/commands/auth.d.ts +18 -0
- package/dist/commands/auth.js +151 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/credentials.d.ts +28 -0
- package/dist/commands/credentials.js +59 -0
- package/dist/commands/credentials.js.map +1 -0
- package/dist/commands/login.d.ts +15 -0
- package/dist/commands/login.js +202 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/setup.d.ts +8 -0
- package/dist/commands/setup.js +95 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/switch_tenant.d.ts +10 -0
- package/dist/commands/switch_tenant.js +117 -0
- package/dist/commands/switch_tenant.js.map +1 -0
- package/dist/commands/tenants.d.ts +9 -0
- package/dist/commands/tenants.js +48 -0
- package/dist/commands/tenants.js.map +1 -0
- package/dist/index.d.ts +13 -14
- package/dist/index.js +120 -57
- package/dist/index.js.map +1 -1
- package/package.json +12 -3
package/dist/client.d.ts
CHANGED
|
@@ -1,36 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BotuyoApiClient - Thin HTTP wrapper around the BotUyo REST API
|
|
3
3
|
* All MCP tools go through this client.
|
|
4
|
+
*
|
|
5
|
+
* Uses a JWT token directly (obtained via `npx @botuyo/mcp login`).
|
|
4
6
|
*/
|
|
5
7
|
export interface BotuyoClientConfig {
|
|
6
8
|
apiUrl: string;
|
|
7
|
-
apiKey: string;
|
|
8
|
-
}
|
|
9
|
-
export interface AuthResult {
|
|
10
9
|
token: string;
|
|
10
|
+
}
|
|
11
|
+
export interface AuthInfo {
|
|
11
12
|
tenantId: string;
|
|
12
13
|
tenantName: string;
|
|
13
14
|
role: string;
|
|
14
|
-
|
|
15
|
-
channels: Array<{
|
|
16
|
-
type: string;
|
|
17
|
-
status: string;
|
|
18
|
-
}>;
|
|
19
|
-
adminPanelUrl: string;
|
|
15
|
+
email: string;
|
|
20
16
|
}
|
|
21
17
|
export declare class BotuyoApiClient {
|
|
22
18
|
private config;
|
|
23
|
-
private
|
|
24
|
-
private authResult;
|
|
25
|
-
private tokenExpiresAt;
|
|
19
|
+
private authInfo;
|
|
26
20
|
constructor(config: BotuyoClientConfig);
|
|
27
|
-
/**
|
|
28
|
-
|
|
21
|
+
/** Verify the stored JWT is still valid by calling GET /api/auth/me */
|
|
22
|
+
verify(): Promise<AuthInfo>;
|
|
29
23
|
/** Make an authenticated GET request */
|
|
30
24
|
get<T = any>(path: string): Promise<T>;
|
|
31
25
|
/** Make an authenticated POST request */
|
|
32
26
|
post<T = any>(path: string, data: unknown): Promise<T>;
|
|
33
27
|
/** Make an authenticated PUT request */
|
|
34
28
|
put<T = any>(path: string, data: unknown): Promise<T>;
|
|
29
|
+
private handleResponse;
|
|
35
30
|
private parseJson;
|
|
36
31
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,71 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BotuyoApiClient - Thin HTTP wrapper around the BotUyo REST API
|
|
3
3
|
* All MCP tools go through this client.
|
|
4
|
+
*
|
|
5
|
+
* Uses a JWT token directly (obtained via `npx @botuyo/mcp login`).
|
|
4
6
|
*/
|
|
5
7
|
import fetch from 'node-fetch';
|
|
6
8
|
export class BotuyoApiClient {
|
|
7
9
|
config;
|
|
8
|
-
|
|
9
|
-
authResult = null;
|
|
10
|
-
tokenExpiresAt = 0;
|
|
10
|
+
authInfo = null;
|
|
11
11
|
constructor(config) {
|
|
12
12
|
this.config = config;
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
15
|
-
async
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const res = await fetch(`${this.config.apiUrl}/api/v1/mcp/auth`, {
|
|
21
|
-
method: 'POST',
|
|
22
|
-
headers: {
|
|
23
|
-
'x-api-key': this.config.apiKey,
|
|
24
|
-
'Content-Type': 'application/json'
|
|
25
|
-
}
|
|
14
|
+
/** Verify the stored JWT is still valid by calling GET /api/auth/me */
|
|
15
|
+
async verify() {
|
|
16
|
+
if (this.authInfo)
|
|
17
|
+
return this.authInfo;
|
|
18
|
+
const res = await fetch(`${this.config.apiUrl}/api/auth/me`, {
|
|
19
|
+
headers: { Authorization: `Bearer ${this.config.token}` }
|
|
26
20
|
});
|
|
27
21
|
const body = await this.parseJson(res);
|
|
28
22
|
if (!body.success) {
|
|
29
|
-
throw new Error(`
|
|
23
|
+
throw new Error(`Session expired or invalid: ${body.error || 'Unknown error'}. Run: npx @botuyo/mcp login`);
|
|
30
24
|
}
|
|
31
|
-
|
|
32
|
-
this.
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
const user = body.data.user;
|
|
26
|
+
this.authInfo = {
|
|
27
|
+
tenantId: user.tenantIds?.[0] || '',
|
|
28
|
+
tenantName: '', // Will be enriched from credentials
|
|
29
|
+
role: user.roles?.[0]?.role || 'member',
|
|
30
|
+
email: user.email
|
|
31
|
+
};
|
|
32
|
+
return this.authInfo;
|
|
35
33
|
}
|
|
36
34
|
/** Make an authenticated GET request */
|
|
37
35
|
async get(path) {
|
|
38
|
-
await this.authenticate();
|
|
39
36
|
const res = await fetch(`${this.config.apiUrl}${path}`, {
|
|
40
|
-
headers: { Authorization: `Bearer ${this.token}` }
|
|
37
|
+
headers: { Authorization: `Bearer ${this.config.token}` }
|
|
41
38
|
});
|
|
42
|
-
return this.
|
|
39
|
+
return this.handleResponse(res);
|
|
43
40
|
}
|
|
44
41
|
/** Make an authenticated POST request */
|
|
45
42
|
async post(path, data) {
|
|
46
|
-
await this.authenticate();
|
|
47
43
|
const res = await fetch(`${this.config.apiUrl}${path}`, {
|
|
48
44
|
method: 'POST',
|
|
49
45
|
headers: {
|
|
50
|
-
Authorization: `Bearer ${this.token}`,
|
|
46
|
+
Authorization: `Bearer ${this.config.token}`,
|
|
51
47
|
'Content-Type': 'application/json'
|
|
52
48
|
},
|
|
53
49
|
body: JSON.stringify(data)
|
|
54
50
|
});
|
|
55
|
-
return this.
|
|
51
|
+
return this.handleResponse(res);
|
|
56
52
|
}
|
|
57
53
|
/** Make an authenticated PUT request */
|
|
58
54
|
async put(path, data) {
|
|
59
|
-
await this.authenticate();
|
|
60
55
|
const res = await fetch(`${this.config.apiUrl}${path}`, {
|
|
61
56
|
method: 'PUT',
|
|
62
57
|
headers: {
|
|
63
|
-
Authorization: `Bearer ${this.token}`,
|
|
58
|
+
Authorization: `Bearer ${this.config.token}`,
|
|
64
59
|
'Content-Type': 'application/json'
|
|
65
60
|
},
|
|
66
61
|
body: JSON.stringify(data)
|
|
67
62
|
});
|
|
68
|
-
return this.
|
|
63
|
+
return this.handleResponse(res);
|
|
64
|
+
}
|
|
65
|
+
async handleResponse(res) {
|
|
66
|
+
const json = await this.parseJson(res);
|
|
67
|
+
// Detect expired token
|
|
68
|
+
if (res.status === 401) {
|
|
69
|
+
throw new Error('Sesión expirada. Ejecutá: npx @botuyo/mcp login');
|
|
70
|
+
}
|
|
71
|
+
return json;
|
|
69
72
|
}
|
|
70
73
|
async parseJson(res) {
|
|
71
74
|
const text = await res.text();
|
|
@@ -77,6 +80,8 @@ export class BotuyoApiClient {
|
|
|
77
80
|
return json;
|
|
78
81
|
}
|
|
79
82
|
catch (e) {
|
|
83
|
+
if (e instanceof Error && e.message.includes('HTTP'))
|
|
84
|
+
throw e;
|
|
80
85
|
throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
81
86
|
}
|
|
82
87
|
}
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @botuyo/mcp auth command (Browser OAuth flow)
|
|
4
|
+
*
|
|
5
|
+
* Usage: npx @botuyo/mcp auth
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Starts a local HTTP server on a random port
|
|
9
|
+
* 2. Opens admin.botuyo.com/mcp-auth?redirect=http://localhost:PORT
|
|
10
|
+
* 3. The admin panel authenticates the user (login/Google),
|
|
11
|
+
* calls POST /api/v1/mcp/oauth/authorize, then redirects to localhost with ?code=...
|
|
12
|
+
* 4. CLI exchanges the code for an API key (POST /api/v1/mcp/oauth/token)
|
|
13
|
+
* 5. Uses the API key to get a JWT (POST /api/v1/mcp/auth)
|
|
14
|
+
* 6. Saves the JWT to ~/.botuyo/credentials.json
|
|
15
|
+
*
|
|
16
|
+
* For terminal-only login (email/password), use `npx @botuyo/mcp login` instead.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runAuth(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @botuyo/mcp auth command (Browser OAuth flow)
|
|
4
|
+
*
|
|
5
|
+
* Usage: npx @botuyo/mcp auth
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Starts a local HTTP server on a random port
|
|
9
|
+
* 2. Opens admin.botuyo.com/mcp-auth?redirect=http://localhost:PORT
|
|
10
|
+
* 3. The admin panel authenticates the user (login/Google),
|
|
11
|
+
* calls POST /api/v1/mcp/oauth/authorize, then redirects to localhost with ?code=...
|
|
12
|
+
* 4. CLI exchanges the code for an API key (POST /api/v1/mcp/oauth/token)
|
|
13
|
+
* 5. Uses the API key to get a JWT (POST /api/v1/mcp/auth)
|
|
14
|
+
* 6. Saves the JWT to ~/.botuyo/credentials.json
|
|
15
|
+
*
|
|
16
|
+
* For terminal-only login (email/password), use `npx @botuyo/mcp login` instead.
|
|
17
|
+
*/
|
|
18
|
+
import http from 'http';
|
|
19
|
+
import { URL } from 'url';
|
|
20
|
+
import { execSync } from 'child_process';
|
|
21
|
+
import { readCredentials, saveCredentials } from './credentials.js';
|
|
22
|
+
import os from 'os';
|
|
23
|
+
const ADMIN_URL = process.env.BOTUYO_ADMIN_URL || 'https://admin.botuyo.com';
|
|
24
|
+
const API_URL = process.env.BOTUYO_API_URL || 'https://api.botuyo.com';
|
|
25
|
+
export async function runAuth(args) {
|
|
26
|
+
console.log('\n🤖 BotUyo MCP — Autenticación por Browser\n');
|
|
27
|
+
const existing = await readCredentials();
|
|
28
|
+
if (existing && !args.includes('--force')) {
|
|
29
|
+
const expired = existing.expiresAt && new Date(existing.expiresAt) < new Date();
|
|
30
|
+
if (!expired) {
|
|
31
|
+
console.log(`✅ Ya estás autenticado como: ${existing.email || 'usuario'}`);
|
|
32
|
+
console.log(` Tenant: ${existing.tenantName} (${existing.role})`);
|
|
33
|
+
console.log('\n Usá --force para re-autenticarte.');
|
|
34
|
+
console.log(' O usá `npx @botuyo/mcp login` para login por terminal.\n');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.log('⚠️ Tu sesión expiró. Vamos a re-autenticarte.\n');
|
|
38
|
+
}
|
|
39
|
+
// Start local callback server
|
|
40
|
+
const { port, waitForCode } = await startCallbackServer();
|
|
41
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
42
|
+
const deviceName = encodeURIComponent(`${os.hostname()} CLI`);
|
|
43
|
+
const authUrl = `${ADMIN_URL}/mcp-auth?redirect=${encodeURIComponent(redirectUri)}&device=${deviceName}`;
|
|
44
|
+
console.log(`📋 Abriendo browser para autorizar...\n ${authUrl}\n`);
|
|
45
|
+
openBrowser(authUrl);
|
|
46
|
+
console.log('Esperando autorización (10 minutos máximo)...\n');
|
|
47
|
+
const code = await waitForCode;
|
|
48
|
+
if (!code) {
|
|
49
|
+
console.error('❌ La autorización expiró o fue cancelada.');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
console.log('✅ Código de autorización recibido! Intercambiando por credenciales...');
|
|
53
|
+
// Exchange code for API key
|
|
54
|
+
const tokenRes = await fetch(`${API_URL}/api/v1/mcp/oauth/token`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: { 'Content-Type': 'application/json' },
|
|
57
|
+
body: JSON.stringify({ code, name: `${os.hostname()} CLI` })
|
|
58
|
+
});
|
|
59
|
+
const tokenData = (await tokenRes.json());
|
|
60
|
+
if (!tokenData.success) {
|
|
61
|
+
console.error(`❌ Intercambio de token fallido: ${tokenData.error}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const apiKey = tokenData.data.key;
|
|
65
|
+
const tenantId = tokenData.data.tenantId;
|
|
66
|
+
const tenantName = tokenData.data.tenantName;
|
|
67
|
+
const role = tokenData.data.role;
|
|
68
|
+
// Now use the API key to get a JWT
|
|
69
|
+
console.log('⏳ Obteniendo sesión JWT...');
|
|
70
|
+
const authRes = await fetch(`${API_URL}/api/v1/mcp/auth`, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
'x-api-key': apiKey,
|
|
74
|
+
'Content-Type': 'application/json'
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
const authData = (await authRes.json());
|
|
78
|
+
if (!authData.success) {
|
|
79
|
+
console.error(`❌ Error al obtener JWT: ${authData.error}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
const creds = {
|
|
83
|
+
token: authData.data.token,
|
|
84
|
+
tenantId,
|
|
85
|
+
tenantName: tenantName || tenantId,
|
|
86
|
+
role: role || authData.data.role || 'viewer',
|
|
87
|
+
email: authData.data.callerEmail || '',
|
|
88
|
+
savedAt: new Date().toISOString(),
|
|
89
|
+
expiresAt: new Date(Date.now() + 1 * 60 * 60 * 1000).toISOString() // JWT from MCP auth = 1h
|
|
90
|
+
};
|
|
91
|
+
await saveCredentials(creds);
|
|
92
|
+
console.log(`\n✅ ¡Autenticado exitosamente!`);
|
|
93
|
+
console.log(` Tenant: ${creds.tenantName}`);
|
|
94
|
+
console.log(` Role: ${creds.role}`);
|
|
95
|
+
console.log(` Email: ${creds.email || '(no disponible)'}`);
|
|
96
|
+
console.log(`\n🚀 Ya podés usar el servidor MCP de BotUyo.\n`);
|
|
97
|
+
}
|
|
98
|
+
// ── Local callback server ─────────────────────────────────────────────────────
|
|
99
|
+
function startCallbackServer() {
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
let resolveCode;
|
|
102
|
+
const waitForCode = new Promise((res) => {
|
|
103
|
+
resolveCode = res;
|
|
104
|
+
});
|
|
105
|
+
const server = http.createServer((req, res) => {
|
|
106
|
+
const url = new URL(req.url || '/', `http://localhost`);
|
|
107
|
+
if (url.pathname === '/callback') {
|
|
108
|
+
const code = url.searchParams.get('code');
|
|
109
|
+
const html = code
|
|
110
|
+
? `<html><body style="font-family:sans-serif;text-align:center;padding:80px">
|
|
111
|
+
<h2>✅ BotUyo CLI Autorizado</h2>
|
|
112
|
+
<p>Podés cerrar esta pestaña y volver a la terminal.</p>
|
|
113
|
+
</body></html>`
|
|
114
|
+
: `<html><body style="font-family:sans-serif;text-align:center;padding:80px">
|
|
115
|
+
<h2>❌ Autorización fallida</h2>
|
|
116
|
+
<p>No se recibió el código. Intentá de nuevo.</p>
|
|
117
|
+
</body></html>`;
|
|
118
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
119
|
+
res.end(html);
|
|
120
|
+
server.close();
|
|
121
|
+
resolveCode(code);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
res.writeHead(404);
|
|
125
|
+
res.end();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
server.listen(0, 'localhost', () => {
|
|
129
|
+
const port = server.address().port;
|
|
130
|
+
// Timeout after 10 minutes
|
|
131
|
+
setTimeout(() => { server.close(); resolveCode(null); }, 10 * 60 * 1000);
|
|
132
|
+
resolve({ port, waitForCode });
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// ── Open browser cross-platform ───────────────────────────────────────────────
|
|
137
|
+
function openBrowser(url) {
|
|
138
|
+
const platform = process.platform;
|
|
139
|
+
try {
|
|
140
|
+
if (platform === 'win32')
|
|
141
|
+
execSync(`start "" "${url}"`, { stdio: 'ignore' });
|
|
142
|
+
else if (platform === 'darwin')
|
|
143
|
+
execSync(`open "${url}"`, { stdio: 'ignore' });
|
|
144
|
+
else
|
|
145
|
+
execSync(`xdg-open "${url}"`, { stdio: 'ignore' });
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
console.log(` No se pudo abrir el browser automáticamente. Visitá:\n ${url}\n`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage for @botuyo/mcp
|
|
3
|
+
* Saves/reads JWT tokens to/from ~/.botuyo/credentials.json
|
|
4
|
+
*/
|
|
5
|
+
export interface BotuyoCredentials {
|
|
6
|
+
token: string;
|
|
7
|
+
tenantId: string;
|
|
8
|
+
tenantName: string;
|
|
9
|
+
role: string;
|
|
10
|
+
email: string;
|
|
11
|
+
savedAt: string;
|
|
12
|
+
expiresAt: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function readCredentials(): Promise<BotuyoCredentials | null>;
|
|
15
|
+
export declare function saveCredentials(creds: BotuyoCredentials): Promise<void>;
|
|
16
|
+
export declare function clearCredentials(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the JWT token to use, in priority order:
|
|
19
|
+
* 1. BOTUYO_TOKEN env var
|
|
20
|
+
* 2. ~/.botuyo/credentials.json
|
|
21
|
+
*
|
|
22
|
+
* Returns null if no token or if expired.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveToken(): Promise<string | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Check if stored credentials are expired
|
|
27
|
+
*/
|
|
28
|
+
export declare function isTokenExpired(): Promise<boolean>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage for @botuyo/mcp
|
|
3
|
+
* Saves/reads JWT tokens to/from ~/.botuyo/credentials.json
|
|
4
|
+
*/
|
|
5
|
+
import { readFile, writeFile, mkdir, unlink } from 'fs/promises';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
const CONFIG_DIR = join(homedir(), '.botuyo');
|
|
9
|
+
const CREDENTIALS_FILE = join(CONFIG_DIR, 'credentials.json');
|
|
10
|
+
export async function readCredentials() {
|
|
11
|
+
try {
|
|
12
|
+
const raw = await readFile(CREDENTIALS_FILE, 'utf8');
|
|
13
|
+
return JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function saveCredentials(creds) {
|
|
20
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
21
|
+
await writeFile(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), 'utf8');
|
|
22
|
+
}
|
|
23
|
+
export async function clearCredentials() {
|
|
24
|
+
try {
|
|
25
|
+
await unlink(CREDENTIALS_FILE);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// already gone
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the JWT token to use, in priority order:
|
|
33
|
+
* 1. BOTUYO_TOKEN env var
|
|
34
|
+
* 2. ~/.botuyo/credentials.json
|
|
35
|
+
*
|
|
36
|
+
* Returns null if no token or if expired.
|
|
37
|
+
*/
|
|
38
|
+
export async function resolveToken() {
|
|
39
|
+
if (process.env.BOTUYO_TOKEN)
|
|
40
|
+
return process.env.BOTUYO_TOKEN;
|
|
41
|
+
const creds = await readCredentials();
|
|
42
|
+
if (!creds?.token)
|
|
43
|
+
return null;
|
|
44
|
+
// Check if expired
|
|
45
|
+
if (creds.expiresAt && new Date(creds.expiresAt) < new Date()) {
|
|
46
|
+
return null; // expired
|
|
47
|
+
}
|
|
48
|
+
return creds.token;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check if stored credentials are expired
|
|
52
|
+
*/
|
|
53
|
+
export async function isTokenExpired() {
|
|
54
|
+
const creds = await readCredentials();
|
|
55
|
+
if (!creds?.expiresAt)
|
|
56
|
+
return true;
|
|
57
|
+
return new Date(creds.expiresAt) < new Date();
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +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;AAY7D,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAA;QACpD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAA;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAwB;IAC5D,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC5C,MAAM,SAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;AAC3E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAA;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAA;IAE7D,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAA;IACrC,IAAI,CAAC,KAAK,EAAE,KAAK;QAAE,OAAO,IAAI,CAAA;IAE9B,mBAAmB;IACnB,IAAI,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;QAC9D,OAAO,IAAI,CAAA,CAAC,UAAU;IACxB,CAAC;IAED,OAAO,KAAK,CAAC,KAAK,CAAA;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAA;IACrC,IAAI,CAAC,KAAK,EAAE,SAAS;QAAE,OAAO,IAAI,CAAA;IAClC,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI,IAAI,EAAE,CAAA;AAC/C,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @botuyo/mcp login command
|
|
4
|
+
*
|
|
5
|
+
* Usage: npx @botuyo/mcp login
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Prompts for email and password in the terminal
|
|
9
|
+
* 2. Calls POST /api/auth/login to get a JWT
|
|
10
|
+
* 3. Calls GET /api/auth/me to get the user's tenants
|
|
11
|
+
* 4. If multiple tenants, asks the user to pick one
|
|
12
|
+
* 5. If needed, calls POST /api/auth/switch-tenant
|
|
13
|
+
* 6. Saves the credentials to ~/.botuyo/credentials.json
|
|
14
|
+
*/
|
|
15
|
+
export declare function runLogin(args: string[]): Promise<void>;
|