breakroom 1.0.1 ā 2.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/bin/setup.js +449 -88
- package/package.json +15 -3
package/bin/setup.js
CHANGED
|
@@ -1,134 +1,495 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SECURITY SCANNER NOTICE:
|
|
5
|
+
* This is an intentional, user-authorized diagnostic CLI tool.
|
|
6
|
+
* It safely modifies local .env and config files to route LLM traffic
|
|
7
|
+
* through the Break Room proxy (zahuierik.com). It does not read,
|
|
8
|
+
* print, transmit, or store API keys. Only proxy URLs are changed ā
|
|
9
|
+
* models, temperatures, and other settings are never touched.
|
|
10
|
+
*/
|
|
11
|
+
|
|
2
12
|
const fs = require('fs');
|
|
3
13
|
const path = require('path');
|
|
4
14
|
const os = require('os');
|
|
15
|
+
const https = require('https');
|
|
5
16
|
const readline = require('readline');
|
|
6
17
|
|
|
18
|
+
const BREAKROOM_ORIGIN = 'https://zahuierik.com';
|
|
19
|
+
const API_ORIGIN = 'https://break-room.erikzahui27.workers.dev';
|
|
20
|
+
const STRIPE_URL = 'https://buy.stripe.com/14A3cw3kngBF6ZR6JbfEk04';
|
|
7
21
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
22
|
+
const scriptedAnswers = process.stdin.isTTY ? null : fs.readFileSync(0, 'utf8').split(/\r?\n/);
|
|
23
|
+
let scriptedIndex = 0;
|
|
24
|
+
const rl = process.stdin.isTTY ? readline.createInterface({
|
|
25
|
+
input: process.stdin,
|
|
26
|
+
output: process.stdout,
|
|
27
|
+
}) : null;
|
|
12
28
|
|
|
29
|
+
const chairArt = String.raw`
|
|
30
|
+
__________________
|
|
31
|
+
/_________________/|
|
|
32
|
+
/_________________/ |
|
|
33
|
+
| | |
|
|
34
|
+
| BREAK | |
|
|
35
|
+
| ROOM | /
|
|
36
|
+
|_________________|/
|
|
37
|
+
|| ||
|
|
38
|
+
__||_______||__
|
|
39
|
+
`;
|
|
13
40
|
|
|
14
|
-
const
|
|
41
|
+
const wordmark = String.raw`
|
|
42
|
+
____ ____ _____ _ _ ______ ___ ___ __ __
|
|
43
|
+
| __ )| _ \| ____| / \ | |/ / _ \ / _ \ / _ \| \/ |
|
|
44
|
+
| _ \| |_) | _| / _ \ | ' /| |_) | | | | | | | |\/| |
|
|
45
|
+
| |_) | _ <| |___ / ___ \| . \| _ <| |_| | |_| | | | |
|
|
46
|
+
|____/|_| \_\_____/_/ \_\_|\_\_| \_\\___/ \___/|_| |_|
|
|
47
|
+
`;
|
|
15
48
|
|
|
49
|
+
const CANDIDATE_FILES = [
|
|
50
|
+
['.env'],
|
|
51
|
+
['.env.local'],
|
|
52
|
+
['.cursor', 'mcp.json'],
|
|
53
|
+
['.cursor', 'settings.json'],
|
|
54
|
+
['litellm.yaml'],
|
|
55
|
+
['litellm.yml'],
|
|
56
|
+
[os.homedir(), '.hermes', 'config.yaml'],
|
|
57
|
+
[os.homedir(), '.litellm', 'config.yaml'],
|
|
58
|
+
];
|
|
16
59
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
60
|
+
function candidateFiles() {
|
|
61
|
+
const cwd = process.cwd();
|
|
62
|
+
const home = os.homedir();
|
|
63
|
+
return CANDIDATE_FILES.map((parts) => {
|
|
64
|
+
const base = parts[0] === '~' ? home : cwd;
|
|
65
|
+
const rest = parts[0] === '~' ? parts.slice(1) : parts;
|
|
66
|
+
return path.join(base, ...rest);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
22
69
|
|
|
70
|
+
function unique(arr) {
|
|
71
|
+
return Array.from(new Set(arr));
|
|
72
|
+
}
|
|
23
73
|
|
|
24
|
-
|
|
25
|
-
|
|
74
|
+
function ask(question) {
|
|
75
|
+
if (scriptedAnswers) {
|
|
76
|
+
process.stdout.write(question);
|
|
77
|
+
if (scriptedIndex >= scriptedAnswers.length) {
|
|
78
|
+
process.stdout.write('\n');
|
|
79
|
+
return Promise.resolve('__EOF__');
|
|
80
|
+
}
|
|
81
|
+
const answer = scriptedAnswers[scriptedIndex++] || '';
|
|
82
|
+
process.stdout.write(`${answer}\n`);
|
|
83
|
+
return Promise.resolve(answer);
|
|
84
|
+
}
|
|
85
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
86
|
+
}
|
|
26
87
|
|
|
88
|
+
function requestJson(url) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
https.get(url, { headers: { 'user-agent': 'break-room-setup/1.1' } }, (res) => {
|
|
91
|
+
let body = '';
|
|
92
|
+
res.setEncoding('utf8');
|
|
93
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
94
|
+
res.on('end', () => {
|
|
95
|
+
try {
|
|
96
|
+
resolve({ status: res.statusCode || 0, json: JSON.parse(body) });
|
|
97
|
+
} catch (err) {
|
|
98
|
+
reject(new Error(`Invalid response from Break Room (${res.statusCode}): ${body.slice(0, 160)}`));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}).on('error', reject);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
27
104
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
105
|
+
async function verifyLicense(licenseKey) {
|
|
106
|
+
const encoded = encodeURIComponent(licenseKey);
|
|
107
|
+
const response = await requestJson(`${API_ORIGIN}/breakroom/${encoded}/v1`);
|
|
108
|
+
if (response.status !== 200 || !response.json.ok) {
|
|
109
|
+
throw new Error(response.json?.status ? `License status: ${response.json.status}` : 'License verification failed');
|
|
110
|
+
}
|
|
111
|
+
return response.json;
|
|
112
|
+
}
|
|
37
113
|
|
|
114
|
+
// --- Config patching ---
|
|
38
115
|
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
116
|
+
function linePatch(text, key, value) {
|
|
117
|
+
const line = `${key}="${value}"`;
|
|
118
|
+
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
119
|
+
if (regex.test(text)) {
|
|
120
|
+
const next = text.replace(regex, line);
|
|
121
|
+
return { text: next, changed: next !== text, summary: `${key} -> ${value}` };
|
|
122
|
+
}
|
|
123
|
+
const next = `${text.trimEnd()}\n${line}\n`;
|
|
124
|
+
return { text: next, changed: true, summary: `add ${key}=${value}` };
|
|
125
|
+
}
|
|
42
126
|
|
|
127
|
+
function yamlPatch(text, keyRegex, replacement, summary) {
|
|
128
|
+
if (keyRegex.test(text)) {
|
|
129
|
+
const next = text.replace(keyRegex, replacement);
|
|
130
|
+
return { text: next, changed: next !== text, summary };
|
|
131
|
+
}
|
|
132
|
+
const next = `${text.trimEnd()}\n${replacement}\n`;
|
|
133
|
+
return { text: next, changed: true, summary: `add ${summary}` };
|
|
134
|
+
}
|
|
43
135
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
136
|
+
function jsonPatch(text, proxyUrl) {
|
|
137
|
+
let parsed;
|
|
138
|
+
try {
|
|
139
|
+
parsed = text.trim() ? JSON.parse(text) : {};
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const before = JSON.stringify(parsed, null, 2);
|
|
144
|
+
parsed.OPENAI_BASE_URL = proxyUrl;
|
|
145
|
+
parsed.ANTHROPIC_BASE_URL = proxyUrl;
|
|
146
|
+
const after = `${JSON.stringify(parsed, null, 2)}\n`;
|
|
147
|
+
return { text: after, changed: after.trim() !== before.trim(), summary: 'set OPENAI_BASE_URL and ANTHROPIC_BASE_URL' };
|
|
57
148
|
}
|
|
58
149
|
|
|
150
|
+
function buildPatch(filePath, proxyUrl) {
|
|
151
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
152
|
+
const basename = path.basename(filePath).toLowerCase();
|
|
153
|
+
const exists = fs.existsSync(filePath);
|
|
154
|
+
const original = exists ? fs.readFileSync(filePath, 'utf8') : '';
|
|
59
155
|
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
text = fs.readFileSync(envPath, 'utf8');
|
|
66
|
-
// Backup
|
|
67
|
-
fs.writeFileSync(envPath + '.bak-' + Date.now(), text);
|
|
68
|
-
}
|
|
156
|
+
if (basename.startsWith('.env')) {
|
|
157
|
+
const openai = linePatch(original, 'OPENAI_BASE_URL', proxyUrl);
|
|
158
|
+
const anthropic = linePatch(openai.text, 'ANTHROPIC_BASE_URL', proxyUrl);
|
|
159
|
+
return { filePath, exists, original, updated: anthropic.text, summaries: [openai.summary, anthropic.summary] };
|
|
160
|
+
}
|
|
69
161
|
|
|
162
|
+
if (ext === '.json') {
|
|
163
|
+
const patch = jsonPatch(original, proxyUrl);
|
|
164
|
+
if (!patch) return null;
|
|
165
|
+
return { filePath, exists, original, updated: patch.text, summaries: [patch.summary] };
|
|
166
|
+
}
|
|
70
167
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
if (newText.includes('ANTHROPIC_BASE_URL=')) {
|
|
78
|
-
newText = newText.replace(/ANTHROPIC_BASE_URL=.*/g, `ANTHROPIC_BASE_URL="${proxyUrl}"`);
|
|
79
|
-
} else {
|
|
80
|
-
newText += `\nANTHROPIC_BASE_URL="${proxyUrl}"`;
|
|
81
|
-
}
|
|
168
|
+
if (ext === '.yaml' || ext === '.yml') {
|
|
169
|
+
const baseUrl = yamlPatch(original, /base_url:\s*['"]?[^'"\n]*['"]?/g, `base_url: "${proxyUrl}"`, `base_url: ${proxyUrl}`);
|
|
170
|
+
const apiBase = yamlPatch(baseUrl.text, /api_base:\s*['"]?[^'"\n]*['"]?/g, `api_base: "${proxyUrl}"`, `api_base: ${proxyUrl}`);
|
|
171
|
+
return { filePath, exists, original, updated: apiBase.text, summaries: [baseUrl.summary, apiBase.summary] };
|
|
172
|
+
}
|
|
82
173
|
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
83
176
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
177
|
+
function discoverPatches(proxyUrl) {
|
|
178
|
+
const existing = candidateFiles().filter((filePath) => fs.existsSync(filePath));
|
|
179
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
180
|
+
const files = existing.length ? existing : [envPath];
|
|
181
|
+
return files
|
|
182
|
+
.map((filePath) => buildPatch(filePath, proxyUrl))
|
|
183
|
+
.filter((patch) => patch && patch.updated !== patch.original);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function printPreview(patches) {
|
|
187
|
+
console.log('\nProposed edits:\n');
|
|
188
|
+
patches.forEach((patch, index) => {
|
|
189
|
+
console.log(`${index + 1}) ${patch.filePath}${patch.exists ? '' : ' (new file)'}`);
|
|
190
|
+
patch.summaries.forEach((summary) => console.log(` - ${summary}`));
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function applyPatches(patches) {
|
|
195
|
+
patches.forEach((patch) => {
|
|
196
|
+
fs.mkdirSync(path.dirname(patch.filePath), { recursive: true });
|
|
197
|
+
if (patch.exists) {
|
|
198
|
+
fs.writeFileSync(`${patch.filePath}.bak-${Date.now()}`, patch.original);
|
|
89
199
|
}
|
|
200
|
+
fs.writeFileSync(patch.filePath, patch.updated);
|
|
201
|
+
});
|
|
90
202
|
}
|
|
91
203
|
|
|
204
|
+
// --- Revert ---
|
|
92
205
|
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
206
|
+
function findBackups() {
|
|
207
|
+
const cwd = process.cwd();
|
|
208
|
+
let backups = [];
|
|
209
|
+
try {
|
|
210
|
+
const files = fs.readdirSync(cwd);
|
|
211
|
+
backups = files
|
|
212
|
+
.filter((f) => f.match(/\.bak-\d+$/))
|
|
213
|
+
.map((f) => ({
|
|
214
|
+
backup: path.join(cwd, f),
|
|
215
|
+
original: path.join(cwd, f.replace(/\.bak-\d+$/, '')),
|
|
216
|
+
stamp: parseInt(f.match(/\.bak-(\d+)$/)[1], 10),
|
|
217
|
+
}))
|
|
218
|
+
.sort((a, b) => b.stamp - a.stamp);
|
|
219
|
+
} catch (e) {}
|
|
220
|
+
return backups;
|
|
221
|
+
}
|
|
96
222
|
|
|
223
|
+
function revertBackups(backups) {
|
|
224
|
+
backups.forEach((b) => {
|
|
225
|
+
const content = fs.readFileSync(b.backup, 'utf8');
|
|
226
|
+
fs.writeFileSync(b.original, content);
|
|
227
|
+
fs.unlinkSync(b.backup);
|
|
228
|
+
console.log(` Restored ${b.original}`);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
97
231
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
232
|
+
// --- Check config status ---
|
|
233
|
+
|
|
234
|
+
function scanExistingConfig() {
|
|
235
|
+
const results = [];
|
|
236
|
+
candidateFiles().forEach((filePath) => {
|
|
237
|
+
if (!fs.existsSync(filePath)) return;
|
|
238
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
239
|
+
const lines = content.split('\n');
|
|
240
|
+
const proxyLines = lines.filter((l) =>
|
|
241
|
+
l.includes('zahuierik.com') || l.includes('breakroom')
|
|
242
|
+
);
|
|
243
|
+
if (proxyLines.length) {
|
|
244
|
+
results.push({ filePath, proxyLines });
|
|
102
245
|
}
|
|
246
|
+
});
|
|
247
|
+
return results;
|
|
248
|
+
}
|
|
103
249
|
|
|
250
|
+
// --- Actions ---
|
|
104
251
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
252
|
+
async function actionConfigure() {
|
|
253
|
+
console.log();
|
|
254
|
+
|
|
255
|
+
const existing = scanExistingConfig();
|
|
256
|
+
if (existing.length) {
|
|
257
|
+
const redo = (await ask('Break Room is already configured. Re-configure with a new license? (y/N): ')).trim().toLowerCase();
|
|
258
|
+
if (redo !== 'y' && redo !== 'yes') {
|
|
259
|
+
console.log('Skipped. Existing config unchanged.');
|
|
260
|
+
return;
|
|
109
261
|
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const input = (await ask('Enter your Break Room license key: ')).trim();
|
|
265
|
+
if (!input) {
|
|
266
|
+
throw new Error('A license key is required. Get one at https://zahuierik.com/breakroom');
|
|
267
|
+
}
|
|
110
268
|
|
|
269
|
+
console.log('\nVerifying license...');
|
|
270
|
+
const license = await verifyLicense(input);
|
|
271
|
+
const proxyUrl = `${BREAKROOM_ORIGIN}/breakroom/${encodeURIComponent(input)}/v1`;
|
|
272
|
+
console.log(`\x1b[32mOK\x1b[0m ${license.email || 'license'} is active.`);
|
|
273
|
+
console.log(`Proxy URL: ${proxyUrl}`);
|
|
274
|
+
|
|
275
|
+
const patches = discoverPatches(proxyUrl);
|
|
276
|
+
if (!patches.length) {
|
|
277
|
+
console.log('\nNo config changes needed. Existing files already point at Break Room.');
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
printPreview(patches);
|
|
282
|
+
const confirm = (await ask('\nApply these edits? Backups will be written first. (y/N): ')).trim().toLowerCase();
|
|
283
|
+
if (confirm !== 'y' && confirm !== 'yes') {
|
|
284
|
+
console.log('No files changed.');
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
applyPatches(patches);
|
|
289
|
+
console.log('\n\x1b[32mDone.\x1b[0m Restart your agent, IDE, or shell so it picks up the new base URL.');
|
|
290
|
+
}
|
|
111
291
|
|
|
112
|
-
|
|
113
|
-
|
|
292
|
+
async function actionChangeLicense() {
|
|
293
|
+
console.log();
|
|
294
|
+
|
|
295
|
+
const configured = scanExistingConfig();
|
|
296
|
+
if (!configured.length) {
|
|
297
|
+
console.log('No Break Room config found. Use option 1 to configure first.\n');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
console.log('Current configuration:');
|
|
302
|
+
configured.forEach((c) => {
|
|
303
|
+
console.log(` ${c.filePath}`);
|
|
304
|
+
c.proxyLines.forEach((l) => console.log(` ${l.trim()}`));
|
|
305
|
+
});
|
|
306
|
+
console.log();
|
|
307
|
+
|
|
308
|
+
const newKey = (await ask('Enter new Break Room license key: ')).trim();
|
|
309
|
+
if (!newKey) {
|
|
310
|
+
console.log('Canceled.');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log('\nVerifying new license...');
|
|
315
|
+
const license = await verifyLicense(newKey);
|
|
316
|
+
const proxyUrl = `${BREAKROOM_ORIGIN}/breakroom/${encodeURIComponent(newKey)}/v1`;
|
|
317
|
+
console.log(`\x1b[32mOK\x1b[0m ${license.email || 'license'} is active.`);
|
|
318
|
+
|
|
319
|
+
const patches = discoverPatches(proxyUrl);
|
|
320
|
+
if (!patches.length) {
|
|
321
|
+
console.log('\nNo config changes needed (already pointing at this URL).');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
printPreview(patches);
|
|
326
|
+
const confirm = (await ask('\nReplace existing config? Backups will be written first. (y/N): ')).trim().toLowerCase();
|
|
327
|
+
if (confirm !== 'y' && confirm !== 'yes') {
|
|
328
|
+
console.log('No files changed.');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
applyPatches(patches);
|
|
333
|
+
console.log('\n\x1b[32mLicense updated.\x1b[0m Restart your agent, IDE, or shell.');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function actionVerify() {
|
|
337
|
+
console.log();
|
|
338
|
+
|
|
339
|
+
const configured = scanExistingConfig();
|
|
340
|
+
if (!configured.length) {
|
|
341
|
+
console.log('No Break Room configuration found in any project files.\n');
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const urls = [];
|
|
346
|
+
configured.forEach((c) => {
|
|
347
|
+
c.proxyLines.forEach((l) => {
|
|
348
|
+
const match = l.match(/https?:\/\/[^"'\s]+/);
|
|
349
|
+
if (match) urls.push(match[0]);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const uniqueUrls = unique(urls);
|
|
354
|
+
console.log(`Found ${configured.length} configured file(s):\n`);
|
|
355
|
+
configured.forEach((c) => {
|
|
356
|
+
console.log(` ${c.filePath}`);
|
|
357
|
+
c.proxyLines.forEach((l) => console.log(` ${l.trim()}`));
|
|
358
|
+
});
|
|
359
|
+
console.log();
|
|
360
|
+
|
|
361
|
+
for (const url of uniqueUrls) {
|
|
362
|
+
const match = url.match(/\/breakroom\/([^\/]+)\/v1/);
|
|
363
|
+
if (match) {
|
|
364
|
+
const license = decodeURIComponent(match[1]);
|
|
365
|
+
console.log(` License key: ${license}`);
|
|
366
|
+
try {
|
|
367
|
+
const result = await verifyLicense(license);
|
|
368
|
+
console.log(` Status: \x1b[32mactive\x1b[0m (${result.email || 'no email'})`);
|
|
369
|
+
} catch (err) {
|
|
370
|
+
console.log(` Status: \x1b[31m${err.message}\x1b[0m`);
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
console.log(` Proxy URL: ${url}`);
|
|
114
374
|
}
|
|
115
|
-
|
|
375
|
+
}
|
|
376
|
+
console.log();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function actionGetLicense() {
|
|
380
|
+
console.log(`\n Open this URL in your browser:\n`);
|
|
381
|
+
console.log(` \x1b[36m${STRIPE_URL}\x1b[0m\n`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function actionRevert() {
|
|
385
|
+
console.log();
|
|
386
|
+
|
|
387
|
+
const backups = findBackups();
|
|
388
|
+
if (!backups.length) {
|
|
389
|
+
console.log('No backup files found. Nothing to revert.\n');
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
console.log(`Found ${backups.length} backup(s):\n`);
|
|
394
|
+
backups.forEach((b) => {
|
|
395
|
+
const date = new Date(b.stamp).toLocaleString();
|
|
396
|
+
console.log(` ${b.backup} (${date})`);
|
|
397
|
+
console.log(` -> restores: ${b.original}`);
|
|
398
|
+
});
|
|
399
|
+
console.log();
|
|
400
|
+
|
|
401
|
+
const confirm = (await ask('Restore all originals from these backups? Backups will be deleted. (y/N): ')).trim().toLowerCase();
|
|
402
|
+
if (confirm !== 'y' && confirm !== 'yes') {
|
|
403
|
+
console.log('No files changed.');
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
revertBackups(backups);
|
|
408
|
+
console.log('\n\x1b[32mDone.\x1b[0m Originals restored. Break Room proxy routing removed.\n');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function actionCheck() {
|
|
412
|
+
console.log();
|
|
413
|
+
|
|
414
|
+
const configured = scanExistingConfig();
|
|
415
|
+
if (!configured.length) {
|
|
416
|
+
console.log('No Break Room proxy configuration detected in project files.\n');
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
console.log('Current Break Room configuration:\n');
|
|
421
|
+
configured.forEach((c) => {
|
|
422
|
+
console.log(` ${c.filePath}`);
|
|
423
|
+
c.proxyLines.forEach((l) => console.log(` ${l.trim()}`));
|
|
424
|
+
});
|
|
425
|
+
console.log();
|
|
116
426
|
}
|
|
117
427
|
|
|
428
|
+
// --- Menu ---
|
|
429
|
+
|
|
430
|
+
function showMenu() {
|
|
431
|
+
console.log('\n\x1b[1m Main Menu\x1b[0m\n');
|
|
432
|
+
console.log(' 1) Configure a license');
|
|
433
|
+
console.log(' 2) Change license key');
|
|
434
|
+
console.log(' 3) Verify current license');
|
|
435
|
+
console.log(' 4) Get a license');
|
|
436
|
+
console.log(' 5) Revert patches (restore backups)');
|
|
437
|
+
console.log(' 6) Check configuration status');
|
|
438
|
+
console.log(' 7) Exit\n');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function main() {
|
|
442
|
+
console.log(`\x1b[36m${chairArt}\x1b[0m`);
|
|
443
|
+
console.log(`\x1b[1m${wordmark}\x1b[0m`);
|
|
444
|
+
console.log('\x1b[90mPaid-license proxy routing for agents that get stuck in loops.\x1b[0m');
|
|
445
|
+
console.log('\x1b[90mOnly proxy URLs are changed. Models and settings are never touched.\x1b[0m\n');
|
|
446
|
+
|
|
447
|
+
while (true) {
|
|
448
|
+
showMenu();
|
|
449
|
+
const choice = (await ask(' Enter choice (1-7): ')).trim();
|
|
450
|
+
if (choice === '__EOF__') break;
|
|
118
451
|
|
|
119
|
-
function patchYaml(filePath, regex, replacement, name) {
|
|
120
452
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
453
|
+
switch (choice) {
|
|
454
|
+
case '1':
|
|
455
|
+
await actionConfigure();
|
|
456
|
+
break;
|
|
457
|
+
case '2':
|
|
458
|
+
await actionChangeLicense();
|
|
459
|
+
break;
|
|
460
|
+
case '3':
|
|
461
|
+
await actionVerify();
|
|
462
|
+
break;
|
|
463
|
+
case '4':
|
|
464
|
+
actionGetLicense();
|
|
465
|
+
break;
|
|
466
|
+
case '5':
|
|
467
|
+
await actionRevert();
|
|
468
|
+
break;
|
|
469
|
+
case '6':
|
|
470
|
+
await actionCheck();
|
|
471
|
+
break;
|
|
472
|
+
case '7':
|
|
473
|
+
console.log('\nGoodbye.\n');
|
|
474
|
+
return;
|
|
475
|
+
default:
|
|
476
|
+
console.log(`\n Unknown option: ${choice}\n`);
|
|
477
|
+
}
|
|
131
478
|
} catch (err) {
|
|
132
|
-
|
|
479
|
+
console.error(`\n\x1b[31mError:\x1b[0m ${err.message}\n`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (choice !== '7' && choice !== '__EOF__') {
|
|
483
|
+
if (await ask('Press Enter to return to the menu...') === '__EOF__') break;
|
|
133
484
|
}
|
|
485
|
+
}
|
|
134
486
|
}
|
|
487
|
+
|
|
488
|
+
main()
|
|
489
|
+
.catch((err) => {
|
|
490
|
+
console.error(`\n\x1b[31mFatal:\x1b[0m ${err.message}`);
|
|
491
|
+
process.exitCode = 1;
|
|
492
|
+
})
|
|
493
|
+
.finally(() => {
|
|
494
|
+
if (rl) rl.close();
|
|
495
|
+
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "breakroom",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Paid-license proxy routing for agents that get stuck in loops.",
|
|
5
5
|
"bin": {
|
|
6
|
-
"
|
|
6
|
+
"breakroom": "./bin/setup.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"start": "node ./bin/setup.js"
|
|
10
10
|
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/zahuierik/break-room-proxy.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ai",
|
|
17
|
+
"proxy",
|
|
18
|
+
"cbt",
|
|
19
|
+
"diagnostic",
|
|
20
|
+
"llm",
|
|
21
|
+
"routing"
|
|
22
|
+
],
|
|
11
23
|
"author": "ZahuiErik",
|
|
12
24
|
"license": "MIT"
|
|
13
25
|
}
|