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.
Files changed (3) hide show
  1. package/README.md +95 -96
  2. package/dist/cli.js +174 -41
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,38 +1,19 @@
1
- # claude-pulse
1
+ # cc-pulse
2
2
 
3
- A real-time statusline for [Claude Code](https://docs.anthropic.com/en/docs/claude-code).
4
-
5
- ![claude-pulse statusline](assets/demo.png)
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
+ [![npm version](https://img.shields.io/npm/v/cc-pulse)](https://www.npmjs.com/package/cc-pulse)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
16
5
 
17
- Five lines of information, updated on every message:
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
- Context goes from green to red as it approaches compaction. Cost goes from green to red. Failed MCP servers and broken hooks are highlighted immediately.
8
+ ![cc-pulse statusline](assets/demo.png)
28
9
 
29
- ## Install
10
+ ## 🚀 Quick Start
30
11
 
31
12
  ```bash
32
13
  npm install -g cc-pulse
33
14
  ```
34
15
 
35
- Add to your Claude Code settings (`~/.claude/settings.json`):
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. The statusline appears above the input area.
27
+ Restart Claude Code the statusline appears above the input area.
47
28
 
48
- <details>
49
- <summary><strong>Install from source</strong></summary>
29
+ ## ✨ Features
50
30
 
51
- ```bash
52
- git clone https://github.com/ali-nr/claude-pulse.git
53
- cd claude-pulse
54
- bun install
55
- bun run build
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
- Use the full path in settings: `"command": "node /path/to/claude-pulse/dist/cli.js"`
40
+ ## 📊 What You Get
59
41
 
60
- </details>
42
+ Five lines of information, updated on every message:
43
+
44
+ ![cc-pulse statusline](assets/demo.png)
61
45
 
62
- ## Customize
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
- Create `~/.config/claude-pulse/config.json` to override defaults. You only need to include what you want to change.
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>Layout</strong></summary>
59
+ <summary><strong>Context Window</strong></summary>
68
60
 
69
- The 5-line structure is fixed. You can enable/disable lines and change separators:
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
- "lines": {
74
- "hooks": { "enabled": false },
75
- "engine": { "separator": " | " }
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
- | Line | Key | Can toggle |
81
- |------|-----|------------|
82
- | Identity | | No (fixed branding) |
83
- | Git | `git` | Yes |
84
- | Engine | `engine` | Yes |
85
- | MCP | `mcp` | Yes |
86
- | Hooks | `hooks` | Yes |
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>Context window</strong></summary>
91
+ <summary><strong>Subscription Tier</strong></summary>
92
92
 
93
- Shows remaining space until auto-compaction triggers. When it reaches 0%, Claude will compact the conversation.
93
+ Set your plan manually (auto-detection isn't reliable):
94
94
 
95
95
  ```json
96
96
  {
97
97
  "components": {
98
- "context": {
99
- "style": "compact",
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
- | Style | Example |
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 servers</strong></summary>
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": true,
117
+ "showOnlyProblems": false,
128
118
  "maxDisplay": 4
129
119
  }
130
120
  }
131
121
  }
132
122
  ```
133
123
 
134
- - `showNames: true` list each server with status icon
135
- - `showOnlyProblems: true` — hide MCP line when everything is healthy
136
- - Failed/disconnected servers always show in red
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
- | `✓` | Connected |
141
- | `✗` | Disconnected |
142
- | `○` | Disabled |
143
- | `▲` | Error |
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` (default) | `⚡Hooks 8 Submit:3 timezone-context,best-practices Post:2 lint-check` |
164
- | `showNames: false` | `⚡Hooks 8 Submit:3 Post:2 Start:2 End:1` |
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 file paths) always show in red with `▲`.
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
- Colors change automatically: green < $1, yellow $1-$2, peach $2-$5, red > $5. Burn rate appears after the session is longer than 1 minute.
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>Subscription tier</strong></summary>
182
+ <summary><strong>Layout</strong></summary>
191
183
 
192
- Disabled by default. There's no official way to detect your plan, so set it manually:
184
+ The 5-line structure is fixed. You can toggle lines and change separators:
193
185
 
194
186
  ```json
195
187
  {
196
- "components": {
197
- "tier": {
198
- "enabled": true,
199
- "override": "max"
200
- }
188
+ "lines": {
189
+ "hooks": { "enabled": false },
190
+ "engine": { "separator": " | " }
201
191
  }
202
192
  }
203
193
  ```
204
194
 
205
- Options: `"pro"`, `"max"`, `"api"`.
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>Other components</strong></summary>
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 percentage |
217
- | `linesChanged` | Shows `+added -removed` lines changed |
218
- | `time` | `format: "12h"` or `"24h"`, `showTimezone: true` |
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
- bun install # Install dependencies
228
- bun run build # Build to dist/
229
- bun run lint # Run Biome linter
230
- bun run dev # Watch mode
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.1",
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 (remainingPercent <= 100 - thresholds.danger) {
105
+ if (usedPercent >= thresholds.danger) {
107
106
  color = theme.red;
108
107
  indicator = " \uD83D\uDD34";
109
- } else if (remainingPercent <= 100 - thresholds.critical) {
108
+ } else if (usedPercent >= thresholds.critical) {
110
109
  color = theme.peach;
111
110
  indicator = " ⚠️";
112
- } else if (remainingPercent <= 100 - thresholds.warn) {
111
+ } else if (usedPercent >= thresholds.warn) {
113
112
  color = theme.yellow;
114
113
  }
115
- const label = config.label ?? "→Compact";
114
+ const label = config.label ?? "Used";
116
115
  const style = config.style ?? "bar";
117
116
  let display;
118
- if (style === "compact") {
119
- display = `${Math.round(remainingPercent)}%`;
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
- const freeTokens = Math.max(0, windowSize - totalUsed);
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 freeTokens = Math.max(0, windowSize - totalUsed);
129
- const remaining = Math.round(remainingPercent / 10);
130
- const depleted = 10 - remaining;
131
- const bar = `${color}${"●".repeat(remaining)}${theme.reset}${"○".repeat(depleted)}`;
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(remainingPercent)}%`;
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 && remainingPercent <= 20) {
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 ?? "\uD83E\uDDE9 MCP";
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 existsSync4, readFileSync as readFileSync4 } from "node:fs";
843
- import { homedir as homedir5 } from "node:os";
844
- import { join as join4 } from "node:path";
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 text = `${color}${theme.bold}${label}${theme.reset}`;
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 = join4(homedir5(), ".claude.json");
986
+ const claudeJsonPath = join5(homedir6(), ".claude.json");
872
987
  try {
873
- if (existsSync4(claudeJsonPath)) {
874
- const content = readFileSync4(claudeJsonPath, "utf-8");
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 existsSync5, readFileSync as readFileSync5 } from "node:fs";
926
- import { homedir as homedir6 } from "node:os";
927
- import { join as join5 } from "node:path";
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
- join5(homedir6(), ".config", "claude-pulse", "config.json"),
930
- join5(homedir6(), ".claude-pulse.json")
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: "→Compact",
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 (existsSync5(configPath)) {
1138
+ if (existsSync6(configPath)) {
1017
1139
  try {
1018
- const content = readFileSync5(configPath, "utf-8");
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", "compact"]).optional(),
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-pulse",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "A customizable, real-time statusline for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {