claude-statuskit 0.3.0__py3-none-any.whl
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.
- claude_statuskit-0.3.0.dist-info/METADATA +205 -0
- claude_statuskit-0.3.0.dist-info/RECORD +22 -0
- claude_statuskit-0.3.0.dist-info/WHEEL +4 -0
- claude_statuskit-0.3.0.dist-info/entry_points.txt +2 -0
- statuskit/__init__.py +120 -0
- statuskit/cli.py +68 -0
- statuskit/core/__init__.py +1 -0
- statuskit/core/config.py +73 -0
- statuskit/core/loader.py +33 -0
- statuskit/core/models.py +142 -0
- statuskit/modules/__init__.py +6 -0
- statuskit/modules/base.py +38 -0
- statuskit/modules/git.py +380 -0
- statuskit/modules/model.py +128 -0
- statuskit/modules/usage_limits.py +485 -0
- statuskit/setup/__init__.py +1 -0
- statuskit/setup/commands.py +222 -0
- statuskit/setup/config.py +28 -0
- statuskit/setup/gitignore.py +64 -0
- statuskit/setup/hooks.py +60 -0
- statuskit/setup/paths.py +32 -0
- statuskit/setup/ui.py +31 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: claude-statuskit
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Modular statusline for Claude Code
|
|
5
|
+
Project-URL: Homepage, https://github.com/NoNameItem/claude-tools/tree/master/packages/statuskit
|
|
6
|
+
Project-URL: Repository, https://github.com/NoNameItem/claude-tools
|
|
7
|
+
Author-email: Artem Vasin <nonameitem@me.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: claude,claude-code,cli,statusline
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: termcolor
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# statuskit
|
|
23
|
+
|
|
24
|
+
[](https://pypi.org/project/claude-statuskit/)
|
|
25
|
+
[](https://pypi.org/project/claude-statuskit/)
|
|
26
|
+
[](https://github.com/NoNameItem/claude-tools/blob/master/LICENSE)
|
|
27
|
+
|
|
28
|
+
Modular statusline for [Claude Code](https://docs.anthropic.com/en/docs/claude-code).
|
|
29
|
+
|
|
30
|
+
Statuskit displays contextual information below Claude's responses: current model, git status, API usage limits, and more. It reads JSON from Claude Code's statusline hook and renders formatted, colored output.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Modular architecture** — enable only the modules you need
|
|
35
|
+
- **Configurable** — customize each module's behavior via TOML config
|
|
36
|
+
- **Built-in modules:**
|
|
37
|
+
- `model` — current Claude model name
|
|
38
|
+
- `git` — branch, remote status, changes, last commit, project/worktree location
|
|
39
|
+
- `usage_limits` — API quota tracking (5h session, 7d weekly) with color-coded warnings
|
|
40
|
+
- **Coming soon:**
|
|
41
|
+
- `beads` — display active beads tasks
|
|
42
|
+
- External modules support — load custom modules from separate packages
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Using uv (recommended)
|
|
48
|
+
uv tool install claude-statuskit
|
|
49
|
+
|
|
50
|
+
# Using pipx
|
|
51
|
+
pipx install claude-statuskit
|
|
52
|
+
|
|
53
|
+
# Using pip
|
|
54
|
+
pip install claude-statuskit
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
Run the setup command to configure Claude Code:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# User-level setup (recommended for personal use)
|
|
63
|
+
statuskit setup
|
|
64
|
+
|
|
65
|
+
# Project-level setup (shared config for team)
|
|
66
|
+
statuskit setup --scope project
|
|
67
|
+
|
|
68
|
+
# Local setup (personal overrides, gitignored)
|
|
69
|
+
statuskit setup --scope local
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Setup will:
|
|
73
|
+
1. Add the statusline hook to Claude Code settings
|
|
74
|
+
2. Create configuration file at the appropriate level
|
|
75
|
+
3. Update gitignore for local configs (if applicable)
|
|
76
|
+
|
|
77
|
+
After setup, restart Claude Code to see the statusline.
|
|
78
|
+
|
|
79
|
+
## Example Output
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
Opus 4.5 · 47m · 120k free (60%)
|
|
83
|
+
claude-tools master ↑1 +2 ~1 ?3 · a1b2c3d 2h
|
|
84
|
+
Session: 45% (2h 30m) · Weekly: 12%
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This shows:
|
|
88
|
+
- **Line 1 (model):** Model name, session duration, context window
|
|
89
|
+
- **Line 2 (git):** Project, branch, remote status, changes, last commit
|
|
90
|
+
- **Line 3 (usage_limits):** API quota for session and weekly limits
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
Configuration files are loaded in priority order (first found wins):
|
|
95
|
+
|
|
96
|
+
| Level | Path | Use case |
|
|
97
|
+
|-------|------|----------|
|
|
98
|
+
| Local | `.claude/statuskit.local.toml` | Personal overrides, gitignored |
|
|
99
|
+
| Project | `.claude/statuskit.toml` | Shared team configuration |
|
|
100
|
+
| User | `~/.claude/statuskit.toml` | Global personal defaults |
|
|
101
|
+
|
|
102
|
+
### Basic Configuration
|
|
103
|
+
|
|
104
|
+
```toml
|
|
105
|
+
# Modules to display (in order)
|
|
106
|
+
modules = ["model", "git", "usage_limits"]
|
|
107
|
+
|
|
108
|
+
# Enable debug output
|
|
109
|
+
debug = false
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Module Configuration
|
|
113
|
+
|
|
114
|
+
Each module can be configured in its own section:
|
|
115
|
+
|
|
116
|
+
```toml
|
|
117
|
+
[git]
|
|
118
|
+
show_branch = true
|
|
119
|
+
commit_age_format = "compact"
|
|
120
|
+
|
|
121
|
+
[usage_limits]
|
|
122
|
+
show_session = true
|
|
123
|
+
show_weekly = true
|
|
124
|
+
multiline = false
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Module Reference
|
|
128
|
+
|
|
129
|
+
### `model` Module
|
|
130
|
+
|
|
131
|
+
Displays model name, session duration, and context window usage.
|
|
132
|
+
|
|
133
|
+
| Parameter | Type | Default | Description |
|
|
134
|
+
|-----------|------|---------|-------------|
|
|
135
|
+
| `show_duration` | bool | `true` | Show session duration |
|
|
136
|
+
| `show_context` | bool | `true` | Show context window usage |
|
|
137
|
+
| `context_format` | string | `"free"` | Context display format (see below) |
|
|
138
|
+
| `context_compact` | bool | `false` | Use compact numbers (e.g., `150k` instead of `150,000`) |
|
|
139
|
+
| `context_threshold_green` | int | `50` | Percentage of free context to show green |
|
|
140
|
+
| `context_threshold_yellow` | int | `25` | Percentage of free context to show yellow (below = red) |
|
|
141
|
+
|
|
142
|
+
**`context_format` values:**
|
|
143
|
+
|
|
144
|
+
| Value | Output example |
|
|
145
|
+
|-------|----------------|
|
|
146
|
+
| `"free"` | `150,000 free (75.0%)` |
|
|
147
|
+
| `"used"` | `50,000 used (25.0%)` |
|
|
148
|
+
| `"ratio"` | `50,000/200,000 (25.0%)` |
|
|
149
|
+
| `"bar"` | `[███████░░░] 70%` |
|
|
150
|
+
|
|
151
|
+
### `git` Module
|
|
152
|
+
|
|
153
|
+
Displays git branch, remote status, changes, last commit, and project/worktree location.
|
|
154
|
+
|
|
155
|
+
| Parameter | Type | Default | Description |
|
|
156
|
+
|-----------|------|---------|-------------|
|
|
157
|
+
| `show_project` | bool | `true` | Show project (repository) name |
|
|
158
|
+
| `show_worktree` | bool | `true` | Show worktree name with 🌲 indicator |
|
|
159
|
+
| `show_folder` | bool | `true` | Show current subfolder relative to repo root |
|
|
160
|
+
| `show_branch` | bool | `true` | Show current branch name |
|
|
161
|
+
| `show_remote_status` | bool | `true` | Show ahead/behind/diverged status |
|
|
162
|
+
| `show_changes` | bool | `true` | Show staged/modified/untracked counts |
|
|
163
|
+
| `show_commit` | bool | `true` | Show last commit hash and age |
|
|
164
|
+
| `commit_age_format` | string | `"relative"` | Commit age format (see below) |
|
|
165
|
+
|
|
166
|
+
**`commit_age_format` values:**
|
|
167
|
+
|
|
168
|
+
| Value | Output example |
|
|
169
|
+
|-------|----------------|
|
|
170
|
+
| `"relative"` | `2 hours ago` |
|
|
171
|
+
| `"compact"` | `2h` |
|
|
172
|
+
|
|
173
|
+
### `usage_limits` Module
|
|
174
|
+
|
|
175
|
+
Displays API usage limits with color-coded warnings based on consumption rate.
|
|
176
|
+
|
|
177
|
+
| Parameter | Type | Default | Description |
|
|
178
|
+
|-----------|------|---------|-------------|
|
|
179
|
+
| `show_session` | bool | `true` | Show 5-hour session limit |
|
|
180
|
+
| `show_weekly` | bool | `true` | Show 7-day weekly limit |
|
|
181
|
+
| `show_sonnet` | bool | `false` | Show Sonnet-only weekly limit |
|
|
182
|
+
| `show_reset_time` | bool | `true` | Show time until reset |
|
|
183
|
+
| `multiline` | bool | `true` | Display each limit on separate line |
|
|
184
|
+
| `show_progress_bar` | bool | `false` | Show visual progress bar |
|
|
185
|
+
| `bar_width` | int | `10` | Progress bar width in characters |
|
|
186
|
+
| `session_time_format` | string | `"remaining"` | Time format for session limit |
|
|
187
|
+
| `weekly_time_format` | string | `"reset_at"` | Time format for weekly limit |
|
|
188
|
+
| `sonnet_time_format` | string | `"reset_at"` | Time format for Sonnet limit |
|
|
189
|
+
| `cache_ttl` | int | `60` | Cache lifetime in seconds |
|
|
190
|
+
|
|
191
|
+
**Time format values:**
|
|
192
|
+
|
|
193
|
+
| Value | Output example |
|
|
194
|
+
|-------|----------------|
|
|
195
|
+
| `"remaining"` | `2h 30m` |
|
|
196
|
+
| `"reset_at"` | `Thu 17:00` |
|
|
197
|
+
|
|
198
|
+
**Color coding:** Usage is colored based on consumption rate vs elapsed time:
|
|
199
|
+
- 🟢 Green — on track or under
|
|
200
|
+
- 🟡 Yellow — approaching the limit trajectory
|
|
201
|
+
- 🔴 Red — ahead of pace, may hit limit
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT — see [LICENSE](https://github.com/NoNameItem/claude-tools/blob/master/LICENSE) for details.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
statuskit/__init__.py,sha256=5YT6UVacwSqav_yy0eTZykROhp3LjbdK8m-Wkia32VY,3642
|
|
2
|
+
statuskit/cli.py,sha256=IRXKUCIiQX-HGjNBeo_ZiA_55L6lUQa1_zsznp2i64Q,1833
|
|
3
|
+
statuskit/core/__init__.py,sha256=ws2smu4R-UF5LWFM35yuGBUuKGuOiy6U68RKmEq3Aqk,37
|
|
4
|
+
statuskit/core/config.py,sha256=HqYhYWv-1VDMDV48R43dSYcJ9qf_ZKdjmvPk3rfjLOQ,2439
|
|
5
|
+
statuskit/core/loader.py,sha256=ehmwwITKZRLgc6yxyOPVqQchHQQFtVSoGn33_Sy_LK0,992
|
|
6
|
+
statuskit/core/models.py,sha256=-9SFMFNCUpYOERLCmta2jdoa-eKl_xGDoYWw4iIuZ0Y,3837
|
|
7
|
+
statuskit/modules/__init__.py,sha256=8Nq_CdDeUtNUtikAqrZnPYmPezDPU_AmHfRTmwfNf7M,163
|
|
8
|
+
statuskit/modules/base.py,sha256=kOuZKZgU3S8oxTENaGuHNuhiYMxkt_MVUlw1C22Hs0M,952
|
|
9
|
+
statuskit/modules/git.py,sha256=ipg_ibj4HKiN26tt_J9Z0IGS7D413nkLIymb6rqxFwQ,12439
|
|
10
|
+
statuskit/modules/model.py,sha256=iJAiFWsjSmTDaMVubabatBLVLOeQbj4TVxHc417hb78,4369
|
|
11
|
+
statuskit/modules/usage_limits.py,sha256=83UhiceuZf0XkxOewdQq49exaVYlibTAkyO154ONfYw,15361
|
|
12
|
+
statuskit/setup/__init__.py,sha256=TlrT2k5z4QUG9Op3w7Ho0lHQCu86NnJCVrJteqnC6NE,35
|
|
13
|
+
statuskit/setup/commands.py,sha256=7ZpsKrscQJj5TPeibY8T-zeiz64jvCGjYD7_htK8kFo,6835
|
|
14
|
+
statuskit/setup/config.py,sha256=IdRHxpknOM2Ho-QwTV98-PXqw1tY_lD72gk6_0xFdm8,645
|
|
15
|
+
statuskit/setup/gitignore.py,sha256=euRZ2lFG3KAM13jMGUt6oiIsjQMifE1zrJuiH3eNe20,1566
|
|
16
|
+
statuskit/setup/hooks.py,sha256=L2owfXpKZle1VAde9c8pPqaR8uXht4cC-44MkWsJMTw,1471
|
|
17
|
+
statuskit/setup/paths.py,sha256=Z_eogXqmsITnlEh9ifWqDMs7YZvrbtWShLT89ZLsi6I,877
|
|
18
|
+
statuskit/setup/ui.py,sha256=Lz9CX02tqqn3GF2EqHsvo2olDd_D7rbN6_u-T5nZ6q4,956
|
|
19
|
+
claude_statuskit-0.3.0.dist-info/METADATA,sha256=ylVqwDjyaR-Dm1uFgobvGK4EheWIe9624JWZ0cEzL2A,7115
|
|
20
|
+
claude_statuskit-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
21
|
+
claude_statuskit-0.3.0.dist-info/entry_points.txt,sha256=6C0Zr7X1ues0_IA2XtSqH7I64SL7qhBxXd612xEGbMo,45
|
|
22
|
+
claude_statuskit-0.3.0.dist-info/RECORD,,
|
statuskit/__init__.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Modular statusline kit for Claude Code."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from argparse import Namespace
|
|
6
|
+
|
|
7
|
+
from termcolor import colored
|
|
8
|
+
|
|
9
|
+
from .cli import create_parser
|
|
10
|
+
from .core.config import load_config
|
|
11
|
+
from .core.loader import load_modules
|
|
12
|
+
from .core.models import RenderContext, StatusInput
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _handle_setup(args: Namespace) -> None:
|
|
16
|
+
"""Handle setup command."""
|
|
17
|
+
from .setup.commands import check_installation
|
|
18
|
+
from .setup.paths import Scope
|
|
19
|
+
from .setup.ui import ConsoleUI
|
|
20
|
+
|
|
21
|
+
if args.check:
|
|
22
|
+
print(check_installation())
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
scope = Scope(args.scope)
|
|
26
|
+
ui = None if args.force else ConsoleUI()
|
|
27
|
+
|
|
28
|
+
if args.remove:
|
|
29
|
+
_handle_remove(scope, args.force, ui)
|
|
30
|
+
else:
|
|
31
|
+
_handle_install(scope, args.force, ui)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _handle_remove(scope, force: bool, ui) -> None:
|
|
35
|
+
"""Handle setup --remove command."""
|
|
36
|
+
from .setup.commands import remove_hook
|
|
37
|
+
|
|
38
|
+
result = remove_hook(scope, force=force, ui=ui)
|
|
39
|
+
if result.not_installed:
|
|
40
|
+
print(f"statuskit is not installed at {scope.value} scope.")
|
|
41
|
+
elif result.success:
|
|
42
|
+
print(f"\u2713 Removed statusline hook from {scope.value} scope.")
|
|
43
|
+
else:
|
|
44
|
+
print(f"Error: {result.message}")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _handle_install(scope, force: bool, ui) -> None:
|
|
49
|
+
"""Handle setup install command."""
|
|
50
|
+
from .setup.commands import install_hook
|
|
51
|
+
|
|
52
|
+
result = install_hook(scope, force=force, ui=ui)
|
|
53
|
+
if result.higher_scope_installed and result.higher_scope:
|
|
54
|
+
print(f"statuskit is already installed at {result.higher_scope.value} scope.")
|
|
55
|
+
print("The hook will work for this project too.")
|
|
56
|
+
if result.config_created:
|
|
57
|
+
print(f"\u2713 Created config file at {scope.value} scope.")
|
|
58
|
+
elif result.already_installed:
|
|
59
|
+
print(f"statuskit is already installed at {scope.value} scope.")
|
|
60
|
+
if result.config_created:
|
|
61
|
+
print("\u2713 Created config file.")
|
|
62
|
+
elif result.success:
|
|
63
|
+
print(f"\u2713 Added statusline hook to {scope.value} scope.")
|
|
64
|
+
if result.backup_created:
|
|
65
|
+
print("\u2713 Created backup of previous settings.")
|
|
66
|
+
if result.config_created:
|
|
67
|
+
print("\u2713 Created config file.")
|
|
68
|
+
if result.gitignore_updated:
|
|
69
|
+
print("\u2713 Added .claude/*.local.* to .gitignore")
|
|
70
|
+
print("\nRun `claude` to see your new statusline!")
|
|
71
|
+
else:
|
|
72
|
+
print(f"Error: {result.message}")
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _render_statusline() -> None:
|
|
77
|
+
"""Read from stdin and render statusline."""
|
|
78
|
+
config = load_config()
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
raw_data = json.load(sys.stdin)
|
|
82
|
+
data = StatusInput.from_dict(raw_data)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
if config.debug:
|
|
85
|
+
print(colored(f"[!] Failed to parse input: {e}", "red"))
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
ctx = RenderContext(debug=config.debug, data=data, cache_dir=config.cache_dir)
|
|
89
|
+
modules = load_modules(config, ctx)
|
|
90
|
+
|
|
91
|
+
for mod in modules:
|
|
92
|
+
try:
|
|
93
|
+
output = mod.render()
|
|
94
|
+
if output:
|
|
95
|
+
print(output)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
if config.debug:
|
|
98
|
+
print(colored(f"[!] {mod.name}: {e}", "red"))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def main() -> None:
|
|
102
|
+
"""Entry point for statuskit command."""
|
|
103
|
+
parser = create_parser()
|
|
104
|
+
args = parser.parse_args()
|
|
105
|
+
|
|
106
|
+
if args.command == "setup":
|
|
107
|
+
_handle_setup(args)
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
if sys.stdin.isatty():
|
|
111
|
+
print("statuskit: reads JSON from stdin")
|
|
112
|
+
print("Usage: echo '{...}' | statuskit")
|
|
113
|
+
print("\nRun 'statuskit setup' to configure Claude Code integration.")
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
_render_statusline()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
main()
|
statuskit/cli.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""CLI argument parsing for statuskit."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from importlib.metadata import version
|
|
5
|
+
|
|
6
|
+
MODULES_HELP = """
|
|
7
|
+
Built-in modules:
|
|
8
|
+
model Display current Claude model name
|
|
9
|
+
git Show git branch and status
|
|
10
|
+
beads Display active beads tasks
|
|
11
|
+
quota Track token usage
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_version() -> str:
|
|
16
|
+
"""Get statuskit version from package metadata."""
|
|
17
|
+
try:
|
|
18
|
+
return version("statuskit")
|
|
19
|
+
except Exception:
|
|
20
|
+
return "0.1.0" # fallback for development
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
24
|
+
"""Create argument parser for statuskit CLI."""
|
|
25
|
+
parser = argparse.ArgumentParser(
|
|
26
|
+
prog="statuskit",
|
|
27
|
+
description="Modular statusline for Claude Code",
|
|
28
|
+
epilog=MODULES_HELP,
|
|
29
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"-V",
|
|
33
|
+
"--version",
|
|
34
|
+
action="version",
|
|
35
|
+
version=f"statuskit {get_version()}",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
39
|
+
|
|
40
|
+
# setup subcommand
|
|
41
|
+
setup_parser = subparsers.add_parser(
|
|
42
|
+
"setup",
|
|
43
|
+
help="Configure Claude Code integration",
|
|
44
|
+
)
|
|
45
|
+
setup_parser.add_argument(
|
|
46
|
+
"-s",
|
|
47
|
+
"--scope",
|
|
48
|
+
choices=["user", "project", "local"],
|
|
49
|
+
default="user",
|
|
50
|
+
help="Installation scope (default: user)",
|
|
51
|
+
)
|
|
52
|
+
setup_parser.add_argument(
|
|
53
|
+
"--check",
|
|
54
|
+
action="store_true",
|
|
55
|
+
help="Check installation status (all scopes)",
|
|
56
|
+
)
|
|
57
|
+
setup_parser.add_argument(
|
|
58
|
+
"--remove",
|
|
59
|
+
action="store_true",
|
|
60
|
+
help="Remove integration (requires --scope)",
|
|
61
|
+
)
|
|
62
|
+
setup_parser.add_argument(
|
|
63
|
+
"--force",
|
|
64
|
+
action="store_true",
|
|
65
|
+
help="Skip confirmations, backup and overwrite",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return parser
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core statuskit infrastructure."""
|
statuskit/core/config.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Configuration loading for statuskit."""
|
|
2
|
+
|
|
3
|
+
import tomllib
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from termcolor import colored
|
|
8
|
+
|
|
9
|
+
# Kept for backward compatibility in tests
|
|
10
|
+
CONFIG_PATH = Path.home() / ".claude" / "statuskit.toml"
|
|
11
|
+
DEFAULT_CACHE_DIR = Path.home() / ".cache" / "statuskit"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _get_config_paths() -> list[Path]:
|
|
15
|
+
"""Get config paths in priority order (highest first)."""
|
|
16
|
+
return [
|
|
17
|
+
Path(".claude") / "statuskit.local.toml", # Local (highest)
|
|
18
|
+
Path(".claude") / "statuskit.toml", # Project
|
|
19
|
+
Path.home() / ".claude" / "statuskit.toml", # User (lowest)
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Config:
|
|
25
|
+
"""Statuskit configuration."""
|
|
26
|
+
|
|
27
|
+
debug: bool = False
|
|
28
|
+
modules: list[str] = field(default_factory=lambda: ["model", "git", "beads", "quota"])
|
|
29
|
+
module_configs: dict[str, dict] = field(default_factory=dict)
|
|
30
|
+
cache_dir: Path = field(default_factory=lambda: DEFAULT_CACHE_DIR)
|
|
31
|
+
|
|
32
|
+
def get_module_config(self, name: str) -> dict:
|
|
33
|
+
"""Get configuration for a specific module."""
|
|
34
|
+
return self.module_configs.get(name, {})
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def load_config() -> Config:
|
|
38
|
+
"""Load configuration from TOML files.
|
|
39
|
+
|
|
40
|
+
Searches in priority order:
|
|
41
|
+
1. .claude/statuskit.local.toml (Local)
|
|
42
|
+
2. .claude/statuskit.toml (Project)
|
|
43
|
+
3. ~/.claude/statuskit.toml (User)
|
|
44
|
+
|
|
45
|
+
Returns defaults if no config file exists.
|
|
46
|
+
Shows error and returns defaults if file is invalid.
|
|
47
|
+
"""
|
|
48
|
+
for config_path in _get_config_paths():
|
|
49
|
+
if config_path.exists():
|
|
50
|
+
try:
|
|
51
|
+
with config_path.open("rb") as f:
|
|
52
|
+
data = tomllib.load(f)
|
|
53
|
+
except (tomllib.TOMLDecodeError, OSError) as e:
|
|
54
|
+
print(colored(f"[!] Config error in {config_path}: {e}", "red"))
|
|
55
|
+
return Config()
|
|
56
|
+
|
|
57
|
+
# Extract module configs
|
|
58
|
+
module_configs = {
|
|
59
|
+
k: v for k, v in data.items() if isinstance(v, dict) and k not in ("debug", "modules", "cache_dir")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Parse cache_dir
|
|
63
|
+
cache_dir_str = data.get("cache_dir")
|
|
64
|
+
cache_dir = Path(cache_dir_str).expanduser() if cache_dir_str else DEFAULT_CACHE_DIR
|
|
65
|
+
|
|
66
|
+
return Config(
|
|
67
|
+
debug=data.get("debug", False),
|
|
68
|
+
modules=data.get("modules", Config().modules),
|
|
69
|
+
module_configs=module_configs,
|
|
70
|
+
cache_dir=cache_dir,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return Config()
|
statuskit/core/loader.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Module loader for statuskit."""
|
|
2
|
+
|
|
3
|
+
from statuskit.core.config import Config
|
|
4
|
+
from statuskit.core.models import RenderContext
|
|
5
|
+
from statuskit.modules import git, model, usage_limits
|
|
6
|
+
from statuskit.modules.base import BaseModule
|
|
7
|
+
|
|
8
|
+
BUILTIN_MODULES: dict[str, type[BaseModule]] = {
|
|
9
|
+
"model": model.ModelModule,
|
|
10
|
+
"usage_limits": usage_limits.UsageLimitsModule,
|
|
11
|
+
"git": git.GitModule,
|
|
12
|
+
# "beads": ..., # v0.3
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_modules(config: Config, ctx: RenderContext) -> list[BaseModule]:
|
|
17
|
+
"""Load modules based on configuration.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
config: Statuskit configuration
|
|
21
|
+
ctx: Render context for modules
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
List of instantiated modules
|
|
25
|
+
"""
|
|
26
|
+
modules = []
|
|
27
|
+
for name in config.modules:
|
|
28
|
+
if name in BUILTIN_MODULES:
|
|
29
|
+
module_config = config.get_module_config(name)
|
|
30
|
+
modules.append(BUILTIN_MODULES[name](ctx, module_config))
|
|
31
|
+
elif ctx.debug:
|
|
32
|
+
print(f"[!] Unknown module: {name}")
|
|
33
|
+
return modules
|
statuskit/core/models.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Data types for statuskit."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Model:
|
|
9
|
+
"""Model information from Claude Code."""
|
|
10
|
+
|
|
11
|
+
id: str | None
|
|
12
|
+
display_name: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Workspace:
|
|
17
|
+
"""Workspace paths from Claude Code."""
|
|
18
|
+
|
|
19
|
+
current_dir: str
|
|
20
|
+
project_dir: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Cost:
|
|
25
|
+
"""Cost and timing information from Claude Code."""
|
|
26
|
+
|
|
27
|
+
total_cost_usd: float | None
|
|
28
|
+
total_duration_ms: int | None
|
|
29
|
+
total_api_duration_ms: int | None
|
|
30
|
+
total_lines_added: int | None
|
|
31
|
+
total_lines_removed: int | None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class CurrentUsage:
|
|
36
|
+
"""Current context window usage."""
|
|
37
|
+
|
|
38
|
+
input_tokens: int
|
|
39
|
+
output_tokens: int
|
|
40
|
+
cache_creation_input_tokens: int
|
|
41
|
+
cache_read_input_tokens: int
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ContextWindow:
|
|
46
|
+
"""Context window information from Claude Code."""
|
|
47
|
+
|
|
48
|
+
context_window_size: int | None
|
|
49
|
+
total_input_tokens: int | None
|
|
50
|
+
total_output_tokens: int | None
|
|
51
|
+
current_usage: CurrentUsage | None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class StatusInput:
|
|
56
|
+
"""Parsed input from Claude Code status hook."""
|
|
57
|
+
|
|
58
|
+
session_id: str | None
|
|
59
|
+
cwd: str | None
|
|
60
|
+
model: Model | None
|
|
61
|
+
workspace: Workspace | None
|
|
62
|
+
cost: Cost | None
|
|
63
|
+
context_window: ContextWindow | None
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_dict(cls, data: dict) -> "StatusInput":
|
|
67
|
+
"""Parse JSON dict into StatusInput dataclass.
|
|
68
|
+
|
|
69
|
+
Missing fields become None.
|
|
70
|
+
"""
|
|
71
|
+
model_data = data.get("model")
|
|
72
|
+
model = (
|
|
73
|
+
Model(
|
|
74
|
+
id=model_data.get("id"),
|
|
75
|
+
display_name=model_data.get("display_name", "Unknown"),
|
|
76
|
+
)
|
|
77
|
+
if model_data
|
|
78
|
+
else None
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
workspace_data = data.get("workspace")
|
|
82
|
+
workspace = (
|
|
83
|
+
Workspace(
|
|
84
|
+
current_dir=workspace_data.get("current_dir", ""),
|
|
85
|
+
project_dir=workspace_data.get("project_dir", ""),
|
|
86
|
+
)
|
|
87
|
+
if workspace_data
|
|
88
|
+
else None
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
cost_data = data.get("cost")
|
|
92
|
+
cost = (
|
|
93
|
+
Cost(
|
|
94
|
+
total_cost_usd=cost_data.get("total_cost_usd"),
|
|
95
|
+
total_duration_ms=cost_data.get("total_duration_ms"),
|
|
96
|
+
total_api_duration_ms=cost_data.get("total_api_duration_ms"),
|
|
97
|
+
total_lines_added=cost_data.get("total_lines_added"),
|
|
98
|
+
total_lines_removed=cost_data.get("total_lines_removed"),
|
|
99
|
+
)
|
|
100
|
+
if cost_data
|
|
101
|
+
else None
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
ctx_data = data.get("context_window")
|
|
105
|
+
context_window = None
|
|
106
|
+
if ctx_data:
|
|
107
|
+
usage_data = ctx_data.get("current_usage")
|
|
108
|
+
current_usage = (
|
|
109
|
+
CurrentUsage(
|
|
110
|
+
input_tokens=usage_data.get("input_tokens", 0),
|
|
111
|
+
output_tokens=usage_data.get("output_tokens", 0),
|
|
112
|
+
cache_creation_input_tokens=usage_data.get("cache_creation_input_tokens", 0),
|
|
113
|
+
cache_read_input_tokens=usage_data.get("cache_read_input_tokens", 0),
|
|
114
|
+
)
|
|
115
|
+
if usage_data
|
|
116
|
+
else None
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
context_window = ContextWindow(
|
|
120
|
+
context_window_size=ctx_data.get("context_window_size"),
|
|
121
|
+
total_input_tokens=ctx_data.get("total_input_tokens"),
|
|
122
|
+
total_output_tokens=ctx_data.get("total_output_tokens"),
|
|
123
|
+
current_usage=current_usage,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return cls(
|
|
127
|
+
session_id=data.get("session_id"),
|
|
128
|
+
cwd=data.get("cwd"),
|
|
129
|
+
model=model,
|
|
130
|
+
workspace=workspace,
|
|
131
|
+
cost=cost,
|
|
132
|
+
context_window=context_window,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class RenderContext:
|
|
138
|
+
"""Context passed to modules for rendering."""
|
|
139
|
+
|
|
140
|
+
debug: bool
|
|
141
|
+
data: StatusInput
|
|
142
|
+
cache_dir: Path | None = None
|