uilint 0.2.145 → 0.2.147

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.
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getDashboardStore
4
- } from "./chunk-ZOLQHFVQ.js";
4
+ } from "./chunk-JEYSQ5KF.js";
5
5
 
6
6
  // src/commands/serve/dashboard/render.tsx
7
7
  import { render } from "ink";
8
8
 
9
9
  // src/commands/serve/dashboard/ServeDashboard.tsx
10
10
  import { useState as useState2, useEffect as useEffect2, useCallback } from "react";
11
- import { Box as Box8, useInput, useApp } from "ink";
11
+ import { Box as Box9, useInput, useApp } from "ink";
12
12
 
13
13
  // src/commands/serve/dashboard/components/ServerHeader.tsx
14
14
  import { Box, Text } from "ink";
@@ -148,78 +148,92 @@ function StatsBar({
148
148
  // src/commands/serve/dashboard/components/BackgroundTasks.tsx
149
149
  import { Box as Box4, Text as Text4 } from "ink";
150
150
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
151
+
152
+ // src/commands/serve/dashboard/components/PluginStatusPanel.tsx
153
+ import { Box as Box5, Text as Text5 } from "ink";
154
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
155
+ var statusDisplay = {
156
+ idle: { icon: "\u25CB", color: "gray", label: "Idle" },
157
+ "waiting-for-ollama": { icon: "\u25CC", color: "yellow", label: "Queued" },
158
+ "using-ollama": { icon: "\u25CF", color: "cyan", label: "Using Ollama" },
159
+ processing: { icon: "\u25CF", color: "blue", label: "Processing" },
160
+ complete: { icon: "\u2713", color: "green", label: "Complete" },
161
+ error: { icon: "\u2717", color: "red", label: "Error" }
162
+ };
151
163
  function ProgressBar({
152
164
  progress,
153
- width = 20
165
+ width = 12
154
166
  }) {
155
167
  const filled = Math.round(progress / 100 * width);
156
168
  const empty = width - filled;
157
- return /* @__PURE__ */ jsxs4(Text4, { children: [
158
- /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "\u2588".repeat(filled) }),
159
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2591".repeat(empty) })
169
+ return /* @__PURE__ */ jsxs5(Text5, { children: [
170
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588".repeat(filled) }),
171
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2591".repeat(empty) })
160
172
  ] });
161
173
  }
162
- function TaskRow({ task }) {
163
- const statusIcon = {
164
- idle: "\u25CB",
165
- // hollow circle
166
- running: "\u25CF",
167
- // filled circle
168
- complete: "\u2713",
169
- // checkmark
170
- error: "\u2717"
171
- // x mark
172
- }[task.status];
173
- const statusColor = {
174
- idle: "gray",
175
- running: "cyan",
176
- complete: "green",
177
- error: "red"
178
- }[task.status];
179
- return /* @__PURE__ */ jsxs4(Box4, { children: [
180
- /* @__PURE__ */ jsxs4(Text4, { color: statusColor, children: [
181
- statusIcon,
174
+ function truncate(str, maxLen) {
175
+ if (str.length <= maxLen) return str;
176
+ return str.slice(0, maxLen - 1) + "\u2026";
177
+ }
178
+ function PluginRow({
179
+ plugin
180
+ }) {
181
+ const display = statusDisplay[plugin.status];
182
+ return /* @__PURE__ */ jsxs5(Box5, { children: [
183
+ /* @__PURE__ */ jsxs5(Text5, { color: display.color, children: [
184
+ display.icon,
182
185
  " "
183
186
  ] }),
184
- /* @__PURE__ */ jsxs4(Text4, { children: [
185
- task.name,
186
- ": "
187
+ /* @__PURE__ */ jsx5(Box5, { width: 13, children: /* @__PURE__ */ jsx5(Text5, { bold: plugin.status !== "idle", children: plugin.name }) }),
188
+ /* @__PURE__ */ jsx5(Box5, { width: 16, children: /* @__PURE__ */ jsx5(Text5, { color: display.color, children: display.label }) }),
189
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
190
+ "(",
191
+ plugin.model,
192
+ ")"
187
193
  ] }),
188
- task.status === "running" && task.progress !== void 0 ? /* @__PURE__ */ jsxs4(Box4, { gap: 1, children: [
189
- /* @__PURE__ */ jsx4(ProgressBar, { progress: task.progress, width: 15 }),
190
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
191
- task.progress.toFixed(0),
194
+ (plugin.status === "waiting-for-ollama" || plugin.status === "using-ollama" || plugin.status === "processing") && plugin.progress !== void 0 && /* @__PURE__ */ jsxs5(Box5, { marginLeft: 1, gap: 1, children: [
195
+ /* @__PURE__ */ jsx5(ProgressBar, { progress: plugin.progress }),
196
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
197
+ plugin.progress.toFixed(0),
192
198
  "%",
193
- task.current !== void 0 && task.total !== void 0 ? ` (${task.current}/${task.total})` : ""
199
+ plugin.current !== void 0 && plugin.total !== void 0 ? ` (${plugin.current}/${plugin.total})` : ""
194
200
  ] })
195
- ] }) : task.status === "complete" ? /* @__PURE__ */ jsx4(Text4, { color: "green", children: "Complete" }) : task.status === "error" ? /* @__PURE__ */ jsx4(Text4, { color: "red", children: task.error || "Failed" }) : /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: task.message || "Idle" })
201
+ ] }),
202
+ plugin.message && plugin.status !== "idle" && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
203
+ " ",
204
+ truncate(plugin.message, 40)
205
+ ] }),
206
+ plugin.status === "error" && plugin.error && /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
207
+ " ",
208
+ truncate(plugin.error, 40)
209
+ ] })
196
210
  ] });
197
211
  }
198
- function BackgroundTasks({
199
- tasks
212
+ function PluginStatusPanel({
213
+ plugins
200
214
  }) {
201
- const taskArray = Array.from(tasks.values());
202
- if (taskArray.length === 0) {
215
+ const pluginArray = Array.from(plugins.values());
216
+ if (pluginArray.length === 0) {
203
217
  return null;
204
218
  }
205
- return /* @__PURE__ */ jsxs4(
206
- Box4,
219
+ return /* @__PURE__ */ jsxs5(
220
+ Box5,
207
221
  {
208
222
  flexDirection: "column",
209
223
  borderStyle: "single",
210
224
  borderColor: "gray",
211
225
  paddingX: 1,
212
226
  children: [
213
- /* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "Background Tasks" }),
214
- taskArray.map((task) => /* @__PURE__ */ jsx4(TaskRow, { task }, task.id))
227
+ /* @__PURE__ */ jsx5(Text5, { bold: true, dimColor: true, children: "Plugins" }),
228
+ pluginArray.map((plugin) => /* @__PURE__ */ jsx5(PluginRow, { plugin }, plugin.id))
215
229
  ]
216
230
  }
217
231
  );
218
232
  }
219
233
 
220
234
  // src/commands/serve/dashboard/components/ActivityLog.tsx
221
- import { Box as Box5, Text as Text5 } from "ink";
222
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
235
+ import { Box as Box6, Text as Text6 } from "ink";
236
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
223
237
  var filterLabels = {
224
238
  all: "All",
225
239
  errors: "Errors",
@@ -239,7 +253,7 @@ function formatTime(date) {
239
253
  function stripAnsi(str) {
240
254
  return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\r/g, "");
241
255
  }
242
- function truncate(str, maxLen) {
256
+ function truncate2(str, maxLen) {
243
257
  const clean = stripAnsi(str).replace(/\n/g, " ");
244
258
  if (clean.length <= maxLen) return clean;
245
259
  return clean.slice(0, maxLen - 1) + "\u2026";
@@ -257,6 +271,7 @@ function getTypeDisplay(type) {
257
271
  "semantic:analyze": { label: "semant", color: "blue" },
258
272
  "semantic:done": { label: "semant", color: "green" },
259
273
  "semantic:error": { label: "semant", color: "red" },
274
+ "semantic:skip": { label: "semant", color: "yellow" },
260
275
  "config:set": { label: "config", color: "yellow" },
261
276
  "rule:config:set": { label: "rule", color: "yellow" },
262
277
  "screenshot:save": { label: "screen", color: "cyan" },
@@ -280,16 +295,16 @@ function ActivityRow({
280
295
  }) {
281
296
  const { label, color } = getTypeDisplay(entry.type);
282
297
  const showDetail = (verbose || entry.isError) && entry.detail;
283
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
284
- /* @__PURE__ */ jsxs5(Box5, { children: [
285
- /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
298
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
299
+ /* @__PURE__ */ jsxs6(Box6, { children: [
300
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
286
301
  formatTime(entry.timestamp),
287
302
  " "
288
303
  ] }),
289
- /* @__PURE__ */ jsx5(Text5, { color, bold: true, children: label.padEnd(7) }),
290
- /* @__PURE__ */ jsx5(Text5, { color: entry.isError ? "red" : entry.isWarning ? "yellow" : void 0, children: truncate(entry.message, MAX_MSG_WIDTH) })
304
+ /* @__PURE__ */ jsx6(Text6, { color, bold: true, children: label.padEnd(7) }),
305
+ /* @__PURE__ */ jsx6(Text6, { color: entry.isError ? "red" : entry.isWarning ? "yellow" : void 0, children: truncate2(entry.message, MAX_MSG_WIDTH) })
291
306
  ] }),
292
- showDetail && /* @__PURE__ */ jsx5(Box5, { paddingLeft: 16, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: truncate(entry.detail, MAX_DETAIL_WIDTH) }) })
307
+ showDetail && /* @__PURE__ */ jsx6(Box6, { paddingLeft: 16, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: truncate2(entry.detail, MAX_DETAIL_WIDTH) }) })
293
308
  ] });
294
309
  }
295
310
  function ActivityLog({
@@ -306,8 +321,8 @@ function ActivityLog({
306
321
  const visibleActivities = filtered.slice(start, end);
307
322
  const isScrolled = start > 0;
308
323
  const hasMoreBelow = end < total;
309
- return /* @__PURE__ */ jsxs5(
310
- Box5,
324
+ return /* @__PURE__ */ jsxs6(
325
+ Box6,
311
326
  {
312
327
  flexDirection: "column",
313
328
  borderStyle: "single",
@@ -315,36 +330,36 @@ function ActivityLog({
315
330
  paddingX: 1,
316
331
  flexGrow: 1,
317
332
  children: [
318
- /* @__PURE__ */ jsxs5(Box5, { justifyContent: "space-between", children: [
319
- /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
320
- /* @__PURE__ */ jsx5(Text5, { bold: true, dimColor: true, children: "Activity" }),
321
- activeFilter !== "all" && /* @__PURE__ */ jsxs5(Text5, { color: "yellow", children: [
333
+ /* @__PURE__ */ jsxs6(Box6, { justifyContent: "space-between", children: [
334
+ /* @__PURE__ */ jsxs6(Box6, { gap: 1, children: [
335
+ /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "Activity" }),
336
+ activeFilter !== "all" && /* @__PURE__ */ jsxs6(Text6, { color: "yellow", children: [
322
337
  "[",
323
338
  filterLabels[activeFilter],
324
339
  "]"
325
340
  ] })
326
341
  ] }),
327
- /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
328
- isScrolled && /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2191" }),
329
- total > maxVisible && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
342
+ /* @__PURE__ */ jsxs6(Box6, { gap: 1, children: [
343
+ isScrolled && /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u2191" }),
344
+ total > maxVisible && /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
330
345
  start + 1,
331
346
  "-",
332
347
  end,
333
348
  " of ",
334
349
  total
335
350
  ] }),
336
- hasMoreBelow && /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2193" })
351
+ hasMoreBelow && /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u2193" })
337
352
  ] })
338
353
  ] }),
339
- visibleActivities.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: activeFilter !== "all" ? `No ${filterLabels[activeFilter].toLowerCase()} activity yet...` : "No activity yet..." }) : visibleActivities.map((entry) => /* @__PURE__ */ jsx5(ActivityRow, { entry, verbose }, entry.id))
354
+ visibleActivities.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: activeFilter !== "all" ? `No ${filterLabels[activeFilter].toLowerCase()} activity yet...` : "No activity yet..." }) : visibleActivities.map((entry) => /* @__PURE__ */ jsx6(ActivityRow, { entry, verbose }, entry.id))
340
355
  ]
341
356
  }
342
357
  );
343
358
  }
344
359
 
345
360
  // src/commands/serve/dashboard/components/HelpBar.tsx
346
- import { Box as Box6, Text as Text6 } from "ink";
347
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
361
+ import { Box as Box7, Text as Text7 } from "ink";
362
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
348
363
  var filterLabels2 = {
349
364
  all: "all",
350
365
  errors: "errors",
@@ -354,47 +369,47 @@ var filterLabels2 = {
354
369
  system: "system"
355
370
  };
356
371
  function HelpBar({ verbose, activeFilter = "all" }) {
357
- return /* @__PURE__ */ jsxs6(Box6, { borderStyle: "single", borderColor: "gray", paddingX: 1, gap: 2, children: [
358
- /* @__PURE__ */ jsxs6(Box6, { children: [
359
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "q" }),
360
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " quit" })
372
+ return /* @__PURE__ */ jsxs7(Box7, { borderStyle: "single", borderColor: "gray", paddingX: 1, gap: 2, children: [
373
+ /* @__PURE__ */ jsxs7(Box7, { children: [
374
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "q" }),
375
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " quit" })
361
376
  ] }),
362
- /* @__PURE__ */ jsxs6(Box6, { children: [
363
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "c" }),
364
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " clear log" })
377
+ /* @__PURE__ */ jsxs7(Box7, { children: [
378
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "c" }),
379
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " clear log" })
365
380
  ] }),
366
- /* @__PURE__ */ jsxs6(Box6, { children: [
367
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "v" }),
368
- /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
381
+ /* @__PURE__ */ jsxs7(Box7, { children: [
382
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "v" }),
383
+ /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
369
384
  " verbose ",
370
385
  verbose ? "(on)" : "(off)"
371
386
  ] })
372
387
  ] }),
373
- /* @__PURE__ */ jsxs6(Box6, { children: [
374
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "f" }),
375
- /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
388
+ /* @__PURE__ */ jsxs7(Box7, { children: [
389
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "f" }),
390
+ /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
376
391
  " filter (",
377
392
  filterLabels2[activeFilter],
378
393
  ")"
379
394
  ] })
380
395
  ] }),
381
- /* @__PURE__ */ jsxs6(Box6, { children: [
382
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "r" }),
383
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " rebuild index" })
396
+ /* @__PURE__ */ jsxs7(Box7, { children: [
397
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "r" }),
398
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " rebuild index" })
384
399
  ] }),
385
- /* @__PURE__ */ jsxs6(Box6, { children: [
386
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "\u2191\u2193" }),
387
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " scroll" })
400
+ /* @__PURE__ */ jsxs7(Box7, { children: [
401
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "\u2191\u2193" }),
402
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " scroll" })
388
403
  ] })
389
404
  ] });
390
405
  }
391
406
 
392
407
  // src/commands/serve/dashboard/components/OllamaStatus.tsx
393
- import { Box as Box7, Text as Text7 } from "ink";
394
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
408
+ import { Box as Box8, Text as Text8 } from "ink";
409
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
395
410
 
396
411
  // src/commands/serve/dashboard/ServeDashboard.tsx
397
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
412
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
398
413
  function ServeDashboard({
399
414
  onQuit,
400
415
  onRebuildIndex
@@ -476,16 +491,16 @@ function ServeDashboard({
476
491
  [exit, onQuit, onRebuildIndex, store, state.activities.length]
477
492
  )
478
493
  );
479
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: "100%", children: [
480
- /* @__PURE__ */ jsx8(ServerHeader, { port: state.port, isRunning: state.isRunning }),
481
- /* @__PURE__ */ jsx8(
494
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: "100%", children: [
495
+ /* @__PURE__ */ jsx9(ServerHeader, { port: state.port, isRunning: state.isRunning }),
496
+ /* @__PURE__ */ jsx9(
482
497
  WorkspaceInfo,
483
498
  {
484
499
  workspaceRoot: state.workspace?.workspaceRoot ?? null,
485
500
  appRoot: state.workspace?.appRoot ?? null
486
501
  }
487
502
  ),
488
- /* @__PURE__ */ jsx8(
503
+ /* @__PURE__ */ jsx9(
489
504
  StatsBar,
490
505
  {
491
506
  connectedClients: state.stats.connectedClients,
@@ -496,8 +511,8 @@ function ServeDashboard({
496
511
  ollamaModel: state.ollamaStatus.model
497
512
  }
498
513
  ),
499
- /* @__PURE__ */ jsx8(BackgroundTasks, { tasks: state.backgroundTasks }),
500
- /* @__PURE__ */ jsx8(
514
+ /* @__PURE__ */ jsx9(PluginStatusPanel, { plugins: state.pluginStates }),
515
+ /* @__PURE__ */ jsx9(
501
516
  ActivityLog,
502
517
  {
503
518
  activities: state.activities,
@@ -507,12 +522,12 @@ function ServeDashboard({
507
522
  activeFilter: state.activeFilter
508
523
  }
509
524
  ),
510
- /* @__PURE__ */ jsx8(HelpBar, { verbose: state.verbose, activeFilter: state.activeFilter })
525
+ /* @__PURE__ */ jsx9(HelpBar, { verbose: state.verbose, activeFilter: state.activeFilter })
511
526
  ] });
512
527
  }
513
528
 
514
529
  // src/commands/serve/dashboard/render.tsx
515
- import { jsx as jsx9 } from "react/jsx-runtime";
530
+ import { jsx as jsx10 } from "react/jsx-runtime";
516
531
  function interceptConsole() {
517
532
  const origLog = console.log;
518
533
  const origError = console.error;
@@ -540,7 +555,7 @@ function renderDashboard(options = {}) {
540
555
  process.stdout.write("\x1B[2J\x1B[H");
541
556
  const restoreConsole = interceptConsole();
542
557
  const { unmount: inkUnmount, waitUntilExit: inkWaitUntilExit } = render(
543
- /* @__PURE__ */ jsx9(
558
+ /* @__PURE__ */ jsx10(
544
559
  ServeDashboard,
545
560
  {
546
561
  onQuit: options.onQuit,
@@ -559,4 +574,4 @@ function renderDashboard(options = {}) {
559
574
  export {
560
575
  renderDashboard
561
576
  };
562
- //# sourceMappingURL=render-43OMCORR.js.map
577
+ //# sourceMappingURL=render-2P4YWHXV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/serve/dashboard/render.tsx","../src/commands/serve/dashboard/ServeDashboard.tsx","../src/commands/serve/dashboard/components/ServerHeader.tsx","../src/commands/serve/dashboard/components/WorkspaceInfo.tsx","../src/commands/serve/dashboard/components/StatsBar.tsx","../src/commands/serve/dashboard/components/BackgroundTasks.tsx","../src/commands/serve/dashboard/components/PluginStatusPanel.tsx","../src/commands/serve/dashboard/components/ActivityLog.tsx","../src/commands/serve/dashboard/components/HelpBar.tsx","../src/commands/serve/dashboard/components/OllamaStatus.tsx"],"sourcesContent":["/**\n * Render the dashboard using Ink\n */\n\nimport React from \"react\";\nimport { render } from \"ink\";\nimport { ServeDashboard } from \"./ServeDashboard.js\";\nimport { getDashboardStore } from \"./store.js\";\n\nexport interface RenderOptions {\n onQuit?: () => void;\n onRebuildIndex?: () => void;\n}\n\n/**\n * Intercept console.log/error/warn so stray output from ESLint rules,\n * child processes, etc. is routed to the dashboard activity log instead\n * of corrupting the Ink terminal UI. Returns a restore function.\n */\nfunction interceptConsole(): () => void {\n const origLog = console.log;\n const origError = console.error;\n const origWarn = console.warn;\n\n const route = (type: \"info\" | \"error\" | \"warning\", args: unknown[]) => {\n const message = args.map(String).join(\" \");\n if (!message.trim()) return;\n getDashboardStore().addActivity({\n type,\n message,\n isError: type === \"error\",\n isWarning: type === \"warning\",\n });\n };\n\n console.log = (...args: unknown[]) => route(\"info\", args);\n console.error = (...args: unknown[]) => route(\"error\", args);\n console.warn = (...args: unknown[]) => route(\"warning\", args);\n\n return () => {\n console.log = origLog;\n console.error = origError;\n console.warn = origWarn;\n };\n}\n\n/**\n * Render the dashboard and return cleanup function\n */\nexport function renderDashboard(options: RenderOptions = {}): {\n unmount: () => void;\n waitUntilExit: () => Promise<void>;\n} {\n // Clear the terminal so prior output doesn't mix with the Ink dashboard\n process.stdout.write(\"\\x1B[2J\\x1B[H\");\n\n // Intercept console methods so stray output goes to the activity log\n const restoreConsole = interceptConsole();\n\n const { unmount: inkUnmount, waitUntilExit: inkWaitUntilExit } = render(\n <ServeDashboard\n onQuit={options.onQuit}\n onRebuildIndex={options.onRebuildIndex}\n />\n );\n\n return {\n unmount: () => {\n restoreConsole();\n inkUnmount();\n },\n waitUntilExit: () => inkWaitUntilExit().finally(restoreConsole),\n };\n}\n","/**\n * ServeDashboard - main Ink component for the WebSocket server CLI dashboard\n */\n\nimport React, { useState, useEffect, useCallback } from \"react\";\nimport { Box, useInput, useApp } from \"ink\";\nimport {\n ServerHeader,\n WorkspaceInfo,\n StatsBar,\n PluginStatusPanel,\n ActivityLog,\n HelpBar,\n} from \"./components/index.js\";\nimport type { DashboardState } from \"./types.js\";\nimport { getDashboardStore } from \"./store.js\";\n\nexport interface ServeDashboardProps {\n /** Callback when user requests to quit */\n onQuit?: () => void;\n /** Callback when user requests to rebuild index */\n onRebuildIndex?: () => void;\n}\n\nexport function ServeDashboard({\n onQuit,\n onRebuildIndex,\n}: ServeDashboardProps): React.ReactElement {\n const { exit } = useApp();\n const store = getDashboardStore();\n\n // Subscribe to store updates\n const [state, setState] = useState<DashboardState>(store.getState());\n const [scrollOffset, setScrollOffset] = useState(0);\n\n useEffect(() => {\n const unsubscribe = store.subscribe(() => {\n setState(store.getState());\n });\n return unsubscribe;\n }, [store]);\n\n // Clamp scrollOffset when activities change\n useEffect(() => {\n setScrollOffset((prev) => {\n const maxOffset = Math.max(0, state.activities.length - 15);\n return Math.min(prev, maxOffset);\n });\n }, [state.activities.length]);\n\n // Check Ollama status periodically\n useEffect(() => {\n const checkOllamaStatus = async () => {\n try {\n const response = await fetch(\"http://localhost:11434/api/tags\", {\n method: \"GET\",\n signal: AbortSignal.timeout(5000),\n });\n\n if (response.ok) {\n const data = (await response.json()) as { models?: Array<{ name?: string }> };\n const models = data.models || [];\n // Get the first model name as the \"active\" model indicator\n const modelName = models.length > 0 ? models[0].name : undefined;\n store.setOllamaStatus({\n status: \"connected\",\n model: modelName,\n lastChecked: new Date(),\n });\n } else {\n store.setOllamaStatus({\n status: \"error\",\n lastChecked: new Date(),\n });\n }\n } catch {\n store.setOllamaStatus({\n status: \"offline\",\n lastChecked: new Date(),\n });\n }\n };\n\n // Initial check\n checkOllamaStatus();\n\n // Check every 30 seconds\n const interval = setInterval(checkOllamaStatus, 30000);\n return () => clearInterval(interval);\n }, [store]);\n\n // Handle keyboard input\n useInput(\n useCallback(\n (input, key) => {\n if (input === \"q\" || (key.ctrl && input === \"c\")) {\n onQuit?.();\n exit();\n } else if (input === \"c\") {\n store.clearActivities();\n setScrollOffset(0);\n } else if (input === \"v\") {\n store.toggleVerbose();\n } else if (input === \"f\") {\n store.cycleFilter();\n setScrollOffset(0);\n } else if (input === \"r\") {\n onRebuildIndex?.();\n } else if (key.downArrow || input === \"j\") {\n setScrollOffset((prev) => {\n const maxOffset = Math.max(0, state.activities.length - 15);\n return Math.min(maxOffset, prev + 1);\n });\n } else if (key.upArrow || input === \"k\") {\n setScrollOffset((prev) => Math.max(0, prev - 1));\n }\n },\n [exit, onQuit, onRebuildIndex, store, state.activities.length]\n )\n );\n\n return (\n <Box flexDirection=\"column\" width=\"100%\">\n <ServerHeader port={state.port} isRunning={state.isRunning} />\n\n <WorkspaceInfo\n workspaceRoot={state.workspace?.workspaceRoot ?? null}\n appRoot={state.workspace?.appRoot ?? null}\n />\n\n <StatsBar\n connectedClients={state.stats.connectedClients}\n subscriptions={state.stats.subscriptions}\n cacheEntries={state.stats.cacheEntries}\n startTime={state.stats.startTime}\n ollamaStatus={state.ollamaStatus.status}\n ollamaModel={state.ollamaStatus.model}\n />\n\n <PluginStatusPanel plugins={state.pluginStates} />\n\n <ActivityLog\n activities={state.activities}\n maxVisible={15}\n verbose={state.verbose}\n scrollOffset={scrollOffset}\n activeFilter={state.activeFilter}\n />\n\n <HelpBar verbose={state.verbose} activeFilter={state.activeFilter} />\n </Box>\n );\n}\n","/**\n * ServerHeader component - displays server status and URL\n */\n\nimport React from \"react\";\nimport { Box, Text } from \"ink\";\n\nexport interface ServerHeaderProps {\n port: number;\n isRunning: boolean;\n}\n\nexport function ServerHeader({\n port,\n isRunning,\n}: ServerHeaderProps): React.ReactElement {\n const url = `ws://localhost:${port}`;\n const statusColor = isRunning ? \"green\" : \"red\";\n const statusIcon = isRunning ? \"\\u25CF\" : \"\\u25CB\"; // Filled/hollow circle\n\n return (\n <Box\n borderStyle=\"single\"\n borderColor=\"cyan\"\n paddingX={1}\n justifyContent=\"space-between\"\n >\n <Text bold color=\"cyan\">\n UILint Server\n </Text>\n <Box>\n <Text dimColor>{url}</Text>\n <Text> </Text>\n <Text color={statusColor}>{statusIcon}</Text>\n </Box>\n </Box>\n );\n}\n","/**\n * WorkspaceInfo component - displays workspace and app root paths\n */\n\nimport React from \"react\";\nimport { Box, Text } from \"ink\";\n\nexport interface WorkspaceInfoProps {\n workspaceRoot: string | null;\n appRoot: string | null;\n}\n\nfunction truncatePath(path: string, maxLen: number = 60): string {\n if (path.length <= maxLen) return path;\n return \"...\" + path.slice(-(maxLen - 3));\n}\n\nexport function WorkspaceInfo({\n workspaceRoot,\n appRoot,\n}: WorkspaceInfoProps): React.ReactElement {\n if (!workspaceRoot && !appRoot) {\n return (\n <Box paddingX={1}>\n <Text dimColor>Initializing...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" paddingX={1}>\n <Box>\n <Text dimColor>Workspace: </Text>\n <Text>{truncatePath(workspaceRoot || \"\")}</Text>\n </Box>\n {appRoot && appRoot !== workspaceRoot && (\n <Box>\n <Text dimColor>App Root: </Text>\n <Text>{truncatePath(appRoot)}</Text>\n </Box>\n )}\n </Box>\n );\n}\n","/**\n * StatsBar component - displays server statistics\n */\n\nimport React, { useState, useEffect } from \"react\";\nimport { Box, Text } from \"ink\";\nimport type { OllamaStatusState } from \"../types.js\";\n\nexport interface StatsBarProps {\n connectedClients: number;\n subscriptions: number;\n cacheEntries: number;\n startTime: Date;\n ollamaStatus?: OllamaStatusState;\n ollamaModel?: string;\n}\n\nfunction formatUptime(startTime: Date): string {\n const diff = Date.now() - startTime.getTime();\n const seconds = Math.floor(diff / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) {\n return `${hours}h ${minutes % 60}m`;\n }\n if (minutes > 0) {\n return `${minutes}m ${seconds % 60}s`;\n }\n return `${seconds}s`;\n}\n\nconst ollamaStatusConfig: Record<\n OllamaStatusState,\n { icon: string; color: string }\n> = {\n checking: { icon: \"\\u25CB\", color: \"gray\" },\n connected: { icon: \"\\u25CF\", color: \"green\" },\n offline: { icon: \"\\u25CF\", color: \"red\" },\n error: { icon: \"\\u25CF\", color: \"yellow\" },\n};\n\nexport function StatsBar({\n connectedClients,\n subscriptions,\n cacheEntries,\n startTime,\n ollamaStatus,\n ollamaModel,\n}: StatsBarProps): React.ReactElement {\n const [, setTick] = useState(0);\n\n // Update uptime display every second\n useEffect(() => {\n const timer = setInterval(() => {\n setTick((t) => t + 1);\n }, 1000);\n return () => clearInterval(timer);\n }, []);\n\n const ollamaConfig = ollamaStatus\n ? ollamaStatusConfig[ollamaStatus]\n : ollamaStatusConfig.checking;\n\n return (\n <Box\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n justifyContent=\"space-between\"\n >\n <Box gap={2}>\n <Box>\n <Text dimColor>Clients: </Text>\n <Text bold color={connectedClients > 0 ? \"green\" : \"gray\"}>\n {connectedClients}\n </Text>\n </Box>\n <Box>\n <Text dimColor>Subs: </Text>\n <Text>{subscriptions}</Text>\n </Box>\n <Box>\n <Text dimColor>Ollama: </Text>\n <Text color={ollamaConfig.color}>{ollamaConfig.icon}</Text>\n {ollamaModel && ollamaStatus === \"connected\" && (\n <Text dimColor> {ollamaModel}</Text>\n )}\n </Box>\n </Box>\n <Box gap={2}>\n <Box>\n <Text dimColor>Cache: </Text>\n <Text>{cacheEntries}</Text>\n </Box>\n <Box>\n <Text dimColor>Uptime: </Text>\n <Text>{formatUptime(startTime)}</Text>\n </Box>\n </Box>\n </Box>\n );\n}\n","/**\n * BackgroundTasks component - displays background task progress\n */\n\nimport React from \"react\";\nimport { Box, Text } from \"ink\";\nimport type { BackgroundTask } from \"../types.js\";\n\nexport interface BackgroundTasksProps {\n tasks: Map<string, BackgroundTask>;\n}\n\nfunction ProgressBar({\n progress,\n width = 20,\n}: {\n progress: number;\n width?: number;\n}): React.ReactElement {\n const filled = Math.round((progress / 100) * width);\n const empty = width - filled;\n\n return (\n <Text>\n <Text color=\"cyan\">{\"\\u2588\".repeat(filled)}</Text>\n <Text dimColor>{\"\\u2591\".repeat(empty)}</Text>\n </Text>\n );\n}\n\nfunction TaskRow({ task }: { task: BackgroundTask }): React.ReactElement {\n const statusIcon = {\n idle: \"\\u25CB\", // hollow circle\n running: \"\\u25CF\", // filled circle\n complete: \"\\u2713\", // checkmark\n error: \"\\u2717\", // x mark\n }[task.status];\n\n const statusColor = {\n idle: \"gray\",\n running: \"cyan\",\n complete: \"green\",\n error: \"red\",\n }[task.status] as \"gray\" | \"cyan\" | \"green\" | \"red\";\n\n return (\n <Box>\n <Text color={statusColor}>{statusIcon} </Text>\n <Text>{task.name}: </Text>\n {task.status === \"running\" && task.progress !== undefined ? (\n <Box gap={1}>\n <ProgressBar progress={task.progress} width={15} />\n <Text dimColor>\n {task.progress.toFixed(0)}%\n {task.current !== undefined && task.total !== undefined\n ? ` (${task.current}/${task.total})`\n : \"\"}\n </Text>\n </Box>\n ) : task.status === \"complete\" ? (\n <Text color=\"green\">Complete</Text>\n ) : task.status === \"error\" ? (\n <Text color=\"red\">{task.error || \"Failed\"}</Text>\n ) : (\n <Text dimColor>{task.message || \"Idle\"}</Text>\n )}\n </Box>\n );\n}\n\nexport function BackgroundTasks({\n tasks,\n}: BackgroundTasksProps): React.ReactElement | null {\n const taskArray = Array.from(tasks.values());\n\n if (taskArray.length === 0) {\n return null;\n }\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n >\n <Text bold dimColor>\n Background Tasks\n </Text>\n {taskArray.map((task) => (\n <TaskRow key={task.id} task={task} />\n ))}\n </Box>\n );\n}\n","/**\n * PluginStatusPanel - persistent at-a-glance view of Ollama plugin states.\n *\n * Shows each plugin's lifecycle status, model, and progress in the CLI dashboard.\n */\n\nimport React from \"react\";\nimport { Box, Text } from \"ink\";\nimport type { PluginState, PluginLifecycle } from \"../types.js\";\n\nexport interface PluginStatusPanelProps {\n plugins: Map<string, PluginState>;\n}\n\nconst statusDisplay: Record<\n PluginLifecycle,\n { icon: string; color: string; label: string }\n> = {\n idle: { icon: \"\\u25CB\", color: \"gray\", label: \"Idle\" },\n \"waiting-for-ollama\": { icon: \"\\u25CC\", color: \"yellow\", label: \"Queued\" },\n \"using-ollama\": { icon: \"\\u25CF\", color: \"cyan\", label: \"Using Ollama\" },\n processing: { icon: \"\\u25CF\", color: \"blue\", label: \"Processing\" },\n complete: { icon: \"\\u2713\", color: \"green\", label: \"Complete\" },\n error: { icon: \"\\u2717\", color: \"red\", label: \"Error\" },\n};\n\nfunction ProgressBar({\n progress,\n width = 12,\n}: {\n progress: number;\n width?: number;\n}): React.ReactElement {\n const filled = Math.round((progress / 100) * width);\n const empty = width - filled;\n return (\n <Text>\n <Text color=\"cyan\">{\"\\u2588\".repeat(filled)}</Text>\n <Text dimColor>{\"\\u2591\".repeat(empty)}</Text>\n </Text>\n );\n}\n\nfunction truncate(str: string, maxLen: number): string {\n if (str.length <= maxLen) return str;\n return str.slice(0, maxLen - 1) + \"\\u2026\";\n}\n\nfunction PluginRow({\n plugin,\n}: {\n plugin: PluginState;\n}): React.ReactElement {\n const display = statusDisplay[plugin.status];\n\n return (\n <Box>\n {/* Status icon */}\n <Text color={display.color}>{display.icon} </Text>\n\n {/* Plugin name — fixed width */}\n <Box width={13}>\n <Text bold={plugin.status !== \"idle\"}>{plugin.name}</Text>\n </Box>\n\n {/* Status label */}\n <Box width={16}>\n <Text color={display.color}>{display.label}</Text>\n </Box>\n\n {/* Model */}\n <Text dimColor>({plugin.model})</Text>\n\n {/* Progress bar for active states */}\n {(plugin.status === \"waiting-for-ollama\" ||\n plugin.status === \"using-ollama\" ||\n plugin.status === \"processing\") &&\n plugin.progress !== undefined && (\n <Box marginLeft={1} gap={1}>\n <ProgressBar progress={plugin.progress} />\n <Text dimColor>\n {plugin.progress.toFixed(0)}%\n {plugin.current !== undefined && plugin.total !== undefined\n ? ` (${plugin.current}/${plugin.total})`\n : \"\"}\n </Text>\n </Box>\n )}\n\n {/* Message */}\n {plugin.message && plugin.status !== \"idle\" && (\n <Text dimColor>{\" \"}{truncate(plugin.message, 40)}</Text>\n )}\n\n {/* Error */}\n {plugin.status === \"error\" && plugin.error && (\n <Text color=\"red\">{\" \"}{truncate(plugin.error, 40)}</Text>\n )}\n </Box>\n );\n}\n\nexport function PluginStatusPanel({\n plugins,\n}: PluginStatusPanelProps): React.ReactElement | null {\n const pluginArray = Array.from(plugins.values());\n\n if (pluginArray.length === 0) {\n return null;\n }\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n >\n <Text bold dimColor>\n Plugins\n </Text>\n {pluginArray.map((plugin) => (\n <PluginRow key={plugin.id} plugin={plugin} />\n ))}\n </Box>\n );\n}\n","/**\n * ActivityLog component - displays recent server activity\n *\n * Supports filtering by category (errors, vision, semantic, lint)\n * and auto-expanding error details.\n */\n\nimport React from \"react\";\nimport { Box, Text } from \"ink\";\nimport type { ActivityEntry, ActivityType, ActivityCategory } from \"../types.js\";\n\nexport interface ActivityLogProps {\n activities: ActivityEntry[];\n maxVisible?: number;\n verbose?: boolean;\n scrollOffset?: number;\n activeFilter?: ActivityCategory;\n}\n\nconst filterLabels: Record<ActivityCategory, string> = {\n all: \"All\",\n errors: \"Errors\",\n vision: \"Vision\",\n semantic: \"Semantic\",\n lint: \"Lint\",\n system: \"System\",\n};\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString(\"en-US\", {\n hour12: false,\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n}\n\n/** Strip ANSI escape sequences from a string */\nfunction stripAnsi(str: string): string {\n // eslint-disable-next-line no-control-regex\n return str.replace(/\\x1B\\[[0-9;]*[a-zA-Z]/g, \"\").replace(/\\r/g, \"\");\n}\n\n/** Truncate a string to maxLen, appending \"…\" if truncated */\nfunction truncate(str: string, maxLen: number): string {\n const clean = stripAnsi(str).replace(/\\n/g, \" \");\n if (clean.length <= maxLen) return clean;\n return clean.slice(0, maxLen - 1) + \"…\";\n}\n\ntype InkColor =\n | \"black\" | \"red\" | \"green\" | \"yellow\" | \"blue\"\n | \"magenta\" | \"cyan\" | \"white\" | \"gray\" | \"grey\";\n\nfunction getTypeDisplay(type: ActivityType): { label: string; color: InkColor } {\n const displays: Record<ActivityType, { label: string; color: InkColor }> = {\n \"lint:file\": { label: \"lint\", color: \"blue\" },\n \"lint:element\": { label: \"lint\", color: \"blue\" },\n \"lint:done\": { label: \"lint\", color: \"green\" },\n subscribe: { label: \"sub\", color: \"cyan\" },\n \"cache:invalidate\": { label: \"cache\", color: \"yellow\" },\n \"vision:analyze\": { label: \"vision\", color: \"magenta\" },\n \"vision:done\": { label: \"vision\", color: \"green\" },\n \"vision:check\": { label: \"vision\", color: \"magenta\" },\n \"semantic:analyze\": { label: \"semant\", color: \"blue\" },\n \"semantic:done\": { label: \"semant\", color: \"green\" },\n \"semantic:error\": { label: \"semant\", color: \"red\" },\n \"semantic:skip\": { label: \"semant\", color: \"yellow\" },\n \"config:set\": { label: \"config\", color: \"yellow\" },\n \"rule:config:set\": { label: \"rule\", color: \"yellow\" },\n \"screenshot:save\": { label: \"screen\", color: \"cyan\" },\n \"screenshot:saved\": { label: \"screen\", color: \"green\" },\n \"coverage:request\": { label: \"cov\", color: \"blue\" },\n \"coverage:result\": { label: \"cov\", color: \"green\" },\n \"file:changed\": { label: \"change\", color: \"yellow\" },\n \"client:connect\": { label: \"client\", color: \"green\" },\n \"client:disconnect\": { label: \"client\", color: \"red\" },\n error: { label: \"error\", color: \"red\" },\n warning: { label: \"warn\", color: \"yellow\" },\n info: { label: \"info\", color: \"gray\" },\n };\n\n return displays[type] || { label: type, color: \"gray\" };\n}\n\n// Max width for message/detail lines (leaves room for time + type label + padding)\nconst MAX_MSG_WIDTH = 120;\nconst MAX_DETAIL_WIDTH = 100;\n\nfunction ActivityRow({\n entry,\n verbose,\n}: {\n entry: ActivityEntry;\n verbose?: boolean;\n}): React.ReactElement {\n const { label, color } = getTypeDisplay(entry.type);\n\n // Always show detail for errors, otherwise respect verbose mode\n const showDetail = (verbose || entry.isError) && entry.detail;\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text dimColor>{formatTime(entry.timestamp)} </Text>\n <Text color={color} bold>\n {label.padEnd(7)}\n </Text>\n <Text color={entry.isError ? \"red\" : entry.isWarning ? \"yellow\" : undefined}>\n {truncate(entry.message, MAX_MSG_WIDTH)}\n </Text>\n </Box>\n {showDetail && (\n <Box paddingLeft={16}>\n <Text dimColor>{truncate(entry.detail!, MAX_DETAIL_WIDTH)}</Text>\n </Box>\n )}\n </Box>\n );\n}\n\nexport function ActivityLog({\n activities,\n maxVisible = 15,\n verbose = false,\n scrollOffset = 0,\n activeFilter = \"all\",\n}: ActivityLogProps): React.ReactElement {\n // Apply category filter\n const filtered =\n activeFilter !== \"all\"\n ? activities.filter((a) => a.category === activeFilter)\n : activities;\n\n const total = filtered.length;\n const start = Math.min(scrollOffset, Math.max(0, total - 1));\n const end = Math.min(start + maxVisible, total);\n const visibleActivities = filtered.slice(start, end);\n\n const isScrolled = start > 0;\n const hasMoreBelow = end < total;\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n flexGrow={1}\n >\n <Box justifyContent=\"space-between\">\n <Box gap={1}>\n <Text bold dimColor>\n Activity\n </Text>\n {activeFilter !== \"all\" && (\n <Text color=\"yellow\">[{filterLabels[activeFilter]}]</Text>\n )}\n </Box>\n <Box gap={1}>\n {isScrolled && (\n <Text color=\"cyan\">{\"\\u2191\"}</Text>\n )}\n {total > maxVisible && (\n <Text dimColor>\n {start + 1}-{end} of {total}\n </Text>\n )}\n {hasMoreBelow && (\n <Text color=\"cyan\">{\"\\u2193\"}</Text>\n )}\n </Box>\n </Box>\n {visibleActivities.length === 0 ? (\n <Text dimColor>\n {activeFilter !== \"all\"\n ? `No ${filterLabels[activeFilter].toLowerCase()} activity yet...`\n : \"No activity yet...\"}\n </Text>\n ) : (\n visibleActivities.map((entry) => (\n <ActivityRow key={entry.id} entry={entry} verbose={verbose} />\n ))\n )}\n </Box>\n );\n}\n","/**\n * HelpBar component - displays keyboard shortcuts\n */\n\nimport React from \"react\";\nimport { Box, Text } from \"ink\";\nimport type { ActivityCategory } from \"../types.js\";\n\nexport interface HelpBarProps {\n verbose: boolean;\n activeFilter?: ActivityCategory;\n}\n\nconst filterLabels: Record<ActivityCategory, string> = {\n all: \"all\",\n errors: \"errors\",\n vision: \"vision\",\n semantic: \"semantic\",\n lint: \"lint\",\n system: \"system\",\n};\n\nexport function HelpBar({ verbose, activeFilter = \"all\" }: HelpBarProps): React.ReactElement {\n return (\n <Box borderStyle=\"single\" borderColor=\"gray\" paddingX={1} gap={2}>\n <Box>\n <Text bold color=\"cyan\">\n q\n </Text>\n <Text dimColor> quit</Text>\n </Box>\n <Box>\n <Text bold color=\"cyan\">\n c\n </Text>\n <Text dimColor> clear log</Text>\n </Box>\n <Box>\n <Text bold color=\"cyan\">\n v\n </Text>\n <Text dimColor> verbose {verbose ? \"(on)\" : \"(off)\"}</Text>\n </Box>\n <Box>\n <Text bold color=\"cyan\">\n f\n </Text>\n <Text dimColor> filter ({filterLabels[activeFilter]})</Text>\n </Box>\n <Box>\n <Text bold color=\"cyan\">\n r\n </Text>\n <Text dimColor> rebuild index</Text>\n </Box>\n <Box>\n <Text bold color=\"cyan\">\n {\"\\u2191\\u2193\"}\n </Text>\n <Text dimColor> scroll</Text>\n </Box>\n </Box>\n );\n}\n","/**\n * OllamaStatus component - displays Ollama connection status\n */\n\nimport React from \"react\";\nimport { Box, Text } from \"ink\";\nimport type { OllamaStatusState } from \"../types.js\";\n\nexport interface OllamaStatusProps {\n status: OllamaStatusState;\n model?: string;\n}\n\nconst statusConfig: Record<\n OllamaStatusState,\n { icon: string; color: string; label: string }\n> = {\n checking: { icon: \"\\u25CB\", color: \"gray\", label: \"checking\" },\n connected: { icon: \"\\u25CF\", color: \"green\", label: \"connected\" },\n offline: { icon: \"\\u25CF\", color: \"red\", label: \"offline\" },\n error: { icon: \"\\u25CF\", color: \"red\", label: \"error\" },\n};\n\nexport function OllamaStatus({\n status,\n model,\n}: OllamaStatusProps): React.ReactElement {\n const config = statusConfig[status];\n\n return (\n <Box gap={1}>\n <Text dimColor>Ollama:</Text>\n <Text color={config.color}>{config.icon}</Text>\n <Text color={config.color}>{config.label}</Text>\n {model && status === \"connected\" && (\n <Text dimColor>({model})</Text>\n )}\n </Box>\n );\n}\n"],"mappings":"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","names":["useState","useEffect","Box","Box","Text","jsx","jsxs","Box","Text","jsx","jsxs","Box","Text","jsx","jsxs","Box","Text","jsx","jsxs","Box","Text","jsx","jsxs","truncate","Box","Text","jsx","jsxs","filterLabels","Box","Text","jsx","jsxs","jsx","jsxs","useState","useEffect","Box","jsx"]}
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  discoverPlugins,
4
4
  loadPluginESLintRules
5
- } from "./chunk-WG2WZTB2.js";
5
+ } from "./chunk-5QUW7BNW.js";
6
6
  import {
7
7
  getInstalledRuleVersions,
8
8
  updateManifestRule
@@ -590,4 +590,4 @@ async function upgrade(options) {
590
590
  export {
591
591
  upgrade
592
592
  };
593
- //# sourceMappingURL=upgrade-TPZ62BT2.js.map
593
+ //# sourceMappingURL=upgrade-2UKW3SIQ.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uilint",
3
- "version": "0.2.145",
3
+ "version": "0.2.147",
4
4
  "description": "CLI for UILint - AI-powered UI consistency checking",
5
5
  "author": "Peter Suggate",
6
6
  "repository": {
@@ -49,14 +49,14 @@
49
49
  "react": "^19.2.3",
50
50
  "typescript": "^5.9.3",
51
51
  "ws": "^8.19.0",
52
- "uilint-core": "0.2.145",
53
- "uilint-eslint": "0.2.145",
54
- "uilint-duplicates": "0.2.145"
52
+ "uilint-core": "0.2.147",
53
+ "uilint-duplicates": "0.2.147",
54
+ "uilint-eslint": "0.2.147"
55
55
  },
56
56
  "peerDependencies": {
57
- "uilint-vision": "0.2.145",
58
- "uilint-semantic": "0.2.145",
59
- "uilint-duplicates": "0.2.145"
57
+ "uilint-vision": "0.2.147",
58
+ "uilint-semantic": "0.2.147",
59
+ "uilint-duplicates": "0.2.147"
60
60
  },
61
61
  "peerDependenciesMeta": {
62
62
  "uilint-vision": {
@@ -83,7 +83,7 @@
83
83
  "ink-testing-library": "^4.0.0",
84
84
  "tsup": "^8.5.1",
85
85
  "vitest": "^4.0.17",
86
- "uilint-react": "0.2.145"
86
+ "uilint-react": "0.2.147"
87
87
  },
88
88
  "keywords": [
89
89
  "cli",
@@ -1,56 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- logInfo
4
- } from "./chunk-CZNPG4UI.js";
5
-
6
- // src/utils/plugin-loader.ts
7
- import { createRequire } from "module";
8
- import { join } from "path";
9
- var KNOWN_PLUGIN_PACKAGES = ["uilint-vision", "uilint-semantic", "uilint-duplicates"];
10
- async function discoverPlugins(resolveFrom) {
11
- const manifests = [];
12
- for (const pkg of KNOWN_PLUGIN_PACKAGES) {
13
- const specifier = `${pkg}/cli-manifest`;
14
- try {
15
- let mod;
16
- if (resolveFrom) {
17
- const req = createRequire(join(resolveFrom, "package.json"));
18
- const resolved = req.resolve(specifier);
19
- mod = await import(resolved);
20
- } else {
21
- mod = await import(specifier);
22
- }
23
- if (mod.cliManifest) {
24
- manifests.push(mod.cliManifest);
25
- }
26
- } catch {
27
- }
28
- }
29
- return manifests;
30
- }
31
- async function loadPluginESLintRules(manifests, resolveFrom) {
32
- const loaded = [];
33
- for (const manifest of manifests) {
34
- try {
35
- if (resolveFrom) {
36
- const req = createRequire(join(resolveFrom, "package.json"));
37
- const resolved = req.resolve(manifest.registerSpecifier);
38
- await import(resolved);
39
- } else {
40
- await import(manifest.registerSpecifier);
41
- }
42
- loaded.push(manifest.packageName);
43
- } catch {
44
- }
45
- }
46
- if (loaded.length > 0) {
47
- logInfo(`Loaded plugin rules: ${loaded.join(", ")}`);
48
- }
49
- return loaded;
50
- }
51
-
52
- export {
53
- discoverPlugins,
54
- loadPluginESLintRules
55
- };
56
- //# sourceMappingURL=chunk-WG2WZTB2.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/plugin-loader.ts"],"sourcesContent":["/**\n * Dynamic plugin loader for the CLI.\n *\n * Probes known plugin package names for a `cli-manifest` subpath export.\n * Each manifest describes the CLI flag, help text, and registration entry\n * point, keeping the core CLI free of plugin-specific knowledge.\n */\n\nimport { createRequire } from \"module\";\nimport { join } from \"path\";\nimport { logInfo } from \"./prompts.js\";\n\n/**\n * Metadata a plugin exposes via its `<pkg>/cli-manifest` subpath export.\n * Plugins define a plain object matching this shape — no shared type import needed.\n */\nexport interface PluginCLIManifest {\n /** npm package name, e.g. \"uilint-vision\" */\n packageName: string;\n /** CLI flag name (without --), e.g. \"vision\" */\n cliFlag: string;\n /** Description shown in --help */\n cliDescription: string;\n /** Import specifier for the register module, e.g. \"uilint-vision/eslint-rules/register\" */\n registerSpecifier: string;\n}\n\n/** Package names to probe for CLI manifests */\nconst KNOWN_PLUGIN_PACKAGES = [\"uilint-vision\", \"uilint-semantic\", \"uilint-duplicates\"];\n\n/**\n * Discover available plugin manifests by probing `<pkg>/cli-manifest`.\n *\n * @param resolveFrom - Optional project path to resolve plugins from.\n * When provided, uses createRequire anchored to the project's package.json\n * so plugins installed in the project's node_modules can be found.\n * @returns Array of discovered plugin manifests\n */\nexport async function discoverPlugins(\n resolveFrom?: string,\n): Promise<PluginCLIManifest[]> {\n const manifests: PluginCLIManifest[] = [];\n\n for (const pkg of KNOWN_PLUGIN_PACKAGES) {\n const specifier = `${pkg}/cli-manifest`;\n try {\n let mod: { cliManifest?: PluginCLIManifest };\n if (resolveFrom) {\n const req = createRequire(join(resolveFrom, \"package.json\"));\n const resolved = req.resolve(specifier);\n mod = (await import(resolved)) as typeof mod;\n } else {\n mod = (await import(specifier)) as typeof mod;\n }\n if (mod.cliManifest) {\n manifests.push(mod.cliManifest);\n }\n } catch {\n // Plugin not installed — skip silently\n }\n }\n\n return manifests;\n}\n\n/**\n * Load ESLint rules from discovered plugins by importing their register modules.\n *\n * @param manifests - Plugin manifests (from discoverPlugins)\n * @param resolveFrom - Optional project path to resolve plugins from.\n * @returns Array of loaded plugin package names\n */\nexport async function loadPluginESLintRules(\n manifests: PluginCLIManifest[],\n resolveFrom?: string,\n): Promise<string[]> {\n const loaded: string[] = [];\n\n for (const manifest of manifests) {\n try {\n if (resolveFrom) {\n const req = createRequire(join(resolveFrom, \"package.json\"));\n const resolved = req.resolve(manifest.registerSpecifier);\n await import(resolved);\n } else {\n await import(manifest.registerSpecifier);\n }\n loaded.push(manifest.packageName);\n } catch {\n // Plugin register module not available — skip silently\n }\n }\n\n if (loaded.length > 0) {\n logInfo(`Loaded plugin rules: ${loaded.join(\", \")}`);\n }\n\n return loaded;\n}\n"],"mappings":";;;;;;AAQA,SAAS,qBAAqB;AAC9B,SAAS,YAAY;AAmBrB,IAAM,wBAAwB,CAAC,iBAAiB,mBAAmB,mBAAmB;AAUtF,eAAsB,gBACpB,aAC8B;AAC9B,QAAM,YAAiC,CAAC;AAExC,aAAW,OAAO,uBAAuB;AACvC,UAAM,YAAY,GAAG,GAAG;AACxB,QAAI;AACF,UAAI;AACJ,UAAI,aAAa;AACf,cAAM,MAAM,cAAc,KAAK,aAAa,cAAc,CAAC;AAC3D,cAAM,WAAW,IAAI,QAAQ,SAAS;AACtC,cAAO,MAAM,OAAO;AAAA,MACtB,OAAO;AACL,cAAO,MAAM,OAAO;AAAA,MACtB;AACA,UAAI,IAAI,aAAa;AACnB,kBAAU,KAAK,IAAI,WAAW;AAAA,MAChC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AASA,eAAsB,sBACpB,WACA,aACmB;AACnB,QAAM,SAAmB,CAAC;AAE1B,aAAW,YAAY,WAAW;AAChC,QAAI;AACF,UAAI,aAAa;AACf,cAAM,MAAM,cAAc,KAAK,aAAa,cAAc,CAAC;AAC3D,cAAM,WAAW,IAAI,QAAQ,SAAS,iBAAiB;AACvD,cAAM,OAAO;AAAA,MACf,OAAO;AACL,cAAM,OAAO,SAAS;AAAA,MACxB;AACA,aAAO,KAAK,SAAS,WAAW;AAAA,IAClC,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,GAAG;AACrB,YAAQ,wBAAwB,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,EACrD;AAEA,SAAO;AACT;","names":[]}