cc-pulse 1.0.2 → 1.2.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 +95 -96
- package/dist/cli.js +225 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,38 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# cc-pulse
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-

|
|
6
|
-
|
|
7
|
-
## Why
|
|
8
|
-
|
|
9
|
-
- **Context & cost** — context window %, input/output/cache token breakdown, cost with burn rate, session duration
|
|
10
|
-
- **MCP server health** — connection status for every server: connected, disconnected, disabled, or erroring
|
|
11
|
-
- **Hook monitoring** — all hooks by event type, with broken path detection
|
|
12
|
-
- **Git at a glance** — branch, new/modified/deleted file counts
|
|
13
|
-
- **Fully customizable** — every component is independently configurable with multiple display styles
|
|
14
|
-
|
|
15
|
-
## What You Get
|
|
3
|
+
[](https://www.npmjs.com/package/cc-pulse)
|
|
4
|
+
[](LICENSE)
|
|
16
5
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
| Line | What it shows |
|
|
20
|
-
|------|---------------|
|
|
21
|
-
| **Identity** | Project name + working directory |
|
|
22
|
-
| **Git** | Current branch + file changes (new, modified, deleted) |
|
|
23
|
-
| **Engine** | Model, remaining % until compaction, token cost, session duration |
|
|
24
|
-
| **MCP** | Server connections with health status (connected, disconnected, disabled, error) |
|
|
25
|
-
| **Hooks** | Active hooks by event type, with broken path detection |
|
|
6
|
+
A real-time statusline for [Claude Code](https://docs.anthropic.com/en/docs/claude-code).
|
|
26
7
|
|
|
27
|
-
|
|
8
|
+

|
|
28
9
|
|
|
29
|
-
##
|
|
10
|
+
## 🚀 Quick Start
|
|
30
11
|
|
|
31
12
|
```bash
|
|
32
13
|
npm install -g cc-pulse
|
|
33
14
|
```
|
|
34
15
|
|
|
35
|
-
Add to
|
|
16
|
+
Add to `~/.claude/settings.json`:
|
|
36
17
|
|
|
37
18
|
```json
|
|
38
19
|
{
|
|
@@ -43,104 +24,115 @@ Add to your Claude Code settings (`~/.claude/settings.json`):
|
|
|
43
24
|
}
|
|
44
25
|
```
|
|
45
26
|
|
|
46
|
-
Restart Claude Code
|
|
27
|
+
Restart Claude Code — the statusline appears above the input area.
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
<summary><strong>Install from source</strong></summary>
|
|
29
|
+
## ✨ Features
|
|
50
30
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
| Feature | Description |
|
|
32
|
+
|---------|-------------|
|
|
33
|
+
| **→Compact indicator** | Shows remaining % until auto-compaction — colors shift from green to red as you approach the limit |
|
|
34
|
+
| **Token breakdown** | Input ↓, output ↑, and cache ⟳ tokens at a glance |
|
|
35
|
+
| **Cost tracking** | Session cost with color coding ($1 yellow, $2 orange, $5+ red) |
|
|
36
|
+
| **MCP health** | Live connection status for all MCP servers |
|
|
37
|
+
| **Hook monitoring** | Active hooks by event type, with broken path detection |
|
|
38
|
+
| **Git status** | Branch name + new/modified/deleted file counts |
|
|
57
39
|
|
|
58
|
-
|
|
40
|
+
## 📊 What You Get
|
|
59
41
|
|
|
60
|
-
|
|
42
|
+
Five lines of information, updated on every message:
|
|
43
|
+
|
|
44
|
+

|
|
61
45
|
|
|
62
|
-
|
|
46
|
+
| Line | Content |
|
|
47
|
+
|------|---------|
|
|
48
|
+
| **Identity** | Project name + working directory |
|
|
49
|
+
| **Git** | Branch + file changes (new, modified, deleted) |
|
|
50
|
+
| **Engine** | Tier, model, context remaining, tokens, cost, duration |
|
|
51
|
+
| **MCP** | Server count + individual status (✓ connected, ✗ disconnected, ○ disabled) |
|
|
52
|
+
| **Hooks** | Hook count by event type, with broken path warnings |
|
|
63
53
|
|
|
64
|
-
|
|
54
|
+
## ⚙️ Configuration
|
|
55
|
+
|
|
56
|
+
Create `~/.config/claude-pulse/config.json` to customize. Only include what you want to change.
|
|
65
57
|
|
|
66
58
|
<details>
|
|
67
|
-
<summary><strong>
|
|
59
|
+
<summary><strong>Context Window</strong></summary>
|
|
68
60
|
|
|
69
|
-
The
|
|
61
|
+
The `→Compact` indicator shows remaining space until auto-compaction. When it reaches 0%, Claude compacts the conversation.
|
|
70
62
|
|
|
71
63
|
```json
|
|
72
64
|
{
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
|
|
65
|
+
"components": {
|
|
66
|
+
"context": {
|
|
67
|
+
"style": "bar",
|
|
68
|
+
"showTokens": true,
|
|
69
|
+
"thresholds": { "warn": 70, "critical": 85, "danger": 95 }
|
|
70
|
+
}
|
|
76
71
|
}
|
|
77
72
|
}
|
|
78
73
|
```
|
|
79
74
|
|
|
80
|
-
|
|
|
81
|
-
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
75
|
+
| Style | Example |
|
|
76
|
+
|-------|---------|
|
|
77
|
+
| `bar` (default) | `→Compact ●●●●●●○○○○ 58%` |
|
|
78
|
+
| `compact` | `→Compact 58%` |
|
|
79
|
+
| `detailed` | `→Compact 116.0k/200.0k (58%)` |
|
|
80
|
+
| `both` | `●●●●●●○○○○ free:116.0k used:84.0k` |
|
|
81
|
+
|
|
82
|
+
**Color thresholds** — as remaining % drops:
|
|
83
|
+
- **Green**: > 30% remaining (safe)
|
|
84
|
+
- **Yellow**: 30% remaining (warn)
|
|
85
|
+
- **Orange**: 15% remaining (critical)
|
|
86
|
+
- **Red + 🔴**: 5% remaining (danger)
|
|
87
87
|
|
|
88
88
|
</details>
|
|
89
89
|
|
|
90
90
|
<details>
|
|
91
|
-
<summary><strong>
|
|
91
|
+
<summary><strong>Subscription Tier</strong></summary>
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
Set your plan manually (auto-detection isn't reliable):
|
|
94
94
|
|
|
95
95
|
```json
|
|
96
96
|
{
|
|
97
97
|
"components": {
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"showTokens": true,
|
|
101
|
-
"showRate": false,
|
|
102
|
-
"thresholds": { "warn": 70, "critical": 85, "danger": 95 }
|
|
98
|
+
"tier": {
|
|
99
|
+
"override": "max"
|
|
103
100
|
}
|
|
104
101
|
}
|
|
105
102
|
}
|
|
106
103
|
```
|
|
107
104
|
|
|
108
|
-
|
|
109
|
-
|-------|---------|
|
|
110
|
-
| `compact` | `→Compact 58%` |
|
|
111
|
-
| `bar` | `●●●●●●○○○○ 58%` |
|
|
112
|
-
| `detailed` | `116.0k/200.0k (58%)` |
|
|
113
|
-
| `both` | `●●●●●●○○○○ free:116.0k used:84.0k` |
|
|
114
|
-
|
|
115
|
-
Enable `showTokens` to see `↓input ↑output ⟳cache` breakdown.
|
|
105
|
+
Options: `"pro"`, `"max"`, `"api"`
|
|
116
106
|
|
|
117
107
|
</details>
|
|
118
108
|
|
|
119
109
|
<details>
|
|
120
|
-
<summary><strong>MCP
|
|
110
|
+
<summary><strong>MCP Servers</strong></summary>
|
|
121
111
|
|
|
122
112
|
```json
|
|
123
113
|
{
|
|
124
114
|
"components": {
|
|
125
115
|
"mcp": {
|
|
126
116
|
"showNames": true,
|
|
127
|
-
"showOnlyProblems":
|
|
117
|
+
"showOnlyProblems": false,
|
|
128
118
|
"maxDisplay": 4
|
|
129
119
|
}
|
|
130
120
|
}
|
|
131
121
|
}
|
|
132
122
|
```
|
|
133
123
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
124
|
+
| Option | Effect |
|
|
125
|
+
|--------|--------|
|
|
126
|
+
| `showNames: true` | List each server with status |
|
|
127
|
+
| `showOnlyProblems: true` | Hide line when all servers healthy |
|
|
128
|
+
| `maxDisplay: 4` | Limit servers shown ("+N more" for rest) |
|
|
137
129
|
|
|
138
130
|
| Icon | Status |
|
|
139
131
|
|------|--------|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
132
|
+
| ✓ | Connected |
|
|
133
|
+
| ✗ | Disconnected (red) |
|
|
134
|
+
| ○ | Disabled |
|
|
135
|
+
| ▲ | Error |
|
|
144
136
|
|
|
145
137
|
</details>
|
|
146
138
|
|
|
@@ -160,11 +152,11 @@ Enable `showTokens` to see `↓input ↑output ⟳cache` breakdown.
|
|
|
160
152
|
|
|
161
153
|
| Setting | Result |
|
|
162
154
|
|---------|--------|
|
|
163
|
-
| Both `true`
|
|
164
|
-
| `showNames: false` | `⚡Hooks 8 Submit:3 Post:2
|
|
155
|
+
| Both `true` | `⚡Hooks 8 Submit:3 timezone-context,best-practices` |
|
|
156
|
+
| `showNames: false` | `⚡Hooks 8 Submit:3 Post:2 End:1` |
|
|
165
157
|
| Both `false` | `⚡Hooks 8` |
|
|
166
158
|
|
|
167
|
-
Broken hooks (invalid
|
|
159
|
+
Broken hooks (invalid paths) show in red with ▲.
|
|
168
160
|
|
|
169
161
|
</details>
|
|
170
162
|
|
|
@@ -182,54 +174,61 @@ Broken hooks (invalid file paths) always show in red with `▲`.
|
|
|
182
174
|
}
|
|
183
175
|
```
|
|
184
176
|
|
|
185
|
-
|
|
177
|
+
Color thresholds: green < $1, yellow $1-$2, orange $2-$5, red > $5
|
|
186
178
|
|
|
187
179
|
</details>
|
|
188
180
|
|
|
189
181
|
<details>
|
|
190
|
-
<summary><strong>
|
|
182
|
+
<summary><strong>Layout</strong></summary>
|
|
191
183
|
|
|
192
|
-
|
|
184
|
+
The 5-line structure is fixed. You can toggle lines and change separators:
|
|
193
185
|
|
|
194
186
|
```json
|
|
195
187
|
{
|
|
196
|
-
"
|
|
197
|
-
"
|
|
198
|
-
|
|
199
|
-
"override": "max"
|
|
200
|
-
}
|
|
188
|
+
"lines": {
|
|
189
|
+
"hooks": { "enabled": false },
|
|
190
|
+
"engine": { "separator": " | " }
|
|
201
191
|
}
|
|
202
192
|
}
|
|
203
193
|
```
|
|
204
194
|
|
|
205
|
-
|
|
195
|
+
| Line | Key | Toggleable |
|
|
196
|
+
|------|-----|------------|
|
|
197
|
+
| Identity | — | No (branding) |
|
|
198
|
+
| Git | `git` | Yes |
|
|
199
|
+
| Engine | `engine` | Yes |
|
|
200
|
+
| MCP | `mcp` | Yes |
|
|
201
|
+
| Hooks | `hooks` | Yes |
|
|
206
202
|
|
|
207
203
|
</details>
|
|
208
204
|
|
|
209
205
|
<details>
|
|
210
|
-
<summary><strong>
|
|
206
|
+
<summary><strong>All Components</strong></summary>
|
|
211
207
|
|
|
212
208
|
| Component | Key Options |
|
|
213
209
|
|-----------|-------------|
|
|
214
210
|
| `model` | `showIcon: true` adds emoji per model |
|
|
215
211
|
| `session` | `showDuration: true`, `showId: false` |
|
|
216
|
-
| `cache` | Shows cache hit rate
|
|
217
|
-
| `linesChanged` | Shows `+added -removed`
|
|
218
|
-
| `time` | `format: "12h"
|
|
212
|
+
| `cache` | Shows cache hit rate |
|
|
213
|
+
| `linesChanged` | Shows `+added -removed` |
|
|
214
|
+
| `time` | `format: "12h"/"24h"`, `showTimezone: true` |
|
|
219
215
|
|
|
220
216
|
All components accept `"enabled": false` to hide them.
|
|
221
217
|
|
|
222
218
|
</details>
|
|
223
219
|
|
|
224
|
-
## Development
|
|
220
|
+
## 🛠️ Development
|
|
225
221
|
|
|
226
222
|
```bash
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
bun
|
|
230
|
-
bun run
|
|
223
|
+
git clone https://github.com/ali-nr/claude-pulse.git
|
|
224
|
+
cd claude-pulse
|
|
225
|
+
bun install
|
|
226
|
+
bun run build
|
|
227
|
+
bun test
|
|
231
228
|
```
|
|
232
229
|
|
|
230
|
+
Use full path in settings: `"command": "node /path/to/claude-pulse/dist/cli.js"`
|
|
231
|
+
|
|
233
232
|
## License
|
|
234
233
|
|
|
235
234
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ var __export = (target, all) => {
|
|
|
12
12
|
// package.json
|
|
13
13
|
var package_default = {
|
|
14
14
|
name: "cc-pulse",
|
|
15
|
-
version: "1.0
|
|
15
|
+
version: "1.2.0",
|
|
16
16
|
description: "A customizable, real-time statusline for Claude Code",
|
|
17
17
|
type: "module",
|
|
18
18
|
bin: {
|
|
@@ -100,40 +100,35 @@ function renderContext(input, config, theme) {
|
|
|
100
100
|
const ctx = input.context_window;
|
|
101
101
|
const thresholds = config.thresholds ?? { warn: 70, critical: 85, danger: 95 };
|
|
102
102
|
const usedPercent = ctx?.used_percentage ?? 0;
|
|
103
|
-
const remainingPercent = 100 - usedPercent;
|
|
104
103
|
let color = theme.green;
|
|
105
104
|
let indicator = "";
|
|
106
|
-
if (
|
|
105
|
+
if (usedPercent >= thresholds.danger) {
|
|
107
106
|
color = theme.red;
|
|
108
107
|
indicator = " \uD83D\uDD34";
|
|
109
|
-
} else if (
|
|
108
|
+
} else if (usedPercent >= thresholds.critical) {
|
|
110
109
|
color = theme.peach;
|
|
111
110
|
indicator = " ⚠️";
|
|
112
|
-
} else if (
|
|
111
|
+
} else if (usedPercent >= thresholds.warn) {
|
|
113
112
|
color = theme.yellow;
|
|
114
113
|
}
|
|
115
|
-
const label = config.label ?? "
|
|
114
|
+
const label = config.label ?? "Used";
|
|
116
115
|
const style = config.style ?? "bar";
|
|
117
116
|
let display;
|
|
118
|
-
if (style === "
|
|
119
|
-
display = `${Math.round(
|
|
117
|
+
if (style === "percent") {
|
|
118
|
+
display = `${Math.round(usedPercent)}%`;
|
|
120
119
|
} else if (style === "detailed" && ctx) {
|
|
121
120
|
const totalUsed = ctx.total_input_tokens + ctx.total_output_tokens;
|
|
122
121
|
const windowSize = ctx.context_window_size;
|
|
123
|
-
|
|
124
|
-
display = `${formatTokens(freeTokens)}/${formatTokens(windowSize)} (${Math.round(remainingPercent)}%)`;
|
|
122
|
+
display = `${formatTokens(totalUsed)}/${formatTokens(windowSize)} (${Math.round(usedPercent)}%)`;
|
|
125
123
|
} else if (style === "bar" || style === "both") {
|
|
126
124
|
const windowSize = ctx?.context_window_size || 200000;
|
|
127
125
|
const totalUsed = (ctx?.total_input_tokens || 0) + (ctx?.total_output_tokens || 0);
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
const freeLabel = `${color}free:${formatTokens(freeTokens)}${theme.reset}`;
|
|
133
|
-
const usedLabel = `${theme.overlay0}used:${formatTokens(totalUsed)}${theme.reset}`;
|
|
134
|
-
display = style === "both" ? `${bar} ${freeLabel} ${usedLabel}` : `${bar} ${color}${Math.round(remainingPercent)}%${theme.reset}`;
|
|
126
|
+
const used = Math.round(usedPercent / 10);
|
|
127
|
+
const free = 10 - used;
|
|
128
|
+
const bar = `${color}${"●".repeat(used)}${theme.reset}${"○".repeat(free)}`;
|
|
129
|
+
display = style === "both" ? `${bar} ${color}${formatTokens(totalUsed)}${theme.reset} ${theme.overlay0}/ ${formatTokens(windowSize)}${theme.reset}` : `${bar} ${color}${Math.round(usedPercent)}%${theme.reset}`;
|
|
135
130
|
} else {
|
|
136
|
-
display = `${Math.round(
|
|
131
|
+
display = `${Math.round(usedPercent)}%`;
|
|
137
132
|
}
|
|
138
133
|
let tokenInfo = "";
|
|
139
134
|
if (config.showTokens && ctx) {
|
|
@@ -157,7 +152,7 @@ function renderContext(input, config, theme) {
|
|
|
157
152
|
}
|
|
158
153
|
}
|
|
159
154
|
let hint = "";
|
|
160
|
-
if (config.showCompactHint &&
|
|
155
|
+
if (config.showCompactHint && usedPercent >= 80) {
|
|
161
156
|
hint = " \uD83D\uDCA1/compact";
|
|
162
157
|
}
|
|
163
158
|
const labelStr = label ? `${label} ` : "";
|
|
@@ -719,29 +714,73 @@ function parseMcpOutput(output) {
|
|
|
719
714
|
return servers;
|
|
720
715
|
}
|
|
721
716
|
// src/components/model.ts
|
|
717
|
+
function parseModelId(modelId) {
|
|
718
|
+
const id = modelId.toLowerCase();
|
|
719
|
+
const newFormat = id.match(/^claude-(\w+)-(\d+)-(\d+)-\d+$/);
|
|
720
|
+
if (newFormat) {
|
|
721
|
+
const [, family, major, minor] = newFormat;
|
|
722
|
+
return { family, version: `${major}.${minor}` };
|
|
723
|
+
}
|
|
724
|
+
const newFormatNoMinor = id.match(/^claude-(\w+)-(\d+)-\d{8}$/);
|
|
725
|
+
if (newFormatNoMinor) {
|
|
726
|
+
const [, family, major] = newFormatNoMinor;
|
|
727
|
+
return { family, version: major };
|
|
728
|
+
}
|
|
729
|
+
const oldFormat = id.match(/^claude-(\d+)-(\d+)-(\w+)-\d+$/);
|
|
730
|
+
if (oldFormat) {
|
|
731
|
+
const [, major, minor, family] = oldFormat;
|
|
732
|
+
return { family, version: `${major}.${minor}` };
|
|
733
|
+
}
|
|
734
|
+
const oldFormatNoMinor = id.match(/^claude-(\d+)-(\w+)-\d+$/);
|
|
735
|
+
if (oldFormatNoMinor) {
|
|
736
|
+
const [, major, family] = oldFormatNoMinor;
|
|
737
|
+
return { family, version: major };
|
|
738
|
+
}
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
722
741
|
function renderModel(input, config, theme) {
|
|
723
742
|
if (config.enabled === false) {
|
|
724
743
|
return { text: "" };
|
|
725
744
|
}
|
|
745
|
+
const modelId = input.model?.id ?? "";
|
|
726
746
|
const displayName = input.model?.display_name ?? "";
|
|
727
|
-
const modelName = displayName.toLowerCase();
|
|
728
747
|
const icons = config.icons ?? { opus: "\uD83E\uDDE0", sonnet: "\uD83C\uDFB5", haiku: "⚡" };
|
|
729
748
|
const showIcon = config.showIcon !== false;
|
|
730
749
|
let icon = "\uD83E\uDD16";
|
|
731
750
|
let color = theme.text;
|
|
732
751
|
let modelLabel = displayName;
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
752
|
+
const parsed = parseModelId(modelId);
|
|
753
|
+
if (parsed) {
|
|
754
|
+
const family = parsed.family;
|
|
755
|
+
const version = parsed.version;
|
|
756
|
+
if (family === "opus") {
|
|
757
|
+
icon = icons.opus;
|
|
758
|
+
color = theme.mauve;
|
|
759
|
+
modelLabel = `Opus ${version}`;
|
|
760
|
+
} else if (family === "sonnet") {
|
|
761
|
+
icon = icons.sonnet;
|
|
762
|
+
color = theme.blue;
|
|
763
|
+
modelLabel = `Sonnet ${version}`;
|
|
764
|
+
} else if (family === "haiku") {
|
|
765
|
+
icon = icons.haiku;
|
|
766
|
+
color = theme.green;
|
|
767
|
+
modelLabel = `Haiku ${version}`;
|
|
768
|
+
}
|
|
769
|
+
} else {
|
|
770
|
+
const modelName = displayName.toLowerCase();
|
|
771
|
+
if (modelName.includes("opus")) {
|
|
772
|
+
icon = icons.opus;
|
|
773
|
+
color = theme.mauve;
|
|
774
|
+
modelLabel = "Opus";
|
|
775
|
+
} else if (modelName.includes("sonnet")) {
|
|
776
|
+
icon = icons.sonnet;
|
|
777
|
+
color = theme.blue;
|
|
778
|
+
modelLabel = "Sonnet";
|
|
779
|
+
} else if (modelName.includes("haiku")) {
|
|
780
|
+
icon = icons.haiku;
|
|
781
|
+
color = theme.green;
|
|
782
|
+
modelLabel = "Haiku";
|
|
783
|
+
}
|
|
745
784
|
}
|
|
746
785
|
const label = config.label !== undefined ? config.label : modelLabel;
|
|
747
786
|
const iconStr = showIcon ? `${icon} ` : "";
|
|
@@ -794,6 +833,124 @@ function renderSession(input, config, theme) {
|
|
|
794
833
|
const text = `${labelPart}${parts.join(" ")}`;
|
|
795
834
|
return { text };
|
|
796
835
|
}
|
|
836
|
+
// src/components/skills.ts
|
|
837
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4 } from "node:fs";
|
|
838
|
+
import { homedir as homedir5 } from "node:os";
|
|
839
|
+
import { join as join4 } from "node:path";
|
|
840
|
+
function renderSkills(config, theme) {
|
|
841
|
+
if (config.enabled === false) {
|
|
842
|
+
return { text: "" };
|
|
843
|
+
}
|
|
844
|
+
const summary = getSkillsSummary();
|
|
845
|
+
const label = config.label ?? "Skills";
|
|
846
|
+
const showNames = config.showNames !== false;
|
|
847
|
+
const showCount = config.showCount !== false;
|
|
848
|
+
const maxDisplay = config.maxDisplay ?? Infinity;
|
|
849
|
+
if (summary.total === 0) {
|
|
850
|
+
const text2 = `${theme.mauve}\uD83C\uDFAF ${label} ${theme.overlay0}0${theme.reset}`;
|
|
851
|
+
return { text: text2 };
|
|
852
|
+
}
|
|
853
|
+
const validNames = summary.skills.filter((s) => s.valid).map((s) => s.name ?? s.folder).slice(0, maxDisplay);
|
|
854
|
+
const brokenNames = summary.skills.filter((s) => !s.valid).map((s) => s.folder);
|
|
855
|
+
const parts = [];
|
|
856
|
+
if (showCount) {
|
|
857
|
+
parts.push(`${theme.mauve}${summary.valid}${theme.reset}`);
|
|
858
|
+
}
|
|
859
|
+
if (showNames && validNames.length > 0) {
|
|
860
|
+
const displayNames = validNames.join(",");
|
|
861
|
+
const overflow = summary.valid > maxDisplay ? `+${summary.valid - maxDisplay}` : "";
|
|
862
|
+
parts.push(`${theme.flamingo}${displayNames}${overflow ? ` ${theme.overlay0}${overflow}` : ""}${theme.reset}`);
|
|
863
|
+
}
|
|
864
|
+
let brokenStr = "";
|
|
865
|
+
if (summary.broken > 0) {
|
|
866
|
+
const brokenDisplay = brokenNames.length <= 2 ? brokenNames.join(",") : `${summary.broken} broken`;
|
|
867
|
+
brokenStr = ` ${theme.red}▲${brokenDisplay}${theme.reset}`;
|
|
868
|
+
}
|
|
869
|
+
const text = `${theme.mauve}\uD83C\uDFAF ${label}${theme.reset}${parts.length ? ` ${parts.join(" ")}` : ""}${brokenStr}`;
|
|
870
|
+
return { text };
|
|
871
|
+
}
|
|
872
|
+
function getSkillsSummary() {
|
|
873
|
+
const skills = [];
|
|
874
|
+
const userSkillsPath = join4(homedir5(), ".claude", "skills");
|
|
875
|
+
scanSkillsDirectory(userSkillsPath, skills);
|
|
876
|
+
const projectSkillsPath = join4(process.cwd(), ".claude", "skills");
|
|
877
|
+
scanSkillsDirectory(projectSkillsPath, skills);
|
|
878
|
+
const seen = new Set;
|
|
879
|
+
const deduped = [];
|
|
880
|
+
for (const skill of skills.reverse()) {
|
|
881
|
+
if (!seen.has(skill.folder)) {
|
|
882
|
+
seen.add(skill.folder);
|
|
883
|
+
deduped.push(skill);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
const valid = deduped.filter((s) => s.valid).length;
|
|
887
|
+
const broken = deduped.filter((s) => !s.valid).length;
|
|
888
|
+
return {
|
|
889
|
+
skills: deduped.reverse(),
|
|
890
|
+
valid,
|
|
891
|
+
broken,
|
|
892
|
+
total: deduped.length
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function scanSkillsDirectory(dirPath, skills) {
|
|
896
|
+
if (!existsSync4(dirPath))
|
|
897
|
+
return;
|
|
898
|
+
try {
|
|
899
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
900
|
+
for (const entry of entries) {
|
|
901
|
+
if (!entry.isDirectory())
|
|
902
|
+
continue;
|
|
903
|
+
const skillInfo = validateSkill(dirPath, entry.name);
|
|
904
|
+
skills.push(skillInfo);
|
|
905
|
+
}
|
|
906
|
+
} catch {}
|
|
907
|
+
}
|
|
908
|
+
function validateSkill(skillsDir, folder) {
|
|
909
|
+
const skillPath = join4(skillsDir, folder);
|
|
910
|
+
const skillMdPath = join4(skillPath, "SKILL.md");
|
|
911
|
+
if (!existsSync4(skillMdPath)) {
|
|
912
|
+
return { folder, valid: false, error: "missing_file" };
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
const content = readFileSync4(skillMdPath, "utf-8");
|
|
916
|
+
const frontmatter = parseFrontmatter(content);
|
|
917
|
+
if (!frontmatter) {
|
|
918
|
+
return { folder, valid: false, error: "invalid_frontmatter" };
|
|
919
|
+
}
|
|
920
|
+
if (!frontmatter.name || !frontmatter.description) {
|
|
921
|
+
return { folder, valid: false, error: "missing_fields" };
|
|
922
|
+
}
|
|
923
|
+
return { folder, valid: true, name: frontmatter.name };
|
|
924
|
+
} catch {
|
|
925
|
+
return { folder, valid: false, error: "invalid_frontmatter" };
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function parseFrontmatter(content) {
|
|
929
|
+
const lines = content.split(`
|
|
930
|
+
`);
|
|
931
|
+
if (lines[0]?.trim() !== "---") {
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
934
|
+
let endIndex = -1;
|
|
935
|
+
for (let i = 1;i < lines.length; i++) {
|
|
936
|
+
if (lines[i]?.trim() === "---") {
|
|
937
|
+
endIndex = i;
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (endIndex === -1) {
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
const result = {};
|
|
945
|
+
for (let i = 1;i < endIndex; i++) {
|
|
946
|
+
const line = lines[i];
|
|
947
|
+
const match = line?.match(/^(\w+):\s*["']?(.+?)["']?\s*$/);
|
|
948
|
+
if (match) {
|
|
949
|
+
result[match[1]] = match[2];
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return result;
|
|
953
|
+
}
|
|
797
954
|
// src/components/system.ts
|
|
798
955
|
function formatTokens2(tokens) {
|
|
799
956
|
if (tokens >= 1e6) {
|
|
@@ -840,9 +997,9 @@ function renderSystem(input, config, theme) {
|
|
|
840
997
|
return { text };
|
|
841
998
|
}
|
|
842
999
|
// src/components/tier.ts
|
|
843
|
-
import { existsSync as
|
|
844
|
-
import { homedir as
|
|
845
|
-
import { join as
|
|
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";
|
|
846
1003
|
var cachedTier = null;
|
|
847
1004
|
var cacheTime = 0;
|
|
848
1005
|
var CACHE_TTL3 = 30000;
|
|
@@ -870,10 +1027,10 @@ function detectTier() {
|
|
|
870
1027
|
if (cachedTier && now - cacheTime < CACHE_TTL3) {
|
|
871
1028
|
return cachedTier;
|
|
872
1029
|
}
|
|
873
|
-
const claudeJsonPath =
|
|
1030
|
+
const claudeJsonPath = join5(homedir6(), ".claude.json");
|
|
874
1031
|
try {
|
|
875
|
-
if (
|
|
876
|
-
const content =
|
|
1032
|
+
if (existsSync5(claudeJsonPath)) {
|
|
1033
|
+
const content = readFileSync5(claudeJsonPath, "utf-8");
|
|
877
1034
|
const data = JSON.parse(content);
|
|
878
1035
|
if (data.oauthAccount?.hasExtraUsageEnabled) {
|
|
879
1036
|
cachedTier = "max";
|
|
@@ -924,12 +1081,12 @@ function renderTime(config, theme) {
|
|
|
924
1081
|
return { text };
|
|
925
1082
|
}
|
|
926
1083
|
// src/config.ts
|
|
927
|
-
import { existsSync as
|
|
928
|
-
import { homedir as
|
|
929
|
-
import { join as
|
|
1084
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
|
|
1085
|
+
import { homedir as homedir7 } from "node:os";
|
|
1086
|
+
import { join as join6 } from "node:path";
|
|
930
1087
|
var CONFIG_PATHS = [
|
|
931
|
-
|
|
932
|
-
|
|
1088
|
+
join6(homedir7(), ".config", "claude-pulse", "config.json"),
|
|
1089
|
+
join6(homedir7(), ".claude-pulse.json")
|
|
933
1090
|
];
|
|
934
1091
|
var FIXED_LINES = [
|
|
935
1092
|
{ name: "identity", enabled: true, components: ["name", "cwd"], separator: " " },
|
|
@@ -941,7 +1098,8 @@ var FIXED_LINES = [
|
|
|
941
1098
|
separator: " │ "
|
|
942
1099
|
},
|
|
943
1100
|
{ name: "mcp", enabled: true, components: ["mcp"], separator: " │ " },
|
|
944
|
-
{ name: "hooks", enabled: true, components: ["hooks"], separator: " │ " }
|
|
1101
|
+
{ name: "hooks", enabled: true, components: ["hooks"], separator: " │ " },
|
|
1102
|
+
{ name: "skills", enabled: true, components: ["skills"], separator: " │ " }
|
|
945
1103
|
];
|
|
946
1104
|
var DEFAULT_CONFIG = {
|
|
947
1105
|
theme: "catppuccin",
|
|
@@ -956,7 +1114,7 @@ var DEFAULT_CONFIG = {
|
|
|
956
1114
|
},
|
|
957
1115
|
context: {
|
|
958
1116
|
enabled: true,
|
|
959
|
-
label: "
|
|
1117
|
+
label: "Used",
|
|
960
1118
|
style: "bar",
|
|
961
1119
|
showRate: false,
|
|
962
1120
|
showTokens: true,
|
|
@@ -1003,6 +1161,12 @@ var DEFAULT_CONFIG = {
|
|
|
1003
1161
|
},
|
|
1004
1162
|
cache: {
|
|
1005
1163
|
enabled: true
|
|
1164
|
+
},
|
|
1165
|
+
skills: {
|
|
1166
|
+
enabled: true,
|
|
1167
|
+
label: "Skills",
|
|
1168
|
+
showCount: true,
|
|
1169
|
+
showNames: true
|
|
1006
1170
|
}
|
|
1007
1171
|
},
|
|
1008
1172
|
interactive: {
|
|
@@ -1015,9 +1179,9 @@ var DEFAULT_CONFIG = {
|
|
|
1015
1179
|
};
|
|
1016
1180
|
function loadConfig() {
|
|
1017
1181
|
for (const configPath of CONFIG_PATHS) {
|
|
1018
|
-
if (
|
|
1182
|
+
if (existsSync6(configPath)) {
|
|
1019
1183
|
try {
|
|
1020
|
-
const content =
|
|
1184
|
+
const content = readFileSync6(configPath, "utf-8");
|
|
1021
1185
|
const userConfig = JSON.parse(content);
|
|
1022
1186
|
return mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
1023
1187
|
} catch {}
|
|
@@ -14662,7 +14826,7 @@ var ModelConfigSchema = exports_external.object({
|
|
|
14662
14826
|
var ContextConfigSchema = exports_external.object({
|
|
14663
14827
|
enabled: exports_external.boolean().optional(),
|
|
14664
14828
|
label: exports_external.string().optional(),
|
|
14665
|
-
style: exports_external.enum(["bar", "percent", "both", "detailed"
|
|
14829
|
+
style: exports_external.enum(["bar", "percent", "both", "detailed"]).optional(),
|
|
14666
14830
|
showRate: exports_external.boolean().optional(),
|
|
14667
14831
|
showCompactHint: exports_external.boolean().optional(),
|
|
14668
14832
|
showTokens: exports_external.boolean().optional(),
|
|
@@ -14746,6 +14910,13 @@ var CacheConfigSchema = exports_external.object({
|
|
|
14746
14910
|
showHitRate: exports_external.boolean().optional(),
|
|
14747
14911
|
showTokensSaved: exports_external.boolean().optional()
|
|
14748
14912
|
});
|
|
14913
|
+
var SkillsConfigSchema = exports_external.object({
|
|
14914
|
+
enabled: exports_external.boolean().optional(),
|
|
14915
|
+
label: exports_external.string().optional(),
|
|
14916
|
+
showCount: exports_external.boolean().optional(),
|
|
14917
|
+
showNames: exports_external.boolean().optional(),
|
|
14918
|
+
maxDisplay: exports_external.number().optional()
|
|
14919
|
+
});
|
|
14749
14920
|
var ComponentConfigsSchema = exports_external.object({
|
|
14750
14921
|
tier: TierConfigSchema.optional(),
|
|
14751
14922
|
model: ModelConfigSchema.optional(),
|
|
@@ -14762,7 +14933,8 @@ var ComponentConfigsSchema = exports_external.object({
|
|
|
14762
14933
|
status: exports_external.object({ enabled: exports_external.boolean().optional() }).optional(),
|
|
14763
14934
|
linesChanged: LinesChangedConfigSchema.optional(),
|
|
14764
14935
|
hooks: HooksConfigSchema.optional(),
|
|
14765
|
-
cache: CacheConfigSchema.optional()
|
|
14936
|
+
cache: CacheConfigSchema.optional(),
|
|
14937
|
+
skills: SkillsConfigSchema.optional()
|
|
14766
14938
|
});
|
|
14767
14939
|
var LineOverrideSchema = exports_external.object({
|
|
14768
14940
|
enabled: exports_external.boolean().optional(),
|
|
@@ -14772,7 +14944,8 @@ var LinesConfigSchema = exports_external.object({
|
|
|
14772
14944
|
git: LineOverrideSchema.optional(),
|
|
14773
14945
|
engine: LineOverrideSchema.optional(),
|
|
14774
14946
|
mcp: LineOverrideSchema.optional(),
|
|
14775
|
-
hooks: LineOverrideSchema.optional()
|
|
14947
|
+
hooks: LineOverrideSchema.optional(),
|
|
14948
|
+
skills: LineOverrideSchema.optional()
|
|
14776
14949
|
});
|
|
14777
14950
|
var PulseConfigSchema = exports_external.object({
|
|
14778
14951
|
theme: exports_external.string(),
|
|
@@ -14920,6 +15093,8 @@ function renderComponent(name, input, config2, theme) {
|
|
|
14920
15093
|
return renderHooks(config2.components.hooks ?? {}, theme);
|
|
14921
15094
|
case "cache":
|
|
14922
15095
|
return renderCache(input, config2.components.cache ?? {}, theme);
|
|
15096
|
+
case "skills":
|
|
15097
|
+
return renderSkills(config2.components.skills ?? {}, theme);
|
|
14923
15098
|
default:
|
|
14924
15099
|
return { text: "" };
|
|
14925
15100
|
}
|