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 +223 -82
- package/dist/claude-scope.cjs +722 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,37 +1,77 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/typescript/typescript-original.svg" alt="TypeScript" width="80" height="80"/>
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- Node.js 18 or higher
|
|
5
|
+
# claude-scope
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
### Beautiful, customizable statusline for Claude Code CLI
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/claude-scope)
|
|
10
|
+
[](LICENSE.md)
|
|
11
|
+
[](https://github.com/YuriNachos/claude-scope)
|
|
12
|
+
[](https://github.com/YuriNachos/claude-scope)
|
|
13
|
+
[](https://codecov.io/gh/YuriNachos/claude-scope)
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
**Features** · [Installation](#-quick-start) · [Configuration](#-ai-powered-customization) · [Widgets](#-available-widgets) · [Themes](#-themes)
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
npx claude-scope@latest
|
|
16
|
-
```
|
|
17
|
+
</div>
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
---
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
64
|
+
---
|
|
31
65
|
|
|
32
|
-
|
|
66
|
+
## Quick Start
|
|
33
67
|
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
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
|
-
|
|
207
|
+
See [AI-CONFIG-GUIDE](AI-CONFIG-GUIDE.md) for complete configuration reference.
|
|
57
208
|
|
|
58
|
-
|
|
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
|
-
|
|
211
|
+
## Advanced Features
|
|
69
212
|
|
|
70
|
-
|
|
213
|
+
<details>
|
|
214
|
+
<summary><b>Zero Runtime Dependencies</b></summary>
|
|
71
215
|
|
|
72
|
-
|
|
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
|
-
|
|
219
|
+
<details>
|
|
220
|
+
<summary><b>Layout & Line System</b></summary>
|
|
86
221
|
|
|
87
|
-
|
|
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
|
-
|
|
229
|
+
<details>
|
|
230
|
+
<summary><b>Widget System</b></summary>
|
|
90
231
|
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
241
|
+
---
|
|
101
242
|
|
|
102
|
-
|
|
243
|
+
## Documentation
|
|
103
244
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
253
|
+
---
|
|
109
254
|
|
|
110
|
-
|
|
255
|
+
## Requirements
|
|
111
256
|
|
|
112
|
-
|
|
257
|
+
- **Node.js** 18 or higher
|
|
258
|
+
- **Claude Code** CLI
|
|
113
259
|
|
|
114
|
-
|
|
260
|
+
---
|
|
115
261
|
|
|
116
|
-
|
|
117
|
-
2. Commit changes
|
|
118
|
-
3. Create and push tag:
|
|
262
|
+
## License
|
|
119
263
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
274
|
+
</div>
|
package/dist/claude-scope.cjs
CHANGED
|
@@ -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
|
};
|