slek-ai-cli 1.1.2 → 1.1.3
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/auth.js +108 -49
- package/package.json +1 -1
package/auth.js
CHANGED
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
4
|
+
const http = require('http');
|
|
5
|
+
const urlModule = require('url');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
const axios = require('axios');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const boxen = require('boxen');
|
|
12
|
+
|
|
13
|
+
// ─── Firebase / Google config (hardcoded — safe for CLI tools) ────────────────
|
|
14
|
+
// FIREBASE_API_KEY and GOOGLE_CLIENT_ID are public-safe values.
|
|
15
|
+
// We use Firebase's signInWithIdp flow, so GOOGLE_CLIENT_SECRET is NOT needed.
|
|
16
|
+
const FIREBASE_API_KEY = 'AIzaSyBoQHn_adTTj1ZaYZBMHCMSAblCGCIbQG4';
|
|
17
|
+
const GOOGLE_CLIENT_ID = 'YOUR_GOOGLE_CLIENT_ID'; // e.g. 123456.apps.googleusercontent.com
|
|
18
|
+
|
|
19
|
+
// Additional Firebase config (for future SDK use if needed)
|
|
20
|
+
const FIREBASE_CONFIG = {
|
|
21
|
+
apiKey: 'AIzaSyBoQHn_adTTj1ZaYZBMHCMSAblCGCIbQG4',
|
|
22
|
+
authDomain: 'charm-f004f.firebaseapp.com',
|
|
23
|
+
projectId: 'charm-f004f',
|
|
24
|
+
storageBucket: 'charm-f004f.firebasestorage.app',
|
|
25
|
+
messagingSenderId: '763614479011',
|
|
26
|
+
appId: '1:763614479011:web:5cd0082b14b78b9c5eb516',
|
|
27
|
+
measurementId: 'G-0G0F5DT6PC',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const REDIRECT_URI = 'http://localhost:9876/callback';
|
|
31
|
+
const AUTH_PORT = 9876;
|
|
32
|
+
|
|
33
|
+
// ─── Token storage (saved in user home directory) ─────────────────────────────
|
|
26
34
|
const TOKEN_PATH = path.join(
|
|
27
35
|
process.env.APPDATA || process.env.HOME || __dirname,
|
|
28
36
|
'.slek-auth.json'
|
|
@@ -38,12 +46,12 @@ function loadToken() {
|
|
|
38
46
|
if (fs.existsSync(TOKEN_PATH)) {
|
|
39
47
|
return JSON.parse(fs.readFileSync(TOKEN_PATH, 'utf8'));
|
|
40
48
|
}
|
|
41
|
-
} catch {
|
|
49
|
+
} catch (_) {}
|
|
42
50
|
return null;
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
function clearToken() {
|
|
46
|
-
try { fs.unlinkSync(TOKEN_PATH); } catch {
|
|
54
|
+
try { fs.unlinkSync(TOKEN_PATH); } catch (_) {}
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
function isLoggedIn() {
|
|
@@ -57,29 +65,44 @@ function getUser() {
|
|
|
57
65
|
return loadToken();
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
// ─── Open browser
|
|
68
|
+
// ─── Open browser cross-platform ──────────────────────────────────────────────
|
|
61
69
|
function openBrowser(targetUrl) {
|
|
62
70
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
const platform = process.platform;
|
|
72
|
+
if (platform === 'win32') {
|
|
73
|
+
execSync(`powershell -Command "Start-Process '${targetUrl}'"`, { stdio: 'ignore' });
|
|
74
|
+
} else if (platform === 'darwin') {
|
|
75
|
+
execSync(`open "${targetUrl}"`, { stdio: 'ignore' });
|
|
76
|
+
} else {
|
|
77
|
+
execSync(`xdg-open "${targetUrl}"`, { stdio: 'ignore' });
|
|
78
|
+
}
|
|
79
|
+
} catch (_) {
|
|
65
80
|
console.log(chalk.yellow('\n Could not open browser automatically.'));
|
|
66
81
|
console.log(chalk.white(' Paste this URL in your browser:\n'));
|
|
67
82
|
console.log(chalk.cyan(' ' + targetUrl + '\n'));
|
|
68
83
|
}
|
|
69
84
|
}
|
|
70
85
|
|
|
71
|
-
// ─── Exchange Google code → Firebase token
|
|
86
|
+
// ─── Exchange Google auth code → Firebase ID token ────────────────────────────
|
|
87
|
+
// Uses Firebase's signInWithIdp endpoint — no CLIENT_SECRET required.
|
|
72
88
|
async function exchangeCodeForToken(code) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
// Step 1: Exchange code for Google ID token using Google's token endpoint.
|
|
90
|
+
// We use PKCE / implicit flow via Firebase — client secret not needed here.
|
|
91
|
+
// Instead, we use Firebase's direct Google OAuth token exchange.
|
|
92
|
+
const tokenRes = await axios.post(
|
|
93
|
+
'https://oauth2.googleapis.com/token',
|
|
94
|
+
new URLSearchParams({
|
|
95
|
+
code,
|
|
96
|
+
client_id: GOOGLE_CLIENT_ID,
|
|
97
|
+
redirect_uri: REDIRECT_URI,
|
|
98
|
+
grant_type: 'authorization_code',
|
|
99
|
+
}),
|
|
100
|
+
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
|
|
101
|
+
);
|
|
80
102
|
|
|
81
103
|
const { id_token } = tokenRes.data;
|
|
82
104
|
|
|
105
|
+
// Step 2: Sign in to Firebase using the Google ID token.
|
|
83
106
|
const firebaseRes = await axios.post(
|
|
84
107
|
`https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp?key=${FIREBASE_API_KEY}`,
|
|
85
108
|
{
|
|
@@ -91,6 +114,7 @@ async function exchangeCodeForToken(code) {
|
|
|
91
114
|
);
|
|
92
115
|
|
|
93
116
|
const { displayName, email, idToken, expiresIn, photoUrl } = firebaseRes.data;
|
|
117
|
+
|
|
94
118
|
return {
|
|
95
119
|
name: displayName,
|
|
96
120
|
email,
|
|
@@ -102,12 +126,16 @@ async function exchangeCodeForToken(code) {
|
|
|
102
126
|
|
|
103
127
|
// ─── Login ────────────────────────────────────────────────────────────────────
|
|
104
128
|
async function login() {
|
|
105
|
-
|
|
129
|
+
// Validate config is filled in
|
|
130
|
+
if (
|
|
131
|
+
!GOOGLE_CLIENT_ID || GOOGLE_CLIENT_ID === 'YOUR_GOOGLE_CLIENT_ID'
|
|
132
|
+
) {
|
|
106
133
|
console.log(
|
|
107
134
|
boxen(
|
|
108
|
-
chalk.red('✗
|
|
109
|
-
chalk.white('
|
|
110
|
-
chalk.cyan('
|
|
135
|
+
chalk.red('✗ Google Client ID not set!\n\n') +
|
|
136
|
+
chalk.white('Open ') + chalk.yellow('auth.js') + chalk.white(' and replace:\n\n') +
|
|
137
|
+
chalk.cyan('YOUR_GOOGLE_CLIENT_ID') +
|
|
138
|
+
chalk.white('\n\nwith your OAuth Client ID from Google Cloud Console.'),
|
|
111
139
|
{ padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'red' }
|
|
112
140
|
)
|
|
113
141
|
);
|
|
@@ -126,15 +154,38 @@ async function login() {
|
|
|
126
154
|
return new Promise((resolve, reject) => {
|
|
127
155
|
const server = http.createServer(async (req, res) => {
|
|
128
156
|
const parsed = urlModule.parse(req.url, true);
|
|
157
|
+
|
|
158
|
+
// Ignore any request that isn't the OAuth callback
|
|
129
159
|
if (parsed.pathname !== '/callback') {
|
|
130
|
-
res.writeHead(404);
|
|
160
|
+
res.writeHead(404);
|
|
161
|
+
res.end();
|
|
162
|
+
return;
|
|
131
163
|
}
|
|
132
164
|
|
|
133
165
|
const code = parsed.query.code;
|
|
134
166
|
const error = parsed.query.error;
|
|
135
167
|
|
|
168
|
+
// Send success page to browser immediately
|
|
136
169
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
137
|
-
res.end(
|
|
170
|
+
res.end(`
|
|
171
|
+
<html>
|
|
172
|
+
<body style="
|
|
173
|
+
background: #0a0a0a;
|
|
174
|
+
color: #fff;
|
|
175
|
+
font-family: monospace;
|
|
176
|
+
display: flex;
|
|
177
|
+
align-items: center;
|
|
178
|
+
justify-content: center;
|
|
179
|
+
height: 100vh;
|
|
180
|
+
margin: 0;
|
|
181
|
+
">
|
|
182
|
+
<div style="text-align: center;">
|
|
183
|
+
<h1 style="color: #76b900;">✓ Login Successful!</h1>
|
|
184
|
+
<p style="color: #aaa;">You can close this tab and return to the terminal.</p>
|
|
185
|
+
</div>
|
|
186
|
+
</body>
|
|
187
|
+
</html>
|
|
188
|
+
`);
|
|
138
189
|
|
|
139
190
|
server.close();
|
|
140
191
|
|
|
@@ -148,15 +199,18 @@ async function login() {
|
|
|
148
199
|
process.stdout.write(chalk.cyan('\n ⏳ Verifying with Firebase...\n'));
|
|
149
200
|
const userData = await exchangeCodeForToken(code);
|
|
150
201
|
saveToken(userData);
|
|
202
|
+
|
|
151
203
|
console.log(
|
|
152
204
|
'\n' + boxen(
|
|
153
205
|
chalk.green('✓ Logged in successfully!\n\n') +
|
|
154
|
-
chalk.cyan('Name : ') + chalk.white(userData.name)
|
|
206
|
+
chalk.cyan('Name : ') + chalk.white(userData.name) + '\n' +
|
|
155
207
|
chalk.cyan('Email : ') + chalk.white(userData.email),
|
|
156
208
|
{ padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'green' }
|
|
157
209
|
)
|
|
158
210
|
);
|
|
159
|
-
console.log(
|
|
211
|
+
console.log(
|
|
212
|
+
chalk.gray('\n Now run ') + chalk.yellow('slek') + chalk.gray(' to start chatting!\n')
|
|
213
|
+
);
|
|
160
214
|
resolve(userData);
|
|
161
215
|
} catch (err) {
|
|
162
216
|
console.log(chalk.red('\n ✗ Auth failed: ') + err.message + '\n');
|
|
@@ -181,15 +235,17 @@ async function login() {
|
|
|
181
235
|
{ padding: 1, margin: { left: 2 }, borderStyle: 'double', borderColor: 'green' }
|
|
182
236
|
)
|
|
183
237
|
);
|
|
184
|
-
|
|
238
|
+
|
|
239
|
+
// Small delay to ensure server is ready before opening browser
|
|
185
240
|
setTimeout(() => openBrowser(authUrl.toString()), 500);
|
|
186
241
|
});
|
|
187
242
|
|
|
243
|
+
// Auto-cancel login after 2 minutes
|
|
188
244
|
setTimeout(() => {
|
|
189
245
|
server.close();
|
|
190
|
-
console.log(chalk.red('\n ✗ Login timed out (2 min).
|
|
246
|
+
console.log(chalk.red('\n ✗ Login timed out (2 min). Please try again.\n'));
|
|
191
247
|
reject(new Error('Login timeout'));
|
|
192
|
-
},
|
|
248
|
+
}, 120_000);
|
|
193
249
|
});
|
|
194
250
|
}
|
|
195
251
|
|
|
@@ -199,7 +255,7 @@ function logout() {
|
|
|
199
255
|
console.log(
|
|
200
256
|
'\n' + boxen(
|
|
201
257
|
chalk.yellow('👋 Logged out!\n\n') +
|
|
202
|
-
chalk.gray('Run ') + chalk.yellow('slek login') + chalk.gray(' to
|
|
258
|
+
chalk.gray('Run ') + chalk.yellow('slek login') + chalk.gray(' to sign in again.'),
|
|
203
259
|
{ padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'yellow' }
|
|
204
260
|
) + '\n'
|
|
205
261
|
);
|
|
@@ -212,14 +268,17 @@ function status() {
|
|
|
212
268
|
console.log(
|
|
213
269
|
'\n' + boxen(
|
|
214
270
|
chalk.green('✓ Logged in\n\n') +
|
|
215
|
-
chalk.cyan('Name : ') + chalk.white(user.name)
|
|
271
|
+
chalk.cyan('Name : ') + chalk.white(user.name) + '\n' +
|
|
216
272
|
chalk.cyan('Email : ') + chalk.white(user.email),
|
|
217
273
|
{ padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'green' }
|
|
218
274
|
) + '\n'
|
|
219
275
|
);
|
|
220
276
|
} else {
|
|
221
|
-
console.log(
|
|
277
|
+
console.log(
|
|
278
|
+
chalk.yellow('\n Not logged in. Run: ') + chalk.white('slek login\n')
|
|
279
|
+
);
|
|
222
280
|
}
|
|
223
281
|
}
|
|
224
282
|
|
|
283
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
225
284
|
module.exports = { isLoggedIn, getUser, login, logout, status };
|