cc-pulse 1.3.2 → 1.5.0
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 +144 -63
- package/dist/cli.js +213 -103
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
|
|
6
6
|
A real-time statusline for [Claude Code](https://docs.anthropic.com/en/docs/claude-code).
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**Full mode** shows names, groups, and token breakdown:
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
**Compact mode** shows counts only:
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
11
17
|
|
|
12
18
|
```bash
|
|
13
19
|
npm install -g cc-pulse
|
|
@@ -24,44 +30,78 @@ Add to `~/.claude/settings.json`:
|
|
|
24
30
|
}
|
|
25
31
|
```
|
|
26
32
|
|
|
27
|
-
Restart Claude Code
|
|
33
|
+
Restart Claude Code and the statusline appears below the input area.
|
|
28
34
|
|
|
29
|
-
##
|
|
35
|
+
## Features
|
|
30
36
|
|
|
31
37
|
| Feature | Description |
|
|
32
38
|
|---------|-------------|
|
|
33
|
-
| **Context usage** |
|
|
34
|
-
| **Token breakdown** | Input
|
|
35
|
-
| **Model info** |
|
|
36
|
-
| **Cost tracking** | Session cost with
|
|
39
|
+
| **Context usage** | Percentage used, colour shifts from green to red as you approach limits |
|
|
40
|
+
| **Token breakdown** | Input, output, and cache tokens at a glance |
|
|
41
|
+
| **Model info** | Model family and version, e.g. "Opus 4.6" |
|
|
42
|
+
| **Cost tracking** | Session cost with colour coding |
|
|
37
43
|
| **MCP health** | Live connection status for all MCP servers |
|
|
38
|
-
| **Hook monitoring** | Active hooks by event type
|
|
39
|
-
| **Skills display** |
|
|
40
|
-
| **Git status** | Branch name
|
|
44
|
+
| **Hook monitoring** | Active hooks by event type with broken path detection |
|
|
45
|
+
| **Skills display** | Adapts automatically: individual names, prefix grouping, or counts only |
|
|
46
|
+
| **Git status** | Branch name and file change counts |
|
|
47
|
+
| **Responsive layout** | Width-aware wrapping and a compact mode for smaller screens |
|
|
41
48
|
|
|
42
|
-
##
|
|
43
|
-
|
|
44
|
-
Six lines of information, updated on every message:
|
|
45
|
-
|
|
46
|
-

|
|
49
|
+
## What You Get
|
|
47
50
|
|
|
48
51
|
| Line | Content |
|
|
49
52
|
|------|---------|
|
|
50
|
-
| **Identity** | Project name
|
|
51
|
-
| **Git** | Branch
|
|
52
|
-
| **Engine** |
|
|
53
|
-
| **MCP** | Server count
|
|
54
|
-
| **Hooks** | Hook count by event type
|
|
55
|
-
| **Skills** |
|
|
53
|
+
| **Identity** | Project name and working directory |
|
|
54
|
+
| **Git** | Branch and file changes (new, modified, deleted) |
|
|
55
|
+
| **Engine** | Model, context usage, tokens, cost, session duration |
|
|
56
|
+
| **MCP** | Server count and individual status |
|
|
57
|
+
| **Hooks** | Hook count by event type with broken path warnings |
|
|
58
|
+
| **Skills** | Names when few, prefix groups when many |
|
|
59
|
+
|
|
60
|
+
## Responsive Display
|
|
61
|
+
|
|
62
|
+
The statusline adapts to your setup automatically.
|
|
63
|
+
|
|
64
|
+
**Skills** adapt based on count:
|
|
65
|
+
- 10 or fewer: lists all names, e.g. `✦ Skills 5 beads excalidraw mermaid tmux repomix`
|
|
66
|
+
- More than 10 with shared prefixes: groups them, e.g. `✦ Skills 89 bmad:77 beads excalidraw ...`
|
|
67
|
+
- More than 10 without groups: caps at 10 names with overflow, e.g. `✦ Skills 15 a b c ... +5`
|
|
68
|
+
|
|
69
|
+
**Hooks** adapt based on total count:
|
|
70
|
+
- 6 or fewer: shows all names per event, e.g. `⚡Hooks 4 Submit:2 lint,format Post:2 test,deploy`
|
|
71
|
+
- More than 6: caps names to 3 per group, e.g. `⚡Hooks 12 Submit:5 lint,format,check +2`
|
|
72
|
+
|
|
73
|
+
**Width-aware wrapping** breaks long lines at component boundaries instead of cutting them off. Items wrap at 5 per line for readability.
|
|
74
|
+
|
|
75
|
+
**Compact mode** collapses everything to counts only. Toggle with `/pulse-compact` or set in config:
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"compact": true
|
|
79
|
+
}
|
|
80
|
+
```
|
|
56
81
|
|
|
57
|
-
##
|
|
82
|
+
## Configuration
|
|
58
83
|
|
|
59
|
-
Create `~/.config/claude-pulse/config.json` to
|
|
84
|
+
Create `~/.config/claude-pulse/config.json` to customise. Only include what you want to change.
|
|
85
|
+
|
|
86
|
+
<details>
|
|
87
|
+
<summary><strong>Compact Mode</strong></summary>
|
|
88
|
+
|
|
89
|
+
Minimal display with counts and essential info only. Toggle with `/pulse-compact` or set manually:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"compact": true
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Skills, hooks, and MCP show counts only. Context hides the token breakdown. Cost hides burn rate. CWD shortens.
|
|
98
|
+
|
|
99
|
+
</details>
|
|
60
100
|
|
|
61
101
|
<details>
|
|
62
102
|
<summary><strong>Context Window</strong></summary>
|
|
63
103
|
|
|
64
|
-
Shows how much of the context window is used.
|
|
104
|
+
Shows how much of the context window is used. Colours shift as usage increases.
|
|
65
105
|
|
|
66
106
|
```json
|
|
67
107
|
{
|
|
@@ -82,30 +122,7 @@ Shows how much of the context window is used. Colors shift as usage increases.
|
|
|
82
122
|
| `detailed` | `Used 116.0k/200.0k (58%)` |
|
|
83
123
|
| `both` | `●●●●●●○○○○ 116.0k / 200.0k` |
|
|
84
124
|
|
|
85
|
-
|
|
86
|
-
- **Green**: < 70% used (safe)
|
|
87
|
-
- **Yellow**: 70% used (warn)
|
|
88
|
-
- **Orange**: 85% used (critical)
|
|
89
|
-
- **Red + 🔴**: 95% used (danger)
|
|
90
|
-
|
|
91
|
-
</details>
|
|
92
|
-
|
|
93
|
-
<details>
|
|
94
|
-
<summary><strong>Subscription Tier</strong></summary>
|
|
95
|
-
|
|
96
|
-
Set your plan manually (auto-detection isn't reliable):
|
|
97
|
-
|
|
98
|
-
```json
|
|
99
|
-
{
|
|
100
|
-
"components": {
|
|
101
|
-
"tier": {
|
|
102
|
-
"override": "max"
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Options: `"pro"`, `"max"`, `"api"`
|
|
125
|
+
Colour thresholds: green below 70%, yellow at 70%, orange at 85%, red at 95%.
|
|
109
126
|
|
|
110
127
|
</details>
|
|
111
128
|
|
|
@@ -126,14 +143,14 @@ Options: `"pro"`, `"max"`, `"api"`
|
|
|
126
143
|
|
|
127
144
|
| Option | Effect |
|
|
128
145
|
|--------|--------|
|
|
129
|
-
| `showNames: true` | List each server with status |
|
|
130
|
-
| `showOnlyProblems: true` | Hide line when all servers healthy |
|
|
131
|
-
| `maxDisplay: 4` | Limit servers shown
|
|
146
|
+
| `showNames: true` | List each server with its status |
|
|
147
|
+
| `showOnlyProblems: true` | Hide the line when all servers are healthy |
|
|
148
|
+
| `maxDisplay: 4` | Limit servers shown, with "+N more" for the rest |
|
|
132
149
|
|
|
133
150
|
| Icon | Status |
|
|
134
151
|
|------|--------|
|
|
135
152
|
| ✓ | Connected |
|
|
136
|
-
| ✗ | Disconnected
|
|
153
|
+
| ✗ | Disconnected |
|
|
137
154
|
| ○ | Disabled |
|
|
138
155
|
| ▲ | Error |
|
|
139
156
|
|
|
@@ -159,6 +176,8 @@ Options: `"pro"`, `"max"`, `"api"`
|
|
|
159
176
|
| `showNames: false` | `⚡Hooks 8 Submit:3 Post:2 End:1` |
|
|
160
177
|
| Both `false` | `⚡Hooks 8` |
|
|
161
178
|
|
|
179
|
+
With more than 6 hooks, names are capped to 3 per event group with a `+N` overflow count.
|
|
180
|
+
|
|
162
181
|
Broken hooks (invalid paths) show in red with ▲.
|
|
163
182
|
|
|
164
183
|
</details>
|
|
@@ -177,7 +196,7 @@ Broken hooks (invalid paths) show in red with ▲.
|
|
|
177
196
|
}
|
|
178
197
|
```
|
|
179
198
|
|
|
180
|
-
|
|
199
|
+
Colour thresholds: green below $1, yellow $1 to $2, orange $2 to $5, red above $5.
|
|
181
200
|
|
|
182
201
|
</details>
|
|
183
202
|
|
|
@@ -198,16 +217,68 @@ Shows your custom slash commands from `~/.claude/skills/` and `.claude/skills/`.
|
|
|
198
217
|
}
|
|
199
218
|
```
|
|
200
219
|
|
|
201
|
-
|
|
202
|
-
|---------|--------|
|
|
203
|
-
| Both `true` | `✦ Skills 5 commit,pr,branch` |
|
|
204
|
-
| `showNames: false` | `✦ Skills 5` |
|
|
205
|
-
| `maxDisplay: 3` | Shows first 3 names + overflow count |
|
|
220
|
+
The display adapts automatically based on how many skills you have. See [Responsive Display](#responsive-display) above.
|
|
206
221
|
|
|
207
222
|
Broken skills (missing SKILL.md or invalid frontmatter) show in red with ▲.
|
|
208
223
|
|
|
209
224
|
</details>
|
|
210
225
|
|
|
226
|
+
<details>
|
|
227
|
+
<summary><strong>CWD (Working Directory)</strong></summary>
|
|
228
|
+
|
|
229
|
+
Control how the current directory is displayed:
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"components": {
|
|
234
|
+
"cwd": {
|
|
235
|
+
"style": "short",
|
|
236
|
+
"maxLength": 30,
|
|
237
|
+
"showIcon": true
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
| Style | Example |
|
|
244
|
+
|-------|---------|
|
|
245
|
+
| `short` (default) | `~/…/fix-1612` |
|
|
246
|
+
| `full` | `/home/user/.worktree/my-project/2026-02-13/fix-1612` |
|
|
247
|
+
| `basename` | `fix-1612` |
|
|
248
|
+
| `project` | Project folder name |
|
|
249
|
+
|
|
250
|
+
Increase `maxLength` to show more of the path, or use `basename` if you only care about the folder name.
|
|
251
|
+
|
|
252
|
+
</details>
|
|
253
|
+
|
|
254
|
+
<details>
|
|
255
|
+
<summary><strong>Dividers</strong></summary>
|
|
256
|
+
|
|
257
|
+
Add horizontal line separators between all status sections:
|
|
258
|
+
|
|
259
|
+
```json
|
|
260
|
+
{
|
|
261
|
+
"dividers": true
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Off by default.
|
|
266
|
+
|
|
267
|
+
</details>
|
|
268
|
+
|
|
269
|
+
<details>
|
|
270
|
+
<summary><strong>Section Separators</strong></summary>
|
|
271
|
+
|
|
272
|
+
Add light `---` separators after MCP, hooks, and skills sections. On by default in full mode, hidden in compact mode.
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"sectionSeparators": false
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
</details>
|
|
281
|
+
|
|
211
282
|
<details>
|
|
212
283
|
<summary><strong>Layout</strong></summary>
|
|
213
284
|
|
|
@@ -224,7 +295,7 @@ The 6-line structure is fixed. You can toggle lines and change separators:
|
|
|
224
295
|
|
|
225
296
|
| Line | Key | Toggleable |
|
|
226
297
|
|------|-----|------------|
|
|
227
|
-
| Identity |
|
|
298
|
+
| Identity | n/a | No |
|
|
228
299
|
| Git | `git` | Yes |
|
|
229
300
|
| Engine | `engine` | Yes |
|
|
230
301
|
| MCP | `mcp` | Yes |
|
|
@@ -238,7 +309,7 @@ The 6-line structure is fixed. You can toggle lines and change separators:
|
|
|
238
309
|
|
|
239
310
|
| Component | Key Options |
|
|
240
311
|
|-----------|-------------|
|
|
241
|
-
| `model` | `showIcon: true`
|
|
312
|
+
| `model` | `showIcon: true` (default), custom `icons: { opus, sonnet, haiku }` |
|
|
242
313
|
| `session` | `showDuration: true`, `showId: false` |
|
|
243
314
|
| `cache` | Shows cache hit rate |
|
|
244
315
|
| `linesChanged` | Shows `+added -removed` |
|
|
@@ -248,7 +319,17 @@ All components accept `"enabled": false` to hide them.
|
|
|
248
319
|
|
|
249
320
|
</details>
|
|
250
321
|
|
|
251
|
-
##
|
|
322
|
+
## Slash Commands
|
|
323
|
+
|
|
324
|
+
cc-pulse ships with a skill you can install to your Claude Code skills directory:
|
|
325
|
+
|
|
326
|
+
| Command | Description |
|
|
327
|
+
|---------|-------------|
|
|
328
|
+
| `/pulse-compact` | Toggle compact mode on/off |
|
|
329
|
+
|
|
330
|
+
Copy `skills/pulse-compact/` to `~/.claude/skills/` or your project's `.claude/skills/`.
|
|
331
|
+
|
|
332
|
+
## Development
|
|
252
333
|
|
|
253
334
|
```bash
|
|
254
335
|
git clone https://github.com/ali-nr/claude-pulse.git
|
|
@@ -258,7 +339,7 @@ bun run build
|
|
|
258
339
|
bun test
|
|
259
340
|
```
|
|
260
341
|
|
|
261
|
-
|
|
342
|
+
For local testing, use the full path in settings: `"command": "node /path/to/claude-pulse/dist/cli.js"`
|
|
262
343
|
|
|
263
344
|
## License
|
|
264
345
|
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
3
7
|
var __export = (target, all) => {
|
|
4
8
|
for (var name in all)
|
|
5
9
|
__defProp(target, name, {
|
|
6
10
|
get: all[name],
|
|
7
11
|
enumerable: true,
|
|
8
12
|
configurable: true,
|
|
9
|
-
set: (
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
10
14
|
});
|
|
11
15
|
};
|
|
12
16
|
// package.json
|
|
13
17
|
var package_default = {
|
|
14
18
|
name: "cc-pulse",
|
|
15
|
-
version: "1.
|
|
19
|
+
version: "1.5.0",
|
|
16
20
|
description: "A customizable, real-time statusline for Claude Code",
|
|
17
21
|
type: "module",
|
|
18
22
|
bin: {
|
|
@@ -451,15 +455,30 @@ function renderHooks(config, theme) {
|
|
|
451
455
|
const text2 = `${theme.yellow}⚡${hookLabel} ${summary.total}${theme.reset}`;
|
|
452
456
|
return { text: text2 };
|
|
453
457
|
}
|
|
454
|
-
const
|
|
458
|
+
const header = `${theme.yellow}⚡${hookLabel} ${summary.total}${theme.reset}`;
|
|
459
|
+
const MAX_NAMES_PER_GROUP = 3;
|
|
460
|
+
const compact = summary.total > 6;
|
|
461
|
+
const items = [];
|
|
462
|
+
for (const [event, detail] of Object.entries(summary.events)) {
|
|
455
463
|
const label = EVENT_LABELS[event] ?? event;
|
|
456
|
-
const goodNames = showNames && detail.names.length > 0 ? ` ${theme.flamingo}${detail.names.join(",")}${theme.reset}` : "";
|
|
457
|
-
const brokenNames = detail.broken.length > 0 ? ` ${theme.red}${detail.broken.join(",")} ▲${theme.reset}` : "";
|
|
458
464
|
const countStr = showCount ? `${theme.peach}${detail.count}${theme.reset}` : "";
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
465
|
+
const eventTag = `${theme.lavender}${label}:${theme.reset}${countStr}`;
|
|
466
|
+
if (showNames && detail.names.length > 0) {
|
|
467
|
+
const cap = compact ? MAX_NAMES_PER_GROUP : detail.names.length;
|
|
468
|
+
const displayed = detail.names.slice(0, cap);
|
|
469
|
+
const overflow = detail.names.length - cap;
|
|
470
|
+
const nameStr = `${theme.flamingo}${displayed.join(",")}${theme.reset}`;
|
|
471
|
+
const overflowStr = overflow > 0 ? ` ${theme.overlay0}+${overflow}${theme.reset}` : "";
|
|
472
|
+
items.push(`${eventTag} ${nameStr}${overflowStr}`);
|
|
473
|
+
} else {
|
|
474
|
+
items.push(eventTag);
|
|
475
|
+
}
|
|
476
|
+
if (detail.broken.length > 0) {
|
|
477
|
+
items.push(`${theme.red}${detail.broken.join(",")} ▲${theme.reset}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const text = items.length > 0 ? `${header} ${items.join(" ")}` : header;
|
|
481
|
+
return { text, header, items };
|
|
463
482
|
}
|
|
464
483
|
function getHooksSummary() {
|
|
465
484
|
const events = {};
|
|
@@ -479,9 +498,11 @@ function extractHookInfo(command) {
|
|
|
479
498
|
const parts = command.split(/\s+/);
|
|
480
499
|
for (const part of parts) {
|
|
481
500
|
if (part.includes("/")) {
|
|
482
|
-
const
|
|
501
|
+
const cleaned = part.replace(/^["']|["']$/g, "");
|
|
502
|
+
const expanded = cleaned.replace(/\$(\w+)/g, (_, v) => process.env[v] ?? `$${v}`);
|
|
503
|
+
const base = expanded.split("/").pop() ?? expanded;
|
|
483
504
|
const name2 = base.replace(/\.[^.]+$/, "");
|
|
484
|
-
const broken = !existsSync2(
|
|
505
|
+
const broken = !existsSync2(expanded);
|
|
485
506
|
return { name: name2, broken };
|
|
486
507
|
}
|
|
487
508
|
}
|
|
@@ -590,7 +611,7 @@ function renderMcp(config, theme) {
|
|
|
590
611
|
}
|
|
591
612
|
const displayServers = servers.slice(0, maxDisplay);
|
|
592
613
|
const remaining = servers.length - maxDisplay;
|
|
593
|
-
const
|
|
614
|
+
const serverItems = displayServers.map((server) => {
|
|
594
615
|
let icon;
|
|
595
616
|
let color;
|
|
596
617
|
switch (server.status) {
|
|
@@ -612,17 +633,17 @@ function renderMcp(config, theme) {
|
|
|
612
633
|
}
|
|
613
634
|
return `${color}${server.name} ${icon}${theme.reset}`;
|
|
614
635
|
});
|
|
615
|
-
let serverStr = serverParts.join(" ");
|
|
616
636
|
if (remaining > 0) {
|
|
617
|
-
|
|
637
|
+
serverItems.push(`${theme.sky}+${remaining} more${theme.reset}`);
|
|
618
638
|
}
|
|
619
639
|
const countStr = `${connectedCount}/${servers.length}`;
|
|
640
|
+
const header = `${theme.sky}${label} ${theme.green}${countStr}${theme.reset}:`;
|
|
620
641
|
if (config.showNames === false) {
|
|
621
642
|
const text2 = `${theme.sky}${label} ${theme.green}${countStr}${theme.reset}`;
|
|
622
643
|
return { text: text2, action: "/mcp" };
|
|
623
644
|
}
|
|
624
|
-
const text = `${
|
|
625
|
-
return { text, action: "/mcp" };
|
|
645
|
+
const text = `${header} ${serverItems.join(" ")}`;
|
|
646
|
+
return { text, header, items: serverItems, action: "/mcp" };
|
|
626
647
|
}
|
|
627
648
|
function getMcpServers() {
|
|
628
649
|
const state = loadState();
|
|
@@ -718,7 +739,7 @@ function parseMcpOutput(output) {
|
|
|
718
739
|
}
|
|
719
740
|
// src/components/model.ts
|
|
720
741
|
function parseModelId(modelId) {
|
|
721
|
-
const id = modelId.toLowerCase();
|
|
742
|
+
const id = modelId.toLowerCase().replace(/\[.*\]$/, "");
|
|
722
743
|
const newFormat = id.match(/^claude-(\w+)-(\d+)-(\d+)-\d+$/);
|
|
723
744
|
if (newFormat) {
|
|
724
745
|
const [, family, major, minor] = newFormat;
|
|
@@ -729,6 +750,16 @@ function parseModelId(modelId) {
|
|
|
729
750
|
const [, family, major] = newFormatNoMinor;
|
|
730
751
|
return { family, version: major };
|
|
731
752
|
}
|
|
753
|
+
const shortWithMinor = id.match(/^claude-(\w+)-(\d+)-(\d+)$/);
|
|
754
|
+
if (shortWithMinor) {
|
|
755
|
+
const [, family, major, minor] = shortWithMinor;
|
|
756
|
+
return { family, version: `${major}.${minor}` };
|
|
757
|
+
}
|
|
758
|
+
const shortNoMinor = id.match(/^claude-(\w+)-(\d+)$/);
|
|
759
|
+
if (shortNoMinor) {
|
|
760
|
+
const [, family, major] = shortNoMinor;
|
|
761
|
+
return { family, version: major };
|
|
762
|
+
}
|
|
732
763
|
const oldFormat = id.match(/^claude-(\d+)-(\d+)-(\w+)-\d+$/);
|
|
733
764
|
if (oldFormat) {
|
|
734
765
|
const [, major, minor, family] = oldFormat;
|
|
@@ -747,7 +778,7 @@ function renderModel(input, config, theme) {
|
|
|
747
778
|
}
|
|
748
779
|
const modelId = input.model?.id ?? "";
|
|
749
780
|
const displayName = input.model?.display_name ?? "";
|
|
750
|
-
const icons = config.icons ?? { opus: "
|
|
781
|
+
const icons = config.icons ?? { opus: "◆", sonnet: "◆", haiku: "◆" };
|
|
751
782
|
const showIcon = config.showIcon !== false;
|
|
752
783
|
let icon = "\uD83E\uDD16";
|
|
753
784
|
let color = theme.text;
|
|
@@ -855,22 +886,69 @@ function renderSkills(config, theme) {
|
|
|
855
886
|
}
|
|
856
887
|
const validNames = summary.skills.filter((s) => s.valid).map((s) => s.name ?? s.folder).slice(0, maxDisplay);
|
|
857
888
|
const brokenNames = summary.skills.filter((s) => !s.valid).map((s) => s.folder);
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
889
|
+
const countStr = showCount ? ` ${theme.mauve}${summary.valid}${theme.reset}` : "";
|
|
890
|
+
const header = `${theme.mauve}✦ ${label}${theme.reset}${countStr}`;
|
|
891
|
+
const items = [];
|
|
892
|
+
const GROUP_THRESHOLD = 10;
|
|
893
|
+
const MIN_PREFIX_COUNT = 3;
|
|
862
894
|
if (showNames && validNames.length > 0) {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
895
|
+
if (validNames.length > GROUP_THRESHOLD) {
|
|
896
|
+
const { groups, ungrouped } = groupByPrefix(validNames, MIN_PREFIX_COUNT);
|
|
897
|
+
const hasGroups = Object.keys(groups).length > 0;
|
|
898
|
+
if (hasGroups) {
|
|
899
|
+
for (const [prefix, names] of Object.entries(groups).sort((a, b) => b[1].length - a[1].length)) {
|
|
900
|
+
items.push(`${theme.overlay1}${prefix}:${theme.peach}${names.length}${theme.reset}`);
|
|
901
|
+
}
|
|
902
|
+
for (const name of ungrouped) {
|
|
903
|
+
items.push(`${theme.flamingo}${name}${theme.reset}`);
|
|
904
|
+
}
|
|
905
|
+
} else {
|
|
906
|
+
const CAP = 10;
|
|
907
|
+
const capped = validNames.slice(0, CAP);
|
|
908
|
+
for (const name of capped) {
|
|
909
|
+
items.push(`${theme.flamingo}${name}${theme.reset}`);
|
|
910
|
+
}
|
|
911
|
+
if (validNames.length > CAP) {
|
|
912
|
+
items.push(`${theme.overlay0}+${validNames.length - CAP}${theme.reset}`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
} else {
|
|
916
|
+
for (const name of validNames) {
|
|
917
|
+
items.push(`${theme.flamingo}${name}${theme.reset}`);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const overflow = summary.valid > maxDisplay ? summary.valid - maxDisplay : 0;
|
|
921
|
+
if (overflow > 0) {
|
|
922
|
+
items.push(`${theme.overlay0}+${overflow}${theme.reset}`);
|
|
923
|
+
}
|
|
866
924
|
}
|
|
867
|
-
let brokenStr = "";
|
|
868
925
|
if (summary.broken > 0) {
|
|
869
|
-
const
|
|
870
|
-
|
|
926
|
+
for (const name of brokenNames) {
|
|
927
|
+
items.push(`${theme.red}▲${name}${theme.reset}`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
const text = items.length > 0 ? `${header} ${items.join(" ")}` : header;
|
|
931
|
+
return { text, header, items };
|
|
932
|
+
}
|
|
933
|
+
function groupByPrefix(names, minCount) {
|
|
934
|
+
const buckets = {};
|
|
935
|
+
for (const name of names) {
|
|
936
|
+
const sep = name.includes(":") ? ":" : "-";
|
|
937
|
+
const prefix = name.split(sep)[0];
|
|
938
|
+
if (!buckets[prefix])
|
|
939
|
+
buckets[prefix] = [];
|
|
940
|
+
buckets[prefix].push(name);
|
|
941
|
+
}
|
|
942
|
+
const groups = {};
|
|
943
|
+
const ungrouped = [];
|
|
944
|
+
for (const [prefix, members] of Object.entries(buckets)) {
|
|
945
|
+
if (members.length >= minCount) {
|
|
946
|
+
groups[prefix] = members;
|
|
947
|
+
} else {
|
|
948
|
+
ungrouped.push(...members);
|
|
949
|
+
}
|
|
871
950
|
}
|
|
872
|
-
|
|
873
|
-
return { text };
|
|
951
|
+
return { groups, ungrouped };
|
|
874
952
|
}
|
|
875
953
|
function getSkillsSummary() {
|
|
876
954
|
const skills = [];
|
|
@@ -999,58 +1077,6 @@ function renderSystem(input, config, theme) {
|
|
|
999
1077
|
const text = parts.join(" │ ");
|
|
1000
1078
|
return { text };
|
|
1001
1079
|
}
|
|
1002
|
-
// src/components/tier.ts
|
|
1003
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
1004
|
-
import { homedir as homedir6 } from "node:os";
|
|
1005
|
-
import { join as join5 } from "node:path";
|
|
1006
|
-
var cachedTier = null;
|
|
1007
|
-
var cacheTime = 0;
|
|
1008
|
-
var CACHE_TTL3 = 30000;
|
|
1009
|
-
function renderTier(config, theme) {
|
|
1010
|
-
if (config.enabled === false) {
|
|
1011
|
-
return { text: "" };
|
|
1012
|
-
}
|
|
1013
|
-
const labels = config.labels ?? { pro: "PRO", max: "MAX", api: "API" };
|
|
1014
|
-
const tier = config.override ?? detectTier();
|
|
1015
|
-
let color = theme.blue;
|
|
1016
|
-
let label = labels.pro;
|
|
1017
|
-
if (tier === "max") {
|
|
1018
|
-
color = theme.mauve;
|
|
1019
|
-
label = labels.max;
|
|
1020
|
-
} else if (tier === "api") {
|
|
1021
|
-
color = theme.green;
|
|
1022
|
-
label = labels.api;
|
|
1023
|
-
}
|
|
1024
|
-
const icon = "◆ ";
|
|
1025
|
-
const text = `${color}${theme.bold}${icon}${label}${theme.reset}`;
|
|
1026
|
-
return { text, action: "/usage" };
|
|
1027
|
-
}
|
|
1028
|
-
function detectTier() {
|
|
1029
|
-
const now = Date.now();
|
|
1030
|
-
if (cachedTier && now - cacheTime < CACHE_TTL3) {
|
|
1031
|
-
return cachedTier;
|
|
1032
|
-
}
|
|
1033
|
-
const claudeJsonPath = join5(homedir6(), ".claude.json");
|
|
1034
|
-
try {
|
|
1035
|
-
if (existsSync5(claudeJsonPath)) {
|
|
1036
|
-
const content = readFileSync5(claudeJsonPath, "utf-8");
|
|
1037
|
-
const data = JSON.parse(content);
|
|
1038
|
-
if (data.oauthAccount?.hasExtraUsageEnabled) {
|
|
1039
|
-
cachedTier = "max";
|
|
1040
|
-
} else if (data.oauthAccount) {
|
|
1041
|
-
cachedTier = "pro";
|
|
1042
|
-
} else {
|
|
1043
|
-
cachedTier = "api";
|
|
1044
|
-
}
|
|
1045
|
-
} else {
|
|
1046
|
-
cachedTier = "api";
|
|
1047
|
-
}
|
|
1048
|
-
} catch {
|
|
1049
|
-
cachedTier = "pro";
|
|
1050
|
-
}
|
|
1051
|
-
cacheTime = now;
|
|
1052
|
-
return cachedTier;
|
|
1053
|
-
}
|
|
1054
1080
|
// src/components/time.ts
|
|
1055
1081
|
function renderTime(config, theme) {
|
|
1056
1082
|
if (config.enabled === false) {
|
|
@@ -1084,12 +1110,12 @@ function renderTime(config, theme) {
|
|
|
1084
1110
|
return { text };
|
|
1085
1111
|
}
|
|
1086
1112
|
// src/config.ts
|
|
1087
|
-
import { existsSync as
|
|
1088
|
-
import { homedir as
|
|
1089
|
-
import { join as
|
|
1113
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
1114
|
+
import { homedir as homedir6 } from "node:os";
|
|
1115
|
+
import { join as join5 } from "node:path";
|
|
1090
1116
|
var CONFIG_PATHS = [
|
|
1091
|
-
|
|
1092
|
-
|
|
1117
|
+
join5(homedir6(), ".config", "claude-pulse", "config.json"),
|
|
1118
|
+
join5(homedir6(), ".claude-pulse.json")
|
|
1093
1119
|
];
|
|
1094
1120
|
var FIXED_LINES = [
|
|
1095
1121
|
{ name: "identity", enabled: true, components: ["name", "cwd"], separator: " " },
|
|
@@ -1097,7 +1123,7 @@ var FIXED_LINES = [
|
|
|
1097
1123
|
{
|
|
1098
1124
|
name: "engine",
|
|
1099
1125
|
enabled: true,
|
|
1100
|
-
components: ["
|
|
1126
|
+
components: ["model", "context", "cost", "session"],
|
|
1101
1127
|
separator: " │ "
|
|
1102
1128
|
},
|
|
1103
1129
|
{ name: "mcp", enabled: true, components: ["mcp"], separator: " │ " },
|
|
@@ -1107,13 +1133,9 @@ var FIXED_LINES = [
|
|
|
1107
1133
|
var DEFAULT_CONFIG = {
|
|
1108
1134
|
theme: "catppuccin",
|
|
1109
1135
|
components: {
|
|
1110
|
-
tier: {
|
|
1111
|
-
enabled: true,
|
|
1112
|
-
labels: { pro: "PRO", max: "MAX", api: "API" }
|
|
1113
|
-
},
|
|
1114
1136
|
model: {
|
|
1115
1137
|
enabled: true,
|
|
1116
|
-
showIcon:
|
|
1138
|
+
showIcon: true
|
|
1117
1139
|
},
|
|
1118
1140
|
context: {
|
|
1119
1141
|
enabled: true,
|
|
@@ -1182,9 +1204,9 @@ var DEFAULT_CONFIG = {
|
|
|
1182
1204
|
};
|
|
1183
1205
|
function loadConfig() {
|
|
1184
1206
|
for (const configPath of CONFIG_PATHS) {
|
|
1185
|
-
if (
|
|
1207
|
+
if (existsSync5(configPath)) {
|
|
1186
1208
|
try {
|
|
1187
|
-
const content =
|
|
1209
|
+
const content = readFileSync5(configPath, "utf-8");
|
|
1188
1210
|
const userConfig = JSON.parse(content);
|
|
1189
1211
|
return mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
1190
1212
|
} catch {}
|
|
@@ -1211,6 +1233,12 @@ function mergeConfig(target, source) {
|
|
|
1211
1233
|
const result = { ...target };
|
|
1212
1234
|
if (source.theme !== undefined)
|
|
1213
1235
|
result.theme = source.theme;
|
|
1236
|
+
if (source.compact !== undefined)
|
|
1237
|
+
result.compact = source.compact;
|
|
1238
|
+
if (source.dividers !== undefined)
|
|
1239
|
+
result.dividers = source.dividers;
|
|
1240
|
+
if (source.sectionSeparators !== undefined)
|
|
1241
|
+
result.sectionSeparators = source.sectionSeparators;
|
|
1214
1242
|
if (source.lines !== undefined)
|
|
1215
1243
|
result.lines = source.lines;
|
|
1216
1244
|
if (source.interactive !== undefined)
|
|
@@ -14805,7 +14833,9 @@ var McpServerSchema = exports_external.object({
|
|
|
14805
14833
|
});
|
|
14806
14834
|
var ComponentOutputSchema = exports_external.object({
|
|
14807
14835
|
text: exports_external.string(),
|
|
14808
|
-
action: exports_external.string().optional()
|
|
14836
|
+
action: exports_external.string().optional(),
|
|
14837
|
+
header: exports_external.string().optional(),
|
|
14838
|
+
items: exports_external.array(exports_external.string()).optional()
|
|
14809
14839
|
});
|
|
14810
14840
|
var TierConfigSchema = exports_external.object({
|
|
14811
14841
|
enabled: exports_external.boolean().optional(),
|
|
@@ -14952,6 +14982,9 @@ var LinesConfigSchema = exports_external.object({
|
|
|
14952
14982
|
});
|
|
14953
14983
|
var PulseConfigSchema = exports_external.object({
|
|
14954
14984
|
theme: exports_external.string(),
|
|
14985
|
+
compact: exports_external.boolean().optional(),
|
|
14986
|
+
dividers: exports_external.boolean().optional(),
|
|
14987
|
+
sectionSeparators: exports_external.boolean().optional(),
|
|
14955
14988
|
lines: LinesConfigSchema.optional(),
|
|
14956
14989
|
components: ComponentConfigsSchema,
|
|
14957
14990
|
interactive: exports_external.object({
|
|
@@ -15004,6 +15037,43 @@ var catppuccin = {
|
|
|
15004
15037
|
dim: "\x1B[2m"
|
|
15005
15038
|
};
|
|
15006
15039
|
|
|
15040
|
+
// src/truncate.ts
|
|
15041
|
+
var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
15042
|
+
function visibleLength(str) {
|
|
15043
|
+
return str.replace(ANSI_RE, "").length;
|
|
15044
|
+
}
|
|
15045
|
+
function wrapParts(parts, separator, maxWidth, indent = 2, maxPerLine = 0) {
|
|
15046
|
+
if (parts.length === 0)
|
|
15047
|
+
return "";
|
|
15048
|
+
const sepWidth = visibleLength(separator);
|
|
15049
|
+
const pad = " ".repeat(indent);
|
|
15050
|
+
const lines = [];
|
|
15051
|
+
let currentLine = parts[0];
|
|
15052
|
+
let currentWidth = visibleLength(parts[0]);
|
|
15053
|
+
let partsOnLine = 1;
|
|
15054
|
+
for (let i = 1;i < parts.length; i++) {
|
|
15055
|
+
const partWidth = visibleLength(parts[i]);
|
|
15056
|
+
const wouldBe = currentWidth + sepWidth + partWidth;
|
|
15057
|
+
const hitMax = maxPerLine > 0 && partsOnLine >= maxPerLine;
|
|
15058
|
+
if (wouldBe > maxWidth || hitMax) {
|
|
15059
|
+
lines.push(currentLine);
|
|
15060
|
+
currentLine = pad + parts[i];
|
|
15061
|
+
currentWidth = indent + partWidth;
|
|
15062
|
+
partsOnLine = 1;
|
|
15063
|
+
} else {
|
|
15064
|
+
currentLine += separator + parts[i];
|
|
15065
|
+
currentWidth = wouldBe;
|
|
15066
|
+
partsOnLine++;
|
|
15067
|
+
}
|
|
15068
|
+
}
|
|
15069
|
+
lines.push(currentLine);
|
|
15070
|
+
return lines.join(`
|
|
15071
|
+
`);
|
|
15072
|
+
}
|
|
15073
|
+
function getTerminalWidth() {
|
|
15074
|
+
return process.stdout.columns || Number(process.env.COLUMNS) || 80;
|
|
15075
|
+
}
|
|
15076
|
+
|
|
15007
15077
|
// src/cli.ts
|
|
15008
15078
|
var VERSION = package_default.version ?? "1.0.0";
|
|
15009
15079
|
async function main() {
|
|
@@ -15042,30 +15112,70 @@ Configuration:
|
|
|
15042
15112
|
const config2 = loadConfig();
|
|
15043
15113
|
const theme = catppuccin;
|
|
15044
15114
|
const lines = getLines(config2);
|
|
15115
|
+
const termWidth = getTerminalWidth();
|
|
15116
|
+
if (config2.compact) {
|
|
15117
|
+
if (config2.components.skills) {
|
|
15118
|
+
config2.components.skills.showNames = false;
|
|
15119
|
+
}
|
|
15120
|
+
if (config2.components.hooks) {
|
|
15121
|
+
config2.components.hooks.showNames = false;
|
|
15122
|
+
config2.components.hooks.showCount = false;
|
|
15123
|
+
}
|
|
15124
|
+
if (config2.components.mcp) {
|
|
15125
|
+
config2.components.mcp.showNames = false;
|
|
15126
|
+
}
|
|
15127
|
+
if (config2.components.context) {
|
|
15128
|
+
config2.components.context.showTokens = false;
|
|
15129
|
+
config2.components.context.showRate = false;
|
|
15130
|
+
}
|
|
15131
|
+
if (config2.components.cost) {
|
|
15132
|
+
config2.components.cost.showBurnRate = false;
|
|
15133
|
+
config2.components.cost.showProjection = false;
|
|
15134
|
+
}
|
|
15135
|
+
if (config2.components.cwd) {
|
|
15136
|
+
config2.components.cwd.maxLength = 15;
|
|
15137
|
+
}
|
|
15138
|
+
}
|
|
15045
15139
|
const outputLines = [];
|
|
15046
15140
|
for (const line of lines) {
|
|
15047
15141
|
if (!line.enabled)
|
|
15048
15142
|
continue;
|
|
15049
|
-
const parts = [];
|
|
15050
15143
|
const separator = ` ${theme.overlay2}│${theme.reset} `;
|
|
15051
15144
|
const sep = line.separator ?? separator;
|
|
15145
|
+
const outputs = [];
|
|
15052
15146
|
for (const componentName of line.components) {
|
|
15053
15147
|
const output = renderComponent(componentName, input, config2, theme);
|
|
15054
15148
|
if (output.text) {
|
|
15055
|
-
|
|
15149
|
+
outputs.push(output);
|
|
15056
15150
|
}
|
|
15057
15151
|
}
|
|
15058
|
-
if (
|
|
15059
|
-
|
|
15152
|
+
if (outputs.length === 0)
|
|
15153
|
+
continue;
|
|
15154
|
+
if (outputs.length === 1 && outputs[0].header && outputs[0].items?.length) {
|
|
15155
|
+
const { header, items } = outputs[0];
|
|
15156
|
+
const rendered = wrapParts([header, ...items], " ", termWidth, 2, 4);
|
|
15157
|
+
outputLines.push(rendered);
|
|
15158
|
+
} else {
|
|
15159
|
+
const parts = outputs.map((o) => o.text);
|
|
15160
|
+
outputLines.push(wrapParts(parts, sep, termWidth));
|
|
15161
|
+
}
|
|
15162
|
+
if (!config2.compact && config2.sectionSeparators !== false && (line.name === "mcp" || line.name === "hooks" || line.name === "skills")) {
|
|
15163
|
+
outputLines.push(`${theme.overlay0}---${theme.reset}`);
|
|
15060
15164
|
}
|
|
15061
15165
|
}
|
|
15062
|
-
|
|
15166
|
+
if (config2.dividers) {
|
|
15167
|
+
const divider = `${theme.overlay0}${"─".repeat(termWidth)}${theme.reset}`;
|
|
15168
|
+
console.log(`${outputLines.join(`
|
|
15169
|
+
${divider}
|
|
15170
|
+
`)}
|
|
15171
|
+
${divider}`);
|
|
15172
|
+
} else {
|
|
15173
|
+
console.log(outputLines.join(`
|
|
15063
15174
|
`));
|
|
15175
|
+
}
|
|
15064
15176
|
}
|
|
15065
15177
|
function renderComponent(name, input, config2, theme) {
|
|
15066
15178
|
switch (name) {
|
|
15067
|
-
case "tier":
|
|
15068
|
-
return renderTier(config2.components.tier ?? {}, theme);
|
|
15069
15179
|
case "model":
|
|
15070
15180
|
return renderModel(input, config2.components.model ?? {}, theme);
|
|
15071
15181
|
case "context":
|