mirror-mind 0.40.0 → 0.40.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.
@@ -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.40.1 — The Bins You Named, or Why Not](v0.40.1.md) — patch release of CR197: the plugin's hook wrappers and MCP entry use `MIRROR_BIN` only as an absolute path to a directory holding the bin they need, the rule `MIRROR_NODE` follows since v0.39.5; a relative value is never used, so a project's own `bin/mirror-hook` cannot run, and a refused value is named with its reason, once a day in `hooks.log` and on the MCP entry's stderr; the forward-slash `C:/…` form Windows machines set counts as absolute, in CI under Git Bash. Also carries CV9's re-baseline (documents only). No migration.
15
16
  - [v0.40.0 — A Reply Stays in Its Own Conversation, and a Journey Can Go](v0.40.0.md) — minor release closing the first release train: CR151, a session's transcript fills only the conversation that session is bound to, each reply stamped with its transcript time, where it used to fill every open reply-less conversation in the transcript's window, another session's included; a transcript no session owns backfills nothing; and CR200, previewed as v0.40.0-next.1, `mirror-mind/desktop` deletes a Journey with every record that belongs to it, by digest, behind a verified backup. Update every install that has a home. No migration.
16
17
  - [v0.40.0-next.1 — A Journey Can Go, Showing What Goes With It](v0.40.0-next.1.md) — preview under npm's `next`, read by no updater, for Nautilus for Mirror (the release train): CR200, `mirror-mind/desktop` gains `previewJourneyDeletion` and `deleteJourneyWithRecords`, deleting a Journey with every record that belongs to it after a preview that counts it, by digest, behind a verified backup; child Journeys refuse. Terminal users receive it with v0.40.0. No migration.
17
18
  - [v0.39.5 — The Node You Named, or Why Not](v0.39.5.md) — patch release of CR192, from PR #47's thread (@rodrigoimmaginario): the hook wrappers, the plugin's MCP entry, and `runtime diagnose` use `MIRROR_NODE` only as an absolute path to an executable file, as the Pi extension does; a relative value is never run, and a refused one is named with its reason, once a day in `hooks.log` and on the MCP entry's stderr; the Windows installer's drive-letter path counts as absolute, in CI under Git Bash and on the installer. No migration.
@@ -0,0 +1,120 @@
1
+ ---
2
+ digest: >
3
+ v0.40.1 is a patch release of one Change Request, CR197. The plugin's hook wrappers and
4
+ MCP entry now use `MIRROR_BIN` only when it is an absolute path to a directory holding the
5
+ bin they need, the rule `MIRROR_NODE` follows since v0.39.5. A relative value could make
6
+ a plugin hook run a JavaScript file from the project the runtime was opened in, and a
7
+ value that no longer held `mirror-hook` was dropped without a word. Now neither is used,
8
+ and the refused value is named with its reason. The forward-slash drive form Windows
9
+ machines set for the Claude Code forwarders counts as absolute, proven in CI under Git
10
+ Bash. No schema change, no migration, and no model or prompt change.
11
+ ---
12
+
13
+ # v0.40.1 — The Bins You Named, or Why Not
14
+
15
+ **Date:** 2026-11-07
16
+
17
+ ## Highlights
18
+
19
+ - **A relative `MIRROR_BIN` is never used**
20
+ ([CR197](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr197-the-hook-wrappers-and-the-mcp-locator-accept-a-relative-mirror-bin.md)).
21
+ `MIRROR_BIN` names the directory holding `mirror` and `mirror-hook`, for the plugin's
22
+ hook wrappers and MCP entry, which a runtime copies into its own cache. A runtime starts
23
+ them from the project it was opened in, and both readers took any value that passed
24
+ `[ -x ]` there: with `MIRROR_BIN=bin`, Node ran the project's `bin/mirror-hook` as
25
+ JavaScript on every plugin hook, and its `bin/mirror` at every MCP server start. Both
26
+ readers now use the value only as an absolute path to a directory holding the bin they
27
+ need as an executable file.
28
+ - **A value that is not used is named, with its reason.** A value that no longer held the
29
+ bin, such as a prefix left behind by a Node switch, used to be dropped without a word.
30
+ Now the hook wrappers write
31
+ `MIRROR_BIN=bin is relative; searching the PATH and the usual places instead` to
32
+ `<mirror home>/hooks.log`, once a UTC day for the same value, and the MCP entry writes
33
+ the same message to its stderr. The reasons are `relative`, `missing`,
34
+ `not a directory`, and `a directory without an executable mirror-hook` (or `mirror`).
35
+ The search then goes on through the PATH and the usual places, as before.
36
+ - **The Windows form counts as absolute.** The rule uses `MIRROR_NODE`'s pattern, so
37
+ `C:/Users/<you>/.mirror/bin`, the forward-slash form Windows machines set for the Claude
38
+ Code forwarders, is absolute, and a drive-relative `C:bin` is refused as `relative`. The
39
+ Windows CI job runs a plugin wrapper with that form under Git Bash.
40
+ - No schema change, no migration, and no model or prompt change.
41
+
42
+ ## Where We Started
43
+
44
+ v0.39.5 gave `MIRROR_NODE` its rule
45
+ ([CR192](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr192-the-hook-wrappers-and-the-mcp-locator-accept-a-relative-mirror-node.md)),
46
+ and its review found the same exposure one variable over: with `MIRROR_BIN=bin`, the
47
+ plugin's prompt hook made Node run the project's `bin/mirror-hook`, probed on 2026-11-04.
48
+ It was captured as CR197 and left out of v0.39.5 on purpose. On 2026-11-07 the CV9
49
+ re-baseline decided to ship it as a patch ahead of the 1.0 gate.
50
+
51
+ ## What Changed
52
+
53
+ **One rule, two variables.** The plugin's bin and agy wrappers, generated by
54
+ `scripts/generate_hook_wrappers.sh`, and the MCP locator in
55
+ `ts/src/guards/claudePlugin.ts` test `MIRROR_BIN` with `ABSOLUTE_PATH_CASE`, the pattern
56
+ `MIRROR_NODE` uses, and a test fails if the copies drift. The bin search now carries the
57
+ pattern's backslashes, so the generator splices it by the text around its placeholder, as
58
+ it does the other shared fragments. The eight plugin wrappers are regenerated; the five
59
+ tree-form wrappers under `.claude/hooks/` read no `MIRROR_BIN` and are unchanged.
60
+
61
+ **A refusal that cannot forge a line.** Line breaks are removed from the value before it is
62
+ logged, as for `MIRROR_NODE`. The locator reads `$MIRROR_BIN` once, where it read it three
63
+ times, because Grok pastes a set variable's value into the MCP arguments it loads.
64
+
65
+ **Tests and CI.** The new tests ran the project's planted `bin/mirror-hook` and
66
+ `bin/mirror` before the fix. They cover each reason, the daily rule, a forged line break,
67
+ both variables refused in one run, the agy form, and a valid value that outranks the PATH.
68
+ The Windows job's Git Bash step runs a plugin wrapper over a prefix shaped like npm's on
69
+ Windows, with `MIRROR_BIN` in the forward-slash drive form.
70
+
71
+ **Docs.** The rule is stated once, in full, in the
72
+ [configuration reference](../reference/configuration.md#hook-bin-resolution), and
73
+ `REFERENCE.md` points there. Troubleshooting shows the refusal line and the fix, and no
74
+ longer says `MIRROR_BIN` is honored as given. Getting Started says the directory is given
75
+ as an absolute path.
76
+
77
+ Validated by the Navigator in a scratch home: two runs of the plugin's prompt hook with
78
+ `MIRROR_BIN=bin` wrote one line,
79
+
80
+ ```text
81
+ 2026-11-07T19:11:56Z claude:user-prompt: MIRROR_BIN=bin is relative; searching the PATH and the usual places instead
82
+ ```
83
+
84
+ the MCP entry printed the same refusal and answered `initialize`, the project's planted
85
+ bins never ran, the prompt was logged, and `/opt/homebrew/bin`, the value his own Claude
86
+ settings set, was used with no refusal.
87
+
88
+ **Also in this release.** CV9's re-baseline
89
+ ([CV9.DS10](../project/roadmap/cv9-mirror-1-0/cv9-ds10-the-1-0-done-condition-describes-the-system-that-exists/index.md)),
90
+ which changes documents only: the 1.0 done condition is now a table of claims, each with
91
+ the story that owns it.
92
+
93
+ ## Conscious Exclusions
94
+
95
+ - `runtime diagnose` does not name a refused `MIRROR_BIN`. It counts the hooks' line as
96
+ `hook_failures_recorded`, and the value and its reason are in `hooks.log`. Deferred to
97
+ [CR198](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr198-runtime-diagnose-does-not-name-a-refused-mirror-node.md),
98
+ which now covers both variables.
99
+ - A relative entry in your own `PATH`, such as `.`: `command -v` finds a project's file
100
+ through it for every command you run, not only Mirror's.
101
+ - An absolute value is trusted wherever it points: whoever sets it chooses what runs.
102
+ - Rodrigo Immaginario's installer run against `main` was asked for and does not gate this
103
+ tag; the Windows CI job is this release's Windows evidence.
104
+
105
+ ## Upgrading
106
+
107
+ ```bash
108
+ mirror runtime update
109
+ ```
110
+
111
+ If you set `MIRROR_BIN` by hand, make it the absolute path of the directory holding
112
+ `mirror-hook`, `$(npm prefix -g)/bin`: a relative one is now refused, with one line a day
113
+ in `hooks.log`. A plugin copied into a runtime's cache keeps the old wrappers until the
114
+ runtime takes the update; `runtime diagnose` reports a copy that is behind.
115
+
116
+ ## Next Horizon
117
+
118
+ CR198, so that `runtime diagnose` names a refused `MIRROR_NODE` or `MIRROR_BIN`; CV9.DS11,
119
+ journeys that can be reshaped without losing a record; and the rest of the way to 1.0 in
120
+ the [CV9 index](../project/roadmap/cv9-mirror-1-0/index.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirror-mind",
3
- "version": "0.40.0",
3
+ "version": "0.40.1",
4
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirror-mind",
3
- "version": "0.40.0",
3
+ "version": "0.40.1",
4
4
  "description": "Mirror Mind — local-first memory and identity for agentic runtimes.",
5
5
  "author": {
6
6
  "name": "Mirror Mind"
@@ -10,7 +10,7 @@
10
10
  "command": "/bin/sh",
11
11
  "args": [
12
12
  "-c",
13
- "mmbin=\"\"; [ -n \"$MIRROR_BIN\" ] && [ -x \"$MIRROR_BIN/mirror\" ] && mmbin=\"$MIRROR_BIN/mirror\"; [ -n \"$mmbin\" ] || mmbin=\"$(command -v mirror 2>/dev/null)\"; for mmdir in \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmbin\" ] && break; [ -x \"$mmdir/mirror\" ] && mmbin=\"$mmdir/mirror\"; done; [ -n \"$mmbin\" ] || { echo \"mirror-mind: mirror not found on PATH; the MCP server cannot start. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory.\" >&2; exit 1; }; mmjs=\"$(dirname \"$mmbin\")/node_modules/mirror-mind/bin/mirror.js\"; [ -f \"$mmjs\" ] && mmbin=\"$mmjs\"; mmnode=\"$MIRROR_NODE\"; mmwhy=\"\"; [ -n \"$mmnode\" ] && case \"$mmnode\" in /*|\\\\*|[A-Za-z]:[\\\\/]*) if [ -d \"$mmnode\" ]; then mmwhy=\"a directory\"; elif [ ! -e \"$mmnode\" ]; then mmwhy=\"missing\"; elif [ ! -f \"$mmnode\" ] || [ ! -x \"$mmnode\" ]; then mmwhy=\"not executable\"; fi ;; *) mmwhy=\"relative\" ;; esac; [ -n \"$mmwhy\" ] && { printf 'mirror-mind: MIRROR_NODE=%s is %s; searching the PATH and the usual places instead\\n' \"$(printf '%s' \"$mmnode\" | tr -d '\\r\\n')\" \"$mmwhy\" >&2; mmnode=\"\"; }; [ -n \"$mmnode\" ] || mmnode=\"$(command -v node 2>/dev/null)\"; for mmdir in \"$(dirname \"$mmbin\")\" \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmnode\" ] && break; [ -x \"$mmdir/node\" ] && mmnode=\"$mmdir/node\"; done; [ -n \"$mmnode\" ] || { echo \"mirror-mind: node not found on PATH; the MCP server cannot start. Set MIRROR_NODE.\" >&2; exit 1; }; exec \"$mmnode\" \"$mmbin\" mcp"
13
+ "mmbin=\"\"; mmbindir=\"$MIRROR_BIN\"; mmwhy=\"\"; [ -n \"$mmbindir\" ] && case \"$mmbindir\" in /*|\\\\*|[A-Za-z]:[\\\\/]*) if [ ! -e \"$mmbindir\" ]; then mmwhy=\"missing\"; elif [ ! -d \"$mmbindir\" ]; then mmwhy=\"not a directory\"; elif [ ! -f \"$mmbindir/mirror\" ] || [ ! -x \"$mmbindir/mirror\" ]; then mmwhy=\"a directory without an executable mirror\"; else mmbin=\"$mmbindir/mirror\"; fi ;; *) mmwhy=\"relative\" ;; esac; [ -n \"$mmwhy\" ] && printf 'mirror-mind: MIRROR_BIN=%s is %s; searching the PATH and the usual places instead\\n' \"$(printf '%s' \"$mmbindir\" | tr -d '\\r\\n')\" \"$mmwhy\" >&2; [ -n \"$mmbin\" ] || mmbin=\"$(command -v mirror 2>/dev/null)\"; for mmdir in \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmbin\" ] && break; [ -x \"$mmdir/mirror\" ] && mmbin=\"$mmdir/mirror\"; done; [ -n \"$mmbin\" ] || { echo \"mirror-mind: mirror not found on PATH; the MCP server cannot start. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory.\" >&2; exit 1; }; mmjs=\"$(dirname \"$mmbin\")/node_modules/mirror-mind/bin/mirror.js\"; [ -f \"$mmjs\" ] && mmbin=\"$mmjs\"; mmnode=\"$MIRROR_NODE\"; mmwhy=\"\"; [ -n \"$mmnode\" ] && case \"$mmnode\" in /*|\\\\*|[A-Za-z]:[\\\\/]*) if [ -d \"$mmnode\" ]; then mmwhy=\"a directory\"; elif [ ! -e \"$mmnode\" ]; then mmwhy=\"missing\"; elif [ ! -f \"$mmnode\" ] || [ ! -x \"$mmnode\" ]; then mmwhy=\"not executable\"; fi ;; *) mmwhy=\"relative\" ;; esac; [ -n \"$mmwhy\" ] && { printf 'mirror-mind: MIRROR_NODE=%s is %s; searching the PATH and the usual places instead\\n' \"$(printf '%s' \"$mmnode\" | tr -d '\\r\\n')\" \"$mmwhy\" >&2; mmnode=\"\"; }; [ -n \"$mmnode\" ] || mmnode=\"$(command -v node 2>/dev/null)\"; for mmdir in \"$(dirname \"$mmbin\")\" \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmnode\" ] && break; [ -x \"$mmdir/node\" ] && mmnode=\"$mmdir/node\"; done; [ -n \"$mmnode\" ] || { echo \"mirror-mind: node not found on PATH; the MCP server cannot start. Set MIRROR_NODE.\" >&2; exit 1; }; exec \"$mmnode\" \"$mmbin\" mcp"
14
14
  ]
15
15
  }
16
16
  }
@@ -62,10 +62,31 @@ note() {
62
62
  }
63
63
 
64
64
  # Find the bin. MIRROR_BIN names the directory holding `mirror` and
65
- # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
66
- # then the PATH; then the global bin directories a GUI launch leaves off it.
65
+ # `mirror-hook` (`$(npm prefix -g)/bin`); then the PATH; then the global bin
66
+ # directories a GUI launch leaves off it.
67
+ #
68
+ # MIRROR_BIN is used only as an absolute path to a directory holding an
69
+ # executable `mirror-hook`, by MIRROR_NODE's rule and pattern (CR197): a
70
+ # relative one would resolve against the project the runtime spawned this hook
71
+ # from, and Node would run that project's file. Anything else is noted with its
72
+ # reason, in MIRROR_NODE's words, once a day, and the search goes on without it.
67
73
  ENTRY=""
68
- [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
74
+ if [ -n "${MIRROR_BIN:-}" ]; then
75
+ refused=""
76
+ case "$MIRROR_BIN" in
77
+ /*|\\*|[A-Za-z]:[\\/]*)
78
+ if [ ! -e "$MIRROR_BIN" ]; then refused="missing"
79
+ elif [ ! -d "$MIRROR_BIN" ]; then refused="not a directory"
80
+ elif [ ! -f "$MIRROR_BIN/mirror-hook" ] || [ ! -x "$MIRROR_BIN/mirror-hook" ]; then refused="a directory without an executable mirror-hook"
81
+ else ENTRY="$MIRROR_BIN/mirror-hook"
82
+ fi
83
+ ;;
84
+ *) refused="relative" ;;
85
+ esac
86
+ if [ -n "$refused" ]; then
87
+ note "MIRROR_BIN=${MIRROR_BIN//[$'\r\n']/} is $refused; searching the PATH and the usual places instead" daily
88
+ fi
89
+ fi
69
90
  [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
70
91
  for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
71
92
  [ -n "$ENTRY" ] && break
@@ -62,10 +62,31 @@ note() {
62
62
  }
63
63
 
64
64
  # Find the bin. MIRROR_BIN names the directory holding `mirror` and
65
- # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
66
- # then the PATH; then the global bin directories a GUI launch leaves off it.
65
+ # `mirror-hook` (`$(npm prefix -g)/bin`); then the PATH; then the global bin
66
+ # directories a GUI launch leaves off it.
67
+ #
68
+ # MIRROR_BIN is used only as an absolute path to a directory holding an
69
+ # executable `mirror-hook`, by MIRROR_NODE's rule and pattern (CR197): a
70
+ # relative one would resolve against the project the runtime spawned this hook
71
+ # from, and Node would run that project's file. Anything else is noted with its
72
+ # reason, in MIRROR_NODE's words, once a day, and the search goes on without it.
67
73
  ENTRY=""
68
- [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
74
+ if [ -n "${MIRROR_BIN:-}" ]; then
75
+ refused=""
76
+ case "$MIRROR_BIN" in
77
+ /*|\\*|[A-Za-z]:[\\/]*)
78
+ if [ ! -e "$MIRROR_BIN" ]; then refused="missing"
79
+ elif [ ! -d "$MIRROR_BIN" ]; then refused="not a directory"
80
+ elif [ ! -f "$MIRROR_BIN/mirror-hook" ] || [ ! -x "$MIRROR_BIN/mirror-hook" ]; then refused="a directory without an executable mirror-hook"
81
+ else ENTRY="$MIRROR_BIN/mirror-hook"
82
+ fi
83
+ ;;
84
+ *) refused="relative" ;;
85
+ esac
86
+ if [ -n "$refused" ]; then
87
+ note "MIRROR_BIN=${MIRROR_BIN//[$'\r\n']/} is $refused; searching the PATH and the usual places instead" daily
88
+ fi
89
+ fi
69
90
  [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
70
91
  for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
71
92
  [ -n "$ENTRY" ] && break
@@ -62,10 +62,31 @@ note() {
62
62
  }
63
63
 
64
64
  # Find the bin. MIRROR_BIN names the directory holding `mirror` and
65
- # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
66
- # then the PATH; then the global bin directories a GUI launch leaves off it.
65
+ # `mirror-hook` (`$(npm prefix -g)/bin`); then the PATH; then the global bin
66
+ # directories a GUI launch leaves off it.
67
+ #
68
+ # MIRROR_BIN is used only as an absolute path to a directory holding an
69
+ # executable `mirror-hook`, by MIRROR_NODE's rule and pattern (CR197): a
70
+ # relative one would resolve against the project the runtime spawned this hook
71
+ # from, and Node would run that project's file. Anything else is noted with its
72
+ # reason, in MIRROR_NODE's words, once a day, and the search goes on without it.
67
73
  ENTRY=""
68
- [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
74
+ if [ -n "${MIRROR_BIN:-}" ]; then
75
+ refused=""
76
+ case "$MIRROR_BIN" in
77
+ /*|\\*|[A-Za-z]:[\\/]*)
78
+ if [ ! -e "$MIRROR_BIN" ]; then refused="missing"
79
+ elif [ ! -d "$MIRROR_BIN" ]; then refused="not a directory"
80
+ elif [ ! -f "$MIRROR_BIN/mirror-hook" ] || [ ! -x "$MIRROR_BIN/mirror-hook" ]; then refused="a directory without an executable mirror-hook"
81
+ else ENTRY="$MIRROR_BIN/mirror-hook"
82
+ fi
83
+ ;;
84
+ *) refused="relative" ;;
85
+ esac
86
+ if [ -n "$refused" ]; then
87
+ note "MIRROR_BIN=${MIRROR_BIN//[$'\r\n']/} is $refused; searching the PATH and the usual places instead" daily
88
+ fi
89
+ fi
69
90
  [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
70
91
  for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
71
92
  [ -n "$ENTRY" ] && break
@@ -57,10 +57,31 @@ note() {
57
57
  }
58
58
 
59
59
  # Find the bin. MIRROR_BIN names the directory holding `mirror` and
60
- # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
61
- # then the PATH; then the global bin directories a GUI launch leaves off it.
60
+ # `mirror-hook` (`$(npm prefix -g)/bin`); then the PATH; then the global bin
61
+ # directories a GUI launch leaves off it.
62
+ #
63
+ # MIRROR_BIN is used only as an absolute path to a directory holding an
64
+ # executable `mirror-hook`, by MIRROR_NODE's rule and pattern (CR197): a
65
+ # relative one would resolve against the project the runtime spawned this hook
66
+ # from, and Node would run that project's file. Anything else is noted with its
67
+ # reason, in MIRROR_NODE's words, once a day, and the search goes on without it.
62
68
  ENTRY=""
63
- [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
69
+ if [ -n "${MIRROR_BIN:-}" ]; then
70
+ refused=""
71
+ case "$MIRROR_BIN" in
72
+ /*|\\*|[A-Za-z]:[\\/]*)
73
+ if [ ! -e "$MIRROR_BIN" ]; then refused="missing"
74
+ elif [ ! -d "$MIRROR_BIN" ]; then refused="not a directory"
75
+ elif [ ! -f "$MIRROR_BIN/mirror-hook" ] || [ ! -x "$MIRROR_BIN/mirror-hook" ]; then refused="a directory without an executable mirror-hook"
76
+ else ENTRY="$MIRROR_BIN/mirror-hook"
77
+ fi
78
+ ;;
79
+ *) refused="relative" ;;
80
+ esac
81
+ if [ -n "$refused" ]; then
82
+ note "MIRROR_BIN=${MIRROR_BIN//[$'\r\n']/} is $refused; searching the PATH and the usual places instead" daily
83
+ fi
84
+ fi
64
85
  [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
65
86
  for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
66
87
  [ -n "$ENTRY" ] && break
@@ -57,10 +57,31 @@ note() {
57
57
  }
58
58
 
59
59
  # Find the bin. MIRROR_BIN names the directory holding `mirror` and
60
- # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
61
- # then the PATH; then the global bin directories a GUI launch leaves off it.
60
+ # `mirror-hook` (`$(npm prefix -g)/bin`); then the PATH; then the global bin
61
+ # directories a GUI launch leaves off it.
62
+ #
63
+ # MIRROR_BIN is used only as an absolute path to a directory holding an
64
+ # executable `mirror-hook`, by MIRROR_NODE's rule and pattern (CR197): a
65
+ # relative one would resolve against the project the runtime spawned this hook
66
+ # from, and Node would run that project's file. Anything else is noted with its
67
+ # reason, in MIRROR_NODE's words, once a day, and the search goes on without it.
62
68
  ENTRY=""
63
- [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
69
+ if [ -n "${MIRROR_BIN:-}" ]; then
70
+ refused=""
71
+ case "$MIRROR_BIN" in
72
+ /*|\\*|[A-Za-z]:[\\/]*)
73
+ if [ ! -e "$MIRROR_BIN" ]; then refused="missing"
74
+ elif [ ! -d "$MIRROR_BIN" ]; then refused="not a directory"
75
+ elif [ ! -f "$MIRROR_BIN/mirror-hook" ] || [ ! -x "$MIRROR_BIN/mirror-hook" ]; then refused="a directory without an executable mirror-hook"
76
+ else ENTRY="$MIRROR_BIN/mirror-hook"
77
+ fi
78
+ ;;
79
+ *) refused="relative" ;;
80
+ esac
81
+ if [ -n "$refused" ]; then
82
+ note "MIRROR_BIN=${MIRROR_BIN//[$'\r\n']/} is $refused; searching the PATH and the usual places instead" daily
83
+ fi
84
+ fi
64
85
  [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
65
86
  for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
66
87
  [ -n "$ENTRY" ] && break
@@ -57,10 +57,31 @@ note() {
57
57
  }
58
58
 
59
59
  # Find the bin. MIRROR_BIN names the directory holding `mirror` and
60
- # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
61
- # then the PATH; then the global bin directories a GUI launch leaves off it.
60
+ # `mirror-hook` (`$(npm prefix -g)/bin`); then the PATH; then the global bin
61
+ # directories a GUI launch leaves off it.
62
+ #
63
+ # MIRROR_BIN is used only as an absolute path to a directory holding an
64
+ # executable `mirror-hook`, by MIRROR_NODE's rule and pattern (CR197): a
65
+ # relative one would resolve against the project the runtime spawned this hook
66
+ # from, and Node would run that project's file. Anything else is noted with its
67
+ # reason, in MIRROR_NODE's words, once a day, and the search goes on without it.
62
68
  ENTRY=""
63
- [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
69
+ if [ -n "${MIRROR_BIN:-}" ]; then
70
+ refused=""
71
+ case "$MIRROR_BIN" in
72
+ /*|\\*|[A-Za-z]:[\\/]*)
73
+ if [ ! -e "$MIRROR_BIN" ]; then refused="missing"
74
+ elif [ ! -d "$MIRROR_BIN" ]; then refused="not a directory"
75
+ elif [ ! -f "$MIRROR_BIN/mirror-hook" ] || [ ! -x "$MIRROR_BIN/mirror-hook" ]; then refused="a directory without an executable mirror-hook"
76
+ else ENTRY="$MIRROR_BIN/mirror-hook"
77
+ fi
78
+ ;;
79
+ *) refused="relative" ;;
80
+ esac
81
+ if [ -n "$refused" ]; then
82
+ note "MIRROR_BIN=${MIRROR_BIN//[$'\r\n']/} is $refused; searching the PATH and the usual places instead" daily
83
+ fi
84
+ fi
64
85
  [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
65
86
  for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
66
87
  [ -n "$ENTRY" ] && break
@@ -57,10 +57,31 @@ note() {
57
57
  }
58
58
 
59
59
  # Find the bin. MIRROR_BIN names the directory holding `mirror` and
60
- # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
61
- # then the PATH; then the global bin directories a GUI launch leaves off it.
60
+ # `mirror-hook` (`$(npm prefix -g)/bin`); then the PATH; then the global bin
61
+ # directories a GUI launch leaves off it.
62
+ #
63
+ # MIRROR_BIN is used only as an absolute path to a directory holding an
64
+ # executable `mirror-hook`, by MIRROR_NODE's rule and pattern (CR197): a
65
+ # relative one would resolve against the project the runtime spawned this hook
66
+ # from, and Node would run that project's file. Anything else is noted with its
67
+ # reason, in MIRROR_NODE's words, once a day, and the search goes on without it.
62
68
  ENTRY=""
63
- [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
69
+ if [ -n "${MIRROR_BIN:-}" ]; then
70
+ refused=""
71
+ case "$MIRROR_BIN" in
72
+ /*|\\*|[A-Za-z]:[\\/]*)
73
+ if [ ! -e "$MIRROR_BIN" ]; then refused="missing"
74
+ elif [ ! -d "$MIRROR_BIN" ]; then refused="not a directory"
75
+ elif [ ! -f "$MIRROR_BIN/mirror-hook" ] || [ ! -x "$MIRROR_BIN/mirror-hook" ]; then refused="a directory without an executable mirror-hook"
76
+ else ENTRY="$MIRROR_BIN/mirror-hook"
77
+ fi
78
+ ;;
79
+ *) refused="relative" ;;
80
+ esac
81
+ if [ -n "$refused" ]; then
82
+ note "MIRROR_BIN=${MIRROR_BIN//[$'\r\n']/} is $refused; searching the PATH and the usual places instead" daily
83
+ fi
84
+ fi
64
85
  [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
65
86
  for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
66
87
  [ -n "$ENTRY" ] && break
@@ -57,10 +57,31 @@ note() {
57
57
  }
58
58
 
59
59
  # Find the bin. MIRROR_BIN names the directory holding `mirror` and
60
- # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
61
- # then the PATH; then the global bin directories a GUI launch leaves off it.
60
+ # `mirror-hook` (`$(npm prefix -g)/bin`); then the PATH; then the global bin
61
+ # directories a GUI launch leaves off it.
62
+ #
63
+ # MIRROR_BIN is used only as an absolute path to a directory holding an
64
+ # executable `mirror-hook`, by MIRROR_NODE's rule and pattern (CR197): a
65
+ # relative one would resolve against the project the runtime spawned this hook
66
+ # from, and Node would run that project's file. Anything else is noted with its
67
+ # reason, in MIRROR_NODE's words, once a day, and the search goes on without it.
62
68
  ENTRY=""
63
- [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
69
+ if [ -n "${MIRROR_BIN:-}" ]; then
70
+ refused=""
71
+ case "$MIRROR_BIN" in
72
+ /*|\\*|[A-Za-z]:[\\/]*)
73
+ if [ ! -e "$MIRROR_BIN" ]; then refused="missing"
74
+ elif [ ! -d "$MIRROR_BIN" ]; then refused="not a directory"
75
+ elif [ ! -f "$MIRROR_BIN/mirror-hook" ] || [ ! -x "$MIRROR_BIN/mirror-hook" ]; then refused="a directory without an executable mirror-hook"
76
+ else ENTRY="$MIRROR_BIN/mirror-hook"
77
+ fi
78
+ ;;
79
+ *) refused="relative" ;;
80
+ esac
81
+ if [ -n "$refused" ]; then
82
+ note "MIRROR_BIN=${MIRROR_BIN//[$'\r\n']/} is $refused; searching the PATH and the usual places instead" daily
83
+ fi
84
+ fi
64
85
  [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
65
86
  for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
66
87
  [ -n "$ENTRY" ] && break
@@ -4,7 +4,7 @@
4
4
  "command": "/bin/sh",
5
5
  "args": [
6
6
  "-c",
7
- "mmbin=\"\"; [ -n \"$MIRROR_BIN\" ] && [ -x \"$MIRROR_BIN/mirror\" ] && mmbin=\"$MIRROR_BIN/mirror\"; [ -n \"$mmbin\" ] || mmbin=\"$(command -v mirror 2>/dev/null)\"; for mmdir in \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmbin\" ] && break; [ -x \"$mmdir/mirror\" ] && mmbin=\"$mmdir/mirror\"; done; [ -n \"$mmbin\" ] || { echo \"mirror-mind: mirror not found on PATH; the MCP server cannot start. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory.\" >&2; exit 1; }; mmjs=\"$(dirname \"$mmbin\")/node_modules/mirror-mind/bin/mirror.js\"; [ -f \"$mmjs\" ] && mmbin=\"$mmjs\"; mmnode=\"$MIRROR_NODE\"; mmwhy=\"\"; [ -n \"$mmnode\" ] && case \"$mmnode\" in /*|\\\\*|[A-Za-z]:[\\\\/]*) if [ -d \"$mmnode\" ]; then mmwhy=\"a directory\"; elif [ ! -e \"$mmnode\" ]; then mmwhy=\"missing\"; elif [ ! -f \"$mmnode\" ] || [ ! -x \"$mmnode\" ]; then mmwhy=\"not executable\"; fi ;; *) mmwhy=\"relative\" ;; esac; [ -n \"$mmwhy\" ] && { printf 'mirror-mind: MIRROR_NODE=%s is %s; searching the PATH and the usual places instead\\n' \"$(printf '%s' \"$mmnode\" | tr -d '\\r\\n')\" \"$mmwhy\" >&2; mmnode=\"\"; }; [ -n \"$mmnode\" ] || mmnode=\"$(command -v node 2>/dev/null)\"; for mmdir in \"$(dirname \"$mmbin\")\" \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmnode\" ] && break; [ -x \"$mmdir/node\" ] && mmnode=\"$mmdir/node\"; done; [ -n \"$mmnode\" ] || { echo \"mirror-mind: node not found on PATH; the MCP server cannot start. Set MIRROR_NODE.\" >&2; exit 1; }; exec \"$mmnode\" \"$mmbin\" mcp"
7
+ "mmbin=\"\"; mmbindir=\"$MIRROR_BIN\"; mmwhy=\"\"; [ -n \"$mmbindir\" ] && case \"$mmbindir\" in /*|\\\\*|[A-Za-z]:[\\\\/]*) if [ ! -e \"$mmbindir\" ]; then mmwhy=\"missing\"; elif [ ! -d \"$mmbindir\" ]; then mmwhy=\"not a directory\"; elif [ ! -f \"$mmbindir/mirror\" ] || [ ! -x \"$mmbindir/mirror\" ]; then mmwhy=\"a directory without an executable mirror\"; else mmbin=\"$mmbindir/mirror\"; fi ;; *) mmwhy=\"relative\" ;; esac; [ -n \"$mmwhy\" ] && printf 'mirror-mind: MIRROR_BIN=%s is %s; searching the PATH and the usual places instead\\n' \"$(printf '%s' \"$mmbindir\" | tr -d '\\r\\n')\" \"$mmwhy\" >&2; [ -n \"$mmbin\" ] || mmbin=\"$(command -v mirror 2>/dev/null)\"; for mmdir in \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmbin\" ] && break; [ -x \"$mmdir/mirror\" ] && mmbin=\"$mmdir/mirror\"; done; [ -n \"$mmbin\" ] || { echo \"mirror-mind: mirror not found on PATH; the MCP server cannot start. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory.\" >&2; exit 1; }; mmjs=\"$(dirname \"$mmbin\")/node_modules/mirror-mind/bin/mirror.js\"; [ -f \"$mmjs\" ] && mmbin=\"$mmjs\"; mmnode=\"$MIRROR_NODE\"; mmwhy=\"\"; [ -n \"$mmnode\" ] && case \"$mmnode\" in /*|\\\\*|[A-Za-z]:[\\\\/]*) if [ -d \"$mmnode\" ]; then mmwhy=\"a directory\"; elif [ ! -e \"$mmnode\" ]; then mmwhy=\"missing\"; elif [ ! -f \"$mmnode\" ] || [ ! -x \"$mmnode\" ]; then mmwhy=\"not executable\"; fi ;; *) mmwhy=\"relative\" ;; esac; [ -n \"$mmwhy\" ] && { printf 'mirror-mind: MIRROR_NODE=%s is %s; searching the PATH and the usual places instead\\n' \"$(printf '%s' \"$mmnode\" | tr -d '\\r\\n')\" \"$mmwhy\" >&2; mmnode=\"\"; }; [ -n \"$mmnode\" ] || mmnode=\"$(command -v node 2>/dev/null)\"; for mmdir in \"$(dirname \"$mmbin\")\" \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmnode\" ] && break; [ -x \"$mmdir/node\" ] && mmnode=\"$mmdir/node\"; done; [ -n \"$mmnode\" ] || { echo \"mirror-mind: node not found on PATH; the MCP server cannot start. Set MIRROR_NODE.\" >&2; exit 1; }; exec \"$mmnode\" \"$mmbin\" mcp"
8
8
  ]
9
9
  }
10
10
  }
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "mirror-mind",
3
3
  "description": "Mirror Mind — local-first memory and identity for agentic runtimes.",
4
- "version": "0.40.0",
4
+ "version": "0.40.1",
5
5
  "mcpServers": {
6
6
  "mirror-mind": {
7
7
  "command": "/bin/sh",
8
8
  "args": [
9
9
  "-c",
10
- "mmbin=\"\"; [ -n \"$MIRROR_BIN\" ] && [ -x \"$MIRROR_BIN/mirror\" ] && mmbin=\"$MIRROR_BIN/mirror\"; [ -n \"$mmbin\" ] || mmbin=\"$(command -v mirror 2>/dev/null)\"; for mmdir in \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmbin\" ] && break; [ -x \"$mmdir/mirror\" ] && mmbin=\"$mmdir/mirror\"; done; [ -n \"$mmbin\" ] || { echo \"mirror-mind: mirror not found on PATH; the MCP server cannot start. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory.\" >&2; exit 1; }; mmjs=\"$(dirname \"$mmbin\")/node_modules/mirror-mind/bin/mirror.js\"; [ -f \"$mmjs\" ] && mmbin=\"$mmjs\"; mmnode=\"$MIRROR_NODE\"; mmwhy=\"\"; [ -n \"$mmnode\" ] && case \"$mmnode\" in /*|\\\\*|[A-Za-z]:[\\\\/]*) if [ -d \"$mmnode\" ]; then mmwhy=\"a directory\"; elif [ ! -e \"$mmnode\" ]; then mmwhy=\"missing\"; elif [ ! -f \"$mmnode\" ] || [ ! -x \"$mmnode\" ]; then mmwhy=\"not executable\"; fi ;; *) mmwhy=\"relative\" ;; esac; [ -n \"$mmwhy\" ] && { printf 'mirror-mind: MIRROR_NODE=%s is %s; searching the PATH and the usual places instead\\n' \"$(printf '%s' \"$mmnode\" | tr -d '\\r\\n')\" \"$mmwhy\" >&2; mmnode=\"\"; }; [ -n \"$mmnode\" ] || mmnode=\"$(command -v node 2>/dev/null)\"; for mmdir in \"$(dirname \"$mmbin\")\" \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmnode\" ] && break; [ -x \"$mmdir/node\" ] && mmnode=\"$mmdir/node\"; done; [ -n \"$mmnode\" ] || { echo \"mirror-mind: node not found on PATH; the MCP server cannot start. Set MIRROR_NODE.\" >&2; exit 1; }; exec \"$mmnode\" \"$mmbin\" mcp"
10
+ "mmbin=\"\"; mmbindir=\"$MIRROR_BIN\"; mmwhy=\"\"; [ -n \"$mmbindir\" ] && case \"$mmbindir\" in /*|\\\\*|[A-Za-z]:[\\\\/]*) if [ ! -e \"$mmbindir\" ]; then mmwhy=\"missing\"; elif [ ! -d \"$mmbindir\" ]; then mmwhy=\"not a directory\"; elif [ ! -f \"$mmbindir/mirror\" ] || [ ! -x \"$mmbindir/mirror\" ]; then mmwhy=\"a directory without an executable mirror\"; else mmbin=\"$mmbindir/mirror\"; fi ;; *) mmwhy=\"relative\" ;; esac; [ -n \"$mmwhy\" ] && printf 'mirror-mind: MIRROR_BIN=%s is %s; searching the PATH and the usual places instead\\n' \"$(printf '%s' \"$mmbindir\" | tr -d '\\r\\n')\" \"$mmwhy\" >&2; [ -n \"$mmbin\" ] || mmbin=\"$(command -v mirror 2>/dev/null)\"; for mmdir in \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmbin\" ] && break; [ -x \"$mmdir/mirror\" ] && mmbin=\"$mmdir/mirror\"; done; [ -n \"$mmbin\" ] || { echo \"mirror-mind: mirror not found on PATH; the MCP server cannot start. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory.\" >&2; exit 1; }; mmjs=\"$(dirname \"$mmbin\")/node_modules/mirror-mind/bin/mirror.js\"; [ -f \"$mmjs\" ] && mmbin=\"$mmjs\"; mmnode=\"$MIRROR_NODE\"; mmwhy=\"\"; [ -n \"$mmnode\" ] && case \"$mmnode\" in /*|\\\\*|[A-Za-z]:[\\\\/]*) if [ -d \"$mmnode\" ]; then mmwhy=\"a directory\"; elif [ ! -e \"$mmnode\" ]; then mmwhy=\"missing\"; elif [ ! -f \"$mmnode\" ] || [ ! -x \"$mmnode\" ]; then mmwhy=\"not executable\"; fi ;; *) mmwhy=\"relative\" ;; esac; [ -n \"$mmwhy\" ] && { printf 'mirror-mind: MIRROR_NODE=%s is %s; searching the PATH and the usual places instead\\n' \"$(printf '%s' \"$mmnode\" | tr -d '\\r\\n')\" \"$mmwhy\" >&2; mmnode=\"\"; }; [ -n \"$mmnode\" ] || mmnode=\"$(command -v node 2>/dev/null)\"; for mmdir in \"$(dirname \"$mmbin\")\" \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmnode\" ] && break; [ -x \"$mmdir/node\" ] && mmnode=\"$mmdir/node\"; done; [ -n \"$mmnode\" ] || { echo \"mirror-mind: node not found on PATH; the MCP server cannot start. Set MIRROR_NODE.\" >&2; exit 1; }; exec \"$mmnode\" \"$mmbin\" mcp"
11
11
  ]
12
12
  }
13
13
  }
@@ -111,6 +111,10 @@ export const ABSOLUTE_PATH_CASE = "/*|\\\\*|[A-Za-z]:[\\\\/]*";
111
111
  * reason, in the Pi extension's words, and the search goes on without it. The
112
112
  * value is read once, into `mmnode`, and printed with `printf` and its line
113
113
  * breaks removed: dash's `echo` turns the `\n` of `\nodejs` into a newline.
114
+ * `MIRROR_BIN` follows the same rule (CR197): only an absolute path to a
115
+ * directory holding an executable `mirror`, read once, into `mmbindir`, and
116
+ * refused in the same words. A relative one would resolve against the project
117
+ * the client started the server from, and Node would run that project's file.
114
118
  *
115
119
  * Written for what three runtimes do to the string (the US6 spike):
116
120
  *
@@ -119,14 +123,17 @@ export const ABSOLUTE_PATH_CASE = "/*|\\\\*|[A-Za-z]:[\\\\/]*";
119
123
  * runtime that did so with an unset name would blank it;
120
124
  * * no positional parameter (`$1`, `$@`, ...) and no backtick, for the same
121
125
  * reason, and because the text travels through two JSON encoders;
122
- * * variables named `mmbin`, `mmnode`, `mmdir`: Grok substitutes SET
126
+ * * variables named `mmbin`, `mmbindir`, `mmnode`, `mmdir`: Grok substitutes SET
123
127
  * environment variables by name inside MCP args at load time (`$HOME`
124
128
  * became the path), so a script variable with a name a person exports
125
129
  * would be replaced before the shell ever saw it.
126
130
  */
127
131
  export const MCP_LOCATOR = [
128
132
  'mmbin=""',
129
- '[ -n "$MIRROR_BIN" ] && [ -x "$MIRROR_BIN/mirror" ] && mmbin="$MIRROR_BIN/mirror"',
133
+ 'mmbindir="$MIRROR_BIN"',
134
+ 'mmwhy=""',
135
+ `[ -n "$mmbindir" ] && case "$mmbindir" in ${ABSOLUTE_PATH_CASE}) if [ ! -e "$mmbindir" ]; then mmwhy="missing"; elif [ ! -d "$mmbindir" ]; then mmwhy="not a directory"; elif [ ! -f "$mmbindir/mirror" ] || [ ! -x "$mmbindir/mirror" ]; then mmwhy="a directory without an executable mirror"; else mmbin="$mmbindir/mirror"; fi ;; *) mmwhy="relative" ;; esac`,
136
+ `[ -n "$mmwhy" ] && printf 'mirror-mind: MIRROR_BIN=%s is %s; searching the PATH and the usual places instead\\n' "$(printf '%s' "$mmbindir" | tr -d '\\r\\n')" "$mmwhy" >&2`,
130
137
  '[ -n "$mmbin" ] || mmbin="$(command -v mirror 2>/dev/null)"',
131
138
  `for mmdir in ${GLOBAL_BIN_DIRS.join(" ")}; do [ -n "$mmbin" ] && break; [ -x "$mmdir/mirror" ] && mmbin="$mmdir/mirror"; done`,
132
139
  '[ -n "$mmbin" ] || { echo "mirror-mind: mirror not found on PATH; the MCP server cannot start. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory." >&2; exit 1; }',