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 +5 -5
- package/package.json +1 -1
- package/src/cli.js +76 -23
- package/src/runtime.js +66 -1
- package/src/util.js +4 -4
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
|
|
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
|
|
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
|
|
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
|
|
83
|
+
muuuuse stop --session demo
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
You can also inspect the lane:
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
|
-
muuuuse
|
|
89
|
+
muuuuse status
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
## Doctor
|
package/package.json
CHANGED
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
|
-
|
|
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
|
|
51
|
-
|
|
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
|
|
63
|
-
|
|
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
|
|
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
|
|
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
|
|
265
|
-
" muuuuse
|
|
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
|
|
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
|
|
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");
|