@mrrlin-dev/mcp 0.3.0 → 0.3.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.
@@ -1,209 +1,51 @@
1
1
  ---
2
2
  name: report-issue
3
- description: Use when a Mrrlin user hits a problem and wants it reported to support. Reads the local Director bridge log, asks the user a few clarifying questions in their own language, packages an English report, and sends it to the shared support Telegram channel via a single POST.
3
+ description: Deprecated shell helper. Use the in-app Director `/report-issue` flow or the web header's `Report issue` entry; both file GitHub issues in fnnzzz/mrrlin.
4
4
  ---
5
5
 
6
- # Report a Mrrlin issue to support
6
+ # Report issue
7
7
 
8
- You are helping a Mrrlin **user/customer** report a problem. Do all the plumbing
9
- yourself and keep it invisible to them: read their local logs, ask only what you
10
- genuinely cannot infer, then send one packaged report to the support Telegram
11
- channel. The user should never see or touch the token, the log path, or the POST.
8
+ This slash-command prompt is kept only as a compatibility shim for operators who
9
+ already installed `/report-issue` into Codex.
12
10
 
13
- ## How this gets invoked
11
+ ## Preferred flows
14
12
 
15
- This prompt ships inside `@mrrlin-dev/mcp`. `mrrlin-mcp report-issue` prints this
16
- file to stdout, so the same text drives all three surfaces:
13
+ Use one of the in-app flows instead of this shell prompt:
17
14
 
18
- - **Terminal (feed it to Codex):** `codex "$(mrrlin-mcp report-issue)"`
19
- - **Codex slash command:** `mrrlin-mcp report-issue > ~/.codex/prompts/report-issue.md` once, then run `/report-issue`.
20
- - **Skill:** it has skill frontmatter (`name` + `description`), so a skill-aware agent can activate it directly.
15
+ 1. In the Director chat, run `/report-issue <what broke>`.
16
+ The app will package:
17
+ - your free-text description
18
+ - screenshots from the current Director turn
19
+ - the recent Director transcript
20
+ - the local Director bridge logs
21
+ - the active project slug and current route
21
22
 
22
- ## Service bot it talks to
23
+ 2. In the top-right user menu, choose `Report issue`.
24
+ That modal lets you describe the problem in free text and attach screenshots.
25
+ It also packages recent Director context when available.
23
26
 
24
- The token below is **intentionally public** `@mrrlinIssuesBot` is a throwaway
25
- service bot dedicated to the Mrrlin support group. Anyone reading this file may
26
- see it; that is by design. The bot can only post to the one chat hardcoded
27
- below, so leaking the token costs at most some spam in that chat — rotate via
28
- @BotFather if it ever happens.
27
+ Both flows rationalize the evidence locally and create a GitHub issue in
28
+ `fnnzzz/mrrlin` through the operator's local `gh` CLI. The issue is labelled as
29
+ `bug`, includes bridge-log excerpts, transcript context, screenshots, and the
30
+ customer-facing description.
29
31
 
30
- - Bot: `@mrrlinIssuesBot` (id `8506614214`)
31
- - Group: "Mrrlin", chat id `-5373779177`
32
+ ## What this legacy prompt should do
32
33
 
33
- ---
34
-
35
- ## What to do
36
-
37
- ### 1. Take the user's hint as the search key
38
-
39
- When the operator invokes `/report-issue`, they typically include a hint along
40
- with the command — a pasted error blurb, a quote of weird output, a one-line
41
- description, or a `sessionId`/`spanId` they copied. **That hint is your primary
42
- search key.** Do not blindly read the tail of the latest log.
43
-
44
- Extract candidate signals from the hint:
45
-
46
- - **Quoted substrings** — verbatim text from the failure (highest priority).
47
- - **Error words** — "error", "failed", "timeout", "401", "500", "ENOENT", stack-trace fragments.
48
- - **Identifiers** — `sessionId` (uuid-shaped) or `spanId` if pasted.
49
- - **Time hints** — "just now", "5 minutes ago", "today around 14:00".
50
-
51
- If the operator gave no hint at all, fall back to the tail (see below) — but
52
- say so plainly when you confirm, so they know the report may have grabbed the
53
- wrong incident.
54
-
55
- ### 2. Find the relevant log window
56
-
57
- Logs live at the first existing dir:
58
-
59
- 1. `$CODEX_HOME/mrrlin/director-bridge/logs/` (only if `CODEX_HOME` is set)
60
- 2. `~/.mrrlin/director-bridge/logs/`
61
-
62
- Files are `bridge-YYYY-MM-DD.jsonl` (UTC dates). Sort newest-first. Each line:
63
-
64
- ```json
65
- {"ts":"...","dir":"in|out","spanId":"...","sessionId":"...","type":"turn|event|error|...","ms":123,"payload":{...}}
66
- ```
34
+ If an operator still invokes `/report-issue` from Codex directly:
67
35
 
68
- Secrets are already redacted by the logger (tokens show as `[REDACTED]`), so the
69
- log lines are safe to forward as-is.
36
+ - Tell them this flow is deprecated.
37
+ - Point them to the two in-app entry points above.
38
+ - Do not mention Telegram or any legacy support-bot flow.
39
+ - Do not invent a separate reporting pipeline.
70
40
 
71
- **With a hint** — grep across the **3 most recent files** (today + last 2 days)
72
- in this signal order, stopping at the first that matches:
41
+ Use concise wording. Example:
73
42
 
74
- 1. The literal quoted substring from the hint (case-insensitive).
75
- 2. The `sessionId` or `spanId` from the hint.
76
- 3. Error-word lines clustered near any time hint the user gave.
43
+ ```text
44
+ Issue reporting moved into the Mrrlin UI.
77
45
 
78
- Pick the **most recent** matching cluster. The "relevant window" = the matching
79
- line + ~10 lines before and ~30 lines after. If the matching line has a
80
- `sessionId`, bound the window to that session.
46
+ - Director chat: `/report-issue <what broke>`
47
+ - Header menu: `Report issue`
81
48
 
82
- **Without a hint** read the last ~200 lines of the newest file. The window is
83
- the last contiguous cluster of `type:"error"` lines (or, if none, the last few
84
- `type:"turn"` lines).
85
-
86
- From the relevant window extract:
87
-
88
- - Every `type:"error"` line and any `dir:"out"` payload that looks like a failure (non-2xx HTTP, stack traces, "failed", "timeout").
89
- - The last few `type:"turn"` lines before the failure (what the user was doing).
90
- - The `sessionId` and `spanId`s tied to the failure.
91
- - `ts` of the first and last lines in the window.
92
-
93
- If the log dir doesn't exist, or the hint matches nothing in the last 3 days,
94
- say so plainly and lean on the user's answers in step 3 — still send the report.
95
-
96
- ### 3. Ask the user — in THEIR language — only what the log doesn't already tell you
97
-
98
- Detect the language the user is writing in and ask in that language. Keep it
99
- short, ask **once**, and **skip any question the hint + log already answered**:
100
-
101
- - **What were you doing** when it broke? — skip if the hint or the preceding `type:"turn"` lines make this obvious.
102
- - **What actually happened (the symptom)?** — skip if the hint is itself a verbatim error/output.
103
- - **What did you expect to happen instead?** — ask only if the expected result is not obvious from the log or hint.
104
-
105
- If the user gives short or partial answers, accept them and move on. Never block
106
- the report on a perfect answer.
107
-
108
- ### 4. Package the report (in ENGLISH) — every string passes through `mrrlin-mcp redact`
109
-
110
- Translate the user's answers to English. Build a plain-text report. Keep the whole
111
- thing under **4096 characters** (Telegram's per-message limit) — trim the log
112
- excerpt first if needed, keeping the error lines over the context lines.
113
-
114
- **Hard rule:** every free-form string going into the report — the user's hint,
115
- their answers, the log excerpt — passes through the shipped scrubber first.
116
- There is no "but the log is already redacted by the logger" exception: the hint
117
- and the user's answers come from outside the logger, so they MUST be scrubbed
118
- here. Run each through:
119
-
120
- ```bash
121
- HINT_REDACTED=$(printf %s "$USER_HINT_RAW" | mrrlin-mcp redact)
122
- EXCERPT_REDACTED=$(mrrlin-mcp redact < /tmp/excerpt-raw.txt)
123
- # (repeat for each user answer)
49
+ Both flows attach bridge logs, transcript context, screenshots, and file a GitHub
50
+ issue in fnnzzz/mrrlin via your local gh CLI.
124
51
  ```
125
-
126
- `mrrlin-mcp redact` reads stdin and writes the redacted bytes to stdout
127
- (empty input → empty output, exit 0). It uses the same regex set as the
128
- bridge logger (Bearer/JWT/GitHub-PAT/long-hex/long-base64). It is best-effort,
129
- not a guarantee — but it is the floor below which raw text is never allowed.
130
-
131
- Use the redacted versions to fill the template:
132
-
133
- ```
134
- 🛠️ Mrrlin issue report
135
- When (UTC): <iso timestamp of the report>
136
- Mrrlin MCP: v<version if known> | OS: <platform> | Node: <version>
137
-
138
- ▶ User hint (verbatim, post-redaction):
139
- <HINT_REDACTED; "(none)" if absent>
140
-
141
- ▶ What the user was doing:
142
- <their answer, in English, post-redaction>
143
-
144
- ▶ Expected result:
145
- <their answer, post-redaction; or "(obvious from context: ...)"; or "(not provided)">
146
-
147
- ▶ Actual result / symptom:
148
- <their answer, in English, post-redaction>
149
-
150
- ▶ Errors from bridge log (<filename>, search strategy: <hint-driven | tail-fallback>):
151
- matched on: "<the exact line that triggered the cluster, truncated to 80 chars; or '(no hint provided)' for tail-fallback>"
152
- <EXCERPT_REDACTED — first the error lines, then the surrounding context>
153
-
154
- ▶ Context:
155
- session=<sessionId> spanIds=<...> window=<first ts>..<last ts>
156
- ```
157
-
158
- ### 5. Send it — a single POST to Telegram
159
-
160
- This is **just one HTTP POST** to the Telegram Bot API `sendMessage` method.
161
- Endpoint and shape:
162
-
163
- ```
164
- POST https://api.telegram.org/bot8506614214:AAGyhO1phWb7ah2aN6_gAX2Co7OXNN3zb0A/sendMessage
165
- Content-Type: application/json
166
- body: { "chat_id": "-5373779177", "text": "<the report>", "disable_web_page_preview": true }
167
- ```
168
-
169
- Do **not** set `parse_mode` — send the report as plain text so nothing needs
170
- escaping. Because the report has newlines and quotes, build the JSON safely
171
- (serialize it; don't hand-concatenate a string), write it to a temp file, and POST
172
- the file so the shell can't mangle it:
173
-
174
- ```bash
175
- # Write the JSON payload with a proper serializer, then:
176
- curl -sS -X POST \
177
- "https://api.telegram.org/bot8506614214:AAGyhO1phWb7ah2aN6_gAX2Co7OXNN3zb0A/sendMessage" \
178
- -H "Content-Type: application/json" \
179
- --data-binary @/tmp/mrrlin-report.json
180
- ```
181
-
182
- Check the response: Telegram returns `{"ok":true,...}` on success. If it returns
183
- `{"ok":false,"description":"..."}` or curl fails, show the user the error and offer
184
- to retry once.
185
-
186
- **Optional — full log as an attachment.** If the trimmed excerpt lost important
187
- lines, also send the full log file as a document (separate call):
188
-
189
- ```bash
190
- curl -sS -X POST \
191
- "https://api.telegram.org/bot8506614214:AAGyhO1phWb7ah2aN6_gAX2Co7OXNN3zb0A/sendDocument" \
192
- -F "chat_id=-5373779177" \
193
- -F "document=@<path-to-bridge-YYYY-MM-DD.jsonl>"
194
- ```
195
-
196
- ### 6. Confirm
197
-
198
- Tell the user, in their language, that the report was sent (or that it failed and
199
- why). Don't dump the raw report or the token back at them — just confirm.
200
-
201
- ## Rules
202
-
203
- - **Every string going into the Telegram body comes out of `mrrlin-mcp redact`** — the user's hint, every translated user answer, the log excerpt. If your pipeline has a path that builds the body from raw text, you are doing it wrong. The bridge logger already redacts what it writes; the hint and the user's answers do NOT come from the logger and must be scrubbed here.
204
- - The operator's hint drives log search. Tail-fallback is the explicit fallback when no hint is provided.
205
- - `matched on:` must contain the literal line that triggered the cluster (truncated to 80 chars), so the channel reader can verify the match instead of trusting an LLM assertion.
206
- - Ask the user in their language; write the report in English. Skip any question the hint + log already answered.
207
- - Hide the mechanics: never surface the token, the log path, or the curl command to the user.
208
- - One report = one `sendMessage` POST. Keep it under 4096 chars; use `sendDocument` only for the optional full log.
209
- - If anything is missing (no log match, vague answers), still send the best report you can rather than giving up — and say in the report which search strategy you used and what `matched on:` you found.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -11,20 +11,22 @@
11
11
  "files": [
12
12
  "dist/bin.cjs",
13
13
  "dist/consensus/personas",
14
- "dist/prompts"
14
+ "dist/prompts",
15
+ "scripts/postinstall-restart.cjs"
15
16
  ],
16
17
  "devDependencies": {
17
18
  "@types/node": "^20.17.50",
18
19
  "@types/proper-lockfile": "^4.1.4",
19
20
  "@types/qrcode": "^1.5.6",
21
+ "@types/semver": "^7.7.1",
20
22
  "@types/ws": "^8.18.1",
21
23
  "esbuild": "^0.24.0",
22
24
  "tsx": "^4.22.3",
23
- "@mrrlin/director-e2e": "0.0.0",
24
25
  "@mrrlin/client": "0.0.0",
26
+ "@mrrlin/director-e2e": "0.0.0",
25
27
  "@mrrlin/wiki": "0.0.0",
26
- "@mrrlin/codex-client": "0.0.0",
27
28
  "@mrrlin/schemas": "0.0.0",
29
+ "@mrrlin/codex-client": "0.0.0",
28
30
  "@mrrlin/tsconfig": "0.0.0"
29
31
  },
30
32
  "dependencies": {
@@ -32,16 +34,18 @@
32
34
  "@modelcontextprotocol/sdk": "^1.0.0",
33
35
  "proper-lockfile": "^4.1.2",
34
36
  "qrcode": "^1.5.4",
37
+ "semver": "^7.8.0",
35
38
  "ws": "^8.18.0",
36
39
  "zod": "^4.0.0"
37
40
  },
38
41
  "scripts": {
39
- "build": "tsc -p tsconfig.json && pnpm run bundle && chmod +x dist/bin.cjs",
40
- "bundle": "esbuild src/bin.ts --bundle --platform=node --format=cjs --target=node20 --outfile=dist/bin.cjs --banner:js='#!/usr/bin/env node' && mkdir -p dist/consensus/personas && cp src/consensus/personas/*.md dist/consensus/personas/ && mkdir -p dist/prompts && cp src/prompts/*.md dist/prompts/",
42
+ "build": "node scripts/write-version.mjs && tsc -p tsconfig.json && pnpm run bundle && chmod +x dist/bin.cjs",
43
+ "postinstall": "node scripts/postinstall-restart.cjs",
44
+ "bundle": "node scripts/write-version.mjs && esbuild src/bin.ts --bundle --platform=node --format=cjs --target=node20 --outfile=dist/bin.cjs --banner:js='#!/usr/bin/env node' && mkdir -p dist/consensus/personas && cp src/consensus/personas/*.md dist/consensus/personas/ && mkdir -p dist/prompts && cp src/prompts/*.md dist/prompts/",
41
45
  "dev": "tsx watch src/bin.ts serve",
42
46
  "lint": "eslint .",
43
47
  "start": "node dist/bin.cjs serve",
44
- "test": "node --import tsx --test $(find src -name '*.test.ts' | sort | tr '\\n' ' ')",
45
- "typecheck": "pnpm --filter @mrrlin/schemas build && pnpm --filter @mrrlin/client build && pnpm --filter @mrrlin/wiki build && pnpm --filter @mrrlin/codex-client build && pnpm --filter @mrrlin/director-e2e build && tsc -p tsconfig.json --noEmit"
48
+ "test": "node scripts/write-version.mjs && node --import tsx --test $(find src -name '*.test.ts' | sort | tr '\\n' ' ')",
49
+ "typecheck": "node scripts/write-version.mjs && pnpm --filter @mrrlin/schemas build && pnpm --filter @mrrlin/client build && pnpm --filter @mrrlin/wiki build && pnpm --filter @mrrlin/codex-client build && pnpm --filter @mrrlin/director-e2e build && tsc -p tsconfig.json --noEmit"
46
50
  }
47
51
  }
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // L2 postinstall nudge for `@mrrlin-dev/mcp`. Sends SIGUSR2 to every running
5
+ // PM2 bridge (matched by canonical name "mrrlin-bridge" OR args "director-bridge")
6
+ // so the bridge can drain its current Codex turn cleanly and let L1 exit on quiet.
7
+ //
8
+ // Guard rails:
9
+ // - Skip on CI or explicit opt-out so test-rigs don't ping running bridges.
10
+ // - Skip when this is NOT a global install (e.g., local `pnpm install` in the
11
+ // monorepo): the postinstall fires in every workspace context otherwise.
12
+ // - Never exit non-zero — a failed nudge must not break the install itself.
13
+
14
+ function runPostinstall() {
15
+ if (process.env.CI === "1" || process.env.MRRLIN_MCP_POSTINSTALL_SKIP === "1") {
16
+ return;
17
+ }
18
+ if (process.env.npm_config_global !== "true") {
19
+ // Local dev install or transitive dep — do not nudge the operator's bridge.
20
+ return;
21
+ }
22
+
23
+ const { spawnSync } = require("node:child_process");
24
+
25
+ const jlist = spawnSync("pm2", ["jlist"], { encoding: "utf8" });
26
+ if (jlist.status !== 0) {
27
+ if (jlist.error && jlist.error.code === "ENOENT") {
28
+ console.log(
29
+ "[mrrlin-mcp] PM2 not available; bridge will self-update via L1 " +
30
+ "(default 15min interval; tune via MRRLIN_DIRECTOR_BRIDGE_SELF_UPDATE_INTERVAL_MS)",
31
+ );
32
+ return;
33
+ }
34
+ console.log(
35
+ `[mrrlin-mcp] pm2 jlist failed (status=${jlist.status}); skipping nudge`,
36
+ );
37
+ return;
38
+ }
39
+
40
+ let entries;
41
+ try {
42
+ entries = JSON.parse(jlist.stdout || "[]");
43
+ if (!Array.isArray(entries)) entries = [];
44
+ } catch (_e) {
45
+ console.log("[mrrlin-mcp] pm2 jlist returned unparseable JSON; skipping nudge");
46
+ return;
47
+ }
48
+
49
+ function argsIncludesDirectorBridge(args) {
50
+ if (typeof args === "string") return args.includes("director-bridge");
51
+ if (Array.isArray(args)) return args.includes("director-bridge");
52
+ return false;
53
+ }
54
+
55
+ const matches = entries.filter((e) => {
56
+ if (!e || typeof e !== "object") return false;
57
+ const env = e.pm2_env || {};
58
+ if (env.status !== "online") return false;
59
+ if (e.name === "mrrlin-bridge") return true;
60
+ if (argsIncludesDirectorBridge(env.args)) return true;
61
+ return false;
62
+ });
63
+
64
+ if (matches.length === 0) {
65
+ console.log(
66
+ "[mrrlin-mcp] no running bridge to nudge; start with: mrrlin-mcp install-service",
67
+ );
68
+ return;
69
+ }
70
+
71
+ for (const entry of matches) {
72
+ const pid = entry.pid;
73
+ if (typeof pid !== "number" || !Number.isInteger(pid)) {
74
+ console.error(`[mrrlin-mcp] skipping entry with non-integer pid: ${pid}`);
75
+ continue;
76
+ }
77
+ try {
78
+ process.kill(pid, "SIGUSR2");
79
+ console.log(
80
+ `[mrrlin-mcp] sent SIGUSR2 to bridge pid=${pid} (name=${entry.name}); ` +
81
+ "will restart on next quiet moment via L1",
82
+ );
83
+ } catch (e) {
84
+ console.error(
85
+ `[mrrlin-mcp] failed to signal pid=${pid}: ${e && e.message ? e.message : e}`,
86
+ );
87
+ }
88
+ }
89
+ }
90
+
91
+ if (require.main === module) {
92
+ try {
93
+ runPostinstall();
94
+ } catch (e) {
95
+ // Best-effort. Never break the install.
96
+ console.error(
97
+ `[mrrlin-mcp] postinstall-restart threw: ${e && e.message ? e.message : e}`,
98
+ );
99
+ }
100
+ process.exit(0);
101
+ }
102
+
103
+ module.exports = { runPostinstall };