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.
- package/bin/setup.js +168 -27
- 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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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 (
|
|
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 !== '
|
|
684
|
+
if (choice !== '0' && choice !== '__EOF__') {
|
|
544
685
|
if (await ask('Press Enter to return to the menu...') === '__EOF__') break;
|
|
545
686
|
}
|
|
546
687
|
}
|