slek-ai-cli 1.1.4 → 1.1.6
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 +1 -2
- package/cli.js +89 -163
- package/package.json +1 -1
- package/.env +0 -18
package/auth.js
CHANGED
|
@@ -14,8 +14,7 @@ const boxen = require('boxen');
|
|
|
14
14
|
// FIREBASE_API_KEY and GOOGLE_CLIENT_ID are public-safe values.
|
|
15
15
|
// We use Firebase's signInWithIdp flow, so GOOGLE_CLIENT_SECRET is NOT needed.
|
|
16
16
|
const FIREBASE_API_KEY = 'AIzaSyBoQHn_adTTj1ZaYZBMHCMSAblCGCIbQG4';
|
|
17
|
-
const GOOGLE_CLIENT_ID = '
|
|
18
|
-
'; // e.g. 123456.apps.googleusercontent.com
|
|
17
|
+
const GOOGLE_CLIENT_ID = 'YOUR_GOOGLE_CLIENT_ID'; // e.g. 123456.apps.googleusercontent.com
|
|
19
18
|
|
|
20
19
|
// Additional Firebase config (for future SDK use if needed)
|
|
21
20
|
const FIREBASE_CONFIG = {
|
package/cli.js
CHANGED
|
@@ -1,28 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
path: require('path').join(__dirname, '.env')
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
// ─── Auth check ───────────────────────────────────────────────────────────────
|
|
4
|
+
// ─── Auth ─────────────────────────────────────────────────────────────────────
|
|
9
5
|
const auth = require('./auth');
|
|
10
6
|
|
|
11
7
|
const arg = process.argv[2];
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
if (arg === '
|
|
15
|
-
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
if (arg === 'logout') {
|
|
19
|
-
auth.logout();
|
|
20
|
-
process.exit(0);
|
|
21
|
-
}
|
|
22
|
-
if (arg === 'status') {
|
|
23
|
-
auth.status();
|
|
24
|
-
process.exit(0);
|
|
25
|
-
}
|
|
9
|
+
if (arg === 'login') { auth.login().catch(() => process.exit(1)); return; }
|
|
10
|
+
if (arg === 'logout') { auth.logout(); process.exit(0); }
|
|
11
|
+
if (arg === 'status') { auth.status(); process.exit(0); }
|
|
26
12
|
|
|
27
13
|
// Block access if not logged in
|
|
28
14
|
if (!auth.isLoggedIn()) {
|
|
@@ -40,31 +26,31 @@ if (!auth.isLoggedIn()) {
|
|
|
40
26
|
}
|
|
41
27
|
|
|
42
28
|
const readline = require('readline');
|
|
43
|
-
const axios
|
|
44
|
-
const chalk
|
|
45
|
-
const boxen
|
|
29
|
+
const axios = require('axios');
|
|
30
|
+
const chalk = require('chalk');
|
|
31
|
+
const boxen = require('boxen');
|
|
46
32
|
const gradient = require('gradient-string');
|
|
47
|
-
const figlet
|
|
33
|
+
const figlet = require('figlet');
|
|
48
34
|
|
|
49
|
-
// ─── NOTE: ora v5 is required (CommonJS compatible) ──────────────────────────
|
|
50
|
-
// If ora gives errors, run: npm install ora@5
|
|
51
35
|
let ora;
|
|
52
36
|
try {
|
|
53
37
|
ora = require('ora');
|
|
54
38
|
} catch {
|
|
55
|
-
// Fallback spinner if ora fails
|
|
56
39
|
ora = (opts) => ({
|
|
57
|
-
start: () => {
|
|
40
|
+
start: () => {
|
|
41
|
+
process.stdout.write(chalk.cyan(' ⏳ ' + (opts.text || 'Loading...') + '\n'));
|
|
42
|
+
return { stop: () => {}, fail: () => {} };
|
|
43
|
+
},
|
|
58
44
|
stop: () => {},
|
|
59
45
|
fail: () => {},
|
|
60
46
|
});
|
|
61
47
|
}
|
|
62
48
|
|
|
63
|
-
// ───
|
|
49
|
+
// ─── API Config ───────────────────────────────────────────────────────────────
|
|
64
50
|
const NVIDIA_API_BASE = 'https://slek-ai-portal.vercel.app/api';
|
|
65
|
-
const DEFAULT_MODEL
|
|
51
|
+
const DEFAULT_MODEL = 'qwen/qwen3.5-122b-a10b';
|
|
66
52
|
|
|
67
|
-
// ─── User config path
|
|
53
|
+
// ─── User config path ─────────────────────────────────────────────────────────
|
|
68
54
|
const CONFIG_PATH = require('path').join(
|
|
69
55
|
process.env.APPDATA || process.env.HOME || __dirname,
|
|
70
56
|
'.slek-config.json'
|
|
@@ -75,24 +61,28 @@ function loadConfig() {
|
|
|
75
61
|
if (require('fs').existsSync(CONFIG_PATH)) {
|
|
76
62
|
return JSON.parse(require('fs').readFileSync(CONFIG_PATH, 'utf8'));
|
|
77
63
|
}
|
|
78
|
-
} catch {
|
|
64
|
+
} catch (_) {}
|
|
79
65
|
return {};
|
|
80
66
|
}
|
|
81
67
|
|
|
82
68
|
function saveConfig(data) {
|
|
83
69
|
const current = loadConfig();
|
|
84
|
-
require('fs').writeFileSync(
|
|
70
|
+
require('fs').writeFileSync(
|
|
71
|
+
CONFIG_PATH,
|
|
72
|
+
JSON.stringify({ ...current, ...data }, null, 2),
|
|
73
|
+
'utf8'
|
|
74
|
+
);
|
|
85
75
|
}
|
|
86
76
|
|
|
87
|
-
// ───
|
|
77
|
+
// ─── Gradients ────────────────────────────────────────────────────────────────
|
|
88
78
|
const nvidiaGradient = gradient(['#76b900', '#00c8ff']);
|
|
89
|
-
const userGradient
|
|
90
|
-
const aiGradient
|
|
91
|
-
const thinkGradient
|
|
79
|
+
const userGradient = gradient(['#ff6b6b', '#feca57']);
|
|
80
|
+
const aiGradient = gradient(['#48dbfb', '#ff9ff3']);
|
|
81
|
+
const thinkGradient = gradient(['#f7971e', '#ffd200']);
|
|
92
82
|
|
|
93
|
-
// ─── State
|
|
94
|
-
let chatHistory
|
|
95
|
-
let apiKey
|
|
83
|
+
// ─── State ────────────────────────────────────────────────────────────────────
|
|
84
|
+
let chatHistory = [];
|
|
85
|
+
let apiKey = loadConfig().apiKey || '';
|
|
96
86
|
const currentUser = auth.getUser();
|
|
97
87
|
|
|
98
88
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -103,17 +93,14 @@ function clearLine() {
|
|
|
103
93
|
// ─── Banner ───────────────────────────────────────────────────────────────────
|
|
104
94
|
async function showBanner() {
|
|
105
95
|
console.clear();
|
|
106
|
-
const banner = figlet.textSync('SLEK AI', {
|
|
107
|
-
font: 'ANSI Shadow',
|
|
108
|
-
horizontalLayout: 'default',
|
|
109
|
-
});
|
|
96
|
+
const banner = figlet.textSync('SLEK AI', { font: 'ANSI Shadow' });
|
|
110
97
|
console.log(nvidiaGradient(banner));
|
|
111
98
|
console.log(
|
|
112
99
|
boxen(
|
|
113
100
|
chalk.bold.white('🚀 SLEK AI CLI') + chalk.gray(' — Powered by NVIDIA API\n') +
|
|
114
|
-
chalk.gray('
|
|
101
|
+
chalk.gray('─'.repeat(42)) + '\n' +
|
|
115
102
|
chalk.cyan(' User : ') + chalk.green(currentUser ? currentUser.name : 'Unknown') + '\n' +
|
|
116
|
-
chalk.gray('
|
|
103
|
+
chalk.gray('─'.repeat(42)) + '\n' +
|
|
117
104
|
chalk.white(' Type ') + chalk.yellow('/help') + chalk.white(' for available commands'),
|
|
118
105
|
{
|
|
119
106
|
padding: 1,
|
|
@@ -128,87 +115,37 @@ async function showBanner() {
|
|
|
128
115
|
|
|
129
116
|
// ─── Help ─────────────────────────────────────────────────────────────────────
|
|
130
117
|
function showHelp() {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
padding: 1,
|
|
143
|
-
|
|
144
|
-
borderStyle: 'round',
|
|
145
|
-
borderColor: 'cyan',
|
|
146
|
-
}
|
|
118
|
+
console.log(
|
|
119
|
+
boxen(
|
|
120
|
+
chalk.bold.cyan('📋 Available Commands\n') +
|
|
121
|
+
chalk.gray('═'.repeat(42)) + '\n\n' +
|
|
122
|
+
chalk.yellow('/help ') + chalk.white('Show this help menu\n') +
|
|
123
|
+
chalk.yellow('/clear ') + chalk.white('Clear chat history\n') +
|
|
124
|
+
chalk.yellow('/history ') + chalk.white('Show chat history\n') +
|
|
125
|
+
chalk.yellow('/save ') + chalk.white('Save chat to file\n') +
|
|
126
|
+
chalk.yellow('/info ') + chalk.white('Show current settings\n') +
|
|
127
|
+
chalk.yellow('/exit ') + chalk.white('Exit the CLI\n\n') +
|
|
128
|
+
chalk.gray('Or just type anything to chat with AI!'),
|
|
129
|
+
{ padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'cyan' }
|
|
130
|
+
)
|
|
147
131
|
);
|
|
148
|
-
console.log(helpBox);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ─── Show Models ──────────────────────────────────────────────────────────────
|
|
152
|
-
function showModels() {
|
|
153
|
-
console.log('\n' + chalk.bold.cyan(' 🤖 Available Models:\n'));
|
|
154
|
-
Object.entries(MODELS).forEach(([key, model]) => {
|
|
155
|
-
const active = model.id === currentModel ? chalk.green(' ◄ ACTIVE') : '';
|
|
156
|
-
const thinking = model.thinking ? chalk.yellow(' [🧠 Thinking]') : '';
|
|
157
|
-
console.log(` ${chalk.yellow(key + '.')} ${chalk.white(model.name)}${thinking}${active}`);
|
|
158
|
-
});
|
|
159
|
-
console.log();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ─── Change Model ─────────────────────────────────────────────────────────────
|
|
163
|
-
async function changeModel(rl) {
|
|
164
|
-
showModels();
|
|
165
|
-
return new Promise(resolve => {
|
|
166
|
-
rl.question(chalk.cyan(' Enter model number (1-7): '), answer => {
|
|
167
|
-
const chosen = MODELS[answer.trim()];
|
|
168
|
-
if (chosen) {
|
|
169
|
-
currentModel = chosen.id;
|
|
170
|
-
enableThinking = chosen.thinking || false;
|
|
171
|
-
const thinkNote = chosen.thinking ? chalk.yellow(' (Thinking mode ON)') : '';
|
|
172
|
-
console.log(chalk.green(`\n ✓ Model: ${chalk.yellow(chosen.name.trim())}${thinkNote}\n`));
|
|
173
|
-
} else {
|
|
174
|
-
console.log(chalk.red('\n ✗ Invalid choice\n'));
|
|
175
|
-
}
|
|
176
|
-
resolve();
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// ─── Toggle Thinking ──────────────────────────────────────────────────────────
|
|
182
|
-
function toggleThinking() {
|
|
183
|
-
enableThinking = !enableThinking;
|
|
184
|
-
if (enableThinking) {
|
|
185
|
-
console.log(chalk.green('\n 🧠 Thinking mode: ON\n'));
|
|
186
|
-
} else {
|
|
187
|
-
console.log(chalk.gray('\n 💤 Thinking mode: OFF\n'));
|
|
188
|
-
}
|
|
189
132
|
}
|
|
190
133
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// ─── Show Info ────────────────────────────────────────────────────────────────
|
|
134
|
+
// ─── Info ─────────────────────────────────────────────────────────────────────
|
|
194
135
|
function showInfo() {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
padding: 1,
|
|
203
|
-
|
|
204
|
-
borderStyle: 'single',
|
|
205
|
-
borderColor: 'yellow',
|
|
206
|
-
}
|
|
136
|
+
console.log(
|
|
137
|
+
'\n' + boxen(
|
|
138
|
+
chalk.bold.white('⚙ Current Settings\n') +
|
|
139
|
+
chalk.gray('─'.repeat(40)) + '\n\n' +
|
|
140
|
+
chalk.cyan('User : ') + chalk.green(currentUser ? currentUser.name : 'Unknown') + '\n' +
|
|
141
|
+
chalk.cyan('Messages : ') + chalk.white(chatHistory.length + ' in history') + '\n' +
|
|
142
|
+
chalk.cyan('API URL : ') + chalk.gray(NVIDIA_API_BASE),
|
|
143
|
+
{ padding: 1, margin: { left: 2 }, borderStyle: 'single', borderColor: 'yellow' }
|
|
144
|
+
)
|
|
207
145
|
);
|
|
208
|
-
console.log('\n' + infoBox);
|
|
209
146
|
}
|
|
210
147
|
|
|
211
|
-
// ───
|
|
148
|
+
// ─── History ──────────────────────────────────────────────────────────────────
|
|
212
149
|
function showHistory() {
|
|
213
150
|
if (chatHistory.length === 0) {
|
|
214
151
|
console.log(chalk.yellow('\n No chat history yet.\n'));
|
|
@@ -239,7 +176,7 @@ async function saveChat() {
|
|
|
239
176
|
|
|
240
177
|
let content = `SLEK AI CLI - Chat Log\n`;
|
|
241
178
|
content += `Date: ${new Date().toLocaleString()}\n`;
|
|
242
|
-
content += `Model: ${
|
|
179
|
+
content += `Model: ${DEFAULT_MODEL}\n`;
|
|
243
180
|
content += `${'═'.repeat(50)}\n\n`;
|
|
244
181
|
|
|
245
182
|
chatHistory.forEach(msg => {
|
|
@@ -252,13 +189,13 @@ async function saveChat() {
|
|
|
252
189
|
});
|
|
253
190
|
|
|
254
191
|
fs.writeFileSync(filepath, content, 'utf8');
|
|
255
|
-
console.log(chalk.green(`\n
|
|
192
|
+
console.log(chalk.green(`\n ✔ Chat saved to: ${chalk.yellow(filename)}\n`));
|
|
256
193
|
}
|
|
257
194
|
|
|
258
195
|
// ─── Stream API Call ──────────────────────────────────────────────────────────
|
|
259
196
|
async function callNvidiaAPIStream(userMessage) {
|
|
260
197
|
if (!apiKey) {
|
|
261
|
-
throw new Error('API key not
|
|
198
|
+
throw new Error('API key not set. Ask the admin for your SLEK API key.');
|
|
262
199
|
}
|
|
263
200
|
|
|
264
201
|
chatHistory.push({ role: 'user', content: userMessage });
|
|
@@ -273,19 +210,17 @@ async function callNvidiaAPIStream(userMessage) {
|
|
|
273
210
|
...chatHistory,
|
|
274
211
|
];
|
|
275
212
|
|
|
276
|
-
const payload = {
|
|
277
|
-
model: DEFAULT_MODEL,
|
|
278
|
-
messages,
|
|
279
|
-
max_tokens: 16384,
|
|
280
|
-
temperature: 0.60,
|
|
281
|
-
top_p: 0.95,
|
|
282
|
-
stream: true,
|
|
283
|
-
chat_template_kwargs: { enable_thinking: true },
|
|
284
|
-
};
|
|
285
|
-
|
|
286
213
|
const response = await axios.post(
|
|
287
214
|
`${NVIDIA_API_BASE}/chat-free`,
|
|
288
|
-
|
|
215
|
+
{
|
|
216
|
+
model: DEFAULT_MODEL,
|
|
217
|
+
messages,
|
|
218
|
+
max_tokens: 16384,
|
|
219
|
+
temperature: 0.60,
|
|
220
|
+
top_p: 0.95,
|
|
221
|
+
stream: true,
|
|
222
|
+
chat_template_kwargs: { enable_thinking: true },
|
|
223
|
+
},
|
|
289
224
|
{
|
|
290
225
|
headers: {
|
|
291
226
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -298,8 +233,8 @@ async function callNvidiaAPIStream(userMessage) {
|
|
|
298
233
|
);
|
|
299
234
|
|
|
300
235
|
return new Promise((resolve, reject) => {
|
|
301
|
-
let fullResponse
|
|
302
|
-
let inThinkBlock
|
|
236
|
+
let fullResponse = '';
|
|
237
|
+
let inThinkBlock = false;
|
|
303
238
|
|
|
304
239
|
process.stdout.write('\n ' + aiGradient('✦ SLEK AI: ') + '\n\n ');
|
|
305
240
|
|
|
@@ -325,12 +260,10 @@ async function callNvidiaAPIStream(userMessage) {
|
|
|
325
260
|
continue;
|
|
326
261
|
}
|
|
327
262
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
} catch { /* ignore malformed SSE lines */ }
|
|
263
|
+
process.stdout.write(
|
|
264
|
+
inThinkBlock ? chalk.dim.yellow(token) : chalk.white(token)
|
|
265
|
+
);
|
|
266
|
+
} catch (_) { /* ignore malformed SSE lines */ }
|
|
334
267
|
}
|
|
335
268
|
}
|
|
336
269
|
});
|
|
@@ -346,7 +279,7 @@ async function callNvidiaAPIStream(userMessage) {
|
|
|
346
279
|
});
|
|
347
280
|
}
|
|
348
281
|
|
|
349
|
-
// ─── Process
|
|
282
|
+
// ─── Process Input ────────────────────────────────────────────────────────────
|
|
350
283
|
async function processInput(input, rl) {
|
|
351
284
|
const trimmed = input.trim();
|
|
352
285
|
if (!trimmed) return;
|
|
@@ -354,14 +287,14 @@ async function processInput(input, rl) {
|
|
|
354
287
|
if (trimmed.startsWith('/')) {
|
|
355
288
|
const cmd = trimmed.toLowerCase().split(' ')[0];
|
|
356
289
|
switch (cmd) {
|
|
357
|
-
case '/help': showHelp();
|
|
290
|
+
case '/help': showHelp(); break;
|
|
358
291
|
case '/clear':
|
|
359
292
|
chatHistory = [];
|
|
360
|
-
console.log(chalk.green('\n
|
|
293
|
+
console.log(chalk.green('\n ✔ Chat history cleared!\n'));
|
|
361
294
|
break;
|
|
362
|
-
case '/history': showHistory();
|
|
363
|
-
case '/save': await saveChat();
|
|
364
|
-
case '/info': showInfo();
|
|
295
|
+
case '/history': showHistory(); break;
|
|
296
|
+
case '/save': await saveChat(); break;
|
|
297
|
+
case '/info': showInfo(); break;
|
|
365
298
|
case '/exit':
|
|
366
299
|
case '/quit':
|
|
367
300
|
console.log('\n' + nvidiaGradient(' 👋 Goodbye! Thanks for using SLEK AI CLI\n'));
|
|
@@ -375,9 +308,9 @@ async function processInput(input, rl) {
|
|
|
375
308
|
}
|
|
376
309
|
|
|
377
310
|
const spinner = ora({
|
|
378
|
-
text:
|
|
379
|
-
spinner:
|
|
380
|
-
color:
|
|
311
|
+
text: chalk.cyan(' Connecting to NVIDIA API...'),
|
|
312
|
+
spinner: 'dots',
|
|
313
|
+
color: 'cyan',
|
|
381
314
|
prefixText: ' ',
|
|
382
315
|
}).start();
|
|
383
316
|
|
|
@@ -393,13 +326,10 @@ async function processInput(input, rl) {
|
|
|
393
326
|
err.message ||
|
|
394
327
|
'Unknown error';
|
|
395
328
|
console.log(
|
|
396
|
-
'\n' +
|
|
397
|
-
|
|
398
|
-
padding: 1,
|
|
399
|
-
|
|
400
|
-
borderStyle: 'round',
|
|
401
|
-
borderColor: 'red',
|
|
402
|
-
}) + '\n'
|
|
329
|
+
'\n' + boxen(
|
|
330
|
+
chalk.red('✗ Error: ') + chalk.white(errMsg),
|
|
331
|
+
{ padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'red' }
|
|
332
|
+
) + '\n'
|
|
403
333
|
);
|
|
404
334
|
}
|
|
405
335
|
}
|
|
@@ -408,9 +338,7 @@ async function processInput(input, rl) {
|
|
|
408
338
|
function showPrompt(rl) {
|
|
409
339
|
const prompt =
|
|
410
340
|
'\n' +
|
|
411
|
-
chalk.gray(' ┌─') +
|
|
412
|
-
userGradient(' You ') +
|
|
413
|
-
chalk.gray('─────────────────────────────────\n') +
|
|
341
|
+
chalk.gray(' ┌─') + userGradient(' You ') + chalk.gray('─────────────────────────────────\n') +
|
|
414
342
|
chalk.gray(' └▶ ');
|
|
415
343
|
rl.setPrompt(prompt);
|
|
416
344
|
rl.prompt();
|
|
@@ -420,11 +348,9 @@ function showPrompt(rl) {
|
|
|
420
348
|
async function main() {
|
|
421
349
|
await showBanner();
|
|
422
350
|
|
|
423
|
-
|
|
424
|
-
|
|
425
351
|
const rl = readline.createInterface({
|
|
426
|
-
input:
|
|
427
|
-
output:
|
|
352
|
+
input: process.stdin,
|
|
353
|
+
output: process.stdout,
|
|
428
354
|
terminal: true,
|
|
429
355
|
});
|
|
430
356
|
|
package/package.json
CHANGED
package/.env
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# NVIDIA API Key (get from https://build.nvidia.com)
|
|
2
|
-
NVIDIA_API_KEY=slek-key-98ab6e89f8b636bed33a2cb08148c0b6
|
|
3
|
-
|
|
4
|
-
# ─── Firebase Config ───────────────────────────────────────
|
|
5
|
-
FIREBASE_API_KEY=AIzaSyBoQHn_adTTj1ZaYZBMHCMSAblCGCIbQG4
|
|
6
|
-
FIREBASE_AUTH_DOMAIN=charm-f004f.firebaseapp.com
|
|
7
|
-
FIREBASE_PROJECT_ID=charm-f004f
|
|
8
|
-
FIREBASE_STORAGE_BUCKET=charm-f004f.firebasestorage.app
|
|
9
|
-
FIREBASE_MESSAGING_SENDER_ID=763614479011
|
|
10
|
-
FIREBASE_APP_ID=1:763614479011:web:5cd0082b14b78b9c5eb516
|
|
11
|
-
FIREBASE_MEASUREMENT_ID=G-0G0F5DT6PC
|
|
12
|
-
|
|
13
|
-
# ─── Google OAuth (from Google Cloud Console) ─────────────
|
|
14
|
-
# https://console.cloud.google.com → APIs & Services → Credentials → Create OAuth 2.0 Client ID
|
|
15
|
-
# Application type: Web application
|
|
16
|
-
# Authorized redirect URI: http://localhost:9876/callback
|
|
17
|
-
GOOGLE_CLIENT_ID=763614479011-32ci230ubh0kuvg1g1ie3jtoeir9orap.apps.googleusercontent.com
|
|
18
|
-
GOOGLE_CLIENT_SECRET=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
|