token-pilot 0.30.1 → 0.30.3
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +13 -6
- package/agents/tp-api-surface-tracker.md +1 -1
- package/agents/tp-audit-scanner.md +1 -1
- package/agents/tp-commit-writer.md +1 -1
- package/agents/tp-context-engineer.md +1 -1
- package/agents/tp-dead-code-finder.md +1 -1
- package/agents/tp-debugger.md +1 -1
- package/agents/tp-dep-health.md +1 -1
- package/agents/tp-doc-writer.md +1 -1
- package/agents/tp-history-explorer.md +1 -1
- package/agents/tp-impact-analyzer.md +1 -1
- package/agents/tp-incident-timeline.md +1 -1
- package/agents/tp-incremental-builder.md +1 -1
- package/agents/tp-migration-scout.md +1 -1
- package/agents/tp-onboard.md +1 -1
- package/agents/tp-performance-profiler.md +1 -1
- package/agents/tp-pr-reviewer.md +1 -1
- package/agents/tp-refactor-planner.md +1 -1
- package/agents/tp-review-impact.md +1 -1
- package/agents/tp-run.md +1 -1
- package/agents/tp-session-restorer.md +1 -1
- package/agents/tp-ship-coordinator.md +1 -1
- package/agents/tp-spec-writer.md +1 -1
- package/agents/tp-test-coverage-gapper.md +1 -1
- package/agents/tp-test-triage.md +1 -1
- package/agents/tp-test-writer.md +1 -1
- package/dist/cli/ecosystem-check.d.ts +56 -0
- package/dist/cli/ecosystem-check.js +244 -0
- package/dist/cli/ecosystem-reminder.d.ts +35 -0
- package/dist/cli/ecosystem-reminder.js +59 -0
- package/dist/hooks/installer.js +0 -9
- package/dist/hooks/pre-edit.js +9 -5
- package/dist/index.js +34 -0
- package/docs/configuration.md +51 -0
- package/docs/ecosystem.md +83 -0
- package/hooks/hooks.json +0 -9
- package/package.json +1 -1
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Token Pilot \u2014 save 60-90% tokens when AI reads code",
|
|
9
|
-
"version": "0.30.
|
|
9
|
+
"version": "0.30.3"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "token-pilot",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "Reduces token consumption by 60-90% via AST-aware lazy file reading, structural symbol navigation, and cross-session tool-usage analytics. 22 MCP tools + 19 subagents + budget watchdog hooks.",
|
|
16
|
-
"version": "0.30.
|
|
16
|
+
"version": "0.30.3",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Digital-Threads"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.3",
|
|
4
4
|
"description": "Saves 60-90% tokens when AI reads code. AST-aware lazy reading, symbol navigation, cross-session tool-usage analytics, 22 subagents (haiku/sonnet/opus-tiered) with budget watchdog.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Digital-Threads",
|
package/README.md
CHANGED
|
@@ -79,13 +79,20 @@ TOKEN_PILOT_MODE=strict npx token-pilot
|
|
|
79
79
|
|
|
80
80
|
## Ecosystem
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|------|------|
|
|
84
|
-
| **Token Pilot** | Enforcement layer — hooks, MCP structural reads, subagents |
|
|
85
|
-
| **[ast-index](https://github.com/defendend/Claude-ast-index-search)** | Structural indexer. Auto-installed by Token Pilot; also a standalone CLI for bash-only agents |
|
|
86
|
-
| **[context-mode](https://github.com/mksglu/claude-context-mode)** | Sandbox executor — runs shell/python/js, only stdout enters the context window |
|
|
82
|
+
Token Pilot owns **input** tokens — the stuff Claude reads from files, git, search. The other half of a session (what Claude *writes* back, how it executes code, how it remembers state across days) is owned by separate tools. They compose cleanly:
|
|
87
83
|
|
|
88
|
-
|
|
84
|
+
| Tool | Owns | Typical savings |
|
|
85
|
+
|------|------|----------------:|
|
|
86
|
+
| **Token Pilot** | code reads, git, search | 60-90% input |
|
|
87
|
+
| **[caveman](https://github.com/JuliusBrussee/caveman)** | Claude's response prose (terse-speak skill) | ~75% output |
|
|
88
|
+
| **[ast-index](https://github.com/defendend/Claude-ast-index-search)** | the structural indexer Token Pilot rides on | foundation |
|
|
89
|
+
| **[context-mode](https://github.com/mksglu/claude-context-mode)** | sandboxed shell / python / js execution | 90%+ on big stdout |
|
|
90
|
+
|
|
91
|
+
A session that pairs `token-pilot` + `caveman` typically hits **~85-90% total reduction** — each cuts a different half, no overlap. Install what you need; none of them assume the others are present.
|
|
92
|
+
|
|
93
|
+
→ [full ecosystem map](docs/ecosystem.md)
|
|
94
|
+
|
|
95
|
+
Rules of thumb: read code → `smart_read`/`read_symbol`; execute code with big output → context-mode `execute`; bash-only agent → `ast-index` CLI. Never copy the whole stack into `CLAUDE.md` — Token Pilot's `doctor` warns when `CLAUDE.md` exceeds 60 lines.
|
|
89
96
|
|
|
90
97
|
## Supported Languages
|
|
91
98
|
|
package/agents/tp-debugger.md
CHANGED
package/agents/tp-dep-health.md
CHANGED
package/agents/tp-doc-writer.md
CHANGED
package/agents/tp-onboard.md
CHANGED
|
@@ -10,7 +10,7 @@ tools:
|
|
|
10
10
|
- mcp__token-pilot__smart_read
|
|
11
11
|
- mcp__token-pilot__smart_read_many
|
|
12
12
|
- mcp__token-pilot__read_section
|
|
13
|
-
token_pilot_version: "0.30.
|
|
13
|
+
token_pilot_version: "0.30.3"
|
|
14
14
|
token_pilot_body_hash: 4e82f7b3c6446663e958fb6bf5eb5348bbdf33389269c888ce0dab766e50561f
|
|
15
15
|
---
|
|
16
16
|
|
package/agents/tp-pr-reviewer.md
CHANGED
package/agents/tp-run.md
CHANGED
package/agents/tp-spec-writer.md
CHANGED
package/agents/tp-test-triage.md
CHANGED
package/agents/tp-test-writer.md
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ecosystem coverage check — detects which complementary tools are
|
|
3
|
+
* installed alongside token-pilot.
|
|
4
|
+
*
|
|
5
|
+
* Scope: only tools whose author has a stable install story and whose
|
|
6
|
+
* problem-space is truly disjoint from ours (we don't recommend things
|
|
7
|
+
* that overlap our own tools/list).
|
|
8
|
+
*
|
|
9
|
+
* Status options:
|
|
10
|
+
* - "installed" — detected on disk, ready to use
|
|
11
|
+
* - "not-installed" — not detected in any known install location
|
|
12
|
+
* - "unknown" — couldn't check (permission denied, unusual OS, etc.)
|
|
13
|
+
*
|
|
14
|
+
* Pure read-only. No network calls. Fast enough to run from `doctor`
|
|
15
|
+
* on every invocation.
|
|
16
|
+
*/
|
|
17
|
+
export type EcosystemToolId = "caveman" | "cavemem" | "context-mode";
|
|
18
|
+
export interface EcosystemToolStatus {
|
|
19
|
+
id: EcosystemToolId;
|
|
20
|
+
name: string;
|
|
21
|
+
role: string;
|
|
22
|
+
status: "installed" | "not-installed" | "unknown";
|
|
23
|
+
detectedAt: string | null;
|
|
24
|
+
installHint: string;
|
|
25
|
+
repo: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Run all ecosystem checks. Order is deterministic — the checks are cheap
|
|
29
|
+
* and we want the doctor output to be stable across runs.
|
|
30
|
+
*/
|
|
31
|
+
export declare function checkEcosystem(): EcosystemToolStatus[];
|
|
32
|
+
/**
|
|
33
|
+
* Render a block suitable for appending to `token-pilot doctor` output.
|
|
34
|
+
* Returns null when every tool is installed — no point printing a
|
|
35
|
+
* noisy "all green" block when the user has nothing to act on.
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatEcosystemBlock(statuses: EcosystemToolStatus[]): string | null;
|
|
38
|
+
export type StatuslineStatus = "configured-chain" | "configured-tp-only" | "configured-caveman-only" | "configured-other" | "not-configured" | "unknown";
|
|
39
|
+
export interface StatuslineCheckResult {
|
|
40
|
+
status: StatuslineStatus;
|
|
41
|
+
configPath: string;
|
|
42
|
+
currentCommand: string | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse `~/.claude/settings.json` and report whether a statusline is
|
|
46
|
+
* wired to token-pilot's own script. Used by `doctor` to nudge toward
|
|
47
|
+
* the chain wrapper when appropriate. Never throws.
|
|
48
|
+
*/
|
|
49
|
+
export declare function checkStatusline(): StatuslineCheckResult;
|
|
50
|
+
/**
|
|
51
|
+
* Render a doctor hint for the statusline badge. Returns null when the
|
|
52
|
+
* user either already has the best config (chain) or has a custom
|
|
53
|
+
* statusLine we don't want to touch.
|
|
54
|
+
*/
|
|
55
|
+
export declare function formatStatuslineHint(result: StatuslineCheckResult, ecosystemStatuses: EcosystemToolStatus[]): string | null;
|
|
56
|
+
//# sourceMappingURL=ecosystem-check.d.ts.map
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ecosystem coverage check — detects which complementary tools are
|
|
3
|
+
* installed alongside token-pilot.
|
|
4
|
+
*
|
|
5
|
+
* Scope: only tools whose author has a stable install story and whose
|
|
6
|
+
* problem-space is truly disjoint from ours (we don't recommend things
|
|
7
|
+
* that overlap our own tools/list).
|
|
8
|
+
*
|
|
9
|
+
* Status options:
|
|
10
|
+
* - "installed" — detected on disk, ready to use
|
|
11
|
+
* - "not-installed" — not detected in any known install location
|
|
12
|
+
* - "unknown" — couldn't check (permission denied, unusual OS, etc.)
|
|
13
|
+
*
|
|
14
|
+
* Pure read-only. No network calls. Fast enough to run from `doctor`
|
|
15
|
+
* on every invocation.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
+
import { homedir } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
/**
|
|
21
|
+
* Conventional Claude Code plugin cache for a given plugin name.
|
|
22
|
+
* Matches the pattern token-pilot itself lives under.
|
|
23
|
+
*/
|
|
24
|
+
function claudePluginCacheDir(plugin) {
|
|
25
|
+
return join(homedir(), ".claude", "plugins", "cache", plugin);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Conventional Gemini CLI extensions dir for a given extension name.
|
|
29
|
+
*/
|
|
30
|
+
function geminiExtensionDir(ext) {
|
|
31
|
+
return join(homedir(), ".gemini", "extensions", ext);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Probe a list of candidate paths; return the first that exists.
|
|
35
|
+
* Any FS error → return null (caller reports "unknown").
|
|
36
|
+
*/
|
|
37
|
+
function firstExisting(paths) {
|
|
38
|
+
for (const p of paths) {
|
|
39
|
+
try {
|
|
40
|
+
if (existsSync(p))
|
|
41
|
+
return p;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Permission denied or similar — move on, the caller decides how
|
|
45
|
+
// to report this. We don't want a doctor run to crash because one
|
|
46
|
+
// probe couldn't stat a path.
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function checkCaveman() {
|
|
53
|
+
const candidates = [
|
|
54
|
+
claudePluginCacheDir("caveman"),
|
|
55
|
+
geminiExtensionDir("caveman"),
|
|
56
|
+
// Codex + Cursor install into project-local dirs — out of scope for a
|
|
57
|
+
// doctor run whose cwd is the user's code. If the user ran caveman
|
|
58
|
+
// through `npx skills`, the marker is usually `.claude/skills/caveman`
|
|
59
|
+
// or `.cursor/rules/caveman.mdc` — also project-local, same reason.
|
|
60
|
+
];
|
|
61
|
+
const detected = firstExisting(candidates);
|
|
62
|
+
return {
|
|
63
|
+
id: "caveman",
|
|
64
|
+
name: "caveman",
|
|
65
|
+
role: "Output compression (terse-speak skill) — cuts ~75% of Claude's response prose",
|
|
66
|
+
status: detected ? "installed" : "not-installed",
|
|
67
|
+
detectedAt: detected,
|
|
68
|
+
installHint: "claude plugin marketplace add JuliusBrussee/caveman && claude plugin install caveman@caveman",
|
|
69
|
+
repo: "https://github.com/JuliusBrussee/caveman",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function checkCavemem() {
|
|
73
|
+
const candidates = [
|
|
74
|
+
claudePluginCacheDir("cavemem"),
|
|
75
|
+
geminiExtensionDir("cavemem"),
|
|
76
|
+
];
|
|
77
|
+
const detected = firstExisting(candidates);
|
|
78
|
+
return {
|
|
79
|
+
id: "cavemem",
|
|
80
|
+
name: "cavemem",
|
|
81
|
+
role: "Cross-session memory — remember context across restarts",
|
|
82
|
+
status: detected ? "installed" : "not-installed",
|
|
83
|
+
detectedAt: detected,
|
|
84
|
+
installHint: "see https://github.com/JuliusBrussee/cavemem",
|
|
85
|
+
repo: "https://github.com/JuliusBrussee/cavemem",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function checkContextMode() {
|
|
89
|
+
const candidates = [
|
|
90
|
+
claudePluginCacheDir("context-mode"),
|
|
91
|
+
claudePluginCacheDir("claude-context-mode"),
|
|
92
|
+
];
|
|
93
|
+
const detected = firstExisting(candidates);
|
|
94
|
+
return {
|
|
95
|
+
id: "context-mode",
|
|
96
|
+
name: "context-mode",
|
|
97
|
+
role: "Sandbox executor — runs shell/python/js, only stdout enters context",
|
|
98
|
+
status: detected ? "installed" : "not-installed",
|
|
99
|
+
detectedAt: detected,
|
|
100
|
+
installHint: "see https://github.com/mksglu/claude-context-mode",
|
|
101
|
+
repo: "https://github.com/mksglu/claude-context-mode",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Run all ecosystem checks. Order is deterministic — the checks are cheap
|
|
106
|
+
* and we want the doctor output to be stable across runs.
|
|
107
|
+
*/
|
|
108
|
+
export function checkEcosystem() {
|
|
109
|
+
return [checkCaveman(), checkContextMode(), checkCavemem()];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Render a block suitable for appending to `token-pilot doctor` output.
|
|
113
|
+
* Returns null when every tool is installed — no point printing a
|
|
114
|
+
* noisy "all green" block when the user has nothing to act on.
|
|
115
|
+
*/
|
|
116
|
+
export function formatEcosystemBlock(statuses) {
|
|
117
|
+
const missing = statuses.filter((s) => s.status === "not-installed");
|
|
118
|
+
const installed = statuses.filter((s) => s.status === "installed");
|
|
119
|
+
// When everything is green we stay silent — the doctor surface is busy
|
|
120
|
+
// enough. Users can still get the full map from `docs/ecosystem.md`.
|
|
121
|
+
if (missing.length === 0 && installed.length === 0)
|
|
122
|
+
return null;
|
|
123
|
+
const lines = ["── ecosystem coverage ──"];
|
|
124
|
+
if (installed.length > 0) {
|
|
125
|
+
for (const s of installed) {
|
|
126
|
+
lines.push(` ✓ ${s.name.padEnd(14)} ${s.role}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const s of missing) {
|
|
130
|
+
lines.push(` ○ ${s.name.padEnd(14)} missing — ${s.role}`);
|
|
131
|
+
lines.push(` install: ${s.installHint}`);
|
|
132
|
+
}
|
|
133
|
+
if (missing.length > 0) {
|
|
134
|
+
lines.push("");
|
|
135
|
+
lines.push(` token-pilot owns INPUT tokens. Each tool above owns a different`);
|
|
136
|
+
lines.push(` half of a session — they do not overlap. See docs/ecosystem.md.`);
|
|
137
|
+
}
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Parse `~/.claude/settings.json` and report whether a statusline is
|
|
142
|
+
* wired to token-pilot's own script. Used by `doctor` to nudge toward
|
|
143
|
+
* the chain wrapper when appropriate. Never throws.
|
|
144
|
+
*/
|
|
145
|
+
export function checkStatusline() {
|
|
146
|
+
const configPath = join(homedir(), ".claude", "settings.json");
|
|
147
|
+
const empty = {
|
|
148
|
+
status: "not-configured",
|
|
149
|
+
configPath,
|
|
150
|
+
currentCommand: null,
|
|
151
|
+
};
|
|
152
|
+
if (!existsSync(configPath))
|
|
153
|
+
return empty;
|
|
154
|
+
let text;
|
|
155
|
+
try {
|
|
156
|
+
text = readFileSync(configPath, "utf-8");
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return { status: "unknown", configPath, currentCommand: null };
|
|
160
|
+
}
|
|
161
|
+
let parsed;
|
|
162
|
+
try {
|
|
163
|
+
parsed = JSON.parse(text);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return { status: "unknown", configPath, currentCommand: null };
|
|
167
|
+
}
|
|
168
|
+
const sl = parsed
|
|
169
|
+
?.statusLine;
|
|
170
|
+
if (!sl || typeof sl.command !== "string")
|
|
171
|
+
return empty;
|
|
172
|
+
const cmd = sl.command;
|
|
173
|
+
if (cmd.includes("statusline-chain.sh")) {
|
|
174
|
+
return { status: "configured-chain", configPath, currentCommand: cmd };
|
|
175
|
+
}
|
|
176
|
+
if (cmd.includes("tp-statusline.sh")) {
|
|
177
|
+
return { status: "configured-tp-only", configPath, currentCommand: cmd };
|
|
178
|
+
}
|
|
179
|
+
if (cmd.includes("caveman-statusline.sh")) {
|
|
180
|
+
return {
|
|
181
|
+
status: "configured-caveman-only",
|
|
182
|
+
configPath,
|
|
183
|
+
currentCommand: cmd,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return { status: "configured-other", configPath, currentCommand: cmd };
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Render a doctor hint for the statusline badge. Returns null when the
|
|
190
|
+
* user either already has the best config (chain) or has a custom
|
|
191
|
+
* statusLine we don't want to touch.
|
|
192
|
+
*/
|
|
193
|
+
export function formatStatuslineHint(result, ecosystemStatuses) {
|
|
194
|
+
const lines = ["── statusline badge ──"];
|
|
195
|
+
const hasCaveman = ecosystemStatuses.some((s) => s.id === "caveman" && s.status === "installed");
|
|
196
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
|
|
197
|
+
switch (result.status) {
|
|
198
|
+
case "configured-chain":
|
|
199
|
+
// User is already on the best config — stay silent.
|
|
200
|
+
return null;
|
|
201
|
+
case "configured-tp-only":
|
|
202
|
+
if (!hasCaveman)
|
|
203
|
+
return null;
|
|
204
|
+
lines.push(` ⚠ statusline points at tp-statusline.sh directly but caveman is`);
|
|
205
|
+
lines.push(` installed — switch to statusline-chain.sh so both badges show.`);
|
|
206
|
+
if (pluginRoot) {
|
|
207
|
+
lines.push(` command: bash "${pluginRoot}/hooks/statusline-chain.sh"`);
|
|
208
|
+
}
|
|
209
|
+
return lines.join("\n");
|
|
210
|
+
case "configured-caveman-only":
|
|
211
|
+
// Caveman's own statusline is already live. Suggest swapping to our
|
|
212
|
+
// chain wrapper so the `[TP]` badge joins in side-by-side.
|
|
213
|
+
lines.push(` ⚠ statusline renders caveman's badge only. Switch to token-pilot's`);
|
|
214
|
+
lines.push(` chain wrapper to also show [TP] with enforcement mode + saved tokens.`);
|
|
215
|
+
if (pluginRoot) {
|
|
216
|
+
lines.push(` command: bash "${pluginRoot}/hooks/statusline-chain.sh"`);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
lines.push(` command: bash "$(ls -t ~/.claude/plugins/cache/token-pilot/token-pilot/*/hooks/statusline-chain.sh 2>/dev/null | head -1)"`);
|
|
220
|
+
}
|
|
221
|
+
return lines.join("\n");
|
|
222
|
+
case "configured-other":
|
|
223
|
+
// Custom statusLine — respect it, never overwrite.
|
|
224
|
+
return null;
|
|
225
|
+
case "unknown":
|
|
226
|
+
return null;
|
|
227
|
+
case "not-configured": {
|
|
228
|
+
lines.push(` ○ no statusline badge configured — add one to see token-pilot`);
|
|
229
|
+
lines.push(` state (enforcement mode + cumulative saved tokens) in`);
|
|
230
|
+
lines.push(` Claude Code's status bar.`);
|
|
231
|
+
lines.push("");
|
|
232
|
+
const command = pluginRoot
|
|
233
|
+
? `bash "${pluginRoot}/hooks/statusline-chain.sh"`
|
|
234
|
+
: `bash "$(ls -t ~/.claude/plugins/cache/token-pilot/token-pilot/*/hooks/statusline-chain.sh 2>/dev/null | head -1)"`;
|
|
235
|
+
lines.push(` Add to ${result.configPath}:`);
|
|
236
|
+
lines.push(` "statusLine": {`);
|
|
237
|
+
lines.push(` "type": "command",`);
|
|
238
|
+
lines.push(` "command": "${command.replace(/"/g, '\\"')}"`);
|
|
239
|
+
lines.push(` }`);
|
|
240
|
+
return lines.join("\n");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=ecosystem-check.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ecosystem reminder — a one-shot nudge at MCP-server startup suggesting
|
|
3
|
+
* the user install caveman for output-side compression.
|
|
4
|
+
*
|
|
5
|
+
* Design lifted from `maybeEmitStartupReminder` (tp-* agents):
|
|
6
|
+
* - at most once per process (new Claude Code session = new process =
|
|
7
|
+
* new single reminder, not a per-session banner every turn);
|
|
8
|
+
* - silent when the user already installed caveman;
|
|
9
|
+
* - silenced completely by `TOKEN_PILOT_NO_ECOSYSTEM_TIPS=1`;
|
|
10
|
+
* - silenced inside spawned subagents (`TOKEN_PILOT_SUBAGENT=1`) so
|
|
11
|
+
* the banner never surfaces through Task-dispatched helpers.
|
|
12
|
+
*
|
|
13
|
+
* Three stderr lines is the max we allow — the whole point is to be
|
|
14
|
+
* helpful without being the thing that bloats the user's first impression.
|
|
15
|
+
*/
|
|
16
|
+
export interface EcosystemReminderOptions {
|
|
17
|
+
env?: NodeJS.ProcessEnv;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Pure predicate — is a reminder currently warranted?
|
|
21
|
+
* Exposed separately from the stateful emitter so tests can exercise the
|
|
22
|
+
* decision matrix without touching stderr or the single-fire latch.
|
|
23
|
+
*/
|
|
24
|
+
export declare function shouldEmitEcosystemReminder(opts?: EcosystemReminderOptions): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Emit the reminder to stderr if conditions warrant it. Single-fire per
|
|
27
|
+
* process. Returns `true` iff it actually wrote output, so callers can
|
|
28
|
+
* log telemetry without racing the latch.
|
|
29
|
+
*/
|
|
30
|
+
export declare function maybeEmitEcosystemReminder(opts?: EcosystemReminderOptions): boolean;
|
|
31
|
+
/** Test-only: reset the single-fire guard between test cases. */
|
|
32
|
+
export declare function __resetEcosystemReminder(): void;
|
|
33
|
+
/** Test-only: expose the canonical message for assertion. */
|
|
34
|
+
export declare const __ECOSYSTEM_REMINDER_MESSAGE: string;
|
|
35
|
+
//# sourceMappingURL=ecosystem-reminder.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ecosystem reminder — a one-shot nudge at MCP-server startup suggesting
|
|
3
|
+
* the user install caveman for output-side compression.
|
|
4
|
+
*
|
|
5
|
+
* Design lifted from `maybeEmitStartupReminder` (tp-* agents):
|
|
6
|
+
* - at most once per process (new Claude Code session = new process =
|
|
7
|
+
* new single reminder, not a per-session banner every turn);
|
|
8
|
+
* - silent when the user already installed caveman;
|
|
9
|
+
* - silenced completely by `TOKEN_PILOT_NO_ECOSYSTEM_TIPS=1`;
|
|
10
|
+
* - silenced inside spawned subagents (`TOKEN_PILOT_SUBAGENT=1`) so
|
|
11
|
+
* the banner never surfaces through Task-dispatched helpers.
|
|
12
|
+
*
|
|
13
|
+
* Three stderr lines is the max we allow — the whole point is to be
|
|
14
|
+
* helpful without being the thing that bloats the user's first impression.
|
|
15
|
+
*/
|
|
16
|
+
import { checkEcosystem } from "./ecosystem-check.js";
|
|
17
|
+
let emitted = false;
|
|
18
|
+
const MESSAGE = "[token-pilot] Tip: pair with caveman for ~75% output token savings\n" +
|
|
19
|
+
" install: claude plugin install caveman@caveman\n" +
|
|
20
|
+
" silence: set TOKEN_PILOT_NO_ECOSYSTEM_TIPS=1\n";
|
|
21
|
+
/**
|
|
22
|
+
* Pure predicate — is a reminder currently warranted?
|
|
23
|
+
* Exposed separately from the stateful emitter so tests can exercise the
|
|
24
|
+
* decision matrix without touching stderr or the single-fire latch.
|
|
25
|
+
*/
|
|
26
|
+
export function shouldEmitEcosystemReminder(opts = {}) {
|
|
27
|
+
const env = opts.env ?? process.env;
|
|
28
|
+
if (env.TOKEN_PILOT_NO_ECOSYSTEM_TIPS === "1")
|
|
29
|
+
return false;
|
|
30
|
+
if (env.TOKEN_PILOT_SUBAGENT === "1")
|
|
31
|
+
return false;
|
|
32
|
+
const statuses = checkEcosystem();
|
|
33
|
+
const caveman = statuses.find((s) => s.id === "caveman");
|
|
34
|
+
// Only remind when we're actually sure caveman is missing. If detection
|
|
35
|
+
// is "unknown" (future value, permission-denied HOME, etc.) we stay
|
|
36
|
+
// silent — a wrong nudge is worse than no nudge.
|
|
37
|
+
return caveman?.status === "not-installed";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Emit the reminder to stderr if conditions warrant it. Single-fire per
|
|
41
|
+
* process. Returns `true` iff it actually wrote output, so callers can
|
|
42
|
+
* log telemetry without racing the latch.
|
|
43
|
+
*/
|
|
44
|
+
export function maybeEmitEcosystemReminder(opts = {}) {
|
|
45
|
+
if (emitted)
|
|
46
|
+
return false;
|
|
47
|
+
if (!shouldEmitEcosystemReminder(opts))
|
|
48
|
+
return false;
|
|
49
|
+
process.stderr.write(MESSAGE);
|
|
50
|
+
emitted = true;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
/** Test-only: reset the single-fire guard between test cases. */
|
|
54
|
+
export function __resetEcosystemReminder() {
|
|
55
|
+
emitted = false;
|
|
56
|
+
}
|
|
57
|
+
/** Test-only: expose the canonical message for assertion. */
|
|
58
|
+
export const __ECOSYSTEM_REMINDER_MESSAGE = MESSAGE;
|
|
59
|
+
//# sourceMappingURL=ecosystem-reminder.js.map
|
package/dist/hooks/installer.js
CHANGED
package/dist/hooks/pre-edit.js
CHANGED
|
@@ -30,7 +30,13 @@
|
|
|
30
30
|
*/
|
|
31
31
|
export function decidePreEdit(input, ctx) {
|
|
32
32
|
const toolName = input.tool_name ?? "";
|
|
33
|
-
|
|
33
|
+
// Only Edit and MultiEdit touch a file partially with an old_string that
|
|
34
|
+
// MUST match disk byte-for-byte — those are the calls read_for_edit
|
|
35
|
+
// actually prepares. Write replaces the whole file (new content, no
|
|
36
|
+
// old_string) so enforcing prep on Write is overreach: blocks legit
|
|
37
|
+
// script regeneration / template overwrites that never needed prep.
|
|
38
|
+
// Pre-v0.30.3 we blocked Write too; that was wrong. Rolled back.
|
|
39
|
+
if (toolName !== "Edit" && toolName !== "MultiEdit") {
|
|
34
40
|
return { kind: "allow" };
|
|
35
41
|
}
|
|
36
42
|
const filePath = input.tool_input?.file_path;
|
|
@@ -41,10 +47,8 @@ export function decidePreEdit(input, ctx) {
|
|
|
41
47
|
// carry the same value, skip enforcement.
|
|
42
48
|
if (!ctx.isCodeFile)
|
|
43
49
|
return { kind: "allow" };
|
|
44
|
-
// Non-existent files
|
|
45
|
-
//
|
|
46
|
-
// - Edit / MultiEdit on a missing path will error downstream in
|
|
47
|
-
// Claude Code itself — nothing for us to add there
|
|
50
|
+
// Non-existent files — Edit/MultiEdit will error downstream in Claude Code
|
|
51
|
+
// itself. Nothing for us to add.
|
|
48
52
|
if (!ctx.fileExists)
|
|
49
53
|
return { kind: "allow" };
|
|
50
54
|
// Explicit escape hatch. Documented as TOKEN_PILOT_BYPASS=1.
|
package/dist/index.js
CHANGED
|
@@ -54,6 +54,7 @@ import { decidePreBash, renderPreBashOutput } from "./hooks/pre-bash.js";
|
|
|
54
54
|
import { decidePreGrep, renderPreGrepOutput } from "./hooks/pre-grep.js";
|
|
55
55
|
import { decidePreEdit, renderPreEditOutput, } from "./hooks/pre-edit.js";
|
|
56
56
|
import { isEditPrepared as isEditPreparedFn } from "./core/edit-prep-state.js";
|
|
57
|
+
import { maybeEmitEcosystemReminder } from "./cli/ecosystem-reminder.js";
|
|
57
58
|
import { parseEnforcementMode } from "./server/enforcement-mode.js";
|
|
58
59
|
const execFileAsync = promisify(execFile);
|
|
59
60
|
export const CODE_EXTENSIONS = new Set([
|
|
@@ -390,6 +391,16 @@ export async function startServer(cliArgs = process.argv.slice(2)) {
|
|
|
390
391
|
}).catch(() => {
|
|
391
392
|
/* ignore */
|
|
392
393
|
});
|
|
394
|
+
// v0.30.x ecosystem nudge — one-time stderr tip suggesting caveman for
|
|
395
|
+
// output compression. Fires at most once per MCP process, stays silent
|
|
396
|
+
// when caveman is already detected or TOKEN_PILOT_NO_ECOSYSTEM_TIPS=1.
|
|
397
|
+
// Static import + synchronous call so nothing ever blocks startServer.
|
|
398
|
+
try {
|
|
399
|
+
maybeEmitEcosystemReminder();
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
/* ecosystem reminder must never block startup */
|
|
403
|
+
}
|
|
393
404
|
// Phase 6 subtask 6.2 — age + size retention on hook-events archives.
|
|
394
405
|
// Fire-and-forget: retention failures must never block startup.
|
|
395
406
|
applyRetention(projectRoot).catch(() => {
|
|
@@ -864,6 +875,29 @@ export async function handleDoctor() {
|
|
|
864
875
|
catch {
|
|
865
876
|
/* ignore */
|
|
866
877
|
}
|
|
878
|
+
// ── Ecosystem coverage ──
|
|
879
|
+
// Checks which complementary tools (caveman, context-mode, cavemem) are
|
|
880
|
+
// installed and prints gaps. Purely advisory — we never install anything.
|
|
881
|
+
// See docs/ecosystem.md for the rationale behind the whitelist.
|
|
882
|
+
try {
|
|
883
|
+
const { checkEcosystem, formatEcosystemBlock, checkStatusline, formatStatuslineHint, } = await import("./cli/ecosystem-check.js");
|
|
884
|
+
const ecosystemStatuses = checkEcosystem();
|
|
885
|
+
const block = formatEcosystemBlock(ecosystemStatuses);
|
|
886
|
+
if (block) {
|
|
887
|
+
console.log(block);
|
|
888
|
+
console.log("");
|
|
889
|
+
}
|
|
890
|
+
// Statusline hint — only prints when actionable (missing, or mid-upgrade
|
|
891
|
+
// when caveman is present but chain wrapper isn't used).
|
|
892
|
+
const statuslineHint = formatStatuslineHint(checkStatusline(), ecosystemStatuses);
|
|
893
|
+
if (statuslineHint) {
|
|
894
|
+
console.log(statuslineHint);
|
|
895
|
+
console.log("");
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
catch {
|
|
899
|
+
/* ecosystem check is best-effort; never break doctor */
|
|
900
|
+
}
|
|
867
901
|
process.exit(0);
|
|
868
902
|
}
|
|
869
903
|
export async function handleInit(targetDir) {
|
package/docs/configuration.md
CHANGED
|
@@ -62,6 +62,57 @@ Handlers remain active regardless of profile — a subagent that explicitly name
|
|
|
62
62
|
TOKEN_PILOT_PROFILE=edit npx token-pilot
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
## Startup reminders
|
|
66
|
+
|
|
67
|
+
Token Pilot prints at most one short tip to **stderr** per MCP process when it detects a gap worth your attention. Reminders are single-fire per session and each is silenced by a dedicated env var:
|
|
68
|
+
|
|
69
|
+
| Reminder | Condition | Silence with |
|
|
70
|
+
|---------|-----------|--------------|
|
|
71
|
+
| `tp-*` subagents not installed | `npx token-pilot install-agents` was never run | `TOKEN_PILOT_NO_AGENT_REMINDER=1` |
|
|
72
|
+
| caveman not installed | caveman skill missing from `~/.claude/plugins/cache/` and `~/.gemini/extensions/` | `TOKEN_PILOT_NO_ECOSYSTEM_TIPS=1` |
|
|
73
|
+
|
|
74
|
+
Both reminders stay silent inside spawned subagents (`TOKEN_PILOT_SUBAGENT=1`) — noise never leaks into Task-dispatched helpers.
|
|
75
|
+
|
|
76
|
+
Run `npx token-pilot doctor` any time to see the full ecosystem coverage table. The reminder is only a nudge; the doctor output is the canonical view.
|
|
77
|
+
|
|
78
|
+
## Statusline badge
|
|
79
|
+
|
|
80
|
+
Claude Code supports a custom `statusLine` command that renders on every keystroke in the bottom bar. Token Pilot ships two scripts for this:
|
|
81
|
+
|
|
82
|
+
| Script | What it shows |
|
|
83
|
+
|--------|---------------|
|
|
84
|
+
| `hooks/tp-statusline.sh` | `[TP]` · `[TP:strict]` · `[TP deny 12k]` (with cumulative saved tokens for the current session) |
|
|
85
|
+
| `hooks/statusline-chain.sh` | Same, **plus** caveman's badge side-by-side if installed: `[CAVEMAN] [TP deny 12k]` |
|
|
86
|
+
|
|
87
|
+
Both scripts are hardened (bounded stdin read, whitelist sanitisation, no symlinks). Safe to keep enabled long-term.
|
|
88
|
+
|
|
89
|
+
### Install (manual, one-time)
|
|
90
|
+
|
|
91
|
+
Add to `~/.claude/settings.json`:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
"statusLine": {
|
|
95
|
+
"type": "command",
|
|
96
|
+
"command": "bash \"$(ls -t ~/.claude/plugins/cache/token-pilot/token-pilot/*/hooks/statusline-chain.sh 2>/dev/null | head -1)\""
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Restart Claude Code. `[TP]` appears in the status bar immediately. When caveman is also installed you'll see both badges.
|
|
101
|
+
|
|
102
|
+
### Other machines
|
|
103
|
+
|
|
104
|
+
Same two-line recipe, or run the Python one-liner below if you're wary of editing JSON by hand:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
python3 -c "import json,os; p=os.path.expanduser('~/.claude/settings.json'); d=json.load(open(p)); d['statusLine']={'type':'command','command':'bash \"\$(ls -t ~/.claude/plugins/cache/token-pilot/token-pilot/*/hooks/statusline-chain.sh 2>/dev/null | head -1)\"'}; json.dump(d,open(p,'w'),indent=2)"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Troubleshooting
|
|
111
|
+
|
|
112
|
+
- `npx token-pilot doctor` surfaces a dedicated **statusline badge** block when the config is missing or when it points at `tp-statusline.sh` directly (we nudge you to the chain wrapper once caveman is detected).
|
|
113
|
+
- If you have a custom `statusLine` already, token-pilot respects it — no override.
|
|
114
|
+
- Colours: `[TP]` is blue (`38;5;39`), caveman's `[CAVEMAN]` is orange (`38;5;172`) — deliberately distinct.
|
|
115
|
+
|
|
65
116
|
## CLI Reference
|
|
66
117
|
|
|
67
118
|
```bash
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Context-Efficient AI Coding — Ecosystem Map
|
|
2
|
+
|
|
3
|
+
Token Pilot solves **one half** of the context problem: the tokens that come *into* Claude's context from reading code. A full coding session has at least four independent places where tokens pile up — the tools below each own one of them.
|
|
4
|
+
|
|
5
|
+
Use them together. They compose cleanly and do not overlap.
|
|
6
|
+
|
|
7
|
+
## Where your tokens actually go
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
11
|
+
│ A COMPLETE CODING SESSION │
|
|
12
|
+
│ │
|
|
13
|
+
│ INPUT side OUTPUT side │
|
|
14
|
+
│ ────────── ─────────── │
|
|
15
|
+
│ 1. Reading code files 4. Claude's response prose │
|
|
16
|
+
│ 2. git diff / log 5. Chain-of-thought noise │
|
|
17
|
+
│ 3. Running shell commands │
|
|
18
|
+
│ 4. Keeping context across sessions │
|
|
19
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## The stack
|
|
23
|
+
|
|
24
|
+
| Tool | Owns | Savings | Mechanism |
|
|
25
|
+
|------|------|--------:|-----------|
|
|
26
|
+
| **[token-pilot](https://github.com/Digital-Threads/token-pilot)** | code reads, git, search | **60-90% input** | MCP tools + PreToolUse hooks |
|
|
27
|
+
| **[caveman](https://github.com/JuliusBrussee/caveman)** | Claude's response prose | **~75% output** | System-prompt skill (terse style) |
|
|
28
|
+
| **[ast-index](https://github.com/defendend/Claude-ast-index-search)** | code indexing (underlying) | foundation for structural reads | Native Rust indexer |
|
|
29
|
+
| **[context-mode](https://github.com/mksglu/claude-context-mode)** | shell / python / js execution | **90%+ on big stdout** | Sandbox — only stdout enters context |
|
|
30
|
+
| **[cavemem](https://github.com/JuliusBrussee/cavemem)** | cross-session memory | context across restarts | Persistent structured recall |
|
|
31
|
+
|
|
32
|
+
**Combined footprint:** a session that spends ~70% on reading code, ~25% on shell output, and ~5% on remembered context could see ~85-90% total reduction when the right tool owns each segment.
|
|
33
|
+
|
|
34
|
+
## What token-pilot does *not* do
|
|
35
|
+
|
|
36
|
+
To keep the boundaries clear:
|
|
37
|
+
|
|
38
|
+
- **token-pilot does not change Claude's response style.** If answers feel long, that's OUTPUT. Install `caveman` for terse-speak.
|
|
39
|
+
- **token-pilot does not execute code.** If `npm test` or long `python` output floods your context, install `context-mode`.
|
|
40
|
+
- **token-pilot does not remember across sessions.** If you're re-explaining context every morning, install `cavemem`.
|
|
41
|
+
- **token-pilot does not index source.** It *uses* ast-index under the hood — installed automatically, but also standalone.
|
|
42
|
+
|
|
43
|
+
## Installing the full stack
|
|
44
|
+
|
|
45
|
+
Each tool is independent — install whatever you need.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Claude Code (plugin system)
|
|
49
|
+
claude plugin marketplace add Digital-Threads/token-pilot
|
|
50
|
+
claude plugin install token-pilot@token-pilot
|
|
51
|
+
|
|
52
|
+
claude plugin marketplace add JuliusBrussee/caveman
|
|
53
|
+
claude plugin install caveman@caveman
|
|
54
|
+
|
|
55
|
+
# token-pilot bootstraps ast-index automatically.
|
|
56
|
+
# context-mode can be installed via its own plugin route.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For other clients (Cursor, Codex, Cline, Windsurf, …) each tool has its own install matrix — follow each project's README.
|
|
60
|
+
|
|
61
|
+
## Why not one meta-plugin?
|
|
62
|
+
|
|
63
|
+
We considered shipping `ai-coding-savings-pack` that bundles all of them. Tradeoffs:
|
|
64
|
+
|
|
65
|
+
- **Pro:** one-command install.
|
|
66
|
+
- **Con:** blast radius. If any component ships a regression, the whole pack looks broken. Each tool has its own release cadence and support surface; coupling them hides those boundaries.
|
|
67
|
+
|
|
68
|
+
For now the recommendation is *install what you need, individually*. Revisit bundling after real combined-usage data shows it pays off.
|
|
69
|
+
|
|
70
|
+
## Measuring the combined effect
|
|
71
|
+
|
|
72
|
+
Each tool ships its own telemetry, read as-is:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx token-pilot tool-audit # input savings (per tool, cumulative)
|
|
76
|
+
npx token-pilot doctor # ecosystem coverage check — see what's installed
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The `doctor` command checks which ecosystem tools are active in the current environment and prints gaps. That's the cheapest way to see "am I leaving savings on the table?"
|
|
80
|
+
|
|
81
|
+
## Credits
|
|
82
|
+
|
|
83
|
+
Everything listed here is MIT or Apache-licensed open source maintained by small teams. If you use them, please star the repos — all of this is volunteer work.
|
package/hooks/hooks.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.3",
|
|
4
4
|
"description": "Save up to 80% tokens when AI reads code — MCP server for token-efficient code navigation, AST-aware structural reading instead of dumping full files into context window",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|