mirror-mind 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.pi/skills/mm-build/SKILL.md +1 -1
  2. package/.pi/skills/mm-explore/SKILL.md +1 -1
  3. package/.pi/skills/mm-help/SKILL.md +2 -2
  4. package/.pi/skills/mm-soul/SKILL.md +1 -1
  5. package/AGENTS.md +7 -6
  6. package/README.md +5 -6
  7. package/docs/releases/index.md +1 -0
  8. package/docs/releases/v0.35.0.md +199 -0
  9. package/package.json +2 -4
  10. package/plugins/mirror-mind/.claude-plugin/plugin.json +1 -1
  11. package/plugins/mirror-mind/agy/pre-invocation.sh +92 -0
  12. package/plugins/mirror-mind/agy/session-start.sh +92 -0
  13. package/plugins/mirror-mind/agy/stop.sh +92 -0
  14. package/plugins/mirror-mind/hooks.json +26 -0
  15. package/plugins/mirror-mind/mcp_config.json +11 -0
  16. package/plugins/mirror-mind/plugin.json +4 -0
  17. package/plugins/mirror-mind/rules/AGENTS.md +189 -0
  18. package/plugins/mirror-mind/skills/mm-build/SKILL.md +1 -1
  19. package/ts/src/conversation/agyTranscript.ts +105 -0
  20. package/ts/src/conversation/commandTurn.ts +1 -1
  21. package/ts/src/conversation/deferredCloseTail.ts +2 -2
  22. package/ts/src/conversation/logger.ts +1 -1
  23. package/ts/src/conversation/runtimePolicy.ts +10 -1
  24. package/ts/src/docs/docsLint.ts +14 -1
  25. package/ts/src/frontDoor/stdinSource.ts +2 -2
  26. package/ts/src/guards/claudePlugin.ts +74 -0
  27. package/ts/src/guards/packContents.ts +3 -2
  28. package/ts/src/guards/piSourcedSkills.ts +1 -1
  29. package/ts/src/guards/retiredSurfaces.ts +31 -0
  30. package/ts/src/hooks/agy.ts +229 -0
  31. package/ts/src/hooks/main.ts +11 -15
  32. package/ts/src/hooks/payload.ts +8 -15
  33. package/ts/src/hooks/runtime.ts +11 -10
  34. package/ts/src/runtime/wiring.ts +18 -36
  35. package/.gemini/hooks/log-assistant.sh +0 -85
  36. package/.gemini/hooks/log-user.sh +0 -85
  37. package/.gemini/hooks/session-end.sh +0 -85
  38. package/.gemini/hooks/session-start.sh +0 -85
  39. package/ts/src/hooks/gemini.ts +0 -128
@@ -10,7 +10,7 @@ Activates Builder Mode for a specific journey. Loads identity context and projec
10
10
 
11
11
  ## Usage
12
12
 
13
- Pi and Gemini CLI:
13
+ Pi:
14
14
 
15
15
  ```
16
16
  /mm-build <journey-slug>
@@ -14,7 +14,7 @@ attractors, experiment proposals, and Builder handoff.
14
14
 
15
15
  ## Usage
16
16
 
17
- Pi and Gemini CLI:
17
+ Pi:
18
18
 
19
19
  ```text
20
20
  /mm-explore <journey-slug>
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: "mm-help"
3
- description: Shows available Mirror Mind commands on Pi and Gemini CLI
3
+ description: Shows available Mirror Mind commands on Pi
4
4
  user-invocable: true
5
5
  ---
6
6
 
7
7
  # Help
8
8
 
9
- Show the user the available commands (same on Pi and Gemini CLI):
9
+ Show the user the available commands:
10
10
 
11
11
  ## Operating Modes
12
12
 
@@ -13,7 +13,7 @@ surface and the question: "how is your day going today?"
13
13
 
14
14
  ## Usage
15
15
 
16
- Pi and Gemini CLI:
16
+ Pi:
17
17
 
18
18
  ```text
19
19
  /mm-soul [journey-slug]
package/AGENTS.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Mirror Mind — Operating Instructions
2
2
 
3
3
  <!-- This file ships in the mirror-mind package and is read by every runtime:
4
- Pi and Codex read it from the project root natively, Claude Code and
5
- Gemini CLI through the @AGENTS.md import in CLAUDE.md, and an installed
6
- package's Pi session receives it from the extension (CV22.DS10.US3, D13).
4
+ Pi and Codex read it from the project root natively, Claude Code through
5
+ the @AGENTS.md import in CLAUDE.md, Antigravity as the plugin's rules, and
6
+ an installed package's Pi session receives it from the extension
7
+ (CV22.DS10.US3, D13).
7
8
  It holds nothing about this repository; that is CLAUDE.md's part. -->
8
9
 
9
10
  ---
@@ -17,9 +18,9 @@ Applies to all sessions, regardless of project.
17
18
  The mirror operates in four modes, chosen automatically based on context:
18
19
  Mirror, Builder, Explorer, and Soul.
19
20
 
20
- Each skill below is named three ways: `/mm-<skill>` in Pi and Gemini CLI,
21
- `$mm-<skill>` in Codex, `/mm:<skill>` in Claude Code. A **plugin** install
22
- prefixes the plugin's name — `/mirror-mind:mm:<skill>` in Claude Code's menu,
21
+ Each skill below is named three ways: `/mm-<skill>` in Pi, `$mm-<skill>` in
22
+ Codex, `/mm:<skill>` in Claude Code. A **plugin** install prefixes the plugin's
23
+ name — `/mirror-mind:mm:<skill>` in Claude Code's menu and in Antigravity,
23
24
  `$mirror-mind:mm:<skill>` in Codex, `mirror-mind:mm-<skill>` as the skill's own
24
25
  name — and the forms below refer to the same skill.
25
26
 
package/README.md CHANGED
@@ -60,8 +60,8 @@ This is not a chatbot with memory. This is a mirror — and yours.
60
60
 
61
61
  ## How It Works
62
62
 
63
- Local-first. SQLite. Four supported runtimes: Pi, Gemini CLI, Codex, Claude
64
- Code. One TypeScript core, run by Node.js. Jungian architecture: Self, Ego,
63
+ Local-first. SQLite. Four supported runtimes: Pi, Claude Code, Codex,
64
+ Antigravity. One TypeScript core, run by Node.js. Jungian architecture: Self, Ego,
65
65
  Personas, Shadow.
66
66
  Context-activated modes — Mirror, Builder, Explorer, and Soul. The identity lives
67
67
  in your machine, not a server.
@@ -79,9 +79,8 @@ in your machine, not a server.
79
79
  - [OpenRouter](https://openrouter.ai) account with at least $5 in credits —
80
80
  embeddings, memory extraction, and multi-LLM
81
81
  - An AI runtime subscription:
82
- [Codex Plus](https://openai.com/codex/) (recommended),
83
- [Claude Code Pro](https://code.claude.com/docs), or
84
- [Gemini AI Pro](https://geminicli.com/)
82
+ [Codex Plus](https://openai.com/codex/) (recommended) or
83
+ [Claude Code Pro](https://code.claude.com/docs)
85
84
  - [Pi](https://github.com/earendil-works/pi/tree/main/packages/coding-agent)
86
85
  — recommended harness (multi-model, not locked to one provider)
87
86
 
@@ -111,7 +110,7 @@ this repository, turning a personal experiment into a reusable framework:
111
110
 
112
111
  - **English** — full migration from Portuguese across code, CLI, schema, identity, and docs
113
112
  - **Framework/user separation** — user identity and memory live privately under `~/.mirror/<user>/`, outside the repository
114
- - **Multi-runtime** — from Claude Code only to four runtimes: Pi, Gemini CLI, Codex, and Claude Code
113
+ - **Multi-runtime** — from Claude Code only to Pi, Claude Code, Codex, and Antigravity
115
114
  - **Multi-user** — independent mirrors for multiple users on the same machine
116
115
  - **Multi-session** — concurrent sessions tracked safely in a database-backed session registry
117
116
  - **Intelligence depth** — hybrid semantic and lexical search, honest reinforcement, two-pass extraction, memory consolidation, and shadow cultivation
@@ -12,6 +12,7 @@ Future release notes should use the structure defined in [Release Notes](../proc
12
12
 
13
13
  ## Releases
14
14
 
15
+ - [v0.35.0 — Four Runtimes, One Successor](v0.35.0.md) — minor release closing CV21.DS8: the Gemini CLI integration no longer ships and Antigravity stands in its place, the gate met by DS7's live L4 on agy 1.4.2. Four runtimes — Pi, Claude Code, Codex, Antigravity — named everywhere; the cutoff recorded in full, with the four hook entries a Gemini CLI user deletes and v0.34.0 as the last release that carries the integration; a retired-surface guard keeps it removed. No migration.
15
16
  - [v0.34.0 — Codex Through the Canonical Plugin](v0.34.0.md) — minor release closing CV21.DS4: Codex runs Mirror through the canonical plugin, installed in two commands from a marketplace the package ships, measured live at L4 on Codex 0.163.0. The v0.32.1 wrapper wiring no longer ships; `runtime diagnose` and `runtime update` say when Codex's cached plugin copy is behind and name the re-add that brings it level. No migration.
16
17
  - [v0.33.4 — A Name Is a Key](v0.33.4.md) — patch release of three first-run corrections: two from a contributor's PR #43 and CR141, which its review opened. `init --help` created a home named `--help`, `init ../escaped` created one outside `.mirror-minds`, and `init "My User"` put a space into `MIRROR_USER`; a user name is now a kebab-case key like a journey slug or a persona id, refused otherwise with a name that would work and nothing created. `identity edit` on Windows opens Notepad when no editor is named. Existing homes untouched. No migration.
17
18
  - [v0.33.3 — Mirror Finds Itself Under Any Path](v0.33.3.md) — patch release of one Change Request, CR140, carried by a contributor's PR #42: Mirror read its own install path from `URL.pathname`, so a checkout or package under a path with a space or a non-ASCII character read as `Install: unknown`, the welcome card showed no update, and `runtime update` refused. The path is now a filesystem path, a guard keeps the idiom out, and an `unknown` install names the path it looked up from. On Windows, npm's global layout is recognized and npm runs with no shell, verified on Windows in CI for the two files they touch; there is still no Windows product. Affected installs update once by hand. No migration.
@@ -0,0 +1,199 @@
1
+ ---
2
+ digest: >
3
+ v0.35.0 is a minor release closing CV21.DS8: the Gemini CLI integration no
4
+ longer ships, and Antigravity stands in its place. Mirror Mind supports four
5
+ runtimes — Pi, Claude Code, Codex, Antigravity — and every document says so.
6
+ The removal waited for its gate: DS7 measured Antigravity at L4 on agy 1.4.2,
7
+ all five bar items, so a Gemini CLI user who moves loses nothing and gains
8
+ MCP. The cutoff is recorded here in full — what no longer exists, why, what
9
+ to do instead, what still works, and what a user who does nothing will see —
10
+ with the four hook entries to delete named, and v0.34.0 named as the last
11
+ release that carries the integration. A retired-surface guard keeps the
12
+ removal removed. No migration: homes, memories, journeys, and the
13
+ conversations logged as gemini_cli are untouched.
14
+ ---
15
+
16
+ # v0.35.0 — Four Runtimes, One Successor
17
+
18
+ **Date:** 2026-10-18
19
+
20
+ ## Highlights
21
+
22
+ - **Gemini CLI is no longer a Mirror runtime; Antigravity is its successor**
23
+ ([decision](../project/decisions.md#gemini-cli-is-retired-and-antigravity-is-its-successor)).
24
+ The four `.gemini/hooks` wrappers, the `gemini:*` hook family, the two
25
+ Gemini CLI steps `mirror init` printed, and the Gemini smoke no longer ship
26
+ (CV21.DS8.US1). The package's description and keywords, and every
27
+ runtime-facing document, name Pi, Claude Code, Codex, and Antigravity
28
+ (CV21.DS8.US2).
29
+ - **The gate was met first.** v0.33.0 gave notice that the integration would
30
+ be removed "only once Antigravity gives a Gemini CLI user what they have
31
+ now". CV21.DS7.US4 measured that on agy 1.4.2, live: all of Mirror's
32
+ skills, the Operating Instructions at session start, Mirror Mode context
33
+ injected once per user turn, every turn logged and the conversation closed,
34
+ setup in one step against Gemini CLI's two — and MCP reachable, which the
35
+ Gemini CLI wiring never had.
36
+ - **The cutoff is written where a user reads it before it happens to them**:
37
+ the section below, the Antigravity section of
38
+ [Getting Started](../getting-started.md#antigravity), and a
39
+ [troubleshooting entry](../process/troubleshooting.md#gemini-cli-runs-a-mirror-hook-that-no-longer-exists)
40
+ for the symptom a Gemini CLI user would meet.
41
+ - **A retired-surface guard keeps the removal removed.** `.gemini/` can never
42
+ ship again from this package: the row is enforced on every push and by the
43
+ release tripwire. That matters because `mirror init` once told users to
44
+ write absolute paths into that directory into their settings — a standing
45
+ instruction to execute whatever a later release might put there.
46
+
47
+ ## Where We Started
48
+
49
+ v0.32.1 shipped Gemini CLI as one of four supported runtimes, at L4 parity,
50
+ wired by two documented steps. On 2026-09-25 the Navigator retired it —
51
+ Google had set a deprecation date for the Gemini Code Assist backend for
52
+ individuals, and Antigravity, which imports Claude-format plugins, was the
53
+ successor — but the call lived in one story's notes while the release kept
54
+ advertising the runtime. The decision was recorded on 2026-10-13, with a
55
+ condition: the integration goes only behind a documented cutoff, and only
56
+ once Antigravity meets the bar a Gemini CLI user already had. DS7 built the
57
+ agy-native layer and measured the bar. DS8 is the cutoff.
58
+
59
+ ## What Changed
60
+
61
+ **The integration is gone from the package.** No `.gemini/hooks`, no `gemini`
62
+ entry in the wiring `mirror init` prints, no `gemini:*` hook names — an
63
+ unknown name is noted in `hooks.log` and the turn proceeds, as any unknown
64
+ name does. The Gemini smoke and the npm smoke's Gemini section retired with
65
+ it; the hook payload reader dropped the one field only Gemini CLI supplied.
66
+
67
+ **The docs say what ships.** Twenty files: README, Getting Started,
68
+ REFERENCE, the Operating Instructions and their plugin copy, the
69
+ runtime-interface spec, the welcome spec, the architecture, and the skills
70
+ that said "Pi and Gemini CLI". Where a runtime list, a wiring step, or a
71
+ parity table had Gemini CLI, it has Antigravity. Where a lesson says what
72
+ Gemini CLI taught — the payload-shape bug its smoke caught, the one-JSON-
73
+ object rule its hook entries kept — the lesson stays, in the past tense.
74
+ Getting Started's subscription table no longer carries a Gemini AI Pro row
75
+ and invents no Antigravity row: what Antigravity requires is Google's to
76
+ say, and Mirror needs only the `agy` command on your PATH.
77
+
78
+ **Antigravity's own documentation is unchanged** beyond taking Gemini CLI's
79
+ place: DS7 wrote it and measured it.
80
+
81
+ ## The Cutoff: Gemini CLI
82
+
83
+ **What you keep.** Everything that is yours. Your `~/.mirror-minds/<user>`
84
+ home, your identity, your memories, your journeys, and every conversation
85
+ you logged — including the ones labelled `gemini_cli`, which stay readable
86
+ in `mm-conversations` and `mm-recall` and are never migrated or relabelled.
87
+ Only the runtime that reads them changes.
88
+
89
+ **Removed.** The four `.gemini/hooks` wrappers (`session-start.sh`,
90
+ `log-user.sh`, `log-assistant.sh`, `session-end.sh`) and the `gemini:*` hook
91
+ family behind them; the two Gemini CLI steps `mirror init` printed (the
92
+ skills link, the four hooks merged into `~/.gemini/settings.json`); the
93
+ Gemini smoke; the runtime from the package's description and keywords and
94
+ from every runtime-facing document.
95
+
96
+ **Why.** Google deprecated the Gemini Code Assist backend for individuals
97
+ (2026-06-18). Antigravity is its successor and imports Claude-format
98
+ plugins — the one plugin Mirror already ships. The removal waited for
99
+ DS7.US4's measurement on agy 1.4.2: all five items of the bar DS12.TS3 set
100
+ PASS, with per-turn injection — the cost the decision feared losing —
101
+ carried by agy's `PreInvocation` hook. The one difference that did
102
+ materialize: agy has no session-end signal, so a conversation closes when
103
+ your next session starts rather than at exit (as Codex's does); logging
104
+ parity holds, and memories extract after the close.
105
+
106
+ **What to do instead.** Install Antigravity, then the one step `mirror init`
107
+ prints when it finds `agy` on your PATH:
108
+
109
+ ```bash
110
+ agy plugin install "<pkg>/plugins/mirror-mind" # <pkg> = $(npm root -g)/mirror-mind
111
+ ```
112
+
113
+ Your home carries over unchanged — nothing to export, nothing to re-seed.
114
+ Same home, same memory, different door. The skills carry the plugin's name:
115
+ `/mirror-mind:mm:mirror`, `/mirror-mind:mm:journeys`, and so on.
116
+
117
+ **What still works.** Gemini *models*. Memory extraction runs on
118
+ `google/gemini-2.5-flash-lite` through OpenRouter, and `consult` reaches the
119
+ `gemini` family as before. Those are model choices, not the runtime.
120
+
121
+ **If you do nothing.** After the update, the four `mirror-*` hook commands in
122
+ your `~/.gemini/settings.json` point at files that no longer exist, while the
123
+ links in `~/.gemini/skills/` still resolve — `.pi/skills` keeps shipping. So
124
+ Gemini CLI keeps Mirror's skills and loses logging and Mirror Mode injection.
125
+ Said plainly: your Gemini CLI sessions stop being logged and Mirror Mode
126
+ stops being injected, and nothing on screen says so. Measured on Gemini CLI
127
+ 0.61.0 in headless mode (`gemini -p`), 2026-10-18: a registered hook whose
128
+ command is missing is skipped silently; the session proceeds. The entries to
129
+ delete, in `~/.gemini/settings.json`: `mirror-session-start` (under
130
+ `SessionStart`), `mirror-log-user` (`BeforeAgent`), `mirror-log-assistant`
131
+ (`AfterAgent`), `mirror-session-end` (`SessionEnd`); and the `mm-*` links in
132
+ `~/.gemini/skills/`. Mirror never wrote those files and does not edit them.
133
+ Whether Antigravity also reads `~/.gemini/skills/` — which would show a
134
+ migrated user the stale links as duplicate skills — was not measured;
135
+ remove the links either way.
136
+
137
+ **To stay on Gemini CLI a while longer.** `npm install -g mirror-mind@0.34.0`
138
+ is the last release that carries the integration. The welcome card and
139
+ `runtime update` will keep offering the newer version every session; the pin
140
+ is a choice to make knowingly, not a quiet state.
141
+
142
+ ## Upgrading
143
+
144
+ ```bash
145
+ npm install -g mirror-mind@latest # or: mirror runtime update
146
+ ```
147
+
148
+ **If you used Gemini CLI:** read the cutoff above; the steps are in Getting
149
+ Started's Antigravity section. **Every other runtime:** nothing to do. No
150
+ migration. Existing homes, sessions, and memories are untouched.
151
+
152
+ ## Also In This Release
153
+
154
+ - **Typecheck is green on `main` again.** DS7.TS3 left one early return in
155
+ `hooks/agy.ts` without the field it had just added; `tsc --noEmit`, a CI
156
+ gate, refused since. Found by DS8.US1's validation, paid in its own commit.
157
+ - **The docs link check is green again.** The plugin's byte copy of the
158
+ Operating Instructions carries a root-relative link that cannot resolve
159
+ under `rules/`; the checker reported it on every `.md` push since DS7.US1.
160
+ The copy's links now resolve where the source lives — not skipped: a link
161
+ the source cannot resolve still fails through the copy.
162
+ - **The P1 redaction invariant outlives the runtime it was written against.**
163
+ "A failed hook never writes the prompt or the reply to `hooks.log`" was
164
+ tested through the Gemini CLI hooks; it now runs through the Claude-family
165
+ hooks every plugin runtime shares.
166
+
167
+ ## Conscious Exclusions
168
+
169
+ - **No second cutoff document.** CV22 needed `pending-cutoffs.md` for eleven
170
+ cutoffs in one release; DS8 has one, and this note is its record.
171
+ - **Mirror does not edit `~/.gemini/settings.json` or `~/.gemini/skills/`.**
172
+ It never wrote them. The note and the guidance name what to remove.
173
+ - **The Claude plugin smoke's negative probe** still cannot run on a machine
174
+ with a globally installed `mirror-hook` (CR142, D-031): pre-existing,
175
+ local-only, carried.
176
+ - **DS5, DS10, DS11** — user extensions on every runtime, Grok through the
177
+ plugin, the contract and docs refresh — are CV21's remaining stories, not
178
+ this release's.
179
+
180
+ ## What We Learned
181
+
182
+ A retirement that lives in a decision while the release advertises the
183
+ runtime has not happened; it happened here in three stories, in order:
184
+ remove, then make the tree say so, then tell the user — and the guard that
185
+ proves each step split by story, so nothing went red between them. The
186
+ sweep found what the inventory missed (a spec line, a history link), which
187
+ is what a guard is for. And the one fact this note most needed — what a
188
+ user who does nothing will see — turned out to be *nothing*: Gemini CLI
189
+ skips a missing hook in silence. That is the strongest reason the cutoff is
190
+ written where the user looks, not only here.
191
+
192
+ ## Next Horizon
193
+
194
+ DS8 closes with this release, and with it the runtime set CV21 set out to
195
+ reach: the canonical plugin on Claude Code, Codex, and Antigravity, Pi
196
+ through its extension. What remains in CV21 is pulled deliberately from the
197
+ CV21 index: user extensions on every runtime (DS5), Grok through the plugin
198
+ (DS10), and the contract and docs refresh that folds all of it into one
199
+ account (DS11).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mirror-mind",
3
- "version": "0.34.0",
4
- "description": "Mirror Mind: a local-first memory and identity framework for agentic AI runtimes (Pi, Claude Code, Gemini CLI, Codex)",
3
+ "version": "0.35.0",
4
+ "description": "Mirror Mind: a local-first memory and identity framework for agentic AI runtimes (Pi, Claude Code, Codex, Antigravity)",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -15,7 +15,6 @@
15
15
  "agent",
16
16
  "pi-package",
17
17
  "claude-code",
18
- "gemini-cli",
19
18
  "codex",
20
19
  "sqlite",
21
20
  "local-first"
@@ -36,7 +35,6 @@
36
35
  ".pi/extensions",
37
36
  ".pi/skills",
38
37
  ".claude/hooks",
39
- ".gemini/hooks",
40
38
  "plugins/mirror-mind",
41
39
  ".agents/plugins",
42
40
  "AGENTS.md",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirror-mind",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "Mirror Mind — local-first memory and identity for agentic runtimes.",
5
5
  "author": {
6
6
  "name": "Mirror Mind"
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ # Mirror Mind hook wrapper — GENERATED by scripts/generate_hook_wrappers.sh.
3
+ # Edit the generator, never this file: every agy wrapper shares these bytes
4
+ # and differs only in the hook name and its stdin gate (CV21.DS7.TS2).
5
+ set -uo pipefail
6
+ HOOK="agy:pre-invocation"
7
+ GATE='"invocationNum"[[:space:]]*:[[:space:]]*-?[0-9]*[1-9]'
8
+
9
+ # agy sends the payload on stdin and blocks its agent loop on this process.
10
+ # Read the payload once; when the gate says this invocation is not ours,
11
+ # answer the empty object without resolving Node at all.
12
+ PAYLOAD="$(cat 2>/dev/null || true)"
13
+ if [ -n "$GATE" ] && printf '%s' "$PAYLOAD" | grep -qE "$GATE"; then
14
+ printf '{}\n'
15
+ exit 0
16
+ fi
17
+
18
+ # This wrapper is the Claude plugin's, and a plugin is copied into Claude
19
+ # Code's own cache: no relative path from here reaches the Mirror tree. It
20
+ # runs the installed `mirror-hook` bin instead, and reads no tree .env.
21
+ REPO_ROOT=""
22
+
23
+ # A hook never fails the user's turn, and never fails in silence: when it
24
+ # cannot do its job it appends ONE line to <mirror home>/hooks.log and exits
25
+ # 0 — after answering agy's contract, so the agent loop never stalls.
26
+ env_file_value() {
27
+ [ -f "$2" ] || return 0
28
+ sed -n -E "s/^[[:space:]]*(export[[:space:]]+)?$1[[:space:]]*=[[:space:]]*//p" "$2" \
29
+ | tail -n 1 | tr -d "\"'\r"
30
+ }
31
+ env_value() {
32
+ local value=""
33
+ if [ -n "${!1+set}" ]; then printf '%s' "${!1}"; return; fi
34
+ [ -z "$REPO_ROOT" ] || value="$(env_file_value "$1" "$REPO_ROOT/.env")"
35
+ [ -n "$value" ] || value="$(env_file_value "$1" "${XDG_CONFIG_HOME:-$HOME/.config}/mirror/env")"
36
+ printf '%s' "$value"
37
+ }
38
+ note() {
39
+ local home user
40
+ home="$(env_value MIRROR_HOME)"
41
+ user="$(env_value MIRROR_USER)"
42
+ if [ -n "$home" ]; then
43
+ home="${home/#\~/$HOME}"
44
+ elif [ -n "$user" ]; then
45
+ home="$HOME/.mirror-minds/$user"
46
+ [ -d "$home" ] || [ ! -d "$HOME/.mirror/$user" ] || home="$HOME/.mirror/$user"
47
+ else
48
+ return 0
49
+ fi
50
+ mkdir -p "$home" 2>/dev/null \
51
+ && printf '%s %s: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$HOOK" "$1" >> "$home/hooks.log" 2>/dev/null
52
+ }
53
+
54
+ # Find the bin. MIRROR_BIN names the directory holding `mirror` and
55
+ # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
56
+ # then the PATH; then the global bin directories a GUI launch leaves off it.
57
+ ENTRY=""
58
+ [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
59
+ [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
60
+ for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
61
+ [ -n "$ENTRY" ] && break
62
+ [ -x "$candidate/mirror-hook" ] && ENTRY="$candidate/mirror-hook"
63
+ done
64
+
65
+ if [ -z "$ENTRY" ]; then
66
+ note "mirror-hook not found on PATH; hook skipped. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory."
67
+ exit 0
68
+ fi
69
+
70
+ NODE="${MIRROR_NODE:-}"
71
+ if [ -z "$NODE" ] || [ ! -x "$NODE" ]; then
72
+ NODE="$(command -v node 2>/dev/null || true)"
73
+ fi
74
+ for candidate in "$(dirname "$ENTRY")/node" "$HOME/.nvm/current/bin/node" /opt/homebrew/bin/node /usr/local/bin/node; do
75
+ [ -n "$NODE" ] && break
76
+ [ -x "$candidate" ] && NODE="$candidate"
77
+ done
78
+
79
+ if [ -z "$NODE" ]; then
80
+ printf '{}\n'
81
+ note "node not found on PATH; hook skipped. Set MIRROR_NODE."
82
+ exit 0
83
+ fi
84
+
85
+ # The payload was consumed by the gate read; re-feed it to the entry.
86
+ printf '%s' "$PAYLOAD" | "$NODE" "$ENTRY" "$HOOK" "$@"
87
+ status=$?
88
+ if [ "$status" -ne 0 ]; then
89
+ printf '{}\n'
90
+ note "node exited $status before the hook finished ($NODE, $("$NODE" --version 2>/dev/null || echo 'version unknown')); Mirror needs Node 24 or later. Set MIRROR_NODE."
91
+ fi
92
+ exit 0
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ # Mirror Mind hook wrapper — GENERATED by scripts/generate_hook_wrappers.sh.
3
+ # Edit the generator, never this file: every agy wrapper shares these bytes
4
+ # and differs only in the hook name and its stdin gate (CV21.DS7.TS2).
5
+ set -uo pipefail
6
+ HOOK="agy:session-start"
7
+ GATE=''
8
+
9
+ # agy sends the payload on stdin and blocks its agent loop on this process.
10
+ # Read the payload once; when the gate says this invocation is not ours,
11
+ # answer the empty object without resolving Node at all.
12
+ PAYLOAD="$(cat 2>/dev/null || true)"
13
+ if [ -n "$GATE" ] && printf '%s' "$PAYLOAD" | grep -qE "$GATE"; then
14
+ printf '{}\n'
15
+ exit 0
16
+ fi
17
+
18
+ # This wrapper is the Claude plugin's, and a plugin is copied into Claude
19
+ # Code's own cache: no relative path from here reaches the Mirror tree. It
20
+ # runs the installed `mirror-hook` bin instead, and reads no tree .env.
21
+ REPO_ROOT=""
22
+
23
+ # A hook never fails the user's turn, and never fails in silence: when it
24
+ # cannot do its job it appends ONE line to <mirror home>/hooks.log and exits
25
+ # 0 — after answering agy's contract, so the agent loop never stalls.
26
+ env_file_value() {
27
+ [ -f "$2" ] || return 0
28
+ sed -n -E "s/^[[:space:]]*(export[[:space:]]+)?$1[[:space:]]*=[[:space:]]*//p" "$2" \
29
+ | tail -n 1 | tr -d "\"'\r"
30
+ }
31
+ env_value() {
32
+ local value=""
33
+ if [ -n "${!1+set}" ]; then printf '%s' "${!1}"; return; fi
34
+ [ -z "$REPO_ROOT" ] || value="$(env_file_value "$1" "$REPO_ROOT/.env")"
35
+ [ -n "$value" ] || value="$(env_file_value "$1" "${XDG_CONFIG_HOME:-$HOME/.config}/mirror/env")"
36
+ printf '%s' "$value"
37
+ }
38
+ note() {
39
+ local home user
40
+ home="$(env_value MIRROR_HOME)"
41
+ user="$(env_value MIRROR_USER)"
42
+ if [ -n "$home" ]; then
43
+ home="${home/#\~/$HOME}"
44
+ elif [ -n "$user" ]; then
45
+ home="$HOME/.mirror-minds/$user"
46
+ [ -d "$home" ] || [ ! -d "$HOME/.mirror/$user" ] || home="$HOME/.mirror/$user"
47
+ else
48
+ return 0
49
+ fi
50
+ mkdir -p "$home" 2>/dev/null \
51
+ && printf '%s %s: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$HOOK" "$1" >> "$home/hooks.log" 2>/dev/null
52
+ }
53
+
54
+ # Find the bin. MIRROR_BIN names the directory holding `mirror` and
55
+ # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
56
+ # then the PATH; then the global bin directories a GUI launch leaves off it.
57
+ ENTRY=""
58
+ [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
59
+ [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
60
+ for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
61
+ [ -n "$ENTRY" ] && break
62
+ [ -x "$candidate/mirror-hook" ] && ENTRY="$candidate/mirror-hook"
63
+ done
64
+
65
+ if [ -z "$ENTRY" ]; then
66
+ note "mirror-hook not found on PATH; hook skipped. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory."
67
+ exit 0
68
+ fi
69
+
70
+ NODE="${MIRROR_NODE:-}"
71
+ if [ -z "$NODE" ] || [ ! -x "$NODE" ]; then
72
+ NODE="$(command -v node 2>/dev/null || true)"
73
+ fi
74
+ for candidate in "$(dirname "$ENTRY")/node" "$HOME/.nvm/current/bin/node" /opt/homebrew/bin/node /usr/local/bin/node; do
75
+ [ -n "$NODE" ] && break
76
+ [ -x "$candidate" ] && NODE="$candidate"
77
+ done
78
+
79
+ if [ -z "$NODE" ]; then
80
+ printf '{}\n'
81
+ note "node not found on PATH; hook skipped. Set MIRROR_NODE."
82
+ exit 0
83
+ fi
84
+
85
+ # The payload was consumed by the gate read; re-feed it to the entry.
86
+ printf '%s' "$PAYLOAD" | "$NODE" "$ENTRY" "$HOOK" "$@"
87
+ status=$?
88
+ if [ "$status" -ne 0 ]; then
89
+ printf '{}\n'
90
+ note "node exited $status before the hook finished ($NODE, $("$NODE" --version 2>/dev/null || echo 'version unknown')); Mirror needs Node 24 or later. Set MIRROR_NODE."
91
+ fi
92
+ exit 0
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ # Mirror Mind hook wrapper — GENERATED by scripts/generate_hook_wrappers.sh.
3
+ # Edit the generator, never this file: every agy wrapper shares these bytes
4
+ # and differs only in the hook name and its stdin gate (CV21.DS7.TS2).
5
+ set -uo pipefail
6
+ HOOK="agy:stop"
7
+ GATE=''
8
+
9
+ # agy sends the payload on stdin and blocks its agent loop on this process.
10
+ # Read the payload once; when the gate says this invocation is not ours,
11
+ # answer the empty object without resolving Node at all.
12
+ PAYLOAD="$(cat 2>/dev/null || true)"
13
+ if [ -n "$GATE" ] && printf '%s' "$PAYLOAD" | grep -qE "$GATE"; then
14
+ printf '{}\n'
15
+ exit 0
16
+ fi
17
+
18
+ # This wrapper is the Claude plugin's, and a plugin is copied into Claude
19
+ # Code's own cache: no relative path from here reaches the Mirror tree. It
20
+ # runs the installed `mirror-hook` bin instead, and reads no tree .env.
21
+ REPO_ROOT=""
22
+
23
+ # A hook never fails the user's turn, and never fails in silence: when it
24
+ # cannot do its job it appends ONE line to <mirror home>/hooks.log and exits
25
+ # 0 — after answering agy's contract, so the agent loop never stalls.
26
+ env_file_value() {
27
+ [ -f "$2" ] || return 0
28
+ sed -n -E "s/^[[:space:]]*(export[[:space:]]+)?$1[[:space:]]*=[[:space:]]*//p" "$2" \
29
+ | tail -n 1 | tr -d "\"'\r"
30
+ }
31
+ env_value() {
32
+ local value=""
33
+ if [ -n "${!1+set}" ]; then printf '%s' "${!1}"; return; fi
34
+ [ -z "$REPO_ROOT" ] || value="$(env_file_value "$1" "$REPO_ROOT/.env")"
35
+ [ -n "$value" ] || value="$(env_file_value "$1" "${XDG_CONFIG_HOME:-$HOME/.config}/mirror/env")"
36
+ printf '%s' "$value"
37
+ }
38
+ note() {
39
+ local home user
40
+ home="$(env_value MIRROR_HOME)"
41
+ user="$(env_value MIRROR_USER)"
42
+ if [ -n "$home" ]; then
43
+ home="${home/#\~/$HOME}"
44
+ elif [ -n "$user" ]; then
45
+ home="$HOME/.mirror-minds/$user"
46
+ [ -d "$home" ] || [ ! -d "$HOME/.mirror/$user" ] || home="$HOME/.mirror/$user"
47
+ else
48
+ return 0
49
+ fi
50
+ mkdir -p "$home" 2>/dev/null \
51
+ && printf '%s %s: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$HOOK" "$1" >> "$home/hooks.log" 2>/dev/null
52
+ }
53
+
54
+ # Find the bin. MIRROR_BIN names the directory holding `mirror` and
55
+ # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
56
+ # then the PATH; then the global bin directories a GUI launch leaves off it.
57
+ ENTRY=""
58
+ [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
59
+ [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
60
+ for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
61
+ [ -n "$ENTRY" ] && break
62
+ [ -x "$candidate/mirror-hook" ] && ENTRY="$candidate/mirror-hook"
63
+ done
64
+
65
+ if [ -z "$ENTRY" ]; then
66
+ note "mirror-hook not found on PATH; hook skipped. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory."
67
+ exit 0
68
+ fi
69
+
70
+ NODE="${MIRROR_NODE:-}"
71
+ if [ -z "$NODE" ] || [ ! -x "$NODE" ]; then
72
+ NODE="$(command -v node 2>/dev/null || true)"
73
+ fi
74
+ for candidate in "$(dirname "$ENTRY")/node" "$HOME/.nvm/current/bin/node" /opt/homebrew/bin/node /usr/local/bin/node; do
75
+ [ -n "$NODE" ] && break
76
+ [ -x "$candidate" ] && NODE="$candidate"
77
+ done
78
+
79
+ if [ -z "$NODE" ]; then
80
+ printf '{}\n'
81
+ note "node not found on PATH; hook skipped. Set MIRROR_NODE."
82
+ exit 0
83
+ fi
84
+
85
+ # The payload was consumed by the gate read; re-feed it to the entry.
86
+ printf '%s' "$PAYLOAD" | "$NODE" "$ENTRY" "$HOOK" "$@"
87
+ status=$?
88
+ if [ "$status" -ne 0 ]; then
89
+ printf '{}\n'
90
+ note "node exited $status before the hook finished ($NODE, $("$NODE" --version 2>/dev/null || echo 'version unknown')); Mirror needs Node 24 or later. Set MIRROR_NODE."
91
+ fi
92
+ exit 0
@@ -0,0 +1,26 @@
1
+ {
2
+ "mirror-mind": {
3
+ "enabled": true,
4
+ "SessionStart": [
5
+ {
6
+ "type": "command",
7
+ "command": "agy/session-start.sh",
8
+ "timeout": 60
9
+ }
10
+ ],
11
+ "PreInvocation": [
12
+ {
13
+ "type": "command",
14
+ "command": "agy/pre-invocation.sh",
15
+ "timeout": 15
16
+ }
17
+ ],
18
+ "Stop": [
19
+ {
20
+ "type": "command",
21
+ "command": "agy/stop.sh",
22
+ "timeout": 30
23
+ }
24
+ ]
25
+ }
26
+ }