claude-scope 0.8.16 → 0.8.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,37 +1,77 @@
1
- # claude-scope
1
+ <div align="center">
2
2
 
3
- Claude Code plugin for session status and analytics.
3
+ <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/typescript/typescript-original.svg" alt="TypeScript" width="80" height="80"/>
4
4
 
5
- **Requirements:**
6
- - Node.js 18 or higher
5
+ # claude-scope
7
6
 
8
- ## Installation
7
+ ### Beautiful, customizable statusline for Claude Code CLI
9
8
 
10
- ### Via npx (recommended)
9
+ [![npm version](https://badge.fury.io/js/claude-scope.svg)](https://www.npmjs.com/package/claude-scope)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)
11
+ [![Node.js Version](https://img.shields.io/node/v/claude-scope.svg)](https://github.com/YuriNachos/claude-scope)
12
+ [![GitHub Stars](https://img.shields.io/github/stars/YuriNachos/claude-scope?style=social)](https://github.com/YuriNachos/claude-scope)
13
+ [![codecov](https://codecov.io/gh/YuriNachos/claude-scope/branch/main/graph/badge.svg)](https://codecov.io/gh/YuriNachos/claude-scope)
11
14
 
12
- No installation required:
15
+ **Features** · [Installation](#-quick-start) · [Configuration](#-ai-powered-customization) · [Widgets](#-available-widgets) · [Themes](#-themes)
13
16
 
14
- ```bash
15
- npx claude-scope@latest
16
- ```
17
+ </div>
17
18
 
18
- ### Via npm globally
19
+ ---
19
20
 
20
- ```bash
21
- npm install -g claude-scope
21
+ <p align="center">
22
+ <i>Real-time session analytics in your terminal — zero runtime dependencies, 14 widgets, 17 themes</i>
23
+ </p>
24
+
25
+ ---
26
+
27
+ ## What is it?
28
+
29
+ **claude-scope** is a CLI plugin for [Claude Code](https://claude.ai/code) that displays real-time session information directly in your statusline. Track your context usage, session cost, git status, active tools, and more — all with beautiful customizable themes.
30
+
31
+ - **Zero runtime dependencies** — pure TypeScript, native Node.js only
32
+ - **14 customizable widgets** — model, context, cost, git, docker, and more
33
+ - **17 built-in themes** — from Dracula to Nord to Cyberpunk
34
+ - **12 display styles** — balanced, playful, compact, verbose, technical...
35
+ - **AI-friendly configuration** — just ask Claude to customize it!
36
+
37
+ ---
38
+
39
+ ## Preview
40
+
41
+ See how claude-scope looks with different themes:
42
+
43
+ **Dracula Theme** (default)
44
+ ```
45
+ Claude Opus 4.5 | 67% [████████] | +127/-42 | $1.24 | 2h 15m
46
+ main [+127 -42] | v0.8.16 | 💾 45.2k | 📄 1 📜 2 🔌 3 🪝 2
47
+ ⚡ Nuxt (running) | 🐳 3/5 🟢 | Edits: 8 | Bash: 3 | Read: 12
22
48
  ```
23
49
 
24
- ### Via bun
50
+ **Tokyo Night Theme**
51
+ ```
52
+ Claude Opus 4.5 | 67% [████████] | +127/-42 | $1.24 | 2h 15m
53
+ main [+127 -42] | v0.8.16 | 💾 45.2k | 📄 1 📜 2 🔌 3 🪝 2
54
+ ⚡ Nuxt (running) | 🐳 3/5 🟢 | Edits: 8 | Bash: 3 | Read: 12
55
+ ```
25
56
 
26
- ```bash
27
- bunx claude-scope@latest
57
+ **Nord Theme**
58
+ ```
59
+ Claude Opus 4.5 | 67% [████████] | +127/-42 | $1.24 | 2h 15m
60
+ main [+127 -42] | v0.8.16 | 💾 45.2k | 📄 1 📜 2 🔌 3 🪝 2
61
+ ⚡ Nuxt (running) | 🐳 3/5 🟢 | Edits: 8 | Bash: 3 | Read: 12
28
62
  ```
29
63
 
30
- ## Configuration
64
+ ---
31
65
 
32
- Add to your `~/.claude/settings.json`:
66
+ ## Quick Start
33
67
 
34
- ```json
68
+ Get up and running in 30 seconds:
69
+
70
+ ```bash
71
+ # 1. Install via npx (no installation required)
72
+ npx -y claude-scope@latest
73
+
74
+ # 2. Add to your ~/.claude/settings.json
35
75
  {
36
76
  "statusLine": {
37
77
  "type": "command",
@@ -39,95 +79,196 @@ Add to your `~/.claude/settings.json`:
39
79
  "padding": 0
40
80
  }
41
81
  }
82
+
83
+ # 3. Restart Claude Code — you're done! 🎉
42
84
  ```
43
85
 
44
- Or for global install:
86
+ After the first run, a default config is automatically created at `~/.claude-scope/config.json` with:
87
+ - **Layout**: `rich` (3 lines)
88
+ - **Style**: `balanced`
89
+ - **Theme**: `dracula`
90
+
91
+ ---
92
+
93
+ ## 🤖 AI-Powered Customization
94
+
95
+ **claude-scope is built to work with AI!** Simply ask Claude to customize it:
96
+
97
+ ```
98
+ ➕ "Add the docker widget to the third line"
99
+ 🎨 "Switch theme to nord"
100
+ 😄 "Make it more playful"
101
+ 🎯 "Show only model and context"
102
+ ```
103
+
104
+ **Example dialogue:**
105
+
106
+ > **User:** Add poker widget and make everything playful
107
+ >
108
+ > **Claude:** Sure! Adding poker widget and changing styles to playful...
109
+ >
110
+ > ✅ Done! Your statusline now shows poker hands with emojis.
111
+
112
+ Claude will automatically modify `~/.claude-scope/config.json` — changes take effect instantly.
113
+
114
+ **How it works:**
115
+ - Config contains `$aiDocs` field linking to [AI-CONFIG-GUIDE](AI-CONFIG-GUIDE.md)
116
+ - AI understands all 14 widgets, 12 styles, and 17 themes
117
+ - Just say what you want — no manual editing needed
118
+
119
+ ---
120
+
121
+ ## Available Widgets
122
+
123
+ | Widget | Description | Balanced | Playful |
124
+ |--------|-------------|----------|---------|
125
+ | **model** | Current Claude model | `Claude Opus 4.5` | `🤖 Opus 4.5` |
126
+ | **context** | Context usage with progress bar | `45% [████████░░░░░░░░░]` | `📊 45% [████████░░░░░░░░░]` |
127
+ | **cost** | Session cost in USD | `$0.42` | `💰 $0.42` |
128
+ | **duration** | Session duration | `1h 15m 30s` | `⌛ 1h 15m` |
129
+ | **lines** | Lines added/removed | `+42/-18` | `➕42 ➖18` |
130
+ | **git** | Git branch and changes | `main [+42 -18]` | `🔀 main ⬆42 ⬇18` |
131
+ | **git-tag** | Latest git tag | `v0.8.16` | `🏷️ v0.8.16` |
132
+ | **config-count** | Config file counts | `📄 1 CLAUDE.md │ 📜 2 rules │ 🔌 3 MCPs` | — |
133
+ | **cache-metrics** | Cache statistics | `💾 35.0k cache` | `💾 35.0k cache` |
134
+ | **active-tools** | Active tools tracking | `Edits: 5 │ Bash: 3 │ Read: 2` | `✏️ Edit 📖 Read 🐚 Bash` |
135
+ | **dev-server** | Dev server status | `⚡ Nuxt (running)` | `🏃 Nuxt` |
136
+ | **docker** | Docker containers | `Docker: 3/5 🟢` | `🐳 Docker: 3/5 🟢` |
137
+ | **poker** | Poker hands (easter egg) | `Hand: A♠ K♠ │ One Pair!` | `🃏 A♠️ K♠️ │ One Pair!` |
138
+
139
+ ### Layout Presets
140
+
141
+ | Preset | Lines | Widgets |
142
+ |--------|-------|---------|
143
+ | **Rich** (default) | 3 | Line 0: model, context, lines, cost, duration<br>Line 1: git, git-tag, cache-metrics, config-count<br>Line 2: dev-server, docker, active-tools |
144
+ | **Balanced** | 2 | Line 0: model, context, lines, cost, duration<br>Line 1: git, git-tag, cache-metrics, config-count |
145
+ | **Compact** | 1 | Line 0: model, context, cost, git, duration |
146
+
147
+ ---
148
+
149
+ ## Themes
150
+
151
+ **Built-in themes** — from classic to futuristic:
152
+
153
+ | Theme | Style | Colors |
154
+ |-------|-------|--------|
155
+ | `dracula` | Popular | Purple, pink, green |
156
+ | `tokyo-night` | Modern | Blue, yellow, green |
157
+ | `nord` | Cool | Arctic blues |
158
+ | `monokai` | Vibrant | **DEFAULT** — bright accents |
159
+ | `catppuccin-mocha` | Pastel | Soft dreamy colors |
160
+ | `github-dark-dimmed` | Standard | GitHub's official dark |
161
+ | `vscode-dark-plus` | Standard | VSCode's default |
162
+ | `one-dark-pro` | IDE | Atom's iconic theme |
163
+ | `solarized-dark` | Classic | Precise CIELAB lightness |
164
+ | `rose-pine` | Pastel | Rose/violet themed |
165
+ | `cyberpunk-neon` | Vibrant | Cyberpunk 2077 neon |
166
+ | `professional-blue` | Professional | Business-oriented blue |
167
+ | `gray` | Minimal | Neutral gray, minimal |
168
+ | `muted-gray` | Muted | Very subtle grays |
169
+ | `slate-blue` | Muted | Calm blue-grays |
170
+ | `dusty-sage` | Muted | Earthy greens |
171
+ | `semantic-classic` | Intuitive | Industry-standard colors |
172
+
173
+ ---
174
+
175
+ ## Configuration
176
+
177
+ ### Quick Config
178
+
179
+ Interactive configuration menu:
180
+
181
+ ```bash
182
+ claude-scope quick-config
183
+ ```
184
+
185
+ Choose from:
186
+ - **Layout**: Rich (3 lines), Balanced (2 lines), Compact (1 line)
187
+ - **Theme**: 17 themes with live preview
188
+ - **Style**: balanced, playful, compact, verbose, technical...
189
+
190
+ ### Manual Configuration
191
+
192
+ Edit `~/.claude-scope/config.json` directly:
45
193
 
46
194
  ```json
47
195
  {
48
- "statusLine": {
49
- "type": "command",
50
- "command": "claude-scope",
51
- "padding": 0
196
+ "version": "1.0.0",
197
+ "$aiDocs": "https://github.com/YuriNachos/claude-scope/blob/main/AI-CONFIG-GUIDE.md",
198
+ "lines": {
199
+ "0": [
200
+ { "id": "model", "style": "balanced", "colors": { "name": "\\u001b[38;2;189;147;229m" } },
201
+ { "id": "context", "style": "balanced", "colors": { "low": "...", "medium": "...", "high": "..." } }
202
+ ]
52
203
  }
53
204
  }
54
205
  ```
55
206
 
56
- ## Usage
207
+ See [AI-CONFIG-GUIDE](AI-CONFIG-GUIDE.md) for complete configuration reference.
57
208
 
58
- Once configured, claude-scope displays real-time session information in your statusline including:
59
- - Git branch and changes
60
- - Model information
61
- - Context usage with progress bar
62
- - Session duration
63
- - Cost estimation
64
- - Lines added/removed
65
- - Configuration counts
66
- - Poker hand (entertainment)
209
+ ---
67
210
 
68
- ### Widget Display Styles
211
+ ## Advanced Features
69
212
 
70
- Each widget supports multiple display styles for customization:
213
+ <details>
214
+ <summary><b>Zero Runtime Dependencies</b></summary>
71
215
 
72
- | Style | Description |
73
- |-------|-------------|
74
- | `balanced` | Default balanced style (minimalism + informativeness) |
75
- | `compact` | Maximally compact display |
76
- | `playful` | Fun style with informative emojis |
77
- | `verbose` | Detailed text descriptions |
78
- | `technical` | Technical details (model IDs, milliseconds, etc.) |
79
- | `symbolic` | Symbol-based representation |
80
- | `labeled` | Prefix labels for clarity |
81
- | `indicator` | Bullet indicator prefix |
82
- | `fancy` | Decorative formatting (brackets, quotes) |
83
- | `compact-verbose` | Compact with K-formatted numbers |
216
+ claude-scope is written in pure TypeScript and uses only native Node.js modules. No external runtime dependencies — maximum performance and security.
217
+ </details>
84
218
 
85
- **Note:** This is an early release with basic functionality. Additional features (repository status, session analytics, etc.) are planned for future releases.
219
+ <details>
220
+ <summary><b>Layout & Line System</b></summary>
86
221
 
87
- ## Quick Config
222
+ - **Line 0**: Primary info (model, context, cost, duration, lines, git, dev-server, docker)
223
+ - **Line 1**: Extended (git-tag, config-count)
224
+ - **Line 2**: Activity (cache-metrics, active-tools)
225
+ - **Line 4**: Poker widget (easter egg)
226
+ - **Line 5**: Empty line widget
227
+ </details>
88
228
 
89
- Interactive configuration for widget style and theme:
229
+ <details>
230
+ <summary><b>Widget System</b></summary>
90
231
 
91
- ```bash
92
- claude-scope quick-config
93
- ```
232
+ Each widget implements the `IWidget` interface with lifecycle methods:
233
+ - `initialize()` — Set up the widget
234
+ - `render()` — Generate output
235
+ - `update()` — Handle new data
236
+ - `isEnabled()` — Check if active
94
237
 
95
- This will launch an interactive menu where you can:
96
- 1. Choose display style: balanced, playful, or compact
97
- 2. Choose from 17 available themes
98
- 3. See live preview as you make selections
238
+ Widgets gracefully degrade on errors if something fails, it returns `null`.
239
+ </details>
99
240
 
100
- Configuration is saved to `~/.claude-scope/config.json`.
241
+ ---
101
242
 
102
- ### Styles
243
+ ## Documentation
103
244
 
104
- - **Balanced**: Clean display with labels (default)
105
- - **Playful**: Fun display with emojis
106
- - **Compact**: Minimal, condensed display
245
+ | Topic | Link |
246
+ |-------|------|
247
+ | Architecture, data flow, providers | [ARCHITECTURE.md](docs/ARCHITECTURE.md) |
248
+ | All widgets, styles, examples | [WIDGETS.md](docs/WIDGETS.md) |
249
+ | Theme system, customization | [THEME-SYSTEM.md](docs/THEME-SYSTEM.md) |
250
+ | Formatters, ANSI colors | [FORMATTERS.md](docs/FORMATTERS.md) |
251
+ | Version history, roadmap | [CHANGELOG.md](docs/CHANGELOG.md) |
107
252
 
108
- ### Themes
253
+ ---
109
254
 
110
- 17 built-in themes available: monokai, nord, dracula, dusty-sage, catppuccin-mocha, cyberpunk-neon, github-dark-dimmed, gray, muted-gray, one-dark-pro, professional-blue, rose-pine, semantic-classic, slate-blue, solarized-dark, tokyo-night, vscode-dark-plus.
255
+ ## Requirements
111
256
 
112
- ## Releasing
257
+ - **Node.js** 18 or higher
258
+ - **Claude Code** CLI
113
259
 
114
- To create a new release:
260
+ ---
115
261
 
116
- 1. Update version in `package.json`
117
- 2. Commit changes
118
- 3. Create and push tag:
262
+ ## License
119
263
 
120
- ```bash
121
- git tag v0.1.0
122
- git push origin main
123
- git push origin v0.1.0
124
- ```
264
+ [MIT](LICENSE.md) — feel free to use this project in your own work!
265
+
266
+ ---
267
+
268
+ <div align="center">
269
+
270
+ **Made with ❤️ by [YuriNachos](https://github.com/YuriNachos)**
125
271
 
126
- The GitHub Actions workflow will:
127
- - Run all tests
128
- - Build the project
129
- - Generate coverage report
130
- - Commit `dist/` to repository
131
- - Create GitHub Release with auto-generated notes
272
+ [GitHub](https://github.com/YuriNachos/claude-scope) · [Issues](https://github.com/YuriNachos/claude-scope/issues) · [Contributing](CONTRIBUTING.md)
132
273
 
133
- **Note:** If tests fail, the release will not be created.
274
+ </div>
@@ -38,12 +38,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
38
38
  function colorize(text, color) {
39
39
  return `${color}${text}${reset}`;
40
40
  }
41
- var reset, gray;
41
+ var reset, red, gray, lightGray, bold;
42
42
  var init_colors = __esm({
43
43
  "src/ui/utils/colors.ts"() {
44
44
  "use strict";
45
45
  reset = "\x1B[0m";
46
+ red = "\x1B[31m";
46
47
  gray = "\x1B[90m";
48
+ lightGray = "\x1B[37m";
49
+ bold = "\x1B[1m";
47
50
  }
48
51
  });
49
52
 
@@ -1378,7 +1381,7 @@ var init_theme = __esm({
1378
1381
  });
1379
1382
 
1380
1383
  // src/constants.ts
1381
- var TIME, DEFAULTS, DEMO_DATA, DEFAULT_PROGRESS_BAR_WIDTH;
1384
+ var TIME, DEFAULTS, ANSI_COLORS, DEMO_DATA, DEFAULT_PROGRESS_BAR_WIDTH;
1382
1385
  var init_constants = __esm({
1383
1386
  "src/constants.ts"() {
1384
1387
  "use strict";
@@ -1396,6 +1399,16 @@ var init_constants = __esm({
1396
1399
  /** Default width for progress bars in characters */
1397
1400
  PROGRESS_BAR_WIDTH: 20
1398
1401
  };
1402
+ ANSI_COLORS = {
1403
+ /** Green color */
1404
+ GREEN: "\x1B[32m",
1405
+ /** Yellow color */
1406
+ YELLOW: "\x1B[33m",
1407
+ /** Red color */
1408
+ RED: "\x1B[31m",
1409
+ /** Reset color */
1410
+ RESET: "\x1B[0m"
1411
+ };
1399
1412
  DEMO_DATA = {
1400
1413
  /** Demo session cost in USD */
1401
1414
  COST_USD: 0.42,
@@ -1516,24 +1529,27 @@ var init_widget_registry = __esm({
1516
1529
  "src/core/widget-registry.ts"() {
1517
1530
  "use strict";
1518
1531
  WidgetRegistry = class {
1519
- widgets = /* @__PURE__ */ new Map();
1532
+ widgets = [];
1520
1533
  /**
1521
1534
  * Register a widget
1522
1535
  */
1523
1536
  async register(widget, context) {
1524
- if (this.widgets.has(widget.id)) {
1525
- throw new Error(`Widget with id '${widget.id}' already registered`);
1526
- }
1527
1537
  if (context) {
1528
1538
  await widget.initialize(context);
1529
1539
  }
1530
- this.widgets.set(widget.id, widget);
1540
+ this.widgets.push(widget);
1531
1541
  }
1532
1542
  /**
1533
1543
  * Unregister a widget
1544
+ * @param widgetOrId Widget instance or widget id
1534
1545
  */
1535
- async unregister(id) {
1536
- const widget = this.widgets.get(id);
1546
+ async unregister(widgetOrId) {
1547
+ let widget;
1548
+ if (typeof widgetOrId === "string") {
1549
+ widget = this.widgets.find((w) => w.id === widgetOrId);
1550
+ } else {
1551
+ widget = this.widgets.find((w) => w === widgetOrId);
1552
+ }
1537
1553
  if (!widget) {
1538
1554
  return;
1539
1555
  }
@@ -1542,26 +1558,29 @@ var init_widget_registry = __esm({
1542
1558
  await widget.cleanup();
1543
1559
  }
1544
1560
  } finally {
1545
- this.widgets.delete(id);
1561
+ const index = this.widgets.indexOf(widget);
1562
+ if (index !== -1) {
1563
+ this.widgets.splice(index, 1);
1564
+ }
1546
1565
  }
1547
1566
  }
1548
1567
  /**
1549
1568
  * Get a widget by id
1550
1569
  */
1551
1570
  get(id) {
1552
- return this.widgets.get(id);
1571
+ return this.widgets.find((w) => w.id === id);
1553
1572
  }
1554
1573
  /**
1555
1574
  * Check if widget is registered
1556
1575
  */
1557
1576
  has(id) {
1558
- return this.widgets.has(id);
1577
+ return this.widgets.some((w) => w.id === id);
1559
1578
  }
1560
1579
  /**
1561
1580
  * Get all registered widgets
1562
1581
  */
1563
1582
  getAll() {
1564
- return Array.from(this.widgets.values());
1583
+ return [...this.widgets];
1565
1584
  }
1566
1585
  /**
1567
1586
  * Get only enabled widgets
@@ -1573,12 +1592,12 @@ var init_widget_registry = __esm({
1573
1592
  * Clear all widgets
1574
1593
  */
1575
1594
  async clear() {
1576
- for (const widget of this.widgets.values()) {
1595
+ for (const widget of this.widgets) {
1577
1596
  if (widget.cleanup) {
1578
1597
  await widget.cleanup();
1579
1598
  }
1580
1599
  }
1581
- this.widgets.clear();
1600
+ this.widgets = [];
1582
1601
  }
1583
1602
  };
1584
1603
  }
@@ -2349,6 +2368,9 @@ function formatDuration(ms) {
2349
2368
  function formatCostUSD(usd) {
2350
2369
  return `$${usd.toFixed(2)}`;
2351
2370
  }
2371
+ function colorize2(text, color) {
2372
+ return `${color}${text}${ANSI_COLORS.RESET}`;
2373
+ }
2352
2374
  function formatK(n) {
2353
2375
  const absN = Math.abs(n);
2354
2376
  if (absN < 1e3) {
@@ -7489,6 +7511,7 @@ function generateBalancedLayout(style, themeName) {
7489
7511
  const theme = getThemeByName(themeName).colors;
7490
7512
  return {
7491
7513
  version: "1.0.0",
7514
+ $aiDocs: "https://github.com/YuriNachos/claude-scope/blob/main/AI-CONFIG-GUIDE.md",
7492
7515
  lines: {
7493
7516
  "0": [
7494
7517
  {
@@ -7557,6 +7580,7 @@ function generateCompactLayout(style, themeName) {
7557
7580
  const theme = getThemeByName(themeName).colors;
7558
7581
  return {
7559
7582
  version: "1.0.0",
7583
+ $aiDocs: "https://github.com/YuriNachos/claude-scope/blob/main/AI-CONFIG-GUIDE.md",
7560
7584
  lines: {
7561
7585
  "0": [
7562
7586
  {
@@ -7597,6 +7621,7 @@ function generateRichLayout(style, themeName) {
7597
7621
  const theme = getThemeByName(themeName).colors;
7598
7622
  return {
7599
7623
  version: "1.0.0",
7624
+ $aiDocs: "https://github.com/YuriNachos/claude-scope/blob/main/AI-CONFIG-GUIDE.md",
7600
7625
  lines: {
7601
7626
  "0": [
7602
7627
  {
@@ -8063,6 +8088,9 @@ async function ensureDefaultConfig() {
8063
8088
 
8064
8089
  // src/config/config-loader.ts
8065
8090
  function getConfigPath() {
8091
+ if (process.env.CLAUDE_SCOPE_CONFIG) {
8092
+ return process.env.CLAUDE_SCOPE_CONFIG;
8093
+ }
8066
8094
  return (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".claude-scope", "config.json");
8067
8095
  }
8068
8096
  async function loadWidgetConfig() {
@@ -8109,10 +8137,704 @@ init_docker_widget();
8109
8137
 
8110
8138
  // src/core/widget-factory.ts
8111
8139
  init_duration_widget();
8140
+
8141
+ // src/widgets/empty-line-widget.ts
8142
+ init_widget_types();
8143
+ init_stdin_data_widget();
8144
+ var EmptyLineWidget = class extends StdinDataWidget {
8145
+ id = "empty-line";
8146
+ metadata = createWidgetMetadata(
8147
+ "Empty Line",
8148
+ "Empty line separator",
8149
+ "1.0.0",
8150
+ "claude-scope",
8151
+ 5
8152
+ // Sixth line (0-indexed)
8153
+ );
8154
+ _lineOverride;
8155
+ /**
8156
+ * All styles return the same value (Braille Pattern Blank).
8157
+ * This method exists for API consistency with other widgets.
8158
+ */
8159
+ setStyle(_style) {
8160
+ }
8161
+ setLine(line) {
8162
+ this._lineOverride = line;
8163
+ }
8164
+ getLine() {
8165
+ return this._lineOverride ?? this.metadata.line ?? 0;
8166
+ }
8167
+ /**
8168
+ * Return Braille Pattern Blank to create a visible empty separator line.
8169
+ * U+2800 occupies cell width but appears blank, ensuring the line renders.
8170
+ */
8171
+ renderWithData(_data, _context) {
8172
+ return "\u2800";
8173
+ }
8174
+ };
8175
+
8176
+ // src/core/widget-factory.ts
8112
8177
  init_git_tag_widget();
8113
8178
  init_git_widget();
8114
8179
  init_lines_widget();
8115
8180
  init_model_widget();
8181
+
8182
+ // src/widgets/poker-widget.ts
8183
+ init_style_types();
8184
+ init_widget_types();
8185
+ init_theme();
8186
+ init_stdin_data_widget();
8187
+
8188
+ // src/widgets/poker/deck.ts
8189
+ var import_node_crypto = require("node:crypto");
8190
+
8191
+ // src/widgets/poker/types.ts
8192
+ var Suit = {
8193
+ Spades: "spades",
8194
+ Hearts: "hearts",
8195
+ Diamonds: "diamonds",
8196
+ Clubs: "clubs"
8197
+ };
8198
+ var SUIT_SYMBOLS = {
8199
+ spades: "\u2660",
8200
+ hearts: "\u2665",
8201
+ diamonds: "\u2666",
8202
+ clubs: "\u2663"
8203
+ };
8204
+ var EMOJI_SYMBOLS = {
8205
+ spades: "\u2660\uFE0F",
8206
+ // ♠️
8207
+ hearts: "\u2665\uFE0F",
8208
+ // ♥️
8209
+ diamonds: "\u2666\uFE0F",
8210
+ // ♦️
8211
+ clubs: "\u2663\uFE0F"
8212
+ // ♣️
8213
+ };
8214
+ function isRedSuit(suit) {
8215
+ return suit === "hearts" || suit === "diamonds";
8216
+ }
8217
+ var Rank = {
8218
+ Two: "2",
8219
+ Three: "3",
8220
+ Four: "4",
8221
+ Five: "5",
8222
+ Six: "6",
8223
+ Seven: "7",
8224
+ Eight: "8",
8225
+ Nine: "9",
8226
+ Ten: "10",
8227
+ Jack: "J",
8228
+ Queen: "Q",
8229
+ King: "K",
8230
+ Ace: "A"
8231
+ };
8232
+ function getRankValue(rank) {
8233
+ const values = {
8234
+ "2": 2,
8235
+ "3": 3,
8236
+ "4": 4,
8237
+ "5": 5,
8238
+ "6": 6,
8239
+ "7": 7,
8240
+ "8": 8,
8241
+ "9": 9,
8242
+ "10": 10,
8243
+ J: 11,
8244
+ Q: 12,
8245
+ K: 13,
8246
+ A: 14
8247
+ };
8248
+ return values[rank];
8249
+ }
8250
+ function formatCard(card) {
8251
+ return `${card.rank}${SUIT_SYMBOLS[card.suit]}`;
8252
+ }
8253
+ function formatCardEmoji(card) {
8254
+ return `${card.rank}${EMOJI_SYMBOLS[card.suit]}`;
8255
+ }
8256
+
8257
+ // src/widgets/poker/deck.ts
8258
+ var ALL_SUITS = [Suit.Spades, Suit.Hearts, Suit.Diamonds, Suit.Clubs];
8259
+ var ALL_RANKS = [
8260
+ Rank.Two,
8261
+ Rank.Three,
8262
+ Rank.Four,
8263
+ Rank.Five,
8264
+ Rank.Six,
8265
+ Rank.Seven,
8266
+ Rank.Eight,
8267
+ Rank.Nine,
8268
+ Rank.Ten,
8269
+ Rank.Jack,
8270
+ Rank.Queen,
8271
+ Rank.King,
8272
+ Rank.Ace
8273
+ ];
8274
+ var Deck = class {
8275
+ cards = [];
8276
+ constructor() {
8277
+ this.initialize();
8278
+ this.shuffle();
8279
+ }
8280
+ /**
8281
+ * Create a standard 52-card deck
8282
+ */
8283
+ initialize() {
8284
+ this.cards = [];
8285
+ for (const suit of ALL_SUITS) {
8286
+ for (const rank of ALL_RANKS) {
8287
+ this.cards.push({ rank, suit });
8288
+ }
8289
+ }
8290
+ }
8291
+ /**
8292
+ * Shuffle deck using Fisher-Yates algorithm with crypto.random
8293
+ */
8294
+ shuffle() {
8295
+ for (let i = this.cards.length - 1; i > 0; i--) {
8296
+ const j = (0, import_node_crypto.randomInt)(0, i + 1);
8297
+ [this.cards[i], this.cards[j]] = [this.cards[j], this.cards[i]];
8298
+ }
8299
+ }
8300
+ /**
8301
+ * Deal one card from the top of the deck
8302
+ * @throws Error if deck is empty
8303
+ */
8304
+ deal() {
8305
+ if (this.cards.length === 0) {
8306
+ throw new Error("Deck is empty");
8307
+ }
8308
+ return this.cards.pop();
8309
+ }
8310
+ /**
8311
+ * Get number of remaining cards in deck
8312
+ */
8313
+ remaining() {
8314
+ return this.cards.length;
8315
+ }
8316
+ };
8317
+
8318
+ // src/widgets/poker/hand-evaluator.ts
8319
+ var HAND_DISPLAY = {
8320
+ [10 /* RoyalFlush */]: { name: "Royal Flush", emoji: "\u{1F3C6}" },
8321
+ [9 /* StraightFlush */]: { name: "Straight Flush", emoji: "\u{1F525}" },
8322
+ [8 /* FourOfAKind */]: { name: "Four of a Kind", emoji: "\u{1F48E}" },
8323
+ [7 /* FullHouse */]: { name: "Full House", emoji: "\u{1F3E0}" },
8324
+ [6 /* Flush */]: { name: "Flush", emoji: "\u{1F4A7}" },
8325
+ [5 /* Straight */]: { name: "Straight", emoji: "\u{1F4C8}" },
8326
+ [4 /* ThreeOfAKind */]: { name: "Three of a Kind", emoji: "\u{1F3AF}" },
8327
+ [3 /* TwoPair */]: { name: "Two Pair", emoji: "\u270C\uFE0F" },
8328
+ [2 /* OnePair */]: { name: "One Pair", emoji: "\u{1F44D}" },
8329
+ [1 /* HighCard */]: { name: "High Card", emoji: "\u{1F0CF}" }
8330
+ };
8331
+ function countRanks(cards) {
8332
+ const counts = /* @__PURE__ */ new Map();
8333
+ for (const card of cards) {
8334
+ const value = getRankValue(card.rank);
8335
+ counts.set(value, (counts.get(value) || 0) + 1);
8336
+ }
8337
+ return counts;
8338
+ }
8339
+ function countSuits(cards) {
8340
+ const counts = /* @__PURE__ */ new Map();
8341
+ for (const card of cards) {
8342
+ counts.set(card.suit, (counts.get(card.suit) || 0) + 1);
8343
+ }
8344
+ return counts;
8345
+ }
8346
+ function findCardsOfRank(cards, targetRank) {
8347
+ const indices = [];
8348
+ for (let i = 0; i < cards.length; i++) {
8349
+ if (getRankValue(cards[i].rank) === targetRank) {
8350
+ indices.push(i);
8351
+ }
8352
+ }
8353
+ return indices;
8354
+ }
8355
+ function findCardsOfSuit(cards, targetSuit) {
8356
+ const indices = [];
8357
+ for (let i = 0; i < cards.length; i++) {
8358
+ if (cards[i].suit === targetSuit) {
8359
+ indices.push(i);
8360
+ }
8361
+ }
8362
+ return indices;
8363
+ }
8364
+ function findFlushSuit(cards) {
8365
+ const suitCounts = countSuits(cards);
8366
+ for (const [suit, count] of suitCounts.entries()) {
8367
+ if (count >= 5) return suit;
8368
+ }
8369
+ return null;
8370
+ }
8371
+ function getStraightIndices(cards, highCard) {
8372
+ const uniqueValues = /* @__PURE__ */ new Set();
8373
+ const cardIndicesByRank = /* @__PURE__ */ new Map();
8374
+ for (let i = 0; i < cards.length; i++) {
8375
+ const value = getRankValue(cards[i].rank);
8376
+ if (!cardIndicesByRank.has(value)) {
8377
+ cardIndicesByRank.set(value, []);
8378
+ uniqueValues.add(value);
8379
+ }
8380
+ cardIndicesByRank.get(value)?.push(i);
8381
+ }
8382
+ const sortedValues = Array.from(uniqueValues).sort((a, b) => b - a);
8383
+ if (sortedValues.includes(14)) {
8384
+ sortedValues.push(1);
8385
+ }
8386
+ for (let i = 0; i <= sortedValues.length - 5; i++) {
8387
+ const current = sortedValues[i];
8388
+ const next1 = sortedValues[i + 1];
8389
+ const next2 = sortedValues[i + 2];
8390
+ const next3 = sortedValues[i + 3];
8391
+ const next4 = sortedValues[i + 4];
8392
+ if (current - next1 === 1 && current - next2 === 2 && current - next3 === 3 && current - next4 === 4) {
8393
+ if (current === highCard) {
8394
+ const indices = [];
8395
+ indices.push(cardIndicesByRank.get(current)[0]);
8396
+ indices.push(cardIndicesByRank.get(next1)[0]);
8397
+ indices.push(cardIndicesByRank.get(next2)[0]);
8398
+ indices.push(cardIndicesByRank.get(next3)[0]);
8399
+ const rank4 = next4 === 1 ? 14 : next4;
8400
+ indices.push(cardIndicesByRank.get(rank4)[0]);
8401
+ return indices;
8402
+ }
8403
+ }
8404
+ }
8405
+ return [];
8406
+ }
8407
+ function getStraightFlushHighCard(cards, suit) {
8408
+ const suitCards = cards.filter((c) => c.suit === suit);
8409
+ return getStraightHighCard(suitCards);
8410
+ }
8411
+ function getStraightFlushIndices(cards, highCard, suit) {
8412
+ const _suitCards = cards.filter((c) => c.suit === suit);
8413
+ const suitCardIndices = [];
8414
+ const indexMap = /* @__PURE__ */ new Map();
8415
+ for (let i = 0; i < cards.length; i++) {
8416
+ if (cards[i].suit === suit) {
8417
+ indexMap.set(suitCardIndices.length, i);
8418
+ suitCardIndices.push(cards[i]);
8419
+ }
8420
+ }
8421
+ const indices = getStraightIndices(suitCardIndices, highCard);
8422
+ return indices.map((idx) => indexMap.get(idx));
8423
+ }
8424
+ function getFullHouseIndices(cards) {
8425
+ const rankCounts = countRanks(cards);
8426
+ let tripsRank = 0;
8427
+ for (const [rank, count] of rankCounts.entries()) {
8428
+ if (count === 3) {
8429
+ tripsRank = rank;
8430
+ break;
8431
+ }
8432
+ }
8433
+ let pairRank = 0;
8434
+ for (const [rank, count] of rankCounts.entries()) {
8435
+ if (count >= 2 && rank !== tripsRank) {
8436
+ pairRank = rank;
8437
+ break;
8438
+ }
8439
+ }
8440
+ if (pairRank === 0) {
8441
+ const tripsRanks = [];
8442
+ for (const [rank, count] of rankCounts.entries()) {
8443
+ if (count === 3) {
8444
+ tripsRanks.push(rank);
8445
+ }
8446
+ }
8447
+ if (tripsRanks.length >= 2) {
8448
+ tripsRanks.sort((a, b) => b - a);
8449
+ tripsRank = tripsRanks[0];
8450
+ pairRank = tripsRanks[1];
8451
+ }
8452
+ }
8453
+ const tripsIndices = findCardsOfRank(cards, tripsRank);
8454
+ const pairIndices = findCardsOfRank(cards, pairRank);
8455
+ return [...tripsIndices.slice(0, 3), ...pairIndices.slice(0, 2)];
8456
+ }
8457
+ function isFlush(cards) {
8458
+ const suitCounts = countSuits(cards);
8459
+ for (const count of suitCounts.values()) {
8460
+ if (count >= 5) return true;
8461
+ }
8462
+ return false;
8463
+ }
8464
+ function getStraightHighCard(cards) {
8465
+ const uniqueValues = /* @__PURE__ */ new Set();
8466
+ for (const card of cards) {
8467
+ uniqueValues.add(getRankValue(card.rank));
8468
+ }
8469
+ const sortedValues = Array.from(uniqueValues).sort((a, b) => b - a);
8470
+ if (sortedValues.includes(14)) {
8471
+ sortedValues.push(1);
8472
+ }
8473
+ for (let i = 0; i <= sortedValues.length - 5; i++) {
8474
+ const current = sortedValues[i];
8475
+ const next1 = sortedValues[i + 1];
8476
+ const next2 = sortedValues[i + 2];
8477
+ const next3 = sortedValues[i + 3];
8478
+ const next4 = sortedValues[i + 4];
8479
+ if (current - next1 === 1 && current - next2 === 2 && current - next3 === 3 && current - next4 === 4) {
8480
+ return current;
8481
+ }
8482
+ }
8483
+ return null;
8484
+ }
8485
+ function getMaxCount(cards) {
8486
+ const rankCounts = countRanks(cards);
8487
+ let maxCount = 0;
8488
+ for (const count of rankCounts.values()) {
8489
+ if (count > maxCount) {
8490
+ maxCount = count;
8491
+ }
8492
+ }
8493
+ return maxCount;
8494
+ }
8495
+ function getPairCount(cards) {
8496
+ const rankCounts = countRanks(cards);
8497
+ let pairCount = 0;
8498
+ for (const count of rankCounts.values()) {
8499
+ if (count === 2) {
8500
+ pairCount++;
8501
+ }
8502
+ }
8503
+ return pairCount;
8504
+ }
8505
+ function getMostCommonRank(cards) {
8506
+ const rankCounts = countRanks(cards);
8507
+ let bestRank = 0;
8508
+ let bestCount = 0;
8509
+ for (const [rank, count] of rankCounts.entries()) {
8510
+ if (count > bestCount) {
8511
+ bestCount = count;
8512
+ bestRank = rank;
8513
+ }
8514
+ }
8515
+ return bestRank > 0 ? bestRank : null;
8516
+ }
8517
+ function getTwoPairRanks(cards) {
8518
+ const rankCounts = countRanks(cards);
8519
+ const pairRanks = [];
8520
+ for (const [rank, count] of rankCounts.entries()) {
8521
+ if (count >= 2) {
8522
+ pairRanks.push(rank);
8523
+ }
8524
+ }
8525
+ pairRanks.sort((a, b) => b - a);
8526
+ return pairRanks.slice(0, 2);
8527
+ }
8528
+ function getHighestCardIndex(cards) {
8529
+ let highestIdx = 0;
8530
+ let highestValue = 0;
8531
+ for (let i = 0; i < cards.length; i++) {
8532
+ const value = getRankValue(cards[i].rank);
8533
+ if (value > highestValue) {
8534
+ highestValue = value;
8535
+ highestIdx = i;
8536
+ }
8537
+ }
8538
+ return highestIdx;
8539
+ }
8540
+ function evaluateHand(hole, board) {
8541
+ const allCards = [...hole, ...board];
8542
+ const flush = isFlush(allCards);
8543
+ const straightHighCard = getStraightHighCard(allCards);
8544
+ const maxCount = getMaxCount(allCards);
8545
+ const pairCount = getPairCount(allCards);
8546
+ if (flush && straightHighCard === 14) {
8547
+ const flushSuit = findFlushSuit(allCards);
8548
+ const sfHighCard = getStraightFlushHighCard(allCards, flushSuit);
8549
+ if (sfHighCard === 14) {
8550
+ const participatingCards = getStraightFlushIndices(allCards, 14, flushSuit);
8551
+ return {
8552
+ rank: 10 /* RoyalFlush */,
8553
+ ...HAND_DISPLAY[10 /* RoyalFlush */],
8554
+ participatingCards
8555
+ };
8556
+ }
8557
+ }
8558
+ if (flush) {
8559
+ const flushSuit = findFlushSuit(allCards);
8560
+ const sfHighCard = getStraightFlushHighCard(allCards, flushSuit);
8561
+ if (sfHighCard !== null) {
8562
+ const participatingCards = getStraightFlushIndices(allCards, sfHighCard, flushSuit);
8563
+ return {
8564
+ rank: 9 /* StraightFlush */,
8565
+ ...HAND_DISPLAY[9 /* StraightFlush */],
8566
+ participatingCards
8567
+ };
8568
+ }
8569
+ }
8570
+ if (maxCount === 4) {
8571
+ const rank = getMostCommonRank(allCards);
8572
+ const participatingCards = findCardsOfRank(allCards, rank);
8573
+ return {
8574
+ rank: 8 /* FourOfAKind */,
8575
+ ...HAND_DISPLAY[8 /* FourOfAKind */],
8576
+ participatingCards
8577
+ };
8578
+ }
8579
+ if (maxCount === 3 && pairCount >= 1) {
8580
+ const participatingCards = getFullHouseIndices(allCards);
8581
+ return { rank: 7 /* FullHouse */, ...HAND_DISPLAY[7 /* FullHouse */], participatingCards };
8582
+ }
8583
+ if (flush) {
8584
+ const flushSuit = findFlushSuit(allCards);
8585
+ const suitIndices = findCardsOfSuit(allCards, flushSuit);
8586
+ const participatingCards = suitIndices.slice(0, 5);
8587
+ return { rank: 6 /* Flush */, ...HAND_DISPLAY[6 /* Flush */], participatingCards };
8588
+ }
8589
+ if (straightHighCard !== null) {
8590
+ const participatingCards = getStraightIndices(allCards, straightHighCard);
8591
+ return { rank: 5 /* Straight */, ...HAND_DISPLAY[5 /* Straight */], participatingCards };
8592
+ }
8593
+ if (maxCount === 3) {
8594
+ const rank = getMostCommonRank(allCards);
8595
+ const participatingCards = findCardsOfRank(allCards, rank);
8596
+ return {
8597
+ rank: 4 /* ThreeOfAKind */,
8598
+ ...HAND_DISPLAY[4 /* ThreeOfAKind */],
8599
+ participatingCards
8600
+ };
8601
+ }
8602
+ if (pairCount >= 2) {
8603
+ const [rank1, rank2] = getTwoPairRanks(allCards);
8604
+ const pair1Indices = findCardsOfRank(allCards, rank1);
8605
+ const pair2Indices = findCardsOfRank(allCards, rank2);
8606
+ const participatingCards = [...pair1Indices, ...pair2Indices];
8607
+ return { rank: 3 /* TwoPair */, ...HAND_DISPLAY[3 /* TwoPair */], participatingCards };
8608
+ }
8609
+ if (pairCount === 1) {
8610
+ const rank = getMostCommonRank(allCards);
8611
+ const participatingCards = findCardsOfRank(allCards, rank);
8612
+ return { rank: 2 /* OnePair */, ...HAND_DISPLAY[2 /* OnePair */], participatingCards };
8613
+ }
8614
+ const highestIdx = getHighestCardIndex(allCards);
8615
+ return {
8616
+ rank: 1 /* HighCard */,
8617
+ ...HAND_DISPLAY[1 /* HighCard */],
8618
+ participatingCards: [highestIdx]
8619
+ };
8620
+ }
8621
+
8622
+ // src/widgets/poker/styles.ts
8623
+ init_colors();
8624
+ init_formatters();
8625
+ var HAND_ABBREVIATIONS = {
8626
+ "Royal Flush": "RF",
8627
+ "Straight Flush": "SF",
8628
+ "Four of a Kind": "4K",
8629
+ "Full House": "FH",
8630
+ Flush: "FL",
8631
+ Straight: "ST",
8632
+ "Three of a Kind": "3K",
8633
+ "Two Pair": "2P",
8634
+ "One Pair": "1P",
8635
+ "High Card": "HC",
8636
+ Nothing: "\u2014"
8637
+ };
8638
+ function formatCardByParticipation(cardData, isParticipating) {
8639
+ const color = isRedSuit(cardData.card.suit) ? red : gray;
8640
+ const cardText = formatCard(cardData.card);
8641
+ if (isParticipating) {
8642
+ return `${color}${bold}(${cardText})${reset} `;
8643
+ } else {
8644
+ return `${color}${cardText}${reset} `;
8645
+ }
8646
+ }
8647
+ function formatCardCompact(cardData, isParticipating) {
8648
+ const color = isRedSuit(cardData.card.suit) ? red : gray;
8649
+ const cardText = formatCardTextCompact(cardData.card);
8650
+ if (isParticipating) {
8651
+ return `${color}${bold}(${cardText})${reset}`;
8652
+ } else {
8653
+ return `${color}${cardText}${reset}`;
8654
+ }
8655
+ }
8656
+ function formatCardTextCompact(card) {
8657
+ const rankSymbols = {
8658
+ "10": "T",
8659
+ "11": "J",
8660
+ "12": "Q",
8661
+ "13": "K",
8662
+ "14": "A"
8663
+ };
8664
+ const rank = String(card.rank);
8665
+ const rankSymbol = rankSymbols[rank] ?? rank;
8666
+ return `${rankSymbol}${card.suit}`;
8667
+ }
8668
+ function formatCardEmojiByParticipation(cardData, isParticipating) {
8669
+ const cardText = formatCardEmoji(cardData.card);
8670
+ if (isParticipating) {
8671
+ return `${bold}(${cardText})${reset} `;
8672
+ } else {
8673
+ return `${cardText} `;
8674
+ }
8675
+ }
8676
+ function formatHandResult(handResult, colors2) {
8677
+ if (!handResult) {
8678
+ return "\u2014";
8679
+ }
8680
+ const playerParticipates = handResult.participatingIndices.some((idx) => idx < 2);
8681
+ const resultText = !playerParticipates ? `Nothing \u{1F0CF}` : `${handResult.name}! ${handResult.emoji}`;
8682
+ if (!colors2) return resultText;
8683
+ return colorize2(resultText, colors2.result);
8684
+ }
8685
+ function getHandAbbreviation(handResult) {
8686
+ if (!handResult) {
8687
+ return "\u2014 (\u2014)";
8688
+ }
8689
+ const abbreviation = HAND_ABBREVIATIONS[handResult.name] ?? "\u2014";
8690
+ return `${abbreviation} (${handResult.name})`;
8691
+ }
8692
+ function balancedStyle2(data, colors2) {
8693
+ const { holeCards, boardCards, handResult } = data;
8694
+ const participatingSet = new Set(handResult?.participatingIndices || []);
8695
+ const handStr = holeCards.map((hc, idx) => formatCardByParticipation(hc, participatingSet.has(idx))).join("");
8696
+ const boardStr = boardCards.map((bc, idx) => formatCardByParticipation(bc, participatingSet.has(idx + 2))).join("");
8697
+ const labelColor = colors2?.participating ?? lightGray;
8698
+ const handLabel = colorize2("Hand:", labelColor);
8699
+ const boardLabel = colorize2("Board:", labelColor);
8700
+ return `${handLabel} ${handStr}| ${boardLabel} ${boardStr}\u2192 ${formatHandResult(handResult, colors2)}`;
8701
+ }
8702
+ var pokerStyles = {
8703
+ balanced: balancedStyle2,
8704
+ compact: balancedStyle2,
8705
+ playful: balancedStyle2,
8706
+ "compact-verbose": (data, colors2) => {
8707
+ const { holeCards, boardCards, handResult } = data;
8708
+ const participatingSet = new Set(handResult?.participatingIndices || []);
8709
+ const handStr = holeCards.map((hc, idx) => formatCardCompact(hc, participatingSet.has(idx))).join("");
8710
+ const boardStr = boardCards.map((bc, idx) => formatCardCompact(bc, participatingSet.has(idx + 2))).join("");
8711
+ const abbreviation = getHandAbbreviation(handResult);
8712
+ const result = `${handStr}| ${boardStr}\u2192 ${abbreviation}`;
8713
+ if (!colors2) return result;
8714
+ return colorize2(result, colors2.result);
8715
+ },
8716
+ emoji: (data, colors2) => {
8717
+ const { holeCards, boardCards, handResult } = data;
8718
+ const participatingSet = new Set(handResult?.participatingIndices || []);
8719
+ const handStr = holeCards.map((hc, idx) => formatCardEmojiByParticipation(hc, participatingSet.has(idx))).join("");
8720
+ const boardStr = boardCards.map((bc, idx) => formatCardEmojiByParticipation(bc, participatingSet.has(idx + 2))).join("");
8721
+ const labelColor = colors2?.participating ?? lightGray;
8722
+ const handLabel = colorize2("Hand:", labelColor);
8723
+ const boardLabel = colorize2("Board:", labelColor);
8724
+ return `${handLabel} ${handStr}| ${boardLabel} ${boardStr}\u2192 ${formatHandResult(handResult, colors2)}`;
8725
+ }
8726
+ };
8727
+
8728
+ // src/widgets/poker-widget.ts
8729
+ var PokerWidget = class extends StdinDataWidget {
8730
+ id = "poker";
8731
+ metadata = createWidgetMetadata(
8732
+ "Poker",
8733
+ "Displays random Texas Hold'em hands for entertainment",
8734
+ "1.0.0",
8735
+ "claude-scope",
8736
+ 4
8737
+ // Fifth line (0-indexed)
8738
+ );
8739
+ holeCards = [];
8740
+ boardCards = [];
8741
+ handResult = null;
8742
+ lastUpdateTimestamp = 0;
8743
+ THROTTLE_MS = 5e3;
8744
+ // 5 seconds
8745
+ colors;
8746
+ _lineOverride;
8747
+ styleFn = pokerStyles.balanced;
8748
+ setStyle(style = DEFAULT_WIDGET_STYLE) {
8749
+ const fn = pokerStyles[style];
8750
+ if (fn) {
8751
+ this.styleFn = fn;
8752
+ }
8753
+ }
8754
+ setLine(line) {
8755
+ this._lineOverride = line;
8756
+ }
8757
+ getLine() {
8758
+ return this._lineOverride ?? this.metadata.line ?? 0;
8759
+ }
8760
+ constructor(colors2) {
8761
+ super();
8762
+ this.colors = colors2 ?? DEFAULT_THEME;
8763
+ }
8764
+ /**
8765
+ * Generate new poker hand on each update
8766
+ */
8767
+ async update(data) {
8768
+ await super.update(data);
8769
+ const now = Date.now();
8770
+ if (this.lastUpdateTimestamp > 0 && now - this.lastUpdateTimestamp < this.THROTTLE_MS) {
8771
+ return;
8772
+ }
8773
+ const deck = new Deck();
8774
+ const hole = [deck.deal(), deck.deal()];
8775
+ const board = [deck.deal(), deck.deal(), deck.deal(), deck.deal(), deck.deal()];
8776
+ const result = evaluateHand(hole, board);
8777
+ this.holeCards = hole.map((card) => ({
8778
+ card,
8779
+ formatted: this.formatCardColor(card)
8780
+ }));
8781
+ this.boardCards = board.map((card) => ({
8782
+ card,
8783
+ formatted: this.formatCardColor(card)
8784
+ }));
8785
+ const playerParticipates = result.participatingCards.some((idx) => idx < 2);
8786
+ if (!playerParticipates) {
8787
+ this.handResult = {
8788
+ text: `Nothing \u{1F0CF}`,
8789
+ participatingIndices: result.participatingCards
8790
+ };
8791
+ } else {
8792
+ this.handResult = {
8793
+ text: `${result.name}! ${result.emoji}`,
8794
+ participatingIndices: result.participatingCards
8795
+ };
8796
+ }
8797
+ this.lastUpdateTimestamp = now;
8798
+ }
8799
+ /**
8800
+ * Format card with appropriate color (red for ♥♦, gray for ♠♣)
8801
+ */
8802
+ formatCardColor(card) {
8803
+ const _color = isRedSuit(card.suit) ? "red" : "gray";
8804
+ return formatCard(card);
8805
+ }
8806
+ renderWithData(_data, _context) {
8807
+ const holeCardsData = this.holeCards.map((hc, idx) => ({
8808
+ card: hc.card,
8809
+ isParticipating: (this.handResult?.participatingIndices || []).includes(idx)
8810
+ }));
8811
+ const boardCardsData = this.boardCards.map((bc, idx) => ({
8812
+ card: bc.card,
8813
+ isParticipating: (this.handResult?.participatingIndices || []).includes(idx + 2)
8814
+ }));
8815
+ const handResult = this.handResult ? {
8816
+ name: this.getHandName(this.handResult.text),
8817
+ emoji: this.getHandEmoji(this.handResult.text),
8818
+ participatingIndices: this.handResult.participatingIndices
8819
+ } : null;
8820
+ const renderData = {
8821
+ holeCards: holeCardsData,
8822
+ boardCards: boardCardsData,
8823
+ handResult
8824
+ };
8825
+ return this.styleFn(renderData, this.colors.poker);
8826
+ }
8827
+ getHandName(text) {
8828
+ const match = text.match(/^([^!]+)/);
8829
+ return match ? match[1].trim() : "Nothing";
8830
+ }
8831
+ getHandEmoji(text) {
8832
+ const match = text.match(/([🃏♠️♥️♦️♣️🎉✨🌟])/u);
8833
+ return match ? match[1] : "\u{1F0CF}";
8834
+ }
8835
+ };
8836
+
8837
+ // src/core/widget-factory.ts
8116
8838
  var WidgetFactory = class {
8117
8839
  transcriptProvider;
8118
8840
  constructor() {
@@ -8149,6 +8871,10 @@ var WidgetFactory = class {
8149
8871
  return new DevServerWidget(DEFAULT_THEME);
8150
8872
  case "docker":
8151
8873
  return new DockerWidget(DEFAULT_THEME);
8874
+ case "poker":
8875
+ return new PokerWidget(DEFAULT_THEME);
8876
+ case "empty-line":
8877
+ return new EmptyLineWidget();
8152
8878
  default:
8153
8879
  return null;
8154
8880
  }
@@ -8169,7 +8895,9 @@ var WidgetFactory = class {
8169
8895
  "cache-metrics",
8170
8896
  "active-tools",
8171
8897
  "dev-server",
8172
- "docker"
8898
+ "docker",
8899
+ "poker",
8900
+ "empty-line"
8173
8901
  ];
8174
8902
  }
8175
8903
  };
@@ -8360,18 +9088,12 @@ async function readStdin() {
8360
9088
  }
8361
9089
  return Buffer.concat(chunks).toString("utf8");
8362
9090
  }
8363
- function applyWidgetConfig(widget, widgetId, config) {
8364
- for (const [lineNum, widgets] of Object.entries(config.lines)) {
8365
- const widgetConfig = widgets.find((w) => w.id === widgetId);
8366
- if (widgetConfig) {
8367
- if (typeof widget.setStyle === "function" && isValidWidgetStyle(widgetConfig.style)) {
8368
- widget.setStyle(widgetConfig.style);
8369
- }
8370
- if (typeof widget.setLine === "function") {
8371
- widget.setLine(parseInt(lineNum, 10));
8372
- }
8373
- break;
8374
- }
9091
+ function applyWidgetConfig(widget, widgetConfig) {
9092
+ if (typeof widget.setStyle === "function" && isValidWidgetStyle(widgetConfig.style)) {
9093
+ widget.setStyle(widgetConfig.style);
9094
+ }
9095
+ if (typeof widget.setLine === "function" && typeof widgetConfig.line === "number") {
9096
+ widget.setLine(widgetConfig.line);
8375
9097
  }
8376
9098
  }
8377
9099
  async function main() {
@@ -8392,11 +9114,14 @@ async function main() {
8392
9114
  const widgetConfig = await loadWidgetConfig();
8393
9115
  const factory = new WidgetFactory();
8394
9116
  if (widgetConfig) {
8395
- for (const [_lineNum, widgets] of Object.entries(widgetConfig.lines)) {
9117
+ for (const [lineNum, widgets] of Object.entries(widgetConfig.lines)) {
8396
9118
  for (const widgetConfigItem of widgets) {
8397
9119
  const widget = factory.createWidget(widgetConfigItem.id);
8398
9120
  if (widget) {
8399
- applyWidgetConfig(widget, widgetConfigItem.id, widgetConfig);
9121
+ applyWidgetConfig(widget, {
9122
+ ...widgetConfigItem,
9123
+ line: parseInt(lineNum, 10)
9124
+ });
8400
9125
  await registry.register(widget);
8401
9126
  }
8402
9127
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-scope",
3
- "version": "0.8.16",
3
+ "version": "0.8.20",
4
4
  "description": "Claude Code plugin for session status and analytics",
5
5
  "license": "MIT",
6
6
  "type": "module",