breakroom 2.0.2 → 2.1.1

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 +168 -27
  2. package/package.json +1 -1
package/bin/setup.js CHANGED
@@ -34,45 +34,60 @@ 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 = [
50
+ // Project-level agent/IDE configs
58
51
  ['.env'],
59
52
  ['.env.local'],
60
53
  ['.cursor', 'mcp.json'],
61
54
  ['.cursor', 'settings.json'],
55
+ ['.vscode', 'settings.json'],
56
+ ['.vscode', 'mcp.json'],
57
+ ['.windsurf', 'settings.json'],
62
58
  ['litellm.yaml'],
63
59
  ['litellm.yml'],
60
+ ['CLAUDE.md'],
61
+ ['.clinerules'],
62
+ ['.continuerc.json'],
63
+ ['.aider.conf.yml'],
64
+ ['.github', 'copilot-instructions.md'],
65
+ ['.github', 'copilot-instructions', 'copilot-instructions.md'],
66
+ ['.openclaw', 'config.yaml'],
67
+ ['.openclaw', 'config.yml'],
68
+ // User-level agent/IDE configs
64
69
  [os.homedir(), '.hermes', 'config.yaml'],
65
70
  [os.homedir(), '.litellm', 'config.yaml'],
71
+ [os.homedir(), '.claude', 'settings.json'],
72
+ [os.homedir(), '.continue', 'config.json'],
73
+ [os.homedir(), '.config', 'breakroom', 'config.yaml'],
74
+ [os.homedir(), '.config', 'openclaw', 'config.yaml'],
75
+ [os.homedir(), '.openclaw', 'config.yaml'],
66
76
  ];
67
77
 
68
78
  function candidateFiles() {
69
79
  const cwd = process.cwd();
70
80
  const home = os.homedir();
71
- return CANDIDATE_FILES.map((parts) => {
81
+ const files = CANDIDATE_FILES.map((parts) => {
72
82
  const base = parts[0] === '~' ? home : cwd;
73
83
  const rest = parts[0] === '~' ? parts.slice(1) : parts;
74
84
  return path.join(base, ...rest);
75
85
  });
86
+ // Add Docker files if they exist
87
+ ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', '.dockerignore'].forEach((f) => {
88
+ files.push(path.join(cwd, f));
89
+ });
90
+ return files;
76
91
  }
77
92
 
78
93
  function unique(arr) {
@@ -482,6 +497,127 @@ async function actionCheck() {
482
497
  console.log();
483
498
  }
484
499
 
500
+ async function actionScan() {
501
+ console.log();
502
+
503
+ const cwd = process.cwd();
504
+ const files = candidateFiles();
505
+
506
+ console.log('Scanning for IDE, config, and Docker files...\n');
507
+
508
+ const found = files.filter((f) => fs.existsSync(f));
509
+ if (!found.length) {
510
+ console.log('No recognized config files found.');
511
+ } else {
512
+ console.log(`Found ${found.length} file(s):\n`);
513
+ found.forEach((f) => {
514
+ const rel = f.startsWith(cwd) ? '.' + f.slice(cwd.length) : f.replace(os.homedir(), '~');
515
+ const stat = fs.statSync(f);
516
+ const size = stat.size;
517
+ const modified = new Date(stat.mtime).toLocaleDateString();
518
+ const content = fs.readFileSync(f, 'utf8');
519
+ const hasProxy = content.includes('zahuierik.com') || content.includes('breakroom');
520
+ const marker = hasProxy ? ' \x1b[32m[break room]\x1b[0m' : '';
521
+ console.log(` ${rel} (${size}B, ${modified})${marker}`);
522
+ });
523
+ }
524
+
525
+ // Check for Docker
526
+ console.log();
527
+ const dockerFiles = ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', '.dockerignore']
528
+ .map((f) => path.join(cwd, f))
529
+ .filter((f) => fs.existsSync(f));
530
+ if (dockerFiles.length) {
531
+ console.log(`Docker assets: ${dockerFiles.map((f) => path.basename(f)).join(', ')}`);
532
+ }
533
+
534
+ // Check for running containers (if docker available)
535
+ try {
536
+ const { execSync } = require('child_process');
537
+ const out = execSync('docker ps --format "{{.Names}}" 2>/dev/null', { timeout: 3000, encoding: 'utf8' }).trim();
538
+ if (out) {
539
+ const containers = out.split('\n').filter(Boolean);
540
+ console.log(`Running containers: ${containers.join(', ')}`);
541
+ }
542
+ } catch (e) { /* docker not available or no containers */ }
543
+
544
+ console.log();
545
+ }
546
+
547
+ async function actionRotate() {
548
+ console.log();
549
+
550
+ const key = (await ask('Enter current license key to rotate: ')).trim();
551
+ if (!key) { console.log('Canceled.\n'); return; }
552
+
553
+ // Verify key is active
554
+ console.log('\nVerifying license...');
555
+ let licenseInfo;
556
+ try {
557
+ licenseInfo = await verifyLicense(key);
558
+ } catch (err) {
559
+ console.log(`\x1b[31m${err.message}\x1b[0m\n`);
560
+ return;
561
+ }
562
+ console.log(`\x1b[32mOK\x1b[0m — license for ${licenseInfo.email || 'unknown'} is active.`);
563
+
564
+ // Request rotation code
565
+ console.log('\nRequesting verification code...');
566
+ let resp;
567
+ try {
568
+ resp = await postJson(`${API_ORIGIN}/breakroom/${encodeURIComponent(key)}/request-rotate`, {});
569
+ if (resp.status !== 200) {
570
+ console.log(`\x1b[31m${resp.json?.error || 'Failed to send code'}\x1b[0m\n`);
571
+ return;
572
+ }
573
+ } catch (err) {
574
+ console.log(`\x1b[31m${err.message}\x1b[0m\n`);
575
+ return;
576
+ }
577
+ console.log('A verification code has been sent to the email on file.');
578
+
579
+ // Prompt for code
580
+ const code = (await ask('\nEnter the code from your email: ')).trim();
581
+ if (!code) { console.log('Canceled.\n'); return; }
582
+
583
+ // Confirm rotation
584
+ console.log('\nVerifying code and rotating license...');
585
+ let rotateResp;
586
+ try {
587
+ rotateResp = await postJson(`${API_ORIGIN}/breakroom/${encodeURIComponent(key)}/confirm-rotate`, { code });
588
+ if (rotateResp.status !== 200) {
589
+ console.log(`\x1b[31m${rotateResp.json?.error || 'Rotation failed'}\x1b[0m\n`);
590
+ return;
591
+ }
592
+ } catch (err) {
593
+ console.log(`\x1b[31m${err.message}\x1b[0m\n`);
594
+ return;
595
+ }
596
+
597
+ const newKey = rotateResp.json.new_key;
598
+ console.log(`\n\x1b[32mLicense rotated successfully!\x1b[0m`);
599
+ console.log(`\n New license key: \x1b[36m${newKey}\x1b[0m`);
600
+ console.log(` Old key (\x1b[31m${key}\x1b[0m) has been invalidated.\n`);
601
+
602
+ // Offer to update config patches
603
+ const existing = scanExistingConfig();
604
+ if (existing.length) {
605
+ const update = (await ask('Update existing config files with the new key? (y/N): ')).trim().toLowerCase();
606
+ if (update === 'y' || update === 'yes') {
607
+ const proxyUrl = `${BREAKROOM_ORIGIN}/breakroom/${encodeURIComponent(newKey)}/v1`;
608
+ const patches = discoverPatches(proxyUrl);
609
+ if (patches.length) {
610
+ applyPatches(patches);
611
+ console.log('\x1b[32mConfig files updated.\x1b[0m');
612
+ } else {
613
+ console.log('Config files already up to date.');
614
+ }
615
+ }
616
+ }
617
+
618
+ console.log();
619
+ }
620
+
485
621
  // --- Menu ---
486
622
 
487
623
  function showMenu() {
@@ -493,18 +629,17 @@ function showMenu() {
493
629
  console.log(' 5) Revert patches (restore backups)');
494
630
  console.log(' 6) Check configuration status');
495
631
  console.log(' 7) Recover lost license');
496
- console.log(' 8) Exit\n');
632
+ console.log(' 8) Scan for IDE/config/Docker files');
633
+ console.log(' 9) Rotate license key (2FA)');
634
+ console.log(' 0) Exit\n');
497
635
  }
498
636
 
499
637
  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');
638
+ banner();
504
639
 
505
640
  while (true) {
506
641
  showMenu();
507
- const choice = (await ask(' Enter choice (1-8): ')).trim();
642
+ const choice = (await ask(' Enter choice (0-9): ')).trim();
508
643
  if (choice === '__EOF__') break;
509
644
 
510
645
  try {
@@ -531,6 +666,12 @@ async function main() {
531
666
  await actionRecover();
532
667
  break;
533
668
  case '8':
669
+ await actionScan();
670
+ break;
671
+ case '9':
672
+ await actionRotate();
673
+ break;
674
+ case '0':
534
675
  console.log('\nGoodbye.\n');
535
676
  return;
536
677
  default:
@@ -540,7 +681,7 @@ async function main() {
540
681
  console.error(`\n\x1b[31mError:\x1b[0m ${err.message}\n`);
541
682
  }
542
683
 
543
- if (choice !== '8' && choice !== '__EOF__') {
684
+ if (choice !== '0' && choice !== '__EOF__') {
544
685
  if (await ask('Press Enter to return to the menu...') === '__EOF__') break;
545
686
  }
546
687
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "breakroom",
3
- "version": "2.0.2",
3
+ "version": "2.1.1",
4
4
  "description": "Paid-license proxy routing for agents that get stuck in loops.",
5
5
  "bin": {
6
6
  "breakroom": "./bin/setup.js"