dev3000 0.0.77 → 0.0.79
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 +18 -0
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +33 -10
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/dev-environment.d.ts +2 -1
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +230 -271
- package/dist/dev-environment.js.map +1 -1
- package/dist/screencast-manager.d.ts +76 -0
- package/dist/screencast-manager.d.ts.map +1 -0
- package/dist/screencast-manager.js +410 -0
- package/dist/screencast-manager.js.map +1 -0
- package/dist/src/tui-interface-impl.tsx +45 -14
- package/dist/tui-interface-impl.d.ts +1 -0
- package/dist/tui-interface-impl.d.ts.map +1 -1
- package/dist/tui-interface-impl.js +24 -7
- package/dist/tui-interface-impl.js.map +1 -1
- package/dist/tui-interface.d.ts +2 -0
- package/dist/tui-interface.d.ts.map +1 -1
- package/dist/tui-interface.js +8 -1
- package/dist/tui-interface.js.map +1 -1
- package/mcp-server/.next/BUILD_ID +1 -1
- package/mcp-server/.next/app-path-routes-manifest.json +4 -1
- package/mcp-server/.next/build-manifest.json +5 -5
- package/mcp-server/.next/fallback-build-manifest.json +2 -2
- package/mcp-server/.next/prerender-manifest.json +3 -3
- package/mcp-server/.next/routes-manifest.json +16 -0
- package/mcp-server/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/mcp-server/.next/server/app/_global-error/page.js +1 -1
- package/mcp-server/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/_global-error.html +2 -2
- package/mcp-server/.next/server/app/_global-error.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/mcp-server/.next/server/app/_not-found/page.js +1 -1
- package/mcp-server/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/_not-found.html +1 -1
- package/mcp-server/.next/server/app/_not-found.rsc +2 -2
- package/mcp-server/.next/server/app/api/jank/[session]/route/app-paths-manifest.json +3 -0
- package/mcp-server/.next/server/app/api/jank/[session]/route/build-manifest.json +11 -0
- package/mcp-server/.next/server/app/api/jank/[session]/route/server-reference-manifest.json +4 -0
- package/mcp-server/.next/server/app/api/jank/[session]/route.js +9 -0
- package/mcp-server/.next/server/app/api/jank/[session]/route.js.map +5 -0
- package/mcp-server/.next/server/app/api/jank/[session]/route.js.nft.json +1 -0
- package/mcp-server/.next/server/app/api/jank/[session]/route_client-reference-manifest.js +2 -0
- package/mcp-server/.next/server/app/api/logs/head/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/logs/list/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/logs/rotate/route.js +1 -1
- package/mcp-server/.next/server/app/api/logs/rotate/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/logs/stream/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/logs/tail/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/screenshots/[filename]/route.js +1 -1
- package/mcp-server/.next/server/app/api/screenshots/[filename]/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/screenshots/list/route/app-paths-manifest.json +3 -0
- package/mcp-server/.next/server/app/api/screenshots/list/route/build-manifest.json +11 -0
- package/mcp-server/.next/server/app/api/screenshots/list/route/server-reference-manifest.json +4 -0
- package/mcp-server/.next/server/app/api/screenshots/list/route.js +7 -0
- package/mcp-server/.next/server/app/api/screenshots/list/route.js.map +5 -0
- package/mcp-server/.next/server/app/api/screenshots/list/route.js.nft.json +1 -0
- package/mcp-server/.next/server/app/api/screenshots/list/route_client-reference-manifest.js +2 -0
- package/mcp-server/.next/server/app/api/tools/route.js +1 -1
- package/mcp-server/.next/server/app/api/tools/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/index.html +1 -1
- package/mcp-server/.next/server/app/index.rsc +3 -3
- package/mcp-server/.next/server/app/logs/page/build-manifest.json +3 -3
- package/mcp-server/.next/server/app/logs/page.js +1 -1
- package/mcp-server/.next/server/app/logs/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/mcp/route.js +3 -2
- package/mcp-server/.next/server/app/mcp/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/page/build-manifest.json +3 -3
- package/mcp-server/.next/server/app/page.js +1 -1
- package/mcp-server/.next/server/app/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/video/[session]/page/app-paths-manifest.json +3 -0
- package/mcp-server/.next/server/app/video/[session]/page/build-manifest.json +18 -0
- package/mcp-server/.next/server/app/video/[session]/page/next-font-manifest.json +6 -0
- package/mcp-server/.next/server/app/video/[session]/page/react-loadable-manifest.json +1 -0
- package/mcp-server/.next/server/app/video/[session]/page/server-reference-manifest.json +4 -0
- package/mcp-server/.next/server/app/video/[session]/page.js +15 -0
- package/mcp-server/.next/server/app/video/[session]/page.js.map +5 -0
- package/mcp-server/.next/server/app/video/[session]/page.js.nft.json +1 -0
- package/mcp-server/.next/server/app/video/[session]/page_client-reference-manifest.js +2 -0
- package/mcp-server/.next/server/app-paths-manifest.json +4 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__00592d3f._.js +34 -0
- package/mcp-server/.next/server/chunks/[root-of-the-server]__00592d3f._.js.map +1 -0
- package/mcp-server/.next/server/chunks/{[root-of-the-server]__5580d2ea._.js → [root-of-the-server]__177c72c6._.js} +3 -3
- package/mcp-server/.next/server/chunks/[root-of-the-server]__177c72c6._.js.map +1 -0
- package/mcp-server/.next/server/chunks/{[root-of-the-server]__ffb73672._.js → [root-of-the-server]__2056c8b5._.js} +2 -2
- package/mcp-server/.next/server/chunks/{[root-of-the-server]__d1f9e389._.js → [root-of-the-server]__55c04517._.js} +2 -2
- package/mcp-server/.next/server/chunks/[root-of-the-server]__6ee9a99f._.js +3 -0
- package/mcp-server/.next/server/chunks/[root-of-the-server]__6ee9a99f._.js.map +1 -0
- package/mcp-server/.next/server/chunks/[root-of-the-server]__9a45c8f9._.js +3 -0
- package/mcp-server/.next/server/chunks/[root-of-the-server]__9a45c8f9._.js.map +1 -0
- package/mcp-server/.next/server/chunks/{[root-of-the-server]__e2089993._.js → [root-of-the-server]__bc773251._.js} +2 -2
- package/mcp-server/.next/server/chunks/[root-of-the-server]__e1a64519._.js +3 -0
- package/mcp-server/.next/server/chunks/[root-of-the-server]__e1a64519._.js.map +1 -0
- package/mcp-server/.next/server/chunks/[root-of-the-server]__e6dcd8bf._.js +3 -0
- package/mcp-server/.next/server/chunks/[root-of-the-server]__e6dcd8bf._.js.map +1 -0
- package/mcp-server/.next/server/chunks/d1d76_next_dist_esm_build_templates_app-route_820fc951.js +3 -0
- package/mcp-server/.next/server/chunks/d1d76_next_dist_esm_build_templates_app-route_820fc951.js.map +1 -0
- package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_jank_[session]_route_actions_3b2b275b.js +3 -0
- package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_jank_[session]_route_actions_3b2b275b.js.map +1 -0
- package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_screenshots_list_route_actions_acfa57bd.js +3 -0
- package/mcp-server/.next/server/chunks/mcp-server__next-internal_server_app_api_screenshots_list_route_actions_acfa57bd.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__bf771f7e._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/{[root-of-the-server]__8db775f9._.js.map → [root-of-the-server]__bf771f7e._.js.map} +1 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__c4e78a20._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/{[root-of-the-server]__e5dec879._.js.map → [root-of-the-server]__c4e78a20._.js.map} +1 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__e1bc1b8a._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__e1bc1b8a._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__fc10c8f1._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__fc10c8f1._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/_0b8335fc._.js +1 -1
- package/mcp-server/.next/server/chunks/ssr/_0b8335fc._.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/_62451611._.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/_b15f05ee._.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/_bacf0748._.js +2 -2
- package/mcp-server/.next/server/chunks/ssr/_bacf0748._.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/_e4aa8f16._.js +4 -0
- package/mcp-server/.next/server/chunks/ssr/_e4aa8f16._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app_video_[session]_page_actions_a6aab323.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/mcp-server__next-internal_server_app_video_[session]_page_actions_a6aab323.js.map +1 -0
- package/mcp-server/.next/server/middleware-build-manifest.js +3 -3
- package/mcp-server/.next/server/server-reference-manifest.js +1 -1
- package/mcp-server/.next/server/server-reference-manifest.json +1 -1
- package/mcp-server/.next/static/chunks/274a8d03fad7f819.js +1 -0
- package/mcp-server/.next/static/chunks/3d37ed424c6aaf63.css +1 -0
- package/mcp-server/.next/static/chunks/543e14c771a22442.js +1 -0
- package/mcp-server/.next/static/chunks/58fdd5192b305065.js +1 -0
- package/mcp-server/.next/static/chunks/6bd684c2018a357c.js +1 -0
- package/mcp-server/.next/static/chunks/6d59e588420330ca.js +1 -0
- package/mcp-server/.next/static/chunks/9625e4da85a132f3.js +1 -0
- package/mcp-server/.next/static/chunks/c36bc797d535a4dc.js +1 -0
- package/mcp-server/.next/static/chunks/{turbopack-7cd5a898ed038e26.js → turbopack-9656e7304584cab2.js} +2 -2
- package/mcp-server/app/api/jank/[session]/route.ts +344 -0
- package/mcp-server/app/api/screenshots/list/route.ts +22 -0
- package/mcp-server/app/logs/LogsClient.tsx +33 -0
- package/mcp-server/app/logs/utils.ts +2 -0
- package/mcp-server/app/mcp/route.ts +20 -0
- package/mcp-server/app/mcp/tools.ts +288 -9
- package/mcp-server/app/video/[session]/page.tsx +237 -0
- package/mcp-server/package.json +4 -6
- package/package.json +7 -1
- package/src/tui-interface-impl.tsx +45 -14
- package/mcp-server/.next/server/chunks/[root-of-the-server]__270b33b7._.js +0 -34
- package/mcp-server/.next/server/chunks/[root-of-the-server]__270b33b7._.js.map +0 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__5580d2ea._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__8db775f9._.js +0 -3
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__e5dec879._.js +0 -3
- package/mcp-server/.next/static/chunks/11ac0d0e69696c72.js +0 -1
- package/mcp-server/.next/static/chunks/172f6179d608c15f.js +0 -1
- package/mcp-server/.next/static/chunks/65b18bf1ede9811a.css +0 -1
- package/mcp-server/.next/static/chunks/bb8a4e5f381a85ec.js +0 -1
- package/mcp-server/.next/static/chunks/e09be78bba2194fd.js +0 -1
- package/mcp-server/.next/static/chunks/fec3a6ddaef02b8b.js +0 -1
- /package/mcp-server/.next/server/chunks/{[root-of-the-server]__ffb73672._.js.map → [root-of-the-server]__2056c8b5._.js.map} +0 -0
- /package/mcp-server/.next/server/chunks/{[root-of-the-server]__d1f9e389._.js.map → [root-of-the-server]__55c04517._.js.map} +0 -0
- /package/mcp-server/.next/server/chunks/{[root-of-the-server]__e2089993._.js.map → [root-of-the-server]__bc773251._.js.map} +0 -0
- /package/mcp-server/.next/static/{NWQx_KX68gOo5fDkdXq6G → T9qI0it_H-9Z-TE5_ROut}/_buildManifest.js +0 -0
- /package/mcp-server/.next/static/{NWQx_KX68gOo5fDkdXq6G → T9qI0it_H-9Z-TE5_ROut}/_clientMiddlewareManifest.json +0 -0
- /package/mcp-server/.next/static/{NWQx_KX68gOo5fDkdXq6G → T9qI0it_H-9Z-TE5_ROut}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { WebSocket } from "ws";
|
|
5
|
+
/**
|
|
6
|
+
* ScreencastManager - Passive screencast capture for navigation events
|
|
7
|
+
*
|
|
8
|
+
* Listens for Page.frameStartedLoading and automatically captures 5 seconds
|
|
9
|
+
* of screencast frames for jank detection. No artificial page reloads needed!
|
|
10
|
+
*/
|
|
11
|
+
export class ScreencastManager {
|
|
12
|
+
cdpUrl;
|
|
13
|
+
logFn;
|
|
14
|
+
ws = null;
|
|
15
|
+
buffer = [];
|
|
16
|
+
isCapturing = false;
|
|
17
|
+
navigationStartTime = 0;
|
|
18
|
+
currentSessionId = "";
|
|
19
|
+
screenshotDir;
|
|
20
|
+
messageId = 1000; // Start high to avoid conflicts
|
|
21
|
+
appPort;
|
|
22
|
+
layoutShifts = [];
|
|
23
|
+
viewportInfo = {};
|
|
24
|
+
clsObserverInstalled = false;
|
|
25
|
+
constructor(cdpUrl, logFn, appPort) {
|
|
26
|
+
this.cdpUrl = cdpUrl;
|
|
27
|
+
this.logFn = logFn;
|
|
28
|
+
this.screenshotDir = process.env.SCREENSHOT_DIR || join(tmpdir(), "dev3000-mcp-deps", "public", "screenshots");
|
|
29
|
+
this.appPort = appPort || process.env.APP_PORT || "3000";
|
|
30
|
+
if (!existsSync(this.screenshotDir)) {
|
|
31
|
+
mkdirSync(this.screenshotDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Start listening for navigation events and capturing screencasts
|
|
36
|
+
*/
|
|
37
|
+
async start() {
|
|
38
|
+
if (this.ws) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
this.ws = new WebSocket(this.cdpUrl);
|
|
43
|
+
this.ws.on("open", () => {
|
|
44
|
+
// Enable Page domain to receive navigation events
|
|
45
|
+
this.send("Page.enable", {});
|
|
46
|
+
// Enable Runtime domain for URL checking
|
|
47
|
+
this.send("Runtime.enable", {});
|
|
48
|
+
});
|
|
49
|
+
this.ws.on("message", (data) => {
|
|
50
|
+
this.handleMessage(JSON.parse(data.toString()));
|
|
51
|
+
});
|
|
52
|
+
this.ws.on("error", () => {
|
|
53
|
+
// Silently handle errors
|
|
54
|
+
});
|
|
55
|
+
this.ws.on("close", () => {
|
|
56
|
+
this.ws = null;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Silently handle errors
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Stop capturing and cleanup
|
|
65
|
+
*/
|
|
66
|
+
async stop() {
|
|
67
|
+
if (this.isCapturing) {
|
|
68
|
+
await this.stopScreencast();
|
|
69
|
+
}
|
|
70
|
+
if (this.ws) {
|
|
71
|
+
this.ws.close();
|
|
72
|
+
this.ws = null;
|
|
73
|
+
}
|
|
74
|
+
// this.logFn("[CDP] Stopped")
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Handle CDP messages
|
|
78
|
+
*/
|
|
79
|
+
handleMessage(message) {
|
|
80
|
+
// Uncomment for CDP event debugging:
|
|
81
|
+
// if (message.method && (message.method.startsWith("Page.") || message.method.startsWith("Network."))) {
|
|
82
|
+
// this.logFn(`[CDP] Received CDP event: ${message.method}`)
|
|
83
|
+
// }
|
|
84
|
+
// Navigation started - check URL before capturing
|
|
85
|
+
// Note: Page.frameStartedNavigating fires earlier than Page.frameStartedLoading
|
|
86
|
+
if (message.method === "Page.frameStartedNavigating" || message.method === "Page.frameStartedLoading") {
|
|
87
|
+
this.checkUrlAndStartCapture();
|
|
88
|
+
}
|
|
89
|
+
// Navigation finished - save and stop
|
|
90
|
+
else if (message.method === "Page.loadEventFired") {
|
|
91
|
+
this.onNavigationComplete();
|
|
92
|
+
}
|
|
93
|
+
// Screencast frame received
|
|
94
|
+
else if (message.method === "Page.screencastFrame" && message.params) {
|
|
95
|
+
this.onScreencastFrame(message.params);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Check URL before starting capture - only capture localhost:{appPort}
|
|
100
|
+
*/
|
|
101
|
+
async checkUrlAndStartCapture() {
|
|
102
|
+
try {
|
|
103
|
+
// Query current page URL using Runtime.evaluate
|
|
104
|
+
const evalId = this.messageId++;
|
|
105
|
+
this.send("Runtime.evaluate", {
|
|
106
|
+
expression: "window.location.href",
|
|
107
|
+
returnByValue: true
|
|
108
|
+
}, evalId);
|
|
109
|
+
// Wait for response (hacky but works for now)
|
|
110
|
+
const checkResponse = (message) => {
|
|
111
|
+
if (message.id === evalId && message.result?.result?.value) {
|
|
112
|
+
const url = message.result.result.value;
|
|
113
|
+
// this.logFn(`[CDP] Current URL: ${url}`)
|
|
114
|
+
// Only capture if it's the app URL (localhost:appPort)
|
|
115
|
+
if (url.includes(`localhost:${this.appPort}`)) {
|
|
116
|
+
// this.logFn("[CDP] URL matches app, starting capture")
|
|
117
|
+
this.onNavigationStart();
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// this.logFn(`[CDP] Skipping capture - URL does not match localhost:${this.appPort}`)
|
|
121
|
+
}
|
|
122
|
+
// Remove listener after handling
|
|
123
|
+
if (this.ws) {
|
|
124
|
+
this.ws.off("message", responseHandler);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const responseHandler = (data) => {
|
|
129
|
+
checkResponse(JSON.parse(data.toString()));
|
|
130
|
+
};
|
|
131
|
+
if (this.ws) {
|
|
132
|
+
this.ws.on("message", responseHandler);
|
|
133
|
+
// Timeout after 500ms
|
|
134
|
+
setTimeout(() => {
|
|
135
|
+
if (this.ws) {
|
|
136
|
+
this.ws.off("message", responseHandler);
|
|
137
|
+
}
|
|
138
|
+
}, 500);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
this.logFn(`[CDP] Failed to check URL - ${error}`);
|
|
143
|
+
// Fall back to capturing anyway
|
|
144
|
+
this.onNavigationStart();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Navigation started - begin capturing screencast
|
|
149
|
+
*/
|
|
150
|
+
onNavigationStart() {
|
|
151
|
+
// this.logFn("[CDP] Navigation started, beginning screencast capture")
|
|
152
|
+
this.navigationStartTime = Date.now();
|
|
153
|
+
this.currentSessionId = new Date()
|
|
154
|
+
.toISOString()
|
|
155
|
+
.replace(/:/g, "-")
|
|
156
|
+
.replace(/\.\d{3}Z$/, "Z");
|
|
157
|
+
this.buffer = [];
|
|
158
|
+
this.layoutShifts = [];
|
|
159
|
+
this.isCapturing = true;
|
|
160
|
+
// Install CLS observer if not already present
|
|
161
|
+
this.installCLSObserver();
|
|
162
|
+
// Start screencast at 15fps with good quality
|
|
163
|
+
this.send("Page.startScreencast", {
|
|
164
|
+
format: "png",
|
|
165
|
+
quality: 80,
|
|
166
|
+
maxWidth: 1920,
|
|
167
|
+
maxHeight: 1080,
|
|
168
|
+
everyNthFrame: 1
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Navigation completed - save frames and stop (after delay to catch hydration)
|
|
173
|
+
*/
|
|
174
|
+
async onNavigationComplete() {
|
|
175
|
+
if (!this.isCapturing)
|
|
176
|
+
return;
|
|
177
|
+
// this.logFn(`[CDP] Page loaded, capturing 2 more seconds for hydration jank...`)
|
|
178
|
+
// Continue capturing for 2 more seconds to catch hydration issues
|
|
179
|
+
// Hydration often happens right after page load completes
|
|
180
|
+
setTimeout(async () => {
|
|
181
|
+
if (!this.isCapturing)
|
|
182
|
+
return;
|
|
183
|
+
// this.logFn(`[CDP] Navigation complete, saving ${this.buffer.length} frames`)
|
|
184
|
+
// Save all buffered frames
|
|
185
|
+
for (const frame of this.buffer) {
|
|
186
|
+
const screenshotPath = join(this.screenshotDir, `${this.currentSessionId}-jank-${frame.timestamp}ms.png`);
|
|
187
|
+
try {
|
|
188
|
+
const buffer = Buffer.from(frame.data, "base64");
|
|
189
|
+
writeFileSync(screenshotPath, buffer);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.logFn(`[CDP] Failed to save frame - ${error}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// this.logFn(`[CDP] Saved ${this.buffer.length} frames for session ${this.currentSessionId}`)
|
|
196
|
+
// Save session metadata with CLS data
|
|
197
|
+
const metadataPath = join(this.screenshotDir, `${this.currentSessionId}-metadata.json`);
|
|
198
|
+
const totalCLS = this.layoutShifts.reduce((sum, shift) => sum + shift.score, 0);
|
|
199
|
+
const metadata = {
|
|
200
|
+
sessionId: this.currentSessionId,
|
|
201
|
+
frameCount: this.buffer.length,
|
|
202
|
+
navigationStartTime: this.navigationStartTime,
|
|
203
|
+
captureEndTime: Date.now(),
|
|
204
|
+
appPort: this.appPort,
|
|
205
|
+
cssViewport: this.viewportInfo, // CSS viewport dimensions from window.innerWidth
|
|
206
|
+
layoutShifts: this.layoutShifts,
|
|
207
|
+
totalCLS,
|
|
208
|
+
clsGrade: totalCLS <= 0.1 ? "good" : totalCLS <= 0.25 ? "needs-improvement" : "poor"
|
|
209
|
+
};
|
|
210
|
+
try {
|
|
211
|
+
writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
212
|
+
if (totalCLS > 0) {
|
|
213
|
+
this.logFn(`[CDP] Detected ${this.layoutShifts.length} layout shifts (CLS: ${totalCLS.toFixed(4)})`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
this.logFn(`[CDP] Failed to save metadata - ${error}`);
|
|
218
|
+
}
|
|
219
|
+
this.logFn(`[CDP] View jank analysis: http://localhost:${process.env.MCP_PORT || "3684"}/video/${this.currentSessionId}`);
|
|
220
|
+
await this.stopScreencast();
|
|
221
|
+
}, 2000);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Received a screencast frame - add to buffer
|
|
225
|
+
*/
|
|
226
|
+
onScreencastFrame(params) {
|
|
227
|
+
if (!this.isCapturing)
|
|
228
|
+
return;
|
|
229
|
+
const frameTimestamp = Date.now() - this.navigationStartTime;
|
|
230
|
+
const frameData = params.data;
|
|
231
|
+
const sessionId = params.sessionId;
|
|
232
|
+
// Acknowledge frame so we get more
|
|
233
|
+
this.send("Page.screencastFrameAck", { sessionId });
|
|
234
|
+
// Buffer frames (no time limit, onNavigationComplete handles stopping)
|
|
235
|
+
this.buffer.push({
|
|
236
|
+
timestamp: frameTimestamp,
|
|
237
|
+
data: frameData,
|
|
238
|
+
absoluteTime: Date.now()
|
|
239
|
+
});
|
|
240
|
+
// Keep buffer trimmed to prevent memory issues (max 10 seconds of frames)
|
|
241
|
+
const now = Date.now();
|
|
242
|
+
this.buffer = this.buffer.filter((f) => now - f.absoluteTime < 10000);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Stop screencast capture
|
|
246
|
+
*/
|
|
247
|
+
async stopScreencast() {
|
|
248
|
+
if (!this.isCapturing)
|
|
249
|
+
return;
|
|
250
|
+
this.send("Page.stopScreencast", {});
|
|
251
|
+
this.isCapturing = false;
|
|
252
|
+
this.buffer = [];
|
|
253
|
+
// this.logFn("[CDP] Stopped screencast capture")
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Install PerformanceObserver for layout shifts (passive, no reload needed)
|
|
257
|
+
*/
|
|
258
|
+
installCLSObserver() {
|
|
259
|
+
if (this.clsObserverInstalled)
|
|
260
|
+
return;
|
|
261
|
+
const observerScript = `
|
|
262
|
+
(function() {
|
|
263
|
+
if (window.__dev3000_cls_observer__) return; // Already installed
|
|
264
|
+
|
|
265
|
+
window.__dev3000_cls_observer__ = true;
|
|
266
|
+
window.__dev3000_layout_shifts__ = [];
|
|
267
|
+
window.__dev3000_viewport__ = {
|
|
268
|
+
width: window.innerWidth,
|
|
269
|
+
height: window.innerHeight,
|
|
270
|
+
devicePixelRatio: window.devicePixelRatio || 1
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const observer = new PerformanceObserver((list) => {
|
|
275
|
+
for (const entry of list.getEntries()) {
|
|
276
|
+
if (entry.entryType === 'layout-shift' && !entry.hadRecentInput) {
|
|
277
|
+
// For each shift, try to get the actual current bounding box
|
|
278
|
+
const sources = entry.sources ? entry.sources.map(s => {
|
|
279
|
+
let actualRect = null;
|
|
280
|
+
if (s.node && s.node.nodeName) {
|
|
281
|
+
try {
|
|
282
|
+
// Query the first matching element (nav, header, etc.)
|
|
283
|
+
const element = document.querySelector(s.node.nodeName.toLowerCase());
|
|
284
|
+
if (element) {
|
|
285
|
+
const rect = element.getBoundingClientRect();
|
|
286
|
+
actualRect = {
|
|
287
|
+
x: rect.x,
|
|
288
|
+
y: rect.y,
|
|
289
|
+
width: rect.width,
|
|
290
|
+
height: rect.height
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
} catch (e) {
|
|
294
|
+
// Ignore errors
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
node: s.node ? s.node.nodeName : undefined,
|
|
300
|
+
previousRect: s.previousRect ? {
|
|
301
|
+
x: s.previousRect.x,
|
|
302
|
+
y: s.previousRect.y,
|
|
303
|
+
width: s.previousRect.width,
|
|
304
|
+
height: s.previousRect.height
|
|
305
|
+
} : {},
|
|
306
|
+
currentRect: s.currentRect ? {
|
|
307
|
+
x: s.currentRect.x,
|
|
308
|
+
y: s.currentRect.y,
|
|
309
|
+
width: s.currentRect.width,
|
|
310
|
+
height: s.currentRect.height
|
|
311
|
+
} : {},
|
|
312
|
+
actualRect: actualRect
|
|
313
|
+
};
|
|
314
|
+
}) : [];
|
|
315
|
+
|
|
316
|
+
window.__dev3000_layout_shifts__.push({
|
|
317
|
+
score: entry.value,
|
|
318
|
+
timestamp: entry.startTime,
|
|
319
|
+
sources: sources
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
observer.observe({ type: 'layout-shift', buffered: true });
|
|
326
|
+
console.log('[dev3000] CLS observer installed');
|
|
327
|
+
} catch (e) {
|
|
328
|
+
console.error('[dev3000] Failed to install CLS observer:', e);
|
|
329
|
+
}
|
|
330
|
+
})();
|
|
331
|
+
`;
|
|
332
|
+
// Inject observer via Runtime.evaluate
|
|
333
|
+
const evalId = this.messageId++;
|
|
334
|
+
this.send("Runtime.evaluate", { expression: observerScript, returnByValue: false }, evalId);
|
|
335
|
+
// Set up periodic polling to retrieve layout shift data
|
|
336
|
+
this.pollLayoutShifts();
|
|
337
|
+
this.clsObserverInstalled = true;
|
|
338
|
+
// this.logFn("Installed CLS observer")
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Poll for layout shift data from the injected observer
|
|
342
|
+
*/
|
|
343
|
+
pollLayoutShifts() {
|
|
344
|
+
if (!this.isCapturing)
|
|
345
|
+
return;
|
|
346
|
+
const pollId = this.messageId++;
|
|
347
|
+
const viewportId = this.messageId++;
|
|
348
|
+
this.send("Runtime.evaluate", {
|
|
349
|
+
expression: "window.__dev3000_layout_shifts__ || []",
|
|
350
|
+
returnByValue: true
|
|
351
|
+
}, pollId);
|
|
352
|
+
// Also get viewport info
|
|
353
|
+
this.send("Runtime.evaluate", {
|
|
354
|
+
expression: "window.__dev3000_viewport__ || {}",
|
|
355
|
+
returnByValue: true
|
|
356
|
+
}, viewportId);
|
|
357
|
+
// Listen for response
|
|
358
|
+
const handlePollResponse = (message) => {
|
|
359
|
+
if (message.id === pollId && message.result?.result?.value) {
|
|
360
|
+
const shifts = message.result.result.value;
|
|
361
|
+
if (shifts.length > this.layoutShifts.length) {
|
|
362
|
+
// New shifts detected
|
|
363
|
+
const newShifts = shifts.slice(this.layoutShifts.length);
|
|
364
|
+
this.layoutShifts.push(...newShifts);
|
|
365
|
+
newShifts.forEach((shift) => {
|
|
366
|
+
this.logFn(`[CDP] Layout shift detected (score: ${shift.score.toFixed(4)}, time: ${shift.timestamp.toFixed(0)}ms)`);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (message.id === viewportId && message.result?.result?.value) {
|
|
371
|
+
this.viewportInfo = message.result.result.value;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
const responseHandler = (data) => {
|
|
375
|
+
handlePollResponse(JSON.parse(data.toString()));
|
|
376
|
+
};
|
|
377
|
+
if (this.ws) {
|
|
378
|
+
this.ws.on("message", responseHandler);
|
|
379
|
+
// Timeout after 500ms
|
|
380
|
+
setTimeout(() => {
|
|
381
|
+
if (this.ws) {
|
|
382
|
+
this.ws.off("message", responseHandler);
|
|
383
|
+
}
|
|
384
|
+
}, 500);
|
|
385
|
+
}
|
|
386
|
+
// Poll every 500ms while capturing
|
|
387
|
+
if (this.isCapturing) {
|
|
388
|
+
setTimeout(() => this.pollLayoutShifts(), 500);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Send CDP command
|
|
393
|
+
*/
|
|
394
|
+
send(method, params, id) {
|
|
395
|
+
if (!this.ws)
|
|
396
|
+
return;
|
|
397
|
+
this.ws.send(JSON.stringify({
|
|
398
|
+
id: id ?? this.messageId++,
|
|
399
|
+
method,
|
|
400
|
+
params
|
|
401
|
+
}));
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get the most recent session ID (for fix_my_app to reference)
|
|
405
|
+
*/
|
|
406
|
+
getLatestSessionId() {
|
|
407
|
+
return this.currentSessionId;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
//# sourceMappingURL=screencast-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screencast-manager.js","sourceRoot":"","sources":["../src/screencast-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,18 +27,23 @@ const COMPACT_LOGO = "d3k"
|
|
|
27
27
|
const FULL_LOGO = [" ▐▌▄▄▄▄ █ ▄ ", " ▐▌ █ █▄▀ ", "▗▞▀▜▌▀▀▀█ █ ▀▄ ", "▝▚▄▟▌▄▄▄█ █ █ "]
|
|
28
28
|
|
|
29
29
|
const TUIApp = ({
|
|
30
|
-
appPort,
|
|
30
|
+
appPort: initialAppPort,
|
|
31
31
|
mcpPort,
|
|
32
32
|
logFile,
|
|
33
33
|
commandName,
|
|
34
34
|
serversOnly,
|
|
35
35
|
version,
|
|
36
36
|
projectName,
|
|
37
|
-
onStatusUpdate
|
|
38
|
-
|
|
37
|
+
onStatusUpdate,
|
|
38
|
+
onAppPortUpdate
|
|
39
|
+
}: TUIOptions & {
|
|
40
|
+
onStatusUpdate: (fn: (status: string | null) => void) => void
|
|
41
|
+
onAppPortUpdate: (fn: (port: string) => void) => void
|
|
42
|
+
}) => {
|
|
39
43
|
const [logs, setLogs] = useState<LogEntry[]>([])
|
|
40
44
|
const [scrollOffset, setScrollOffset] = useState(0)
|
|
41
45
|
const [initStatus, setInitStatus] = useState<string | null>("Initializing...")
|
|
46
|
+
const [appPort, setAppPort] = useState<string>(initialAppPort)
|
|
42
47
|
const logIdCounter = useRef(0)
|
|
43
48
|
const { stdout } = useStdout()
|
|
44
49
|
const ctrlCMessageDefault = "^C quit"
|
|
@@ -99,6 +104,13 @@ const TUIApp = ({
|
|
|
99
104
|
})
|
|
100
105
|
}, [onStatusUpdate])
|
|
101
106
|
|
|
107
|
+
// Provide app port update function to parent
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
onAppPortUpdate((port: string) => {
|
|
110
|
+
setAppPort(port)
|
|
111
|
+
})
|
|
112
|
+
}, [onAppPortUpdate])
|
|
113
|
+
|
|
102
114
|
// Calculate available lines for logs dynamically based on terminal height and mode
|
|
103
115
|
const calculateMaxVisibleLogs = () => {
|
|
104
116
|
if (isVeryCompact) {
|
|
@@ -248,12 +260,15 @@ const TUIApp = ({
|
|
|
248
260
|
{initStatus && <Text dimColor>- {initStatus}</Text>}
|
|
249
261
|
</Box>
|
|
250
262
|
{!isVeryCompact && (
|
|
251
|
-
|
|
263
|
+
<Box flexDirection="column">
|
|
252
264
|
<Text dimColor>
|
|
253
265
|
App: localhost:{appPort} | MCP: localhost:{mcpPort}
|
|
254
266
|
</Text>
|
|
255
|
-
<Text dimColor
|
|
256
|
-
|
|
267
|
+
<Text dimColor>
|
|
268
|
+
📸 http://localhost:{mcpPort}/logs
|
|
269
|
+
{projectName ? `?project=${encodeURIComponent(projectName)}` : ""}
|
|
270
|
+
</Text>
|
|
271
|
+
</Box>
|
|
257
272
|
)}
|
|
258
273
|
</Box>
|
|
259
274
|
</Box>
|
|
@@ -332,9 +347,9 @@ const TUIApp = ({
|
|
|
332
347
|
if (parts) {
|
|
333
348
|
let [, timestamp, source, type, message] = parts
|
|
334
349
|
|
|
335
|
-
// Replace
|
|
336
|
-
if (message
|
|
337
|
-
message = message.replace(
|
|
350
|
+
// Replace warning emoji in ERROR/WARNING messages for consistent terminal rendering
|
|
351
|
+
if (message && (type === "ERROR" || type === "WARNING")) {
|
|
352
|
+
message = message.replace(/⚠/g, "[!]")
|
|
338
353
|
}
|
|
339
354
|
|
|
340
355
|
// In very compact mode, simplify the output
|
|
@@ -438,19 +453,27 @@ const TUIApp = ({
|
|
|
438
453
|
|
|
439
454
|
{/* Bottom status line - no border, just text */}
|
|
440
455
|
<Box paddingX={1} justifyContent="space-between">
|
|
441
|
-
<Text color="#A18CE5"
|
|
456
|
+
<Text color="#A18CE5">
|
|
457
|
+
⏵⏵{" "}
|
|
458
|
+
{isVeryCompact
|
|
459
|
+
? logFile.split("/").slice(-2, -1)[0] || "logs" // Just show directory name
|
|
460
|
+
: logFile.replace(process.env.HOME || "", "~")}
|
|
461
|
+
</Text>
|
|
442
462
|
<Text color="#A18CE5">{ctrlCMessage}</Text>
|
|
443
463
|
</Box>
|
|
444
464
|
</Box>
|
|
445
465
|
)
|
|
446
466
|
}
|
|
447
467
|
|
|
448
|
-
export async function runTUI(
|
|
449
|
-
|
|
450
|
-
|
|
468
|
+
export async function runTUI(options: TUIOptions): Promise<{
|
|
469
|
+
app: { unmount: () => void }
|
|
470
|
+
updateStatus: (status: string | null) => void
|
|
471
|
+
updateAppPort: (port: string) => void
|
|
472
|
+
}> {
|
|
451
473
|
return new Promise((resolve, reject) => {
|
|
452
474
|
try {
|
|
453
475
|
let statusUpdater: ((status: string | null) => void) | null = null
|
|
476
|
+
let appPortUpdater: ((port: string) => void) | null = null
|
|
454
477
|
|
|
455
478
|
const app = render(
|
|
456
479
|
<TUIApp
|
|
@@ -458,11 +481,14 @@ export async function runTUI(
|
|
|
458
481
|
onStatusUpdate={(fn) => {
|
|
459
482
|
statusUpdater = fn
|
|
460
483
|
}}
|
|
484
|
+
onAppPortUpdate={(fn) => {
|
|
485
|
+
appPortUpdater = fn
|
|
486
|
+
}}
|
|
461
487
|
/>,
|
|
462
488
|
{ exitOnCtrlC: false }
|
|
463
489
|
)
|
|
464
490
|
|
|
465
|
-
// Give React time to set up the
|
|
491
|
+
// Give React time to set up the updaters
|
|
466
492
|
setTimeout(() => {
|
|
467
493
|
resolve({
|
|
468
494
|
app,
|
|
@@ -470,6 +496,11 @@ export async function runTUI(
|
|
|
470
496
|
if (statusUpdater) {
|
|
471
497
|
statusUpdater(status)
|
|
472
498
|
}
|
|
499
|
+
},
|
|
500
|
+
updateAppPort: (port: string) => {
|
|
501
|
+
if (appPortUpdater) {
|
|
502
|
+
appPortUpdater(port)
|
|
503
|
+
}
|
|
473
504
|
}
|
|
474
505
|
})
|
|
475
506
|
}, 100)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui-interface-impl.d.ts","sourceRoot":"","sources":["../src/tui-interface-impl.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;
|
|
1
|
+
{"version":3,"file":"tui-interface-impl.d.ts","sourceRoot":"","sources":["../src/tui-interface-impl.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAocD,wBAAsB,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC;IACzD,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;IAC5B,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC7C,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACtC,CAAC,CAwCD"}
|
|
@@ -8,10 +8,11 @@ import { LOG_COLORS } from "./constants/log-colors.js";
|
|
|
8
8
|
const COMPACT_LOGO = "d3k";
|
|
9
9
|
// Full ASCII logo lines as array for easier rendering
|
|
10
10
|
const FULL_LOGO = [" ▐▌▄▄▄▄ █ ▄ ", " ▐▌ █ █▄▀ ", "▗▞▀▜▌▀▀▀█ █ ▀▄ ", "▝▚▄▟▌▄▄▄█ █ █ "];
|
|
11
|
-
const TUIApp = ({ appPort, mcpPort, logFile, commandName, serversOnly, version, projectName, onStatusUpdate }) => {
|
|
11
|
+
const TUIApp = ({ appPort: initialAppPort, mcpPort, logFile, commandName, serversOnly, version, projectName, onStatusUpdate, onAppPortUpdate }) => {
|
|
12
12
|
const [logs, setLogs] = useState([]);
|
|
13
13
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
14
14
|
const [initStatus, setInitStatus] = useState("Initializing...");
|
|
15
|
+
const [appPort, setAppPort] = useState(initialAppPort);
|
|
15
16
|
const logIdCounter = useRef(0);
|
|
16
17
|
const { stdout } = useStdout();
|
|
17
18
|
const ctrlCMessageDefault = "^C quit";
|
|
@@ -65,6 +66,12 @@ const TUIApp = ({ appPort, mcpPort, logFile, commandName, serversOnly, version,
|
|
|
65
66
|
}
|
|
66
67
|
});
|
|
67
68
|
}, [onStatusUpdate]);
|
|
69
|
+
// Provide app port update function to parent
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
onAppPortUpdate((port) => {
|
|
72
|
+
setAppPort(port);
|
|
73
|
+
});
|
|
74
|
+
}, [onAppPortUpdate]);
|
|
68
75
|
// Calculate available lines for logs dynamically based on terminal height and mode
|
|
69
76
|
const calculateMaxVisibleLogs = () => {
|
|
70
77
|
if (isVeryCompact) {
|
|
@@ -193,7 +200,7 @@ const TUIApp = ({ appPort, mcpPort, logFile, commandName, serversOnly, version,
|
|
|
193
200
|
// Calculate visible logs
|
|
194
201
|
const visibleLogs = logs.slice(Math.max(0, logs.length - maxVisibleLogs - scrollOffset), logs.length - scrollOffset);
|
|
195
202
|
// Render compact header for small terminals
|
|
196
|
-
const renderCompactHeader = () => (_jsx(Box, { borderStyle: "single", borderColor: "#A18CE5", paddingX: 1, marginBottom: 1, children: _jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#A18CE5", bold: true, children: COMPACT_LOGO }), _jsxs(Text, { children: [" v", version, " "] }), initStatus && _jsxs(Text, { dimColor: true, children: ["- ", initStatus] })] }), !isVeryCompact && (_jsxs(
|
|
203
|
+
const renderCompactHeader = () => (_jsx(Box, { borderStyle: "single", borderColor: "#A18CE5", paddingX: 1, marginBottom: 1, children: _jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#A18CE5", bold: true, children: COMPACT_LOGO }), _jsxs(Text, { children: [" v", version, " "] }), initStatus && _jsxs(Text, { dimColor: true, children: ["- ", initStatus] })] }), !isVeryCompact && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["App: localhost:", appPort, " | MCP: localhost:", mcpPort] }), _jsxs(Text, { dimColor: true, children: ["\uD83D\uDCF8 http://localhost:", mcpPort, "/logs", projectName ? `?project=${encodeURIComponent(projectName)}` : ""] })] }))] }) }));
|
|
197
204
|
// Render normal header
|
|
198
205
|
const renderNormalHeader = () => {
|
|
199
206
|
// Create custom top border with title embedded (like Claude Code)
|
|
@@ -212,9 +219,9 @@ const TUIApp = ({ appPort, mcpPort, logFile, commandName, serversOnly, version,
|
|
|
212
219
|
const parts = log.content.match(/^\[(.*?)\] \[(.*?)\] (?:\[(.*?)\] )?(.*)$/);
|
|
213
220
|
if (parts) {
|
|
214
221
|
let [, timestamp, source, type, message] = parts;
|
|
215
|
-
// Replace
|
|
216
|
-
if (message
|
|
217
|
-
message = message.replace(
|
|
222
|
+
// Replace warning emoji in ERROR/WARNING messages for consistent terminal rendering
|
|
223
|
+
if (message && (type === "ERROR" || type === "WARNING")) {
|
|
224
|
+
message = message.replace(/⚠/g, "[!]");
|
|
218
225
|
}
|
|
219
226
|
// In very compact mode, simplify the output
|
|
220
227
|
if (isVeryCompact) {
|
|
@@ -258,16 +265,21 @@ const TUIApp = ({ appPort, mcpPort, logFile, commandName, serversOnly, version,
|
|
|
258
265
|
}
|
|
259
266
|
// Fallback for unparsed lines
|
|
260
267
|
return (_jsx(Text, { wrap: "truncate-end", children: log.content }, log.id));
|
|
261
|
-
})) }), !isVeryCompact && logs.length > maxVisibleLogs && scrollOffset > 0 && (_jsxs(Text, { dimColor: true, children: ["(", scrollOffset, " lines below)"] }))] }), _jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: "#A18CE5", children: ["\u23F5\u23F5
|
|
268
|
+
})) }), !isVeryCompact && logs.length > maxVisibleLogs && scrollOffset > 0 && (_jsxs(Text, { dimColor: true, children: ["(", scrollOffset, " lines below)"] }))] }), _jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: "#A18CE5", children: ["\u23F5\u23F5", " ", isVeryCompact
|
|
269
|
+
? logFile.split("/").slice(-2, -1)[0] || "logs" // Just show directory name
|
|
270
|
+
: logFile.replace(process.env.HOME || "", "~")] }), _jsx(Text, { color: "#A18CE5", children: ctrlCMessage })] })] }));
|
|
262
271
|
};
|
|
263
272
|
export async function runTUI(options) {
|
|
264
273
|
return new Promise((resolve, reject) => {
|
|
265
274
|
try {
|
|
266
275
|
let statusUpdater = null;
|
|
276
|
+
let appPortUpdater = null;
|
|
267
277
|
const app = render(_jsx(TUIApp, { ...options, onStatusUpdate: (fn) => {
|
|
268
278
|
statusUpdater = fn;
|
|
279
|
+
}, onAppPortUpdate: (fn) => {
|
|
280
|
+
appPortUpdater = fn;
|
|
269
281
|
} }), { exitOnCtrlC: false });
|
|
270
|
-
// Give React time to set up the
|
|
282
|
+
// Give React time to set up the updaters
|
|
271
283
|
setTimeout(() => {
|
|
272
284
|
resolve({
|
|
273
285
|
app,
|
|
@@ -275,6 +287,11 @@ export async function runTUI(options) {
|
|
|
275
287
|
if (statusUpdater) {
|
|
276
288
|
statusUpdater(status);
|
|
277
289
|
}
|
|
290
|
+
},
|
|
291
|
+
updateAppPort: (port) => {
|
|
292
|
+
if (appPortUpdater) {
|
|
293
|
+
appPortUpdater(port);
|
|
294
|
+
}
|
|
278
295
|
}
|
|
279
296
|
});
|
|
280
297
|
}, 100);
|