claude-scope 0.6.2 → 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 +1951 -991
  2. package/package.json +2 -2
@@ -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,120 +148,395 @@ 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;
182
+ /**
183
+ * Get a widget by id
184
+ */
185
+ get(id) {
186
+ return this.widgets.get(id);
187
+ }
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
+ }
271
214
  }
215
+ this.widgets.clear();
272
216
  }
273
217
  };
274
- function createGit(cwd) {
275
- return new NativeGit(cwd);
276
- }
277
218
 
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}`;
219
+ // src/validation/result.ts
220
+ function success(data) {
221
+ return { success: true, data };
286
222
  }
287
-
288
- // src/ui/theme/helpers.ts
289
- function rgb(r, g, b) {
290
- return `\x1B[38;2;${r};${g};${b}m`;
223
+ function failure(path2, message, value) {
224
+ return { success: false, error: { path: path2, message, value } };
291
225
  }
292
- function createBaseColors(params) {
226
+ function formatError(error) {
227
+ const path2 = error.path.length > 0 ? error.path.join(".") : "root";
228
+ return `${path2}: ${error.message}`;
229
+ }
230
+
231
+ // src/validation/combinators.ts
232
+ function object(shape) {
293
233
  return {
294
- text: params.modelColor,
295
- muted: params.durationColor,
296
- accent: params.accentColor,
297
- border: params.durationColor
234
+ validate(value) {
235
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
236
+ return failure([], "Expected object", value);
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);
251
+ }
298
252
  };
299
253
  }
300
- function createSemanticColors(params) {
254
+ function optional(validator) {
301
255
  return {
302
- success: params.contextLow,
303
- warning: params.contextMedium,
304
- error: params.contextHigh,
305
- info: params.branchColor
256
+ validate(value) {
257
+ if (value === void 0) return success(void 0);
258
+ return validator.validate(value);
259
+ }
306
260
  };
307
261
  }
308
- function createThemeColors(params) {
309
- const base = createBaseColors({
310
- modelColor: params.model,
311
- durationColor: params.duration,
312
- accentColor: params.accent
313
- });
314
- const semantic = createSemanticColors({
315
- contextLow: params.contextLow,
316
- contextMedium: params.contextMedium,
317
- contextHigh: params.contextHigh,
318
- branchColor: params.branch
319
- });
262
+ function nullable(validator) {
320
263
  return {
321
- base,
322
- semantic,
323
- git: {
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: {
324
540
  branch: params.branch,
325
541
  changes: params.changes
326
542
  },
@@ -350,6 +566,21 @@ function createThemeColors(params) {
350
566
  participating: params.model,
351
567
  nonParticipating: params.duration,
352
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
353
584
  }
354
585
  };
355
586
  }
@@ -369,7 +600,18 @@ var GRAY_THEME = {
369
600
  cost: gray,
370
601
  model: gray,
371
602
  duration: gray,
372
- accent: 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
373
615
  })
374
616
  };
375
617
 
@@ -398,8 +640,30 @@ var CATPPUCCIN_MOCHA_THEME = {
398
640
  // Mauve
399
641
  duration: rgb(147, 153, 178),
400
642
  // Text gray
401
- accent: rgb(243, 139, 168)
643
+ accent: rgb(243, 139, 168),
402
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
403
667
  })
404
668
  };
405
669
 
@@ -428,8 +692,30 @@ var CYBERPUNK_NEON_THEME = {
428
692
  // Purple neon
429
693
  duration: rgb(0, 191, 255),
430
694
  // Cyan neon
431
- accent: rgb(255, 0, 122)
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),
432
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
433
719
  })
434
720
  };
435
721
 
@@ -458,7 +744,29 @@ var DRACULA_THEME = {
458
744
  // Comment gray
459
745
  duration: rgb(68, 71, 90),
460
746
  // Selection gray
461
- accent: rgb(189, 147, 249)
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)
462
770
  // Purple
463
771
  })
464
772
  };
@@ -483,7 +791,24 @@ var DUSTY_SAGE_THEME = {
483
791
  cost: rgb(156, 163, 175),
484
792
  model: rgb(148, 163, 184),
485
793
  duration: rgb(120, 130, 140),
486
- accent: rgb(120, 140, 130)
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
487
812
  })
488
813
  };
489
814
 
@@ -512,18 +837,40 @@ var GITHUB_DARK_DIMMED_THEME = {
512
837
  // Gray
513
838
  duration: rgb(110, 118, 129),
514
839
  // Dark gray
515
- accent: rgb(88, 166, 255)
840
+ accent: rgb(88, 166, 255),
516
841
  // GitHub blue
517
- })
518
- };
519
-
520
- // src/ui/theme/themes/monokai-theme.ts
521
- var MONOKAI_THEME = {
522
- name: "monokai",
523
- description: "Vibrant, high-contrast",
524
- colors: createThemeColors({
525
- branch: rgb(102, 217, 239),
526
- // Cyan
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
527
874
  changes: rgb(249, 26, 114),
528
875
  // Pink
529
876
  contextLow: rgb(166, 226, 46),
@@ -542,8 +889,30 @@ var MONOKAI_THEME = {
542
889
  // Purple
543
890
  duration: rgb(102, 217, 239),
544
891
  // Cyan
545
- accent: rgb(249, 26, 114)
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),
546
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
547
916
  })
548
917
  };
549
918
 
@@ -567,7 +936,24 @@ var MUTED_GRAY_THEME = {
567
936
  cost: rgb(156, 163, 175),
568
937
  model: rgb(148, 163, 184),
569
938
  duration: rgb(107, 114, 128),
570
- accent: rgb(156, 163, 175)
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
571
957
  })
572
958
  };
573
959
 
@@ -596,8 +982,30 @@ var NORD_THEME = {
596
982
  // Nordic blue
597
983
  duration: rgb(94, 129, 172),
598
984
  // Nordic dark blue
599
- accent: rgb(136, 192, 208)
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),
600
1004
  // Nordic cyan
1005
+ toolsTarget: rgb(129, 161, 193),
1006
+ // Nordic blue
1007
+ toolsCount: rgb(216, 222, 233)
1008
+ // Nordic white
601
1009
  })
602
1010
  };
603
1011
 
@@ -626,8 +1034,30 @@ var ONE_DARK_PRO_THEME = {
626
1034
  // Gray
627
1035
  duration: rgb(125, 148, 173),
628
1036
  // Dark gray
629
- accent: rgb(97, 175, 239)
1037
+ accent: rgb(97, 175, 239),
630
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
631
1061
  })
632
1062
  };
633
1063
 
@@ -656,8 +1086,30 @@ var PROFESSIONAL_BLUE_THEME = {
656
1086
  // Purple
657
1087
  duration: rgb(203, 213, 225),
658
1088
  // Light gray
659
- accent: rgb(37, 99, 235)
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),
660
1108
  // Royal blue
1109
+ toolsTarget: rgb(148, 163, 184),
1110
+ // Slate gray
1111
+ toolsCount: rgb(167, 139, 250)
1112
+ // Purple
661
1113
  })
662
1114
  };
663
1115
 
@@ -686,8 +1138,30 @@ var ROSE_PINE_THEME = {
686
1138
  // Pine violet
687
1139
  duration: rgb(148, 137, 176),
688
1140
  // Pine mute
689
- accent: rgb(235, 111, 146)
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),
690
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
691
1165
  })
692
1166
  };
693
1167
 
@@ -716,8 +1190,30 @@ var SEMANTIC_CLASSIC_THEME = {
716
1190
  // Indigo
717
1191
  duration: rgb(107, 114, 128),
718
1192
  // Gray
719
- accent: rgb(59, 130, 246)
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),
720
1212
  // Blue
1213
+ toolsTarget: rgb(107, 114, 128),
1214
+ // Gray
1215
+ toolsCount: rgb(99, 102, 241)
1216
+ // Indigo
721
1217
  })
722
1218
  };
723
1219
 
@@ -741,7 +1237,24 @@ var SLATE_BLUE_THEME = {
741
1237
  cost: rgb(156, 163, 175),
742
1238
  model: rgb(148, 163, 184),
743
1239
  duration: rgb(100, 116, 139),
744
- accent: 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
745
1258
  })
746
1259
  };
747
1260
 
@@ -770,8 +1283,30 @@ var SOLARIZED_DARK_THEME = {
770
1283
  // Base0
771
1284
  duration: rgb(88, 110, 117),
772
1285
  // Base01
773
- accent: rgb(38, 139, 210)
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),
774
1305
  // Blue
1306
+ toolsTarget: rgb(131, 148, 150),
1307
+ // Base0
1308
+ toolsCount: rgb(203, 75, 22)
1309
+ // Orange
775
1310
  })
776
1311
  };
777
1312
 
@@ -800,8 +1335,30 @@ var TOKYO_NIGHT_THEME = {
800
1335
  // White-ish
801
1336
  duration: rgb(113, 119, 161),
802
1337
  // Dark blue-gray
803
- accent: rgb(122, 132, 173)
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),
804
1357
  // Blue
1358
+ toolsTarget: rgb(169, 177, 214),
1359
+ // White-ish
1360
+ toolsCount: rgb(158, 206, 209)
1361
+ // Teal
805
1362
  })
806
1363
  };
807
1364
 
@@ -830,429 +1387,875 @@ var VSCODE_DARK_PLUS_THEME = {
830
1387
  // Gray
831
1388
  duration: rgb(125, 148, 173),
832
1389
  // Dark gray
833
- accent: rgb(0, 122, 204)
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),
834
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
835
1414
  })
836
1415
  };
837
1416
 
838
1417
  // src/ui/theme/index.ts
839
1418
  var DEFAULT_THEME = VSCODE_DARK_PLUS_THEME.colors;
840
1419
 
841
- // src/ui/utils/style-utils.ts
842
- function withLabel(prefix, value) {
843
- if (prefix === "") return value;
844
- return `${prefix}: ${value}`;
845
- }
846
- function withIndicator(value) {
847
- return `\u25CF ${value}`;
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]}`;
848
1485
  }
849
- function progressBar(percent, width = 10) {
850
- const clamped = Math.max(0, Math.min(100, percent));
851
- const filled = Math.round(clamped / 100 * width);
852
- const empty = width - filled;
853
- return "\u2588".repeat(filled) + "\u2591".repeat(empty);
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;
854
1493
  }
855
-
856
- // src/widgets/git/styles.ts
857
- var gitStyles = {
858
- minimal: (data, colors) => {
859
- if (!colors) return data.branch;
860
- return colorize(data.branch, colors.branch);
861
- },
1494
+ var activeToolsStyles = {
1495
+ /**
1496
+ * balanced: Running tools with ◐ spinner, completed aggregated with ✓ ×count
1497
+ */
862
1498
  balanced: (data, colors) => {
863
- if (data.changes && data.changes.files > 0) {
864
- const parts = [];
865
- if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
866
- if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
867
- if (parts.length > 0) {
868
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
869
- const changes = colors ? colorize(`[${parts.join(" ")}]`, colors.changes) : `[${parts.join(" ")}]`;
870
- return `${branch} ${changes}`;
871
- }
872
- }
873
- return colors ? colorize(data.branch, colors.branch) : data.branch;
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(" | ");
874
1516
  },
1517
+ /**
1518
+ * compact: [ToolName] format for all tools
1519
+ */
875
1520
  compact: (data, colors) => {
876
- if (data.changes && data.changes.files > 0) {
877
- const parts = [];
878
- if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
879
- if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
880
- if (parts.length > 0) {
881
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
882
- const changesStr = parts.join("/");
883
- return `${branch} ${changesStr}`;
884
- }
1521
+ const parts = [];
1522
+ const c = colors ?? getDefaultColors();
1523
+ for (const tool of data.running) {
1524
+ parts.push(`[${colorize(tool.name, c.tools.name)}]`);
885
1525
  }
886
- return colors ? colorize(data.branch, colors.branch) : data.branch;
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);
887
1541
  },
1542
+ /**
1543
+ * playful: Emojis (📖✏️✨🔄🔍📁) with tool names
1544
+ */
888
1545
  playful: (data, colors) => {
889
- if (data.changes && data.changes.files > 0) {
890
- const parts = [];
891
- if (data.changes.insertions > 0) parts.push(`\u2B06${data.changes.insertions}`);
892
- if (data.changes.deletions > 0) parts.push(`\u2B07${data.changes.deletions}`);
893
- if (parts.length > 0) {
894
- const branch2 = colors ? colorize(data.branch, colors.branch) : data.branch;
895
- return `\u{1F500} ${branch2} ${parts.join(" ")}`;
896
- }
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}`);
897
1559
  }
898
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
899
- return `\u{1F500} ${branch}`;
1560
+ if (parts.length === 0) {
1561
+ return "";
1562
+ }
1563
+ return parts.join(", ");
900
1564
  },
901
- verbose: (data, colors) => {
902
- if (data.changes && data.changes.files > 0) {
903
- const parts = [];
904
- if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions} insertions`);
905
- if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions} deletions`);
906
- if (parts.length > 0) {
907
- const branch2 = colors ? colorize(data.branch, colors.branch) : data.branch;
908
- const changes = colors ? colorize(`[${parts.join(", ")}]`, colors.changes) : `[${parts.join(", ")}]`;
909
- return `branch: ${branch2} ${changes}`;
910
- }
1565
+ /**
1566
+ * verbose: Full text labels "Running:" and "Completed:"
1567
+ */
1568
+ verbose: (data, colors) => {
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)}`);
911
1574
  }
912
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
913
- 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(" | ");
914
1585
  },
1586
+ /**
1587
+ * labeled: "Tools:" prefix with all tools
1588
+ */
915
1589
  labeled: (data, colors) => {
916
- if (data.changes && data.changes.files > 0) {
917
- const parts = [];
918
- if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
919
- if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
920
- if (parts.length > 0) {
921
- const branch2 = colors ? colorize(data.branch, colors.branch) : data.branch;
922
- const changes = `${data.changes.files} files: ${parts.join("/")}`;
923
- return `Git: ${branch2} [${changes}]`;
924
- }
925
- }
926
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
927
- 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(" | ")}`;
928
1607
  },
1608
+ /**
1609
+ * indicator: ● bullet indicators
1610
+ */
929
1611
  indicator: (data, colors) => {
930
- if (data.changes && data.changes.files > 0) {
931
- const parts = [];
932
- if (data.changes.insertions > 0) parts.push(`+${data.changes.insertions}`);
933
- if (data.changes.deletions > 0) parts.push(`-${data.changes.deletions}`);
934
- if (parts.length > 0) {
935
- const branch = colors ? colorize(data.branch, colors.branch) : data.branch;
936
- const changes = colors ? colorize(`[${parts.join(" ")}]`, colors.changes) : `[${parts.join(" ")}]`;
937
- return `\u25CF ${branch} ${changes}`;
938
- }
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)}`);
939
1617
  }
940
- 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(" | ");
941
1626
  }
942
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
+ }
943
1690
 
944
- // src/widgets/git/git-widget.ts
945
- var GitWidget = class {
946
- id = "git";
947
- metadata = createWidgetMetadata(
948
- "Git Widget",
949
- "Displays current git branch",
950
- "1.0.0",
951
- "claude-scope",
952
- 0
953
- // First line
954
- );
955
- gitFactory;
956
- git = null;
957
- enabled = true;
958
- cwd = null;
959
- colors;
960
- 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;
961
1710
  /**
962
- * @param gitFactory - Optional factory function for creating IGit instances
963
- * If not provided, uses default createGit (production)
964
- * Tests can inject MockGit factory here
965
- * @param colors - Optional theme colors
1711
+ * Set display style
1712
+ * @param style - Style to use for rendering
966
1713
  */
967
- constructor(gitFactory, colors) {
968
- this.gitFactory = gitFactory || createGit;
969
- this.colors = colors ?? DEFAULT_THEME;
1714
+ setStyle(style) {
1715
+ this.style = style;
970
1716
  }
971
- setStyle(style = "balanced") {
972
- const fn = gitStyles[style];
973
- if (fn) {
974
- this.styleFn = fn;
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
+ }
975
1729
  }
1730
+ return counts;
976
1731
  }
977
- async initialize(context) {
978
- this.enabled = context.config?.enabled !== false;
1732
+ /**
1733
+ * Prepare render data from tools
1734
+ * @returns Render data with running, completed, and error tools
1735
+ */
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 };
979
1741
  }
980
- async render(context) {
981
- if (!this.enabled || !this.git || !this.cwd) {
982
- return null;
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;
983
1754
  }
984
- try {
985
- const status = await this.git.status();
986
- const branch = status.current || null;
987
- if (!branch) {
988
- return null;
989
- }
990
- let changes;
991
- try {
992
- const diffSummary = await this.git.diffSummary();
993
- if (diffSummary.fileCount > 0) {
994
- let insertions = 0;
995
- let deletions = 0;
996
- for (const file of diffSummary.files) {
997
- insertions += file.insertions || 0;
998
- deletions += file.deletions || 0;
999
- }
1000
- if (insertions > 0 || deletions > 0) {
1001
- changes = { files: diffSummary.fileCount, insertions, deletions };
1002
- }
1003
- }
1004
- } catch {
1005
- }
1006
- const renderData = { branch, changes };
1007
- return this.styleFn(renderData, this.colors.git);
1008
- } catch {
1755
+ }
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) {
1009
1763
  return null;
1010
1764
  }
1011
- }
1012
- async update(data) {
1013
- if (data.cwd !== this.cwd) {
1014
- this.cwd = data.cwd;
1015
- this.git = this.gitFactory(data.cwd);
1765
+ const styleFn = activeToolsStyles[this.style] ?? activeToolsStyles.balanced;
1766
+ if (!styleFn) {
1767
+ return null;
1016
1768
  }
1769
+ return styleFn(this.renderData, this.theme);
1017
1770
  }
1771
+ /**
1772
+ * Check if widget should render
1773
+ * @returns true if there are tools to display
1774
+ */
1018
1775
  isEnabled() {
1019
- return this.enabled;
1020
- }
1021
- async cleanup() {
1776
+ return super.isEnabled() && this.tools.length > 0;
1022
1777
  }
1023
1778
  };
1024
1779
 
1025
- // src/widgets/git-tag/styles.ts
1026
- 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
+ */
1027
1823
  balanced: (data, colors) => {
1028
- const tag = data.tag || "\u2014";
1029
- if (!colors) return tag;
1030
- 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})`;
1031
1829
  },
1830
+ /**
1831
+ * compact: Cache: 70%
1832
+ */
1032
1833
  compact: (data, colors) => {
1033
- if (!data.tag) return "\u2014";
1034
- const tag = data.tag.replace(/^v/, "");
1035
- if (!colors) return tag;
1036
- 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}%`;
1037
1839
  },
1840
+ /**
1841
+ * playful: 💾 [███████░] 70% with progress bar
1842
+ */
1038
1843
  playful: (data, colors) => {
1039
- const tag = data.tag || "\u2014";
1040
- if (!colors) return `\u{1F3F7}\uFE0F ${tag}`;
1041
- 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}`;
1042
1849
  },
1850
+ /**
1851
+ * verbose: Cache: 35.0k tokens (70%) | $0.03 saved
1852
+ */
1043
1853
  verbose: (data, colors) => {
1044
- if (!data.tag) return "version: none";
1045
- const tag = `version ${data.tag}`;
1046
- if (!colors) return tag;
1047
- 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}`;
1048
1859
  },
1860
+ /**
1861
+ * labeled: Cache Hit: 70% | $0.03 saved
1862
+ */
1049
1863
  labeled: (data, colors) => {
1050
- const tag = data.tag || "none";
1051
- if (!colors) return withLabel("Tag", tag);
1052
- 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}`;
1053
1868
  },
1869
+ /**
1870
+ * indicator: ● 70% cached
1871
+ */
1054
1872
  indicator: (data, colors) => {
1055
- const tag = data.tag || "\u2014";
1056
- if (!colors) return withIndicator(tag);
1057
- 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");
1058
1889
  }
1059
1890
  };
1060
1891
 
1061
- // src/widgets/git/git-tag-widget.ts
1062
- var GitTagWidget = class {
1063
- id = "git-tag";
1892
+ // src/widgets/cache-metrics/cache-metrics-widget.ts
1893
+ var CacheMetricsWidget = class extends StdinDataWidget {
1894
+ id = "cache-metrics";
1064
1895
  metadata = createWidgetMetadata(
1065
- "Git Tag Widget",
1066
- "Displays the latest git tag",
1896
+ "Cache Metrics",
1897
+ "Cache hit rate and savings display",
1067
1898
  "1.0.0",
1068
1899
  "claude-scope",
1069
- 1
1070
- // Second line
1900
+ 2
1901
+ // Third line
1071
1902
  );
1072
- gitFactory;
1073
- git = null;
1074
- enabled = true;
1075
- cwd = null;
1076
- colors;
1077
- styleFn = gitTagStyles.balanced;
1903
+ theme;
1904
+ style = "balanced";
1905
+ renderData;
1906
+ constructor(theme) {
1907
+ super();
1908
+ this.theme = theme ?? DEFAULT_THEME;
1909
+ }
1078
1910
  /**
1079
- * @param gitFactory - Optional factory function for creating IGit instances
1080
- * If not provided, uses default createGit (production)
1081
- * Tests can inject MockGit factory here
1082
- * @param colors - Optional theme colors
1911
+ * Set display style
1083
1912
  */
1084
- constructor(gitFactory, colors) {
1085
- this.gitFactory = gitFactory || createGit;
1086
- this.colors = colors ?? DEFAULT_THEME;
1913
+ setStyle(style) {
1914
+ this.style = style;
1087
1915
  }
1088
- setStyle(style = "balanced") {
1089
- const fn = gitTagStyles[style];
1090
- if (fn) {
1091
- 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;
1092
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
+ };
1093
1940
  }
1094
- async initialize(context) {
1095
- this.enabled = context.config?.enabled !== false;
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;
1096
1948
  }
1097
- async render(context) {
1098
- if (!this.enabled || !this.git || !this.cwd) {
1099
- return null;
1100
- }
1101
- try {
1102
- const latestTag = await (this.git.latestTag?.() ?? Promise.resolve(null));
1103
- const renderData = { tag: latestTag };
1104
- return this.styleFn(renderData, this.colors.git);
1105
- } catch {
1949
+ /**
1950
+ * Render the cache metrics display
1951
+ */
1952
+ renderWithData(_data, _context) {
1953
+ if (!this.renderData) {
1106
1954
  return null;
1107
1955
  }
1108
- }
1109
- async update(data) {
1110
- if (data.cwd !== this.cwd) {
1111
- this.cwd = data.cwd;
1112
- this.git = this.gitFactory(data.cwd);
1956
+ const styleFn = cacheMetricsStyles[this.style] ?? cacheMetricsStyles.balanced;
1957
+ if (!styleFn) {
1958
+ return null;
1113
1959
  }
1960
+ return styleFn(this.renderData, this.theme);
1114
1961
  }
1962
+ /**
1963
+ * Widget is enabled when we have cache metrics data
1964
+ */
1115
1965
  isEnabled() {
1116
- return this.enabled;
1117
- }
1118
- async cleanup() {
1966
+ return this.renderData !== void 0;
1119
1967
  }
1120
1968
  };
1121
1969
 
1122
- // src/widgets/core/stdin-data-widget.ts
1123
- var StdinDataWidget = class {
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
1124
1982
  /**
1125
- * Stored stdin data from last update
1983
+ * Get config counts with hybrid caching
1984
+ * Scans filesystem if cache is stale (>5 seconds)
1126
1985
  */
1127
- data = null;
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
+ }
1128
1995
  /**
1129
- * Widget enabled state
1996
+ * Scan filesystem for Claude Code configurations
1130
1997
  */
1131
- enabled = true;
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
+ }
2053
+ }
2054
+ return { claudeMdCount, rulesCount, mcpCount, hooksCount };
2055
+ }
1132
2056
  /**
1133
- * Initialize widget with context
1134
- * @param context - Widget initialization context
2057
+ * Check if file exists
1135
2058
  */
1136
- async initialize(context) {
1137
- this.enabled = context.config?.enabled !== false;
2059
+ async fileExists(filePath) {
2060
+ try {
2061
+ await fs.access(filePath);
2062
+ return true;
2063
+ } catch {
2064
+ return false;
2065
+ }
1138
2066
  }
1139
2067
  /**
1140
- * Update widget with new stdin data
1141
- * @param data - Stdin data from Claude Code
2068
+ * Read and parse JSON file
1142
2069
  */
1143
- async update(data) {
1144
- 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
+ }
1145
2077
  }
1146
2078
  /**
1147
- * Get stored stdin data
1148
- * @returns Stored stdin data
1149
- * @throws Error if data has not been initialized (update not called)
2079
+ * Count MCP servers in config object
1150
2080
  */
1151
- getData() {
1152
- if (!this.data) {
1153
- 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;
1154
2084
  }
1155
- return this.data;
2085
+ return Object.keys(config.mcpServers).length;
1156
2086
  }
1157
2087
  /**
1158
- * Check if widget is enabled
1159
- * @returns true if widget should render
2088
+ * Count hooks in config object
1160
2089
  */
1161
- isEnabled() {
1162
- 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;
1163
2095
  }
1164
2096
  /**
1165
- * Template method - final, subclasses implement renderWithData()
1166
- *
1167
- * Handles null data checks and calls renderWithData() hook.
1168
- *
1169
- * @param context - Render context
1170
- * @returns Rendered string, or null if widget should not display
2097
+ * Recursively count .md files in directory
1171
2098
  */
1172
- async render(context) {
1173
- if (!this.data || !this.enabled) {
1174
- 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;
1175
2116
  }
1176
- return this.renderWithData(this.data, context);
1177
2117
  }
1178
2118
  };
1179
2119
 
1180
- // src/widgets/model/styles.ts
1181
- function getShortName(displayName) {
1182
- return displayName.replace(/^Claude\s+/, "");
1183
- }
1184
- var modelStyles = {
1185
- balanced: (data, colors) => {
1186
- if (!colors) return data.displayName;
1187
- return colorize(data.displayName, colors.name);
1188
- },
1189
- compact: (data, colors) => {
1190
- const shortName = getShortName(data.displayName);
1191
- if (!colors) return shortName;
1192
- return colorize(shortName, colors.name);
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}`);
2127
+ }
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 ");
1193
2138
  },
1194
- playful: (data, colors) => {
1195
- const shortName = getShortName(data.displayName);
1196
- if (!colors) return `\u{1F916} ${shortName}`;
1197
- return `\u{1F916} ${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 ");
1198
2156
  },
1199
- technical: (data, colors) => {
1200
- if (!colors) return data.id;
1201
- const match = data.id.match(/^(.+?)-(\d[\d.]*)$/);
1202
- if (match) {
1203
- return colorize(match[1], colors.name) + colorize(`-${match[2]}`, colors.version);
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}`);
1204
2162
  }
1205
- return colorize(data.id, colors.name);
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 ");
1206
2173
  },
1207
- symbolic: (data, colors) => {
1208
- const shortName = getShortName(data.displayName);
1209
- if (!colors) return `\u25C6 ${shortName}`;
1210
- return `\u25C6 ${colorize(shortName, colors.name)}`;
1211
- },
1212
- labeled: (data, colors) => {
1213
- const shortName = getShortName(data.displayName);
1214
- if (!colors) return withLabel("Model", shortName);
1215
- return withLabel("Model", colorize(shortName, colors.name));
1216
- },
1217
- indicator: (data, colors) => {
1218
- const shortName = getShortName(data.displayName);
1219
- if (!colors) return withIndicator(shortName);
1220
- 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 ");
1221
2190
  }
1222
2191
  };
1223
2192
 
1224
- // src/widgets/model-widget.ts
1225
- var ModelWidget = class extends StdinDataWidget {
1226
- id = "model";
2193
+ // src/widgets/config-count-widget.ts
2194
+ var ConfigCountWidget = class {
2195
+ id = "config-count";
1227
2196
  metadata = createWidgetMetadata(
1228
- "Model",
1229
- "Displays the current Claude model name",
2197
+ "Config Count",
2198
+ "Displays Claude Code configuration counts",
1230
2199
  "1.0.0",
1231
2200
  "claude-scope",
1232
- 0
1233
- // First line
2201
+ 1
2202
+ // Second line
1234
2203
  );
1235
- colors;
1236
- styleFn = modelStyles.balanced;
1237
- constructor(colors) {
1238
- super();
1239
- this.colors = colors ?? DEFAULT_THEME;
1240
- }
1241
- setStyle(style = "balanced") {
1242
- 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];
1243
2210
  if (fn) {
1244
2211
  this.styleFn = fn;
1245
2212
  }
1246
2213
  }
1247
- 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;
1248
2232
  const renderData = {
1249
- displayName: data.model.display_name,
1250
- id: data.model.id
2233
+ claudeMdCount,
2234
+ rulesCount,
2235
+ mcpCount,
2236
+ hooksCount
1251
2237
  };
1252
- return this.styleFn(renderData, this.colors.model);
2238
+ return this.styleFn(renderData);
2239
+ }
2240
+ async cleanup() {
1253
2241
  }
1254
2242
  };
1255
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
+
1256
2259
  // src/widgets/context/styles.ts
1257
2260
  function getContextColor(percent, colors) {
1258
2261
  const clampedPercent = Math.max(0, Math.min(100, percent));
@@ -1440,83 +2443,6 @@ var CostWidget = class extends StdinDataWidget {
1440
2443
  }
1441
2444
  };
1442
2445
 
1443
- // src/widgets/lines/styles.ts
1444
- var linesStyles = {
1445
- balanced: (data, colors) => {
1446
- if (!colors) return `+${data.added}/-${data.removed}`;
1447
- const addedStr = colorize(`+${data.added}`, colors.added);
1448
- const removedStr = colorize(`-${data.removed}`, colors.removed);
1449
- return `${addedStr}/${removedStr}`;
1450
- },
1451
- compact: (data, colors) => {
1452
- if (!colors) return `+${data.added}-${data.removed}`;
1453
- const addedStr = colorize(`+${data.added}`, colors.added);
1454
- const removedStr = colorize(`-${data.removed}`, colors.removed);
1455
- return `${addedStr}${removedStr}`;
1456
- },
1457
- playful: (data, colors) => {
1458
- if (!colors) return `\u2795${data.added} \u2796${data.removed}`;
1459
- const addedStr = colorize(`\u2795${data.added}`, colors.added);
1460
- const removedStr = colorize(`\u2796${data.removed}`, colors.removed);
1461
- return `${addedStr} ${removedStr}`;
1462
- },
1463
- verbose: (data, colors) => {
1464
- const parts = [];
1465
- if (data.added > 0) {
1466
- const text = `+${data.added} added`;
1467
- parts.push(colors ? colorize(text, colors.added) : text);
1468
- }
1469
- if (data.removed > 0) {
1470
- const text = `-${data.removed} removed`;
1471
- parts.push(colors ? colorize(text, colors.removed) : text);
1472
- }
1473
- return parts.join(", ");
1474
- },
1475
- labeled: (data, colors) => {
1476
- const addedStr = colors ? colorize(`+${data.added}`, colors.added) : `+${data.added}`;
1477
- const removedStr = colors ? colorize(`-${data.removed}`, colors.removed) : `-${data.removed}`;
1478
- const lines = `${addedStr}/${removedStr}`;
1479
- return withLabel("Lines", lines);
1480
- },
1481
- indicator: (data, colors) => {
1482
- const addedStr = colors ? colorize(`+${data.added}`, colors.added) : `+${data.added}`;
1483
- const removedStr = colors ? colorize(`-${data.removed}`, colors.removed) : `-${data.removed}`;
1484
- const lines = `${addedStr}/${removedStr}`;
1485
- return withIndicator(lines);
1486
- }
1487
- };
1488
-
1489
- // src/widgets/lines-widget.ts
1490
- var LinesWidget = class extends StdinDataWidget {
1491
- id = "lines";
1492
- metadata = createWidgetMetadata(
1493
- "Lines",
1494
- "Displays lines added/removed in session",
1495
- "1.0.0",
1496
- "claude-scope",
1497
- 0
1498
- // First line
1499
- );
1500
- colors;
1501
- styleFn = linesStyles.balanced;
1502
- constructor(colors) {
1503
- super();
1504
- this.colors = colors ?? DEFAULT_THEME;
1505
- }
1506
- setStyle(style = "balanced") {
1507
- const fn = linesStyles[style];
1508
- if (fn) {
1509
- this.styleFn = fn;
1510
- }
1511
- }
1512
- renderWithData(data, _context) {
1513
- const added = data.cost?.total_lines_added ?? 0;
1514
- const removed = data.cost?.total_lines_removed ?? 0;
1515
- const renderData = { added, removed };
1516
- return this.styleFn(renderData, this.colors.lines);
1517
- }
1518
- };
1519
-
1520
2446
  // src/widgets/duration/styles.ts
1521
2447
  var durationStyles = {
1522
2448
  balanced: (data, colors) => {
@@ -1629,277 +2555,505 @@ var DurationWidget = class extends StdinDataWidget {
1629
2555
  }
1630
2556
  };
1631
2557
 
1632
- // src/providers/config-provider.ts
1633
- var fs = __toESM(require("fs/promises"), 1);
1634
- var path = __toESM(require("path"), 1);
1635
- var os = __toESM(require("os"), 1);
1636
- var ConfigProvider = class {
1637
- cachedCounts;
1638
- lastScan = 0;
1639
- cacheInterval = 5e3;
1640
- // 5 seconds
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
+ );
1641
2569
  /**
1642
- * Get config counts with hybrid caching
1643
- * Scans filesystem if cache is stale (>5 seconds)
2570
+ * All styles return the same value (Braille Pattern Blank).
2571
+ * This method exists for API consistency with other widgets.
1644
2572
  */
1645
- async getConfigs(options = {}) {
1646
- const now = Date.now();
1647
- if (this.cachedCounts && now - this.lastScan < this.cacheInterval) {
1648
- return this.cachedCounts;
1649
- }
1650
- this.cachedCounts = await this.scanConfigs(options);
1651
- this.lastScan = now;
1652
- return this.cachedCounts;
2573
+ setStyle(_style) {
1653
2574
  }
1654
2575
  /**
1655
- * Scan filesystem for Claude Code configurations
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.
1656
2578
  */
1657
- async scanConfigs(options) {
1658
- let claudeMdCount = 0;
1659
- let rulesCount = 0;
1660
- let mcpCount = 0;
1661
- let hooksCount = 0;
1662
- const homeDir = os.homedir();
1663
- const claudeDir = path.join(homeDir, ".claude");
1664
- const cwd = options.cwd;
1665
- if (await this.fileExists(path.join(claudeDir, "CLAUDE.md"))) {
1666
- claudeMdCount++;
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 };
1667
2603
  }
1668
- rulesCount += await this.countRulesInDir(path.join(claudeDir, "rules"));
1669
- const userSettings = path.join(claudeDir, "settings.json");
1670
- const userSettingsData = await this.readJsonFile(userSettings);
1671
- if (userSettingsData) {
1672
- mcpCount += this.countMcpServers(userSettingsData);
1673
- hooksCount += this.countHooks(userSettingsData);
2604
+ }
2605
+ async diffSummary(options) {
2606
+ const args = ["diff", "--shortstat"];
2607
+ if (options) {
2608
+ args.push(...options);
1674
2609
  }
1675
- const userClaudeJson = path.join(homeDir, ".claude.json");
1676
- const userClaudeData = await this.readJsonFile(userClaudeJson);
1677
- if (userClaudeData) {
1678
- const userMcpCount = this.countMcpServers(userClaudeData);
1679
- mcpCount += Math.max(0, userMcpCount - this.countMcpServers(userSettingsData || {}));
1680
- }
1681
- if (cwd) {
1682
- if (await this.fileExists(path.join(cwd, "CLAUDE.md"))) {
1683
- claudeMdCount++;
1684
- }
1685
- if (await this.fileExists(path.join(cwd, "CLAUDE.local.md"))) {
1686
- claudeMdCount++;
1687
- }
1688
- if (await this.fileExists(path.join(cwd, ".claude", "CLAUDE.md"))) {
1689
- claudeMdCount++;
1690
- }
1691
- if (await this.fileExists(path.join(cwd, ".claude", "CLAUDE.local.md"))) {
1692
- claudeMdCount++;
1693
- }
1694
- rulesCount += await this.countRulesInDir(path.join(cwd, ".claude", "rules"));
1695
- const mcpJson = path.join(cwd, ".mcp.json");
1696
- const mcpData = await this.readJsonFile(mcpJson);
1697
- if (mcpData) {
1698
- mcpCount += this.countMcpServers(mcpData);
1699
- }
1700
- const projectSettings = path.join(cwd, ".claude", "settings.json");
1701
- const projectSettingsData = await this.readJsonFile(projectSettings);
1702
- if (projectSettingsData) {
1703
- mcpCount += this.countMcpServers(projectSettingsData);
1704
- hooksCount += this.countHooks(projectSettingsData);
1705
- }
1706
- const localSettings = path.join(cwd, ".claude", "settings.local.json");
1707
- const localSettingsData = await this.readJsonFile(localSettings);
1708
- if (localSettingsData) {
1709
- mcpCount += this.countMcpServers(localSettingsData);
1710
- hooksCount += this.countHooks(localSettingsData);
1711
- }
1712
- }
1713
- return { claudeMdCount, rulesCount, mcpCount, hooksCount };
1714
- }
1715
- /**
1716
- * Check if file exists
1717
- */
1718
- async fileExists(filePath) {
1719
2610
  try {
1720
- await fs.access(filePath);
1721
- return true;
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 };
1722
2622
  } catch {
1723
- return false;
2623
+ return { fileCount: 0, files: [] };
1724
2624
  }
1725
2625
  }
1726
- /**
1727
- * Read and parse JSON file
1728
- */
1729
- async readJsonFile(filePath) {
2626
+ async latestTag() {
1730
2627
  try {
1731
- const content = await fs.readFile(filePath, "utf8");
1732
- return JSON.parse(content);
2628
+ const { stdout } = await execFileAsync("git", ["describe", "--tags", "--abbrev=0"], {
2629
+ cwd: this.cwd
2630
+ });
2631
+ return stdout.trim();
1733
2632
  } catch {
1734
2633
  return null;
1735
2634
  }
1736
2635
  }
1737
- /**
1738
- * Count MCP servers in config object
1739
- */
1740
- countMcpServers(config) {
1741
- if (!config || !config.mcpServers || typeof config.mcpServers !== "object") {
1742
- return 0;
1743
- }
1744
- return Object.keys(config.mcpServers).length;
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));
1745
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",
2683
+ "1.0.0",
2684
+ "claude-scope",
2685
+ 1
2686
+ // Second line
2687
+ );
2688
+ gitFactory;
2689
+ git = null;
2690
+ enabled = true;
2691
+ cwd = null;
2692
+ colors;
2693
+ styleFn = gitTagStyles.balanced;
1746
2694
  /**
1747
- * Count hooks in config object
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
1748
2699
  */
1749
- countHooks(config) {
1750
- if (!config || !config.hooks || typeof config.hooks !== "object") {
1751
- return 0;
2700
+ constructor(gitFactory, colors) {
2701
+ this.gitFactory = gitFactory || createGit;
2702
+ this.colors = colors ?? DEFAULT_THEME;
2703
+ }
2704
+ setStyle(style = "balanced") {
2705
+ const fn = gitTagStyles[style];
2706
+ if (fn) {
2707
+ this.styleFn = fn;
1752
2708
  }
1753
- return Object.keys(config.hooks).length;
1754
2709
  }
1755
- /**
1756
- * Recursively count .md files in directory
1757
- */
1758
- async countRulesInDir(rulesDir) {
1759
- const exists = await this.fileExists(rulesDir);
1760
- if (!exists) return 0;
2710
+ async initialize(context) {
2711
+ this.enabled = context.config?.enabled !== false;
2712
+ }
2713
+ async render(context) {
2714
+ if (!this.enabled || !this.git || !this.cwd) {
2715
+ return null;
2716
+ }
1761
2717
  try {
1762
- let count = 0;
1763
- const entries = await fs.readdir(rulesDir, { withFileTypes: true });
1764
- for (const entry of entries) {
1765
- const fullPath = path.join(rulesDir, entry.name);
1766
- if (entry.isDirectory()) {
1767
- count += await this.countRulesInDir(fullPath);
1768
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
1769
- count++;
1770
- }
1771
- }
1772
- return count;
2718
+ const latestTag = await (this.git.latestTag?.() ?? Promise.resolve(null));
2719
+ const renderData = { tag: latestTag };
2720
+ return this.styleFn(renderData, this.colors.git);
1773
2721
  } catch {
1774
- return 0;
2722
+ return null;
2723
+ }
2724
+ }
2725
+ async update(data) {
2726
+ if (data.cwd !== this.cwd) {
2727
+ this.cwd = data.cwd;
2728
+ this.git = this.gitFactory(data.cwd);
1775
2729
  }
1776
2730
  }
2731
+ isEnabled() {
2732
+ return this.enabled;
2733
+ }
2734
+ async cleanup() {
2735
+ }
1777
2736
  };
1778
2737
 
1779
- // src/widgets/config-count/styles.ts
1780
- var configCountStyles = {
1781
- balanced: (data) => {
1782
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
1783
- const parts = [];
1784
- if (claudeMdCount > 0) {
1785
- parts.push(`CLAUDE.md:${claudeMdCount}`);
1786
- }
1787
- if (rulesCount > 0) {
1788
- parts.push(`rules:${rulesCount}`);
1789
- }
1790
- if (mcpCount > 0) {
1791
- parts.push(`MCPs:${mcpCount}`);
1792
- }
1793
- if (hooksCount > 0) {
1794
- parts.push(`hooks:${hooksCount}`);
1795
- }
1796
- return parts.join(" \u2502 ");
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);
1797
2743
  },
1798
- compact: (data) => {
1799
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
1800
- const parts = [];
1801
- if (claudeMdCount > 0) {
1802
- parts.push(`${claudeMdCount} docs`);
1803
- }
1804
- if (rulesCount > 0) {
1805
- parts.push(`${rulesCount} rules`);
1806
- }
1807
- if (mcpCount > 0) {
1808
- parts.push(`${mcpCount} MCPs`);
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}`;
2753
+ }
1809
2754
  }
1810
- if (hooksCount > 0) {
1811
- const hookLabel = hooksCount === 1 ? "hook" : "hooks";
1812
- parts.push(`${hooksCount} ${hookLabel}`);
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}`;
2766
+ }
1813
2767
  }
1814
- return parts.join(" \u2502 ");
2768
+ return colors ? colorize(data.branch, colors.branch) : data.branch;
1815
2769
  },
1816
- playful: (data) => {
1817
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
1818
- const parts = [];
1819
- if (claudeMdCount > 0) {
1820
- parts.push(`\u{1F4C4} CLAUDE.md:${claudeMdCount}`);
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(" ")}`;
2778
+ }
1821
2779
  }
1822
- if (rulesCount > 0) {
1823
- parts.push(`\u{1F4DC} rules:${rulesCount}`);
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
+ }
1824
2793
  }
1825
- if (mcpCount > 0) {
1826
- parts.push(`\u{1F50C} MCPs:${mcpCount}`);
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
+ }
1827
2807
  }
1828
- if (hooksCount > 0) {
1829
- parts.push(`\u{1FA9D} hooks:${hooksCount}`);
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
+ }
1830
2821
  }
1831
- return parts.join(" \u2502 ");
2822
+ return withIndicator(colors ? colorize(data.branch, colors.branch) : data.branch);
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;
2843
+ /**
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
2848
+ */
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;
2857
+ }
2858
+ }
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
+ }
2866
+ try {
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
+ }
2885
+ }
2886
+ } catch {
2887
+ }
2888
+ const renderData = { branch, changes };
2889
+ return this.styleFn(renderData, this.colors.git);
2890
+ } catch {
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);
2898
+ }
2899
+ }
2900
+ isEnabled() {
2901
+ return this.enabled;
2902
+ }
2903
+ async cleanup() {
2904
+ }
2905
+ };
2906
+
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}`;
1832
2914
  },
1833
- verbose: (data) => {
1834
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = data;
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}`;
2920
+ },
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}`;
2926
+ },
2927
+ verbose: (data, colors) => {
1835
2928
  const parts = [];
1836
- if (claudeMdCount > 0) {
1837
- parts.push(`${claudeMdCount} CLAUDE.md`);
1838
- }
1839
- if (rulesCount > 0) {
1840
- parts.push(`${rulesCount} rules`);
2929
+ if (data.added > 0) {
2930
+ const text = `+${data.added} added`;
2931
+ parts.push(colors ? colorize(text, colors.added) : text);
1841
2932
  }
1842
- if (mcpCount > 0) {
1843
- 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);
1844
2936
  }
1845
- if (hooksCount > 0) {
1846
- 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;
1847
2974
  }
1848
- 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);
1849
2981
  }
1850
2982
  };
1851
2983
 
1852
- // src/core/style-types.ts
1853
- 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
+ };
1854
3027
 
1855
- // src/widgets/config-count-widget.ts
1856
- var ConfigCountWidget = class {
1857
- id = "config-count";
3028
+ // src/widgets/model-widget.ts
3029
+ var ModelWidget = class extends StdinDataWidget {
3030
+ id = "model";
1858
3031
  metadata = createWidgetMetadata(
1859
- "Config Count",
1860
- "Displays Claude Code configuration counts",
3032
+ "Model",
3033
+ "Displays the current Claude model name",
1861
3034
  "1.0.0",
1862
3035
  "claude-scope",
1863
- 1
1864
- // Second line
3036
+ 0
3037
+ // First line
1865
3038
  );
1866
- configProvider = new ConfigProvider();
1867
- configs;
1868
- cwd;
1869
- styleFn = configCountStyles.balanced;
1870
- setStyle(style = DEFAULT_WIDGET_STYLE) {
1871
- 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];
1872
3047
  if (fn) {
1873
3048
  this.styleFn = fn;
1874
3049
  }
1875
3050
  }
1876
- async initialize() {
1877
- }
1878
- async update(data) {
1879
- this.cwd = data.cwd;
1880
- this.configs = await this.configProvider.getConfigs({ cwd: data.cwd });
1881
- }
1882
- isEnabled() {
1883
- if (!this.configs) {
1884
- return false;
1885
- }
1886
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = this.configs;
1887
- return claudeMdCount > 0 || rulesCount > 0 || mcpCount > 0 || hooksCount > 0;
1888
- }
1889
- async render(context) {
1890
- if (!this.configs) {
1891
- return null;
1892
- }
1893
- const { claudeMdCount, rulesCount, mcpCount, hooksCount } = this.configs;
3051
+ renderWithData(data, _context) {
1894
3052
  const renderData = {
1895
- claudeMdCount,
1896
- rulesCount,
1897
- mcpCount,
1898
- hooksCount
3053
+ displayName: data.model.display_name,
3054
+ id: data.model.id
1899
3055
  };
1900
- return this.styleFn(renderData);
1901
- }
1902
- async cleanup() {
3056
+ return this.styleFn(renderData, this.colors.model);
1903
3057
  }
1904
3058
  };
1905
3059
 
@@ -2451,8 +3605,8 @@ var PokerWidget = class extends StdinDataWidget {
2451
3605
  "Displays random Texas Hold'em hands for entertainment",
2452
3606
  "1.0.0",
2453
3607
  "claude-scope",
2454
- 2
2455
- // Third line (0-indexed)
3608
+ 4
3609
+ // Fifth line (0-indexed)
2456
3610
  );
2457
3611
  holeCards = [];
2458
3612
  boardCards = [];
@@ -2545,207 +3699,6 @@ var PokerWidget = class extends StdinDataWidget {
2545
3699
  }
2546
3700
  };
2547
3701
 
2548
- // src/widgets/empty-line-widget.ts
2549
- var EmptyLineWidget = class extends StdinDataWidget {
2550
- id = "empty-line";
2551
- metadata = createWidgetMetadata(
2552
- "Empty Line",
2553
- "Empty line separator",
2554
- "1.0.0",
2555
- "claude-scope",
2556
- 3
2557
- // Fourth line (0-indexed)
2558
- );
2559
- /**
2560
- * All styles return the same value (Braille Pattern Blank).
2561
- * This method exists for API consistency with other widgets.
2562
- */
2563
- setStyle(_style) {
2564
- }
2565
- /**
2566
- * Return Braille Pattern Blank to create a visible empty separator line.
2567
- * U+2800 occupies cell width but appears blank, ensuring the line renders.
2568
- */
2569
- renderWithData(_data, _context) {
2570
- return "\u2800";
2571
- }
2572
- };
2573
-
2574
- // src/validation/result.ts
2575
- function success(data) {
2576
- return { success: true, data };
2577
- }
2578
- function failure(path2, message, value) {
2579
- return { success: false, error: { path: path2, message, value } };
2580
- }
2581
- function formatError(error) {
2582
- const path2 = error.path.length > 0 ? error.path.join(".") : "root";
2583
- return `${path2}: ${error.message}`;
2584
- }
2585
-
2586
- // src/validation/validators.ts
2587
- function string() {
2588
- return {
2589
- validate(value) {
2590
- if (typeof value === "string") return success(value);
2591
- return failure([], "Expected string", value);
2592
- }
2593
- };
2594
- }
2595
- function number() {
2596
- return {
2597
- validate(value) {
2598
- if (typeof value === "number" && !Number.isNaN(value)) return success(value);
2599
- return failure([], "Expected number", value);
2600
- }
2601
- };
2602
- }
2603
- function literal(expected) {
2604
- return {
2605
- validate(value) {
2606
- if (value === expected) return success(expected);
2607
- return failure([], `Expected '${expected}'`, value);
2608
- }
2609
- };
2610
- }
2611
-
2612
- // src/validation/combinators.ts
2613
- function object(shape) {
2614
- return {
2615
- validate(value) {
2616
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
2617
- return failure([], "Expected object", value);
2618
- }
2619
- const result = {};
2620
- for (const [key, validator] of Object.entries(shape)) {
2621
- const fieldValue = value[key];
2622
- const validationResult = validator.validate(fieldValue);
2623
- if (!validationResult.success) {
2624
- return {
2625
- success: false,
2626
- error: { ...validationResult.error, path: [key, ...validationResult.error.path] }
2627
- };
2628
- }
2629
- result[key] = validationResult.data;
2630
- }
2631
- return success(result);
2632
- }
2633
- };
2634
- }
2635
- function optional(validator) {
2636
- return {
2637
- validate(value) {
2638
- if (value === void 0) return success(void 0);
2639
- return validator.validate(value);
2640
- }
2641
- };
2642
- }
2643
- function nullable(validator) {
2644
- return {
2645
- validate(value) {
2646
- if (value === null) return success(null);
2647
- return validator.validate(value);
2648
- }
2649
- };
2650
- }
2651
-
2652
- // src/schemas/stdin-schema.ts
2653
- var ContextUsageSchema = object({
2654
- input_tokens: number(),
2655
- output_tokens: number(),
2656
- cache_creation_input_tokens: number(),
2657
- cache_read_input_tokens: number()
2658
- });
2659
- var CostInfoSchema = object({
2660
- total_cost_usd: optional(number()),
2661
- total_duration_ms: optional(number()),
2662
- total_api_duration_ms: optional(number()),
2663
- total_lines_added: optional(number()),
2664
- total_lines_removed: optional(number())
2665
- });
2666
- var ContextWindowSchema = object({
2667
- total_input_tokens: number(),
2668
- total_output_tokens: number(),
2669
- context_window_size: number(),
2670
- current_usage: nullable(ContextUsageSchema)
2671
- });
2672
- var ModelInfoSchema = object({
2673
- id: string(),
2674
- display_name: string()
2675
- });
2676
- var WorkspaceSchema = object({
2677
- current_dir: string(),
2678
- project_dir: string()
2679
- });
2680
- var OutputStyleSchema = object({
2681
- name: string()
2682
- });
2683
- var StdinDataSchema = object({
2684
- hook_event_name: optional(literal("Status")),
2685
- session_id: string(),
2686
- transcript_path: string(),
2687
- cwd: string(),
2688
- model: ModelInfoSchema,
2689
- workspace: WorkspaceSchema,
2690
- version: string(),
2691
- output_style: OutputStyleSchema,
2692
- cost: optional(CostInfoSchema),
2693
- context_window: ContextWindowSchema
2694
- });
2695
-
2696
- // src/data/stdin-provider.ts
2697
- var StdinParseError = class extends Error {
2698
- constructor(message) {
2699
- super(message);
2700
- this.name = "StdinParseError";
2701
- }
2702
- };
2703
- var StdinValidationError = class extends Error {
2704
- constructor(message) {
2705
- super(message);
2706
- this.name = "StdinValidationError";
2707
- }
2708
- };
2709
- var StdinProvider = class {
2710
- /**
2711
- * Parse and validate JSON string from stdin
2712
- * @param input JSON string to parse
2713
- * @returns Validated StdinData object
2714
- * @throws StdinParseError if JSON is malformed
2715
- * @throws StdinValidationError if data doesn't match schema
2716
- */
2717
- async parse(input) {
2718
- if (!input || input.trim().length === 0) {
2719
- throw new StdinParseError("stdin data is empty");
2720
- }
2721
- let data;
2722
- try {
2723
- data = JSON.parse(input);
2724
- } catch (error) {
2725
- throw new StdinParseError(`Invalid JSON: ${error.message}`);
2726
- }
2727
- const result = StdinDataSchema.validate(data);
2728
- if (!result.success) {
2729
- throw new StdinValidationError(`Validation failed: ${formatError(result.error)}`);
2730
- }
2731
- return result.data;
2732
- }
2733
- /**
2734
- * Safe parse that returns result instead of throwing
2735
- * Useful for testing and optional validation
2736
- * @param input JSON string to parse
2737
- * @returns Result object with success flag
2738
- */
2739
- async safeParse(input) {
2740
- try {
2741
- const data = await this.parse(input);
2742
- return { success: true, data };
2743
- } catch (error) {
2744
- return { success: false, error: error.message };
2745
- }
2746
- }
2747
- };
2748
-
2749
3702
  // src/index.ts
2750
3703
  async function readStdin() {
2751
3704
  const chunks = [];
@@ -2764,6 +3717,7 @@ async function main() {
2764
3717
  const provider = new StdinProvider();
2765
3718
  const stdinData = await provider.parse(stdin);
2766
3719
  const registry = new WidgetRegistry();
3720
+ const transcriptProvider = new TranscriptProvider();
2767
3721
  await registry.register(new ModelWidget());
2768
3722
  await registry.register(new ContextWidget());
2769
3723
  await registry.register(new CostWidget());
@@ -2772,6 +3726,12 @@ async function main() {
2772
3726
  await registry.register(new GitWidget());
2773
3727
  await registry.register(new GitTagWidget());
2774
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
+ }
2775
3735
  await registry.register(new PokerWidget());
2776
3736
  await registry.register(new EmptyLineWidget());
2777
3737
  const renderer = new Renderer({