muuuuse 0.2.0 → 1.3.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 +43 -55
- package/bin/muuse.js +0 -0
- package/package.json +8 -3
- package/src/agents.js +29 -0
- package/src/cli.js +51 -124
- package/src/runtime.js +437 -498
- package/src/util.js +44 -47
package/README.md
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
# 🔌Muuuuse
|
|
2
2
|
|
|
3
|
-
`muuuuse` installs one CLI
|
|
3
|
+
`muuuuse` installs one CLI:
|
|
4
4
|
|
|
5
5
|
- `muuuuse`
|
|
6
6
|
|
|
7
|
-
The
|
|
7
|
+
The public brand stays `🔌Muuuuse`. The terminal command stays `muuuuse`.
|
|
8
8
|
|
|
9
|
-
## What It
|
|
9
|
+
## What It Is Now
|
|
10
10
|
|
|
11
|
-
`🔌Muuuuse`
|
|
11
|
+
`🔌Muuuuse` no longer expects you to arm a terminal first and launch something later.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- seat `2` listens in another terminal
|
|
15
|
-
- seat `3` is the controller that auto-pairs them
|
|
13
|
+
The main flow is:
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
```bash
|
|
16
|
+
muuuuse 1 <program...>
|
|
17
|
+
muuuuse 2 <program...>
|
|
18
|
+
muuuuse 3 stop
|
|
19
|
+
```
|
|
20
|
+
|
|
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.
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
`muuuuse 3 stop` is the cleanup command for that lane.
|
|
20
24
|
|
|
21
25
|
## Install
|
|
22
26
|
|
|
@@ -24,72 +28,66 @@ Remote control is intentionally out of scope here. Use `codeman` or `codemansbot
|
|
|
24
28
|
npm install -g muuuuse
|
|
25
29
|
```
|
|
26
30
|
|
|
27
|
-
##
|
|
31
|
+
## Fastest AI Flow
|
|
28
32
|
|
|
29
33
|
Terminal 1:
|
|
30
34
|
|
|
31
35
|
```bash
|
|
32
|
-
muuuuse 1
|
|
33
|
-
codex -m gpt-5.4 -c model_reasoning_effort=low --dangerously-bypass-approvals-and-sandbox --no-alt-screen
|
|
36
|
+
muuuuse 1 codex
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
Terminal 2:
|
|
37
40
|
|
|
38
41
|
```bash
|
|
39
|
-
muuuuse 2
|
|
40
|
-
claude --dangerously-skip-permissions --permission-mode bypassPermissions
|
|
42
|
+
muuuuse 2 gemini
|
|
41
43
|
```
|
|
42
44
|
|
|
43
45
|
Terminal 3:
|
|
44
46
|
|
|
45
47
|
```bash
|
|
46
|
-
muuuuse 3
|
|
48
|
+
muuuuse 3 stop
|
|
47
49
|
```
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
Known presets expand to recommended flags automatically:
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
- `codex`
|
|
54
|
+
- `claude`
|
|
55
|
+
- `gemini`
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
So `muuuuse 1 codex` launches the fuller Codex command, not just bare `codex`.
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
codex -m gpt-5.4 -c model_reasoning_effort=low --dangerously-bypass-approvals-and-sandbox --no-alt-screen
|
|
59
|
-
claude --dangerously-skip-permissions --permission-mode bypassPermissions
|
|
60
|
-
gemini --approval-mode yolo --sandbox=false
|
|
61
|
-
```
|
|
59
|
+
## Generic Program Flow
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
This is not AI-only. Any local program can be wrapped directly.
|
|
64
62
|
|
|
65
|
-
|
|
63
|
+
Example:
|
|
66
64
|
|
|
67
65
|
```bash
|
|
68
|
-
muuuuse
|
|
66
|
+
muuuuse 1 bash -lc 'while read line; do printf "left: %s\n\n" "$line"; done'
|
|
67
|
+
muuuuse 2 bash -lc 'while read line; do printf "right: %s\n\n" "$line"; done'
|
|
69
68
|
```
|
|
70
69
|
|
|
71
|
-
|
|
70
|
+
Type into one seat and the other seat will receive the relayed block.
|
|
72
71
|
|
|
73
|
-
For a
|
|
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.
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
muuuuse script 4
|
|
77
|
-
```
|
|
74
|
+
## Sessions
|
|
78
75
|
|
|
79
|
-
|
|
76
|
+
Seats auto-pair by current working directory by default.
|
|
80
77
|
|
|
81
|
-
|
|
78
|
+
If you want an explicit lane name, use:
|
|
82
79
|
|
|
83
80
|
```bash
|
|
84
|
-
muuuuse
|
|
81
|
+
muuuuse 1 --session demo codex
|
|
82
|
+
muuuuse 2 --session demo gemini
|
|
83
|
+
muuuuse 3 --session demo stop
|
|
85
84
|
```
|
|
86
85
|
|
|
87
|
-
|
|
86
|
+
You can also inspect the lane:
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
- at least one local CLI you want to mirror: `codex`, `claude`, `gemini`, or script mode
|
|
88
|
+
```bash
|
|
89
|
+
muuuuse 3 status
|
|
90
|
+
```
|
|
93
91
|
|
|
94
92
|
## Doctor
|
|
95
93
|
|
|
@@ -97,21 +95,11 @@ muuuuse live
|
|
|
97
95
|
muuuuse doctor
|
|
98
96
|
```
|
|
99
97
|
|
|
100
|
-
This checks
|
|
101
|
-
|
|
102
|
-
- `git`
|
|
103
|
-
- `npm`
|
|
104
|
-
- `tmux`
|
|
105
|
-
- `codex`
|
|
106
|
-
- `claude`
|
|
107
|
-
- `gemini`
|
|
108
|
-
- `/root/npm.txt` or the fallback npm token path
|
|
98
|
+
This checks the local runtime plus common agent binaries if you use them.
|
|
109
99
|
|
|
110
100
|
## Notes
|
|
111
101
|
|
|
112
102
|
- local only
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
- no reasoning forwarding
|
|
117
|
-
- controller exit stops the relay
|
|
103
|
+
- no tmux requirement for the main path
|
|
104
|
+
- no remote control surface here; that belongs to `codeman`
|
|
105
|
+
- best with programs that naturally produce turn-shaped output
|
package/bin/muuse.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muuuuse",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "🔌Muuuuse
|
|
3
|
+
"version": "1.3.0",
|
|
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": {
|
|
7
7
|
"muuuuse": "bin/muuse.js"
|
|
@@ -30,9 +30,11 @@
|
|
|
30
30
|
"keywords": [
|
|
31
31
|
"terminal",
|
|
32
32
|
"ai",
|
|
33
|
-
"
|
|
33
|
+
"pty",
|
|
34
34
|
"relay",
|
|
35
35
|
"local",
|
|
36
|
+
"automation",
|
|
37
|
+
"script",
|
|
36
38
|
"codex",
|
|
37
39
|
"claude",
|
|
38
40
|
"gemini"
|
|
@@ -41,5 +43,8 @@
|
|
|
41
43
|
"test": "node test/cli.test.js",
|
|
42
44
|
"pack:local": "npm pack",
|
|
43
45
|
"prepublishOnly": "npm test"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"node-pty": "^1.1.0"
|
|
44
49
|
}
|
|
45
50
|
}
|
package/src/agents.js
CHANGED
|
@@ -44,6 +44,33 @@ const PRESETS = {
|
|
|
44
44
|
},
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
function expandPresetCommand(commandTokens, usePresets = true) {
|
|
48
|
+
if (!usePresets || !Array.isArray(commandTokens) || commandTokens.length !== 1) {
|
|
49
|
+
return Array.isArray(commandTokens) ? [...commandTokens] : [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const preset = PRESETS[String(commandTokens[0] || "").toLowerCase()];
|
|
53
|
+
return preset ? [...preset.command] : [...commandTokens];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function detectAgentTypeFromCommand(commandTokens) {
|
|
57
|
+
const executable = path.basename(String(commandTokens?.[0] || "")).toLowerCase();
|
|
58
|
+
if (!executable) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (executable === "codex") {
|
|
63
|
+
return "codex";
|
|
64
|
+
}
|
|
65
|
+
if (executable === "claude") {
|
|
66
|
+
return "claude";
|
|
67
|
+
}
|
|
68
|
+
if (executable === "gemini") {
|
|
69
|
+
return "gemini";
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
47
74
|
const CODEX_ROOT = path.join(os.homedir(), ".codex", "sessions");
|
|
48
75
|
const CLAUDE_ROOT = path.join(os.homedir(), ".claude", "projects");
|
|
49
76
|
const GEMINI_ROOT = path.join(os.homedir(), ".gemini", "tmp");
|
|
@@ -434,6 +461,8 @@ function readGeminiAnswers(filePath, lastMessageId = null) {
|
|
|
434
461
|
module.exports = {
|
|
435
462
|
PRESETS,
|
|
436
463
|
detectAgent,
|
|
464
|
+
detectAgentTypeFromCommand,
|
|
465
|
+
expandPresetCommand,
|
|
437
466
|
parseClaudeFinalLine,
|
|
438
467
|
parseCodexFinalLine,
|
|
439
468
|
readClaudeAnswers,
|
package/src/cli.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
const fs = require("node:fs");
|
|
2
|
-
const path = require("node:path");
|
|
3
|
-
const readline = require("node:readline/promises");
|
|
4
|
-
|
|
5
|
-
const { PRESETS } = require("./agents");
|
|
6
|
-
const { Controller, SeatDaemon, armSeat, configureScript, enableLiveMode } = require("./runtime");
|
|
7
|
-
const { getPaneInfo, insideTmux } = require("./tmux");
|
|
8
1
|
const {
|
|
9
2
|
BRAND,
|
|
10
3
|
commandExists,
|
|
11
|
-
findFirstExisting,
|
|
12
4
|
parseFlags,
|
|
13
5
|
readCommandVersion,
|
|
14
|
-
toInt,
|
|
15
6
|
usage,
|
|
16
7
|
} = require("./util");
|
|
8
|
+
const {
|
|
9
|
+
SeatProcess,
|
|
10
|
+
readSessionStatus,
|
|
11
|
+
resolveProgramTokens,
|
|
12
|
+
resolveSessionName,
|
|
13
|
+
stopSession,
|
|
14
|
+
} = require("./runtime");
|
|
17
15
|
|
|
18
16
|
async function main(argv = process.argv.slice(2)) {
|
|
19
17
|
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
@@ -28,130 +26,67 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
28
26
|
return;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
if (command === "daemon") {
|
|
32
|
-
const sessionName = argv[1];
|
|
33
|
-
const seatId = toInt(argv[2], 0);
|
|
34
|
-
if (!sessionName || ![1, 2].includes(seatId)) {
|
|
35
|
-
throw new Error("daemon requires <session-name> <1|2>.");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const daemon = new SeatDaemon(sessionName, seatId);
|
|
39
|
-
const code = await daemon.run();
|
|
40
|
-
process.exit(code);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
29
|
if (command === "1" || command === "2") {
|
|
44
|
-
armCurrentPane(Number(command));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (command === "3") {
|
|
49
30
|
const { positionals, flags } = parseFlags(argv.slice(1));
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
31
|
+
const sessionName = resolveSessionName(flags.session, process.cwd());
|
|
32
|
+
const commandTokens = resolveProgramTokens(positionals, !flags.noPreset);
|
|
33
|
+
const seat = new SeatProcess({
|
|
34
|
+
commandTokens,
|
|
35
|
+
cwd: process.cwd(),
|
|
55
36
|
maxRelays: flags.maxRelays,
|
|
37
|
+
seatId: Number(command),
|
|
38
|
+
sessionName,
|
|
56
39
|
});
|
|
57
|
-
const code = await
|
|
40
|
+
const code = await seat.run();
|
|
58
41
|
process.exit(code);
|
|
59
42
|
}
|
|
60
43
|
|
|
61
|
-
if (command === "
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function requireTmuxPane() {
|
|
80
|
-
if (!insideTmux()) {
|
|
81
|
-
throw new Error("muuuuse must run inside tmux so it can arm and inject the current pane.");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const paneInfo = getPaneInfo();
|
|
85
|
-
if (!paneInfo) {
|
|
86
|
-
throw new Error("tmux is active, but the current pane could not be resolved.");
|
|
87
|
-
}
|
|
88
|
-
return paneInfo;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function armCurrentPane(seatId) {
|
|
92
|
-
const paneInfo = requireTmuxPane();
|
|
93
|
-
const binPath = path.resolve(__dirname, "..", "bin", "muuse.js");
|
|
94
|
-
const meta = armSeat({
|
|
95
|
-
seatId,
|
|
96
|
-
paneInfo,
|
|
97
|
-
binPath,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
process.stdout.write(`${BRAND} armed seat ${seatId} in tmux session ${meta.sessionName}.\n`);
|
|
101
|
-
process.stdout.write(`Pane: ${meta.paneId}\n`);
|
|
102
|
-
process.stdout.write(`Path: ${meta.cwd}\n`);
|
|
103
|
-
process.stdout.write("\nLaunch one of these in this same terminal whenever you're ready:\n");
|
|
104
|
-
for (const preset of Object.values(PRESETS)) {
|
|
105
|
-
process.stdout.write(`- ${preset.command.join(" ")}\n`);
|
|
106
|
-
}
|
|
107
|
-
process.stdout.write("\nOr switch this seat to deterministic mode with `muuuuse script`.\n");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function setScriptMode(argv) {
|
|
111
|
-
const paneInfo = requireTmuxPane();
|
|
112
|
-
const { positionals, flags } = parseFlags(argv);
|
|
113
|
-
const count = Math.max(1, toInt(positionals[0] || 1, 1));
|
|
114
|
-
const steps = [...flags.step];
|
|
115
|
-
|
|
116
|
-
if (steps.length < count) {
|
|
117
|
-
const rl = readline.createInterface({
|
|
118
|
-
input: process.stdin,
|
|
119
|
-
output: process.stdout,
|
|
120
|
-
});
|
|
44
|
+
if (command === "3") {
|
|
45
|
+
const { positionals, flags } = parseFlags(argv.slice(1));
|
|
46
|
+
const sessionName = resolveSessionName(flags.session, process.cwd());
|
|
47
|
+
const action = String(positionals[0] || "status").toLowerCase();
|
|
48
|
+
|
|
49
|
+
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
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
121
60
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
61
|
+
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`);
|
|
127
67
|
continue;
|
|
128
68
|
}
|
|
129
|
-
|
|
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`);
|
|
130
73
|
}
|
|
131
|
-
|
|
132
|
-
rl.close();
|
|
74
|
+
return;
|
|
133
75
|
}
|
|
134
|
-
}
|
|
135
76
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
paneId: paneInfo.paneId,
|
|
139
|
-
steps: steps.slice(0, count),
|
|
140
|
-
});
|
|
77
|
+
throw new Error(`Unknown seat 3 action '${action}'. Try \`muuuuse 3 stop\` or \`muuuuse 3 status\`.`);
|
|
78
|
+
}
|
|
141
79
|
|
|
142
|
-
|
|
143
|
-
process.stdout.write(result.steps.length === 1 ? ".\n" : "s.\n");
|
|
80
|
+
throw new Error(`Unknown command '${command}'.`);
|
|
144
81
|
}
|
|
145
82
|
|
|
146
83
|
function runDoctor() {
|
|
147
84
|
const checks = [
|
|
148
|
-
checkBinary("
|
|
85
|
+
checkBinary("node", ["--version"], true),
|
|
149
86
|
checkBinary("npm", ["--version"], true),
|
|
150
|
-
checkBinary("tmux", ["-V"], true),
|
|
151
87
|
checkBinary("codex", ["--version"], false),
|
|
152
88
|
checkBinary("claude", ["--version"], false),
|
|
153
89
|
checkBinary("gemini", ["--version"], false),
|
|
154
|
-
checkPath("npm token file", findFirstExisting(["/root/npm.txt", "/root/_ops-bank/credentials/npm.txt"]), true),
|
|
155
90
|
];
|
|
156
91
|
|
|
157
92
|
process.stdout.write(`${BRAND} doctor\n\n`);
|
|
@@ -159,12 +94,11 @@ function runDoctor() {
|
|
|
159
94
|
process.stdout.write(`${item.ok ? "OK " : "MISS"} ${item.label}${item.detail ? `: ${item.detail}` : ""}\n`);
|
|
160
95
|
}
|
|
161
96
|
|
|
162
|
-
process.stdout.write("\
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
process.stdout.write("\nRemote routing lives in Codeman / codemansbot. This package stays local-only.\n");
|
|
97
|
+
process.stdout.write("\nKnown presets\n");
|
|
98
|
+
process.stdout.write("- codex\n");
|
|
99
|
+
process.stdout.write("- claude\n");
|
|
100
|
+
process.stdout.write("- gemini\n");
|
|
101
|
+
process.stdout.write("\nAny other local program can be wrapped directly with `muuuuse 1 <program...>` or `muuuuse 2 <program...>`.\n");
|
|
168
102
|
|
|
169
103
|
const missingRequired = checks.some((item) => item.required && !item.ok);
|
|
170
104
|
if (missingRequired) {
|
|
@@ -180,13 +114,6 @@ function checkBinary(command, versionArgs, required) {
|
|
|
180
114
|
return { label: command, ok: true, detail: version, required };
|
|
181
115
|
}
|
|
182
116
|
|
|
183
|
-
function checkPath(label, filePath, required) {
|
|
184
|
-
if (!filePath || !fs.existsSync(filePath)) {
|
|
185
|
-
return { label, ok: false, detail: "not found", required };
|
|
186
|
-
}
|
|
187
|
-
return { label, ok: true, detail: filePath, required };
|
|
188
|
-
}
|
|
189
|
-
|
|
190
117
|
module.exports = {
|
|
191
118
|
main,
|
|
192
119
|
runDoctor,
|