vibe-code-explainer 0.1.10 → 0.2.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.
- package/README.md +181 -78
- package/dist/{chunk-IIUJ6UAO.js → chunk-2PUO5G3C.js} +75 -2
- package/dist/chunk-2PUO5G3C.js.map +1 -0
- package/dist/chunk-5NCRRHU7.js +89 -0
- package/dist/chunk-5NCRRHU7.js.map +1 -0
- package/dist/{chunk-OXXWT37Z.js → chunk-SWGQLRTO.js} +24 -11
- package/dist/chunk-SWGQLRTO.js.map +1 -0
- package/dist/{chunk-QTQXXXT4.js → chunk-YS2XIZIA.js} +29 -12
- package/dist/chunk-YS2XIZIA.js.map +1 -0
- package/dist/cli/index.js +4 -4
- package/dist/{config-NF5WYSJB.js → config-5PDPXG7Z.js} +128 -8
- package/dist/config-5PDPXG7Z.js.map +1 -0
- package/dist/hooks/post-tool.js +9 -7
- package/dist/hooks/post-tool.js.map +1 -1
- package/dist/{init-5ZJML72X.js → init-KUVD2YGA.js} +110 -31
- package/dist/init-KUVD2YGA.js.map +1 -0
- package/dist/{ollama-Z5EWJ4H6.js → ollama-34TOVCUY.js} +3 -2
- package/dist/schema-TBXFNCIG.js +17 -0
- package/dist/uninstall-CNGJWJYQ.js +101 -0
- package/dist/uninstall-CNGJWJYQ.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-IIUJ6UAO.js.map +0 -1
- package/dist/chunk-OXXWT37Z.js.map +0 -1
- package/dist/chunk-PGDNR7HQ.js +0 -50
- package/dist/chunk-PGDNR7HQ.js.map +0 -1
- package/dist/chunk-QTQXXXT4.js.map +0 -1
- package/dist/config-NF5WYSJB.js.map +0 -1
- package/dist/init-5ZJML72X.js.map +0 -1
- package/dist/schema-SJTKT73Y.js +0 -11
- package/dist/uninstall-BXMUKVRD.js +0 -63
- package/dist/uninstall-BXMUKVRD.js.map +0 -1
- /package/dist/{ollama-Z5EWJ4H6.js.map → ollama-34TOVCUY.js.map} +0 -0
- /package/dist/{schema-SJTKT73Y.js.map → schema-TBXFNCIG.js.map} +0 -0
package/README.md
CHANGED
|
@@ -78,56 +78,88 @@ with no changes on your side.
|
|
|
78
78
|
|
|
79
79
|
### 2. Run the installer
|
|
80
80
|
|
|
81
|
-
In your terminal
|
|
82
|
-
explanations:
|
|
81
|
+
In your terminal:
|
|
83
82
|
|
|
84
83
|
```bash
|
|
85
|
-
cd path/to/your/project
|
|
84
|
+
cd path/to/your/project # or stay in your home directory for a global install
|
|
86
85
|
npx vibe-code-explainer init
|
|
87
86
|
```
|
|
88
87
|
|
|
89
|
-
The installer is interactive and
|
|
90
|
-
|
|
91
|
-
1.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
```
|
|
88
|
+
The installer is interactive and walks you through six steps:
|
|
89
|
+
|
|
90
|
+
#### 1. Install scope: project or global
|
|
91
|
+
|
|
92
|
+
- **This project only** — hooks live in `.claude/settings.local.json` and the
|
|
93
|
+
config in `./code-explainer.config.json`. Only this project's Claude Code
|
|
94
|
+
sessions get explanations. Best if you only want it in one place or if
|
|
95
|
+
different projects need different settings.
|
|
96
|
+
- **Globally (every project)** — hooks live in `~/.claude/settings.json`
|
|
97
|
+
(your user-level Claude Code config) and the config in
|
|
98
|
+
`~/.code-explainer.config.json`. **Every** Claude Code session on your
|
|
99
|
+
machine gets explanations automatically, no per-project setup needed.
|
|
100
|
+
The package is installed once via `npm install -g`.
|
|
101
|
+
|
|
102
|
+
A project config always takes precedence over the global config if both
|
|
103
|
+
exist, so you can set a global default and override per-project.
|
|
104
|
+
|
|
105
|
+
#### 2. Engine
|
|
106
|
+
|
|
107
|
+
- **Local LLM (Ollama)** — free, private, works offline. Your code never
|
|
108
|
+
leaves your machine.
|
|
109
|
+
- **Claude Code (native)** — uses `claude -p` under the hood for the
|
|
110
|
+
highest-quality explanations and unrelated-change detection. Costs API
|
|
111
|
+
tokens per explanation.
|
|
112
|
+
|
|
113
|
+
#### 3. Detail level
|
|
114
|
+
|
|
115
|
+
- **Standard** — 1–2 sentence explanation per change (recommended)
|
|
116
|
+
- **Minimal** — one short sentence per change
|
|
117
|
+
- **Verbose** — detailed bullet-point breakdown of every change
|
|
118
|
+
|
|
119
|
+
#### 4. Language
|
|
120
|
+
|
|
121
|
+
Pick the language the explanations are written in. Supported:
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
English, Portuguese, Spanish, French, German, Italian, Chinese, Japanese,
|
|
124
|
+
Korean.
|
|
125
|
+
|
|
126
|
+
Only the `summary` and `riskReason` fields are translated. JSON keys and the
|
|
127
|
+
risk labels (`none` / `low` / `medium` / `high`) stay in English.
|
|
128
|
+
|
|
129
|
+
#### 5. Model (Ollama only)
|
|
130
|
+
|
|
131
|
+
- If you have an NVIDIA GPU, the installer auto-detects your VRAM and picks
|
|
132
|
+
the right model.
|
|
133
|
+
- Otherwise, you get a chooser with VRAM hints:
|
|
134
|
+
|
|
135
|
+
| Model | Recommended for | Notes |
|
|
136
|
+
|-------|-----------------|-------|
|
|
137
|
+
| `qwen3.5:4b` | ≤ 8 GB VRAM | newest (Mar 2026), fastest, ~3.4 GB |
|
|
138
|
+
| `qwen2.5-coder:7b` | ≤ 8 GB VRAM | code-specialized, ~4.7 GB |
|
|
139
|
+
| `qwen3.5:9b` | 8–12 GB VRAM | newest, general-purpose, ~6.6 GB |
|
|
140
|
+
| `qwen2.5-coder:14b` | 12–16 GB VRAM | code-specialized, ~9 GB |
|
|
141
|
+
| `qwen3.5:27b` | 16–24 GB VRAM | newest, ~17 GB |
|
|
142
|
+
| `qwen2.5-coder:32b` | ≥ 24 GB VRAM | best code quality, ~19 GB |
|
|
143
|
+
|
|
144
|
+
Pick whichever matches your hardware. The newer `qwen3.5` family (released
|
|
145
|
+
March 2026) is general-purpose with strong coding ability. The
|
|
146
|
+
`qwen2.5-coder` family is code-specialized and remains a solid choice.
|
|
147
|
+
|
|
148
|
+
#### 6. Pull the model + warmup (Ollama only)
|
|
149
|
+
|
|
150
|
+
The installer runs `ollama pull <model>` and shows the real download progress
|
|
151
|
+
bar. Then it sends a trivial warmup diff so the first real explanation is
|
|
152
|
+
fast (otherwise the model has to load into VRAM on first use, which can take
|
|
153
|
+
10–15 seconds).
|
|
154
|
+
|
|
155
|
+
Skip the warmup with:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx vibe-code-explainer init --skip-warmup
|
|
159
|
+
```
|
|
129
160
|
|
|
130
|
-
|
|
161
|
+
When the installer finishes, you're ready to go. Next time you run `claude`
|
|
162
|
+
in a directory covered by this install, you'll see explanations.
|
|
131
163
|
|
|
132
164
|
---
|
|
133
165
|
|
|
@@ -182,21 +214,46 @@ Every explanation includes a risk rating:
|
|
|
182
214
|
|
|
183
215
|
## Configuration
|
|
184
216
|
|
|
185
|
-
Change any setting at any time by running
|
|
217
|
+
Change any setting at any time by running `config`. Which command depends on
|
|
218
|
+
how you installed it:
|
|
219
|
+
|
|
220
|
+
**If you installed globally** (the package is on your PATH):
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
vibe-code-explainer config
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**If you installed per-project** (or want to be safe):
|
|
186
227
|
|
|
187
228
|
```bash
|
|
188
229
|
npx vibe-code-explainer config
|
|
189
230
|
```
|
|
190
231
|
|
|
191
|
-
|
|
192
|
-
|
|
232
|
+
Both commands do the same thing. Use whichever works in your shell. If you
|
|
233
|
+
see a stale version run via `npx`, force the latest with:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
npx vibe-code-explainer@latest config
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The menu auto-detects whether to edit the project config (`./code-explainer.config.json`)
|
|
240
|
+
or the global config (`~/.code-explainer.config.json`): project config wins
|
|
241
|
+
if both exist.
|
|
242
|
+
|
|
243
|
+
It opens an interactive menu showing your current settings. Pick what you
|
|
244
|
+
want to change, one at a time. Every change is saved immediately. When you
|
|
245
|
+
change the model, the tool checks whether Ollama already has it pulled and
|
|
246
|
+
offers to download it on the spot if not.
|
|
193
247
|
|
|
194
248
|
```
|
|
249
|
+
code-explainer config (project)
|
|
250
|
+
|
|
195
251
|
Current settings:
|
|
196
252
|
Engine: Local LLM (Ollama)
|
|
197
|
-
Model:
|
|
253
|
+
Model: qwen3.5:4b
|
|
198
254
|
Ollama URL: http://localhost:11434
|
|
199
|
-
Detail level:
|
|
255
|
+
Detail level: standard
|
|
256
|
+
Language: English
|
|
200
257
|
Hooks: Edit ✓ Write ✓ Bash ✓
|
|
201
258
|
Excluded: *.lock, dist/**, node_modules/**
|
|
202
259
|
Skip if slow: 8s
|
|
@@ -206,27 +263,35 @@ Current settings:
|
|
|
206
263
|
Model
|
|
207
264
|
Ollama URL
|
|
208
265
|
Detail level
|
|
266
|
+
Language
|
|
209
267
|
Enable/disable hooks
|
|
210
268
|
File exclusions
|
|
211
269
|
Latency timeout
|
|
212
270
|
Back (save and exit)
|
|
213
271
|
```
|
|
214
272
|
|
|
273
|
+
If you installed globally, `config` edits `~/.code-explainer.config.json`.
|
|
274
|
+
If you installed per-project, it edits `./code-explainer.config.json`.
|
|
275
|
+
If both exist, the project config takes precedence at runtime.
|
|
276
|
+
|
|
215
277
|
### Configurable options
|
|
216
278
|
|
|
217
279
|
- **Engine** — swap between Ollama (local) and Claude Code (native). Switching
|
|
218
280
|
to Claude Code requires the `claude` CLI to be authenticated.
|
|
219
|
-
- **Model** — pick a different Ollama model. The VRAM hints are visible in
|
|
220
|
-
chooser. You can also
|
|
221
|
-
|
|
222
|
-
- **Ollama URL** — defaults to `http://localhost:11434`. Change this if you
|
|
223
|
-
Ollama in a Docker container on a different port, or on a separate
|
|
224
|
-
Non-loopback URLs trigger a security warning because your code
|
|
225
|
-
over the network.
|
|
281
|
+
- **Model** — pick a different Ollama model. The VRAM hints are visible in
|
|
282
|
+
the chooser. You can also set any model name Ollama supports by editing the
|
|
283
|
+
JSON file directly (e.g., `deepseek-coder-v2:16b`).
|
|
284
|
+
- **Ollama URL** — defaults to `http://localhost:11434`. Change this if you
|
|
285
|
+
run Ollama in a Docker container on a different port, or on a separate
|
|
286
|
+
machine. Non-loopback URLs trigger a security warning because your code
|
|
287
|
+
would be sent over the network.
|
|
226
288
|
- **Detail level** — minimal / standard / verbose. See
|
|
227
|
-
[Install → Detail level](#
|
|
228
|
-
- **
|
|
229
|
-
|
|
289
|
+
[Install → Detail level](#3-detail-level) for what each produces.
|
|
290
|
+
- **Language** — English, Portuguese, Spanish, French, German, Italian,
|
|
291
|
+
Chinese, Japanese, Korean. Applies to the `summary` and `riskReason`
|
|
292
|
+
fields; JSON keys and risk labels stay in English.
|
|
293
|
+
- **Hooks** — turn on or off individually. If Bash explanations feel noisy,
|
|
294
|
+
disable just that hook and keep Edit + Write.
|
|
230
295
|
- **File exclusions** — glob patterns for files you never want explained.
|
|
231
296
|
Defaults cover lockfiles, build output, and dependencies. Add patterns like
|
|
232
297
|
`*.generated.*` if your project has codegen.
|
|
@@ -236,15 +301,21 @@ Current settings:
|
|
|
236
301
|
|
|
237
302
|
### Editing the config file directly
|
|
238
303
|
|
|
239
|
-
If you prefer editing JSON manually
|
|
240
|
-
|
|
304
|
+
If you prefer editing JSON manually:
|
|
305
|
+
|
|
306
|
+
- **Project install:** edit `code-explainer.config.json` in your project root.
|
|
307
|
+
- **Global install:** edit `~/.code-explainer.config.json` in your home
|
|
308
|
+
directory.
|
|
309
|
+
|
|
310
|
+
Full config schema:
|
|
241
311
|
|
|
242
312
|
```json
|
|
243
313
|
{
|
|
244
314
|
"engine": "ollama",
|
|
245
|
-
"ollamaModel": "
|
|
315
|
+
"ollamaModel": "qwen3.5:4b",
|
|
246
316
|
"ollamaUrl": "http://localhost:11434",
|
|
247
317
|
"detailLevel": "standard",
|
|
318
|
+
"language": "en",
|
|
248
319
|
"hooks": {
|
|
249
320
|
"edit": true,
|
|
250
321
|
"write": true,
|
|
@@ -264,16 +335,33 @@ If you prefer editing JSON manually, the config lives at
|
|
|
264
335
|
}
|
|
265
336
|
```
|
|
266
337
|
|
|
338
|
+
Field types:
|
|
339
|
+
|
|
340
|
+
| Field | Values |
|
|
341
|
+
|-------|--------|
|
|
342
|
+
| `engine` | `"ollama"` or `"claude"` |
|
|
343
|
+
| `ollamaModel` | Any model tag available on your Ollama install |
|
|
344
|
+
| `ollamaUrl` | Any valid URL (warns on non-loopback) |
|
|
345
|
+
| `detailLevel` | `"minimal"` / `"standard"` / `"verbose"` |
|
|
346
|
+
| `language` | `"en"` / `"pt"` / `"es"` / `"fr"` / `"de"` / `"it"` / `"zh"` / `"ja"` / `"ko"` |
|
|
347
|
+
| `hooks.edit` / `hooks.write` / `hooks.bash` | `true` or `false` |
|
|
348
|
+
| `exclude` | Array of glob patterns |
|
|
349
|
+
| `skipIfSlowMs` | Number in milliseconds; `0` means never skip |
|
|
350
|
+
|
|
267
351
|
Changes take effect on the next Claude Code tool call — no restart needed.
|
|
268
352
|
|
|
269
353
|
---
|
|
270
354
|
|
|
271
355
|
## Session tools
|
|
272
356
|
|
|
357
|
+
All commands below accept both forms: `vibe-code-explainer <cmd>` (if
|
|
358
|
+
globally installed, on PATH) or `npx vibe-code-explainer <cmd>` (if per-project).
|
|
359
|
+
|
|
273
360
|
### Summary of what Claude has done
|
|
274
361
|
|
|
275
362
|
```bash
|
|
276
|
-
|
|
363
|
+
vibe-code-explainer summary
|
|
364
|
+
# or: npx vibe-code-explainer summary
|
|
277
365
|
```
|
|
278
366
|
|
|
279
367
|
Prints a report of every explained change in your current Claude Code session:
|
|
@@ -282,7 +370,8 @@ total changes, files touched, risk breakdown, and flagged unrelated files.
|
|
|
282
370
|
### Clear the session
|
|
283
371
|
|
|
284
372
|
```bash
|
|
285
|
-
|
|
373
|
+
vibe-code-explainer session end
|
|
374
|
+
# or: npx vibe-code-explainer session end
|
|
286
375
|
```
|
|
287
376
|
|
|
288
377
|
Clears the session state (tracked files and cache). Run this when you start a
|
|
@@ -291,7 +380,8 @@ new task so the drift-detection summary doesn't include old edits.
|
|
|
291
380
|
### Warm up the model
|
|
292
381
|
|
|
293
382
|
```bash
|
|
294
|
-
|
|
383
|
+
vibe-code-explainer warmup
|
|
384
|
+
# or: npx vibe-code-explainer warmup
|
|
295
385
|
```
|
|
296
386
|
|
|
297
387
|
Sends a trivial diff to Ollama to pre-load the model. Useful if you closed and
|
|
@@ -303,17 +393,29 @@ slow (10–15 seconds) as the model gets loaded into VRAM.
|
|
|
303
393
|
## Uninstall
|
|
304
394
|
|
|
305
395
|
```bash
|
|
306
|
-
|
|
396
|
+
vibe-code-explainer uninstall
|
|
397
|
+
# or: npx vibe-code-explainer uninstall
|
|
307
398
|
```
|
|
308
399
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
400
|
+
The uninstaller auto-detects whether you have a project install, a global
|
|
401
|
+
install, or both. If both exist, it asks which to remove.
|
|
402
|
+
|
|
403
|
+
**Project uninstall:** removes the PostToolUse hook entries from
|
|
404
|
+
`.claude/settings.local.json` and deletes `code-explainer.config.json`. Other
|
|
405
|
+
entries (hooks from other tools, other settings) are preserved.
|
|
312
406
|
|
|
313
|
-
|
|
407
|
+
**Global uninstall:** removes the PostToolUse hooks from
|
|
408
|
+
`~/.claude/settings.json` and deletes `~/.code-explainer.config.json`. The
|
|
409
|
+
globally-installed npm package stays on disk — remove it with:
|
|
314
410
|
|
|
315
411
|
```bash
|
|
316
|
-
|
|
412
|
+
npm uninstall -g vibe-code-explainer
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Ollama and any pulled models stay installed either way. To remove them:
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
ollama rm qwen3.5:4b
|
|
317
419
|
# or whichever model you pulled
|
|
318
420
|
```
|
|
319
421
|
|
|
@@ -333,9 +435,10 @@ Most common causes, in order:
|
|
|
333
435
|
ollama serve
|
|
334
436
|
```
|
|
335
437
|
Then try another edit.
|
|
336
|
-
3. **The engine is misconfigured.** Run `
|
|
337
|
-
check the engine and URL. Try
|
|
338
|
-
test — if that works, Ollama is
|
|
438
|
+
3. **The engine is misconfigured.** Run `vibe-code-explainer config` (or
|
|
439
|
+
`npx vibe-code-explainer config`) and check the engine and URL. Try
|
|
440
|
+
switching to the Claude Code engine as a test — if that works, Ollama is
|
|
441
|
+
the issue.
|
|
339
442
|
4. **The file is excluded.** Check your `exclude` patterns in the config.
|
|
340
443
|
|
|
341
444
|
### The explanation took too long and got skipped
|
|
@@ -343,8 +446,8 @@ Most common causes, in order:
|
|
|
343
446
|
Default timeout is 8 seconds. Cold starts with Ollama (first explanation
|
|
344
447
|
after starting the service) can be 10–15 seconds. Two fixes:
|
|
345
448
|
|
|
346
|
-
- Run `
|
|
347
|
-
- Raise the timeout to 15 seconds via `
|
|
449
|
+
- Run `vibe-code-explainer warmup` right after starting Ollama.
|
|
450
|
+
- Raise the timeout to 15 seconds via `vibe-code-explainer config →
|
|
348
451
|
Latency timeout`.
|
|
349
452
|
|
|
350
453
|
### Explanations are low-quality
|
|
@@ -354,7 +457,7 @@ business logic, security-relevant changes). If explanations feel wrong on
|
|
|
354
457
|
tricky diffs, switch to the Claude Code engine:
|
|
355
458
|
|
|
356
459
|
```bash
|
|
357
|
-
|
|
460
|
+
vibe-code-explainer config
|
|
358
461
|
# → Engine → Claude Code (native)
|
|
359
462
|
```
|
|
360
463
|
|
|
@@ -366,7 +469,7 @@ cost of API tokens per explanation.
|
|
|
366
469
|
Add a glob pattern via the config menu:
|
|
367
470
|
|
|
368
471
|
```
|
|
369
|
-
|
|
472
|
+
vibe-code-explainer config → File exclusions → Add a pattern
|
|
370
473
|
```
|
|
371
474
|
|
|
372
475
|
For example, `*.md` to skip all markdown files, or `src/generated/**` to skip
|
|
@@ -375,7 +478,7 @@ a whole directory.
|
|
|
375
478
|
### I want to turn off Bash explanations but keep Edit/Write
|
|
376
479
|
|
|
377
480
|
```
|
|
378
|
-
|
|
481
|
+
vibe-code-explainer config → Enable/disable hooks
|
|
379
482
|
```
|
|
380
483
|
|
|
381
484
|
Uncheck Bash, keep Edit and Write checked.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/config/merge.ts
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
5
6
|
import { dirname, join } from "path";
|
|
6
7
|
var HOOK_MARKER = "code-explainer";
|
|
7
8
|
function buildHookCommand(hookScriptPath) {
|
|
@@ -97,9 +98,81 @@ function removeHooksFromSettings(projectRoot, { useLocal = true } = {}) {
|
|
|
97
98
|
}
|
|
98
99
|
return { removed: removedAny, path: lastPath };
|
|
99
100
|
}
|
|
101
|
+
function mergeHooksIntoUserSettings(hookScriptPath) {
|
|
102
|
+
const userClaudeDir = join(homedir(), ".claude");
|
|
103
|
+
const settingsPath = join(userClaudeDir, "settings.json");
|
|
104
|
+
let settings = {};
|
|
105
|
+
let created = false;
|
|
106
|
+
if (existsSync(settingsPath)) {
|
|
107
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
108
|
+
try {
|
|
109
|
+
settings = JSON.parse(raw);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
112
|
+
throw new Error(
|
|
113
|
+
`[code-explainer] Cannot merge hooks into ${settingsPath}. The file is not valid JSON. Fix: repair the JSON manually or delete the file to regenerate. Original error: ${msg}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (typeof settings !== "object" || settings === null || Array.isArray(settings)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`[code-explainer] Cannot merge hooks into ${settingsPath}. The file does not contain a JSON object at the top level.`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
created = true;
|
|
123
|
+
if (!existsSync(userClaudeDir)) {
|
|
124
|
+
mkdirSync(userClaudeDir, { recursive: true });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!settings.hooks) settings.hooks = {};
|
|
128
|
+
const ourEntries = {
|
|
129
|
+
PostToolUse: [
|
|
130
|
+
{
|
|
131
|
+
matcher: "Edit|Write|MultiEdit",
|
|
132
|
+
hooks: [{ type: "command", command: `node "${hookScriptPath}"` }]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
matcher: "Bash",
|
|
136
|
+
hooks: [{ type: "command", command: `node "${hookScriptPath}"` }]
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
};
|
|
140
|
+
const existingPostTool = settings.hooks.PostToolUse ?? [];
|
|
141
|
+
const cleaned = existingPostTool.map((entry) => ({
|
|
142
|
+
...entry,
|
|
143
|
+
hooks: entry.hooks.filter((h) => !isCodeExplainerHook(h.command))
|
|
144
|
+
})).filter((entry) => entry.hooks.length > 0);
|
|
145
|
+
settings.hooks.PostToolUse = [...cleaned, ...ourEntries.PostToolUse];
|
|
146
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
147
|
+
return { created, path: settingsPath };
|
|
148
|
+
}
|
|
149
|
+
function removeHooksFromUserSettings() {
|
|
150
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
151
|
+
if (!existsSync(settingsPath)) return { removed: false, path: null };
|
|
152
|
+
let settings;
|
|
153
|
+
try {
|
|
154
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
155
|
+
} catch {
|
|
156
|
+
return { removed: false, path: null };
|
|
157
|
+
}
|
|
158
|
+
if (!settings.hooks?.PostToolUse) return { removed: false, path: null };
|
|
159
|
+
const before = JSON.stringify(settings.hooks.PostToolUse);
|
|
160
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse.map((entry) => ({
|
|
161
|
+
...entry,
|
|
162
|
+
hooks: entry.hooks.filter((h) => !isCodeExplainerHook(h.command))
|
|
163
|
+
})).filter((entry) => entry.hooks.length > 0);
|
|
164
|
+
const after = JSON.stringify(settings.hooks.PostToolUse);
|
|
165
|
+
if (before === after) return { removed: false, path: null };
|
|
166
|
+
if (settings.hooks.PostToolUse.length === 0) delete settings.hooks.PostToolUse;
|
|
167
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
168
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
169
|
+
return { removed: true, path: settingsPath };
|
|
170
|
+
}
|
|
100
171
|
|
|
101
172
|
export {
|
|
102
173
|
mergeHooksIntoSettings,
|
|
103
|
-
removeHooksFromSettings
|
|
174
|
+
removeHooksFromSettings,
|
|
175
|
+
mergeHooksIntoUserSettings,
|
|
176
|
+
removeHooksFromUserSettings
|
|
104
177
|
};
|
|
105
|
-
//# sourceMappingURL=chunk-
|
|
178
|
+
//# sourceMappingURL=chunk-2PUO5G3C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/merge.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport const HOOK_MARKER = \"code-explainer\";\n\ninterface HookMatcherEntry {\n matcher: string;\n hooks: Array<{\n type: \"command\";\n command: string;\n }>;\n}\n\ninterface ClaudeSettings {\n hooks?: Record<string, HookMatcherEntry[]>;\n [key: string]: unknown;\n}\n\nfunction buildHookCommand(hookScriptPath: string): string {\n return `node \"${hookScriptPath}\"`;\n}\n\nfunction buildCodeExplainerEntries(hookScriptPath: string): Record<string, HookMatcherEntry[]> {\n const command = buildHookCommand(hookScriptPath);\n return {\n PostToolUse: [\n {\n matcher: \"Edit|Write|MultiEdit\",\n hooks: [{ type: \"command\", command }],\n },\n {\n matcher: \"Bash\",\n hooks: [{ type: \"command\", command }],\n },\n ],\n };\n}\n\nfunction isCodeExplainerHook(cmd: string): boolean {\n return cmd.includes(HOOK_MARKER) && cmd.includes(\"post-tool\");\n}\n\nexport interface MergeResult {\n created: boolean;\n path: string;\n}\n\n/**\n * Read, parse, merge code-explainer hooks into, and write back the settings file.\n * Creates `.claude/settings.json` if it doesn't exist. Preserves all existing\n * hooks and other top-level keys. Idempotent — re-running does not duplicate.\n *\n * Throws if the existing file is malformed JSON, so the caller can surface\n * the error clearly instead of corrupting user settings.\n */\nexport function mergeHooksIntoSettings(\n projectRoot: string,\n hookScriptPath: string,\n { useLocal = true }: { useLocal?: boolean } = {}\n): MergeResult {\n const claudeDir = join(projectRoot, \".claude\");\n const filename = useLocal ? \"settings.local.json\" : \"settings.json\";\n const settingsPath = join(claudeDir, filename);\n\n let settings: ClaudeSettings = {};\n let created = false;\n\n if (existsSync(settingsPath)) {\n const raw = readFileSync(settingsPath, \"utf-8\");\n try {\n settings = JSON.parse(raw);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[code-explainer] Cannot merge hooks into ${settingsPath}. The file is not valid JSON. Fix: repair the JSON manually (check for trailing commas, unquoted keys) or delete the file to regenerate. Original error: ${msg}`\n );\n }\n if (typeof settings !== \"object\" || settings === null || Array.isArray(settings)) {\n throw new Error(\n `[code-explainer] Cannot merge hooks into ${settingsPath}. The file does not contain a JSON object at the top level. Fix: ensure the file starts with { and ends with }.`\n );\n }\n } else {\n created = true;\n if (!existsSync(claudeDir)) {\n mkdirSync(claudeDir, { recursive: true });\n }\n }\n\n if (!settings.hooks) settings.hooks = {};\n\n const ourEntries = buildCodeExplainerEntries(hookScriptPath);\n const existingPostTool = settings.hooks.PostToolUse ?? [];\n\n // Remove any previous code-explainer entries to keep idempotency.\n const cleaned = existingPostTool\n .map((entry) => ({\n ...entry,\n hooks: entry.hooks.filter((h) => !isCodeExplainerHook(h.command)),\n }))\n .filter((entry) => entry.hooks.length > 0);\n\n settings.hooks.PostToolUse = [...cleaned, ...ourEntries.PostToolUse];\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\");\n\n return { created, path: settingsPath };\n}\n\n/**\n * Remove all code-explainer hook entries from the settings file, preserving\n * other hooks and config. Does nothing if the file or hook entries do not\n * exist. Never throws for missing files.\n */\nexport function removeHooksFromSettings(\n projectRoot: string,\n { useLocal = true }: { useLocal?: boolean } = {}\n): { removed: boolean; path: string | null } {\n const candidates = useLocal\n ? [\".claude/settings.local.json\", \".claude/settings.json\"]\n : [\".claude/settings.json\"];\n\n let removedAny = false;\n let lastPath: string | null = null;\n\n for (const rel of candidates) {\n const path = join(projectRoot, rel);\n if (!existsSync(path)) continue;\n\n let settings: ClaudeSettings;\n try {\n settings = JSON.parse(readFileSync(path, \"utf-8\"));\n } catch {\n // Don't corrupt malformed files during uninstall.\n continue;\n }\n\n if (!settings.hooks?.PostToolUse) continue;\n\n const before = JSON.stringify(settings.hooks.PostToolUse);\n settings.hooks.PostToolUse = settings.hooks.PostToolUse\n .map((entry) => ({\n ...entry,\n hooks: entry.hooks.filter((h) => !isCodeExplainerHook(h.command)),\n }))\n .filter((entry) => entry.hooks.length > 0);\n const after = JSON.stringify(settings.hooks.PostToolUse);\n\n if (before !== after) {\n if (settings.hooks.PostToolUse.length === 0) {\n delete settings.hooks.PostToolUse;\n }\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks;\n }\n writeFileSync(path, JSON.stringify(settings, null, 2) + \"\\n\");\n removedAny = true;\n lastPath = path;\n }\n }\n\n return { removed: removedAny, path: lastPath };\n}\n\nexport { dirname };\n\n/**\n * Merge code-explainer hooks into the user-level ~/.claude/settings.json,\n * so hooks fire in every project. Used by the global install path.\n */\nexport function mergeHooksIntoUserSettings(hookScriptPath: string): MergeResult {\n const userClaudeDir = join(homedir(), \".claude\");\n const settingsPath = join(userClaudeDir, \"settings.json\");\n\n let settings: ClaudeSettings = {};\n let created = false;\n\n if (existsSync(settingsPath)) {\n const raw = readFileSync(settingsPath, \"utf-8\");\n try {\n settings = JSON.parse(raw);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[code-explainer] Cannot merge hooks into ${settingsPath}. The file is not valid JSON. Fix: repair the JSON manually or delete the file to regenerate. Original error: ${msg}`\n );\n }\n if (typeof settings !== \"object\" || settings === null || Array.isArray(settings)) {\n throw new Error(\n `[code-explainer] Cannot merge hooks into ${settingsPath}. The file does not contain a JSON object at the top level.`\n );\n }\n } else {\n created = true;\n if (!existsSync(userClaudeDir)) {\n mkdirSync(userClaudeDir, { recursive: true });\n }\n }\n\n if (!settings.hooks) settings.hooks = {};\n\n const ourEntries = {\n PostToolUse: [\n {\n matcher: \"Edit|Write|MultiEdit\",\n hooks: [{ type: \"command\" as const, command: `node \"${hookScriptPath}\"` }],\n },\n {\n matcher: \"Bash\",\n hooks: [{ type: \"command\" as const, command: `node \"${hookScriptPath}\"` }],\n },\n ],\n };\n\n const existingPostTool = settings.hooks.PostToolUse ?? [];\n const cleaned = existingPostTool\n .map((entry) => ({\n ...entry,\n hooks: entry.hooks.filter((h) => !isCodeExplainerHook(h.command)),\n }))\n .filter((entry) => entry.hooks.length > 0);\n\n settings.hooks.PostToolUse = [...cleaned, ...ourEntries.PostToolUse];\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\");\n\n return { created, path: settingsPath };\n}\n\n/**\n * Remove code-explainer hook entries from ~/.claude/settings.json.\n * Preserves other hooks and config. Never throws on missing files.\n */\nexport function removeHooksFromUserSettings(): { removed: boolean; path: string | null } {\n const settingsPath = join(homedir(), \".claude\", \"settings.json\");\n if (!existsSync(settingsPath)) return { removed: false, path: null };\n\n let settings: ClaudeSettings;\n try {\n settings = JSON.parse(readFileSync(settingsPath, \"utf-8\"));\n } catch {\n return { removed: false, path: null };\n }\n\n if (!settings.hooks?.PostToolUse) return { removed: false, path: null };\n\n const before = JSON.stringify(settings.hooks.PostToolUse);\n settings.hooks.PostToolUse = settings.hooks.PostToolUse\n .map((entry) => ({\n ...entry,\n hooks: entry.hooks.filter((h) => !isCodeExplainerHook(h.command)),\n }))\n .filter((entry) => entry.hooks.length > 0);\n const after = JSON.stringify(settings.hooks.PostToolUse);\n\n if (before === after) return { removed: false, path: null };\n\n if (settings.hooks.PostToolUse.length === 0) delete settings.hooks.PostToolUse;\n if (Object.keys(settings.hooks).length === 0) delete settings.hooks;\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\");\n return { removed: true, path: settingsPath };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/config/schema.ts
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
var LANGUAGE_NAMES = {
|
|
8
|
+
en: "English",
|
|
9
|
+
pt: "Portuguese",
|
|
10
|
+
es: "Spanish",
|
|
11
|
+
fr: "French",
|
|
12
|
+
de: "German",
|
|
13
|
+
it: "Italian",
|
|
14
|
+
zh: "Chinese",
|
|
15
|
+
ja: "Japanese",
|
|
16
|
+
ko: "Korean"
|
|
17
|
+
};
|
|
18
|
+
var CONFIG_FILENAME = "code-explainer.config.json";
|
|
19
|
+
function getGlobalConfigPath() {
|
|
20
|
+
return join(homedir(), ".code-explainer.config.json");
|
|
21
|
+
}
|
|
22
|
+
var DEFAULT_CONFIG = {
|
|
23
|
+
engine: "ollama",
|
|
24
|
+
ollamaModel: "qwen3.5:4b",
|
|
25
|
+
ollamaUrl: "http://localhost:11434",
|
|
26
|
+
detailLevel: "standard",
|
|
27
|
+
language: "en",
|
|
28
|
+
hooks: {
|
|
29
|
+
edit: true,
|
|
30
|
+
write: true,
|
|
31
|
+
bash: true
|
|
32
|
+
},
|
|
33
|
+
exclude: ["*.lock", "dist/**", "node_modules/**"],
|
|
34
|
+
skipIfSlowMs: 8e3,
|
|
35
|
+
bashFilter: {
|
|
36
|
+
capturePatterns: [
|
|
37
|
+
"rm",
|
|
38
|
+
"mv",
|
|
39
|
+
"cp",
|
|
40
|
+
"mkdir",
|
|
41
|
+
"npm install",
|
|
42
|
+
"pip install",
|
|
43
|
+
"yarn add",
|
|
44
|
+
"pnpm add",
|
|
45
|
+
"chmod",
|
|
46
|
+
"chown",
|
|
47
|
+
"git checkout",
|
|
48
|
+
"git reset",
|
|
49
|
+
"git revert",
|
|
50
|
+
"sed -i"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
function mergeConfig(base, overlay) {
|
|
55
|
+
return {
|
|
56
|
+
...base,
|
|
57
|
+
...overlay,
|
|
58
|
+
hooks: { ...base.hooks, ...overlay.hooks ?? {} },
|
|
59
|
+
bashFilter: {
|
|
60
|
+
...base.bashFilter,
|
|
61
|
+
...overlay.bashFilter ?? {}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function tryReadJson(path) {
|
|
66
|
+
if (!existsSync(path)) return null;
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function loadConfig(configPath) {
|
|
74
|
+
const globalConfig = tryReadJson(getGlobalConfigPath());
|
|
75
|
+
const projectConfig = tryReadJson(configPath);
|
|
76
|
+
let result = DEFAULT_CONFIG;
|
|
77
|
+
if (globalConfig) result = mergeConfig(result, globalConfig);
|
|
78
|
+
if (projectConfig) result = mergeConfig(result, projectConfig);
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
LANGUAGE_NAMES,
|
|
84
|
+
CONFIG_FILENAME,
|
|
85
|
+
getGlobalConfigPath,
|
|
86
|
+
DEFAULT_CONFIG,
|
|
87
|
+
loadConfig
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=chunk-5NCRRHU7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/schema.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport type Engine = \"ollama\" | \"claude\";\nexport type DetailLevel = \"minimal\" | \"standard\" | \"verbose\";\nexport type RiskLevel = \"none\" | \"low\" | \"medium\" | \"high\";\n\nexport type Language =\n | \"en\"\n | \"pt\"\n | \"es\"\n | \"fr\"\n | \"de\"\n | \"it\"\n | \"zh\"\n | \"ja\"\n | \"ko\";\n\nexport const LANGUAGE_NAMES: Record<Language, string> = {\n en: \"English\",\n pt: \"Portuguese\",\n es: \"Spanish\",\n fr: \"French\",\n de: \"German\",\n it: \"Italian\",\n zh: \"Chinese\",\n ja: \"Japanese\",\n ko: \"Korean\",\n};\n\nexport interface HooksConfig {\n edit: boolean;\n write: boolean;\n bash: boolean;\n}\n\nexport interface BashFilterConfig {\n capturePatterns: string[];\n}\n\nexport interface Config {\n engine: Engine;\n ollamaModel: string;\n ollamaUrl: string;\n detailLevel: DetailLevel;\n language: Language;\n hooks: HooksConfig;\n exclude: string[];\n skipIfSlowMs: number;\n bashFilter: BashFilterConfig;\n}\n\nexport interface ExplanationResult {\n summary: string;\n risk: RiskLevel;\n riskReason: string;\n}\n\nexport interface HookPayload {\n session_id: string;\n transcript_path: string;\n cwd: string;\n permission_mode: string;\n hook_event_name: string;\n tool_name: string;\n tool_input: Record<string, unknown>;\n tool_response: string;\n}\n\nexport const CONFIG_FILENAME = \"code-explainer.config.json\";\n\nexport function getGlobalConfigPath(): string {\n return join(homedir(), \".code-explainer.config.json\");\n}\n\nexport const DEFAULT_CONFIG: Config = {\n engine: \"ollama\",\n ollamaModel: \"qwen3.5:4b\",\n ollamaUrl: \"http://localhost:11434\",\n detailLevel: \"standard\",\n language: \"en\",\n hooks: {\n edit: true,\n write: true,\n bash: true,\n },\n exclude: [\"*.lock\", \"dist/**\", \"node_modules/**\"],\n skipIfSlowMs: 8000,\n bashFilter: {\n capturePatterns: [\n \"rm\",\n \"mv\",\n \"cp\",\n \"mkdir\",\n \"npm install\",\n \"pip install\",\n \"yarn add\",\n \"pnpm add\",\n \"chmod\",\n \"chown\",\n \"git checkout\",\n \"git reset\",\n \"git revert\",\n \"sed -i\",\n ],\n },\n};\n\nfunction mergeConfig(base: Config, overlay: Partial<Config>): Config {\n return {\n ...base,\n ...overlay,\n hooks: { ...base.hooks, ...(overlay.hooks ?? {}) },\n bashFilter: {\n ...base.bashFilter,\n ...(overlay.bashFilter ?? {}),\n },\n };\n}\n\nfunction tryReadJson(path: string): Partial<Config> | null {\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, \"utf-8\")) as Partial<Config>;\n } catch {\n return null;\n }\n}\n\n/**\n * Load config with three-level resolution, most specific first:\n * 1. Project config (passed as configPath) — overrides everything\n * 2. Global user config (~/.code-explainer.config.json)\n * 3. Built-in defaults\n *\n * A project config that lacks a field falls through to the global; a global\n * that lacks a field falls through to defaults. This lets a global install\n * set everyone's defaults while still allowing per-project overrides.\n */\nexport function loadConfig(configPath: string): Config {\n const globalConfig = tryReadJson(getGlobalConfigPath());\n const projectConfig = tryReadJson(configPath);\n\n let result = DEFAULT_CONFIG;\n if (globalConfig) result = mergeConfig(result, globalConfig);\n if (projectConfig) result = mergeConfig(result, projectConfig);\n return result;\n}\n"],"mappings":";;;AAAA,SAAS,YAAY,oBAAoB;AACzC,SAAS,eAAe;AACxB,SAAS,YAAY;AAiBd,IAAM,iBAA2C;AAAA,EACtD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAyCO,IAAM,kBAAkB;AAExB,SAAS,sBAA8B;AAC5C,SAAO,KAAK,QAAQ,GAAG,6BAA6B;AACtD;AAEO,IAAM,iBAAyB;AAAA,EACpC,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,WAAW;AAAA,EACX,aAAa;AAAA,EACb,UAAU;AAAA,EACV,OAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AAAA,EACA,SAAS,CAAC,UAAU,WAAW,iBAAiB;AAAA,EAChD,cAAc;AAAA,EACd,YAAY;AAAA,IACV,iBAAiB;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,YAAY,MAAc,SAAkC;AACnE,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA,IACH,OAAO,EAAE,GAAG,KAAK,OAAO,GAAI,QAAQ,SAAS,CAAC,EAAG;AAAA,IACjD,YAAY;AAAA,MACV,GAAG,KAAK;AAAA,MACR,GAAI,QAAQ,cAAc,CAAC;AAAA,IAC7B;AAAA,EACF;AACF;AAEA,SAAS,YAAY,MAAsC;AACzD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAYO,SAAS,WAAW,YAA4B;AACrD,QAAM,eAAe,YAAY,oBAAoB,CAAC;AACtD,QAAM,gBAAgB,YAAY,UAAU;AAE5C,MAAI,SAAS;AACb,MAAI,aAAc,UAAS,YAAY,QAAQ,YAAY;AAC3D,MAAI,cAAe,UAAS,YAAY,QAAQ,aAAa;AAC7D,SAAO;AACT;","names":[]}
|