kompactor 0.7.3__tar.gz → 0.7.4__tar.gz
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.
- {kompactor-0.7.3 → kompactor-0.7.4}/PKG-INFO +53 -8
- {kompactor-0.7.3 → kompactor-0.7.4}/README.md +52 -7
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/__init__.py +1 -1
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/cli.py +8 -3
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/hooks.py +1 -23
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/installer.py +14 -5
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/static/usage.html +1 -1
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/store.py +17 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor.egg-info/PKG-INFO +53 -8
- {kompactor-0.7.3 → kompactor-0.7.4}/pyproject.toml +1 -1
- {kompactor-0.7.3 → kompactor-0.7.4}/tests/test_kompactor.py +12 -17
- {kompactor-0.7.3 → kompactor-0.7.4}/LICENSE +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/__main__.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/_launch.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/config.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/estimate.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/filters.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/graph.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/langs.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/outline.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/prove.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/rules.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/samples.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/server.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/shellwrap.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/static/fonts/OFL.txt +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/static/fonts/plex-mono-400.woff2 +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/static/fonts/plex-mono-500.woff2 +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/static/fonts/plex-sans-400.woff2 +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/static/fonts/plex-sans-500.woff2 +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/static/fonts/plex-sans-600.woff2 +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/static/logo-mark.svg +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/tokens.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor/ui.html +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor.egg-info/SOURCES.txt +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor.egg-info/dependency_links.txt +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor.egg-info/entry_points.txt +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor.egg-info/requires.txt +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/kompactor.egg-info/top_level.txt +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/setup.cfg +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/tests/test_graph.py +0 -0
- {kompactor-0.7.3 → kompactor-0.7.4}/tests/test_shell.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kompactor
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: Cut the tokens Claude Code spends on noise: compacted command output (failing runs included), outline-first reads, a local code graph, and per-turn savings.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/amarkum/kompactor
|
|
@@ -21,7 +21,10 @@ Requires-Dist: tiktoken>=0.7; extra == "exact"
|
|
|
21
21
|
Dynamic: license-file
|
|
22
22
|
|
|
23
23
|
<p align="center">
|
|
24
|
-
<
|
|
24
|
+
<picture>
|
|
25
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/amarkum/kompactor/main/docs/brand/kompactor-logo-dark.png">
|
|
26
|
+
<img src="https://raw.githubusercontent.com/amarkum/kompactor/main/docs/brand/kompactor-logo-light.png" alt="Kompactor" width="240">
|
|
27
|
+
</picture>
|
|
25
28
|
</p>
|
|
26
29
|
|
|
27
30
|
<p align="center"><b>Cut the tokens Claude Code spends on noise.</b><br>
|
|
@@ -39,7 +42,7 @@ Most of a Claude Code session's tokens aren't your prompt or Claude's answer. Th
|
|
|
39
42
|
|
|
40
43
|
## Contents
|
|
41
44
|
|
|
42
|
-
[Install](#install) · [After you install](#after-you-install) · [How it works](#how-it-works) · [Use it from Claude Code](#use-it-from-claude-code) · [Quality vs tokens](#quality-vs-tokens) · [See your savings](#see-your-savings) · [Turn it off](#turn-it-off-or-remove-it) · [Evidence](#evidence) · [Code graph](#code-graph) · [Teams](#use-it-across-a-team) · [Commands](#commands)
|
|
45
|
+
[Install](#install) · [After you install](#after-you-install) · [How it works](#how-it-works) · [Examples](#examples) · [Use it from Claude Code](#use-it-from-claude-code) · [Quality vs tokens](#quality-vs-tokens) · [See your savings](#see-your-savings) · [Turn it off](#turn-it-off-or-remove-it) · [What it runs and changes](#what-kompactor-runs-and-changes) · [Evidence](#evidence) · [Code graph](#code-graph) · [Teams](#use-it-across-a-team) · [Troubleshooting](#troubleshooting) · [Commands](#commands) · [Support](#support-and-security)
|
|
43
46
|
|
|
44
47
|
## Install
|
|
45
48
|
|
|
@@ -59,7 +62,7 @@ pipx install kompactor # or: pip install kompactor · uv tool install kom
|
|
|
59
62
|
kompactor setup
|
|
60
63
|
```
|
|
61
64
|
|
|
62
|
-
**VS Code.** Download `kompactor-0.7.
|
|
65
|
+
**VS Code.** Download `kompactor-0.7.4.vsix` from [Releases](https://github.com/amarkum/kompactor/releases), install it (Extensions › `⋯` › *Install from VSIX…*), and click **Enable** when Kompactor asks.
|
|
63
66
|
|
|
64
67
|
## After you install
|
|
65
68
|
|
|
@@ -67,7 +70,7 @@ kompactor setup
|
|
|
67
70
|
2. **Work as usual.** You don't call Kompactor; it acts on Claude's tool calls by itself. After each reply you'll see a line such as `Kompactor: −8.2k tokens this turn`, and the status line shows the running total.
|
|
68
71
|
3. **Check it's on:** run `kompactor` in a terminal (or `/kompactor` in Claude Code). It shows whether it's on, your level, savings so far and how to change things:
|
|
69
72
|
```
|
|
70
|
-
Kompactor 0.7.
|
|
73
|
+
Kompactor 0.7.4: ON
|
|
71
74
|
Connected via the Claude Code plugin. New Claude Code sessions are compacted.
|
|
72
75
|
Level: Balanced (level 3 of 5). Change it: `kompactor config preset 1-5`, the dashboard, or /kompactor balanced
|
|
73
76
|
Saved so far: 41.3k tokens (−38%) over 52 tool calls. Details: `kompactor gain`
|
|
@@ -88,6 +91,18 @@ Kompactor uses Claude Code's hooks, so it sits between Claude's tools and Claude
|
|
|
88
91
|
|
|
89
92
|
**Nothing is lost.** Every shortened output keeps a raw copy that Claude can fetch with `kompactor show <id>`. Kompactor never changes what a command does, your permission rules still apply, and if anything goes wrong it lets the original output through. Everything runs on your machine; nothing is sent anywhere ([privacy](https://github.com/amarkum/kompactor/blob/main/PRIVACY.md)).
|
|
90
93
|
|
|
94
|
+
## Examples
|
|
95
|
+
|
|
96
|
+
Ask Claude Code the way you normally would. These prompts come from the [evidence](#evidence) runs:
|
|
97
|
+
|
|
98
|
+
| You ask | What Kompactor does | Measured |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| "Run the tests and tell me which one fails and why." | Claude sees the failing test, its error and the summary line instead of hundreds of passing tests, even when the run exits non-zero. | Go: −33% tokens; vitest: −37% cost |
|
|
101
|
+
| "Where is the session cookie signed?" | Big files arrive as outlines with line ranges, and the first search adds a short map of the main modules, so Claude finds the code in fewer turns. | Flask: −26% tokens |
|
|
102
|
+
| "Install the dependencies and check that it builds." | Download and progress chatter is dropped; warnings, errors and the result are kept. | `npm install`: ~33,000 › 469 tokens |
|
|
103
|
+
| "What does changing `createStore` affect?" | The code graph lists the files that use it and the tests to run. | 27/27 answers correct |
|
|
104
|
+
| `/kompactor review` | Lists cuts in your current diff: duplicated helpers, unused options, dead branches. | |
|
|
105
|
+
|
|
91
106
|
## Use it from Claude Code
|
|
92
107
|
|
|
93
108
|
Type these in any Claude Code chat:
|
|
@@ -140,6 +155,20 @@ kompactor uninstall # remove hooks, status line, shell prefix and skill (add -
|
|
|
140
155
|
```
|
|
141
156
|
Then remove the package the way you installed it: `claude plugin uninstall kompactor`, `pipx uninstall kompactor`, or uninstall the extension in VS Code. Setup never replaces a status line or shell prefix you already had, and keeps a backup in `~/.claude/settings.json.kompactor-backup`.
|
|
142
157
|
|
|
158
|
+
## What Kompactor runs and changes
|
|
159
|
+
|
|
160
|
+
Everything below happens on your machine. Kompactor makes no network requests of its own and has no telemetry. (If you install the optional `tiktoken` package for exact token counts, tiktoken downloads its token table once, on first use.)
|
|
161
|
+
|
|
162
|
+
**What it runs.** Python hooks that Claude Code calls (`python3 kompactor/_launch.py hook <name>` from the plugin folder): after Bash, Read, Grep, Glob and edit tool calls; before Read; at session start, subagent start and before compaction; and when a turn ends. After `/kompactor setup` on macOS and Linux, Claude's Bash commands run through Kompactor's shell prefix (`~/.kompactor/bin/kompactor-shell`) so failing runs can be compacted too. The prefix runs after Claude Code's permission check, on Claude's original command, and returns its exit code unchanged.
|
|
163
|
+
|
|
164
|
+
**What it adds to Claude's context.** Compacted tool output, outlines in place of large file reads, a code map once per session, a one-line savings note after each turn, and short reply rules at session start and for subagents. The rules are plain text you can read in [`kompactor/rules.py`](https://github.com/amarkum/kompactor/blob/main/kompactor/rules.py); the level decides which ones apply (none at level 1).
|
|
165
|
+
|
|
166
|
+
**What it reads.** The tool output Claude Code passes to its hooks, the files Claude reads (to outline them), your project's source (for the code graph), and the context size Claude Code gives the status line. Hooks never read your session transcripts. `kompactor estimate` and the dashboard's **Replay your history** panel read your local Claude Code session logs in `~/.claude/projects`, only when you run them, to show what Kompactor would have saved; the result isn't stored or sent anywhere.
|
|
167
|
+
|
|
168
|
+
**What it writes.** `~/.kompactor/`: settings, the savings ledger, raw copies of shortened output (for `kompactor show`), the code graph index and the `~/.kompactor/bin/` launchers (written at session start). History is deleted after 30 days by default (`retention_days`). Setup edits `~/.claude/settings.json` to add Kompactor's hooks (non-plugin installs), a status line (only if you have none) and `CLAUDE_CODE_SHELL_PREFIX` (only if it isn't set), keeping a backup in `settings.json.kompactor-backup`; non-plugin installs also get the `/kompactor` skill in `~/.claude/skills`. Nothing is written to your repositories.
|
|
169
|
+
|
|
170
|
+
**What it never does.** Change your permission rules or sandbox settings, change what a command does, or send your code or output anywhere. Details: [privacy policy](https://github.com/amarkum/kompactor/blob/main/PRIVACY.md).
|
|
171
|
+
|
|
143
172
|
## Evidence
|
|
144
173
|
|
|
145
174
|
`kompactor prove` runs each task in real headless Claude Code sessions (`claude -p`), 3 times without Kompactor and 3 times with it, interleaved, with the same prompt, tools, turn limit and budget cap. Tokens are input + cache writes + cache reads + output across every API call; cost is Claude Code's own figure.
|
|
@@ -218,7 +247,17 @@ When a teammate trusts the folder, Claude Code installs the plugin; each person
|
|
|
218
247
|
{ "preset": 4, "overrides": { "never_compact": ["terraform plan"] } }
|
|
219
248
|
```
|
|
220
249
|
|
|
221
|
-
**Admins** can put the same keys in Claude Code's managed settings to roll it out to every machine. To control what your team runs, fork the repo and use `your-org/kompactor` in the commands above. The
|
|
250
|
+
**Admins** can put the same keys in Claude Code's managed settings to roll it out to every machine. To control what your team runs, fork the repo and use `your-org/kompactor` in the commands above. The dashboard's **Guide** page covers rollout, settings and FAQs in more detail.
|
|
251
|
+
|
|
252
|
+
## Troubleshooting
|
|
253
|
+
|
|
254
|
+
| Problem | What to do |
|
|
255
|
+
|---|---|
|
|
256
|
+
| No savings line after replies | Restart Claude Code after setup, then run `kompactor doctor`: it checks the installation and shows recent hook errors. The dashboard's **Self-test** panel runs every hook once. |
|
|
257
|
+
| An output looks too short | Claude can fetch the original with `kompactor show <id>` (the id is in the shortened output), or search earlier outputs with `kompactor search "text"`. |
|
|
258
|
+
| A command should never be shortened | Add it to **Never compact these commands** on the dashboard, or in `.kompactor.json` (`"never_compact": ["terraform plan"]`). For a single run, start the command with `KOMPACTOR_RAW=1`. |
|
|
259
|
+
| Something seems off | `kompactor stop` turns Kompactor off at once; every hook also lets the original output through if it hits an error. Errors are logged to `~/.kompactor/hook-errors.log`. |
|
|
260
|
+
| The dashboard port is busy | `kompactor start` picks a free port by itself and prints the address. |
|
|
222
261
|
|
|
223
262
|
## Commands
|
|
224
263
|
|
|
@@ -239,7 +278,7 @@ kompactor ui the dashboard, in the foreground
|
|
|
239
278
|
## Development
|
|
240
279
|
|
|
241
280
|
```bash
|
|
242
|
-
python -m unittest discover tests #
|
|
281
|
+
python -m unittest discover tests # 80 tests, including the real shell prefix run as Claude Code runs it
|
|
243
282
|
node vscode/test/smoke.js # VS Code extension against a mock API
|
|
244
283
|
./scripts/build-vsix.sh # › dist/kompactor-<version>.vsix
|
|
245
284
|
python scripts/bump_version.py X.Y.Z # bump every version; pushing to main publishes to PyPI
|
|
@@ -249,4 +288,10 @@ How Claude Code's hooks behave on success, failure and very long output, and why
|
|
|
249
288
|
|
|
250
289
|
Kompactor was called Kompact until version 0.4; installing it replaces an old Kompact install's settings.
|
|
251
290
|
|
|
252
|
-
|
|
291
|
+
## Support and security
|
|
292
|
+
|
|
293
|
+
- **Questions, bugs and ideas:** [open an issue](https://github.com/amarkum/kompactor/issues).
|
|
294
|
+
- **Security problems:** please report them privately, as described in [SECURITY.md](https://github.com/amarkum/kompactor/blob/main/SECURITY.md).
|
|
295
|
+
- **Privacy:** [PRIVACY.md](https://github.com/amarkum/kompactor/blob/main/PRIVACY.md) explains what Kompactor stores and how to delete it.
|
|
296
|
+
|
|
297
|
+
[MIT License](https://github.com/amarkum/kompactor/blob/main/LICENSE)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/amarkum/kompactor/main/docs/brand/kompactor-logo-dark.png">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/amarkum/kompactor/main/docs/brand/kompactor-logo-light.png" alt="Kompactor" width="240">
|
|
5
|
+
</picture>
|
|
3
6
|
</p>
|
|
4
7
|
|
|
5
8
|
<p align="center"><b>Cut the tokens Claude Code spends on noise.</b><br>
|
|
@@ -17,7 +20,7 @@ Most of a Claude Code session's tokens aren't your prompt or Claude's answer. Th
|
|
|
17
20
|
|
|
18
21
|
## Contents
|
|
19
22
|
|
|
20
|
-
[Install](#install) · [After you install](#after-you-install) · [How it works](#how-it-works) · [Use it from Claude Code](#use-it-from-claude-code) · [Quality vs tokens](#quality-vs-tokens) · [See your savings](#see-your-savings) · [Turn it off](#turn-it-off-or-remove-it) · [Evidence](#evidence) · [Code graph](#code-graph) · [Teams](#use-it-across-a-team) · [Commands](#commands)
|
|
23
|
+
[Install](#install) · [After you install](#after-you-install) · [How it works](#how-it-works) · [Examples](#examples) · [Use it from Claude Code](#use-it-from-claude-code) · [Quality vs tokens](#quality-vs-tokens) · [See your savings](#see-your-savings) · [Turn it off](#turn-it-off-or-remove-it) · [What it runs and changes](#what-kompactor-runs-and-changes) · [Evidence](#evidence) · [Code graph](#code-graph) · [Teams](#use-it-across-a-team) · [Troubleshooting](#troubleshooting) · [Commands](#commands) · [Support](#support-and-security)
|
|
21
24
|
|
|
22
25
|
## Install
|
|
23
26
|
|
|
@@ -37,7 +40,7 @@ pipx install kompactor # or: pip install kompactor · uv tool install kom
|
|
|
37
40
|
kompactor setup
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
**VS Code.** Download `kompactor-0.7.
|
|
43
|
+
**VS Code.** Download `kompactor-0.7.4.vsix` from [Releases](https://github.com/amarkum/kompactor/releases), install it (Extensions › `⋯` › *Install from VSIX…*), and click **Enable** when Kompactor asks.
|
|
41
44
|
|
|
42
45
|
## After you install
|
|
43
46
|
|
|
@@ -45,7 +48,7 @@ kompactor setup
|
|
|
45
48
|
2. **Work as usual.** You don't call Kompactor; it acts on Claude's tool calls by itself. After each reply you'll see a line such as `Kompactor: −8.2k tokens this turn`, and the status line shows the running total.
|
|
46
49
|
3. **Check it's on:** run `kompactor` in a terminal (or `/kompactor` in Claude Code). It shows whether it's on, your level, savings so far and how to change things:
|
|
47
50
|
```
|
|
48
|
-
Kompactor 0.7.
|
|
51
|
+
Kompactor 0.7.4: ON
|
|
49
52
|
Connected via the Claude Code plugin. New Claude Code sessions are compacted.
|
|
50
53
|
Level: Balanced (level 3 of 5). Change it: `kompactor config preset 1-5`, the dashboard, or /kompactor balanced
|
|
51
54
|
Saved so far: 41.3k tokens (−38%) over 52 tool calls. Details: `kompactor gain`
|
|
@@ -66,6 +69,18 @@ Kompactor uses Claude Code's hooks, so it sits between Claude's tools and Claude
|
|
|
66
69
|
|
|
67
70
|
**Nothing is lost.** Every shortened output keeps a raw copy that Claude can fetch with `kompactor show <id>`. Kompactor never changes what a command does, your permission rules still apply, and if anything goes wrong it lets the original output through. Everything runs on your machine; nothing is sent anywhere ([privacy](https://github.com/amarkum/kompactor/blob/main/PRIVACY.md)).
|
|
68
71
|
|
|
72
|
+
## Examples
|
|
73
|
+
|
|
74
|
+
Ask Claude Code the way you normally would. These prompts come from the [evidence](#evidence) runs:
|
|
75
|
+
|
|
76
|
+
| You ask | What Kompactor does | Measured |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| "Run the tests and tell me which one fails and why." | Claude sees the failing test, its error and the summary line instead of hundreds of passing tests, even when the run exits non-zero. | Go: −33% tokens; vitest: −37% cost |
|
|
79
|
+
| "Where is the session cookie signed?" | Big files arrive as outlines with line ranges, and the first search adds a short map of the main modules, so Claude finds the code in fewer turns. | Flask: −26% tokens |
|
|
80
|
+
| "Install the dependencies and check that it builds." | Download and progress chatter is dropped; warnings, errors and the result are kept. | `npm install`: ~33,000 › 469 tokens |
|
|
81
|
+
| "What does changing `createStore` affect?" | The code graph lists the files that use it and the tests to run. | 27/27 answers correct |
|
|
82
|
+
| `/kompactor review` | Lists cuts in your current diff: duplicated helpers, unused options, dead branches. | |
|
|
83
|
+
|
|
69
84
|
## Use it from Claude Code
|
|
70
85
|
|
|
71
86
|
Type these in any Claude Code chat:
|
|
@@ -118,6 +133,20 @@ kompactor uninstall # remove hooks, status line, shell prefix and skill (add -
|
|
|
118
133
|
```
|
|
119
134
|
Then remove the package the way you installed it: `claude plugin uninstall kompactor`, `pipx uninstall kompactor`, or uninstall the extension in VS Code. Setup never replaces a status line or shell prefix you already had, and keeps a backup in `~/.claude/settings.json.kompactor-backup`.
|
|
120
135
|
|
|
136
|
+
## What Kompactor runs and changes
|
|
137
|
+
|
|
138
|
+
Everything below happens on your machine. Kompactor makes no network requests of its own and has no telemetry. (If you install the optional `tiktoken` package for exact token counts, tiktoken downloads its token table once, on first use.)
|
|
139
|
+
|
|
140
|
+
**What it runs.** Python hooks that Claude Code calls (`python3 kompactor/_launch.py hook <name>` from the plugin folder): after Bash, Read, Grep, Glob and edit tool calls; before Read; at session start, subagent start and before compaction; and when a turn ends. After `/kompactor setup` on macOS and Linux, Claude's Bash commands run through Kompactor's shell prefix (`~/.kompactor/bin/kompactor-shell`) so failing runs can be compacted too. The prefix runs after Claude Code's permission check, on Claude's original command, and returns its exit code unchanged.
|
|
141
|
+
|
|
142
|
+
**What it adds to Claude's context.** Compacted tool output, outlines in place of large file reads, a code map once per session, a one-line savings note after each turn, and short reply rules at session start and for subagents. The rules are plain text you can read in [`kompactor/rules.py`](https://github.com/amarkum/kompactor/blob/main/kompactor/rules.py); the level decides which ones apply (none at level 1).
|
|
143
|
+
|
|
144
|
+
**What it reads.** The tool output Claude Code passes to its hooks, the files Claude reads (to outline them), your project's source (for the code graph), and the context size Claude Code gives the status line. Hooks never read your session transcripts. `kompactor estimate` and the dashboard's **Replay your history** panel read your local Claude Code session logs in `~/.claude/projects`, only when you run them, to show what Kompactor would have saved; the result isn't stored or sent anywhere.
|
|
145
|
+
|
|
146
|
+
**What it writes.** `~/.kompactor/`: settings, the savings ledger, raw copies of shortened output (for `kompactor show`), the code graph index and the `~/.kompactor/bin/` launchers (written at session start). History is deleted after 30 days by default (`retention_days`). Setup edits `~/.claude/settings.json` to add Kompactor's hooks (non-plugin installs), a status line (only if you have none) and `CLAUDE_CODE_SHELL_PREFIX` (only if it isn't set), keeping a backup in `settings.json.kompactor-backup`; non-plugin installs also get the `/kompactor` skill in `~/.claude/skills`. Nothing is written to your repositories.
|
|
147
|
+
|
|
148
|
+
**What it never does.** Change your permission rules or sandbox settings, change what a command does, or send your code or output anywhere. Details: [privacy policy](https://github.com/amarkum/kompactor/blob/main/PRIVACY.md).
|
|
149
|
+
|
|
121
150
|
## Evidence
|
|
122
151
|
|
|
123
152
|
`kompactor prove` runs each task in real headless Claude Code sessions (`claude -p`), 3 times without Kompactor and 3 times with it, interleaved, with the same prompt, tools, turn limit and budget cap. Tokens are input + cache writes + cache reads + output across every API call; cost is Claude Code's own figure.
|
|
@@ -196,7 +225,17 @@ When a teammate trusts the folder, Claude Code installs the plugin; each person
|
|
|
196
225
|
{ "preset": 4, "overrides": { "never_compact": ["terraform plan"] } }
|
|
197
226
|
```
|
|
198
227
|
|
|
199
|
-
**Admins** can put the same keys in Claude Code's managed settings to roll it out to every machine. To control what your team runs, fork the repo and use `your-org/kompactor` in the commands above. The
|
|
228
|
+
**Admins** can put the same keys in Claude Code's managed settings to roll it out to every machine. To control what your team runs, fork the repo and use `your-org/kompactor` in the commands above. The dashboard's **Guide** page covers rollout, settings and FAQs in more detail.
|
|
229
|
+
|
|
230
|
+
## Troubleshooting
|
|
231
|
+
|
|
232
|
+
| Problem | What to do |
|
|
233
|
+
|---|---|
|
|
234
|
+
| No savings line after replies | Restart Claude Code after setup, then run `kompactor doctor`: it checks the installation and shows recent hook errors. The dashboard's **Self-test** panel runs every hook once. |
|
|
235
|
+
| An output looks too short | Claude can fetch the original with `kompactor show <id>` (the id is in the shortened output), or search earlier outputs with `kompactor search "text"`. |
|
|
236
|
+
| A command should never be shortened | Add it to **Never compact these commands** on the dashboard, or in `.kompactor.json` (`"never_compact": ["terraform plan"]`). For a single run, start the command with `KOMPACTOR_RAW=1`. |
|
|
237
|
+
| Something seems off | `kompactor stop` turns Kompactor off at once; every hook also lets the original output through if it hits an error. Errors are logged to `~/.kompactor/hook-errors.log`. |
|
|
238
|
+
| The dashboard port is busy | `kompactor start` picks a free port by itself and prints the address. |
|
|
200
239
|
|
|
201
240
|
## Commands
|
|
202
241
|
|
|
@@ -217,7 +256,7 @@ kompactor ui the dashboard, in the foreground
|
|
|
217
256
|
## Development
|
|
218
257
|
|
|
219
258
|
```bash
|
|
220
|
-
python -m unittest discover tests #
|
|
259
|
+
python -m unittest discover tests # 80 tests, including the real shell prefix run as Claude Code runs it
|
|
221
260
|
node vscode/test/smoke.js # VS Code extension against a mock API
|
|
222
261
|
./scripts/build-vsix.sh # › dist/kompactor-<version>.vsix
|
|
223
262
|
python scripts/bump_version.py X.Y.Z # bump every version; pushing to main publishes to PyPI
|
|
@@ -227,4 +266,10 @@ How Claude Code's hooks behave on success, failure and very long output, and why
|
|
|
227
266
|
|
|
228
267
|
Kompactor was called Kompact until version 0.4; installing it replaces an old Kompact install's settings.
|
|
229
268
|
|
|
230
|
-
|
|
269
|
+
## Support and security
|
|
270
|
+
|
|
271
|
+
- **Questions, bugs and ideas:** [open an issue](https://github.com/amarkum/kompactor/issues).
|
|
272
|
+
- **Security problems:** please report them privately, as described in [SECURITY.md](https://github.com/amarkum/kompactor/blob/main/SECURITY.md).
|
|
273
|
+
- **Privacy:** [PRIVACY.md](https://github.com/amarkum/kompactor/blob/main/PRIVACY.md) explains what Kompactor stores and how to delete it.
|
|
274
|
+
|
|
275
|
+
[MIT License](https://github.com/amarkum/kompactor/blob/main/LICENSE)
|
|
@@ -391,7 +391,7 @@ def cmd_uninstall(a):
|
|
|
391
391
|
print(" Kept savings history and config in ~/.kompactor (add --purge to delete).")
|
|
392
392
|
if installer.status()["plugin_enabled"]:
|
|
393
393
|
print(" The Claude Code plugin is still enabled; run: claude plugin uninstall kompactor")
|
|
394
|
-
print("
|
|
394
|
+
print(" Installed it as a Python package? Remove that too, with the tool you installed it with (pip, pipx or uv).")
|
|
395
395
|
return 0
|
|
396
396
|
|
|
397
397
|
|
|
@@ -411,13 +411,18 @@ def cmd_statusline(a):
|
|
|
411
411
|
print("Kompactor · stopped (kompactor start)")
|
|
412
412
|
return 0
|
|
413
413
|
sid = event.get("session_id", "")
|
|
414
|
+
ctx = (event.get("context_window") or {}).get("total_input_tokens")
|
|
415
|
+
ctx = int(ctx) if isinstance(ctx, (int, float)) and ctx > 0 else 0
|
|
416
|
+
if sid and ctx:
|
|
417
|
+
store.note_context(sid, ctx)
|
|
414
418
|
cfg = config.load((event.get("workspace") or {}).get("current_dir") or event.get("cwd") or os.getcwd())
|
|
415
419
|
name = config.PRESETS[cfg["preset"]]["name"]
|
|
416
420
|
tot = store.session_totals(sid) if sid else {}
|
|
417
421
|
if tot.get("saved", 0) > 0:
|
|
418
422
|
parts = [f"Kompactor −{hooks._fmt(tot['saved'])}"]
|
|
419
|
-
|
|
420
|
-
|
|
423
|
+
ctx = ctx or tot["context"]
|
|
424
|
+
if ctx and tot["in_context_saved"] > 0:
|
|
425
|
+
parts.append(f"ctx −{100 * tot['in_context_saved'] / (ctx + tot['in_context_saved']):.0f}%")
|
|
421
426
|
if tot["last_turn_saved"] > 0:
|
|
422
427
|
parts.append(f"last turn −{hooks._fmt(tot['last_turn_saved'])}")
|
|
423
428
|
parts.append(name)
|
|
@@ -284,28 +284,6 @@ def session_start(event: dict, cfg: dict) -> None:
|
|
|
284
284
|
_emit({"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": text}})
|
|
285
285
|
|
|
286
286
|
|
|
287
|
-
def _context_tokens(transcript_path: str) -> int:
|
|
288
|
-
"""Size of the context Claude last saw, from the newest usage block in the transcript."""
|
|
289
|
-
try:
|
|
290
|
-
with open(transcript_path, "rb") as f:
|
|
291
|
-
f.seek(0, 2)
|
|
292
|
-
f.seek(max(0, f.tell() - 400_000))
|
|
293
|
-
tail = f.read().decode("utf-8", "replace").splitlines()
|
|
294
|
-
except (OSError, TypeError):
|
|
295
|
-
return 0
|
|
296
|
-
for line in reversed(tail):
|
|
297
|
-
if '"usage"' not in line:
|
|
298
|
-
continue
|
|
299
|
-
try:
|
|
300
|
-
u = json.loads(line).get("message", {}).get("usage", {})
|
|
301
|
-
except ValueError:
|
|
302
|
-
continue
|
|
303
|
-
if u:
|
|
304
|
-
return int(u.get("input_tokens", 0) + u.get("cache_read_input_tokens", 0)
|
|
305
|
-
+ u.get("cache_creation_input_tokens", 0))
|
|
306
|
-
return 0
|
|
307
|
-
|
|
308
|
-
|
|
309
287
|
def turn_message(turn: dict, totals: dict) -> str:
|
|
310
288
|
parts = []
|
|
311
289
|
if turn["saved"] > 0:
|
|
@@ -325,7 +303,7 @@ def stop(event: dict, cfg: dict) -> None:
|
|
|
325
303
|
if event.get("stop_hook_active"):
|
|
326
304
|
return
|
|
327
305
|
session = event.get("session_id", "")
|
|
328
|
-
ctx =
|
|
306
|
+
ctx = store.context_size(session) # recorded by the status line; transcripts are never read
|
|
329
307
|
turn, totals = store.close_turn(session, cfg["project"], ctx)
|
|
330
308
|
msg = turn_message(turn, totals)
|
|
331
309
|
store.write_live({"ts": time.time(), "session": session, "project": cfg["project"], "preset": cfg["preset"],
|
|
@@ -91,7 +91,7 @@ def prefix_path() -> Path:
|
|
|
91
91
|
|
|
92
92
|
def prefix_active() -> bool:
|
|
93
93
|
"""True inside a Claude Code session whose Bash commands run through Kompactor's shell prefix."""
|
|
94
|
-
cur = os.environ.get(
|
|
94
|
+
cur = os.environ.get("CLAUDE_CODE_SHELL_PREFIX", "")
|
|
95
95
|
return bool(PREFIX_OURS.search(cur)) and os.path.exists(cur)
|
|
96
96
|
|
|
97
97
|
|
|
@@ -122,10 +122,10 @@ def parse_source(url: str) -> dict:
|
|
|
122
122
|
return {"repo": repo, "clone": url, "marketplace": url, "dir": parts[-1]}
|
|
123
123
|
|
|
124
124
|
|
|
125
|
-
def source() -> dict:
|
|
125
|
+
def source(repo=None) -> dict:
|
|
126
126
|
"""The repo the dashboard's setup instructions name: Kompactor's public repo, or a team's fork set
|
|
127
127
|
with KOMPACTOR_REPO (owner/repo or a git URL). Nothing is read from git or install records."""
|
|
128
|
-
url = os.environ.get("KOMPACTOR_REPO", "").strip() or DEFAULT_REPO
|
|
128
|
+
url = (os.environ.get("KOMPACTOR_REPO", "") if repo is None else repo).strip() or DEFAULT_REPO
|
|
129
129
|
if re.fullmatch(r"[\w.-]+/[\w.-]+", url):
|
|
130
130
|
url = f"https://github.com/{url}"
|
|
131
131
|
return parse_source(url)
|
|
@@ -292,6 +292,15 @@ def install(scope: str = "user", project: str = "", statusline: bool = True, uni
|
|
|
292
292
|
# self-test: run every installed hook command exactly as Claude Code would
|
|
293
293
|
|
|
294
294
|
|
|
295
|
+
def _selftest_env(tmp: str) -> dict:
|
|
296
|
+
"""Only what a hook needs to start: the self-test never hands hooks the rest of your environment."""
|
|
297
|
+
env = {"PATH": os.environ.get("PATH", ""), "HOME": os.environ.get("HOME", ""),
|
|
298
|
+
"USERPROFILE": os.environ.get("USERPROFILE", ""), "SYSTEMROOT": os.environ.get("SYSTEMROOT", ""),
|
|
299
|
+
"TMPDIR": os.environ.get("TMPDIR", ""), "TEMP": os.environ.get("TEMP", ""),
|
|
300
|
+
"TMP": os.environ.get("TMP", ""), "LANG": os.environ.get("LANG", "")}
|
|
301
|
+
return {**{k: v for k, v in env.items() if v}, "KOMPACTOR_HOME": tmp}
|
|
302
|
+
|
|
303
|
+
|
|
295
304
|
def selftest() -> list:
|
|
296
305
|
"""Run each Kompactor hook from settings.json with a realistic event in an isolated
|
|
297
306
|
KOMPACTOR_HOME. Returns [{hook, ok, ms, detail}]."""
|
|
@@ -337,7 +346,7 @@ def selftest() -> list:
|
|
|
337
346
|
continue
|
|
338
347
|
cmd = f"{launcher()} hook {handler}" # plugin hooks: exercise the same code path
|
|
339
348
|
ev = {"session_id": "kompactor-selftest", "cwd": tmp, "hook_event_name": event, **events[handler]}
|
|
340
|
-
env =
|
|
349
|
+
env = _selftest_env(tmp)
|
|
341
350
|
t0 = _time.time()
|
|
342
351
|
try:
|
|
343
352
|
p = subprocess.run(cmd, shell=True, input=json.dumps(ev), capture_output=True, text=True, timeout=20,
|
|
@@ -372,7 +381,7 @@ def _check_prefix(prefix: str, tmp: str) -> dict:
|
|
|
372
381
|
t0 = _time.time()
|
|
373
382
|
try:
|
|
374
383
|
p = subprocess.run([prefix, full], capture_output=True, text=True, timeout=30,
|
|
375
|
-
env=
|
|
384
|
+
env=_selftest_env(tmp))
|
|
376
385
|
ok = p.returncode == 1 and "test_boom" in p.stdout and "test_7 PASSED" not in p.stdout and "[kompactor:" in p.stdout
|
|
377
386
|
detail = "ok" if ok else f"exit {p.returncode}: {(p.stdout + p.stderr)[-160:]}"
|
|
378
387
|
except (OSError, subprocess.TimeoutExpired) as e:
|
|
@@ -139,7 +139,7 @@ svg.chev { width: 11px; height: 11px; stroke: var(--ink-3); fill: none; stroke-w
|
|
|
139
139
|
<pre><code>pipx install kompactor
|
|
140
140
|
kompactor setup</code></pre></div>
|
|
141
141
|
<div class="card"><b>VS Code</b><span class="muted">Download the extension file from the repo's Releases page, then click <b>Enable</b> when Kompactor asks.</span>
|
|
142
|
-
<pre><code>code --install-extension kompactor-0.7.
|
|
142
|
+
<pre><code>code --install-extension kompactor-0.7.4.vsix</code></pre></div>
|
|
143
143
|
</div>
|
|
144
144
|
<p class="muted">Windows: run <code>.\kompactor\install.ps1</code> from a clone, or use the VS Code extension. The CLI and the VS Code extension read the same <code>~/.claude/settings.json</code>, so one install covers both. A backup of that file is kept as <code>settings.json.kompactor-backup</code>.</p>
|
|
145
145
|
<p class="muted">Setup adds hooks, a status line (if you don't have one) and, on macOS and Linux, Claude Code's <code>CLAUDE_CODE_SHELL_PREFIX</code> setting pointing at <code>~/.kompactor/bin/kompactor-shell</code>, which is what lets Kompactor compact failing commands. It never replaces a status line or shell prefix you already have. Restart open Claude Code sessions after setup.</p>
|
|
@@ -30,6 +30,9 @@ CREATE TABLE IF NOT EXISTS turns (
|
|
|
30
30
|
session TEXT, idx INTEGER, ts REAL, project TEXT, raw_tokens INTEGER, kept_tokens INTEGER,
|
|
31
31
|
context_tokens INTEGER, features TEXT
|
|
32
32
|
);
|
|
33
|
+
CREATE TABLE IF NOT EXISTS context (
|
|
34
|
+
session TEXT PRIMARY KEY, tokens INTEGER, ts REAL
|
|
35
|
+
);
|
|
33
36
|
"""
|
|
34
37
|
|
|
35
38
|
|
|
@@ -113,6 +116,19 @@ def close_turn(session: str, project: str, context_tokens: int = 0):
|
|
|
113
116
|
return turn, totals
|
|
114
117
|
|
|
115
118
|
|
|
119
|
+
def note_context(session: str, tokens: int) -> None:
|
|
120
|
+
"""The context size Claude Code hands the status line (context_window.total_input_tokens).
|
|
121
|
+
Kompactor never reads session transcripts for it."""
|
|
122
|
+
with connect() as db:
|
|
123
|
+
db.execute("INSERT OR REPLACE INTO context VALUES (?,?,?)", (session, int(tokens), time.time()))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def context_size(session: str) -> int:
|
|
127
|
+
with connect() as db:
|
|
128
|
+
row = db.execute("SELECT tokens FROM context WHERE session=?", (session,)).fetchone()
|
|
129
|
+
return row[0] if row else 0
|
|
130
|
+
|
|
131
|
+
|
|
116
132
|
def session_totals(session: str) -> dict:
|
|
117
133
|
"""Savings so far for one session (for status lines when several sessions run at once)."""
|
|
118
134
|
with connect() as db:
|
|
@@ -138,6 +154,7 @@ def prune(retention_days: float) -> None:
|
|
|
138
154
|
db.execute(f"DELETE FROM {table} WHERE ts < ?", (cutoff,))
|
|
139
155
|
db.execute("DELETE FROM reads WHERE ts < ?", (time.time() - 2 * 86400,))
|
|
140
156
|
db.execute("DELETE FROM flags WHERE ts < ?", (time.time() - 2 * 86400,))
|
|
157
|
+
db.execute("DELETE FROM context WHERE ts < ?", (time.time() - 2 * 86400,))
|
|
141
158
|
db.execute("DELETE FROM sessions WHERE COALESCE(NULLIF(last_turn, 0), started) < ?", (cutoff,))
|
|
142
159
|
|
|
143
160
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kompactor
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: Cut the tokens Claude Code spends on noise: compacted command output (failing runs included), outline-first reads, a local code graph, and per-turn savings.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/amarkum/kompactor
|
|
@@ -21,7 +21,10 @@ Requires-Dist: tiktoken>=0.7; extra == "exact"
|
|
|
21
21
|
Dynamic: license-file
|
|
22
22
|
|
|
23
23
|
<p align="center">
|
|
24
|
-
<
|
|
24
|
+
<picture>
|
|
25
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/amarkum/kompactor/main/docs/brand/kompactor-logo-dark.png">
|
|
26
|
+
<img src="https://raw.githubusercontent.com/amarkum/kompactor/main/docs/brand/kompactor-logo-light.png" alt="Kompactor" width="240">
|
|
27
|
+
</picture>
|
|
25
28
|
</p>
|
|
26
29
|
|
|
27
30
|
<p align="center"><b>Cut the tokens Claude Code spends on noise.</b><br>
|
|
@@ -39,7 +42,7 @@ Most of a Claude Code session's tokens aren't your prompt or Claude's answer. Th
|
|
|
39
42
|
|
|
40
43
|
## Contents
|
|
41
44
|
|
|
42
|
-
[Install](#install) · [After you install](#after-you-install) · [How it works](#how-it-works) · [Use it from Claude Code](#use-it-from-claude-code) · [Quality vs tokens](#quality-vs-tokens) · [See your savings](#see-your-savings) · [Turn it off](#turn-it-off-or-remove-it) · [Evidence](#evidence) · [Code graph](#code-graph) · [Teams](#use-it-across-a-team) · [Commands](#commands)
|
|
45
|
+
[Install](#install) · [After you install](#after-you-install) · [How it works](#how-it-works) · [Examples](#examples) · [Use it from Claude Code](#use-it-from-claude-code) · [Quality vs tokens](#quality-vs-tokens) · [See your savings](#see-your-savings) · [Turn it off](#turn-it-off-or-remove-it) · [What it runs and changes](#what-kompactor-runs-and-changes) · [Evidence](#evidence) · [Code graph](#code-graph) · [Teams](#use-it-across-a-team) · [Troubleshooting](#troubleshooting) · [Commands](#commands) · [Support](#support-and-security)
|
|
43
46
|
|
|
44
47
|
## Install
|
|
45
48
|
|
|
@@ -59,7 +62,7 @@ pipx install kompactor # or: pip install kompactor · uv tool install kom
|
|
|
59
62
|
kompactor setup
|
|
60
63
|
```
|
|
61
64
|
|
|
62
|
-
**VS Code.** Download `kompactor-0.7.
|
|
65
|
+
**VS Code.** Download `kompactor-0.7.4.vsix` from [Releases](https://github.com/amarkum/kompactor/releases), install it (Extensions › `⋯` › *Install from VSIX…*), and click **Enable** when Kompactor asks.
|
|
63
66
|
|
|
64
67
|
## After you install
|
|
65
68
|
|
|
@@ -67,7 +70,7 @@ kompactor setup
|
|
|
67
70
|
2. **Work as usual.** You don't call Kompactor; it acts on Claude's tool calls by itself. After each reply you'll see a line such as `Kompactor: −8.2k tokens this turn`, and the status line shows the running total.
|
|
68
71
|
3. **Check it's on:** run `kompactor` in a terminal (or `/kompactor` in Claude Code). It shows whether it's on, your level, savings so far and how to change things:
|
|
69
72
|
```
|
|
70
|
-
Kompactor 0.7.
|
|
73
|
+
Kompactor 0.7.4: ON
|
|
71
74
|
Connected via the Claude Code plugin. New Claude Code sessions are compacted.
|
|
72
75
|
Level: Balanced (level 3 of 5). Change it: `kompactor config preset 1-5`, the dashboard, or /kompactor balanced
|
|
73
76
|
Saved so far: 41.3k tokens (−38%) over 52 tool calls. Details: `kompactor gain`
|
|
@@ -88,6 +91,18 @@ Kompactor uses Claude Code's hooks, so it sits between Claude's tools and Claude
|
|
|
88
91
|
|
|
89
92
|
**Nothing is lost.** Every shortened output keeps a raw copy that Claude can fetch with `kompactor show <id>`. Kompactor never changes what a command does, your permission rules still apply, and if anything goes wrong it lets the original output through. Everything runs on your machine; nothing is sent anywhere ([privacy](https://github.com/amarkum/kompactor/blob/main/PRIVACY.md)).
|
|
90
93
|
|
|
94
|
+
## Examples
|
|
95
|
+
|
|
96
|
+
Ask Claude Code the way you normally would. These prompts come from the [evidence](#evidence) runs:
|
|
97
|
+
|
|
98
|
+
| You ask | What Kompactor does | Measured |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| "Run the tests and tell me which one fails and why." | Claude sees the failing test, its error and the summary line instead of hundreds of passing tests, even when the run exits non-zero. | Go: −33% tokens; vitest: −37% cost |
|
|
101
|
+
| "Where is the session cookie signed?" | Big files arrive as outlines with line ranges, and the first search adds a short map of the main modules, so Claude finds the code in fewer turns. | Flask: −26% tokens |
|
|
102
|
+
| "Install the dependencies and check that it builds." | Download and progress chatter is dropped; warnings, errors and the result are kept. | `npm install`: ~33,000 › 469 tokens |
|
|
103
|
+
| "What does changing `createStore` affect?" | The code graph lists the files that use it and the tests to run. | 27/27 answers correct |
|
|
104
|
+
| `/kompactor review` | Lists cuts in your current diff: duplicated helpers, unused options, dead branches. | |
|
|
105
|
+
|
|
91
106
|
## Use it from Claude Code
|
|
92
107
|
|
|
93
108
|
Type these in any Claude Code chat:
|
|
@@ -140,6 +155,20 @@ kompactor uninstall # remove hooks, status line, shell prefix and skill (add -
|
|
|
140
155
|
```
|
|
141
156
|
Then remove the package the way you installed it: `claude plugin uninstall kompactor`, `pipx uninstall kompactor`, or uninstall the extension in VS Code. Setup never replaces a status line or shell prefix you already had, and keeps a backup in `~/.claude/settings.json.kompactor-backup`.
|
|
142
157
|
|
|
158
|
+
## What Kompactor runs and changes
|
|
159
|
+
|
|
160
|
+
Everything below happens on your machine. Kompactor makes no network requests of its own and has no telemetry. (If you install the optional `tiktoken` package for exact token counts, tiktoken downloads its token table once, on first use.)
|
|
161
|
+
|
|
162
|
+
**What it runs.** Python hooks that Claude Code calls (`python3 kompactor/_launch.py hook <name>` from the plugin folder): after Bash, Read, Grep, Glob and edit tool calls; before Read; at session start, subagent start and before compaction; and when a turn ends. After `/kompactor setup` on macOS and Linux, Claude's Bash commands run through Kompactor's shell prefix (`~/.kompactor/bin/kompactor-shell`) so failing runs can be compacted too. The prefix runs after Claude Code's permission check, on Claude's original command, and returns its exit code unchanged.
|
|
163
|
+
|
|
164
|
+
**What it adds to Claude's context.** Compacted tool output, outlines in place of large file reads, a code map once per session, a one-line savings note after each turn, and short reply rules at session start and for subagents. The rules are plain text you can read in [`kompactor/rules.py`](https://github.com/amarkum/kompactor/blob/main/kompactor/rules.py); the level decides which ones apply (none at level 1).
|
|
165
|
+
|
|
166
|
+
**What it reads.** The tool output Claude Code passes to its hooks, the files Claude reads (to outline them), your project's source (for the code graph), and the context size Claude Code gives the status line. Hooks never read your session transcripts. `kompactor estimate` and the dashboard's **Replay your history** panel read your local Claude Code session logs in `~/.claude/projects`, only when you run them, to show what Kompactor would have saved; the result isn't stored or sent anywhere.
|
|
167
|
+
|
|
168
|
+
**What it writes.** `~/.kompactor/`: settings, the savings ledger, raw copies of shortened output (for `kompactor show`), the code graph index and the `~/.kompactor/bin/` launchers (written at session start). History is deleted after 30 days by default (`retention_days`). Setup edits `~/.claude/settings.json` to add Kompactor's hooks (non-plugin installs), a status line (only if you have none) and `CLAUDE_CODE_SHELL_PREFIX` (only if it isn't set), keeping a backup in `settings.json.kompactor-backup`; non-plugin installs also get the `/kompactor` skill in `~/.claude/skills`. Nothing is written to your repositories.
|
|
169
|
+
|
|
170
|
+
**What it never does.** Change your permission rules or sandbox settings, change what a command does, or send your code or output anywhere. Details: [privacy policy](https://github.com/amarkum/kompactor/blob/main/PRIVACY.md).
|
|
171
|
+
|
|
143
172
|
## Evidence
|
|
144
173
|
|
|
145
174
|
`kompactor prove` runs each task in real headless Claude Code sessions (`claude -p`), 3 times without Kompactor and 3 times with it, interleaved, with the same prompt, tools, turn limit and budget cap. Tokens are input + cache writes + cache reads + output across every API call; cost is Claude Code's own figure.
|
|
@@ -218,7 +247,17 @@ When a teammate trusts the folder, Claude Code installs the plugin; each person
|
|
|
218
247
|
{ "preset": 4, "overrides": { "never_compact": ["terraform plan"] } }
|
|
219
248
|
```
|
|
220
249
|
|
|
221
|
-
**Admins** can put the same keys in Claude Code's managed settings to roll it out to every machine. To control what your team runs, fork the repo and use `your-org/kompactor` in the commands above. The
|
|
250
|
+
**Admins** can put the same keys in Claude Code's managed settings to roll it out to every machine. To control what your team runs, fork the repo and use `your-org/kompactor` in the commands above. The dashboard's **Guide** page covers rollout, settings and FAQs in more detail.
|
|
251
|
+
|
|
252
|
+
## Troubleshooting
|
|
253
|
+
|
|
254
|
+
| Problem | What to do |
|
|
255
|
+
|---|---|
|
|
256
|
+
| No savings line after replies | Restart Claude Code after setup, then run `kompactor doctor`: it checks the installation and shows recent hook errors. The dashboard's **Self-test** panel runs every hook once. |
|
|
257
|
+
| An output looks too short | Claude can fetch the original with `kompactor show <id>` (the id is in the shortened output), or search earlier outputs with `kompactor search "text"`. |
|
|
258
|
+
| A command should never be shortened | Add it to **Never compact these commands** on the dashboard, or in `.kompactor.json` (`"never_compact": ["terraform plan"]`). For a single run, start the command with `KOMPACTOR_RAW=1`. |
|
|
259
|
+
| Something seems off | `kompactor stop` turns Kompactor off at once; every hook also lets the original output through if it hits an error. Errors are logged to `~/.kompactor/hook-errors.log`. |
|
|
260
|
+
| The dashboard port is busy | `kompactor start` picks a free port by itself and prints the address. |
|
|
222
261
|
|
|
223
262
|
## Commands
|
|
224
263
|
|
|
@@ -239,7 +278,7 @@ kompactor ui the dashboard, in the foreground
|
|
|
239
278
|
## Development
|
|
240
279
|
|
|
241
280
|
```bash
|
|
242
|
-
python -m unittest discover tests #
|
|
281
|
+
python -m unittest discover tests # 80 tests, including the real shell prefix run as Claude Code runs it
|
|
243
282
|
node vscode/test/smoke.js # VS Code extension against a mock API
|
|
244
283
|
./scripts/build-vsix.sh # › dist/kompactor-<version>.vsix
|
|
245
284
|
python scripts/bump_version.py X.Y.Z # bump every version; pushing to main publishes to PyPI
|
|
@@ -249,4 +288,10 @@ How Claude Code's hooks behave on success, failure and very long output, and why
|
|
|
249
288
|
|
|
250
289
|
Kompactor was called Kompact until version 0.4; installing it replaces an old Kompact install's settings.
|
|
251
290
|
|
|
252
|
-
|
|
291
|
+
## Support and security
|
|
292
|
+
|
|
293
|
+
- **Questions, bugs and ideas:** [open an issue](https://github.com/amarkum/kompactor/issues).
|
|
294
|
+
- **Security problems:** please report them privately, as described in [SECURITY.md](https://github.com/amarkum/kompactor/blob/main/SECURITY.md).
|
|
295
|
+
- **Privacy:** [PRIVACY.md](https://github.com/amarkum/kompactor/blob/main/PRIVACY.md) explains what Kompactor stores and how to delete it.
|
|
296
|
+
|
|
297
|
+
[MIT License](https://github.com/amarkum/kompactor/blob/main/LICENSE)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kompactor"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.4"
|
|
8
8
|
description = "Cut the tokens Claude Code spends on noise: compacted command output (failing runs included), outline-first reads, a local code graph, and per-turn savings."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -435,12 +435,6 @@ class PresetAndLanguageTests(unittest.TestCase):
|
|
|
435
435
|
|
|
436
436
|
|
|
437
437
|
class DocsTests(unittest.TestCase):
|
|
438
|
-
def test_docs_copy_in_sync(self):
|
|
439
|
-
import subprocess
|
|
440
|
-
root = Path(__file__).resolve().parent.parent
|
|
441
|
-
self.assertEqual(subprocess.run([sys.executable, str(root / "scripts/sync_docs.py"), "--check"]).returncode, 0,
|
|
442
|
-
"docs/usage.html is stale: run python scripts/sync_docs.py")
|
|
443
|
-
|
|
444
438
|
def test_versions_agree(self):
|
|
445
439
|
root = Path(__file__).resolve().parent.parent
|
|
446
440
|
import re as _re
|
|
@@ -469,9 +463,15 @@ class TurnAndSetupTests(unittest.TestCase):
|
|
|
469
463
|
ev = {"session_id": "turn1", "tool_input": {"command": "git log"}, "cwd": TMP,
|
|
470
464
|
"tool_response": {"stdout": GIT_LOG, "stderr": "", "interrupted": False, "isImage": False}}
|
|
471
465
|
run_hook("post-bash", ev)
|
|
466
|
+
from kompactor import cli
|
|
467
|
+
# Claude Code tells the status line how big the context is; Kompactor records that number
|
|
468
|
+
sys.stdin = io.StringIO(json.dumps({"session_id": "turn1", "workspace": {"current_dir": TMP},
|
|
469
|
+
"context_window": {"total_input_tokens": 21000}}))
|
|
470
|
+
with redirect_stdout(io.StringIO()):
|
|
471
|
+
cli.main(["statusline"])
|
|
472
|
+
# ...and never reads the session transcript, even when the event names one
|
|
472
473
|
transcript = Path(TMP, "t.jsonl")
|
|
473
|
-
transcript.write_text(json.dumps({"type": "assistant", "message": {"usage": {
|
|
474
|
-
"input_tokens": 5, "cache_read_input_tokens": 20000, "cache_creation_input_tokens": 995}}}) + "\n")
|
|
474
|
+
transcript.write_text(json.dumps({"type": "assistant", "message": {"usage": {"input_tokens": 99999}}}) + "\n")
|
|
475
475
|
sys.stdin = io.StringIO(json.dumps({"session_id": "turn1", "transcript_path": str(transcript), "cwd": TMP}))
|
|
476
476
|
buf = io.StringIO()
|
|
477
477
|
with redirect_stdout(buf):
|
|
@@ -482,7 +482,6 @@ class TurnAndSetupTests(unittest.TestCase):
|
|
|
482
482
|
self.assertIn("context ≈", msg)
|
|
483
483
|
live = store.read_live()
|
|
484
484
|
self.assertEqual(live["session"], "turn1")
|
|
485
|
-
from kompactor import cli
|
|
486
485
|
sys.stdin = io.StringIO(json.dumps({"session_id": "turn1", "workspace": {"current_dir": TMP}}))
|
|
487
486
|
buf = io.StringIO()
|
|
488
487
|
with redirect_stdout(buf):
|
|
@@ -558,7 +557,7 @@ class SourceTests(unittest.TestCase):
|
|
|
558
557
|
"""The dashboard shows teammates install commands for the public repo, or a fork named by KOMPACTOR_REPO."""
|
|
559
558
|
|
|
560
559
|
def test_parse_source(self):
|
|
561
|
-
self.assertEqual(installer.parse_source("https://
|
|
560
|
+
self.assertEqual(installer.parse_source("https://someone@github.com/acme/kompactor.git"),
|
|
562
561
|
{"repo": "acme/kompactor", "clone": "https://github.com/acme/kompactor.git",
|
|
563
562
|
"marketplace": "acme/kompactor", "dir": "kompactor"})
|
|
564
563
|
self.assertEqual(installer.parse_source("git@github.com:acme/kompactor.git")["marketplace"], "acme/kompactor")
|
|
@@ -567,13 +566,9 @@ class SourceTests(unittest.TestCase):
|
|
|
567
566
|
self.assertEqual(installer.parse_source(""), {})
|
|
568
567
|
|
|
569
568
|
def test_source_defaults_to_the_public_repo(self):
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
with mock.patch.dict(os.environ, {"KOMPACTOR_REPO": "acme/kompactor"}): # a team's fork
|
|
574
|
-
self.assertEqual(installer.source()["clone"], "https://github.com/acme/kompactor.git")
|
|
575
|
-
with mock.patch.dict(os.environ, {"KOMPACTOR_REPO": "https://ghp_x@git.acme.dev/tools/kompactor.git"}):
|
|
576
|
-
self.assertNotIn("ghp_x", json.dumps(installer.source()))
|
|
569
|
+
self.assertEqual(installer.source("")["marketplace"], "amarkum/kompactor")
|
|
570
|
+
self.assertEqual(installer.source("acme/kompactor")["clone"], "https://github.com/acme/kompactor.git") # a fork
|
|
571
|
+
self.assertNotIn("someone@", json.dumps(installer.source("https://someone@git.acme.dev/tools/kompactor.git")))
|
|
577
572
|
|
|
578
573
|
|
|
579
574
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|