token-pilot 0.24.1 → 0.26.5
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 +29 -12
- package/.claude-plugin/plugin.json +23 -5
- package/CHANGELOG.md +182 -0
- package/README.md +128 -15
- package/dist/agents/tp-api-surface-tracker.md +4 -3
- package/dist/agents/tp-audit-scanner.md +1 -1
- package/dist/agents/tp-commit-writer.md +1 -1
- package/dist/agents/tp-dead-code-finder.md +22 -7
- package/dist/agents/tp-debugger.md +1 -1
- package/dist/agents/tp-dep-health.md +3 -2
- package/dist/agents/tp-history-explorer.md +1 -1
- package/dist/agents/tp-impact-analyzer.md +1 -1
- package/dist/agents/tp-incident-timeline.md +1 -1
- package/dist/agents/tp-migration-scout.md +1 -1
- package/dist/agents/tp-onboard.md +1 -1
- package/dist/agents/tp-pr-reviewer.md +1 -1
- package/dist/agents/tp-refactor-planner.md +1 -1
- package/dist/agents/tp-review-impact.md +1 -1
- package/dist/agents/tp-run.md +1 -1
- package/dist/agents/tp-session-restorer.md +1 -1
- package/dist/agents/tp-test-coverage-gapper.md +1 -1
- package/dist/agents/tp-test-triage.md +1 -1
- package/dist/agents/tp-test-writer.md +1 -1
- package/dist/cli/detect-client.d.ts +39 -0
- package/dist/cli/detect-client.js +106 -0
- package/dist/cli/install-agents.d.ts +1 -0
- package/dist/cli/install-agents.js +31 -1
- package/dist/cli/tool-audit.d.ts +58 -0
- package/dist/cli/tool-audit.js +123 -0
- package/dist/cli/typo-guard.d.ts +1 -1
- package/dist/cli/typo-guard.js +1 -0
- package/dist/core/tool-call-log.d.ts +63 -0
- package/dist/core/tool-call-log.js +171 -0
- package/dist/handlers/read-symbols.js +23 -1
- package/dist/hooks/installer.js +27 -12
- package/dist/index.js +55 -0
- package/dist/server/profile-recommender.d.ts +48 -0
- package/dist/server/profile-recommender.js +102 -0
- package/dist/server/token-estimates.d.ts +17 -3
- package/dist/server/token-estimates.js +77 -45
- package/dist/server/tool-definitions.js +1 -1
- package/dist/server/tool-profiles.d.ts +46 -0
- package/dist/server/tool-profiles.js +81 -0
- package/dist/server.js +38 -1
- package/package.json +1 -1
- package/start.sh +0 -0
- package/.mcp.json +0 -8
|
@@ -7,7 +7,7 @@ tools:
|
|
|
7
7
|
- mcp__token-pilot__read_diff
|
|
8
8
|
- mcp__token-pilot__outline
|
|
9
9
|
- mcp__token-pilot__read_symbol
|
|
10
|
-
token_pilot_version: "0.
|
|
10
|
+
token_pilot_version: "0.26.5"
|
|
11
11
|
token_pilot_body_hash: a058518619fd6e2def0c9226f6c70438a5e0a80efe680c935414ecd7e1b14a4f
|
|
12
12
|
---
|
|
13
13
|
|
package/dist/agents/tp-run.md
CHANGED
|
@@ -7,7 +7,7 @@ tools:
|
|
|
7
7
|
- mcp__token-pilot__read_range
|
|
8
8
|
- mcp__token-pilot__find_usages
|
|
9
9
|
- mcp__token-pilot__read_symbol
|
|
10
|
-
token_pilot_version: "0.
|
|
10
|
+
token_pilot_version: "0.26.5"
|
|
11
11
|
token_pilot_body_hash: 255912c47661d203c8f9a735237bc419f97e937f788a01811bbe126ee3dd5878
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.26.0 — AI-client detection for install-agents.
|
|
3
|
+
*
|
|
4
|
+
* `tp-*` subagents are a Claude Code concept: `.md` files in
|
|
5
|
+
* `~/.claude/agents/` with `tools:` frontmatter, invoked via the `Task`
|
|
6
|
+
* tool. Other clients (Cursor, Codex CLI, Gemini CLI, Cline) read MCP
|
|
7
|
+
* tool descriptions directly and have no subagent surface — they still
|
|
8
|
+
* get our MCP tools + Read hook, but the 19 tp-* delegates sit idle.
|
|
9
|
+
*
|
|
10
|
+
* This detector tries to recognise the active client from env vars and
|
|
11
|
+
* on-disk config directories, so `install-agents` can warn non-Claude
|
|
12
|
+
* users before silently creating a dir nothing will read.
|
|
13
|
+
*
|
|
14
|
+
* Note: detection is best-effort. It returns the *most likely* client or
|
|
15
|
+
* "unknown" — never throws, never asks the user. A false "claude-code"
|
|
16
|
+
* result only costs one unused directory; a false "cursor" would print a
|
|
17
|
+
* misleading warning. We err on the side of "claude-code" when in doubt.
|
|
18
|
+
*/
|
|
19
|
+
export type DetectedClient = "claude-code" | "cursor" | "codex" | "gemini" | "cline" | "unknown";
|
|
20
|
+
export interface DetectionResult {
|
|
21
|
+
client: DetectedClient;
|
|
22
|
+
source: string;
|
|
23
|
+
subagentsSupported: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Identify which AI client is likely running token-pilot, by inspecting
|
|
27
|
+
* env vars first (cheapest) then filesystem markers.
|
|
28
|
+
*
|
|
29
|
+
* @param homeDir user's home directory (injected for tests)
|
|
30
|
+
* @param projectRoot current project root (injected for tests)
|
|
31
|
+
* @param env process.env (injected for tests)
|
|
32
|
+
*/
|
|
33
|
+
export declare function detectClient(homeDir: string, projectRoot: string, env?: NodeJS.ProcessEnv): Promise<DetectionResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Compose a human-readable warning for `install-agents` to print when
|
|
36
|
+
* the detected client doesn't support subagents. Null when it does.
|
|
37
|
+
*/
|
|
38
|
+
export declare function nonClaudeClientWarning(detection: DetectionResult): string | null;
|
|
39
|
+
//# sourceMappingURL=detect-client.d.ts.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.26.0 — AI-client detection for install-agents.
|
|
3
|
+
*
|
|
4
|
+
* `tp-*` subagents are a Claude Code concept: `.md` files in
|
|
5
|
+
* `~/.claude/agents/` with `tools:` frontmatter, invoked via the `Task`
|
|
6
|
+
* tool. Other clients (Cursor, Codex CLI, Gemini CLI, Cline) read MCP
|
|
7
|
+
* tool descriptions directly and have no subagent surface — they still
|
|
8
|
+
* get our MCP tools + Read hook, but the 19 tp-* delegates sit idle.
|
|
9
|
+
*
|
|
10
|
+
* This detector tries to recognise the active client from env vars and
|
|
11
|
+
* on-disk config directories, so `install-agents` can warn non-Claude
|
|
12
|
+
* users before silently creating a dir nothing will read.
|
|
13
|
+
*
|
|
14
|
+
* Note: detection is best-effort. It returns the *most likely* client or
|
|
15
|
+
* "unknown" — never throws, never asks the user. A false "claude-code"
|
|
16
|
+
* result only costs one unused directory; a false "cursor" would print a
|
|
17
|
+
* misleading warning. We err on the side of "claude-code" when in doubt.
|
|
18
|
+
*/
|
|
19
|
+
import { promises as fs } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
/**
|
|
22
|
+
* Identify which AI client is likely running token-pilot, by inspecting
|
|
23
|
+
* env vars first (cheapest) then filesystem markers.
|
|
24
|
+
*
|
|
25
|
+
* @param homeDir user's home directory (injected for tests)
|
|
26
|
+
* @param projectRoot current project root (injected for tests)
|
|
27
|
+
* @param env process.env (injected for tests)
|
|
28
|
+
*/
|
|
29
|
+
export async function detectClient(homeDir, projectRoot, env = process.env) {
|
|
30
|
+
// 1. Env-var signals — fastest, most reliable when set.
|
|
31
|
+
if (env.CLAUDE_PLUGIN_ROOT) {
|
|
32
|
+
return {
|
|
33
|
+
client: "claude-code",
|
|
34
|
+
source: "CLAUDE_PLUGIN_ROOT env",
|
|
35
|
+
subagentsSupported: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (env.CURSOR_TRACE_ID || env.CURSOR_SESSION_ID) {
|
|
39
|
+
return {
|
|
40
|
+
client: "cursor",
|
|
41
|
+
source: "CURSOR_* env",
|
|
42
|
+
subagentsSupported: false,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (env.GEMINI_CLI === "1" || env.GOOGLE_CLOUD_CODE === "1") {
|
|
46
|
+
return {
|
|
47
|
+
client: "gemini",
|
|
48
|
+
source: "GEMINI_* env",
|
|
49
|
+
subagentsSupported: false,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (env.OPENAI_CODEX === "1" || env.CODEX_MODE) {
|
|
53
|
+
return {
|
|
54
|
+
client: "codex",
|
|
55
|
+
source: "CODEX_* env",
|
|
56
|
+
subagentsSupported: false,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// 2. Filesystem signals. Order matters — check the ones with the
|
|
60
|
+
// strongest on-disk footprint first.
|
|
61
|
+
const markers = [
|
|
62
|
+
[
|
|
63
|
+
join(homeDir, ".claude", "agents"),
|
|
64
|
+
"~/.claude/agents/",
|
|
65
|
+
"claude-code",
|
|
66
|
+
true,
|
|
67
|
+
],
|
|
68
|
+
[join(homeDir, ".claude"), "~/.claude/", "claude-code", true],
|
|
69
|
+
[join(projectRoot, ".cursor"), ".cursor/", "cursor", false],
|
|
70
|
+
[join(homeDir, ".cursor"), "~/.cursor/", "cursor", false],
|
|
71
|
+
[join(homeDir, ".codex"), "~/.codex/", "codex", false],
|
|
72
|
+
[join(homeDir, ".gemini"), "~/.gemini/", "gemini", false],
|
|
73
|
+
[join(projectRoot, ".gemini"), ".gemini/", "gemini", false],
|
|
74
|
+
];
|
|
75
|
+
for (const [path, source, client, subagentsSupported] of markers) {
|
|
76
|
+
try {
|
|
77
|
+
await fs.access(path);
|
|
78
|
+
return { client, source, subagentsSupported };
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* try next */
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// 3. Nothing recognised — assume Claude Code (safest default: the user
|
|
85
|
+
// explicitly ran `install-agents`, so they likely have Claude Code
|
|
86
|
+
// but haven't used it yet, or ran via CI where env vars are sparse).
|
|
87
|
+
return {
|
|
88
|
+
client: "unknown",
|
|
89
|
+
source: "no markers found",
|
|
90
|
+
subagentsSupported: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Compose a human-readable warning for `install-agents` to print when
|
|
95
|
+
* the detected client doesn't support subagents. Null when it does.
|
|
96
|
+
*/
|
|
97
|
+
export function nonClaudeClientWarning(detection) {
|
|
98
|
+
if (detection.subagentsSupported)
|
|
99
|
+
return null;
|
|
100
|
+
return (`[token-pilot] Detected ${detection.client} (${detection.source}).\n` +
|
|
101
|
+
`[token-pilot] tp-* subagents are a Claude Code concept and will NOT be\n` +
|
|
102
|
+
`[token-pilot] auto-invoked in ${detection.client}. Your MCP tools and Read hook\n` +
|
|
103
|
+
`[token-pilot] still work fully. If you meant to install for Claude Code (multiple\n` +
|
|
104
|
+
`[token-pilot] AI clients coexist), pass --scope=user explicitly and re-run.`);
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=detect-client.js.map
|
|
@@ -29,6 +29,7 @@ import { homedir } from "node:os";
|
|
|
29
29
|
import { dirname, join } from "node:path";
|
|
30
30
|
import { fileURLToPath } from "node:url";
|
|
31
31
|
import { parseFrontmatter } from "./agent-frontmatter.js";
|
|
32
|
+
import { detectClient, nonClaudeClientWarning } from "./detect-client.js";
|
|
32
33
|
function targetDirFor(opts) {
|
|
33
34
|
const root = opts.scope === "user" ? opts.homeDir : opts.projectRoot;
|
|
34
35
|
return join(root, ".claude", "agents");
|
|
@@ -301,6 +302,35 @@ export async function handleInstallAgents(argv, opts) {
|
|
|
301
302
|
const scopeArg = parseFlag(argv, "scope");
|
|
302
303
|
const force = parseFlag(argv, "force") !== undefined;
|
|
303
304
|
const projectRoot = opts?.projectRoot ?? process.cwd();
|
|
305
|
+
const homeDir = opts?.homeDir ?? homedir();
|
|
306
|
+
const env = opts?.env ?? process.env;
|
|
307
|
+
// v0.26.5 — plugin-aware note. If we're running as a Claude Code
|
|
308
|
+
// plugin (CLAUDE_PLUGIN_ROOT set), `install-agents` is still useful —
|
|
309
|
+
// tp-* subagents are separate from plugin hooks. But the directory
|
|
310
|
+
// the plugin installer created is the authoritative one; we print a
|
|
311
|
+
// short note so the user doesn't think they need to reinstall on
|
|
312
|
+
// every plugin update.
|
|
313
|
+
if (env.CLAUDE_PLUGIN_ROOT) {
|
|
314
|
+
process.stderr.write("[token-pilot] Note: you're running as a Claude Code plugin. " +
|
|
315
|
+
"install-agents still works for tp-* subagents (those are separate " +
|
|
316
|
+
"from plugin hooks). Continuing.\n");
|
|
317
|
+
}
|
|
318
|
+
// v0.26.0 — detect non-Claude clients where tp-* subagents have no
|
|
319
|
+
// runtime. We still let users force-install via --scope (they may run
|
|
320
|
+
// multiple clients side-by-side), but we warn loudly so nobody ends
|
|
321
|
+
// up with a ghost ~/.claude/agents/ directory nothing invokes.
|
|
322
|
+
const detection = await detectClient(homeDir, projectRoot, env);
|
|
323
|
+
const warning = nonClaudeClientWarning(detection);
|
|
324
|
+
if (warning) {
|
|
325
|
+
process.stderr.write(warning + "\n");
|
|
326
|
+
if (scopeArg === undefined) {
|
|
327
|
+
// Silent bail is worse than a warning — return 0 so CI/postinstall
|
|
328
|
+
// don't treat this as a failure, but skip creating the directory.
|
|
329
|
+
process.stderr.write("[token-pilot] Skipping agent install (pass --scope to override).\n");
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
process.stderr.write(`[token-pilot] --scope=${scopeArg} supplied — proceeding anyway.\n`);
|
|
333
|
+
}
|
|
304
334
|
let scope;
|
|
305
335
|
if (scopeArg === "user" || scopeArg === "project") {
|
|
306
336
|
scope = scopeArg;
|
|
@@ -330,7 +360,7 @@ export async function handleInstallAgents(argv, opts) {
|
|
|
330
360
|
const result = await installAgents({
|
|
331
361
|
scope,
|
|
332
362
|
projectRoot,
|
|
333
|
-
homeDir
|
|
363
|
+
homeDir,
|
|
334
364
|
distAgentsDir,
|
|
335
365
|
force,
|
|
336
366
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.26.2 — `npx token-pilot tool-audit`.
|
|
3
|
+
*
|
|
4
|
+
* Reads `.token-pilot/tool-calls.jsonl` + archives and emits a per-tool
|
|
5
|
+
* savings distribution across every session the user has ever run. The
|
|
6
|
+
* whole point is to answer "which tools actually save tokens" with
|
|
7
|
+
* data, not with a single field-report session.
|
|
8
|
+
*
|
|
9
|
+
* Output is human-readable by default; `--json` for scripts/CI.
|
|
10
|
+
* Exit code is always 0 — this is a diagnostic, not a gate. A future
|
|
11
|
+
* `tool-audit --fail-below=20` could turn it into a CI signal once we
|
|
12
|
+
* trust the baseline.
|
|
13
|
+
*/
|
|
14
|
+
import { type ToolCallEvent } from "../core/tool-call-log.js";
|
|
15
|
+
export interface ToolAuditRow {
|
|
16
|
+
tool: string;
|
|
17
|
+
count: number;
|
|
18
|
+
tokensReturned: number;
|
|
19
|
+
tokensWouldBe: number;
|
|
20
|
+
saved: number;
|
|
21
|
+
reductionPct: number;
|
|
22
|
+
/** Calls where the recorder claimed NO savings (pass-through) — separate so
|
|
23
|
+
* they don't poison the reduction average. */
|
|
24
|
+
noneCalls: number;
|
|
25
|
+
/** True when reduction is below the low-value threshold AND we have enough
|
|
26
|
+
* samples (≥5) to make a claim — avoids flagging tools after 1 bad run. */
|
|
27
|
+
lowValue: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Aggregate raw events into one row per tool. Pure — tested in
|
|
31
|
+
* isolation from the filesystem.
|
|
32
|
+
*/
|
|
33
|
+
export declare function aggregateToolCalls(events: ToolCallEvent[], lowValueThreshold?: number, minSamples?: number): ToolAuditRow[];
|
|
34
|
+
/**
|
|
35
|
+
* Format rows as a fixed-width table for the terminal. Pure.
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatTable(rows: ToolAuditRow[], opts: {
|
|
38
|
+
totalEvents: number;
|
|
39
|
+
}): string;
|
|
40
|
+
export interface ToolAuditOptions {
|
|
41
|
+
projectRoot: string;
|
|
42
|
+
json?: boolean;
|
|
43
|
+
/** For tests. */
|
|
44
|
+
now?: Date;
|
|
45
|
+
}
|
|
46
|
+
export declare function runToolAudit(opts: ToolAuditOptions): Promise<{
|
|
47
|
+
stdout: string;
|
|
48
|
+
exitCode: number;
|
|
49
|
+
rows: ToolAuditRow[];
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* CLI entry. `argv` is the raw `process.argv.slice(2)` after
|
|
53
|
+
* dispatching to this subcommand.
|
|
54
|
+
*/
|
|
55
|
+
export declare function handleToolAudit(argv: string[], opts?: {
|
|
56
|
+
projectRoot?: string;
|
|
57
|
+
}): Promise<number>;
|
|
58
|
+
//# sourceMappingURL=tool-audit.d.ts.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.26.2 — `npx token-pilot tool-audit`.
|
|
3
|
+
*
|
|
4
|
+
* Reads `.token-pilot/tool-calls.jsonl` + archives and emits a per-tool
|
|
5
|
+
* savings distribution across every session the user has ever run. The
|
|
6
|
+
* whole point is to answer "which tools actually save tokens" with
|
|
7
|
+
* data, not with a single field-report session.
|
|
8
|
+
*
|
|
9
|
+
* Output is human-readable by default; `--json` for scripts/CI.
|
|
10
|
+
* Exit code is always 0 — this is a diagnostic, not a gate. A future
|
|
11
|
+
* `tool-audit --fail-below=20` could turn it into a CI signal once we
|
|
12
|
+
* trust the baseline.
|
|
13
|
+
*/
|
|
14
|
+
import { loadAllToolCalls } from "../core/tool-call-log.js";
|
|
15
|
+
/**
|
|
16
|
+
* Aggregate raw events into one row per tool. Pure — tested in
|
|
17
|
+
* isolation from the filesystem.
|
|
18
|
+
*/
|
|
19
|
+
export function aggregateToolCalls(events, lowValueThreshold = 20, minSamples = 5) {
|
|
20
|
+
const byTool = new Map();
|
|
21
|
+
for (const e of events) {
|
|
22
|
+
const row = byTool.get(e.tool) ?? {
|
|
23
|
+
count: 0,
|
|
24
|
+
tokensReturned: 0,
|
|
25
|
+
tokensWouldBe: 0,
|
|
26
|
+
noneCalls: 0,
|
|
27
|
+
};
|
|
28
|
+
row.count++;
|
|
29
|
+
row.tokensReturned += e.tokensReturned;
|
|
30
|
+
row.tokensWouldBe += e.tokensWouldBe;
|
|
31
|
+
if (e.savingsCategory === "none")
|
|
32
|
+
row.noneCalls++;
|
|
33
|
+
byTool.set(e.tool, row);
|
|
34
|
+
}
|
|
35
|
+
const rows = [];
|
|
36
|
+
for (const [tool, r] of byTool) {
|
|
37
|
+
const saved = Math.max(0, r.tokensWouldBe - r.tokensReturned);
|
|
38
|
+
const reductionPct = r.tokensWouldBe > 0
|
|
39
|
+
? Math.round((1 - r.tokensReturned / r.tokensWouldBe) * 100)
|
|
40
|
+
: 0;
|
|
41
|
+
const lowValue = r.count >= minSamples && reductionPct < lowValueThreshold;
|
|
42
|
+
rows.push({
|
|
43
|
+
tool,
|
|
44
|
+
count: r.count,
|
|
45
|
+
tokensReturned: r.tokensReturned,
|
|
46
|
+
tokensWouldBe: r.tokensWouldBe,
|
|
47
|
+
saved,
|
|
48
|
+
reductionPct,
|
|
49
|
+
noneCalls: r.noneCalls,
|
|
50
|
+
lowValue,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Sort by tokens saved desc — the first row is your biggest
|
|
54
|
+
// contributor to overall savings, low-value tools sink to the bottom.
|
|
55
|
+
rows.sort((a, b) => b.saved - a.saved);
|
|
56
|
+
return rows;
|
|
57
|
+
}
|
|
58
|
+
function fmtTokens(n) {
|
|
59
|
+
if (n >= 1_000_000)
|
|
60
|
+
return (n / 1_000_000).toFixed(1) + "M";
|
|
61
|
+
if (n >= 1_000)
|
|
62
|
+
return (n / 1_000).toFixed(1) + "k";
|
|
63
|
+
return String(n);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Format rows as a fixed-width table for the terminal. Pure.
|
|
67
|
+
*/
|
|
68
|
+
export function formatTable(rows, opts) {
|
|
69
|
+
if (rows.length === 0) {
|
|
70
|
+
return `No tool calls recorded yet.
|
|
71
|
+
Run a few MCP tool calls from your AI client, then re-run \`npx token-pilot tool-audit\`.`;
|
|
72
|
+
}
|
|
73
|
+
const lines = [];
|
|
74
|
+
lines.push(`Token Pilot — tool audit`);
|
|
75
|
+
lines.push(` ${opts.totalEvents} calls across ${rows.length} tools (cumulative across sessions)`);
|
|
76
|
+
lines.push("");
|
|
77
|
+
lines.push(" Tool Calls Saved Returned Reduction");
|
|
78
|
+
lines.push(" ─────────────────────────────────────────────────────────────────");
|
|
79
|
+
for (const r of rows) {
|
|
80
|
+
const tool = r.tool.padEnd(24);
|
|
81
|
+
const count = String(r.count).padStart(6);
|
|
82
|
+
const saved = fmtTokens(r.saved).padStart(9);
|
|
83
|
+
const returned = fmtTokens(r.tokensReturned).padStart(9);
|
|
84
|
+
const pct = `${r.reductionPct}%`.padStart(6);
|
|
85
|
+
const flag = r.lowValue ? " ⚠ low-value" : "";
|
|
86
|
+
lines.push(` ${tool} ${count} ${saved} ${returned} ${pct}${flag}`);
|
|
87
|
+
}
|
|
88
|
+
const lowValueRows = rows.filter((r) => r.lowValue);
|
|
89
|
+
if (lowValueRows.length > 0) {
|
|
90
|
+
lines.push("");
|
|
91
|
+
lines.push("Low-value tools flagged above have <20% token reduction across ≥5 calls.");
|
|
92
|
+
lines.push("Consider: check their `none` passthrough count, or whether a cheaper alternative (Grep, Read) would do the job.");
|
|
93
|
+
}
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
96
|
+
export async function runToolAudit(opts) {
|
|
97
|
+
const events = await loadAllToolCalls(opts.projectRoot);
|
|
98
|
+
const rows = aggregateToolCalls(events);
|
|
99
|
+
if (opts.json) {
|
|
100
|
+
return {
|
|
101
|
+
stdout: JSON.stringify({ totalEvents: events.length, tools: rows }, null, 2),
|
|
102
|
+
exitCode: 0,
|
|
103
|
+
rows,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
stdout: formatTable(rows, { totalEvents: events.length }),
|
|
108
|
+
exitCode: 0,
|
|
109
|
+
rows,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* CLI entry. `argv` is the raw `process.argv.slice(2)` after
|
|
114
|
+
* dispatching to this subcommand.
|
|
115
|
+
*/
|
|
116
|
+
export async function handleToolAudit(argv, opts) {
|
|
117
|
+
const json = argv.includes("--json");
|
|
118
|
+
const projectRoot = opts?.projectRoot ?? process.cwd();
|
|
119
|
+
const { stdout, exitCode } = await runToolAudit({ projectRoot, json });
|
|
120
|
+
process.stdout.write(stdout + "\n");
|
|
121
|
+
return exitCode;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=tool-audit.js.map
|
package/dist/cli/typo-guard.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* Everything else passes through untouched — a real project root like
|
|
18
18
|
* `/home/user/my-project` or `./subdir` goes to startServer as before.
|
|
19
19
|
*/
|
|
20
|
-
export declare const KNOWN_COMMANDS: readonly ["hook-read", "hook-edit", "hook-post-bash", "hook-post-task", "hook-session-start", "install-hook", "uninstall-hook", "install-ast-index", "doctor", "bless-agents", "unbless-agents", "install-agents", "uninstall-agents", "stats", "save-doc", "list-docs", "init", "--version", "-v", "--help", "-h"];
|
|
20
|
+
export declare const KNOWN_COMMANDS: readonly ["hook-read", "hook-edit", "hook-post-bash", "hook-post-task", "hook-session-start", "install-hook", "uninstall-hook", "install-ast-index", "doctor", "bless-agents", "unbless-agents", "install-agents", "uninstall-agents", "stats", "tool-audit", "save-doc", "list-docs", "init", "--version", "-v", "--help", "-h"];
|
|
21
21
|
export interface TypoGuardResult {
|
|
22
22
|
kind: "pass-through" | "typo";
|
|
23
23
|
suggestion?: string;
|
package/dist/cli/typo-guard.js
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.26.2 — persistent MCP tool-call log.
|
|
3
|
+
*
|
|
4
|
+
* Separate from `hook-events.jsonl` (which records Read-hook outcomes),
|
|
5
|
+
* this file accumulates every MCP tool invocation with its token
|
|
6
|
+
* accounting across ALL sessions. Used by `npx token-pilot tool-audit`
|
|
7
|
+
* to produce a per-tool savings distribution that survives `/clear`,
|
|
8
|
+
* session restarts, and even reboots — i.e. the data-driven base we
|
|
9
|
+
* need before pruning or modifying tools based on "savings".
|
|
10
|
+
*
|
|
11
|
+
* Why not piggy-back on hook-events.jsonl? Different data model: hook
|
|
12
|
+
* events are a denied/allowed bitstream keyed by filepath+lineCount,
|
|
13
|
+
* tool calls are rich records with tokensReturned, wouldBe, category,
|
|
14
|
+
* delegation status. Forcing both into one schema would hurt both
|
|
15
|
+
* readers.
|
|
16
|
+
*
|
|
17
|
+
* File path: `<projectRoot>/.token-pilot/tool-calls.jsonl`. Rotation,
|
|
18
|
+
* retention, and best-effort error handling follow the same contract
|
|
19
|
+
* as event-log.ts — identical 10 MB / 30-day / 100 MB caps so overall
|
|
20
|
+
* .token-pilot/ disk usage stays predictable.
|
|
21
|
+
*/
|
|
22
|
+
import type { SavingsCategory } from "./session-analytics.js";
|
|
23
|
+
export declare const TOOL_LOG_ROTATION_BYTES = 10000000;
|
|
24
|
+
export declare const TOOL_LOG_RETENTION_MAX_AGE_DAYS = 30;
|
|
25
|
+
export declare const TOOL_LOG_RETENTION_MAX_TOTAL_BYTES = 100000000;
|
|
26
|
+
/**
|
|
27
|
+
* Persisted shape of one MCP tool call. Mirrors the in-memory
|
|
28
|
+
* `ToolCall` in session-analytics.ts but trims runtime-only fields
|
|
29
|
+
* (intent, decisionTrace — those are re-derivable from args/tool if
|
|
30
|
+
* ever needed, not worth the disk cost).
|
|
31
|
+
*/
|
|
32
|
+
export interface ToolCallEvent {
|
|
33
|
+
ts: number;
|
|
34
|
+
session_id: string;
|
|
35
|
+
tool: string;
|
|
36
|
+
path?: string;
|
|
37
|
+
tokensReturned: number;
|
|
38
|
+
tokensWouldBe: number;
|
|
39
|
+
savingsCategory: SavingsCategory;
|
|
40
|
+
sessionCacheHit?: boolean;
|
|
41
|
+
delegatedToContextMode?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare function currentToolLogPath(projectRoot: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Append one tool call. Never throws — telemetry must not break the
|
|
46
|
+
* tool-response path (the caller awaits this but treats errors as
|
|
47
|
+
* silent via `.catch(() => undefined)` at the call site).
|
|
48
|
+
*/
|
|
49
|
+
export declare function appendToolCall(projectRoot: string, event: ToolCallEvent): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Read every tool-call event from the current file + all archives.
|
|
52
|
+
* Malformed JSONL lines are skipped silently — one bad line should not
|
|
53
|
+
* poison the dataset. Returns events in *insertion order within each
|
|
54
|
+
* file*, which happens to be chronological because append-only.
|
|
55
|
+
*/
|
|
56
|
+
export declare function loadAllToolCalls(projectRoot: string): Promise<ToolCallEvent[]>;
|
|
57
|
+
export declare function retentionDeletions(files: Array<{
|
|
58
|
+
path: string;
|
|
59
|
+
mtime: Date;
|
|
60
|
+
size: number;
|
|
61
|
+
}>, now: Date, maxAgeDays?: number, maxTotalBytes?: number): string[];
|
|
62
|
+
export declare function applyRetention(projectRoot: string, now?: Date): Promise<void>;
|
|
63
|
+
//# sourceMappingURL=tool-call-log.d.ts.map
|