@actant/dashboard 0.2.4

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.
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/actant.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Actant Dashboard</title>
8
+ <script type="module" crossorigin src="/assets/index-UhcoO_kg.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-Qm48lTG5.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
@@ -0,0 +1,8 @@
1
+ interface DashboardOptions {
2
+ port?: number;
3
+ socketPath: string;
4
+ open?: boolean;
5
+ }
6
+ declare function startDashboard(options: DashboardOptions): Promise<void>;
7
+
8
+ export { type DashboardOptions, startDashboard };
package/dist/index.js ADDED
@@ -0,0 +1,118 @@
1
+ // src/index.ts
2
+ import { createServer } from "http";
3
+ import { createLogger } from "@actant/shared";
4
+
5
+ // src/server.ts
6
+ import { readFile } from "fs/promises";
7
+ import { join, extname } from "path";
8
+ import { fileURLToPath } from "url";
9
+ import { createApiHandler, RpcBridge } from "@actant/rest-api";
10
+ var MIME_TYPES = {
11
+ ".html": "text/html",
12
+ ".js": "application/javascript",
13
+ ".css": "text/css",
14
+ ".json": "application/json",
15
+ ".png": "image/png",
16
+ ".svg": "image/svg+xml",
17
+ ".ico": "image/x-icon"
18
+ };
19
+ function getClientDir() {
20
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : join(fileURLToPath(import.meta.url), "..");
21
+ return join(thisDir, "client");
22
+ }
23
+ function createRequestHandler(socketPath) {
24
+ const bridge = new RpcBridge(socketPath);
25
+ const apiHandler = createApiHandler({ bridge });
26
+ const clientDir = getClientDir();
27
+ return async (req, res) => {
28
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
29
+ const pathname = url.pathname;
30
+ if (pathname.startsWith("/v1/") || pathname === "/v1" || pathname === "/sse" || pathname.startsWith("/api/")) {
31
+ if (pathname.startsWith("/api/")) {
32
+ const rewritten = pathname.replace("/api/", "/v1/");
33
+ const originalUrl = req.url;
34
+ req.url = rewritten + url.search;
35
+ await apiHandler(req, res);
36
+ req.url = originalUrl;
37
+ return;
38
+ }
39
+ if (pathname === "/sse") {
40
+ const originalUrl = req.url;
41
+ req.url = "/v1/sse";
42
+ await apiHandler(req, res);
43
+ req.url = originalUrl;
44
+ return;
45
+ }
46
+ return apiHandler(req, res);
47
+ }
48
+ try {
49
+ await serveStatic(clientDir, pathname, res);
50
+ } catch (err) {
51
+ res.writeHead(500, { "Content-Type": "application/json" });
52
+ res.end(JSON.stringify({ error: String(err) }));
53
+ }
54
+ };
55
+ }
56
+ async function serveStatic(clientDir, pathname, res) {
57
+ const filePath = pathname === "/" ? "/index.html" : pathname;
58
+ const fullPath = join(clientDir, filePath);
59
+ try {
60
+ const content = await readFile(fullPath);
61
+ const ext = extname(filePath);
62
+ const mime = MIME_TYPES[ext] ?? "application/octet-stream";
63
+ res.writeHead(200, { "Content-Type": mime });
64
+ res.end(content);
65
+ } catch {
66
+ try {
67
+ const indexContent = await readFile(join(clientDir, "index.html"));
68
+ res.writeHead(200, { "Content-Type": "text/html" });
69
+ res.end(indexContent);
70
+ } catch {
71
+ res.writeHead(404, { "Content-Type": "text/plain" });
72
+ res.end(
73
+ "Dashboard client not built. Run: pnpm --filter @actant/dashboard build:client"
74
+ );
75
+ }
76
+ }
77
+ }
78
+
79
+ // src/index.ts
80
+ var logger = createLogger("dashboard");
81
+ async function startDashboard(options) {
82
+ const port = options.port ?? 3200;
83
+ const { RpcBridge: RpcBridge2 } = await import("@actant/rest-api");
84
+ const bridge = new RpcBridge2(options.socketPath);
85
+ const alive = await bridge.ping();
86
+ if (!alive) {
87
+ throw new Error("Cannot connect to Actant daemon. Is it running?");
88
+ }
89
+ const handler = createRequestHandler(options.socketPath);
90
+ const server = createServer(handler);
91
+ server.listen(port, () => {
92
+ const url = `http://localhost:${port}`;
93
+ logger.info({ port, url }, "Dashboard server started");
94
+ logger.info(`Dashboard: ${url}`);
95
+ logger.info(`REST API: ${url}/v1`);
96
+ logger.info(`SSE: ${url}/v1/sse`);
97
+ if (options.open !== false) {
98
+ import("child_process").then(({ exec }) => {
99
+ const cmd = process.platform === "win32" ? "start" : process.platform === "darwin" ? "open" : "xdg-open";
100
+ exec(`${cmd} ${url}`);
101
+ }).catch(() => {
102
+ });
103
+ }
104
+ });
105
+ const signals = ["SIGINT", "SIGTERM"];
106
+ for (const sig of signals) {
107
+ process.on(sig, () => {
108
+ server.close();
109
+ process.exit(0);
110
+ });
111
+ }
112
+ await new Promise(() => {
113
+ });
114
+ }
115
+ export {
116
+ startDashboard
117
+ };
118
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/server.ts"],"sourcesContent":["import { createServer } from \"node:http\";\nimport { createLogger } from \"@actant/shared\";\nimport { createRequestHandler } from \"./server\";\n\nconst logger = createLogger(\"dashboard\");\n\nexport interface DashboardOptions {\n port?: number;\n socketPath: string;\n open?: boolean;\n}\n\nexport async function startDashboard(options: DashboardOptions): Promise<void> {\n const port = options.port ?? 3200;\n\n // Verify daemon connectivity via rest-api bridge\n const { RpcBridge } = await import(\"@actant/rest-api\");\n const bridge = new RpcBridge(options.socketPath);\n const alive = await bridge.ping();\n if (!alive) {\n throw new Error(\"Cannot connect to Actant daemon. Is it running?\");\n }\n\n const handler = createRequestHandler(options.socketPath);\n const server = createServer(handler);\n\n server.listen(port, () => {\n const url = `http://localhost:${port}`;\n logger.info({ port, url }, \"Dashboard server started\");\n logger.info(`Dashboard: ${url}`);\n logger.info(`REST API: ${url}/v1`);\n logger.info(`SSE: ${url}/v1/sse`);\n\n if (options.open !== false) {\n import(\"node:child_process\")\n .then(({ exec }) => {\n const cmd =\n process.platform === \"win32\"\n ? \"start\"\n : process.platform === \"darwin\"\n ? \"open\"\n : \"xdg-open\";\n exec(`${cmd} ${url}`);\n })\n .catch(() => {});\n }\n });\n\n // Graceful shutdown\n const signals: NodeJS.Signals[] = [\"SIGINT\", \"SIGTERM\"];\n for (const sig of signals) {\n process.on(sig, () => {\n server.close();\n process.exit(0);\n });\n }\n\n await new Promise(() => {});\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join, extname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type {\n IncomingMessage,\n ServerResponse,\n RequestListener,\n} from \"node:http\";\nimport { createApiHandler, RpcBridge } from \"@actant/rest-api\";\n\nconst MIME_TYPES: Record<string, string> = {\n \".html\": \"text/html\",\n \".js\": \"application/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".png\": \"image/png\",\n \".svg\": \"image/svg+xml\",\n \".ico\": \"image/x-icon\",\n};\n\nfunction getClientDir(): string {\n const thisDir =\n typeof __dirname !== \"undefined\"\n ? __dirname\n : join(fileURLToPath(import.meta.url), \"..\");\n return join(thisDir, \"client\");\n}\n\nexport function createRequestHandler(socketPath: string): RequestListener {\n const bridge = new RpcBridge(socketPath);\n const apiHandler = createApiHandler({ bridge });\n const clientDir = getClientDir();\n\n return async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host ?? \"localhost\"}`);\n const pathname = url.pathname;\n\n // Delegate API and SSE requests to the rest-api handler\n if (\n pathname.startsWith(\"/v1/\") ||\n pathname === \"/v1\" ||\n pathname === \"/sse\" ||\n pathname.startsWith(\"/api/\")\n ) {\n // Rewrite legacy /api/* to /v1/* for backward compatibility\n if (pathname.startsWith(\"/api/\")) {\n const rewritten = pathname.replace(\"/api/\", \"/v1/\");\n const originalUrl = req.url;\n req.url = rewritten + url.search;\n await apiHandler(req, res);\n req.url = originalUrl;\n return;\n }\n\n // /sse → /v1/sse rewrite\n if (pathname === \"/sse\") {\n const originalUrl = req.url;\n req.url = \"/v1/sse\";\n await apiHandler(req, res);\n req.url = originalUrl;\n return;\n }\n\n return apiHandler(req, res);\n }\n\n // Static files for the SPA\n try {\n await serveStatic(clientDir, pathname, res);\n } catch (err) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: String(err) }));\n }\n };\n}\n\nasync function serveStatic(\n clientDir: string,\n pathname: string,\n res: ServerResponse,\n): Promise<void> {\n const filePath = pathname === \"/\" ? \"/index.html\" : pathname;\n const fullPath = join(clientDir, filePath);\n\n try {\n const content = await readFile(fullPath);\n const ext = extname(filePath);\n const mime = MIME_TYPES[ext] ?? \"application/octet-stream\";\n res.writeHead(200, { \"Content-Type\": mime });\n res.end(content);\n } catch {\n // SPA fallback: serve index.html for any unmatched route\n try {\n const indexContent = await readFile(join(clientDir, \"index.html\"));\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(indexContent);\n } catch {\n res.writeHead(404, { \"Content-Type\": \"text/plain\" });\n res.end(\n \"Dashboard client not built. Run: pnpm --filter @actant/dashboard build:client\",\n );\n }\n }\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;;;ACD7B,SAAS,gBAAgB;AACzB,SAAS,MAAM,eAAe;AAC9B,SAAS,qBAAqB;AAM9B,SAAS,kBAAkB,iBAAiB;AAE5C,IAAM,aAAqC;AAAA,EACzC,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AACV;AAEA,SAAS,eAAuB;AAC9B,QAAM,UACJ,OAAO,cAAc,cACjB,YACA,KAAK,cAAc,YAAY,GAAG,GAAG,IAAI;AAC/C,SAAO,KAAK,SAAS,QAAQ;AAC/B;AAEO,SAAS,qBAAqB,YAAqC;AACxE,QAAM,SAAS,IAAI,UAAU,UAAU;AACvC,QAAM,aAAa,iBAAiB,EAAE,OAAO,CAAC;AAC9C,QAAM,YAAY,aAAa;AAE/B,SAAO,OAAO,KAAsB,QAAwB;AAC1D,UAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,QAAQ,WAAW,EAAE;AAC/E,UAAM,WAAW,IAAI;AAGrB,QACE,SAAS,WAAW,MAAM,KAC1B,aAAa,SACb,aAAa,UACb,SAAS,WAAW,OAAO,GAC3B;AAEA,UAAI,SAAS,WAAW,OAAO,GAAG;AAChC,cAAM,YAAY,SAAS,QAAQ,SAAS,MAAM;AAClD,cAAM,cAAc,IAAI;AACxB,YAAI,MAAM,YAAY,IAAI;AAC1B,cAAM,WAAW,KAAK,GAAG;AACzB,YAAI,MAAM;AACV;AAAA,MACF;AAGA,UAAI,aAAa,QAAQ;AACvB,cAAM,cAAc,IAAI;AACxB,YAAI,MAAM;AACV,cAAM,WAAW,KAAK,GAAG;AACzB,YAAI,MAAM;AACV;AAAA,MACF;AAEA,aAAO,WAAW,KAAK,GAAG;AAAA,IAC5B;AAGA,QAAI;AACF,YAAM,YAAY,WAAW,UAAU,GAAG;AAAA,IAC5C,SAAS,KAAK;AACZ,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC,CAAC;AAAA,IAChD;AAAA,EACF;AACF;AAEA,eAAe,YACb,WACA,UACA,KACe;AACf,QAAM,WAAW,aAAa,MAAM,gBAAgB;AACpD,QAAM,WAAW,KAAK,WAAW,QAAQ;AAEzC,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,QAAQ;AACvC,UAAM,MAAM,QAAQ,QAAQ;AAC5B,UAAM,OAAO,WAAW,GAAG,KAAK;AAChC,QAAI,UAAU,KAAK,EAAE,gBAAgB,KAAK,CAAC;AAC3C,QAAI,IAAI,OAAO;AAAA,EACjB,QAAQ;AAEN,QAAI;AACF,YAAM,eAAe,MAAM,SAAS,KAAK,WAAW,YAAY,CAAC;AACjE,UAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC;AAClD,UAAI,IAAI,YAAY;AAAA,IACtB,QAAQ;AACN,UAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,UAAI;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ADnGA,IAAM,SAAS,aAAa,WAAW;AAQvC,eAAsB,eAAe,SAA0C;AAC7E,QAAM,OAAO,QAAQ,QAAQ;AAG7B,QAAM,EAAE,WAAAA,WAAU,IAAI,MAAM,OAAO,kBAAkB;AACrD,QAAM,SAAS,IAAIA,WAAU,QAAQ,UAAU;AAC/C,QAAM,QAAQ,MAAM,OAAO,KAAK;AAChC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AAEA,QAAM,UAAU,qBAAqB,QAAQ,UAAU;AACvD,QAAM,SAAS,aAAa,OAAO;AAEnC,SAAO,OAAO,MAAM,MAAM;AACxB,UAAM,MAAM,oBAAoB,IAAI;AACpC,WAAO,KAAK,EAAE,MAAM,IAAI,GAAG,0BAA0B;AACrD,WAAO,KAAK,cAAc,GAAG,EAAE;AAC/B,WAAO,KAAK,cAAc,GAAG,KAAK;AAClC,WAAO,KAAK,cAAc,GAAG,SAAS;AAEtC,QAAI,QAAQ,SAAS,OAAO;AAC1B,aAAO,eAAoB,EACxB,KAAK,CAAC,EAAE,KAAK,MAAM;AAClB,cAAM,MACJ,QAAQ,aAAa,UACjB,UACA,QAAQ,aAAa,WACnB,SACA;AACR,aAAK,GAAG,GAAG,IAAI,GAAG,EAAE;AAAA,MACtB,CAAC,EACA,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACnB;AAAA,EACF,CAAC;AAGD,QAAM,UAA4B,CAAC,UAAU,SAAS;AACtD,aAAW,OAAO,SAAS;AACzB,YAAQ,GAAG,KAAK,MAAM;AACpB,aAAO,MAAM;AACb,cAAQ,KAAK,CAAC;AAAA,IAChB,CAAC;AAAA,EACH;AAEA,QAAM,IAAI,QAAQ,MAAM;AAAA,EAAC,CAAC;AAC5B;","names":["RpcBridge"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@actant/dashboard",
3
+ "version": "0.2.4",
4
+ "description": "Web dashboard for monitoring Actant AI agents",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "dependencies": {
22
+ "@actant/rest-api": "0.2.4",
23
+ "@actant/shared": "0.2.4"
24
+ },
25
+ "devDependencies": {
26
+ "@radix-ui/react-slot": "^1.2.4",
27
+ "@tailwindcss/vite": "^4",
28
+ "@types/node": "^22",
29
+ "@types/react": "^19",
30
+ "@types/react-dom": "^19",
31
+ "@vitejs/plugin-react": "^4",
32
+ "class-variance-authority": "^0.7",
33
+ "clsx": "^2",
34
+ "i18next": "^25.8.13",
35
+ "i18next-browser-languagedetector": "^8.2.1",
36
+ "lucide-react": "^0.474",
37
+ "react": "^19",
38
+ "react-dom": "^19",
39
+ "react-i18next": "^16.5.4",
40
+ "react-router-dom": "^7",
41
+ "tailwind-merge": "^3",
42
+ "tailwindcss": "^4",
43
+ "tsup": "^8.5.0",
44
+ "typescript": "^5.9.0",
45
+ "vite": "^6"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup && pnpm build:client",
49
+ "build:server": "tsup",
50
+ "build:client": "vite build --config client/vite.config.ts",
51
+ "dev:client": "vite dev --config client/vite.config.ts",
52
+ "dev": "tsx --watch src/index.ts",
53
+ "type-check": "tsc --noEmit",
54
+ "clean": "rimraf dist"
55
+ }
56
+ }