muuuuse 1.3.0 → 1.3.2

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
@@ -15,12 +15,12 @@ The main flow is:
15
15
  ```bash
16
16
  muuuuse 1 <program...>
17
17
  muuuuse 2 <program...>
18
- muuuuse 3 stop
18
+ muuuuse stop
19
19
  ```
20
20
 
21
21
  Seat `1` and seat `2` each launch and own a real local program under a PTY wrapper. Once both seats are alive in the same lane, they automatically bounce final blocks between each other by typing the partner answer plus `Enter` into the wrapped program.
22
22
 
23
- `muuuuse 3 stop` is the cleanup command for that lane.
23
+ `muuuuse stop` is the real cleanup command. With no flags it force-stops every tracked lane. Use `--session <name>` if you only want one explicit lane.
24
24
 
25
25
  ## Install
26
26
 
@@ -45,7 +45,7 @@ muuuuse 2 gemini
45
45
  Terminal 3:
46
46
 
47
47
  ```bash
48
- muuuuse 3 stop
48
+ muuuuse stop
49
49
  ```
50
50
 
51
51
  Known presets expand to recommended flags automatically:
@@ -69,7 +69,7 @@ muuuuse 2 bash -lc 'while read line; do printf "right: %s\n\n" "$line"; done'
69
69
 
70
70
  Type into one seat and the other seat will receive the relayed block.
71
71
 
72
- For Codex, Claude, and Gemini, `🔌Muuuuse` prefers their structured session logs. For anything else, it falls back to the last stable output block after a turn goes idle.
72
+ For Codex, Claude, and Gemini, `🔌Muuuuse` waits for their structured final-answer logs instead of relaying transient screen chatter. For anything else, it first looks for an explicit `(answer)` block and otherwise falls back to the last stable output block after a turn goes idle.
73
73
 
74
74
  ## Sessions
75
75
 
@@ -80,13 +80,13 @@ If you want an explicit lane name, use:
80
80
  ```bash
81
81
  muuuuse 1 --session demo codex
82
82
  muuuuse 2 --session demo gemini
83
- muuuuse 3 --session demo stop
83
+ muuuuse stop --session demo
84
84
  ```
85
85
 
86
86
  You can also inspect the lane:
87
87
 
88
88
  ```bash
89
- muuuuse 3 status
89
+ muuuuse status
90
90
  ```
91
91
 
92
92
  ## Doctor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muuuuse",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "🔌Muuuuse wraps two local programs and bounces final blocks between them from the terminal you launched.",
5
5
  "type": "commonjs",
6
6
  "bin": {
package/src/agents.js CHANGED
@@ -76,6 +76,7 @@ const CLAUDE_ROOT = path.join(os.homedir(), ".claude", "projects");
76
76
  const GEMINI_ROOT = path.join(os.homedir(), ".gemini", "tmp");
77
77
  const CODEX_SNAPSHOT_ROOT = path.join(os.homedir(), ".codex", "shell_snapshots");
78
78
  const codexSnapshotPaneCache = new Map();
79
+ const codexSnapshotExportsCache = new Map();
79
80
 
80
81
  function walkFiles(rootPath, predicate, results = []) {
81
82
  try {
@@ -157,6 +158,10 @@ function chooseCandidate(candidates, currentPath, processStartedAtMs) {
157
158
  return null;
158
159
  }
159
160
 
161
+ if (cwdMatches.length === 1) {
162
+ return cwdMatches[0].path;
163
+ }
164
+
160
165
  if (processStartedAtMs !== null) {
161
166
  const preciseMatches = cwdMatches
162
167
  .map((candidate) => ({
@@ -166,13 +171,12 @@ function chooseCandidate(candidates, currentPath, processStartedAtMs) {
166
171
  .filter((candidate) => Number.isFinite(candidate.diffMs) && candidate.diffMs <= SESSION_MATCH_WINDOW_MS)
167
172
  .sort((left, right) => left.diffMs - right.diffMs || right.mtimeMs - left.mtimeMs);
168
173
 
169
- if (preciseMatches.length > 0) {
174
+ if (preciseMatches.length === 1) {
170
175
  return preciseMatches[0].path;
171
176
  }
172
177
  }
173
178
 
174
- const fallback = cwdMatches.sort((left, right) => right.mtimeMs - left.mtimeMs)[0];
175
- return fallback ? fallback.path : null;
179
+ return null;
176
180
  }
177
181
 
178
182
  function extractThreadId(filePath) {
@@ -202,6 +206,49 @@ function readCodexSnapshotPane(threadId) {
202
206
  }
203
207
  }
204
208
 
209
+ function readCodexSnapshotExports(threadId) {
210
+ if (!threadId) {
211
+ return {};
212
+ }
213
+
214
+ if (codexSnapshotExportsCache.has(threadId)) {
215
+ return codexSnapshotExportsCache.get(threadId);
216
+ }
217
+
218
+ const snapshotPath = path.join(CODEX_SNAPSHOT_ROOT, `${threadId}.sh`);
219
+ try {
220
+ const contents = fs.readFileSync(snapshotPath, "utf8");
221
+ const exportsMap = {};
222
+ const pattern = /^declare -x ([A-Z0-9_]+)="((?:[^"\\]|\\.)*)"$/gm;
223
+
224
+ for (const match of contents.matchAll(pattern)) {
225
+ const [, key = "", rawValue = ""] = match;
226
+ exportsMap[key] = rawValue
227
+ .replace(/\\"/g, "\"")
228
+ .replace(/\\\\/g, "\\");
229
+ }
230
+
231
+ codexSnapshotExportsCache.set(threadId, exportsMap);
232
+ return exportsMap;
233
+ } catch (error) {
234
+ codexSnapshotExportsCache.set(threadId, {});
235
+ return {};
236
+ }
237
+ }
238
+
239
+ function snapshotEnvMatches(exportsMap, expectedEnv = null) {
240
+ if (!expectedEnv || typeof expectedEnv !== "object") {
241
+ return true;
242
+ }
243
+
244
+ return Object.entries(expectedEnv).every(([key, value]) => {
245
+ if (value === undefined || value === null) {
246
+ return true;
247
+ }
248
+ return exportsMap[key] === String(value);
249
+ });
250
+ }
251
+
205
252
  function readCodexCandidate(filePath) {
206
253
  try {
207
254
  const [firstLine] = readFirstLines(filePath, 1);
@@ -216,6 +263,7 @@ function readCodexCandidate(filePath) {
216
263
  return {
217
264
  path: filePath,
218
265
  threadId: extractThreadId(filePath),
266
+ snapshotExports: readCodexSnapshotExports(extractThreadId(filePath)),
219
267
  snapshotPaneId: readCodexSnapshotPane(extractThreadId(filePath)),
220
268
  cwd: entry.payload.cwd,
221
269
  startedAtMs: Date.parse(entry.payload.timestamp),
@@ -226,12 +274,24 @@ function readCodexCandidate(filePath) {
226
274
  }
227
275
  }
228
276
 
229
- function selectCodexSessionFile(currentPath, processStartedAtMs, paneId = null) {
277
+ function selectCodexSessionFile(currentPath, processStartedAtMs, options = {}) {
278
+ const paneId = options.paneId || null;
279
+ const snapshotEnv = options.snapshotEnv || null;
230
280
  const candidates = walkFiles(CODEX_ROOT, (filePath) => filePath.endsWith(".jsonl"))
231
281
  .map((filePath) => readCodexCandidate(filePath))
232
282
  .filter((candidate) => candidate !== null);
233
283
 
234
284
  let scopedCandidates = candidates;
285
+ if (snapshotEnv) {
286
+ const exactEnvMatches = scopedCandidates.filter((candidate) => snapshotEnvMatches(candidate.snapshotExports, snapshotEnv));
287
+ if (exactEnvMatches.length === 1) {
288
+ return exactEnvMatches[0].path;
289
+ }
290
+ if (exactEnvMatches.length > 1) {
291
+ scopedCandidates = exactEnvMatches;
292
+ }
293
+ }
294
+
235
295
  if (paneId) {
236
296
  const exactPaneMatches = scopedCandidates.filter((candidate) => candidate.snapshotPaneId === paneId);
237
297
  if (exactPaneMatches.length > 0) {
@@ -415,12 +475,16 @@ function selectGeminiSessionFile(currentPath, processStartedAtMs) {
415
475
  .filter((candidate) => Number.isFinite(candidate.diffMs) && candidate.diffMs <= SESSION_MATCH_WINDOW_MS)
416
476
  .sort((left, right) => left.diffMs - right.diffMs || right.lastUpdatedMs - left.lastUpdatedMs);
417
477
 
418
- if (preciseMatches.length > 0) {
478
+ if (preciseMatches.length === 1) {
419
479
  return preciseMatches[0].path;
420
480
  }
421
481
  }
422
482
 
423
- return candidates.sort((left, right) => right.lastUpdatedMs - left.lastUpdatedMs || right.mtimeMs - left.mtimeMs)[0].path;
483
+ if (candidates.length === 1) {
484
+ return candidates[0].path;
485
+ }
486
+
487
+ return null;
424
488
  }
425
489
 
426
490
  function readGeminiAnswers(filePath, lastMessageId = null) {
@@ -460,6 +524,7 @@ function readGeminiAnswers(filePath, lastMessageId = null) {
460
524
 
461
525
  module.exports = {
462
526
  PRESETS,
527
+ chooseCandidate,
463
528
  detectAgent,
464
529
  detectAgentTypeFromCommand,
465
530
  expandPresetCommand,
package/src/cli.js CHANGED
@@ -7,10 +7,11 @@ const {
7
7
  } = require("./util");
8
8
  const {
9
9
  SeatProcess,
10
+ readAllSessionStatuses,
10
11
  readSessionStatus,
11
12
  resolveProgramTokens,
12
13
  resolveSessionName,
13
- stopSession,
14
+ stopSessions,
14
15
  } = require("./runtime");
15
16
 
16
17
  async function main(argv = process.argv.slice(2)) {
@@ -26,6 +27,51 @@ async function main(argv = process.argv.slice(2)) {
26
27
  return;
27
28
  }
28
29
 
30
+ if (command === "stop") {
31
+ const { flags } = parseFlags(argv.slice(1));
32
+ const result = await stopSessions(flags.session || null);
33
+ if (result.sessions.length === 0) {
34
+ process.stdout.write(`${BRAND} no live sessions found.\n`);
35
+ return;
36
+ }
37
+
38
+ if (flags.session) {
39
+ process.stdout.write(`${BRAND} stop requested for session ${flags.session}.\n`);
40
+ } else {
41
+ process.stdout.write(`${BRAND} stop requested for all sessions.\n`);
42
+ }
43
+
44
+ for (const session of result.sessions) {
45
+ process.stdout.write(`${session.sessionName}\n`);
46
+ for (const seat of session.seats) {
47
+ process.stdout.write(
48
+ `seat ${seat.seatId}: wrapper ${describeStopResult(seat.wrapperStopped, seat.wrapperForced)}`
49
+ + `, child ${describeStopResult(seat.childStopped, seat.childForced)}\n`
50
+ );
51
+ }
52
+ }
53
+ return;
54
+ }
55
+
56
+ if (command === "status") {
57
+ const { flags } = parseFlags(argv.slice(1));
58
+ if (flags.session) {
59
+ printSessionStatus(readSessionStatus(flags.session));
60
+ return;
61
+ }
62
+
63
+ const sessions = readAllSessionStatuses();
64
+ if (sessions.length === 0) {
65
+ process.stdout.write(`${BRAND} no tracked sessions.\n`);
66
+ return;
67
+ }
68
+
69
+ for (const session of sessions) {
70
+ printSessionStatus(session);
71
+ }
72
+ return;
73
+ }
74
+
29
75
  if (command === "1" || command === "2") {
30
76
  const { positionals, flags } = parseFlags(argv.slice(1));
31
77
  const sessionName = resolveSessionName(flags.session, process.cwd());
@@ -43,38 +89,21 @@ async function main(argv = process.argv.slice(2)) {
43
89
 
44
90
  if (command === "3") {
45
91
  const { positionals, flags } = parseFlags(argv.slice(1));
46
- const sessionName = resolveSessionName(flags.session, process.cwd());
47
92
  const action = String(positionals[0] || "status").toLowerCase();
48
93
 
49
94
  if (action === "stop") {
50
- const result = stopSession(sessionName);
51
- process.stdout.write(`${BRAND} stop requested for session ${result.sessionName}.\n`);
52
- for (const seat of result.seats) {
53
- process.stdout.write(
54
- `seat ${seat.seatId}: wrapper ${seat.wrapperStopped ? "signaled" : "idle"}`
55
- + `, child ${seat.childStopped ? "signaled" : "idle"}\n`
56
- );
57
- }
95
+ const forwarded = ["stop", ...argv.slice(1).filter((token) => token !== "stop")];
96
+ await main(forwarded);
58
97
  return;
59
98
  }
60
99
 
61
100
  if (action === "status") {
62
- const status = readSessionStatus(sessionName);
63
- process.stdout.write(`${BRAND} session ${status.sessionName}\n`);
64
- for (const seat of status.seats) {
65
- if (!seat.status) {
66
- process.stdout.write(`seat ${seat.seatId}: idle\n`);
67
- continue;
68
- }
69
-
70
- const state = seat.status.state || "unknown";
71
- const program = Array.isArray(seat.status.command) ? seat.status.command.join(" ") : "";
72
- process.stdout.write(`seat ${seat.seatId}: ${state}${program ? ` (${program})` : ""}\n`);
73
- }
101
+ const forwarded = ["status", ...argv.slice(1).filter((token) => token !== "status")];
102
+ await main(forwarded);
74
103
  return;
75
104
  }
76
105
 
77
- throw new Error(`Unknown seat 3 action '${action}'. Try \`muuuuse 3 stop\` or \`muuuuse 3 status\`.`);
106
+ throw new Error(`Unknown seat 3 action '${action}'. Try \`muuuuse stop\` or \`muuuuse status\`.`);
78
107
  }
79
108
 
80
109
  throw new Error(`Unknown command '${command}'.`);
@@ -114,6 +143,30 @@ function checkBinary(command, versionArgs, required) {
114
143
  return { label: command, ok: true, detail: version, required };
115
144
  }
116
145
 
146
+ function describeStopResult(signaled, forced) {
147
+ if (forced) {
148
+ return "killed";
149
+ }
150
+ if (signaled) {
151
+ return "signaled";
152
+ }
153
+ return "idle";
154
+ }
155
+
156
+ function printSessionStatus(status) {
157
+ process.stdout.write(`${BRAND} session ${status.sessionName}\n`);
158
+ for (const seat of status.seats) {
159
+ if (!seat.status) {
160
+ process.stdout.write(`seat ${seat.seatId}: idle\n`);
161
+ continue;
162
+ }
163
+
164
+ const state = seat.status.state || "unknown";
165
+ const program = Array.isArray(seat.status.command) ? seat.status.command.join(" ") : "";
166
+ process.stdout.write(`seat ${seat.seatId}: ${state}${program ? ` (${program})` : ""}\n`);
167
+ }
168
+ }
169
+
117
170
  module.exports = {
118
171
  main,
119
172
  runDoctor,
package/src/runtime.js CHANGED
@@ -20,6 +20,7 @@ const {
20
20
  getDefaultSessionName,
21
21
  getFileSize,
22
22
  getSeatPaths,
23
+ getStateRoot,
23
24
  hashText,
24
25
  isPidAlive,
25
26
  readAppendedText,
@@ -31,7 +32,6 @@ const {
31
32
  } = require("./util");
32
33
 
33
34
  const GENERIC_IDLE_MS = 900;
34
- const GENERIC_FALLBACK_DELAY_MS = 4000;
35
35
 
36
36
  function resolveSessionName(sessionOverride, currentPath = process.cwd()) {
37
37
  return sessionOverride || getDefaultSessionName(currentPath);
@@ -79,9 +79,9 @@ function parseAnswerEntries(text) {
79
79
  .filter((entry) => entry && entry.type === "answer" && typeof entry.text === "string");
80
80
  }
81
81
 
82
- function resolveSessionFile(agentType, currentPath, processStartedAtMs) {
82
+ function resolveSessionFile(agentType, currentPath, processStartedAtMs, options = {}) {
83
83
  if (agentType === "codex") {
84
- return selectCodexSessionFile(currentPath, processStartedAtMs);
84
+ return selectCodexSessionFile(currentPath, processStartedAtMs, options);
85
85
  }
86
86
  if (agentType === "claude") {
87
87
  return selectClaudeSessionFile(currentPath, processStartedAtMs);
@@ -161,6 +161,11 @@ function extractGenericAnswer(rawText, lastInputText) {
161
161
  }
162
162
  }
163
163
 
164
+ const markerAnswer = extractMarkedAnswer(candidate);
165
+ if (markerAnswer) {
166
+ return markerAnswer;
167
+ }
168
+
164
169
  const blocks = candidate
165
170
  .split(/\n{2,}/)
166
171
  .map((block) => block.trim())
@@ -173,6 +178,18 @@ function extractGenericAnswer(rawText, lastInputText) {
173
178
  return sanitizeRelayText(blocks[blocks.length - 1]);
174
179
  }
175
180
 
181
+ function extractMarkedAnswer(content) {
182
+ const lines = String(content || "").split("\n");
183
+ const answerIndex = lines.findIndex((line) => line.trim().startsWith("(answer)"));
184
+ if (answerIndex === -1) {
185
+ return null;
186
+ }
187
+
188
+ const answerLines = lines.slice(answerIndex);
189
+ answerLines[0] = answerLines[0].trim().replace(/^\(answer\)\s*/, "");
190
+ return sanitizeRelayText(answerLines.join("\n"));
191
+ }
192
+
176
193
  class SeatProcess {
177
194
  constructor(options) {
178
195
  this.seatId = options.seatId;
@@ -196,6 +213,8 @@ class SeatProcess {
196
213
  this.stopped = false;
197
214
  this.stdinCleanup = null;
198
215
  this.resizeCleanup = null;
216
+ this.childToken = createId(16);
217
+ this.processStartedAtMs = null;
199
218
 
200
219
  this.sessionState = {
201
220
  file: null,
@@ -217,6 +236,7 @@ class SeatProcess {
217
236
  cwd: this.cwd,
218
237
  pid: process.pid,
219
238
  childPid: this.childPid,
239
+ childToken: this.childToken,
220
240
  agentType: this.agentType,
221
241
  command: this.commandTokens,
222
242
  commandLine: formatCommand(this.commandTokens),
@@ -232,6 +252,7 @@ class SeatProcess {
232
252
  cwd: this.cwd,
233
253
  pid: process.pid,
234
254
  childPid: this.childPid,
255
+ childToken: this.childToken,
235
256
  agentType: this.agentType,
236
257
  command: this.commandTokens,
237
258
  relayCount: this.relayCount,
@@ -245,6 +266,7 @@ class SeatProcess {
245
266
  this.stopped = true;
246
267
  };
247
268
  process.once("SIGINT", stop);
269
+ process.once("SIGHUP", stop);
248
270
  process.once("SIGTERM", stop);
249
271
  }
250
272
 
@@ -260,15 +282,22 @@ class SeatProcess {
260
282
  this.genericTracker.noteTurnStart("");
261
283
  }
262
284
  };
285
+ const handleEnd = () => {
286
+ this.stopped = true;
287
+ };
263
288
 
264
289
  if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
265
290
  process.stdin.setRawMode(true);
266
291
  }
267
292
  process.stdin.resume();
268
293
  process.stdin.on("data", handleData);
294
+ process.stdin.on("close", handleEnd);
295
+ process.stdin.on("end", handleEnd);
269
296
 
270
297
  this.stdinCleanup = () => {
271
298
  process.stdin.off("data", handleData);
299
+ process.stdin.off("close", handleEnd);
300
+ process.stdin.off("end", handleEnd);
272
301
  if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
273
302
  process.stdin.setRawMode(false);
274
303
  }
@@ -307,6 +336,7 @@ class SeatProcess {
307
336
  cwd: this.cwd,
308
337
  env: {
309
338
  ...process.env,
339
+ MUUUUSE_CHILD_TOKEN: this.childToken,
310
340
  MUUUUSE_SEAT: String(this.seatId),
311
341
  MUUUUSE_SESSION: this.sessionName,
312
342
  },
@@ -315,6 +345,7 @@ class SeatProcess {
315
345
  });
316
346
 
317
347
  this.childPid = this.child.pid;
348
+ this.processStartedAtMs = Date.now();
318
349
  this.writeMeta();
319
350
  this.writeStatus({
320
351
  partnerSeatId: this.partnerSeatId,
@@ -352,15 +383,7 @@ class SeatProcess {
352
383
  }
353
384
 
354
385
  shouldUseGenericCapture() {
355
- if (!this.agentType) {
356
- return true;
357
- }
358
-
359
- if (this.sessionState.file) {
360
- return false;
361
- }
362
-
363
- return Date.now() - this.startedAtMs >= GENERIC_FALLBACK_DELAY_MS;
386
+ return !this.agentType;
364
387
  }
365
388
 
366
389
  pullPartnerEvents() {
@@ -401,7 +424,15 @@ class SeatProcess {
401
424
  return;
402
425
  }
403
426
 
404
- const sessionFile = resolveSessionFile(this.agentType, this.cwd, this.startedAtMs);
427
+ const sessionFile = resolveSessionFile(this.agentType, this.cwd, this.processStartedAtMs, {
428
+ snapshotEnv: this.agentType === "codex"
429
+ ? {
430
+ MUUUUSE_CHILD_TOKEN: this.childToken,
431
+ MUUUUSE_SEAT: String(this.seatId),
432
+ MUUUUSE_SESSION: this.sessionName,
433
+ }
434
+ : null,
435
+ });
405
436
  if (!sessionFile) {
406
437
  return;
407
438
  }
@@ -506,7 +537,7 @@ class SeatProcess {
506
537
 
507
538
  this.log(`${BRAND} seat ${this.seatId} started in session ${this.sessionName}.`);
508
539
  this.log(`Command: ${formatCommand(this.commandTokens)}`);
509
- this.log(`Stop both seats from another terminal with: muuuuse 3 stop`);
540
+ this.log(`Stop everything from another terminal with: muuuuse stop`);
510
541
 
511
542
  try {
512
543
  while (!this.stopped) {
@@ -598,6 +629,56 @@ function stopSession(sessionName) {
598
629
  };
599
630
  }
600
631
 
632
+ function listSessionNames() {
633
+ const sessionsRoot = path.join(getStateRoot(), "sessions");
634
+ try {
635
+ return fs.readdirSync(sessionsRoot, { withFileTypes: true })
636
+ .filter((entry) => entry.isDirectory())
637
+ .map((entry) => entry.name)
638
+ .sort();
639
+ } catch (error) {
640
+ return [];
641
+ }
642
+ }
643
+
644
+ async function stopSessions(sessionName = null) {
645
+ const sessionNames = sessionName ? [sessionName] : listSessionNames();
646
+ const sessionResults = sessionNames.map((name) => stopSession(name));
647
+
648
+ await sleep(200);
649
+
650
+ for (const sessionResult of sessionResults) {
651
+ for (const seat of sessionResult.seats) {
652
+ if (seat.wrapperPid && isPidAlive(seat.wrapperPid)) {
653
+ try {
654
+ process.kill(seat.wrapperPid, "SIGKILL");
655
+ seat.wrapperForced = true;
656
+ } catch (error) {
657
+ seat.wrapperForced = false;
658
+ }
659
+ } else {
660
+ seat.wrapperForced = false;
661
+ }
662
+
663
+ if (seat.childPid && isPidAlive(seat.childPid)) {
664
+ try {
665
+ process.kill(seat.childPid, "SIGKILL");
666
+ seat.childForced = true;
667
+ } catch (error) {
668
+ seat.childForced = false;
669
+ }
670
+ } else {
671
+ seat.childForced = false;
672
+ }
673
+ }
674
+ }
675
+
676
+ return {
677
+ sessionName,
678
+ sessions: sessionResults,
679
+ };
680
+ }
681
+
601
682
  function readSessionStatus(sessionName) {
602
683
  return {
603
684
  sessionName,
@@ -612,11 +693,17 @@ function readSessionStatus(sessionName) {
612
693
  };
613
694
  }
614
695
 
696
+ function readAllSessionStatuses() {
697
+ return listSessionNames().map((sessionName) => readSessionStatus(sessionName));
698
+ }
699
+
615
700
  module.exports = {
616
701
  SeatProcess,
617
702
  formatCommand,
703
+ readAllSessionStatuses,
618
704
  readSessionStatus,
619
705
  resolveProgramTokens,
620
706
  resolveSessionName,
621
707
  stopSession,
708
+ stopSessions,
622
709
  };
package/src/util.js CHANGED
@@ -261,19 +261,19 @@ function usage() {
261
261
  "Usage:",
262
262
  " muuuuse 1 <program...>",
263
263
  " muuuuse 2 <program...>",
264
- " muuuuse 3 stop",
265
- " muuuuse 3 status",
264
+ " muuuuse stop",
265
+ " muuuuse status",
266
266
  " muuuuse doctor",
267
267
  "",
268
268
  "Examples:",
269
269
  " muuuuse 1 codex",
270
270
  " muuuuse 2 gemini",
271
- " muuuuse 3 stop",
271
+ " muuuuse stop",
272
272
  " muuuuse 1 bash -lc 'while read line; do printf \"script one: %s\\n\\n\" \"$line\"; done'",
273
273
  "",
274
274
  "Notes:",
275
275
  " - Seats auto-pair by current working directory by default.",
276
- " - Use `--session <name>` on seats and seat 3 if you want an explicit shared lane.",
276
+ " - Use `--session <name>` on seats and stop/status if you want an explicit shared lane.",
277
277
  " - Known presets (`codex`, `claude`, `gemini`) expand to recommended launch flags.",
278
278
  " - Any other program runs as-is inside the current terminal under a PTY wrapper.",
279
279
  ].join("\n");