lakonai 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +359 -0
- package/assets/logo.svg +12 -0
- package/bin/lakon.js +177 -0
- package/package.json +53 -0
- package/src/filters/cat.js +18 -0
- package/src/filters/git.js +95 -0
- package/src/filters/grep.js +14 -0
- package/src/filters/index.js +35 -0
- package/src/filters/ls.js +34 -0
- package/src/filters/utils.js +29 -0
- package/src/hooks/bash-rewrite.js +54 -0
- package/src/hooks/grep-guard.js +83 -0
- package/src/hooks/read-guard.js +183 -0
- package/src/hooks/session-start.js +33 -0
- package/src/hooks/stop-hook.js +84 -0
- package/src/hooks/throttle.js +32 -0
- package/src/hooks/version-check.js +177 -0
- package/src/install/backup.js +70 -0
- package/src/install/claude-commands.js +71 -0
- package/src/install/claude-hook.js +177 -0
- package/src/install/index.js +159 -0
- package/src/install/paths.js +9 -0
- package/src/install/platforms.js +129 -0
- package/src/rules/caveman.md +103 -0
- package/src/tracking.js +232 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bargadev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/logo.svg" width="140" alt="lakon" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">lakon</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Cut LLM tokens by up to 94% — without losing a single identifier.</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<em>Spartan replies for AI agents. Less words. Win wars.</em>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="https://www.npmjs.com/package/lakon"><img src="https://img.shields.io/npm/v/lakon?color=0F0F0F&label=npm" alt="npm" /></a>
|
|
17
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-0F0F0F" alt="MIT" /></a>
|
|
18
|
+
<img src="https://img.shields.io/badge/node-%E2%89%A518-0F0F0F" alt="node ≥18" />
|
|
19
|
+
<img src="https://img.shields.io/badge/deps-0-0F0F0F" alt="zero dependencies" />
|
|
20
|
+
<img src="https://img.shields.io/badge/agents-6-0F0F0F" alt="6 AI agents" />
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
One command. <strong>Three fronts</strong>: terse model output · filtered shell output · blocked junk reads.<br/>
|
|
25
|
+
Plus session-level token tracking and one-day update notifications.<br/>
|
|
26
|
+
Works across <strong>Claude Code, Codex, Cursor, Windsurf, Cline, Gemini CLI</strong>.
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## At a glance — measured savings
|
|
32
|
+
|
|
33
|
+
| Command | Raw tokens | Filtered | Saved |
|
|
34
|
+
|----------------------------------|-----------:|---------:|--------:|
|
|
35
|
+
| `git log -p -10` | 10,497 | 78 | **-94%** |
|
|
36
|
+
| `ls -laR` (deep directory) | 23,624 | 117 | **-94%** |
|
|
37
|
+
| `git diff HEAD~5` | 13,230 | 798 | **-89%** |
|
|
38
|
+
| `git log --stat -50` | 4,845 | 439 | **-86%** |
|
|
39
|
+
| `git status` | 17 | 1 | **-89%** |
|
|
40
|
+
| `Read pnpm-lock.yaml` | ~56,000 | **blocked** | **-95%** |
|
|
41
|
+
| `Grep` (auto `head_limit`) | unbounded | 30 matches | **capped** |
|
|
42
|
+
|
|
43
|
+
Conservative numbers — peaks go higher in practice. Run `lakon inspect <cmd>` on your own commands to measure.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## The story behind the name
|
|
48
|
+
|
|
49
|
+
In 346 BC, Philip II of Macedon — father of Alexander the Great — sent the Spartans a message:
|
|
50
|
+
|
|
51
|
+
> *"If I invade Lakonía, I will raze your cities to the ground."*
|
|
52
|
+
|
|
53
|
+
The Spartans replied with a single word:
|
|
54
|
+
|
|
55
|
+
> *"If."*
|
|
56
|
+
|
|
57
|
+
That region was **Lakonía**. Its people gave the English language the word **laconic** — using as few words as possible. They didn't waste breath, didn't waste arrows, didn't waste anything.
|
|
58
|
+
|
|
59
|
+
Your AI coding agent does. It opens with *"Sure! I'd be happy to help…"*, repeats your question back, and explains what the diff already shows. It reads `git log` in full when one line per commit would do. Every wasted token is a soldier you didn't need to send.
|
|
60
|
+
|
|
61
|
+
**lakon trims both sides.**
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Three fronts. One install.
|
|
66
|
+
|
|
67
|
+
| Front | Wasted tokens look like… | lakon fixes it by… |
|
|
68
|
+
|--------------------------------|-------------------------------------------------------|-------------------------------------------------------------------------------------|
|
|
69
|
+
| **Output** (the model) | *"Great question! Let me explain…"* | Installing a terse-response rule. No preamble, no recap, no restating. |
|
|
70
|
+
| **Input** (your shell tools) | `git log` dumping 1.8 k tokens of author metadata | Wrapping `git`/`ls`/`grep`/`cat`/`tree`/`head`/`tail` and compressing before context. |
|
|
71
|
+
| **Reads** (file ingestion) | Agent runs `Read` on `pnpm-lock.yaml` → 80 k of nothing | A `PreToolUse` hook on `Read` blocks lockfiles & `node_modules`, caps files >800 lines. |
|
|
72
|
+
| **Search** (Grep tool) | `Grep` returns 800 matches and you re-read every one | A `PreToolUse` hook on `Grep` auto-caps `head_limit` at 30 with a one-shot hint. |
|
|
73
|
+
| **Analysis** (the rule) | `Read` 5k of logs to count errors in your head | "Think in code" — write `node -e '…filter…count'`, consume only the answer. |
|
|
74
|
+
|
|
75
|
+
Other tools stop at one front. lakon does all three transparently — your agent doesn't have to remember anything.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm install -g lakon
|
|
83
|
+
lakon install
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
That's it. `lakon install` configures your **global** agents — Claude Code, Codex, Gemini CLI — by writing rule blocks under `~/` only. It never touches your current directory by default.
|
|
87
|
+
|
|
88
|
+
Working inside a repo and want **per-project** rules (Cursor, Windsurf, Cline)? Add `--here`:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
cd path/to/your/repo
|
|
92
|
+
lakon install --here # globals + per-project rules in this dir
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
From the next session forward your agent:
|
|
96
|
+
|
|
97
|
+
1. **Responds tersely** — no preamble, no restating, no recap. (rule in `CLAUDE.md` / equivalent)
|
|
98
|
+
2. **Has its `Bash` calls auto-rewritten** — `PreToolUse` hook intercepts `git`/`ls`/`cat`/`grep`/etc and prefixes them with `lakon` transparently.
|
|
99
|
+
3. **Has its `Read` calls guarded** — a second hook denies `node_modules/`, lockfiles, and build artifacts (with a hint to `grep` instead), and auto-caps reads over 800 lines.
|
|
100
|
+
4. **Has its `Grep` calls capped** — a third hook auto-sets `head_limit` to 30 if you didn't, with a once-per-session hint to use `output_mode:"count"` for tallies.
|
|
101
|
+
5. **Is told to "think in code"** — for any count/filter/parse task, the rule pushes the agent toward a one-shot `node -e` (or `awk`) script that consumes the data so the agent consumes only the answer.
|
|
102
|
+
6. **Logs per-turn LLM token usage** — a `Stop` hook records `input_tokens` / `output_tokens` / `cache_read` after each model turn so `lakon gain` shows model-side savings alongside shell-side savings.
|
|
103
|
+
7. **Tells you about new versions** — a `SessionStart` hook checks npm once per day and surfaces a `lakon X.Y.Z available` notice inside the session (opt-out: `LAKON_NO_UPDATE_CHECK=1`).
|
|
104
|
+
|
|
105
|
+
You'll see savings stack up immediately in `lakon gain`.
|
|
106
|
+
|
|
107
|
+
> Hooks are currently Claude Code-only (the only platform with documented hook APIs). For Codex/Cursor/Windsurf/Cline/Gemini, the rule asks the model to grep-before-Read and use the `lakon` prefix itself.
|
|
108
|
+
|
|
109
|
+
> **Worried?** Every install backs up the target file first. `lakon revert` puts it back byte-for-byte.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Use the filter directly
|
|
114
|
+
|
|
115
|
+
The CLI works as a standalone tool too. Run any shell command through `lakon` (or the short alias `lak`) to filter its output:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
lakon git status # compressed git status
|
|
119
|
+
lakon git log -50 # one line per commit (hash + subject)
|
|
120
|
+
lakon git diff # only +/- lines, no noise
|
|
121
|
+
lakon ls -la # size + name only
|
|
122
|
+
lakon grep -r foo src/ # truncates at 40 matches
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Unsupported commands run unchanged.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## See your savings
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
lakon gain
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
lakon — savings this week: 36.4k tok saved across 512 shell calls (67%)
|
|
137
|
+
|
|
138
|
+
shell + read/grep guards (tokens filtered before context)
|
|
139
|
+
win calls before after saved %
|
|
140
|
+
1h 12 1.2k tok 380 tok 820 tok 68%
|
|
141
|
+
24h 87 9.8k tok 3.1k tok 6.7k tok 68%
|
|
142
|
+
7d 512 54.2k tok 17.8k tok 36.4k tok 67%
|
|
143
|
+
30d 2104 241.0k tok 79.2k tok 161.8k tok 67%
|
|
144
|
+
all 2104 241.0k tok 79.2k tok 161.8k tok 67%
|
|
145
|
+
|
|
146
|
+
llm output (model tokens — terse style trims this side)
|
|
147
|
+
win turns input output cache-read
|
|
148
|
+
1h 4 2.1k tok 320 tok 48.7k tok
|
|
149
|
+
24h 38 18.4k tok 2.6k tok 412.2k tok
|
|
150
|
+
7d 210 102.5k tok 14.1k tok 2.3M tok
|
|
151
|
+
|
|
152
|
+
top commands (all time)
|
|
153
|
+
git 812x saved 124.3k tok 72%
|
|
154
|
+
ls 541x saved 18.2k tok 58%
|
|
155
|
+
grep 339x saved 12.0k tok 65%
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The top block measures **input savings** (what filtered shell + guards prevented from entering context). The `llm output` block measures **model-side activity** so you can see verbosity dropping over time (and cache hits climbing). Set `LAKON_COLOR=1`/`0` (or `NO_COLOR=1`) to force/disable ANSI colors when piping.
|
|
159
|
+
|
|
160
|
+
### Inspect a single command
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
lakon inspect git log
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
cmd: git log
|
|
168
|
+
raw: 1234 tokens (8127 bytes)
|
|
169
|
+
filtered: 187 tokens (1240 bytes)
|
|
170
|
+
saved: 85%
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Commands
|
|
176
|
+
|
|
177
|
+
| Command | What it does |
|
|
178
|
+
|----------------------------------|-----------------------------------------------------------------------|
|
|
179
|
+
| `lakon install` | Install rule for detected GLOBAL agents only (no CWD writes) |
|
|
180
|
+
| `lakon install --here` | Globals + per-project rules (Cursor/Windsurf/Cline) in current dir |
|
|
181
|
+
| `lakon install --only <p>` | Install just one platform by id (any scope) |
|
|
182
|
+
| `lakon uninstall` | Strip the lakon block from each config (keeps your other content) |
|
|
183
|
+
| `lakon revert [--only <p>]` | Restore each config to its pre-install state from backup |
|
|
184
|
+
| `lakon backups` | Show backup history per platform |
|
|
185
|
+
| `lakon list` | Show supported platforms and which are detected |
|
|
186
|
+
| `lakon <cmd> [args]` | Run a command, filter its output, track savings |
|
|
187
|
+
| `lakon gain` | Show savings by hour / day / week / month / all-time + session totals |
|
|
188
|
+
| `lakon inspect <cmd>` | Run once and show raw-vs-filtered (no tracking) |
|
|
189
|
+
| `lakon reset` | Wipe the savings log |
|
|
190
|
+
| `lakon version` / `--version` / `-v` | Print the installed lakon version |
|
|
191
|
+
|
|
192
|
+
`lak` is the short alias for `lakon` — same behavior.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Supported filters
|
|
197
|
+
|
|
198
|
+
| Command | What it does |
|
|
199
|
+
|------------------------|-------------------------------------------------------------------|
|
|
200
|
+
| `git log` | One line per commit (`<hash> <subject>`), capped at 50 |
|
|
201
|
+
| `git status` | Drops hint paragraphs, separates changed vs untracked |
|
|
202
|
+
| `git diff` / `show` | Only `+`/`-`/`@@` lines, drops `index`/`---`/`+++`, cap 120 lines |
|
|
203
|
+
| `ls -la` / `tree` | `<size>\t<name>` (drops perms / dates / link targets), cap 60 |
|
|
204
|
+
| `cat` | Collapses blank-line runs, cap 200 lines |
|
|
205
|
+
| `head` / `tail` | Cap 50 lines |
|
|
206
|
+
| `grep` / `rg` / `ag` | Cap 15 matches with "tighten the pattern" hint |
|
|
207
|
+
|
|
208
|
+
Unsupported commands run unchanged (passthrough), still tracked at 0 % savings.
|
|
209
|
+
|
|
210
|
+
### Read tool guard (Claude Code)
|
|
211
|
+
|
|
212
|
+
The `Read` hook automatically:
|
|
213
|
+
|
|
214
|
+
- **Denies** paths under `node_modules/`, `vendor/`, `dist/`, `build/`, `target/`, `.next/`, `.nuxt/`, `.turbo/`, `.svelte-kit/`, `.parcel-cache/`, `.vercel/`, `coverage/`, `__pycache__/`, `.venv/`, `.git/objects/`, `__snapshots__/`, `.ipynb_checkpoints/`, `.mypy_cache/`, `.pytest_cache/`, `.ruff_cache/`, `.tox/`, `cypress/screenshots/`, `cypress/videos/`, `playwright-report/`, `test-results/`, `.idea/`, `.vscode/`, `tmp/`
|
|
215
|
+
- **Denies** lockfiles (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `Cargo.lock`, `go.sum`, `*.lock`)
|
|
216
|
+
- **Denies** build artifacts (`*.min.js`, `*.min.css`, `*.min.mjs`, `*.tsbuildinfo`, `*.map`, `*.log`, `*.pyc`, `*.pyo`, `*.so`, `*.o`, `*.a`, `*.dylib`, `*.dll`, `*.exe`, `*.class`, `*.wasm`)
|
|
217
|
+
- **Caps** files over 800 lines at 800 (with hint to `Read` again with `offset` for more, or `grep -n` for the symbol you need)
|
|
218
|
+
|
|
219
|
+
Each deny returns a one-line reason the model reads, so it knows to `grep -n` the symbol instead.
|
|
220
|
+
|
|
221
|
+
### Grep tool guard (Claude Code)
|
|
222
|
+
|
|
223
|
+
The `Grep` hook auto-sets `head_limit` to **30** when the agent didn't pass one. First call per 4-hour window includes a one-line hint suggesting `output_mode:"count"` for tallies; subsequent calls cap silently.
|
|
224
|
+
|
|
225
|
+
### Session output tracking (Claude Code)
|
|
226
|
+
|
|
227
|
+
A `Stop` hook fires at the end of every model turn, reads the latest `usage` block from the transcript, and appends a `cmd: "session"` entry to the log with `input_tokens`, `output_tokens`, `cache_read_input_tokens`, and `cache_creation_input_tokens`.
|
|
228
|
+
|
|
229
|
+
`lakon gain` renders these in a separate **session output** block (see example above) — so you can watch model-side verbosity drop and cache-hit ratios climb over time. Top commands list excludes session entries; they're not shell calls.
|
|
230
|
+
|
|
231
|
+
### Update notifications (Claude Code)
|
|
232
|
+
|
|
233
|
+
A `SessionStart` hook checks `registry.npmjs.org/lakon/latest` at most once per 24 hours (cached at `~/.lakon/version.json`) and, if a newer version exists, emits a `hookSpecificOutput.additionalContext` that surfaces inside the Claude session:
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
lakon 0.7.0 available (you have 0.6.0). Update: npm i -g lakon@latest
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Outside Claude, `lakon gain` and `lakon version` print the same notice on stderr (yellow when TTY).
|
|
240
|
+
|
|
241
|
+
**Opt out:** `LAKON_NO_UPDATE_CHECK=1`.
|
|
242
|
+
**Test endpoint:** `LAKON_REGISTRY_URL=http://localhost:8080/` (overrides the npm URL for local testing).
|
|
243
|
+
|
|
244
|
+
### Multi-profile Claude Code
|
|
245
|
+
|
|
246
|
+
If you use wrapper aliases like `claude-my=CLAUDE_CONFIG_DIR=$HOME/.claude-my claude` (e.g. one profile per Anthropic account or org), set the same env var when running `lakon install` so hooks and the rule file land in the right config dir:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
CLAUDE_CONFIG_DIR=$HOME/.claude-my lakon install
|
|
250
|
+
CLAUDE_CONFIG_DIR=$HOME/.claude-arco lakon install
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Each profile gets its own independent install. `lakon uninstall` / `lakon revert` respect the same env var.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Supported AI agents
|
|
258
|
+
|
|
259
|
+
| Agent | Scope | What `lakon install` writes |
|
|
260
|
+
|-----------------|----------|------------------------------------------------------------------------------------------------------|
|
|
261
|
+
| Claude Code¹ | global | Rule block in `~/.claude/CLAUDE.md` + **five** hooks in `~/.claude/settings.json` (`PreToolUse`: Bash rewrite + Read guard + Grep guard; `Stop`: session-usage log; `SessionStart`: update notify) + `/lakon:gain` `/lakon:reset` `/lakon:inspect` slash commands |
|
|
262
|
+
| Codex CLI | global | Rule block in `~/.codex/AGENTS.md` |
|
|
263
|
+
| Gemini CLI | global | Rule block in `~/.gemini/GEMINI.md` |
|
|
264
|
+
| Cursor | project² | `.cursor/rules/lakon.mdc` in the current dir |
|
|
265
|
+
| Windsurf | project² | `.windsurf/rules/lakon.md` in the current dir |
|
|
266
|
+
| Cline | project² | `.clinerules/lakon.md` in the current dir |
|
|
267
|
+
|
|
268
|
+
¹ "Claude Code" covers **every** Claude Code frontend — terminal CLI, VS Code extension, JetBrains plugin, desktop app. All read the same `~/.claude/CLAUDE.md` + `~/.claude/settings.json`, so one install lights up all of them.
|
|
269
|
+
|
|
270
|
+
² Project-scoped tools only read rules from the current directory, so `lakon install` skips them by default to avoid scattering files across your repos. Add `--here` (or use `--project`) when you actually want them in the current dir.
|
|
271
|
+
|
|
272
|
+
Each install is **idempotent** (rerunning replaces the existing block) and **reversible** (`uninstall` strips it, `revert` restores from backup).
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Backup & revert
|
|
277
|
+
|
|
278
|
+
Before writing to your config file for the first time, `lakon` copies it into `~/.lakon/backups/<platform>/<filename>.<timestamp>.bak`. Every install thereafter appends another snapshot to that file's manifest.
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
lakon uninstall # strips just the lakon block; keeps your other CLAUDE.md content
|
|
282
|
+
lakon revert # restores the file to its pre-install state, byte for byte
|
|
283
|
+
lakon backups # shows every snapshot, per platform, with timestamps
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Use `uninstall` to remove lakon while keeping your other edits. Use `revert` when you want a clean rollback to exactly the file you had before.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## How tracking works
|
|
291
|
+
|
|
292
|
+
Every filtered command appends a JSON line to `~/.lakon/log.jsonl`. The `Stop` hook appends one line per model turn with token counts (`cmd: "session"`). `lakon gain` reads that log and renders both sides separately.
|
|
293
|
+
|
|
294
|
+
The log stores: timestamp, command name, first few args, raw/filtered token counts (shell entries); timestamp, session id, `input_tokens` / `output_tokens` / `cache_read` / `cache_create` (session entries). **No file contents. No full arguments. No transcript content. No data ever leaves your machine** — except the one daily HEAD request to `registry.npmjs.org` for the update check (opt-out: `LAKON_NO_UPDATE_CHECK=1`).
|
|
295
|
+
|
|
296
|
+
Override the location with `LAKON_HOME=/path`. Disable per-command logging with `LAKON_NO_TRACK=1`.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Configuration
|
|
301
|
+
|
|
302
|
+
| Env var | Effect |
|
|
303
|
+
|-------------------------|-----------------------------------------------------------------------------|
|
|
304
|
+
| `LAKON_HOME` | Where to keep the log + backups + version cache (default `~/.lakon`) |
|
|
305
|
+
| `LAKON_NO_TRACK` | Set to `1` to disable per-command logging |
|
|
306
|
+
| `LAKON_NO_UPDATE_CHECK` | Set to `1` to disable the `SessionStart` npm check + terminal hint |
|
|
307
|
+
| `LAKON_REGISTRY_URL` | Override the npm registry URL used by the update check (testing) |
|
|
308
|
+
| `LAKON_COLOR` | `1` forces ANSI colors in `lakon gain`; `0` disables; unset = TTY auto-detect |
|
|
309
|
+
| `NO_COLOR` | Standard. Disables ANSI colors when set to any non-empty value. |
|
|
310
|
+
| `CLAUDE_CONFIG_DIR` | When set during `lakon install` / `uninstall`, hooks + rule land in that dir instead of `~/.claude/`. Used for multi-profile setups. |
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Philosophy
|
|
315
|
+
|
|
316
|
+
> *"Brevity is the soul of wit."* — Shakespeare, *Hamlet*
|
|
317
|
+
> *"Vēnī, vīdī, vīcī."* — Julius Caesar, three words to describe winning a war.
|
|
318
|
+
> *"If."* — Spartans, refusing to be intimidated by a single conditional.
|
|
319
|
+
|
|
320
|
+
Every token your agent emits or reads is paid for — in latency, in money, in context budget. The fastest way to think clearly is to speak briefly. lakon doesn't make your agent dumber; it makes it Spartan.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Development
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
git clone https://github.com/bargadev/lakon-lib
|
|
328
|
+
cd lakon-lib
|
|
329
|
+
npm install # only devDeps (c8 for coverage); zero runtime deps
|
|
330
|
+
node --test tests/ # run the suite
|
|
331
|
+
npm run test:coverage # text + HTML coverage report (coverage/index.html)
|
|
332
|
+
npm run test:coverage:check # fail if any metric drops below 100%
|
|
333
|
+
node bin/lakon.js --help
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Suite: **187 tests**. Coverage gate: **100% lines / 100% branches / 100% functions / 100% statements**. Zero runtime dependencies. Node ≥ 18.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Credits
|
|
341
|
+
|
|
342
|
+
Built on ideas from two excellent projects:
|
|
343
|
+
|
|
344
|
+
- [**caveman**](https://github.com/juliusbrussee/caveman) — terse-prose rule + auto-clarity carve-outs.
|
|
345
|
+
- [**rtk**](https://github.com/rtk-ai/rtk) — CLI output filtering as a force multiplier for LLM agents.
|
|
346
|
+
|
|
347
|
+
lakon condenses both into one zero-dependency npm package with a single install command, automatic backups, and time-windowed savings tracking.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## License
|
|
352
|
+
|
|
353
|
+
MIT. See [LICENSE](LICENSE).
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
<p align="center">
|
|
358
|
+
<sub>Speak less. Ship more.</sub>
|
|
359
|
+
</p>
|
package/assets/logo.svg
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" width="240" height="240" role="img" aria-label="lakon">
|
|
2
|
+
<title>lakon</title>
|
|
3
|
+
<desc>Spartan lambda inside a circular shield — symbol of Lakonía, ancient Sparta.</desc>
|
|
4
|
+
<style>
|
|
5
|
+
.mark { stroke: #0F0F0F; fill: none; }
|
|
6
|
+
@media (prefers-color-scheme: dark) {
|
|
7
|
+
.mark { stroke: #F5F5F5; }
|
|
8
|
+
}
|
|
9
|
+
</style>
|
|
10
|
+
<circle class="mark" cx="120" cy="120" r="106" stroke-width="5"/>
|
|
11
|
+
<path class="mark" d="M 72 178 L 120 60 L 168 178" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
|
|
12
|
+
</svg>
|
package/bin/lakon.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
const { filterCommand, isSupported, countTokensApprox } = require('../src/filters');
|
|
6
|
+
const { install, uninstall, revert, listPlatforms, backupsReport } = require('../src/install');
|
|
7
|
+
const tracking = require('../src/tracking');
|
|
8
|
+
const versionCheck = require('../src/hooks/version-check');
|
|
9
|
+
|
|
10
|
+
const HELP = `lakon — spartan replies for AI agents
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
lakon <cmd> [args...] Run <cmd> and filter its output (tracks savings)
|
|
14
|
+
lak <cmd> [args...] (short alias)
|
|
15
|
+
|
|
16
|
+
lakon install Install rule + hooks for detected GLOBAL platforms
|
|
17
|
+
(Claude Code / Codex / Gemini — touches ~/ only)
|
|
18
|
+
lakon install --here Same as above + per-project rules (Cursor /
|
|
19
|
+
Windsurf / Cline) written into the current dir
|
|
20
|
+
lakon install --only <p> Install just one platform by id (any scope)
|
|
21
|
+
(every install backs up the target file first)
|
|
22
|
+
lakon uninstall Strip the lakon block (keeps rest of file)
|
|
23
|
+
lakon revert [--only <p>] Restore files to pre-install state from backup
|
|
24
|
+
lakon backups Show backup history per platform
|
|
25
|
+
lakon list Show supported platforms
|
|
26
|
+
|
|
27
|
+
lakon gain Show token savings (hour / day / week / month / all)
|
|
28
|
+
lakon inspect <cmd> ... Run <cmd> once and show raw vs filtered (no tracking)
|
|
29
|
+
lakon reset Wipe the savings log
|
|
30
|
+
lakon version Print the installed lakon version
|
|
31
|
+
lakon --help This help
|
|
32
|
+
|
|
33
|
+
Supported filters: git (log/status/diff/show), ls, tree, cat, head, tail, grep, rg, ag.
|
|
34
|
+
Unsupported commands run unchanged (passthrough, still tracked as 0% savings).
|
|
35
|
+
|
|
36
|
+
Multi-profile Claude Code (e.g. claude-my / claude-arco wrappers):
|
|
37
|
+
CLAUDE_CONFIG_DIR=$HOME/.claude-my lakon install
|
|
38
|
+
CLAUDE_CONFIG_DIR=$HOME/.claude-arco lakon install
|
|
39
|
+
|
|
40
|
+
Update notifications:
|
|
41
|
+
SessionStart hook + \`lakon gain\` / \`lakon version\` check npm once per day.
|
|
42
|
+
Disable with LAKON_NO_UPDATE_CHECK=1.
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
function runAndFilter(cmd, args) {
|
|
46
|
+
const child = spawnSync(cmd, args, { encoding: 'utf8', stdio: ['inherit', 'pipe', 'inherit'] });
|
|
47
|
+
if (child.error) {
|
|
48
|
+
process.stderr.write(`lakon: ${child.error.message}\n`);
|
|
49
|
+
process.exit(127);
|
|
50
|
+
}
|
|
51
|
+
/* c8 ignore next */
|
|
52
|
+
const raw = child.stdout || '';
|
|
53
|
+
const filtered = isSupported(cmd) ? filterCommand(cmd, args, raw) : raw;
|
|
54
|
+
process.stdout.write(filtered);
|
|
55
|
+
/* c8 ignore next */
|
|
56
|
+
if (filtered && !filtered.endsWith('\n')) process.stdout.write('\n');
|
|
57
|
+
|
|
58
|
+
tracking.record({
|
|
59
|
+
cmd,
|
|
60
|
+
args,
|
|
61
|
+
rawTokens: countTokensApprox(raw),
|
|
62
|
+
filteredTokens: countTokensApprox(filtered),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/* c8 ignore next */
|
|
66
|
+
process.exit(child.status ?? 0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function inspectCmd(rest) {
|
|
70
|
+
if (!rest.length) {
|
|
71
|
+
process.stderr.write('lakon inspect: missing command\n');
|
|
72
|
+
process.exit(2);
|
|
73
|
+
}
|
|
74
|
+
const [cmd, ...args] = rest;
|
|
75
|
+
const child = spawnSync(cmd, args, { encoding: 'utf8' });
|
|
76
|
+
/* c8 ignore next */
|
|
77
|
+
const raw = child.stdout || '';
|
|
78
|
+
const filtered = isSupported(cmd) ? filterCommand(cmd, args, raw) : raw;
|
|
79
|
+
const rawTokens = countTokensApprox(raw);
|
|
80
|
+
const newTokens = countTokensApprox(filtered);
|
|
81
|
+
/* c8 ignore next */
|
|
82
|
+
const saved = rawTokens === 0 ? 0 : Math.round((1 - newTokens / rawTokens) * 100);
|
|
83
|
+
process.stdout.write(
|
|
84
|
+
`cmd: ${cmd} ${args.join(' ')}\n` +
|
|
85
|
+
`raw: ${rawTokens} tokens (${raw.length} bytes)\n` +
|
|
86
|
+
`filtered: ${newTokens} tokens (${filtered.length} bytes)\n` +
|
|
87
|
+
`saved: ${saved}%\n`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function printVersion() {
|
|
92
|
+
const pkg = require('../package.json');
|
|
93
|
+
process.stdout.write(`${pkg.name} ${pkg.version}\n`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function maybePrintUpdateHint() {
|
|
97
|
+
try {
|
|
98
|
+
const update = versionCheck.getCachedUpdate();
|
|
99
|
+
if (update) {
|
|
100
|
+
/* c8 ignore next */
|
|
101
|
+
const color = !process.env.NO_COLOR && process.stderr.isTTY;
|
|
102
|
+
const msg = versionCheck.formatNotice(update);
|
|
103
|
+
/* c8 ignore next */
|
|
104
|
+
process.stderr.write(color ? `\n\x1b[33m${msg}\x1b[0m\n` : `\n${msg}\n`);
|
|
105
|
+
}
|
|
106
|
+
/* c8 ignore next 3 */
|
|
107
|
+
} catch {
|
|
108
|
+
// never let update hint break a command
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function main() {
|
|
113
|
+
const argv = process.argv.slice(2);
|
|
114
|
+
if (!argv.length || argv[0] === '--help' || argv[0] === '-h') {
|
|
115
|
+
process.stdout.write(HELP);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (argv[0] === '--version' || argv[0] === '-v' || argv[0] === 'version') {
|
|
119
|
+
printVersion();
|
|
120
|
+
await versionCheck.checkForUpdate().catch(() => {});
|
|
121
|
+
maybePrintUpdateHint();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const [first, ...rest] = argv;
|
|
126
|
+
|
|
127
|
+
if (first === 'install') {
|
|
128
|
+
const onlyIdx = rest.indexOf('--only');
|
|
129
|
+
/* c8 ignore next */
|
|
130
|
+
const only = onlyIdx >= 0 ? rest[onlyIdx + 1] : null;
|
|
131
|
+
const here = rest.includes('--here');
|
|
132
|
+
await install({ only, here });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (first === 'uninstall') {
|
|
136
|
+
await uninstall();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (first === 'revert') {
|
|
140
|
+
const onlyIdx = rest.indexOf('--only');
|
|
141
|
+
/* c8 ignore next */
|
|
142
|
+
const only = onlyIdx >= 0 ? rest[onlyIdx + 1] : null;
|
|
143
|
+
await revert({ only });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (first === 'backups') {
|
|
147
|
+
process.stdout.write(backupsReport());
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (first === 'list') {
|
|
151
|
+
process.stdout.write(listPlatforms().join('\n') + '\n');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (first === 'gain' || first === 'stats') {
|
|
155
|
+
process.stdout.write(tracking.report());
|
|
156
|
+
await versionCheck.checkForUpdate().catch(() => {});
|
|
157
|
+
maybePrintUpdateHint();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (first === 'inspect') {
|
|
161
|
+
inspectCmd(rest);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (first === 'reset') {
|
|
165
|
+
const ok = tracking.reset();
|
|
166
|
+
process.stdout.write(ok ? 'lakon: log cleared\n' : 'lakon: nothing to clear\n');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
runAndFilter(first, rest);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* c8 ignore next 4 */
|
|
174
|
+
main().catch((err) => {
|
|
175
|
+
process.stderr.write(`lakon: ${err.message}\n`);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lakonai",
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Spartan replies for AI agents — terse model output + filtered CLI output for Claude Code, Codex, Cursor, Windsurf, Cline, and Gemini.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"codex",
|
|
9
|
+
"cursor",
|
|
10
|
+
"windsurf",
|
|
11
|
+
"cline",
|
|
12
|
+
"gemini",
|
|
13
|
+
"llm",
|
|
14
|
+
"tokens",
|
|
15
|
+
"context-window",
|
|
16
|
+
"ai-coding",
|
|
17
|
+
"agent"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "bargadev",
|
|
21
|
+
"homepage": "https://github.com/bargadev/lakon-lib",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/bargadev/lakon-lib.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/bargadev/lakon-lib/issues"
|
|
28
|
+
},
|
|
29
|
+
"type": "commonjs",
|
|
30
|
+
"main": "src/filters/index.js",
|
|
31
|
+
"bin": {
|
|
32
|
+
"lakon": "bin/lakon.js",
|
|
33
|
+
"lak": "bin/lakon.js"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"bin/",
|
|
37
|
+
"src/",
|
|
38
|
+
"assets/",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"test": "node --test tests/",
|
|
44
|
+
"test:coverage": "c8 --reporter=text --reporter=html node --test tests/",
|
|
45
|
+
"test:coverage:check": "c8 --check-coverage --lines 100 --branches 100 --functions 100 node --test tests/"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"c8": "^11.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { stripAnsi, truncateLines } = require('./utils');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_MAX_LINES = 200;
|
|
6
|
+
const BLANK_RUN_RE = /\n[\t ]*\n([\t ]*\n)+/g;
|
|
7
|
+
|
|
8
|
+
function compactBlankRuns(text) {
|
|
9
|
+
return text.replace(BLANK_RUN_RE, '\n\n');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function filter(raw, opts = {}) {
|
|
13
|
+
const max = opts.maxLines || DEFAULT_MAX_LINES;
|
|
14
|
+
const compacted = compactBlankRuns(stripAnsi(raw));
|
|
15
|
+
return truncateLines(compacted, max);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { filter, compactBlankRuns };
|