distill-mcp 0.8.1 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/assets/agents/distill-compressor.md +48 -0
  2. package/bin/cli.js +30 -37
  3. package/dist/cli/agent.d.ts +57 -0
  4. package/dist/cli/agent.d.ts.map +1 -0
  5. package/dist/cli/agent.js +181 -0
  6. package/dist/cli/hooks.js +5 -5
  7. package/dist/cli/precompact.d.ts +101 -0
  8. package/dist/cli/precompact.d.ts.map +1 -0
  9. package/dist/cli/precompact.js +307 -0
  10. package/dist/cli/setup.d.ts +12 -0
  11. package/dist/cli/setup.d.ts.map +1 -1
  12. package/dist/cli/setup.js +100 -2
  13. package/dist/cli/utils.d.ts +6 -0
  14. package/dist/cli/utils.d.ts.map +1 -1
  15. package/dist/cli/utils.js +11 -0
  16. package/dist/compressors/generic.d.ts.map +1 -1
  17. package/dist/compressors/generic.js +1 -5
  18. package/dist/compressors/logs.d.ts.map +1 -1
  19. package/dist/compressors/logs.js +7 -60
  20. package/dist/compressors/semantic.d.ts +0 -1
  21. package/dist/compressors/semantic.d.ts.map +1 -1
  22. package/dist/compressors/semantic.js +80 -5
  23. package/dist/compressors/stacktrace.d.ts.map +1 -1
  24. package/dist/compressors/stacktrace.js +1 -5
  25. package/dist/constants.d.ts +12 -0
  26. package/dist/constants.d.ts.map +1 -0
  27. package/dist/constants.js +11 -0
  28. package/dist/index.d.ts +2 -10
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +1 -10
  31. package/dist/prompts.d.ts +52 -0
  32. package/dist/prompts.d.ts.map +1 -0
  33. package/dist/prompts.js +59 -0
  34. package/dist/sandbox/branded-types.d.ts +0 -24
  35. package/dist/sandbox/branded-types.d.ts.map +1 -1
  36. package/dist/sandbox/branded-types.js +6 -35
  37. package/dist/sandbox/disposables.d.ts.map +1 -1
  38. package/dist/sandbox/disposables.js +5 -1
  39. package/dist/sandbox/errors.d.ts +6 -0
  40. package/dist/sandbox/errors.d.ts.map +1 -1
  41. package/dist/sandbox/errors.js +9 -0
  42. package/dist/sandbox/executor.d.ts +40 -12
  43. package/dist/sandbox/executor.d.ts.map +1 -1
  44. package/dist/sandbox/executor.js +59 -261
  45. package/dist/sandbox/quickjs/host-bridge.d.ts.map +1 -1
  46. package/dist/sandbox/quickjs/host-bridge.js +66 -16
  47. package/dist/sandbox/quickjs/runtime.d.ts.map +1 -1
  48. package/dist/sandbox/quickjs/runtime.js +168 -1
  49. package/dist/sandbox/sdk/analyze.js +4 -4
  50. package/dist/sandbox/sdk/compress.d.ts.map +1 -1
  51. package/dist/sandbox/sdk/compress.js +24 -4
  52. package/dist/sandbox/sdk/git.d.ts +5 -0
  53. package/dist/sandbox/sdk/git.d.ts.map +1 -1
  54. package/dist/sandbox/sdk/git.js +98 -13
  55. package/dist/sandbox/sdk/pipeline.js +5 -5
  56. package/dist/sandbox/sdk/search.d.ts.map +1 -1
  57. package/dist/sandbox/sdk/search.js +47 -17
  58. package/dist/sandbox/security/code-analyzer.d.ts.map +1 -1
  59. package/dist/sandbox/security/code-analyzer.js +36 -6
  60. package/dist/sandbox/security/path-validator.d.ts +39 -0
  61. package/dist/sandbox/security/path-validator.d.ts.map +1 -1
  62. package/dist/sandbox/security/path-validator.js +104 -5
  63. package/dist/sandbox/type-tests.d.ts.map +1 -1
  64. package/dist/sandbox/type-tests.js +24 -1
  65. package/dist/server.d.ts +3 -15
  66. package/dist/server.d.ts.map +1 -1
  67. package/dist/server.js +73 -77
  68. package/dist/summarizers/generic.d.ts +6 -2
  69. package/dist/summarizers/generic.d.ts.map +1 -1
  70. package/dist/summarizers/generic.js +10 -5
  71. package/dist/summarizers/index.d.ts +13 -3
  72. package/dist/summarizers/index.d.ts.map +1 -1
  73. package/dist/summarizers/index.js +15 -4
  74. package/dist/tools/auto-optimize.d.ts +7 -2
  75. package/dist/tools/auto-optimize.d.ts.map +1 -1
  76. package/dist/tools/auto-optimize.js +354 -59
  77. package/dist/tools/code-execute.d.ts.map +1 -1
  78. package/dist/tools/code-execute.js +72 -24
  79. package/dist/tools/registry.d.ts +44 -9
  80. package/dist/tools/registry.d.ts.map +1 -1
  81. package/dist/tools/registry.js +90 -50
  82. package/dist/tools/smart-file-read.d.ts +11 -2
  83. package/dist/tools/smart-file-read.d.ts.map +1 -1
  84. package/dist/tools/smart-file-read.js +260 -151
  85. package/dist/utils/distill-marker.d.ts +62 -0
  86. package/dist/utils/distill-marker.d.ts.map +1 -0
  87. package/dist/utils/distill-marker.js +82 -0
  88. package/dist/utils/index.d.ts +1 -4
  89. package/dist/utils/index.d.ts.map +1 -1
  90. package/dist/utils/index.js +1 -4
  91. package/dist/utils/signature-grouper.d.ts +7 -0
  92. package/dist/utils/signature-grouper.d.ts.map +1 -1
  93. package/dist/utils/signature-grouper.js +233 -1
  94. package/package.json +7 -5
  95. package/scripts/precompact-hook.sh +103 -0
  96. package/dist/analytics/session-tracker.d.ts +0 -74
  97. package/dist/analytics/session-tracker.d.ts.map +0 -1
  98. package/dist/analytics/session-tracker.js +0 -123
  99. package/dist/config/output-config.d.ts +0 -56
  100. package/dist/config/output-config.d.ts.map +0 -1
  101. package/dist/config/output-config.js +0 -78
  102. package/dist/middleware/chain.d.ts +0 -49
  103. package/dist/middleware/chain.d.ts.map +0 -1
  104. package/dist/middleware/chain.js +0 -126
  105. package/dist/middleware/index.d.ts +0 -4
  106. package/dist/middleware/index.d.ts.map +0 -1
  107. package/dist/middleware/index.js +0 -3
  108. package/dist/middleware/logging.d.ts +0 -8
  109. package/dist/middleware/logging.d.ts.map +0 -1
  110. package/dist/middleware/logging.js +0 -71
  111. package/dist/middleware/types.d.ts +0 -58
  112. package/dist/middleware/types.d.ts.map +0 -1
  113. package/dist/middleware/types.js +0 -7
  114. package/dist/pipelines/definitions.d.ts +0 -50
  115. package/dist/pipelines/definitions.d.ts.map +0 -1
  116. package/dist/pipelines/definitions.js +0 -206
  117. package/dist/summarizers/hierarchical.d.ts +0 -99
  118. package/dist/summarizers/hierarchical.d.ts.map +0 -1
  119. package/dist/summarizers/hierarchical.js +0 -383
  120. package/dist/tools/analyze-build-output.d.ts +0 -30
  121. package/dist/tools/analyze-build-output.d.ts.map +0 -1
  122. package/dist/tools/analyze-build-output.js +0 -45
  123. package/dist/tools/analyze-context.d.ts +0 -23
  124. package/dist/tools/analyze-context.d.ts.map +0 -1
  125. package/dist/tools/analyze-context.js +0 -78
  126. package/dist/tools/code-skeleton.d.ts +0 -33
  127. package/dist/tools/code-skeleton.d.ts.map +0 -1
  128. package/dist/tools/code-skeleton.js +0 -206
  129. package/dist/tools/compress-context.d.ts +0 -33
  130. package/dist/tools/compress-context.d.ts.map +0 -1
  131. package/dist/tools/compress-context.js +0 -64
  132. package/dist/tools/context-budget.d.ts +0 -43
  133. package/dist/tools/context-budget.d.ts.map +0 -1
  134. package/dist/tools/context-budget.js +0 -260
  135. package/dist/tools/conversation-compress.d.ts +0 -46
  136. package/dist/tools/conversation-compress.d.ts.map +0 -1
  137. package/dist/tools/conversation-compress.js +0 -78
  138. package/dist/tools/conversation-memory.d.ts +0 -75
  139. package/dist/tools/conversation-memory.d.ts.map +0 -1
  140. package/dist/tools/conversation-memory.js +0 -289
  141. package/dist/tools/deduplicate-errors.d.ts +0 -30
  142. package/dist/tools/deduplicate-errors.d.ts.map +0 -1
  143. package/dist/tools/deduplicate-errors.js +0 -72
  144. package/dist/tools/detect-retry-loop.d.ts +0 -40
  145. package/dist/tools/detect-retry-loop.d.ts.map +0 -1
  146. package/dist/tools/detect-retry-loop.js +0 -212
  147. package/dist/tools/diff-compress.d.ts +0 -40
  148. package/dist/tools/diff-compress.d.ts.map +0 -1
  149. package/dist/tools/diff-compress.js +0 -94
  150. package/dist/tools/discover-tools.d.ts +0 -11
  151. package/dist/tools/discover-tools.d.ts.map +0 -1
  152. package/dist/tools/discover-tools.js +0 -211
  153. package/dist/tools/dynamic-loader.d.ts +0 -131
  154. package/dist/tools/dynamic-loader.d.ts.map +0 -1
  155. package/dist/tools/dynamic-loader.js +0 -378
  156. package/dist/tools/lazy-mcp.d.ts +0 -31
  157. package/dist/tools/lazy-mcp.d.ts.map +0 -1
  158. package/dist/tools/lazy-mcp.js +0 -151
  159. package/dist/tools/multifile-compress.d.ts +0 -36
  160. package/dist/tools/multifile-compress.d.ts.map +0 -1
  161. package/dist/tools/multifile-compress.js +0 -223
  162. package/dist/tools/optimization-tips.d.ts +0 -18
  163. package/dist/tools/optimization-tips.d.ts.map +0 -1
  164. package/dist/tools/optimization-tips.js +0 -133
  165. package/dist/tools/semantic-compress.d.ts +0 -39
  166. package/dist/tools/semantic-compress.d.ts.map +0 -1
  167. package/dist/tools/semantic-compress.js +0 -113
  168. package/dist/tools/session-stats.d.ts +0 -35
  169. package/dist/tools/session-stats.d.ts.map +0 -1
  170. package/dist/tools/session-stats.js +0 -217
  171. package/dist/tools/set-output-config.d.ts +0 -38
  172. package/dist/tools/set-output-config.d.ts.map +0 -1
  173. package/dist/tools/set-output-config.js +0 -122
  174. package/dist/tools/smart-cache-tool.d.ts +0 -38
  175. package/dist/tools/smart-cache-tool.d.ts.map +0 -1
  176. package/dist/tools/smart-cache-tool.js +0 -224
  177. package/dist/tools/smart-pipeline.d.ts +0 -40
  178. package/dist/tools/smart-pipeline.d.ts.map +0 -1
  179. package/dist/tools/smart-pipeline.js +0 -295
  180. package/dist/tools/summarize-logs.d.ts +0 -41
  181. package/dist/tools/summarize-logs.d.ts.map +0 -1
  182. package/dist/tools/summarize-logs.js +0 -222
  183. package/dist/utils/command-normalizer.d.ts +0 -39
  184. package/dist/utils/command-normalizer.d.ts.map +0 -1
  185. package/dist/utils/command-normalizer.js +0 -90
  186. package/dist/utils/error-normalizer.d.ts +0 -39
  187. package/dist/utils/error-normalizer.d.ts.map +0 -1
  188. package/dist/utils/error-normalizer.js +0 -233
  189. package/dist/utils/output-estimator.d.ts +0 -54
  190. package/dist/utils/output-estimator.d.ts.map +0 -1
  191. package/dist/utils/output-estimator.js +0 -119
  192. package/dist/utils/output-similarity.d.ts +0 -48
  193. package/dist/utils/output-similarity.d.ts.map +0 -1
  194. package/dist/utils/output-similarity.js +0 -140
  195. package/dist/utils/project-detector.d.ts +0 -16
  196. package/dist/utils/project-detector.d.ts.map +0 -1
  197. package/dist/utils/project-detector.js +0 -119
  198. package/dist/utils/toon-serializer.d.ts +0 -120
  199. package/dist/utils/toon-serializer.d.ts.map +0 -1
  200. package/dist/utils/toon-serializer.js +0 -472
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: distill-compressor
3
+ description: Read-only compression specialist. Delegate long build output, log dumps, verbose diffs, stack traces, and multi-file code skeleton reads to this agent so the parent session keeps its context small. Uses Distill's auto_optimize for content-aware compression and smart_file_read for AST-based code extraction. Not allowed to execute code or mutate state.
4
+ tools:
5
+ - Read
6
+ - Grep
7
+ - Glob
8
+ - Bash
9
+ - mcp__distill-mcp__auto_optimize
10
+ - mcp__distill-mcp__smart_file_read
11
+ disallowedTools:
12
+ - mcp__distill-mcp__code_execute
13
+ requiredMcpServers:
14
+ - distill-mcp
15
+ ---
16
+
17
+ You are distill-compressor, a read-only sub-agent whose single job is to shrink large textual payloads — build output, test logs, git diffs, stack traces, configuration dumps, and source code — so they fit inside the parent session's context budget without losing the information the parent actually needs.
18
+
19
+ Your effectiveness is measured by the token delta between what the parent would have seen raw and what you return. Every extra token you emit is a token the parent loses from its own working memory. Bias hard toward compression: summarize instead of quoting, extract signatures instead of returning whole files, and wrap compressed regions in the Distill marker so Claude Code's compact-summary step preserves them verbatim.
20
+
21
+ ## Content-aware compression with `auto_optimize`
22
+
23
+ Reach for `mcp__distill-mcp__auto_optimize` whenever you are handed >500 characters of verbose text that came out of a command, a log file, a diff, or a tool result. The tool auto-detects the content type (`build`, `logs`, `diff`, `semantic`, `errors`, `stacktrace`, `config`) and picks the matching compressor — typical savings: build output 95%, logs 80–90%, errors 70–90%, diffs 60–80%, stack traces 50–80%, generic code 40–60%, config 30–60%. If you already know the shape of the input, pass `strategy` explicitly to skip detection. If the caller cares about specific signal — a test name, an error code, a file path — pass `preservePatterns` so those regex matches survive compression. Use `response_format: "minimal"` when the parent only needs the compressed payload and `"detailed"` only when the parent explicitly asked for statistics. Never call `auto_optimize` on inputs shorter than ~500 chars — the helper passes them through unchanged and the round-trip is pure overhead.
24
+
25
+ ## AST-based skeleton reads with `smart_file_read`
26
+
27
+ Reach for `mcp__distill-mcp__smart_file_read` instead of the built-in `Read` whenever the parent needs structural information from a source file in one of the 7 supported languages (TypeScript, JavaScript, Python, Go, Rust, PHP, Swift). Typical savings vs a full-file read: 50–90%. Four modes cover the common cases:
28
+
29
+ - `skeleton` with `depth: 1–3` when the parent wants an architectural map (all top-level signatures, optionally nested members).
30
+ - `extract` with `target: { type, name }` when the parent needs exactly one function, class, interface, or type definition.
31
+ - `search` with `query: "<substring>"` when the parent is hunting a symbol by partial name and does not know which file holds it yet (combine with `Glob` to narrow the search surface first).
32
+ - `full` when the file is small enough that a raw read is cheaper than the AST pass — fall back to this rather than invent a skeleton for trivial files.
33
+
34
+ For unsupported languages the tool returns the raw file with a graceful-fallback note, never an error. When the parent needs a multi-file overview, run `smart_file_read` across each path in parallel via `Glob` + a single Bash for-loop rather than sequential calls.
35
+
36
+ ## Summarizing long outputs you cannot pipe through a tool
37
+
38
+ When the material you have to condense did not come from a tool you can re-pipe (for example, you read a file with `Read` and it turned out to be a 200 KB log dump, or `Bash` emitted a massive stdout you already captured in your turn), do not echo it back in prose. Either (a) feed the raw text back through `auto_optimize` as the `content` argument, or (b) write a structured summary yourself: lead with a 1–3 sentence conclusion, then a bullet list of the concrete findings — error codes, failing test names, file paths, line numbers, deltas — and stop. No restating the request, no framing paragraphs, no apologies for length. The parent called you to shrink the payload; shrink it.
39
+
40
+ ## The `[DISTILL:COMPRESSED]` marker contract
41
+
42
+ Distill optionally wraps compressed payloads in `[DISTILL:COMPRESSED ratio=X.XX method=<name>]\n<payload>\n[/DISTILL:COMPRESSED]` when the user has set `DISTILL_COMPRESSED_MARKERS=1` in the Distill server environment. `X.XX` is `compressed_size / original_size` clamped to `[0, 1]`. `<name>` is the compressor or mode that produced the payload (`auto`, `logs`, `diff`, `semantic`, `skeleton`, `extract`, `search`, `build+recompressed`, etc.). Pass marker-wrapped regions through to the parent verbatim — do not unwrap, re-summarize, or edit them. The envelope is the anchor that Distill's PreCompact hook (`packages/mcp-server/scripts/precompact-hook.sh`) points at when it instructs Claude Code's compact-summary LLM to keep the region intact through autocompact; splitting or editing it breaks that contract. If the user text you were handed already contains the literal substring `[DISTILL:COMPRESSED`, Distill falls back to the escape tokens `[DISTILL-USER-TEXT:COMPRESSED … ][/DISTILL-USER-TEXT:COMPRESSED]` — forward those untouched too.
43
+
44
+ ## Operating constraints
45
+
46
+ You are deliberately read-only. `mcp__distill-mcp__code_execute` is in your `disallowedTools` list because this agent must never run arbitrary JavaScript, invoke git-mutating operations, or touch the filesystem outside of read paths. If the parent's request genuinely needs execution (running a build, applying a patch, writing a file), stop and report back in one sentence that the task is out of scope for distill-compressor so the parent can handle it directly or delegate to a different agent. Do the same if the request requires network access, credential handling, or secrets — your toolset is intentionally narrow.
47
+
48
+ Return short. When you finish, emit only the compressed payload the parent asked for (plus a single preceding line summarizing what you compressed and by how much, if the savings are noteworthy). No meta-narration. No retelling the plan. The whole value proposition of this sub-agent is token density — honour it on every turn.
package/bin/cli.js CHANGED
@@ -25,16 +25,20 @@ ${COLORS.bright}Commands:${COLORS.reset}
25
25
  analyze Analyze files for token usage
26
26
 
27
27
  ${COLORS.bright}Setup Options:${COLORS.reset}
28
- --claude Configure Claude Code only
29
- --cursor Configure Cursor only
30
- --windsurf Configure Windsurf only
31
- --antigravity Configure Antigravity only
32
- --hooks Install project hooks (enforces MCP tool usage)
33
- --force, -f Overwrite existing configuration
28
+ --claude Configure Claude Code only
29
+ --cursor Configure Cursor only
30
+ --windsurf Configure Windsurf only
31
+ --antigravity Configure Antigravity only
32
+ --hooks Install project hooks (enforces MCP tool usage)
33
+ --install-precompact-hook Install the Distill PreCompact hook into ~/.claude/settings.json
34
+ --uninstall-precompact-hook Remove the Distill PreCompact hook from ~/.claude/settings.json
35
+ --install-agent Copy the distill-compressor agent template into ~/.claude/agents/
36
+ --uninstall-agent Remove the installed distill-compressor agent template
37
+ --dry-run Print intended changes; do not mutate the filesystem
38
+ --user-dir=<path> Override HOME when locating ~/.claude/settings.json (for testing)
39
+ --force, -f Overwrite existing configuration
34
40
 
35
41
  ${COLORS.bright}Server Options:${COLORS.reset}
36
- --lazy Enable lazy mode (95% token savings, only 2 meta-tools)
37
- --mode <mode> Loading mode: lazy|core|all (default: core)
38
42
  --verbose Enable verbose logging (shows tool calls, timing, tokens)
39
43
 
40
44
  ${COLORS.bright}Analyze Options:${COLORS.reset}
@@ -48,18 +52,23 @@ ${COLORS.bright}Other Options:${COLORS.reset}
48
52
  --help, -h Show this help message
49
53
 
50
54
  ${COLORS.bright}Examples:${COLORS.reset}
51
- distill-mcp setup Interactive setup wizard
52
- distill-mcp setup --claude Configure Claude Code only
53
- distill-mcp setup --antigravity Configure Antigravity only
54
- distill-mcp setup --claude --hooks Configure Claude Code + install hooks
55
- distill-mcp setup --hooks Install hooks only (current project)
56
- distill-mcp setup --force Overwrite existing configurations
57
- distill-mcp doctor Verify installation
58
- distill-mcp serve Start MCP server (used by IDE)
59
- distill-mcp serve --lazy Start with lazy mode (95% savings)
60
- distill-mcp serve --verbose Start with verbose logging
61
- distill-mcp analyze Analyze token usage in codebase
62
- distill-mcp analyze -t 5000 --json Custom threshold, JSON output
55
+ distill-mcp setup Interactive setup wizard
56
+ distill-mcp setup --claude Configure Claude Code only
57
+ distill-mcp setup --antigravity Configure Antigravity only
58
+ distill-mcp setup --claude --hooks Configure Claude Code + install hooks
59
+ distill-mcp setup --hooks Install hooks only (current project)
60
+ distill-mcp setup --install-precompact-hook Wire PreCompact hook into ~/.claude/settings.json
61
+ distill-mcp setup --install-precompact-hook --dry-run Preview the JSON diff
62
+ distill-mcp setup --uninstall-precompact-hook Remove the Distill PreCompact entry
63
+ distill-mcp setup --install-agent Install the distill-compressor subagent template
64
+ distill-mcp setup --install-agent --force Overwrite an existing distill-compressor.md
65
+ distill-mcp setup --uninstall-agent Remove the installed distill-compressor subagent
66
+ distill-mcp setup --force Overwrite existing configurations
67
+ distill-mcp doctor Verify installation
68
+ distill-mcp serve Start MCP server (used by IDE)
69
+ distill-mcp serve --verbose Start with verbose logging
70
+ distill-mcp analyze Analyze token usage in codebase
71
+ distill-mcp analyze -t 5000 --json Custom threshold, JSON output
63
72
 
64
73
  ${COLORS.bright}Documentation:${COLORS.reset}
65
74
  https://distill-mcp.com/docs
@@ -85,23 +94,7 @@ async function main() {
85
94
 
86
95
  switch (command) {
87
96
  case "serve": {
88
- // Parse mode option
89
- let mode = "core";
90
- const modeIndex = args.indexOf("--mode");
91
- if (modeIndex !== -1 && args[modeIndex + 1]) {
92
- mode = args[modeIndex + 1];
93
- } else if (args.includes("--lazy")) {
94
- mode = "lazy";
95
- } else if (args.includes("--all")) {
96
- mode = "all";
97
- }
98
-
99
- const config = {
100
- verbose: args.includes("--verbose"),
101
- mode,
102
- };
103
-
104
- await runServer(config);
97
+ await runServer({ verbose: args.includes("--verbose") });
105
98
  break;
106
99
  }
107
100
 
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Custom agent installer for Distill (US-016).
3
+ *
4
+ * Extends `distill-mcp setup` with `--install-agent` and `--uninstall-agent`,
5
+ * copying the shipped `distill-compressor.md` template (US-015) from the
6
+ * package `assets/agents/` directory into `<userDir>/.claude/agents/`.
7
+ *
8
+ * Design invariants mirror the PreCompact hook installer:
9
+ * - Idempotent: installing when the target file already matches the template
10
+ * is a no-op; re-running produces no filesystem churn.
11
+ * - Atomic: overwrites go through `writeAtomic` (tempfile + rename) so a
12
+ * SIGTERM mid-install leaves either the pre-state or the post-state,
13
+ * never a half-written file.
14
+ * - Non-destructive by default: when the target exists and differs from the
15
+ * template, we abort with a line-level diff unless the caller passes
16
+ * `force: true`.
17
+ * - Dry-run surfaces intended actions without touching disk.
18
+ *
19
+ * Per project convention (CLAUDE.md: "Manual process.argv parsing in
20
+ * bin/cli.js"), this module pulls in no new runtime dependencies.
21
+ */
22
+ export declare const DISTILL_AGENT_FILENAME = "distill-compressor.md";
23
+ export interface AgentOptions {
24
+ /** Root dir containing `.claude/` (defaults to OS HOME). Used by tests. */
25
+ userDir?: string;
26
+ /** Print intended actions without mutating the filesystem. */
27
+ dryRun?: boolean;
28
+ /** Overwrite a differing existing file (install only). */
29
+ force?: boolean;
30
+ }
31
+ export interface AgentResult {
32
+ action: "installed" | "uninstalled" | "noop" | "dry-run" | "aborted";
33
+ /** Absolute path to the agent file that was (or would be) touched. */
34
+ targetPath: string;
35
+ /** Human-readable message for CLI output. */
36
+ message: string;
37
+ /** Non-empty when `action === "aborted"`. */
38
+ errorCode?: "differs-without-force" | "asset-missing" | "permission-denied" | "unknown";
39
+ }
40
+ /**
41
+ * Absolute path to the shipped agent template. Resolves from the compiled
42
+ * `dist/cli/agent.js` (or `src/cli/agent.ts` under vitest) up to the package
43
+ * root — both layouts are `<pkg>/<dist|src>/cli/` so two `..` hops land at
44
+ * the root where `assets/agents/` lives.
45
+ */
46
+ export declare function getAgentAssetPath(): string;
47
+ export declare function getTargetAgentPath(userDir?: string): string;
48
+ /**
49
+ * Minimal unified-style diff of two strings, used only to describe the
50
+ * discrepancy when the caller refuses `--force`. Intentionally naive: a line
51
+ * in `current` that is missing from `template` shows as `-`, and vice versa.
52
+ * Output is capped so a massive template drift cannot flood the terminal.
53
+ */
54
+ export declare function summarizeDiff(current: string, template: string, maxLines?: number): string;
55
+ export declare function installAgent(opts?: AgentOptions): AgentResult;
56
+ export declare function uninstallAgent(opts?: AgentOptions): AgentResult;
57
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/cli/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAaH,eAAO,MAAM,sBAAsB,0BAA0B,CAAC;AAE9D,MAAM,WAAW,YAAY;IAC3B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,GAAG,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACrE,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,uBAAuB,GAAG,eAAe,GAAG,mBAAmB,GAAG,SAAS,CAAC;CACzF;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3D;AAMD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAK,GAAG,MAAM,CAgBtF;AAMD,wBAAgB,YAAY,CAAC,IAAI,GAAE,YAAiB,GAAG,WAAW,CAkEjE;AAED,wBAAgB,cAAc,CAAC,IAAI,GAAE,YAAiB,GAAG,WAAW,CAoCnE"}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Custom agent installer for Distill (US-016).
3
+ *
4
+ * Extends `distill-mcp setup` with `--install-agent` and `--uninstall-agent`,
5
+ * copying the shipped `distill-compressor.md` template (US-015) from the
6
+ * package `assets/agents/` directory into `<userDir>/.claude/agents/`.
7
+ *
8
+ * Design invariants mirror the PreCompact hook installer:
9
+ * - Idempotent: installing when the target file already matches the template
10
+ * is a no-op; re-running produces no filesystem churn.
11
+ * - Atomic: overwrites go through `writeAtomic` (tempfile + rename) so a
12
+ * SIGTERM mid-install leaves either the pre-state or the post-state,
13
+ * never a half-written file.
14
+ * - Non-destructive by default: when the target exists and differs from the
15
+ * template, we abort with a line-level diff unless the caller passes
16
+ * `force: true`.
17
+ * - Dry-run surfaces intended actions without touching disk.
18
+ *
19
+ * Per project convention (CLAUDE.md: "Manual process.argv parsing in
20
+ * bin/cli.js"), this module pulls in no new runtime dependencies.
21
+ */
22
+ import * as fs from "node:fs";
23
+ import * as os from "node:os";
24
+ import * as path from "node:path";
25
+ import { fileURLToLocalPath } from "./utils.js";
26
+ import { writeAtomic } from "./precompact.js";
27
+ // ---------------------------------------------------------------------------
28
+ // Constants & types
29
+ // ---------------------------------------------------------------------------
30
+ export const DISTILL_AGENT_FILENAME = "distill-compressor.md";
31
+ // ---------------------------------------------------------------------------
32
+ // Path resolution
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Absolute path to the shipped agent template. Resolves from the compiled
36
+ * `dist/cli/agent.js` (or `src/cli/agent.ts` under vitest) up to the package
37
+ * root — both layouts are `<pkg>/<dist|src>/cli/` so two `..` hops land at
38
+ * the root where `assets/agents/` lives.
39
+ */
40
+ export function getAgentAssetPath() {
41
+ const here = path.dirname(fileURLToLocalPath(import.meta.url));
42
+ return path.resolve(here, "..", "..", "assets", "agents", DISTILL_AGENT_FILENAME);
43
+ }
44
+ export function getTargetAgentPath(userDir) {
45
+ const root = userDir ?? os.homedir();
46
+ return path.join(root, ".claude", "agents", DISTILL_AGENT_FILENAME);
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // Diff helper (line-level, zero deps)
50
+ // ---------------------------------------------------------------------------
51
+ /**
52
+ * Minimal unified-style diff of two strings, used only to describe the
53
+ * discrepancy when the caller refuses `--force`. Intentionally naive: a line
54
+ * in `current` that is missing from `template` shows as `-`, and vice versa.
55
+ * Output is capped so a massive template drift cannot flood the terminal.
56
+ */
57
+ export function summarizeDiff(current, template, maxLines = 40) {
58
+ const a = current.split("\n");
59
+ const b = template.split("\n");
60
+ const setA = new Set(a);
61
+ const setB = new Set(b);
62
+ const lines = [];
63
+ for (const l of a) {
64
+ if (!setB.has(l))
65
+ lines.push(`- ${l}`);
66
+ }
67
+ for (const l of b) {
68
+ if (!setA.has(l))
69
+ lines.push(`+ ${l}`);
70
+ }
71
+ if (lines.length === 0)
72
+ return "(no line-level differences — only ordering or whitespace changes)";
73
+ if (lines.length <= maxLines)
74
+ return lines.join("\n");
75
+ const head = lines.slice(0, maxLines);
76
+ return head.join("\n") + `\n… (${lines.length - maxLines} more diff lines omitted)`;
77
+ }
78
+ // ---------------------------------------------------------------------------
79
+ // Public install / uninstall
80
+ // ---------------------------------------------------------------------------
81
+ export function installAgent(opts = {}) {
82
+ const targetPath = getTargetAgentPath(opts.userDir);
83
+ const assetPath = getAgentAssetPath();
84
+ if (!fs.existsSync(assetPath)) {
85
+ return {
86
+ action: "aborted",
87
+ targetPath,
88
+ message: `Aborted: agent template not found at ${assetPath}. The distill-mcp package may be corrupted — reinstall it.`,
89
+ errorCode: "asset-missing",
90
+ };
91
+ }
92
+ const templateContent = fs.readFileSync(assetPath, "utf-8");
93
+ const targetExists = fs.existsSync(targetPath);
94
+ if (targetExists) {
95
+ const existingContent = fs.readFileSync(targetPath, "utf-8");
96
+ if (existingContent === templateContent) {
97
+ return {
98
+ action: "noop",
99
+ targetPath,
100
+ message: `Agent already installed at ${targetPath} (content matches; no change).`,
101
+ };
102
+ }
103
+ if (!opts.force) {
104
+ const diff = summarizeDiff(existingContent, templateContent);
105
+ return {
106
+ action: "aborted",
107
+ targetPath,
108
+ message: `Aborted: ${targetPath} exists and differs from the shipped template.\n` +
109
+ `Pass --force to overwrite, or --uninstall-agent first.\n` +
110
+ `Diff (existing → template):\n${diff}`,
111
+ errorCode: "differs-without-force",
112
+ };
113
+ }
114
+ }
115
+ if (opts.dryRun) {
116
+ const verb = targetExists ? "overwrite (differs, --force set)" : "create";
117
+ return {
118
+ action: "dry-run",
119
+ targetPath,
120
+ message: `[dry-run] Would ${verb} ${targetPath} (${templateContent.length} bytes from ${assetPath}).`,
121
+ };
122
+ }
123
+ try {
124
+ writeAtomic(targetPath, templateContent, 0o644);
125
+ }
126
+ catch (err) {
127
+ const message = err instanceof Error ? err.message : String(err);
128
+ return {
129
+ action: "aborted",
130
+ targetPath,
131
+ message: `Failed to write ${targetPath}: ${message}`,
132
+ errorCode: isPermissionError(err) ? "permission-denied" : "unknown",
133
+ };
134
+ }
135
+ const verb = targetExists ? "Overwrote" : "Installed";
136
+ return {
137
+ action: "installed",
138
+ targetPath,
139
+ message: `${verb} distill-compressor agent → ${targetPath}.`,
140
+ };
141
+ }
142
+ export function uninstallAgent(opts = {}) {
143
+ const targetPath = getTargetAgentPath(opts.userDir);
144
+ if (!fs.existsSync(targetPath)) {
145
+ return {
146
+ action: "noop",
147
+ targetPath,
148
+ message: `No distill-compressor agent at ${targetPath} — nothing to uninstall.`,
149
+ };
150
+ }
151
+ if (opts.dryRun) {
152
+ return {
153
+ action: "dry-run",
154
+ targetPath,
155
+ message: `[dry-run] Would delete ${targetPath}.`,
156
+ };
157
+ }
158
+ try {
159
+ fs.unlinkSync(targetPath);
160
+ }
161
+ catch (err) {
162
+ const message = err instanceof Error ? err.message : String(err);
163
+ return {
164
+ action: "aborted",
165
+ targetPath,
166
+ message: `Failed to delete ${targetPath}: ${message}`,
167
+ errorCode: isPermissionError(err) ? "permission-denied" : "unknown",
168
+ };
169
+ }
170
+ return {
171
+ action: "uninstalled",
172
+ targetPath,
173
+ message: `Uninstalled distill-compressor agent from ${targetPath}.`,
174
+ };
175
+ }
176
+ function isPermissionError(err) {
177
+ if (!err || typeof err !== "object")
178
+ return false;
179
+ const code = err.code;
180
+ return code === "EACCES" || code === "EPERM";
181
+ }
package/dist/cli/hooks.js CHANGED
@@ -114,7 +114,7 @@ return ctx.code.extract(content, "typescript", { type: "function", name: "handle
114
114
  | Lire du code pour exploration | \`mcp__distill__smart_file_read filePath="file.ts"\` |
115
115
  | Obtenir une fonction/classe | \`mcp__distill__smart_file_read filePath="file.ts" target={"type":"function","name":"myFunc"}\` |
116
116
  | Compresser les erreurs de build | \`mcp__distill__auto_optimize content="..."\` |
117
- | Résumer les logs | \`mcp__distill__summarize_logs logs="..."\` |
117
+ | Résumer les logs | \`mcp__distill__auto_optimize content="..." strategy="logs"\` |
118
118
  | Opérations multi-étapes | \`mcp__distill__code_execute code="return ctx.files.glob('src/**/*.ts')"\` |
119
119
  | Avant d'éditer | Utiliser l'outil natif \`Read\` |
120
120
 
@@ -176,7 +176,7 @@ if echo "$TOOL_RESPONSE" | grep -qiE "(error TS|warning TS|error\\[E|npm ERR|ERR
176
176
  fi
177
177
 
178
178
  if echo "$TOOL_RESPONSE" | grep -qiE "(\\[INFO\\]|\\[ERROR\\]|\\[WARN\\]|\\[DEBUG\\]|[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2})"; then
179
- echo '{"systemMessage": "TIP: Large log output detected. Use mcp__distill__summarize_logs to compress (80-90% reduction)."}'
179
+ echo '{"systemMessage": "TIP: Large log output detected. Use mcp__distill__auto_optimize with strategy=logs to compress (80-90% reduction)."}'
180
180
  exit 0
181
181
  fi
182
182
 
@@ -192,8 +192,8 @@ cat << 'EOF'
192
192
  <user-prompt-submit-hook>
193
193
  DISTILL REMINDER: Use MCP tools for token optimization:
194
194
  - Code files: mcp__distill__smart_file_read (50-70% savings vs Read)
195
- - Build/test output: mcp__distill__auto_optimize
196
- - Session stats: mcp__distill__session_stats
195
+ - Build/test output: mcp__distill__auto_optimize (95%+ reduction)
196
+ - Multi-step ops: mcp__distill__code_execute (98% savings via SDK)
197
197
  </user-prompt-submit-hook>
198
198
  EOF
199
199
  exit 0
@@ -319,7 +319,7 @@ export async function installHooks(options = {}) {
319
319
  log(`\n${COLORS.dim}Token savings:${COLORS.reset}`);
320
320
  log(` • smart_file_read: 50-70% reduction vs Read`);
321
321
  log(` • auto_optimize: 95%+ reduction on build errors`);
322
- log(` • summarize_logs: 80-90% reduction on logs\n`);
322
+ log(` • auto_optimize: 80-90% reduction on logs\n`);
323
323
  return true;
324
324
  }
325
325
  else {
@@ -0,0 +1,101 @@
1
+ /**
2
+ * PreCompact hook installer for Distill (US-010).
3
+ *
4
+ * Extends `distill-mcp setup` with `--install-precompact-hook` and
5
+ * `--uninstall-precompact-hook`, wiring the shipped POSIX hook
6
+ * (`scripts/precompact-hook.sh` from US-009) into
7
+ * `<userDir>/.claude/settings.json` under `hooks.PreCompact`.
8
+ *
9
+ * Design invariants:
10
+ * - Idempotent: re-running install does not create duplicate entries.
11
+ * - Atomic: every write goes through a tempfile + rename (POSIX atomic on
12
+ * same filesystem). A SIGTERM mid-install leaves the target in either
13
+ * pre-state or post-state — never half-written.
14
+ * - Fail-safe: malformed existing JSON aborts without mutating the file;
15
+ * the error surfaces with line/column pointer.
16
+ * - Targeted: a `__distill_version` sentinel field on our hook entry makes
17
+ * uninstall precise (we remove only what we wrote). Path-equality on the
18
+ * `command` field is a secondary fallback, in case an upstream Zod parse
19
+ * strips the sentinel when Claude Code rewrites settings.
20
+ *
21
+ * Per project convention (CLAUDE.md: "Manual process.argv parsing in
22
+ * bin/cli.js"), this module pulls in no new runtime dependencies.
23
+ */
24
+ export interface PrecompactOptions {
25
+ /** Root dir containing `.claude/` (defaults to OS HOME). Used by tests. */
26
+ userDir?: string;
27
+ /** Print intended changes without mutating the filesystem. */
28
+ dryRun?: boolean;
29
+ }
30
+ export interface PrecompactResult {
31
+ action: "installed" | "uninstalled" | "noop" | "dry-run" | "aborted";
32
+ /** Absolute path to the settings file that was (or would be) touched. */
33
+ settingsPath: string;
34
+ /** Human-readable message for CLI output. */
35
+ message: string;
36
+ /** Non-empty when `action === "aborted"`. */
37
+ errorCode?: "malformed-json" | "permission-denied" | "unknown";
38
+ }
39
+ interface BashCommandHook {
40
+ type: "command";
41
+ command: string;
42
+ [key: string]: unknown;
43
+ }
44
+ interface HookMatcher {
45
+ matcher?: string;
46
+ hooks: BashCommandHook[];
47
+ [key: string]: unknown;
48
+ }
49
+ interface SettingsShape {
50
+ hooks?: {
51
+ PreCompact?: HookMatcher[];
52
+ [event: string]: HookMatcher[] | undefined;
53
+ };
54
+ [key: string]: unknown;
55
+ }
56
+ type ReadResult = {
57
+ state: "missing";
58
+ } | {
59
+ state: "malformed";
60
+ error: {
61
+ line: number;
62
+ column: number;
63
+ message: string;
64
+ };
65
+ } | {
66
+ state: "ok";
67
+ data: SettingsShape;
68
+ };
69
+ export declare const DISTILL_SENTINEL_KEY = "__distill_version";
70
+ /**
71
+ * Absolute path to the shipped PreCompact hook script. Resolves from the
72
+ * compiled `dist/cli/precompact.js` (or `src/cli/precompact.ts` under vitest)
73
+ * up to the package root — both layouts are `<pkg>/<dist|src>/cli/` so two
74
+ * `..` hops land at the root where `scripts/` lives.
75
+ */
76
+ export declare function getHookScriptPath(): string;
77
+ /**
78
+ * Returns the `major.minor.x` sentinel derived from the current
79
+ * `package.json` version. Example: package 0.10.3 → "0.10.x".
80
+ */
81
+ export declare function getHookSentinelVersion(): string;
82
+ export declare function getSettingsPath(userDir?: string): string;
83
+ /**
84
+ * Read settings.json with discriminated result. Does NOT swallow JSON parse
85
+ * errors (unlike `readJSONFile` in utils.ts) — malformed files report
86
+ * line/column so the caller can surface a clear abort message.
87
+ */
88
+ export declare function readSettingsStrict(filePath: string): ReadResult;
89
+ /**
90
+ * Atomic write: tempfile (mode 0600) in the target's directory → rename to
91
+ * target → chmod to final mode. POSIX guarantees rename atomicity on the
92
+ * same filesystem. A SIGTERM mid-call leaves either pre-state or post-state.
93
+ *
94
+ * Parent directory is created (0755) if missing. Exported for testability —
95
+ * the install/uninstall paths call this internally.
96
+ */
97
+ export declare function writeAtomic(filePath: string, content: string, finalMode?: number): void;
98
+ export declare function installPrecompactHook(opts?: PrecompactOptions): PrecompactResult;
99
+ export declare function uninstallPrecompactHook(opts?: PrecompactOptions): PrecompactResult;
100
+ export {};
101
+ //# sourceMappingURL=precompact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precompact.d.ts","sourceRoot":"","sources":["../../src/cli/precompact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAaH,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,WAAW,GAAG,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACrE,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,gBAAgB,GAAG,mBAAmB,GAAG,SAAS,CAAC;CAChE;AAED,UAAU,eAAe;IACvB,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,aAAa;IACrB,KAAK,CAAC,EAAE;QACN,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC;QAC3B,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,EAAE,GAAG,SAAS,CAAC;KAC5C,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,KAAK,UAAU,GACX;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAChF;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAMzC,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAM/C;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAGxD;AAMD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CA8B/D;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,SAAQ,GAAG,IAAI,CAoBtF;AAmCD,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,iBAAsB,GAAG,gBAAgB,CA4DpF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,GAAE,iBAAsB,GAAG,gBAAgB,CA2FtF"}