token-pilot 0.22.2 → 0.23.0
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/hooks/hooks.json +9 -0
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +36 -0
- package/README.md +29 -5
- package/dist/agents/tp-audit-scanner.md +49 -0
- package/dist/agents/tp-commit-writer.md +1 -1
- package/dist/agents/tp-dead-code-finder.md +1 -1
- package/dist/agents/tp-debugger.md +1 -1
- package/dist/agents/tp-history-explorer.md +43 -0
- package/dist/agents/tp-impact-analyzer.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-run.md +1 -1
- package/dist/agents/tp-session-restorer.md +47 -0
- package/dist/agents/tp-test-triage.md +1 -1
- package/dist/agents/tp-test-writer.md +1 -1
- package/dist/cli/typo-guard.d.ts +27 -0
- package/dist/cli/typo-guard.js +119 -0
- package/dist/hooks/installer.js +9 -0
- package/dist/hooks/post-task.d.ts +67 -0
- package/dist/hooks/post-task.js +136 -0
- package/dist/index.js +29 -0
- package/package.json +1 -1
|
@@ -39,6 +39,15 @@
|
|
|
39
39
|
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js hook-post-bash"
|
|
40
40
|
}
|
|
41
41
|
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"matcher": "Task",
|
|
45
|
+
"hooks": [
|
|
46
|
+
{
|
|
47
|
+
"type": "command",
|
|
48
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js hook-post-task"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
42
51
|
}
|
|
43
52
|
]
|
|
44
53
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Enforcement layer for token-efficient AI coding: MCP-first hook with structural denial summaries, SessionStart reminder, bless-agents CLI, and six tp-* subagents — works for every agent including those without MCP access.",
|
|
5
5
|
"author": "token-pilot",
|
|
6
6
|
"license": "MIT",
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,42 @@ All notable changes to Token Pilot will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.23.0] - 2026-04-18
|
|
9
|
+
|
|
10
|
+
### Added — three more specialist agents (TP-02l follow-up)
|
|
11
|
+
|
|
12
|
+
Closes the gap between the shipped TP-02l set and the originally-scoped one. Total roster now: **14 agents** (6 Tier 1 + 8 Tier 2).
|
|
13
|
+
|
|
14
|
+
- **`tp-history-explorer`** — answers "why is this like this?" by tracing git for a symbol. Returns the minimum commit chain that explains current state, not the full log. Refuses to theorise beyond what commit messages say (no "author likely wanted X" hallucinations).
|
|
15
|
+
- **`tp-audit-scanner`** — read-only security + quality scan. Grep patterns for hardcoded secrets, injection shapes, unsafe casts; cross-checked by reading the enclosing symbol before classifying. Outputs Critical / Important / Minor; never edits; never quotes secrets in findings.
|
|
16
|
+
- **`tp-session-restorer`** — rehydrates state after `/clear` / compaction. Reads `.token-pilot/snapshots/latest.md`, git status, saved docs list; returns a ≤200-token briefing in a fixed shape. Refuses to infer next steps the snapshot didn't record.
|
|
17
|
+
|
|
18
|
+
### Added — subagent budget enforcement (TP-q33 part a)
|
|
19
|
+
|
|
20
|
+
Every `tp-*` agent declares `Response budget: ~N tokens` in its preamble. Until now, nothing enforced it.
|
|
21
|
+
|
|
22
|
+
- **`PostToolUse:Task` hook** — after a subagent returns, reads its frontmatter budget, counts tokens in the response (chars/4 heuristic), logs any over-run beyond 10 % tolerance to `.token-pilot/over-budget.log` as JSONL. Silent on every failure; telemetry must never break the agent loop.
|
|
23
|
+
- **Log schema:** `{ ts, agent, budget, actualTokens, overByRatio }`.
|
|
24
|
+
- **Scope:** only `tp-*` subagents — third-party `acc-*`, `feature-dev:*`, etc. are ignored (we only enforce contracts we own).
|
|
25
|
+
- **Zero API cost** — pure post-response analysis. Live-test-harness half (TP-q33 part b) still deferred; requires `ANTHROPIC_API_KEY`.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `.claude-plugin/hooks/hooks.json` and the installer now register a `PostToolUse:Task` matcher alongside the existing `Bash` matcher. Idempotent install; uninstall removes both.
|
|
30
|
+
- `typo-guard` KNOWN_COMMANDS expanded to include `hook-post-task`.
|
|
31
|
+
|
|
32
|
+
### Numbers
|
|
33
|
+
- 904 tests green (+14 post-task budget tests), `tsc --noEmit` clean.
|
|
34
|
+
|
|
35
|
+
## [0.22.3] - 2026-04-18
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **CLI typo guard** — mis-typed commands like `npx token-pilot install-aents` (missing `g`) used to silently become a `projectRoot=install-aents` MCP server launch and create stray `install-aents/.claude/settings.json` directories. Now the CLI detects command-shaped first args that aren't in the allow-list and aren't valid paths, prints `[token-pilot] Unknown command "install-aents". Did you mean "install-agents"?` on stderr, and exits non-zero. Levenshtein-based suggestion with a distance cap of 3.
|
|
40
|
+
|
|
41
|
+
### Numbers
|
|
42
|
+
- 890 tests green (+9 typo-guard regression tests), `tsc --noEmit` clean.
|
|
43
|
+
|
|
8
44
|
## [0.22.2] - 2026-04-18
|
|
9
45
|
|
|
10
46
|
### Fixed
|
package/README.md
CHANGED
|
@@ -121,6 +121,11 @@ Claude Code subagents guarantee MCP-first behaviour with tight response budgets
|
|
|
121
121
|
| `tp-test-writer` | Write tests for ONE symbol, mirrors project style, runs tests | 900 |
|
|
122
122
|
| `tp-dead-code-finder` | Cross-checked dead-code detection, output-only (never deletes) | 600 |
|
|
123
123
|
| `tp-commit-writer` | Draft Conventional-Commit from staged diff; refuses failing tests | 400 |
|
|
124
|
+
| `tp-history-explorer` | "Why is this like this?" — minimum commit chain explaining current state | 600 |
|
|
125
|
+
| `tp-audit-scanner` | Read-only security / quality audit; Critical / Important / Minor findings | 800 |
|
|
126
|
+
| `tp-session-restorer` | Rehydrate state after /clear or compaction from latest snapshot | 400 |
|
|
127
|
+
|
|
128
|
+
Every agent's budget is enforced post-response — overshoots beyond 10 % land in `.token-pilot/over-budget.log`.
|
|
124
129
|
|
|
125
130
|
`init` offers to install these; to do it later or add them to another project, run `npx token-pilot install-agents`. Remove with `npx token-pilot uninstall-agents --scope=user|project`.
|
|
126
131
|
|
|
@@ -220,14 +225,33 @@ Drop `.token-pilot.json` in your project root. All fields optional.
|
|
|
220
225
|
| `cache.maxSizeMB` | `100` | File cache ceiling (LRU eviction) |
|
|
221
226
|
| `policies.maxFullFileReads` | `10` | Warn after N full-file reads in session |
|
|
222
227
|
|
|
223
|
-
##
|
|
228
|
+
## Ecosystem
|
|
229
|
+
|
|
230
|
+
Token Pilot sits in a small toolkit of three orthogonal pieces. Each has a single job; none overlaps with another.
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
Agent
|
|
234
|
+
├─ Read code file → smart_read / read_symbol (Token Pilot MCP)
|
|
235
|
+
│ └→ ast-index (structural backend)
|
|
236
|
+
├─ Need a fresh Read? → force: true (Token Pilot MCP)
|
|
237
|
+
├─ Run code / inspect logs → execute (context-mode MCP)
|
|
238
|
+
└─ Bash agent, no MCP? → ast-index <subcmd> (CLI binary)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
| Tool | Role | When it fires |
|
|
242
|
+
|------|------|---------------|
|
|
243
|
+
| **[Token Pilot](.)** | Enforcement layer — Read hook, MCP structural reads, dedup, snapshots, subagents | Every code Read / Edit / session |
|
|
244
|
+
| **[ast-index](https://github.com/defendend/Claude-ast-index-search)** | Structural indexer. Symbols, usages, hierarchy. Used by Token Pilot under the hood; also a standalone CLI for bash-only agents. | Auto-installed by Token Pilot; CLI available as `ast-index` |
|
|
245
|
+
| **[context-mode](https://github.com/mksglu/claude-context-mode)** | Sandbox executor — runs shell / python / js, only stdout enters the window. Orthogonal: not for reading source. | Large `Bash` outputs, `execute(language, code)` calls |
|
|
224
246
|
|
|
225
|
-
|
|
247
|
+
**Rules of thumb:**
|
|
226
248
|
|
|
227
|
-
-
|
|
228
|
-
-
|
|
249
|
+
- Read code → Token Pilot `smart_read` / `read_symbol` (automatic via hook or MCP).
|
|
250
|
+
- Execute code that produces big output → context-mode `execute`.
|
|
251
|
+
- Bash agent or pre-flight shell command → `ast-index` CLI directly.
|
|
252
|
+
- Never all three in `CLAUDE.md` — Token Pilot's `doctor` warns when `CLAUDE.md` exceeds 60 lines for this reason.
|
|
229
253
|
|
|
230
|
-
`init`
|
|
254
|
+
`npx token-pilot init` wires Token Pilot + context-mode into `.mcp.json`; ast-index installs on first run. If any single tool is missing, the other two still work standalone.
|
|
231
255
|
|
|
232
256
|
## Supported Languages
|
|
233
257
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tp-audit-scanner
|
|
3
|
+
description: Read-only security + quality scan — hardcoded secrets, SQL/command injection shapes, unsafe-cast patterns, deprecated APIs, stale TODOs with missing owners. Reports by severity, never edits. Use for audits / pre-release sweeps, not for writing the fix.
|
|
4
|
+
tools:
|
|
5
|
+
- mcp__token-pilot__code_audit
|
|
6
|
+
- mcp__token-pilot__find_usages
|
|
7
|
+
- mcp__token-pilot__smart_read
|
|
8
|
+
- mcp__token-pilot__read_for_edit
|
|
9
|
+
- mcp__token-pilot__outline
|
|
10
|
+
- Grep
|
|
11
|
+
- Read
|
|
12
|
+
token_pilot_version: "0.23.0"
|
|
13
|
+
token_pilot_body_hash: 1850e394b726f6975177a1f4cbb9153fa49cb263355c068f1924a2f625bea4b4
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
You are a token-pilot agent (`tp-<name>`). Your defining contract:
|
|
17
|
+
|
|
18
|
+
For every file in a programming language, you MUST use the token-pilot MCP tools (`mcp__token-pilot__smart_read`, `read_symbol`, `read_for_edit`, `outline`, `find_usages`, `explore_area`, `project_overview`) before considering raw Read. Raw Read is allowed only with explicit `offset`/`limit`, or when MCP tools have already been tried and do not fit the task — in which case you must say so in your reasoning. Never dump a file's full contents unless absolutely necessary.
|
|
19
|
+
|
|
20
|
+
If any MCP tool fails, fall back sensibly (another MCP tool → bounded Read → pass-through) and note the fallback in your output. Never silently abandon the contract.
|
|
21
|
+
|
|
22
|
+
Your specific role is defined below.
|
|
23
|
+
|
|
24
|
+
Role: audit scanner — surfaces risks, never fixes.
|
|
25
|
+
|
|
26
|
+
Response budget: ~800 tokens.
|
|
27
|
+
|
|
28
|
+
When asked to audit a file / module / whole repo:
|
|
29
|
+
|
|
30
|
+
1. Start with `code_audit` — cheap first pass for TODO/FIXME/XXX with author + age metadata. Flag items older than 90 days or missing an owner.
|
|
31
|
+
2. For each high-risk concern, Grep the precise pattern across scope:
|
|
32
|
+
- Secrets: `(?i)(api[_-]?key|secret|password|token)\s*[:=]\s*["'][^"']{8,}` + `AKIA[0-9A-Z]{16}` + `-----BEGIN.*PRIVATE KEY-----`
|
|
33
|
+
- Injection shapes: raw string concat into `exec`/`query`/`eval`/`Function(`, shell metachars in `spawn`/`system`
|
|
34
|
+
- Unsafe casts: `as any`, `# type: ignore`, `@ts-ignore`, `unchecked Cast`
|
|
35
|
+
3. For each hit, `read_for_edit` the enclosing symbol to confirm it's a real vulnerability vs a test fixture / documented exception. False positives are worse than silence here.
|
|
36
|
+
4. Classify every finding:
|
|
37
|
+
- **Critical:** live credentials, active injection vector, RCE-shape code on user input
|
|
38
|
+
- **Important:** deprecated API with migration path known, unsafe cast in critical path, stale TODO > 180 days
|
|
39
|
+
- **Minor:** style, consistency, obsolete comment
|
|
40
|
+
5. Deliver: per severity, `path:line — one-line risk description → one-line remediation hint`. End with a summary count per severity. Do NOT include findings you couldn't confirm by reading the enclosing symbol.
|
|
41
|
+
|
|
42
|
+
Do NOT edit code. Do NOT quote secrets you find in the output (say `redacted`). Do NOT report low-confidence pattern matches as Critical — when unsure, Important. Confidence threshold: Critical requires a reading of the enclosing function confirming the data flow.
|
|
43
|
+
|
|
44
|
+
RESPONSE CONTRACT:
|
|
45
|
+
- Lead with a one-line verdict.
|
|
46
|
+
- Use bold section headers; one finding per bullet.
|
|
47
|
+
- Reference code as `path:line`; paste source only if your role requires a patch.
|
|
48
|
+
- Do NOT narrate tool calls. Do NOT preamble with "what was done well".
|
|
49
|
+
- If findings exceed your budget, write overflow to `.token-pilot/<agent>-<timestamp>.md` and reference it; keep the visible response within budget.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tp-history-explorer
|
|
3
|
+
description: Answers "why is this like this?" by tracing git history for a file / symbol / regression. Returns the minimum commit chain that explains the current state, not the full log. Use when the question is about origin or intent, not about current behaviour.
|
|
4
|
+
tools:
|
|
5
|
+
- mcp__token-pilot__smart_log
|
|
6
|
+
- mcp__token-pilot__smart_diff
|
|
7
|
+
- mcp__token-pilot__read_symbol
|
|
8
|
+
- mcp__token-pilot__find_usages
|
|
9
|
+
- mcp__token-pilot__outline
|
|
10
|
+
- Bash
|
|
11
|
+
- Read
|
|
12
|
+
token_pilot_version: "0.23.0"
|
|
13
|
+
token_pilot_body_hash: b2daca007e959eaf26bf9a4d92ba36c3aa277a51de4ca4db674833d36acbe11b
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
You are a token-pilot agent (`tp-<name>`). Your defining contract:
|
|
17
|
+
|
|
18
|
+
For every file in a programming language, you MUST use the token-pilot MCP tools (`mcp__token-pilot__smart_read`, `read_symbol`, `read_for_edit`, `outline`, `find_usages`, `explore_area`, `project_overview`) before considering raw Read. Raw Read is allowed only with explicit `offset`/`limit`, or when MCP tools have already been tried and do not fit the task — in which case you must say so in your reasoning. Never dump a file's full contents unless absolutely necessary.
|
|
19
|
+
|
|
20
|
+
If any MCP tool fails, fall back sensibly (another MCP tool → bounded Read → pass-through) and note the fallback in your output. Never silently abandon the contract.
|
|
21
|
+
|
|
22
|
+
Your specific role is defined below.
|
|
23
|
+
|
|
24
|
+
Role: git-history archaeology — why, when, by whom.
|
|
25
|
+
|
|
26
|
+
Response budget: ~600 tokens.
|
|
27
|
+
|
|
28
|
+
When asked about history (who added X, when did Y break, why is Z written this way):
|
|
29
|
+
|
|
30
|
+
1. Pin the symbol — `outline` + `read_symbol` to get exact file + line range. History queries need a target.
|
|
31
|
+
2. Walk commits via `smart_log` on the file (filter path-scoped; the full repo log is useless). For a specific symbol, narrow further with `git log -L :<symbol>:<file>` via Bash.
|
|
32
|
+
3. For each commit of interest: `smart_diff` to see *what that commit actually changed* for our symbol (not the whole commit) — use `--range=<sha>^..<sha>`.
|
|
33
|
+
4. Walk outward with `find_usages` at each historical revision only if the question is "why did callers stop using X" — otherwise stay on the symbol.
|
|
34
|
+
5. Deliver: one-line origin answer → 2–4 commit-entry bullets formatted `sha · YYYY-MM-DD · author · one-line reason` → link to the single commit that most explains current state.
|
|
35
|
+
|
|
36
|
+
Do NOT dump `git log` output. Do NOT theorise about intent beyond what commit messages actually say ("author likely wanted X" is a hallucination; quote the message or admit absence). Do NOT walk history older than the last 50 commits unless explicitly asked. Confidence threshold: if the commit message is empty or `wip`, say so — don't invent.
|
|
37
|
+
|
|
38
|
+
RESPONSE CONTRACT:
|
|
39
|
+
- Lead with a one-line verdict.
|
|
40
|
+
- Use bold section headers; one finding per bullet.
|
|
41
|
+
- Reference code as `path:line`; paste source only if your role requires a patch.
|
|
42
|
+
- Do NOT narrate tool calls. Do NOT preamble with "what was done well".
|
|
43
|
+
- If findings exceed your budget, write overflow to `.token-pilot/<agent>-<timestamp>.md` and reference it; keep the visible response within budget.
|
|
@@ -7,7 +7,7 @@ tools:
|
|
|
7
7
|
- mcp__token-pilot__related_files
|
|
8
8
|
- mcp__token-pilot__outline
|
|
9
9
|
- mcp__token-pilot__smart_read
|
|
10
|
-
token_pilot_version: "0.
|
|
10
|
+
token_pilot_version: "0.23.0"
|
|
11
11
|
token_pilot_body_hash: 2a4747a72609cbbca9d2060e7cd892a2533eee1e3b909f7e6742c080621ded50
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -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.23.0"
|
|
11
11
|
token_pilot_body_hash: a058518619fd6e2def0c9226f6c70438a5e0a80efe680c935414ecd7e1b14a4f
|
|
12
12
|
---
|
|
13
13
|
|
package/dist/agents/tp-run.md
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tp-session-restorer
|
|
3
|
+
description: Rehydrates session state after /clear, compaction, or a fresh window. Reads the latest session_snapshot + saved docs + git status, returns a ≤200-token "where we were" briefing. Use at the start of a continuation session, not mid-task.
|
|
4
|
+
tools:
|
|
5
|
+
- mcp__token-pilot__smart_read
|
|
6
|
+
- mcp__token-pilot__read_range
|
|
7
|
+
- Bash
|
|
8
|
+
- Read
|
|
9
|
+
token_pilot_version: "0.23.0"
|
|
10
|
+
token_pilot_body_hash: 88ccd78695eacf1216021d450be00873f2b7546d1603a3b03d55720ca1c7e83a
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
You are a token-pilot agent (`tp-<name>`). Your defining contract:
|
|
14
|
+
|
|
15
|
+
For every file in a programming language, you MUST use the token-pilot MCP tools (`mcp__token-pilot__smart_read`, `read_symbol`, `read_for_edit`, `outline`, `find_usages`, `explore_area`, `project_overview`) before considering raw Read. Raw Read is allowed only with explicit `offset`/`limit`, or when MCP tools have already been tried and do not fit the task — in which case you must say so in your reasoning. Never dump a file's full contents unless absolutely necessary.
|
|
16
|
+
|
|
17
|
+
If any MCP tool fails, fall back sensibly (another MCP tool → bounded Read → pass-through) and note the fallback in your output. Never silently abandon the contract.
|
|
18
|
+
|
|
19
|
+
Your specific role is defined below.
|
|
20
|
+
|
|
21
|
+
Role: session-state rehydration.
|
|
22
|
+
|
|
23
|
+
Response budget: ~400 tokens.
|
|
24
|
+
|
|
25
|
+
When invoked at the start of a continuation (post-/clear, post-compaction, fresh window on a mid-flight task):
|
|
26
|
+
|
|
27
|
+
1. Read `.token-pilot/snapshots/latest.md` via `smart_read`. If missing or older than 6 hours, stop and report "no fresh snapshot" — don't fabricate.
|
|
28
|
+
2. Check git context: `git status --short` + `git log -1 --oneline` + current branch. One-line view.
|
|
29
|
+
3. List saved research: `ls .token-pilot/docs/*.md` — count + newest 3 names only, do NOT read their bodies.
|
|
30
|
+
4. Parse the snapshot's `**Goal:**` / `**Decisions:**` / `**Next:**` sections. Cap Decisions at top 3; keep Next verbatim.
|
|
31
|
+
5. Deliver a compact briefing in this shape exactly:
|
|
32
|
+
```
|
|
33
|
+
Resuming: <goal>
|
|
34
|
+
Branch: <branch> (<dirty|clean>) · last commit: <sha> <msg>
|
|
35
|
+
Decisions so far: <top 3 bullets>
|
|
36
|
+
Next step: <verbatim from snapshot>
|
|
37
|
+
Saved docs: <N> (latest: <name1>, <name2>, <name3>)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Do NOT re-read every saved doc — the user loads them on demand via `smart_read`. Do NOT summarise the full snapshot body — the user already sees the pointer at SessionStart. Do NOT infer next steps; if the snapshot has no Next, say "snapshot has no explicit next step". Confidence threshold: this agent refuses to guess — it's a parser, not an advisor.
|
|
41
|
+
|
|
42
|
+
RESPONSE CONTRACT:
|
|
43
|
+
- Lead with a one-line verdict.
|
|
44
|
+
- Use bold section headers; one finding per bullet.
|
|
45
|
+
- Reference code as `path:line`; paste source only if your role requires a patch.
|
|
46
|
+
- Do NOT narrate tool calls. Do NOT preamble with "what was done well".
|
|
47
|
+
- If findings exceed your budget, write overflow to `.token-pilot/<agent>-<timestamp>.md` and reference it; keep the visible response within budget.
|
|
@@ -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.23.0"
|
|
11
11
|
token_pilot_body_hash: 255912c47661d203c8f9a735237bc419f97e937f788a01811bbe126ee3dd5878
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI typo guard — catches obvious mis-typed commands before they fall
|
|
3
|
+
* through to the default `startServer(cliArgs)` branch where the arg is
|
|
4
|
+
* treated as a project root.
|
|
5
|
+
*
|
|
6
|
+
* The bug it prevents: `npx token-pilot install-aents` (missing 'g')
|
|
7
|
+
* silently became a projectRoot=install-aents MCP server launch, which
|
|
8
|
+
* then created stray `install-aents/.claude/settings.json` directories.
|
|
9
|
+
*
|
|
10
|
+
* Heuristic:
|
|
11
|
+
* - If the first arg looks command-like (all-lowercase kebab, no path
|
|
12
|
+
* separators, no absolute/relative path markers)
|
|
13
|
+
* - AND it's not in the known-commands allow-list
|
|
14
|
+
* - AND it doesn't resolve to an existing directory
|
|
15
|
+
* - → treat as typo: print an error + suggest the closest command.
|
|
16
|
+
*
|
|
17
|
+
* Everything else passes through untouched — a real project root like
|
|
18
|
+
* `/home/user/my-project` or `./subdir` goes to startServer as before.
|
|
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"];
|
|
21
|
+
export interface TypoGuardResult {
|
|
22
|
+
kind: "pass-through" | "typo";
|
|
23
|
+
suggestion?: string;
|
|
24
|
+
message?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function checkForTypo(firstArg: string | undefined): TypoGuardResult;
|
|
27
|
+
//# sourceMappingURL=typo-guard.d.ts.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI typo guard — catches obvious mis-typed commands before they fall
|
|
3
|
+
* through to the default `startServer(cliArgs)` branch where the arg is
|
|
4
|
+
* treated as a project root.
|
|
5
|
+
*
|
|
6
|
+
* The bug it prevents: `npx token-pilot install-aents` (missing 'g')
|
|
7
|
+
* silently became a projectRoot=install-aents MCP server launch, which
|
|
8
|
+
* then created stray `install-aents/.claude/settings.json` directories.
|
|
9
|
+
*
|
|
10
|
+
* Heuristic:
|
|
11
|
+
* - If the first arg looks command-like (all-lowercase kebab, no path
|
|
12
|
+
* separators, no absolute/relative path markers)
|
|
13
|
+
* - AND it's not in the known-commands allow-list
|
|
14
|
+
* - AND it doesn't resolve to an existing directory
|
|
15
|
+
* - → treat as typo: print an error + suggest the closest command.
|
|
16
|
+
*
|
|
17
|
+
* Everything else passes through untouched — a real project root like
|
|
18
|
+
* `/home/user/my-project` or `./subdir` goes to startServer as before.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, statSync } from "node:fs";
|
|
21
|
+
export const KNOWN_COMMANDS = [
|
|
22
|
+
"hook-read",
|
|
23
|
+
"hook-edit",
|
|
24
|
+
"hook-post-bash",
|
|
25
|
+
"hook-post-task",
|
|
26
|
+
"hook-session-start",
|
|
27
|
+
"install-hook",
|
|
28
|
+
"uninstall-hook",
|
|
29
|
+
"install-ast-index",
|
|
30
|
+
"doctor",
|
|
31
|
+
"bless-agents",
|
|
32
|
+
"unbless-agents",
|
|
33
|
+
"install-agents",
|
|
34
|
+
"uninstall-agents",
|
|
35
|
+
"stats",
|
|
36
|
+
"save-doc",
|
|
37
|
+
"list-docs",
|
|
38
|
+
"init",
|
|
39
|
+
"--version",
|
|
40
|
+
"-v",
|
|
41
|
+
"--help",
|
|
42
|
+
"-h",
|
|
43
|
+
];
|
|
44
|
+
const COMMAND_LIKE_RE = /^[a-z]+(-[a-z]+)+$/;
|
|
45
|
+
function looksLikeCommand(arg) {
|
|
46
|
+
if (!arg)
|
|
47
|
+
return false;
|
|
48
|
+
if (arg.startsWith("-") && !arg.startsWith("--"))
|
|
49
|
+
return false;
|
|
50
|
+
if (arg.includes("/") || arg.includes("\\"))
|
|
51
|
+
return false;
|
|
52
|
+
if (arg === "." || arg === "..")
|
|
53
|
+
return false;
|
|
54
|
+
return COMMAND_LIKE_RE.test(arg);
|
|
55
|
+
}
|
|
56
|
+
function existsAsDir(arg) {
|
|
57
|
+
try {
|
|
58
|
+
return existsSync(arg) && statSync(arg).isDirectory();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Levenshtein distance — cheap enough on 20-item allow-list.
|
|
66
|
+
*/
|
|
67
|
+
function distance(a, b) {
|
|
68
|
+
if (a === b)
|
|
69
|
+
return 0;
|
|
70
|
+
const m = a.length;
|
|
71
|
+
const n = b.length;
|
|
72
|
+
if (m === 0)
|
|
73
|
+
return n;
|
|
74
|
+
if (n === 0)
|
|
75
|
+
return m;
|
|
76
|
+
const prev = new Array(n + 1);
|
|
77
|
+
const curr = new Array(n + 1);
|
|
78
|
+
for (let j = 0; j <= n; j++)
|
|
79
|
+
prev[j] = j;
|
|
80
|
+
for (let i = 1; i <= m; i++) {
|
|
81
|
+
curr[0] = i;
|
|
82
|
+
for (let j = 1; j <= n; j++) {
|
|
83
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
84
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
85
|
+
}
|
|
86
|
+
for (let j = 0; j <= n; j++)
|
|
87
|
+
prev[j] = curr[j];
|
|
88
|
+
}
|
|
89
|
+
return prev[n];
|
|
90
|
+
}
|
|
91
|
+
export function checkForTypo(firstArg) {
|
|
92
|
+
if (!firstArg)
|
|
93
|
+
return { kind: "pass-through" };
|
|
94
|
+
if (KNOWN_COMMANDS.includes(firstArg)) {
|
|
95
|
+
return { kind: "pass-through" };
|
|
96
|
+
}
|
|
97
|
+
if (!looksLikeCommand(firstArg))
|
|
98
|
+
return { kind: "pass-through" };
|
|
99
|
+
if (existsAsDir(firstArg))
|
|
100
|
+
return { kind: "pass-through" };
|
|
101
|
+
// Find closest match among command-like known commands.
|
|
102
|
+
let best = "";
|
|
103
|
+
let bestDistance = Infinity;
|
|
104
|
+
for (const cmd of KNOWN_COMMANDS) {
|
|
105
|
+
if (cmd.startsWith("-"))
|
|
106
|
+
continue; // skip flags for suggestion
|
|
107
|
+
const d = distance(firstArg, cmd);
|
|
108
|
+
if (d < bestDistance) {
|
|
109
|
+
bestDistance = d;
|
|
110
|
+
best = cmd;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const suggestion = bestDistance <= 3 ? best : undefined;
|
|
114
|
+
const message = suggestion
|
|
115
|
+
? `Unknown command "${firstArg}". Did you mean "${suggestion}"?`
|
|
116
|
+
: `Unknown command "${firstArg}". Run "token-pilot --help" for the full list.`;
|
|
117
|
+
return { kind: "typo", suggestion, message };
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=typo-guard.js.map
|
package/dist/hooks/installer.js
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TP-q33(a) — PostToolUse:Task budget enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code's `Task` tool is how subagents are dispatched. When one
|
|
5
|
+
* returns, this hook:
|
|
6
|
+
* 1. Identifies the subagent by `tool_input.subagent_type`.
|
|
7
|
+
* 2. Loads its agent markdown (project first, then ~/.claude/agents).
|
|
8
|
+
* 3. Reads the `Response budget: ~N tokens` line from the body.
|
|
9
|
+
* 4. Counts tokens in the `tool_response` body (chars/4 heuristic).
|
|
10
|
+
* 5. If actual > budget × (1 + OVER_BUDGET_TOLERANCE), append a JSONL
|
|
11
|
+
* entry to `.token-pilot/over-budget.log`.
|
|
12
|
+
*
|
|
13
|
+
* Silent on every failure — telemetry must never break the agent loop.
|
|
14
|
+
* Non-tp-* subagents are ignored (we only enforce our own contracts).
|
|
15
|
+
*/
|
|
16
|
+
export declare const OVER_BUDGET_LOG = "over-budget.log";
|
|
17
|
+
/** Ratio above which we flag — 0.1 = 10 % grace. */
|
|
18
|
+
export declare const OVER_BUDGET_TOLERANCE = 0.1;
|
|
19
|
+
export declare function parseAgentBudget(body: string): number | null;
|
|
20
|
+
/**
|
|
21
|
+
* Count approx tokens in the `tool_response.content[*].text` blocks of a
|
|
22
|
+
* PostToolUse hook input for the Task tool. Returns null for anything
|
|
23
|
+
* other than a well-formed Task response.
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractSubagentTokens(input: {
|
|
26
|
+
tool_name?: string;
|
|
27
|
+
tool_response?: unknown;
|
|
28
|
+
}): number | null;
|
|
29
|
+
export interface BudgetDecisionInput {
|
|
30
|
+
agentName: string;
|
|
31
|
+
budget: number | null;
|
|
32
|
+
actualTokens: number;
|
|
33
|
+
}
|
|
34
|
+
export interface BudgetDecisionResult {
|
|
35
|
+
overBudget: boolean;
|
|
36
|
+
overByRatio: number;
|
|
37
|
+
message: string | null;
|
|
38
|
+
}
|
|
39
|
+
export declare function decideBudgetAdvice(input: BudgetDecisionInput): BudgetDecisionResult;
|
|
40
|
+
export interface OverBudgetEntry {
|
|
41
|
+
ts: number;
|
|
42
|
+
agent: string;
|
|
43
|
+
budget: number;
|
|
44
|
+
actualTokens: number;
|
|
45
|
+
overByRatio: number;
|
|
46
|
+
}
|
|
47
|
+
export declare function appendOverBudgetLog(projectRoot: string, entry: OverBudgetEntry): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Locate the markdown body for a `tp-*` subagent — project-level first,
|
|
50
|
+
* then user-level. Returns null when neither exists. Non-tp-* subagents
|
|
51
|
+
* are rejected up front so we never peek outside our namespace.
|
|
52
|
+
*/
|
|
53
|
+
export declare function loadAgentBody(projectRoot: string, homeDir: string, agentName: string): Promise<string | null>;
|
|
54
|
+
export interface PostTaskHookInput {
|
|
55
|
+
tool_name?: string;
|
|
56
|
+
tool_input?: {
|
|
57
|
+
subagent_type?: string;
|
|
58
|
+
};
|
|
59
|
+
tool_response?: unknown;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Full post-Task processing: read frontmatter, count tokens, log over-budget.
|
|
63
|
+
* Returns the advice message (or null) so the caller can optionally emit
|
|
64
|
+
* `additionalContext` — though the primary output channel is the log file.
|
|
65
|
+
*/
|
|
66
|
+
export declare function processPostTask(projectRoot: string, homeDir: string, input: PostTaskHookInput): Promise<string | null>;
|
|
67
|
+
//# sourceMappingURL=post-task.d.ts.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TP-q33(a) — PostToolUse:Task budget enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code's `Task` tool is how subagents are dispatched. When one
|
|
5
|
+
* returns, this hook:
|
|
6
|
+
* 1. Identifies the subagent by `tool_input.subagent_type`.
|
|
7
|
+
* 2. Loads its agent markdown (project first, then ~/.claude/agents).
|
|
8
|
+
* 3. Reads the `Response budget: ~N tokens` line from the body.
|
|
9
|
+
* 4. Counts tokens in the `tool_response` body (chars/4 heuristic).
|
|
10
|
+
* 5. If actual > budget × (1 + OVER_BUDGET_TOLERANCE), append a JSONL
|
|
11
|
+
* entry to `.token-pilot/over-budget.log`.
|
|
12
|
+
*
|
|
13
|
+
* Silent on every failure — telemetry must never break the agent loop.
|
|
14
|
+
* Non-tp-* subagents are ignored (we only enforce our own contracts).
|
|
15
|
+
*/
|
|
16
|
+
import { promises as fs } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
export const OVER_BUDGET_LOG = "over-budget.log";
|
|
19
|
+
/** Ratio above which we flag — 0.1 = 10 % grace. */
|
|
20
|
+
export const OVER_BUDGET_TOLERANCE = 0.1;
|
|
21
|
+
const BUDGET_RE = /Response budget:\s*~?\s*(\d{2,6})\s*tokens?/i;
|
|
22
|
+
export function parseAgentBudget(body) {
|
|
23
|
+
const m = body.match(BUDGET_RE);
|
|
24
|
+
if (!m)
|
|
25
|
+
return null;
|
|
26
|
+
const n = Number.parseInt(m[1], 10);
|
|
27
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Count approx tokens in the `tool_response.content[*].text` blocks of a
|
|
31
|
+
* PostToolUse hook input for the Task tool. Returns null for anything
|
|
32
|
+
* other than a well-formed Task response.
|
|
33
|
+
*/
|
|
34
|
+
export function extractSubagentTokens(input) {
|
|
35
|
+
if (input.tool_name !== "Task")
|
|
36
|
+
return null;
|
|
37
|
+
const resp = input.tool_response;
|
|
38
|
+
if (!resp || typeof resp !== "object")
|
|
39
|
+
return null;
|
|
40
|
+
const parts = Array.isArray(resp.content) ? resp.content : [];
|
|
41
|
+
let chars = 0;
|
|
42
|
+
for (const p of parts) {
|
|
43
|
+
if (typeof p?.text === "string")
|
|
44
|
+
chars += p.text.length;
|
|
45
|
+
}
|
|
46
|
+
if (chars === 0)
|
|
47
|
+
return null;
|
|
48
|
+
return Math.ceil(chars / 4);
|
|
49
|
+
}
|
|
50
|
+
export function decideBudgetAdvice(input) {
|
|
51
|
+
if (input.budget == null || input.budget <= 0) {
|
|
52
|
+
return { overBudget: false, overByRatio: 0, message: null };
|
|
53
|
+
}
|
|
54
|
+
const allowed = input.budget * (1 + OVER_BUDGET_TOLERANCE);
|
|
55
|
+
if (input.actualTokens <= allowed) {
|
|
56
|
+
return {
|
|
57
|
+
overBudget: false,
|
|
58
|
+
overByRatio: input.actualTokens / input.budget - 1,
|
|
59
|
+
message: null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const ratio = input.actualTokens / input.budget - 1;
|
|
63
|
+
const pct = Math.round(ratio * 100);
|
|
64
|
+
return {
|
|
65
|
+
overBudget: true,
|
|
66
|
+
overByRatio: ratio,
|
|
67
|
+
message: `${input.agentName} exceeded budget (~${input.actualTokens} tokens vs budget ${input.budget}, +${pct}%). See .token-pilot/over-budget.log.`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export async function appendOverBudgetLog(projectRoot, entry) {
|
|
71
|
+
try {
|
|
72
|
+
const dir = join(projectRoot, ".token-pilot");
|
|
73
|
+
await fs.mkdir(dir, { recursive: true });
|
|
74
|
+
const line = JSON.stringify(entry) + "\n";
|
|
75
|
+
await fs.appendFile(join(dir, OVER_BUDGET_LOG), line);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
/* silent — logging must never break the hook */
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Locate the markdown body for a `tp-*` subagent — project-level first,
|
|
83
|
+
* then user-level. Returns null when neither exists. Non-tp-* subagents
|
|
84
|
+
* are rejected up front so we never peek outside our namespace.
|
|
85
|
+
*/
|
|
86
|
+
export async function loadAgentBody(projectRoot, homeDir, agentName) {
|
|
87
|
+
if (!agentName.startsWith("tp-"))
|
|
88
|
+
return null;
|
|
89
|
+
const candidates = [
|
|
90
|
+
join(projectRoot, ".claude", "agents", `${agentName}.md`),
|
|
91
|
+
join(homeDir, ".claude", "agents", `${agentName}.md`),
|
|
92
|
+
];
|
|
93
|
+
for (const p of candidates) {
|
|
94
|
+
try {
|
|
95
|
+
return await fs.readFile(p, "utf-8");
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
/* try next */
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Full post-Task processing: read frontmatter, count tokens, log over-budget.
|
|
105
|
+
* Returns the advice message (or null) so the caller can optionally emit
|
|
106
|
+
* `additionalContext` — though the primary output channel is the log file.
|
|
107
|
+
*/
|
|
108
|
+
export async function processPostTask(projectRoot, homeDir, input) {
|
|
109
|
+
if (input.tool_name !== "Task")
|
|
110
|
+
return null;
|
|
111
|
+
const agentName = input.tool_input?.subagent_type;
|
|
112
|
+
if (typeof agentName !== "string" || !agentName.startsWith("tp-")) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const actualTokens = extractSubagentTokens(input);
|
|
116
|
+
if (actualTokens == null)
|
|
117
|
+
return null;
|
|
118
|
+
const body = await loadAgentBody(projectRoot, homeDir, agentName);
|
|
119
|
+
const budget = body ? parseAgentBudget(body) : null;
|
|
120
|
+
const decision = decideBudgetAdvice({
|
|
121
|
+
agentName,
|
|
122
|
+
budget,
|
|
123
|
+
actualTokens,
|
|
124
|
+
});
|
|
125
|
+
if (decision.overBudget && budget != null) {
|
|
126
|
+
await appendOverBudgetLog(projectRoot, {
|
|
127
|
+
ts: Date.now(),
|
|
128
|
+
agent: agentName,
|
|
129
|
+
budget,
|
|
130
|
+
actualTokens,
|
|
131
|
+
overByRatio: decision.overByRatio,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return decision.message;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=post-task.js.map
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,8 @@ import { handleSessionStart } from "./hooks/session-start.js";
|
|
|
18
18
|
import { computeEffectiveThreshold } from "./hooks/adaptive-threshold.js";
|
|
19
19
|
import { loadSessionSavedTokens } from "./core/session-savings.js";
|
|
20
20
|
import { handleSaveDocCli, handleListDocsCli } from "./cli/save-doc.js";
|
|
21
|
+
import { checkForTypo } from "./cli/typo-guard.js";
|
|
22
|
+
import { processPostTask } from "./hooks/post-task.js";
|
|
21
23
|
import { isContextModeInstalledSync } from "./integration/context-mode-detector.js";
|
|
22
24
|
import { handleBlessAgents } from "./cli/bless-agents.js";
|
|
23
25
|
import { unblessAgents } from "./cli/unbless-agents.js";
|
|
@@ -87,6 +89,13 @@ export function getVersion() {
|
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
export async function main(cliArgs = process.argv.slice(2)) {
|
|
92
|
+
// Guard against mis-typed commands like `install-aents` silently
|
|
93
|
+
// becoming a projectRoot=install-aents server launch. See TP-v0.22.3.
|
|
94
|
+
const typo = checkForTypo(cliArgs[0]);
|
|
95
|
+
if (typo.kind === "typo") {
|
|
96
|
+
process.stderr.write(`[token-pilot] ${typo.message}\n`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
90
99
|
switch (cliArgs[0]) {
|
|
91
100
|
case "hook-read": {
|
|
92
101
|
const cfg = await loadConfig(process.cwd());
|
|
@@ -116,6 +125,26 @@ export async function main(cliArgs = process.argv.slice(2)) {
|
|
|
116
125
|
process.exit(0);
|
|
117
126
|
return;
|
|
118
127
|
}
|
|
128
|
+
case "hook-post-task": {
|
|
129
|
+
try {
|
|
130
|
+
const stdin = readFileSync(0, "utf-8");
|
|
131
|
+
const input = JSON.parse(stdin);
|
|
132
|
+
const message = await processPostTask(process.cwd(), homedir(), input);
|
|
133
|
+
if (message) {
|
|
134
|
+
process.stdout.write(JSON.stringify({
|
|
135
|
+
hookSpecificOutput: {
|
|
136
|
+
hookEventName: "PostToolUse",
|
|
137
|
+
additionalContext: `[token-pilot] ${message}`,
|
|
138
|
+
},
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
/* silent — hook must not break */
|
|
144
|
+
}
|
|
145
|
+
process.exit(0);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
119
148
|
case "hook-session-start": {
|
|
120
149
|
const cfg = await loadConfig(process.cwd());
|
|
121
150
|
// `sessionStart.enabled` is independent of `hooks.mode` by design —
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
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",
|