dev3000 0.0.186 → 0.0.187

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.
Files changed (235) hide show
  1. package/README.md +102 -13
  2. package/bin/dev3000 +1 -2
  3. package/dist/cdp-monitor.d.ts +7 -12
  4. package/dist/cdp-monitor.d.ts.map +1 -1
  5. package/dist/cdp-monitor.js +86 -170
  6. package/dist/cdp-monitor.js.map +1 -1
  7. package/dist/cli.d.ts +1 -2
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +1 -1369
  10. package/dist/cli.js.map +1 -1
  11. package/dist/index.d.ts +1 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +0 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/portless.d.ts +3 -0
  16. package/dist/portless.d.ts.map +1 -1
  17. package/dist/portless.js +10 -0
  18. package/dist/portless.js.map +1 -1
  19. package/dist/runtime/bridge.d.ts +26 -0
  20. package/dist/runtime/bridge.d.ts.map +1 -0
  21. package/dist/runtime/bridge.js +340 -0
  22. package/dist/runtime/bridge.js.map +1 -0
  23. package/dist/runtime/chrome-isolation.d.ts +16 -0
  24. package/dist/runtime/chrome-isolation.d.ts.map +1 -0
  25. package/dist/runtime/chrome-isolation.js +97 -0
  26. package/dist/runtime/chrome-isolation.js.map +1 -0
  27. package/dist/runtime/cli.js +116 -26
  28. package/dist/runtime/cli.js.map +1 -1
  29. package/dist/runtime/event-views.d.ts +21 -0
  30. package/dist/runtime/event-views.d.ts.map +1 -0
  31. package/dist/runtime/event-views.js +53 -0
  32. package/dist/runtime/event-views.js.map +1 -0
  33. package/dist/runtime/harness.d.ts +16 -0
  34. package/dist/runtime/harness.d.ts.map +1 -0
  35. package/dist/runtime/harness.js +68 -0
  36. package/dist/runtime/harness.js.map +1 -0
  37. package/dist/runtime/local-proxy.d.ts +12 -0
  38. package/dist/runtime/local-proxy.d.ts.map +1 -0
  39. package/dist/runtime/local-proxy.js +114 -0
  40. package/dist/runtime/local-proxy.js.map +1 -0
  41. package/dist/runtime/runtime.d.ts +14 -0
  42. package/dist/runtime/runtime.d.ts.map +1 -1
  43. package/dist/runtime/runtime.js +80 -19
  44. package/dist/runtime/runtime.js.map +1 -1
  45. package/dist/runtime/sandbox-agent.d.ts +6 -0
  46. package/dist/runtime/sandbox-agent.d.ts.map +1 -0
  47. package/dist/runtime/sandbox-agent.js +35 -0
  48. package/dist/runtime/sandbox-agent.js.map +1 -0
  49. package/dist/runtime/sandbox-command.d.ts +9 -0
  50. package/dist/runtime/sandbox-command.d.ts.map +1 -0
  51. package/dist/runtime/sandbox-command.js +101 -0
  52. package/dist/runtime/sandbox-command.js.map +1 -0
  53. package/dist/runtime/sandbox-proxy.d.ts +11 -0
  54. package/dist/runtime/sandbox-proxy.d.ts.map +1 -0
  55. package/dist/runtime/sandbox-proxy.js +123 -0
  56. package/dist/runtime/sandbox-proxy.js.map +1 -0
  57. package/dist/runtime/sandbox-sessions.d.ts +31 -0
  58. package/dist/runtime/sandbox-sessions.d.ts.map +1 -0
  59. package/dist/runtime/sandbox-sessions.js +102 -0
  60. package/dist/runtime/sandbox-sessions.js.map +1 -0
  61. package/dist/runtime/sandbox.d.ts +21 -0
  62. package/dist/runtime/sandbox.d.ts.map +1 -0
  63. package/dist/runtime/sandbox.js +481 -0
  64. package/dist/runtime/sandbox.js.map +1 -0
  65. package/dist/utils/agent-browser.d.ts +2 -120
  66. package/dist/utils/agent-browser.d.ts.map +1 -1
  67. package/dist/utils/agent-browser.js +13 -376
  68. package/dist/utils/agent-browser.js.map +1 -1
  69. package/dist/utils/project-name.d.ts +2 -2
  70. package/dist/utils/project-name.js +2 -2
  71. package/package.json +13 -22
  72. package/skills/d3k/SKILL.md +23 -1
  73. package/dist/commands/crawl.d.ts +0 -12
  74. package/dist/commands/crawl.d.ts.map +0 -1
  75. package/dist/commands/crawl.js +0 -101
  76. package/dist/commands/crawl.js.map +0 -1
  77. package/dist/commands/errors.d.ts +0 -14
  78. package/dist/commands/errors.d.ts.map +0 -1
  79. package/dist/commands/errors.js +0 -246
  80. package/dist/commands/errors.js.map +0 -1
  81. package/dist/commands/fix.d.ts +0 -13
  82. package/dist/commands/fix.d.ts.map +0 -1
  83. package/dist/commands/fix.js +0 -219
  84. package/dist/commands/fix.js.map +0 -1
  85. package/dist/commands/logs.d.ts +0 -13
  86. package/dist/commands/logs.d.ts.map +0 -1
  87. package/dist/commands/logs.js +0 -146
  88. package/dist/commands/logs.js.map +0 -1
  89. package/dist/commands/resume.d.ts +0 -11
  90. package/dist/commands/resume.d.ts.map +0 -1
  91. package/dist/commands/resume.js +0 -75
  92. package/dist/commands/resume.js.map +0 -1
  93. package/dist/commands/status.d.ts +0 -21
  94. package/dist/commands/status.d.ts.map +0 -1
  95. package/dist/commands/status.js +0 -44
  96. package/dist/commands/status.js.map +0 -1
  97. package/dist/components/AgentSelector.d.ts +0 -12
  98. package/dist/components/AgentSelector.d.ts.map +0 -1
  99. package/dist/components/AgentSelector.js +0 -99
  100. package/dist/components/AgentSelector.js.map +0 -1
  101. package/dist/components/PackageSelector.d.ts +0 -13
  102. package/dist/components/PackageSelector.d.ts.map +0 -1
  103. package/dist/components/PackageSelector.js +0 -79
  104. package/dist/components/PackageSelector.js.map +0 -1
  105. package/dist/constants/log-colors.d.ts +0 -43
  106. package/dist/constants/log-colors.d.ts.map +0 -1
  107. package/dist/constants/log-colors.js +0 -59
  108. package/dist/constants/log-colors.js.map +0 -1
  109. package/dist/dev-environment.d.ts +0 -187
  110. package/dist/dev-environment.d.ts.map +0 -1
  111. package/dist/dev-environment.js +0 -2248
  112. package/dist/dev-environment.js.map +0 -1
  113. package/dist/screencast-manager.d.ts +0 -78
  114. package/dist/screencast-manager.d.ts.map +0 -1
  115. package/dist/screencast-manager.js +0 -529
  116. package/dist/screencast-manager.js.map +0 -1
  117. package/dist/services/parsers/error-detectors/base.d.ts +0 -24
  118. package/dist/services/parsers/error-detectors/base.d.ts.map +0 -1
  119. package/dist/services/parsers/error-detectors/base.js +0 -44
  120. package/dist/services/parsers/error-detectors/base.js.map +0 -1
  121. package/dist/services/parsers/error-detectors/index.d.ts +0 -6
  122. package/dist/services/parsers/error-detectors/index.d.ts.map +0 -1
  123. package/dist/services/parsers/error-detectors/index.js +0 -6
  124. package/dist/services/parsers/error-detectors/index.js.map +0 -1
  125. package/dist/services/parsers/error-detectors/nextjs.d.ts +0 -9
  126. package/dist/services/parsers/error-detectors/nextjs.d.ts.map +0 -1
  127. package/dist/services/parsers/error-detectors/nextjs.js +0 -52
  128. package/dist/services/parsers/error-detectors/nextjs.js.map +0 -1
  129. package/dist/services/parsers/index.d.ts +0 -9
  130. package/dist/services/parsers/index.d.ts.map +0 -1
  131. package/dist/services/parsers/index.js +0 -10
  132. package/dist/services/parsers/index.js.map +0 -1
  133. package/dist/services/parsers/log-parsers/base.d.ts +0 -26
  134. package/dist/services/parsers/log-parsers/base.d.ts.map +0 -1
  135. package/dist/services/parsers/log-parsers/base.js +0 -6
  136. package/dist/services/parsers/log-parsers/base.js.map +0 -1
  137. package/dist/services/parsers/log-parsers/index.d.ts +0 -6
  138. package/dist/services/parsers/log-parsers/index.d.ts.map +0 -1
  139. package/dist/services/parsers/log-parsers/index.js +0 -5
  140. package/dist/services/parsers/log-parsers/index.js.map +0 -1
  141. package/dist/services/parsers/log-parsers/standard.d.ts +0 -9
  142. package/dist/services/parsers/log-parsers/standard.d.ts.map +0 -1
  143. package/dist/services/parsers/log-parsers/standard.js +0 -17
  144. package/dist/services/parsers/log-parsers/standard.js.map +0 -1
  145. package/dist/services/parsers/output-processor.d.ts +0 -39
  146. package/dist/services/parsers/output-processor.d.ts.map +0 -1
  147. package/dist/services/parsers/output-processor.js +0 -54
  148. package/dist/services/parsers/output-processor.js.map +0 -1
  149. package/dist/skills/d3k/internal-skill.md +0 -32
  150. package/dist/skills/index.d.ts +0 -54
  151. package/dist/skills/index.d.ts.map +0 -1
  152. package/dist/skills/index.js +0 -214
  153. package/dist/skills/index.js.map +0 -1
  154. package/dist/src/tui-interface-impl.tsx +0 -644
  155. package/dist/tui-interface-impl.d.ts +0 -30
  156. package/dist/tui-interface-impl.d.ts.map +0 -1
  157. package/dist/tui-interface-impl.js +0 -403
  158. package/dist/tui-interface-impl.js.map +0 -1
  159. package/dist/tui-interface-opentui.d.ts +0 -33
  160. package/dist/tui-interface-opentui.d.ts.map +0 -1
  161. package/dist/tui-interface-opentui.js +0 -1128
  162. package/dist/tui-interface-opentui.js.map +0 -1
  163. package/dist/tui-interface.d.ts +0 -38
  164. package/dist/tui-interface.d.ts.map +0 -1
  165. package/dist/tui-interface.js +0 -84
  166. package/dist/tui-interface.js.map +0 -1
  167. package/dist/utils/agent-detection.d.ts +0 -17
  168. package/dist/utils/agent-detection.d.ts.map +0 -1
  169. package/dist/utils/agent-detection.js +0 -81
  170. package/dist/utils/agent-detection.js.map +0 -1
  171. package/dist/utils/agent-selection.d.ts +0 -25
  172. package/dist/utils/agent-selection.d.ts.map +0 -1
  173. package/dist/utils/agent-selection.js +0 -73
  174. package/dist/utils/agent-selection.js.map +0 -1
  175. package/dist/utils/browser-command-argv.d.ts +0 -19
  176. package/dist/utils/browser-command-argv.d.ts.map +0 -1
  177. package/dist/utils/browser-command-argv.js +0 -149
  178. package/dist/utils/browser-command-argv.js.map +0 -1
  179. package/dist/utils/build-version.d.ts +0 -2
  180. package/dist/utils/build-version.d.ts.map +0 -1
  181. package/dist/utils/build-version.js +0 -8
  182. package/dist/utils/build-version.js.map +0 -1
  183. package/dist/utils/cli-options.d.ts +0 -5
  184. package/dist/utils/cli-options.d.ts.map +0 -1
  185. package/dist/utils/cli-options.js +0 -36
  186. package/dist/utils/cli-options.js.map +0 -1
  187. package/dist/utils/d3k-dir.d.ts +0 -3
  188. package/dist/utils/d3k-dir.d.ts.map +0 -1
  189. package/dist/utils/d3k-dir.js +0 -12
  190. package/dist/utils/d3k-dir.js.map +0 -1
  191. package/dist/utils/d3k-skill-installer.d.ts +0 -4
  192. package/dist/utils/d3k-skill-installer.d.ts.map +0 -1
  193. package/dist/utils/d3k-skill-installer.js +0 -30
  194. package/dist/utils/d3k-skill-installer.js.map +0 -1
  195. package/dist/utils/log-filename.d.ts +0 -30
  196. package/dist/utils/log-filename.d.ts.map +0 -1
  197. package/dist/utils/log-filename.js +0 -60
  198. package/dist/utils/log-filename.js.map +0 -1
  199. package/dist/utils/project-detector.d.ts +0 -25
  200. package/dist/utils/project-detector.d.ts.map +0 -1
  201. package/dist/utils/project-detector.js +0 -153
  202. package/dist/utils/project-detector.js.map +0 -1
  203. package/dist/utils/project-metadata.d.ts +0 -4
  204. package/dist/utils/project-metadata.d.ts.map +0 -1
  205. package/dist/utils/project-metadata.js +0 -48
  206. package/dist/utils/project-metadata.js.map +0 -1
  207. package/dist/utils/repo-validate.d.ts +0 -2
  208. package/dist/utils/repo-validate.d.ts.map +0 -1
  209. package/dist/utils/repo-validate.js +0 -10
  210. package/dist/utils/repo-validate.js.map +0 -1
  211. package/dist/utils/session.d.ts +0 -19
  212. package/dist/utils/session.d.ts.map +0 -1
  213. package/dist/utils/session.js +0 -65
  214. package/dist/utils/session.js.map +0 -1
  215. package/dist/utils/skill-installer.d.ts +0 -65
  216. package/dist/utils/skill-installer.d.ts.map +0 -1
  217. package/dist/utils/skill-installer.js +0 -265
  218. package/dist/utils/skill-installer.js.map +0 -1
  219. package/dist/utils/timestamp.d.ts +0 -20
  220. package/dist/utils/timestamp.d.ts.map +0 -1
  221. package/dist/utils/timestamp.js +0 -57
  222. package/dist/utils/timestamp.js.map +0 -1
  223. package/dist/utils/tmux-helpers.d.ts +0 -36
  224. package/dist/utils/tmux-helpers.d.ts.map +0 -1
  225. package/dist/utils/tmux-helpers.js +0 -118
  226. package/dist/utils/tmux-helpers.js.map +0 -1
  227. package/dist/utils/user-config.d.ts +0 -12
  228. package/dist/utils/user-config.d.ts.map +0 -1
  229. package/dist/utils/user-config.js +0 -46
  230. package/dist/utils/user-config.js.map +0 -1
  231. package/dist/utils/version-check.d.ts +0 -52
  232. package/dist/utils/version-check.d.ts.map +0 -1
  233. package/dist/utils/version-check.js +0 -304
  234. package/dist/utils/version-check.js.map +0 -1
  235. package/src/tui-interface-impl.tsx +0 -644
@@ -1,644 +0,0 @@
1
- import chalk from "chalk"
2
- import { createReadStream, unwatchFile, watchFile } from "fs"
3
- import { Box, render, Text, useInput, useWindowSize } from "ink"
4
- import Spinner from "ink-spinner"
5
- import { memo, useEffect, useRef, useState } from "react"
6
- import type { Readable } from "stream"
7
- import { LOG_COLORS } from "./constants/log-colors.js"
8
-
9
- export type UpdateInfo =
10
- | { type: "available"; latestVersion: string }
11
- | { type: "updated"; newVersion: string; autoHide?: boolean }
12
- | null
13
-
14
- export interface TUIOptions {
15
- appPort: string
16
- appUrl?: string | null
17
- logFile: string
18
- commandName: string
19
- serversOnly?: boolean
20
- version: string
21
- projectName?: string
22
- updateInfo?: UpdateInfo
23
- useHttps?: boolean
24
- }
25
-
26
- interface LogEntry {
27
- id: number
28
- content: string
29
- }
30
-
31
- // Compact ASCII logo for very small terminals
32
- const COMPACT_LOGO = "d3k"
33
-
34
- // Full ASCII logo lines as array for easier rendering
35
- const FULL_LOGO = [" ▐▌▄▄▄▄ █ ▄ ", " ▐▌ █ █▄▀ ", "▗▞▀▜▌▀▀▀█ █ ▀▄ ", "▝▚▄▟▌▄▄▄█ █ █ "]
36
-
37
- // Type colors map - defined outside component to avoid recreation
38
- const TYPE_COLORS: Record<string, string> = {
39
- NETWORK: LOG_COLORS.NETWORK,
40
- ERROR: LOG_COLORS.ERROR,
41
- WARNING: LOG_COLORS.WARNING,
42
- INFO: LOG_COLORS.INFO,
43
- LOG: LOG_COLORS.LOG,
44
- DEBUG: LOG_COLORS.DEBUG,
45
- SCREENSHOT: LOG_COLORS.SCREENSHOT,
46
- DOM: LOG_COLORS.DOM,
47
- CDP: LOG_COLORS.CDP,
48
- CHROME: LOG_COLORS.CHROME,
49
- CRASH: LOG_COLORS.CRASH,
50
- EXIT: LOG_COLORS.EXIT,
51
- REPLAY: LOG_COLORS.REPLAY,
52
- NAVIGATION: LOG_COLORS.NAVIGATION,
53
- INTERACTION: LOG_COLORS.INTERACTION,
54
- GET: LOG_COLORS.SERVER,
55
- POST: LOG_COLORS.SERVER,
56
- PUT: LOG_COLORS.SERVER,
57
- DELETE: LOG_COLORS.SERVER,
58
- PATCH: LOG_COLORS.SERVER,
59
- HEAD: LOG_COLORS.SERVER,
60
- OPTIONS: LOG_COLORS.SERVER
61
- }
62
-
63
- // Memoized log line component to prevent re-parsing on every render
64
- const LogLine = memo(
65
- ({ log, isCompact, isVeryCompact }: { log: LogEntry; isCompact: boolean; isVeryCompact: boolean }) => {
66
- // Parse log line to colorize different parts
67
- const parts = log.content.match(/^\[(.*?)\] \[(.*?)\] (?:\[(.*?)\] )?(.*)$/)
68
-
69
- if (parts) {
70
- let [, timestamp, source, type, message] = parts
71
-
72
- // Extract HTTP method from SERVER logs as a secondary tag
73
- if (source === "SERVER" && !type && message) {
74
- const methodMatch = message.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s/)
75
- if (methodMatch) {
76
- type = methodMatch[1]
77
- message = message.slice(type.length + 1) // Remove method from message
78
- }
79
- }
80
-
81
- // Replace warning emoji in ERROR/WARNING messages for consistent terminal rendering
82
- if (message && (type === "ERROR" || type === "WARNING")) {
83
- message = message.replace(/⚠/g, "[!]")
84
- }
85
-
86
- // In very compact mode, simplify the output
87
- if (isVeryCompact) {
88
- const shortSource = source === "BROWSER" ? "B" : "S"
89
- const shortType = type ? type.split(".")[0].charAt(0) : ""
90
- return (
91
- <Text wrap="truncate-end">
92
- <Text dimColor>[{shortSource}]</Text>
93
- {shortType && <Text dimColor>[{shortType}]</Text>}
94
- <Text> {message}</Text>
95
- </Text>
96
- )
97
- }
98
-
99
- // Use shared color constants
100
- const sourceColor = source === "BROWSER" ? LOG_COLORS.BROWSER : LOG_COLORS.SERVER
101
-
102
- // In compact mode, skip padding
103
- if (isCompact) {
104
- return (
105
- <Text wrap="truncate-end">
106
- <Text dimColor>[{timestamp}]</Text>
107
- <Text> </Text>
108
- <Text color={sourceColor} bold>
109
- [{source.charAt(0)}]
110
- </Text>
111
- {type && (
112
- <>
113
- <Text> </Text>
114
- <Text color={TYPE_COLORS[type] || "#A0A0A0"}>[{type}]</Text>
115
- </>
116
- )}
117
- <Text> {message}</Text>
118
- </Text>
119
- )
120
- }
121
-
122
- // Normal mode with minimal padding
123
- return (
124
- <Text wrap="truncate-end">
125
- <Text dimColor>[{timestamp}]</Text>
126
- <Text> </Text>
127
- <Text color={sourceColor} bold>
128
- [{source}]
129
- </Text>
130
- {type ? (
131
- <>
132
- <Text> </Text>
133
- <Text color={TYPE_COLORS[type] || "#A0A0A0"}>[{type}]</Text>
134
- <Text> </Text>
135
- </>
136
- ) : (
137
- <Text> </Text>
138
- )}
139
- <Text>{message}</Text>
140
- </Text>
141
- )
142
- }
143
-
144
- // Fallback for unparsed lines
145
- return <Text wrap="truncate-end">{log.content}</Text>
146
- }
147
- )
148
-
149
- const TUIApp = ({
150
- appPort: initialAppPort,
151
- appUrl: initialAppUrl,
152
- logFile,
153
- commandName: _commandName,
154
- serversOnly,
155
- version,
156
- projectName,
157
- updateInfo: initialUpdateInfo,
158
- useHttps: initialUseHttps,
159
- onStatusUpdate,
160
- onAppPortUpdate,
161
- onAppUrlUpdate,
162
- onUpdateInfoUpdate,
163
- onUseHttpsUpdate
164
- }: TUIOptions & {
165
- onStatusUpdate: (fn: (status: string | null) => void) => void
166
- onAppPortUpdate: (fn: (port: string) => void) => void
167
- onAppUrlUpdate: (fn: (url: string | null) => void) => void
168
- onUpdateInfoUpdate: (fn: (info: UpdateInfo) => void) => void
169
- onUseHttpsUpdate: (fn: (useHttps: boolean) => void) => void
170
- }) => {
171
- const [logs, setLogs] = useState<LogEntry[]>([])
172
- const [scrollOffset, setScrollOffset] = useState(0)
173
- const [initStatus, setInitStatus] = useState<string | null>(null)
174
- const [appPort, setAppPort] = useState<string>(initialAppPort)
175
- const [appUrl, setAppUrl] = useState<string | null>(initialAppUrl || null)
176
- const [useHttps, setUseHttps] = useState<boolean>(initialUseHttps || false)
177
- const [updateInfo, setUpdateInfo] = useState<UpdateInfo>(initialUpdateInfo || null)
178
- const [portConfirmed, setPortConfirmed] = useState<boolean>(false)
179
- const logIdCounter = useRef(0)
180
- const [clearFromLogId, setClearFromLogId] = useState<number>(0) // Track log ID to clear from
181
- const { columns: termWidth, rows: termHeight } = useWindowSize()
182
- const ctrlCMessageDefault = "" // Removed - click to focus/resize works now
183
- const [ctrlCMessage, setCtrlCMessage] = useState(ctrlCMessageDefault)
184
- const maxScrollOffsetRef = useRef(0)
185
-
186
- // Determine if we should use compact mode
187
- const isCompact = termWidth < 80
188
- const isVeryCompact = termWidth < 60 || termHeight < 15
189
-
190
- // Provide status update function to parent
191
- useEffect(() => {
192
- onStatusUpdate((status: string | null) => {
193
- // Check if this is the "Press Ctrl+C again" warning
194
- if (status?.includes("Press Ctrl+C again")) {
195
- // Update the bottom Ctrl+C message with warning emoji
196
- setCtrlCMessage("⚠️ ^C again to quit")
197
- // Clear the init status since we don't want it in the header anymore
198
- setInitStatus(null)
199
- // Reset after 3 seconds
200
- setTimeout(() => {
201
- setCtrlCMessage(ctrlCMessageDefault)
202
- }, 3000)
203
- } else {
204
- setInitStatus(status)
205
- }
206
- })
207
- }, [onStatusUpdate])
208
-
209
- // Provide app port update function to parent
210
- useEffect(() => {
211
- onAppPortUpdate((port: string) => {
212
- setAppPort(port)
213
- setPortConfirmed(true)
214
- })
215
- }, [onAppPortUpdate])
216
-
217
- useEffect(() => {
218
- onAppUrlUpdate((url: string | null) => {
219
- setAppUrl(url)
220
- })
221
- }, [onAppUrlUpdate])
222
-
223
- // Provide update info function to parent
224
- useEffect(() => {
225
- onUpdateInfoUpdate((info: UpdateInfo) => {
226
- setUpdateInfo(info)
227
- // Auto-hide "updated" messages after 10 seconds
228
- if (info?.type === "updated" && info.autoHide !== false) {
229
- setTimeout(() => {
230
- setUpdateInfo(null)
231
- }, 10000)
232
- }
233
- })
234
- }, [onUpdateInfoUpdate])
235
-
236
- // Provide useHttps update function to parent
237
- useEffect(() => {
238
- onUseHttpsUpdate((https: boolean) => {
239
- setUseHttps(https)
240
- })
241
- }, [onUseHttpsUpdate])
242
-
243
- // Calculate available lines for logs dynamically based on terminal height and mode
244
- const calculateMaxVisibleLogs = () => {
245
- if (isVeryCompact) {
246
- // In very compact mode, use most of the screen for logs, account for bottom status line
247
- return Math.max(3, termHeight - 7)
248
- } else if (isCompact) {
249
- // In compact mode, reduce header size, account for bottom status line
250
- return Math.max(3, termHeight - 9)
251
- } else {
252
- // Normal mode calculation - account for all UI elements
253
- const headerBorderLines = 2 // Top border (with title) + bottom border
254
- const headerContentLines = 4 // Logo is 4 lines tall
255
- const logBoxBorderLines = 2 // Top and bottom border of log box
256
- const logBoxHeaderLines = 1 // "Logs (X total)" text
257
- const logBoxFooterLines = 1 // Always reserve space for "(X lines below)" to keep layout stable
258
- const bottomStatusLine = 1 // Log path and quit message
259
- const totalReservedLines =
260
- headerBorderLines +
261
- headerContentLines +
262
- logBoxBorderLines +
263
- logBoxHeaderLines +
264
- logBoxFooterLines +
265
- bottomStatusLine -
266
- 1 // Reclaim the extra blank line
267
- return Math.max(3, termHeight - totalReservedLines)
268
- }
269
- }
270
-
271
- const maxLogs = 1000
272
- const maxVisibleLogs = calculateMaxVisibleLogs()
273
- const maxScrollOffset = Math.max(0, maxLogs - maxVisibleLogs)
274
- maxScrollOffsetRef.current = maxScrollOffset
275
-
276
- useEffect(() => {
277
- let logStream: Readable | undefined
278
- let buffer = ""
279
- let pendingLogs: LogEntry[] = []
280
- let flushTimeout: NodeJS.Timeout | null = null
281
-
282
- // Batch log updates to prevent excessive renders
283
- const flushPendingLogs = () => {
284
- if (pendingLogs.length === 0) return
285
-
286
- const logsToAdd = pendingLogs
287
- pendingLogs = []
288
- flushTimeout = null
289
-
290
- setLogs((prevLogs) => {
291
- const updated = [...prevLogs, ...logsToAdd]
292
- // Keep only last N logs to prevent memory issues
293
- if (updated.length > maxLogs) {
294
- return updated.slice(-maxLogs)
295
- }
296
- return updated
297
- })
298
-
299
- // Auto-scroll to bottom only if user is already at the bottom
300
- // Otherwise, increment scroll offset by count of new logs
301
- setScrollOffset((currentOffset) => {
302
- return currentOffset === 0 ? 0 : Math.min(maxScrollOffsetRef.current, currentOffset + logsToAdd.length)
303
- })
304
- }
305
-
306
- const appendLog = (line: string) => {
307
- const newLog: LogEntry = {
308
- id: logIdCounter.current++,
309
- content: line
310
- }
311
-
312
- pendingLogs.push(newLog)
313
-
314
- // Debounce: flush after 50ms of no new logs
315
- // Terminal synchronized updates prevent flicker, so we can be more responsive
316
- if (flushTimeout) {
317
- clearTimeout(flushTimeout)
318
- }
319
- flushTimeout = setTimeout(flushPendingLogs, 50)
320
- }
321
-
322
- // Create a read stream for the log file
323
- logStream = createReadStream(logFile, {
324
- encoding: "utf8",
325
- start: 0
326
- })
327
-
328
- logStream.on("data", (chunk) => {
329
- buffer += chunk.toString()
330
- const lines = buffer.split("\n")
331
-
332
- // Keep the last incomplete line in buffer
333
- buffer = lines.pop() || ""
334
-
335
- // Add complete lines to logs
336
- for (const line of lines) {
337
- if (line.trim()) {
338
- appendLog(line)
339
- }
340
- }
341
- })
342
-
343
- logStream.on("error", (error) => {
344
- appendLog(chalk.red(`Error reading log file: ${error.message}`))
345
- })
346
-
347
- // Watch for new content
348
- watchFile(logFile, { interval: 100 }, (curr, prev) => {
349
- if (curr.size > prev.size) {
350
- // File has grown, read new content
351
- const stream = createReadStream(logFile, {
352
- encoding: "utf8",
353
- start: prev.size
354
- })
355
-
356
- let watchBuffer = ""
357
- stream.on("data", (chunk) => {
358
- watchBuffer += chunk.toString()
359
- const lines = watchBuffer.split("\n")
360
- watchBuffer = lines.pop() || ""
361
-
362
- for (const line of lines) {
363
- if (line.trim()) {
364
- appendLog(line)
365
- }
366
- }
367
- })
368
- }
369
- })
370
-
371
- // Cleanup
372
- return () => {
373
- if (logStream) {
374
- logStream.destroy()
375
- }
376
- if (flushTimeout) {
377
- clearTimeout(flushTimeout)
378
- }
379
- unwatchFile(logFile)
380
- }
381
- }, [logFile])
382
-
383
- // Handle keyboard input
384
- useInput((input, key) => {
385
- if (key.ctrl && input === "c") {
386
- // Send SIGINT to trigger main process shutdown handler
387
- process.kill(process.pid, "SIGINT")
388
- } else if (key.ctrl && input === "l") {
389
- // Ctrl-L: Clear logs box - set clear point to last log ID
390
- const lastLogId = logs.length > 0 ? logs[logs.length - 1].id : logIdCounter.current
391
- setClearFromLogId(lastLogId)
392
- setScrollOffset(0) // Reset scroll to bottom
393
- } else if (key.upArrow) {
394
- const filteredCount = logs.filter((log) => log.id > clearFromLogId).length
395
- setScrollOffset((prev) => Math.min(prev + 1, Math.max(0, filteredCount - maxVisibleLogs)))
396
- } else if (key.downArrow) {
397
- setScrollOffset((prev) => Math.max(0, prev - 1))
398
- } else if (key.pageUp) {
399
- const filteredCount = logs.filter((log) => log.id > clearFromLogId).length
400
- setScrollOffset((prev) => Math.min(prev + maxVisibleLogs, Math.max(0, filteredCount - maxVisibleLogs)))
401
- } else if (key.pageDown) {
402
- setScrollOffset((prev) => Math.max(0, prev - maxVisibleLogs))
403
- } else if (input === "G" && key.shift) {
404
- // Shift+G to go to end
405
- setScrollOffset(0)
406
- } else if (input === "g" && !key.shift) {
407
- // g to go to beginning
408
- const filteredCount = logs.filter((log) => log.id > clearFromLogId).length
409
- setScrollOffset(Math.max(0, filteredCount - maxVisibleLogs))
410
- }
411
- })
412
-
413
- // Calculate visible logs - filter to only show logs after the clear point
414
- const filteredLogs = logs.filter((log) => log.id > clearFromLogId)
415
- const visibleLogs = filteredLogs.slice(
416
- Math.max(0, filteredLogs.length - maxVisibleLogs - scrollOffset),
417
- filteredLogs.length - scrollOffset
418
- )
419
-
420
- // Get clean project name (strip hash suffix like "-935beb")
421
- const cleanProjectName = projectName ? projectName.replace(/-[a-f0-9]{6}$/, "") : ""
422
- const displayedAppUrl = appUrl || `${useHttps ? "https" : "http"}://localhost:${appPort}`
423
-
424
- // Render compact header for small terminals
425
- const renderCompactHeader = () => (
426
- <Box borderStyle="single" borderColor="#A18CE5" paddingX={1}>
427
- <Box flexDirection="column" width="100%">
428
- <Box>
429
- <Text color="#A18CE5" bold>
430
- {COMPACT_LOGO}
431
- </Text>
432
- <Text dimColor> v{version}</Text>
433
- </Box>
434
- {!isVeryCompact && (
435
- <Box flexDirection="column">
436
- <Text dimColor>App: {displayedAppUrl}</Text>
437
- <Text dimColor>📋 Logs: {logFile}</Text>
438
- </Box>
439
- )}
440
- </Box>
441
- </Box>
442
- )
443
-
444
- // Render normal header
445
- const renderNormalHeader = () => {
446
- // Create custom top border with title embedded (like Claude Code)
447
- // Note: commandName omitted since ASCII art already shows "d3k"
448
- const title = ` v${version} ${initStatus ? `- ${initStatus} ` : ""}`
449
- const borderChar = "─"
450
- const leftPadding = 2
451
- // Account for border characters and padding
452
- const availableWidth = termWidth - 2 // -2 for corner characters
453
- const titleLength = title.length
454
- const rightBorderLength = Math.max(0, availableWidth - titleLength - leftPadding)
455
- const topBorderLine = `╭${borderChar.repeat(leftPadding)}${title}${borderChar.repeat(rightBorderLength)}╮`
456
-
457
- return (
458
- <Box flexDirection="column">
459
- {/* Custom top border with embedded title */}
460
- <Text color="#A18CE5">{topBorderLine}</Text>
461
-
462
- {/* Content with side borders only */}
463
- <Box borderStyle="round" borderColor="#A18CE5" borderTop={false} paddingX={1}>
464
- <Box flexDirection="row" gap={1}>
465
- {/* ASCII Logo on the left */}
466
- {/* biome-ignore format: preserve ASCII art alignment */}
467
- <Box flexDirection="column" alignItems="flex-start">
468
- {FULL_LOGO.map((line) => (
469
- <Text key={line} color="#A18CE5" bold>
470
- {line}
471
- </Text>
472
- ))}
473
- </Box>
474
-
475
- {/* Info on the right */}
476
- <Box flexDirection="column" flexGrow={1}>
477
- <Box>
478
- <Text color="cyan">🌐 App: {displayedAppUrl} </Text>
479
- {!portConfirmed && <Spinner type="dots" />}
480
- </Box>
481
- <Text color="cyan">📋 Logs: {logFile}</Text>
482
- {serversOnly && <Text color="cyan">🖥️ Servers-only mode - browser monitoring disabled</Text>}
483
- </Box>
484
- </Box>
485
- </Box>
486
- </Box>
487
- )
488
- }
489
-
490
- // Calculate the height for the logs box to ensure stable layout from first render
491
- // This prevents the layout from shifting as logs fill in
492
- const logsBoxHeight = maxVisibleLogs + 3 // +3 for header line, borders
493
-
494
- return (
495
- <Box flexDirection="column" height={termHeight}>
496
- {/* Header Box - responsive to terminal size */}
497
- {isCompact ? renderCompactHeader() : renderNormalHeader()}
498
-
499
- {/* Logs Box - explicit height for stable initial render */}
500
- <Box flexDirection="column" borderStyle="single" borderColor="gray" paddingX={1} height={logsBoxHeight}>
501
- {!isVeryCompact && (
502
- <Text color="gray" dimColor>
503
- Logs ({filteredLogs.length} total{scrollOffset > 0 && `, scrolled up ${scrollOffset} lines`})
504
- </Text>
505
- )}
506
-
507
- {/* Logs content area */}
508
- <Box flexDirection="column">
509
- {visibleLogs.length === 0 ? (
510
- <Text dimColor>Waiting for logs...</Text>
511
- ) : (
512
- visibleLogs.map((log) => (
513
- <LogLine key={log.id} log={log} isCompact={isCompact} isVeryCompact={isVeryCompact} />
514
- ))
515
- )}
516
- </Box>
517
-
518
- {/* Scroll indicator - only show when scrolled up and not in very compact mode */}
519
- {!isVeryCompact && logs.length > maxVisibleLogs && scrollOffset > 0 && (
520
- <Text dimColor>({scrollOffset} lines below)</Text>
521
- )}
522
- </Box>
523
-
524
- {/* Bottom status line */}
525
- <Box paddingX={1} justifyContent={isCompact ? "flex-end" : "space-between"}>
526
- {!isCompact && (
527
- <Box>
528
- <Text color="#A18CE5">{logFile.replace(process.env.HOME || "", "~")}</Text>
529
- </Box>
530
- )}
531
- <Box gap={2}>
532
- {isCompact && <Text color="#A18CE5">{cleanProjectName || "d3k"}</Text>}
533
- {updateInfo?.type === "available" && (
534
- <Text color="yellow">↑ v{updateInfo.latestVersion} available (d3k upgrade)</Text>
535
- )}
536
- {updateInfo?.type === "updated" && <Text color="green">✓ Updated to v{updateInfo.newVersion}</Text>}
537
- {ctrlCMessage && <Text color="#A18CE5">{ctrlCMessage}</Text>}
538
- </Box>
539
- </Box>
540
- </Box>
541
- )
542
- }
543
-
544
- export async function runTUI(options: TUIOptions): Promise<{
545
- app: { unmount: () => void }
546
- updateStatus: (status: string | null) => void
547
- updateAppPort: (port: string) => void
548
- updateAppUrl: (url: string | null) => void
549
- updateUpdateInfo: (info: UpdateInfo) => void
550
- updateUseHttps: (useHttps: boolean) => void
551
- }> {
552
- return new Promise((resolve, reject) => {
553
- try {
554
- let statusUpdater: ((status: string | null) => void) | null = null
555
- let appPortUpdater: ((port: string) => void) | null = null
556
- let appUrlUpdater: ((url: string | null) => void) | null = null
557
- let updateInfoUpdater: ((info: UpdateInfo) => void) | null = null
558
- let httpsUpdater: ((useHttps: boolean) => void) | null = null
559
-
560
- // Wrap stdout.write to add synchronized update escape sequences
561
- // This tells the terminal to buffer all output until the end marker
562
- // Supported by iTerm2, Kitty, WezTerm, and other modern terminals
563
- const originalWrite = process.stdout.write.bind(process.stdout)
564
- const syncStart = "\x1b[?2026h" // Begin synchronized update (DECSM 2026)
565
- const syncEnd = "\x1b[?2026l" // End synchronized update (DECRM 2026)
566
-
567
- process.stdout.write = ((
568
- chunk: string | Uint8Array,
569
- encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),
570
- cb?: (err?: Error | null) => void
571
- ): boolean => {
572
- if (typeof chunk === "string" && chunk.length > 0) {
573
- // Wrap output in synchronized update markers to prevent partial renders
574
- const wrapped = syncStart + chunk + syncEnd
575
- if (typeof encodingOrCb === "function") {
576
- return originalWrite(wrapped, encodingOrCb)
577
- }
578
- return originalWrite(wrapped, encodingOrCb, cb)
579
- }
580
- if (typeof encodingOrCb === "function") {
581
- return originalWrite(chunk, encodingOrCb)
582
- }
583
- return originalWrite(chunk, encodingOrCb, cb)
584
- }) as typeof process.stdout.write
585
-
586
- const app = render(
587
- <TUIApp
588
- {...options}
589
- onStatusUpdate={(fn) => {
590
- statusUpdater = fn
591
- }}
592
- onAppPortUpdate={(fn) => {
593
- appPortUpdater = fn
594
- }}
595
- onAppUrlUpdate={(fn) => {
596
- appUrlUpdater = fn
597
- }}
598
- onUpdateInfoUpdate={(fn) => {
599
- updateInfoUpdater = fn
600
- }}
601
- onUseHttpsUpdate={(fn) => {
602
- httpsUpdater = fn
603
- }}
604
- />,
605
- { exitOnCtrlC: false }
606
- )
607
-
608
- // Give React one tick to set up the updaters
609
- setImmediate(() => {
610
- resolve({
611
- app,
612
- updateStatus: (status: string | null) => {
613
- if (statusUpdater) {
614
- statusUpdater(status)
615
- }
616
- },
617
- updateAppPort: (port: string) => {
618
- if (appPortUpdater) {
619
- appPortUpdater(port)
620
- }
621
- },
622
- updateAppUrl: (url: string | null) => {
623
- if (appUrlUpdater) {
624
- appUrlUpdater(url)
625
- }
626
- },
627
- updateUpdateInfo: (info: UpdateInfo) => {
628
- if (updateInfoUpdater) {
629
- updateInfoUpdater(info)
630
- }
631
- },
632
- updateUseHttps: (useHttps: boolean) => {
633
- if (httpsUpdater) {
634
- httpsUpdater(useHttps)
635
- }
636
- }
637
- })
638
- })
639
- } catch (error) {
640
- console.error("Error in runTUI render:", error)
641
- reject(error)
642
- }
643
- })
644
- }