claude-scope 0.1.7 → 0.1.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 (98) hide show
  1. package/dist/claude-scope.js +738 -0
  2. package/dist/index.js +0 -0
  3. package/package.json +9 -5
  4. package/dist/constants.d.ts +0 -56
  5. package/dist/constants.d.ts.map +0 -1
  6. package/dist/constants.js +0 -57
  7. package/dist/constants.js.map +0 -1
  8. package/dist/core/renderer.d.ts +0 -51
  9. package/dist/core/renderer.d.ts.map +0 -1
  10. package/dist/core/renderer.js +0 -75
  11. package/dist/core/renderer.js.map +0 -1
  12. package/dist/core/types.d.ts +0 -56
  13. package/dist/core/types.d.ts.map +0 -1
  14. package/dist/core/types.js +0 -5
  15. package/dist/core/types.js.map +0 -1
  16. package/dist/core/widget-registry.d.ts +0 -40
  17. package/dist/core/widget-registry.d.ts.map +0 -1
  18. package/dist/core/widget-registry.js +0 -75
  19. package/dist/core/widget-registry.js.map +0 -1
  20. package/dist/core/widget-types.d.ts +0 -30
  21. package/dist/core/widget-types.d.ts.map +0 -1
  22. package/dist/core/widget-types.js +0 -30
  23. package/dist/core/widget-types.js.map +0 -1
  24. package/dist/data/stdin-provider.d.ts +0 -44
  25. package/dist/data/stdin-provider.d.ts.map +0 -1
  26. package/dist/data/stdin-provider.js +0 -72
  27. package/dist/data/stdin-provider.js.map +0 -1
  28. package/dist/index.d.ts +0 -10
  29. package/dist/index.d.ts.map +0 -1
  30. package/dist/index.js.map +0 -1
  31. package/dist/providers/git-provider.d.ts +0 -71
  32. package/dist/providers/git-provider.d.ts.map +0 -1
  33. package/dist/providers/git-provider.js +0 -73
  34. package/dist/providers/git-provider.js.map +0 -1
  35. package/dist/schemas/stdin-schema.d.ts +0 -84
  36. package/dist/schemas/stdin-schema.d.ts.map +0 -1
  37. package/dist/schemas/stdin-schema.js +0 -48
  38. package/dist/schemas/stdin-schema.js.map +0 -1
  39. package/dist/types.d.ts +0 -31
  40. package/dist/types.d.ts.map +0 -1
  41. package/dist/types.js +0 -8
  42. package/dist/types.js.map +0 -1
  43. package/dist/ui/utils/colors.d.ts +0 -52
  44. package/dist/ui/utils/colors.d.ts.map +0 -1
  45. package/dist/ui/utils/colors.js +0 -54
  46. package/dist/ui/utils/colors.js.map +0 -1
  47. package/dist/ui/utils/formatters.d.ts +0 -56
  48. package/dist/ui/utils/formatters.d.ts.map +0 -1
  49. package/dist/ui/utils/formatters.js +0 -114
  50. package/dist/ui/utils/formatters.js.map +0 -1
  51. package/dist/validation/combinators.d.ts +0 -10
  52. package/dist/validation/combinators.d.ts.map +0 -1
  53. package/dist/validation/combinators.js +0 -49
  54. package/dist/validation/combinators.js.map +0 -1
  55. package/dist/validation/core.d.ts +0 -30
  56. package/dist/validation/core.d.ts.map +0 -1
  57. package/dist/validation/core.js +0 -2
  58. package/dist/validation/core.js.map +0 -1
  59. package/dist/validation/index.d.ts +0 -4
  60. package/dist/validation/index.d.ts.map +0 -1
  61. package/dist/validation/index.js +0 -4
  62. package/dist/validation/index.js.map +0 -1
  63. package/dist/validation/result.d.ts +0 -5
  64. package/dist/validation/result.d.ts.map +0 -1
  65. package/dist/validation/result.js +0 -11
  66. package/dist/validation/result.js.map +0 -1
  67. package/dist/validation/validators.d.ts +0 -7
  68. package/dist/validation/validators.d.ts.map +0 -1
  69. package/dist/validation/validators.js +0 -41
  70. package/dist/validation/validators.js.map +0 -1
  71. package/dist/widgets/context-widget.d.ts +0 -13
  72. package/dist/widgets/context-widget.d.ts.map +0 -1
  73. package/dist/widgets/context-widget.js +0 -31
  74. package/dist/widgets/context-widget.js.map +0 -1
  75. package/dist/widgets/core/stdin-data-widget.d.ts +0 -93
  76. package/dist/widgets/core/stdin-data-widget.d.ts.map +0 -1
  77. package/dist/widgets/core/stdin-data-widget.js +0 -84
  78. package/dist/widgets/core/stdin-data-widget.js.map +0 -1
  79. package/dist/widgets/cost-widget.d.ts +0 -13
  80. package/dist/widgets/cost-widget.d.ts.map +0 -1
  81. package/dist/widgets/cost-widget.js +0 -18
  82. package/dist/widgets/cost-widget.js.map +0 -1
  83. package/dist/widgets/duration-widget.d.ts +0 -13
  84. package/dist/widgets/duration-widget.d.ts.map +0 -1
  85. package/dist/widgets/duration-widget.js +0 -18
  86. package/dist/widgets/duration-widget.js.map +0 -1
  87. package/dist/widgets/git/git-changes-widget.d.ts +0 -38
  88. package/dist/widgets/git/git-changes-widget.d.ts.map +0 -1
  89. package/dist/widgets/git/git-changes-widget.js +0 -91
  90. package/dist/widgets/git/git-changes-widget.js.map +0 -1
  91. package/dist/widgets/git/git-widget.d.ts +0 -37
  92. package/dist/widgets/git/git-widget.d.ts.map +0 -1
  93. package/dist/widgets/git/git-widget.js +0 -67
  94. package/dist/widgets/git/git-widget.js.map +0 -1
  95. package/dist/widgets/model-widget.d.ts +0 -13
  96. package/dist/widgets/model-widget.d.ts.map +0 -1
  97. package/dist/widgets/model-widget.js +0 -15
  98. package/dist/widgets/model-widget.js.map +0 -1
@@ -0,0 +1,738 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/widget-registry.ts
4
+ var WidgetRegistry = class {
5
+ widgets = /* @__PURE__ */ new Map();
6
+ /**
7
+ * Register a widget
8
+ */
9
+ async register(widget, context) {
10
+ if (this.widgets.has(widget.id)) {
11
+ throw new Error(`Widget with id '${widget.id}' already registered`);
12
+ }
13
+ if (context) {
14
+ await widget.initialize(context);
15
+ }
16
+ this.widgets.set(widget.id, widget);
17
+ }
18
+ /**
19
+ * Unregister a widget
20
+ */
21
+ async unregister(id) {
22
+ const widget = this.widgets.get(id);
23
+ if (!widget) {
24
+ return;
25
+ }
26
+ try {
27
+ if (widget.cleanup) {
28
+ await widget.cleanup();
29
+ }
30
+ } finally {
31
+ this.widgets.delete(id);
32
+ }
33
+ }
34
+ /**
35
+ * Get a widget by id
36
+ */
37
+ get(id) {
38
+ return this.widgets.get(id);
39
+ }
40
+ /**
41
+ * Check if widget is registered
42
+ */
43
+ has(id) {
44
+ return this.widgets.has(id);
45
+ }
46
+ /**
47
+ * Get all registered widgets
48
+ */
49
+ getAll() {
50
+ return Array.from(this.widgets.values());
51
+ }
52
+ /**
53
+ * Get only enabled widgets
54
+ */
55
+ getEnabledWidgets() {
56
+ return this.getAll().filter((w) => w.isEnabled());
57
+ }
58
+ /**
59
+ * Clear all widgets
60
+ */
61
+ async clear() {
62
+ for (const widget of this.widgets.values()) {
63
+ if (widget.cleanup) {
64
+ await widget.cleanup();
65
+ }
66
+ }
67
+ this.widgets.clear();
68
+ }
69
+ };
70
+
71
+ // src/constants.ts
72
+ var TIME = {
73
+ /** Milliseconds per second */
74
+ MS_PER_SECOND: 1e3,
75
+ /** Seconds per minute */
76
+ SECONDS_PER_MINUTE: 60,
77
+ /** Seconds per hour */
78
+ SECONDS_PER_HOUR: 3600
79
+ };
80
+ var COST_THRESHOLDS = {
81
+ /** Below this value, show 4 decimal places ($0.0012) */
82
+ SMALL: 0.01,
83
+ /** Above this value, show no decimal places ($123) */
84
+ LARGE: 100
85
+ };
86
+ var CONTEXT_THRESHOLDS = {
87
+ /** Below this: green (low usage) */
88
+ LOW_MEDIUM: 50,
89
+ /** Below this: yellow (medium usage), above: red (high usage) */
90
+ MEDIUM_HIGH: 80
91
+ };
92
+ var DEFAULTS = {
93
+ /** Default separator between widgets */
94
+ SEPARATOR: " ",
95
+ /** Default width for progress bars in characters */
96
+ PROGRESS_BAR_WIDTH: 20
97
+ };
98
+ var ANSI_COLORS = {
99
+ /** Green color */
100
+ GREEN: "\x1B[32m",
101
+ /** Yellow color */
102
+ YELLOW: "\x1B[33m",
103
+ /** Red color */
104
+ RED: "\x1B[31m",
105
+ /** Reset color */
106
+ RESET: "\x1B[0m"
107
+ };
108
+ var DEFAULT_PROGRESS_BAR_WIDTH = DEFAULTS.PROGRESS_BAR_WIDTH;
109
+
110
+ // src/core/renderer.ts
111
+ var Renderer = class {
112
+ separator;
113
+ onError;
114
+ showErrors;
115
+ constructor(options = {}) {
116
+ this.separator = options.separator ?? DEFAULTS.SEPARATOR;
117
+ this.onError = options.onError;
118
+ this.showErrors = options.showErrors ?? false;
119
+ }
120
+ /**
121
+ * Render widgets into a single line with error boundaries
122
+ *
123
+ * Widgets that throw errors are logged (via onError callback) and skipped,
124
+ * allowing other widgets to continue rendering.
125
+ *
126
+ * @param widgets - Array of widgets to render
127
+ * @param context - Render context with width and timestamp
128
+ * @returns Combined widget outputs separated by separator
129
+ */
130
+ async render(widgets, context) {
131
+ const outputs = [];
132
+ for (const widget of widgets) {
133
+ if (!widget.isEnabled()) {
134
+ continue;
135
+ }
136
+ try {
137
+ const output = await widget.render(context);
138
+ if (output !== null) {
139
+ outputs.push(output);
140
+ }
141
+ } catch (error) {
142
+ this.handleError(error, widget);
143
+ if (this.showErrors) {
144
+ outputs.push(`${widget.id}:<err>`);
145
+ }
146
+ }
147
+ }
148
+ return outputs.join(this.separator);
149
+ }
150
+ /**
151
+ * Set custom separator
152
+ */
153
+ setSeparator(separator) {
154
+ this.separator = separator;
155
+ }
156
+ /**
157
+ * Handle widget render errors
158
+ *
159
+ * Calls the onError callback if provided, otherwise logs to console.warn
160
+ */
161
+ handleError(error, widget) {
162
+ if (this.onError) {
163
+ this.onError(error, widget);
164
+ } else {
165
+ console.warn(`[Widget ${widget.id}] ${error.message}`);
166
+ }
167
+ }
168
+ };
169
+
170
+ // src/core/widget-types.ts
171
+ function createWidgetMetadata(name, description, version = "1.0.0", author = "claude-scope") {
172
+ return {
173
+ name,
174
+ description,
175
+ version,
176
+ author
177
+ };
178
+ }
179
+
180
+ // src/providers/git-provider.ts
181
+ import { execFile } from "node:child_process";
182
+ import { promisify } from "node:util";
183
+ var execFileAsync = promisify(execFile);
184
+ var NativeGit = class {
185
+ cwd;
186
+ constructor(cwd) {
187
+ this.cwd = cwd;
188
+ }
189
+ async status() {
190
+ try {
191
+ const { stdout } = await execFileAsync("git", ["status", "--branch", "--short"], {
192
+ cwd: this.cwd
193
+ });
194
+ const match = stdout.match(/^##\s+(\S+)/m);
195
+ const current = match ? match[1] : null;
196
+ return { current };
197
+ } catch {
198
+ return { current: null };
199
+ }
200
+ }
201
+ async diffSummary(options) {
202
+ const args = ["diff", "--shortstat"];
203
+ if (options) {
204
+ args.push(...options);
205
+ }
206
+ try {
207
+ const { stdout } = await execFileAsync("git", args, {
208
+ cwd: this.cwd
209
+ });
210
+ const insertionMatch = stdout.match(/(\d+)\s+insertion/);
211
+ const deletionMatch = stdout.match(/(\d+)\s+deletion/);
212
+ const insertions = insertionMatch ? parseInt(insertionMatch[1], 10) : 0;
213
+ const deletions = deletionMatch ? parseInt(deletionMatch[1], 10) : 0;
214
+ const files = insertions > 0 || deletions > 0 ? [{ file: "(total)", insertions, deletions }] : [];
215
+ return { files };
216
+ } catch {
217
+ return { files: [] };
218
+ }
219
+ }
220
+ };
221
+ function createGit(cwd) {
222
+ return new NativeGit(cwd);
223
+ }
224
+
225
+ // src/widgets/git/git-widget.ts
226
+ var GitWidget = class {
227
+ id = "git";
228
+ metadata = createWidgetMetadata(
229
+ "Git Widget",
230
+ "Displays current git branch"
231
+ );
232
+ gitFactory;
233
+ git = null;
234
+ enabled = true;
235
+ cwd = null;
236
+ /**
237
+ * @param gitFactory - Optional factory function for creating IGit instances
238
+ * If not provided, uses default createGit (production)
239
+ * Tests can inject MockGit factory here
240
+ */
241
+ constructor(gitFactory) {
242
+ this.gitFactory = gitFactory || createGit;
243
+ }
244
+ async initialize(context) {
245
+ this.enabled = context.config?.enabled !== false;
246
+ }
247
+ async render(context) {
248
+ if (!this.enabled || !this.git || !this.cwd) {
249
+ return null;
250
+ }
251
+ try {
252
+ const status = await this.git.status();
253
+ const branch = status.current || null;
254
+ if (!branch) {
255
+ return null;
256
+ }
257
+ return ` ${branch}`;
258
+ } catch {
259
+ return null;
260
+ }
261
+ }
262
+ async update(data) {
263
+ if (data.cwd !== this.cwd) {
264
+ this.cwd = data.cwd;
265
+ this.git = this.gitFactory(data.cwd);
266
+ }
267
+ }
268
+ isEnabled() {
269
+ return this.enabled;
270
+ }
271
+ async cleanup() {
272
+ }
273
+ };
274
+
275
+ // src/widgets/core/stdin-data-widget.ts
276
+ var StdinDataWidget = class {
277
+ /**
278
+ * Stored stdin data from last update
279
+ */
280
+ data = null;
281
+ /**
282
+ * Widget enabled state
283
+ */
284
+ enabled = true;
285
+ /**
286
+ * Initialize widget with context
287
+ * @param context - Widget initialization context
288
+ */
289
+ async initialize(context) {
290
+ this.enabled = context.config?.enabled !== false;
291
+ }
292
+ /**
293
+ * Update widget with new stdin data
294
+ * @param data - Stdin data from Claude Code
295
+ */
296
+ async update(data) {
297
+ this.data = data;
298
+ }
299
+ /**
300
+ * Get stored stdin data
301
+ * @returns Stored stdin data
302
+ * @throws Error if data has not been initialized (update not called)
303
+ */
304
+ getData() {
305
+ if (!this.data) {
306
+ throw new Error(`Widget ${this.id} data not initialized. Call update() first.`);
307
+ }
308
+ return this.data;
309
+ }
310
+ /**
311
+ * Check if widget is enabled
312
+ * @returns true if widget should render
313
+ */
314
+ isEnabled() {
315
+ return this.enabled;
316
+ }
317
+ /**
318
+ * Template method - final, subclasses implement renderWithData()
319
+ *
320
+ * Handles null data checks and calls renderWithData() hook.
321
+ *
322
+ * @param context - Render context
323
+ * @returns Rendered string, or null if widget should not display
324
+ */
325
+ async render(context) {
326
+ if (!this.data || !this.enabled) {
327
+ return null;
328
+ }
329
+ return this.renderWithData(this.data, context);
330
+ }
331
+ };
332
+
333
+ // src/widgets/model-widget.ts
334
+ var ModelWidget = class extends StdinDataWidget {
335
+ id = "model";
336
+ metadata = createWidgetMetadata(
337
+ "Model",
338
+ "Displays the current Claude model name"
339
+ );
340
+ renderWithData(data, context) {
341
+ return data.model.display_name;
342
+ }
343
+ };
344
+
345
+ // src/ui/utils/formatters.ts
346
+ function formatDuration(ms) {
347
+ if (ms <= 0) return "0s";
348
+ const seconds = Math.floor(ms / TIME.MS_PER_SECOND);
349
+ const hours = Math.floor(seconds / TIME.SECONDS_PER_HOUR);
350
+ const minutes = Math.floor(seconds % TIME.SECONDS_PER_HOUR / TIME.SECONDS_PER_MINUTE);
351
+ const secs = seconds % TIME.SECONDS_PER_MINUTE;
352
+ const parts = [];
353
+ if (hours > 0) {
354
+ parts.push(`${hours}h`);
355
+ parts.push(`${minutes}m`);
356
+ parts.push(`${secs}s`);
357
+ } else if (minutes > 0) {
358
+ parts.push(`${minutes}m`);
359
+ parts.push(`${secs}s`);
360
+ } else {
361
+ parts.push(`${secs}s`);
362
+ }
363
+ return parts.join(" ");
364
+ }
365
+ function formatCostUSD(usd) {
366
+ const absUsd = Math.abs(usd);
367
+ if (usd < 0) {
368
+ return `$${usd.toFixed(2)}`;
369
+ } else if (absUsd < COST_THRESHOLDS.SMALL) {
370
+ return `$${usd.toFixed(4)}`;
371
+ } else if (absUsd < COST_THRESHOLDS.LARGE) {
372
+ return `$${usd.toFixed(2)}`;
373
+ } else {
374
+ return `$${Math.floor(usd).toFixed(0)}`;
375
+ }
376
+ }
377
+ function progressBar(percent, width = DEFAULTS.PROGRESS_BAR_WIDTH) {
378
+ const clampedPercent = Math.max(0, Math.min(100, percent));
379
+ const filled = Math.round(clampedPercent / 100 * width);
380
+ const empty = width - filled;
381
+ return "\u2588".repeat(filled) + "\u2591".repeat(empty);
382
+ }
383
+ function getContextColor(percent) {
384
+ const clampedPercent = Math.max(0, Math.min(100, percent));
385
+ if (clampedPercent < CONTEXT_THRESHOLDS.LOW_MEDIUM) {
386
+ return ANSI_COLORS.GREEN;
387
+ } else if (clampedPercent < CONTEXT_THRESHOLDS.MEDIUM_HIGH) {
388
+ return ANSI_COLORS.YELLOW;
389
+ } else {
390
+ return ANSI_COLORS.RED;
391
+ }
392
+ }
393
+ function colorize(text, color) {
394
+ return `${color}${text}${ANSI_COLORS.RESET}`;
395
+ }
396
+
397
+ // src/widgets/context-widget.ts
398
+ var ContextWidget = class extends StdinDataWidget {
399
+ id = "context";
400
+ metadata = createWidgetMetadata(
401
+ "Context",
402
+ "Displays context window usage with progress bar"
403
+ );
404
+ renderWithData(data, context) {
405
+ const { current_usage, context_window_size } = data.context_window;
406
+ if (!current_usage) return null;
407
+ const used = current_usage.input_tokens + current_usage.cache_creation_input_tokens + current_usage.output_tokens;
408
+ const percent = Math.round(used / context_window_size * 100);
409
+ const bar = progressBar(percent, DEFAULTS.PROGRESS_BAR_WIDTH);
410
+ const color = getContextColor(percent);
411
+ return colorize(`[${bar}] ${percent}%`, color);
412
+ }
413
+ };
414
+
415
+ // src/widgets/cost-widget.ts
416
+ var CostWidget = class extends StdinDataWidget {
417
+ id = "cost";
418
+ metadata = createWidgetMetadata(
419
+ "Cost",
420
+ "Displays session cost in USD"
421
+ );
422
+ renderWithData(data, context) {
423
+ if (!data.cost || data.cost.total_cost_usd === void 0) return null;
424
+ return formatCostUSD(data.cost.total_cost_usd);
425
+ }
426
+ };
427
+
428
+ // src/widgets/duration-widget.ts
429
+ var DurationWidget = class extends StdinDataWidget {
430
+ id = "duration";
431
+ metadata = createWidgetMetadata(
432
+ "Duration",
433
+ "Displays elapsed session time"
434
+ );
435
+ renderWithData(data, context) {
436
+ if (!data.cost || data.cost.total_duration_ms === void 0) return null;
437
+ return formatDuration(data.cost.total_duration_ms);
438
+ }
439
+ };
440
+
441
+ // src/widgets/git/git-changes-widget.ts
442
+ var GitChangesWidget = class {
443
+ id = "git-changes";
444
+ metadata = createWidgetMetadata(
445
+ "Git Changes",
446
+ "Displays git diff statistics"
447
+ );
448
+ gitFactory;
449
+ git = null;
450
+ enabled = true;
451
+ cwd = null;
452
+ /**
453
+ * @param gitFactory - Optional factory function for creating IGit instances
454
+ * If not provided, uses default createGit (production)
455
+ * Tests can inject MockGit factory here
456
+ */
457
+ constructor(gitFactory) {
458
+ this.gitFactory = gitFactory || createGit;
459
+ }
460
+ async initialize(context) {
461
+ this.enabled = context.config?.enabled !== false;
462
+ }
463
+ async update(data) {
464
+ if (data.cwd !== this.cwd) {
465
+ this.cwd = data.cwd;
466
+ this.git = this.gitFactory(data.cwd);
467
+ }
468
+ }
469
+ async render(context) {
470
+ if (!this.enabled || !this.git || !this.cwd) {
471
+ return null;
472
+ }
473
+ let changes;
474
+ try {
475
+ const summary = await this.git.diffSummary(["--shortstat"]);
476
+ let insertions = 0;
477
+ let deletions = 0;
478
+ if (summary.files && summary.files.length > 0) {
479
+ for (const file of summary.files) {
480
+ if (typeof file.insertions === "number") {
481
+ insertions += file.insertions;
482
+ }
483
+ if (typeof file.deletions === "number") {
484
+ deletions += file.deletions;
485
+ }
486
+ }
487
+ }
488
+ if (insertions === 0 && deletions === 0) {
489
+ return null;
490
+ }
491
+ changes = { insertions, deletions };
492
+ } catch {
493
+ return null;
494
+ }
495
+ if (!changes) return null;
496
+ if (changes.insertions === 0 && changes.deletions === 0) {
497
+ return null;
498
+ }
499
+ const parts = [];
500
+ if (changes.insertions > 0) parts.push(`+${changes.insertions}`);
501
+ if (changes.deletions > 0) parts.push(`-${changes.deletions}`);
502
+ return parts.join(",");
503
+ }
504
+ isEnabled() {
505
+ return this.enabled;
506
+ }
507
+ async cleanup() {
508
+ }
509
+ };
510
+
511
+ // src/validation/result.ts
512
+ function success(data) {
513
+ return { success: true, data };
514
+ }
515
+ function failure(path, message, value) {
516
+ return { success: false, error: { path, message, value } };
517
+ }
518
+ function formatError(error) {
519
+ const path = error.path.length > 0 ? error.path.join(".") : "root";
520
+ return `${path}: ${error.message}`;
521
+ }
522
+
523
+ // src/validation/validators.ts
524
+ function string() {
525
+ return {
526
+ validate(value) {
527
+ if (typeof value === "string") return success(value);
528
+ return failure([], "Expected string", value);
529
+ }
530
+ };
531
+ }
532
+ function number() {
533
+ return {
534
+ validate(value) {
535
+ if (typeof value === "number" && !Number.isNaN(value)) return success(value);
536
+ return failure([], "Expected number", value);
537
+ }
538
+ };
539
+ }
540
+ function literal(expected) {
541
+ return {
542
+ validate(value) {
543
+ if (value === expected) return success(expected);
544
+ return failure([], `Expected '${expected}'`, value);
545
+ }
546
+ };
547
+ }
548
+
549
+ // src/validation/combinators.ts
550
+ function object(shape) {
551
+ return {
552
+ validate(value) {
553
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
554
+ return failure([], "Expected object", value);
555
+ }
556
+ const result = {};
557
+ for (const [key, validator] of Object.entries(shape)) {
558
+ const fieldValue = value[key];
559
+ const validationResult = validator.validate(fieldValue);
560
+ if (!validationResult.success) {
561
+ return {
562
+ success: false,
563
+ error: { ...validationResult.error, path: [key, ...validationResult.error.path] }
564
+ };
565
+ }
566
+ result[key] = validationResult.data;
567
+ }
568
+ return success(result);
569
+ }
570
+ };
571
+ }
572
+ function optional(validator) {
573
+ return {
574
+ validate(value) {
575
+ if (value === void 0) return success(void 0);
576
+ return validator.validate(value);
577
+ }
578
+ };
579
+ }
580
+ function nullable(validator) {
581
+ return {
582
+ validate(value) {
583
+ if (value === null) return success(null);
584
+ return validator.validate(value);
585
+ }
586
+ };
587
+ }
588
+
589
+ // src/schemas/stdin-schema.ts
590
+ var ContextUsageSchema = object({
591
+ input_tokens: number(),
592
+ output_tokens: number(),
593
+ cache_creation_input_tokens: number(),
594
+ cache_read_input_tokens: number()
595
+ });
596
+ var CostInfoSchema = object({
597
+ total_cost_usd: optional(number()),
598
+ total_duration_ms: optional(number()),
599
+ total_api_duration_ms: optional(number()),
600
+ total_lines_added: optional(number()),
601
+ total_lines_removed: optional(number())
602
+ });
603
+ var ContextWindowSchema = object({
604
+ total_input_tokens: number(),
605
+ total_output_tokens: number(),
606
+ context_window_size: number(),
607
+ current_usage: nullable(ContextUsageSchema)
608
+ });
609
+ var ModelInfoSchema = object({
610
+ id: string(),
611
+ display_name: string()
612
+ });
613
+ var WorkspaceSchema = object({
614
+ current_dir: string(),
615
+ project_dir: string()
616
+ });
617
+ var OutputStyleSchema = object({
618
+ name: string()
619
+ });
620
+ var StdinDataSchema = object({
621
+ hook_event_name: literal("Status"),
622
+ session_id: string(),
623
+ transcript_path: string(),
624
+ cwd: string(),
625
+ model: ModelInfoSchema,
626
+ workspace: WorkspaceSchema,
627
+ version: string(),
628
+ output_style: OutputStyleSchema,
629
+ cost: optional(CostInfoSchema),
630
+ context_window: ContextWindowSchema
631
+ });
632
+
633
+ // src/data/stdin-provider.ts
634
+ var StdinParseError = class extends Error {
635
+ constructor(message) {
636
+ super(message);
637
+ this.name = "StdinParseError";
638
+ }
639
+ };
640
+ var StdinValidationError = class extends Error {
641
+ constructor(message) {
642
+ super(message);
643
+ this.name = "StdinValidationError";
644
+ }
645
+ };
646
+ var StdinProvider = class {
647
+ /**
648
+ * Parse and validate JSON string from stdin
649
+ * @param input JSON string to parse
650
+ * @returns Validated StdinData object
651
+ * @throws StdinParseError if JSON is malformed
652
+ * @throws StdinValidationError if data doesn't match schema
653
+ */
654
+ async parse(input) {
655
+ if (!input || input.trim().length === 0) {
656
+ throw new StdinParseError("stdin data is empty");
657
+ }
658
+ let data;
659
+ try {
660
+ data = JSON.parse(input);
661
+ } catch (error) {
662
+ throw new StdinParseError(`Invalid JSON: ${error.message}`);
663
+ }
664
+ const result = StdinDataSchema.validate(data);
665
+ if (!result.success) {
666
+ throw new StdinValidationError(
667
+ `Validation failed: ${formatError(result.error)}`
668
+ );
669
+ }
670
+ return result.data;
671
+ }
672
+ /**
673
+ * Safe parse that returns result instead of throwing
674
+ * Useful for testing and optional validation
675
+ * @param input JSON string to parse
676
+ * @returns Result object with success flag
677
+ */
678
+ async safeParse(input) {
679
+ try {
680
+ const data = await this.parse(input);
681
+ return { success: true, data };
682
+ } catch (error) {
683
+ return { success: false, error: error.message };
684
+ }
685
+ }
686
+ };
687
+
688
+ // src/index.ts
689
+ async function readStdin() {
690
+ const chunks = [];
691
+ for await (const chunk of process.stdin) {
692
+ chunks.push(chunk);
693
+ }
694
+ return Buffer.concat(chunks).toString("utf8");
695
+ }
696
+ async function main() {
697
+ try {
698
+ const stdin = await readStdin();
699
+ if (!stdin || stdin.trim().length === 0) {
700
+ return "";
701
+ }
702
+ const provider = new StdinProvider();
703
+ const stdinData = await provider.parse(stdin);
704
+ const registry = new WidgetRegistry();
705
+ await registry.register(new ModelWidget());
706
+ await registry.register(new ContextWidget());
707
+ await registry.register(new CostWidget());
708
+ await registry.register(new DurationWidget());
709
+ await registry.register(new GitWidget());
710
+ await registry.register(new GitChangesWidget());
711
+ const renderer = new Renderer({
712
+ separator: " \u2502 ",
713
+ onError: (error, widget) => {
714
+ },
715
+ showErrors: false
716
+ });
717
+ for (const widget of registry.getAll()) {
718
+ await widget.update(stdinData);
719
+ }
720
+ const output = await renderer.render(
721
+ registry.getEnabledWidgets(),
722
+ { width: 80, timestamp: Date.now() }
723
+ );
724
+ return output || "";
725
+ } catch (error) {
726
+ return "";
727
+ }
728
+ }
729
+ main().then((output) => {
730
+ if (output) {
731
+ console.log(output);
732
+ }
733
+ }).catch(() => {
734
+ process.exit(0);
735
+ });
736
+ export {
737
+ main
738
+ };