Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- **Status bar sparkline** (`claudeStatus.statusBar.showSparkline`, default `false`) —
show a compact utilization meter (`▰▰▰▱▱`) next to the 5h percentage so remaining
capacity is visible at a glance. Custom status bar formats gain matching
`${bar5h}` / `${bar7d}` placeholders.

---

## [0.6.2] — 2026-06-13
Expand Down
1 change: 1 addition & 0 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ npm run package # → vscode-claude-status-*.vsix
| `claudeStatus.statusBar.alignment` | `"left"` \| `"right"` | `"left"` | ステータスバーの位置 |
| `claudeStatus.statusBar.showProjectCost` | `boolean` | `true` | ステータスバーにプロジェクトコストを表示 |
| `claudeStatus.statusBar.format` | `string \| null` | `null` | カスタムラベルテンプレート。プレースホルダー: `${usage5h}`, `${usage7d}`, `${cost5h}`, `${cost7d}`, `${costDay}`。`null` = デフォルト自動生成 |
| `claudeStatus.statusBar.showSparkline` | `boolean` | `false` | ステータスバーにコンパクトな 5h 利用率メーター(▰▰▰▱▱)を表示 |
| `claudeStatus.cache.ttlSeconds` | `number` (60–3600) | `300` | APIキャッシュTTL(秒) |
| `claudeStatus.rateLimitApi.enabled` | `boolean` | `true` | レート制限%をAnthropicAPIから取得。`false` で新規APIコール停止。キャッシュがあれば `[Xm ago]` 付きで % を継続表示 |
| `claudeStatus.realtime.enabled` | `boolean` | `false` | TTL秒ごとにAPIをポーリング(`rateLimitApi.enabled` が必要) |
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ All settings are under the `claudeStatus` namespace in VS Code Settings.
| `claudeStatus.statusBar.alignment` | `"left"` \| `"right"` | `"left"` | Status bar position |
| `claudeStatus.statusBar.showProjectCost` | `boolean` | `true` | Show project cost in status bar |
| `claudeStatus.statusBar.format` | `string \| null` | `null` | Custom label template. Placeholders: `${usage5h}`, `${usage7d}`, `${cost5h}`, `${cost7d}`, `${costDay}`. `null` = default auto-generated label |
| `claudeStatus.statusBar.showSparkline` | `boolean` | `false` | Show a compact 5h utilization meter (▰▰▰▱▱) in the status bar |
| `claudeStatus.cache.ttlSeconds` | `number` (60–3600) | `300` | API cache TTL in seconds |
| `claudeStatus.rateLimitApi.enabled` | `boolean` | `true` | Fetch rate-limit % from Anthropic API. When disabled, no new API calls are made; cached % is still shown with a staleness indicator (`[Xm ago]`) if available |
| `claudeStatus.realtime.enabled` | `boolean` | `false` | Poll rate-limit API every TTL seconds (requires `rateLimitApi.enabled`) |
Expand Down
1 change: 1 addition & 0 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ npm run package # → vscode-claude-status-*.vsix
| `claudeStatus.statusBar.alignment` | `"left"` \| `"right"` | `"left"` | 状态栏位置 |
| `claudeStatus.statusBar.showProjectCost` | `boolean` | `true` | 在状态栏显示项目费用 |
| `claudeStatus.statusBar.format` | `string \| null` | `null` | 自定义标签模板。占位符:`${usage5h}`, `${usage7d}`, `${cost5h}`, `${cost7d}`, `${costDay}`。`null` = 默认自动生成 |
| `claudeStatus.statusBar.showSparkline` | `boolean` | `false` | 在状态栏显示紧凑的 5h 利用率仪表(▰▰▰▱▱) |
| `claudeStatus.cache.ttlSeconds` | `number` (60–3600) | `300` | API 缓存 TTL(秒) |
| `claudeStatus.rateLimitApi.enabled` | `boolean` | `true` | 从 Anthropic API 获取速率限制 %。禁用时停止新的 API 调用;若有缓存,仍以 `[Xm ago]` 显示 % |
| `claudeStatus.realtime.enabled` | `boolean` | `false` | 每隔 TTL 秒轮询速率限制 API(需启用 `rateLimitApi.enabled`) |
Expand Down
10 changes: 9 additions & 1 deletion docs/SETTINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,13 @@ What the status bar and dashboard show, depending on your Claude plan and key se
"claudeStatus.statusBar.format": {
"type": ["string", "null"],
"default": null,
"description": "Custom status bar format string. Use placeholders: ${usage5h}, ${usage7d}, ${cost5h}, ${cost7d}, ${costDay}. null = default auto-generated label."
"description": "Custom status bar format string. Use placeholders: ${usage5h}, ${usage7d}, ${bar5h}, ${bar7d}, ${cost5h}, ${cost7d}, ${costDay}. null = default auto-generated label."
},

"claudeStatus.statusBar.showSparkline": {
"type": "boolean",
"default": false,
"description": "Show a compact utilization meter (▰▰▰▱▱) next to the 5h percentage in the status bar (percent mode, Claude.ai only). Custom formats can use the ${bar5h} / ${bar7d} placeholders instead."
},

"claudeStatus.cache.ttlSeconds": {
Expand Down Expand Up @@ -203,6 +209,8 @@ Set `claudeStatus.statusBar.format` to a template string to replace the default
|-------------|------|----------------|-------|
| `${usage5h}` | integer 0–100 | `35` | 5h rate-limit utilization. Always `0` for non-claude-ai providers. |
| `${usage7d}` | integer 0–100 | `72` | 7d rate-limit utilization. `0` if no 7d window. |
| `${bar5h}` | 5-char meter | `▰▰▱▱▱` | 5h utilization as a compact meter (rounded to nearest fifth). |
| `${bar7d}` | 5-char meter | `▰▰▰▰▱` | 7d utilization as a compact meter. All-empty if no 7d window. |
| `${cost5h}` | decimal string | `1.23` | Local token cost in the last 5h (USD, 2 decimal places). |
| `${cost7d}` | decimal string | `5.67` | Local token cost in the last 7 days (USD, 2 decimal places). |
| `${costDay}` | decimal string | `2.34` | Local token cost today (USD, 2 decimal places). |
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@
"default": null,
"description": "%config.statusBar.format.desc%"
},
"claudeStatus.statusBar.showSparkline": {
"type": "boolean",
"default": false,
"description": "%config.statusBar.showSparkline.desc%"
},
"claudeStatus.cache.ttlSeconds": {
"type": "number",
"default": 300,
Expand Down
1 change: 1 addition & 0 deletions package.nls.ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"config.statusBar.alignment.desc": "ステータスバーアイテムの位置。",
"config.statusBar.showProjectCost.desc": "ステータスバーに現在のプロジェクトコストを表示する。",
"config.statusBar.format.desc": "ステータスバーのカスタム書式文字列。使用可能なプレースホルダー: ${usage5h}(5h 利用率 0-100)、${usage7d}(7d 利用率 0-100)、${cost5h}(5h コスト、例: 1.23)、${cost7d}(7d コスト)、${costDay}(本日のコスト)。null でデフォルト書式を使用します。",
"config.statusBar.showSparkline.desc": "ステータスバーの 5h パーセンテージの横にコンパクトな利用率メーター(▰▰▰▱▱)を表示します(percent モード、Claude.ai のみ)。カスタム書式では代わりに ${bar5h} / ${bar7d} プレースホルダを使用できます。",
"config.cache.ttlSeconds.desc": "キャッシュの有効期間(秒)。Claude Code がアイドル中はこの間隔より頻繁に API を呼び出しません。",
"config.rateLimitApi.enabled.desc": "Anthropic API からレート制限の利用率(5h/7d %)を取得する。無効にすると、ローカルのトークンコストのみ表示されます(レート制限の割合は非表示)。デフォルト: true — Claude Code が実行中のときのみ API を呼び出すため消費は無視できる程度です。",
"config.realtime.enabled.desc": "Claude Code のアクティビティに関係なく、cache.ttlSeconds ごとにレート制限 API をポーリングします。rateLimitApi.enabled が true である必要があります。API 使用量が増加します。",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"config.statusBar.alignment.desc": "Position of the status bar item.",
"config.statusBar.showProjectCost.desc": "Show current project cost in the status bar.",
"config.statusBar.format.desc": "Custom status bar format string. Use placeholders: ${usage5h} (5h utilization 0-100), ${usage7d} (7d utilization 0-100), ${cost5h} (5h cost, e.g. 1.23), ${cost7d} (7d cost), ${costDay} (today's cost). Set to null to use the default format.",
"config.statusBar.showSparkline.desc": "Show a compact utilization meter (▰▰▰▱▱) next to the 5h percentage in the status bar (percent mode, Claude.ai only). Custom formats can use the ${bar5h} / ${bar7d} placeholders instead.",
"config.cache.ttlSeconds.desc": "Cache TTL in seconds. API is not called more often than this when Claude Code is idle.",
"config.rateLimitApi.enabled.desc": "Enable fetching rate-limit utilization (5h/7d %) from the Anthropic API. When disabled, only local token cost is shown (no rate-limit percentages). Default: true — API consumption is negligible because calls are made only when Claude Code is actively running.",
"config.realtime.enabled.desc": "Poll the rate-limit API every cache.ttlSeconds regardless of Claude Code activity. Requires rateLimitApi.enabled to be true. Increases API usage.",
Expand Down
1 change: 1 addition & 0 deletions package.nls.zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"config.statusBar.alignment.desc": "状态栏项目的位置。",
"config.statusBar.showProjectCost.desc": "在状态栏中显示当前项目费用。",
"config.statusBar.format.desc": "状态栏自定义格式字符串。可用占位符:${usage5h}(5h 利用率 0-100)、${usage7d}(7d 利用率 0-100)、${cost5h}(5h 费用,例如 1.23)、${cost7d}(7d 费用)、${costDay}(今日费用)。设为 null 使用默认格式。",
"config.statusBar.showSparkline.desc": "在状态栏 5h 百分比旁显示紧凑的利用率仪表(▰▰▰▱▱)(percent 模式,仅 Claude.ai)。自定义格式可改用 ${bar5h} / ${bar7d} 占位符。",
"config.cache.ttlSeconds.desc": "缓存有效期(秒)。Claude Code 空闲时,API 调用频率不会超过此间隔。",
"config.rateLimitApi.enabled.desc": "启用从 Anthropic API 获取速率限制利用率(5h/7d %)。禁用时,仅显示本地令牌费用(不显示速率限制百分比)。默认值:true — 仅在 Claude Code 活跃运行时才调用 API,消耗可忽略不计。",
"config.realtime.enabled.desc": "无论 Claude Code 活动状态如何,每隔 cache.ttlSeconds 轮询速率限制 API。需要 rateLimitApi.enabled 为 true。会增加 API 使用量。",
Expand Down
4 changes: 4 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export class ExtensionConfig {
return this.cfg.get('statusBar.format', null);
}

get showSparkline(): boolean {
return this.cfg.get('statusBar.showSparkline', false);
}

get cacheTtlSeconds(): number {
return this.cfg.get('cache.ttlSeconds', 300);
}
Expand Down
12 changes: 11 additions & 1 deletion src/statusBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ function buildBar(utilization: number, width: number): string {
return 'X'.repeat(filled) + '.'.repeat(width - filled);
}

// Compact utilization meter for the status bar label (e.g. "▰▰▰▱▱" at 60%)
export function buildUtilizationBar(utilization: number, width = 5): string {
const clamped = Math.min(1, Math.max(0, utilization));
const filled = Math.round(clamped * width);
return '▰'.repeat(filled) + '▱'.repeat(width - filled);
}

function truncateName(name: string): string {
return name.length > 12 ? name.slice(0, 11) + '…' : name;
}
Expand All @@ -42,6 +49,8 @@ function applyFormat(format: string, data: ClaudeUsageData): string {
return format
.replace(/\$\{usage5h\}/g, `${Math.round(utilization5h * 100)}`)
.replace(/\$\{usage7d\}/g, `${Math.round(utilization7d * 100)}`)
.replace(/\$\{bar5h\}/g, buildUtilizationBar(utilization5h))
.replace(/\$\{bar7d\}/g, buildUtilizationBar(utilization7d))
.replace(/\$\{cost5h\}/g, cost5h.toFixed(2))
.replace(/\$\{cost7d\}/g, cost7d.toFixed(2))
.replace(/\$\{costDay\}/g, costDay.toFixed(2));
Expand Down Expand Up @@ -81,8 +90,9 @@ export function buildLabel(data: ClaudeUsageData, projectCosts: ProjectCostData[
part5h = `5h:100%✗`;
part7d = '';
} else {
const spark5h = config.showSparkline ? `${buildUtilizationBar(utilization5h)} ` : '';
const warn5h = utilization5h >= 0.75 ? '⚠' : '';
part5h = `5h:${formatPercent(utilization5h)}${warn5h}`;
part5h = `5h:${spark5h}${formatPercent(utilization5h)}${warn5h}`;
if (has7dLimit) {
const warn7d = utilization7d >= 0.75 ? '⚠' : '';
part7d = ` 7d:${formatPercent(utilization7d)}${warn7d}`;
Expand Down
26 changes: 25 additions & 1 deletion src/test/suite/statusBar.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as assert from 'assert';
import { buildLabel, buildTooltip, formatDuration } from '../../statusBar';
import { buildLabel, buildTooltip, buildUtilizationBar, formatDuration } from '../../statusBar';
import { ClaudeUsageData } from '../../data/dataManager';

function makeData(overrides: Partial<ClaudeUsageData> = {}): ClaudeUsageData {
Expand Down Expand Up @@ -54,6 +54,30 @@ suite('formatDuration', () => {
});
});

suite('buildUtilizationBar', () => {
test('renders empty, partial, and full bars', () => {
assert.strictEqual(buildUtilizationBar(0), '▱▱▱▱▱');
assert.strictEqual(buildUtilizationBar(0.6), '▰▰▰▱▱');
assert.strictEqual(buildUtilizationBar(1), '▰▰▰▰▰');
});

test('rounds to the nearest cell', () => {
assert.strictEqual(buildUtilizationBar(0.09), '▱▱▱▱▱'); // 0.45 cells → 0
assert.strictEqual(buildUtilizationBar(0.11), '▰▱▱▱▱'); // 0.55 cells → 1
assert.strictEqual(buildUtilizationBar(0.5), '▰▰▰▱▱'); // 2.5 cells → 3 (round half up)
});

test('clamps out-of-range utilization', () => {
assert.strictEqual(buildUtilizationBar(-0.5), '▱▱▱▱▱');
assert.strictEqual(buildUtilizationBar(1.7), '▰▰▰▰▰');
});

test('respects a custom width', () => {
assert.strictEqual(buildUtilizationBar(0.5, 8), '▰▰▰▰▱▱▱▱');
assert.strictEqual(buildUtilizationBar(0.5, 8).length, 8);
});
});

suite('StatusBar', () => {
test('buildLabel shows not-logged-in for no-credentials', () => {
const label = buildLabel(makeData({ dataSource: 'no-credentials' }));
Expand Down
Loading