keyline-cli 1.0.0

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/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # KeyLine
2
+
3
+ > **Secure Terminal Communication Protocol**
4
+
5
+ KeyLine is a persistent, encrypted chat protocol for the terminal. No browsers. No trackers. Just raw, distributed messaging for the modern node.
6
+
7
+ ![Banner](https://github.com/user-attachments/assets/placeholder)
8
+
9
+ ## Features
10
+
11
+ - 🔒 **Secure**: End-to-end encrypted messaging (via Supabase RLS).
12
+ - 🆔 **Identity**: Unique alphanumeric IDs (e.g., `ABC1234`).
13
+ - ⚡ **Real-time**: Instant message delivery using Supabase Realtime.
14
+ - 🎨 **Hacker UX**: Premium retro-terminal interface.
15
+
16
+ ## Installation
17
+
18
+ You can run KeyLine directly using `npx` or install it globally.
19
+
20
+ ### Option 1: Quick Run (Recommended)
21
+
22
+ ```bash
23
+ npx keyline
24
+ ```
25
+
26
+ ### Option 2: Global Install
27
+
28
+ ```bash
29
+ npm install -g keyline
30
+ keyline
31
+ ```
32
+
33
+ ## Configuration (Optional)
34
+
35
+ By default, KeyLine connects to the official secure public network. No configuration is required.
36
+
37
+ **Advanced: Self-Hosting**
38
+ If you want to host your own backend:
39
+ 1. Create a `.env` file in the directory where you run the tool.
40
+ 2. Add your Supabase credentials:
41
+
42
+ ```env
43
+ SUPABASE_URL=your_supabase_project_url
44
+ SUPABASE_ANON_KEY=your_supabase_anon_key
45
+ ```
46
+
47
+ > If you are the host, ensure your Supabase instance has the correct schema applied (see `update_schema.sql`).
48
+
49
+ ## Commands
50
+
51
+ - `/help` - Show protocol list
52
+ - `/clear` - Flush display buffer
53
+ - `/info` - Peer identity status
54
+ - `/shrug` - ¯\\_(ツ)_/¯
55
+ - `/flip` - (╯°□°)╯︵ ┻━┻
56
+ - `/nuke` - Emergency disconnect
57
+
58
+ ## License
59
+
60
+ ISC
package/keyline.js ADDED
@@ -0,0 +1,577 @@
1
+ #!/usr/bin/env node
2
+ const { createClient } = require('@supabase/supabase-js');
3
+ const inquirer = require('inquirer');
4
+ const chalk = require('chalk');
5
+ const figlet = require('figlet');
6
+ const ora = require('ora');
7
+ const { customAlphabet } = require('nanoid');
8
+ require('dotenv').config();
9
+
10
+ // Default Configuration (KeyLine Public Network)
11
+ const DEFAULT_CONFIG = {
12
+ SUPABASE_URL: 'https://xpzaefdnzddsibxqgwnx.supabase.co',
13
+ SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhwemFlZmRuemRkc2lieHFnd254Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjY5MDk3MDksImV4cCI6MjA4MjQ4NTcwOX0.tyjkDMoC34msnoBOky7JUVuuhEw4m7cLsl86JUTSPQY'
14
+ };
15
+
16
+ const SUPABASE_URL = process.env.SUPABASE_URL || DEFAULT_CONFIG.SUPABASE_URL;
17
+ const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || DEFAULT_CONFIG.SUPABASE_ANON_KEY;
18
+
19
+ const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
20
+ const nanoid = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 7);
21
+
22
+ const hackerTheme = {
23
+ primary: chalk.greenBright,
24
+ secondary: chalk.green,
25
+ error: chalk.redBright,
26
+ info: chalk.cyan,
27
+ system: chalk.white.dim,
28
+ warning: chalk.yellowBright,
29
+ accent: chalk.magentaBright,
30
+ bg: chalk.bgBlack
31
+ };
32
+
33
+ const SLEEP = (ms) => new Promise(resolve => setTimeout(resolve, ms));
34
+
35
+ async function typewriter(text, color = hackerTheme.primary, speed = 10) {
36
+ for (const char of text) {
37
+ process.stdout.write(color(char));
38
+ await SLEEP(speed);
39
+ }
40
+ process.stdout.write('\n');
41
+ }
42
+
43
+ function clearConsole() {
44
+ process.stdout.write('\x1Bc');
45
+ }
46
+
47
+ function renderBox(content, title = 'SYSTEM') {
48
+ const lines = content.split('\n');
49
+ const width = Math.max(...lines.map(l => l.length), title.length) + 4;
50
+ const border = '═'.repeat(width);
51
+
52
+ console.log(hackerTheme.secondary(`╔═ ${title} ${'═'.repeat(width - title.length - 3)}╗`));
53
+ lines.forEach(line => {
54
+ console.log(hackerTheme.secondary('║ ') + hackerTheme.primary(line.padEnd(width - 2)) + hackerTheme.secondary('║'));
55
+ });
56
+ console.log(hackerTheme.secondary(`╚${border}╝`));
57
+ }
58
+
59
+ function showBanner() {
60
+ console.log(hackerTheme.primary(figlet.textSync('KeyLine', { horizontalLayout: 'full' })));
61
+ console.log(hackerTheme.system('--- SECURE TERMINAL CONNECTION ESTABLISHED ---\n'));
62
+ }
63
+
64
+ async function generateUniqueID() {
65
+ // ID format: 3 letters + 4 numbers (e.g., ABC1234)
66
+ const letters = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 3)();
67
+ const numbers = customAlphabet('0123456789', 4)();
68
+ return `${letters}${numbers}`;
69
+ }
70
+
71
+ async function main() {
72
+ clearConsole();
73
+ showBanner();
74
+
75
+ // Credentials now guaranteed by defaults
76
+
77
+ const { action } = await inquirer.prompt([
78
+ {
79
+ type: 'list',
80
+ name: 'action',
81
+ message: hackerTheme.primary('Select initial protocol:'),
82
+ choices: [
83
+ { name: hackerTheme.secondary('[LOGIN] Access existing terminal'), value: 'login' },
84
+ { name: hackerTheme.secondary('[SIGNUP] Register new node'), value: 'signup' },
85
+ { name: hackerTheme.error('[EXIT] Terminate session'), value: 'exit' }
86
+ ]
87
+ }
88
+ ]);
89
+
90
+ if (action === 'exit') process.exit(0);
91
+
92
+ if (action === 'signup') {
93
+ await handleSignup();
94
+ } else {
95
+ await handleLogin();
96
+ }
97
+ }
98
+
99
+ async function handleSignup() {
100
+ const { displayName } = await inquirer.prompt([
101
+ { type: 'input', name: 'displayName', message: hackerTheme.primary('Enter Display Name (or type "back"):') }
102
+ ]);
103
+
104
+ if (displayName.toLowerCase() === 'back') return main();
105
+
106
+ const { email, password, chatPassword } = await inquirer.prompt([
107
+ { type: 'input', name: 'email', message: hackerTheme.primary('Enter Email (for identity):') },
108
+ { type: 'password', name: 'password', message: hackerTheme.primary('Enter Secure Account Password:') },
109
+ { type: 'password', name: 'chatPassword', message: hackerTheme.warning('Set Chat Access Password (others need this to add you):') }
110
+ ]);
111
+
112
+ const spinner = ora(hackerTheme.info('Encrypting identity and generating ID...')).start();
113
+
114
+ try {
115
+ const { data: authData, error: authError } = await supabase.auth.signUp({
116
+ email,
117
+ password
118
+ });
119
+
120
+ if (authError) throw authError;
121
+
122
+ if (authData.session) {
123
+ const uniqueID = await generateUniqueID();
124
+
125
+ const { error: profileError } = await supabase
126
+ .from('profiles')
127
+ .insert([
128
+ { id: authData.user.id, display_name: displayName, unique_id: uniqueID, chat_password: chatPassword }
129
+ ]);
130
+
131
+ if (profileError) throw profileError;
132
+
133
+ spinner.succeed(hackerTheme.primary(`Identity registered! Your Unique ID is: ${chalk.white.bold(uniqueID)}`));
134
+ await showMainMenu(authData.user);
135
+ } else {
136
+ spinner.succeed(hackerTheme.primary('Identity registered!'));
137
+ console.log(hackerTheme.warning('\n[SYSTEM] ATTENTION: Verification required.'));
138
+ console.log(hackerTheme.info('Please check your mailbox and verify your email ID.'));
139
+ console.log(hackerTheme.info('Return to this terminal and [LOGIN] after verification.\n'));
140
+
141
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: hackerTheme.system('Press Enter to return to main menu...') }]);
142
+ await main();
143
+ }
144
+ } catch (err) {
145
+ spinner.fail(hackerTheme.error(`Registration failed: ${err.message}`));
146
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: hackerTheme.system('Press Enter to retry...') }]);
147
+ await main();
148
+ }
149
+ }
150
+
151
+ async function handleLogin() {
152
+ const { email } = await inquirer.prompt([
153
+ { type: 'input', name: 'email', message: hackerTheme.primary('Enter Email (or type "back"):') }
154
+ ]);
155
+
156
+ if (email.toLowerCase() === 'back') return main();
157
+
158
+ const { password } = await inquirer.prompt([
159
+ { type: 'password', name: 'password', message: hackerTheme.primary('Enter Password:') }
160
+ ]);
161
+
162
+ const spinner = ora(hackerTheme.info('Authenticating credentials...')).start();
163
+
164
+ try {
165
+ const { data, error } = await supabase.auth.signInWithPassword({
166
+ email,
167
+ password
168
+ });
169
+
170
+ if (error) throw error;
171
+
172
+ spinner.succeed(hackerTheme.primary('Access granted. Welcome back.'));
173
+ await showMainMenu(data.user);
174
+ } catch (err) {
175
+ spinner.fail(hackerTheme.error(`Authentication failed: ${err.message}`));
176
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: hackerTheme.system('Press Enter to retry...') }]);
177
+ await main();
178
+ }
179
+ }
180
+
181
+ async function handleMissingProfile(user) {
182
+ const { displayName, chatPassword } = await inquirer.prompt([
183
+ { type: 'input', name: 'displayName', message: hackerTheme.primary('Complete your profile. Enter Display Name:') },
184
+ { type: 'password', name: 'chatPassword', message: hackerTheme.warning('Set Chat Access Password (others need this to add you):') }
185
+ ]);
186
+
187
+ const spinner = ora(hackerTheme.info('Completing registration...')).start();
188
+ const uniqueID = await generateUniqueID();
189
+
190
+ try {
191
+ const { error: profileError } = await supabase
192
+ .from('profiles')
193
+ .insert([
194
+ { id: user.id, display_name: displayName, unique_id: uniqueID, chat_password: chatPassword }
195
+ ]);
196
+
197
+ if (profileError) throw profileError;
198
+
199
+ spinner.succeed(hackerTheme.primary(`Profile created! Your ID is: ${chalk.white.bold(uniqueID)}`));
200
+ await showMainMenu(user);
201
+ } catch (err) {
202
+ spinner.fail(hackerTheme.error(`Failed to create profile: ${err.message}`));
203
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: hackerTheme.system('Press Enter to retry...') }]);
204
+ await main();
205
+ }
206
+ }
207
+
208
+ async function showMainMenu(user) {
209
+ const { error: profileError, data: profile } = await supabase
210
+ .from('profiles')
211
+ .select('*')
212
+ .eq('id', user.id)
213
+ .single();
214
+
215
+ if (profileError) {
216
+ if (profileError.code === 'PGRST116') {
217
+ console.log(hackerTheme.warning('\n[SYSTEM] Warning: Profile not found for this account.'));
218
+ return handleMissingProfile(user);
219
+ }
220
+ console.log(hackerTheme.error(`[ERROR] Profile retrieval failed: ${profileError.message}`));
221
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: hackerTheme.system('Press Enter to return to main menu...') }]);
222
+ return main();
223
+ }
224
+
225
+ clearConsole();
226
+ showBanner();
227
+ console.log(hackerTheme.info(`User: ${profile.display_name} | ID: ${profile.unique_id}\n`));
228
+
229
+ const { action } = await inquirer.prompt([
230
+ {
231
+ type: 'list',
232
+ name: 'action',
233
+ message: hackerTheme.primary('COMMAND MENU:'),
234
+ choices: [
235
+ { name: hackerTheme.secondary('[SEARCH] Find user by ID'), value: 'search' },
236
+ { name: hackerTheme.secondary('[REQUESTS] View chat requests'), value: 'requests' },
237
+ { name: hackerTheme.secondary('[CHATS] My active conversations'), value: 'chats' },
238
+ { name: hackerTheme.error('[LOGOUT] Disconnect'), value: 'logout' }
239
+ ]
240
+ }
241
+ ]);
242
+
243
+ switch (action) {
244
+ case 'search':
245
+ await handleSearch(profile);
246
+ break;
247
+ case 'requests':
248
+ await handleRequests(profile);
249
+ break;
250
+ case 'chats':
251
+ await handleChats(profile);
252
+ break;
253
+ case 'logout':
254
+ await supabase.auth.signOut();
255
+ await main();
256
+ break;
257
+ }
258
+ }
259
+
260
+ async function handleSearch(profile) {
261
+ const { targetID } = await inquirer.prompt([
262
+ { type: 'input', name: 'targetID', message: hackerTheme.primary('Enter Unique ID to search (or type "back"):') }
263
+ ]);
264
+
265
+ if (targetID.toLowerCase() === 'back') return showMainMenu({ id: profile.id });
266
+
267
+ if (targetID === profile.unique_id) {
268
+ console.log(hackerTheme.error('[ERROR] Cannot search for yourself.'));
269
+ return showMainMenu({ id: profile.id });
270
+ }
271
+
272
+ const spinner = ora(hackerTheme.info('Pinging target ID...')).start();
273
+
274
+ try {
275
+ const { data: targetProfile, error } = await supabase
276
+ .from('profiles')
277
+ .select('*')
278
+ .eq('unique_id', targetID)
279
+ .single();
280
+
281
+ if (error || !targetProfile) {
282
+ spinner.fail(hackerTheme.error('ID not found in network.'));
283
+ } else {
284
+ spinner.stop();
285
+ // Check if relationship already exists
286
+ const { data: existing, error: checkError } = await supabase
287
+ .from('chat_requests')
288
+ .select('*')
289
+ .or(`and(sender_id.eq.${profile.id},receiver_id.eq.${targetProfile.id}),and(sender_id.eq.${targetProfile.id},receiver_id.eq.${profile.id})`)
290
+ .single();
291
+
292
+ if (existing) {
293
+ if (existing.status === 'accepted') {
294
+ console.log(hackerTheme.info(`[SYSTEM] Connection with ${targetProfile.display_name} already exists.`));
295
+ } else if (existing.sender_id === profile.id) {
296
+ console.log(hackerTheme.info(`[SYSTEM] Signal already transmitted. Waiting for ${targetProfile.display_name} to accept.`));
297
+ } else {
298
+ console.log(hackerTheme.warning(`[SYSTEM] ${targetProfile.display_name} has already sent you a request! Check your [REQUESTS].`));
299
+ }
300
+ } else {
301
+ console.log(hackerTheme.primary(`Target found: ${targetProfile.display_name}`));
302
+
303
+ // Chat Password Verification
304
+ if (targetProfile.chat_password) {
305
+ const { inputPassword } = await inquirer.prompt([
306
+ { type: 'password', name: 'inputPassword', message: hackerTheme.warning(`Enter Chat Access Password for ${targetProfile.unique_id}:`) }
307
+ ]);
308
+
309
+ if (inputPassword !== targetProfile.chat_password) {
310
+ console.log(hackerTheme.error('[ACCESS DENIED] Incorrect Chat Password.'));
311
+ return showMainMenu({ id: profile.id });
312
+ }
313
+ console.log(hackerTheme.secondary('[ACCESS GRANTED] Credentials verified.'));
314
+ }
315
+
316
+ const { confirm } = await inquirer.prompt([
317
+ {
318
+ type: 'confirm',
319
+ name: 'confirm',
320
+ message: hackerTheme.primary(`Send chat request to ${targetProfile.display_name}?`),
321
+ default: true
322
+ }
323
+ ]);
324
+
325
+ if (confirm) {
326
+ const { error: reqError } = await supabase
327
+ .from('chat_requests')
328
+ .insert([
329
+ { sender_id: profile.id, receiver_id: targetProfile.id, status: 'pending' }
330
+ ]);
331
+
332
+ if (reqError) throw reqError;
333
+ console.log(hackerTheme.primary('Request transmitted successfully.'));
334
+ }
335
+ }
336
+ }
337
+ } catch (err) {
338
+ spinner.fail(hackerTheme.error(`Operation failed: ${err.message}`));
339
+ }
340
+
341
+ await showMainMenu({ id: profile.id });
342
+ }
343
+
344
+ async function handleRequests(profile) {
345
+ const spinner = ora(hackerTheme.info('Fetching pending requests...')).start();
346
+
347
+ try {
348
+ const { data: requests, error } = await supabase
349
+ .from('chat_requests')
350
+ .select(`
351
+ id,
352
+ sender_id,
353
+ profiles:sender_id (display_name, unique_id)
354
+ `)
355
+ .eq('receiver_id', profile.id)
356
+ .eq('status', 'pending');
357
+
358
+ if (error) throw error;
359
+
360
+ spinner.stop();
361
+
362
+ if (requests.length === 0) {
363
+ console.log(hackerTheme.info('No pending requests.'));
364
+ } else {
365
+ const { requestAction } = await inquirer.prompt([
366
+ {
367
+ type: 'list',
368
+ name: 'requestAction',
369
+ message: hackerTheme.primary('PENDING REQUESTS:'),
370
+ choices: [
371
+ ...requests.map(r => ({
372
+ name: `${r.profiles.display_name} (${r.profiles.unique_id})`,
373
+ value: r
374
+ })),
375
+ { name: hackerTheme.error('[BACK] Main menu'), value: 'back' }
376
+ ]
377
+ }
378
+ ]);
379
+
380
+ if (requestAction !== 'back') {
381
+ const { decision } = await inquirer.prompt([
382
+ {
383
+ type: 'list',
384
+ name: 'decision',
385
+ message: `Action for ${requestAction.profiles.display_name}:`,
386
+ choices: [
387
+ { name: hackerTheme.primary('[ACCEPT] Establish link'), value: 'accepted' },
388
+ { name: hackerTheme.error('[REJECT] Deny access'), value: 'rejected' },
389
+ { name: hackerTheme.system('[CANCEL] do nothing'), value: 'cancel' }
390
+ ]
391
+ }
392
+ ]);
393
+
394
+ if (decision !== 'cancel') {
395
+ const { error: updateError } = await supabase
396
+ .from('chat_requests')
397
+ .update({ status: decision })
398
+ .eq('id', requestAction.id);
399
+
400
+ if (updateError) throw updateError;
401
+ console.log(hackerTheme.primary(`Link status updated to: ${decision}`));
402
+ }
403
+ }
404
+ }
405
+ } catch (err) {
406
+ spinner.fail(hackerTheme.error(`Failed to manage requests: ${err.message}`));
407
+ }
408
+
409
+ await showMainMenu({ id: profile.id });
410
+ }
411
+
412
+ async function handleChats(profile) {
413
+ const spinner = ora(hackerTheme.info('Opening active channels...')).start();
414
+
415
+ try {
416
+ // Get accepted requests where user is sender or receiver
417
+ const { data: links, error } = await supabase
418
+ .from('chat_requests')
419
+ .select(`
420
+ id,
421
+ sender_id,
422
+ receiver_id,
423
+ sender:sender_id (id, display_name, unique_id),
424
+ receiver:receiver_id (id, display_name, unique_id)
425
+ `)
426
+ .or(`sender_id.eq.${profile.id},receiver_id.eq.${profile.id}`)
427
+ .eq('status', 'accepted');
428
+
429
+ if (error) throw error;
430
+
431
+ spinner.stop();
432
+
433
+ if (links.length === 0) {
434
+ console.log(hackerTheme.info('No active chats found. Find someone by ID first!'));
435
+ } else {
436
+ const { chat } = await inquirer.prompt([
437
+ {
438
+ type: 'list',
439
+ name: 'chat',
440
+ message: hackerTheme.primary('ACTIVE CHANNELS:'),
441
+ choices: [
442
+ ...links.map(l => {
443
+ const other = l.sender_id === profile.id ? l.receiver : l.sender;
444
+ return { name: `${other.display_name} (${other.unique_id})`, value: other };
445
+ }),
446
+ { name: hackerTheme.error('[BACK] Main menu'), value: 'back' }
447
+ ]
448
+ }
449
+ ]);
450
+
451
+ if (chat !== 'back') {
452
+ await startChat(profile, chat);
453
+ }
454
+ }
455
+ } catch (err) {
456
+ spinner.fail(hackerTheme.error(`Failed to load chats: ${err.message}`));
457
+ }
458
+
459
+ await showMainMenu({ id: profile.id });
460
+ }
461
+
462
+ async function startChat(me, other) {
463
+ clearConsole();
464
+ showBanner();
465
+ renderBox(`CONNECTION ESTABLISHED\nPEER: ${other.display_name}\nID: ${other.unique_id}\nSTATUS: ENCRYPTED`, 'SECURE_CHANNEL');
466
+ console.log(hackerTheme.system('Type "/help" for protocol list or "/exit" to disconnect.\n'));
467
+
468
+ // Fetch history
469
+ const { data: messages, error: fetchError } = await supabase
470
+ .from('messages')
471
+ .select('*')
472
+ .or(`and(sender_id.eq.${me.id},receiver_id.eq.${other.id}),and(sender_id.eq.${other.id},receiver_id.eq.${me.id})`)
473
+ .order('created_at', { ascending: true });
474
+
475
+ if (!fetchError) {
476
+ messages.forEach(msg => {
477
+ const label = msg.sender_id === me.id ? hackerTheme.primary('[YOU]') : hackerTheme.secondary(`[${other.display_name}]`);
478
+ console.log(`${label}: ${msg.content}`);
479
+ });
480
+ }
481
+
482
+ // Subscribe to new messages
483
+ const channel = supabase
484
+ .channel(`chat_${me.id}_${other.id}`)
485
+ .on('postgres_changes', {
486
+ event: 'INSERT',
487
+ schema: 'public',
488
+ table: 'messages',
489
+ filter: `receiver_id=eq.${me.id}`
490
+ }, payload => {
491
+ if (payload.new.sender_id === other.id) {
492
+ console.log(`${hackerTheme.secondary(`[${other.display_name}]`)}: ${payload.new.content}`);
493
+ }
494
+ })
495
+ .subscribe();
496
+
497
+ let active = true;
498
+ while (active) {
499
+ const { content } = await inquirer.prompt([
500
+ { type: 'input', name: 'content', message: hackerTheme.primary('>_ ') }
501
+ ]);
502
+
503
+ if (content.startsWith('/')) {
504
+ const cmd = content.split(' ')[0].toLowerCase();
505
+ switch (cmd) {
506
+ case '/exit':
507
+ active = false;
508
+ break;
509
+ case '/help':
510
+ renderBox(
511
+ '/help - Show this manual\n' +
512
+ '/clear - Flush display buffer\n' +
513
+ '/info - Peer identity status\n' +
514
+ '/shrug - Inject ASCII shrug\n' +
515
+ '/flip - Table maneuver\n' +
516
+ '/nuke - Emergency disconnect',
517
+ 'COMMAND_PROTOCOLS'
518
+ );
519
+ break;
520
+ case '/clear':
521
+ clearConsole();
522
+ showBanner();
523
+ renderBox(`PEER: ${other.display_name}\nID: ${other.unique_id}`, 'SESSION_RECOVERED');
524
+ break;
525
+ case '/info':
526
+ renderBox(`NAME: ${other.display_name}\nUID: ${other.unique_id}\nLINK_STABILITY: 100%`, 'PEER_INTEL');
527
+ break;
528
+ case '/shrug':
529
+ await sendMessage(me.id, other.id, '¯\\_(ツ)_/¯');
530
+ break;
531
+ case '/flip':
532
+ await sendMessage(me.id, other.id, '(╯°□°)╯︵ ┻━┻');
533
+ break;
534
+ case '/nuke':
535
+ await handleNuke();
536
+ process.exit(0);
537
+ break;
538
+ default:
539
+ console.log(hackerTheme.error(`[SYSTEM] Invalid protocol: ${cmd}`));
540
+ }
541
+ } else if (content.trim()) {
542
+ await sendMessage(me.id, other.id, content);
543
+ }
544
+ }
545
+
546
+ supabase.removeChannel(channel);
547
+ }
548
+
549
+ async function sendMessage(senderId, receiverId, content) {
550
+ const { error: sendError } = await supabase
551
+ .from('messages')
552
+ .insert([{ sender_id: senderId, receiver_id: receiverId, content }]);
553
+
554
+ if (sendError) {
555
+ console.log(hackerTheme.error(`[ERROR] Transmission failed: ${sendError.message}`));
556
+ } else {
557
+ // Local echo for self messages if not handled by subscription (subscription only filters for receiver)
558
+ console.log(`${hackerTheme.primary('[YOU]')}: ${content}`);
559
+ }
560
+ }
561
+
562
+ async function handleNuke() {
563
+ const chars = '█▓▒░/\\<>#!?@$%^&*()';
564
+ const lines = 10;
565
+ for (let i = 0; i < lines; i++) {
566
+ let noise = '';
567
+ for (let j = 0; j < 50; j++) {
568
+ noise += chars[Math.floor(Math.random() * chars.length)];
569
+ }
570
+ console.log(hackerTheme.error(noise));
571
+ await SLEEP(50);
572
+ }
573
+ console.log(hackerTheme.error('\n!!! CRITICAL SYSTEM FAILURE: EMERGENCY SHUTDOWN INITIATED !!!\n'));
574
+ await SLEEP(500);
575
+ }
576
+
577
+ main();
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "keyline-cli",
3
+ "version": "1.0.0",
4
+ "description": "Secure Terminal Communication Protocol",
5
+ "main": "keyline.js",
6
+ "bin": {
7
+ "keyline": "./keyline.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "cli",
14
+ "chat",
15
+ "secure",
16
+ "terminal"
17
+ ],
18
+ "author": "KeyLine Inc.",
19
+ "license": "ISC",
20
+ "type": "commonjs",
21
+ "dependencies": {
22
+ "@supabase/supabase-js": "^2.39.1",
23
+ "chalk": "^4.1.2",
24
+ "dotenv": "^16.3.1",
25
+ "figlet": "^1.7.0",
26
+ "inquirer": "^8.2.6",
27
+ "nanoid": "^3.3.7",
28
+ "ora": "^5.4.1"
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ ALTER TABLE profiles ADD COLUMN IF NOT EXISTS chat_password TEXT;