ashlr-plugin 1.36.4 → 1.37.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 (81) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/README.md +49 -8
  5. package/commands/ashlr-doctor.md +29 -0
  6. package/commands/ashlr-status.md +1 -1
  7. package/docs/PHANTOM-USAGE.md +58 -0
  8. package/docs/architecture.md +5 -5
  9. package/docs/ashlr-handshake.md +36 -0
  10. package/docs/assets/og.svg +1 -1
  11. package/docs/benchmarks-v2.json +226 -177
  12. package/docs/benchmarks.md +20 -10
  13. package/docs/competitive-wozcode.md +2 -2
  14. package/docs/embeddings-gateway.md +39 -0
  15. package/docs/index.html +3 -3
  16. package/docs/legal.md +1 -1
  17. package/docs/llm-providers.md +15 -1
  18. package/docs/multi-host-mcp.md +25 -3
  19. package/docs/phantom-preflight.md +23 -0
  20. package/docs/team-genome.md +2 -1
  21. package/hooks/_hook-timing-ledger.ts +2 -9
  22. package/hooks/hooks.json +3 -106
  23. package/hooks/posttooluse-stale-result.ts +3 -0
  24. package/hooks/pretooluse-common.ts +1 -1
  25. package/hooks/pretooluse-notebookedit.ts +1 -1
  26. package/hooks/pretooluse-write.ts +1 -1
  27. package/hooks/session-start.ts +48 -26
  28. package/package.json +9 -4
  29. package/ports/cursor/mcp.json +1 -1
  30. package/scripts/_read-thresholds.ts +119 -0
  31. package/scripts/ashlr-mcp.mjs +15 -0
  32. package/scripts/ashlr-mcp.ts +3 -0
  33. package/scripts/ashlr-plugin.mjs +59 -0
  34. package/scripts/bench-read-real.ts +90 -0
  35. package/scripts/bootstrap.mjs +14 -4
  36. package/scripts/cli.ts +35 -10
  37. package/scripts/context-status.ts +7 -2
  38. package/scripts/deploy-smoke.sh +8 -4
  39. package/scripts/doctor.ts +171 -42
  40. package/scripts/genome-auto-consolidate.ts +4 -2
  41. package/scripts/genome-init.ts +3 -3
  42. package/scripts/genome-key.ts +64 -7
  43. package/scripts/handshake.mjs +51 -0
  44. package/scripts/hook-bootstrap.mjs +170 -17
  45. package/scripts/mcp-entrypoint.ts +3 -0
  46. package/scripts/ollama-setup.ts +5 -4
  47. package/scripts/run-benchmark.ts +28 -18
  48. package/scripts/upgrade-flow.ts +9 -14
  49. package/servers/_base-urls.ts +48 -0
  50. package/servers/_companions.ts +258 -0
  51. package/servers/_cwd-clamp.ts +38 -1
  52. package/servers/_edit-match.ts +12 -2
  53. package/servers/_embedding-model.ts +77 -0
  54. package/servers/_gateway-target.ts +179 -0
  55. package/servers/_llm-providers/anthropic.ts +10 -4
  56. package/servers/_llm-providers/cloud.ts +9 -7
  57. package/servers/_llm-providers/index.ts +8 -6
  58. package/servers/_llm-providers/local.ts +9 -5
  59. package/servers/_mcp-host.ts +6 -0
  60. package/servers/_miss-tracker.ts +121 -0
  61. package/servers/_phantom-exec.ts +135 -0
  62. package/servers/_phantom-preflight.ts +130 -0
  63. package/servers/_pid-alive.ts +40 -0
  64. package/servers/_pro-token-keychain.ts +149 -0
  65. package/servers/_redact-keys.ts +75 -0
  66. package/servers/_rg-text.ts +111 -0
  67. package/servers/_router.ts +20 -2
  68. package/servers/_run-with-timeout.ts +56 -12
  69. package/servers/_test-watch.ts +1 -10
  70. package/servers/_token-measure.ts +3 -1
  71. package/servers/_tool-base.ts +6 -0
  72. package/servers/bash-server.ts +1 -10
  73. package/servers/edit-server.ts +24 -1
  74. package/servers/efficiency-server.ts +12 -2
  75. package/servers/genome-server-handlers.ts +2 -2
  76. package/servers/genome-server.ts +17 -17
  77. package/servers/grep-server.ts +6 -2
  78. package/servers/orient-server-handlers.ts +3 -2
  79. package/servers/orient-server.ts +16 -9
  80. package/servers/read-server.ts +1 -1
  81. package/servers/write-server-handlers.ts +10 -2
@@ -25,7 +25,7 @@ Three technical capabilities are worth adopting or beating. One pricing model (p
25
25
  | Per-tool measured savings | No (aggregate self-report) | Yes |
26
26
  | Open source + auditable | No (minified, account-gated) | Yes |
27
27
  | Opt-in telemetry | No (PostHog on-by-default, hardcoded token) | Yes |
28
- | Tool breadth | Narrow (deliberate) | 40 tools, 33 commands |
28
+ | Tool breadth | Narrow (deliberate) | 40 MCP tools, 34 slash commands |
29
29
  | `llms.txt` | Yes | No |
30
30
  | Public savings leaderboard | Yes (gamified) | No |
31
31
  | Multi-surface install (VS Code / Cursor / Windows) | Yes | macOS/Linux primary |
@@ -79,7 +79,7 @@ Message these proactively in docs, README, and the benchmark blog:
79
79
  - **Open source + auditable.** WozCode is minified and requires account creation to inspect behavior. Ashlr is fully open — every tool, hook, and genome interaction is readable.
80
80
  - **Measured savings, not self-reported.** Ashlr tracks per-tool token and cost savings with a verifiable methodology. WozCode's numbers are inconsistent across their own pages.
81
81
  - **Opt-in telemetry.** WozCode ships PostHog with a hardcoded token, on by default. Ashlr's telemetry is opt-in and documented.
82
- - **Breadth.** 40 tools, 33 commands, genome RAG, `/ashlr-brief`, `/ashlr-efficient`. WozCode's surface is deliberately narrow.
82
+ - **Breadth.** 40 MCP tools, 34 slash commands, genome RAG, `/ashlr-brief`, `/ashlr-efficient`. WozCode's surface is deliberately narrow.
83
83
  - **No account required.** Ashlr installs with one curl command and one slash command. WozCode requires account creation before the plugin is functional.
84
84
 
85
85
  ---
@@ -0,0 +1,39 @@
1
+ # Embeddings through LiteLLM or another OpenAI-compatible endpoint
2
+
3
+ This is opt-in and off by default. Without `ASHLR_EMBED_FORMAT=openai`, nothing changes. `ASHLR_EMBED_URL` keeps its Ollama format (`{ model, prompt }` in, `{ embedding }` out), sends no key, and the BM25 embedder still runs when no URL is set.
4
+
5
+ ## Turning it on
6
+
7
+ ```sh
8
+ ASHLR_EMBED_FORMAT=openai
9
+ ASHLR_EMBED_URL=http://127.0.0.1:4000/v1 # a local LiteLLM proxy, for example
10
+ ASHLR_EMBED_MODEL=text-embedding-3-small # whatever model name your gateway serves
11
+ ASHLR_EMBED_KEY=sk-... # optional
12
+ ```
13
+
14
+ With the format set, the plugin POSTs `{ "model": ..., "input": <text> }` to `<ASHLR_EMBED_URL>/embeddings` and reads `data[0].embedding`. A URL that already ends in `/embeddings` is used as is. `ASHLR_EMBED_KEY` is sent as `Authorization: Bearer <key>` and only in this mode. In the Ollama format it is ignored.
15
+
16
+ Any failure falls back to the built-in BM25 embedder, as the Ollama path does. Failures include a refused URL, a timeout after 5 s, an error status, and a body without a numeric `data[0].embedding`.
17
+
18
+ ## Gateway rules
19
+
20
+ These are the same rules as the other keyed model calls (see [llm-providers.md](llm-providers.md)):
21
+
22
+ - The URL must be https, or plain http only to `127.0.0.1` or `[::1]`. `localhost` by name is refused, as is plain http to any other host.
23
+ - Credentials in the URL (`https://user:pass@...`) are refused.
24
+ - Redirects are not followed. A 3xx is a refusal, so the key never follows one.
25
+ - With `ASHLR_GATEWAY_STRICT=1`, the target must also be loopback, an exact https `host:port` in `ASHLR_GATEWAY_HOSTS`, or a built-in provider host.
26
+
27
+ A refused URL makes no request.
28
+
29
+ ## Phantom
30
+
31
+ Under `phantom exec -- <cmd>`, set `ASHLR_EMBED_KEY` to the session's `phm_` placeholder and `ASHLR_EMBED_URL` to the proxy URL for your provider. The proxy URL is `http://127.0.0.1:<port>/...`, so it passes the rules above. The placeholder is sent unchanged and the proxy swaps in the real key on the way out. The plugin never resolves a `phm:` vault reference and never runs `phantom reveal`. If `ASHLR_EMBED_KEY` is a `phm:` reference, no request is made and BM25 is used.
32
+
33
+ ## What is never logged
34
+
35
+ The embedding call writes nothing to stdout or stderr: not the key, not the text being embedded, and not the response. `scripts/context-status.ts` names an openai-format endpoint as `scheme://host:port` only, so a Phantom session token in the URL path is not printed.
36
+
37
+ ## Caveats
38
+
39
+ The URL handling, rules and request shape are covered by tests against local stub servers. The plugin has not been tested against a live LiteLLM or Phantom session here. A remote model's vectors have a different dimension than the 256-dim BM25 vectors, as with the existing Ollama mode, so keep one embedder per context database.
package/docs/index.html CHANGED
@@ -12,7 +12,7 @@
12
12
  <meta property="og:site_name" content="ashlr" />
13
13
  <meta property="og:url" content="https://plugin.ashlr.ai/" />
14
14
  <meta property="og:title" content="ashlr · The Token Ledger" />
15
- <meta property="og:description" content="Open-source Codex and Claude Code plugin. Mean −57% token savings across reference repos. MIT." />
15
+ <meta property="og:description" content="Open-source Codex and Claude Code plugin. Per-call savings: reads about −75%, greps about −50% (not end-to-end). MIT." />
16
16
  <meta property="og:image" content="https://plugin.ashlr.ai/assets/og.png" />
17
17
  <meta property="og:image:width" content="1200" />
18
18
  <meta property="og:image:height" content="630" />
@@ -21,7 +21,7 @@
21
21
  <!-- Twitter / X -->
22
22
  <meta name="twitter:card" content="summary_large_image" />
23
23
  <meta name="twitter:title" content="ashlr · The Token Ledger" />
24
- <meta name="twitter:description" content="Open-source Codex and Claude Code plugin. Mean −57% token savings across reference repos. MIT." />
24
+ <meta name="twitter:description" content="Open-source Codex and Claude Code plugin. Per-call savings: reads about −75%, greps about −50% (not end-to-end). MIT." />
25
25
  <meta name="twitter:image" content="https://plugin.ashlr.ai/assets/og.png" />
26
26
 
27
27
  <link rel="canonical" href="https://plugin.ashlr.ai/" />
@@ -1670,7 +1670,7 @@
1670
1670
  <p style="margin-top: 28px; font-family: var(--font-display); font-variation-settings: 'opsz' 16; font-size: 16px; line-height: 1.5; color: var(--ink-55); max-width: 720px;">
1671
1671
  <em>Files under 2 KB see 0% savings — snipCompact only fires on tool-results
1672
1672
  over 2,000 chars. That's by design: there's nothing to trim on a small file.
1673
- The story is: <strong style="color: var(--debit);">mean −57% across reference repos.</strong> Agents read big files; big files see big savings.</em>
1673
+ The story is: <strong style="color: var(--debit);">per-call reads about −75%, greps about −50% (not end-to-end).</strong> Agents read big files; big files see big savings.</em>
1674
1674
  </p>
1675
1675
 
1676
1676
  <p style="margin-top: 20px; font-family: var(--font-mono); font-size: 13px; color: var(--ink-55);">
package/docs/legal.md CHANGED
@@ -10,7 +10,7 @@ Last reviewed: 2026-04-18
10
10
 
11
11
  ### Entity and policy text
12
12
 
13
- - [x] **Entity name confirmed.** Privacy, Terms, and DPA now say "AshlrAI Inc" (Delaware corporation). Contact email is `support@ashlr.ai` across all pages.
13
+ - [x] **Entity name confirmed.** Privacy, Terms, and DPA now say "AshlrAI Inc" (Virginia corporation). Contact email is `support@ashlr.ai` across all pages.
14
14
  - [x] **Governing law clause.** Delaware, in `site/app/terms/page.tsx` section 10. Placeholder removed.
15
15
  - [ ] **Data residency claims.** Privacy Policy section 8 states "US-East (iad)" for Fly.io and Neon. Verify this matches the actual region in `fly.toml` and the Neon project settings before go-live.
16
16
  - [ ] **Age verification.** Both Privacy Policy (section 9) and Terms (section 3) set a 13+ minimum. If you ever target education or government markets you may need FERPA/COPPA review.
@@ -46,7 +46,7 @@ Connects to an OpenAI-compatible server (LM Studio, Ollama, etc.).
46
46
 
47
47
  | Var | Default | Purpose |
48
48
  |-----|---------|---------|
49
- | `ASHLR_LLM_URL` | `http://localhost:1234/v1` | Base URL of the local server |
49
+ | `ASHLR_LLM_URL` | `http://127.0.0.1:1234/v1` | Base URL of the local server |
50
50
  | `ASHLR_LLM_KEY` | `local-llm` | Bearer token (most local servers don't validate) |
51
51
  | `ASHLR_LLM_MODEL` | `qwen/qwen3-coder-30b@8bit` | Model name to request |
52
52
 
@@ -97,3 +97,17 @@ Implementation lives in `servers/_llm-providers/`:
97
97
  | `index.ts` | `selectProvider()` + `summarizeIfLarge()` facade |
98
98
 
99
99
  `servers/_summarize.ts` re-exports `summarizeIfLarge` from `index.ts` — all existing callers are unchanged.
100
+
101
+ ## Strict gateway targets (opt-in)
102
+
103
+ Off by default. Set `ASHLR_GATEWAY_STRICT=1` to restrict where a model call that carries a key (`Authorization` or `x-api-key`) may go:
104
+
105
+ - `127.0.0.1` or `[::1]` by IP literal, over http or https on any port (a local LiteLLM, LM Studio or Ollama). `localhost` by name does not count.
106
+ - https to an exact `host:port` listed in `ASHLR_GATEWAY_HOSTS`, comma-separated, for example `ASHLR_GATEWAY_HOSTS=litellm.internal:4000,gw.example.com:443`. Port 443 is assumed when the URL has none. An entry without a port makes every listed target refuse until it is fixed.
107
+ - https to the built-in provider hosts on 443: `api.anthropic.com`, `api.openai.com`, `api.ashlr.ai`.
108
+
109
+ In strict mode, credentials in the URL are refused and redirects are never followed: a 3xx is a refusal, because fetch forwards a custom header such as `x-api-key` to the redirect target. A refused call fails the same way a network error does, so summarization falls back and orient and genome merging fall back to their offline paths. Refusal messages name the scheme, host and port only, never the key, path or query.
110
+
111
+ Covered calls: the local provider (`ASHLR_LLM_URL`, availability probe and chat), the legacy internal `endpointOverride` path, the Anthropic and cloud providers, token measurement, `ashlr__orient`'s LLM call, `ashlr__genome_consolidate`'s LLM merge, and the genome auto-consolidate reachability probes.
112
+
113
+ `ashlr__orient`, `ashlr__genome_consolidate`, the local and cloud providers (`ASHLR_LLM_URL`, `ASHLR_API_URL`), the Anthropic summarizer, token measurement and the genome auto-consolidate Anthropic and OpenAI probes apply the base rules even without strict mode (for the Anthropic and OpenAI calls the URL is `ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL`). For orient and genome, the endpoint comes only from `ASHLR_LLM_URL` (default `http://127.0.0.1:1234/v1`), which must be https or http to `127.0.0.1` / `[::1]`, with no credentials in the URL and no redirects followed. A tool argument cannot change it; genome's `model` argument only chooses `none` (offline) or LLM merging. Keyless calls (Ollama setup, `ASHLR_EMBED_URL` in its default Ollama format) are unchanged. The opt-in OpenAI-compatible embedding mode follows these rules; see [embeddings-gateway.md](embeddings-gateway.md).
@@ -47,6 +47,8 @@ ASHLR_MCP_HOST=claude-code # default — preserved for back-compat
47
47
  ASHLR_MCP_HOST=cline
48
48
  ASHLR_MCP_HOST=claude-desktop
49
49
  ASHLR_MCP_HOST=codex-cli
50
+ ASHLR_MCP_HOST=gemini-cli
51
+ ASHLR_MCP_HOST=cursor
50
52
  ASHLR_MCP_HOST=generic # safe default for anything else
51
53
  ```
52
54
 
@@ -61,7 +63,7 @@ If the env var is missing, the server infers `claude-code` when it sees
61
63
  ## Cline (cline_mcp_settings.json)
62
64
 
63
65
  Cline's MCP config lives in your OS-specific Cline settings dir (see
64
- [Cline MCP docs](https://github.com/cline/cline/blob/main/docs/mcp/README.md)).
66
+ [Cline MCP docs](https://docs.cline.bot/mcp/mcp-overview)).
65
67
  The file is typically at:
66
68
 
67
69
  - macOS: `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
@@ -121,6 +123,26 @@ Windows). Add:
121
123
 
122
124
  Quit and reopen Claude Desktop. The tool count appears next to the input box.
123
125
 
126
+ ## Gemini CLI (~/.gemini/settings.json)
127
+
128
+ Gemini CLI reads MCP servers from `~/.gemini/settings.json` (user) or
129
+ `.gemini/settings.json` (project) under `mcpServers`:
130
+
131
+ ```json
132
+ {
133
+ "mcpServers": {
134
+ "ashlr": {
135
+ "command": "bun",
136
+ "args": ["run", "/abs/path/to/ashlr-plugin/scripts/ashlr-mcp.ts"],
137
+ "env": { "ASHLR_MCP_HOST": "gemini-cli" }
138
+ }
139
+ }
140
+ }
141
+ ```
142
+
143
+ Run `gemini mcp list` to confirm the server loads. Cursor uses the same shape in
144
+ `.cursor/mcp.json` with `ASHLR_MCP_HOST` set to `cursor`.
145
+
124
146
  ## OpenAI Codex CLI (~/.codex/config.toml)
125
147
 
126
148
  Codex can install Ashlr as a plugin from `.codex-plugin/plugin.json`, or use
@@ -171,7 +193,7 @@ These features are host-agnostic and Just Work everywhere:
171
193
  host. `ashlr-plugin stats --json` CLI works everywhere Bun runs.
172
194
  - **Genome retrieval** — `.ashlrcode/genome/` lookups work identically.
173
195
  Cloud-genome sync requires Pro/Team auth (see limitations below).
174
- - **Telemetry consent** — `ASHLR_TELEMETRY_OPT_IN=1` honored identically.
196
+ - **Telemetry consent** — `ASHLR_TELEMETRY=on` honored identically.
175
197
  No host ever auto-enables it.
176
198
  - **Per-handler crash isolation** — one tool throwing won't kill the
177
199
  process. Stack traces redirect to `~/.ashlr/crash-dumps/`.
@@ -203,7 +225,7 @@ conventions; we don't try to fight them.
203
225
  Install Bun once: `curl -fsSL https://bun.sh/install | bash` (or use the
204
226
  node trampoline shown above — it auto-installs Bun on first invocation).
205
227
 
206
- **Tools register but every call returns "no stats yet"**
228
+ **Tools register but `ashlr-plugin stats --json` shows an empty ledger**
207
229
  Stats are written on tool _call_, not on registration. Run any `ashlr__*`
208
230
  tool from your host once, then `ashlr-plugin stats --json` will return data.
209
231
 
@@ -0,0 +1,23 @@
1
+ # Phantom agent preflight in `ashlr doctor`
2
+
3
+ `ashlr doctor` can run Phantom's value-free agent preflight,
4
+ `phantom agent doctor --json --strict`, and show its verdict as a
5
+ `phantom preflight` line in the install section. It is off by default.
6
+
7
+ | `ASHLR_PHANTOM_PREFLIGHT` | Line status |
8
+ |---|---|
9
+ | unset, `0` | No line; phantom is not run. |
10
+ | `1`, `true`, `on` | `ready` ok, `degraded` or `unknown` warn, `blocked` fail. |
11
+ | `strict` | `ready` ok, anything else fail. |
12
+
13
+ The preflight follows phantom-secrets
14
+ [docs/agent-preflight-contract.md](https://github.com/ashlrai/phantom-secrets/blob/main/docs/agent-preflight-contract.md)
15
+ (schema version 1; `--strict` exits 0 ready, 2 degraded, 1 blocked, 3 could
16
+ not evaluate). It reads only project files and environment variables: no vault
17
+ open, no keychain access.
18
+
19
+ The line shows the verdict and, when blocked, the critical check ids. It never
20
+ shows secret names, values or phantom's own messages. A missing `phantom`, or a
21
+ version without `agent doctor --json`, gives a warning line and nothing else.
22
+ Run `phantom agent doctor` for the full report and fix commands, and run tools
23
+ with `phantom exec -- <cmd>` so they get `phm_` placeholders instead of keys.
@@ -428,7 +428,8 @@ bun run scripts/genome-key.ts export <genomeId>
428
428
  # Prints base32-encoded key after confirmation prompt
429
429
 
430
430
  # On each team member's machine:
431
- bun run scripts/genome-key.ts import <genomeId> <BASE32_KEY>
431
+ bun run scripts/genome-key.ts import <genomeId>
432
+ # paste the key when asked (input is hidden), or pipe it on stdin
432
433
  ```
433
434
 
434
435
  Once the key file exists at `~/.ashlr/team-keys/<genomeId>.key`, every push is
@@ -16,6 +16,7 @@ import {
16
16
  writeFileSync,
17
17
  } from "fs";
18
18
  import { basename, dirname, join } from "path";
19
+ import { pidAlive } from "../servers/_pid-alive";
19
20
 
20
21
  export const HOOK_TIMING_FILE_MAX_BYTES = 16 * 1024 * 1024;
21
22
  export const HOOK_TIMING_BATCH_MAX_BYTES = 256 * 1024;
@@ -135,14 +136,6 @@ function readOwner(lockPath: string): LockOwner | null {
135
136
  }
136
137
  }
137
138
 
138
- function processIsAlive(pid: number): boolean {
139
- try {
140
- process.kill(pid, 0);
141
- return true;
142
- } catch (error) {
143
- return (error as NodeJS.ErrnoException).code !== "ESRCH";
144
- }
145
- }
146
139
 
147
140
  interface LockIdentity {
148
141
  lockDev: number;
@@ -219,7 +212,7 @@ function inspectStaleLock(lockPath: string): LockIdentity | null {
219
212
  const age = Math.max(0, now - Math.min(created, mtimeMs));
220
213
  if (age > LOCK_HARD_STALE_MS) return identity;
221
214
  if (age <= LOCK_STALE_MS) return null;
222
- return owner === null || !processIsAlive(owner.pid) ? identity : null;
215
+ return owner === null || !pidAlive(owner.pid) ? identity : null;
223
216
  } catch {
224
217
  return null;
225
218
  }
package/hooks/hooks.json CHANGED
@@ -5,31 +5,7 @@
5
5
  "hooks": [
6
6
  {
7
7
  "type": "command",
8
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.ts\""
9
- },
10
- {
11
- "type": "command",
12
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-brief.ts\""
13
- },
14
- {
15
- "type": "command",
16
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-search.ts\""
17
- },
18
- {
19
- "type": "command",
20
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-lean-tools.ts\""
21
- },
22
- {
23
- "type": "command",
24
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-genome-author.ts\""
25
- },
26
- {
27
- "type": "command",
28
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-cost-refactor.ts\""
29
- },
30
- {
31
- "type": "command",
32
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-efficient.ts\""
8
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" --group \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-brief.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-search.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-lean-tools.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-genome-author.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-cost-refactor.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-efficient.ts\""
33
9
  }
34
10
  ]
35
11
  }
@@ -168,90 +144,11 @@
168
144
  ],
169
145
  "PostToolUse": [
170
146
  {
171
- "matcher": "mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write",
172
- "hooks": [
173
- {
174
- "type": "command",
175
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use-embedding.ts\""
176
- }
177
- ]
178
- },
179
- {
180
- "matcher": "Edit|MultiEdit|Write|Bash|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write",
181
- "hooks": [
182
- {
183
- "type": "command",
184
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/audit-upload.ts\""
185
- }
186
- ]
187
- },
188
- {
189
- "matcher": "Edit|MultiEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural",
147
+ "matcher": "mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write|Edit|MultiEdit|Write|Bash|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__rename_file|Read|Grep|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep",
190
148
  "hooks": [
191
149
  {
192
150
  "type": "command",
193
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/edit-batching-nudge.ts\""
194
- },
195
- {
196
- "type": "command",
197
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/genome-scribe-hook.ts\""
198
- }
199
- ]
200
- },
201
- {
202
- "matcher": "Edit|Write|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__write|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__rename_file",
203
- "hooks": [
204
- {
205
- "type": "command",
206
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-genome-refresh.ts\""
207
- }
208
- ]
209
- },
210
- {
211
- "matcher": "Read|Grep|Edit|Write|MultiEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write",
212
- "hooks": [
213
- {
214
- "type": "command",
215
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/orient-nudge-hook.ts\""
216
- }
217
- ]
218
- },
219
- {
220
- "matcher": "Read|Grep|Edit|Write|MultiEdit",
221
- "hooks": [
222
- {
223
- "type": "command",
224
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-native-nudge.ts\""
225
- }
226
- ]
227
- },
228
- {
229
- "matcher": "Read|Grep|Edit|Write|Bash|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write",
230
- "hooks": [
231
- {
232
- "type": "command",
233
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use-genome.ts\""
234
- },
235
- {
236
- "type": "command",
237
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/session-log-append.ts\""
238
- },
239
- {
240
- "type": "command",
241
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/pulse-emit.ts\""
242
- }
243
- ]
244
- },
245
- {
246
- "matcher": "Read|Grep|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep",
247
- "hooks": [
248
- {
249
- "type": "command",
250
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-stale-result.ts\""
251
- },
252
- {
253
- "type": "command",
254
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-dedupe-output.ts\""
151
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" --group --match \"mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use-embedding.ts\" --match \"Edit|MultiEdit|Write|Bash|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/audit-upload.ts\" --match \"Edit|MultiEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural\" \"${CLAUDE_PLUGIN_ROOT}/hooks/edit-batching-nudge.ts\" --match \"Edit|MultiEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural\" \"${CLAUDE_PLUGIN_ROOT}/hooks/genome-scribe-hook.ts\" --match \"Edit|Write|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__write|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__rename_file\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-genome-refresh.ts\" --match \"Read|Grep|Edit|Write|MultiEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/orient-nudge-hook.ts\" --match \"Read|Grep|Edit|Write|MultiEdit\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-native-nudge.ts\" --match \"Read|Grep|Edit|Write|Bash|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use-genome.ts\" --match \"Read|Grep|Edit|Write|Bash|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/session-log-append.ts\" --match \"Read|Grep|Edit|Write|Bash|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/pulse-emit.ts\" --match \"Read|Grep|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-stale-result.ts\" --match \"Read|Grep|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-dedupe-output.ts\""
255
152
  }
256
153
  ]
257
154
  }
@@ -31,6 +31,7 @@ import {
31
31
  staleByteTotal,
32
32
  STALE_BYTES_NUDGE_THRESHOLD,
33
33
  } from "../servers/_history-tracker";
34
+ import { observeToolCall } from "../servers/_miss-tracker";
34
35
  import { logPreCompactionNudgeEvent } from "../servers/_telemetry";
35
36
  // logMultiTurnStaleEvent removed in v1.30 (1.5) — multi_turn_stale_estimate was never populated.
36
37
 
@@ -178,6 +179,8 @@ export function decide(
178
179
 
179
180
  // Record this result in the history JSONL.
180
181
  const content = extractContent(payload);
182
+ // Miss tracking (self-tuning P1): turn index of this call = entries so far.
183
+ observeToolCall(toolName, payload.tool_input, readHistory(sessionId, homeDir).length, sessionId, homeDir);
181
184
  recordResult(toolName, content, sessionId, homeDir, now);
182
185
 
183
186
  // -------------------------------------------------------------------------
@@ -416,7 +416,7 @@ export function buildToolRedirectBlock(opts: {
416
416
  savingsPct: number;
417
417
  bypassNote?: string;
418
418
  }): RedirectBlockOutput {
419
- const bypass = opts.bypassNote ?? "set ASHLR_HOOK_MODE=nudge in ~/.ashlr/config.json";
419
+ const bypass = opts.bypassNote ?? 'set ASHLR_HOOK_MODE=nudge, or "hookMode": "nudge" in ~/.ashlr/config.json';
420
420
  const reason =
421
421
  `[ashlr bypass: ${bypass}] ~${opts.savingsPct}% token savings. Call this instead:\n\n` +
422
422
  ` tool_use: ${opts.mcpToolName}\n` +
@@ -112,7 +112,7 @@ if (!notebookPath || !isInsideCwd(notebookPath)) {
112
112
 
113
113
  const mcp = "mcp__plugin_ashlr_ashlr__ashlr__notebook_edit";
114
114
  const reason =
115
- `[ashlr] To bypass: set ASHLR_HOOK_MODE=nudge in ~/.ashlr/config.json. ` +
115
+ `[ashlr] To bypass: set ASHLR_HOOK_MODE=nudge, or "hookMode": "nudge" in ~/.ashlr/config.json. ` +
116
116
  `Current rule: blocking built-in NotebookEdit on ${notebookPath || "<notebook>"} — ` +
117
117
  `call ${mcp} instead, which applies the cell edit and returns only the edited cell ` +
118
118
  `plus one cell above and below (full notebook never echoed). ` +
@@ -118,7 +118,7 @@ if (!isInsideCwd(payload!.file_path)) {
118
118
 
119
119
  const mcp = "mcp__plugin_ashlr_ashlr__ashlr__write";
120
120
  const reason =
121
- `[ashlr] To bypass: set ASHLR_HOOK_MODE=nudge in ~/.ashlr/config.json. ` +
121
+ `[ashlr] To bypass: set ASHLR_HOOK_MODE=nudge, or "hookMode": "nudge" in ~/.ashlr/config.json. ` +
122
122
  `Current rule: blocking built-in Write on ${payload!.file_path} (${size} bytes) — ` +
123
123
  `call ${mcp} instead, which delegates to ashlr__edit (diff format) for existing ` +
124
124
  `files and returns only a compact diff summary, avoiding the full content echo ` +
@@ -22,6 +22,7 @@
22
22
  * (an empty additionalContext) rather than hang the session.
23
23
  */
24
24
 
25
+ import { keychainLoad, migrateEnvFileToken } from "../servers/_pro-token-keychain.ts";
25
26
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs";
26
27
  import { homedir } from "os";
27
28
  import { basename, dirname, join } from "path";
@@ -763,6 +764,22 @@ function sourceAshlrEnv(): void {
763
764
  }
764
765
 
765
766
 
767
+ /**
768
+ * Move a plaintext ASHLR_PRO_TOKEN line from ~/.ashlr/env into the OS
769
+ * keychain (kept in the file if the keychain is unavailable), then load the
770
+ * token from the keychain when the environment does not already set it.
771
+ */
772
+ function loadProTokenFromKeychain(): void {
773
+ try {
774
+ migrateEnvFileToken(join(homedir(), ".ashlr", "env"));
775
+ if (process.env["ASHLR_PRO_TOKEN"]) return;
776
+ const token = keychainLoad();
777
+ if (token) process.env["ASHLR_PRO_TOKEN"] = token;
778
+ } catch {
779
+ /* keychain is optional, never break the hook */
780
+ }
781
+ }
782
+
766
783
  /**
767
784
  * Detect the active ashlr tier for Q2 cloud delta sync. Mirrors
768
785
  * scripts/check-tier.ts so we can call syncCloudDeltas without spawning a
@@ -789,10 +806,30 @@ async function detectTierForSync(): Promise<Tier> {
789
806
  }
790
807
  }
791
808
 
809
+ /**
810
+ * Race `work` against a `ms` ceiling and clear the timer once either side
811
+ * settles. A bare `setTimeout` in a Promise.race keeps the hook process alive
812
+ * until it fires, which held every SessionStart open for the full 2s.
813
+ */
814
+ async function raceWithTimeout<T, F>(work: Promise<T>, ms: number, fallback: F): Promise<T | F> {
815
+ let timer: ReturnType<typeof setTimeout> | undefined;
816
+ try {
817
+ return await Promise.race([
818
+ work,
819
+ new Promise<F>((resolve) => {
820
+ timer = setTimeout(() => resolve(fallback), ms);
821
+ }),
822
+ ]);
823
+ } finally {
824
+ clearTimeout(timer);
825
+ }
826
+ }
827
+
792
828
  async function main(): Promise<void> {
793
829
  // Source ~/.ashlr/env so ASHLR_PRO_TOKEN from the upgrade flow is available
794
830
  // without requiring a shell restart.
795
831
  sourceAshlrEnv();
832
+ loadProTokenFromKeychain();
796
833
 
797
834
  // First-run: bootstrap dependencies if missing. Silent no-op otherwise.
798
835
  ensureDepsInstalled();
@@ -846,24 +883,15 @@ async function main(): Promise<void> {
846
883
 
847
884
  let result: BuildResult;
848
885
  try {
849
- result = await Promise.race([
850
- Promise.resolve(buildResponse()),
851
- new Promise<BuildResult>((resolve) =>
852
- setTimeout(
853
- () =>
854
- resolve({
855
- output: {
856
- hookSpecificOutput: {
857
- hookEventName: "SessionStart",
858
- additionalContext: "[ashlr baseline · timed out]",
859
- },
860
- },
861
- notice: null,
862
- }),
863
- SCAN_BUDGET_MS,
864
- )
865
- ),
866
- ]);
886
+ result = await raceWithTimeout(Promise.resolve(buildResponse()), SCAN_BUDGET_MS, {
887
+ output: {
888
+ hookSpecificOutput: {
889
+ hookEventName: "SessionStart",
890
+ additionalContext: "[ashlr baseline · timed out]",
891
+ },
892
+ },
893
+ notice: null,
894
+ });
867
895
  } catch {
868
896
  result = {
869
897
  output: { hookSpecificOutput: { hookEventName: "SessionStart" } },
@@ -934,10 +962,7 @@ async function main(): Promise<void> {
934
962
  // race only protects against the fire-and-forget reference outliving the
935
963
  // hook (e.g. a pending fetch that the AbortController didn't actually
936
964
  // abort because the runtime was wedged).
937
- void Promise.race([
938
- checkForUpdate({ currentVersion }),
939
- new Promise<void>((resolve) => setTimeout(resolve, 2000)),
940
- ]).catch((e) => {
965
+ void raceWithTimeout(checkForUpdate({ currentVersion }), 2000, undefined).catch((e) => {
941
966
  process.stderr.write(
942
967
  "[ashlr-session-start] update check failed: " +
943
968
  (e instanceof Error ? e.message : String(e)) +
@@ -969,10 +994,7 @@ async function main(): Promise<void> {
969
994
  // fetch can never blow the SessionStart budget. Failures are swallowed.
970
995
  try {
971
996
  const tier = await detectTierForSync();
972
- await Promise.race([
973
- syncCloudDeltas({ tier }),
974
- new Promise<void>((resolve) => setTimeout(resolve, 2000)),
975
- ]);
997
+ await raceWithTimeout(syncCloudDeltas({ tier }), 2000, undefined);
976
998
  } catch (e) {
977
999
  process.stderr.write(
978
1000
  "[ashlr-session-start] cloud delta sync failed: " +
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "ashlr-plugin",
3
- "version": "1.36.4",
3
+ "version": "1.37.1",
4
+ "mcpName": "io.github.ashlrai/ashlr-plugin",
4
5
  "description": "Multi-host AI coding efficiency layer for Codex and Claude Code, powered by @ashlr/core-efficiency.",
5
6
  "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ashlrai/ashlr-plugin.git"
10
+ },
6
11
  "type": "module",
7
12
  "bin": {
8
- "ashlr-plugin": "./scripts/cli.ts",
9
- "ashlr-mcp": "./scripts/ashlr-mcp.ts"
13
+ "ashlr-plugin": "./scripts/ashlr-plugin.mjs",
14
+ "ashlr-mcp": "./scripts/ashlr-mcp.mjs"
10
15
  },
11
16
  "files": [
12
17
  ".agents/plugins/marketplace.json",
@@ -39,7 +44,7 @@
39
44
  "version:check": "bun run scripts/check-version-sync.ts"
40
45
  },
41
46
  "dependencies": {
42
- "@ashlr/core-efficiency": "github:ashlrai/ashlr-core-efficiency#275610156ee314c3335025f0ba0c2379089e068c",
47
+ "@ashlr/core-efficiency": "0.3.0",
43
48
  "@modelcontextprotocol/sdk": "^1.0.0",
44
49
  "minimatch": "^10.2.5",
45
50
  "postgres": "^3.4.9",
@@ -4,7 +4,7 @@
4
4
  "command": "bun",
5
5
  "args": ["run", "<ASHLR_PLUGIN_ROOT>/scripts/ashlr-mcp.ts"],
6
6
  "env": {
7
- "ASHLR_MCP_HOST": "generic"
7
+ "ASHLR_MCP_HOST": "cursor"
8
8
  }
9
9
  }
10
10
  }