muuuuse 1.3.1 → 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:
@@ -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.1",
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/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,
@@ -265,6 +266,7 @@ class SeatProcess {
265
266
  this.stopped = true;
266
267
  };
267
268
  process.once("SIGINT", stop);
269
+ process.once("SIGHUP", stop);
268
270
  process.once("SIGTERM", stop);
269
271
  }
270
272
 
@@ -280,15 +282,22 @@ class SeatProcess {
280
282
  this.genericTracker.noteTurnStart("");
281
283
  }
282
284
  };
285
+ const handleEnd = () => {
286
+ this.stopped = true;
287
+ };
283
288
 
284
289
  if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
285
290
  process.stdin.setRawMode(true);
286
291
  }
287
292
  process.stdin.resume();
288
293
  process.stdin.on("data", handleData);
294
+ process.stdin.on("close", handleEnd);
295
+ process.stdin.on("end", handleEnd);
289
296
 
290
297
  this.stdinCleanup = () => {
291
298
  process.stdin.off("data", handleData);
299
+ process.stdin.off("close", handleEnd);
300
+ process.stdin.off("end", handleEnd);
292
301
  if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
293
302
  process.stdin.setRawMode(false);
294
303
  }
@@ -528,7 +537,7 @@ class SeatProcess {
528
537
 
529
538
  this.log(`${BRAND} seat ${this.seatId} started in session ${this.sessionName}.`);
530
539
  this.log(`Command: ${formatCommand(this.commandTokens)}`);
531
- this.log(`Stop both seats from another terminal with: muuuuse 3 stop`);
540
+ this.log(`Stop everything from another terminal with: muuuuse stop`);
532
541
 
533
542
  try {
534
543
  while (!this.stopped) {
@@ -620,6 +629,56 @@ function stopSession(sessionName) {
620
629
  };
621
630
  }
622
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
+
623
682
  function readSessionStatus(sessionName) {
624
683
  return {
625
684
  sessionName,
@@ -634,11 +693,17 @@ function readSessionStatus(sessionName) {
634
693
  };
635
694
  }
636
695
 
696
+ function readAllSessionStatuses() {
697
+ return listSessionNames().map((sessionName) => readSessionStatus(sessionName));
698
+ }
699
+
637
700
  module.exports = {
638
701
  SeatProcess,
639
702
  formatCommand,
703
+ readAllSessionStatuses,
640
704
  readSessionStatus,
641
705
  resolveProgramTokens,
642
706
  resolveSessionName,
643
707
  stopSession,
708
+ stopSessions,
644
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");