free-coding-models 0.3.40 → 0.3.42
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/CHANGELOG.md +6 -15
- package/README.md +19 -1
- package/package.json +2 -1
- package/src/app.js +27 -0
- package/src/config.js +7 -0
- package/src/key-handler.js +27 -1
- package/src/overlays.js +11 -1
- package/src/shell-env.js +393 -0
- package/src/tool-bootstrap.js +7 -0
- package/src/tool-launchers.js +30 -1
- package/src/tool-metadata.js +3 -0
- package/src/utils.js +2 -0
- package/web/dist/assets/index-DwztVNMT.css +1 -0
- package/web/dist/assets/index-fk7MxoC4.js +11 -0
- package/web/dist/index.html +21 -0
- package/web/server.js +68 -7
package/web/server.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* GET /styles.css → Dashboard styles
|
|
12
12
|
* GET /app.js → Dashboard client JS
|
|
13
13
|
* GET /api/models → All model metadata (JSON)
|
|
14
|
+
* GET /api/health → Lightweight dashboard health probe
|
|
14
15
|
* GET /api/config → Current config (sanitized — masked keys)
|
|
15
16
|
* GET /api/key/:prov → Reveal a provider's full API key
|
|
16
17
|
* GET /api/events → SSE stream of live ping results
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
} from '../src/utils.js'
|
|
33
34
|
|
|
34
35
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
36
|
+
const SERVER_SIGNATURE = 'free-coding-models-web'
|
|
35
37
|
|
|
36
38
|
// ─── State ───────────────────────────────────────────────────────────────────
|
|
37
39
|
|
|
@@ -215,6 +217,8 @@ function serveDistFile(res, pathname) {
|
|
|
215
217
|
}
|
|
216
218
|
|
|
217
219
|
function handleRequest(req, res) {
|
|
220
|
+
res.setHeader('X-FCM-Server', SERVER_SIGNATURE)
|
|
221
|
+
|
|
218
222
|
// CORS for local dev
|
|
219
223
|
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
220
224
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
|
@@ -265,6 +269,11 @@ function handleRequest(req, res) {
|
|
|
265
269
|
res.end(JSON.stringify(getModelsPayload()))
|
|
266
270
|
break
|
|
267
271
|
|
|
272
|
+
case '/api/health':
|
|
273
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
274
|
+
res.end(JSON.stringify({ ok: true, app: SERVER_SIGNATURE }))
|
|
275
|
+
break
|
|
276
|
+
|
|
268
277
|
case '/api/config':
|
|
269
278
|
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
270
279
|
res.end(JSON.stringify(getConfigPayload()))
|
|
@@ -335,6 +344,40 @@ function checkPortInUse(port) {
|
|
|
335
344
|
})
|
|
336
345
|
}
|
|
337
346
|
|
|
347
|
+
export async function inspectExistingWebServer(port) {
|
|
348
|
+
const inUse = await checkPortInUse(port)
|
|
349
|
+
if (!inUse) return { inUse: false, isFcm: false }
|
|
350
|
+
|
|
351
|
+
const controller = new AbortController()
|
|
352
|
+
const timeout = setTimeout(() => controller.abort(), 750)
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
// 📖 Probe a tiny health route so we only reuse a port when the running
|
|
356
|
+
// 📖 process is actually the free-coding-models dashboard, not any random app.
|
|
357
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
|
|
358
|
+
signal: controller.signal,
|
|
359
|
+
headers: { Accept: 'application/json' },
|
|
360
|
+
})
|
|
361
|
+
const payload = await response.json().catch(() => null)
|
|
362
|
+
const signature = response.headers.get('x-fcm-server')
|
|
363
|
+
return {
|
|
364
|
+
inUse: true,
|
|
365
|
+
isFcm: signature === SERVER_SIGNATURE || payload?.app === SERVER_SIGNATURE,
|
|
366
|
+
}
|
|
367
|
+
} catch {
|
|
368
|
+
return { inUse: true, isFcm: false }
|
|
369
|
+
} finally {
|
|
370
|
+
clearTimeout(timeout)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export async function findAvailablePort(startPort, maxAttempts = 20) {
|
|
375
|
+
for (let port = startPort; port < startPort + maxAttempts; port++) {
|
|
376
|
+
if (!(await checkPortInUse(port))) return port
|
|
377
|
+
}
|
|
378
|
+
throw new Error(`No free local port found between ${startPort} and ${startPort + maxAttempts - 1}`)
|
|
379
|
+
}
|
|
380
|
+
|
|
338
381
|
function openBrowser(url) {
|
|
339
382
|
const cmd = process.platform === 'darwin' ? 'open'
|
|
340
383
|
: process.platform === 'win32' ? 'start'
|
|
@@ -346,11 +389,12 @@ function openBrowser(url) {
|
|
|
346
389
|
|
|
347
390
|
// ─── Exports ─────────────────────────────────────────────────────────────────
|
|
348
391
|
|
|
349
|
-
export async function startWebServer(port = 3333, { open = true } = {}) {
|
|
350
|
-
const
|
|
351
|
-
|
|
392
|
+
export async function startWebServer(port = 3333, { open = true, startPingLoop = true } = {}) {
|
|
393
|
+
const portStatus = await inspectExistingWebServer(port)
|
|
394
|
+
|
|
395
|
+
if (portStatus.inUse && portStatus.isFcm) {
|
|
396
|
+
const url = `http://localhost:${port}`
|
|
352
397
|
|
|
353
|
-
if (alreadyRunning) {
|
|
354
398
|
console.log()
|
|
355
399
|
console.log(` ⚡ free-coding-models Web Dashboard already running`)
|
|
356
400
|
console.log(` 🌐 ${url}`)
|
|
@@ -359,9 +403,21 @@ export async function startWebServer(port = 3333, { open = true } = {}) {
|
|
|
359
403
|
return null
|
|
360
404
|
}
|
|
361
405
|
|
|
406
|
+
let resolvedPort = port
|
|
407
|
+
if (portStatus.inUse && !portStatus.isFcm) {
|
|
408
|
+
resolvedPort = await findAvailablePort(port + 1)
|
|
409
|
+
console.log()
|
|
410
|
+
console.log(` ⚠️ Port ${port} is already in use by another local app`)
|
|
411
|
+
console.log(` ↪ Starting free-coding-models Web Dashboard on port ${resolvedPort} instead`)
|
|
412
|
+
console.log()
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const url = `http://localhost:${resolvedPort}`
|
|
416
|
+
|
|
362
417
|
const server = createServer(handleRequest)
|
|
418
|
+
let pingLoopTimer = null
|
|
363
419
|
|
|
364
|
-
server.listen(
|
|
420
|
+
server.listen(resolvedPort, () => {
|
|
365
421
|
console.log()
|
|
366
422
|
console.log(` ⚡ free-coding-models Web Dashboard`)
|
|
367
423
|
console.log(` 🌐 ${url}`)
|
|
@@ -373,10 +429,15 @@ export async function startWebServer(port = 3333, { open = true } = {}) {
|
|
|
373
429
|
})
|
|
374
430
|
|
|
375
431
|
async function schedulePingLoop() {
|
|
432
|
+
if (!server.listening) return
|
|
376
433
|
await pingAllModels()
|
|
377
|
-
setTimeout(schedulePingLoop, 10_000)
|
|
434
|
+
pingLoopTimer = setTimeout(schedulePingLoop, 10_000)
|
|
378
435
|
}
|
|
379
|
-
|
|
436
|
+
|
|
437
|
+
if (startPingLoop) schedulePingLoop()
|
|
438
|
+
server.on('close', () => {
|
|
439
|
+
if (pingLoopTimer) clearTimeout(pingLoopTimer)
|
|
440
|
+
})
|
|
380
441
|
|
|
381
442
|
return server
|
|
382
443
|
}
|