muuuuse 4.0.1 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,56 +1,44 @@
1
1
  # 🔌Muuuuse
2
2
 
3
- `🔌Muuuuse` is a tiny terminal relay.
3
+ `🔌Muuuuse` is a tiny terminal relay with flexible routing.
4
4
 
5
5
  It does one job:
6
- - arm terminal one with `muuuuse 1`
7
- - arm terminal two with `muuuuse 2`
8
- - have seat 1 generate a session key and seat 2 sign it
9
- - additional isolated pairs work the same way: `3/4`, `5/6`, `7/8`, ...
10
- - choose per-seat relay mode with `flow on` or `flow off`
6
+ - arm any number of terminals with `muuuuse <N>`
7
+ - each seat can route to any other seats with individual flow modes
11
8
  - watch Codex, Claude, or Gemini for local assistant output
12
- - inject that output into the other armed terminal
9
+ - relay that output to configured targets with per-target flow control
13
10
  - keep looping until you stop it
14
11
 
15
12
  The whole surface is:
16
13
 
17
14
  ```bash
18
15
  muuuuse 1
19
- muuuuse 1 flow on
20
- muuuuse 1 flow off
21
- muuuuse 1 flow off continue 5
22
16
  muuuuse 2
23
- muuuuse 2 flow off
24
- muuuuse 2 flow on continue 3
25
- muuuuse 3
26
- muuuuse 3 flow on
27
- muuuuse 4
28
- muuuuse 4 flow off continue 1
17
+ muuuuse 3 link 1 flow on 2 flow off
18
+ muuuuse 4 link 3 flow on
29
19
  muuuuse status
30
20
  muuuuse stop
31
21
  ```
32
22
 
33
- ## Flow
23
+ ## Routing
34
24
 
35
- Terminal 1:
25
+ Any seat can route to any targets. Syntax:
36
26
 
37
27
  ```bash
38
- muuuuse 1 flow on
28
+ muuuuse <seat> link <target1> flow <on|off> [<target2> flow <on|off> ...]
39
29
  ```
40
30
 
41
- Terminal 2:
31
+ Example: Seat 3 routes to seat 1 with full output (flow on) and seat 4 with final answers only (flow off):
42
32
 
43
33
  ```bash
44
- muuuuse 2 flow off
34
+ muuuuse 3 link 1 flow on 4 flow off
45
35
  ```
46
36
 
47
- Now both shells are armed. `muuuuse 1` generates the session key, `muuuuse 2` signs it, and only that signed pair relays. Every odd/even adjacent pair works the same way in parallel: `3/4`, `5/6`, `7/8`, and so on. Use those shells normally.
37
+ Each seat defines its own routing targets. When that seat gets an answer from Claude/Codex/Gemini, it sends to all configured targets with their respective flow modes.
48
38
 
49
- `flow on` means that seat relays commentary and final answers. `flow off` means that seat relays and accepts final answers only. Mixed calibration is allowed per seat.
39
+ `flow on` means relay both thinking/commentary and final answers. `flow off` means relay final answers only.
50
40
 
51
- `continue <seat>` forwards that seat's relayed output into another armed seat without changing the signed odd/even pair law. This lets you build local loops like `1 -> 2 -> 3 -> 4 -> 1` while every adjacent pair still keeps its own session keypair.
52
-
53
- If you want Codex in one and Gemini in the other, start them inside the armed shells:
41
+ If you want Codex in one and Gemini in another, start them inside the armed shells:
54
42
 
55
43
  ```bash
56
44
  codex
@@ -60,7 +48,7 @@ codex
60
48
  gemini
61
49
  ```
62
50
 
63
- `🔌Muuuuse` tails the local session logs for supported CLIs, relays according to each seat's flow mode, types that output into the other seat, and then sends Enter as a separate keystroke.
51
+ `🔌Muuuuse` tails the local session logs for supported CLIs, relays according to each target's flow mode, types that output into the target seat, and then sends Enter as a separate keystroke.
64
52
 
65
53
  Check the live state from any terminal:
66
54
 
@@ -77,8 +65,9 @@ muuuuse stop
77
65
  ## Notes
78
66
 
79
67
  - state lives under `~/.muuuuse`
80
- - only the signed armed pair can exchange relay events
81
- - `continue <seat>` is a separate local forwarding lane and can target any armed seat number
68
+ - each seat defines its own routing targets independently
69
+ - any seat can route to any other seat number
70
+ - flow modes are per-target (not per-seat)
82
71
  - supported relay detection is built for Codex, Claude, and Gemini
83
72
 
84
73
  ## Install
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "muuuuse",
3
- "version": "4.0.1",
4
- "description": "🔌Muuuuse arms regular terminals in isolated pairs and can continue relay output into any other armed seat.",
3
+ "version": "5.0.1",
4
+ "description": "🔌Muuuuse arms terminals with flexible routing to any targets - pure message relay graph.",
5
5
  "type": "commonjs",
6
6
  "bin": {
7
7
  "muuuuse": "bin/muuse.js"
package/src/agents.js CHANGED
@@ -69,6 +69,44 @@ function detectAgent(processes) {
69
69
  return buildDetectedAgent("gemini", process);
70
70
  }
71
71
  }
72
+
73
+ // Fallback: if no agent process found, check for Claude Code running in a separate terminal
74
+ // (Claude Code's session updates even if it's not a child process of this shell)
75
+ if (processes.length > 0) {
76
+ // Use the bash/shell process as reference for timing
77
+ const shellProcess = processes[0];
78
+ const now = Date.now();
79
+ const recentThreshold = now - 2 * 60 * 1000; // Last 2 minutes
80
+
81
+ const CLAUDE_ROOT = path.join(os.homedir(), ".claude", "projects");
82
+ if (fs.existsSync(CLAUDE_ROOT)) {
83
+ try {
84
+ const sessionFiles = walkFiles(CLAUDE_ROOT, (f) => f.endsWith(".jsonl"))
85
+ .map(filePath => {
86
+ try {
87
+ const stat = fs.statSync(filePath);
88
+ return { filePath, mtimeMs: stat.mtimeMs };
89
+ } catch {
90
+ return null;
91
+ }
92
+ })
93
+ .filter(e => e && e.mtimeMs > recentThreshold)
94
+ .sort((a, b) => b.mtimeMs - a.mtimeMs)[0];
95
+
96
+ if (sessionFiles) {
97
+ return {
98
+ type: "claude",
99
+ pid: process.pid || shellProcess.pid,
100
+ args: "claude-code-terminal-session",
101
+ cwd: shellProcess.cwd || null,
102
+ elapsedSeconds: Math.round((Date.now() - shellProcess.startedAtMs) / 1000),
103
+ processStartedAtMs: Date.now() - (shellProcess.elapsedSeconds ?? 0) * 1000,
104
+ };
105
+ }
106
+ } catch {}
107
+ }
108
+ }
109
+
72
110
  return null;
73
111
  }
74
112
 
package/src/cli.js CHANGED
@@ -105,16 +105,7 @@ function renderSeatStatus(seat) {
105
105
  }
106
106
 
107
107
  function renderLinkTargets(seat) {
108
- const targets = [];
109
- if (seat.partnerSeatId) {
110
- targets.push({
111
- targetSeatId: seat.partnerSeatId,
112
- flowMode: seat.flowMode || "off",
113
- });
114
- }
115
- for (const target of Array.isArray(seat.continueTargets) ? seat.continueTargets : []) {
116
- targets.push(target);
117
- }
108
+ const targets = Array.isArray(seat.continueTargets) ? seat.continueTargets : [];
118
109
  if (targets.length === 0) {
119
110
  return "";
120
111
  }
@@ -165,9 +156,7 @@ function parseSeatOptions(command, args) {
165
156
  }
166
157
 
167
158
  function parseLinkTargets(args, seatId, defaultFlowMode) {
168
- const partnerSeatId = seatId ? getPartnerSeatId(seatId) : null;
169
- const continueTargets = [];
170
- let flowMode = defaultFlowMode;
159
+ const targets = [];
171
160
  let consumed = 0;
172
161
 
173
162
  while (consumed < args.length) {
@@ -181,19 +170,15 @@ function parseLinkTargets(args, seatId, defaultFlowMode) {
181
170
  break;
182
171
  }
183
172
 
184
- if (targetSeatId === partnerSeatId) {
185
- flowMode = targetFlowMode;
186
- } else {
187
- upsertTarget(continueTargets, {
188
- targetSeatId,
189
- flowMode: targetFlowMode,
190
- });
191
- }
173
+ upsertTarget(targets, {
174
+ targetSeatId,
175
+ flowMode: targetFlowMode,
176
+ });
192
177
 
193
178
  consumed += 3;
194
179
  }
195
180
 
196
- return { consumed, continueTargets, flowMode };
181
+ return { consumed, continueTargets: targets, flowMode: defaultFlowMode };
197
182
  }
198
183
 
199
184
  function parseFlowModeToken(flowToken, modeToken) {
package/src/runtime.js CHANGED
@@ -222,7 +222,12 @@ function findJoinableSessionName(currentPath = process.cwd(), seatId = 2) {
222
222
  const stopRequestedAtMs = Date.parse(stopRequest?.requestedAt || "");
223
223
  const createdAtMs = Date.parse(controller?.createdAt || anchorMeta?.startedAt || anchorStatus?.updatedAt || "");
224
224
 
225
- if (!anchorLive || seatLive) {
225
+ // Accept session if: controller exists AND (anchor is live OR anchor has written meta/status files).
226
+ // This handles the startup race: anchor might not have a live PID yet, but if it wrote files, it's initialized.
227
+ const controllerExists = controller !== null;
228
+ const anchorInitialized = anchorMeta !== null || anchorStatus !== null;
229
+ const anchorReady = anchorLive || anchorInitialized;
230
+ if (!controllerExists || !anchorReady || seatLive) {
226
231
  return null;
227
232
  }
228
233
 
@@ -1524,12 +1529,12 @@ class ArmedSeat {
1524
1529
  );
1525
1530
 
1526
1531
  appendJsonl(this.paths.eventsPath, signedEntry);
1527
- this.forwardContinuation(signedEntry);
1532
+ this.routeToTargets(signedEntry);
1528
1533
  this.rememberEmittedAnswer(answerKey);
1529
1534
  this.log(`[${this.seatId}] ${previewText(payload)}`);
1530
1535
  }
1531
1536
 
1532
- forwardContinuation(signedEntry) {
1537
+ routeToTargets(signedEntry) {
1533
1538
  if (this.continueTargets.length === 0) {
1534
1539
  return;
1535
1540
  }
@@ -1541,7 +1546,7 @@ class ArmedSeat {
1541
1546
 
1542
1547
  const target = this.findContinuationTarget(targetEntry.targetSeatId);
1543
1548
  if (!target) {
1544
- this.log(`[${this.seatId}] continue ${targetEntry.targetSeatId} unavailable`);
1549
+ this.log(`[${this.seatId}] target ${targetEntry.targetSeatId} unavailable`);
1545
1550
  continue;
1546
1551
  }
1547
1552