cc-pulse 1.0.1 → 1.1.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 +174 -41
- 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.1.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} ${Math.round(remainingPercent)}%`;
|
|
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} ` : "";
|
|
@@ -354,9 +349,10 @@ function renderBranch(config, theme) {
|
|
|
354
349
|
if (!git.branch) {
|
|
355
350
|
return { text: "" };
|
|
356
351
|
}
|
|
352
|
+
const icon = "⎇ ";
|
|
357
353
|
const label = config.label ?? "";
|
|
358
354
|
const labelStr = label ? `${theme.teal}${label} ${theme.reset}` : "";
|
|
359
|
-
const text = `${labelStr}${theme.teal}${git.branch}${theme.reset}`;
|
|
355
|
+
const text = `${theme.teal}${icon}${theme.reset}${labelStr}${theme.teal}${git.branch}${theme.reset}`;
|
|
360
356
|
return { text };
|
|
361
357
|
}
|
|
362
358
|
function renderStatus(config, theme) {
|
|
@@ -552,7 +548,7 @@ function renderMcp(config, theme) {
|
|
|
552
548
|
if (config.enabled === false) {
|
|
553
549
|
return { text: "" };
|
|
554
550
|
}
|
|
555
|
-
const label = config.label ?? "
|
|
551
|
+
const label = config.label ?? "⬢ MCP";
|
|
556
552
|
const icons = config.icons ?? {
|
|
557
553
|
connected: "✓",
|
|
558
554
|
disconnected: "✗",
|
|
@@ -793,6 +789,124 @@ function renderSession(input, config, theme) {
|
|
|
793
789
|
const text = `${labelPart}${parts.join(" ")}`;
|
|
794
790
|
return { text };
|
|
795
791
|
}
|
|
792
|
+
// src/components/skills.ts
|
|
793
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4 } from "node:fs";
|
|
794
|
+
import { homedir as homedir5 } from "node:os";
|
|
795
|
+
import { join as join4 } from "node:path";
|
|
796
|
+
function renderSkills(config, theme) {
|
|
797
|
+
if (config.enabled === false) {
|
|
798
|
+
return { text: "" };
|
|
799
|
+
}
|
|
800
|
+
const summary = getSkillsSummary();
|
|
801
|
+
const label = config.label ?? "Skills";
|
|
802
|
+
const showNames = config.showNames !== false;
|
|
803
|
+
const showCount = config.showCount !== false;
|
|
804
|
+
const maxDisplay = config.maxDisplay ?? Infinity;
|
|
805
|
+
if (summary.total === 0) {
|
|
806
|
+
const text2 = `${theme.mauve}\uD83C\uDFAF ${label} ${theme.overlay0}0${theme.reset}`;
|
|
807
|
+
return { text: text2 };
|
|
808
|
+
}
|
|
809
|
+
const validNames = summary.skills.filter((s) => s.valid).map((s) => s.name ?? s.folder).slice(0, maxDisplay);
|
|
810
|
+
const brokenNames = summary.skills.filter((s) => !s.valid).map((s) => s.folder);
|
|
811
|
+
const parts = [];
|
|
812
|
+
if (showCount) {
|
|
813
|
+
parts.push(`${theme.mauve}${summary.valid}${theme.reset}`);
|
|
814
|
+
}
|
|
815
|
+
if (showNames && validNames.length > 0) {
|
|
816
|
+
const displayNames = validNames.join(",");
|
|
817
|
+
const overflow = summary.valid > maxDisplay ? `+${summary.valid - maxDisplay}` : "";
|
|
818
|
+
parts.push(`${theme.flamingo}${displayNames}${overflow ? ` ${theme.overlay0}${overflow}` : ""}${theme.reset}`);
|
|
819
|
+
}
|
|
820
|
+
let brokenStr = "";
|
|
821
|
+
if (summary.broken > 0) {
|
|
822
|
+
const brokenDisplay = brokenNames.length <= 2 ? brokenNames.join(",") : `${summary.broken} broken`;
|
|
823
|
+
brokenStr = ` ${theme.red}▲${brokenDisplay}${theme.reset}`;
|
|
824
|
+
}
|
|
825
|
+
const text = `${theme.mauve}\uD83C\uDFAF ${label}${theme.reset} ${parts.join(" ")}${brokenStr}`;
|
|
826
|
+
return { text };
|
|
827
|
+
}
|
|
828
|
+
function getSkillsSummary() {
|
|
829
|
+
const skills = [];
|
|
830
|
+
const userSkillsPath = join4(homedir5(), ".claude", "skills");
|
|
831
|
+
scanSkillsDirectory(userSkillsPath, skills);
|
|
832
|
+
const projectSkillsPath = join4(process.cwd(), ".claude", "skills");
|
|
833
|
+
scanSkillsDirectory(projectSkillsPath, skills);
|
|
834
|
+
const seen = new Set;
|
|
835
|
+
const deduped = [];
|
|
836
|
+
for (const skill of skills.reverse()) {
|
|
837
|
+
if (!seen.has(skill.folder)) {
|
|
838
|
+
seen.add(skill.folder);
|
|
839
|
+
deduped.push(skill);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
const valid = deduped.filter((s) => s.valid).length;
|
|
843
|
+
const broken = deduped.filter((s) => !s.valid).length;
|
|
844
|
+
return {
|
|
845
|
+
skills: deduped.reverse(),
|
|
846
|
+
valid,
|
|
847
|
+
broken,
|
|
848
|
+
total: deduped.length
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
function scanSkillsDirectory(dirPath, skills) {
|
|
852
|
+
if (!existsSync4(dirPath))
|
|
853
|
+
return;
|
|
854
|
+
try {
|
|
855
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
856
|
+
for (const entry of entries) {
|
|
857
|
+
if (!entry.isDirectory())
|
|
858
|
+
continue;
|
|
859
|
+
const skillInfo = validateSkill(dirPath, entry.name);
|
|
860
|
+
skills.push(skillInfo);
|
|
861
|
+
}
|
|
862
|
+
} catch {}
|
|
863
|
+
}
|
|
864
|
+
function validateSkill(skillsDir, folder) {
|
|
865
|
+
const skillPath = join4(skillsDir, folder);
|
|
866
|
+
const skillMdPath = join4(skillPath, "SKILL.md");
|
|
867
|
+
if (!existsSync4(skillMdPath)) {
|
|
868
|
+
return { folder, valid: false, error: "missing_file" };
|
|
869
|
+
}
|
|
870
|
+
try {
|
|
871
|
+
const content = readFileSync4(skillMdPath, "utf-8");
|
|
872
|
+
const frontmatter = parseFrontmatter(content);
|
|
873
|
+
if (!frontmatter) {
|
|
874
|
+
return { folder, valid: false, error: "invalid_frontmatter" };
|
|
875
|
+
}
|
|
876
|
+
if (!frontmatter.name || !frontmatter.description) {
|
|
877
|
+
return { folder, valid: false, error: "missing_fields" };
|
|
878
|
+
}
|
|
879
|
+
return { folder, valid: true, name: frontmatter.name };
|
|
880
|
+
} catch {
|
|
881
|
+
return { folder, valid: false, error: "invalid_frontmatter" };
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
function parseFrontmatter(content) {
|
|
885
|
+
const lines = content.split(`
|
|
886
|
+
`);
|
|
887
|
+
if (lines[0]?.trim() !== "---") {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
let endIndex = -1;
|
|
891
|
+
for (let i = 1;i < lines.length; i++) {
|
|
892
|
+
if (lines[i]?.trim() === "---") {
|
|
893
|
+
endIndex = i;
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
if (endIndex === -1) {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
const result = {};
|
|
901
|
+
for (let i = 1;i < endIndex; i++) {
|
|
902
|
+
const line = lines[i];
|
|
903
|
+
const match = line?.match(/^(\w+):\s*["']?(.+?)["']?\s*$/);
|
|
904
|
+
if (match) {
|
|
905
|
+
result[match[1]] = match[2];
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return result;
|
|
909
|
+
}
|
|
796
910
|
// src/components/system.ts
|
|
797
911
|
function formatTokens2(tokens) {
|
|
798
912
|
if (tokens >= 1e6) {
|
|
@@ -839,9 +953,9 @@ function renderSystem(input, config, theme) {
|
|
|
839
953
|
return { text };
|
|
840
954
|
}
|
|
841
955
|
// src/components/tier.ts
|
|
842
|
-
import { existsSync as
|
|
843
|
-
import { homedir as
|
|
844
|
-
import { join as
|
|
956
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
957
|
+
import { homedir as homedir6 } from "node:os";
|
|
958
|
+
import { join as join5 } from "node:path";
|
|
845
959
|
var cachedTier = null;
|
|
846
960
|
var cacheTime = 0;
|
|
847
961
|
var CACHE_TTL3 = 30000;
|
|
@@ -860,7 +974,8 @@ function renderTier(config, theme) {
|
|
|
860
974
|
color = theme.green;
|
|
861
975
|
label = labels.api;
|
|
862
976
|
}
|
|
863
|
-
const
|
|
977
|
+
const icon = "◆ ";
|
|
978
|
+
const text = `${color}${theme.bold}${icon}${label}${theme.reset}`;
|
|
864
979
|
return { text, action: "/usage" };
|
|
865
980
|
}
|
|
866
981
|
function detectTier() {
|
|
@@ -868,10 +983,10 @@ function detectTier() {
|
|
|
868
983
|
if (cachedTier && now - cacheTime < CACHE_TTL3) {
|
|
869
984
|
return cachedTier;
|
|
870
985
|
}
|
|
871
|
-
const claudeJsonPath =
|
|
986
|
+
const claudeJsonPath = join5(homedir6(), ".claude.json");
|
|
872
987
|
try {
|
|
873
|
-
if (
|
|
874
|
-
const content =
|
|
988
|
+
if (existsSync5(claudeJsonPath)) {
|
|
989
|
+
const content = readFileSync5(claudeJsonPath, "utf-8");
|
|
875
990
|
const data = JSON.parse(content);
|
|
876
991
|
if (data.oauthAccount?.hasExtraUsageEnabled) {
|
|
877
992
|
cachedTier = "max";
|
|
@@ -922,12 +1037,12 @@ function renderTime(config, theme) {
|
|
|
922
1037
|
return { text };
|
|
923
1038
|
}
|
|
924
1039
|
// src/config.ts
|
|
925
|
-
import { existsSync as
|
|
926
|
-
import { homedir as
|
|
927
|
-
import { join as
|
|
1040
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
|
|
1041
|
+
import { homedir as homedir7 } from "node:os";
|
|
1042
|
+
import { join as join6 } from "node:path";
|
|
928
1043
|
var CONFIG_PATHS = [
|
|
929
|
-
|
|
930
|
-
|
|
1044
|
+
join6(homedir7(), ".config", "claude-pulse", "config.json"),
|
|
1045
|
+
join6(homedir7(), ".claude-pulse.json")
|
|
931
1046
|
];
|
|
932
1047
|
var FIXED_LINES = [
|
|
933
1048
|
{ name: "identity", enabled: true, components: ["name", "cwd"], separator: " " },
|
|
@@ -939,7 +1054,8 @@ var FIXED_LINES = [
|
|
|
939
1054
|
separator: " │ "
|
|
940
1055
|
},
|
|
941
1056
|
{ name: "mcp", enabled: true, components: ["mcp"], separator: " │ " },
|
|
942
|
-
{ name: "hooks", enabled: true, components: ["hooks"], separator: " │ " }
|
|
1057
|
+
{ name: "hooks", enabled: true, components: ["hooks"], separator: " │ " },
|
|
1058
|
+
{ name: "skills", enabled: true, components: ["skills"], separator: " │ " }
|
|
943
1059
|
];
|
|
944
1060
|
var DEFAULT_CONFIG = {
|
|
945
1061
|
theme: "catppuccin",
|
|
@@ -954,7 +1070,7 @@ var DEFAULT_CONFIG = {
|
|
|
954
1070
|
},
|
|
955
1071
|
context: {
|
|
956
1072
|
enabled: true,
|
|
957
|
-
label: "
|
|
1073
|
+
label: "Used",
|
|
958
1074
|
style: "bar",
|
|
959
1075
|
showRate: false,
|
|
960
1076
|
showTokens: true,
|
|
@@ -974,7 +1090,7 @@ var DEFAULT_CONFIG = {
|
|
|
974
1090
|
},
|
|
975
1091
|
mcp: {
|
|
976
1092
|
enabled: true,
|
|
977
|
-
label: "MCP",
|
|
1093
|
+
label: "⬢ MCP",
|
|
978
1094
|
showNames: true,
|
|
979
1095
|
showOnlyProblems: false,
|
|
980
1096
|
style: "auto",
|
|
@@ -1001,6 +1117,12 @@ var DEFAULT_CONFIG = {
|
|
|
1001
1117
|
},
|
|
1002
1118
|
cache: {
|
|
1003
1119
|
enabled: true
|
|
1120
|
+
},
|
|
1121
|
+
skills: {
|
|
1122
|
+
enabled: true,
|
|
1123
|
+
label: "Skills",
|
|
1124
|
+
showCount: true,
|
|
1125
|
+
showNames: true
|
|
1004
1126
|
}
|
|
1005
1127
|
},
|
|
1006
1128
|
interactive: {
|
|
@@ -1013,9 +1135,9 @@ var DEFAULT_CONFIG = {
|
|
|
1013
1135
|
};
|
|
1014
1136
|
function loadConfig() {
|
|
1015
1137
|
for (const configPath of CONFIG_PATHS) {
|
|
1016
|
-
if (
|
|
1138
|
+
if (existsSync6(configPath)) {
|
|
1017
1139
|
try {
|
|
1018
|
-
const content =
|
|
1140
|
+
const content = readFileSync6(configPath, "utf-8");
|
|
1019
1141
|
const userConfig = JSON.parse(content);
|
|
1020
1142
|
return mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
1021
1143
|
} catch {}
|
|
@@ -14660,7 +14782,7 @@ var ModelConfigSchema = exports_external.object({
|
|
|
14660
14782
|
var ContextConfigSchema = exports_external.object({
|
|
14661
14783
|
enabled: exports_external.boolean().optional(),
|
|
14662
14784
|
label: exports_external.string().optional(),
|
|
14663
|
-
style: exports_external.enum(["bar", "percent", "both", "detailed"
|
|
14785
|
+
style: exports_external.enum(["bar", "percent", "both", "detailed"]).optional(),
|
|
14664
14786
|
showRate: exports_external.boolean().optional(),
|
|
14665
14787
|
showCompactHint: exports_external.boolean().optional(),
|
|
14666
14788
|
showTokens: exports_external.boolean().optional(),
|
|
@@ -14744,6 +14866,13 @@ var CacheConfigSchema = exports_external.object({
|
|
|
14744
14866
|
showHitRate: exports_external.boolean().optional(),
|
|
14745
14867
|
showTokensSaved: exports_external.boolean().optional()
|
|
14746
14868
|
});
|
|
14869
|
+
var SkillsConfigSchema = exports_external.object({
|
|
14870
|
+
enabled: exports_external.boolean().optional(),
|
|
14871
|
+
label: exports_external.string().optional(),
|
|
14872
|
+
showCount: exports_external.boolean().optional(),
|
|
14873
|
+
showNames: exports_external.boolean().optional(),
|
|
14874
|
+
maxDisplay: exports_external.number().optional()
|
|
14875
|
+
});
|
|
14747
14876
|
var ComponentConfigsSchema = exports_external.object({
|
|
14748
14877
|
tier: TierConfigSchema.optional(),
|
|
14749
14878
|
model: ModelConfigSchema.optional(),
|
|
@@ -14760,7 +14889,8 @@ var ComponentConfigsSchema = exports_external.object({
|
|
|
14760
14889
|
status: exports_external.object({ enabled: exports_external.boolean().optional() }).optional(),
|
|
14761
14890
|
linesChanged: LinesChangedConfigSchema.optional(),
|
|
14762
14891
|
hooks: HooksConfigSchema.optional(),
|
|
14763
|
-
cache: CacheConfigSchema.optional()
|
|
14892
|
+
cache: CacheConfigSchema.optional(),
|
|
14893
|
+
skills: SkillsConfigSchema.optional()
|
|
14764
14894
|
});
|
|
14765
14895
|
var LineOverrideSchema = exports_external.object({
|
|
14766
14896
|
enabled: exports_external.boolean().optional(),
|
|
@@ -14770,7 +14900,8 @@ var LinesConfigSchema = exports_external.object({
|
|
|
14770
14900
|
git: LineOverrideSchema.optional(),
|
|
14771
14901
|
engine: LineOverrideSchema.optional(),
|
|
14772
14902
|
mcp: LineOverrideSchema.optional(),
|
|
14773
|
-
hooks: LineOverrideSchema.optional()
|
|
14903
|
+
hooks: LineOverrideSchema.optional(),
|
|
14904
|
+
skills: LineOverrideSchema.optional()
|
|
14774
14905
|
});
|
|
14775
14906
|
var PulseConfigSchema = exports_external.object({
|
|
14776
14907
|
theme: exports_external.string(),
|
|
@@ -14918,6 +15049,8 @@ function renderComponent(name, input, config2, theme) {
|
|
|
14918
15049
|
return renderHooks(config2.components.hooks ?? {}, theme);
|
|
14919
15050
|
case "cache":
|
|
14920
15051
|
return renderCache(input, config2.components.cache ?? {}, theme);
|
|
15052
|
+
case "skills":
|
|
15053
|
+
return renderSkills(config2.components.skills ?? {}, theme);
|
|
14921
15054
|
default:
|
|
14922
15055
|
return { text: "" };
|
|
14923
15056
|
}
|