cc-pulse 1.3.2 → 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 +82 -39
- package/dist/cli.js +191 -100
- 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.6", "Sonnet 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
|
|
|
@@ -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: {
|
|
@@ -451,15 +455,27 @@ 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 = Object.entries(summary.events).map(([event, detail]) => {
|
|
455
462
|
const label = EVENT_LABELS[event] ?? event;
|
|
456
|
-
const
|
|
457
|
-
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}` : "";
|
|
458
464
|
const countStr = showCount ? `${theme.peach}${detail.count}${theme.reset}` : "";
|
|
459
|
-
|
|
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}`;
|
|
460
476
|
});
|
|
461
|
-
const text =
|
|
462
|
-
return { text };
|
|
477
|
+
const text = items.length > 0 ? `${header} ${items.join(" ")}` : header;
|
|
478
|
+
return { text, header, items };
|
|
463
479
|
}
|
|
464
480
|
function getHooksSummary() {
|
|
465
481
|
const events = {};
|
|
@@ -590,7 +606,7 @@ function renderMcp(config, theme) {
|
|
|
590
606
|
}
|
|
591
607
|
const displayServers = servers.slice(0, maxDisplay);
|
|
592
608
|
const remaining = servers.length - maxDisplay;
|
|
593
|
-
const
|
|
609
|
+
const serverItems = displayServers.map((server) => {
|
|
594
610
|
let icon;
|
|
595
611
|
let color;
|
|
596
612
|
switch (server.status) {
|
|
@@ -612,17 +628,17 @@ function renderMcp(config, theme) {
|
|
|
612
628
|
}
|
|
613
629
|
return `${color}${server.name} ${icon}${theme.reset}`;
|
|
614
630
|
});
|
|
615
|
-
let serverStr = serverParts.join(" ");
|
|
616
631
|
if (remaining > 0) {
|
|
617
|
-
|
|
632
|
+
serverItems.push(`${theme.sky}+${remaining} more${theme.reset}`);
|
|
618
633
|
}
|
|
619
634
|
const countStr = `${connectedCount}/${servers.length}`;
|
|
635
|
+
const header = `${theme.sky}${label} ${theme.green}${countStr}${theme.reset}:`;
|
|
620
636
|
if (config.showNames === false) {
|
|
621
637
|
const text2 = `${theme.sky}${label} ${theme.green}${countStr}${theme.reset}`;
|
|
622
638
|
return { text: text2, action: "/mcp" };
|
|
623
639
|
}
|
|
624
|
-
const text = `${
|
|
625
|
-
return { text, action: "/mcp" };
|
|
640
|
+
const text = `${header} ${serverItems.join(" ")}`;
|
|
641
|
+
return { text, header, items: serverItems, action: "/mcp" };
|
|
626
642
|
}
|
|
627
643
|
function getMcpServers() {
|
|
628
644
|
const state = loadState();
|
|
@@ -718,7 +734,7 @@ function parseMcpOutput(output) {
|
|
|
718
734
|
}
|
|
719
735
|
// src/components/model.ts
|
|
720
736
|
function parseModelId(modelId) {
|
|
721
|
-
const id = modelId.toLowerCase();
|
|
737
|
+
const id = modelId.toLowerCase().replace(/\[.*\]$/, "");
|
|
722
738
|
const newFormat = id.match(/^claude-(\w+)-(\d+)-(\d+)-\d+$/);
|
|
723
739
|
if (newFormat) {
|
|
724
740
|
const [, family, major, minor] = newFormat;
|
|
@@ -729,6 +745,16 @@ function parseModelId(modelId) {
|
|
|
729
745
|
const [, family, major] = newFormatNoMinor;
|
|
730
746
|
return { family, version: major };
|
|
731
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
|
+
}
|
|
732
758
|
const oldFormat = id.match(/^claude-(\d+)-(\d+)-(\w+)-\d+$/);
|
|
733
759
|
if (oldFormat) {
|
|
734
760
|
const [, major, minor, family] = oldFormat;
|
|
@@ -747,7 +773,7 @@ function renderModel(input, config, theme) {
|
|
|
747
773
|
}
|
|
748
774
|
const modelId = input.model?.id ?? "";
|
|
749
775
|
const displayName = input.model?.display_name ?? "";
|
|
750
|
-
const icons = config.icons ?? { opus: "
|
|
776
|
+
const icons = config.icons ?? { opus: "◆", sonnet: "◆", haiku: "◆" };
|
|
751
777
|
const showIcon = config.showIcon !== false;
|
|
752
778
|
let icon = "\uD83E\uDD16";
|
|
753
779
|
let color = theme.text;
|
|
@@ -855,22 +881,65 @@ function renderSkills(config, theme) {
|
|
|
855
881
|
}
|
|
856
882
|
const validNames = summary.skills.filter((s) => s.valid).map((s) => s.name ?? s.folder).slice(0, maxDisplay);
|
|
857
883
|
const brokenNames = summary.skills.filter((s) => !s.valid).map((s) => s.folder);
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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;
|
|
862
889
|
if (showNames && validNames.length > 0) {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
+
}
|
|
866
915
|
}
|
|
867
|
-
let brokenStr = "";
|
|
868
916
|
if (summary.broken > 0) {
|
|
869
|
-
const
|
|
870
|
-
|
|
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
|
+
}
|
|
871
941
|
}
|
|
872
|
-
|
|
873
|
-
return { text };
|
|
942
|
+
return { groups, ungrouped };
|
|
874
943
|
}
|
|
875
944
|
function getSkillsSummary() {
|
|
876
945
|
const skills = [];
|
|
@@ -999,58 +1068,6 @@ function renderSystem(input, config, theme) {
|
|
|
999
1068
|
const text = parts.join(" │ ");
|
|
1000
1069
|
return { text };
|
|
1001
1070
|
}
|
|
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
1071
|
// src/components/time.ts
|
|
1055
1072
|
function renderTime(config, theme) {
|
|
1056
1073
|
if (config.enabled === false) {
|
|
@@ -1084,12 +1101,12 @@ function renderTime(config, theme) {
|
|
|
1084
1101
|
return { text };
|
|
1085
1102
|
}
|
|
1086
1103
|
// src/config.ts
|
|
1087
|
-
import { existsSync as
|
|
1088
|
-
import { homedir as
|
|
1089
|
-
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";
|
|
1090
1107
|
var CONFIG_PATHS = [
|
|
1091
|
-
|
|
1092
|
-
|
|
1108
|
+
join5(homedir6(), ".config", "claude-pulse", "config.json"),
|
|
1109
|
+
join5(homedir6(), ".claude-pulse.json")
|
|
1093
1110
|
];
|
|
1094
1111
|
var FIXED_LINES = [
|
|
1095
1112
|
{ name: "identity", enabled: true, components: ["name", "cwd"], separator: " " },
|
|
@@ -1097,7 +1114,7 @@ var FIXED_LINES = [
|
|
|
1097
1114
|
{
|
|
1098
1115
|
name: "engine",
|
|
1099
1116
|
enabled: true,
|
|
1100
|
-
components: ["
|
|
1117
|
+
components: ["model", "context", "cost", "session"],
|
|
1101
1118
|
separator: " │ "
|
|
1102
1119
|
},
|
|
1103
1120
|
{ name: "mcp", enabled: true, components: ["mcp"], separator: " │ " },
|
|
@@ -1107,13 +1124,9 @@ var FIXED_LINES = [
|
|
|
1107
1124
|
var DEFAULT_CONFIG = {
|
|
1108
1125
|
theme: "catppuccin",
|
|
1109
1126
|
components: {
|
|
1110
|
-
tier: {
|
|
1111
|
-
enabled: true,
|
|
1112
|
-
labels: { pro: "PRO", max: "MAX", api: "API" }
|
|
1113
|
-
},
|
|
1114
1127
|
model: {
|
|
1115
1128
|
enabled: true,
|
|
1116
|
-
showIcon:
|
|
1129
|
+
showIcon: true
|
|
1117
1130
|
},
|
|
1118
1131
|
context: {
|
|
1119
1132
|
enabled: true,
|
|
@@ -1182,9 +1195,9 @@ var DEFAULT_CONFIG = {
|
|
|
1182
1195
|
};
|
|
1183
1196
|
function loadConfig() {
|
|
1184
1197
|
for (const configPath of CONFIG_PATHS) {
|
|
1185
|
-
if (
|
|
1198
|
+
if (existsSync5(configPath)) {
|
|
1186
1199
|
try {
|
|
1187
|
-
const content =
|
|
1200
|
+
const content = readFileSync5(configPath, "utf-8");
|
|
1188
1201
|
const userConfig = JSON.parse(content);
|
|
1189
1202
|
return mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
1190
1203
|
} catch {}
|
|
@@ -1211,6 +1224,10 @@ function mergeConfig(target, source) {
|
|
|
1211
1224
|
const result = { ...target };
|
|
1212
1225
|
if (source.theme !== undefined)
|
|
1213
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;
|
|
1214
1231
|
if (source.lines !== undefined)
|
|
1215
1232
|
result.lines = source.lines;
|
|
1216
1233
|
if (source.interactive !== undefined)
|
|
@@ -14805,7 +14822,9 @@ var McpServerSchema = exports_external.object({
|
|
|
14805
14822
|
});
|
|
14806
14823
|
var ComponentOutputSchema = exports_external.object({
|
|
14807
14824
|
text: exports_external.string(),
|
|
14808
|
-
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()
|
|
14809
14828
|
});
|
|
14810
14829
|
var TierConfigSchema = exports_external.object({
|
|
14811
14830
|
enabled: exports_external.boolean().optional(),
|
|
@@ -14952,6 +14971,8 @@ var LinesConfigSchema = exports_external.object({
|
|
|
14952
14971
|
});
|
|
14953
14972
|
var PulseConfigSchema = exports_external.object({
|
|
14954
14973
|
theme: exports_external.string(),
|
|
14974
|
+
compact: exports_external.boolean().optional(),
|
|
14975
|
+
dividers: exports_external.boolean().optional(),
|
|
14955
14976
|
lines: LinesConfigSchema.optional(),
|
|
14956
14977
|
components: ComponentConfigsSchema,
|
|
14957
14978
|
interactive: exports_external.object({
|
|
@@ -15004,6 +15025,39 @@ var catppuccin = {
|
|
|
15004
15025
|
dim: "\x1B[2m"
|
|
15005
15026
|
};
|
|
15006
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
|
+
|
|
15007
15061
|
// src/cli.ts
|
|
15008
15062
|
var VERSION = package_default.version ?? "1.0.0";
|
|
15009
15063
|
async function main() {
|
|
@@ -15042,30 +15096,67 @@ Configuration:
|
|
|
15042
15096
|
const config2 = loadConfig();
|
|
15043
15097
|
const theme = catppuccin;
|
|
15044
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
|
+
}
|
|
15045
15123
|
const outputLines = [];
|
|
15046
15124
|
for (const line of lines) {
|
|
15047
15125
|
if (!line.enabled)
|
|
15048
15126
|
continue;
|
|
15049
|
-
const parts = [];
|
|
15050
15127
|
const separator = ` ${theme.overlay2}│${theme.reset} `;
|
|
15051
15128
|
const sep = line.separator ?? separator;
|
|
15129
|
+
const outputs = [];
|
|
15052
15130
|
for (const componentName of line.components) {
|
|
15053
15131
|
const output = renderComponent(componentName, input, config2, theme);
|
|
15054
15132
|
if (output.text) {
|
|
15055
|
-
|
|
15133
|
+
outputs.push(output);
|
|
15056
15134
|
}
|
|
15057
15135
|
}
|
|
15058
|
-
if (
|
|
15059
|
-
|
|
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));
|
|
15060
15145
|
}
|
|
15061
15146
|
}
|
|
15062
|
-
|
|
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(`
|
|
15063
15155
|
`));
|
|
15156
|
+
}
|
|
15064
15157
|
}
|
|
15065
15158
|
function renderComponent(name, input, config2, theme) {
|
|
15066
15159
|
switch (name) {
|
|
15067
|
-
case "tier":
|
|
15068
|
-
return renderTier(config2.components.tier ?? {}, theme);
|
|
15069
15160
|
case "model":
|
|
15070
15161
|
return renderModel(input, config2.components.model ?? {}, theme);
|
|
15071
15162
|
case "context":
|