distill-mcp 0.8.0 → 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 (205) 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.d.ts +9 -0
  7. package/dist/cli/hooks.d.ts.map +1 -1
  8. package/dist/cli/hooks.js +176 -5
  9. package/dist/cli/index.d.ts +1 -1
  10. package/dist/cli/index.d.ts.map +1 -1
  11. package/dist/cli/index.js +1 -1
  12. package/dist/cli/precompact.d.ts +101 -0
  13. package/dist/cli/precompact.d.ts.map +1 -0
  14. package/dist/cli/precompact.js +307 -0
  15. package/dist/cli/setup.d.ts +12 -0
  16. package/dist/cli/setup.d.ts.map +1 -1
  17. package/dist/cli/setup.js +120 -9
  18. package/dist/cli/utils.d.ts +7 -1
  19. package/dist/cli/utils.d.ts.map +1 -1
  20. package/dist/cli/utils.js +23 -4
  21. package/dist/compressors/generic.d.ts.map +1 -1
  22. package/dist/compressors/generic.js +1 -5
  23. package/dist/compressors/logs.d.ts.map +1 -1
  24. package/dist/compressors/logs.js +7 -60
  25. package/dist/compressors/semantic.d.ts +0 -1
  26. package/dist/compressors/semantic.d.ts.map +1 -1
  27. package/dist/compressors/semantic.js +80 -5
  28. package/dist/compressors/stacktrace.d.ts.map +1 -1
  29. package/dist/compressors/stacktrace.js +1 -5
  30. package/dist/constants.d.ts +12 -0
  31. package/dist/constants.d.ts.map +1 -0
  32. package/dist/constants.js +11 -0
  33. package/dist/index.d.ts +2 -10
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -10
  36. package/dist/prompts.d.ts +52 -0
  37. package/dist/prompts.d.ts.map +1 -0
  38. package/dist/prompts.js +59 -0
  39. package/dist/sandbox/branded-types.d.ts +0 -24
  40. package/dist/sandbox/branded-types.d.ts.map +1 -1
  41. package/dist/sandbox/branded-types.js +6 -35
  42. package/dist/sandbox/disposables.d.ts.map +1 -1
  43. package/dist/sandbox/disposables.js +5 -1
  44. package/dist/sandbox/errors.d.ts +6 -0
  45. package/dist/sandbox/errors.d.ts.map +1 -1
  46. package/dist/sandbox/errors.js +9 -0
  47. package/dist/sandbox/executor.d.ts +40 -12
  48. package/dist/sandbox/executor.d.ts.map +1 -1
  49. package/dist/sandbox/executor.js +59 -261
  50. package/dist/sandbox/quickjs/host-bridge.d.ts.map +1 -1
  51. package/dist/sandbox/quickjs/host-bridge.js +66 -16
  52. package/dist/sandbox/quickjs/runtime.d.ts.map +1 -1
  53. package/dist/sandbox/quickjs/runtime.js +168 -1
  54. package/dist/sandbox/sdk/analyze.js +4 -4
  55. package/dist/sandbox/sdk/compress.d.ts.map +1 -1
  56. package/dist/sandbox/sdk/compress.js +24 -4
  57. package/dist/sandbox/sdk/git.d.ts +5 -0
  58. package/dist/sandbox/sdk/git.d.ts.map +1 -1
  59. package/dist/sandbox/sdk/git.js +98 -13
  60. package/dist/sandbox/sdk/pipeline.js +5 -5
  61. package/dist/sandbox/sdk/search.d.ts.map +1 -1
  62. package/dist/sandbox/sdk/search.js +47 -17
  63. package/dist/sandbox/security/code-analyzer.d.ts.map +1 -1
  64. package/dist/sandbox/security/code-analyzer.js +36 -6
  65. package/dist/sandbox/security/path-validator.d.ts +39 -0
  66. package/dist/sandbox/security/path-validator.d.ts.map +1 -1
  67. package/dist/sandbox/security/path-validator.js +104 -5
  68. package/dist/sandbox/type-tests.d.ts.map +1 -1
  69. package/dist/sandbox/type-tests.js +24 -1
  70. package/dist/server.d.ts +3 -15
  71. package/dist/server.d.ts.map +1 -1
  72. package/dist/server.js +73 -77
  73. package/dist/summarizers/generic.d.ts +6 -2
  74. package/dist/summarizers/generic.d.ts.map +1 -1
  75. package/dist/summarizers/generic.js +10 -5
  76. package/dist/summarizers/index.d.ts +13 -3
  77. package/dist/summarizers/index.d.ts.map +1 -1
  78. package/dist/summarizers/index.js +15 -4
  79. package/dist/tools/auto-optimize.d.ts +7 -2
  80. package/dist/tools/auto-optimize.d.ts.map +1 -1
  81. package/dist/tools/auto-optimize.js +354 -59
  82. package/dist/tools/code-execute.d.ts.map +1 -1
  83. package/dist/tools/code-execute.js +72 -24
  84. package/dist/tools/registry.d.ts +44 -9
  85. package/dist/tools/registry.d.ts.map +1 -1
  86. package/dist/tools/registry.js +90 -50
  87. package/dist/tools/smart-file-read.d.ts +11 -2
  88. package/dist/tools/smart-file-read.d.ts.map +1 -1
  89. package/dist/tools/smart-file-read.js +260 -151
  90. package/dist/utils/distill-marker.d.ts +62 -0
  91. package/dist/utils/distill-marker.d.ts.map +1 -0
  92. package/dist/utils/distill-marker.js +82 -0
  93. package/dist/utils/index.d.ts +1 -4
  94. package/dist/utils/index.d.ts.map +1 -1
  95. package/dist/utils/index.js +1 -4
  96. package/dist/utils/signature-grouper.d.ts +7 -0
  97. package/dist/utils/signature-grouper.d.ts.map +1 -1
  98. package/dist/utils/signature-grouper.js +233 -1
  99. package/package.json +7 -5
  100. package/scripts/precompact-hook.sh +103 -0
  101. package/dist/analytics/session-tracker.d.ts +0 -74
  102. package/dist/analytics/session-tracker.d.ts.map +0 -1
  103. package/dist/analytics/session-tracker.js +0 -123
  104. package/dist/config/output-config.d.ts +0 -56
  105. package/dist/config/output-config.d.ts.map +0 -1
  106. package/dist/config/output-config.js +0 -78
  107. package/dist/middleware/chain.d.ts +0 -49
  108. package/dist/middleware/chain.d.ts.map +0 -1
  109. package/dist/middleware/chain.js +0 -126
  110. package/dist/middleware/index.d.ts +0 -4
  111. package/dist/middleware/index.d.ts.map +0 -1
  112. package/dist/middleware/index.js +0 -3
  113. package/dist/middleware/logging.d.ts +0 -8
  114. package/dist/middleware/logging.d.ts.map +0 -1
  115. package/dist/middleware/logging.js +0 -71
  116. package/dist/middleware/types.d.ts +0 -58
  117. package/dist/middleware/types.d.ts.map +0 -1
  118. package/dist/middleware/types.js +0 -7
  119. package/dist/pipelines/definitions.d.ts +0 -50
  120. package/dist/pipelines/definitions.d.ts.map +0 -1
  121. package/dist/pipelines/definitions.js +0 -206
  122. package/dist/summarizers/hierarchical.d.ts +0 -99
  123. package/dist/summarizers/hierarchical.d.ts.map +0 -1
  124. package/dist/summarizers/hierarchical.js +0 -383
  125. package/dist/tools/analyze-build-output.d.ts +0 -30
  126. package/dist/tools/analyze-build-output.d.ts.map +0 -1
  127. package/dist/tools/analyze-build-output.js +0 -45
  128. package/dist/tools/analyze-context.d.ts +0 -23
  129. package/dist/tools/analyze-context.d.ts.map +0 -1
  130. package/dist/tools/analyze-context.js +0 -78
  131. package/dist/tools/code-skeleton.d.ts +0 -33
  132. package/dist/tools/code-skeleton.d.ts.map +0 -1
  133. package/dist/tools/code-skeleton.js +0 -206
  134. package/dist/tools/compress-context.d.ts +0 -33
  135. package/dist/tools/compress-context.d.ts.map +0 -1
  136. package/dist/tools/compress-context.js +0 -64
  137. package/dist/tools/context-budget.d.ts +0 -43
  138. package/dist/tools/context-budget.d.ts.map +0 -1
  139. package/dist/tools/context-budget.js +0 -260
  140. package/dist/tools/conversation-compress.d.ts +0 -46
  141. package/dist/tools/conversation-compress.d.ts.map +0 -1
  142. package/dist/tools/conversation-compress.js +0 -78
  143. package/dist/tools/conversation-memory.d.ts +0 -75
  144. package/dist/tools/conversation-memory.d.ts.map +0 -1
  145. package/dist/tools/conversation-memory.js +0 -289
  146. package/dist/tools/deduplicate-errors.d.ts +0 -30
  147. package/dist/tools/deduplicate-errors.d.ts.map +0 -1
  148. package/dist/tools/deduplicate-errors.js +0 -72
  149. package/dist/tools/detect-retry-loop.d.ts +0 -40
  150. package/dist/tools/detect-retry-loop.d.ts.map +0 -1
  151. package/dist/tools/detect-retry-loop.js +0 -212
  152. package/dist/tools/diff-compress.d.ts +0 -40
  153. package/dist/tools/diff-compress.d.ts.map +0 -1
  154. package/dist/tools/diff-compress.js +0 -94
  155. package/dist/tools/discover-tools.d.ts +0 -11
  156. package/dist/tools/discover-tools.d.ts.map +0 -1
  157. package/dist/tools/discover-tools.js +0 -211
  158. package/dist/tools/dynamic-loader.d.ts +0 -131
  159. package/dist/tools/dynamic-loader.d.ts.map +0 -1
  160. package/dist/tools/dynamic-loader.js +0 -378
  161. package/dist/tools/lazy-mcp.d.ts +0 -31
  162. package/dist/tools/lazy-mcp.d.ts.map +0 -1
  163. package/dist/tools/lazy-mcp.js +0 -151
  164. package/dist/tools/multifile-compress.d.ts +0 -36
  165. package/dist/tools/multifile-compress.d.ts.map +0 -1
  166. package/dist/tools/multifile-compress.js +0 -223
  167. package/dist/tools/optimization-tips.d.ts +0 -18
  168. package/dist/tools/optimization-tips.d.ts.map +0 -1
  169. package/dist/tools/optimization-tips.js +0 -133
  170. package/dist/tools/semantic-compress.d.ts +0 -39
  171. package/dist/tools/semantic-compress.d.ts.map +0 -1
  172. package/dist/tools/semantic-compress.js +0 -113
  173. package/dist/tools/session-stats.d.ts +0 -35
  174. package/dist/tools/session-stats.d.ts.map +0 -1
  175. package/dist/tools/session-stats.js +0 -217
  176. package/dist/tools/set-output-config.d.ts +0 -38
  177. package/dist/tools/set-output-config.d.ts.map +0 -1
  178. package/dist/tools/set-output-config.js +0 -122
  179. package/dist/tools/smart-cache-tool.d.ts +0 -38
  180. package/dist/tools/smart-cache-tool.d.ts.map +0 -1
  181. package/dist/tools/smart-cache-tool.js +0 -224
  182. package/dist/tools/smart-pipeline.d.ts +0 -40
  183. package/dist/tools/smart-pipeline.d.ts.map +0 -1
  184. package/dist/tools/smart-pipeline.js +0 -295
  185. package/dist/tools/summarize-logs.d.ts +0 -41
  186. package/dist/tools/summarize-logs.d.ts.map +0 -1
  187. package/dist/tools/summarize-logs.js +0 -222
  188. package/dist/utils/command-normalizer.d.ts +0 -39
  189. package/dist/utils/command-normalizer.d.ts.map +0 -1
  190. package/dist/utils/command-normalizer.js +0 -90
  191. package/dist/utils/error-normalizer.d.ts +0 -39
  192. package/dist/utils/error-normalizer.d.ts.map +0 -1
  193. package/dist/utils/error-normalizer.js +0 -233
  194. package/dist/utils/output-estimator.d.ts +0 -54
  195. package/dist/utils/output-estimator.d.ts.map +0 -1
  196. package/dist/utils/output-estimator.js +0 -119
  197. package/dist/utils/output-similarity.d.ts +0 -48
  198. package/dist/utils/output-similarity.d.ts.map +0 -1
  199. package/dist/utils/output-similarity.js +0 -140
  200. package/dist/utils/project-detector.d.ts +0 -16
  201. package/dist/utils/project-detector.d.ts.map +0 -1
  202. package/dist/utils/project-detector.js +0 -119
  203. package/dist/utils/toon-serializer.d.ts +0 -120
  204. package/dist/utils/toon-serializer.d.ts.map +0 -1
  205. 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
+ }
@@ -11,4 +11,13 @@ export interface InstallHooksOptions {
11
11
  force?: boolean;
12
12
  }
13
13
  export declare function installHooks(options?: InstallHooksOptions): Promise<boolean>;
14
+ export interface UpdateClaudeMdOptions {
15
+ projectDir?: string;
16
+ force?: boolean;
17
+ }
18
+ /**
19
+ * Updates CLAUDE.md with Distill MCP directives.
20
+ * Returns true if updated, false if CLAUDE.md doesn't exist.
21
+ */
22
+ export declare function updateClaudeMd(options?: UpdateClaudeMdOptions): boolean;
14
23
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/cli/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA+JH,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,OAAO,CAAC,CAqGtF"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/cli/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAgRH,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,OAAO,CAAC,CAqGtF;AAgBD,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CA0D3E"}
package/dist/cli/hooks.js CHANGED
@@ -6,10 +6,120 @@
6
6
  * - PostToolUse: Reminds to use auto_optimize for large Bash outputs
7
7
  * - UserPromptSubmit: Injects MCP tool reminders
8
8
  */
9
- import { existsSync, mkdirSync, writeFileSync, chmodSync } from "fs";
9
+ import { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync } from "fs";
10
10
  import { join } from "path";
11
11
  import { success, warn, info, error, log, COLORS, readJSONFile, writeJSONFile } from "./utils.js";
12
12
  // ============================================================================
13
+ // CLAUDE.md Content
14
+ // ============================================================================
15
+ const CLAUDE_MD_SECTION_START = "## Directives d'utilisation des outils MCP";
16
+ const CLAUDE_MD_SECTION_END = "<!-- END DISTILL -->";
17
+ const CLAUDE_MD_CONTENT = `## Directives d'utilisation des outils MCP
18
+
19
+ Utilisez les outils Distill MCP pour des opérations économes en tokens :
20
+
21
+ ### Règle 1 : Lecture intelligente de fichiers
22
+
23
+ Lors de la lecture de fichiers source pour **exploration ou compréhension** :
24
+
25
+ \`\`\`
26
+ mcp__distill__smart_file_read filePath="path/to/file.ts"
27
+ \`\`\`
28
+
29
+ **Quand utiliser Read natif à la place :**
30
+ - Avant d'éditer un fichier (Edit nécessite Read d'abord)
31
+ - Fichiers de configuration : \`.json\`, \`.yaml\`, \`.toml\`, \`.md\`, \`.env\`
32
+
33
+ ### Règle 2 : Compresser les sorties volumineuses
34
+
35
+ Après les commandes Bash qui produisent une sortie volumineuse (>500 caractères) :
36
+
37
+ \`\`\`
38
+ mcp__distill__auto_optimize content="<collez la sortie volumineuse>"
39
+ \`\`\`
40
+
41
+ ### Règle 3 : SDK d'exécution de code pour les opérations complexes
42
+
43
+ Pour les opérations multi-étapes, utilisez \`code_execute\` au lieu de plusieurs appels d'outils (**98% d'économie de tokens**) :
44
+
45
+ \`\`\`
46
+ mcp__distill__code_execute code="<code typescript>"
47
+ \`\`\`
48
+
49
+ **API du SDK (\`ctx\`) :**
50
+
51
+ *Compression :*
52
+ - \`ctx.compress.auto(content, hint?)\` - Détection auto et compression
53
+ - \`ctx.compress.logs(logs)\` - Résumer les logs
54
+ - \`ctx.compress.diff(diff)\` - Compresser les git diff
55
+ - \`ctx.compress.semantic(content, ratio?)\` - Compression TF-IDF
56
+
57
+ *Code :*
58
+ - \`ctx.code.parse(content, lang)\` - Parser en structure AST
59
+ - \`ctx.code.extract(content, lang, {type, name})\` - Extraire un élément
60
+ - \`ctx.code.skeleton(content, lang)\` - Obtenir les signatures uniquement
61
+
62
+ *Fichiers :*
63
+ - \`ctx.files.read(path)\` - Lire le contenu d'un fichier
64
+ - \`ctx.files.exists(path)\` - Vérifier si un fichier existe
65
+ - \`ctx.files.glob(pattern)\` - Trouver des fichiers par pattern
66
+
67
+ *Git :*
68
+ - \`ctx.git.diff(ref?)\` - Obtenir le diff git
69
+ - \`ctx.git.log(limit?)\` - Historique des commits
70
+ - \`ctx.git.status()\` - Statut du repo
71
+ - \`ctx.git.branch()\` - Info sur les branches
72
+ - \`ctx.git.blame(file, line?)\` - Git blame d'un fichier
73
+
74
+ *Recherche :*
75
+ - \`ctx.search.grep(pattern, glob?)\` - Rechercher un pattern dans les fichiers
76
+ - \`ctx.search.symbols(query, glob?)\` - Rechercher des symboles (fonctions, classes)
77
+ - \`ctx.search.files(pattern)\` - Rechercher des fichiers par pattern
78
+ - \`ctx.search.references(symbol, glob?)\` - Trouver les références d'un symbole
79
+
80
+ *Analyse :*
81
+ - \`ctx.analyze.dependencies(file)\` - Analyser les imports/exports
82
+ - \`ctx.analyze.callGraph(fn, file, depth?)\` - Construire le graphe d'appels
83
+ - \`ctx.analyze.exports(file)\` - Obtenir les exports d'un fichier
84
+ - \`ctx.analyze.structure(dir?, depth?)\` - Structure du répertoire avec analyse
85
+
86
+ *Utilitaires :*
87
+ - \`ctx.utils.countTokens(text)\` - Compter les tokens
88
+ - \`ctx.utils.detectType(content)\` - Détecter le type de contenu
89
+ - \`ctx.utils.detectLanguage(path)\` - Détecter le langage depuis le chemin
90
+
91
+ **Exemples :**
92
+
93
+ \`\`\`typescript
94
+ // Obtenir les squelettes de tous les fichiers TypeScript
95
+ const files = ctx.files.glob("src/**/*.ts").slice(0, 5);
96
+ return files.map(f => ({
97
+ file: f,
98
+ skeleton: ctx.code.skeleton(ctx.files.read(f), "typescript")
99
+ }));
100
+
101
+ // Compresser et analyser les logs
102
+ const logs = ctx.files.read("server.log");
103
+ return ctx.compress.logs(logs);
104
+
105
+ // Extraire une fonction spécifique
106
+ const content = ctx.files.read("src/api.ts");
107
+ return ctx.code.extract(content, "typescript", { type: "function", name: "handleRequest" });
108
+ \`\`\`
109
+
110
+ ### Référence rapide
111
+
112
+ | Action | Utiliser |
113
+ |--------|----------|
114
+ | Lire du code pour exploration | \`mcp__distill__smart_file_read filePath="file.ts"\` |
115
+ | Obtenir une fonction/classe | \`mcp__distill__smart_file_read filePath="file.ts" target={"type":"function","name":"myFunc"}\` |
116
+ | Compresser les erreurs de build | \`mcp__distill__auto_optimize content="..."\` |
117
+ | Résumer les logs | \`mcp__distill__auto_optimize content="..." strategy="logs"\` |
118
+ | Opérations multi-étapes | \`mcp__distill__code_execute code="return ctx.files.glob('src/**/*.ts')"\` |
119
+ | Avant d'éditer | Utiliser l'outil natif \`Read\` |
120
+
121
+ <!-- END DISTILL -->`;
122
+ // ============================================================================
13
123
  // Hook Script Templates
14
124
  // ============================================================================
15
125
  const PRE_READ_CHECK_SCRIPT = `#!/bin/bash
@@ -66,7 +176,7 @@ if echo "$TOOL_RESPONSE" | grep -qiE "(error TS|warning TS|error\\[E|npm ERR|ERR
66
176
  fi
67
177
 
68
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
69
- 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)."}'
70
180
  exit 0
71
181
  fi
72
182
 
@@ -82,8 +192,8 @@ cat << 'EOF'
82
192
  <user-prompt-submit-hook>
83
193
  DISTILL REMINDER: Use MCP tools for token optimization:
84
194
  - Code files: mcp__distill__smart_file_read (50-70% savings vs Read)
85
- - Build/test output: mcp__distill__auto_optimize
86
- - 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)
87
197
  </user-prompt-submit-hook>
88
198
  EOF
89
199
  exit 0
@@ -209,7 +319,7 @@ export async function installHooks(options = {}) {
209
319
  log(`\n${COLORS.dim}Token savings:${COLORS.reset}`);
210
320
  log(` • smart_file_read: 50-70% reduction vs Read`);
211
321
  log(` • auto_optimize: 95%+ reduction on build errors`);
212
- log(` • summarize_logs: 80-90% reduction on logs\n`);
322
+ log(` • auto_optimize: 80-90% reduction on logs\n`);
213
323
  return true;
214
324
  }
215
325
  else {
@@ -227,3 +337,64 @@ function checkJqInstalled() {
227
337
  return false;
228
338
  }
229
339
  }
340
+ /**
341
+ * Updates CLAUDE.md with Distill MCP directives.
342
+ * Returns true if updated, false if CLAUDE.md doesn't exist.
343
+ */
344
+ export function updateClaudeMd(options = {}) {
345
+ const projectDir = options.projectDir || process.cwd();
346
+ const force = options.force || false;
347
+ const claudeMdPath = join(projectDir, "CLAUDE.md");
348
+ // Check if CLAUDE.md exists
349
+ if (!existsSync(claudeMdPath)) {
350
+ warn("CLAUDE.md not found.");
351
+ log(`\n${COLORS.dim}To create CLAUDE.md, run in Claude Code:${COLORS.reset}`);
352
+ log(` ${COLORS.cyan}/init${COLORS.reset}`);
353
+ log(`\nThen run setup again to add Distill directives.\n`);
354
+ return false;
355
+ }
356
+ // Read existing content
357
+ let content;
358
+ try {
359
+ content = readFileSync(claudeMdPath, "utf-8");
360
+ }
361
+ catch (err) {
362
+ error(`Failed to read CLAUDE.md: ${err}`);
363
+ return false;
364
+ }
365
+ // Check if Distill section already exists
366
+ const sectionStartIndex = content.indexOf(CLAUDE_MD_SECTION_START);
367
+ const sectionEndIndex = content.indexOf(CLAUDE_MD_SECTION_END);
368
+ let newContent;
369
+ if (sectionStartIndex !== -1 && sectionEndIndex !== -1) {
370
+ // Section exists - replace it
371
+ if (!force) {
372
+ info("Distill directives already present in CLAUDE.md. Use --force to update.");
373
+ return true;
374
+ }
375
+ // Replace existing section
376
+ const beforeSection = content.substring(0, sectionStartIndex).trimEnd();
377
+ const afterSection = content.substring(sectionEndIndex + CLAUDE_MD_SECTION_END.length).trimStart();
378
+ newContent = beforeSection + "\n\n" + CLAUDE_MD_CONTENT + (afterSection ? "\n\n" + afterSection : "\n");
379
+ }
380
+ else if (sectionStartIndex !== -1) {
381
+ // Partial section (start found but no end marker) - warn and skip
382
+ warn("Found partial Distill section in CLAUDE.md without end marker.");
383
+ log(`Add ${COLORS.dim}${CLAUDE_MD_SECTION_END}${COLORS.reset} at the end of the section, then run setup again.`);
384
+ return false;
385
+ }
386
+ else {
387
+ // No section exists - append
388
+ newContent = content.trimEnd() + "\n\n" + CLAUDE_MD_CONTENT + "\n";
389
+ }
390
+ // Write updated content
391
+ try {
392
+ writeFileSync(claudeMdPath, newContent, "utf-8");
393
+ success("Updated CLAUDE.md with Distill directives");
394
+ return true;
395
+ }
396
+ catch (err) {
397
+ error(`Failed to write CLAUDE.md: ${err}`);
398
+ return false;
399
+ }
400
+ }
@@ -1,5 +1,5 @@
1
1
  export { setup, parseSetupArgs, type SetupOptions } from "./setup.js";
2
2
  export { doctor } from "./doctor.js";
3
- export { installHooks, type InstallHooksOptions } from "./hooks.js";
3
+ export { installHooks, updateClaudeMd, type InstallHooksOptions, type UpdateClaudeMdOptions } from "./hooks.js";
4
4
  export * from "./utils.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACpE,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAChH,cAAc,YAAY,CAAC"}
package/dist/cli/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { setup, parseSetupArgs } from "./setup.js";
2
2
  export { doctor } from "./doctor.js";
3
- export { installHooks } from "./hooks.js";
3
+ export { installHooks, updateClaudeMd } from "./hooks.js";
4
4
  export * from "./utils.js";