muuuuse 0.2.0 → 1.3.1
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 +100 -6
- package/src/cli.js +51 -124
- package/src/runtime.js +457 -496
- 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
|
|
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.
|
|
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.1",
|
|
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,11 +44,39 @@ 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");
|
|
50
77
|
const CODEX_SNAPSHOT_ROOT = path.join(os.homedir(), ".codex", "shell_snapshots");
|
|
51
78
|
const codexSnapshotPaneCache = new Map();
|
|
79
|
+
const codexSnapshotExportsCache = new Map();
|
|
52
80
|
|
|
53
81
|
function walkFiles(rootPath, predicate, results = []) {
|
|
54
82
|
try {
|
|
@@ -130,6 +158,10 @@ function chooseCandidate(candidates, currentPath, processStartedAtMs) {
|
|
|
130
158
|
return null;
|
|
131
159
|
}
|
|
132
160
|
|
|
161
|
+
if (cwdMatches.length === 1) {
|
|
162
|
+
return cwdMatches[0].path;
|
|
163
|
+
}
|
|
164
|
+
|
|
133
165
|
if (processStartedAtMs !== null) {
|
|
134
166
|
const preciseMatches = cwdMatches
|
|
135
167
|
.map((candidate) => ({
|
|
@@ -139,13 +171,12 @@ function chooseCandidate(candidates, currentPath, processStartedAtMs) {
|
|
|
139
171
|
.filter((candidate) => Number.isFinite(candidate.diffMs) && candidate.diffMs <= SESSION_MATCH_WINDOW_MS)
|
|
140
172
|
.sort((left, right) => left.diffMs - right.diffMs || right.mtimeMs - left.mtimeMs);
|
|
141
173
|
|
|
142
|
-
if (preciseMatches.length
|
|
174
|
+
if (preciseMatches.length === 1) {
|
|
143
175
|
return preciseMatches[0].path;
|
|
144
176
|
}
|
|
145
177
|
}
|
|
146
178
|
|
|
147
|
-
|
|
148
|
-
return fallback ? fallback.path : null;
|
|
179
|
+
return null;
|
|
149
180
|
}
|
|
150
181
|
|
|
151
182
|
function extractThreadId(filePath) {
|
|
@@ -175,6 +206,49 @@ function readCodexSnapshotPane(threadId) {
|
|
|
175
206
|
}
|
|
176
207
|
}
|
|
177
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
|
+
|
|
178
252
|
function readCodexCandidate(filePath) {
|
|
179
253
|
try {
|
|
180
254
|
const [firstLine] = readFirstLines(filePath, 1);
|
|
@@ -189,6 +263,7 @@ function readCodexCandidate(filePath) {
|
|
|
189
263
|
return {
|
|
190
264
|
path: filePath,
|
|
191
265
|
threadId: extractThreadId(filePath),
|
|
266
|
+
snapshotExports: readCodexSnapshotExports(extractThreadId(filePath)),
|
|
192
267
|
snapshotPaneId: readCodexSnapshotPane(extractThreadId(filePath)),
|
|
193
268
|
cwd: entry.payload.cwd,
|
|
194
269
|
startedAtMs: Date.parse(entry.payload.timestamp),
|
|
@@ -199,12 +274,24 @@ function readCodexCandidate(filePath) {
|
|
|
199
274
|
}
|
|
200
275
|
}
|
|
201
276
|
|
|
202
|
-
function selectCodexSessionFile(currentPath, processStartedAtMs,
|
|
277
|
+
function selectCodexSessionFile(currentPath, processStartedAtMs, options = {}) {
|
|
278
|
+
const paneId = options.paneId || null;
|
|
279
|
+
const snapshotEnv = options.snapshotEnv || null;
|
|
203
280
|
const candidates = walkFiles(CODEX_ROOT, (filePath) => filePath.endsWith(".jsonl"))
|
|
204
281
|
.map((filePath) => readCodexCandidate(filePath))
|
|
205
282
|
.filter((candidate) => candidate !== null);
|
|
206
283
|
|
|
207
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
|
+
|
|
208
295
|
if (paneId) {
|
|
209
296
|
const exactPaneMatches = scopedCandidates.filter((candidate) => candidate.snapshotPaneId === paneId);
|
|
210
297
|
if (exactPaneMatches.length > 0) {
|
|
@@ -388,12 +475,16 @@ function selectGeminiSessionFile(currentPath, processStartedAtMs) {
|
|
|
388
475
|
.filter((candidate) => Number.isFinite(candidate.diffMs) && candidate.diffMs <= SESSION_MATCH_WINDOW_MS)
|
|
389
476
|
.sort((left, right) => left.diffMs - right.diffMs || right.lastUpdatedMs - left.lastUpdatedMs);
|
|
390
477
|
|
|
391
|
-
if (preciseMatches.length
|
|
478
|
+
if (preciseMatches.length === 1) {
|
|
392
479
|
return preciseMatches[0].path;
|
|
393
480
|
}
|
|
394
481
|
}
|
|
395
482
|
|
|
396
|
-
|
|
483
|
+
if (candidates.length === 1) {
|
|
484
|
+
return candidates[0].path;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return null;
|
|
397
488
|
}
|
|
398
489
|
|
|
399
490
|
function readGeminiAnswers(filePath, lastMessageId = null) {
|
|
@@ -433,7 +524,10 @@ function readGeminiAnswers(filePath, lastMessageId = null) {
|
|
|
433
524
|
|
|
434
525
|
module.exports = {
|
|
435
526
|
PRESETS,
|
|
527
|
+
chooseCandidate,
|
|
436
528
|
detectAgent,
|
|
529
|
+
detectAgentTypeFromCommand,
|
|
530
|
+
expandPresetCommand,
|
|
437
531
|
parseClaudeFinalLine,
|
|
438
532
|
parseCodexFinalLine,
|
|
439
533
|
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,
|