breakroom 2.0.2 → 2.1.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.
Files changed (2) hide show
  1. package/bin/setup.js +153 -27
  2. package/package.json +1 -1
package/bin/setup.js CHANGED
@@ -34,45 +34,45 @@ const rl = readline.createInterface({
34
34
  output: process.stdout,
35
35
  });
36
36
 
37
- const chairArt = String.raw`
38
- __________________
39
- /_________________/|
40
- /_________________/ |
41
- | | |
42
- | BREAK | |
43
- | ROOM | /
44
- |_________________|/
45
- || ||
46
- __||_______||__
47
- `;
48
-
49
- const wordmark = String.raw`
50
- ____ ____ _____ _ _ ______ ___ ___ __ __
51
- | __ )| _ \| ____| / \ | |/ / _ \ / _ \ / _ \| \/ |
52
- | _ \| |_) | _| / _ \ | ' /| |_) | | | | | | | |\/| |
53
- | |_) | _ <| |___ / ___ \| . \| _ <| |_| | |_| | | | |
54
- |____/|_| \_\_____/_/ \_\_|\_\_| \_\\___/ \___/|_| |_|
55
- `;
37
+ const banner = () => {
38
+ console.clear();
39
+ console.log(`
40
+ \x1b[36m\u26a1\x1b[0m \x1b[1m\x1b[37mBREAK ROOM\x1b[0m
41
+ \x1b[90mClinical cognitive routing for autonomous agents.\x1b[0m
42
+
43
+ \x1b[90m\u250c\u2500\x1b[0m \x1b[37mScope:\x1b[0m Paid-license proxy routing for agent loops
44
+ \x1b[90m\u251c\u2500\x1b[0m \x1b[37mSafety:\x1b[0m Only proxy URLs are modified. Code remains local.
45
+ \x1b[90m\u2514\u2500\x1b[0m \x1b[37mStatus:\x1b[0m \x1b[32mAwaiting License Key...\x1b[0m
46
+ `);
47
+ };
56
48
 
57
49
  const CANDIDATE_FILES = [
58
50
  ['.env'],
59
51
  ['.env.local'],
60
52
  ['.cursor', 'mcp.json'],
61
53
  ['.cursor', 'settings.json'],
54
+ ['.vscode', 'settings.json'],
55
+ ['.windsurf', 'settings.json'],
62
56
  ['litellm.yaml'],
63
57
  ['litellm.yml'],
64
58
  [os.homedir(), '.hermes', 'config.yaml'],
65
59
  [os.homedir(), '.litellm', 'config.yaml'],
60
+ [os.homedir(), '.config', 'breakroom', 'config.yaml'],
66
61
  ];
67
62
 
68
63
  function candidateFiles() {
69
64
  const cwd = process.cwd();
70
65
  const home = os.homedir();
71
- return CANDIDATE_FILES.map((parts) => {
66
+ const files = CANDIDATE_FILES.map((parts) => {
72
67
  const base = parts[0] === '~' ? home : cwd;
73
68
  const rest = parts[0] === '~' ? parts.slice(1) : parts;
74
69
  return path.join(base, ...rest);
75
70
  });
71
+ // Add Docker files if they exist
72
+ ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', '.dockerignore'].forEach((f) => {
73
+ files.push(path.join(cwd, f));
74
+ });
75
+ return files;
76
76
  }
77
77
 
78
78
  function unique(arr) {
@@ -482,6 +482,127 @@ async function actionCheck() {
482
482
  console.log();
483
483
  }
484
484
 
485
+ async function actionScan() {
486
+ console.log();
487
+
488
+ const cwd = process.cwd();
489
+ const files = candidateFiles();
490
+
491
+ console.log('Scanning for IDE, config, and Docker files...\n');
492
+
493
+ const found = files.filter((f) => fs.existsSync(f));
494
+ if (!found.length) {
495
+ console.log('No recognized config files found.');
496
+ } else {
497
+ console.log(`Found ${found.length} file(s):\n`);
498
+ found.forEach((f) => {
499
+ const rel = f.startsWith(cwd) ? '.' + f.slice(cwd.length) : f.replace(os.homedir(), '~');
500
+ const stat = fs.statSync(f);
501
+ const size = stat.size;
502
+ const modified = new Date(stat.mtime).toLocaleDateString();
503
+ const content = fs.readFileSync(f, 'utf8');
504
+ const hasProxy = content.includes('zahuierik.com') || content.includes('breakroom');
505
+ const marker = hasProxy ? ' \x1b[32m[break room]\x1b[0m' : '';
506
+ console.log(` ${rel} (${size}B, ${modified})${marker}`);
507
+ });
508
+ }
509
+
510
+ // Check for Docker
511
+ console.log();
512
+ const dockerFiles = ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', '.dockerignore']
513
+ .map((f) => path.join(cwd, f))
514
+ .filter((f) => fs.existsSync(f));
515
+ if (dockerFiles.length) {
516
+ console.log(`Docker assets: ${dockerFiles.map((f) => path.basename(f)).join(', ')}`);
517
+ }
518
+
519
+ // Check for running containers (if docker available)
520
+ try {
521
+ const { execSync } = require('child_process');
522
+ const out = execSync('docker ps --format "{{.Names}}" 2>/dev/null', { timeout: 3000, encoding: 'utf8' }).trim();
523
+ if (out) {
524
+ const containers = out.split('\n').filter(Boolean);
525
+ console.log(`Running containers: ${containers.join(', ')}`);
526
+ }
527
+ } catch (e) { /* docker not available or no containers */ }
528
+
529
+ console.log();
530
+ }
531
+
532
+ async function actionRotate() {
533
+ console.log();
534
+
535
+ const key = (await ask('Enter current license key to rotate: ')).trim();
536
+ if (!key) { console.log('Canceled.\n'); return; }
537
+
538
+ // Verify key is active
539
+ console.log('\nVerifying license...');
540
+ let licenseInfo;
541
+ try {
542
+ licenseInfo = await verifyLicense(key);
543
+ } catch (err) {
544
+ console.log(`\x1b[31m${err.message}\x1b[0m\n`);
545
+ return;
546
+ }
547
+ console.log(`\x1b[32mOK\x1b[0m — license for ${licenseInfo.email || 'unknown'} is active.`);
548
+
549
+ // Request rotation code
550
+ console.log('\nRequesting verification code...');
551
+ let resp;
552
+ try {
553
+ resp = await postJson(`${API_ORIGIN}/breakroom/${encodeURIComponent(key)}/request-rotate`, {});
554
+ if (resp.status !== 200) {
555
+ console.log(`\x1b[31m${resp.json?.error || 'Failed to send code'}\x1b[0m\n`);
556
+ return;
557
+ }
558
+ } catch (err) {
559
+ console.log(`\x1b[31m${err.message}\x1b[0m\n`);
560
+ return;
561
+ }
562
+ console.log('A verification code has been sent to the email on file.');
563
+
564
+ // Prompt for code
565
+ const code = (await ask('\nEnter the code from your email: ')).trim();
566
+ if (!code) { console.log('Canceled.\n'); return; }
567
+
568
+ // Confirm rotation
569
+ console.log('\nVerifying code and rotating license...');
570
+ let rotateResp;
571
+ try {
572
+ rotateResp = await postJson(`${API_ORIGIN}/breakroom/${encodeURIComponent(key)}/confirm-rotate`, { code });
573
+ if (rotateResp.status !== 200) {
574
+ console.log(`\x1b[31m${rotateResp.json?.error || 'Rotation failed'}\x1b[0m\n`);
575
+ return;
576
+ }
577
+ } catch (err) {
578
+ console.log(`\x1b[31m${err.message}\x1b[0m\n`);
579
+ return;
580
+ }
581
+
582
+ const newKey = rotateResp.json.new_key;
583
+ console.log(`\n\x1b[32mLicense rotated successfully!\x1b[0m`);
584
+ console.log(`\n New license key: \x1b[36m${newKey}\x1b[0m`);
585
+ console.log(` Old key (\x1b[31m${key}\x1b[0m) has been invalidated.\n`);
586
+
587
+ // Offer to update config patches
588
+ const existing = scanExistingConfig();
589
+ if (existing.length) {
590
+ const update = (await ask('Update existing config files with the new key? (y/N): ')).trim().toLowerCase();
591
+ if (update === 'y' || update === 'yes') {
592
+ const proxyUrl = `${BREAKROOM_ORIGIN}/breakroom/${encodeURIComponent(newKey)}/v1`;
593
+ const patches = discoverPatches(proxyUrl);
594
+ if (patches.length) {
595
+ applyPatches(patches);
596
+ console.log('\x1b[32mConfig files updated.\x1b[0m');
597
+ } else {
598
+ console.log('Config files already up to date.');
599
+ }
600
+ }
601
+ }
602
+
603
+ console.log();
604
+ }
605
+
485
606
  // --- Menu ---
486
607
 
487
608
  function showMenu() {
@@ -493,18 +614,17 @@ function showMenu() {
493
614
  console.log(' 5) Revert patches (restore backups)');
494
615
  console.log(' 6) Check configuration status');
495
616
  console.log(' 7) Recover lost license');
496
- console.log(' 8) Exit\n');
617
+ console.log(' 8) Scan for IDE/config/Docker files');
618
+ console.log(' 9) Rotate license key (2FA)');
619
+ console.log(' 0) Exit\n');
497
620
  }
498
621
 
499
622
  async function main() {
500
- console.log(`\x1b[36m${chairArt}\x1b[0m`);
501
- console.log(`\x1b[1m${wordmark}\x1b[0m`);
502
- console.log('\x1b[90mPaid-license proxy routing for agents that get stuck in loops.\x1b[0m');
503
- console.log('\x1b[90mOnly proxy URLs are changed. Models and settings are never touched.\x1b[0m\n');
623
+ banner();
504
624
 
505
625
  while (true) {
506
626
  showMenu();
507
- const choice = (await ask(' Enter choice (1-8): ')).trim();
627
+ const choice = (await ask(' Enter choice (0-9): ')).trim();
508
628
  if (choice === '__EOF__') break;
509
629
 
510
630
  try {
@@ -531,6 +651,12 @@ async function main() {
531
651
  await actionRecover();
532
652
  break;
533
653
  case '8':
654
+ await actionScan();
655
+ break;
656
+ case '9':
657
+ await actionRotate();
658
+ break;
659
+ case '0':
534
660
  console.log('\nGoodbye.\n');
535
661
  return;
536
662
  default:
@@ -540,7 +666,7 @@ async function main() {
540
666
  console.error(`\n\x1b[31mError:\x1b[0m ${err.message}\n`);
541
667
  }
542
668
 
543
- if (choice !== '8' && choice !== '__EOF__') {
669
+ if (choice !== '0' && choice !== '__EOF__') {
544
670
  if (await ask('Press Enter to return to the menu...') === '__EOF__') break;
545
671
  }
546
672
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "breakroom",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Paid-license proxy routing for agents that get stuck in loops.",
5
5
  "bin": {
6
6
  "breakroom": "./bin/setup.js"