muuuuse 2.1.0 → 2.2.0
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 +11 -6
- package/package.json +2 -2
- package/src/agents.js +30 -10
- package/src/cli.js +20 -4
- package/src/runtime.js +78 -28
- package/src/util.js +9 -4
package/README.md
CHANGED
|
@@ -6,15 +6,18 @@ It does one job:
|
|
|
6
6
|
- arm terminal one with `muuuuse 1`
|
|
7
7
|
- arm terminal two with `muuuuse 2`
|
|
8
8
|
- have seat 1 generate a session key and seat 2 sign it
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- choose per-seat relay mode with `flow on` or `flow off`
|
|
10
|
+
- watch Codex, Claude, or Gemini for local assistant output
|
|
11
|
+
- inject that output into the other armed terminal
|
|
11
12
|
- keep looping until you stop it
|
|
12
13
|
|
|
13
14
|
The whole surface is:
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
17
|
muuuuse 1
|
|
18
|
+
muuuuse 1 flow on
|
|
17
19
|
muuuuse 2
|
|
20
|
+
muuuuse 2 flow off
|
|
18
21
|
muuuuse status
|
|
19
22
|
muuuuse stop
|
|
20
23
|
```
|
|
@@ -24,17 +27,19 @@ muuuuse stop
|
|
|
24
27
|
Terminal 1:
|
|
25
28
|
|
|
26
29
|
```bash
|
|
27
|
-
muuuuse 1
|
|
30
|
+
muuuuse 1 flow on
|
|
28
31
|
```
|
|
29
32
|
|
|
30
33
|
Terminal 2:
|
|
31
34
|
|
|
32
35
|
```bash
|
|
33
|
-
muuuuse 2
|
|
36
|
+
muuuuse 2 flow off
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
Now both shells are armed. `muuuuse 1` generates the session key, `muuuuse 2` signs it, and only that signed pair relays. Use those shells normally.
|
|
37
40
|
|
|
41
|
+
`flow on` means that seat sends commentary and final answers. `flow off` means that seat waits for final answers only. Each seat decides what it sends out, so mixed calibration is allowed.
|
|
42
|
+
|
|
38
43
|
If you want Codex in one and Gemini in the other, start them inside the armed shells:
|
|
39
44
|
|
|
40
45
|
```bash
|
|
@@ -45,7 +50,7 @@ codex
|
|
|
45
50
|
gemini
|
|
46
51
|
```
|
|
47
52
|
|
|
48
|
-
`🔌Muuuuse` tails the local session logs for supported CLIs,
|
|
53
|
+
`🔌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.
|
|
49
54
|
|
|
50
55
|
Check the live state from any terminal:
|
|
51
56
|
|
|
@@ -64,7 +69,7 @@ muuuuse stop
|
|
|
64
69
|
- no tmux
|
|
65
70
|
- state lives under `~/.muuuuse`
|
|
66
71
|
- only the signed armed pair can exchange relay events
|
|
67
|
-
- supported
|
|
72
|
+
- supported relay detection is built for Codex, Claude, and Gemini
|
|
68
73
|
- `codeman` remains the larger transport/control layer; `muuuuse` stays local and minimal
|
|
69
74
|
|
|
70
75
|
## Install
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muuuuse",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "🔌Muuuuse arms two regular terminals and relays
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "🔌Muuuuse arms two regular terminals and relays assistant output between them.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"muuuuse": "bin/muuse.js"
|
package/src/agents.js
CHANGED
|
@@ -459,14 +459,17 @@ function extractCodexAssistantText(content) {
|
|
|
459
459
|
.join("\n");
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
-
function
|
|
462
|
+
function parseCodexAssistantLine(line, options = {}) {
|
|
463
|
+
const flowMode = options.flowMode === true;
|
|
463
464
|
try {
|
|
464
465
|
const entry = JSON.parse(line);
|
|
465
466
|
if (entry?.type !== "response_item" || entry.payload?.type !== "message" || entry.payload?.role !== "assistant") {
|
|
466
467
|
return null;
|
|
467
468
|
}
|
|
468
469
|
|
|
469
|
-
|
|
470
|
+
const phase = String(entry.payload?.phase || "").trim().toLowerCase();
|
|
471
|
+
const relayablePhase = phase === "final_answer" || (flowMode && phase === "commentary");
|
|
472
|
+
if (!relayablePhase) {
|
|
470
473
|
return null;
|
|
471
474
|
}
|
|
472
475
|
|
|
@@ -485,6 +488,10 @@ function parseCodexFinalLine(line) {
|
|
|
485
488
|
}
|
|
486
489
|
}
|
|
487
490
|
|
|
491
|
+
function parseCodexFinalLine(line) {
|
|
492
|
+
return parseCodexAssistantLine(line, { flowMode: false });
|
|
493
|
+
}
|
|
494
|
+
|
|
488
495
|
function isAnswerNewEnough(answer, sinceMs = null) {
|
|
489
496
|
if (!Number.isFinite(sinceMs)) {
|
|
490
497
|
return true;
|
|
@@ -498,13 +505,13 @@ function isAnswerNewEnough(answer, sinceMs = null) {
|
|
|
498
505
|
return answerMs >= sinceMs;
|
|
499
506
|
}
|
|
500
507
|
|
|
501
|
-
function readCodexAnswers(filePath, offset, sinceMs = null) {
|
|
508
|
+
function readCodexAnswers(filePath, offset, sinceMs = null, options = {}) {
|
|
502
509
|
const { nextOffset, text } = readAppendedText(filePath, offset);
|
|
503
510
|
const answers = text
|
|
504
511
|
.split("\n")
|
|
505
512
|
.map((line) => line.trim())
|
|
506
513
|
.filter((line) => line.length > 0)
|
|
507
|
-
.map((line) =>
|
|
514
|
+
.map((line) => parseCodexAssistantLine(line, options))
|
|
508
515
|
.filter((entry) => entry !== null)
|
|
509
516
|
.filter((entry) => isAnswerNewEnough(entry, sinceMs));
|
|
510
517
|
|
|
@@ -547,7 +554,8 @@ function selectClaudeSessionFile(currentPath, processStartedAtMs, options = {})
|
|
|
547
554
|
return selectSessionCandidatePath(candidates, currentPath, processStartedAtMs);
|
|
548
555
|
}
|
|
549
556
|
|
|
550
|
-
function extractClaudeAssistantText(content) {
|
|
557
|
+
function extractClaudeAssistantText(content, options = {}) {
|
|
558
|
+
const flowMode = options.flowMode === true;
|
|
551
559
|
if (!Array.isArray(content)) {
|
|
552
560
|
return "";
|
|
553
561
|
}
|
|
@@ -560,20 +568,28 @@ function extractClaudeAssistantText(content) {
|
|
|
560
568
|
if (item.type === "text" && typeof item.text === "string") {
|
|
561
569
|
return [item.text.trim()];
|
|
562
570
|
}
|
|
571
|
+
if (flowMode && item.type === "thinking" && typeof item.thinking === "string") {
|
|
572
|
+
return [item.thinking.trim()];
|
|
573
|
+
}
|
|
563
574
|
return [];
|
|
564
575
|
})
|
|
565
576
|
.filter((text) => text.length > 0)
|
|
566
577
|
.join("\n");
|
|
567
578
|
}
|
|
568
579
|
|
|
569
|
-
function
|
|
580
|
+
function parseClaudeAssistantLine(line, options = {}) {
|
|
581
|
+
const flowMode = options.flowMode === true;
|
|
570
582
|
try {
|
|
571
583
|
const entry = JSON.parse(line);
|
|
572
|
-
if (entry?.type !== "assistant" || entry.message?.role !== "assistant"
|
|
584
|
+
if (entry?.type !== "assistant" || entry.message?.role !== "assistant") {
|
|
573
585
|
return null;
|
|
574
586
|
}
|
|
575
587
|
|
|
576
|
-
|
|
588
|
+
if (!flowMode && entry.message?.stop_reason !== "end_turn") {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const text = sanitizeRelayText(extractClaudeAssistantText(entry.message.content, options));
|
|
577
593
|
if (!text) {
|
|
578
594
|
return null;
|
|
579
595
|
}
|
|
@@ -588,13 +604,17 @@ function parseClaudeFinalLine(line) {
|
|
|
588
604
|
}
|
|
589
605
|
}
|
|
590
606
|
|
|
591
|
-
function
|
|
607
|
+
function parseClaudeFinalLine(line) {
|
|
608
|
+
return parseClaudeAssistantLine(line, { flowMode: false });
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function readClaudeAnswers(filePath, offset, sinceMs = null, options = {}) {
|
|
592
612
|
const { nextOffset, text } = readAppendedText(filePath, offset);
|
|
593
613
|
const answers = text
|
|
594
614
|
.split("\n")
|
|
595
615
|
.map((line) => line.trim())
|
|
596
616
|
.filter((line) => line.length > 0)
|
|
597
|
-
.map((line) =>
|
|
617
|
+
.map((line) => parseClaudeAssistantLine(line, options))
|
|
598
618
|
.filter((entry) => entry !== null)
|
|
599
619
|
.filter((entry) => isAnswerNewEnough(entry, sinceMs));
|
|
600
620
|
|
package/src/cli.js
CHANGED
|
@@ -55,12 +55,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
if (command === "1" || command === "2") {
|
|
58
|
-
|
|
59
|
-
throw new Error(`\`muuuuse ${command}\` takes no extra arguments. Run it directly in the terminal you want to arm.`);
|
|
60
|
-
}
|
|
61
|
-
|
|
58
|
+
const flowMode = parseSeatFlowMode(command, argv.slice(1));
|
|
62
59
|
const seat = new ArmedSeat({
|
|
63
60
|
cwd: process.cwd(),
|
|
61
|
+
flowMode,
|
|
64
62
|
seatId: Number(command),
|
|
65
63
|
});
|
|
66
64
|
const code = await seat.run();
|
|
@@ -74,6 +72,7 @@ function renderSeatStatus(seat) {
|
|
|
74
72
|
const bits = [
|
|
75
73
|
`seat ${seat.seatId}: ${seat.state}`,
|
|
76
74
|
`agent ${seat.agent || "idle"}`,
|
|
75
|
+
`flow ${seat.flowMode || "off"}`,
|
|
77
76
|
`relays ${seat.relayCount}`,
|
|
78
77
|
`wrapper ${seat.wrapperPid || "-"}`,
|
|
79
78
|
`child ${seat.childPid || "-"}`,
|
|
@@ -99,6 +98,23 @@ function renderSeatStatus(seat) {
|
|
|
99
98
|
return output;
|
|
100
99
|
}
|
|
101
100
|
|
|
101
|
+
function parseSeatFlowMode(command, args) {
|
|
102
|
+
if (args.length === 0) {
|
|
103
|
+
return "off";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (args.length === 2 && String(args[0]).trim().toLowerCase() === "flow") {
|
|
107
|
+
const flowMode = String(args[1]).trim().toLowerCase();
|
|
108
|
+
if (flowMode === "on" || flowMode === "off") {
|
|
109
|
+
return flowMode;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
throw new Error(
|
|
114
|
+
`\`muuuuse ${command}\` accepts either no extra arguments or \`flow on\` / \`flow off\`. Run it directly in the terminal you want to arm.`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
module.exports = {
|
|
103
119
|
main,
|
|
104
120
|
};
|
package/src/runtime.js
CHANGED
|
@@ -51,6 +51,10 @@ const CHILD_ENV_DROP_KEYS = [
|
|
|
51
51
|
"CODEX_THREAD_ID",
|
|
52
52
|
];
|
|
53
53
|
|
|
54
|
+
function normalizeFlowMode(flowMode) {
|
|
55
|
+
return String(flowMode || "").trim().toLowerCase() === "on" ? "on" : "off";
|
|
56
|
+
}
|
|
57
|
+
|
|
54
58
|
function resolveShell() {
|
|
55
59
|
const shell = String(process.env.SHELL || "").trim();
|
|
56
60
|
return shell || "/bin/bash";
|
|
@@ -214,6 +218,51 @@ function parseAnswerEntries(text) {
|
|
|
214
218
|
.filter((entry) => entry && entry.type === "answer" && typeof entry.text === "string");
|
|
215
219
|
}
|
|
216
220
|
|
|
221
|
+
function readSessionHeaderText(filePath, maxBytes = 16384) {
|
|
222
|
+
try {
|
|
223
|
+
const fd = fs.openSync(filePath, "r");
|
|
224
|
+
try {
|
|
225
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
226
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
227
|
+
return buffer.toString("utf8", 0, bytesRead);
|
|
228
|
+
} finally {
|
|
229
|
+
fs.closeSync(fd);
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
return "";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function readSessionFileStartedAtMs(agentType, filePath) {
|
|
237
|
+
try {
|
|
238
|
+
if (agentType === "gemini") {
|
|
239
|
+
const entry = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
240
|
+
return Date.parse(entry.startTime || entry.lastUpdated || "");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const header = readSessionHeaderText(filePath);
|
|
244
|
+
const lines = header
|
|
245
|
+
.split("\n")
|
|
246
|
+
.map((line) => line.trim())
|
|
247
|
+
.filter(Boolean);
|
|
248
|
+
|
|
249
|
+
for (const line of lines) {
|
|
250
|
+
const entry = JSON.parse(line);
|
|
251
|
+
if (agentType === "codex" && entry?.type === "session_meta") {
|
|
252
|
+
return Date.parse(entry.payload?.timestamp || "");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (agentType === "claude") {
|
|
256
|
+
return Date.parse(entry.timestamp || entry.message?.timestamp || "");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
return Number.NaN;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return Number.NaN;
|
|
264
|
+
}
|
|
265
|
+
|
|
217
266
|
function readProcessCwd(pid) {
|
|
218
267
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
219
268
|
return null;
|
|
@@ -387,35 +436,23 @@ function readSeatChallenge(paths, sessionName) {
|
|
|
387
436
|
}
|
|
388
437
|
|
|
389
438
|
async function sendTextAndEnter(child, text, shouldAbort = () => false) {
|
|
390
|
-
const
|
|
439
|
+
const payload = String(text || "")
|
|
440
|
+
.replace(/\r/g, "")
|
|
441
|
+
.replace(/\s*\n+\s*/g, " ")
|
|
442
|
+
.replace(/[ \t]{2,}/g, " ")
|
|
443
|
+
.trim();
|
|
391
444
|
|
|
392
|
-
|
|
445
|
+
if (payload.length > 0) {
|
|
393
446
|
if (shouldAbort() || !child) {
|
|
394
447
|
return false;
|
|
395
448
|
}
|
|
396
449
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
} catch {
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
await sleep(TYPE_DELAY_MS);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
if (index < lines.length - 1) {
|
|
408
|
-
if (shouldAbort()) {
|
|
409
|
-
return false;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
try {
|
|
413
|
-
child.write("\r");
|
|
414
|
-
} catch {
|
|
415
|
-
return false;
|
|
416
|
-
}
|
|
417
|
-
await sleep(TYPE_DELAY_MS);
|
|
450
|
+
try {
|
|
451
|
+
child.write(payload);
|
|
452
|
+
} catch {
|
|
453
|
+
return false;
|
|
418
454
|
}
|
|
455
|
+
await sleep(TYPE_DELAY_MS);
|
|
419
456
|
}
|
|
420
457
|
|
|
421
458
|
if (shouldAbort() || !child) {
|
|
@@ -435,6 +472,7 @@ class ArmedSeat {
|
|
|
435
472
|
constructor(options) {
|
|
436
473
|
this.seatId = options.seatId;
|
|
437
474
|
this.partnerSeatId = options.seatId === 1 ? 2 : 1;
|
|
475
|
+
this.flowMode = normalizeFlowMode(options.flowMode);
|
|
438
476
|
this.cwd = normalizeWorkingPath(options.cwd);
|
|
439
477
|
this.sessionName = resolveSessionName(this.cwd, this.seatId);
|
|
440
478
|
if (!this.sessionName) {
|
|
@@ -503,6 +541,7 @@ class ArmedSeat {
|
|
|
503
541
|
seatId: this.seatId,
|
|
504
542
|
partnerSeatId: this.partnerSeatId,
|
|
505
543
|
sessionName: this.sessionName,
|
|
544
|
+
flowMode: this.flowMode,
|
|
506
545
|
cwd: this.cwd,
|
|
507
546
|
pid: process.pid,
|
|
508
547
|
childPid: this.childPid,
|
|
@@ -517,6 +556,7 @@ class ArmedSeat {
|
|
|
517
556
|
seatId: this.seatId,
|
|
518
557
|
partnerSeatId: this.partnerSeatId,
|
|
519
558
|
sessionName: this.sessionName,
|
|
559
|
+
flowMode: this.flowMode,
|
|
520
560
|
cwd: this.cwd,
|
|
521
561
|
pid: process.pid,
|
|
522
562
|
childPid: this.childPid,
|
|
@@ -1079,6 +1119,10 @@ class ArmedSeat {
|
|
|
1079
1119
|
this.liveState.sessionFile = resolvedSessionFile;
|
|
1080
1120
|
this.liveState.offset = 0;
|
|
1081
1121
|
this.liveState.lastMessageId = null;
|
|
1122
|
+
const sessionStartedAtMs = readSessionFileStartedAtMs(detectedAgent.type, resolvedSessionFile);
|
|
1123
|
+
if (Number.isFinite(sessionStartedAtMs)) {
|
|
1124
|
+
this.liveState.captureSinceMs = Math.min(this.liveState.captureSinceMs, sessionStartedAtMs);
|
|
1125
|
+
}
|
|
1082
1126
|
}
|
|
1083
1127
|
|
|
1084
1128
|
if (!this.liveState.sessionFile) {
|
|
@@ -1096,7 +1140,8 @@ class ArmedSeat {
|
|
|
1096
1140
|
const result = readCodexAnswers(
|
|
1097
1141
|
this.liveState.sessionFile,
|
|
1098
1142
|
this.liveState.offset,
|
|
1099
|
-
this.liveState.captureSinceMs
|
|
1143
|
+
this.liveState.captureSinceMs,
|
|
1144
|
+
{ flowMode: this.flowMode === "on" }
|
|
1100
1145
|
);
|
|
1101
1146
|
this.liveState.offset = result.nextOffset;
|
|
1102
1147
|
answers.push(...result.answers);
|
|
@@ -1104,7 +1149,8 @@ class ArmedSeat {
|
|
|
1104
1149
|
const result = readClaudeAnswers(
|
|
1105
1150
|
this.liveState.sessionFile,
|
|
1106
1151
|
this.liveState.offset,
|
|
1107
|
-
this.liveState.captureSinceMs
|
|
1152
|
+
this.liveState.captureSinceMs,
|
|
1153
|
+
{ flowMode: this.flowMode === "on" }
|
|
1108
1154
|
);
|
|
1109
1155
|
this.liveState.offset = result.nextOffset;
|
|
1110
1156
|
answers.push(...result.answers);
|
|
@@ -1112,7 +1158,8 @@ class ArmedSeat {
|
|
|
1112
1158
|
const result = readGeminiAnswers(
|
|
1113
1159
|
this.liveState.sessionFile,
|
|
1114
1160
|
this.liveState.lastMessageId,
|
|
1115
|
-
this.liveState.captureSinceMs
|
|
1161
|
+
this.liveState.captureSinceMs,
|
|
1162
|
+
{ flowMode: this.flowMode === "on" }
|
|
1116
1163
|
);
|
|
1117
1164
|
this.liveState.lastMessageId = result.lastMessageId;
|
|
1118
1165
|
this.liveState.offset = result.fileSize;
|
|
@@ -1161,7 +1208,7 @@ class ArmedSeat {
|
|
|
1161
1208
|
}
|
|
1162
1209
|
|
|
1163
1210
|
const pendingInboundContext = this.getPendingInboundContext();
|
|
1164
|
-
if (pendingInboundContext && pendingInboundContext.hop >= MAX_RELAY_CHAIN_HOP) {
|
|
1211
|
+
if (this.flowMode !== "on" && pendingInboundContext && pendingInboundContext.hop >= MAX_RELAY_CHAIN_HOP) {
|
|
1165
1212
|
this.log(`[${this.seatId}] suppressed relay loop: ${previewText(payload)}`);
|
|
1166
1213
|
return;
|
|
1167
1214
|
}
|
|
@@ -1223,6 +1270,7 @@ class ArmedSeat {
|
|
|
1223
1270
|
this.writeStatus({
|
|
1224
1271
|
state: live.state,
|
|
1225
1272
|
agent: live.agent,
|
|
1273
|
+
flowMode: this.flowMode,
|
|
1226
1274
|
cwd: live.cwd,
|
|
1227
1275
|
log: live.log,
|
|
1228
1276
|
lastAnswerAt: live.lastAnswerAt,
|
|
@@ -1239,7 +1287,8 @@ class ArmedSeat {
|
|
|
1239
1287
|
this.installResizeHandler();
|
|
1240
1288
|
|
|
1241
1289
|
this.log(`${BRAND} seat ${this.seatId} armed for ${this.sessionName}.`);
|
|
1242
|
-
this.log("Use this shell normally. Codex, Claude, and Gemini
|
|
1290
|
+
this.log("Use this shell normally. Codex, Claude, and Gemini relay automatically from their local session logs.");
|
|
1291
|
+
this.log(`Seat ${this.seatId} relay mode is flow ${this.flowMode}.`);
|
|
1243
1292
|
if (this.seatId === 1) {
|
|
1244
1293
|
this.log("Seat 1 generated the session key and is waiting for seat 2 to sign it.");
|
|
1245
1294
|
} else {
|
|
@@ -1339,6 +1388,7 @@ function buildSeatReport(sessionName, seatId) {
|
|
|
1339
1388
|
return {
|
|
1340
1389
|
seatId,
|
|
1341
1390
|
state: wrapperLive ? status?.state || "running" : "orphaned_child",
|
|
1391
|
+
flowMode: status?.flowMode || meta?.flowMode || "off",
|
|
1342
1392
|
wrapperPid,
|
|
1343
1393
|
childPid,
|
|
1344
1394
|
wrapperLive,
|
package/src/util.js
CHANGED
|
@@ -245,11 +245,15 @@ function listSessionNames() {
|
|
|
245
245
|
|
|
246
246
|
function usage() {
|
|
247
247
|
return [
|
|
248
|
-
`${BRAND} arms two regular terminals and relays
|
|
248
|
+
`${BRAND} arms two regular terminals and relays assistant output between them.`,
|
|
249
249
|
"",
|
|
250
250
|
"Usage:",
|
|
251
251
|
" muuuuse 1",
|
|
252
|
+
" muuuuse 1 flow on",
|
|
253
|
+
" muuuuse 1 flow off",
|
|
252
254
|
" muuuuse 2",
|
|
255
|
+
" muuuuse 2 flow on",
|
|
256
|
+
" muuuuse 2 flow off",
|
|
253
257
|
" muuuuse stop",
|
|
254
258
|
" muuuuse status",
|
|
255
259
|
"",
|
|
@@ -257,9 +261,10 @@ function usage() {
|
|
|
257
261
|
" 1. Run `muuuuse 1` in terminal one.",
|
|
258
262
|
" 2. Run `muuuuse 2` in terminal two.",
|
|
259
263
|
" 3. Seat 1 generates the session key and seat 2 signs it automatically.",
|
|
260
|
-
" 4.
|
|
261
|
-
" 5.
|
|
262
|
-
" 6.
|
|
264
|
+
" 4. Optional: arm each seat with `flow on` or `flow off`.",
|
|
265
|
+
" 5. Use those armed shells normally.",
|
|
266
|
+
" 6. `flow off` sends final answers only. `flow on` keeps assistant commentary bouncing.",
|
|
267
|
+
" 7. Run `muuuuse status` or `muuuuse stop` from any shell.",
|
|
263
268
|
"",
|
|
264
269
|
"Notes:",
|
|
265
270
|
" - No tmux.",
|