@micsushi/agent-hotline 1.0.0 → 1.0.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@micsushi/agent-hotline",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Local read-aloud hooks and tray app for AI coding agents.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ah": "packages/backend/bin/agent-hotline.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"packages/backend/bin/",
|
|
15
15
|
"packages/backend/src/",
|
|
16
16
|
"packages/backend/skills/",
|
|
17
|
+
"packages/backend/web/",
|
|
17
18
|
"packages/backend/package.json"
|
|
18
19
|
],
|
|
19
20
|
"publishConfig": {
|
|
@@ -33,6 +34,8 @@
|
|
|
33
34
|
"install-hook": "node scripts/install-hook.js",
|
|
34
35
|
"install-skill": "node scripts/install-skill.js",
|
|
35
36
|
"install-hotline": "node packages/backend/bin/agent-hotline.js install",
|
|
37
|
+
"stage-web": "node scripts/stage-web.mjs",
|
|
38
|
+
"prepack": "node scripts/stage-web.mjs",
|
|
36
39
|
"test": "npm --prefix packages/backend test && npm --workspace @agent-hotline/desktop run test",
|
|
37
40
|
"lint": "eslint . && npm run rust:clippy",
|
|
38
41
|
"lint:fix": "eslint . --fix",
|
|
@@ -463,6 +463,68 @@ function getPathname(req) {
|
|
|
463
463
|
return new URL(req.url, "http://127.0.0.1").pathname;
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
+
// Built desktop UI, served so `agent-hotline run` opens the full app in a
|
|
467
|
+
// browser. The npm package ships a pruned copy under web/ (no heavy local-TTS
|
|
468
|
+
// assets); a source checkout uses the full desktop/dist build. Absent in either
|
|
469
|
+
// case, routes fall back to the inline page() console.
|
|
470
|
+
const WEB_CANDIDATES = [
|
|
471
|
+
path.resolve(__dirname, "../web"),
|
|
472
|
+
path.resolve(__dirname, "../../desktop/dist")
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
function webDir() {
|
|
476
|
+
for (const dir of WEB_CANDIDATES) {
|
|
477
|
+
try {
|
|
478
|
+
if (fs.statSync(path.join(dir, "index.html")).isFile()) return dir;
|
|
479
|
+
} catch {
|
|
480
|
+
// try next candidate
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const STATIC_CONTENT_TYPES = {
|
|
487
|
+
".html": "text/html; charset=utf-8",
|
|
488
|
+
".js": "text/javascript; charset=utf-8",
|
|
489
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
490
|
+
".css": "text/css; charset=utf-8",
|
|
491
|
+
".json": "application/json; charset=utf-8",
|
|
492
|
+
".svg": "image/svg+xml",
|
|
493
|
+
".png": "image/png",
|
|
494
|
+
".jpg": "image/jpeg",
|
|
495
|
+
".jpeg": "image/jpeg",
|
|
496
|
+
".webp": "image/webp",
|
|
497
|
+
".ico": "image/x-icon",
|
|
498
|
+
".woff": "font/woff",
|
|
499
|
+
".woff2": "font/woff2",
|
|
500
|
+
".ttf": "font/ttf",
|
|
501
|
+
".wasm": "application/wasm",
|
|
502
|
+
".map": "application/json; charset=utf-8"
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// Resolve a request path to a file inside the active web dir, refusing anything
|
|
506
|
+
// that escapes the directory. Returns the absolute path or null.
|
|
507
|
+
function resolveStaticFile(pathname) {
|
|
508
|
+
const root = webDir();
|
|
509
|
+
if (!root) return null;
|
|
510
|
+
const rel = decodeURIComponent(pathname).replace(/^\/+/, "");
|
|
511
|
+
const target = path.resolve(root, rel);
|
|
512
|
+
if (target !== root && !target.startsWith(root + path.sep)) return null;
|
|
513
|
+
try {
|
|
514
|
+
if (fs.statSync(target).isFile()) return target;
|
|
515
|
+
} catch {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function serveStaticFile(res, filePath) {
|
|
522
|
+
const type =
|
|
523
|
+
STATIC_CONTENT_TYPES[path.extname(filePath).toLowerCase()] || "application/octet-stream";
|
|
524
|
+
res.writeHead(200, { "Content-Type": type });
|
|
525
|
+
fs.createReadStream(filePath).pipe(res);
|
|
526
|
+
}
|
|
527
|
+
|
|
466
528
|
function page() {
|
|
467
529
|
return `<!doctype html>
|
|
468
530
|
<html lang="en">
|
|
@@ -634,11 +696,25 @@ function createServer(options = {}) {
|
|
|
634
696
|
const pathname = getPathname(req);
|
|
635
697
|
|
|
636
698
|
if (req.method === "GET" && pathname === "/") {
|
|
699
|
+
const index = resolveStaticFile("/index.html");
|
|
700
|
+
if (index) {
|
|
701
|
+
serveStaticFile(res, index);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
637
704
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
638
705
|
res.end(page());
|
|
639
706
|
return;
|
|
640
707
|
}
|
|
641
708
|
|
|
709
|
+
// Tell the browser-served UI to call this same origin for the API, so a
|
|
710
|
+
// custom --port still works. The bundled static config.json (used by the
|
|
711
|
+
// packaged desktop app) is overridden here only for HTTP requests.
|
|
712
|
+
if (req.method === "GET" && pathname === "/config.json") {
|
|
713
|
+
const host = req.headers.host || `${HOST}:${PORT}`;
|
|
714
|
+
sendJson(res, 200, { backendUrl: `http://${host}` });
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
|
|
642
718
|
if (req.method === "GET" && (pathname === "/health" || pathname === "/api/health")) {
|
|
643
719
|
sendJson(res, 200, { ok: true, service: "agent-hotline", host: HOST });
|
|
644
720
|
return;
|
|
@@ -901,6 +977,16 @@ function createServer(options = {}) {
|
|
|
901
977
|
return;
|
|
902
978
|
}
|
|
903
979
|
|
|
980
|
+
// Static assets for the built UI (JS/CSS/fonts/etc). API paths never reach
|
|
981
|
+
// here, so this only serves the desktop dist bundle.
|
|
982
|
+
if (req.method === "GET" && !pathname.startsWith("/api/")) {
|
|
983
|
+
const file = resolveStaticFile(pathname);
|
|
984
|
+
if (file) {
|
|
985
|
+
serveStaticFile(res, file);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
904
990
|
throw createHttpError(404, "not_found", "Not found");
|
|
905
991
|
} catch (error) {
|
|
906
992
|
if (!error.status && /Queue item not found/.test(error.message)) {
|