dev3000 0.0.114 → 0.0.116
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/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +20 -28
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/cli.js +74 -22
- package/dist/cli.js.map +1 -1
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +43 -11
- package/dist/dev-environment.js.map +1 -1
- package/dist/src/tui-interface-impl.tsx +175 -127
- package/dist/tui-interface-impl.d.ts.map +1 -1
- package/dist/tui-interface-impl.js +113 -74
- package/dist/tui-interface-impl.js.map +1 -1
- package/mcp-server/.next/BUILD_ID +1 -1
- package/mcp-server/.next/build-manifest.json +2 -2
- package/mcp-server/.next/fallback-build-manifest.json +2 -2
- package/mcp-server/.next/prerender-manifest.json +3 -3
- 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/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_global-error.segments/_tree.segment.rsc +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/_not-found.segments/_full.segment.rsc +2 -2
- package/mcp-server/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/mcp-server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/mcp-server/.next/server/app/api/cloud/start-fix/route.js +2 -2
- package/mcp-server/.next/server/app/api/cloud/start-fix/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/auth/error/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/auth/error.html +1 -1
- package/mcp-server/.next/server/app/auth/error.rsc +2 -2
- package/mcp-server/.next/server/app/auth/error.segments/_full.segment.rsc +2 -2
- package/mcp-server/.next/server/app/auth/error.segments/_head.segment.rsc +1 -1
- package/mcp-server/.next/server/app/auth/error.segments/_index.segment.rsc +2 -2
- package/mcp-server/.next/server/app/auth/error.segments/_tree.segment.rsc +2 -2
- package/mcp-server/.next/server/app/auth/error.segments/auth/error/__PAGE__.segment.rsc +1 -1
- package/mcp-server/.next/server/app/auth/error.segments/auth/error.segment.rsc +1 -1
- package/mcp-server/.next/server/app/auth/error.segments/auth.segment.rsc +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/index.segments/__PAGE__.segment.rsc +2 -2
- package/mcp-server/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/mcp-server/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/mcp-server/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/mcp-server/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- 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 +2 -2
- 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/signin/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/video/[session]/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/workflows/[id]/report/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/workflows/new/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/workflows/new/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/workflows/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/workflows/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__157de66b._.js +284 -37
- package/mcp-server/.next/server/chunks/[root-of-the-server]__157de66b._.js.map +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__730a8fd0._.js +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__730a8fd0._.js.map +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__748f411f._.js +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__748f411f._.js.map +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__8a84f9f4._.js +20 -20
- package/mcp-server/.next/server/chunks/[root-of-the-server]__8a84f9f4._.js.map +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__c1681338._.js +3 -0
- package/mcp-server/.next/server/chunks/[root-of-the-server]__c1681338._.js.map +1 -0
- package/mcp-server/.next/server/chunks/[root-of-the-server]__ec6a1335._.js.map +1 -1
- package/mcp-server/.next/server/chunks/bee4f_next_dist_esm_build_templates_app-route_1ece9366.js +5 -5
- package/mcp-server/.next/server/chunks/bee4f_next_dist_esm_build_templates_app-route_1ece9366.js.map +1 -1
- package/mcp-server/.next/server/chunks/mcp-server_app_api_cloud_fix-workflow_steps_ts_b65f3271._.js +121 -27
- package/mcp-server/.next/server/chunks/mcp-server_app_api_cloud_fix-workflow_steps_ts_b65f3271._.js.map +1 -1
- package/mcp-server/.next/server/chunks/node_modules__pnpm_85ddbe9c._.js +1 -1
- package/mcp-server/.next/server/chunks/node_modules__pnpm_85ddbe9c._.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__2e44f0db._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__2e44f0db._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__3585c949._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__3585c949._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__477c3bbb._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__477c3bbb._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/{[root-of-the-server]__570677dc._.js → [root-of-the-server]__880839a0._.js} +2 -2
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__880839a0._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/_41b8f993._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/_41b8f993._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/_9ba0ef29._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/_9ba0ef29._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/_cd4dc25e._.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/mcp-server_app_workflows_new_new-workflow-client_tsx_1312c046._.js +2 -2
- package/mcp-server/.next/server/chunks/ssr/mcp-server_app_workflows_new_new-workflow-client_tsx_1312c046._.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/mcp-server_app_workflows_workflows-client_tsx_268cfd4a._.js +7 -0
- package/mcp-server/.next/server/chunks/ssr/mcp-server_app_workflows_workflows-client_tsx_268cfd4a._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_961f21c4._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_961f21c4._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_a82244bf._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_a82244bf._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/{node_modules__pnpm_07527699._.js → node_modules__pnpm_eb98e511._.js} +2 -2
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_eb98e511._.js.map +1 -0
- 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/000849a6a897f531.css +1 -0
- package/mcp-server/.next/static/chunks/048cee2510ddb1a0.js +1 -0
- package/mcp-server/.next/static/chunks/0622bd0e093adee7.js +3 -0
- package/mcp-server/.next/static/chunks/{46f60efee5f19794.js → 16359f64918a93f3.js} +1 -1
- package/mcp-server/.next/static/chunks/1851a3e70d7efc10.js +1 -0
- package/mcp-server/.next/static/chunks/{cc6addc4bb10fa11.js → 2ad16eeb719786f1.js} +1 -1
- package/mcp-server/.next/static/chunks/57feca7a4e06545e.js +7 -0
- package/mcp-server/.next/static/chunks/93db5737a327ab0c.js +6 -0
- package/mcp-server/.next/static/chunks/9fd3c715ecfb4d05.js +1 -0
- package/mcp-server/.next/static/chunks/b4b1ec6435790587.js +1 -0
- package/mcp-server/.next/static/chunks/cfe150cb2048b7e8.js +1 -0
- package/mcp-server/app/api/cloud/fix-workflow/steps.ts +359 -28
- package/mcp-server/app/api/cloud/fix-workflow/workflow.ts +16 -8
- package/mcp-server/app/api/cloud/start-fix/route.ts +2 -2
- package/mcp-server/app/api/tools/route.ts +11 -12
- package/mcp-server/app/api/workflows/route.ts +45 -1
- package/mcp-server/app/mcp/tools.ts +58 -98
- package/mcp-server/app/workflows/workflows-client.tsx +259 -100
- package/package.json +1 -1
- package/src/tui-interface-impl.tsx +175 -127
- package/mcp-server/.next/server/chunks/[root-of-the-server]__75d68567._.js +0 -3
- package/mcp-server/.next/server/chunks/[root-of-the-server]__75d68567._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__0ff05d72._.js +0 -3
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__0ff05d72._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__27cc5956._.js +0 -3
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__27cc5956._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__570677dc._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__ef510343._.js +0 -3
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__ef510343._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_07527699._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_7cc36047._.js +0 -3
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_7cc36047._.js.map +0 -1
- package/mcp-server/.next/static/chunks/07848f6bd2a7e5f6.js +0 -3
- package/mcp-server/.next/static/chunks/637a66565f27572f.js +0 -6
- package/mcp-server/.next/static/chunks/aed4fb5252a4bc95.js +0 -3
- package/mcp-server/.next/static/chunks/e8d521464b0c96ca.css +0 -1
- package/mcp-server/.next/static/chunks/ff53279afa939907.js +0 -1
- package/mcp-server/.next/static/chunks/ffa2ecb6845be49c.js +0 -1
- /package/mcp-server/.next/static/{ZvLsxTWoDyQzaSsr_VeX6 → G5taiQ-Jp0B_MdvkQuoIT}/_buildManifest.js +0 -0
- /package/mcp-server/.next/static/{ZvLsxTWoDyQzaSsr_VeX6 → G5taiQ-Jp0B_MdvkQuoIT}/_clientMiddlewareManifest.json +0 -0
- /package/mcp-server/.next/static/{ZvLsxTWoDyQzaSsr_VeX6 → G5taiQ-Jp0B_MdvkQuoIT}/_ssgManifest.js +0 -0
|
@@ -2,7 +2,7 @@ import chalk from "chalk"
|
|
|
2
2
|
import { createReadStream, unwatchFile, watchFile } from "fs"
|
|
3
3
|
import { Box, render, Text, useInput, useStdout } from "ink"
|
|
4
4
|
import Spinner from "ink-spinner"
|
|
5
|
-
import { useEffect, useRef, useState } from "react"
|
|
5
|
+
import { memo, useEffect, useRef, useState } from "react"
|
|
6
6
|
import type { Readable } from "stream"
|
|
7
7
|
import { LOG_COLORS } from "./constants/log-colors.js"
|
|
8
8
|
|
|
@@ -29,6 +29,117 @@ const COMPACT_LOGO = "d3k"
|
|
|
29
29
|
// Full ASCII logo lines as array for easier rendering
|
|
30
30
|
const FULL_LOGO = [" ▐▌▄▄▄▄ █ ▄ ", " ▐▌ █ █▄▀ ", "▗▞▀▜▌▀▀▀█ █ ▀▄ ", "▝▚▄▟▌▄▄▄█ █ █ "]
|
|
31
31
|
|
|
32
|
+
// Type colors map - defined outside component to avoid recreation
|
|
33
|
+
const TYPE_COLORS: Record<string, string> = {
|
|
34
|
+
NETWORK: LOG_COLORS.NETWORK,
|
|
35
|
+
ERROR: LOG_COLORS.ERROR,
|
|
36
|
+
WARNING: LOG_COLORS.WARNING,
|
|
37
|
+
INFO: LOG_COLORS.INFO,
|
|
38
|
+
LOG: LOG_COLORS.LOG,
|
|
39
|
+
DEBUG: LOG_COLORS.DEBUG,
|
|
40
|
+
SCREENSHOT: LOG_COLORS.SCREENSHOT,
|
|
41
|
+
DOM: LOG_COLORS.DOM,
|
|
42
|
+
CDP: LOG_COLORS.CDP,
|
|
43
|
+
CHROME: LOG_COLORS.CHROME,
|
|
44
|
+
CRASH: LOG_COLORS.CRASH,
|
|
45
|
+
REPLAY: LOG_COLORS.REPLAY,
|
|
46
|
+
NAVIGATION: LOG_COLORS.NAVIGATION,
|
|
47
|
+
INTERACTION: LOG_COLORS.INTERACTION,
|
|
48
|
+
GET: LOG_COLORS.SERVER,
|
|
49
|
+
POST: LOG_COLORS.SERVER,
|
|
50
|
+
PUT: LOG_COLORS.SERVER,
|
|
51
|
+
DELETE: LOG_COLORS.SERVER,
|
|
52
|
+
PATCH: LOG_COLORS.SERVER,
|
|
53
|
+
HEAD: LOG_COLORS.SERVER,
|
|
54
|
+
OPTIONS: LOG_COLORS.SERVER
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Memoized log line component to prevent re-parsing on every render
|
|
58
|
+
const LogLine = memo(
|
|
59
|
+
({ log, isCompact, isVeryCompact }: { log: LogEntry; isCompact: boolean; isVeryCompact: boolean }) => {
|
|
60
|
+
// Parse log line to colorize different parts
|
|
61
|
+
const parts = log.content.match(/^\[(.*?)\] \[(.*?)\] (?:\[(.*?)\] )?(.*)$/)
|
|
62
|
+
|
|
63
|
+
if (parts) {
|
|
64
|
+
let [, timestamp, source, type, message] = parts
|
|
65
|
+
|
|
66
|
+
// Extract HTTP method from SERVER logs as a secondary tag
|
|
67
|
+
if (source === "SERVER" && !type && message) {
|
|
68
|
+
const methodMatch = message.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s/)
|
|
69
|
+
if (methodMatch) {
|
|
70
|
+
type = methodMatch[1]
|
|
71
|
+
message = message.slice(type.length + 1) // Remove method from message
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Replace warning emoji in ERROR/WARNING messages for consistent terminal rendering
|
|
76
|
+
if (message && (type === "ERROR" || type === "WARNING")) {
|
|
77
|
+
message = message.replace(/⚠/g, "[!]")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// In very compact mode, simplify the output
|
|
81
|
+
if (isVeryCompact) {
|
|
82
|
+
const shortSource = source === "BROWSER" ? "B" : "S"
|
|
83
|
+
const shortType = type ? type.split(".")[0].charAt(0) : ""
|
|
84
|
+
return (
|
|
85
|
+
<Text wrap="truncate-end">
|
|
86
|
+
<Text dimColor>[{shortSource}]</Text>
|
|
87
|
+
{shortType && <Text dimColor>[{shortType}]</Text>}
|
|
88
|
+
<Text> {message}</Text>
|
|
89
|
+
</Text>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Use shared color constants
|
|
94
|
+
const sourceColor = source === "BROWSER" ? LOG_COLORS.BROWSER : LOG_COLORS.SERVER
|
|
95
|
+
|
|
96
|
+
// In compact mode, skip padding
|
|
97
|
+
if (isCompact) {
|
|
98
|
+
return (
|
|
99
|
+
<Text wrap="truncate-end">
|
|
100
|
+
<Text dimColor>[{timestamp}]</Text>
|
|
101
|
+
<Text> </Text>
|
|
102
|
+
<Text color={sourceColor} bold>
|
|
103
|
+
[{source.charAt(0)}]
|
|
104
|
+
</Text>
|
|
105
|
+
{type && (
|
|
106
|
+
<>
|
|
107
|
+
<Text> </Text>
|
|
108
|
+
<Text color={TYPE_COLORS[type] || "#A0A0A0"}>[{type}]</Text>
|
|
109
|
+
</>
|
|
110
|
+
)}
|
|
111
|
+
<Text> {message}</Text>
|
|
112
|
+
</Text>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Normal mode with minimal padding
|
|
117
|
+
return (
|
|
118
|
+
<Text wrap="truncate-end">
|
|
119
|
+
<Text dimColor>[{timestamp}]</Text>
|
|
120
|
+
<Text> </Text>
|
|
121
|
+
<Text color={sourceColor} bold>
|
|
122
|
+
[{source}]
|
|
123
|
+
</Text>
|
|
124
|
+
{type ? (
|
|
125
|
+
<>
|
|
126
|
+
<Text> </Text>
|
|
127
|
+
<Text color={TYPE_COLORS[type] || "#A0A0A0"}>[{type}]</Text>
|
|
128
|
+
<Text> </Text>
|
|
129
|
+
</>
|
|
130
|
+
) : (
|
|
131
|
+
<Text> </Text>
|
|
132
|
+
)}
|
|
133
|
+
<Text>{message}</Text>
|
|
134
|
+
</Text>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Fallback for unparsed lines
|
|
139
|
+
return <Text wrap="truncate-end">{log.content}</Text>
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
|
|
32
143
|
const TUIApp = ({
|
|
33
144
|
appPort: initialAppPort,
|
|
34
145
|
mcpPort,
|
|
@@ -155,19 +266,19 @@ const TUIApp = ({
|
|
|
155
266
|
useEffect(() => {
|
|
156
267
|
let logStream: Readable | undefined
|
|
157
268
|
let buffer = ""
|
|
269
|
+
let pendingLogs: LogEntry[] = []
|
|
270
|
+
let flushTimeout: NodeJS.Timeout | null = null
|
|
158
271
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
272
|
+
// Batch log updates to prevent excessive renders
|
|
273
|
+
const flushPendingLogs = () => {
|
|
274
|
+
if (pendingLogs.length === 0) return
|
|
163
275
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
276
|
+
const logsToAdd = pendingLogs
|
|
277
|
+
pendingLogs = []
|
|
278
|
+
flushTimeout = null
|
|
168
279
|
|
|
169
280
|
setLogs((prevLogs) => {
|
|
170
|
-
const updated = [...prevLogs,
|
|
281
|
+
const updated = [...prevLogs, ...logsToAdd]
|
|
171
282
|
// Keep only last N logs to prevent memory issues
|
|
172
283
|
if (updated.length > maxLogs) {
|
|
173
284
|
return updated.slice(-maxLogs)
|
|
@@ -176,12 +287,32 @@ const TUIApp = ({
|
|
|
176
287
|
})
|
|
177
288
|
|
|
178
289
|
// Auto-scroll to bottom only if user is already at the bottom
|
|
179
|
-
// Otherwise, increment scroll offset by
|
|
290
|
+
// Otherwise, increment scroll offset by count of new logs
|
|
180
291
|
setScrollOffset((currentOffset) => {
|
|
181
|
-
return currentOffset === 0 ? 0 : Math.min(maxScrollOffsetRef.current, currentOffset +
|
|
292
|
+
return currentOffset === 0 ? 0 : Math.min(maxScrollOffsetRef.current, currentOffset + logsToAdd.length)
|
|
182
293
|
})
|
|
183
294
|
}
|
|
184
295
|
|
|
296
|
+
const appendLog = (line: string) => {
|
|
297
|
+
if (NEXTJS_MCP_404_REGEX.test(line)) {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const newLog: LogEntry = {
|
|
302
|
+
id: logIdCounter.current++,
|
|
303
|
+
content: line
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
pendingLogs.push(newLog)
|
|
307
|
+
|
|
308
|
+
// Debounce: flush after 50ms of no new logs
|
|
309
|
+
// Terminal synchronized updates prevent flicker, so we can be more responsive
|
|
310
|
+
if (flushTimeout) {
|
|
311
|
+
clearTimeout(flushTimeout)
|
|
312
|
+
}
|
|
313
|
+
flushTimeout = setTimeout(flushPendingLogs, 50)
|
|
314
|
+
}
|
|
315
|
+
|
|
185
316
|
// Create a read stream for the log file
|
|
186
317
|
logStream = createReadStream(logFile, {
|
|
187
318
|
encoding: "utf8",
|
|
@@ -236,6 +367,9 @@ const TUIApp = ({
|
|
|
236
367
|
if (logStream) {
|
|
237
368
|
logStream.destroy()
|
|
238
369
|
}
|
|
370
|
+
if (flushTimeout) {
|
|
371
|
+
clearTimeout(flushTimeout)
|
|
372
|
+
}
|
|
239
373
|
unwatchFile(logFile)
|
|
240
374
|
}
|
|
241
375
|
}, [logFile])
|
|
@@ -376,121 +510,9 @@ const TUIApp = ({
|
|
|
376
510
|
{visibleLogs.length === 0 ? (
|
|
377
511
|
<Text dimColor>Waiting for logs...</Text>
|
|
378
512
|
) : (
|
|
379
|
-
visibleLogs.map((log) =>
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if (parts) {
|
|
384
|
-
let [, timestamp, source, type, message] = parts
|
|
385
|
-
|
|
386
|
-
// Extract HTTP method from SERVER logs as a secondary tag
|
|
387
|
-
if (source === "SERVER" && !type && message) {
|
|
388
|
-
const methodMatch = message.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s/)
|
|
389
|
-
if (methodMatch) {
|
|
390
|
-
type = methodMatch[1]
|
|
391
|
-
message = message.slice(type.length + 1) // Remove method from message
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Replace warning emoji in ERROR/WARNING messages for consistent terminal rendering
|
|
396
|
-
if (message && (type === "ERROR" || type === "WARNING")) {
|
|
397
|
-
message = message.replace(/⚠/g, "[!]")
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// In very compact mode, simplify the output
|
|
401
|
-
if (isVeryCompact) {
|
|
402
|
-
const shortSource = source === "BROWSER" ? "B" : "S"
|
|
403
|
-
const shortType = type ? type.split(".")[0].charAt(0) : ""
|
|
404
|
-
return (
|
|
405
|
-
<Text key={log.id} wrap="truncate-end">
|
|
406
|
-
<Text dimColor>[{shortSource}]</Text>
|
|
407
|
-
{shortType && <Text dimColor>[{shortType}]</Text>}
|
|
408
|
-
<Text> {message}</Text>
|
|
409
|
-
</Text>
|
|
410
|
-
)
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Use shared color constants
|
|
414
|
-
const sourceColor = source === "BROWSER" ? LOG_COLORS.BROWSER : LOG_COLORS.SERVER
|
|
415
|
-
const typeColors: Record<string, string> = {
|
|
416
|
-
NETWORK: LOG_COLORS.NETWORK,
|
|
417
|
-
ERROR: LOG_COLORS.ERROR,
|
|
418
|
-
WARNING: LOG_COLORS.WARNING,
|
|
419
|
-
INFO: LOG_COLORS.INFO,
|
|
420
|
-
LOG: LOG_COLORS.LOG,
|
|
421
|
-
DEBUG: LOG_COLORS.DEBUG,
|
|
422
|
-
SCREENSHOT: LOG_COLORS.SCREENSHOT,
|
|
423
|
-
DOM: LOG_COLORS.DOM,
|
|
424
|
-
CDP: LOG_COLORS.CDP,
|
|
425
|
-
CHROME: LOG_COLORS.CHROME,
|
|
426
|
-
CRASH: LOG_COLORS.CRASH,
|
|
427
|
-
REPLAY: LOG_COLORS.REPLAY,
|
|
428
|
-
NAVIGATION: LOG_COLORS.NAVIGATION,
|
|
429
|
-
INTERACTION: LOG_COLORS.INTERACTION,
|
|
430
|
-
GET: LOG_COLORS.SERVER,
|
|
431
|
-
POST: LOG_COLORS.SERVER,
|
|
432
|
-
PUT: LOG_COLORS.SERVER,
|
|
433
|
-
DELETE: LOG_COLORS.SERVER,
|
|
434
|
-
PATCH: LOG_COLORS.SERVER,
|
|
435
|
-
HEAD: LOG_COLORS.SERVER,
|
|
436
|
-
OPTIONS: LOG_COLORS.SERVER
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// In compact mode, skip padding
|
|
440
|
-
if (isCompact) {
|
|
441
|
-
return (
|
|
442
|
-
<Text key={log.id} wrap="truncate-end">
|
|
443
|
-
<Text dimColor>[{timestamp}]</Text>
|
|
444
|
-
<Text> </Text>
|
|
445
|
-
<Text color={sourceColor} bold>
|
|
446
|
-
[{source.charAt(0)}]
|
|
447
|
-
</Text>
|
|
448
|
-
{type && (
|
|
449
|
-
<>
|
|
450
|
-
<Text> </Text>
|
|
451
|
-
<Text color={typeColors[type] || "#A0A0A0"}>[{type}]</Text>
|
|
452
|
-
</>
|
|
453
|
-
)}
|
|
454
|
-
<Text> {message}</Text>
|
|
455
|
-
</Text>
|
|
456
|
-
)
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Normal mode with minimal padding
|
|
460
|
-
// Single space after source
|
|
461
|
-
const sourceSpacing = ""
|
|
462
|
-
|
|
463
|
-
// Single space after type
|
|
464
|
-
const typeSpacing = ""
|
|
465
|
-
|
|
466
|
-
return (
|
|
467
|
-
<Text key={log.id} wrap="truncate-end">
|
|
468
|
-
<Text dimColor>[{timestamp}]</Text>
|
|
469
|
-
<Text> </Text>
|
|
470
|
-
<Text color={sourceColor} bold>
|
|
471
|
-
[{source}]
|
|
472
|
-
</Text>
|
|
473
|
-
{type ? (
|
|
474
|
-
<>
|
|
475
|
-
<Text>{sourceSpacing} </Text>
|
|
476
|
-
<Text color={typeColors[type] || "#A0A0A0"}>[{type}]</Text>
|
|
477
|
-
<Text>{typeSpacing} </Text>
|
|
478
|
-
</>
|
|
479
|
-
) : (
|
|
480
|
-
<Text> </Text>
|
|
481
|
-
)}
|
|
482
|
-
<Text>{message}</Text>
|
|
483
|
-
</Text>
|
|
484
|
-
)
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Fallback for unparsed lines
|
|
488
|
-
return (
|
|
489
|
-
<Text key={log.id} wrap="truncate-end">
|
|
490
|
-
{log.content}
|
|
491
|
-
</Text>
|
|
492
|
-
)
|
|
493
|
-
})
|
|
513
|
+
visibleLogs.map((log) => (
|
|
514
|
+
<LogLine key={log.id} log={log} isCompact={isCompact} isVeryCompact={isVeryCompact} />
|
|
515
|
+
))
|
|
494
516
|
)}
|
|
495
517
|
</Box>
|
|
496
518
|
|
|
@@ -524,6 +546,32 @@ export async function runTUI(options: TUIOptions): Promise<{
|
|
|
524
546
|
let statusUpdater: ((status: string | null) => void) | null = null
|
|
525
547
|
let appPortUpdater: ((port: string) => void) | null = null
|
|
526
548
|
|
|
549
|
+
// Wrap stdout.write to add synchronized update escape sequences
|
|
550
|
+
// This tells the terminal to buffer all output until the end marker
|
|
551
|
+
// Supported by iTerm2, Kitty, WezTerm, and other modern terminals
|
|
552
|
+
const originalWrite = process.stdout.write.bind(process.stdout)
|
|
553
|
+
const syncStart = "\x1b[?2026h" // Begin synchronized update (DECSM 2026)
|
|
554
|
+
const syncEnd = "\x1b[?2026l" // End synchronized update (DECRM 2026)
|
|
555
|
+
|
|
556
|
+
process.stdout.write = ((
|
|
557
|
+
chunk: string | Uint8Array,
|
|
558
|
+
encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),
|
|
559
|
+
cb?: (err?: Error | null) => void
|
|
560
|
+
): boolean => {
|
|
561
|
+
if (typeof chunk === "string" && chunk.length > 0) {
|
|
562
|
+
// Wrap output in synchronized update markers to prevent partial renders
|
|
563
|
+
const wrapped = syncStart + chunk + syncEnd
|
|
564
|
+
if (typeof encodingOrCb === "function") {
|
|
565
|
+
return originalWrite(wrapped, encodingOrCb)
|
|
566
|
+
}
|
|
567
|
+
return originalWrite(wrapped, encodingOrCb, cb)
|
|
568
|
+
}
|
|
569
|
+
if (typeof encodingOrCb === "function") {
|
|
570
|
+
return originalWrite(chunk, encodingOrCb)
|
|
571
|
+
}
|
|
572
|
+
return originalWrite(chunk, encodingOrCb, cb)
|
|
573
|
+
}) as typeof process.stdout.write
|
|
574
|
+
|
|
527
575
|
const app = render(
|
|
528
576
|
<TUIApp
|
|
529
577
|
{...options}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui-interface-impl.d.ts","sourceRoot":"","sources":["../src/tui-interface-impl.tsx"],"names":[],"mappings":"AAQA,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":"AAQA,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;AA0gBD,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,CAkED"}
|
|
@@ -1,15 +1,75 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { createReadStream, unwatchFile, watchFile } from "fs";
|
|
4
4
|
import { Box, render, Text, useInput, useStdout } from "ink";
|
|
5
5
|
import Spinner from "ink-spinner";
|
|
6
|
-
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import { memo, useEffect, useRef, useState } from "react";
|
|
7
7
|
import { LOG_COLORS } from "./constants/log-colors.js";
|
|
8
8
|
const NEXTJS_MCP_404_REGEX = /(?:\[POST\]|POST)\s+\/_next\/mcp\b[^\n]*\b404\b/i;
|
|
9
9
|
// Compact ASCII logo for very small terminals
|
|
10
10
|
const COMPACT_LOGO = "d3k";
|
|
11
11
|
// Full ASCII logo lines as array for easier rendering
|
|
12
12
|
const FULL_LOGO = [" ▐▌▄▄▄▄ █ ▄ ", " ▐▌ █ █▄▀ ", "▗▞▀▜▌▀▀▀█ █ ▀▄ ", "▝▚▄▟▌▄▄▄█ █ █ "];
|
|
13
|
+
// Type colors map - defined outside component to avoid recreation
|
|
14
|
+
const TYPE_COLORS = {
|
|
15
|
+
NETWORK: LOG_COLORS.NETWORK,
|
|
16
|
+
ERROR: LOG_COLORS.ERROR,
|
|
17
|
+
WARNING: LOG_COLORS.WARNING,
|
|
18
|
+
INFO: LOG_COLORS.INFO,
|
|
19
|
+
LOG: LOG_COLORS.LOG,
|
|
20
|
+
DEBUG: LOG_COLORS.DEBUG,
|
|
21
|
+
SCREENSHOT: LOG_COLORS.SCREENSHOT,
|
|
22
|
+
DOM: LOG_COLORS.DOM,
|
|
23
|
+
CDP: LOG_COLORS.CDP,
|
|
24
|
+
CHROME: LOG_COLORS.CHROME,
|
|
25
|
+
CRASH: LOG_COLORS.CRASH,
|
|
26
|
+
REPLAY: LOG_COLORS.REPLAY,
|
|
27
|
+
NAVIGATION: LOG_COLORS.NAVIGATION,
|
|
28
|
+
INTERACTION: LOG_COLORS.INTERACTION,
|
|
29
|
+
GET: LOG_COLORS.SERVER,
|
|
30
|
+
POST: LOG_COLORS.SERVER,
|
|
31
|
+
PUT: LOG_COLORS.SERVER,
|
|
32
|
+
DELETE: LOG_COLORS.SERVER,
|
|
33
|
+
PATCH: LOG_COLORS.SERVER,
|
|
34
|
+
HEAD: LOG_COLORS.SERVER,
|
|
35
|
+
OPTIONS: LOG_COLORS.SERVER
|
|
36
|
+
};
|
|
37
|
+
// Memoized log line component to prevent re-parsing on every render
|
|
38
|
+
const LogLine = memo(({ log, isCompact, isVeryCompact }) => {
|
|
39
|
+
// Parse log line to colorize different parts
|
|
40
|
+
const parts = log.content.match(/^\[(.*?)\] \[(.*?)\] (?:\[(.*?)\] )?(.*)$/);
|
|
41
|
+
if (parts) {
|
|
42
|
+
let [, timestamp, source, type, message] = parts;
|
|
43
|
+
// Extract HTTP method from SERVER logs as a secondary tag
|
|
44
|
+
if (source === "SERVER" && !type && message) {
|
|
45
|
+
const methodMatch = message.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s/);
|
|
46
|
+
if (methodMatch) {
|
|
47
|
+
type = methodMatch[1];
|
|
48
|
+
message = message.slice(type.length + 1); // Remove method from message
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Replace warning emoji in ERROR/WARNING messages for consistent terminal rendering
|
|
52
|
+
if (message && (type === "ERROR" || type === "WARNING")) {
|
|
53
|
+
message = message.replace(/⚠/g, "[!]");
|
|
54
|
+
}
|
|
55
|
+
// In very compact mode, simplify the output
|
|
56
|
+
if (isVeryCompact) {
|
|
57
|
+
const shortSource = source === "BROWSER" ? "B" : "S";
|
|
58
|
+
const shortType = type ? type.split(".")[0].charAt(0) : "";
|
|
59
|
+
return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { dimColor: true, children: ["[", shortSource, "]"] }), shortType && _jsxs(Text, { dimColor: true, children: ["[", shortType, "]"] }), _jsxs(Text, { children: [" ", message] })] }));
|
|
60
|
+
}
|
|
61
|
+
// Use shared color constants
|
|
62
|
+
const sourceColor = source === "BROWSER" ? LOG_COLORS.BROWSER : LOG_COLORS.SERVER;
|
|
63
|
+
// In compact mode, skip padding
|
|
64
|
+
if (isCompact) {
|
|
65
|
+
return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { dimColor: true, children: ["[", timestamp, "]"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: sourceColor, bold: true, children: ["[", source.charAt(0), "]"] }), type && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: TYPE_COLORS[type] || "#A0A0A0", children: ["[", type, "]"] })] })), _jsxs(Text, { children: [" ", message] })] }));
|
|
66
|
+
}
|
|
67
|
+
// Normal mode with minimal padding
|
|
68
|
+
return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { dimColor: true, children: ["[", timestamp, "]"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: sourceColor, bold: true, children: ["[", source, "]"] }), type ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: TYPE_COLORS[type] || "#A0A0A0", children: ["[", type, "]"] }), _jsx(Text, { children: " " })] })) : (_jsx(Text, { children: " " })), _jsx(Text, { children: message })] }));
|
|
69
|
+
}
|
|
70
|
+
// Fallback for unparsed lines
|
|
71
|
+
return _jsx(Text, { wrap: "truncate-end", children: log.content });
|
|
72
|
+
});
|
|
13
73
|
const TUIApp = ({ appPort: initialAppPort, mcpPort, logFile, commandName, serversOnly, version, projectName, onStatusUpdate, onAppPortUpdate }) => {
|
|
14
74
|
const [logs, setLogs] = useState([]);
|
|
15
75
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
@@ -114,16 +174,17 @@ const TUIApp = ({ appPort: initialAppPort, mcpPort, logFile, commandName, server
|
|
|
114
174
|
useEffect(() => {
|
|
115
175
|
let logStream;
|
|
116
176
|
let buffer = "";
|
|
117
|
-
|
|
118
|
-
|
|
177
|
+
let pendingLogs = [];
|
|
178
|
+
let flushTimeout = null;
|
|
179
|
+
// Batch log updates to prevent excessive renders
|
|
180
|
+
const flushPendingLogs = () => {
|
|
181
|
+
if (pendingLogs.length === 0)
|
|
119
182
|
return;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
content: line
|
|
124
|
-
};
|
|
183
|
+
const logsToAdd = pendingLogs;
|
|
184
|
+
pendingLogs = [];
|
|
185
|
+
flushTimeout = null;
|
|
125
186
|
setLogs((prevLogs) => {
|
|
126
|
-
const updated = [...prevLogs,
|
|
187
|
+
const updated = [...prevLogs, ...logsToAdd];
|
|
127
188
|
// Keep only last N logs to prevent memory issues
|
|
128
189
|
if (updated.length > maxLogs) {
|
|
129
190
|
return updated.slice(-maxLogs);
|
|
@@ -131,11 +192,27 @@ const TUIApp = ({ appPort: initialAppPort, mcpPort, logFile, commandName, server
|
|
|
131
192
|
return updated;
|
|
132
193
|
});
|
|
133
194
|
// Auto-scroll to bottom only if user is already at the bottom
|
|
134
|
-
// Otherwise, increment scroll offset by
|
|
195
|
+
// Otherwise, increment scroll offset by count of new logs
|
|
135
196
|
setScrollOffset((currentOffset) => {
|
|
136
|
-
return currentOffset === 0 ? 0 : Math.min(maxScrollOffsetRef.current, currentOffset +
|
|
197
|
+
return currentOffset === 0 ? 0 : Math.min(maxScrollOffsetRef.current, currentOffset + logsToAdd.length);
|
|
137
198
|
});
|
|
138
199
|
};
|
|
200
|
+
const appendLog = (line) => {
|
|
201
|
+
if (NEXTJS_MCP_404_REGEX.test(line)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const newLog = {
|
|
205
|
+
id: logIdCounter.current++,
|
|
206
|
+
content: line
|
|
207
|
+
};
|
|
208
|
+
pendingLogs.push(newLog);
|
|
209
|
+
// Debounce: flush after 50ms of no new logs
|
|
210
|
+
// Terminal synchronized updates prevent flicker, so we can be more responsive
|
|
211
|
+
if (flushTimeout) {
|
|
212
|
+
clearTimeout(flushTimeout);
|
|
213
|
+
}
|
|
214
|
+
flushTimeout = setTimeout(flushPendingLogs, 50);
|
|
215
|
+
};
|
|
139
216
|
// Create a read stream for the log file
|
|
140
217
|
logStream = createReadStream(logFile, {
|
|
141
218
|
encoding: "utf8",
|
|
@@ -182,6 +259,9 @@ const TUIApp = ({ appPort: initialAppPort, mcpPort, logFile, commandName, server
|
|
|
182
259
|
if (logStream) {
|
|
183
260
|
logStream.destroy();
|
|
184
261
|
}
|
|
262
|
+
if (flushTimeout) {
|
|
263
|
+
clearTimeout(flushTimeout);
|
|
264
|
+
}
|
|
185
265
|
unwatchFile(logFile);
|
|
186
266
|
};
|
|
187
267
|
}, [logFile]);
|
|
@@ -242,68 +322,7 @@ const TUIApp = ({ appPort: initialAppPort, mcpPort, logFile, commandName, server
|
|
|
242
322
|
// Calculate the height for the logs box to ensure stable layout from first render
|
|
243
323
|
// This prevents the layout from shifting as logs fill in
|
|
244
324
|
const logsBoxHeight = maxVisibleLogs + 3; // +3 for header line, borders
|
|
245
|
-
return (_jsxs(Box, { flexDirection: "column", height: termHeight, children: [isCompact ? renderCompactHeader() : renderNormalHeader(), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, height: logsBoxHeight, children: [!isVeryCompact && (_jsxs(Text, { color: "gray", dimColor: true, children: ["Logs (", filteredLogs.length, " total", scrollOffset > 0 && `, scrolled up ${scrollOffset} lines`, ")"] })), _jsx(Box, { flexDirection: "column", children: visibleLogs.length === 0 ? (_jsx(Text, { dimColor: true, children: "Waiting for logs..." })) : (visibleLogs.map((log) => {
|
|
246
|
-
// Parse log line to colorize different parts
|
|
247
|
-
const parts = log.content.match(/^\[(.*?)\] \[(.*?)\] (?:\[(.*?)\] )?(.*)$/);
|
|
248
|
-
if (parts) {
|
|
249
|
-
let [, timestamp, source, type, message] = parts;
|
|
250
|
-
// Extract HTTP method from SERVER logs as a secondary tag
|
|
251
|
-
if (source === "SERVER" && !type && message) {
|
|
252
|
-
const methodMatch = message.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s/);
|
|
253
|
-
if (methodMatch) {
|
|
254
|
-
type = methodMatch[1];
|
|
255
|
-
message = message.slice(type.length + 1); // Remove method from message
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
// Replace warning emoji in ERROR/WARNING messages for consistent terminal rendering
|
|
259
|
-
if (message && (type === "ERROR" || type === "WARNING")) {
|
|
260
|
-
message = message.replace(/⚠/g, "[!]");
|
|
261
|
-
}
|
|
262
|
-
// In very compact mode, simplify the output
|
|
263
|
-
if (isVeryCompact) {
|
|
264
|
-
const shortSource = source === "BROWSER" ? "B" : "S";
|
|
265
|
-
const shortType = type ? type.split(".")[0].charAt(0) : "";
|
|
266
|
-
return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { dimColor: true, children: ["[", shortSource, "]"] }), shortType && _jsxs(Text, { dimColor: true, children: ["[", shortType, "]"] }), _jsxs(Text, { children: [" ", message] })] }, log.id));
|
|
267
|
-
}
|
|
268
|
-
// Use shared color constants
|
|
269
|
-
const sourceColor = source === "BROWSER" ? LOG_COLORS.BROWSER : LOG_COLORS.SERVER;
|
|
270
|
-
const typeColors = {
|
|
271
|
-
NETWORK: LOG_COLORS.NETWORK,
|
|
272
|
-
ERROR: LOG_COLORS.ERROR,
|
|
273
|
-
WARNING: LOG_COLORS.WARNING,
|
|
274
|
-
INFO: LOG_COLORS.INFO,
|
|
275
|
-
LOG: LOG_COLORS.LOG,
|
|
276
|
-
DEBUG: LOG_COLORS.DEBUG,
|
|
277
|
-
SCREENSHOT: LOG_COLORS.SCREENSHOT,
|
|
278
|
-
DOM: LOG_COLORS.DOM,
|
|
279
|
-
CDP: LOG_COLORS.CDP,
|
|
280
|
-
CHROME: LOG_COLORS.CHROME,
|
|
281
|
-
CRASH: LOG_COLORS.CRASH,
|
|
282
|
-
REPLAY: LOG_COLORS.REPLAY,
|
|
283
|
-
NAVIGATION: LOG_COLORS.NAVIGATION,
|
|
284
|
-
INTERACTION: LOG_COLORS.INTERACTION,
|
|
285
|
-
GET: LOG_COLORS.SERVER,
|
|
286
|
-
POST: LOG_COLORS.SERVER,
|
|
287
|
-
PUT: LOG_COLORS.SERVER,
|
|
288
|
-
DELETE: LOG_COLORS.SERVER,
|
|
289
|
-
PATCH: LOG_COLORS.SERVER,
|
|
290
|
-
HEAD: LOG_COLORS.SERVER,
|
|
291
|
-
OPTIONS: LOG_COLORS.SERVER
|
|
292
|
-
};
|
|
293
|
-
// In compact mode, skip padding
|
|
294
|
-
if (isCompact) {
|
|
295
|
-
return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { dimColor: true, children: ["[", timestamp, "]"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: sourceColor, bold: true, children: ["[", source.charAt(0), "]"] }), type && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: typeColors[type] || "#A0A0A0", children: ["[", type, "]"] })] })), _jsxs(Text, { children: [" ", message] })] }, log.id));
|
|
296
|
-
}
|
|
297
|
-
// Normal mode with minimal padding
|
|
298
|
-
// Single space after source
|
|
299
|
-
const sourceSpacing = "";
|
|
300
|
-
// Single space after type
|
|
301
|
-
const typeSpacing = "";
|
|
302
|
-
return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { dimColor: true, children: ["[", timestamp, "]"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: sourceColor, bold: true, children: ["[", source, "]"] }), type ? (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [sourceSpacing, " "] }), _jsxs(Text, { color: typeColors[type] || "#A0A0A0", children: ["[", type, "]"] }), _jsxs(Text, { children: [typeSpacing, " "] })] })) : (_jsx(Text, { children: " " })), _jsx(Text, { children: message })] }, log.id));
|
|
303
|
-
}
|
|
304
|
-
// Fallback for unparsed lines
|
|
305
|
-
return (_jsx(Text, { wrap: "truncate-end", children: log.content }, log.id));
|
|
306
|
-
})) }), !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
|
|
325
|
+
return (_jsxs(Box, { flexDirection: "column", height: termHeight, children: [isCompact ? renderCompactHeader() : renderNormalHeader(), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, height: logsBoxHeight, children: [!isVeryCompact && (_jsxs(Text, { color: "gray", dimColor: true, children: ["Logs (", filteredLogs.length, " total", scrollOffset > 0 && `, scrolled up ${scrollOffset} lines`, ")"] })), _jsx(Box, { flexDirection: "column", children: visibleLogs.length === 0 ? (_jsx(Text, { dimColor: true, children: "Waiting for logs..." })) : (visibleLogs.map((log) => (_jsx(LogLine, { log: log, isCompact: isCompact, isVeryCompact: isVeryCompact }, log.id)))) }), !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
|
|
307
326
|
? logFile.split("/").slice(-2, -1)[0] || "logs" // Just show directory name
|
|
308
327
|
: logFile.replace(process.env.HOME || "", "~")] }), _jsx(Text, { color: "#A18CE5", children: ctrlCMessage })] })] }));
|
|
309
328
|
};
|
|
@@ -312,6 +331,26 @@ export async function runTUI(options) {
|
|
|
312
331
|
try {
|
|
313
332
|
let statusUpdater = null;
|
|
314
333
|
let appPortUpdater = null;
|
|
334
|
+
// Wrap stdout.write to add synchronized update escape sequences
|
|
335
|
+
// This tells the terminal to buffer all output until the end marker
|
|
336
|
+
// Supported by iTerm2, Kitty, WezTerm, and other modern terminals
|
|
337
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
338
|
+
const syncStart = "\x1b[?2026h"; // Begin synchronized update (DECSM 2026)
|
|
339
|
+
const syncEnd = "\x1b[?2026l"; // End synchronized update (DECRM 2026)
|
|
340
|
+
process.stdout.write = ((chunk, encodingOrCb, cb) => {
|
|
341
|
+
if (typeof chunk === "string" && chunk.length > 0) {
|
|
342
|
+
// Wrap output in synchronized update markers to prevent partial renders
|
|
343
|
+
const wrapped = syncStart + chunk + syncEnd;
|
|
344
|
+
if (typeof encodingOrCb === "function") {
|
|
345
|
+
return originalWrite(wrapped, encodingOrCb);
|
|
346
|
+
}
|
|
347
|
+
return originalWrite(wrapped, encodingOrCb, cb);
|
|
348
|
+
}
|
|
349
|
+
if (typeof encodingOrCb === "function") {
|
|
350
|
+
return originalWrite(chunk, encodingOrCb);
|
|
351
|
+
}
|
|
352
|
+
return originalWrite(chunk, encodingOrCb, cb);
|
|
353
|
+
});
|
|
315
354
|
const app = render(_jsx(TUIApp, { ...options, onStatusUpdate: (fn) => {
|
|
316
355
|
statusUpdater = fn;
|
|
317
356
|
}, onAppPortUpdate: (fn) => {
|