muuuuse 1.3.2 → 1.4.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/src/agents.js DELETED
@@ -1,539 +0,0 @@
1
- const { createHash } = require("node:crypto");
2
- const fs = require("node:fs");
3
- const os = require("node:os");
4
- const path = require("node:path");
5
-
6
- const {
7
- SESSION_MATCH_WINDOW_MS,
8
- getFileSize,
9
- hashText,
10
- readAppendedText,
11
- sanitizeRelayText,
12
- } = require("./util");
13
-
14
- const PRESETS = {
15
- codex: {
16
- label: "Codex",
17
- command: [
18
- "codex",
19
- "-m",
20
- "gpt-5.4",
21
- "-c",
22
- "model_reasoning_effort=low",
23
- "--dangerously-bypass-approvals-and-sandbox",
24
- "--no-alt-screen",
25
- ],
26
- },
27
- claude: {
28
- label: "Claude Code",
29
- command: [
30
- "claude",
31
- "--dangerously-skip-permissions",
32
- "--permission-mode",
33
- "bypassPermissions",
34
- ],
35
- },
36
- gemini: {
37
- label: "Gemini CLI",
38
- command: [
39
- "gemini",
40
- "--approval-mode",
41
- "yolo",
42
- "--sandbox=false",
43
- ],
44
- },
45
- };
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
-
74
- const CODEX_ROOT = path.join(os.homedir(), ".codex", "sessions");
75
- const CLAUDE_ROOT = path.join(os.homedir(), ".claude", "projects");
76
- const GEMINI_ROOT = path.join(os.homedir(), ".gemini", "tmp");
77
- const CODEX_SNAPSHOT_ROOT = path.join(os.homedir(), ".codex", "shell_snapshots");
78
- const codexSnapshotPaneCache = new Map();
79
- const codexSnapshotExportsCache = new Map();
80
-
81
- function walkFiles(rootPath, predicate, results = []) {
82
- try {
83
- const entries = fs.readdirSync(rootPath, { withFileTypes: true });
84
- for (const entry of entries) {
85
- const absolutePath = path.join(rootPath, entry.name);
86
- if (entry.isDirectory()) {
87
- walkFiles(absolutePath, predicate, results);
88
- } else if (predicate(absolutePath)) {
89
- results.push(absolutePath);
90
- }
91
- }
92
- } catch (error) {
93
- return results;
94
- }
95
-
96
- return results;
97
- }
98
-
99
- function commandMatches(args, command) {
100
- const pattern = new RegExp(`(^|[\\\\/\\s])${command}(\\s|$)`, "i");
101
- return pattern.test(args);
102
- }
103
-
104
- function detectAgent(processes) {
105
- const ordered = [...processes].sort((left, right) => left.elapsedSeconds - right.elapsedSeconds);
106
- for (const process of ordered) {
107
- if (commandMatches(process.args, "codex")) {
108
- return buildDetectedAgent("codex", process);
109
- }
110
- if (commandMatches(process.args, "claude")) {
111
- return buildDetectedAgent("claude", process);
112
- }
113
- if (commandMatches(process.args, "gemini")) {
114
- return buildDetectedAgent("gemini", process);
115
- }
116
- }
117
- return null;
118
- }
119
-
120
- function buildDetectedAgent(type, process) {
121
- return {
122
- type,
123
- pid: process.pid,
124
- args: process.args,
125
- elapsedSeconds: process.elapsedSeconds,
126
- processStartedAtMs: Date.now() - process.elapsedSeconds * 1000,
127
- };
128
- }
129
-
130
- function readFirstLines(filePath, maxLines = 20) {
131
- const lines = [];
132
- const fd = fs.openSync(filePath, "r");
133
- try {
134
- const buffer = Buffer.alloc(16384);
135
- const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
136
- if (bytesRead === 0) {
137
- return lines;
138
- }
139
-
140
- for (const line of buffer.toString("utf8", 0, bytesRead).split("\n")) {
141
- if (line.trim().length === 0) {
142
- continue;
143
- }
144
- lines.push(line.trim());
145
- if (lines.length >= maxLines) {
146
- break;
147
- }
148
- }
149
- return lines;
150
- } finally {
151
- fs.closeSync(fd);
152
- }
153
- }
154
-
155
- function chooseCandidate(candidates, currentPath, processStartedAtMs) {
156
- const cwdMatches = candidates.filter((candidate) => candidate.cwd === currentPath);
157
- if (cwdMatches.length === 0) {
158
- return null;
159
- }
160
-
161
- if (cwdMatches.length === 1) {
162
- return cwdMatches[0].path;
163
- }
164
-
165
- if (processStartedAtMs !== null) {
166
- const preciseMatches = cwdMatches
167
- .map((candidate) => ({
168
- ...candidate,
169
- diffMs: Math.abs(candidate.startedAtMs - processStartedAtMs),
170
- }))
171
- .filter((candidate) => Number.isFinite(candidate.diffMs) && candidate.diffMs <= SESSION_MATCH_WINDOW_MS)
172
- .sort((left, right) => left.diffMs - right.diffMs || right.mtimeMs - left.mtimeMs);
173
-
174
- if (preciseMatches.length === 1) {
175
- return preciseMatches[0].path;
176
- }
177
- }
178
-
179
- return null;
180
- }
181
-
182
- function extractThreadId(filePath) {
183
- const match = path.basename(filePath).match(/([0-9a-f]{8}-[0-9a-f-]{27})\.jsonl$/i);
184
- return match ? match[1] : null;
185
- }
186
-
187
- function readCodexSnapshotPane(threadId) {
188
- if (!threadId) {
189
- return null;
190
- }
191
-
192
- if (codexSnapshotPaneCache.has(threadId)) {
193
- return codexSnapshotPaneCache.get(threadId);
194
- }
195
-
196
- const snapshotPath = path.join(CODEX_SNAPSHOT_ROOT, `${threadId}.sh`);
197
- try {
198
- const contents = fs.readFileSync(snapshotPath, "utf8");
199
- const match = contents.match(/declare -x TMUX_PANE="([^"]+)"/);
200
- const paneId = match ? match[1] : null;
201
- codexSnapshotPaneCache.set(threadId, paneId);
202
- return paneId;
203
- } catch (error) {
204
- codexSnapshotPaneCache.set(threadId, null);
205
- return null;
206
- }
207
- }
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
-
252
- function readCodexCandidate(filePath) {
253
- try {
254
- const [firstLine] = readFirstLines(filePath, 1);
255
- if (!firstLine) {
256
- return null;
257
- }
258
- const entry = JSON.parse(firstLine);
259
- if (entry?.type !== "session_meta" || typeof entry.payload?.cwd !== "string") {
260
- return null;
261
- }
262
-
263
- return {
264
- path: filePath,
265
- threadId: extractThreadId(filePath),
266
- snapshotExports: readCodexSnapshotExports(extractThreadId(filePath)),
267
- snapshotPaneId: readCodexSnapshotPane(extractThreadId(filePath)),
268
- cwd: entry.payload.cwd,
269
- startedAtMs: Date.parse(entry.payload.timestamp),
270
- mtimeMs: fs.statSync(filePath).mtimeMs,
271
- };
272
- } catch (error) {
273
- return null;
274
- }
275
- }
276
-
277
- function selectCodexSessionFile(currentPath, processStartedAtMs, options = {}) {
278
- const paneId = options.paneId || null;
279
- const snapshotEnv = options.snapshotEnv || null;
280
- const candidates = walkFiles(CODEX_ROOT, (filePath) => filePath.endsWith(".jsonl"))
281
- .map((filePath) => readCodexCandidate(filePath))
282
- .filter((candidate) => candidate !== null);
283
-
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
-
295
- if (paneId) {
296
- const exactPaneMatches = scopedCandidates.filter((candidate) => candidate.snapshotPaneId === paneId);
297
- if (exactPaneMatches.length > 0) {
298
- scopedCandidates = exactPaneMatches;
299
- } else {
300
- scopedCandidates = scopedCandidates.filter((candidate) => candidate.snapshotPaneId === null);
301
- }
302
- }
303
-
304
- return chooseCandidate(scopedCandidates, currentPath, processStartedAtMs);
305
- }
306
-
307
- function extractCodexAssistantText(content) {
308
- if (!Array.isArray(content)) {
309
- return "";
310
- }
311
-
312
- return content
313
- .flatMap((item) => {
314
- if (!item || typeof item !== "object") {
315
- return [];
316
- }
317
- if (item.type === "output_text" && typeof item.text === "string") {
318
- return [item.text.trim()];
319
- }
320
- return [];
321
- })
322
- .filter((text) => text.length > 0)
323
- .join("\n");
324
- }
325
-
326
- function parseCodexFinalLine(line) {
327
- try {
328
- const entry = JSON.parse(line);
329
- if (entry?.type !== "response_item" || entry.payload?.type !== "message" || entry.payload?.role !== "assistant") {
330
- return null;
331
- }
332
-
333
- const text = sanitizeRelayText(extractCodexAssistantText(entry.payload.content));
334
- if (!text) {
335
- return null;
336
- }
337
-
338
- return {
339
- id: entry.payload.id || hashText(line),
340
- text,
341
- timestamp: entry.timestamp || entry.payload.timestamp || new Date().toISOString(),
342
- };
343
- } catch (error) {
344
- return null;
345
- }
346
- }
347
-
348
- function readCodexAnswers(filePath, offset) {
349
- const { nextOffset, text } = readAppendedText(filePath, offset);
350
- const answers = text
351
- .split("\n")
352
- .map((line) => line.trim())
353
- .filter((line) => line.length > 0)
354
- .map((line) => parseCodexFinalLine(line))
355
- .filter((entry) => entry !== null);
356
-
357
- return { nextOffset, answers };
358
- }
359
-
360
- function readClaudeCandidate(filePath) {
361
- try {
362
- const lines = readFirstLines(filePath, 12);
363
- for (const line of lines) {
364
- const entry = JSON.parse(line);
365
- if (typeof entry.cwd !== "string") {
366
- continue;
367
- }
368
- return {
369
- path: filePath,
370
- cwd: entry.cwd,
371
- startedAtMs: Date.parse(entry.timestamp || entry.message?.timestamp || 0),
372
- mtimeMs: fs.statSync(filePath).mtimeMs,
373
- };
374
- }
375
- return null;
376
- } catch (error) {
377
- return null;
378
- }
379
- }
380
-
381
- function selectClaudeSessionFile(currentPath, processStartedAtMs) {
382
- const candidates = walkFiles(CLAUDE_ROOT, (filePath) => filePath.endsWith(".jsonl"))
383
- .map((filePath) => readClaudeCandidate(filePath))
384
- .filter((candidate) => candidate !== null);
385
-
386
- return chooseCandidate(candidates, currentPath, processStartedAtMs);
387
- }
388
-
389
- function extractClaudeAssistantText(content) {
390
- if (!Array.isArray(content)) {
391
- return "";
392
- }
393
-
394
- return content
395
- .flatMap((item) => {
396
- if (!item || typeof item !== "object") {
397
- return [];
398
- }
399
- if (item.type === "text" && typeof item.text === "string") {
400
- return [item.text.trim()];
401
- }
402
- return [];
403
- })
404
- .filter((text) => text.length > 0)
405
- .join("\n");
406
- }
407
-
408
- function parseClaudeFinalLine(line) {
409
- try {
410
- const entry = JSON.parse(line);
411
- if (entry?.type !== "assistant" || entry.message?.role !== "assistant" || entry.message?.stop_reason !== "end_turn") {
412
- return null;
413
- }
414
-
415
- const text = sanitizeRelayText(extractClaudeAssistantText(entry.message.content));
416
- if (!text) {
417
- return null;
418
- }
419
-
420
- return {
421
- id: entry.uuid || entry.message.id || hashText(line),
422
- text,
423
- timestamp: entry.timestamp || new Date().toISOString(),
424
- };
425
- } catch (error) {
426
- return null;
427
- }
428
- }
429
-
430
- function readClaudeAnswers(filePath, offset) {
431
- const { nextOffset, text } = readAppendedText(filePath, offset);
432
- const answers = text
433
- .split("\n")
434
- .map((line) => line.trim())
435
- .filter((line) => line.length > 0)
436
- .map((line) => parseClaudeFinalLine(line))
437
- .filter((entry) => entry !== null);
438
-
439
- return { nextOffset, answers };
440
- }
441
-
442
- function readGeminiCandidate(filePath) {
443
- try {
444
- const raw = fs.readFileSync(filePath, "utf8");
445
- const entry = JSON.parse(raw);
446
- return {
447
- path: filePath,
448
- projectHash: entry.projectHash,
449
- cwdHash: entry.projectHash,
450
- startedAtMs: Date.parse(entry.startTime),
451
- mtimeMs: fs.statSync(filePath).mtimeMs,
452
- lastUpdatedMs: Date.parse(entry.lastUpdated),
453
- };
454
- } catch (error) {
455
- return null;
456
- }
457
- }
458
-
459
- function selectGeminiSessionFile(currentPath, processStartedAtMs) {
460
- const projectHash = createHash("sha256").update(currentPath).digest("hex");
461
- const candidates = walkFiles(GEMINI_ROOT, (filePath) => filePath.endsWith(".json"))
462
- .map((filePath) => readGeminiCandidate(filePath))
463
- .filter((candidate) => candidate !== null && candidate.projectHash === projectHash);
464
-
465
- if (candidates.length === 0) {
466
- return null;
467
- }
468
-
469
- if (processStartedAtMs !== null) {
470
- const preciseMatches = candidates
471
- .map((candidate) => ({
472
- ...candidate,
473
- diffMs: Math.abs(candidate.startedAtMs - processStartedAtMs),
474
- }))
475
- .filter((candidate) => Number.isFinite(candidate.diffMs) && candidate.diffMs <= SESSION_MATCH_WINDOW_MS)
476
- .sort((left, right) => left.diffMs - right.diffMs || right.lastUpdatedMs - left.lastUpdatedMs);
477
-
478
- if (preciseMatches.length === 1) {
479
- return preciseMatches[0].path;
480
- }
481
- }
482
-
483
- if (candidates.length === 1) {
484
- return candidates[0].path;
485
- }
486
-
487
- return null;
488
- }
489
-
490
- function readGeminiAnswers(filePath, lastMessageId = null) {
491
- try {
492
- const entry = JSON.parse(fs.readFileSync(filePath, "utf8"));
493
- const messages = Array.isArray(entry.messages) ? entry.messages : [];
494
- const finalMessages = messages.filter((message) => {
495
- const toolCalls = Array.isArray(message.toolCalls) ? message.toolCalls : [];
496
- return message.type === "gemini" && typeof message.content === "string" && message.content.trim() && toolCalls.length === 0;
497
- });
498
-
499
- let startIndex = 0;
500
- if (lastMessageId) {
501
- const previousIndex = finalMessages.findIndex((message) => message.id === lastMessageId);
502
- startIndex = previousIndex === -1 ? finalMessages.length : previousIndex + 1;
503
- }
504
-
505
- const answers = finalMessages.slice(startIndex).map((message) => ({
506
- id: message.id || hashText(JSON.stringify(message)),
507
- text: sanitizeRelayText(message.content),
508
- timestamp: message.timestamp || entry.lastUpdated || new Date().toISOString(),
509
- }));
510
-
511
- return {
512
- answers: answers.filter((answer) => answer.text.length > 0),
513
- lastMessageId: finalMessages.length > 0 ? finalMessages[finalMessages.length - 1].id : lastMessageId,
514
- fileSize: getFileSize(filePath),
515
- };
516
- } catch (error) {
517
- return {
518
- answers: [],
519
- lastMessageId,
520
- fileSize: 0,
521
- };
522
- }
523
- }
524
-
525
- module.exports = {
526
- PRESETS,
527
- chooseCandidate,
528
- detectAgent,
529
- detectAgentTypeFromCommand,
530
- expandPresetCommand,
531
- parseClaudeFinalLine,
532
- parseCodexFinalLine,
533
- readClaudeAnswers,
534
- readCodexAnswers,
535
- readGeminiAnswers,
536
- selectClaudeSessionFile,
537
- selectCodexSessionFile,
538
- selectGeminiSessionFile,
539
- };
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
- };