claude-scope 0.6.1 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/claude-scope.cjs +2487 -1028
  2. package/package.json +3 -3
@@ -35,73 +35,14 @@ __export(index_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
- // src/core/widget-registry.ts
39
- var WidgetRegistry = class {
40
- widgets = /* @__PURE__ */ new Map();
41
- /**
42
- * Register a widget
43
- */
44
- async register(widget, context) {
45
- if (this.widgets.has(widget.id)) {
46
- throw new Error(`Widget with id '${widget.id}' already registered`);
47
- }
48
- if (context) {
49
- await widget.initialize(context);
50
- }
51
- this.widgets.set(widget.id, widget);
52
- }
53
- /**
54
- * Unregister a widget
55
- */
56
- async unregister(id) {
57
- const widget = this.widgets.get(id);
58
- if (!widget) {
59
- return;
60
- }
61
- try {
62
- if (widget.cleanup) {
63
- await widget.cleanup();
64
- }
65
- } finally {
66
- this.widgets.delete(id);
67
- }
68
- }
69
- /**
70
- * Get a widget by id
71
- */
72
- get(id) {
73
- return this.widgets.get(id);
74
- }
75
- /**
76
- * Check if widget is registered
77
- */
78
- has(id) {
79
- return this.widgets.has(id);
80
- }
81
- /**
82
- * Get all registered widgets
83
- */
84
- getAll() {
85
- return Array.from(this.widgets.values());
86
- }
87
- /**
88
- * Get only enabled widgets
89
- */
90
- getEnabledWidgets() {
91
- return this.getAll().filter((w) => w.isEnabled());
92
- }
93
- /**
94
- * Clear all widgets
95
- */
96
- async clear() {
97
- for (const widget of this.widgets.values()) {
98
- if (widget.cleanup) {
99
- await widget.cleanup();
100
- }
101
- }
102
- this.widgets.clear();
103
- }
38
+ // src/config/widget-flags.ts
39
+ var WIDGET_FLAGS = {
40
+ activeTools: true,
41
+ cacheMetrics: true
104
42
  };
43
+ function isWidgetEnabled(name) {
44
+ return WIDGET_FLAGS[name] ?? true;
45
+ }
105
46
 
106
47
  // src/constants.ts
107
48
  var TIME = {
@@ -207,553 +148,2114 @@ var Renderer = class {
207
148
  }
208
149
  };
209
150
 
210
- // src/core/widget-types.ts
211
- function createWidgetMetadata(name, description, version = "1.0.0", author = "claude-scope", line = 0) {
212
- return {
213
- name,
214
- description,
215
- version,
216
- author,
217
- line
218
- };
219
- }
220
-
221
- // src/providers/git-provider.ts
222
- var import_node_child_process = require("node:child_process");
223
- var import_node_util = require("node:util");
224
- var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
225
- var NativeGit = class {
226
- cwd;
227
- constructor(cwd) {
228
- this.cwd = cwd;
229
- }
230
- async status() {
231
- try {
232
- const { stdout } = await execFileAsync("git", ["status", "--branch", "--short"], {
233
- cwd: this.cwd
234
- });
235
- const match = stdout.match(/^##\s+(\S+)/m);
236
- const current = match ? match[1] : null;
237
- return { current };
238
- } catch {
239
- return { current: null };
151
+ // src/core/widget-registry.ts
152
+ var WidgetRegistry = class {
153
+ widgets = /* @__PURE__ */ new Map();
154
+ /**
155
+ * Register a widget
156
+ */
157
+ async register(widget, context) {
158
+ if (this.widgets.has(widget.id)) {
159
+ throw new Error(`Widget with id '${widget.id}' already registered`);
160
+ }
161
+ if (context) {
162
+ await widget.initialize(context);
240
163
  }
164
+ this.widgets.set(widget.id, widget);
241
165
  }
242
- async diffSummary(options) {
243
- const args = ["diff", "--shortstat"];
244
- if (options) {
245
- args.push(...options);
166
+ /**
167
+ * Unregister a widget
168
+ */
169
+ async unregister(id) {
170
+ const widget = this.widgets.get(id);
171
+ if (!widget) {
172
+ return;
246
173
  }
247
174
  try {
248
- const { stdout } = await execFileAsync("git", args, {
249
- cwd: this.cwd
250
- });
251
- const fileMatch = stdout.match(/(\d+)\s+file(s?)\s+changed/);
252
- const insertionMatch = stdout.match(/(\d+)\s+insertion/);
253
- const deletionMatch = stdout.match(/(\d+)\s+deletion/);
254
- const fileCount = fileMatch ? parseInt(fileMatch[1], 10) : 0;
255
- const insertions = insertionMatch ? parseInt(insertionMatch[1], 10) : 0;
256
- const deletions = deletionMatch ? parseInt(deletionMatch[1], 10) : 0;
257
- const files = insertions > 0 || deletions > 0 ? [{ file: "(total)", insertions, deletions }] : [];
258
- return { fileCount, files };
259
- } catch {
260
- return { fileCount: 0, files: [] };
175
+ if (widget.cleanup) {
176
+ await widget.cleanup();
177
+ }
178
+ } finally {
179
+ this.widgets.delete(id);
261
180
  }
262
181
  }
263
- async latestTag() {
264
- try {
265
- const { stdout } = await execFileAsync("git", ["describe", "--tags", "--abbrev=0"], {
266
- cwd: this.cwd
267
- });
268
- return stdout.trim();
269
- } catch {
270
- return null;
271
- }
182
+ /**
183
+ * Get a widget by id
184
+ */
185
+ get(id) {
186
+ return this.widgets.get(id);
272
187
  }
273
- };
274
- function createGit(cwd) {
275
- return new NativeGit(cwd);
276
- }
277
-
278
- // src/ui/utils/colors.ts
279
- var reset = "\x1B[0m";
280
- var red = "\x1B[31m";
281
- var gray = "\x1B[90m";
282
- var lightGray = "\x1B[37m";
283
- var bold = "\x1B[1m";
284
- function colorize(text, color) {
285
- return `${color}${text}${reset}`;
286
- }
287
-
288
- // src/ui/theme/gray-theme.ts
289
- var GRAY_THEME = {
290
- name: "gray",
291
- description: "Neutral gray theme for minimal color distraction",
292
- colors: {
293
- base: {
294
- text: gray,
295
- muted: gray,
296
- accent: gray,
297
- border: gray
298
- },
299
- semantic: {
300
- success: gray,
301
- warning: gray,
302
- error: gray,
303
- info: gray
304
- },
305
- git: {
306
- branch: gray,
307
- changes: gray
308
- },
309
- context: {
310
- low: gray,
311
- medium: gray,
312
- high: gray,
313
- bar: gray
314
- },
315
- lines: {
316
- added: gray,
317
- removed: gray
318
- },
319
- cost: {
320
- amount: gray,
321
- currency: gray
322
- },
323
- duration: {
324
- value: gray,
325
- unit: gray
326
- },
327
- model: {
328
- name: gray,
329
- version: gray
330
- },
331
- poker: {
332
- participating: lightGray,
333
- nonParticipating: gray,
334
- result: gray
188
+ /**
189
+ * Check if widget is registered
190
+ */
191
+ has(id) {
192
+ return this.widgets.has(id);
193
+ }
194
+ /**
195
+ * Get all registered widgets
196
+ */
197
+ getAll() {
198
+ return Array.from(this.widgets.values());
199
+ }
200
+ /**
201
+ * Get only enabled widgets
202
+ */
203
+ getEnabledWidgets() {
204
+ return this.getAll().filter((w) => w.isEnabled());
205
+ }
206
+ /**
207
+ * Clear all widgets
208
+ */
209
+ async clear() {
210
+ for (const widget of this.widgets.values()) {
211
+ if (widget.cleanup) {
212
+ await widget.cleanup();
213
+ }
335
214
  }
215
+ this.widgets.clear();
336
216
  }
337
217
  };
338
218
 
339
- // src/ui/theme/index.ts
340
- var DEFAULT_THEME = GRAY_THEME.colors;
341
-
342
- // src/ui/utils/style-utils.ts
343
- function withLabel(prefix, value) {
344
- if (prefix === "") return value;
345
- return `${prefix}: ${value}`;
219
+ // src/validation/result.ts
220
+ function success(data) {
221
+ return { success: true, data };
346
222
  }
347
- function withIndicator(value) {
348
- return `\u25CF ${value}`;
223
+ function failure(path2, message, value) {
224
+ return { success: false, error: { path: path2, message, value } };
349
225
  }
350
- function progressBar(percent, width = 10) {
351
- const clamped = Math.max(0, Math.min(100, percent));
352
- const filled = Math.round(clamped / 100 * width);
353
- const empty = width - filled;
354
- return "\u2588".repeat(filled) + "\u2591".repeat(empty);
226
+ function formatError(error) {
227
+ const path2 = error.path.length > 0 ? error.path.join(".") : "root";
228
+ return `${path2}: ${error.message}`;
355
229
  }
356
230
 
357
- // src/widgets/git/styles.ts
358
- var gitStyles = {
359
- minimal: (data, colors) => {
360
- if (!colors) return data.branch;
361
- return colorize(data.branch, colors.branch);
362
- },
363
- balanced: (data, colors) => {
364
- if (data.changes && data.changes.files > 0) {
365
- const parts = [];
366
- if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
367
- if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
368
- if (parts.length > 0) {
369
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
370
- const changes = colors ? colorize(`[${parts.join(" ")}]`, colors.changes) : `[${parts.join(" ")}]`;
371
- return `${branch} ${changes}`;
372
- }
373
- }
374
- return colors ? colorize(data.branch, colors.branch) : data.branch;
375
- },
376
- compact: (data, colors) => {
377
- if (data.changes && data.changes.files > 0) {
378
- const parts = [];
379
- if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
380
- if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
381
- if (parts.length > 0) {
382
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
383
- const changesStr = parts.join("/");
384
- return `${branch} ${changesStr}`;
231
+ // src/validation/combinators.ts
232
+ function object(shape) {
233
+ return {
234
+ validate(value) {
235
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
236
+ return failure([], "Expected object", value);
385
237
  }
238
+ const result = {};
239
+ for (const [key, validator] of Object.entries(shape)) {
240
+ const fieldValue = value[key];
241
+ const validationResult = validator.validate(fieldValue);
242
+ if (!validationResult.success) {
243
+ return {
244
+ success: false,
245
+ error: { ...validationResult.error, path: [key, ...validationResult.error.path] }
246
+ };
247
+ }
248
+ result[key] = validationResult.data;
249
+ }
250
+ return success(result);
386
251
  }
387
- return colors ? colorize(data.branch, colors.branch) : data.branch;
252
+ };
253
+ }
254
+ function optional(validator) {
255
+ return {
256
+ validate(value) {
257
+ if (value === void 0) return success(void 0);
258
+ return validator.validate(value);
259
+ }
260
+ };
261
+ }
262
+ function nullable(validator) {
263
+ return {
264
+ validate(value) {
265
+ if (value === null) return success(null);
266
+ return validator.validate(value);
267
+ }
268
+ };
269
+ }
270
+
271
+ // src/validation/validators.ts
272
+ function string() {
273
+ return {
274
+ validate(value) {
275
+ if (typeof value === "string") return success(value);
276
+ return failure([], "Expected string", value);
277
+ }
278
+ };
279
+ }
280
+ function number() {
281
+ return {
282
+ validate(value) {
283
+ if (typeof value === "number" && !Number.isNaN(value)) return success(value);
284
+ return failure([], "Expected number", value);
285
+ }
286
+ };
287
+ }
288
+ function literal(expected) {
289
+ return {
290
+ validate(value) {
291
+ if (value === expected) return success(expected);
292
+ return failure([], `Expected '${expected}'`, value);
293
+ }
294
+ };
295
+ }
296
+
297
+ // src/schemas/stdin-schema.ts
298
+ var ContextUsageSchema = object({
299
+ input_tokens: number(),
300
+ output_tokens: number(),
301
+ cache_creation_input_tokens: number(),
302
+ cache_read_input_tokens: number()
303
+ });
304
+ var CostInfoSchema = object({
305
+ total_cost_usd: optional(number()),
306
+ total_duration_ms: optional(number()),
307
+ total_api_duration_ms: optional(number()),
308
+ total_lines_added: optional(number()),
309
+ total_lines_removed: optional(number())
310
+ });
311
+ var ContextWindowSchema = object({
312
+ total_input_tokens: number(),
313
+ total_output_tokens: number(),
314
+ context_window_size: number(),
315
+ current_usage: nullable(ContextUsageSchema)
316
+ });
317
+ var ModelInfoSchema = object({
318
+ id: string(),
319
+ display_name: string()
320
+ });
321
+ var WorkspaceSchema = object({
322
+ current_dir: string(),
323
+ project_dir: string()
324
+ });
325
+ var OutputStyleSchema = object({
326
+ name: string()
327
+ });
328
+ var StdinDataSchema = object({
329
+ hook_event_name: optional(literal("Status")),
330
+ session_id: string(),
331
+ transcript_path: string(),
332
+ cwd: string(),
333
+ model: ModelInfoSchema,
334
+ workspace: WorkspaceSchema,
335
+ version: string(),
336
+ output_style: OutputStyleSchema,
337
+ cost: optional(CostInfoSchema),
338
+ context_window: ContextWindowSchema
339
+ });
340
+
341
+ // src/data/stdin-provider.ts
342
+ var StdinParseError = class extends Error {
343
+ constructor(message) {
344
+ super(message);
345
+ this.name = "StdinParseError";
346
+ }
347
+ };
348
+ var StdinValidationError = class extends Error {
349
+ constructor(message) {
350
+ super(message);
351
+ this.name = "StdinValidationError";
352
+ }
353
+ };
354
+ var StdinProvider = class {
355
+ /**
356
+ * Parse and validate JSON string from stdin
357
+ * @param input JSON string to parse
358
+ * @returns Validated StdinData object
359
+ * @throws StdinParseError if JSON is malformed
360
+ * @throws StdinValidationError if data doesn't match schema
361
+ */
362
+ async parse(input) {
363
+ if (!input || input.trim().length === 0) {
364
+ throw new StdinParseError("stdin data is empty");
365
+ }
366
+ let data;
367
+ try {
368
+ data = JSON.parse(input);
369
+ } catch (error) {
370
+ throw new StdinParseError(`Invalid JSON: ${error.message}`);
371
+ }
372
+ const result = StdinDataSchema.validate(data);
373
+ if (!result.success) {
374
+ throw new StdinValidationError(`Validation failed: ${formatError(result.error)}`);
375
+ }
376
+ return result.data;
377
+ }
378
+ /**
379
+ * Safe parse that returns result instead of throwing
380
+ * Useful for testing and optional validation
381
+ * @param input JSON string to parse
382
+ * @returns Result object with success flag
383
+ */
384
+ async safeParse(input) {
385
+ try {
386
+ const data = await this.parse(input);
387
+ return { success: true, data };
388
+ } catch (error) {
389
+ return { success: false, error: error.message };
390
+ }
391
+ }
392
+ };
393
+
394
+ // src/providers/transcript-provider.ts
395
+ var import_fs = require("fs");
396
+ var import_readline = require("readline");
397
+ var TranscriptProvider = class {
398
+ MAX_TOOLS = 20;
399
+ /**
400
+ * Parse tools from a JSONL transcript file
401
+ * @param transcriptPath Path to the transcript file
402
+ * @returns Array of tool entries, limited to last 20
403
+ */
404
+ async parseTools(transcriptPath) {
405
+ if (!(0, import_fs.existsSync)(transcriptPath)) {
406
+ return [];
407
+ }
408
+ const toolMap = /* @__PURE__ */ new Map();
409
+ try {
410
+ const fileStream = (0, import_fs.createReadStream)(transcriptPath, { encoding: "utf-8" });
411
+ const rl = (0, import_readline.createInterface)({
412
+ input: fileStream,
413
+ crlfDelay: Infinity
414
+ });
415
+ for await (const line of rl) {
416
+ if (!line.trim()) continue;
417
+ try {
418
+ const entry = JSON.parse(line);
419
+ this.processLine(entry, toolMap);
420
+ } catch {
421
+ }
422
+ }
423
+ const tools = Array.from(toolMap.values());
424
+ return tools.slice(-this.MAX_TOOLS);
425
+ } catch {
426
+ return [];
427
+ }
428
+ }
429
+ /**
430
+ * Process a single transcript line and update tool map
431
+ */
432
+ processLine(line, toolMap) {
433
+ const blocks = line.message?.content ?? [];
434
+ const timestamp = /* @__PURE__ */ new Date();
435
+ for (const block of blocks) {
436
+ if (block.type === "tool_use" && block.id && block.name) {
437
+ const tool = {
438
+ id: block.id,
439
+ name: block.name,
440
+ target: this.extractTarget(block.name, block.input),
441
+ status: "running",
442
+ startTime: timestamp
443
+ };
444
+ toolMap.set(block.id, tool);
445
+ }
446
+ if (block.type === "tool_result" && block.tool_use_id) {
447
+ const existing = toolMap.get(block.tool_use_id);
448
+ if (existing) {
449
+ existing.status = block.is_error ? "error" : "completed";
450
+ existing.endTime = timestamp;
451
+ }
452
+ }
453
+ }
454
+ }
455
+ /**
456
+ * Extract target from tool input based on tool type
457
+ */
458
+ extractTarget(toolName, input) {
459
+ if (!input) return void 0;
460
+ switch (toolName) {
461
+ case "Read":
462
+ case "Write":
463
+ case "Edit":
464
+ return this.asString(input.file_path ?? input.path);
465
+ case "Glob":
466
+ return this.asString(input.pattern);
467
+ case "Grep":
468
+ return this.asString(input.pattern);
469
+ case "Bash": {
470
+ const cmd = this.asString(input.command);
471
+ return cmd ? this.truncateCommand(cmd) : void 0;
472
+ }
473
+ default:
474
+ return void 0;
475
+ }
476
+ }
477
+ /**
478
+ * Safely convert value to string
479
+ */
480
+ asString(value) {
481
+ if (typeof value === "string") return value;
482
+ if (typeof value === "number") return String(value);
483
+ return void 0;
484
+ }
485
+ /**
486
+ * Truncate long commands to 30 chars
487
+ */
488
+ truncateCommand(cmd) {
489
+ if (cmd.length <= 30) return cmd;
490
+ return cmd.slice(0, 30) + "...";
491
+ }
492
+ };
493
+
494
+ // src/ui/utils/colors.ts
495
+ var reset = "\x1B[0m";
496
+ var red = "\x1B[31m";
497
+ var gray = "\x1B[90m";
498
+ var lightGray = "\x1B[37m";
499
+ var bold = "\x1B[1m";
500
+ function colorize(text, color) {
501
+ return `${color}${text}${reset}`;
502
+ }
503
+
504
+ // src/ui/theme/helpers.ts
505
+ function rgb(r, g, b) {
506
+ return `\x1B[38;2;${r};${g};${b}m`;
507
+ }
508
+ function createBaseColors(params) {
509
+ return {
510
+ text: params.modelColor,
511
+ muted: params.durationColor,
512
+ accent: params.accentColor,
513
+ border: params.durationColor
514
+ };
515
+ }
516
+ function createSemanticColors(params) {
517
+ return {
518
+ success: params.contextLow,
519
+ warning: params.contextMedium,
520
+ error: params.contextHigh,
521
+ info: params.branchColor
522
+ };
523
+ }
524
+ function createThemeColors(params) {
525
+ const base = createBaseColors({
526
+ modelColor: params.model,
527
+ durationColor: params.duration,
528
+ accentColor: params.accent
529
+ });
530
+ const semantic = createSemanticColors({
531
+ contextLow: params.contextLow,
532
+ contextMedium: params.contextMedium,
533
+ contextHigh: params.contextHigh,
534
+ branchColor: params.branch
535
+ });
536
+ return {
537
+ base,
538
+ semantic,
539
+ git: {
540
+ branch: params.branch,
541
+ changes: params.changes
542
+ },
543
+ context: {
544
+ low: params.contextLow,
545
+ medium: params.contextMedium,
546
+ high: params.contextHigh,
547
+ bar: params.contextLow
548
+ },
549
+ lines: {
550
+ added: params.linesAdded,
551
+ removed: params.linesRemoved
552
+ },
553
+ cost: {
554
+ amount: params.cost,
555
+ currency: params.cost
556
+ },
557
+ duration: {
558
+ value: params.duration,
559
+ unit: params.duration
560
+ },
561
+ model: {
562
+ name: params.model,
563
+ version: params.model
564
+ },
565
+ poker: {
566
+ participating: params.model,
567
+ nonParticipating: params.duration,
568
+ result: params.accent
569
+ },
570
+ cache: {
571
+ high: params.cacheHigh,
572
+ medium: params.cacheMedium,
573
+ low: params.cacheLow,
574
+ read: params.cacheRead,
575
+ write: params.cacheWrite
576
+ },
577
+ tools: {
578
+ running: params.toolsRunning,
579
+ completed: params.toolsCompleted,
580
+ error: params.toolsError,
581
+ name: params.toolsName,
582
+ target: params.toolsTarget,
583
+ count: params.toolsCount
584
+ }
585
+ };
586
+ }
587
+
588
+ // src/ui/theme/gray-theme.ts
589
+ var GRAY_THEME = {
590
+ name: "gray",
591
+ description: "Neutral gray theme for minimal color distraction",
592
+ colors: createThemeColors({
593
+ branch: gray,
594
+ changes: gray,
595
+ contextLow: gray,
596
+ contextMedium: gray,
597
+ contextHigh: gray,
598
+ linesAdded: gray,
599
+ linesRemoved: gray,
600
+ cost: gray,
601
+ model: gray,
602
+ duration: gray,
603
+ accent: gray,
604
+ cacheHigh: gray,
605
+ cacheMedium: gray,
606
+ cacheLow: gray,
607
+ cacheRead: gray,
608
+ cacheWrite: gray,
609
+ toolsRunning: gray,
610
+ toolsCompleted: gray,
611
+ toolsError: gray,
612
+ toolsName: gray,
613
+ toolsTarget: gray,
614
+ toolsCount: gray
615
+ })
616
+ };
617
+
618
+ // src/ui/theme/themes/catppuccin-mocha-theme.ts
619
+ var CATPPUCCIN_MOCHA_THEME = {
620
+ name: "catppuccin-mocha",
621
+ description: "Soothing pastel theme",
622
+ colors: createThemeColors({
623
+ branch: rgb(137, 180, 250),
624
+ // Blue
625
+ changes: rgb(166, 227, 161),
626
+ // Green
627
+ contextLow: rgb(166, 227, 161),
628
+ // Green
629
+ contextMedium: rgb(238, 212, 159),
630
+ // Yellow
631
+ contextHigh: rgb(243, 139, 168),
632
+ // Red
633
+ linesAdded: rgb(166, 227, 161),
634
+ // Green
635
+ linesRemoved: rgb(243, 139, 168),
636
+ // Red
637
+ cost: rgb(245, 224, 220),
638
+ // Rosewater
639
+ model: rgb(203, 166, 247),
640
+ // Mauve
641
+ duration: rgb(147, 153, 178),
642
+ // Text gray
643
+ accent: rgb(243, 139, 168),
644
+ // Pink
645
+ cacheHigh: rgb(166, 227, 161),
646
+ // Green
647
+ cacheMedium: rgb(238, 212, 159),
648
+ // Yellow
649
+ cacheLow: rgb(243, 139, 168),
650
+ // Red
651
+ cacheRead: rgb(137, 180, 250),
652
+ // Blue
653
+ cacheWrite: rgb(203, 166, 247),
654
+ // Mauve
655
+ toolsRunning: rgb(238, 212, 159),
656
+ // Yellow
657
+ toolsCompleted: rgb(166, 227, 161),
658
+ // Green
659
+ toolsError: rgb(243, 139, 168),
660
+ // Red
661
+ toolsName: rgb(137, 180, 250),
662
+ // Blue
663
+ toolsTarget: rgb(147, 153, 178),
664
+ // Gray
665
+ toolsCount: rgb(203, 166, 247)
666
+ // Mauve
667
+ })
668
+ };
669
+
670
+ // src/ui/theme/themes/cyberpunk-neon-theme.ts
671
+ var CYBERPUNK_NEON_THEME = {
672
+ name: "cyberpunk-neon",
673
+ description: "High-contrast neon cyberpunk aesthetic",
674
+ colors: createThemeColors({
675
+ branch: rgb(0, 191, 255),
676
+ // Cyan neon
677
+ changes: rgb(255, 0, 122),
678
+ // Magenta neon
679
+ contextLow: rgb(0, 255, 122),
680
+ // Green neon
681
+ contextMedium: rgb(255, 214, 0),
682
+ // Yellow neon
683
+ contextHigh: rgb(255, 0, 122),
684
+ // Magenta neon
685
+ linesAdded: rgb(0, 255, 122),
686
+ // Green neon
687
+ linesRemoved: rgb(255, 0, 122),
688
+ // Magenta neon
689
+ cost: rgb(255, 111, 97),
690
+ // Orange neon
691
+ model: rgb(140, 27, 255),
692
+ // Purple neon
693
+ duration: rgb(0, 191, 255),
694
+ // Cyan neon
695
+ accent: rgb(255, 0, 122),
696
+ // Magenta neon
697
+ cacheHigh: rgb(0, 255, 122),
698
+ // Green neon
699
+ cacheMedium: rgb(255, 214, 0),
700
+ // Yellow neon
701
+ cacheLow: rgb(255, 0, 122),
702
+ // Magenta neon
703
+ cacheRead: rgb(0, 191, 255),
704
+ // Cyan neon
705
+ cacheWrite: rgb(140, 27, 255),
706
+ // Purple neon
707
+ toolsRunning: rgb(255, 214, 0),
708
+ // Yellow neon
709
+ toolsCompleted: rgb(0, 255, 122),
710
+ // Green neon
711
+ toolsError: rgb(255, 0, 122),
712
+ // Magenta neon
713
+ toolsName: rgb(0, 191, 255),
714
+ // Cyan neon
715
+ toolsTarget: rgb(140, 27, 255),
716
+ // Purple neon
717
+ toolsCount: rgb(255, 111, 97)
718
+ // Orange neon
719
+ })
720
+ };
721
+
722
+ // src/ui/theme/themes/dracula-theme.ts
723
+ var DRACULA_THEME = {
724
+ name: "dracula",
725
+ description: "Purple/pink accent theme",
726
+ colors: createThemeColors({
727
+ branch: rgb(189, 147, 249),
728
+ // Purple
729
+ changes: rgb(139, 233, 253),
730
+ // Cyan
731
+ contextLow: rgb(80, 250, 123),
732
+ // Green
733
+ contextMedium: rgb(241, 250, 140),
734
+ // Yellow
735
+ contextHigh: rgb(255, 85, 85),
736
+ // Red
737
+ linesAdded: rgb(80, 250, 123),
738
+ // Green
739
+ linesRemoved: rgb(255, 85, 85),
740
+ // Red
741
+ cost: rgb(255, 184, 108),
742
+ // Orange
743
+ model: rgb(98, 114, 164),
744
+ // Comment gray
745
+ duration: rgb(68, 71, 90),
746
+ // Selection gray
747
+ accent: rgb(189, 147, 249),
748
+ // Purple
749
+ cacheHigh: rgb(80, 250, 123),
750
+ // Green
751
+ cacheMedium: rgb(241, 250, 140),
752
+ // Yellow
753
+ cacheLow: rgb(255, 85, 85),
754
+ // Red
755
+ cacheRead: rgb(139, 233, 253),
756
+ // Cyan
757
+ cacheWrite: rgb(189, 147, 249),
758
+ // Purple
759
+ toolsRunning: rgb(241, 250, 140),
760
+ // Yellow
761
+ toolsCompleted: rgb(80, 250, 123),
762
+ // Green
763
+ toolsError: rgb(255, 85, 85),
764
+ // Red
765
+ toolsName: rgb(139, 233, 253),
766
+ // Cyan
767
+ toolsTarget: rgb(98, 114, 164),
768
+ // Gray
769
+ toolsCount: rgb(189, 147, 249)
770
+ // Purple
771
+ })
772
+ };
773
+
774
+ // src/ui/theme/themes/dusty-sage-theme.ts
775
+ var DUSTY_SAGE_THEME = {
776
+ name: "dusty-sage",
777
+ description: "Earthy muted greens with peaceful forest fog aesthetic",
778
+ colors: createThemeColors({
779
+ branch: rgb(120, 140, 130),
780
+ // Dusty green
781
+ changes: rgb(135, 145, 140),
782
+ // Sage gray
783
+ contextLow: rgb(135, 145, 140),
784
+ // Subtle sage (low)
785
+ contextMedium: rgb(150, 160, 145),
786
+ // Medium sage
787
+ contextHigh: rgb(165, 175, 160),
788
+ // Light sage (high)
789
+ linesAdded: rgb(135, 145, 140),
790
+ linesRemoved: rgb(135, 145, 140),
791
+ cost: rgb(156, 163, 175),
792
+ model: rgb(148, 163, 184),
793
+ duration: rgb(120, 130, 140),
794
+ accent: rgb(120, 140, 130),
795
+ cacheHigh: rgb(135, 145, 140),
796
+ cacheMedium: rgb(150, 160, 145),
797
+ cacheLow: rgb(165, 175, 160),
798
+ cacheRead: rgb(120, 140, 130),
799
+ cacheWrite: rgb(148, 163, 184),
800
+ toolsRunning: rgb(150, 160, 145),
801
+ // Medium sage
802
+ toolsCompleted: rgb(135, 145, 140),
803
+ // Subtle sage
804
+ toolsError: rgb(165, 175, 160),
805
+ // Light sage
806
+ toolsName: rgb(120, 140, 130),
807
+ // Dusty green
808
+ toolsTarget: rgb(148, 163, 184),
809
+ // Gray
810
+ toolsCount: rgb(156, 163, 175)
811
+ // Light gray
812
+ })
813
+ };
814
+
815
+ // src/ui/theme/themes/github-dark-dimmed-theme.ts
816
+ var GITHUB_DARK_DIMMED_THEME = {
817
+ name: "github-dark-dimmed",
818
+ description: "GitHub's official dark theme (dimmed)",
819
+ colors: createThemeColors({
820
+ branch: rgb(88, 166, 255),
821
+ // GitHub blue
822
+ changes: rgb(156, 220, 254),
823
+ // Light blue
824
+ contextLow: rgb(35, 134, 54),
825
+ // GitHub green
826
+ contextMedium: rgb(210, 153, 34),
827
+ // GitHub orange
828
+ contextHigh: rgb(248, 81, 73),
829
+ // GitHub red
830
+ linesAdded: rgb(35, 134, 54),
831
+ // GitHub green
832
+ linesRemoved: rgb(248, 81, 73),
833
+ // GitHub red
834
+ cost: rgb(163, 113, 247),
835
+ // Purple
836
+ model: rgb(201, 209, 217),
837
+ // Gray
838
+ duration: rgb(110, 118, 129),
839
+ // Dark gray
840
+ accent: rgb(88, 166, 255),
841
+ // GitHub blue
842
+ cacheHigh: rgb(35, 134, 54),
843
+ // GitHub green
844
+ cacheMedium: rgb(210, 153, 34),
845
+ // GitHub orange
846
+ cacheLow: rgb(248, 81, 73),
847
+ // GitHub red
848
+ cacheRead: rgb(88, 166, 255),
849
+ // GitHub blue
850
+ cacheWrite: rgb(163, 113, 247),
851
+ // Purple
852
+ toolsRunning: rgb(210, 153, 34),
853
+ // GitHub orange
854
+ toolsCompleted: rgb(35, 134, 54),
855
+ // GitHub green
856
+ toolsError: rgb(248, 81, 73),
857
+ // GitHub red
858
+ toolsName: rgb(88, 166, 255),
859
+ // GitHub blue
860
+ toolsTarget: rgb(201, 209, 217),
861
+ // Gray
862
+ toolsCount: rgb(163, 113, 247)
863
+ // Purple
864
+ })
865
+ };
866
+
867
+ // src/ui/theme/themes/monokai-theme.ts
868
+ var MONOKAI_THEME = {
869
+ name: "monokai",
870
+ description: "Vibrant, high-contrast",
871
+ colors: createThemeColors({
872
+ branch: rgb(102, 217, 239),
873
+ // Cyan
874
+ changes: rgb(249, 26, 114),
875
+ // Pink
876
+ contextLow: rgb(166, 226, 46),
877
+ // Green
878
+ contextMedium: rgb(253, 151, 31),
879
+ // Orange
880
+ contextHigh: rgb(249, 26, 114),
881
+ // Pink
882
+ linesAdded: rgb(166, 226, 46),
883
+ // Green
884
+ linesRemoved: rgb(249, 26, 114),
885
+ // Pink
886
+ cost: rgb(254, 128, 25),
887
+ // Bright orange
888
+ model: rgb(174, 129, 255),
889
+ // Purple
890
+ duration: rgb(102, 217, 239),
891
+ // Cyan
892
+ accent: rgb(249, 26, 114),
893
+ // Pink
894
+ cacheHigh: rgb(166, 226, 46),
895
+ // Green
896
+ cacheMedium: rgb(253, 151, 31),
897
+ // Orange
898
+ cacheLow: rgb(249, 26, 114),
899
+ // Pink
900
+ cacheRead: rgb(102, 217, 239),
901
+ // Cyan
902
+ cacheWrite: rgb(174, 129, 255),
903
+ // Purple
904
+ toolsRunning: rgb(253, 151, 31),
905
+ // Orange
906
+ toolsCompleted: rgb(166, 226, 46),
907
+ // Green
908
+ toolsError: rgb(249, 26, 114),
909
+ // Pink
910
+ toolsName: rgb(102, 217, 239),
911
+ // Cyan
912
+ toolsTarget: rgb(174, 129, 255),
913
+ // Purple
914
+ toolsCount: rgb(254, 128, 25)
915
+ // Bright orange
916
+ })
917
+ };
918
+
919
+ // src/ui/theme/themes/muted-gray-theme.ts
920
+ var MUTED_GRAY_THEME = {
921
+ name: "muted-gray",
922
+ description: "Very subtle grays with almost invisible progress bar",
923
+ colors: createThemeColors({
924
+ branch: rgb(156, 163, 175),
925
+ // Slate gray
926
+ changes: rgb(148, 163, 184),
927
+ // Lighter slate
928
+ contextLow: rgb(148, 163, 184),
929
+ // Subtle gray (low)
930
+ contextMedium: rgb(160, 174, 192),
931
+ // Medium gray
932
+ contextHigh: rgb(175, 188, 201),
933
+ // Light gray (high)
934
+ linesAdded: rgb(148, 163, 184),
935
+ linesRemoved: rgb(148, 163, 184),
936
+ cost: rgb(156, 163, 175),
937
+ model: rgb(148, 163, 184),
938
+ duration: rgb(107, 114, 128),
939
+ accent: rgb(156, 163, 175),
940
+ cacheHigh: rgb(148, 163, 184),
941
+ cacheMedium: rgb(160, 174, 192),
942
+ cacheLow: rgb(175, 188, 201),
943
+ cacheRead: rgb(156, 163, 175),
944
+ cacheWrite: rgb(148, 163, 184),
945
+ toolsRunning: rgb(160, 174, 192),
946
+ // Medium gray
947
+ toolsCompleted: rgb(148, 163, 184),
948
+ // Subtle gray
949
+ toolsError: rgb(175, 188, 201),
950
+ // Light gray
951
+ toolsName: rgb(156, 163, 175),
952
+ // Slate gray
953
+ toolsTarget: rgb(148, 163, 184),
954
+ // Lighter slate
955
+ toolsCount: rgb(156, 163, 175)
956
+ // Slate gray
957
+ })
958
+ };
959
+
960
+ // src/ui/theme/themes/nord-theme.ts
961
+ var NORD_THEME = {
962
+ name: "nord",
963
+ description: "Arctic, north-bluish color palette",
964
+ colors: createThemeColors({
965
+ branch: rgb(136, 192, 208),
966
+ // Nordic cyan
967
+ changes: rgb(143, 188, 187),
968
+ // Nordic blue-gray
969
+ contextLow: rgb(163, 190, 140),
970
+ // Nordic green
971
+ contextMedium: rgb(235, 203, 139),
972
+ // Nordic yellow
973
+ contextHigh: rgb(191, 97, 106),
974
+ // Nordic red
975
+ linesAdded: rgb(163, 190, 140),
976
+ // Nordic green
977
+ linesRemoved: rgb(191, 97, 106),
978
+ // Nordic red
979
+ cost: rgb(216, 222, 233),
980
+ // Nordic white
981
+ model: rgb(129, 161, 193),
982
+ // Nordic blue
983
+ duration: rgb(94, 129, 172),
984
+ // Nordic dark blue
985
+ accent: rgb(136, 192, 208),
986
+ // Nordic cyan
987
+ cacheHigh: rgb(163, 190, 140),
988
+ // Nordic green
989
+ cacheMedium: rgb(235, 203, 139),
990
+ // Nordic yellow
991
+ cacheLow: rgb(191, 97, 106),
992
+ // Nordic red
993
+ cacheRead: rgb(136, 192, 208),
994
+ // Nordic cyan
995
+ cacheWrite: rgb(129, 161, 193),
996
+ // Nordic blue
997
+ toolsRunning: rgb(235, 203, 139),
998
+ // Nordic yellow
999
+ toolsCompleted: rgb(163, 190, 140),
1000
+ // Nordic green
1001
+ toolsError: rgb(191, 97, 106),
1002
+ // Nordic red
1003
+ toolsName: rgb(136, 192, 208),
1004
+ // Nordic cyan
1005
+ toolsTarget: rgb(129, 161, 193),
1006
+ // Nordic blue
1007
+ toolsCount: rgb(216, 222, 233)
1008
+ // Nordic white
1009
+ })
1010
+ };
1011
+
1012
+ // src/ui/theme/themes/one-dark-pro-theme.ts
1013
+ var ONE_DARK_PRO_THEME = {
1014
+ name: "one-dark-pro",
1015
+ description: "Atom's iconic theme",
1016
+ colors: createThemeColors({
1017
+ branch: rgb(97, 175, 239),
1018
+ // Blue
1019
+ changes: rgb(152, 195, 121),
1020
+ // Green
1021
+ contextLow: rgb(152, 195, 121),
1022
+ // Green
1023
+ contextMedium: rgb(229, 192, 123),
1024
+ // Yellow
1025
+ contextHigh: rgb(224, 108, 117),
1026
+ // Red
1027
+ linesAdded: rgb(152, 195, 121),
1028
+ // Green
1029
+ linesRemoved: rgb(224, 108, 117),
1030
+ // Red
1031
+ cost: rgb(209, 154, 102),
1032
+ // Orange
1033
+ model: rgb(171, 178, 191),
1034
+ // Gray
1035
+ duration: rgb(125, 148, 173),
1036
+ // Dark gray
1037
+ accent: rgb(97, 175, 239),
1038
+ // Blue
1039
+ cacheHigh: rgb(152, 195, 121),
1040
+ // Green
1041
+ cacheMedium: rgb(229, 192, 123),
1042
+ // Yellow
1043
+ cacheLow: rgb(224, 108, 117),
1044
+ // Red
1045
+ cacheRead: rgb(97, 175, 239),
1046
+ // Blue
1047
+ cacheWrite: rgb(171, 178, 191),
1048
+ // Gray
1049
+ toolsRunning: rgb(229, 192, 123),
1050
+ // Yellow
1051
+ toolsCompleted: rgb(152, 195, 121),
1052
+ // Green
1053
+ toolsError: rgb(224, 108, 117),
1054
+ // Red
1055
+ toolsName: rgb(97, 175, 239),
1056
+ // Blue
1057
+ toolsTarget: rgb(171, 178, 191),
1058
+ // Gray
1059
+ toolsCount: rgb(209, 154, 102)
1060
+ // Orange
1061
+ })
1062
+ };
1063
+
1064
+ // src/ui/theme/themes/professional-blue-theme.ts
1065
+ var PROFESSIONAL_BLUE_THEME = {
1066
+ name: "professional-blue",
1067
+ description: "Clean, business-oriented blue color scheme",
1068
+ colors: createThemeColors({
1069
+ branch: rgb(37, 99, 235),
1070
+ // Royal blue
1071
+ changes: rgb(148, 163, 184),
1072
+ // Slate gray
1073
+ contextLow: rgb(96, 165, 250),
1074
+ // Light blue
1075
+ contextMedium: rgb(251, 191, 36),
1076
+ // Amber
1077
+ contextHigh: rgb(248, 113, 113),
1078
+ // Red
1079
+ linesAdded: rgb(74, 222, 128),
1080
+ // Green
1081
+ linesRemoved: rgb(248, 113, 113),
1082
+ // Red
1083
+ cost: rgb(251, 146, 60),
1084
+ // Orange
1085
+ model: rgb(167, 139, 250),
1086
+ // Purple
1087
+ duration: rgb(203, 213, 225),
1088
+ // Light gray
1089
+ accent: rgb(37, 99, 235),
1090
+ // Royal blue
1091
+ cacheHigh: rgb(74, 222, 128),
1092
+ // Green
1093
+ cacheMedium: rgb(251, 191, 36),
1094
+ // Amber
1095
+ cacheLow: rgb(248, 113, 113),
1096
+ // Red
1097
+ cacheRead: rgb(96, 165, 250),
1098
+ // Light blue
1099
+ cacheWrite: rgb(167, 139, 250),
1100
+ // Purple
1101
+ toolsRunning: rgb(251, 191, 36),
1102
+ // Amber
1103
+ toolsCompleted: rgb(74, 222, 128),
1104
+ // Green
1105
+ toolsError: rgb(248, 113, 113),
1106
+ // Red
1107
+ toolsName: rgb(37, 99, 235),
1108
+ // Royal blue
1109
+ toolsTarget: rgb(148, 163, 184),
1110
+ // Slate gray
1111
+ toolsCount: rgb(167, 139, 250)
1112
+ // Purple
1113
+ })
1114
+ };
1115
+
1116
+ // src/ui/theme/themes/rose-pine-theme.ts
1117
+ var ROSE_PINE_THEME = {
1118
+ name: "rose-pine",
1119
+ description: "Rose/violet themed",
1120
+ colors: createThemeColors({
1121
+ branch: rgb(156, 207, 216),
1122
+ // Pine cyan
1123
+ changes: rgb(235, 188, 186),
1124
+ // Rose
1125
+ contextLow: rgb(156, 207, 216),
1126
+ // Pine cyan
1127
+ contextMedium: rgb(233, 201, 176),
1128
+ // Pine beige
1129
+ contextHigh: rgb(235, 111, 146),
1130
+ // Pine red
1131
+ linesAdded: rgb(156, 207, 216),
1132
+ // Pine cyan
1133
+ linesRemoved: rgb(235, 111, 146),
1134
+ // Pine red
1135
+ cost: rgb(226, 185, 218),
1136
+ // Pine pink
1137
+ model: rgb(224, 208, 245),
1138
+ // Pine violet
1139
+ duration: rgb(148, 137, 176),
1140
+ // Pine mute
1141
+ accent: rgb(235, 111, 146),
1142
+ // Pine red
1143
+ cacheHigh: rgb(156, 207, 216),
1144
+ // Pine cyan
1145
+ cacheMedium: rgb(233, 201, 176),
1146
+ // Pine beige
1147
+ cacheLow: rgb(235, 111, 146),
1148
+ // Pine red
1149
+ cacheRead: rgb(156, 207, 216),
1150
+ // Pine cyan
1151
+ cacheWrite: rgb(224, 208, 245),
1152
+ // Pine violet
1153
+ toolsRunning: rgb(233, 201, 176),
1154
+ // Pine beige
1155
+ toolsCompleted: rgb(156, 207, 216),
1156
+ // Pine cyan
1157
+ toolsError: rgb(235, 111, 146),
1158
+ // Pine red
1159
+ toolsName: rgb(156, 207, 216),
1160
+ // Pine cyan
1161
+ toolsTarget: rgb(224, 208, 245),
1162
+ // Pine violet
1163
+ toolsCount: rgb(226, 185, 218)
1164
+ // Pine pink
1165
+ })
1166
+ };
1167
+
1168
+ // src/ui/theme/themes/semantic-classic-theme.ts
1169
+ var SEMANTIC_CLASSIC_THEME = {
1170
+ name: "semantic-classic",
1171
+ description: "Industry-standard semantic colors for maximum clarity",
1172
+ colors: createThemeColors({
1173
+ branch: rgb(59, 130, 246),
1174
+ // Blue
1175
+ changes: rgb(107, 114, 128),
1176
+ // Gray
1177
+ contextLow: rgb(34, 197, 94),
1178
+ // Green
1179
+ contextMedium: rgb(234, 179, 8),
1180
+ // Yellow
1181
+ contextHigh: rgb(239, 68, 68),
1182
+ // Red
1183
+ linesAdded: rgb(34, 197, 94),
1184
+ // Green
1185
+ linesRemoved: rgb(239, 68, 68),
1186
+ // Red
1187
+ cost: rgb(249, 115, 22),
1188
+ // Orange
1189
+ model: rgb(99, 102, 241),
1190
+ // Indigo
1191
+ duration: rgb(107, 114, 128),
1192
+ // Gray
1193
+ accent: rgb(59, 130, 246),
1194
+ // Blue
1195
+ cacheHigh: rgb(34, 197, 94),
1196
+ // Green
1197
+ cacheMedium: rgb(234, 179, 8),
1198
+ // Yellow
1199
+ cacheLow: rgb(239, 68, 68),
1200
+ // Red
1201
+ cacheRead: rgb(59, 130, 246),
1202
+ // Blue
1203
+ cacheWrite: rgb(99, 102, 241),
1204
+ // Indigo
1205
+ toolsRunning: rgb(234, 179, 8),
1206
+ // Yellow
1207
+ toolsCompleted: rgb(34, 197, 94),
1208
+ // Green
1209
+ toolsError: rgb(239, 68, 68),
1210
+ // Red
1211
+ toolsName: rgb(59, 130, 246),
1212
+ // Blue
1213
+ toolsTarget: rgb(107, 114, 128),
1214
+ // Gray
1215
+ toolsCount: rgb(99, 102, 241)
1216
+ // Indigo
1217
+ })
1218
+ };
1219
+
1220
+ // src/ui/theme/themes/slate-blue-theme.ts
1221
+ var SLATE_BLUE_THEME = {
1222
+ name: "slate-blue",
1223
+ description: "Calm blue-grays with gentle ocean tones",
1224
+ colors: createThemeColors({
1225
+ branch: rgb(100, 116, 139),
1226
+ // Cool slate
1227
+ changes: rgb(148, 163, 184),
1228
+ // Neutral slate
1229
+ contextLow: rgb(148, 163, 184),
1230
+ // Subtle slate-blue (low)
1231
+ contextMedium: rgb(160, 174, 192),
1232
+ // Medium slate
1233
+ contextHigh: rgb(175, 188, 201),
1234
+ // Light slate (high)
1235
+ linesAdded: rgb(148, 163, 184),
1236
+ linesRemoved: rgb(148, 163, 184),
1237
+ cost: rgb(156, 163, 175),
1238
+ model: rgb(148, 163, 184),
1239
+ duration: rgb(100, 116, 139),
1240
+ accent: rgb(100, 116, 139),
1241
+ cacheHigh: rgb(148, 163, 184),
1242
+ cacheMedium: rgb(160, 174, 192),
1243
+ cacheLow: rgb(175, 188, 201),
1244
+ cacheRead: rgb(100, 116, 139),
1245
+ cacheWrite: rgb(148, 163, 184),
1246
+ toolsRunning: rgb(160, 174, 192),
1247
+ // Medium slate
1248
+ toolsCompleted: rgb(148, 163, 184),
1249
+ // Subtle slate-blue
1250
+ toolsError: rgb(175, 188, 201),
1251
+ // Light slate
1252
+ toolsName: rgb(100, 116, 139),
1253
+ // Cool slate
1254
+ toolsTarget: rgb(148, 163, 184),
1255
+ // Neutral slate
1256
+ toolsCount: rgb(156, 163, 175)
1257
+ // Light slate
1258
+ })
1259
+ };
1260
+
1261
+ // src/ui/theme/themes/solarized-dark-theme.ts
1262
+ var SOLARIZED_DARK_THEME = {
1263
+ name: "solarized-dark",
1264
+ description: "Precise CIELAB lightness",
1265
+ colors: createThemeColors({
1266
+ branch: rgb(38, 139, 210),
1267
+ // Blue
1268
+ changes: rgb(133, 153, 0),
1269
+ // Olive
1270
+ contextLow: rgb(133, 153, 0),
1271
+ // Olive
1272
+ contextMedium: rgb(181, 137, 0),
1273
+ // Yellow
1274
+ contextHigh: rgb(220, 50, 47),
1275
+ // Red
1276
+ linesAdded: rgb(133, 153, 0),
1277
+ // Olive
1278
+ linesRemoved: rgb(220, 50, 47),
1279
+ // Red
1280
+ cost: rgb(203, 75, 22),
1281
+ // Orange
1282
+ model: rgb(131, 148, 150),
1283
+ // Base0
1284
+ duration: rgb(88, 110, 117),
1285
+ // Base01
1286
+ accent: rgb(38, 139, 210),
1287
+ // Blue
1288
+ cacheHigh: rgb(133, 153, 0),
1289
+ // Olive
1290
+ cacheMedium: rgb(181, 137, 0),
1291
+ // Yellow
1292
+ cacheLow: rgb(220, 50, 47),
1293
+ // Red
1294
+ cacheRead: rgb(38, 139, 210),
1295
+ // Blue
1296
+ cacheWrite: rgb(147, 161, 161),
1297
+ // Base1
1298
+ toolsRunning: rgb(181, 137, 0),
1299
+ // Yellow
1300
+ toolsCompleted: rgb(133, 153, 0),
1301
+ // Olive
1302
+ toolsError: rgb(220, 50, 47),
1303
+ // Red
1304
+ toolsName: rgb(38, 139, 210),
1305
+ // Blue
1306
+ toolsTarget: rgb(131, 148, 150),
1307
+ // Base0
1308
+ toolsCount: rgb(203, 75, 22)
1309
+ // Orange
1310
+ })
1311
+ };
1312
+
1313
+ // src/ui/theme/themes/tokyo-night-theme.ts
1314
+ var TOKYO_NIGHT_THEME = {
1315
+ name: "tokyo-night",
1316
+ description: "Clean, dark Tokyo-inspired",
1317
+ colors: createThemeColors({
1318
+ branch: rgb(122, 132, 173),
1319
+ // Blue
1320
+ changes: rgb(122, 162, 247),
1321
+ // Dark blue
1322
+ contextLow: rgb(146, 180, 203),
1323
+ // Cyan
1324
+ contextMedium: rgb(232, 166, 162),
1325
+ // Pink-red
1326
+ contextHigh: rgb(249, 86, 119),
1327
+ // Red
1328
+ linesAdded: rgb(146, 180, 203),
1329
+ // Cyan
1330
+ linesRemoved: rgb(249, 86, 119),
1331
+ // Red
1332
+ cost: rgb(158, 206, 209),
1333
+ // Teal
1334
+ model: rgb(169, 177, 214),
1335
+ // White-ish
1336
+ duration: rgb(113, 119, 161),
1337
+ // Dark blue-gray
1338
+ accent: rgb(122, 132, 173),
1339
+ // Blue
1340
+ cacheHigh: rgb(146, 180, 203),
1341
+ // Cyan
1342
+ cacheMedium: rgb(232, 166, 162),
1343
+ // Pink-red
1344
+ cacheLow: rgb(249, 86, 119),
1345
+ // Red
1346
+ cacheRead: rgb(122, 132, 173),
1347
+ // Blue
1348
+ cacheWrite: rgb(169, 177, 214),
1349
+ // White-ish
1350
+ toolsRunning: rgb(232, 166, 162),
1351
+ // Pink-red
1352
+ toolsCompleted: rgb(146, 180, 203),
1353
+ // Cyan
1354
+ toolsError: rgb(249, 86, 119),
1355
+ // Red
1356
+ toolsName: rgb(122, 132, 173),
1357
+ // Blue
1358
+ toolsTarget: rgb(169, 177, 214),
1359
+ // White-ish
1360
+ toolsCount: rgb(158, 206, 209)
1361
+ // Teal
1362
+ })
1363
+ };
1364
+
1365
+ // src/ui/theme/themes/vscode-dark-plus-theme.ts
1366
+ var VSCODE_DARK_PLUS_THEME = {
1367
+ name: "vscode-dark-plus",
1368
+ description: "Visual Studio Code's default dark theme (claude-scope default)",
1369
+ colors: createThemeColors({
1370
+ branch: rgb(0, 122, 204),
1371
+ // VSCode blue
1372
+ changes: rgb(78, 201, 176),
1373
+ // Teal
1374
+ contextLow: rgb(78, 201, 176),
1375
+ // Teal
1376
+ contextMedium: rgb(220, 220, 170),
1377
+ // Yellow
1378
+ contextHigh: rgb(244, 71, 71),
1379
+ // Red
1380
+ linesAdded: rgb(78, 201, 176),
1381
+ // Teal
1382
+ linesRemoved: rgb(244, 71, 71),
1383
+ // Red
1384
+ cost: rgb(206, 145, 120),
1385
+ // Orange
1386
+ model: rgb(171, 178, 191),
1387
+ // Gray
1388
+ duration: rgb(125, 148, 173),
1389
+ // Dark gray
1390
+ accent: rgb(0, 122, 204),
1391
+ // VSCode blue
1392
+ cacheHigh: rgb(78, 201, 176),
1393
+ // Teal
1394
+ cacheMedium: rgb(220, 220, 170),
1395
+ // Yellow
1396
+ cacheLow: rgb(244, 71, 71),
1397
+ // Red
1398
+ cacheRead: rgb(0, 122, 204),
1399
+ // VSCode blue
1400
+ cacheWrite: rgb(171, 178, 191),
1401
+ // Gray
1402
+ toolsRunning: rgb(251, 191, 36),
1403
+ // Yellow
1404
+ toolsCompleted: rgb(74, 222, 128),
1405
+ // Green
1406
+ toolsError: rgb(248, 113, 113),
1407
+ // Red
1408
+ toolsName: rgb(96, 165, 250),
1409
+ // Blue
1410
+ toolsTarget: rgb(156, 163, 175),
1411
+ // Gray
1412
+ toolsCount: rgb(167, 139, 250)
1413
+ // Purple
1414
+ })
1415
+ };
1416
+
1417
+ // src/ui/theme/index.ts
1418
+ var DEFAULT_THEME = VSCODE_DARK_PLUS_THEME.colors;
1419
+
1420
+ // src/widgets/core/stdin-data-widget.ts
1421
+ var StdinDataWidget = class {
1422
+ /**
1423
+ * Stored stdin data from last update
1424
+ */
1425
+ data = null;
1426
+ /**
1427
+ * Widget enabled state
1428
+ */
1429
+ enabled = true;
1430
+ /**
1431
+ * Initialize widget with context
1432
+ * @param context - Widget initialization context
1433
+ */
1434
+ async initialize(context) {
1435
+ this.enabled = context.config?.enabled !== false;
1436
+ }
1437
+ /**
1438
+ * Update widget with new stdin data
1439
+ * @param data - Stdin data from Claude Code
1440
+ */
1441
+ async update(data) {
1442
+ this.data = data;
1443
+ }
1444
+ /**
1445
+ * Get stored stdin data
1446
+ * @returns Stored stdin data
1447
+ * @throws Error if data has not been initialized (update not called)
1448
+ */
1449
+ getData() {
1450
+ if (!this.data) {
1451
+ throw new Error(`Widget ${this.id} data not initialized. Call update() first.`);
1452
+ }
1453
+ return this.data;
1454
+ }
1455
+ /**
1456
+ * Check if widget is enabled
1457
+ * @returns true if widget should render
1458
+ */
1459
+ isEnabled() {
1460
+ return this.enabled;
1461
+ }
1462
+ /**
1463
+ * Template method - final, subclasses implement renderWithData()
1464
+ *
1465
+ * Handles null data checks and calls renderWithData() hook.
1466
+ *
1467
+ * @param context - Render context
1468
+ * @returns Rendered string, or null if widget should not display
1469
+ */
1470
+ async render(context) {
1471
+ if (!this.data || !this.enabled) {
1472
+ return null;
1473
+ }
1474
+ return this.renderWithData(this.data, context);
1475
+ }
1476
+ };
1477
+
1478
+ // src/widgets/active-tools/styles.ts
1479
+ function truncatePath(path2) {
1480
+ if (path2.length <= 30) {
1481
+ return path2;
1482
+ }
1483
+ const parts = path2.split("/");
1484
+ return `.../${parts[parts.length - 1]}`;
1485
+ }
1486
+ function formatTool(name, target, colors) {
1487
+ const nameStr = colorize(name, colors.tools.name);
1488
+ if (target) {
1489
+ const targetStr = colorize(`: ${truncatePath(target)}`, colors.tools.target);
1490
+ return `${nameStr}${targetStr}`;
1491
+ }
1492
+ return nameStr;
1493
+ }
1494
+ var activeToolsStyles = {
1495
+ /**
1496
+ * balanced: Running tools with ◐ spinner, completed aggregated with ✓ ×count
1497
+ */
1498
+ balanced: (data, colors) => {
1499
+ const parts = [];
1500
+ for (const tool of data.running.slice(-2)) {
1501
+ const indicator = colors ? colorize("\u25D0", colors.tools.running) : "\u25D0";
1502
+ parts.push(
1503
+ `${indicator} ${formatTool(tool.name, tool.target, colors ?? getDefaultColors())}`
1504
+ );
1505
+ }
1506
+ const sorted = Array.from(data.completed.entries()).sort((a, b) => b[1] - a[1]).slice(0, 4);
1507
+ for (const [name, count] of sorted) {
1508
+ const check = colors ? colorize("\u2713", colors.tools.completed) : "\u2713";
1509
+ const countStr = colors ? colorize(`\xD7${count}`, colors.tools.count) : `\xD7${count}`;
1510
+ parts.push(`${check} ${name} ${countStr}`);
1511
+ }
1512
+ if (parts.length === 0) {
1513
+ return "";
1514
+ }
1515
+ return parts.join(" | ");
388
1516
  },
1517
+ /**
1518
+ * compact: [ToolName] format for all tools
1519
+ */
1520
+ compact: (data, colors) => {
1521
+ const parts = [];
1522
+ const c = colors ?? getDefaultColors();
1523
+ for (const tool of data.running) {
1524
+ parts.push(`[${colorize(tool.name, c.tools.name)}]`);
1525
+ }
1526
+ for (const [name] of Array.from(data.completed.entries()).slice(0, 3)) {
1527
+ parts.push(`[${colorize(name, c.tools.completed)}]`);
1528
+ }
1529
+ if (parts.length === 0) {
1530
+ return "";
1531
+ }
1532
+ return parts.join(" ");
1533
+ },
1534
+ /**
1535
+ * minimal: Same as compact
1536
+ */
1537
+ minimal: (data, colors) => {
1538
+ const compactStyle = activeToolsStyles.compact;
1539
+ if (!compactStyle) return "";
1540
+ return compactStyle(data, colors);
1541
+ },
1542
+ /**
1543
+ * playful: Emojis (📖✏️✨🔄🔍📁) with tool names
1544
+ */
389
1545
  playful: (data, colors) => {
390
- if (data.changes && data.changes.files > 0) {
391
- const parts = [];
392
- if (data.changes.insertions > 0) parts.push(`\u2B06${data.changes.insertions}`);
393
- if (data.changes.deletions > 0) parts.push(`\u2B07${data.changes.deletions}`);
394
- if (parts.length > 0) {
395
- const branch2 = colors ? colorize(data.branch, colors.branch) : data.branch;
396
- return `\u{1F500} ${branch2} ${parts.join(" ")}`;
397
- }
1546
+ const parts = [];
1547
+ const emojis = {
1548
+ Read: "\u{1F4D6}",
1549
+ Write: "\u270F\uFE0F",
1550
+ Edit: "\u2728",
1551
+ Bash: "\u{1F504}",
1552
+ Grep: "\u{1F50D}",
1553
+ Glob: "\u{1F4C1}"
1554
+ };
1555
+ for (const tool of data.running.slice(-3)) {
1556
+ const emoji = emojis[tool.name] ?? "\u{1F527}";
1557
+ const nameStr = colors ? colorize(tool.name, colors.tools.name) : tool.name;
1558
+ parts.push(`${emoji} ${nameStr}`);
398
1559
  }
399
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
400
- return `\u{1F500} ${branch}`;
1560
+ if (parts.length === 0) {
1561
+ return "";
1562
+ }
1563
+ return parts.join(", ");
401
1564
  },
1565
+ /**
1566
+ * verbose: Full text labels "Running:" and "Completed:"
1567
+ */
402
1568
  verbose: (data, colors) => {
403
- if (data.changes && data.changes.files > 0) {
404
- const parts = [];
405
- if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions} insertions`);
406
- if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions} deletions`);
407
- if (parts.length > 0) {
408
- const branch2 = colors ? colorize(data.branch, colors.branch) : data.branch;
409
- const changes = colors ? colorize(`[${parts.join(", ")}]`, colors.changes) : `[${parts.join(", ")}]`;
410
- return `branch: ${branch2} ${changes}`;
411
- }
1569
+ const parts = [];
1570
+ const c = colors ?? getDefaultColors();
1571
+ for (const tool of data.running) {
1572
+ const label = colorize("Running:", c.tools.running);
1573
+ parts.push(`${label} ${formatTool(tool.name, tool.target, c)}`);
412
1574
  }
413
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
414
- return `branch: ${branch} (HEAD)`;
1575
+ const sorted = Array.from(data.completed.entries()).sort((a, b) => b[1] - a[1]).slice(0, 3);
1576
+ for (const [name, count] of sorted) {
1577
+ const label = colorize("Completed:", c.tools.completed);
1578
+ const countStr = colorize(`(${count}x)`, c.tools.count);
1579
+ parts.push(`${label} ${name} ${countStr}`);
1580
+ }
1581
+ if (parts.length === 0) {
1582
+ return "";
1583
+ }
1584
+ return parts.join(" | ");
415
1585
  },
1586
+ /**
1587
+ * labeled: "Tools:" prefix with all tools
1588
+ */
416
1589
  labeled: (data, colors) => {
417
- if (data.changes && data.changes.files > 0) {
418
- const parts = [];
419
- if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
420
- if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
421
- if (parts.length > 0) {
422
- const branch2 = colors ? colorize(data.branch, colors.branch) : data.branch;
423
- const changes = `${data.changes.files} files: ${parts.join("/")}`;
424
- return `Git: ${branch2} [${changes}]`;
425
- }
426
- }
427
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
428
- return `Git: ${branch}`;
1590
+ const c = colors ?? getDefaultColors();
1591
+ const allTools = [
1592
+ ...data.running.map((t) => {
1593
+ const indicator = colorize("\u25D0", c.tools.running);
1594
+ return `${indicator} ${formatTool(t.name, t.target, c)}`;
1595
+ }),
1596
+ ...Array.from(data.completed.entries()).slice(0, 3).map(([name, count]) => {
1597
+ const indicator = colorize("\u2713", c.tools.completed);
1598
+ const countStr = colorize(`\xD7${count}`, c.tools.count);
1599
+ return `${indicator} ${name} ${countStr}`;
1600
+ })
1601
+ ];
1602
+ if (allTools.length === 0) {
1603
+ return "";
1604
+ }
1605
+ const prefix = colors ? colorize("Tools:", c.semantic.info) : "Tools:";
1606
+ return `${prefix}: ${allTools.join(" | ")}`;
429
1607
  },
1608
+ /**
1609
+ * indicator: ● bullet indicators
1610
+ */
430
1611
  indicator: (data, colors) => {
431
- if (data.changes && data.changes.files > 0) {
432
- const parts = [];
433
- if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
434
- if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
435
- if (parts.length > 0) {
436
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
437
- const changes = colors ? colorize(`[${parts.join(" ")}]`, colors.changes) : `[${parts.join(" ")}]`;
438
- return `\u25CF ${branch} ${changes}`;
439
- }
1612
+ const parts = [];
1613
+ const c = colors ?? getDefaultColors();
1614
+ for (const tool of data.running) {
1615
+ const bullet = colorize("\u25CF", c.semantic.info);
1616
+ parts.push(`${bullet} ${formatTool(tool.name, tool.target, c)}`);
440
1617
  }
441
- return withIndicator(colors ? colorize(data.branch, colors.branch) : data.branch);
1618
+ for (const [name] of Array.from(data.completed.entries()).slice(0, 3)) {
1619
+ const bullet = colorize("\u25CF", c.tools.completed);
1620
+ parts.push(`${bullet} ${name}`);
1621
+ }
1622
+ if (parts.length === 0) {
1623
+ return "";
1624
+ }
1625
+ return parts.join(" | ");
442
1626
  }
443
1627
  };
1628
+ function getDefaultColors() {
1629
+ return {
1630
+ base: {
1631
+ text: "\x1B[37m",
1632
+ muted: "\x1B[90m",
1633
+ accent: "\x1B[36m",
1634
+ border: "\x1B[90m"
1635
+ },
1636
+ semantic: {
1637
+ success: "\x1B[32m",
1638
+ warning: "\x1B[33m",
1639
+ error: "\x1B[31m",
1640
+ info: "\x1B[36m"
1641
+ },
1642
+ git: {
1643
+ branch: "\x1B[36m",
1644
+ changes: "\x1B[33m"
1645
+ },
1646
+ context: {
1647
+ low: "\x1B[32m",
1648
+ medium: "\x1B[33m",
1649
+ high: "\x1B[31m",
1650
+ bar: "\x1B[37m"
1651
+ },
1652
+ lines: {
1653
+ added: "\x1B[32m",
1654
+ removed: "\x1B[31m"
1655
+ },
1656
+ cost: {
1657
+ amount: "\x1B[37m",
1658
+ currency: "\x1B[90m"
1659
+ },
1660
+ duration: {
1661
+ value: "\x1B[37m",
1662
+ unit: "\x1B[90m"
1663
+ },
1664
+ model: {
1665
+ name: "\x1B[36m",
1666
+ version: "\x1B[90m"
1667
+ },
1668
+ poker: {
1669
+ participating: "\x1B[37m",
1670
+ nonParticipating: "\x1B[90m",
1671
+ result: "\x1B[36m"
1672
+ },
1673
+ cache: {
1674
+ high: "\x1B[32m",
1675
+ medium: "\x1B[33m",
1676
+ low: "\x1B[31m",
1677
+ read: "\x1B[34m",
1678
+ write: "\x1B[35m"
1679
+ },
1680
+ tools: {
1681
+ running: "\x1B[33m",
1682
+ completed: "\x1B[32m",
1683
+ error: "\x1B[31m",
1684
+ name: "\x1B[34m",
1685
+ target: "\x1B[90m",
1686
+ count: "\x1B[35m"
1687
+ }
1688
+ };
1689
+ }
444
1690
 
445
- // src/widgets/git/git-widget.ts
446
- var GitWidget = class {
447
- id = "git";
448
- metadata = createWidgetMetadata(
449
- "Git Widget",
450
- "Displays current git branch",
451
- "1.0.0",
452
- "claude-scope",
453
- 0
454
- // First line
455
- );
456
- gitFactory;
457
- git = null;
458
- enabled = true;
459
- cwd = null;
460
- colors;
461
- styleFn = gitStyles.balanced;
1691
+ // src/widgets/active-tools/active-tools-widget.ts
1692
+ var ActiveToolsWidget = class extends StdinDataWidget {
1693
+ constructor(theme, transcriptProvider) {
1694
+ super();
1695
+ this.theme = theme;
1696
+ this.transcriptProvider = transcriptProvider;
1697
+ }
1698
+ id = "active-tools";
1699
+ metadata = {
1700
+ name: "Active Tools",
1701
+ description: "Active tools display from transcript",
1702
+ version: "1.0.0",
1703
+ author: "claude-scope",
1704
+ line: 2
1705
+ // Display on third line (0-indexed)
1706
+ };
1707
+ style = "balanced";
1708
+ tools = [];
1709
+ renderData;
1710
+ /**
1711
+ * Set display style
1712
+ * @param style - Style to use for rendering
1713
+ */
1714
+ setStyle(style) {
1715
+ this.style = style;
1716
+ }
1717
+ /**
1718
+ * Aggregate completed tools by name
1719
+ * @param tools - Array of tool entries
1720
+ * @returns Map of tool name to count
1721
+ */
1722
+ aggregateCompleted(tools) {
1723
+ const counts = /* @__PURE__ */ new Map();
1724
+ for (const tool of tools) {
1725
+ if (tool.status === "completed" || tool.status === "error") {
1726
+ const current = counts.get(tool.name) ?? 0;
1727
+ counts.set(tool.name, current + 1);
1728
+ }
1729
+ }
1730
+ return counts;
1731
+ }
462
1732
  /**
463
- * @param gitFactory - Optional factory function for creating IGit instances
464
- * If not provided, uses default createGit (production)
465
- * Tests can inject MockGit factory here
466
- * @param colors - Optional theme colors
1733
+ * Prepare render data from tools
1734
+ * @returns Render data with running, completed, and error tools
467
1735
  */
468
- constructor(gitFactory, colors) {
469
- this.gitFactory = gitFactory || createGit;
470
- this.colors = colors ?? DEFAULT_THEME;
1736
+ prepareRenderData() {
1737
+ const running = this.tools.filter((t) => t.status === "running");
1738
+ const completed = this.aggregateCompleted(this.tools);
1739
+ const errors = this.tools.filter((t) => t.status === "error");
1740
+ return { running, completed, errors };
471
1741
  }
472
- setStyle(style = "balanced") {
473
- const fn = gitStyles[style];
474
- if (fn) {
475
- this.styleFn = fn;
1742
+ /**
1743
+ * Update widget with new stdin data
1744
+ * @param data - Stdin data from Claude Code
1745
+ */
1746
+ async update(data) {
1747
+ await super.update(data);
1748
+ if (data.transcript_path) {
1749
+ this.tools = await this.transcriptProvider.parseTools(data.transcript_path);
1750
+ this.renderData = this.prepareRenderData();
1751
+ } else {
1752
+ this.tools = [];
1753
+ this.renderData = void 0;
476
1754
  }
477
1755
  }
478
- async initialize(context) {
479
- this.enabled = context.config?.enabled !== false;
480
- }
481
- async render(context) {
482
- if (!this.enabled || !this.git || !this.cwd) {
1756
+ /**
1757
+ * Render widget output
1758
+ * @param context - Render context
1759
+ * @returns Rendered string or null if no tools
1760
+ */
1761
+ renderWithData(data, context) {
1762
+ if (!this.renderData || this.tools.length === 0) {
483
1763
  return null;
484
1764
  }
485
- try {
486
- const status = await this.git.status();
487
- const branch = status.current || null;
488
- if (!branch) {
489
- return null;
490
- }
491
- let changes;
492
- try {
493
- const diffSummary = await this.git.diffSummary();
494
- if (diffSummary.fileCount > 0) {
495
- let insertions = 0;
496
- let deletions = 0;
497
- for (const file of diffSummary.files) {
498
- insertions += file.insertions || 0;
499
- deletions += file.deletions || 0;
500
- }
501
- if (insertions > 0 || deletions > 0) {
502
- changes = { files: diffSummary.fileCount, insertions, deletions };
503
- }
504
- }
505
- } catch {
506
- }
507
- const renderData = { branch, changes };
508
- return this.styleFn(renderData, this.colors.git);
509
- } catch {
1765
+ const styleFn = activeToolsStyles[this.style] ?? activeToolsStyles.balanced;
1766
+ if (!styleFn) {
510
1767
  return null;
511
1768
  }
1769
+ return styleFn(this.renderData, this.theme);
512
1770
  }
513
- async update(data) {
514
- if (data.cwd !== this.cwd) {
515
- this.cwd = data.cwd;
516
- this.git = this.gitFactory(data.cwd);
517
- }
518
- }
1771
+ /**
1772
+ * Check if widget should render
1773
+ * @returns true if there are tools to display
1774
+ */
519
1775
  isEnabled() {
520
- return this.enabled;
521
- }
522
- async cleanup() {
1776
+ return super.isEnabled() && this.tools.length > 0;
523
1777
  }
524
1778
  };
525
1779
 
526
- // src/widgets/git-tag/styles.ts
527
- var gitTagStyles = {
1780
+ // src/core/widget-types.ts
1781
+ function createWidgetMetadata(name, description, version = "1.0.0", author = "claude-scope", line = 0) {
1782
+ return {
1783
+ name,
1784
+ description,
1785
+ version,
1786
+ author,
1787
+ line
1788
+ };
1789
+ }
1790
+
1791
+ // src/widgets/cache-metrics/styles.ts
1792
+ function formatK(n) {
1793
+ if (n < 1e3) {
1794
+ return n.toString();
1795
+ }
1796
+ const k = n / 1e3;
1797
+ return k < 10 ? `${k.toFixed(1)}k` : `${Math.round(k)}k`;
1798
+ }
1799
+ function formatCurrency(usd) {
1800
+ if (usd < 5e-3 && usd > 0) {
1801
+ return "<$0.01";
1802
+ }
1803
+ return `$${usd.toFixed(2)}`;
1804
+ }
1805
+ function createProgressBar(percentage, width) {
1806
+ const filled = Math.round(percentage / 100 * width);
1807
+ const empty = width - filled;
1808
+ return "\u2588".repeat(filled) + "\u2591".repeat(empty);
1809
+ }
1810
+ function getCacheColor(hitRate, colors) {
1811
+ if (hitRate > 70) {
1812
+ return colors.cache.high;
1813
+ } else if (hitRate >= 40) {
1814
+ return colors.cache.medium;
1815
+ } else {
1816
+ return colors.cache.low;
1817
+ }
1818
+ }
1819
+ var cacheMetricsStyles = {
1820
+ /**
1821
+ * balanced: 💾 70% cached (35.0k tokens) with color coding
1822
+ */
528
1823
  balanced: (data, colors) => {
529
- const tag = data.tag || "\u2014";
530
- if (!colors) return tag;
531
- return colorize(tag, colors.branch);
1824
+ const { hitRate, cacheRead } = data;
1825
+ const color = colors ? getCacheColor(hitRate, colors) : "";
1826
+ const percentage = color ? `${color}${hitRate.toFixed(0)}%` : `${hitRate.toFixed(0)}%`;
1827
+ const tokens = colors ? `${colors.cache.read}${formatK(cacheRead)} tokens` : `${formatK(cacheRead)} tokens`;
1828
+ return `\u{1F4BE} ${percentage} cached (${tokens})`;
532
1829
  },
1830
+ /**
1831
+ * compact: Cache: 70%
1832
+ */
533
1833
  compact: (data, colors) => {
534
- if (!data.tag) return "\u2014";
535
- const tag = data.tag.replace(/^v/, "");
536
- if (!colors) return tag;
537
- return colorize(tag, colors.branch);
1834
+ const hitRate = data.hitRate.toFixed(0);
1835
+ if (colors) {
1836
+ return `${colors.cache.read}Cache: ${hitRate}%`;
1837
+ }
1838
+ return `Cache: ${hitRate}%`;
538
1839
  },
1840
+ /**
1841
+ * playful: 💾 [███████░] 70% with progress bar
1842
+ */
539
1843
  playful: (data, colors) => {
540
- const tag = data.tag || "\u2014";
541
- if (!colors) return `\u{1F3F7}\uFE0F ${tag}`;
542
- return `\u{1F3F7}\uFE0F ${colorize(tag, colors.branch)}`;
1844
+ const { hitRate } = data;
1845
+ const bar = createProgressBar(hitRate, 7);
1846
+ const color = colors ? getCacheColor(hitRate, colors) : "";
1847
+ const barAndPercent = color ? `${color}[${bar}] ${hitRate.toFixed(0)}%` : `[${bar}] ${hitRate.toFixed(0)}%`;
1848
+ return `\u{1F4BE} ${barAndPercent}`;
543
1849
  },
1850
+ /**
1851
+ * verbose: Cache: 35.0k tokens (70%) | $0.03 saved
1852
+ */
544
1853
  verbose: (data, colors) => {
545
- if (!data.tag) return "version: none";
546
- const tag = `version ${data.tag}`;
547
- if (!colors) return tag;
548
- return `version ${colorize(data.tag, colors.branch)}`;
1854
+ const { cacheRead, hitRate, savings } = data;
1855
+ const tokens = colors ? `${colors.cache.read}${formatK(cacheRead)} tokens` : `${formatK(cacheRead)} tokens`;
1856
+ const percent = `${hitRate.toFixed(0)}%`;
1857
+ const saved = colors ? `${colors.cache.write}${formatCurrency(savings)} saved` : `${formatCurrency(savings)} saved`;
1858
+ return `Cache: ${tokens} (${percent}) | ${saved}`;
549
1859
  },
1860
+ /**
1861
+ * labeled: Cache Hit: 70% | $0.03 saved
1862
+ */
550
1863
  labeled: (data, colors) => {
551
- const tag = data.tag || "none";
552
- if (!colors) return withLabel("Tag", tag);
553
- return withLabel("Tag", colorize(tag, colors.branch));
1864
+ const { hitRate, savings } = data;
1865
+ const percent = colors ? `${colors.cache.read}${hitRate.toFixed(0)}%` : `${hitRate.toFixed(0)}%`;
1866
+ const saved = colors ? `${colors.cache.write}${formatCurrency(savings)} saved` : `${formatCurrency(savings)} saved`;
1867
+ return `Cache Hit: ${percent} | ${saved}`;
554
1868
  },
1869
+ /**
1870
+ * indicator: ● 70% cached
1871
+ */
555
1872
  indicator: (data, colors) => {
556
- const tag = data.tag || "\u2014";
557
- if (!colors) return withIndicator(tag);
558
- return withIndicator(colorize(tag, colors.branch));
1873
+ const { hitRate } = data;
1874
+ const color = colors ? getCacheColor(hitRate, colors) : "";
1875
+ const percentage = color ? `${color}${hitRate.toFixed(0)}%` : `${hitRate.toFixed(0)}%`;
1876
+ return `\u25CF ${percentage} cached`;
1877
+ },
1878
+ /**
1879
+ * breakdown: Multi-line with ├─ Read: and └─ Write: breakdown
1880
+ */
1881
+ breakdown: (data, colors) => {
1882
+ const { cacheRead, cacheWrite, hitRate, savings } = data;
1883
+ const color = colors ? getCacheColor(hitRate, colors) : "";
1884
+ const percent = color ? `${color}${hitRate.toFixed(0)}%` : `${hitRate.toFixed(0)}%`;
1885
+ const saved = colors ? `${colors.cache.write}${formatCurrency(savings)} saved` : `${formatCurrency(savings)} saved`;
1886
+ const read = colors ? `${colors.cache.read}${formatK(cacheRead)}` : formatK(cacheRead);
1887
+ const write = colors ? `${colors.cache.write}${formatK(cacheWrite)}` : formatK(cacheWrite);
1888
+ return [`\u{1F4BE} ${percent} cached | ${saved}`, `\u251C\u2500 Read: ${read}`, `\u2514\u2500 Write: ${write}`].join("\n");
559
1889
  }
560
1890
  };
561
1891
 
562
- // src/widgets/git/git-tag-widget.ts
563
- var GitTagWidget = class {
564
- id = "git-tag";
1892
+ // src/widgets/cache-metrics/cache-metrics-widget.ts
1893
+ var CacheMetricsWidget = class extends StdinDataWidget {
1894
+ id = "cache-metrics";
565
1895
  metadata = createWidgetMetadata(
566
- "Git Tag Widget",
567
- "Displays the latest git tag",
1896
+ "Cache Metrics",
1897
+ "Cache hit rate and savings display",
568
1898
  "1.0.0",
569
1899
  "claude-scope",
570
- 1
571
- // Second line
1900
+ 2
1901
+ // Third line
572
1902
  );
573
- gitFactory;
574
- git = null;
575
- enabled = true;
576
- cwd = null;
577
- colors;
578
- styleFn = gitTagStyles.balanced;
1903
+ theme;
1904
+ style = "balanced";
1905
+ renderData;
1906
+ constructor(theme) {
1907
+ super();
1908
+ this.theme = theme ?? DEFAULT_THEME;
1909
+ }
579
1910
  /**
580
- * @param gitFactory - Optional factory function for creating IGit instances
581
- * If not provided, uses default createGit (production)
582
- * Tests can inject MockGit factory here
583
- * @param colors - Optional theme colors
1911
+ * Set display style
584
1912
  */
585
- constructor(gitFactory, colors) {
586
- this.gitFactory = gitFactory || createGit;
587
- this.colors = colors ?? DEFAULT_THEME;
1913
+ setStyle(style) {
1914
+ this.style = style;
588
1915
  }
589
- setStyle(style = "balanced") {
590
- const fn = gitTagStyles[style];
591
- if (fn) {
592
- this.styleFn = fn;
1916
+ /**
1917
+ * Calculate cache metrics from context usage data
1918
+ * Returns null if no usage data is available
1919
+ */
1920
+ calculateMetrics(data) {
1921
+ const usage = data.context_window?.current_usage;
1922
+ if (!usage) {
1923
+ return null;
1924
+ }
1925
+ const cacheRead = usage.cache_read_input_tokens ?? 0;
1926
+ const cacheWrite = usage.cache_creation_input_tokens ?? 0;
1927
+ const inputTokens = usage.input_tokens ?? 0;
1928
+ const outputTokens = usage.output_tokens ?? 0;
1929
+ const totalTokens = inputTokens + outputTokens;
1930
+ const hitRate = inputTokens > 0 ? Math.round(cacheRead / inputTokens * 100) : 0;
1931
+ const costPerToken = 3e-6;
1932
+ const savings = cacheRead * 0.9 * costPerToken;
1933
+ return {
1934
+ cacheRead,
1935
+ cacheWrite,
1936
+ totalTokens,
1937
+ hitRate,
1938
+ savings
1939
+ };
1940
+ }
1941
+ /**
1942
+ * Update widget with new data and calculate metrics
1943
+ */
1944
+ async update(data) {
1945
+ await super.update(data);
1946
+ const metrics = this.calculateMetrics(data);
1947
+ this.renderData = metrics ?? void 0;
1948
+ }
1949
+ /**
1950
+ * Render the cache metrics display
1951
+ */
1952
+ renderWithData(_data, _context) {
1953
+ if (!this.renderData) {
1954
+ return null;
1955
+ }
1956
+ const styleFn = cacheMetricsStyles[this.style] ?? cacheMetricsStyles.balanced;
1957
+ if (!styleFn) {
1958
+ return null;
1959
+ }
1960
+ return styleFn(this.renderData, this.theme);
1961
+ }
1962
+ /**
1963
+ * Widget is enabled when we have cache metrics data
1964
+ */
1965
+ isEnabled() {
1966
+ return this.renderData !== void 0;
1967
+ }
1968
+ };
1969
+
1970
+ // src/core/style-types.ts
1971
+ var DEFAULT_WIDGET_STYLE = "balanced";
1972
+
1973
+ // src/providers/config-provider.ts
1974
+ var fs = __toESM(require("fs/promises"), 1);
1975
+ var os = __toESM(require("os"), 1);
1976
+ var path = __toESM(require("path"), 1);
1977
+ var ConfigProvider = class {
1978
+ cachedCounts;
1979
+ lastScan = 0;
1980
+ cacheInterval = 5e3;
1981
+ // 5 seconds
1982
+ /**
1983
+ * Get config counts with hybrid caching
1984
+ * Scans filesystem if cache is stale (>5 seconds)
1985
+ */
1986
+ async getConfigs(options = {}) {
1987
+ const now = Date.now();
1988
+ if (this.cachedCounts && now - this.lastScan < this.cacheInterval) {
1989
+ return this.cachedCounts;
1990
+ }
1991
+ this.cachedCounts = await this.scanConfigs(options);
1992
+ this.lastScan = now;
1993
+ return this.cachedCounts;
1994
+ }
1995
+ /**
1996
+ * Scan filesystem for Claude Code configurations
1997
+ */
1998
+ async scanConfigs(options) {
1999
+ let claudeMdCount = 0;
2000
+ let rulesCount = 0;
2001
+ let mcpCount = 0;
2002
+ let hooksCount = 0;
2003
+ const homeDir = os.homedir();
2004
+ const claudeDir = path.join(homeDir, ".claude");
2005
+ const cwd = options.cwd;
2006
+ if (await this.fileExists(path.join(claudeDir, "CLAUDE.md"))) {
2007
+ claudeMdCount++;
2008
+ }
2009
+ rulesCount += await this.countRulesInDir(path.join(claudeDir, "rules"));
2010
+ const userSettings = path.join(claudeDir, "settings.json");
2011
+ const userSettingsData = await this.readJsonFile(userSettings);
2012
+ if (userSettingsData) {
2013
+ mcpCount += this.countMcpServers(userSettingsData);
2014
+ hooksCount += this.countHooks(userSettingsData);
2015
+ }
2016
+ const userClaudeJson = path.join(homeDir, ".claude.json");
2017
+ const userClaudeData = await this.readJsonFile(userClaudeJson);
2018
+ if (userClaudeData) {
2019
+ const userMcpCount = this.countMcpServers(userClaudeData);
2020
+ mcpCount += Math.max(0, userMcpCount - this.countMcpServers(userSettingsData || {}));
2021
+ }
2022
+ if (cwd) {
2023
+ if (await this.fileExists(path.join(cwd, "CLAUDE.md"))) {
2024
+ claudeMdCount++;
2025
+ }
2026
+ if (await this.fileExists(path.join(cwd, "CLAUDE.local.md"))) {
2027
+ claudeMdCount++;
2028
+ }
2029
+ if (await this.fileExists(path.join(cwd, ".claude", "CLAUDE.md"))) {
2030
+ claudeMdCount++;
2031
+ }
2032
+ if (await this.fileExists(path.join(cwd, ".claude", "CLAUDE.local.md"))) {
2033
+ claudeMdCount++;
2034
+ }
2035
+ rulesCount += await this.countRulesInDir(path.join(cwd, ".claude", "rules"));
2036
+ const mcpJson = path.join(cwd, ".mcp.json");
2037
+ const mcpData = await this.readJsonFile(mcpJson);
2038
+ if (mcpData) {
2039
+ mcpCount += this.countMcpServers(mcpData);
2040
+ }
2041
+ const projectSettings = path.join(cwd, ".claude", "settings.json");
2042
+ const projectSettingsData = await this.readJsonFile(projectSettings);
2043
+ if (projectSettingsData) {
2044
+ mcpCount += this.countMcpServers(projectSettingsData);
2045
+ hooksCount += this.countHooks(projectSettingsData);
2046
+ }
2047
+ const localSettings = path.join(cwd, ".claude", "settings.local.json");
2048
+ const localSettingsData = await this.readJsonFile(localSettings);
2049
+ if (localSettingsData) {
2050
+ mcpCount += this.countMcpServers(localSettingsData);
2051
+ hooksCount += this.countHooks(localSettingsData);
2052
+ }
593
2053
  }
2054
+ return { claudeMdCount, rulesCount, mcpCount, hooksCount };
594
2055
  }
595
- async initialize(context) {
596
- this.enabled = context.config?.enabled !== false;
597
- }
598
- async render(context) {
599
- if (!this.enabled || !this.git || !this.cwd) {
600
- return null;
601
- }
2056
+ /**
2057
+ * Check if file exists
2058
+ */
2059
+ async fileExists(filePath) {
602
2060
  try {
603
- const latestTag = await (this.git.latestTag?.() ?? Promise.resolve(null));
604
- const renderData = { tag: latestTag };
605
- return this.styleFn(renderData, this.colors.git);
2061
+ await fs.access(filePath);
2062
+ return true;
606
2063
  } catch {
607
- return null;
608
- }
609
- }
610
- async update(data) {
611
- if (data.cwd !== this.cwd) {
612
- this.cwd = data.cwd;
613
- this.git = this.gitFactory(data.cwd);
2064
+ return false;
614
2065
  }
615
2066
  }
616
- isEnabled() {
617
- return this.enabled;
618
- }
619
- async cleanup() {
620
- }
621
- };
622
-
623
- // src/widgets/core/stdin-data-widget.ts
624
- var StdinDataWidget = class {
625
- /**
626
- * Stored stdin data from last update
627
- */
628
- data = null;
629
- /**
630
- * Widget enabled state
631
- */
632
- enabled = true;
633
- /**
634
- * Initialize widget with context
635
- * @param context - Widget initialization context
636
- */
637
- async initialize(context) {
638
- this.enabled = context.config?.enabled !== false;
639
- }
640
2067
  /**
641
- * Update widget with new stdin data
642
- * @param data - Stdin data from Claude Code
2068
+ * Read and parse JSON file
643
2069
  */
644
- async update(data) {
645
- this.data = data;
2070
+ async readJsonFile(filePath) {
2071
+ try {
2072
+ const content = await fs.readFile(filePath, "utf8");
2073
+ return JSON.parse(content);
2074
+ } catch {
2075
+ return null;
2076
+ }
646
2077
  }
647
2078
  /**
648
- * Get stored stdin data
649
- * @returns Stored stdin data
650
- * @throws Error if data has not been initialized (update not called)
2079
+ * Count MCP servers in config object
651
2080
  */
652
- getData() {
653
- if (!this.data) {
654
- throw new Error(`Widget ${this.id} data not initialized. Call update() first.`);
2081
+ countMcpServers(config) {
2082
+ if (!config || !config.mcpServers || typeof config.mcpServers !== "object") {
2083
+ return 0;
655
2084
  }
656
- return this.data;
2085
+ return Object.keys(config.mcpServers).length;
657
2086
  }
658
2087
  /**
659
- * Check if widget is enabled
660
- * @returns true if widget should render
2088
+ * Count hooks in config object
661
2089
  */
662
- isEnabled() {
663
- return this.enabled;
2090
+ countHooks(config) {
2091
+ if (!config || !config.hooks || typeof config.hooks !== "object") {
2092
+ return 0;
2093
+ }
2094
+ return Object.keys(config.hooks).length;
664
2095
  }
665
2096
  /**
666
- * Template method - final, subclasses implement renderWithData()
667
- *
668
- * Handles null data checks and calls renderWithData() hook.
669
- *
670
- * @param context - Render context
671
- * @returns Rendered string, or null if widget should not display
2097
+ * Recursively count .md files in directory
672
2098
  */
673
- async render(context) {
674
- if (!this.data || !this.enabled) {
675
- return null;
2099
+ async countRulesInDir(rulesDir) {
2100
+ const exists = await this.fileExists(rulesDir);
2101
+ if (!exists) return 0;
2102
+ try {
2103
+ let count = 0;
2104
+ const entries = await fs.readdir(rulesDir, { withFileTypes: true });
2105
+ for (const entry of entries) {
2106
+ const fullPath = path.join(rulesDir, entry.name);
2107
+ if (entry.isDirectory()) {
2108
+ count += await this.countRulesInDir(fullPath);
2109
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
2110
+ count++;
2111
+ }
2112
+ }
2113
+ return count;
2114
+ } catch {
2115
+ return 0;
676
2116
  }
677
- return this.renderWithData(this.data, context);
678
2117
  }
679
2118
  };
680
2119
 
681
- // src/widgets/model/styles.ts
682
- function getShortName(displayName) {
683
- return displayName.replace(/^Claude\s+/, "");
684
- }
685
- var modelStyles = {
686
- balanced: (data, colors) => {
687
- if (!colors) return data.displayName;
688
- return colorize(data.displayName, colors.name);
689
- },
690
- compact: (data, colors) => {
691
- const shortName = getShortName(data.displayName);
692
- if (!colors) return shortName;
693
- return colorize(shortName, colors.name);
694
- },
695
- playful: (data, colors) => {
696
- const shortName = getShortName(data.displayName);
697
- if (!colors) return `\u{1F916} ${shortName}`;
698
- return `\u{1F916} ${colorize(shortName, colors.name)}`;
699
- },
700
- technical: (data, colors) => {
701
- if (!colors) return data.id;
702
- const match = data.id.match(/^(.+?)-(\d[\d.]*)$/);
703
- if (match) {
704
- return colorize(match[1], colors.name) + colorize(`-${match[2]}`, colors.version);
2120
+ // src/widgets/config-count/styles.ts
2121
+ var configCountStyles = {
2122
+ balanced: (data) => {
2123
+ const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
2124
+ const parts = [];
2125
+ if (claudeMdCount > 0) {
2126
+ parts.push(`CLAUDE.md:${claudeMdCount}`);
705
2127
  }
706
- return colorize(data.id, colors.name);
2128
+ if (rulesCount > 0) {
2129
+ parts.push(`rules:${rulesCount}`);
2130
+ }
2131
+ if (mcpCount > 0) {
2132
+ parts.push(`MCPs:${mcpCount}`);
2133
+ }
2134
+ if (hooksCount > 0) {
2135
+ parts.push(`hooks:${hooksCount}`);
2136
+ }
2137
+ return parts.join(" \u2502 ");
707
2138
  },
708
- symbolic: (data, colors) => {
709
- const shortName = getShortName(data.displayName);
710
- if (!colors) return `\u25C6 ${shortName}`;
711
- return `\u25C6 ${colorize(shortName, colors.name)}`;
2139
+ compact: (data) => {
2140
+ const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
2141
+ const parts = [];
2142
+ if (claudeMdCount > 0) {
2143
+ parts.push(`${claudeMdCount} docs`);
2144
+ }
2145
+ if (rulesCount > 0) {
2146
+ parts.push(`${rulesCount} rules`);
2147
+ }
2148
+ if (mcpCount > 0) {
2149
+ parts.push(`${mcpCount} MCPs`);
2150
+ }
2151
+ if (hooksCount > 0) {
2152
+ const hookLabel = hooksCount === 1 ? "hook" : "hooks";
2153
+ parts.push(`${hooksCount} ${hookLabel}`);
2154
+ }
2155
+ return parts.join(" \u2502 ");
712
2156
  },
713
- labeled: (data, colors) => {
714
- const shortName = getShortName(data.displayName);
715
- if (!colors) return withLabel("Model", shortName);
716
- return withLabel("Model", colorize(shortName, colors.name));
2157
+ playful: (data) => {
2158
+ const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
2159
+ const parts = [];
2160
+ if (claudeMdCount > 0) {
2161
+ parts.push(`\u{1F4C4} CLAUDE.md:${claudeMdCount}`);
2162
+ }
2163
+ if (rulesCount > 0) {
2164
+ parts.push(`\u{1F4DC} rules:${rulesCount}`);
2165
+ }
2166
+ if (mcpCount > 0) {
2167
+ parts.push(`\u{1F50C} MCPs:${mcpCount}`);
2168
+ }
2169
+ if (hooksCount > 0) {
2170
+ parts.push(`\u{1FA9D} hooks:${hooksCount}`);
2171
+ }
2172
+ return parts.join(" \u2502 ");
717
2173
  },
718
- indicator: (data, colors) => {
719
- const shortName = getShortName(data.displayName);
720
- if (!colors) return withIndicator(shortName);
721
- return withIndicator(colorize(shortName, colors.name));
2174
+ verbose: (data) => {
2175
+ const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
2176
+ const parts = [];
2177
+ if (claudeMdCount > 0) {
2178
+ parts.push(`${claudeMdCount} CLAUDE.md`);
2179
+ }
2180
+ if (rulesCount > 0) {
2181
+ parts.push(`${rulesCount} rules`);
2182
+ }
2183
+ if (mcpCount > 0) {
2184
+ parts.push(`${mcpCount} MCP servers`);
2185
+ }
2186
+ if (hooksCount > 0) {
2187
+ parts.push(`${hooksCount} hook`);
2188
+ }
2189
+ return parts.join(" \u2502 ");
722
2190
  }
723
2191
  };
724
2192
 
725
- // src/widgets/model-widget.ts
726
- var ModelWidget = class extends StdinDataWidget {
727
- id = "model";
2193
+ // src/widgets/config-count-widget.ts
2194
+ var ConfigCountWidget = class {
2195
+ id = "config-count";
728
2196
  metadata = createWidgetMetadata(
729
- "Model",
730
- "Displays the current Claude model name",
2197
+ "Config Count",
2198
+ "Displays Claude Code configuration counts",
731
2199
  "1.0.0",
732
2200
  "claude-scope",
733
- 0
734
- // First line
2201
+ 1
2202
+ // Second line
735
2203
  );
736
- colors;
737
- styleFn = modelStyles.balanced;
738
- constructor(colors) {
739
- super();
740
- this.colors = colors ?? DEFAULT_THEME;
741
- }
742
- setStyle(style = "balanced") {
743
- const fn = modelStyles[style];
2204
+ configProvider = new ConfigProvider();
2205
+ configs;
2206
+ cwd;
2207
+ styleFn = configCountStyles.balanced;
2208
+ setStyle(style = DEFAULT_WIDGET_STYLE) {
2209
+ const fn = configCountStyles[style];
744
2210
  if (fn) {
745
2211
  this.styleFn = fn;
746
2212
  }
747
2213
  }
748
- renderWithData(data, _context) {
2214
+ async initialize() {
2215
+ }
2216
+ async update(data) {
2217
+ this.cwd = data.cwd;
2218
+ this.configs = await this.configProvider.getConfigs({ cwd: data.cwd });
2219
+ }
2220
+ isEnabled() {
2221
+ if (!this.configs) {
2222
+ return false;
2223
+ }
2224
+ const { claudeMdCount, rulesCount, mcpCount, hooksCount } = this.configs;
2225
+ return claudeMdCount > 0 || rulesCount > 0 || mcpCount > 0 || hooksCount > 0;
2226
+ }
2227
+ async render(context) {
2228
+ if (!this.configs) {
2229
+ return null;
2230
+ }
2231
+ const { claudeMdCount, rulesCount, mcpCount, hooksCount } = this.configs;
749
2232
  const renderData = {
750
- displayName: data.model.display_name,
751
- id: data.model.id
2233
+ claudeMdCount,
2234
+ rulesCount,
2235
+ mcpCount,
2236
+ hooksCount
752
2237
  };
753
- return this.styleFn(renderData, this.colors.model);
2238
+ return this.styleFn(renderData);
2239
+ }
2240
+ async cleanup() {
754
2241
  }
755
2242
  };
756
2243
 
2244
+ // src/ui/utils/style-utils.ts
2245
+ function withLabel(prefix, value) {
2246
+ if (prefix === "") return value;
2247
+ return `${prefix}: ${value}`;
2248
+ }
2249
+ function withIndicator(value) {
2250
+ return `\u25CF ${value}`;
2251
+ }
2252
+ function progressBar(percent, width = 10) {
2253
+ const clamped = Math.max(0, Math.min(100, percent));
2254
+ const filled = Math.round(clamped / 100 * width);
2255
+ const empty = width - filled;
2256
+ return "\u2588".repeat(filled) + "\u2591".repeat(empty);
2257
+ }
2258
+
757
2259
  // src/widgets/context/styles.ts
758
2260
  function getContextColor(percent, colors) {
759
2261
  const clampedPercent = Math.max(0, Math.min(100, percent));
@@ -941,83 +2443,6 @@ var CostWidget = class extends StdinDataWidget {
941
2443
  }
942
2444
  };
943
2445
 
944
- // src/widgets/lines/styles.ts
945
- var linesStyles = {
946
- balanced: (data, colors) => {
947
- if (!colors) return `+${data.added}/-${data.removed}`;
948
- const addedStr = colorize(`+${data.added}`, colors.added);
949
- const removedStr = colorize(`-${data.removed}`, colors.removed);
950
- return `${addedStr}/${removedStr}`;
951
- },
952
- compact: (data, colors) => {
953
- if (!colors) return `+${data.added}-${data.removed}`;
954
- const addedStr = colorize(`+${data.added}`, colors.added);
955
- const removedStr = colorize(`-${data.removed}`, colors.removed);
956
- return `${addedStr}${removedStr}`;
957
- },
958
- playful: (data, colors) => {
959
- if (!colors) return `\u2795${data.added} \u2796${data.removed}`;
960
- const addedStr = colorize(`\u2795${data.added}`, colors.added);
961
- const removedStr = colorize(`\u2796${data.removed}`, colors.removed);
962
- return `${addedStr} ${removedStr}`;
963
- },
964
- verbose: (data, colors) => {
965
- const parts = [];
966
- if (data.added > 0) {
967
- const text = `+${data.added} added`;
968
- parts.push(colors ? colorize(text, colors.added) : text);
969
- }
970
- if (data.removed > 0) {
971
- const text = `-${data.removed} removed`;
972
- parts.push(colors ? colorize(text, colors.removed) : text);
973
- }
974
- return parts.join(", ");
975
- },
976
- labeled: (data, colors) => {
977
- const addedStr = colors ? colorize(`+${data.added}`, colors.added) : `+${data.added}`;
978
- const removedStr = colors ? colorize(`-${data.removed}`, colors.removed) : `-${data.removed}`;
979
- const lines = `${addedStr}/${removedStr}`;
980
- return withLabel("Lines", lines);
981
- },
982
- indicator: (data, colors) => {
983
- const addedStr = colors ? colorize(`+${data.added}`, colors.added) : `+${data.added}`;
984
- const removedStr = colors ? colorize(`-${data.removed}`, colors.removed) : `-${data.removed}`;
985
- const lines = `${addedStr}/${removedStr}`;
986
- return withIndicator(lines);
987
- }
988
- };
989
-
990
- // src/widgets/lines-widget.ts
991
- var LinesWidget = class extends StdinDataWidget {
992
- id = "lines";
993
- metadata = createWidgetMetadata(
994
- "Lines",
995
- "Displays lines added/removed in session",
996
- "1.0.0",
997
- "claude-scope",
998
- 0
999
- // First line
1000
- );
1001
- colors;
1002
- styleFn = linesStyles.balanced;
1003
- constructor(colors) {
1004
- super();
1005
- this.colors = colors ?? DEFAULT_THEME;
1006
- }
1007
- setStyle(style = "balanced") {
1008
- const fn = linesStyles[style];
1009
- if (fn) {
1010
- this.styleFn = fn;
1011
- }
1012
- }
1013
- renderWithData(data, _context) {
1014
- const added = data.cost?.total_lines_added ?? 0;
1015
- const removed = data.cost?.total_lines_removed ?? 0;
1016
- const renderData = { added, removed };
1017
- return this.styleFn(renderData, this.colors.lines);
1018
- }
1019
- };
1020
-
1021
2446
  // src/widgets/duration/styles.ts
1022
2447
  var durationStyles = {
1023
2448
  balanced: (data, colors) => {
@@ -1102,305 +2527,533 @@ function formatDurationWithColors(ms, colors) {
1102
2527
  var DurationWidget = class extends StdinDataWidget {
1103
2528
  id = "duration";
1104
2529
  metadata = createWidgetMetadata(
1105
- "Duration",
1106
- "Displays elapsed session time",
2530
+ "Duration",
2531
+ "Displays elapsed session time",
2532
+ "1.0.0",
2533
+ "claude-scope",
2534
+ 0
2535
+ // First line
2536
+ );
2537
+ colors;
2538
+ styleFn = durationStyles.balanced;
2539
+ constructor(colors) {
2540
+ super();
2541
+ this.colors = colors ?? DEFAULT_THEME;
2542
+ }
2543
+ setStyle(style = "balanced") {
2544
+ const fn = durationStyles[style];
2545
+ if (fn) {
2546
+ this.styleFn = fn;
2547
+ }
2548
+ }
2549
+ renderWithData(data, _context) {
2550
+ if (!data.cost || data.cost.total_duration_ms === void 0) return null;
2551
+ const renderData = {
2552
+ durationMs: data.cost.total_duration_ms
2553
+ };
2554
+ return this.styleFn(renderData, this.colors.duration);
2555
+ }
2556
+ };
2557
+
2558
+ // src/widgets/empty-line-widget.ts
2559
+ var EmptyLineWidget = class extends StdinDataWidget {
2560
+ id = "empty-line";
2561
+ metadata = createWidgetMetadata(
2562
+ "Empty Line",
2563
+ "Empty line separator",
2564
+ "1.0.0",
2565
+ "claude-scope",
2566
+ 5
2567
+ // Sixth line (0-indexed)
2568
+ );
2569
+ /**
2570
+ * All styles return the same value (Braille Pattern Blank).
2571
+ * This method exists for API consistency with other widgets.
2572
+ */
2573
+ setStyle(_style) {
2574
+ }
2575
+ /**
2576
+ * Return Braille Pattern Blank to create a visible empty separator line.
2577
+ * U+2800 occupies cell width but appears blank, ensuring the line renders.
2578
+ */
2579
+ renderWithData(_data, _context) {
2580
+ return "\u2800";
2581
+ }
2582
+ };
2583
+
2584
+ // src/providers/git-provider.ts
2585
+ var import_node_child_process = require("node:child_process");
2586
+ var import_node_util = require("node:util");
2587
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
2588
+ var NativeGit = class {
2589
+ cwd;
2590
+ constructor(cwd) {
2591
+ this.cwd = cwd;
2592
+ }
2593
+ async status() {
2594
+ try {
2595
+ const { stdout } = await execFileAsync("git", ["status", "--branch", "--short"], {
2596
+ cwd: this.cwd
2597
+ });
2598
+ const match = stdout.match(/^##\s+(\S+)/m);
2599
+ const current = match ? match[1] : null;
2600
+ return { current };
2601
+ } catch {
2602
+ return { current: null };
2603
+ }
2604
+ }
2605
+ async diffSummary(options) {
2606
+ const args = ["diff", "--shortstat"];
2607
+ if (options) {
2608
+ args.push(...options);
2609
+ }
2610
+ try {
2611
+ const { stdout } = await execFileAsync("git", args, {
2612
+ cwd: this.cwd
2613
+ });
2614
+ const fileMatch = stdout.match(/(\d+)\s+file(s?)\s+changed/);
2615
+ const insertionMatch = stdout.match(/(\d+)\s+insertion/);
2616
+ const deletionMatch = stdout.match(/(\d+)\s+deletion/);
2617
+ const fileCount = fileMatch ? parseInt(fileMatch[1], 10) : 0;
2618
+ const insertions = insertionMatch ? parseInt(insertionMatch[1], 10) : 0;
2619
+ const deletions = deletionMatch ? parseInt(deletionMatch[1], 10) : 0;
2620
+ const files = insertions > 0 || deletions > 0 ? [{ file: "(total)", insertions, deletions }] : [];
2621
+ return { fileCount, files };
2622
+ } catch {
2623
+ return { fileCount: 0, files: [] };
2624
+ }
2625
+ }
2626
+ async latestTag() {
2627
+ try {
2628
+ const { stdout } = await execFileAsync("git", ["describe", "--tags", "--abbrev=0"], {
2629
+ cwd: this.cwd
2630
+ });
2631
+ return stdout.trim();
2632
+ } catch {
2633
+ return null;
2634
+ }
2635
+ }
2636
+ };
2637
+ function createGit(cwd) {
2638
+ return new NativeGit(cwd);
2639
+ }
2640
+
2641
+ // src/widgets/git-tag/styles.ts
2642
+ var gitTagStyles = {
2643
+ balanced: (data, colors) => {
2644
+ const tag = data.tag || "\u2014";
2645
+ if (!colors) return tag;
2646
+ return colorize(tag, colors.branch);
2647
+ },
2648
+ compact: (data, colors) => {
2649
+ if (!data.tag) return "\u2014";
2650
+ const tag = data.tag.replace(/^v/, "");
2651
+ if (!colors) return tag;
2652
+ return colorize(tag, colors.branch);
2653
+ },
2654
+ playful: (data, colors) => {
2655
+ const tag = data.tag || "\u2014";
2656
+ if (!colors) return `\u{1F3F7}\uFE0F ${tag}`;
2657
+ return `\u{1F3F7}\uFE0F ${colorize(tag, colors.branch)}`;
2658
+ },
2659
+ verbose: (data, colors) => {
2660
+ if (!data.tag) return "version: none";
2661
+ const tag = `version ${data.tag}`;
2662
+ if (!colors) return tag;
2663
+ return `version ${colorize(data.tag, colors.branch)}`;
2664
+ },
2665
+ labeled: (data, colors) => {
2666
+ const tag = data.tag || "none";
2667
+ if (!colors) return withLabel("Tag", tag);
2668
+ return withLabel("Tag", colorize(tag, colors.branch));
2669
+ },
2670
+ indicator: (data, colors) => {
2671
+ const tag = data.tag || "\u2014";
2672
+ if (!colors) return withIndicator(tag);
2673
+ return withIndicator(colorize(tag, colors.branch));
2674
+ }
2675
+ };
2676
+
2677
+ // src/widgets/git/git-tag-widget.ts
2678
+ var GitTagWidget = class {
2679
+ id = "git-tag";
2680
+ metadata = createWidgetMetadata(
2681
+ "Git Tag Widget",
2682
+ "Displays the latest git tag",
1107
2683
  "1.0.0",
1108
2684
  "claude-scope",
1109
- 0
1110
- // First line
2685
+ 1
2686
+ // Second line
1111
2687
  );
2688
+ gitFactory;
2689
+ git = null;
2690
+ enabled = true;
2691
+ cwd = null;
1112
2692
  colors;
1113
- styleFn = durationStyles.balanced;
1114
- constructor(colors) {
1115
- super();
2693
+ styleFn = gitTagStyles.balanced;
2694
+ /**
2695
+ * @param gitFactory - Optional factory function for creating IGit instances
2696
+ * If not provided, uses default createGit (production)
2697
+ * Tests can inject MockGit factory here
2698
+ * @param colors - Optional theme colors
2699
+ */
2700
+ constructor(gitFactory, colors) {
2701
+ this.gitFactory = gitFactory || createGit;
1116
2702
  this.colors = colors ?? DEFAULT_THEME;
1117
2703
  }
1118
2704
  setStyle(style = "balanced") {
1119
- const fn = durationStyles[style];
2705
+ const fn = gitTagStyles[style];
1120
2706
  if (fn) {
1121
2707
  this.styleFn = fn;
1122
2708
  }
1123
2709
  }
1124
- renderWithData(data, _context) {
1125
- if (!data.cost || data.cost.total_duration_ms === void 0) return null;
1126
- const renderData = {
1127
- durationMs: data.cost.total_duration_ms
1128
- };
1129
- return this.styleFn(renderData, this.colors.duration);
1130
- }
1131
- };
1132
-
1133
- // src/providers/config-provider.ts
1134
- var fs = __toESM(require("fs/promises"), 1);
1135
- var path = __toESM(require("path"), 1);
1136
- var os = __toESM(require("os"), 1);
1137
- var ConfigProvider = class {
1138
- cachedCounts;
1139
- lastScan = 0;
1140
- cacheInterval = 5e3;
1141
- // 5 seconds
1142
- /**
1143
- * Get config counts with hybrid caching
1144
- * Scans filesystem if cache is stale (>5 seconds)
1145
- */
1146
- async getConfigs(options = {}) {
1147
- const now = Date.now();
1148
- if (this.cachedCounts && now - this.lastScan < this.cacheInterval) {
1149
- return this.cachedCounts;
1150
- }
1151
- this.cachedCounts = await this.scanConfigs(options);
1152
- this.lastScan = now;
1153
- return this.cachedCounts;
2710
+ async initialize(context) {
2711
+ this.enabled = context.config?.enabled !== false;
1154
2712
  }
1155
- /**
1156
- * Scan filesystem for Claude Code configurations
1157
- */
1158
- async scanConfigs(options) {
1159
- let claudeMdCount = 0;
1160
- let rulesCount = 0;
1161
- let mcpCount = 0;
1162
- let hooksCount = 0;
1163
- const homeDir = os.homedir();
1164
- const claudeDir = path.join(homeDir, ".claude");
1165
- const cwd = options.cwd;
1166
- if (await this.fileExists(path.join(claudeDir, "CLAUDE.md"))) {
1167
- claudeMdCount++;
2713
+ async render(context) {
2714
+ if (!this.enabled || !this.git || !this.cwd) {
2715
+ return null;
1168
2716
  }
1169
- rulesCount += await this.countRulesInDir(path.join(claudeDir, "rules"));
1170
- const userSettings = path.join(claudeDir, "settings.json");
1171
- const userSettingsData = await this.readJsonFile(userSettings);
1172
- if (userSettingsData) {
1173
- mcpCount += this.countMcpServers(userSettingsData);
1174
- hooksCount += this.countHooks(userSettingsData);
2717
+ try {
2718
+ const latestTag = await (this.git.latestTag?.() ?? Promise.resolve(null));
2719
+ const renderData = { tag: latestTag };
2720
+ return this.styleFn(renderData, this.colors.git);
2721
+ } catch {
2722
+ return null;
1175
2723
  }
1176
- const userClaudeJson = path.join(homeDir, ".claude.json");
1177
- const userClaudeData = await this.readJsonFile(userClaudeJson);
1178
- if (userClaudeData) {
1179
- const userMcpCount = this.countMcpServers(userClaudeData);
1180
- mcpCount += Math.max(0, userMcpCount - this.countMcpServers(userSettingsData || {}));
2724
+ }
2725
+ async update(data) {
2726
+ if (data.cwd !== this.cwd) {
2727
+ this.cwd = data.cwd;
2728
+ this.git = this.gitFactory(data.cwd);
1181
2729
  }
1182
- if (cwd) {
1183
- if (await this.fileExists(path.join(cwd, "CLAUDE.md"))) {
1184
- claudeMdCount++;
1185
- }
1186
- if (await this.fileExists(path.join(cwd, "CLAUDE.local.md"))) {
1187
- claudeMdCount++;
1188
- }
1189
- if (await this.fileExists(path.join(cwd, ".claude", "CLAUDE.md"))) {
1190
- claudeMdCount++;
1191
- }
1192
- if (await this.fileExists(path.join(cwd, ".claude", "CLAUDE.local.md"))) {
1193
- claudeMdCount++;
1194
- }
1195
- rulesCount += await this.countRulesInDir(path.join(cwd, ".claude", "rules"));
1196
- const mcpJson = path.join(cwd, ".mcp.json");
1197
- const mcpData = await this.readJsonFile(mcpJson);
1198
- if (mcpData) {
1199
- mcpCount += this.countMcpServers(mcpData);
2730
+ }
2731
+ isEnabled() {
2732
+ return this.enabled;
2733
+ }
2734
+ async cleanup() {
2735
+ }
2736
+ };
2737
+
2738
+ // src/widgets/git/styles.ts
2739
+ var gitStyles = {
2740
+ minimal: (data, colors) => {
2741
+ if (!colors) return data.branch;
2742
+ return colorize(data.branch, colors.branch);
2743
+ },
2744
+ balanced: (data, colors) => {
2745
+ if (data.changes && data.changes.files > 0) {
2746
+ const parts = [];
2747
+ if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
2748
+ if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
2749
+ if (parts.length > 0) {
2750
+ const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
2751
+ const changes = colors ? colorize(`[${parts.join(" ")}]`, colors.changes) : `[${parts.join(" ")}]`;
2752
+ return `${branch} ${changes}`;
1200
2753
  }
1201
- const projectSettings = path.join(cwd, ".claude", "settings.json");
1202
- const projectSettingsData = await this.readJsonFile(projectSettings);
1203
- if (projectSettingsData) {
1204
- mcpCount += this.countMcpServers(projectSettingsData);
1205
- hooksCount += this.countHooks(projectSettingsData);
2754
+ }
2755
+ return colors ? colorize(data.branch, colors.branch) : data.branch;
2756
+ },
2757
+ compact: (data, colors) => {
2758
+ if (data.changes && data.changes.files > 0) {
2759
+ const parts = [];
2760
+ if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
2761
+ if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
2762
+ if (parts.length > 0) {
2763
+ const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
2764
+ const changesStr = parts.join("/");
2765
+ return `${branch} ${changesStr}`;
1206
2766
  }
1207
- const localSettings = path.join(cwd, ".claude", "settings.local.json");
1208
- const localSettingsData = await this.readJsonFile(localSettings);
1209
- if (localSettingsData) {
1210
- mcpCount += this.countMcpServers(localSettingsData);
1211
- hooksCount += this.countHooks(localSettingsData);
2767
+ }
2768
+ return colors ? colorize(data.branch, colors.branch) : data.branch;
2769
+ },
2770
+ playful: (data, colors) => {
2771
+ if (data.changes && data.changes.files > 0) {
2772
+ const parts = [];
2773
+ if (data.changes.insertions > 0) parts.push(`\u2B06${data.changes.insertions}`);
2774
+ if (data.changes.deletions > 0) parts.push(`\u2B07${data.changes.deletions}`);
2775
+ if (parts.length > 0) {
2776
+ const branch2 = colors ? colorize(data.branch, colors.branch) : data.branch;
2777
+ return `\u{1F500} ${branch2} ${parts.join(" ")}`;
1212
2778
  }
1213
2779
  }
1214
- return { claudeMdCount, rulesCount, mcpCount, hooksCount };
1215
- }
1216
- /**
1217
- * Check if file exists
1218
- */
1219
- async fileExists(filePath) {
1220
- try {
1221
- await fs.access(filePath);
1222
- return true;
1223
- } catch {
1224
- return false;
2780
+ const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
2781
+ return `\u{1F500} ${branch}`;
2782
+ },
2783
+ verbose: (data, colors) => {
2784
+ if (data.changes && data.changes.files > 0) {
2785
+ const parts = [];
2786
+ if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions} insertions`);
2787
+ if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions} deletions`);
2788
+ if (parts.length > 0) {
2789
+ const branch2 = colors ? colorize(data.branch, colors.branch) : data.branch;
2790
+ const changes = colors ? colorize(`[${parts.join(", ")}]`, colors.changes) : `[${parts.join(", ")}]`;
2791
+ return `branch: ${branch2} ${changes}`;
2792
+ }
1225
2793
  }
1226
- }
1227
- /**
1228
- * Read and parse JSON file
1229
- */
1230
- async readJsonFile(filePath) {
1231
- try {
1232
- const content = await fs.readFile(filePath, "utf8");
1233
- return JSON.parse(content);
1234
- } catch {
1235
- return null;
2794
+ const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
2795
+ return `branch: ${branch} (HEAD)`;
2796
+ },
2797
+ labeled: (data, colors) => {
2798
+ if (data.changes && data.changes.files > 0) {
2799
+ const parts = [];
2800
+ if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
2801
+ if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
2802
+ if (parts.length > 0) {
2803
+ const branch2 = colors ? colorize(data.branch, colors.branch) : data.branch;
2804
+ const changes = `${data.changes.files} files: ${parts.join("/")}`;
2805
+ return `Git: ${branch2} [${changes}]`;
2806
+ }
1236
2807
  }
1237
- }
1238
- /**
1239
- * Count MCP servers in config object
1240
- */
1241
- countMcpServers(config) {
1242
- if (!config || !config.mcpServers || typeof config.mcpServers !== "object") {
1243
- return 0;
2808
+ const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
2809
+ return `Git: ${branch}`;
2810
+ },
2811
+ indicator: (data, colors) => {
2812
+ if (data.changes && data.changes.files > 0) {
2813
+ const parts = [];
2814
+ if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
2815
+ if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
2816
+ if (parts.length > 0) {
2817
+ const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
2818
+ const changes = colors ? colorize(`[${parts.join(" ")}]`, colors.changes) : `[${parts.join(" ")}]`;
2819
+ return `\u25CF ${branch} ${changes}`;
2820
+ }
1244
2821
  }
1245
- return Object.keys(config.mcpServers).length;
2822
+ return withIndicator(colors ? colorize(data.branch, colors.branch) : data.branch);
1246
2823
  }
2824
+ };
2825
+
2826
+ // src/widgets/git/git-widget.ts
2827
+ var GitWidget = class {
2828
+ id = "git";
2829
+ metadata = createWidgetMetadata(
2830
+ "Git Widget",
2831
+ "Displays current git branch",
2832
+ "1.0.0",
2833
+ "claude-scope",
2834
+ 0
2835
+ // First line
2836
+ );
2837
+ gitFactory;
2838
+ git = null;
2839
+ enabled = true;
2840
+ cwd = null;
2841
+ colors;
2842
+ styleFn = gitStyles.balanced;
1247
2843
  /**
1248
- * Count hooks in config object
2844
+ * @param gitFactory - Optional factory function for creating IGit instances
2845
+ * If not provided, uses default createGit (production)
2846
+ * Tests can inject MockGit factory here
2847
+ * @param colors - Optional theme colors
1249
2848
  */
1250
- countHooks(config) {
1251
- if (!config || !config.hooks || typeof config.hooks !== "object") {
1252
- return 0;
2849
+ constructor(gitFactory, colors) {
2850
+ this.gitFactory = gitFactory || createGit;
2851
+ this.colors = colors ?? DEFAULT_THEME;
2852
+ }
2853
+ setStyle(style = "balanced") {
2854
+ const fn = gitStyles[style];
2855
+ if (fn) {
2856
+ this.styleFn = fn;
1253
2857
  }
1254
- return Object.keys(config.hooks).length;
1255
2858
  }
1256
- /**
1257
- * Recursively count .md files in directory
1258
- */
1259
- async countRulesInDir(rulesDir) {
1260
- const exists = await this.fileExists(rulesDir);
1261
- if (!exists) return 0;
2859
+ async initialize(context) {
2860
+ this.enabled = context.config?.enabled !== false;
2861
+ }
2862
+ async render(context) {
2863
+ if (!this.enabled || !this.git || !this.cwd) {
2864
+ return null;
2865
+ }
1262
2866
  try {
1263
- let count = 0;
1264
- const entries = await fs.readdir(rulesDir, { withFileTypes: true });
1265
- for (const entry of entries) {
1266
- const fullPath = path.join(rulesDir, entry.name);
1267
- if (entry.isDirectory()) {
1268
- count += await this.countRulesInDir(fullPath);
1269
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
1270
- count++;
2867
+ const status = await this.git.status();
2868
+ const branch = status.current || null;
2869
+ if (!branch) {
2870
+ return null;
2871
+ }
2872
+ let changes;
2873
+ try {
2874
+ const diffSummary = await this.git.diffSummary();
2875
+ if (diffSummary.fileCount > 0) {
2876
+ let insertions = 0;
2877
+ let deletions = 0;
2878
+ for (const file of diffSummary.files) {
2879
+ insertions += file.insertions || 0;
2880
+ deletions += file.deletions || 0;
2881
+ }
2882
+ if (insertions > 0 || deletions > 0) {
2883
+ changes = { files: diffSummary.fileCount, insertions, deletions };
2884
+ }
1271
2885
  }
2886
+ } catch {
1272
2887
  }
1273
- return count;
2888
+ const renderData = { branch, changes };
2889
+ return this.styleFn(renderData, this.colors.git);
1274
2890
  } catch {
1275
- return 0;
2891
+ return null;
2892
+ }
2893
+ }
2894
+ async update(data) {
2895
+ if (data.cwd !== this.cwd) {
2896
+ this.cwd = data.cwd;
2897
+ this.git = this.gitFactory(data.cwd);
1276
2898
  }
1277
2899
  }
2900
+ isEnabled() {
2901
+ return this.enabled;
2902
+ }
2903
+ async cleanup() {
2904
+ }
1278
2905
  };
1279
2906
 
1280
- // src/widgets/config-count/styles.ts
1281
- var configCountStyles = {
1282
- balanced: (data) => {
1283
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
1284
- const parts = [];
1285
- if (claudeMdCount > 0) {
1286
- parts.push(`CLAUDE.md:${claudeMdCount}`);
1287
- }
1288
- if (rulesCount > 0) {
1289
- parts.push(`rules:${rulesCount}`);
1290
- }
1291
- if (mcpCount > 0) {
1292
- parts.push(`MCPs:${mcpCount}`);
1293
- }
1294
- if (hooksCount > 0) {
1295
- parts.push(`hooks:${hooksCount}`);
1296
- }
1297
- return parts.join(" \u2502 ");
2907
+ // src/widgets/lines/styles.ts
2908
+ var linesStyles = {
2909
+ balanced: (data, colors) => {
2910
+ if (!colors) return `+${data.added}/-${data.removed}`;
2911
+ const addedStr = colorize(`+${data.added}`, colors.added);
2912
+ const removedStr = colorize(`-${data.removed}`, colors.removed);
2913
+ return `${addedStr}/${removedStr}`;
1298
2914
  },
1299
- compact: (data) => {
1300
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
1301
- const parts = [];
1302
- if (claudeMdCount > 0) {
1303
- parts.push(`${claudeMdCount} docs`);
1304
- }
1305
- if (rulesCount > 0) {
1306
- parts.push(`${rulesCount} rules`);
1307
- }
1308
- if (mcpCount > 0) {
1309
- parts.push(`${mcpCount} MCPs`);
1310
- }
1311
- if (hooksCount > 0) {
1312
- const hookLabel = hooksCount === 1 ? "hook" : "hooks";
1313
- parts.push(`${hooksCount} ${hookLabel}`);
1314
- }
1315
- return parts.join(" \u2502 ");
2915
+ compact: (data, colors) => {
2916
+ if (!colors) return `+${data.added}-${data.removed}`;
2917
+ const addedStr = colorize(`+${data.added}`, colors.added);
2918
+ const removedStr = colorize(`-${data.removed}`, colors.removed);
2919
+ return `${addedStr}${removedStr}`;
1316
2920
  },
1317
- playful: (data) => {
1318
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
1319
- const parts = [];
1320
- if (claudeMdCount > 0) {
1321
- parts.push(`\u{1F4C4} CLAUDE.md:${claudeMdCount}`);
1322
- }
1323
- if (rulesCount > 0) {
1324
- parts.push(`\u{1F4DC} rules:${rulesCount}`);
1325
- }
1326
- if (mcpCount > 0) {
1327
- parts.push(`\u{1F50C} MCPs:${mcpCount}`);
1328
- }
1329
- if (hooksCount > 0) {
1330
- parts.push(`\u{1FA9D} hooks:${hooksCount}`);
1331
- }
1332
- return parts.join(" \u2502 ");
2921
+ playful: (data, colors) => {
2922
+ if (!colors) return `\u2795${data.added} \u2796${data.removed}`;
2923
+ const addedStr = colorize(`\u2795${data.added}`, colors.added);
2924
+ const removedStr = colorize(`\u2796${data.removed}`, colors.removed);
2925
+ return `${addedStr} ${removedStr}`;
1333
2926
  },
1334
- verbose: (data) => {
1335
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
2927
+ verbose: (data, colors) => {
1336
2928
  const parts = [];
1337
- if (claudeMdCount > 0) {
1338
- parts.push(`${claudeMdCount} CLAUDE.md`);
1339
- }
1340
- if (rulesCount > 0) {
1341
- parts.push(`${rulesCount} rules`);
2929
+ if (data.added > 0) {
2930
+ const text = `+${data.added} added`;
2931
+ parts.push(colors ? colorize(text, colors.added) : text);
1342
2932
  }
1343
- if (mcpCount > 0) {
1344
- parts.push(`${mcpCount} MCP servers`);
2933
+ if (data.removed > 0) {
2934
+ const text = `-${data.removed} removed`;
2935
+ parts.push(colors ? colorize(text, colors.removed) : text);
1345
2936
  }
1346
- if (hooksCount > 0) {
1347
- parts.push(`${hooksCount} hook`);
2937
+ return parts.join(", ");
2938
+ },
2939
+ labeled: (data, colors) => {
2940
+ const addedStr = colors ? colorize(`+${data.added}`, colors.added) : `+${data.added}`;
2941
+ const removedStr = colors ? colorize(`-${data.removed}`, colors.removed) : `-${data.removed}`;
2942
+ const lines = `${addedStr}/${removedStr}`;
2943
+ return withLabel("Lines", lines);
2944
+ },
2945
+ indicator: (data, colors) => {
2946
+ const addedStr = colors ? colorize(`+${data.added}`, colors.added) : `+${data.added}`;
2947
+ const removedStr = colors ? colorize(`-${data.removed}`, colors.removed) : `-${data.removed}`;
2948
+ const lines = `${addedStr}/${removedStr}`;
2949
+ return withIndicator(lines);
2950
+ }
2951
+ };
2952
+
2953
+ // src/widgets/lines-widget.ts
2954
+ var LinesWidget = class extends StdinDataWidget {
2955
+ id = "lines";
2956
+ metadata = createWidgetMetadata(
2957
+ "Lines",
2958
+ "Displays lines added/removed in session",
2959
+ "1.0.0",
2960
+ "claude-scope",
2961
+ 0
2962
+ // First line
2963
+ );
2964
+ colors;
2965
+ styleFn = linesStyles.balanced;
2966
+ constructor(colors) {
2967
+ super();
2968
+ this.colors = colors ?? DEFAULT_THEME;
2969
+ }
2970
+ setStyle(style = "balanced") {
2971
+ const fn = linesStyles[style];
2972
+ if (fn) {
2973
+ this.styleFn = fn;
1348
2974
  }
1349
- return parts.join(" \u2502 ");
2975
+ }
2976
+ renderWithData(data, _context) {
2977
+ const added = data.cost?.total_lines_added ?? 0;
2978
+ const removed = data.cost?.total_lines_removed ?? 0;
2979
+ const renderData = { added, removed };
2980
+ return this.styleFn(renderData, this.colors.lines);
1350
2981
  }
1351
2982
  };
1352
2983
 
1353
- // src/core/style-types.ts
1354
- var DEFAULT_WIDGET_STYLE = "balanced";
2984
+ // src/widgets/model/styles.ts
2985
+ function getShortName(displayName) {
2986
+ return displayName.replace(/^Claude\s+/, "");
2987
+ }
2988
+ var modelStyles = {
2989
+ balanced: (data, colors) => {
2990
+ if (!colors) return data.displayName;
2991
+ return colorize(data.displayName, colors.name);
2992
+ },
2993
+ compact: (data, colors) => {
2994
+ const shortName = getShortName(data.displayName);
2995
+ if (!colors) return shortName;
2996
+ return colorize(shortName, colors.name);
2997
+ },
2998
+ playful: (data, colors) => {
2999
+ const shortName = getShortName(data.displayName);
3000
+ if (!colors) return `\u{1F916} ${shortName}`;
3001
+ return `\u{1F916} ${colorize(shortName, colors.name)}`;
3002
+ },
3003
+ technical: (data, colors) => {
3004
+ if (!colors) return data.id;
3005
+ const match = data.id.match(/^(.+?)-(\d[\d.]*)$/);
3006
+ if (match) {
3007
+ return colorize(match[1], colors.name) + colorize(`-${match[2]}`, colors.version);
3008
+ }
3009
+ return colorize(data.id, colors.name);
3010
+ },
3011
+ symbolic: (data, colors) => {
3012
+ const shortName = getShortName(data.displayName);
3013
+ if (!colors) return `\u25C6 ${shortName}`;
3014
+ return `\u25C6 ${colorize(shortName, colors.name)}`;
3015
+ },
3016
+ labeled: (data, colors) => {
3017
+ const shortName = getShortName(data.displayName);
3018
+ if (!colors) return withLabel("Model", shortName);
3019
+ return withLabel("Model", colorize(shortName, colors.name));
3020
+ },
3021
+ indicator: (data, colors) => {
3022
+ const shortName = getShortName(data.displayName);
3023
+ if (!colors) return withIndicator(shortName);
3024
+ return withIndicator(colorize(shortName, colors.name));
3025
+ }
3026
+ };
1355
3027
 
1356
- // src/widgets/config-count-widget.ts
1357
- var ConfigCountWidget = class {
1358
- id = "config-count";
3028
+ // src/widgets/model-widget.ts
3029
+ var ModelWidget = class extends StdinDataWidget {
3030
+ id = "model";
1359
3031
  metadata = createWidgetMetadata(
1360
- "Config Count",
1361
- "Displays Claude Code configuration counts",
3032
+ "Model",
3033
+ "Displays the current Claude model name",
1362
3034
  "1.0.0",
1363
3035
  "claude-scope",
1364
- 1
1365
- // Second line
3036
+ 0
3037
+ // First line
1366
3038
  );
1367
- configProvider = new ConfigProvider();
1368
- configs;
1369
- cwd;
1370
- styleFn = configCountStyles.balanced;
1371
- setStyle(style = DEFAULT_WIDGET_STYLE) {
1372
- const fn = configCountStyles[style];
3039
+ colors;
3040
+ styleFn = modelStyles.balanced;
3041
+ constructor(colors) {
3042
+ super();
3043
+ this.colors = colors ?? DEFAULT_THEME;
3044
+ }
3045
+ setStyle(style = "balanced") {
3046
+ const fn = modelStyles[style];
1373
3047
  if (fn) {
1374
3048
  this.styleFn = fn;
1375
3049
  }
1376
3050
  }
1377
- async initialize() {
1378
- }
1379
- async update(data) {
1380
- this.cwd = data.cwd;
1381
- this.configs = await this.configProvider.getConfigs({ cwd: data.cwd });
1382
- }
1383
- isEnabled() {
1384
- if (!this.configs) {
1385
- return false;
1386
- }
1387
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = this.configs;
1388
- return claudeMdCount > 0 || rulesCount > 0 || mcpCount > 0 || hooksCount > 0;
1389
- }
1390
- async render(context) {
1391
- if (!this.configs) {
1392
- return null;
1393
- }
1394
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = this.configs;
3051
+ renderWithData(data, _context) {
1395
3052
  const renderData = {
1396
- claudeMdCount,
1397
- rulesCount,
1398
- mcpCount,
1399
- hooksCount
3053
+ displayName: data.model.display_name,
3054
+ id: data.model.id
1400
3055
  };
1401
- return this.styleFn(renderData);
1402
- }
1403
- async cleanup() {
3056
+ return this.styleFn(renderData, this.colors.model);
1404
3057
  }
1405
3058
  };
1406
3059
 
@@ -1952,8 +3605,8 @@ var PokerWidget = class extends StdinDataWidget {
1952
3605
  "Displays random Texas Hold'em hands for entertainment",
1953
3606
  "1.0.0",
1954
3607
  "claude-scope",
1955
- 2
1956
- // Third line (0-indexed)
3608
+ 4
3609
+ // Fifth line (0-indexed)
1957
3610
  );
1958
3611
  holeCards = [];
1959
3612
  boardCards = [];
@@ -2046,207 +3699,6 @@ var PokerWidget = class extends StdinDataWidget {
2046
3699
  }
2047
3700
  };
2048
3701
 
2049
- // src/widgets/empty-line-widget.ts
2050
- var EmptyLineWidget = class extends StdinDataWidget {
2051
- id = "empty-line";
2052
- metadata = createWidgetMetadata(
2053
- "Empty Line",
2054
- "Empty line separator",
2055
- "1.0.0",
2056
- "claude-scope",
2057
- 3
2058
- // Fourth line (0-indexed)
2059
- );
2060
- /**
2061
- * All styles return the same value (Braille Pattern Blank).
2062
- * This method exists for API consistency with other widgets.
2063
- */
2064
- setStyle(_style) {
2065
- }
2066
- /**
2067
- * Return Braille Pattern Blank to create a visible empty separator line.
2068
- * U+2800 occupies cell width but appears blank, ensuring the line renders.
2069
- */
2070
- renderWithData(_data, _context) {
2071
- return "\u2800";
2072
- }
2073
- };
2074
-
2075
- // src/validation/result.ts
2076
- function success(data) {
2077
- return { success: true, data };
2078
- }
2079
- function failure(path2, message, value) {
2080
- return { success: false, error: { path: path2, message, value } };
2081
- }
2082
- function formatError(error) {
2083
- const path2 = error.path.length > 0 ? error.path.join(".") : "root";
2084
- return `${path2}: ${error.message}`;
2085
- }
2086
-
2087
- // src/validation/validators.ts
2088
- function string() {
2089
- return {
2090
- validate(value) {
2091
- if (typeof value === "string") return success(value);
2092
- return failure([], "Expected string", value);
2093
- }
2094
- };
2095
- }
2096
- function number() {
2097
- return {
2098
- validate(value) {
2099
- if (typeof value === "number" && !Number.isNaN(value)) return success(value);
2100
- return failure([], "Expected number", value);
2101
- }
2102
- };
2103
- }
2104
- function literal(expected) {
2105
- return {
2106
- validate(value) {
2107
- if (value === expected) return success(expected);
2108
- return failure([], `Expected '${expected}'`, value);
2109
- }
2110
- };
2111
- }
2112
-
2113
- // src/validation/combinators.ts
2114
- function object(shape) {
2115
- return {
2116
- validate(value) {
2117
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
2118
- return failure([], "Expected object", value);
2119
- }
2120
- const result = {};
2121
- for (const [key, validator] of Object.entries(shape)) {
2122
- const fieldValue = value[key];
2123
- const validationResult = validator.validate(fieldValue);
2124
- if (!validationResult.success) {
2125
- return {
2126
- success: false,
2127
- error: { ...validationResult.error, path: [key, ...validationResult.error.path] }
2128
- };
2129
- }
2130
- result[key] = validationResult.data;
2131
- }
2132
- return success(result);
2133
- }
2134
- };
2135
- }
2136
- function optional(validator) {
2137
- return {
2138
- validate(value) {
2139
- if (value === void 0) return success(void 0);
2140
- return validator.validate(value);
2141
- }
2142
- };
2143
- }
2144
- function nullable(validator) {
2145
- return {
2146
- validate(value) {
2147
- if (value === null) return success(null);
2148
- return validator.validate(value);
2149
- }
2150
- };
2151
- }
2152
-
2153
- // src/schemas/stdin-schema.ts
2154
- var ContextUsageSchema = object({
2155
- input_tokens: number(),
2156
- output_tokens: number(),
2157
- cache_creation_input_tokens: number(),
2158
- cache_read_input_tokens: number()
2159
- });
2160
- var CostInfoSchema = object({
2161
- total_cost_usd: optional(number()),
2162
- total_duration_ms: optional(number()),
2163
- total_api_duration_ms: optional(number()),
2164
- total_lines_added: optional(number()),
2165
- total_lines_removed: optional(number())
2166
- });
2167
- var ContextWindowSchema = object({
2168
- total_input_tokens: number(),
2169
- total_output_tokens: number(),
2170
- context_window_size: number(),
2171
- current_usage: nullable(ContextUsageSchema)
2172
- });
2173
- var ModelInfoSchema = object({
2174
- id: string(),
2175
- display_name: string()
2176
- });
2177
- var WorkspaceSchema = object({
2178
- current_dir: string(),
2179
- project_dir: string()
2180
- });
2181
- var OutputStyleSchema = object({
2182
- name: string()
2183
- });
2184
- var StdinDataSchema = object({
2185
- hook_event_name: optional(literal("Status")),
2186
- session_id: string(),
2187
- transcript_path: string(),
2188
- cwd: string(),
2189
- model: ModelInfoSchema,
2190
- workspace: WorkspaceSchema,
2191
- version: string(),
2192
- output_style: OutputStyleSchema,
2193
- cost: optional(CostInfoSchema),
2194
- context_window: ContextWindowSchema
2195
- });
2196
-
2197
- // src/data/stdin-provider.ts
2198
- var StdinParseError = class extends Error {
2199
- constructor(message) {
2200
- super(message);
2201
- this.name = "StdinParseError";
2202
- }
2203
- };
2204
- var StdinValidationError = class extends Error {
2205
- constructor(message) {
2206
- super(message);
2207
- this.name = "StdinValidationError";
2208
- }
2209
- };
2210
- var StdinProvider = class {
2211
- /**
2212
- * Parse and validate JSON string from stdin
2213
- * @param input JSON string to parse
2214
- * @returns Validated StdinData object
2215
- * @throws StdinParseError if JSON is malformed
2216
- * @throws StdinValidationError if data doesn't match schema
2217
- */
2218
- async parse(input) {
2219
- if (!input || input.trim().length === 0) {
2220
- throw new StdinParseError("stdin data is empty");
2221
- }
2222
- let data;
2223
- try {
2224
- data = JSON.parse(input);
2225
- } catch (error) {
2226
- throw new StdinParseError(`Invalid JSON: ${error.message}`);
2227
- }
2228
- const result = StdinDataSchema.validate(data);
2229
- if (!result.success) {
2230
- throw new StdinValidationError(`Validation failed: ${formatError(result.error)}`);
2231
- }
2232
- return result.data;
2233
- }
2234
- /**
2235
- * Safe parse that returns result instead of throwing
2236
- * Useful for testing and optional validation
2237
- * @param input JSON string to parse
2238
- * @returns Result object with success flag
2239
- */
2240
- async safeParse(input) {
2241
- try {
2242
- const data = await this.parse(input);
2243
- return { success: true, data };
2244
- } catch (error) {
2245
- return { success: false, error: error.message };
2246
- }
2247
- }
2248
- };
2249
-
2250
3702
  // src/index.ts
2251
3703
  async function readStdin() {
2252
3704
  const chunks = [];
@@ -2265,6 +3717,7 @@ async function main() {
2265
3717
  const provider = new StdinProvider();
2266
3718
  const stdinData = await provider.parse(stdin);
2267
3719
  const registry = new WidgetRegistry();
3720
+ const transcriptProvider = new TranscriptProvider();
2268
3721
  await registry.register(new ModelWidget());
2269
3722
  await registry.register(new ContextWidget());
2270
3723
  await registry.register(new CostWidget());
@@ -2273,6 +3726,12 @@ async function main() {
2273
3726
  await registry.register(new GitWidget());
2274
3727
  await registry.register(new GitTagWidget());
2275
3728
  await registry.register(new ConfigCountWidget());
3729
+ if (isWidgetEnabled("cacheMetrics")) {
3730
+ await registry.register(new CacheMetricsWidget(DEFAULT_THEME));
3731
+ }
3732
+ if (isWidgetEnabled("activeTools")) {
3733
+ await registry.register(new ActiveToolsWidget(DEFAULT_THEME, transcriptProvider));
3734
+ }
2276
3735
  await registry.register(new PokerWidget());
2277
3736
  await registry.register(new EmptyLineWidget());
2278
3737
  const renderer = new Renderer({