cc-pulse 1.3.1 → 1.4.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 +84 -41
- package/dist/cli.js +200 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ A real-time statusline for [Claude Code](https://docs.anthropic.com/en/docs/clau
|
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Quick Start
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
npm install -g cc-pulse
|
|
@@ -24,40 +24,79 @@ Add to `~/.claude/settings.json`:
|
|
|
24
24
|
}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Restart Claude Code — the statusline appears
|
|
27
|
+
Restart Claude Code — the statusline appears below the input area.
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Features
|
|
30
30
|
|
|
31
31
|
| Feature | Description |
|
|
32
32
|
|---------|-------------|
|
|
33
33
|
| **Context usage** | Shows % used with color-coded bar — green to red as you approach limits |
|
|
34
|
-
| **Token breakdown** | Input
|
|
35
|
-
| **Model info** | Shows model with version (e.g., "Opus 4.
|
|
34
|
+
| **Token breakdown** | Input, output, and cache tokens at a glance |
|
|
35
|
+
| **Model info** | Shows model with version (e.g., "◆ Opus 4.6", "◆ Sonnet 4") |
|
|
36
36
|
| **Cost tracking** | Session cost with color coding ($1 yellow, $2 orange, $5+ red) |
|
|
37
37
|
| **MCP health** | Live connection status for all MCP servers |
|
|
38
38
|
| **Hook monitoring** | Active hooks by event type, with broken path detection |
|
|
39
|
-
| **Skills display** |
|
|
39
|
+
| **Skills display** | Adaptive display — individual names, prefix grouping, or compact counts |
|
|
40
40
|
| **Git status** | Branch name + new/modified/deleted file counts |
|
|
41
|
+
| **Responsive layout** | Width-aware wrapping + compact mode for smaller screens |
|
|
41
42
|
|
|
42
|
-
##
|
|
43
|
+
## What You Get
|
|
43
44
|
|
|
44
45
|
Six lines of information, updated on every message:
|
|
45
46
|
|
|
46
|
-

|
|
47
|
-
|
|
48
47
|
| Line | Content |
|
|
49
48
|
|------|---------|
|
|
50
49
|
| **Identity** | Project name + working directory |
|
|
51
50
|
| **Git** | Branch + file changes (new, modified, deleted) |
|
|
52
|
-
| **Engine** |
|
|
51
|
+
| **Engine** | Model, context used, tokens, cost, duration |
|
|
53
52
|
| **MCP** | Server count + individual status (✓ connected, ✗ disconnected, ○ disabled) |
|
|
54
53
|
| **Hooks** | Hook count by event type, with broken path warnings |
|
|
55
|
-
| **Skills** |
|
|
54
|
+
| **Skills** | Adaptive: names when few, prefix groups when many |
|
|
55
|
+
|
|
56
|
+
## Responsive Display
|
|
57
|
+
|
|
58
|
+
The statusline adapts to your setup automatically:
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
**Skills** — adapts based on count:
|
|
61
|
+
- **10 or fewer**: lists all names — `✦ Skills 5 beads,excalidraw,mermaid,tmux,repomix`
|
|
62
|
+
- **More than 10 with shared prefixes**: groups them — `✦ Skills 89 bmad:77 beads,excalidraw,mermaid,...`
|
|
63
|
+
- **More than 10, no groups**: caps at 10 names with overflow — `✦ Skills 15 a,b,c,d,e,f,g,h,i,j +5`
|
|
64
|
+
|
|
65
|
+
**Hooks** — adapts based on total count:
|
|
66
|
+
- **6 or fewer**: shows all names per event — `⚡Hooks 4 Submit:2 lint,format Post:2 test,deploy`
|
|
67
|
+
- **More than 6**: caps names to 3 per group — `⚡Hooks 12 Submit:5 lint,format,check +2`
|
|
68
|
+
|
|
69
|
+
**Width-aware wrapping** — when a line exceeds terminal width, components wrap onto indented continuation lines instead of being cut off.
|
|
70
|
+
|
|
71
|
+
**Compact mode** — toggle with `/pulse-compact` or set in config:
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"compact": true
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
In compact mode, everything collapses to counts only:
|
|
79
|
+
- `⬢ MCP 3/4` | `⚡Hooks 8` | `✦ Skills 89` | `Used 30%` | `$2.50`
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
58
82
|
|
|
59
83
|
Create `~/.config/claude-pulse/config.json` to customize. Only include what you want to change.
|
|
60
84
|
|
|
85
|
+
<details>
|
|
86
|
+
<summary><strong>Compact Mode</strong></summary>
|
|
87
|
+
|
|
88
|
+
Minimal display showing only counts and essential info. Toggle with the `/pulse-compact` slash command, or set manually:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"compact": true
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
When enabled: skills, hooks, and MCP show counts only; context hides token breakdown; cost hides burn rate; CWD shortens.
|
|
97
|
+
|
|
98
|
+
</details>
|
|
99
|
+
|
|
61
100
|
<details>
|
|
62
101
|
<summary><strong>Context Window</strong></summary>
|
|
63
102
|
|
|
@@ -77,8 +116,8 @@ Shows how much of the context window is used. Colors shift as usage increases.
|
|
|
77
116
|
|
|
78
117
|
| Style | Example |
|
|
79
118
|
|-------|---------|
|
|
80
|
-
| `bar`
|
|
81
|
-
| `percent` | `Used 58%` |
|
|
119
|
+
| `bar` | `Used ●●●●●●○○○○ 58%` |
|
|
120
|
+
| `percent` (default) | `Used 58%` |
|
|
82
121
|
| `detailed` | `Used 116.0k/200.0k (58%)` |
|
|
83
122
|
| `both` | `●●●●●●○○○○ 116.0k / 200.0k` |
|
|
84
123
|
|
|
@@ -86,26 +125,7 @@ Shows how much of the context window is used. Colors shift as usage increases.
|
|
|
86
125
|
- **Green**: < 70% used (safe)
|
|
87
126
|
- **Yellow**: 70% used (warn)
|
|
88
127
|
- **Orange**: 85% used (critical)
|
|
89
|
-
- **Red
|
|
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"`
|
|
128
|
+
- **Red**: 95% used (danger)
|
|
109
129
|
|
|
110
130
|
</details>
|
|
111
131
|
|
|
@@ -159,6 +179,8 @@ Options: `"pro"`, `"max"`, `"api"`
|
|
|
159
179
|
| `showNames: false` | `⚡Hooks 8 Submit:3 Post:2 End:1` |
|
|
160
180
|
| Both `false` | `⚡Hooks 8` |
|
|
161
181
|
|
|
182
|
+
When you have many hooks (>6), names are automatically capped to 3 per event group with a `+N` overflow indicator.
|
|
183
|
+
|
|
162
184
|
Broken hooks (invalid paths) show in red with ▲.
|
|
163
185
|
|
|
164
186
|
</details>
|
|
@@ -198,16 +220,27 @@ Shows your custom slash commands from `~/.claude/skills/` and `.claude/skills/`.
|
|
|
198
220
|
}
|
|
199
221
|
```
|
|
200
222
|
|
|
201
|
-
|
|
202
|
-
|---------|--------|
|
|
203
|
-
| Both `true` | `✦ Skills 5 commit,pr,branch` |
|
|
204
|
-
| `showNames: false` | `✦ Skills 5` |
|
|
205
|
-
| `maxDisplay: 3` | Shows first 3 names + overflow count |
|
|
223
|
+
The display adapts automatically based on how many skills you have — see [Responsive Display](#responsive-display) above.
|
|
206
224
|
|
|
207
225
|
Broken skills (missing SKILL.md or invalid frontmatter) show in red with ▲.
|
|
208
226
|
|
|
209
227
|
</details>
|
|
210
228
|
|
|
229
|
+
<details>
|
|
230
|
+
<summary><strong>Dividers</strong></summary>
|
|
231
|
+
|
|
232
|
+
Add horizontal line separators between status sections:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"dividers": true
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Off by default.
|
|
241
|
+
|
|
242
|
+
</details>
|
|
243
|
+
|
|
211
244
|
<details>
|
|
212
245
|
<summary><strong>Layout</strong></summary>
|
|
213
246
|
|
|
@@ -238,7 +271,7 @@ The 6-line structure is fixed. You can toggle lines and change separators:
|
|
|
238
271
|
|
|
239
272
|
| Component | Key Options |
|
|
240
273
|
|-----------|-------------|
|
|
241
|
-
| `model` | `showIcon: true`
|
|
274
|
+
| `model` | `showIcon: true` (default), custom `icons: { opus, sonnet, haiku }` |
|
|
242
275
|
| `session` | `showDuration: true`, `showId: false` |
|
|
243
276
|
| `cache` | Shows cache hit rate |
|
|
244
277
|
| `linesChanged` | Shows `+added -removed` |
|
|
@@ -248,7 +281,17 @@ All components accept `"enabled": false` to hide them.
|
|
|
248
281
|
|
|
249
282
|
</details>
|
|
250
283
|
|
|
251
|
-
##
|
|
284
|
+
## Slash Commands
|
|
285
|
+
|
|
286
|
+
cc-pulse ships with a skill you can install to your Claude Code skills directory:
|
|
287
|
+
|
|
288
|
+
| Command | Description |
|
|
289
|
+
|---------|-------------|
|
|
290
|
+
| `/pulse-compact` | Toggle compact mode on/off |
|
|
291
|
+
|
|
292
|
+
To install, copy `skills/pulse-compact/` to `~/.claude/skills/` or your project's `.claude/skills/`.
|
|
293
|
+
|
|
294
|
+
## Development
|
|
252
295
|
|
|
253
296
|
```bash
|
|
254
297
|
git clone https://github.com/ali-nr/claude-pulse.git
|
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.4.0",
|
|
16
20
|
description: "A customizable, real-time statusline for Claude Code",
|
|
17
21
|
type: "module",
|
|
18
22
|
bin: {
|
|
@@ -383,16 +387,18 @@ function getGitInfo() {
|
|
|
383
387
|
let status = "";
|
|
384
388
|
const statusLines = { added: 0, modified: 0, deleted: 0 };
|
|
385
389
|
try {
|
|
386
|
-
branch = execSync("git branch --show-current
|
|
390
|
+
branch = execSync("git branch --show-current", {
|
|
387
391
|
encoding: "utf-8",
|
|
388
|
-
timeout: 1000
|
|
392
|
+
timeout: 1000,
|
|
393
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
389
394
|
}).trim();
|
|
390
395
|
} catch {}
|
|
391
396
|
if (branch) {
|
|
392
397
|
try {
|
|
393
|
-
const porcelain = execSync("git status --porcelain
|
|
398
|
+
const porcelain = execSync("git status --porcelain", {
|
|
394
399
|
encoding: "utf-8",
|
|
395
|
-
timeout: 1000
|
|
400
|
+
timeout: 1000,
|
|
401
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
396
402
|
});
|
|
397
403
|
const lines = porcelain.trim().split(`
|
|
398
404
|
`).filter(Boolean);
|
|
@@ -449,15 +455,27 @@ function renderHooks(config, theme) {
|
|
|
449
455
|
const text2 = `${theme.yellow}⚡${hookLabel} ${summary.total}${theme.reset}`;
|
|
450
456
|
return { text: text2 };
|
|
451
457
|
}
|
|
452
|
-
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 = Object.entries(summary.events).map(([event, detail]) => {
|
|
453
462
|
const label = EVENT_LABELS[event] ?? event;
|
|
454
|
-
const
|
|
455
|
-
const brokenNames = detail.broken.length > 0 ? ` ${theme.red}${detail.broken.join(",")} ▲${theme.reset}` : "";
|
|
463
|
+
const brokenStr = detail.broken.length > 0 ? ` ${theme.red}${detail.broken.join(",")} ▲${theme.reset}` : "";
|
|
456
464
|
const countStr = showCount ? `${theme.peach}${detail.count}${theme.reset}` : "";
|
|
457
|
-
|
|
465
|
+
let goodNames = "";
|
|
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
|
+
goodNames = ` ${theme.flamingo}${displayed.join(",")}${theme.reset}`;
|
|
471
|
+
if (overflow > 0) {
|
|
472
|
+
goodNames += ` ${theme.overlay0}+${overflow}${theme.reset}`;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return `${theme.lavender}${label}:${theme.reset}${countStr}${goodNames}${brokenStr}`;
|
|
458
476
|
});
|
|
459
|
-
const text =
|
|
460
|
-
return { text };
|
|
477
|
+
const text = items.length > 0 ? `${header} ${items.join(" ")}` : header;
|
|
478
|
+
return { text, header, items };
|
|
461
479
|
}
|
|
462
480
|
function getHooksSummary() {
|
|
463
481
|
const events = {};
|
|
@@ -588,7 +606,7 @@ function renderMcp(config, theme) {
|
|
|
588
606
|
}
|
|
589
607
|
const displayServers = servers.slice(0, maxDisplay);
|
|
590
608
|
const remaining = servers.length - maxDisplay;
|
|
591
|
-
const
|
|
609
|
+
const serverItems = displayServers.map((server) => {
|
|
592
610
|
let icon;
|
|
593
611
|
let color;
|
|
594
612
|
switch (server.status) {
|
|
@@ -610,17 +628,17 @@ function renderMcp(config, theme) {
|
|
|
610
628
|
}
|
|
611
629
|
return `${color}${server.name} ${icon}${theme.reset}`;
|
|
612
630
|
});
|
|
613
|
-
let serverStr = serverParts.join(" ");
|
|
614
631
|
if (remaining > 0) {
|
|
615
|
-
|
|
632
|
+
serverItems.push(`${theme.sky}+${remaining} more${theme.reset}`);
|
|
616
633
|
}
|
|
617
634
|
const countStr = `${connectedCount}/${servers.length}`;
|
|
635
|
+
const header = `${theme.sky}${label} ${theme.green}${countStr}${theme.reset}:`;
|
|
618
636
|
if (config.showNames === false) {
|
|
619
637
|
const text2 = `${theme.sky}${label} ${theme.green}${countStr}${theme.reset}`;
|
|
620
638
|
return { text: text2, action: "/mcp" };
|
|
621
639
|
}
|
|
622
|
-
const text = `${
|
|
623
|
-
return { text, action: "/mcp" };
|
|
640
|
+
const text = `${header} ${serverItems.join(" ")}`;
|
|
641
|
+
return { text, header, items: serverItems, action: "/mcp" };
|
|
624
642
|
}
|
|
625
643
|
function getMcpServers() {
|
|
626
644
|
const state = loadState();
|
|
@@ -631,9 +649,10 @@ function getMcpServers() {
|
|
|
631
649
|
servers = state.mcp.servers.map((s) => ({ ...s }));
|
|
632
650
|
} else {
|
|
633
651
|
try {
|
|
634
|
-
const output = execSync2("claude mcp list
|
|
652
|
+
const output = execSync2("claude mcp list", {
|
|
635
653
|
encoding: "utf-8",
|
|
636
|
-
timeout: 3000
|
|
654
|
+
timeout: 3000,
|
|
655
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
637
656
|
});
|
|
638
657
|
servers = parseMcpOutput(output);
|
|
639
658
|
saveState({
|
|
@@ -715,7 +734,7 @@ function parseMcpOutput(output) {
|
|
|
715
734
|
}
|
|
716
735
|
// src/components/model.ts
|
|
717
736
|
function parseModelId(modelId) {
|
|
718
|
-
const id = modelId.toLowerCase();
|
|
737
|
+
const id = modelId.toLowerCase().replace(/\[.*\]$/, "");
|
|
719
738
|
const newFormat = id.match(/^claude-(\w+)-(\d+)-(\d+)-\d+$/);
|
|
720
739
|
if (newFormat) {
|
|
721
740
|
const [, family, major, minor] = newFormat;
|
|
@@ -726,6 +745,16 @@ function parseModelId(modelId) {
|
|
|
726
745
|
const [, family, major] = newFormatNoMinor;
|
|
727
746
|
return { family, version: major };
|
|
728
747
|
}
|
|
748
|
+
const shortWithMinor = id.match(/^claude-(\w+)-(\d+)-(\d+)$/);
|
|
749
|
+
if (shortWithMinor) {
|
|
750
|
+
const [, family, major, minor] = shortWithMinor;
|
|
751
|
+
return { family, version: `${major}.${minor}` };
|
|
752
|
+
}
|
|
753
|
+
const shortNoMinor = id.match(/^claude-(\w+)-(\d+)$/);
|
|
754
|
+
if (shortNoMinor) {
|
|
755
|
+
const [, family, major] = shortNoMinor;
|
|
756
|
+
return { family, version: major };
|
|
757
|
+
}
|
|
729
758
|
const oldFormat = id.match(/^claude-(\d+)-(\d+)-(\w+)-\d+$/);
|
|
730
759
|
if (oldFormat) {
|
|
731
760
|
const [, major, minor, family] = oldFormat;
|
|
@@ -744,7 +773,7 @@ function renderModel(input, config, theme) {
|
|
|
744
773
|
}
|
|
745
774
|
const modelId = input.model?.id ?? "";
|
|
746
775
|
const displayName = input.model?.display_name ?? "";
|
|
747
|
-
const icons = config.icons ?? { opus: "
|
|
776
|
+
const icons = config.icons ?? { opus: "◆", sonnet: "◆", haiku: "◆" };
|
|
748
777
|
const showIcon = config.showIcon !== false;
|
|
749
778
|
let icon = "\uD83E\uDD16";
|
|
750
779
|
let color = theme.text;
|
|
@@ -852,22 +881,65 @@ function renderSkills(config, theme) {
|
|
|
852
881
|
}
|
|
853
882
|
const validNames = summary.skills.filter((s) => s.valid).map((s) => s.name ?? s.folder).slice(0, maxDisplay);
|
|
854
883
|
const brokenNames = summary.skills.filter((s) => !s.valid).map((s) => s.folder);
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
884
|
+
const countStr = showCount ? ` ${theme.mauve}${summary.valid}${theme.reset}` : "";
|
|
885
|
+
const header = `${theme.mauve}✦ ${label}${theme.reset}${countStr}`;
|
|
886
|
+
const items = [];
|
|
887
|
+
const GROUP_THRESHOLD = 10;
|
|
888
|
+
const MIN_PREFIX_COUNT = 3;
|
|
859
889
|
if (showNames && validNames.length > 0) {
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
890
|
+
if (validNames.length > GROUP_THRESHOLD) {
|
|
891
|
+
const { groups, ungrouped } = groupByPrefix(validNames, MIN_PREFIX_COUNT);
|
|
892
|
+
const hasGroups = Object.keys(groups).length > 0;
|
|
893
|
+
if (hasGroups) {
|
|
894
|
+
for (const [prefix, names] of Object.entries(groups).sort((a, b) => b[1].length - a[1].length)) {
|
|
895
|
+
items.push(`${theme.overlay1}${prefix}:${theme.peach}${names.length}${theme.reset}`);
|
|
896
|
+
}
|
|
897
|
+
if (ungrouped.length > 0) {
|
|
898
|
+
items.push(`${theme.flamingo}${ungrouped.join(",")}${theme.reset}`);
|
|
899
|
+
}
|
|
900
|
+
} else {
|
|
901
|
+
const CAP = 10;
|
|
902
|
+
const capped = validNames.slice(0, CAP);
|
|
903
|
+
items.push(`${theme.flamingo}${capped.join(",")}${theme.reset}`);
|
|
904
|
+
if (validNames.length > CAP) {
|
|
905
|
+
items.push(`${theme.overlay0}+${validNames.length - CAP}${theme.reset}`);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
} else {
|
|
909
|
+
items.push(`${theme.flamingo}${validNames.join(",")}${theme.reset}`);
|
|
910
|
+
}
|
|
911
|
+
const overflow = summary.valid > maxDisplay ? summary.valid - maxDisplay : 0;
|
|
912
|
+
if (overflow > 0) {
|
|
913
|
+
items.push(`${theme.overlay0}+${overflow}${theme.reset}`);
|
|
914
|
+
}
|
|
863
915
|
}
|
|
864
|
-
let brokenStr = "";
|
|
865
916
|
if (summary.broken > 0) {
|
|
866
|
-
const
|
|
867
|
-
|
|
917
|
+
for (const name of brokenNames) {
|
|
918
|
+
items.push(`${theme.red}▲${name}${theme.reset}`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
const text = items.length > 0 ? `${header} ${items.join(" ")}` : header;
|
|
922
|
+
return { text, header, items };
|
|
923
|
+
}
|
|
924
|
+
function groupByPrefix(names, minCount) {
|
|
925
|
+
const buckets = {};
|
|
926
|
+
for (const name of names) {
|
|
927
|
+
const sep = name.includes(":") ? ":" : "-";
|
|
928
|
+
const prefix = name.split(sep)[0];
|
|
929
|
+
if (!buckets[prefix])
|
|
930
|
+
buckets[prefix] = [];
|
|
931
|
+
buckets[prefix].push(name);
|
|
932
|
+
}
|
|
933
|
+
const groups = {};
|
|
934
|
+
const ungrouped = [];
|
|
935
|
+
for (const [prefix, members] of Object.entries(buckets)) {
|
|
936
|
+
if (members.length >= minCount) {
|
|
937
|
+
groups[prefix] = members;
|
|
938
|
+
} else {
|
|
939
|
+
ungrouped.push(...members);
|
|
940
|
+
}
|
|
868
941
|
}
|
|
869
|
-
|
|
870
|
-
return { text };
|
|
942
|
+
return { groups, ungrouped };
|
|
871
943
|
}
|
|
872
944
|
function getSkillsSummary() {
|
|
873
945
|
const skills = [];
|
|
@@ -996,58 +1068,6 @@ function renderSystem(input, config, theme) {
|
|
|
996
1068
|
const text = parts.join(" │ ");
|
|
997
1069
|
return { text };
|
|
998
1070
|
}
|
|
999
|
-
// src/components/tier.ts
|
|
1000
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
1001
|
-
import { homedir as homedir6 } from "node:os";
|
|
1002
|
-
import { join as join5 } from "node:path";
|
|
1003
|
-
var cachedTier = null;
|
|
1004
|
-
var cacheTime = 0;
|
|
1005
|
-
var CACHE_TTL3 = 30000;
|
|
1006
|
-
function renderTier(config, theme) {
|
|
1007
|
-
if (config.enabled === false) {
|
|
1008
|
-
return { text: "" };
|
|
1009
|
-
}
|
|
1010
|
-
const labels = config.labels ?? { pro: "PRO", max: "MAX", api: "API" };
|
|
1011
|
-
const tier = config.override ?? detectTier();
|
|
1012
|
-
let color = theme.blue;
|
|
1013
|
-
let label = labels.pro;
|
|
1014
|
-
if (tier === "max") {
|
|
1015
|
-
color = theme.mauve;
|
|
1016
|
-
label = labels.max;
|
|
1017
|
-
} else if (tier === "api") {
|
|
1018
|
-
color = theme.green;
|
|
1019
|
-
label = labels.api;
|
|
1020
|
-
}
|
|
1021
|
-
const icon = "◆ ";
|
|
1022
|
-
const text = `${color}${theme.bold}${icon}${label}${theme.reset}`;
|
|
1023
|
-
return { text, action: "/usage" };
|
|
1024
|
-
}
|
|
1025
|
-
function detectTier() {
|
|
1026
|
-
const now = Date.now();
|
|
1027
|
-
if (cachedTier && now - cacheTime < CACHE_TTL3) {
|
|
1028
|
-
return cachedTier;
|
|
1029
|
-
}
|
|
1030
|
-
const claudeJsonPath = join5(homedir6(), ".claude.json");
|
|
1031
|
-
try {
|
|
1032
|
-
if (existsSync5(claudeJsonPath)) {
|
|
1033
|
-
const content = readFileSync5(claudeJsonPath, "utf-8");
|
|
1034
|
-
const data = JSON.parse(content);
|
|
1035
|
-
if (data.oauthAccount?.hasExtraUsageEnabled) {
|
|
1036
|
-
cachedTier = "max";
|
|
1037
|
-
} else if (data.oauthAccount) {
|
|
1038
|
-
cachedTier = "pro";
|
|
1039
|
-
} else {
|
|
1040
|
-
cachedTier = "api";
|
|
1041
|
-
}
|
|
1042
|
-
} else {
|
|
1043
|
-
cachedTier = "api";
|
|
1044
|
-
}
|
|
1045
|
-
} catch {
|
|
1046
|
-
cachedTier = "pro";
|
|
1047
|
-
}
|
|
1048
|
-
cacheTime = now;
|
|
1049
|
-
return cachedTier;
|
|
1050
|
-
}
|
|
1051
1071
|
// src/components/time.ts
|
|
1052
1072
|
function renderTime(config, theme) {
|
|
1053
1073
|
if (config.enabled === false) {
|
|
@@ -1081,12 +1101,12 @@ function renderTime(config, theme) {
|
|
|
1081
1101
|
return { text };
|
|
1082
1102
|
}
|
|
1083
1103
|
// src/config.ts
|
|
1084
|
-
import { existsSync as
|
|
1085
|
-
import { homedir as
|
|
1086
|
-
import { join as
|
|
1104
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
1105
|
+
import { homedir as homedir6 } from "node:os";
|
|
1106
|
+
import { join as join5 } from "node:path";
|
|
1087
1107
|
var CONFIG_PATHS = [
|
|
1088
|
-
|
|
1089
|
-
|
|
1108
|
+
join5(homedir6(), ".config", "claude-pulse", "config.json"),
|
|
1109
|
+
join5(homedir6(), ".claude-pulse.json")
|
|
1090
1110
|
];
|
|
1091
1111
|
var FIXED_LINES = [
|
|
1092
1112
|
{ name: "identity", enabled: true, components: ["name", "cwd"], separator: " " },
|
|
@@ -1094,7 +1114,7 @@ var FIXED_LINES = [
|
|
|
1094
1114
|
{
|
|
1095
1115
|
name: "engine",
|
|
1096
1116
|
enabled: true,
|
|
1097
|
-
components: ["
|
|
1117
|
+
components: ["model", "context", "cost", "session"],
|
|
1098
1118
|
separator: " │ "
|
|
1099
1119
|
},
|
|
1100
1120
|
{ name: "mcp", enabled: true, components: ["mcp"], separator: " │ " },
|
|
@@ -1104,13 +1124,9 @@ var FIXED_LINES = [
|
|
|
1104
1124
|
var DEFAULT_CONFIG = {
|
|
1105
1125
|
theme: "catppuccin",
|
|
1106
1126
|
components: {
|
|
1107
|
-
tier: {
|
|
1108
|
-
enabled: true,
|
|
1109
|
-
labels: { pro: "PRO", max: "MAX", api: "API" }
|
|
1110
|
-
},
|
|
1111
1127
|
model: {
|
|
1112
1128
|
enabled: true,
|
|
1113
|
-
showIcon:
|
|
1129
|
+
showIcon: true
|
|
1114
1130
|
},
|
|
1115
1131
|
context: {
|
|
1116
1132
|
enabled: true,
|
|
@@ -1179,9 +1195,9 @@ var DEFAULT_CONFIG = {
|
|
|
1179
1195
|
};
|
|
1180
1196
|
function loadConfig() {
|
|
1181
1197
|
for (const configPath of CONFIG_PATHS) {
|
|
1182
|
-
if (
|
|
1198
|
+
if (existsSync5(configPath)) {
|
|
1183
1199
|
try {
|
|
1184
|
-
const content =
|
|
1200
|
+
const content = readFileSync5(configPath, "utf-8");
|
|
1185
1201
|
const userConfig = JSON.parse(content);
|
|
1186
1202
|
return mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
1187
1203
|
} catch {}
|
|
@@ -1208,6 +1224,10 @@ function mergeConfig(target, source) {
|
|
|
1208
1224
|
const result = { ...target };
|
|
1209
1225
|
if (source.theme !== undefined)
|
|
1210
1226
|
result.theme = source.theme;
|
|
1227
|
+
if (source.compact !== undefined)
|
|
1228
|
+
result.compact = source.compact;
|
|
1229
|
+
if (source.dividers !== undefined)
|
|
1230
|
+
result.dividers = source.dividers;
|
|
1211
1231
|
if (source.lines !== undefined)
|
|
1212
1232
|
result.lines = source.lines;
|
|
1213
1233
|
if (source.interactive !== undefined)
|
|
@@ -14802,7 +14822,9 @@ var McpServerSchema = exports_external.object({
|
|
|
14802
14822
|
});
|
|
14803
14823
|
var ComponentOutputSchema = exports_external.object({
|
|
14804
14824
|
text: exports_external.string(),
|
|
14805
|
-
action: exports_external.string().optional()
|
|
14825
|
+
action: exports_external.string().optional(),
|
|
14826
|
+
header: exports_external.string().optional(),
|
|
14827
|
+
items: exports_external.array(exports_external.string()).optional()
|
|
14806
14828
|
});
|
|
14807
14829
|
var TierConfigSchema = exports_external.object({
|
|
14808
14830
|
enabled: exports_external.boolean().optional(),
|
|
@@ -14949,6 +14971,8 @@ var LinesConfigSchema = exports_external.object({
|
|
|
14949
14971
|
});
|
|
14950
14972
|
var PulseConfigSchema = exports_external.object({
|
|
14951
14973
|
theme: exports_external.string(),
|
|
14974
|
+
compact: exports_external.boolean().optional(),
|
|
14975
|
+
dividers: exports_external.boolean().optional(),
|
|
14952
14976
|
lines: LinesConfigSchema.optional(),
|
|
14953
14977
|
components: ComponentConfigsSchema,
|
|
14954
14978
|
interactive: exports_external.object({
|
|
@@ -15001,6 +15025,39 @@ var catppuccin = {
|
|
|
15001
15025
|
dim: "\x1B[2m"
|
|
15002
15026
|
};
|
|
15003
15027
|
|
|
15028
|
+
// src/truncate.ts
|
|
15029
|
+
var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
15030
|
+
function visibleLength(str) {
|
|
15031
|
+
return str.replace(ANSI_RE, "").length;
|
|
15032
|
+
}
|
|
15033
|
+
function wrapParts(parts, separator, maxWidth, indent = 2) {
|
|
15034
|
+
if (parts.length === 0)
|
|
15035
|
+
return "";
|
|
15036
|
+
const sepWidth = visibleLength(separator);
|
|
15037
|
+
const pad = " ".repeat(indent);
|
|
15038
|
+
const lines = [];
|
|
15039
|
+
let currentLine = parts[0];
|
|
15040
|
+
let currentWidth = visibleLength(parts[0]);
|
|
15041
|
+
for (let i = 1;i < parts.length; i++) {
|
|
15042
|
+
const partWidth = visibleLength(parts[i]);
|
|
15043
|
+
const wouldBe = currentWidth + sepWidth + partWidth;
|
|
15044
|
+
if (wouldBe > maxWidth) {
|
|
15045
|
+
lines.push(currentLine);
|
|
15046
|
+
currentLine = pad + parts[i];
|
|
15047
|
+
currentWidth = indent + partWidth;
|
|
15048
|
+
} else {
|
|
15049
|
+
currentLine += separator + parts[i];
|
|
15050
|
+
currentWidth = wouldBe;
|
|
15051
|
+
}
|
|
15052
|
+
}
|
|
15053
|
+
lines.push(currentLine);
|
|
15054
|
+
return lines.join(`
|
|
15055
|
+
`);
|
|
15056
|
+
}
|
|
15057
|
+
function getTerminalWidth() {
|
|
15058
|
+
return process.stdout.columns || Number(process.env.COLUMNS) || 80;
|
|
15059
|
+
}
|
|
15060
|
+
|
|
15004
15061
|
// src/cli.ts
|
|
15005
15062
|
var VERSION = package_default.version ?? "1.0.0";
|
|
15006
15063
|
async function main() {
|
|
@@ -15039,30 +15096,67 @@ Configuration:
|
|
|
15039
15096
|
const config2 = loadConfig();
|
|
15040
15097
|
const theme = catppuccin;
|
|
15041
15098
|
const lines = getLines(config2);
|
|
15099
|
+
const termWidth = getTerminalWidth();
|
|
15100
|
+
if (config2.compact) {
|
|
15101
|
+
if (config2.components.skills) {
|
|
15102
|
+
config2.components.skills.showNames = false;
|
|
15103
|
+
}
|
|
15104
|
+
if (config2.components.hooks) {
|
|
15105
|
+
config2.components.hooks.showNames = false;
|
|
15106
|
+
config2.components.hooks.showCount = false;
|
|
15107
|
+
}
|
|
15108
|
+
if (config2.components.mcp) {
|
|
15109
|
+
config2.components.mcp.showNames = false;
|
|
15110
|
+
}
|
|
15111
|
+
if (config2.components.context) {
|
|
15112
|
+
config2.components.context.showTokens = false;
|
|
15113
|
+
config2.components.context.showRate = false;
|
|
15114
|
+
}
|
|
15115
|
+
if (config2.components.cost) {
|
|
15116
|
+
config2.components.cost.showBurnRate = false;
|
|
15117
|
+
config2.components.cost.showProjection = false;
|
|
15118
|
+
}
|
|
15119
|
+
if (config2.components.cwd) {
|
|
15120
|
+
config2.components.cwd.maxLength = 15;
|
|
15121
|
+
}
|
|
15122
|
+
}
|
|
15042
15123
|
const outputLines = [];
|
|
15043
15124
|
for (const line of lines) {
|
|
15044
15125
|
if (!line.enabled)
|
|
15045
15126
|
continue;
|
|
15046
|
-
const parts = [];
|
|
15047
15127
|
const separator = ` ${theme.overlay2}│${theme.reset} `;
|
|
15048
15128
|
const sep = line.separator ?? separator;
|
|
15129
|
+
const outputs = [];
|
|
15049
15130
|
for (const componentName of line.components) {
|
|
15050
15131
|
const output = renderComponent(componentName, input, config2, theme);
|
|
15051
15132
|
if (output.text) {
|
|
15052
|
-
|
|
15133
|
+
outputs.push(output);
|
|
15053
15134
|
}
|
|
15054
15135
|
}
|
|
15055
|
-
if (
|
|
15056
|
-
|
|
15136
|
+
if (outputs.length === 0)
|
|
15137
|
+
continue;
|
|
15138
|
+
if (outputs.length === 1 && outputs[0].header && outputs[0].items?.length) {
|
|
15139
|
+
const { header, items } = outputs[0];
|
|
15140
|
+
const rendered = wrapParts([header, ...items], " ", termWidth);
|
|
15141
|
+
outputLines.push(rendered);
|
|
15142
|
+
} else {
|
|
15143
|
+
const parts = outputs.map((o) => o.text);
|
|
15144
|
+
outputLines.push(wrapParts(parts, sep, termWidth));
|
|
15057
15145
|
}
|
|
15058
15146
|
}
|
|
15059
|
-
|
|
15147
|
+
if (config2.dividers) {
|
|
15148
|
+
const divider = `${theme.overlay0}${"─".repeat(termWidth)}${theme.reset}`;
|
|
15149
|
+
console.log(outputLines.join(`
|
|
15150
|
+
${divider}
|
|
15151
|
+
`) + `
|
|
15152
|
+
${divider}`);
|
|
15153
|
+
} else {
|
|
15154
|
+
console.log(outputLines.join(`
|
|
15060
15155
|
`));
|
|
15156
|
+
}
|
|
15061
15157
|
}
|
|
15062
15158
|
function renderComponent(name, input, config2, theme) {
|
|
15063
15159
|
switch (name) {
|
|
15064
|
-
case "tier":
|
|
15065
|
-
return renderTier(config2.components.tier ?? {}, theme);
|
|
15066
15160
|
case "model":
|
|
15067
15161
|
return renderModel(input, config2.components.model ?? {}, theme);
|
|
15068
15162
|
case "context":
|