dev3000 0.0.186 → 0.0.188
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 +101 -13
- package/bin/dev3000 +1 -2
- package/dist/cdp-monitor.d.ts +7 -12
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +86 -170
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/cli.d.ts +1 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -1369
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/portless.d.ts +3 -0
- package/dist/portless.d.ts.map +1 -1
- package/dist/portless.js +10 -0
- package/dist/portless.js.map +1 -1
- package/dist/runtime/bridge.d.ts +26 -0
- package/dist/runtime/bridge.d.ts.map +1 -0
- package/dist/runtime/bridge.js +340 -0
- package/dist/runtime/bridge.js.map +1 -0
- package/dist/runtime/chrome-isolation.d.ts +16 -0
- package/dist/runtime/chrome-isolation.d.ts.map +1 -0
- package/dist/runtime/chrome-isolation.js +119 -0
- package/dist/runtime/chrome-isolation.js.map +1 -0
- package/dist/runtime/cli.js +116 -26
- package/dist/runtime/cli.js.map +1 -1
- package/dist/runtime/event-views.d.ts +21 -0
- package/dist/runtime/event-views.d.ts.map +1 -0
- package/dist/runtime/event-views.js +53 -0
- package/dist/runtime/event-views.js.map +1 -0
- package/dist/runtime/harness.d.ts +16 -0
- package/dist/runtime/harness.d.ts.map +1 -0
- package/dist/runtime/harness.js +68 -0
- package/dist/runtime/harness.js.map +1 -0
- package/dist/runtime/local-proxy.d.ts +12 -0
- package/dist/runtime/local-proxy.d.ts.map +1 -0
- package/dist/runtime/local-proxy.js +114 -0
- package/dist/runtime/local-proxy.js.map +1 -0
- package/dist/runtime/runtime.d.ts +14 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +80 -19
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/sandbox-agent.d.ts +6 -0
- package/dist/runtime/sandbox-agent.d.ts.map +1 -0
- package/dist/runtime/sandbox-agent.js +35 -0
- package/dist/runtime/sandbox-agent.js.map +1 -0
- package/dist/runtime/sandbox-command.d.ts +9 -0
- package/dist/runtime/sandbox-command.d.ts.map +1 -0
- package/dist/runtime/sandbox-command.js +101 -0
- package/dist/runtime/sandbox-command.js.map +1 -0
- package/dist/runtime/sandbox-proxy.d.ts +11 -0
- package/dist/runtime/sandbox-proxy.d.ts.map +1 -0
- package/dist/runtime/sandbox-proxy.js +123 -0
- package/dist/runtime/sandbox-proxy.js.map +1 -0
- package/dist/runtime/sandbox-sessions.d.ts +31 -0
- package/dist/runtime/sandbox-sessions.d.ts.map +1 -0
- package/dist/runtime/sandbox-sessions.js +102 -0
- package/dist/runtime/sandbox-sessions.js.map +1 -0
- package/dist/runtime/sandbox.d.ts +21 -0
- package/dist/runtime/sandbox.d.ts.map +1 -0
- package/dist/runtime/sandbox.js +481 -0
- package/dist/runtime/sandbox.js.map +1 -0
- package/dist/utils/agent-browser.d.ts +2 -120
- package/dist/utils/agent-browser.d.ts.map +1 -1
- package/dist/utils/agent-browser.js +13 -376
- package/dist/utils/agent-browser.js.map +1 -1
- package/dist/utils/project-name.d.ts +2 -2
- package/dist/utils/project-name.js +2 -2
- package/package.json +13 -22
- package/skills/d3k/SKILL.md +23 -1
- package/dist/commands/crawl.d.ts +0 -12
- package/dist/commands/crawl.d.ts.map +0 -1
- package/dist/commands/crawl.js +0 -101
- package/dist/commands/crawl.js.map +0 -1
- package/dist/commands/errors.d.ts +0 -14
- package/dist/commands/errors.d.ts.map +0 -1
- package/dist/commands/errors.js +0 -246
- package/dist/commands/errors.js.map +0 -1
- package/dist/commands/fix.d.ts +0 -13
- package/dist/commands/fix.d.ts.map +0 -1
- package/dist/commands/fix.js +0 -219
- package/dist/commands/fix.js.map +0 -1
- package/dist/commands/logs.d.ts +0 -13
- package/dist/commands/logs.d.ts.map +0 -1
- package/dist/commands/logs.js +0 -146
- package/dist/commands/logs.js.map +0 -1
- package/dist/commands/resume.d.ts +0 -11
- package/dist/commands/resume.d.ts.map +0 -1
- package/dist/commands/resume.js +0 -75
- package/dist/commands/resume.js.map +0 -1
- package/dist/commands/status.d.ts +0 -21
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/status.js +0 -44
- package/dist/commands/status.js.map +0 -1
- package/dist/components/AgentSelector.d.ts +0 -12
- package/dist/components/AgentSelector.d.ts.map +0 -1
- package/dist/components/AgentSelector.js +0 -99
- package/dist/components/AgentSelector.js.map +0 -1
- package/dist/components/PackageSelector.d.ts +0 -13
- package/dist/components/PackageSelector.d.ts.map +0 -1
- package/dist/components/PackageSelector.js +0 -79
- package/dist/components/PackageSelector.js.map +0 -1
- package/dist/constants/log-colors.d.ts +0 -43
- package/dist/constants/log-colors.d.ts.map +0 -1
- package/dist/constants/log-colors.js +0 -59
- package/dist/constants/log-colors.js.map +0 -1
- package/dist/dev-environment.d.ts +0 -187
- package/dist/dev-environment.d.ts.map +0 -1
- package/dist/dev-environment.js +0 -2248
- package/dist/dev-environment.js.map +0 -1
- package/dist/screencast-manager.d.ts +0 -78
- package/dist/screencast-manager.d.ts.map +0 -1
- package/dist/screencast-manager.js +0 -529
- package/dist/screencast-manager.js.map +0 -1
- package/dist/services/parsers/error-detectors/base.d.ts +0 -24
- package/dist/services/parsers/error-detectors/base.d.ts.map +0 -1
- package/dist/services/parsers/error-detectors/base.js +0 -44
- package/dist/services/parsers/error-detectors/base.js.map +0 -1
- package/dist/services/parsers/error-detectors/index.d.ts +0 -6
- package/dist/services/parsers/error-detectors/index.d.ts.map +0 -1
- package/dist/services/parsers/error-detectors/index.js +0 -6
- package/dist/services/parsers/error-detectors/index.js.map +0 -1
- package/dist/services/parsers/error-detectors/nextjs.d.ts +0 -9
- package/dist/services/parsers/error-detectors/nextjs.d.ts.map +0 -1
- package/dist/services/parsers/error-detectors/nextjs.js +0 -52
- package/dist/services/parsers/error-detectors/nextjs.js.map +0 -1
- package/dist/services/parsers/index.d.ts +0 -9
- package/dist/services/parsers/index.d.ts.map +0 -1
- package/dist/services/parsers/index.js +0 -10
- package/dist/services/parsers/index.js.map +0 -1
- package/dist/services/parsers/log-parsers/base.d.ts +0 -26
- package/dist/services/parsers/log-parsers/base.d.ts.map +0 -1
- package/dist/services/parsers/log-parsers/base.js +0 -6
- package/dist/services/parsers/log-parsers/base.js.map +0 -1
- package/dist/services/parsers/log-parsers/index.d.ts +0 -6
- package/dist/services/parsers/log-parsers/index.d.ts.map +0 -1
- package/dist/services/parsers/log-parsers/index.js +0 -5
- package/dist/services/parsers/log-parsers/index.js.map +0 -1
- package/dist/services/parsers/log-parsers/standard.d.ts +0 -9
- package/dist/services/parsers/log-parsers/standard.d.ts.map +0 -1
- package/dist/services/parsers/log-parsers/standard.js +0 -17
- package/dist/services/parsers/log-parsers/standard.js.map +0 -1
- package/dist/services/parsers/output-processor.d.ts +0 -39
- package/dist/services/parsers/output-processor.d.ts.map +0 -1
- package/dist/services/parsers/output-processor.js +0 -54
- package/dist/services/parsers/output-processor.js.map +0 -1
- package/dist/skills/d3k/internal-skill.md +0 -32
- package/dist/skills/index.d.ts +0 -54
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/skills/index.js +0 -214
- package/dist/skills/index.js.map +0 -1
- package/dist/src/tui-interface-impl.tsx +0 -644
- package/dist/tui-interface-impl.d.ts +0 -30
- package/dist/tui-interface-impl.d.ts.map +0 -1
- package/dist/tui-interface-impl.js +0 -403
- package/dist/tui-interface-impl.js.map +0 -1
- package/dist/tui-interface-opentui.d.ts +0 -33
- package/dist/tui-interface-opentui.d.ts.map +0 -1
- package/dist/tui-interface-opentui.js +0 -1128
- package/dist/tui-interface-opentui.js.map +0 -1
- package/dist/tui-interface.d.ts +0 -38
- package/dist/tui-interface.d.ts.map +0 -1
- package/dist/tui-interface.js +0 -84
- package/dist/tui-interface.js.map +0 -1
- package/dist/utils/agent-detection.d.ts +0 -17
- package/dist/utils/agent-detection.d.ts.map +0 -1
- package/dist/utils/agent-detection.js +0 -81
- package/dist/utils/agent-detection.js.map +0 -1
- package/dist/utils/agent-selection.d.ts +0 -25
- package/dist/utils/agent-selection.d.ts.map +0 -1
- package/dist/utils/agent-selection.js +0 -73
- package/dist/utils/agent-selection.js.map +0 -1
- package/dist/utils/browser-command-argv.d.ts +0 -19
- package/dist/utils/browser-command-argv.d.ts.map +0 -1
- package/dist/utils/browser-command-argv.js +0 -149
- package/dist/utils/browser-command-argv.js.map +0 -1
- package/dist/utils/build-version.d.ts +0 -2
- package/dist/utils/build-version.d.ts.map +0 -1
- package/dist/utils/build-version.js +0 -8
- package/dist/utils/build-version.js.map +0 -1
- package/dist/utils/cli-options.d.ts +0 -5
- package/dist/utils/cli-options.d.ts.map +0 -1
- package/dist/utils/cli-options.js +0 -36
- package/dist/utils/cli-options.js.map +0 -1
- package/dist/utils/d3k-dir.d.ts +0 -3
- package/dist/utils/d3k-dir.d.ts.map +0 -1
- package/dist/utils/d3k-dir.js +0 -12
- package/dist/utils/d3k-dir.js.map +0 -1
- package/dist/utils/d3k-skill-installer.d.ts +0 -4
- package/dist/utils/d3k-skill-installer.d.ts.map +0 -1
- package/dist/utils/d3k-skill-installer.js +0 -30
- package/dist/utils/d3k-skill-installer.js.map +0 -1
- package/dist/utils/log-filename.d.ts +0 -30
- package/dist/utils/log-filename.d.ts.map +0 -1
- package/dist/utils/log-filename.js +0 -60
- package/dist/utils/log-filename.js.map +0 -1
- package/dist/utils/project-detector.d.ts +0 -25
- package/dist/utils/project-detector.d.ts.map +0 -1
- package/dist/utils/project-detector.js +0 -153
- package/dist/utils/project-detector.js.map +0 -1
- package/dist/utils/project-metadata.d.ts +0 -4
- package/dist/utils/project-metadata.d.ts.map +0 -1
- package/dist/utils/project-metadata.js +0 -48
- package/dist/utils/project-metadata.js.map +0 -1
- package/dist/utils/repo-validate.d.ts +0 -2
- package/dist/utils/repo-validate.d.ts.map +0 -1
- package/dist/utils/repo-validate.js +0 -10
- package/dist/utils/repo-validate.js.map +0 -1
- package/dist/utils/session.d.ts +0 -19
- package/dist/utils/session.d.ts.map +0 -1
- package/dist/utils/session.js +0 -65
- package/dist/utils/session.js.map +0 -1
- package/dist/utils/skill-installer.d.ts +0 -65
- package/dist/utils/skill-installer.d.ts.map +0 -1
- package/dist/utils/skill-installer.js +0 -265
- package/dist/utils/skill-installer.js.map +0 -1
- package/dist/utils/timestamp.d.ts +0 -20
- package/dist/utils/timestamp.d.ts.map +0 -1
- package/dist/utils/timestamp.js +0 -57
- package/dist/utils/timestamp.js.map +0 -1
- package/dist/utils/tmux-helpers.d.ts +0 -36
- package/dist/utils/tmux-helpers.d.ts.map +0 -1
- package/dist/utils/tmux-helpers.js +0 -118
- package/dist/utils/tmux-helpers.js.map +0 -1
- package/dist/utils/user-config.d.ts +0 -12
- package/dist/utils/user-config.d.ts.map +0 -1
- package/dist/utils/user-config.js +0 -46
- package/dist/utils/user-config.js.map +0 -1
- package/dist/utils/version-check.d.ts +0 -52
- package/dist/utils/version-check.d.ts.map +0 -1
- package/dist/utils/version-check.js +0 -304
- package/dist/utils/version-check.js.map +0 -1
- package/src/tui-interface-impl.tsx +0 -644
|
@@ -1,1128 +0,0 @@
|
|
|
1
|
-
import { BoxRenderable, bold, createCliRenderer, cyan, dim, fg, green, MacOSScrollAccel, RGBA, ScrollBoxRenderable, StyledText, TextRenderable, t, yellow } from "@opentui/core";
|
|
2
|
-
import { spawnSync } from "child_process";
|
|
3
|
-
import { appendFileSync, createReadStream, mkdirSync, unwatchFile, watchFile, writeFileSync } from "fs";
|
|
4
|
-
import { tmpdir } from "os";
|
|
5
|
-
import { join } from "path";
|
|
6
|
-
import { LOG_COLORS } from "./constants/log-colors.js";
|
|
7
|
-
import { formatTimeDelta, parseTimestampToMs } from "./utils/timestamp.js";
|
|
8
|
-
// Compact ASCII logo for very small terminals
|
|
9
|
-
const COMPACT_LOGO = "d3k";
|
|
10
|
-
// Full ASCII logo lines
|
|
11
|
-
const FULL_LOGO = [" ▐▌▄▄▄▄ █ ▄ ", " ▐▌ █ █▄▀ ", "▗▞▀▜▌▀▀▀█ █ ▀▄ ", "▝▚▄▟▌▄▄▄█ █ █ "];
|
|
12
|
-
// Brand purple color
|
|
13
|
-
const BRAND_PURPLE = "#A18CE5";
|
|
14
|
-
// Type colors map
|
|
15
|
-
const TYPE_COLORS = {
|
|
16
|
-
NETWORK: LOG_COLORS.NETWORK,
|
|
17
|
-
ERROR: LOG_COLORS.ERROR,
|
|
18
|
-
WARNING: LOG_COLORS.WARNING,
|
|
19
|
-
INFO: LOG_COLORS.INFO,
|
|
20
|
-
LOG: LOG_COLORS.LOG,
|
|
21
|
-
DEBUG: LOG_COLORS.DEBUG,
|
|
22
|
-
SCREENSHOT: LOG_COLORS.SCREENSHOT,
|
|
23
|
-
DOM: LOG_COLORS.DOM,
|
|
24
|
-
CDP: LOG_COLORS.CDP,
|
|
25
|
-
CHROME: LOG_COLORS.CHROME,
|
|
26
|
-
CRASH: LOG_COLORS.CRASH,
|
|
27
|
-
EXIT: LOG_COLORS.EXIT,
|
|
28
|
-
REPLAY: LOG_COLORS.REPLAY,
|
|
29
|
-
NAVIGATION: LOG_COLORS.NAVIGATION,
|
|
30
|
-
INTERACTION: LOG_COLORS.INTERACTION,
|
|
31
|
-
GET: LOG_COLORS.SERVER,
|
|
32
|
-
POST: LOG_COLORS.SERVER,
|
|
33
|
-
PUT: LOG_COLORS.SERVER,
|
|
34
|
-
DELETE: LOG_COLORS.SERVER,
|
|
35
|
-
PATCH: LOG_COLORS.SERVER,
|
|
36
|
-
HEAD: LOG_COLORS.SERVER,
|
|
37
|
-
OPTIONS: LOG_COLORS.SERVER
|
|
38
|
-
};
|
|
39
|
-
// Color for delta timestamps
|
|
40
|
-
const DELTA_COLOR = "#FFD700"; // Gold/yellow for visibility
|
|
41
|
-
// Helper to format log line with StyledText
|
|
42
|
-
function formatLogLine(content, isCompact, baseTimestampMs, isBaseLog) {
|
|
43
|
-
const parts = content.match(/^\[(.*?)\] \[(.*?)\] (?:\[(.*?)\] )?(.*)$/);
|
|
44
|
-
if (parts) {
|
|
45
|
-
let [, timestamp, source, type, message] = parts;
|
|
46
|
-
// Extract HTTP method from SERVER logs
|
|
47
|
-
if (source === "SERVER" && !type && message) {
|
|
48
|
-
const methodMatch = message.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s/);
|
|
49
|
-
if (methodMatch) {
|
|
50
|
-
type = methodMatch[1];
|
|
51
|
-
message = message.slice(type.length + 1);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// Replace warning emoji
|
|
55
|
-
if (message && (type === "ERROR" || type === "WARNING")) {
|
|
56
|
-
message = message.replace(/\u26A0/g, "[!]");
|
|
57
|
-
}
|
|
58
|
-
const sourceColor = source === "BROWSER" ? LOG_COLORS.BROWSER : LOG_COLORS.SERVER;
|
|
59
|
-
const typeColor = TYPE_COLORS[type] || "#A0A0A0";
|
|
60
|
-
// Format timestamp - show delta if base timestamp is set
|
|
61
|
-
let displayTimestamp = timestamp;
|
|
62
|
-
// Track which style to use: 0 = dim (normal), 1 = delta color, 2 = delta bold (base log)
|
|
63
|
-
let timestampMode = 0;
|
|
64
|
-
if (baseTimestampMs !== undefined) {
|
|
65
|
-
const currentMs = parseTimestampToMs(timestamp);
|
|
66
|
-
if (currentMs !== null) {
|
|
67
|
-
if (isBaseLog) {
|
|
68
|
-
// The base log shows "BASE" indicator (padded to match delta width)
|
|
69
|
-
displayTimestamp = isCompact ? "BASE" : " BASE ";
|
|
70
|
-
timestampMode = 2;
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
// Other logs show delta from base
|
|
74
|
-
const delta = formatTimeDelta(currentMs - baseTimestampMs);
|
|
75
|
-
displayTimestamp = isCompact ? delta : `${delta.padStart(10)} `;
|
|
76
|
-
timestampMode = 1;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// Always show full timestamp (HH:MM:SS.mmm) - no truncation
|
|
81
|
-
// Build styled timestamp based on mode
|
|
82
|
-
const styledTimestamp = timestampMode === 2
|
|
83
|
-
? bold(fg(DELTA_COLOR)(displayTimestamp))
|
|
84
|
-
: timestampMode === 1
|
|
85
|
-
? fg(DELTA_COLOR)(displayTimestamp)
|
|
86
|
-
: dim(displayTimestamp);
|
|
87
|
-
// Add leading space to account for scroll indicator overlap
|
|
88
|
-
const pad = " ";
|
|
89
|
-
if (isCompact) {
|
|
90
|
-
const sourceChar = source.charAt(0);
|
|
91
|
-
if (type) {
|
|
92
|
-
const typeChar = type.charAt(0);
|
|
93
|
-
// Compact mode: single char tags with space after timestamp
|
|
94
|
-
return t `${pad}${styledTimestamp} ${fg(sourceColor)(`[${sourceChar}]`)}${fg(typeColor)(`[${typeChar}]`)} ${message || ""}`;
|
|
95
|
-
}
|
|
96
|
-
return t `${pad}${styledTimestamp} ${fg(sourceColor)(`[${sourceChar}]`)} ${message || ""}`;
|
|
97
|
-
}
|
|
98
|
-
if (type) {
|
|
99
|
-
return t `${pad}${styledTimestamp} ${bold(fg(sourceColor)(`[${source}]`))} ${fg(typeColor)(`[${type}]`)} ${message || ""}`;
|
|
100
|
-
}
|
|
101
|
-
return t `${pad}${styledTimestamp} ${bold(fg(sourceColor)(`[${source}]`))} ${message || ""}`;
|
|
102
|
-
}
|
|
103
|
-
// Fallback for unparseable lines - still add padding
|
|
104
|
-
return t ` ${content}`;
|
|
105
|
-
}
|
|
106
|
-
export function applySelectionToStyledText(content, bgColor, fgColor) {
|
|
107
|
-
return new StyledText(content.chunks.map((chunk) => ({
|
|
108
|
-
...chunk,
|
|
109
|
-
bg: bgColor,
|
|
110
|
-
fg: fgColor ?? chunk.fg
|
|
111
|
-
})));
|
|
112
|
-
}
|
|
113
|
-
class D3kTUI {
|
|
114
|
-
renderer = null;
|
|
115
|
-
options;
|
|
116
|
-
logs = [];
|
|
117
|
-
logIdCounter = 0;
|
|
118
|
-
clearFromLogId = 0;
|
|
119
|
-
maxLogs = 1000;
|
|
120
|
-
// UI Components
|
|
121
|
-
headerBox = null;
|
|
122
|
-
logsScrollBox = null;
|
|
123
|
-
logsContainer = null;
|
|
124
|
-
statusText = null;
|
|
125
|
-
// State
|
|
126
|
-
appPort;
|
|
127
|
-
appUrl;
|
|
128
|
-
useHttps;
|
|
129
|
-
portConfirmed = false;
|
|
130
|
-
updateInfo;
|
|
131
|
-
initStatus = null;
|
|
132
|
-
ctrlCMessage = "";
|
|
133
|
-
ctrlCTimeout = null;
|
|
134
|
-
// Base timestamp for delta display (set via keyboard, not mouse click)
|
|
135
|
-
baseTimestampMs = undefined;
|
|
136
|
-
baseLogId = undefined;
|
|
137
|
-
// Debug mode for tracking selection/focus issues
|
|
138
|
-
debugMode = false;
|
|
139
|
-
lastFocusedId = null;
|
|
140
|
-
debugLogFile = join(process.env.HOME || tmpdir(), ".d3k", "tui-debug.log");
|
|
141
|
-
selectionDebugEnabled = process.env.D3K_TUI_SELECTION_DEBUG !== "0";
|
|
142
|
-
selectionDebugLogFile = join(process.env.HOME || tmpdir(), ".d3k", "tui-selection-debug.log");
|
|
143
|
-
lastCopiedSelection = null;
|
|
144
|
-
selectionCopyHandler = null;
|
|
145
|
-
isCompact = false;
|
|
146
|
-
isVeryCompact = false;
|
|
147
|
-
isRebuilding = false;
|
|
148
|
-
logContentWidth = 80;
|
|
149
|
-
logRenderables = new Map();
|
|
150
|
-
logBaseContent = new Map();
|
|
151
|
-
logSelection = null;
|
|
152
|
-
logSelectionBackground = RGBA.fromInts(70, 130, 180);
|
|
153
|
-
logSelectionForeground = RGBA.fromInts(255, 255, 255);
|
|
154
|
-
constructor(options) {
|
|
155
|
-
this.options = options;
|
|
156
|
-
this.appPort = options.appPort;
|
|
157
|
-
this.appUrl = options.appUrl || null;
|
|
158
|
-
this.useHttps = options.useHttps || false;
|
|
159
|
-
this.updateInfo = options.updateInfo || null;
|
|
160
|
-
}
|
|
161
|
-
async start() {
|
|
162
|
-
const config = {
|
|
163
|
-
useMouse: true,
|
|
164
|
-
exitOnCtrlC: false,
|
|
165
|
-
// @opentui/core 0.4 replaced `useAlternateScreen: true` with `screenMode`.
|
|
166
|
-
screenMode: "alternate-screen",
|
|
167
|
-
targetFps: 20,
|
|
168
|
-
maxFps: 30
|
|
169
|
-
};
|
|
170
|
-
// Filter out OpenTUI's terminal detection messages from stdout and stderr
|
|
171
|
-
// These messages like "info(terminal): Terminal detected..." interfere with TUI rendering
|
|
172
|
-
const createFilteredWrite = (originalWrite) => {
|
|
173
|
-
return function (chunk, encodingOrCallback, callback) {
|
|
174
|
-
const str = typeof chunk === "string" ? chunk : chunk.toString();
|
|
175
|
-
if (str.includes("info(terminal)") || str.includes("Terminal detected")) {
|
|
176
|
-
return true; // Silently ignore
|
|
177
|
-
}
|
|
178
|
-
if (typeof encodingOrCallback === "function") {
|
|
179
|
-
return originalWrite(chunk, encodingOrCallback);
|
|
180
|
-
}
|
|
181
|
-
return originalWrite(chunk, encodingOrCallback, callback);
|
|
182
|
-
};
|
|
183
|
-
};
|
|
184
|
-
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
185
|
-
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
186
|
-
process.stdout.write = createFilteredWrite(originalStdoutWrite);
|
|
187
|
-
process.stderr.write = createFilteredWrite(originalStderrWrite);
|
|
188
|
-
this.initializeSelectionDebugLog();
|
|
189
|
-
this.renderer = await createCliRenderer(config);
|
|
190
|
-
// Setup UI after renderer is created
|
|
191
|
-
this.setupUI();
|
|
192
|
-
this.setupKeyboardHandlers();
|
|
193
|
-
this.setupFocusTracking();
|
|
194
|
-
this.setupSelectionCopy();
|
|
195
|
-
this.renderer.start();
|
|
196
|
-
// Delay log file watcher start to allow layout to compute valid positions
|
|
197
|
-
// Without this delay, initial logs get NaN X positions and selection fails
|
|
198
|
-
setTimeout(() => {
|
|
199
|
-
this.startLogFileWatcher();
|
|
200
|
-
}, 100);
|
|
201
|
-
// Force a redraw after start to ensure borders render correctly
|
|
202
|
-
process.stdout.write("\x1b[2J\x1b[H");
|
|
203
|
-
this.renderer.requestRender();
|
|
204
|
-
setTimeout(() => {
|
|
205
|
-
this.logSelectionDebug(`[START] renderer=${this.renderer?.width}x${this.renderer?.height} logContentWidth=${this.logContentWidth}`);
|
|
206
|
-
this.logLogPaneMetrics("post-start");
|
|
207
|
-
}, 150);
|
|
208
|
-
return {
|
|
209
|
-
app: { unmount: () => this.shutdown() },
|
|
210
|
-
updateStatus: (status) => this.setStatus(status),
|
|
211
|
-
updateAppPort: (port) => this.setAppPort(port),
|
|
212
|
-
updateAppUrl: (url) => this.setAppUrl(url),
|
|
213
|
-
updateUpdateInfo: (info) => this.setUpdateInfo(info),
|
|
214
|
-
updateUseHttps: (useHttps) => this.setUseHttps(useHttps)
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
setupUI() {
|
|
218
|
-
if (!this.renderer)
|
|
219
|
-
return;
|
|
220
|
-
const { width, height } = this.renderer;
|
|
221
|
-
const isCompact = width < 80;
|
|
222
|
-
const isVeryCompact = width < 60 || height < 15;
|
|
223
|
-
this.logContentWidth = Math.max(20, width - 6);
|
|
224
|
-
this.isCompact = isCompact;
|
|
225
|
-
this.isVeryCompact = isVeryCompact;
|
|
226
|
-
// Main container
|
|
227
|
-
const mainContainer = new BoxRenderable(this.renderer, {
|
|
228
|
-
id: "main",
|
|
229
|
-
width: "100%",
|
|
230
|
-
height: "100%",
|
|
231
|
-
flexDirection: "column"
|
|
232
|
-
});
|
|
233
|
-
this.renderer.root.add(mainContainer);
|
|
234
|
-
// Header
|
|
235
|
-
this.headerBox = this.createHeader(isCompact, isVeryCompact);
|
|
236
|
-
mainContainer.add(this.headerBox);
|
|
237
|
-
// Logs section (scrollable)
|
|
238
|
-
const logsSection = new BoxRenderable(this.renderer, {
|
|
239
|
-
id: "logs-section",
|
|
240
|
-
flexGrow: 1,
|
|
241
|
-
flexDirection: "column",
|
|
242
|
-
border: true,
|
|
243
|
-
borderStyle: "single",
|
|
244
|
-
borderColor: "#808080",
|
|
245
|
-
paddingRight: 1
|
|
246
|
-
});
|
|
247
|
-
mainContainer.add(logsSection);
|
|
248
|
-
// Scrollable logs container
|
|
249
|
-
this.logsScrollBox = new ScrollBoxRenderable(this.renderer, {
|
|
250
|
-
id: "logs-scroll",
|
|
251
|
-
flexGrow: 1,
|
|
252
|
-
scrollY: true,
|
|
253
|
-
scrollX: false,
|
|
254
|
-
stickyScroll: true,
|
|
255
|
-
stickyStart: "bottom",
|
|
256
|
-
scrollAcceleration: new MacOSScrollAccel({ maxMultiplier: 8 }),
|
|
257
|
-
viewportCulling: false, // Disable culling to ensure all items are in hit grid
|
|
258
|
-
onMouse: (event) => {
|
|
259
|
-
// Log ALL mouse events to understand the flow
|
|
260
|
-
if (event.type === "down" || event.type === "up") {
|
|
261
|
-
const target = event.target;
|
|
262
|
-
const targetId = target?.id || "(none)";
|
|
263
|
-
const targetX = target?.x ?? "?";
|
|
264
|
-
const targetY = target?.y ?? "?";
|
|
265
|
-
const targetW = target?.width ?? "?";
|
|
266
|
-
const targetH = target?.height ?? "?";
|
|
267
|
-
this.debugLog(`[MOUSE_${event.type.toUpperCase()}] screen(${event.x},${event.y}) target=${targetId} pos(${targetX},${targetY}) size(${targetW}x${targetH}) selectable=${target?.selectable} hasSelection=${this.renderer?.hasSelection}`);
|
|
268
|
-
this.logSelectionDebug(`[MOUSE_${event.type.toUpperCase()}] screen(${event.x},${event.y}) target=${targetId} pos(${targetX},${targetY}) size(${targetW}x${targetH}) selectable=${target?.selectable} ${this.describeSelection()}`);
|
|
269
|
-
}
|
|
270
|
-
},
|
|
271
|
-
onMouseDown: (event) => {
|
|
272
|
-
if (event.button === 0) {
|
|
273
|
-
const logLine = this.getSelectionLogLine(event);
|
|
274
|
-
if (logLine) {
|
|
275
|
-
this.logSelectionDebug(`[START_SELECTION] target=${logLine.id} pos(${logLine.x},${logLine.y}) size(${logLine.width}x${logLine.height}) screen(${event.x},${event.y})`);
|
|
276
|
-
this.beginLogSelection(logLine.id, event.x, event.y);
|
|
277
|
-
this.logSelectionDebug(`[START_SELECTION_AFTER] ${this.describeSelection()}`);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const target = event.target;
|
|
281
|
-
const targetId = target?.id || "(none)";
|
|
282
|
-
const selection = this.renderer?.getSelection();
|
|
283
|
-
this.debugLog(`[MOUSE_DOWN_CB] x=${event.x}, y=${event.y}, target=${targetId}, hasSelection=${this.renderer?.hasSelection}, isDragging=${selection?.isDragging}`);
|
|
284
|
-
this.logSelectionDebug(`[MOUSE_DOWN_CB] x=${event.x}, y=${event.y}, target=${targetId} ${this.describeSelection()}`);
|
|
285
|
-
},
|
|
286
|
-
onMouseUp: (event) => {
|
|
287
|
-
if (event.button === 0) {
|
|
288
|
-
const logLine = this.getSelectionLogLine(event);
|
|
289
|
-
this.finishLogSelection(logLine?.id ?? null, event.x, event.y);
|
|
290
|
-
this.logSelectionDebug(`[FINISH_SELECTION] target=${logLine?.id || "(none)"} screen(${event.x},${event.y}) ${this.describeSelection()}`);
|
|
291
|
-
}
|
|
292
|
-
const selectedText = this.getSelectedLogText() || "(none)";
|
|
293
|
-
this.debugLog(`[MOUSE_UP] x=${event.x}, y=${event.y}, hasSelection=${Boolean(this.logSelection)}, text="${selectedText.slice(0, 30)}"`);
|
|
294
|
-
this.logSelectionDebug(`[MOUSE_UP] x=${event.x}, y=${event.y}, text="${selectedText.slice(0, 60)}" ${this.describeSelection()}`);
|
|
295
|
-
},
|
|
296
|
-
onMouseDrag: (event) => {
|
|
297
|
-
if (this.logSelection) {
|
|
298
|
-
const logLine = this.getSelectionLogLine(event);
|
|
299
|
-
this.updateLogSelection(logLine?.id ?? null, event.x, event.y);
|
|
300
|
-
this.logSelectionDebug(`[UPDATE_SELECTION] target=${logLine?.id || "(none)"} screen(${event.x},${event.y}) ${this.describeSelection()}`);
|
|
301
|
-
}
|
|
302
|
-
const target = event.target;
|
|
303
|
-
const targetId = target?.id || "(none)";
|
|
304
|
-
this.debugLog(`[MOUSE_DRAG] x=${event.x}, y=${event.y}, target=${targetId}, isDragging=${event.isDragging}`);
|
|
305
|
-
this.logSelectionDebug(`[MOUSE_DRAG] x=${event.x}, y=${event.y}, target=${targetId}, eventDragging=${event.isDragging} ${this.describeSelection()}`);
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
this.logsScrollBox.content.flexDirection = "column";
|
|
309
|
-
this.logsScrollBox.content.left = 0;
|
|
310
|
-
this.logsScrollBox.content.top = 0;
|
|
311
|
-
this.logsScrollBox.content.width = this.logContentWidth;
|
|
312
|
-
logsSection.add(this.logsScrollBox);
|
|
313
|
-
// Track focus changes on scroll box
|
|
314
|
-
this.logsScrollBox.on("focused", () => {
|
|
315
|
-
this.debugLog(`[FOCUS] logsScrollBox focused`);
|
|
316
|
-
this.logSelectionDebug("[FOCUS] logsScrollBox focused");
|
|
317
|
-
});
|
|
318
|
-
this.logsScrollBox.on("blurred", () => {
|
|
319
|
-
this.debugLog(`[FOCUS] logsScrollBox blurred`);
|
|
320
|
-
this.logSelectionDebug("[FOCUS] logsScrollBox blurred");
|
|
321
|
-
});
|
|
322
|
-
// Container for log lines inside scroll box - provides valid X positions
|
|
323
|
-
this.logsContainer = new BoxRenderable(this.renderer, {
|
|
324
|
-
id: "logs-content",
|
|
325
|
-
flexDirection: "column",
|
|
326
|
-
width: this.logContentWidth
|
|
327
|
-
});
|
|
328
|
-
this.logsScrollBox.add(this.logsContainer);
|
|
329
|
-
// Bottom status line - show "agent has access" hint in both modes
|
|
330
|
-
const statusLine = new BoxRenderable(this.renderer, {
|
|
331
|
-
id: "status-line",
|
|
332
|
-
height: 1,
|
|
333
|
-
flexDirection: "row",
|
|
334
|
-
justifyContent: "space-between",
|
|
335
|
-
paddingLeft: 1,
|
|
336
|
-
paddingRight: 1
|
|
337
|
-
});
|
|
338
|
-
mainContainer.add(statusLine);
|
|
339
|
-
// Help text (left side) - always show agent access hint
|
|
340
|
-
const helpText = new TextRenderable(this.renderer, {
|
|
341
|
-
id: "help-text",
|
|
342
|
-
content: t `${dim("<- agent has access to ↑")}`
|
|
343
|
-
});
|
|
344
|
-
statusLine.add(helpText);
|
|
345
|
-
// Status info (right side) - only in non-compact mode
|
|
346
|
-
if (!isCompact) {
|
|
347
|
-
this.statusText = new TextRenderable(this.renderer, {
|
|
348
|
-
id: "status-text",
|
|
349
|
-
content: this.buildStatusContent(false),
|
|
350
|
-
marginLeft: "auto"
|
|
351
|
-
});
|
|
352
|
-
statusLine.add(this.statusText);
|
|
353
|
-
}
|
|
354
|
-
else {
|
|
355
|
-
this.statusText = null;
|
|
356
|
-
}
|
|
357
|
-
// Handle resize - only rebuild when compact mode changes
|
|
358
|
-
this.renderer.root.onSizeChange = () => {
|
|
359
|
-
if (!this.renderer || this.isRebuilding)
|
|
360
|
-
return;
|
|
361
|
-
this.logContentWidth = Math.max(20, this.renderer.width - 6);
|
|
362
|
-
const nextCompact = this.renderer.width < 80;
|
|
363
|
-
const nextVeryCompact = this.renderer.width < 60 || this.renderer.height < 15;
|
|
364
|
-
if (nextCompact !== this.isCompact || nextVeryCompact !== this.isVeryCompact) {
|
|
365
|
-
this.rebuildUI();
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
if (this.logsContainer) {
|
|
369
|
-
this.logsContainer.width = this.logContentWidth;
|
|
370
|
-
}
|
|
371
|
-
if (this.logsScrollBox) {
|
|
372
|
-
this.logsScrollBox.content.left = 0;
|
|
373
|
-
this.logsScrollBox.content.top = 0;
|
|
374
|
-
this.logsScrollBox.content.width = this.logContentWidth;
|
|
375
|
-
}
|
|
376
|
-
for (const child of this.logsContainer?.getChildren() || []) {
|
|
377
|
-
if (child instanceof TextRenderable) {
|
|
378
|
-
child.width = this.logContentWidth;
|
|
379
|
-
child.left = 0;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
this.renderer.requestRender();
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
createHeader(isCompact, isVeryCompact) {
|
|
386
|
-
if (!this.renderer)
|
|
387
|
-
throw new Error("Renderer not initialized");
|
|
388
|
-
// In compact mode, no box border - just logo and version
|
|
389
|
-
if (isCompact || isVeryCompact) {
|
|
390
|
-
const headerBox = new BoxRenderable(this.renderer, {
|
|
391
|
-
id: "header",
|
|
392
|
-
flexDirection: "row",
|
|
393
|
-
paddingLeft: 1,
|
|
394
|
-
height: 1
|
|
395
|
-
});
|
|
396
|
-
// Only: logo and version
|
|
397
|
-
const logoText = new TextRenderable(this.renderer, {
|
|
398
|
-
id: "logo-compact",
|
|
399
|
-
content: t `${bold(fg(BRAND_PURPLE)(COMPACT_LOGO))}${dim(` v${this.options.version}`)}`
|
|
400
|
-
});
|
|
401
|
-
headerBox.add(logoText);
|
|
402
|
-
return headerBox;
|
|
403
|
-
}
|
|
404
|
-
// Full mode: bordered box with ASCII logo and info
|
|
405
|
-
const headerBox = new BoxRenderable(this.renderer, {
|
|
406
|
-
id: "header",
|
|
407
|
-
border: true,
|
|
408
|
-
borderStyle: "single",
|
|
409
|
-
borderColor: BRAND_PURPLE,
|
|
410
|
-
paddingLeft: 1,
|
|
411
|
-
paddingRight: 1,
|
|
412
|
-
flexDirection: "row",
|
|
413
|
-
alignItems: "flex-start",
|
|
414
|
-
height: 6
|
|
415
|
-
});
|
|
416
|
-
// ASCII logo column
|
|
417
|
-
const logoCol = new BoxRenderable(this.renderer, {
|
|
418
|
-
id: "logo-col",
|
|
419
|
-
flexDirection: "column",
|
|
420
|
-
marginRight: 2
|
|
421
|
-
});
|
|
422
|
-
headerBox.add(logoCol);
|
|
423
|
-
for (const line of FULL_LOGO) {
|
|
424
|
-
const logoLine = new TextRenderable(this.renderer, {
|
|
425
|
-
content: t `${bold(fg(BRAND_PURPLE)(line))}`
|
|
426
|
-
});
|
|
427
|
-
logoCol.add(logoLine);
|
|
428
|
-
}
|
|
429
|
-
// Info column
|
|
430
|
-
const infoCol = new BoxRenderable(this.renderer, {
|
|
431
|
-
id: "info-col",
|
|
432
|
-
flexDirection: "column",
|
|
433
|
-
flexGrow: 1
|
|
434
|
-
});
|
|
435
|
-
headerBox.add(infoCol);
|
|
436
|
-
const protocol = this.useHttps ? "https" : "http";
|
|
437
|
-
const portStatus = this.portConfirmed ? "" : " ...";
|
|
438
|
-
const displayedAppUrl = this.appUrl || `${protocol}://localhost:${this.appPort}`;
|
|
439
|
-
const appLine = new TextRenderable(this.renderer, {
|
|
440
|
-
id: "app-url",
|
|
441
|
-
content: this.portConfirmed
|
|
442
|
-
? t `${cyan(`🌐 App: ${displayedAppUrl}`)}`
|
|
443
|
-
: t `${cyan(`🌐 App: ${displayedAppUrl}`)}${yellow(portStatus)}`
|
|
444
|
-
});
|
|
445
|
-
infoCol.add(appLine);
|
|
446
|
-
// Show logs path with ~/ instead of full home path
|
|
447
|
-
const logsPath = this.options.logFile.replace(process.env.HOME || "", "~");
|
|
448
|
-
const logsLine = new TextRenderable(this.renderer, {
|
|
449
|
-
id: "logs-file",
|
|
450
|
-
content: t `${cyan(`📋 Logs: ${logsPath}`)}`
|
|
451
|
-
});
|
|
452
|
-
infoCol.add(logsLine);
|
|
453
|
-
if (this.options.serversOnly) {
|
|
454
|
-
const serversLine = new TextRenderable(this.renderer, {
|
|
455
|
-
id: "servers-only",
|
|
456
|
-
content: t `${cyan("🖥️ Servers-only mode")}`
|
|
457
|
-
});
|
|
458
|
-
infoCol.add(serversLine);
|
|
459
|
-
}
|
|
460
|
-
if (this.initStatus) {
|
|
461
|
-
const statusLine = new TextRenderable(this.renderer, {
|
|
462
|
-
id: "init-status",
|
|
463
|
-
content: t `${bold(fg(BRAND_PURPLE)(this.initStatus))}`
|
|
464
|
-
});
|
|
465
|
-
infoCol.add(statusLine);
|
|
466
|
-
}
|
|
467
|
-
return headerBox;
|
|
468
|
-
}
|
|
469
|
-
buildStatusContent(isCompact) {
|
|
470
|
-
const parts = [];
|
|
471
|
-
if (isCompact) {
|
|
472
|
-
const cleanName = this.options.projectName?.replace(/-[a-f0-9]{6}$/, "") || "d3k";
|
|
473
|
-
parts.push(cleanName);
|
|
474
|
-
}
|
|
475
|
-
if (this.updateInfo?.type === "available") {
|
|
476
|
-
return t `${yellow(`↑ v${this.updateInfo.latestVersion} available (d3k upgrade)`)}`;
|
|
477
|
-
}
|
|
478
|
-
if (this.updateInfo?.type === "updated") {
|
|
479
|
-
return t `${green(`✓ Updated to v${this.updateInfo.newVersion}`)}`;
|
|
480
|
-
}
|
|
481
|
-
if (this.ctrlCMessage) {
|
|
482
|
-
return t `${fg(BRAND_PURPLE)(this.ctrlCMessage)}`;
|
|
483
|
-
}
|
|
484
|
-
if (parts.length > 0) {
|
|
485
|
-
return t `${fg(BRAND_PURPLE)(parts.join(" "))}`;
|
|
486
|
-
}
|
|
487
|
-
return t ``;
|
|
488
|
-
}
|
|
489
|
-
rebuildUI() {
|
|
490
|
-
if (!this.renderer)
|
|
491
|
-
return;
|
|
492
|
-
if (this.isRebuilding)
|
|
493
|
-
return;
|
|
494
|
-
this.isRebuilding = true;
|
|
495
|
-
// Clear and rebuild - OpenTUI handles resize
|
|
496
|
-
const root = this.renderer.root;
|
|
497
|
-
for (const child of root.getChildren()) {
|
|
498
|
-
child.destroy();
|
|
499
|
-
}
|
|
500
|
-
this.setupUI();
|
|
501
|
-
// Delay log refresh to allow layout to compute valid positions
|
|
502
|
-
// Without this delay, logs get NaN X positions and selection fails
|
|
503
|
-
setTimeout(() => {
|
|
504
|
-
this.refreshLogs();
|
|
505
|
-
this.isRebuilding = false;
|
|
506
|
-
}, 100);
|
|
507
|
-
}
|
|
508
|
-
setupKeyboardHandlers() {
|
|
509
|
-
if (!this.renderer)
|
|
510
|
-
return;
|
|
511
|
-
let ctrlCPending = false;
|
|
512
|
-
this.renderer.keyInput.on("keypress", (key) => {
|
|
513
|
-
// Ctrl+Shift+C or Cmd+C: copy selection to clipboard
|
|
514
|
-
if ((key.ctrl && key.shift && key.name === "c") || (key.meta && key.name === "c")) {
|
|
515
|
-
this.copySelectionToClipboard();
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
// Handle Ctrl+C with double-tap protection
|
|
519
|
-
if (key.ctrl && key.name === "c") {
|
|
520
|
-
if (ctrlCPending) {
|
|
521
|
-
// Second Ctrl+C - call shutdown callback directly
|
|
522
|
-
// This bypasses signal handling which can be unreliable in TUI mode
|
|
523
|
-
if (this.options.onRequestShutdown) {
|
|
524
|
-
this.options.onRequestShutdown();
|
|
525
|
-
}
|
|
526
|
-
else {
|
|
527
|
-
// Fallback to signal if no callback provided
|
|
528
|
-
process.emit("SIGINT");
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
// First Ctrl+C - show warning
|
|
533
|
-
ctrlCPending = true;
|
|
534
|
-
this.ctrlCMessage = "⚠️ ^C again to quit";
|
|
535
|
-
this.updateStatusDisplay();
|
|
536
|
-
setTimeout(() => {
|
|
537
|
-
ctrlCPending = false;
|
|
538
|
-
this.ctrlCMessage = "";
|
|
539
|
-
this.updateStatusDisplay();
|
|
540
|
-
}, 3000);
|
|
541
|
-
}
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
// Handle Ctrl+L to clear logs
|
|
545
|
-
if (key.ctrl && key.name === "l") {
|
|
546
|
-
this.clearFromLogId = this.logs.length > 0 ? this.logs[this.logs.length - 1].id : this.logIdCounter;
|
|
547
|
-
this.logsScrollBox?.scrollTo({ x: 0, y: 0 });
|
|
548
|
-
this.refreshLogs();
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
// Handle Escape to clear base timestamp (exit delta mode)
|
|
552
|
-
if (key.name === "escape") {
|
|
553
|
-
if (this.baseTimestampMs !== undefined) {
|
|
554
|
-
this.baseTimestampMs = undefined;
|
|
555
|
-
this.baseLogId = undefined;
|
|
556
|
-
this.refreshLogs();
|
|
557
|
-
}
|
|
558
|
-
this.clearLogSelection();
|
|
559
|
-
this.renderer?.clearSelection();
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
// Debug: Shift+D to toggle debug mode (verbose logging to file)
|
|
563
|
-
if (key.name === "d" && key.shift && !key.ctrl) {
|
|
564
|
-
this.debugMode = !this.debugMode;
|
|
565
|
-
if (this.debugMode) {
|
|
566
|
-
// Ensure directory exists and clear the debug log file when enabling
|
|
567
|
-
try {
|
|
568
|
-
mkdirSync(join(process.env.HOME || tmpdir(), ".d3k"), { recursive: true });
|
|
569
|
-
writeFileSync(this.debugLogFile, `Debug mode enabled at ${new Date().toISOString()}\n`);
|
|
570
|
-
}
|
|
571
|
-
catch {
|
|
572
|
-
// Ignore write errors
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
const entry = {
|
|
576
|
-
id: ++this.logIdCounter,
|
|
577
|
-
content: `[DEBUG] Debug mode ${this.debugMode ? `ENABLED - logging to ${this.debugLogFile}` : "DISABLED"}`
|
|
578
|
-
};
|
|
579
|
-
this.logs.push(entry);
|
|
580
|
-
this.addLogLines([entry]);
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
// Debug: Press 'd' to check selection state and focus info (shows in UI and writes to file)
|
|
584
|
-
if (key.name === "d" && !key.ctrl && !key.shift) {
|
|
585
|
-
const selection = this.renderer?.getSelection();
|
|
586
|
-
const hasSelection = this.renderer?.hasSelection;
|
|
587
|
-
const selectedText = selection?.getSelectedText() || "(none)";
|
|
588
|
-
const focusedRenderable = this.renderer?.currentFocusedRenderable;
|
|
589
|
-
const focusedId = focusedRenderable?.id || "(none)";
|
|
590
|
-
const selectionContainer = this.renderer?.getSelectionContainer();
|
|
591
|
-
const containerId = selectionContainer?.id || "(none)";
|
|
592
|
-
const message = `hasSelection: ${hasSelection}, isDragging: ${selection?.isDragging}, renderables: ${selection?.selectedRenderables?.length ?? 0}, focus: ${focusedId}, container: ${containerId}, text: "${selectedText.slice(0, 30)}"`;
|
|
593
|
-
// Write to file if debug mode is on
|
|
594
|
-
this.debugLog(`[SELECTION_STATE] ${message}`);
|
|
595
|
-
// Always show in UI
|
|
596
|
-
const debugEntry = {
|
|
597
|
-
id: ++this.logIdCounter,
|
|
598
|
-
content: `[DEBUG] ${message}`
|
|
599
|
-
};
|
|
600
|
-
this.logs.push(debugEntry);
|
|
601
|
-
this.addLogLines([debugEntry]);
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
// Keyboard scrolling (also works alongside mouse scroll)
|
|
605
|
-
if (this.logsScrollBox) {
|
|
606
|
-
if (key.name === "up") {
|
|
607
|
-
this.logsScrollBox.scrollBy(-1);
|
|
608
|
-
}
|
|
609
|
-
else if (key.name === "down") {
|
|
610
|
-
this.logsScrollBox.scrollBy(1);
|
|
611
|
-
}
|
|
612
|
-
else if (key.name === "pageup") {
|
|
613
|
-
this.logsScrollBox.scrollBy(-10);
|
|
614
|
-
}
|
|
615
|
-
else if (key.name === "pagedown") {
|
|
616
|
-
this.logsScrollBox.scrollBy(10);
|
|
617
|
-
}
|
|
618
|
-
else if (key.name === "g" && !key.shift) {
|
|
619
|
-
// g - go to top
|
|
620
|
-
this.logsScrollBox.scrollTo({ x: 0, y: 0 });
|
|
621
|
-
}
|
|
622
|
-
else if (key.name === "g" && key.shift) {
|
|
623
|
-
// G - go to bottom
|
|
624
|
-
this.logsScrollBox.scrollTo({ x: 0, y: this.logsScrollBox.scrollHeight });
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
setupFocusTracking() {
|
|
630
|
-
if (!this.renderer)
|
|
631
|
-
return;
|
|
632
|
-
// Track focus changes via frame callback (since there's no global focus event)
|
|
633
|
-
this.renderer.setFrameCallback(async () => {
|
|
634
|
-
if (!this.debugMode || !this.renderer)
|
|
635
|
-
return;
|
|
636
|
-
const focused = this.renderer.currentFocusedRenderable;
|
|
637
|
-
const currentId = focused?.id || null;
|
|
638
|
-
if (currentId !== this.lastFocusedId) {
|
|
639
|
-
this.debugLog(`[FOCUS_CHANGE] ${this.lastFocusedId || "(none)"} -> ${currentId || "(none)"}`);
|
|
640
|
-
this.lastFocusedId = currentId;
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
setupSelectionCopy() {
|
|
645
|
-
if (!this.renderer || this.selectionCopyHandler)
|
|
646
|
-
return;
|
|
647
|
-
this.selectionCopyHandler = (selection) => {
|
|
648
|
-
if (!selection) {
|
|
649
|
-
this.lastCopiedSelection = null;
|
|
650
|
-
this.logSelectionDebug("[SELECTION_EVENT] selection cleared");
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
this.logSelectionDebug(`[SELECTION_EVENT] ${this.describeSelection(selection)}`);
|
|
654
|
-
if (selection.isDragging || !selection.isActive)
|
|
655
|
-
return;
|
|
656
|
-
const text = selection.getSelectedText();
|
|
657
|
-
if (!text || text.trim().length === 0)
|
|
658
|
-
return;
|
|
659
|
-
if (text === this.lastCopiedSelection)
|
|
660
|
-
return;
|
|
661
|
-
this.lastCopiedSelection = text;
|
|
662
|
-
this.copyToClipboard(text);
|
|
663
|
-
this.logSelectionDebug(`[SELECTION_COPY] length=${text.length} text="${text.slice(0, 120)}"`);
|
|
664
|
-
};
|
|
665
|
-
this.renderer.on("selection", this.selectionCopyHandler);
|
|
666
|
-
}
|
|
667
|
-
copySelectionToClipboard() {
|
|
668
|
-
const text = this.getSelectedLogText() || this.renderer?.getSelection()?.getSelectedText();
|
|
669
|
-
if (!text || text.trim().length === 0)
|
|
670
|
-
return;
|
|
671
|
-
this.lastCopiedSelection = text;
|
|
672
|
-
this.copyToClipboard(text);
|
|
673
|
-
}
|
|
674
|
-
copyToClipboard(text) {
|
|
675
|
-
if (process.platform === "darwin") {
|
|
676
|
-
try {
|
|
677
|
-
spawnSync("pbcopy", { input: text });
|
|
678
|
-
}
|
|
679
|
-
catch (error) {
|
|
680
|
-
this.debugLog(`[CLIPBOARD] Failed to copy selection: ${String(error)}`);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
startLogFileWatcher() {
|
|
685
|
-
let buffer = "";
|
|
686
|
-
let pendingLogs = [];
|
|
687
|
-
let flushTimeout = null;
|
|
688
|
-
const flushPendingLogs = () => {
|
|
689
|
-
if (pendingLogs.length === 0)
|
|
690
|
-
return;
|
|
691
|
-
const logsToAdd = pendingLogs;
|
|
692
|
-
pendingLogs = [];
|
|
693
|
-
flushTimeout = null;
|
|
694
|
-
this.logs.push(...logsToAdd);
|
|
695
|
-
if (this.logs.length > this.maxLogs) {
|
|
696
|
-
this.logs = this.logs.slice(-this.maxLogs);
|
|
697
|
-
}
|
|
698
|
-
this.addLogLines(logsToAdd);
|
|
699
|
-
};
|
|
700
|
-
const appendLog = (line) => {
|
|
701
|
-
// Extract timestamp from log line format: [timestamp] [source] ...
|
|
702
|
-
const timestampMatch = line.match(/^\[(.*?)\]/);
|
|
703
|
-
const timestamp = timestampMatch ? timestampMatch[1] : undefined;
|
|
704
|
-
const newLog = {
|
|
705
|
-
id: this.logIdCounter++,
|
|
706
|
-
content: line,
|
|
707
|
-
timestamp
|
|
708
|
-
};
|
|
709
|
-
pendingLogs.push(newLog);
|
|
710
|
-
if (flushTimeout) {
|
|
711
|
-
clearTimeout(flushTimeout);
|
|
712
|
-
}
|
|
713
|
-
flushTimeout = setTimeout(flushPendingLogs, 50);
|
|
714
|
-
};
|
|
715
|
-
// Initial read
|
|
716
|
-
const logStream = createReadStream(this.options.logFile, {
|
|
717
|
-
encoding: "utf8",
|
|
718
|
-
start: 0
|
|
719
|
-
});
|
|
720
|
-
logStream.on("data", (chunk) => {
|
|
721
|
-
buffer += chunk.toString();
|
|
722
|
-
const lines = buffer.split("\n");
|
|
723
|
-
buffer = lines.pop() || "";
|
|
724
|
-
for (const line of lines) {
|
|
725
|
-
if (line.trim()) {
|
|
726
|
-
appendLog(line);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
});
|
|
730
|
-
logStream.on("error", (error) => {
|
|
731
|
-
appendLog(`Error reading log file: ${error.message}`);
|
|
732
|
-
});
|
|
733
|
-
// Watch for changes
|
|
734
|
-
watchFile(this.options.logFile, { interval: 100 }, (curr, prev) => {
|
|
735
|
-
if (curr.size > prev.size) {
|
|
736
|
-
const stream = createReadStream(this.options.logFile, {
|
|
737
|
-
encoding: "utf8",
|
|
738
|
-
start: prev.size
|
|
739
|
-
});
|
|
740
|
-
let watchBuffer = "";
|
|
741
|
-
stream.on("data", (chunk) => {
|
|
742
|
-
watchBuffer += chunk.toString();
|
|
743
|
-
const lines = watchBuffer.split("\n");
|
|
744
|
-
watchBuffer = lines.pop() || "";
|
|
745
|
-
for (const line of lines) {
|
|
746
|
-
if (line.trim()) {
|
|
747
|
-
appendLog(line);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
addLogLines(newLogs) {
|
|
755
|
-
if (!this.renderer || !this.logsContainer)
|
|
756
|
-
return;
|
|
757
|
-
const isCompact = this.renderer.width < 100;
|
|
758
|
-
for (const log of newLogs) {
|
|
759
|
-
if (log.id <= this.clearFromLogId)
|
|
760
|
-
continue;
|
|
761
|
-
const isBaseLog = this.baseLogId === log.id;
|
|
762
|
-
const formatted = formatLogLine(log.content, isCompact, this.baseTimestampMs, isBaseLog);
|
|
763
|
-
this.logBaseContent.set(log.id, formatted);
|
|
764
|
-
// Track click start position to detect clicks vs drags
|
|
765
|
-
let clickStartX = -1;
|
|
766
|
-
let clickStartY = -1;
|
|
767
|
-
const logLine = new TextRenderable(this.renderer, {
|
|
768
|
-
id: `log-${log.id}`,
|
|
769
|
-
content: formatted,
|
|
770
|
-
wrapMode: "none",
|
|
771
|
-
selectable: false,
|
|
772
|
-
selectionBg: RGBA.fromInts(70, 130, 180), // Steel blue highlight
|
|
773
|
-
selectionFg: RGBA.fromInts(255, 255, 255), // White text on selection
|
|
774
|
-
width: this.logContentWidth,
|
|
775
|
-
left: 0,
|
|
776
|
-
flexShrink: 0,
|
|
777
|
-
onMouseDown: (event) => {
|
|
778
|
-
// Record start position for click detection
|
|
779
|
-
clickStartX = event.x;
|
|
780
|
-
clickStartY = event.y;
|
|
781
|
-
this.debugLog(`[TEXT_DOWN] id=${logLine.id} screen(${event.x},${event.y}) pos(${logLine.x},${logLine.y}) size(${logLine.width}x${logLine.height})`);
|
|
782
|
-
this.logSelectionDebug(`[TEXT_DOWN] id=${logLine.id} screen(${event.x},${event.y}) pos(${logLine.x},${logLine.y}) size(${logLine.width}x${logLine.height})`);
|
|
783
|
-
},
|
|
784
|
-
onMouseUp: (event) => {
|
|
785
|
-
// Check if this was a click (no movement) vs a drag
|
|
786
|
-
const isClick = clickStartX === event.x && clickStartY === event.y;
|
|
787
|
-
if (isClick && log.timestamp) {
|
|
788
|
-
// Only trigger baseline on clicks within timestamp column (first ~15 chars)
|
|
789
|
-
// Calculate position within the text (accounting for renderable's X position)
|
|
790
|
-
const textLocalX = event.x - (logLine.x || 0);
|
|
791
|
-
const TIMESTAMP_COLUMN_WIDTH = 15; // "[HH:MM:SS.mmm] " is ~14 chars
|
|
792
|
-
if (textLocalX <= TIMESTAMP_COLUMN_WIDTH) {
|
|
793
|
-
this.clearLogSelection();
|
|
794
|
-
// Toggle baseline timestamp on click
|
|
795
|
-
const timestampMs = parseTimestampToMs(log.timestamp);
|
|
796
|
-
if (timestampMs !== null) {
|
|
797
|
-
if (this.baseLogId === log.id) {
|
|
798
|
-
// Clicking same log clears the baseline
|
|
799
|
-
this.baseTimestampMs = undefined;
|
|
800
|
-
this.baseLogId = undefined;
|
|
801
|
-
}
|
|
802
|
-
else {
|
|
803
|
-
// Set this log as the new baseline
|
|
804
|
-
this.baseTimestampMs = timestampMs;
|
|
805
|
-
this.baseLogId = log.id;
|
|
806
|
-
}
|
|
807
|
-
this.refreshLogs();
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
// Reset click tracking
|
|
812
|
-
clickStartX = -1;
|
|
813
|
-
clickStartY = -1;
|
|
814
|
-
}
|
|
815
|
-
});
|
|
816
|
-
this.logRenderables.set(log.id, logLine);
|
|
817
|
-
this.logsContainer.add(logLine);
|
|
818
|
-
this.updateLogLineSelectionStyle(log.id);
|
|
819
|
-
}
|
|
820
|
-
// Force render to update hit grid after adding new logs
|
|
821
|
-
// This prevents stale hit grid when new logs trigger scroll/layout changes
|
|
822
|
-
this.renderer.requestRender();
|
|
823
|
-
setTimeout(() => {
|
|
824
|
-
this.logLogPaneMetrics(`after-add-${newLogs.length}`);
|
|
825
|
-
}, 0);
|
|
826
|
-
}
|
|
827
|
-
findLogLineAt(x, y) {
|
|
828
|
-
if (!this.logsContainer)
|
|
829
|
-
return null;
|
|
830
|
-
for (const child of this.logsContainer.getChildren()) {
|
|
831
|
-
if (!(child instanceof TextRenderable))
|
|
832
|
-
continue;
|
|
833
|
-
const withinX = x >= child.x && x < child.x + child.width;
|
|
834
|
-
const withinY = y >= child.y && y < child.y + child.height;
|
|
835
|
-
if (withinX && withinY)
|
|
836
|
-
return child;
|
|
837
|
-
}
|
|
838
|
-
return null;
|
|
839
|
-
}
|
|
840
|
-
findLogLineAtRow(y) {
|
|
841
|
-
if (!this.logsContainer)
|
|
842
|
-
return null;
|
|
843
|
-
for (const child of this.logsContainer.getChildren()) {
|
|
844
|
-
if (!(child instanceof TextRenderable))
|
|
845
|
-
continue;
|
|
846
|
-
const withinY = y >= child.y && y < child.y + Math.max(child.height, 1);
|
|
847
|
-
if (withinY)
|
|
848
|
-
return child;
|
|
849
|
-
}
|
|
850
|
-
return null;
|
|
851
|
-
}
|
|
852
|
-
getSelectionLogLine(event) {
|
|
853
|
-
const target = event.target;
|
|
854
|
-
if (target?.id?.startsWith("log-")) {
|
|
855
|
-
const child = this.logsContainer?.getChildren().find((entry) => entry.id === target.id);
|
|
856
|
-
if (child instanceof TextRenderable) {
|
|
857
|
-
this.logSelectionDebug(`[HIT] direct target=${target.id} pos(${child.x},${child.y}) size(${child.width}x${child.height})`);
|
|
858
|
-
return child;
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
const fallback = this.findLogLineAt(event.x, event.y);
|
|
862
|
-
if (fallback) {
|
|
863
|
-
this.logSelectionDebug(`[HIT] fallback screen(${event.x},${event.y}) -> ${fallback.id} pos(${fallback.x},${fallback.y}) size(${fallback.width}x${fallback.height})`);
|
|
864
|
-
return fallback;
|
|
865
|
-
}
|
|
866
|
-
const rowFallback = this.findLogLineAtRow(event.y);
|
|
867
|
-
this.logSelectionDebug(`[HIT] row-fallback screen(${event.x},${event.y}) -> ${rowFallback?.id || "(none)"} pos(${rowFallback?.x ?? "?"},${rowFallback?.y ?? "?"}) size(${rowFallback?.width ?? "?"}x${rowFallback?.height ?? "?"})`);
|
|
868
|
-
return rowFallback;
|
|
869
|
-
}
|
|
870
|
-
refreshLogs() {
|
|
871
|
-
if (!this.renderer || !this.logsContainer)
|
|
872
|
-
return;
|
|
873
|
-
// Clear existing log lines
|
|
874
|
-
for (const child of this.logsContainer.getChildren()) {
|
|
875
|
-
child.destroy();
|
|
876
|
-
}
|
|
877
|
-
this.logRenderables.clear();
|
|
878
|
-
this.logBaseContent.clear();
|
|
879
|
-
// Re-add filtered logs
|
|
880
|
-
const filteredLogs = this.logs.filter((log) => log.id > this.clearFromLogId);
|
|
881
|
-
this.addLogLines(filteredLogs);
|
|
882
|
-
this.normalizeLogSelection();
|
|
883
|
-
}
|
|
884
|
-
beginLogSelection(logRenderableId, x, y) {
|
|
885
|
-
const logId = this.parseLogRenderableId(logRenderableId);
|
|
886
|
-
if (logId === null)
|
|
887
|
-
return;
|
|
888
|
-
this.logSelection = {
|
|
889
|
-
anchorLogId: logId,
|
|
890
|
-
focusLogId: logId,
|
|
891
|
-
startX: x,
|
|
892
|
-
startY: y,
|
|
893
|
-
hasDragged: false
|
|
894
|
-
};
|
|
895
|
-
this.updateLogSelectionStyles();
|
|
896
|
-
}
|
|
897
|
-
updateLogSelection(logRenderableId, x, y) {
|
|
898
|
-
if (!this.logSelection)
|
|
899
|
-
return;
|
|
900
|
-
const logId = logRenderableId ? this.parseLogRenderableId(logRenderableId) : null;
|
|
901
|
-
if (logId !== null) {
|
|
902
|
-
this.logSelection.focusLogId = logId;
|
|
903
|
-
}
|
|
904
|
-
if (x !== this.logSelection.startX || y !== this.logSelection.startY) {
|
|
905
|
-
this.logSelection.hasDragged = true;
|
|
906
|
-
}
|
|
907
|
-
this.updateLogSelectionStyles();
|
|
908
|
-
}
|
|
909
|
-
finishLogSelection(logRenderableId, x, y) {
|
|
910
|
-
if (!this.logSelection)
|
|
911
|
-
return;
|
|
912
|
-
this.updateLogSelection(logRenderableId, x, y);
|
|
913
|
-
if (!this.logSelection?.hasDragged) {
|
|
914
|
-
this.clearLogSelection();
|
|
915
|
-
return;
|
|
916
|
-
}
|
|
917
|
-
const text = this.getSelectedLogText();
|
|
918
|
-
if (!text || text.trim().length === 0 || text === this.lastCopiedSelection)
|
|
919
|
-
return;
|
|
920
|
-
this.lastCopiedSelection = text;
|
|
921
|
-
this.copyToClipboard(text);
|
|
922
|
-
this.logSelectionDebug(`[SELECTION_COPY] length=${text.length} text="${text.slice(0, 120)}"`);
|
|
923
|
-
}
|
|
924
|
-
clearLogSelection() {
|
|
925
|
-
if (!this.logSelection)
|
|
926
|
-
return;
|
|
927
|
-
this.logSelection = null;
|
|
928
|
-
this.updateLogSelectionStyles();
|
|
929
|
-
}
|
|
930
|
-
normalizeLogSelection() {
|
|
931
|
-
if (!this.logSelection)
|
|
932
|
-
return;
|
|
933
|
-
const selectedLogIds = this.getSelectedLogIds();
|
|
934
|
-
if (selectedLogIds.length === 0) {
|
|
935
|
-
this.logSelection = null;
|
|
936
|
-
this.updateLogSelectionStyles();
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
getSelectedLogIds() {
|
|
940
|
-
if (!this.logSelection)
|
|
941
|
-
return [];
|
|
942
|
-
const visibleLogIds = [...this.logRenderables.keys()].sort((a, b) => a - b);
|
|
943
|
-
const anchorIndex = visibleLogIds.indexOf(this.logSelection.anchorLogId);
|
|
944
|
-
const focusIndex = visibleLogIds.indexOf(this.logSelection.focusLogId);
|
|
945
|
-
if (anchorIndex === -1 || focusIndex === -1)
|
|
946
|
-
return [];
|
|
947
|
-
const startIndex = Math.min(anchorIndex, focusIndex);
|
|
948
|
-
const endIndex = Math.max(anchorIndex, focusIndex);
|
|
949
|
-
return visibleLogIds.slice(startIndex, endIndex + 1);
|
|
950
|
-
}
|
|
951
|
-
getSelectedLogText() {
|
|
952
|
-
const selectedIds = new Set(this.getSelectedLogIds());
|
|
953
|
-
if (selectedIds.size === 0)
|
|
954
|
-
return "";
|
|
955
|
-
return this.logs
|
|
956
|
-
.filter((log) => log.id > this.clearFromLogId && selectedIds.has(log.id))
|
|
957
|
-
.map((log) => log.content)
|
|
958
|
-
.join("\n");
|
|
959
|
-
}
|
|
960
|
-
updateLogSelectionStyles() {
|
|
961
|
-
for (const logId of this.logRenderables.keys()) {
|
|
962
|
-
this.updateLogLineSelectionStyle(logId);
|
|
963
|
-
}
|
|
964
|
-
this.renderer?.requestRender();
|
|
965
|
-
}
|
|
966
|
-
updateLogLineSelectionStyle(logId) {
|
|
967
|
-
const renderable = this.logRenderables.get(logId);
|
|
968
|
-
const baseContent = this.logBaseContent.get(logId);
|
|
969
|
-
if (!renderable || !baseContent)
|
|
970
|
-
return;
|
|
971
|
-
const selectedIds = new Set(this.getSelectedLogIds());
|
|
972
|
-
renderable.content = selectedIds.has(logId)
|
|
973
|
-
? applySelectionToStyledText(baseContent, this.logSelectionBackground, this.logSelectionForeground)
|
|
974
|
-
: baseContent;
|
|
975
|
-
}
|
|
976
|
-
parseLogRenderableId(logRenderableId) {
|
|
977
|
-
const match = logRenderableId.match(/^log-(\d+)$/);
|
|
978
|
-
if (!match)
|
|
979
|
-
return null;
|
|
980
|
-
const logId = Number.parseInt(match[1], 10);
|
|
981
|
-
return Number.isNaN(logId) ? null : logId;
|
|
982
|
-
}
|
|
983
|
-
updateStatusDisplay() {
|
|
984
|
-
if (!this.statusText || !this.renderer)
|
|
985
|
-
return;
|
|
986
|
-
const isCompact = this.renderer.width < 100;
|
|
987
|
-
this.statusText.content = this.buildStatusContent(isCompact);
|
|
988
|
-
}
|
|
989
|
-
// Public update methods
|
|
990
|
-
setStatus(status) {
|
|
991
|
-
if (status?.includes("Press Ctrl+C again")) {
|
|
992
|
-
this.ctrlCMessage = "⚠️ ^C again to quit";
|
|
993
|
-
this.initStatus = null;
|
|
994
|
-
this.updateStatusDisplay();
|
|
995
|
-
if (this.ctrlCTimeout)
|
|
996
|
-
clearTimeout(this.ctrlCTimeout);
|
|
997
|
-
this.ctrlCTimeout = setTimeout(() => {
|
|
998
|
-
this.ctrlCMessage = "";
|
|
999
|
-
this.updateStatusDisplay();
|
|
1000
|
-
}, 3000);
|
|
1001
|
-
}
|
|
1002
|
-
else {
|
|
1003
|
-
this.initStatus = status;
|
|
1004
|
-
this.rebuildUI();
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
setAppPort(port) {
|
|
1008
|
-
this.appPort = port;
|
|
1009
|
-
this.portConfirmed = true;
|
|
1010
|
-
this.rebuildUI();
|
|
1011
|
-
}
|
|
1012
|
-
setAppUrl(url) {
|
|
1013
|
-
this.appUrl = url;
|
|
1014
|
-
this.rebuildUI();
|
|
1015
|
-
}
|
|
1016
|
-
setUpdateInfo(info) {
|
|
1017
|
-
this.updateInfo = info;
|
|
1018
|
-
this.updateStatusDisplay();
|
|
1019
|
-
if (info?.type === "updated" && info.autoHide !== false) {
|
|
1020
|
-
setTimeout(() => {
|
|
1021
|
-
this.updateInfo = null;
|
|
1022
|
-
this.updateStatusDisplay();
|
|
1023
|
-
}, 10000);
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
setUseHttps(useHttps) {
|
|
1027
|
-
this.useHttps = useHttps;
|
|
1028
|
-
this.rebuildUI();
|
|
1029
|
-
}
|
|
1030
|
-
debugLog(message) {
|
|
1031
|
-
if (!this.debugMode)
|
|
1032
|
-
return;
|
|
1033
|
-
const timestamp = new Date().toISOString();
|
|
1034
|
-
const logLine = `[${timestamp}] ${message}\n`;
|
|
1035
|
-
try {
|
|
1036
|
-
appendFileSync(this.debugLogFile, logLine);
|
|
1037
|
-
}
|
|
1038
|
-
catch {
|
|
1039
|
-
// Ignore write errors
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
initializeSelectionDebugLog() {
|
|
1043
|
-
if (!this.selectionDebugEnabled)
|
|
1044
|
-
return;
|
|
1045
|
-
try {
|
|
1046
|
-
mkdirSync(join(process.env.HOME || tmpdir(), ".d3k"), { recursive: true });
|
|
1047
|
-
writeFileSync(this.selectionDebugLogFile, [
|
|
1048
|
-
`Selection debug started at ${new Date().toISOString()}`,
|
|
1049
|
-
`project=${this.options.projectName || "(unknown)"}`,
|
|
1050
|
-
`logFile=${this.options.logFile}`,
|
|
1051
|
-
`appPort=${this.options.appPort}`,
|
|
1052
|
-
""
|
|
1053
|
-
].join("\n"));
|
|
1054
|
-
}
|
|
1055
|
-
catch {
|
|
1056
|
-
// Ignore write errors
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
logSelectionDebug(message) {
|
|
1060
|
-
if (!this.selectionDebugEnabled)
|
|
1061
|
-
return;
|
|
1062
|
-
const timestamp = new Date().toISOString();
|
|
1063
|
-
try {
|
|
1064
|
-
appendFileSync(this.selectionDebugLogFile, `[${timestamp}] ${message}\n`);
|
|
1065
|
-
}
|
|
1066
|
-
catch {
|
|
1067
|
-
// Ignore write errors
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
describeSelection(selection = this.renderer?.getSelection() || null) {
|
|
1071
|
-
const logSelectionIds = this.getSelectedLogIds();
|
|
1072
|
-
const logSelectionText = this.getSelectedLogText();
|
|
1073
|
-
const logSelectionDescription = logSelectionIds.length === 0
|
|
1074
|
-
? "logSelection=(none)"
|
|
1075
|
-
: `logSelection={dragging:${Boolean(this.logSelection?.hasDragged)},lines:${logSelectionIds.length},textLen:${logSelectionText.length}}`;
|
|
1076
|
-
if (!selection) {
|
|
1077
|
-
return logSelectionDescription;
|
|
1078
|
-
}
|
|
1079
|
-
const text = selection.getSelectedText();
|
|
1080
|
-
return `${logSelectionDescription} nativeSelection={active:${selection.isActive},dragging:${selection.isDragging},renderables:${selection.selectedRenderables.length},textLen:${text.length}}`;
|
|
1081
|
-
}
|
|
1082
|
-
logLogPaneMetrics(reason) {
|
|
1083
|
-
if (!this.selectionDebugEnabled)
|
|
1084
|
-
return;
|
|
1085
|
-
const children = this.logsContainer?.getChildren() || [];
|
|
1086
|
-
const logLines = children.filter((child) => child instanceof TextRenderable);
|
|
1087
|
-
const sample = logLines
|
|
1088
|
-
.slice(0, 3)
|
|
1089
|
-
.map((line) => `${line.id}@(${line.x},${line.y}) ${line.width}x${line.height}`)
|
|
1090
|
-
.join(", ");
|
|
1091
|
-
const last = logLines.at(-1);
|
|
1092
|
-
this.logSelectionDebug(`[LAYOUT:${reason}] scroll=${this.logsScrollBox?.x ?? "?"},${this.logsScrollBox?.y ?? "?"} ${this.logsScrollBox?.width ?? "?"}x${this.logsScrollBox?.height ?? "?"} content=${this.logsContainer?.x ?? "?"},${this.logsContainer?.y ?? "?"} ${this.logsContainer?.width ?? "?"}x${this.logsContainer?.height ?? "?"} lines=${logLines.length} sample=[${sample}] last=${last ? `${last.id}@(${last.x},${last.y}) ${last.width}x${last.height}` : "(none)"}`);
|
|
1093
|
-
}
|
|
1094
|
-
shutdown() {
|
|
1095
|
-
unwatchFile(this.options.logFile);
|
|
1096
|
-
if (this.renderer) {
|
|
1097
|
-
try {
|
|
1098
|
-
if (this.selectionCopyHandler) {
|
|
1099
|
-
this.renderer.off("selection", this.selectionCopyHandler);
|
|
1100
|
-
this.selectionCopyHandler = null;
|
|
1101
|
-
}
|
|
1102
|
-
this.renderer.destroy();
|
|
1103
|
-
}
|
|
1104
|
-
catch (error) {
|
|
1105
|
-
this.debugLog(`Renderer destroy failed during shutdown: ${error}`);
|
|
1106
|
-
}
|
|
1107
|
-
this.renderer = null;
|
|
1108
|
-
}
|
|
1109
|
-
// Explicitly reset terminal state:
|
|
1110
|
-
// - Disable mouse tracking modes (SGR, all motion, cell motion, basic)
|
|
1111
|
-
// - Exit alternate screen buffer
|
|
1112
|
-
// - Clear screen and scrollback
|
|
1113
|
-
// - Show cursor
|
|
1114
|
-
process.stdout.write("\x1b[?1006l" + // Disable SGR extended mouse mode
|
|
1115
|
-
"\x1b[?1003l" + // Disable all motion tracking
|
|
1116
|
-
"\x1b[?1002l" + // Disable cell motion tracking
|
|
1117
|
-
"\x1b[?1000l" + // Disable basic mouse tracking
|
|
1118
|
-
"\x1b[?1049l" + // Exit alternate screen buffer
|
|
1119
|
-
"\x1b[2J\x1b[H\x1b[3J" + // Clear screen and scrollback
|
|
1120
|
-
"\x1b[?25h" // Show cursor
|
|
1121
|
-
);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
export async function runTUI(options) {
|
|
1125
|
-
const tui = new D3kTUI(options);
|
|
1126
|
-
return tui.start();
|
|
1127
|
-
}
|
|
1128
|
-
//# sourceMappingURL=tui-interface-opentui.js.map
|