lookout-hq 0.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/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # Lookout
2
+
3
+ Lookout is a small PM2 control room for local repos. It reads your existing
4
+ `ecosystem.config.*`, shows running processes and package scripts in a focused
5
+ dashboard, and gives you quick actions for status, logs, restart, start, and
6
+ stop.
7
+
8
+ The npm package is `lookout-hq`; the installed command is `lookout`.
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ npm install -g lookout-hq
14
+ ```
15
+
16
+ Or run it without a global install:
17
+
18
+ ```sh
19
+ npx lookout-hq serve --port 8790
20
+ ```
21
+
22
+ ## Requirements
23
+
24
+ - Node.js 20 or newer
25
+ - PM2 available globally or in your repo's `node_modules/.bin`
26
+ - An `ecosystem.config.cjs`, `ecosystem.config.js`, or `ecosystem.config.mjs`
27
+ file in the repo you want to manage
28
+
29
+ ## Quick Start
30
+
31
+ From a repo that has a PM2 ecosystem file:
32
+
33
+ ```sh
34
+ lookout start
35
+ lookout status
36
+ lookout serve --port 8790
37
+ ```
38
+
39
+ Then open:
40
+
41
+ ```txt
42
+ http://127.0.0.1:8790
43
+ ```
44
+
45
+ ## Commands
46
+
47
+ ```sh
48
+ lookout serve --port 8790
49
+ lookout start [task...]
50
+ lookout stop [task...]
51
+ lookout restart [task...]
52
+ lookout status
53
+ lookout logs [task...]
54
+ lookout doctor
55
+ ```
56
+
57
+ `start`, `stop`, `restart`, and `logs` are PM2-backed commands. If task names
58
+ are provided, Lookout targets only those PM2 apps. Without task names, it uses
59
+ the repo ecosystem file or all PM2 apps where that matches PM2 behavior.
60
+
61
+ ## Dashboard
62
+
63
+ The dashboard is served by the CLI and includes:
64
+
65
+ - repo-first process overview
66
+ - PM2 process table with state, PID, CPU, memory, uptime, and restarts
67
+ - package script discovery from local `package.json` files
68
+ - linked PM2 process actions
69
+ - modal log viewer
70
+ - light and dark mode
71
+
72
+ ## Linking Scripts To Processes
73
+
74
+ Lookout can connect package scripts to PM2 apps when you add
75
+ `LOOKOUT_PACKAGE` and `LOOKOUT_SCRIPT` to the app's PM2 environment.
76
+
77
+ ```js
78
+ module.exports = {
79
+ apps: [
80
+ {
81
+ name: "web",
82
+ cwd: "apps/web",
83
+ script: "pnpm",
84
+ args: "dev",
85
+ env: {
86
+ LOOKOUT_PACKAGE: "web",
87
+ LOOKOUT_SCRIPT: "dev",
88
+ },
89
+ },
90
+ ],
91
+ };
92
+ ```
93
+
94
+ This lets the dashboard show which package script owns a process and which
95
+ scripts are unmanaged.
96
+
97
+ ## Development
98
+
99
+ Inside this repo:
100
+
101
+ ```sh
102
+ pnpm --filter lookout-hq dev
103
+ pnpm --filter lookout-hq package:local
104
+ ```
105
+
106
+ The package is built with Vite+:
107
+
108
+ - `vp build` builds the React dashboard into `ui/dist`
109
+ - `vp pack` bundles the Node CLI into `dist/cli.mjs`
110
+ - `vp pm pack` creates a local npm tarball
111
+
112
+ ## Notes
113
+
114
+ Lookout intentionally uses PM2 and the ecosystem file as the source of truth.
115
+ It does not maintain a second process config format.
package/dist/cli.mjs ADDED
@@ -0,0 +1,363 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { createServer } from "node:http";
4
+ import { createHash } from "node:crypto";
5
+ import { spawn, spawnSync } from "node:child_process";
6
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
7
+ import { basename, dirname, extname, join, resolve } from "node:path";
8
+ import { fileURLToPath, pathToFileURL } from "node:url";
9
+ //#region src/cli.ts
10
+ const requireFromCli = createRequire(import.meta.url);
11
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
12
+ async function main() {
13
+ const [command = "serve", ...args] = process.argv.slice(2);
14
+ if (command === "serve") return serve(readPort(args));
15
+ if (command === "status") return printJson(await repoStatus());
16
+ if (command === "up" || command === "start") return pm2Start(args);
17
+ if (command === "down" || command === "stop") return pm2Stop(args);
18
+ if (command === "restart") return pm2Restart(args);
19
+ if (command === "logs") return pm2Logs(args);
20
+ if (command === "doctor") return doctor();
21
+ if (command === "help" || command === "--help" || command === "-h") return help();
22
+ throw new Error(`Unknown command "${command}". Run lookout help.`);
23
+ }
24
+ function readPort(args) {
25
+ const index = args.findIndex((arg) => arg === "--port" || arg === "-p");
26
+ const raw = index >= 0 ? args[index + 1] : process.env.LOOKOUT_PORT;
27
+ return raw ? Number(raw) : 8790;
28
+ }
29
+ async function serve(port) {
30
+ createServer(async (request, response) => {
31
+ try {
32
+ await handleRequest(request, response);
33
+ } catch (error) {
34
+ writeJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
35
+ }
36
+ }).listen(port, "127.0.0.1", () => {
37
+ console.log(`lookout dashboard: http://127.0.0.1:${port}`);
38
+ });
39
+ }
40
+ async function handleRequest(request, response) {
41
+ const url = new URL(request.url || "/", "http://lookout.local");
42
+ const method = request.method || "GET";
43
+ if (method === "GET" && url.pathname === "/api/repos") return writeJson(response, 200, [await repoStatus()]);
44
+ if (method === "GET" && url.pathname === "/api/status") {
45
+ const repo = await repoStatus();
46
+ return writeJson(response, 200, {
47
+ project: repo.project,
48
+ backend: repo.backend,
49
+ config_path: repo.config_path,
50
+ tasks: repo.tasks
51
+ });
52
+ }
53
+ if (method === "GET" && url.pathname.startsWith("/api/repos/") && url.pathname.includes("/logs/")) {
54
+ const { task } = parseRepoLogPath(url.pathname);
55
+ return writeText(response, 200, taskLogs(task));
56
+ }
57
+ if (method === "POST" && url.pathname.startsWith("/api/repos/") && url.pathname.includes("/tasks/")) {
58
+ const { task, action } = parseRepoTaskPath(url.pathname);
59
+ await runAction(task, action);
60
+ response.writeHead(204).end();
61
+ return;
62
+ }
63
+ if (url.pathname.startsWith("/api/")) return writeJson(response, 404, { error: "not found" });
64
+ return serveStatic(url.pathname, response);
65
+ }
66
+ function parseRepoLogPath(pathname) {
67
+ const parts = pathname.replace(/^\/api\/repos\//, "").split("/");
68
+ return {
69
+ repo: decodeURIComponent(parts[0] || ""),
70
+ task: decodeURIComponent(parts[2] || "")
71
+ };
72
+ }
73
+ function parseRepoTaskPath(pathname) {
74
+ const parts = pathname.replace(/^\/api\/repos\//, "").split("/");
75
+ return {
76
+ repo: decodeURIComponent(parts[0] || ""),
77
+ task: decodeURIComponent(parts[2] || ""),
78
+ action: decodeURIComponent(parts[3] || "")
79
+ };
80
+ }
81
+ async function repoStatus() {
82
+ const root = findRepoRoot();
83
+ const configPath = findEcosystemConfig(root);
84
+ const apps = await ecosystemApps(configPath);
85
+ const processes = pm2Processes(root);
86
+ const tasks = Object.fromEntries(apps.map((app) => {
87
+ const process = processes.get(app.name);
88
+ const packageName = app.env?.LOOKOUT_PACKAGE;
89
+ const script = app.env?.LOOKOUT_SCRIPT;
90
+ const packagePath = taskCwd(root, app);
91
+ return [app.name, {
92
+ name: app.name,
93
+ status: process ? pm2StatusLabel(process.status) : "stopped",
94
+ pid: process?.pid,
95
+ log_path: process?.outLogPath,
96
+ backend_id: app.name,
97
+ cpu: process?.cpu,
98
+ memory_bytes: process?.memoryBytes,
99
+ restarts: process?.restarts,
100
+ uptime_ms: process?.uptimeMs,
101
+ command: displayCommand(app),
102
+ package: packageName,
103
+ script,
104
+ package_path: packagePath
105
+ }];
106
+ }));
107
+ return {
108
+ id: repoId(root),
109
+ root,
110
+ project: basename(root),
111
+ backend: "pm2",
112
+ config_path: configPath,
113
+ tasks,
114
+ scripts: packageScripts(root, apps, processes)
115
+ };
116
+ }
117
+ function findRepoRoot() {
118
+ let current = process.cwd();
119
+ while (true) {
120
+ if (existsSync(join(current, "package.json")) && findEcosystemConfig(current, false)) return current;
121
+ const parent = dirname(current);
122
+ if (parent === current) return process.cwd();
123
+ current = parent;
124
+ }
125
+ }
126
+ function findEcosystemConfig(root = process.cwd(), required = true) {
127
+ for (const name of [
128
+ "ecosystem.config.cjs",
129
+ "ecosystem.config.js",
130
+ "ecosystem.config.mjs"
131
+ ]) {
132
+ const path = join(root, name);
133
+ if (existsSync(path)) return path;
134
+ }
135
+ if (!required) return "";
136
+ throw new Error("could not find ecosystem.config.*");
137
+ }
138
+ async function ecosystemApps(configPath) {
139
+ const loaded = extname(configPath) === ".mjs" ? await import(pathToFileURL(configPath).href) : requireFromCli(configPath);
140
+ const config = loaded.default || loaded;
141
+ return Array.isArray(config.apps) ? config.apps : [];
142
+ }
143
+ function pm2Processes(root) {
144
+ const output = spawnSync(pm2Executable(root), ["jlist"], {
145
+ cwd: root,
146
+ encoding: "utf8"
147
+ });
148
+ if (output.status !== 0) return /* @__PURE__ */ new Map();
149
+ const apps = JSON.parse(output.stdout || "[]");
150
+ return new Map(apps.flatMap((app) => {
151
+ const name = app.name || app.pm2_env?.name;
152
+ if (!name) return [];
153
+ return [[name, {
154
+ name,
155
+ status: app.pm2_env?.status || "unknown",
156
+ pid: numberOrUndefined(app.pid),
157
+ cpu: numberOrUndefined(app.monit?.cpu),
158
+ memoryBytes: numberOrUndefined(app.monit?.memory),
159
+ restarts: numberOrUndefined(app.pm2_env?.restart_time),
160
+ uptimeMs: app.pm2_env?.pm_uptime ? Date.now() - Number(app.pm2_env.pm_uptime) : void 0,
161
+ outLogPath: app.pm2_env?.pm_out_log_path,
162
+ errorLogPath: app.pm2_env?.pm_err_log_path
163
+ }]];
164
+ }));
165
+ }
166
+ function packageScripts(root, apps, processes) {
167
+ const appByPackageScript = new Map(apps.flatMap((app) => {
168
+ const packageName = app.env?.LOOKOUT_PACKAGE;
169
+ const script = app.env?.LOOKOUT_SCRIPT;
170
+ return packageName && script ? [[`${packageName}:${script}`, app]] : [];
171
+ }));
172
+ return packageJsonFiles(root).flatMap((packageJsonPath) => {
173
+ const json = JSON.parse(readFileSync(packageJsonPath, "utf8"));
174
+ const packageName = json.name || basename(dirname(packageJsonPath));
175
+ return Object.entries(json.scripts || {}).map(([script, command]) => {
176
+ const app = appByPackageScript.get(`${packageName}:${script}`);
177
+ const process = app ? processes.get(app.name) : void 0;
178
+ return {
179
+ package: packageName,
180
+ package_path: dirname(packageJsonPath),
181
+ script,
182
+ command,
183
+ task: app?.name || null,
184
+ status: process ? pm2StatusLabel(process.status) : app ? "stopped" : null,
185
+ backend_id: app?.name || null
186
+ };
187
+ });
188
+ });
189
+ }
190
+ function packageJsonFiles(root) {
191
+ const ignored = new Set([
192
+ ".git",
193
+ "node_modules",
194
+ "dist",
195
+ "target",
196
+ "storybook-static",
197
+ ".turbo"
198
+ ]);
199
+ const files = [];
200
+ const visit = (dir) => {
201
+ for (const entry of readdirSync(dir)) {
202
+ if (ignored.has(entry)) continue;
203
+ const path = join(dir, entry);
204
+ if (statSync(path).isDirectory()) visit(path);
205
+ else if (entry === "package.json") files.push(path);
206
+ }
207
+ };
208
+ visit(root);
209
+ return files;
210
+ }
211
+ async function runAction(task, action) {
212
+ const root = findRepoRoot();
213
+ if (action === "open-folder") return openFolder((await repoStatus()).tasks[task]?.package_path || root);
214
+ if (action === "restart") return pm2Restart([task]);
215
+ if (action === "up" || action === "start" || action === "resume") return pm2Start([task]);
216
+ if (action === "down" || action === "stop" || action === "pause") return pm2Stop([task]);
217
+ throw new Error(`unknown action "${action}"`);
218
+ }
219
+ function pm2Start(tasks) {
220
+ const root = findRepoRoot();
221
+ return runPm2(root, [
222
+ "start",
223
+ findEcosystemConfig(root),
224
+ ...onlyArgs(tasks)
225
+ ]);
226
+ }
227
+ function pm2Stop(tasks) {
228
+ return runPm2(findRepoRoot(), ["stop", ...tasks.length ? tasks : ["all"]]);
229
+ }
230
+ function pm2Restart(tasks) {
231
+ const root = findRepoRoot();
232
+ if (tasks.length) return runPm2(root, [
233
+ "restart",
234
+ ...tasks,
235
+ "--update-env"
236
+ ]);
237
+ return runPm2(root, [
238
+ "restart",
239
+ findEcosystemConfig(root),
240
+ "--update-env"
241
+ ]);
242
+ }
243
+ function pm2Logs(tasks) {
244
+ return runPm2(findRepoRoot(), ["logs", ...tasks.length ? tasks : []], "inherit");
245
+ }
246
+ function runPm2(root, args, stdio = "inherit") {
247
+ if (spawnSync(pm2Executable(root), args, {
248
+ cwd: root,
249
+ stdio
250
+ }).status !== 0) throw new Error(`pm2 ${args.join(" ")} failed`);
251
+ }
252
+ function pm2Executable(root) {
253
+ const binary = process.platform === "win32" ? "pm2.cmd" : "pm2";
254
+ let current = root;
255
+ while (true) {
256
+ const candidate = join(current, "node_modules", ".bin", binary);
257
+ if (existsSync(candidate)) return candidate;
258
+ const parent = dirname(current);
259
+ if (parent === current) return binary;
260
+ current = parent;
261
+ }
262
+ }
263
+ function onlyArgs(tasks) {
264
+ return tasks.length ? ["--only", tasks.join(",")] : [];
265
+ }
266
+ function taskLogs(task) {
267
+ const process = pm2Processes(findRepoRoot()).get(task);
268
+ return [process?.errorLogPath, process?.outLogPath].filter(Boolean).map((path) => readFileSafe(path)).filter(Boolean).join("\n");
269
+ }
270
+ function serveStatic(pathname, response) {
271
+ const dist = uiDist();
272
+ const filePath = pathname === "/" ? join(dist, "index.html") : join(dist, pathname.slice(1));
273
+ const safePath = filePath.startsWith(dist) && existsSync(filePath) ? filePath : join(dist, "index.html");
274
+ if (!existsSync(safePath)) return writeText(response, 200, "Build the dashboard first with `pnpm --filter lookout-hq build`.");
275
+ response.writeHead(200, { "content-type": contentType(safePath) });
276
+ response.end(readFileSync(safePath));
277
+ }
278
+ function uiDist() {
279
+ const candidates = [
280
+ process.env.LOOKOUT_UI_DIST,
281
+ join(packageRoot, "ui", "dist"),
282
+ join(process.cwd(), "apps", "lookout", "ui", "dist"),
283
+ join(process.cwd(), "ui", "dist")
284
+ ].filter(Boolean);
285
+ return candidates.find((candidate) => existsSync(candidate)) || candidates[1];
286
+ }
287
+ function contentType(path) {
288
+ if (path.endsWith(".html")) return "text/html; charset=utf-8";
289
+ if (path.endsWith(".js")) return "text/javascript; charset=utf-8";
290
+ if (path.endsWith(".css")) return "text/css; charset=utf-8";
291
+ if (path.endsWith(".png")) return "image/png";
292
+ if (path.endsWith(".svg")) return "image/svg+xml";
293
+ return "application/octet-stream";
294
+ }
295
+ function displayCommand(app) {
296
+ return [app.script, ...Array.isArray(app.args) ? app.args : app.args ? [app.args] : []].filter(Boolean).join(" ");
297
+ }
298
+ function taskCwd(root, app) {
299
+ return resolve(root, app.cwd || ".");
300
+ }
301
+ function repoId(root) {
302
+ return createHash("sha1").update(root).digest("hex").slice(0, 15);
303
+ }
304
+ function pm2StatusLabel(status) {
305
+ if (status === "online" || status === "launching") return "running";
306
+ if (status === "stopped") return "stopped";
307
+ return status;
308
+ }
309
+ function numberOrUndefined(value) {
310
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
311
+ }
312
+ function readFileSafe(path) {
313
+ try {
314
+ return readFileSync(path, "utf8");
315
+ } catch {
316
+ return "";
317
+ }
318
+ }
319
+ function openFolder(path) {
320
+ spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer" : "xdg-open", [path], {
321
+ detached: true,
322
+ stdio: "ignore"
323
+ }).unref();
324
+ }
325
+ function writeJson(response, status, body) {
326
+ response.writeHead(status, { "content-type": "application/json" });
327
+ response.end(JSON.stringify(body));
328
+ }
329
+ function writeText(response, status, body) {
330
+ response.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
331
+ response.end(body);
332
+ }
333
+ function printJson(value) {
334
+ console.log(JSON.stringify(value, null, 2));
335
+ }
336
+ function doctor() {
337
+ const root = findRepoRoot();
338
+ console.log(`repo: ${root}`);
339
+ console.log(`ecosystem: ${findEcosystemConfig(root)}`);
340
+ console.log(`pm2: ${pm2Executable(root)}`);
341
+ console.log(`ui: ${uiDist()}`);
342
+ }
343
+ function help() {
344
+ console.log(`Lookout
345
+
346
+ Commands:
347
+ lookout serve --port 8790
348
+ lookout start [task...]
349
+ lookout stop [task...]
350
+ lookout restart [task...]
351
+ lookout status
352
+ lookout logs [task...]
353
+ lookout doctor
354
+ `);
355
+ }
356
+ main().catch((error) => {
357
+ console.error(error instanceof Error ? error.message : error);
358
+ process.exitCode = 1;
359
+ });
360
+ //#endregion
361
+ export {};
362
+
363
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createHash } from \"node:crypto\";\nimport { spawn, spawnSync } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { basename, dirname, extname, join, resolve } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\ntype EcosystemApp = {\n name: string;\n cwd?: string;\n script?: string;\n args?: string | string[];\n env?: Record<string, string>;\n};\n\ntype Pm2Process = {\n name: string;\n status: string;\n pid?: number;\n cpu?: number;\n memoryBytes?: number;\n restarts?: number;\n uptimeMs?: number;\n outLogPath?: string;\n errorLogPath?: string;\n};\n\ntype PackageScript = {\n package: string;\n package_path: string;\n script: string;\n command: string;\n task?: string | null;\n status?: string | null;\n backend_id?: string | null;\n};\n\ntype RuntimeTask = {\n name: string;\n status: string;\n pid?: number;\n log_path?: string;\n backend_id?: string;\n cpu?: number;\n memory_bytes?: number;\n restarts?: number;\n uptime_ms?: number;\n command?: string;\n package?: string;\n script?: string;\n package_path?: string;\n};\n\ntype RepoStatus = {\n id: string;\n root: string;\n project: string;\n backend: \"pm2\";\n config_path: string;\n tasks: Record<string, RuntimeTask>;\n scripts: PackageScript[];\n};\n\nconst requireFromCli = createRequire(import.meta.url);\nconst cliDir = dirname(fileURLToPath(import.meta.url));\nconst packageRoot = resolve(cliDir, \"..\");\n\nasync function main() {\n const [command = \"serve\", ...args] = process.argv.slice(2);\n\n if (command === \"serve\") return serve(readPort(args));\n if (command === \"status\") return printJson(await repoStatus());\n if (command === \"up\" || command === \"start\") return pm2Start(args);\n if (command === \"down\" || command === \"stop\") return pm2Stop(args);\n if (command === \"restart\") return pm2Restart(args);\n if (command === \"logs\") return pm2Logs(args);\n if (command === \"doctor\") return doctor();\n if (command === \"help\" || command === \"--help\" || command === \"-h\") return help();\n\n throw new Error(`Unknown command \"${command}\". Run lookout help.`);\n}\n\nfunction readPort(args: string[]) {\n const index = args.findIndex((arg) => arg === \"--port\" || arg === \"-p\");\n const raw = index >= 0 ? args[index + 1] : process.env.LOOKOUT_PORT;\n return raw ? Number(raw) : 8790;\n}\n\nasync function serve(port: number) {\n const server = createServer(async (request, response) => {\n try {\n await handleRequest(request, response);\n } catch (error) {\n writeJson(response, 500, { error: error instanceof Error ? error.message : String(error) });\n }\n });\n\n server.listen(port, \"127.0.0.1\", () => {\n console.log(`lookout dashboard: http://127.0.0.1:${port}`);\n });\n}\n\nasync function handleRequest(request: IncomingMessage, response: ServerResponse) {\n const url = new URL(request.url || \"/\", \"http://lookout.local\");\n const method = request.method || \"GET\";\n\n if (method === \"GET\" && url.pathname === \"/api/repos\") {\n return writeJson(response, 200, [await repoStatus()]);\n }\n\n if (method === \"GET\" && url.pathname === \"/api/status\") {\n const repo = await repoStatus();\n return writeJson(response, 200, {\n project: repo.project,\n backend: repo.backend,\n config_path: repo.config_path,\n tasks: repo.tasks,\n });\n }\n\n if (method === \"GET\" && url.pathname.startsWith(\"/api/repos/\") && url.pathname.includes(\"/logs/\")) {\n const { task } = parseRepoLogPath(url.pathname);\n return writeText(response, 200, taskLogs(task));\n }\n\n if (method === \"POST\" && url.pathname.startsWith(\"/api/repos/\") && url.pathname.includes(\"/tasks/\")) {\n const { task, action } = parseRepoTaskPath(url.pathname);\n await runAction(task, action);\n response.writeHead(204).end();\n return;\n }\n\n if (url.pathname.startsWith(\"/api/\")) {\n return writeJson(response, 404, { error: \"not found\" });\n }\n\n return serveStatic(url.pathname, response);\n}\n\nfunction parseRepoLogPath(pathname: string) {\n const parts = pathname.replace(/^\\/api\\/repos\\//, \"\").split(\"/\");\n return { repo: decodeURIComponent(parts[0] || \"\"), task: decodeURIComponent(parts[2] || \"\") };\n}\n\nfunction parseRepoTaskPath(pathname: string) {\n const parts = pathname.replace(/^\\/api\\/repos\\//, \"\").split(\"/\");\n return {\n repo: decodeURIComponent(parts[0] || \"\"),\n task: decodeURIComponent(parts[2] || \"\"),\n action: decodeURIComponent(parts[3] || \"\"),\n };\n}\n\nasync function repoStatus(): Promise<RepoStatus> {\n const root = findRepoRoot();\n const configPath = findEcosystemConfig(root);\n const apps = await ecosystemApps(configPath);\n const processes = pm2Processes(root);\n const tasks = Object.fromEntries(\n apps.map((app) => {\n const process = processes.get(app.name);\n const packageName = app.env?.LOOKOUT_PACKAGE;\n const script = app.env?.LOOKOUT_SCRIPT;\n const packagePath = taskCwd(root, app);\n\n return [\n app.name,\n {\n name: app.name,\n status: process ? pm2StatusLabel(process.status) : \"stopped\",\n pid: process?.pid,\n log_path: process?.outLogPath,\n backend_id: app.name,\n cpu: process?.cpu,\n memory_bytes: process?.memoryBytes,\n restarts: process?.restarts,\n uptime_ms: process?.uptimeMs,\n command: displayCommand(app),\n package: packageName,\n script,\n package_path: packagePath,\n },\n ];\n }),\n );\n\n return {\n id: repoId(root),\n root,\n project: basename(root),\n backend: \"pm2\",\n config_path: configPath,\n tasks,\n scripts: packageScripts(root, apps, processes),\n };\n}\n\nfunction findRepoRoot() {\n let current = process.cwd();\n while (true) {\n if (existsSync(join(current, \"package.json\")) && findEcosystemConfig(current, false)) return current;\n const parent = dirname(current);\n if (parent === current) return process.cwd();\n current = parent;\n }\n}\n\nfunction findEcosystemConfig(root = process.cwd(), required = true) {\n const names = [\"ecosystem.config.cjs\", \"ecosystem.config.js\", \"ecosystem.config.mjs\"];\n for (const name of names) {\n const path = join(root, name);\n if (existsSync(path)) return path;\n }\n if (!required) return \"\";\n throw new Error(\"could not find ecosystem.config.*\");\n}\n\nasync function ecosystemApps(configPath: string): Promise<EcosystemApp[]> {\n const loaded =\n extname(configPath) === \".mjs\"\n ? await import(pathToFileURL(configPath).href)\n : requireFromCli(configPath);\n const config = loaded.default || loaded;\n return Array.isArray(config.apps) ? config.apps : [];\n}\n\nfunction pm2Processes(root: string) {\n const output = spawnSync(pm2Executable(root), [\"jlist\"], {\n cwd: root,\n encoding: \"utf8\",\n });\n if (output.status !== 0) return new Map<string, Pm2Process>();\n\n const apps = JSON.parse(output.stdout || \"[]\") as Array<Record<string, any>>;\n return new Map(\n apps.flatMap((app) => {\n const name = app.name || app.pm2_env?.name;\n if (!name) return [];\n return [\n [\n name,\n {\n name,\n status: app.pm2_env?.status || \"unknown\",\n pid: numberOrUndefined(app.pid),\n cpu: numberOrUndefined(app.monit?.cpu),\n memoryBytes: numberOrUndefined(app.monit?.memory),\n restarts: numberOrUndefined(app.pm2_env?.restart_time),\n uptimeMs: app.pm2_env?.pm_uptime ? Date.now() - Number(app.pm2_env.pm_uptime) : undefined,\n outLogPath: app.pm2_env?.pm_out_log_path,\n errorLogPath: app.pm2_env?.pm_err_log_path,\n },\n ],\n ];\n }),\n );\n}\n\nfunction packageScripts(root: string, apps: EcosystemApp[], processes: Map<string, Pm2Process>) {\n const appByPackageScript = new Map(\n apps.flatMap((app) => {\n const packageName = app.env?.LOOKOUT_PACKAGE;\n const script = app.env?.LOOKOUT_SCRIPT;\n return packageName && script ? [[`${packageName}:${script}`, app] as const] : [];\n }),\n );\n\n return packageJsonFiles(root).flatMap((packageJsonPath) => {\n const json = JSON.parse(readFileSync(packageJsonPath, \"utf8\")) as {\n name?: string;\n scripts?: Record<string, string>;\n };\n const packageName = json.name || basename(dirname(packageJsonPath));\n return Object.entries(json.scripts || {}).map(([script, command]) => {\n const app = appByPackageScript.get(`${packageName}:${script}`);\n const process = app ? processes.get(app.name) : undefined;\n return {\n package: packageName,\n package_path: dirname(packageJsonPath),\n script,\n command,\n task: app?.name || null,\n status: process ? pm2StatusLabel(process.status) : app ? \"stopped\" : null,\n backend_id: app?.name || null,\n };\n });\n });\n}\n\nfunction packageJsonFiles(root: string) {\n const ignored = new Set([\".git\", \"node_modules\", \"dist\", \"target\", \"storybook-static\", \".turbo\"]);\n const files: string[] = [];\n const visit = (dir: string) => {\n for (const entry of readdirSync(dir)) {\n if (ignored.has(entry)) continue;\n const path = join(dir, entry);\n const stat = statSync(path);\n if (stat.isDirectory()) visit(path);\n else if (entry === \"package.json\") files.push(path);\n }\n };\n visit(root);\n return files;\n}\n\nasync function runAction(task: string, action: string) {\n const root = findRepoRoot();\n if (action === \"open-folder\") return openFolder((await repoStatus()).tasks[task]?.package_path || root);\n if (action === \"restart\") return pm2Restart([task]);\n if (action === \"up\" || action === \"start\" || action === \"resume\") return pm2Start([task]);\n if (action === \"down\" || action === \"stop\" || action === \"pause\") return pm2Stop([task]);\n throw new Error(`unknown action \"${action}\"`);\n}\n\nfunction pm2Start(tasks: string[]) {\n const root = findRepoRoot();\n const configPath = findEcosystemConfig(root);\n return runPm2(root, [\"start\", configPath, ...onlyArgs(tasks)]);\n}\n\nfunction pm2Stop(tasks: string[]) {\n const root = findRepoRoot();\n return runPm2(root, [\"stop\", ...(tasks.length ? tasks : [\"all\"])]);\n}\n\nfunction pm2Restart(tasks: string[]) {\n const root = findRepoRoot();\n if (tasks.length) return runPm2(root, [\"restart\", ...tasks, \"--update-env\"]);\n return runPm2(root, [\"restart\", findEcosystemConfig(root), \"--update-env\"]);\n}\n\nfunction pm2Logs(tasks: string[]) {\n const root = findRepoRoot();\n return runPm2(root, [\"logs\", ...(tasks.length ? tasks : [])], \"inherit\");\n}\n\nfunction runPm2(root: string, args: string[], stdio: \"inherit\" | \"pipe\" = \"inherit\") {\n const result = spawnSync(pm2Executable(root), args, { cwd: root, stdio });\n if (result.status !== 0) throw new Error(`pm2 ${args.join(\" \")} failed`);\n}\n\nfunction pm2Executable(root: string) {\n const binary = process.platform === \"win32\" ? \"pm2.cmd\" : \"pm2\";\n let current = root;\n while (true) {\n const candidate = join(current, \"node_modules\", \".bin\", binary);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(current);\n if (parent === current) return binary;\n current = parent;\n }\n}\n\nfunction onlyArgs(tasks: string[]) {\n return tasks.length ? [\"--only\", tasks.join(\",\")] : [];\n}\n\nfunction taskLogs(task: string) {\n const process = pm2Processes(findRepoRoot()).get(task);\n return [process?.errorLogPath, process?.outLogPath]\n .filter(Boolean)\n .map((path) => readFileSafe(path as string))\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nfunction serveStatic(pathname: string, response: ServerResponse) {\n const dist = uiDist();\n const filePath = pathname === \"/\" ? join(dist, \"index.html\") : join(dist, pathname.slice(1));\n const safePath = filePath.startsWith(dist) && existsSync(filePath) ? filePath : join(dist, \"index.html\");\n if (!existsSync(safePath)) {\n return writeText(response, 200, \"Build the dashboard first with `pnpm --filter lookout-hq build`.\");\n }\n response.writeHead(200, { \"content-type\": contentType(safePath) });\n response.end(readFileSync(safePath));\n}\n\nfunction uiDist() {\n const candidates = [\n process.env.LOOKOUT_UI_DIST,\n join(packageRoot, \"ui\", \"dist\"),\n join(process.cwd(), \"apps\", \"lookout\", \"ui\", \"dist\"),\n join(process.cwd(), \"ui\", \"dist\"),\n ].filter(Boolean) as string[];\n return candidates.find((candidate) => existsSync(candidate)) || candidates[1];\n}\n\nfunction contentType(path: string) {\n if (path.endsWith(\".html\")) return \"text/html; charset=utf-8\";\n if (path.endsWith(\".js\")) return \"text/javascript; charset=utf-8\";\n if (path.endsWith(\".css\")) return \"text/css; charset=utf-8\";\n if (path.endsWith(\".png\")) return \"image/png\";\n if (path.endsWith(\".svg\")) return \"image/svg+xml\";\n return \"application/octet-stream\";\n}\n\nfunction displayCommand(app: EcosystemApp) {\n return [app.script, ...(Array.isArray(app.args) ? app.args : app.args ? [app.args] : [])]\n .filter(Boolean)\n .join(\" \");\n}\n\nfunction taskCwd(root: string, app: EcosystemApp) {\n return resolve(root, app.cwd || \".\");\n}\n\nfunction repoId(root: string) {\n return createHash(\"sha1\").update(root).digest(\"hex\").slice(0, 15);\n}\n\nfunction pm2StatusLabel(status: string) {\n if (status === \"online\" || status === \"launching\") return \"running\";\n if (status === \"stopped\") return \"stopped\";\n return status;\n}\n\nfunction numberOrUndefined(value: unknown) {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction readFileSafe(path: string) {\n try {\n return readFileSync(path, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\nfunction openFolder(path: string) {\n const command = process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"explorer\" : \"xdg-open\";\n spawn(command, [path], { detached: true, stdio: \"ignore\" }).unref();\n}\n\nfunction writeJson(response: ServerResponse, status: number, body: unknown) {\n response.writeHead(status, { \"content-type\": \"application/json\" });\n response.end(JSON.stringify(body));\n}\n\nfunction writeText(response: ServerResponse, status: number, body: string) {\n response.writeHead(status, { \"content-type\": \"text/plain; charset=utf-8\" });\n response.end(body);\n}\n\nfunction printJson(value: unknown) {\n console.log(JSON.stringify(value, null, 2));\n}\n\nfunction doctor() {\n const root = findRepoRoot();\n console.log(`repo: ${root}`);\n console.log(`ecosystem: ${findEcosystemConfig(root)}`);\n console.log(`pm2: ${pm2Executable(root)}`);\n console.log(`ui: ${uiDist()}`);\n}\n\nfunction help() {\n console.log(`Lookout\n\nCommands:\n lookout serve --port 8790\n lookout start [task...]\n lookout stop [task...]\n lookout restart [task...]\n lookout status\n lookout logs [task...]\n lookout doctor\n`);\n}\n\nmain().catch((error) => {\n console.error(error instanceof Error ? error.message : error);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "lookout-hq",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "bin": {
7
+ "lookout": "dist/cli.mjs"
8
+ },
9
+ "files": [
10
+ "dist/cli.mjs",
11
+ "dist/cli.mjs.map",
12
+ "ui/dist",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "dev": "node scripts/dev.mjs",
17
+ "dev:ui": "vp dev --host 0.0.0.0 --port 5176",
18
+ "serve:api": "vp exec tsx src/cli.ts serve --port 8790",
19
+ "build": "vp build && vp pack",
20
+ "pack": "vp pack",
21
+ "package:local": "vp run build && vp pm pack --pack-destination ./dist",
22
+ "preview": "vp preview --host 0.0.0.0 --port 5176",
23
+ "lint": "vp lint ui/src",
24
+ "typecheck": "vp exec tsc --noEmit"
25
+ },
26
+ "dependencies": {},
27
+ "devDependencies": {
28
+ "@curve/ui": "workspace:*",
29
+ "@hugeicons/core-free-icons": "^4.1.4",
30
+ "@vitejs/plugin-react": "^6.0.1",
31
+ "agentation": "^3.0.2",
32
+ "react": "^19.2.6",
33
+ "react-dom": "^19.2.6",
34
+ "react-router-dom": "^7.15.1",
35
+ "typescript": "^6.0.3",
36
+ "vite": "^8.0.11",
37
+ "vite-plus": "^0.1.21"
38
+ }
39
+ }