muuuuse 1.3.2 → 1.4.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/src/util.js CHANGED
@@ -2,24 +2,11 @@ const { createHash, randomBytes } = require("node:crypto");
2
2
  const fs = require("node:fs");
3
3
  const os = require("node:os");
4
4
  const path = require("node:path");
5
- const { spawnSync } = require("node:child_process");
6
5
 
7
6
  const BRAND = "🔌Muuuuse";
8
- const POLL_MS = 250;
9
- const SESSION_MATCH_WINDOW_MS = 5 * 60 * 1000;
7
+ const POLL_MS = 220;
10
8
  const MAX_RELAY_CHARS = 4000;
11
-
12
- const FLAG_ALIASES = new Map([
13
- ["--max-relays", "maxRelays"],
14
- ["--no-preset", "noPreset"],
15
- ["--session", "session"],
16
- ]);
17
-
18
- const BOOLEAN_FLAGS = new Set(["noPreset"]);
19
-
20
- function shellEscape(value) {
21
- return `'${String(value).replace(/'/g, `'\\''`)}'`;
22
- }
9
+ const SESSION_MATCH_WINDOW_MS = 5 * 60 * 1000;
23
10
 
24
11
  function createId(length = 10) {
25
12
  return randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
@@ -34,12 +21,6 @@ function ensureDir(dirPath) {
34
21
  return dirPath;
35
22
  }
36
23
 
37
- function resetDir(dirPath) {
38
- fs.rmSync(dirPath, { recursive: true, force: true });
39
- fs.mkdirSync(dirPath, { recursive: true });
40
- return dirPath;
41
- }
42
-
43
24
  function writeJson(filePath, value) {
44
25
  ensureDir(path.dirname(filePath));
45
26
  const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
@@ -50,7 +31,7 @@ function writeJson(filePath, value) {
50
31
  function readJson(filePath, fallback = null) {
51
32
  try {
52
33
  return JSON.parse(fs.readFileSync(filePath, "utf8"));
53
- } catch (error) {
34
+ } catch {
54
35
  return fallback;
55
36
  }
56
37
  }
@@ -65,10 +46,7 @@ function readAppendedText(filePath, previousOffset = 0) {
65
46
  const stats = fs.statSync(filePath);
66
47
  const startOffset = stats.size < previousOffset ? 0 : previousOffset;
67
48
  if (stats.size === startOffset) {
68
- return {
69
- nextOffset: startOffset,
70
- text: "",
71
- };
49
+ return { nextOffset: startOffset, text: "" };
72
50
  }
73
51
 
74
52
  const fd = fs.openSync(filePath, "r");
@@ -85,10 +63,7 @@ function readAppendedText(filePath, previousOffset = 0) {
85
63
  }
86
64
  } catch (error) {
87
65
  if (error && error.code === "ENOENT") {
88
- return {
89
- nextOffset: 0,
90
- text: "",
91
- };
66
+ return { nextOffset: 0, text: "" };
92
67
  }
93
68
  throw error;
94
69
  }
@@ -97,29 +72,11 @@ function readAppendedText(filePath, previousOffset = 0) {
97
72
  function getFileSize(filePath) {
98
73
  try {
99
74
  return fs.statSync(filePath).size;
100
- } catch (error) {
75
+ } catch {
101
76
  return 0;
102
77
  }
103
78
  }
104
79
 
105
- function commandExists(command) {
106
- const result = spawnSync("bash", ["-lc", `command -v ${shellEscape(command)} >/dev/null 2>&1`], {
107
- encoding: "utf8",
108
- });
109
- return result.status === 0;
110
- }
111
-
112
- function readCommandVersion(command, args = ["--version"]) {
113
- const result = spawnSync(command, args, {
114
- encoding: "utf8",
115
- timeout: 4000,
116
- });
117
- if (result.status !== 0) {
118
- return null;
119
- }
120
- return (result.stdout || result.stderr || "").trim().split("\n")[0] || null;
121
- }
122
-
123
80
  function stripAnsi(text) {
124
81
  return String(text || "").replace(
125
82
  // eslint-disable-next-line no-control-regex
@@ -132,6 +89,7 @@ function sanitizeRelayText(input, maxChars = MAX_RELAY_CHARS) {
132
89
  const normalized = stripAnsi(input)
133
90
  .replace(/\r/g, "")
134
91
  .replace(/\u0000/g, "")
92
+ .replace(/\u0007/g, "")
135
93
  .replace(/\n{3,}/g, "\n\n")
136
94
  .trim();
137
95
 
@@ -142,11 +100,6 @@ function sanitizeRelayText(input, maxChars = MAX_RELAY_CHARS) {
142
100
  return `${normalized.slice(0, maxChars - 3).trimEnd()}...`;
143
101
  }
144
102
 
145
- function toInt(value, fallback) {
146
- const parsed = Number.parseInt(String(value), 10);
147
- return Number.isFinite(parsed) ? parsed : fallback;
148
- }
149
-
150
103
  function isPidAlive(pid) {
151
104
  if (!Number.isInteger(pid) || pid <= 0) {
152
105
  return false;
@@ -155,7 +108,7 @@ function isPidAlive(pid) {
155
108
  try {
156
109
  process.kill(pid, 0);
157
110
  return true;
158
- } catch (error) {
111
+ } catch {
159
112
  return false;
160
113
  }
161
114
  }
@@ -179,7 +132,7 @@ function getDefaultSessionName(currentPath = process.cwd()) {
179
132
  const resolvedPath = (() => {
180
133
  try {
181
134
  return fs.realpathSync(currentPath);
182
- } catch (error) {
135
+ } catch {
183
136
  return path.resolve(currentPath);
184
137
  }
185
138
  })();
@@ -192,6 +145,15 @@ function getSessionDir(sessionName) {
192
145
  return ensureDir(path.join(getStateRoot(), "sessions", slugifySegment(sessionName)));
193
146
  }
194
147
 
148
+ function getSessionPaths(sessionName) {
149
+ const dir = getSessionDir(sessionName);
150
+ return {
151
+ dir,
152
+ controllerPath: path.join(dir, "controller.json"),
153
+ stopPath: path.join(dir, "stop.json"),
154
+ };
155
+ }
156
+
195
157
  function getSeatDir(sessionName, seatId) {
196
158
  return ensureDir(path.join(getSessionDir(sessionName), `seat-${seatId}`));
197
159
  }
@@ -200,111 +162,69 @@ function getSeatPaths(sessionName, seatId) {
200
162
  const dir = getSeatDir(sessionName, seatId);
201
163
  return {
202
164
  dir,
165
+ daemonPath: path.join(dir, "daemon.json"),
203
166
  eventsPath: path.join(dir, "events.jsonl"),
204
167
  metaPath: path.join(dir, "meta.json"),
168
+ pipePath: path.join(dir, "pipe.log"),
205
169
  statusPath: path.join(dir, "status.json"),
206
170
  };
207
171
  }
208
172
 
209
- function parseFlags(argv) {
210
- const positionals = [];
211
- const flags = {
212
- maxRelays: Number.POSITIVE_INFINITY,
213
- noPreset: false,
214
- session: null,
215
- };
216
-
217
- for (let index = 0; index < argv.length; index += 1) {
218
- const token = argv[index];
219
- if (!token.startsWith("--")) {
220
- positionals.push(token);
221
- continue;
222
- }
223
-
224
- const [rawFlag, inlineValue] = token.split("=", 2);
225
- const key = FLAG_ALIASES.get(rawFlag);
226
- if (!key) {
227
- throw new Error(`Unknown flag: ${rawFlag}`);
228
- }
229
-
230
- if (BOOLEAN_FLAGS.has(key)) {
231
- flags[key] = true;
232
- continue;
233
- }
234
-
235
- const next = inlineValue !== undefined ? inlineValue : argv[index + 1];
236
- if (inlineValue === undefined) {
237
- index += 1;
238
- }
239
-
240
- if (next === undefined) {
241
- throw new Error(`Missing value for ${rawFlag}`);
242
- }
243
-
244
- flags[key] = next;
173
+ function listSessionNames() {
174
+ const sessionsRoot = path.join(getStateRoot(), "sessions");
175
+ try {
176
+ return fs.readdirSync(sessionsRoot, { withFileTypes: true })
177
+ .filter((entry) => entry.isDirectory())
178
+ .map((entry) => entry.name)
179
+ .sort();
180
+ } catch {
181
+ return [];
245
182
  }
246
-
247
- flags.maxRelays = flags.maxRelays === Number.POSITIVE_INFINITY
248
- ? Number.POSITIVE_INFINITY
249
- : toInt(flags.maxRelays, Number.POSITIVE_INFINITY);
250
-
251
- return {
252
- positionals,
253
- flags,
254
- };
255
183
  }
256
184
 
257
185
  function usage() {
258
186
  return [
259
- `${BRAND} wraps two local programs and bounces final blocks between them.`,
187
+ `${BRAND} arms two regular terminals and relays final answers between them.`,
260
188
  "",
261
189
  "Usage:",
262
- " muuuuse 1 <program...>",
263
- " muuuuse 2 <program...>",
190
+ " muuuuse 1",
191
+ " muuuuse 2",
264
192
  " muuuuse stop",
265
193
  " muuuuse status",
266
- " muuuuse doctor",
267
194
  "",
268
- "Examples:",
269
- " muuuuse 1 codex",
270
- " muuuuse 2 gemini",
271
- " muuuuse stop",
272
- " muuuuse 1 bash -lc 'while read line; do printf \"script one: %s\\n\\n\" \"$line\"; done'",
195
+ "Flow:",
196
+ " 1. Run `muuuuse 1` in terminal one.",
197
+ " 2. Run `muuuuse 2` in terminal two.",
198
+ " 3. Use those armed shells normally.",
199
+ " 4. Codex, Claude, and Gemini final answers relay automatically from their local session logs.",
200
+ " 5. Run `muuuuse status` or `muuuuse stop` from any shell.",
273
201
  "",
274
202
  "Notes:",
275
- " - Seats auto-pair by current working directory by default.",
276
- " - Use `--session <name>` on seats and stop/status if you want an explicit shared lane.",
277
- " - Known presets (`codex`, `claude`, `gemini`) expand to recommended launch flags.",
278
- " - Any other program runs as-is inside the current terminal under a PTY wrapper.",
203
+ " - No tmux.",
204
+ " - `muuuuse stop` and `muuuuse status` work from another terminal or the same one.",
205
+ " - State lives under `~/.muuuuse`.",
279
206
  ].join("\n");
280
207
  }
281
208
 
282
209
  module.exports = {
283
210
  BRAND,
284
- MAX_RELAY_CHARS,
285
211
  POLL_MS,
286
212
  SESSION_MATCH_WINDOW_MS,
287
213
  appendJsonl,
288
- commandExists,
289
214
  createId,
290
215
  ensureDir,
291
216
  getDefaultSessionName,
292
217
  getFileSize,
293
- getSeatDir,
294
218
  getSeatPaths,
295
- getSessionDir,
219
+ getSessionPaths,
296
220
  getStateRoot,
297
221
  hashText,
298
222
  isPidAlive,
299
- parseFlags,
223
+ listSessionNames,
300
224
  readAppendedText,
301
- readCommandVersion,
302
225
  readJson,
303
- resetDir,
304
226
  sanitizeRelayText,
305
- shellEscape,
306
227
  sleep,
307
- toInt,
308
228
  usage,
309
229
  writeJson,
310
230
  };
package/src/tmux.js DELETED
@@ -1,170 +0,0 @@
1
- const { execFileSync } = require("node:child_process");
2
- const SLEEP_BUFFER = new SharedArrayBuffer(4);
3
- const SLEEP_VIEW = new Int32Array(SLEEP_BUFFER);
4
-
5
- function runTmux(args) {
6
- return execFileSync("tmux", args, { encoding: "utf8" });
7
- }
8
-
9
- function insideTmux() {
10
- return Boolean(process.env.TMUX && process.env.TMUX_PANE);
11
- }
12
-
13
- function getPaneInfo(target = process.env.TMUX_PANE) {
14
- if (!target) {
15
- return null;
16
- }
17
-
18
- try {
19
- const output = runTmux([
20
- "display-message",
21
- "-p",
22
- "-t",
23
- target,
24
- "#{session_name}\t#{window_index}\t#{window_name}\t#{pane_id}\t#{pane_current_path}\t#{pane_pid}",
25
- ]).trim();
26
-
27
- if (!output) {
28
- return null;
29
- }
30
-
31
- const [sessionName = "", windowIndex = "", windowName = "", paneId = "", currentPath = "", panePid = ""] =
32
- output.split("\t");
33
-
34
- return {
35
- sessionName,
36
- windowIndex: Number.parseInt(windowIndex, 10),
37
- windowName,
38
- paneId,
39
- currentPath,
40
- panePid: Number.parseInt(panePid, 10),
41
- };
42
- } catch (error) {
43
- return null;
44
- }
45
- }
46
-
47
- function paneExists(paneId) {
48
- if (!paneId) {
49
- return false;
50
- }
51
-
52
- try {
53
- const output = runTmux(["display-message", "-p", "-t", paneId, "#{pane_id}"]).trim();
54
- return output.length > 0;
55
- } catch (error) {
56
- return false;
57
- }
58
- }
59
-
60
- function setPaneTitle(paneId, title) {
61
- try {
62
- runTmux(["select-pane", "-t", paneId, "-T", title]);
63
- return true;
64
- } catch (error) {
65
- return false;
66
- }
67
- }
68
-
69
- function sendLiteral(paneId, text) {
70
- const chunkSize = 800;
71
- for (let start = 0; start < text.length; start += chunkSize) {
72
- const chunk = text.slice(start, start + chunkSize);
73
- if (chunk.length > 0) {
74
- runTmux(["send-keys", "-t", paneId, "-l", chunk]);
75
- }
76
- }
77
- }
78
-
79
- function sleepSync(ms) {
80
- Atomics.wait(SLEEP_VIEW, 0, 0, ms);
81
- }
82
-
83
- function sendTextAndEnter(paneId, text) {
84
- const lines = String(text || "").replace(/\r/g, "").split("\n");
85
-
86
- for (let index = 0; index < lines.length; index += 1) {
87
- const line = lines[index];
88
- if (line.length > 0) {
89
- sendLiteral(paneId, line);
90
- sleepSync(120);
91
- }
92
-
93
- if (index < lines.length - 1) {
94
- runTmux(["send-keys", "-t", paneId, "Enter"]);
95
- }
96
- }
97
-
98
- sleepSync(120);
99
- runTmux(["send-keys", "-t", paneId, "Enter"]);
100
- }
101
-
102
- function capturePaneText(paneId, lines = 220) {
103
- try {
104
- return runTmux(["capture-pane", "-p", "-J", "-S", `-${lines}`, "-t", paneId]);
105
- } catch (error) {
106
- return "";
107
- }
108
- }
109
-
110
- function getPaneChildProcesses(paneId) {
111
- const info = getPaneInfo(paneId);
112
- if (!info || !Number.isInteger(info.panePid)) {
113
- return [];
114
- }
115
-
116
- try {
117
- const output = execFileSync("ps", ["-axo", "pid=,ppid=,etimes=,command="], {
118
- encoding: "utf8",
119
- });
120
-
121
- const processes = output
122
- .split("\n")
123
- .map((line) => line.trim())
124
- .filter((line) => line.length > 0)
125
- .map((line) => {
126
- const match = line.match(/^(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/);
127
- if (!match) {
128
- return null;
129
- }
130
-
131
- return {
132
- pid: Number.parseInt(match[1], 10),
133
- ppid: Number.parseInt(match[2], 10),
134
- elapsedSeconds: Number.parseInt(match[3], 10),
135
- args: match[4],
136
- };
137
- })
138
- .filter((entry) => entry !== null);
139
-
140
- const descendants = [];
141
- const queue = [info.panePid];
142
- const seen = new Set(queue);
143
-
144
- while (queue.length > 0) {
145
- const parentPid = queue.shift();
146
- for (const process of processes) {
147
- if (process.ppid !== parentPid || seen.has(process.pid)) {
148
- continue;
149
- }
150
- seen.add(process.pid);
151
- queue.push(process.pid);
152
- descendants.push(process);
153
- }
154
- }
155
-
156
- return descendants.sort((left, right) => left.elapsedSeconds - right.elapsedSeconds);
157
- } catch (error) {
158
- return [];
159
- }
160
- }
161
-
162
- module.exports = {
163
- capturePaneText,
164
- getPaneChildProcesses,
165
- getPaneInfo,
166
- insideTmux,
167
- paneExists,
168
- sendTextAndEnter,
169
- setPaneTitle,
170
- };