@prads01/blackbox 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/api.js +65 -0
- package/dist/commands/analyze.js +30 -0
- package/dist/commands/init.js +11 -0
- package/dist/commands/setup.js +148 -0
- package/dist/commands/status.js +23 -0
- package/dist/commands/watch.js +64 -0
- package/dist/config.js +51 -0
- package/dist/files.js +20 -0
- package/dist/index.js +71 -0
- package/dist/report.js +36 -0
- package/dist/tui/app.js +408 -0
- package/dist/tui/data.js +150 -0
- package/dist/tui/keybindings.js +122 -0
- package/dist/tui/render.js +292 -0
- package/dist/tui/types.js +1 -0
- package/dist/tui/watch-service.js +73 -0
- package/dist/types.js +1 -0
- package/dist/utils/format.js +18 -0
- package/package.json +38 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
const ACCENT = "#7c3aed";
|
|
2
|
+
const ACCENT_HEX = "#7c3aed";
|
|
3
|
+
export const NAV_ITEMS = [
|
|
4
|
+
{ key: "dashboard", label: "Dashboard" },
|
|
5
|
+
{ key: "incidents", label: "Incidents" },
|
|
6
|
+
{ key: "analyze", label: "Analyze" },
|
|
7
|
+
{ key: "watch", label: "Watch" },
|
|
8
|
+
{ key: "sandbox", label: "Sandbox" },
|
|
9
|
+
{ key: "settings", label: "Settings" },
|
|
10
|
+
];
|
|
11
|
+
function severityTag(severity) {
|
|
12
|
+
if (severity === "critical")
|
|
13
|
+
return "{red-fg}CRITICAL{/red-fg}";
|
|
14
|
+
if (severity === "high")
|
|
15
|
+
return "{yellow-fg}HIGH{/yellow-fg}";
|
|
16
|
+
if (severity === "medium")
|
|
17
|
+
return `{${ACCENT}-fg}MEDIUM{/${ACCENT}-fg}`;
|
|
18
|
+
return "{green-fg}LOW{/green-fg}";
|
|
19
|
+
}
|
|
20
|
+
function incidentStatusTag(status) {
|
|
21
|
+
if (status === "investigating")
|
|
22
|
+
return "{yellow-fg}INVESTIGATING{/yellow-fg}";
|
|
23
|
+
if (status === "sandbox_verified")
|
|
24
|
+
return `{${ACCENT}-fg}SANDBOX VERIFIED{/${ACCENT}-fg}`;
|
|
25
|
+
if (status === "pr_opened")
|
|
26
|
+
return `{${ACCENT}-fg}PR OPENED{/${ACCENT}-fg}`;
|
|
27
|
+
if (status === "fixed")
|
|
28
|
+
return "{green-fg}FIXED{/green-fg}";
|
|
29
|
+
return "{white-fg}NEW{/white-fg}";
|
|
30
|
+
}
|
|
31
|
+
function formatTimestamp(value) {
|
|
32
|
+
if (!value)
|
|
33
|
+
return "N/A";
|
|
34
|
+
const date = new Date(value);
|
|
35
|
+
if (Number.isNaN(date.getTime()))
|
|
36
|
+
return value;
|
|
37
|
+
return date.toLocaleString();
|
|
38
|
+
}
|
|
39
|
+
function getSandboxReadyIncidents(state) {
|
|
40
|
+
return state.incidents.filter((incident) => {
|
|
41
|
+
const sandboxResult = state.sandboxResultsByIncidentId[incident.id];
|
|
42
|
+
return !sandboxResult || sandboxResult.status !== "success";
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function canCreatePrFromSandbox(state) {
|
|
46
|
+
return (state.view === "sandbox" &&
|
|
47
|
+
state.sandboxMode === "result" &&
|
|
48
|
+
!!state.lastSandboxResult &&
|
|
49
|
+
state.lastSandboxResult.verification.passed &&
|
|
50
|
+
state.lastSandboxResult.workspacePath.trim().length > 0);
|
|
51
|
+
}
|
|
52
|
+
function renderDashboard(state) {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
const recent = state.incidents.slice(0, 5);
|
|
55
|
+
const total = state.incidents.length;
|
|
56
|
+
const critical = state.incidents.filter((incident) => incident.severity === "critical").length;
|
|
57
|
+
const last10Min = state.incidents.filter((incident) => {
|
|
58
|
+
const ts = new Date(incident.timestamp).getTime();
|
|
59
|
+
return Number.isFinite(ts) && now - ts <= 10 * 60 * 1000;
|
|
60
|
+
}).length;
|
|
61
|
+
const unresolved = getSandboxReadyIncidents(state).length;
|
|
62
|
+
const sandboxReady = unresolved;
|
|
63
|
+
return [
|
|
64
|
+
"{bold}{white-fg}BlackBox Operational Summary{/white-fg}{/bold}",
|
|
65
|
+
"",
|
|
66
|
+
"{bold}{white-fg}Core Metrics{/white-fg}{/bold}",
|
|
67
|
+
`Total Incidents: ${total}`,
|
|
68
|
+
`Critical Incidents: ${critical}`,
|
|
69
|
+
`Incidents (last 10m): ${last10Min}`,
|
|
70
|
+
`Unresolved Incidents: ${unresolved}`,
|
|
71
|
+
`Sandbox-Ready Incidents: ${sandboxReady}`,
|
|
72
|
+
"",
|
|
73
|
+
"{bold}{white-fg}Live System Status{/white-fg}{/bold}",
|
|
74
|
+
`API Reachable: ${state.apiReachable ? "{green-fg}YES{/green-fg}" : "{red-fg}NO{/red-fg}"}`,
|
|
75
|
+
`Watch Status: ${state.watchActive ? "{green-fg}ACTIVE{/green-fg}" : "{yellow-fg}STOPPED{/yellow-fg}"}`,
|
|
76
|
+
`Environment: ${state.config.environment}`,
|
|
77
|
+
`Monitored Log: ${state.config.log_file}`,
|
|
78
|
+
`Last Analysis: ${formatTimestamp(state.lastAnalysisAt)}`,
|
|
79
|
+
`Last Sandbox Result: ${state.lastSandboxResult
|
|
80
|
+
? state.lastSandboxResult.status === "success"
|
|
81
|
+
? "{green-fg}SUCCESS{/green-fg}"
|
|
82
|
+
: "{red-fg}FAILED{/red-fg}"
|
|
83
|
+
: "NONE"}`,
|
|
84
|
+
"",
|
|
85
|
+
"{bold}{white-fg}Recent Activity{/white-fg}{/bold}",
|
|
86
|
+
...(recent.length > 0
|
|
87
|
+
? recent.map((incident, index) => `${index + 1}. ${incident.incidentTitle} [${incidentStatusTag(incident.status)}]`)
|
|
88
|
+
: ["No recent incidents."]),
|
|
89
|
+
].join("\n");
|
|
90
|
+
}
|
|
91
|
+
function renderIncidentList(state) {
|
|
92
|
+
if (state.incidents.length === 0) {
|
|
93
|
+
return "No incidents available. Press 'a' to run analysis.";
|
|
94
|
+
}
|
|
95
|
+
return [
|
|
96
|
+
"{bold}{white-fg}Incidents{/white-fg}{/bold}",
|
|
97
|
+
"{gray-fg}Use Up/Down to select, Enter to open details.{/gray-fg}",
|
|
98
|
+
"",
|
|
99
|
+
...state.incidents.slice(0, 50).map((incident, index) => {
|
|
100
|
+
const selected = index === state.selectedIncidentIndex;
|
|
101
|
+
const pointer = selected ? ">" : " ";
|
|
102
|
+
return `${pointer} ${incident.incidentTitle} | ${incident.severity.toUpperCase()} | ${formatTimestamp(incident.timestamp)} | ${incidentStatusTag(incident.status)}`;
|
|
103
|
+
}),
|
|
104
|
+
].join("\n");
|
|
105
|
+
}
|
|
106
|
+
function renderIncidentDetail(state) {
|
|
107
|
+
const incident = state.incidents[state.selectedIncidentIndex];
|
|
108
|
+
if (!incident) {
|
|
109
|
+
return "Incident not found.";
|
|
110
|
+
}
|
|
111
|
+
return [
|
|
112
|
+
"{bold}{white-fg}Incident Report{/white-fg}{/bold}",
|
|
113
|
+
"{gray-fg}Backspace to return to incidents list.{/gray-fg}",
|
|
114
|
+
"",
|
|
115
|
+
`{bold}Title{/bold}: ${incident.incidentTitle}`,
|
|
116
|
+
`{bold}Severity{/bold}: ${severityTag(incident.severity)}`,
|
|
117
|
+
`{bold}Status{/bold}: ${incidentStatusTag(incident.status)}`,
|
|
118
|
+
`{bold}Timestamp{/bold}: ${formatTimestamp(incident.timestamp)}`,
|
|
119
|
+
"",
|
|
120
|
+
"{bold}{white-fg}Contradiction{/white-fg}{/bold}",
|
|
121
|
+
incident.topContradiction,
|
|
122
|
+
"",
|
|
123
|
+
"{bold}{white-fg}Root Cause{/white-fg}{/bold}",
|
|
124
|
+
incident.rootCause,
|
|
125
|
+
"",
|
|
126
|
+
"{bold}{white-fg}Impact{/white-fg}{/bold}",
|
|
127
|
+
incident.impact,
|
|
128
|
+
"",
|
|
129
|
+
"{bold}{white-fg}Proposed Fix{/white-fg}{/bold}",
|
|
130
|
+
incident.fixSuggestion?.trim() || "No fix suggestion available.",
|
|
131
|
+
"",
|
|
132
|
+
"{bold}{white-fg}Evidence Points{/white-fg}{/bold}",
|
|
133
|
+
...(incident.evidencePoints?.length
|
|
134
|
+
? incident.evidencePoints.slice(0, 5).map((point) => `- ${point}`)
|
|
135
|
+
: ["- No evidence points available."]),
|
|
136
|
+
].join("\n");
|
|
137
|
+
}
|
|
138
|
+
function renderIncidents(state) {
|
|
139
|
+
return state.incidentsMode === "detail" ? renderIncidentDetail(state) : renderIncidentList(state);
|
|
140
|
+
}
|
|
141
|
+
function renderAnalyze(state) {
|
|
142
|
+
return [
|
|
143
|
+
"{bold}{white-fg}Analyze{/white-fg}{/bold}",
|
|
144
|
+
"",
|
|
145
|
+
`Status: ${state.analyzeInFlight ? "{yellow-fg}ANALYZING...{/yellow-fg}" : "{green-fg}IDLE{/green-fg}"}`,
|
|
146
|
+
`Last run: ${formatTimestamp(state.lastAnalysisAt)}`,
|
|
147
|
+
"",
|
|
148
|
+
"Press {bold}a{/bold} to trigger analysis once.",
|
|
149
|
+
"Repeated keypresses are ignored while analysis is running.",
|
|
150
|
+
].join("\n");
|
|
151
|
+
}
|
|
152
|
+
function renderWatch(state) {
|
|
153
|
+
const logs = state.watchLogs.length === 0 ? ["No watch events yet."] : state.watchLogs;
|
|
154
|
+
return [
|
|
155
|
+
"{bold}{white-fg}Watch Monitor{/white-fg}{/bold}",
|
|
156
|
+
`Status: ${state.watchActive ? "{green-fg}ACTIVE{/green-fg}" : "{yellow-fg}STOPPED{/yellow-fg}"}`,
|
|
157
|
+
`Monitored log file: ${state.config.log_file}`,
|
|
158
|
+
"",
|
|
159
|
+
...logs.slice(-14),
|
|
160
|
+
].join("\n");
|
|
161
|
+
}
|
|
162
|
+
function renderSandbox(state) {
|
|
163
|
+
const ready = getSandboxReadyIncidents(state);
|
|
164
|
+
if (state.sandboxMode === "result" && state.lastSandboxResult) {
|
|
165
|
+
const result = state.lastSandboxResult;
|
|
166
|
+
const status = result.status === "success" ? "{green-fg}SUCCESS{/green-fg}" : "{red-fg}FAILED{/red-fg}";
|
|
167
|
+
return [
|
|
168
|
+
"{bold}{white-fg}Sandbox Fix Runner{/white-fg}{/bold}",
|
|
169
|
+
"{gray-fg}Backspace to return to sandbox-ready incident list. Press c to create PR.{/gray-fg}",
|
|
170
|
+
"",
|
|
171
|
+
`Status: ${status}`,
|
|
172
|
+
"",
|
|
173
|
+
`{bold}Patch Summary{/bold}: ${result.patchSummary}`,
|
|
174
|
+
`{bold}Files Changed{/bold}: ${result.filesChanged.join(", ") || "N/A"}`,
|
|
175
|
+
`{bold}Workspace{/bold}: ${result.workspacePath}`,
|
|
176
|
+
"",
|
|
177
|
+
`{bold}Verification{/bold}: ${result.verification.passed ? "{green-fg}PASSED{/green-fg}" : "{red-fg}FAILED{/red-fg}"}`,
|
|
178
|
+
result.verification.summary,
|
|
179
|
+
...result.verification.details.map((line) => `- ${line}`),
|
|
180
|
+
"",
|
|
181
|
+
"{bold}{white-fg}PR Preview{/white-fg}{/bold}",
|
|
182
|
+
`Title: ${result.prPreview.title}`,
|
|
183
|
+
`Branch: ${result.prPreview.branchName}`,
|
|
184
|
+
`Diff: ${result.prPreview.diffSnippet || "N/A"}`,
|
|
185
|
+
"",
|
|
186
|
+
"{bold}{white-fg}PR Creation{/white-fg}{/bold}",
|
|
187
|
+
`State: ${state.sandboxPrInFlight
|
|
188
|
+
? "{yellow-fg}Creating PR...{/yellow-fg}"
|
|
189
|
+
: state.sandboxPrResult?.status === "success"
|
|
190
|
+
? "{green-fg}Created{/green-fg}"
|
|
191
|
+
: state.sandboxPrError
|
|
192
|
+
? "{red-fg}Failed{/red-fg}"
|
|
193
|
+
: "Not started"}`,
|
|
194
|
+
...(state.sandboxPrResult
|
|
195
|
+
? [
|
|
196
|
+
`PR Number: ${state.sandboxPrResult.prNumber ?? "N/A"}`,
|
|
197
|
+
`PR URL: ${state.sandboxPrResult.prUrl || "N/A"}`,
|
|
198
|
+
`Branch: ${state.sandboxPrResult.branchName || "N/A"}`,
|
|
199
|
+
`Commit SHA: ${state.sandboxPrResult.commitSha || "N/A"}`,
|
|
200
|
+
`${state.sandboxPrResult.status === "success" ? "{green-fg}" : "{red-fg}"}${state.sandboxPrResult.summary}${state.sandboxPrResult.status === "success" ? "{/green-fg}" : "{/red-fg}"}`,
|
|
201
|
+
]
|
|
202
|
+
: state.sandboxPrError
|
|
203
|
+
? [`{red-fg}${state.sandboxPrError}{/red-fg}`]
|
|
204
|
+
: ["Press c to create a real GitHub PR."]),
|
|
205
|
+
].join("\n");
|
|
206
|
+
}
|
|
207
|
+
if (ready.length === 0) {
|
|
208
|
+
return [
|
|
209
|
+
"{bold}{white-fg}Sandbox Fix Runner{/white-fg}{/bold}",
|
|
210
|
+
"",
|
|
211
|
+
"{yellow-fg}No sandbox-ready incidents.{/yellow-fg}",
|
|
212
|
+
"",
|
|
213
|
+
"Analyze incidents first, then run sandbox verification from this view.",
|
|
214
|
+
].join("\n");
|
|
215
|
+
}
|
|
216
|
+
return [
|
|
217
|
+
"{bold}{white-fg}Sandbox Fix Runner{/white-fg}{/bold}",
|
|
218
|
+
"{gray-fg}Use Up/Down to select. Press Enter or x to run sandbox fix.{/gray-fg}",
|
|
219
|
+
"",
|
|
220
|
+
...ready.map((incident, index) => {
|
|
221
|
+
const selected = index === state.selectedSandboxIndex;
|
|
222
|
+
const pointer = selected ? ">" : " ";
|
|
223
|
+
return `${pointer} ${incident.incidentTitle} | ${incident.severity.toUpperCase()} | ${formatTimestamp(incident.timestamp)}`;
|
|
224
|
+
}),
|
|
225
|
+
].join("\n");
|
|
226
|
+
}
|
|
227
|
+
function renderSettings(state) {
|
|
228
|
+
return [
|
|
229
|
+
"{bold}{white-fg}Settings{/white-fg}{/bold}",
|
|
230
|
+
"",
|
|
231
|
+
`Project: ${state.config.project_name}`,
|
|
232
|
+
`Environment: ${state.config.environment}`,
|
|
233
|
+
`API Base URL: ${state.config.api_base_url}`,
|
|
234
|
+
`Log File: ${state.config.log_file}`,
|
|
235
|
+
`Expected File: ${state.config.expected_file}`,
|
|
236
|
+
`Config File: ${state.config.config_file}`,
|
|
237
|
+
`Commit File: ${state.config.commit_file}`,
|
|
238
|
+
"",
|
|
239
|
+
`API Reachable: ${state.apiReachable ? "{green-fg}YES{/green-fg}" : "{red-fg}NO{/red-fg}"}`,
|
|
240
|
+
].join("\n");
|
|
241
|
+
}
|
|
242
|
+
export function viewToIndex(view) {
|
|
243
|
+
const index = NAV_ITEMS.findIndex((item) => item.key === view);
|
|
244
|
+
return index >= 0 ? index : 0;
|
|
245
|
+
}
|
|
246
|
+
export function indexToView(index) {
|
|
247
|
+
return NAV_ITEMS[Math.max(0, Math.min(index, NAV_ITEMS.length - 1))].key;
|
|
248
|
+
}
|
|
249
|
+
export function renderSidebar(sidebar, state) {
|
|
250
|
+
sidebar.setItems(NAV_ITEMS.map((item, index) => {
|
|
251
|
+
const cursor = index === state.selectedNavIndex ? ">" : " ";
|
|
252
|
+
const active = item.key === state.view ? "*" : " ";
|
|
253
|
+
return `${cursor}${active} ${item.label}`;
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
export function renderMain(main, state) {
|
|
257
|
+
if (state.view === "dashboard")
|
|
258
|
+
main.setContent(renderDashboard(state));
|
|
259
|
+
if (state.view === "incidents")
|
|
260
|
+
main.setContent(renderIncidents(state));
|
|
261
|
+
if (state.view === "analyze")
|
|
262
|
+
main.setContent(renderAnalyze(state));
|
|
263
|
+
if (state.view === "watch")
|
|
264
|
+
main.setContent(renderWatch(state));
|
|
265
|
+
if (state.view === "sandbox")
|
|
266
|
+
main.setContent(renderSandbox(state));
|
|
267
|
+
if (state.view === "settings")
|
|
268
|
+
main.setContent(renderSettings(state));
|
|
269
|
+
}
|
|
270
|
+
export function renderFooter(footer, state) {
|
|
271
|
+
const hotkeysBase = "a analyze | w watch | i incidents | d dashboard | s sandbox | x sandbox-fix | f mark-fixed | v mark-investigating";
|
|
272
|
+
const hotkeys = canCreatePrFromSandbox(state)
|
|
273
|
+
? `${hotkeysBase} | c create-pr | r refresh | q quit`
|
|
274
|
+
: `${hotkeysBase} | r refresh | q quit`;
|
|
275
|
+
const status = [
|
|
276
|
+
`focus:${state.focusZone.toUpperCase()}`,
|
|
277
|
+
`api:${state.apiReachable ? "up" : "down"}`,
|
|
278
|
+
`env:${state.config.environment}`,
|
|
279
|
+
`last:${formatTimestamp(state.lastAnalysisAt)}`,
|
|
280
|
+
`watch:${state.watchActive ? "on" : "off"}`,
|
|
281
|
+
`sandbox-ready:${getSandboxReadyIncidents(state).length}`,
|
|
282
|
+
].join(" | ");
|
|
283
|
+
const busy = state.busyMessage ? ` {yellow-fg}${state.busyMessage}{/yellow-fg}` : "";
|
|
284
|
+
const statusMessage = state.statusMessage ? ` {green-fg}${state.statusMessage}{/green-fg}` : "";
|
|
285
|
+
const error = state.errorMessage ? ` {red-fg}${state.errorMessage}{/red-fg}` : "";
|
|
286
|
+
footer.setContent(`${hotkeys}\n${status}${busy}${statusMessage}${error}`);
|
|
287
|
+
}
|
|
288
|
+
export const palette = {
|
|
289
|
+
accent: ACCENT,
|
|
290
|
+
accentHex: ACCENT_HEX,
|
|
291
|
+
borderDefault: "gray",
|
|
292
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import chokidar from "chokidar";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const WATCH_DEBOUNCE_MS = 1200;
|
|
4
|
+
const WATCH_COOLDOWN_MS = 2000;
|
|
5
|
+
export class TuiWatchService {
|
|
6
|
+
config;
|
|
7
|
+
callbacks;
|
|
8
|
+
watcher = null;
|
|
9
|
+
debounceTimer = null;
|
|
10
|
+
isAnalyzing = false;
|
|
11
|
+
lastRunAt = 0;
|
|
12
|
+
absoluteLogPath;
|
|
13
|
+
constructor(config, callbacks) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.callbacks = callbacks;
|
|
16
|
+
this.absoluteLogPath = path.resolve(process.cwd(), config.log_file);
|
|
17
|
+
}
|
|
18
|
+
async start() {
|
|
19
|
+
if (this.watcher)
|
|
20
|
+
return;
|
|
21
|
+
this.callbacks.onLog(`[WATCH] Monitoring ${this.absoluteLogPath}`);
|
|
22
|
+
this.watcher = chokidar.watch(this.absoluteLogPath, {
|
|
23
|
+
awaitWriteFinish: {
|
|
24
|
+
stabilityThreshold: 1500,
|
|
25
|
+
pollInterval: 100,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
this.watcher.on("change", () => {
|
|
29
|
+
if (this.isAnalyzing)
|
|
30
|
+
return;
|
|
31
|
+
if (Date.now() - this.lastRunAt < WATCH_COOLDOWN_MS)
|
|
32
|
+
return;
|
|
33
|
+
if (this.debounceTimer) {
|
|
34
|
+
clearTimeout(this.debounceTimer);
|
|
35
|
+
}
|
|
36
|
+
this.debounceTimer = setTimeout(() => {
|
|
37
|
+
this.debounceTimer = null;
|
|
38
|
+
void this.runAnalyze();
|
|
39
|
+
}, WATCH_DEBOUNCE_MS);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async stop() {
|
|
43
|
+
if (this.debounceTimer) {
|
|
44
|
+
clearTimeout(this.debounceTimer);
|
|
45
|
+
this.debounceTimer = null;
|
|
46
|
+
}
|
|
47
|
+
if (this.watcher) {
|
|
48
|
+
await this.watcher.close();
|
|
49
|
+
this.watcher = null;
|
|
50
|
+
this.callbacks.onLog("[WATCH] Stopped monitoring.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async runAnalyze() {
|
|
54
|
+
if (this.isAnalyzing)
|
|
55
|
+
return;
|
|
56
|
+
if (Date.now() - this.lastRunAt < WATCH_COOLDOWN_MS)
|
|
57
|
+
return;
|
|
58
|
+
this.isAnalyzing = true;
|
|
59
|
+
this.callbacks.onLog("[DETECTED] Log change detected. Re-running analysis...");
|
|
60
|
+
try {
|
|
61
|
+
await this.callbacks.onChangeAnalyze();
|
|
62
|
+
this.callbacks.onLog("[WATCH] Analysis complete.");
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
+
this.callbacks.onLog(`[WATCH][ERROR] ${message}`);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
this.isAnalyzing = false;
|
|
70
|
+
this.lastRunAt = Date.now();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
export const colors = {
|
|
3
|
+
title: (value) => pc.bold(pc.cyan(value)),
|
|
4
|
+
muted: (value) => pc.dim(value),
|
|
5
|
+
ok: (value) => pc.green(value),
|
|
6
|
+
warn: (value) => pc.yellow(value),
|
|
7
|
+
bad: (value) => pc.red(value),
|
|
8
|
+
key: (value) => pc.bold(pc.white(value)),
|
|
9
|
+
};
|
|
10
|
+
export function section(title) {
|
|
11
|
+
console.log(`\n${colors.title(`=== ${title} ===`)}`);
|
|
12
|
+
}
|
|
13
|
+
export function row(label, value) {
|
|
14
|
+
console.log(`${colors.key(label)} ${value}`);
|
|
15
|
+
}
|
|
16
|
+
export function line() {
|
|
17
|
+
console.log(colors.muted("------------------------------------------------------------"));
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prads01/blackbox",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "BlackBox CLI for local incident analysis and sandboxed fixes",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"blackbox": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/index.js",
|
|
17
|
+
"dev": "tsx src/index.ts",
|
|
18
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@clack/prompts": "^1.1.0",
|
|
23
|
+
"@types/blessed": "^0.1.27",
|
|
24
|
+
"blessed": "^0.1.81",
|
|
25
|
+
"boxen": "^8.0.1",
|
|
26
|
+
"chalk": "^5.4.1",
|
|
27
|
+
"chokidar": "^4.0.3",
|
|
28
|
+
"commander": "^13.1.0",
|
|
29
|
+
"js-yaml": "^4.1.0",
|
|
30
|
+
"ora": "^8.2.0",
|
|
31
|
+
"picocolors": "^1.1.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^20.19.19",
|
|
35
|
+
"tsx": "^4.20.6",
|
|
36
|
+
"typescript": "^5.9.3"
|
|
37
|
+
}
|
|
38
|
+
}
|