claude-scope 0.8.16 → 0.8.18

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,
@@ -2349,6 +2362,9 @@ function formatDuration(ms) {
2349
2362
  function formatCostUSD(usd) {
2350
2363
  return `$${usd.toFixed(2)}`;
2351
2364
  }
2365
+ function colorize2(text, color) {
2366
+ return `${color}${text}${ANSI_COLORS.RESET}`;
2367
+ }
2352
2368
  function formatK(n) {
2353
2369
  const absN = Math.abs(n);
2354
2370
  if (absN < 1e3) {
@@ -7489,6 +7505,7 @@ function generateBalancedLayout(style, themeName) {
7489
7505
  const theme = getThemeByName(themeName).colors;
7490
7506
  return {
7491
7507
  version: "1.0.0",
7508
+ $aiDocs: "https://github.com/YuriNachos/claude-scope/blob/main/AI-CONFIG-GUIDE.md",
7492
7509
  lines: {
7493
7510
  "0": [
7494
7511
  {
@@ -7557,6 +7574,7 @@ function generateCompactLayout(style, themeName) {
7557
7574
  const theme = getThemeByName(themeName).colors;
7558
7575
  return {
7559
7576
  version: "1.0.0",
7577
+ $aiDocs: "https://github.com/YuriNachos/claude-scope/blob/main/AI-CONFIG-GUIDE.md",
7560
7578
  lines: {
7561
7579
  "0": [
7562
7580
  {
@@ -7597,6 +7615,7 @@ function generateRichLayout(style, themeName) {
7597
7615
  const theme = getThemeByName(themeName).colors;
7598
7616
  return {
7599
7617
  version: "1.0.0",
7618
+ $aiDocs: "https://github.com/YuriNachos/claude-scope/blob/main/AI-CONFIG-GUIDE.md",
7600
7619
  lines: {
7601
7620
  "0": [
7602
7621
  {
@@ -8109,10 +8128,704 @@ init_docker_widget();
8109
8128
 
8110
8129
  // src/core/widget-factory.ts
8111
8130
  init_duration_widget();
8131
+
8132
+ // src/widgets/empty-line-widget.ts
8133
+ init_widget_types();
8134
+ init_stdin_data_widget();
8135
+ var EmptyLineWidget = class extends StdinDataWidget {
8136
+ id = "empty-line";
8137
+ metadata = createWidgetMetadata(
8138
+ "Empty Line",
8139
+ "Empty line separator",
8140
+ "1.0.0",
8141
+ "claude-scope",
8142
+ 5
8143
+ // Sixth line (0-indexed)
8144
+ );
8145
+ _lineOverride;
8146
+ /**
8147
+ * All styles return the same value (Braille Pattern Blank).
8148
+ * This method exists for API consistency with other widgets.
8149
+ */
8150
+ setStyle(_style) {
8151
+ }
8152
+ setLine(line) {
8153
+ this._lineOverride = line;
8154
+ }
8155
+ getLine() {
8156
+ return this._lineOverride ?? this.metadata.line ?? 0;
8157
+ }
8158
+ /**
8159
+ * Return Braille Pattern Blank to create a visible empty separator line.
8160
+ * U+2800 occupies cell width but appears blank, ensuring the line renders.
8161
+ */
8162
+ renderWithData(_data, _context) {
8163
+ return "\u2800";
8164
+ }
8165
+ };
8166
+
8167
+ // src/core/widget-factory.ts
8112
8168
  init_git_tag_widget();
8113
8169
  init_git_widget();
8114
8170
  init_lines_widget();
8115
8171
  init_model_widget();
8172
+
8173
+ // src/widgets/poker-widget.ts
8174
+ init_style_types();
8175
+ init_widget_types();
8176
+ init_theme();
8177
+ init_stdin_data_widget();
8178
+
8179
+ // src/widgets/poker/deck.ts
8180
+ var import_node_crypto = require("node:crypto");
8181
+
8182
+ // src/widgets/poker/types.ts
8183
+ var Suit = {
8184
+ Spades: "spades",
8185
+ Hearts: "hearts",
8186
+ Diamonds: "diamonds",
8187
+ Clubs: "clubs"
8188
+ };
8189
+ var SUIT_SYMBOLS = {
8190
+ spades: "\u2660",
8191
+ hearts: "\u2665",
8192
+ diamonds: "\u2666",
8193
+ clubs: "\u2663"
8194
+ };
8195
+ var EMOJI_SYMBOLS = {
8196
+ spades: "\u2660\uFE0F",
8197
+ // ♠️
8198
+ hearts: "\u2665\uFE0F",
8199
+ // ♥️
8200
+ diamonds: "\u2666\uFE0F",
8201
+ // ♦️
8202
+ clubs: "\u2663\uFE0F"
8203
+ // ♣️
8204
+ };
8205
+ function isRedSuit(suit) {
8206
+ return suit === "hearts" || suit === "diamonds";
8207
+ }
8208
+ var Rank = {
8209
+ Two: "2",
8210
+ Three: "3",
8211
+ Four: "4",
8212
+ Five: "5",
8213
+ Six: "6",
8214
+ Seven: "7",
8215
+ Eight: "8",
8216
+ Nine: "9",
8217
+ Ten: "10",
8218
+ Jack: "J",
8219
+ Queen: "Q",
8220
+ King: "K",
8221
+ Ace: "A"
8222
+ };
8223
+ function getRankValue(rank) {
8224
+ const values = {
8225
+ "2": 2,
8226
+ "3": 3,
8227
+ "4": 4,
8228
+ "5": 5,
8229
+ "6": 6,
8230
+ "7": 7,
8231
+ "8": 8,
8232
+ "9": 9,
8233
+ "10": 10,
8234
+ J: 11,
8235
+ Q: 12,
8236
+ K: 13,
8237
+ A: 14
8238
+ };
8239
+ return values[rank];
8240
+ }
8241
+ function formatCard(card) {
8242
+ return `${card.rank}${SUIT_SYMBOLS[card.suit]}`;
8243
+ }
8244
+ function formatCardEmoji(card) {
8245
+ return `${card.rank}${EMOJI_SYMBOLS[card.suit]}`;
8246
+ }
8247
+
8248
+ // src/widgets/poker/deck.ts
8249
+ var ALL_SUITS = [Suit.Spades, Suit.Hearts, Suit.Diamonds, Suit.Clubs];
8250
+ var ALL_RANKS = [
8251
+ Rank.Two,
8252
+ Rank.Three,
8253
+ Rank.Four,
8254
+ Rank.Five,
8255
+ Rank.Six,
8256
+ Rank.Seven,
8257
+ Rank.Eight,
8258
+ Rank.Nine,
8259
+ Rank.Ten,
8260
+ Rank.Jack,
8261
+ Rank.Queen,
8262
+ Rank.King,
8263
+ Rank.Ace
8264
+ ];
8265
+ var Deck = class {
8266
+ cards = [];
8267
+ constructor() {
8268
+ this.initialize();
8269
+ this.shuffle();
8270
+ }
8271
+ /**
8272
+ * Create a standard 52-card deck
8273
+ */
8274
+ initialize() {
8275
+ this.cards = [];
8276
+ for (const suit of ALL_SUITS) {
8277
+ for (const rank of ALL_RANKS) {
8278
+ this.cards.push({ rank, suit });
8279
+ }
8280
+ }
8281
+ }
8282
+ /**
8283
+ * Shuffle deck using Fisher-Yates algorithm with crypto.random
8284
+ */
8285
+ shuffle() {
8286
+ for (let i = this.cards.length - 1; i > 0; i--) {
8287
+ const j = (0, import_node_crypto.randomInt)(0, i + 1);
8288
+ [this.cards[i], this.cards[j]] = [this.cards[j], this.cards[i]];
8289
+ }
8290
+ }
8291
+ /**
8292
+ * Deal one card from the top of the deck
8293
+ * @throws Error if deck is empty
8294
+ */
8295
+ deal() {
8296
+ if (this.cards.length === 0) {
8297
+ throw new Error("Deck is empty");
8298
+ }
8299
+ return this.cards.pop();
8300
+ }
8301
+ /**
8302
+ * Get number of remaining cards in deck
8303
+ */
8304
+ remaining() {
8305
+ return this.cards.length;
8306
+ }
8307
+ };
8308
+
8309
+ // src/widgets/poker/hand-evaluator.ts
8310
+ var HAND_DISPLAY = {
8311
+ [10 /* RoyalFlush */]: { name: "Royal Flush", emoji: "\u{1F3C6}" },
8312
+ [9 /* StraightFlush */]: { name: "Straight Flush", emoji: "\u{1F525}" },
8313
+ [8 /* FourOfAKind */]: { name: "Four of a Kind", emoji: "\u{1F48E}" },
8314
+ [7 /* FullHouse */]: { name: "Full House", emoji: "\u{1F3E0}" },
8315
+ [6 /* Flush */]: { name: "Flush", emoji: "\u{1F4A7}" },
8316
+ [5 /* Straight */]: { name: "Straight", emoji: "\u{1F4C8}" },
8317
+ [4 /* ThreeOfAKind */]: { name: "Three of a Kind", emoji: "\u{1F3AF}" },
8318
+ [3 /* TwoPair */]: { name: "Two Pair", emoji: "\u270C\uFE0F" },
8319
+ [2 /* OnePair */]: { name: "One Pair", emoji: "\u{1F44D}" },
8320
+ [1 /* HighCard */]: { name: "High Card", emoji: "\u{1F0CF}" }
8321
+ };
8322
+ function countRanks(cards) {
8323
+ const counts = /* @__PURE__ */ new Map();
8324
+ for (const card of cards) {
8325
+ const value = getRankValue(card.rank);
8326
+ counts.set(value, (counts.get(value) || 0) + 1);
8327
+ }
8328
+ return counts;
8329
+ }
8330
+ function countSuits(cards) {
8331
+ const counts = /* @__PURE__ */ new Map();
8332
+ for (const card of cards) {
8333
+ counts.set(card.suit, (counts.get(card.suit) || 0) + 1);
8334
+ }
8335
+ return counts;
8336
+ }
8337
+ function findCardsOfRank(cards, targetRank) {
8338
+ const indices = [];
8339
+ for (let i = 0; i < cards.length; i++) {
8340
+ if (getRankValue(cards[i].rank) === targetRank) {
8341
+ indices.push(i);
8342
+ }
8343
+ }
8344
+ return indices;
8345
+ }
8346
+ function findCardsOfSuit(cards, targetSuit) {
8347
+ const indices = [];
8348
+ for (let i = 0; i < cards.length; i++) {
8349
+ if (cards[i].suit === targetSuit) {
8350
+ indices.push(i);
8351
+ }
8352
+ }
8353
+ return indices;
8354
+ }
8355
+ function findFlushSuit(cards) {
8356
+ const suitCounts = countSuits(cards);
8357
+ for (const [suit, count] of suitCounts.entries()) {
8358
+ if (count >= 5) return suit;
8359
+ }
8360
+ return null;
8361
+ }
8362
+ function getStraightIndices(cards, highCard) {
8363
+ const uniqueValues = /* @__PURE__ */ new Set();
8364
+ const cardIndicesByRank = /* @__PURE__ */ new Map();
8365
+ for (let i = 0; i < cards.length; i++) {
8366
+ const value = getRankValue(cards[i].rank);
8367
+ if (!cardIndicesByRank.has(value)) {
8368
+ cardIndicesByRank.set(value, []);
8369
+ uniqueValues.add(value);
8370
+ }
8371
+ cardIndicesByRank.get(value)?.push(i);
8372
+ }
8373
+ const sortedValues = Array.from(uniqueValues).sort((a, b) => b - a);
8374
+ if (sortedValues.includes(14)) {
8375
+ sortedValues.push(1);
8376
+ }
8377
+ for (let i = 0; i <= sortedValues.length - 5; i++) {
8378
+ const current = sortedValues[i];
8379
+ const next1 = sortedValues[i + 1];
8380
+ const next2 = sortedValues[i + 2];
8381
+ const next3 = sortedValues[i + 3];
8382
+ const next4 = sortedValues[i + 4];
8383
+ if (current - next1 === 1 && current - next2 === 2 && current - next3 === 3 && current - next4 === 4) {
8384
+ if (current === highCard) {
8385
+ const indices = [];
8386
+ indices.push(cardIndicesByRank.get(current)[0]);
8387
+ indices.push(cardIndicesByRank.get(next1)[0]);
8388
+ indices.push(cardIndicesByRank.get(next2)[0]);
8389
+ indices.push(cardIndicesByRank.get(next3)[0]);
8390
+ const rank4 = next4 === 1 ? 14 : next4;
8391
+ indices.push(cardIndicesByRank.get(rank4)[0]);
8392
+ return indices;
8393
+ }
8394
+ }
8395
+ }
8396
+ return [];
8397
+ }
8398
+ function getStraightFlushHighCard(cards, suit) {
8399
+ const suitCards = cards.filter((c) => c.suit === suit);
8400
+ return getStraightHighCard(suitCards);
8401
+ }
8402
+ function getStraightFlushIndices(cards, highCard, suit) {
8403
+ const _suitCards = cards.filter((c) => c.suit === suit);
8404
+ const suitCardIndices = [];
8405
+ const indexMap = /* @__PURE__ */ new Map();
8406
+ for (let i = 0; i < cards.length; i++) {
8407
+ if (cards[i].suit === suit) {
8408
+ indexMap.set(suitCardIndices.length, i);
8409
+ suitCardIndices.push(cards[i]);
8410
+ }
8411
+ }
8412
+ const indices = getStraightIndices(suitCardIndices, highCard);
8413
+ return indices.map((idx) => indexMap.get(idx));
8414
+ }
8415
+ function getFullHouseIndices(cards) {
8416
+ const rankCounts = countRanks(cards);
8417
+ let tripsRank = 0;
8418
+ for (const [rank, count] of rankCounts.entries()) {
8419
+ if (count === 3) {
8420
+ tripsRank = rank;
8421
+ break;
8422
+ }
8423
+ }
8424
+ let pairRank = 0;
8425
+ for (const [rank, count] of rankCounts.entries()) {
8426
+ if (count >= 2 && rank !== tripsRank) {
8427
+ pairRank = rank;
8428
+ break;
8429
+ }
8430
+ }
8431
+ if (pairRank === 0) {
8432
+ const tripsRanks = [];
8433
+ for (const [rank, count] of rankCounts.entries()) {
8434
+ if (count === 3) {
8435
+ tripsRanks.push(rank);
8436
+ }
8437
+ }
8438
+ if (tripsRanks.length >= 2) {
8439
+ tripsRanks.sort((a, b) => b - a);
8440
+ tripsRank = tripsRanks[0];
8441
+ pairRank = tripsRanks[1];
8442
+ }
8443
+ }
8444
+ const tripsIndices = findCardsOfRank(cards, tripsRank);
8445
+ const pairIndices = findCardsOfRank(cards, pairRank);
8446
+ return [...tripsIndices.slice(0, 3), ...pairIndices.slice(0, 2)];
8447
+ }
8448
+ function isFlush(cards) {
8449
+ const suitCounts = countSuits(cards);
8450
+ for (const count of suitCounts.values()) {
8451
+ if (count >= 5) return true;
8452
+ }
8453
+ return false;
8454
+ }
8455
+ function getStraightHighCard(cards) {
8456
+ const uniqueValues = /* @__PURE__ */ new Set();
8457
+ for (const card of cards) {
8458
+ uniqueValues.add(getRankValue(card.rank));
8459
+ }
8460
+ const sortedValues = Array.from(uniqueValues).sort((a, b) => b - a);
8461
+ if (sortedValues.includes(14)) {
8462
+ sortedValues.push(1);
8463
+ }
8464
+ for (let i = 0; i <= sortedValues.length - 5; i++) {
8465
+ const current = sortedValues[i];
8466
+ const next1 = sortedValues[i + 1];
8467
+ const next2 = sortedValues[i + 2];
8468
+ const next3 = sortedValues[i + 3];
8469
+ const next4 = sortedValues[i + 4];
8470
+ if (current - next1 === 1 && current - next2 === 2 && current - next3 === 3 && current - next4 === 4) {
8471
+ return current;
8472
+ }
8473
+ }
8474
+ return null;
8475
+ }
8476
+ function getMaxCount(cards) {
8477
+ const rankCounts = countRanks(cards);
8478
+ let maxCount = 0;
8479
+ for (const count of rankCounts.values()) {
8480
+ if (count > maxCount) {
8481
+ maxCount = count;
8482
+ }
8483
+ }
8484
+ return maxCount;
8485
+ }
8486
+ function getPairCount(cards) {
8487
+ const rankCounts = countRanks(cards);
8488
+ let pairCount = 0;
8489
+ for (const count of rankCounts.values()) {
8490
+ if (count === 2) {
8491
+ pairCount++;
8492
+ }
8493
+ }
8494
+ return pairCount;
8495
+ }
8496
+ function getMostCommonRank(cards) {
8497
+ const rankCounts = countRanks(cards);
8498
+ let bestRank = 0;
8499
+ let bestCount = 0;
8500
+ for (const [rank, count] of rankCounts.entries()) {
8501
+ if (count > bestCount) {
8502
+ bestCount = count;
8503
+ bestRank = rank;
8504
+ }
8505
+ }
8506
+ return bestRank > 0 ? bestRank : null;
8507
+ }
8508
+ function getTwoPairRanks(cards) {
8509
+ const rankCounts = countRanks(cards);
8510
+ const pairRanks = [];
8511
+ for (const [rank, count] of rankCounts.entries()) {
8512
+ if (count >= 2) {
8513
+ pairRanks.push(rank);
8514
+ }
8515
+ }
8516
+ pairRanks.sort((a, b) => b - a);
8517
+ return pairRanks.slice(0, 2);
8518
+ }
8519
+ function getHighestCardIndex(cards) {
8520
+ let highestIdx = 0;
8521
+ let highestValue = 0;
8522
+ for (let i = 0; i < cards.length; i++) {
8523
+ const value = getRankValue(cards[i].rank);
8524
+ if (value > highestValue) {
8525
+ highestValue = value;
8526
+ highestIdx = i;
8527
+ }
8528
+ }
8529
+ return highestIdx;
8530
+ }
8531
+ function evaluateHand(hole, board) {
8532
+ const allCards = [...hole, ...board];
8533
+ const flush = isFlush(allCards);
8534
+ const straightHighCard = getStraightHighCard(allCards);
8535
+ const maxCount = getMaxCount(allCards);
8536
+ const pairCount = getPairCount(allCards);
8537
+ if (flush && straightHighCard === 14) {
8538
+ const flushSuit = findFlushSuit(allCards);
8539
+ const sfHighCard = getStraightFlushHighCard(allCards, flushSuit);
8540
+ if (sfHighCard === 14) {
8541
+ const participatingCards = getStraightFlushIndices(allCards, 14, flushSuit);
8542
+ return {
8543
+ rank: 10 /* RoyalFlush */,
8544
+ ...HAND_DISPLAY[10 /* RoyalFlush */],
8545
+ participatingCards
8546
+ };
8547
+ }
8548
+ }
8549
+ if (flush) {
8550
+ const flushSuit = findFlushSuit(allCards);
8551
+ const sfHighCard = getStraightFlushHighCard(allCards, flushSuit);
8552
+ if (sfHighCard !== null) {
8553
+ const participatingCards = getStraightFlushIndices(allCards, sfHighCard, flushSuit);
8554
+ return {
8555
+ rank: 9 /* StraightFlush */,
8556
+ ...HAND_DISPLAY[9 /* StraightFlush */],
8557
+ participatingCards
8558
+ };
8559
+ }
8560
+ }
8561
+ if (maxCount === 4) {
8562
+ const rank = getMostCommonRank(allCards);
8563
+ const participatingCards = findCardsOfRank(allCards, rank);
8564
+ return {
8565
+ rank: 8 /* FourOfAKind */,
8566
+ ...HAND_DISPLAY[8 /* FourOfAKind */],
8567
+ participatingCards
8568
+ };
8569
+ }
8570
+ if (maxCount === 3 && pairCount >= 1) {
8571
+ const participatingCards = getFullHouseIndices(allCards);
8572
+ return { rank: 7 /* FullHouse */, ...HAND_DISPLAY[7 /* FullHouse */], participatingCards };
8573
+ }
8574
+ if (flush) {
8575
+ const flushSuit = findFlushSuit(allCards);
8576
+ const suitIndices = findCardsOfSuit(allCards, flushSuit);
8577
+ const participatingCards = suitIndices.slice(0, 5);
8578
+ return { rank: 6 /* Flush */, ...HAND_DISPLAY[6 /* Flush */], participatingCards };
8579
+ }
8580
+ if (straightHighCard !== null) {
8581
+ const participatingCards = getStraightIndices(allCards, straightHighCard);
8582
+ return { rank: 5 /* Straight */, ...HAND_DISPLAY[5 /* Straight */], participatingCards };
8583
+ }
8584
+ if (maxCount === 3) {
8585
+ const rank = getMostCommonRank(allCards);
8586
+ const participatingCards = findCardsOfRank(allCards, rank);
8587
+ return {
8588
+ rank: 4 /* ThreeOfAKind */,
8589
+ ...HAND_DISPLAY[4 /* ThreeOfAKind */],
8590
+ participatingCards
8591
+ };
8592
+ }
8593
+ if (pairCount >= 2) {
8594
+ const [rank1, rank2] = getTwoPairRanks(allCards);
8595
+ const pair1Indices = findCardsOfRank(allCards, rank1);
8596
+ const pair2Indices = findCardsOfRank(allCards, rank2);
8597
+ const participatingCards = [...pair1Indices, ...pair2Indices];
8598
+ return { rank: 3 /* TwoPair */, ...HAND_DISPLAY[3 /* TwoPair */], participatingCards };
8599
+ }
8600
+ if (pairCount === 1) {
8601
+ const rank = getMostCommonRank(allCards);
8602
+ const participatingCards = findCardsOfRank(allCards, rank);
8603
+ return { rank: 2 /* OnePair */, ...HAND_DISPLAY[2 /* OnePair */], participatingCards };
8604
+ }
8605
+ const highestIdx = getHighestCardIndex(allCards);
8606
+ return {
8607
+ rank: 1 /* HighCard */,
8608
+ ...HAND_DISPLAY[1 /* HighCard */],
8609
+ participatingCards: [highestIdx]
8610
+ };
8611
+ }
8612
+
8613
+ // src/widgets/poker/styles.ts
8614
+ init_colors();
8615
+ init_formatters();
8616
+ var HAND_ABBREVIATIONS = {
8617
+ "Royal Flush": "RF",
8618
+ "Straight Flush": "SF",
8619
+ "Four of a Kind": "4K",
8620
+ "Full House": "FH",
8621
+ Flush: "FL",
8622
+ Straight: "ST",
8623
+ "Three of a Kind": "3K",
8624
+ "Two Pair": "2P",
8625
+ "One Pair": "1P",
8626
+ "High Card": "HC",
8627
+ Nothing: "\u2014"
8628
+ };
8629
+ function formatCardByParticipation(cardData, isParticipating) {
8630
+ const color = isRedSuit(cardData.card.suit) ? red : gray;
8631
+ const cardText = formatCard(cardData.card);
8632
+ if (isParticipating) {
8633
+ return `${color}${bold}(${cardText})${reset} `;
8634
+ } else {
8635
+ return `${color}${cardText}${reset} `;
8636
+ }
8637
+ }
8638
+ function formatCardCompact(cardData, isParticipating) {
8639
+ const color = isRedSuit(cardData.card.suit) ? red : gray;
8640
+ const cardText = formatCardTextCompact(cardData.card);
8641
+ if (isParticipating) {
8642
+ return `${color}${bold}(${cardText})${reset}`;
8643
+ } else {
8644
+ return `${color}${cardText}${reset}`;
8645
+ }
8646
+ }
8647
+ function formatCardTextCompact(card) {
8648
+ const rankSymbols = {
8649
+ "10": "T",
8650
+ "11": "J",
8651
+ "12": "Q",
8652
+ "13": "K",
8653
+ "14": "A"
8654
+ };
8655
+ const rank = String(card.rank);
8656
+ const rankSymbol = rankSymbols[rank] ?? rank;
8657
+ return `${rankSymbol}${card.suit}`;
8658
+ }
8659
+ function formatCardEmojiByParticipation(cardData, isParticipating) {
8660
+ const cardText = formatCardEmoji(cardData.card);
8661
+ if (isParticipating) {
8662
+ return `${bold}(${cardText})${reset} `;
8663
+ } else {
8664
+ return `${cardText} `;
8665
+ }
8666
+ }
8667
+ function formatHandResult(handResult, colors2) {
8668
+ if (!handResult) {
8669
+ return "\u2014";
8670
+ }
8671
+ const playerParticipates = handResult.participatingIndices.some((idx) => idx < 2);
8672
+ const resultText = !playerParticipates ? `Nothing \u{1F0CF}` : `${handResult.name}! ${handResult.emoji}`;
8673
+ if (!colors2) return resultText;
8674
+ return colorize2(resultText, colors2.result);
8675
+ }
8676
+ function getHandAbbreviation(handResult) {
8677
+ if (!handResult) {
8678
+ return "\u2014 (\u2014)";
8679
+ }
8680
+ const abbreviation = HAND_ABBREVIATIONS[handResult.name] ?? "\u2014";
8681
+ return `${abbreviation} (${handResult.name})`;
8682
+ }
8683
+ function balancedStyle2(data, colors2) {
8684
+ const { holeCards, boardCards, handResult } = data;
8685
+ const participatingSet = new Set(handResult?.participatingIndices || []);
8686
+ const handStr = holeCards.map((hc, idx) => formatCardByParticipation(hc, participatingSet.has(idx))).join("");
8687
+ const boardStr = boardCards.map((bc, idx) => formatCardByParticipation(bc, participatingSet.has(idx + 2))).join("");
8688
+ const labelColor = colors2?.participating ?? lightGray;
8689
+ const handLabel = colorize2("Hand:", labelColor);
8690
+ const boardLabel = colorize2("Board:", labelColor);
8691
+ return `${handLabel} ${handStr}| ${boardLabel} ${boardStr}\u2192 ${formatHandResult(handResult, colors2)}`;
8692
+ }
8693
+ var pokerStyles = {
8694
+ balanced: balancedStyle2,
8695
+ compact: balancedStyle2,
8696
+ playful: balancedStyle2,
8697
+ "compact-verbose": (data, colors2) => {
8698
+ const { holeCards, boardCards, handResult } = data;
8699
+ const participatingSet = new Set(handResult?.participatingIndices || []);
8700
+ const handStr = holeCards.map((hc, idx) => formatCardCompact(hc, participatingSet.has(idx))).join("");
8701
+ const boardStr = boardCards.map((bc, idx) => formatCardCompact(bc, participatingSet.has(idx + 2))).join("");
8702
+ const abbreviation = getHandAbbreviation(handResult);
8703
+ const result = `${handStr}| ${boardStr}\u2192 ${abbreviation}`;
8704
+ if (!colors2) return result;
8705
+ return colorize2(result, colors2.result);
8706
+ },
8707
+ emoji: (data, colors2) => {
8708
+ const { holeCards, boardCards, handResult } = data;
8709
+ const participatingSet = new Set(handResult?.participatingIndices || []);
8710
+ const handStr = holeCards.map((hc, idx) => formatCardEmojiByParticipation(hc, participatingSet.has(idx))).join("");
8711
+ const boardStr = boardCards.map((bc, idx) => formatCardEmojiByParticipation(bc, participatingSet.has(idx + 2))).join("");
8712
+ const labelColor = colors2?.participating ?? lightGray;
8713
+ const handLabel = colorize2("Hand:", labelColor);
8714
+ const boardLabel = colorize2("Board:", labelColor);
8715
+ return `${handLabel} ${handStr}| ${boardLabel} ${boardStr}\u2192 ${formatHandResult(handResult, colors2)}`;
8716
+ }
8717
+ };
8718
+
8719
+ // src/widgets/poker-widget.ts
8720
+ var PokerWidget = class extends StdinDataWidget {
8721
+ id = "poker";
8722
+ metadata = createWidgetMetadata(
8723
+ "Poker",
8724
+ "Displays random Texas Hold'em hands for entertainment",
8725
+ "1.0.0",
8726
+ "claude-scope",
8727
+ 4
8728
+ // Fifth line (0-indexed)
8729
+ );
8730
+ holeCards = [];
8731
+ boardCards = [];
8732
+ handResult = null;
8733
+ lastUpdateTimestamp = 0;
8734
+ THROTTLE_MS = 5e3;
8735
+ // 5 seconds
8736
+ colors;
8737
+ _lineOverride;
8738
+ styleFn = pokerStyles.balanced;
8739
+ setStyle(style = DEFAULT_WIDGET_STYLE) {
8740
+ const fn = pokerStyles[style];
8741
+ if (fn) {
8742
+ this.styleFn = fn;
8743
+ }
8744
+ }
8745
+ setLine(line) {
8746
+ this._lineOverride = line;
8747
+ }
8748
+ getLine() {
8749
+ return this._lineOverride ?? this.metadata.line ?? 0;
8750
+ }
8751
+ constructor(colors2) {
8752
+ super();
8753
+ this.colors = colors2 ?? DEFAULT_THEME;
8754
+ }
8755
+ /**
8756
+ * Generate new poker hand on each update
8757
+ */
8758
+ async update(data) {
8759
+ await super.update(data);
8760
+ const now = Date.now();
8761
+ if (this.lastUpdateTimestamp > 0 && now - this.lastUpdateTimestamp < this.THROTTLE_MS) {
8762
+ return;
8763
+ }
8764
+ const deck = new Deck();
8765
+ const hole = [deck.deal(), deck.deal()];
8766
+ const board = [deck.deal(), deck.deal(), deck.deal(), deck.deal(), deck.deal()];
8767
+ const result = evaluateHand(hole, board);
8768
+ this.holeCards = hole.map((card) => ({
8769
+ card,
8770
+ formatted: this.formatCardColor(card)
8771
+ }));
8772
+ this.boardCards = board.map((card) => ({
8773
+ card,
8774
+ formatted: this.formatCardColor(card)
8775
+ }));
8776
+ const playerParticipates = result.participatingCards.some((idx) => idx < 2);
8777
+ if (!playerParticipates) {
8778
+ this.handResult = {
8779
+ text: `Nothing \u{1F0CF}`,
8780
+ participatingIndices: result.participatingCards
8781
+ };
8782
+ } else {
8783
+ this.handResult = {
8784
+ text: `${result.name}! ${result.emoji}`,
8785
+ participatingIndices: result.participatingCards
8786
+ };
8787
+ }
8788
+ this.lastUpdateTimestamp = now;
8789
+ }
8790
+ /**
8791
+ * Format card with appropriate color (red for ♥♦, gray for ♠♣)
8792
+ */
8793
+ formatCardColor(card) {
8794
+ const _color = isRedSuit(card.suit) ? "red" : "gray";
8795
+ return formatCard(card);
8796
+ }
8797
+ renderWithData(_data, _context) {
8798
+ const holeCardsData = this.holeCards.map((hc, idx) => ({
8799
+ card: hc.card,
8800
+ isParticipating: (this.handResult?.participatingIndices || []).includes(idx)
8801
+ }));
8802
+ const boardCardsData = this.boardCards.map((bc, idx) => ({
8803
+ card: bc.card,
8804
+ isParticipating: (this.handResult?.participatingIndices || []).includes(idx + 2)
8805
+ }));
8806
+ const handResult = this.handResult ? {
8807
+ name: this.getHandName(this.handResult.text),
8808
+ emoji: this.getHandEmoji(this.handResult.text),
8809
+ participatingIndices: this.handResult.participatingIndices
8810
+ } : null;
8811
+ const renderData = {
8812
+ holeCards: holeCardsData,
8813
+ boardCards: boardCardsData,
8814
+ handResult
8815
+ };
8816
+ return this.styleFn(renderData, this.colors.poker);
8817
+ }
8818
+ getHandName(text) {
8819
+ const match = text.match(/^([^!]+)/);
8820
+ return match ? match[1].trim() : "Nothing";
8821
+ }
8822
+ getHandEmoji(text) {
8823
+ const match = text.match(/([🃏♠️♥️♦️♣️🎉✨🌟])/u);
8824
+ return match ? match[1] : "\u{1F0CF}";
8825
+ }
8826
+ };
8827
+
8828
+ // src/core/widget-factory.ts
8116
8829
  var WidgetFactory = class {
8117
8830
  transcriptProvider;
8118
8831
  constructor() {
@@ -8149,6 +8862,10 @@ var WidgetFactory = class {
8149
8862
  return new DevServerWidget(DEFAULT_THEME);
8150
8863
  case "docker":
8151
8864
  return new DockerWidget(DEFAULT_THEME);
8865
+ case "poker":
8866
+ return new PokerWidget(DEFAULT_THEME);
8867
+ case "empty-line":
8868
+ return new EmptyLineWidget();
8152
8869
  default:
8153
8870
  return null;
8154
8871
  }
@@ -8169,7 +8886,9 @@ var WidgetFactory = class {
8169
8886
  "cache-metrics",
8170
8887
  "active-tools",
8171
8888
  "dev-server",
8172
- "docker"
8889
+ "docker",
8890
+ "poker",
8891
+ "empty-line"
8173
8892
  ];
8174
8893
  }
8175
8894
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-scope",
3
- "version": "0.8.16",
3
+ "version": "0.8.18",
4
4
  "description": "Claude Code plugin for session status and analytics",
5
5
  "license": "MIT",
6
6
  "type": "module",