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.
- package/bin/setup.js +153 -27
- 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
|
|
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 = [
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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 (
|
|
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 !== '
|
|
669
|
+
if (choice !== '0' && choice !== '__EOF__') {
|
|
544
670
|
if (await ask('Press Enter to return to the menu...') === '__EOF__') break;
|
|
545
671
|
}
|
|
546
672
|
}
|