errorhub-cli 1.1.4 → 1.1.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +0 -422
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "errorhub-cli",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/index.ts DELETED
@@ -1,422 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import open from 'open';
5
- import express from 'express';
6
- import fs from 'fs';
7
- import os from 'os';
8
- import path from 'path';
9
- import net from 'net';
10
- import { fileURLToPath } from 'url';
11
- import clc from 'cli-color';
12
-
13
- const program = new Command();
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = path.dirname(__filename);
16
- const CONFIG_PATH = path.join(os.homedir(), '.token.json');
17
- const PROJECTS = path.join(os.homedir(), 'projectId.json');
18
-
19
- // ─── Color Palette ────────────────────────────────────────────────────────────
20
- const c = {
21
- cyan: clc.xterm(51).bold,
22
- cyanDim: clc.xterm(38),
23
- teal: clc.xterm(6).bold,
24
- green: clc.xterm(46).bold,
25
- greenDim: clc.xterm(34),
26
- red: clc.xterm(196).bold,
27
- orange: clc.xterm(214).bold,
28
- yellow: clc.xterm(226).bold,
29
- white: clc.xterm(255).bold,
30
- dim: clc.xterm(240),
31
- bgTeal: clc.bgXterm(6),
32
- bgRed: clc.bgXterm(196),
33
- bgGreen: clc.bgXterm(28),
34
- bgOrange: clc.bgXterm(208),
35
- };
36
-
37
- const FRONTENDBASE = "https://errorhub.vercel.app";
38
- const BACKENDBASE = "https://errorhub.onrender.com"
39
-
40
- // ─── Layout Helpers ───────────────────────────────────────────────────────────
41
- const W = 62;
42
-
43
- const line = (char = '─') => c.teal('─'.repeat(W));
44
- const dLine = () => c.teal('═'.repeat(W));
45
- const blank = () => console.log(c.teal('│') + ' '.repeat(W - 2) + c.teal('│'));
46
-
47
- const boxRow = (text: string, color = c.white) => {
48
- const inner = W - 4;
49
- const stripped = text.replace(/\x1B\[[0-9;]*m/g, '');
50
- const pad = Math.max(0, inner - stripped.length);
51
- const left = Math.floor(pad / 2);
52
- const right = pad - left;
53
- console.log(c.teal('│') + ' ' + ' '.repeat(left) + color(text) + ' '.repeat(right) + ' ' + c.teal('│'));
54
- };
55
-
56
- const labelRow = (label: string, value: string) => {
57
- const l = c.cyanDim(label.padEnd(14));
58
- const v = c.white(value);
59
- const raw = label.padEnd(14) + value;
60
- const pad = Math.max(0, W - 4 - raw.length);
61
- console.log(c.teal('│') + ' ' + l + v + ' '.repeat(pad) + ' ' + c.teal('│'));
62
- };
63
-
64
- // ─── ASCII Logo ───────────────────────────────────────────────────────────────
65
- const LOGO = [
66
- '███████╗██████╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗██╗ ██╗██████╗ ',
67
- '██╔════╝██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██║ ██║██║ ██║██╔══██╗',
68
- '█████╗ ██████╔╝██████╔╝██║ ██║██████╔╝███████║██║ ██║██████╔╝',
69
- '██╔══╝ ██╔══██╗██╔══██╗██║ ██║██╔══██╗██╔══██║██║ ██║██╔══██╗',
70
- '███████╗██║ ██║██║ ██║╚██████╔╝██║ ██║██║ ██║╚██████╔╝██████╔╝',
71
- '╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝',
72
- '',
73
- ' ██████╗██╗ ██╗ ',
74
- ' ██╔════╝██║ ██║ ',
75
- ' ██║ ██║ ██║ ',
76
- ' ██║ ██║ ██║ ',
77
- ' ╚██████╗███████╗██║ ',
78
- ' ╚═════╝╚══════╝╚═╝ ',
79
- ];
80
-
81
- const printLogo = () => {
82
- console.log('');
83
- LOGO.forEach((row, i) => {
84
- const color = i < 3 ? c.cyan : i < 6 ? c.teal : c.green;
85
- console.log(color(row));
86
- });
87
- console.log('');
88
- };
89
-
90
- // ─── Banner ───────────────────────────────────────────────────────────────────
91
- const printBanner = (title: string, subtitle?: string) => {
92
- console.log('');
93
- console.log(c.teal('╔' + '═'.repeat(W - 2) + '╗'));
94
- boxRow('');
95
- boxRow(title, c.cyan);
96
- if (subtitle) boxRow(subtitle, c.dim);
97
- boxRow('');
98
- console.log(c.teal('╠' + '═'.repeat(W - 2) + '╣'));
99
- };
100
-
101
- const closeBanner = () => {
102
- console.log(c.teal('╚' + '═'.repeat(W - 2) + '╝'));
103
- console.log('');
104
- };
105
-
106
- // ─── Status Badges ────────────────────────────────────────────────────────────
107
- const badge = {
108
- ok: (msg: string) => console.log(c.bgGreen(' ✔ OK ') + ' ' + c.green(msg)),
109
- error: (msg: string) => console.log(c.bgRed(' ✘ ERR ') + ' ' + c.red(msg)),
110
- info: (msg: string) => console.log(c.bgTeal(' ℹ INFO') + ' ' + c.cyanDim(msg)),
111
- warn: (msg: string) => console.log(c.bgOrange(' ⚠ WARN') + ' ' + c.orange(msg)),
112
- loading: (msg: string) => console.log(c.cyan(' ⟳ ') + c.cyanDim(msg)),
113
- };
114
-
115
- // ─── Spinner ──────────────────────────────────────────────────────────────────
116
- const spinner = (label: string) => {
117
- const frames = ['⠋', '⠙', '⠸', '⠴', '⠦', '⠇'];
118
- let i = 0;
119
- const iv = setInterval(() => {
120
- process.stdout.write('\r' + c.cyan(frames[i++ % frames.length]) + ' ' + c.cyanDim(label));
121
- }, 80);
122
- return () => {
123
- clearInterval(iv);
124
- process.stdout.write('\r' + ' '.repeat(label.length + 4) + '\r');
125
- };
126
- };
127
-
128
- // ─── Section Divider ──────────────────────────────────────────────────────────
129
- const section = (title: string) => {
130
- console.log('');
131
- const l = '─'.repeat(4);
132
- console.log(c.teal(l) + ' ' + c.yellow(title.toUpperCase()) + ' ' + c.teal(l));
133
- };
134
-
135
- // ─── Free Port Helper ─────────────────────────────────────────────────────────
136
- function getFreePort(startPort = 4000): Promise<number> {
137
- return new Promise((resolve) => {
138
- const server = net.createServer();
139
- server.listen(startPort, () => {
140
- const port = (server.address() as net.AddressInfo).port;
141
- server.close(() => resolve(port));
142
- });
143
- server.on('error', () => {
144
- resolve(getFreePort(startPort + 1));
145
- });
146
- });
147
- }
148
-
149
- // ─────────────────────────────────────────────────────────────────────────────
150
- // CLI Setup
151
- // ─────────────────────────────────────────────────────────────────────────────
152
-
153
- program
154
- .name('errorhub-cli')
155
- .description('CLI with JWT login')
156
- .version('1.0.1');
157
-
158
- // ─────────────────────────────────────────────────────────────────────────────
159
- // INIT
160
- // ─────────────────────────────────────────────────────────────────────────────
161
-
162
- program
163
- .command('init')
164
- .description('Initialize project')
165
- .action(async () => {
166
- printLogo();
167
- printBanner('PROJECT INITIALIZER', 'Setting up your workspace...');
168
- console.log(c.teal('│'));
169
- labelRow('Version:', '1.0.0');
170
- labelRow('Node:', process.version);
171
- labelRow('Platform:', process.platform);
172
- labelRow('CWD:', process.cwd().slice(0, 40));
173
- console.log(c.teal('│'));
174
- closeBanner();
175
-
176
- section('Launching Auth Server');
177
- console.log('');
178
-
179
- const app = express();
180
- const port = await getFreePort();
181
- const stop = spinner(`Starting local server on port ${port}...`);
182
-
183
- const server = app.listen(port, () => {
184
- stop();
185
- badge.ok(`Server listening on ${c.cyan(`http://localhost:${port}`)}`);
186
-
187
- const loginUrl = `${FRONTENDBASE}/login?callback=http://localhost:${port}/callback`;
188
- badge.info(`Opening: ${c.cyanDim(loginUrl)}`);
189
- console.log('');
190
- open(loginUrl);
191
- });
192
-
193
- section('Ready');
194
- console.log('');
195
- console.log(c.dim(' Waiting for browser login flow to complete...'));
196
- console.log('');
197
- });
198
-
199
- // ─────────────────────────────────────────────────────────────────────────────
200
- // LOGIN
201
- // ─────────────────────────────────────────────────────────────────────────────
202
-
203
- program
204
- .command('login')
205
- .description('Login and store JWT')
206
- .action(async () => {
207
- printLogo();
208
- printBanner('AUTHENTICATION', 'Secure JWT login flow');
209
- labelRow('Auth Server:', FRONTENDBASE);
210
- labelRow('Config Path:', CONFIG_PATH.replace(os.homedir(), '~'));
211
- console.log(c.teal('│'));
212
- closeBanner();
213
-
214
- section('Starting OAuth Flow');
215
- console.log('');
216
-
217
- const app = express();
218
- const port = await getFreePort();
219
- const stop = spinner(`Binding to port ${port}...`);
220
-
221
- const server = app.listen(port, () => {
222
- stop();
223
- const loginUrl = `${FRONTENDBASE}/login?callback=http://localhost:${port}/callback`;
224
- badge.ok(`Local callback server ready on port ${c.cyan(String(port))}`);
225
- badge.loading('Opening browser for login...');
226
- console.log('');
227
- open(loginUrl);
228
- });
229
-
230
- app.get('/callback', (req, res) => {
231
- const token = req.query.token as string;
232
-
233
- if (!token) {
234
- res.status(400).send(renderHtmlPage('Error', '✘ No token received.', '#ff2d2d'));
235
- badge.error('Callback hit but no token was received.');
236
- return;
237
- }
238
-
239
- fs.writeFileSync(CONFIG_PATH, JSON.stringify({ token }, null, 2));
240
- res.send(renderHtmlPage('Success!', '✔ Login successful — you may close this tab.', '#00e5a0'));
241
-
242
- section('Login Complete');
243
- console.log('');
244
- badge.ok('JWT received and stored successfully!');
245
- console.log('');
246
-
247
- console.log(c.teal('┌' + '─'.repeat(W - 2) + '┐'));
248
- const preview = token.length > 50
249
- ? token.slice(0, 24) + c.dim(' ··· ') + token.slice(-24)
250
- : token;
251
- boxRow('TOKEN PREVIEW', c.yellow);
252
- boxRow(preview, c.cyanDim);
253
- console.log(c.teal('└' + '─'.repeat(W - 2) + '┘'));
254
- console.log('');
255
- console.log(c.dim(` Saved to: ${CONFIG_PATH}`));
256
- console.log('');
257
-
258
- server.close();
259
- });
260
- });
261
-
262
- // ─────────────────────────────────────────────────────────────────────────────
263
- // WHOAMI
264
- // ─────────────────────────────────────────────────────────────────────────────
265
-
266
- program
267
- .command('whoami')
268
- .description('Read stored JWT')
269
- .action(() => {
270
- printLogo();
271
- printBanner('IDENTITY CHECK', 'Reading stored credentials');
272
- labelRow('Config:', CONFIG_PATH.replace(os.homedir(), '~'));
273
- console.log(c.teal('│'));
274
- closeBanner();
275
-
276
- if (!fs.existsSync(CONFIG_PATH)) {
277
- console.log('');
278
- badge.error('No credentials found. Run ' + c.cyan('errorhub login') + ' first.');
279
- console.log('');
280
- console.log(c.teal('┌' + '─'.repeat(W - 2) + '┐'));
281
- boxRow('NOT AUTHENTICATED', c.red);
282
- boxRow('Use: errorhub login', c.dim);
283
- console.log(c.teal('└' + '─'.repeat(W - 2) + '┘'));
284
- console.log('');
285
- return;
286
- }
287
-
288
- const data = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
289
- const { token } = data;
290
-
291
- let jwtInfo: { header?: any; payload?: any } = {};
292
- try {
293
- const [h, p] = token.split('.');
294
- jwtInfo.header = JSON.parse(Buffer.from(h, 'base64').toString());
295
- jwtInfo.payload = JSON.parse(Buffer.from(p, 'base64').toString());
296
- } catch {
297
- // not a valid JWT
298
- }
299
-
300
- section('Stored Token');
301
- console.log('');
302
- console.log(c.teal('┌' + '─'.repeat(W - 2) + '┐'));
303
-
304
- if (jwtInfo.payload) {
305
- const p = jwtInfo.payload;
306
- boxRow('JWT DECODED', c.yellow);
307
- console.log(c.teal('├' + '─'.repeat(W - 2) + '┤'));
308
-
309
- const fields: [string, any][] = Object.entries({
310
- sub: p.sub ?? '—',
311
- name: p.name ?? '—',
312
- email: p.email ?? '—',
313
- role: p.role ?? '—',
314
- iat: p.iat ? new Date(p.iat * 1000).toISOString() : '—',
315
- exp: p.exp ? new Date(p.exp * 1000).toISOString() : '—',
316
- alg: jwtInfo.header?.alg ?? '—',
317
- });
318
-
319
- for (const [k, v] of fields) {
320
- labelRow(`${k}:`, String(v));
321
- }
322
-
323
- if (jwtInfo.payload.exp) {
324
- const expired = Date.now() / 1000 > jwtInfo.payload.exp;
325
- console.log(c.teal('├' + '─'.repeat(W - 2) + '┤'));
326
- boxRow(expired ? '⚠ TOKEN EXPIRED' : '✔ TOKEN VALID', expired ? c.red : c.green);
327
- }
328
- }
329
-
330
- console.log(c.teal('├' + '─'.repeat(W - 2) + '┤'));
331
- boxRow('RAW TOKEN', c.yellow);
332
- const chunks = token.match(/.{1,54}/g) ?? [token];
333
- for (const chunk of chunks) {
334
- boxRow(chunk, c.cyanDim);
335
- }
336
- console.log(c.teal('└' + '─'.repeat(W - 2) + '┘'));
337
- console.log('');
338
- });
339
-
340
- // ─────────────────────────────────────────────────────────────────────────────
341
- // PROJECT
342
- // ─────────────────────────────────────────────────────────────────────────────
343
-
344
- program
345
- .command('project <apiKey>')
346
- .description('Find and store project by API key')
347
- .action(async (apiKey) => {
348
-
349
-
350
- if (!fs.existsSync(CONFIG_PATH)) {
351
- badge.error('Not logged in. Run errorhub login first.');
352
- return;
353
- }
354
-
355
- const data = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
356
- const token = data.token;
357
-
358
- const stop = spinner('Fetching projects...');
359
-
360
- try {
361
- const response = await fetch(`${BACKENDBASE}/projects`, {
362
- headers: { Authorization: `Bearer ${token}` }
363
- });
364
- const projects = await response.json();
365
- stop();
366
-
367
- const match = projects.find((p: any) => p.api_key === apiKey);
368
-
369
- if (!match) {
370
- badge.error('No project found with that API key.');
371
- return;
372
- }
373
-
374
- fs.writeFileSync(PROJECTS, JSON.stringify({ projectId: match.id }, null, 2));
375
- badge.ok(`Project found and saved: ${c.cyan(match.id)}`);
376
- } catch (e) {
377
- stop();
378
- badge.error('Failed to fetch projects. Check your connection.');
379
- }
380
- });
381
-
382
- // ─────────────────────────────────────────────────────────────────────────────
383
- // HTML Helper
384
- // ─────────────────────────────────────────────────────────────────────────────
385
-
386
- function renderHtmlPage(title: string, message: string, color: string): string {
387
- return `<!DOCTYPE html>
388
- <html>
389
- <head>
390
- <meta charset="UTF-8"/>
391
- <title>${title}</title>
392
- <style>
393
- * { margin:0; padding:0; box-sizing:border-box; }
394
- body {
395
- background: #0a0e14;
396
- color: #e0e0e0;
397
- font-family: 'Courier New', monospace;
398
- display: flex;
399
- align-items: center;
400
- justify-content: center;
401
- height: 100vh;
402
- }
403
- .card {
404
- border: 1px solid ${color};
405
- padding: 3rem 4rem;
406
- text-align: center;
407
- box-shadow: 0 0 40px ${color}44;
408
- }
409
- h1 { font-size: 2.5rem; color: ${color}; letter-spacing: 0.2em; }
410
- p { margin-top: 1rem; color: #888; font-size: 0.9rem; }
411
- </style>
412
- </head>
413
- <body>
414
- <div class="card">
415
- <h1>${title}</h1>
416
- <p>${message}</p>
417
- </div>
418
- </body>
419
- </html>`;
420
- }
421
-
422
- program.parse();