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 +223 -82
- package/dist/claude-scope.cjs +755 -30
- 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,
|
|
@@ -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 =
|
|
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.
|
|
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(
|
|
1536
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
1595
|
+
for (const widget of this.widgets) {
|
|
1577
1596
|
if (widget.cleanup) {
|
|
1578
1597
|
await widget.cleanup();
|
|
1579
1598
|
}
|
|
1580
1599
|
}
|
|
1581
|
-
this.widgets
|
|
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,
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
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 [
|
|
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,
|
|
9121
|
+
applyWidgetConfig(widget, {
|
|
9122
|
+
...widgetConfigItem,
|
|
9123
|
+
line: parseInt(lineNum, 10)
|
|
9124
|
+
});
|
|
8400
9125
|
await registry.register(widget);
|
|
8401
9126
|
}
|
|
8402
9127
|
}
|