tmuxes 0.1.9 → 0.1.11
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/LICENSE +21 -21
- package/README.md +283 -296
- package/{server/bin → bin}/tmuxes.js +36 -36
- package/dist/agentHooks.js +91 -0
- package/dist/agentHooks.js.map +1 -0
- package/dist/agentOutput.js +30 -0
- package/dist/agentOutput.js.map +1 -0
- package/dist/agentState.js +45 -0
- package/dist/agentState.js.map +1 -0
- package/dist/config.js +32 -0
- package/dist/config.js.map +1 -0
- package/dist/exe.js +37 -0
- package/dist/exe.js.map +1 -0
- package/dist/exec.js +43 -0
- package/dist/exec.js.map +1 -0
- package/dist/files.js +308 -0
- package/dist/files.js.map +1 -0
- package/dist/foldersStore.js +103 -0
- package/dist/foldersStore.js.map +1 -0
- package/dist/index.js +117 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.js +16 -0
- package/dist/logger.js.map +1 -0
- package/{server/src/monitor.ts → dist/monitor.js} +9 -10
- package/dist/monitor.js.map +1 -0
- package/dist/openBrowser.js +31 -0
- package/dist/openBrowser.js.map +1 -0
- package/{server/src/platform.ts → dist/platform.js} +5 -4
- package/dist/platform.js.map +1 -0
- package/dist/rest/router.js +198 -0
- package/dist/rest/router.js.map +1 -0
- package/dist/targetCommand.js +60 -0
- package/dist/targetCommand.js.map +1 -0
- package/dist/targets.js +131 -0
- package/dist/targets.js.map +1 -0
- package/dist/tmux/builder.js +174 -0
- package/dist/tmux/builder.js.map +1 -0
- package/dist/tmux/formats.js +61 -0
- package/dist/tmux/formats.js.map +1 -0
- package/dist/tmux/sessions.js +157 -0
- package/dist/tmux/sessions.js.map +1 -0
- package/dist/validate.js +65 -0
- package/dist/validate.js.map +1 -0
- package/dist/windowsSsh.js +209 -0
- package/dist/windowsSsh.js.map +1 -0
- package/dist/winshell/manager.js +267 -0
- package/dist/winshell/manager.js.map +1 -0
- package/dist/ws/protocol.js +4 -0
- package/dist/ws/protocol.js.map +1 -0
- package/dist/ws/sshState.js +35 -0
- package/dist/ws/sshState.js.map +1 -0
- package/dist/ws/terminalSession.js +204 -0
- package/dist/ws/terminalSession.js.map +1 -0
- package/dist/ws/wsServer.js +151 -0
- package/dist/ws/wsServer.js.map +1 -0
- package/dist/wsl.js +35 -0
- package/dist/wsl.js.map +1 -0
- package/package.json +61 -28
- package/public/assets/index-D_X5SnGx.css +1 -0
- package/public/assets/index-Dl69CPyt.js +44 -0
- package/{client → public}/index.html +13 -12
- package/.node-version +0 -1
- package/.nvmrc +0 -1
- package/.tmp-npm-cache/_cacache/content-v2/sha512/43/27/5e000b8b9c56a6ccc66f709485499f4304e2cb1982582ba571321c07b3ef56fcabd2c671898cc8003365a0485b6fd8e73e7b17b073cec0f7d1628c1a99df +0 -0
- package/.tmp-npm-cache/_cacache/content-v2/sha512/51/cf/4301295d74559ed494bae160d54d8741077f89faebb311882ac065019246951e7b53f3dcb913793c42b331e14c7070c4810c3cdc27a427d103a7db4614e0 +0 -0
- package/.tmp-npm-cache/_cacache/content-v2/sha512/c3/4d/d68a454a916e74c2617f586fbf770981b33811d667c2547eb0e9fc21938f4ee7e98f1ceee4bde8ad8815b5f6efe21b60eee798837d68f51a3340d7e5bb7a +0 -0
- package/.tmp-npm-cache/_cacache/content-v2/sha512/fe/40/2abfbefc96299e8bf714aa91d62607190ae299e102cf5933db2e2904640d65d25d67dbbb6fa2ddc92a17f00b9dbfdf2e37487f67d96ec36c64a285b59a7d +0 -0
- package/.tmp-npm-cache/_cacache/index-v5/27/fe/81a3de6ce7ae3d1e41a3421de20c5629998c4ee5d0ffe2037630f03b03b2 +0 -4
- package/.tmp-npm-cache/_cacache/index-v5/65/22/dd66711f62681fce09aabb2357a2907b4a0c778ac5227c4baf9603fd86e8 +0 -4
- package/.tmp-npm-cache/_update-notifier-last-checked +0 -0
- package/AGENTS.md +0 -15
- package/CLAUDE.md +0 -3
- package/README.en.md +0 -304
- package/SECURITY.md +0 -31
- package/client/package.json +0 -29
- package/client/src/App.tsx +0 -123
- package/client/src/activity.ts +0 -5
- package/client/src/api.ts +0 -130
- package/client/src/attention.tsx +0 -157
- package/client/src/components/FileExplorer.tsx +0 -156
- package/client/src/components/FileViewer.tsx +0 -194
- package/client/src/components/SessionRow.tsx +0 -108
- package/client/src/components/SessionTree.tsx +0 -197
- package/client/src/components/SettingsButton.tsx +0 -122
- package/client/src/components/Sidebar.tsx +0 -96
- package/client/src/components/StatusBanner.tsx +0 -31
- package/client/src/components/TargetGroup.tsx +0 -275
- package/client/src/components/TerminalPanel.tsx +0 -192
- package/client/src/folders.ts +0 -245
- package/client/src/hooks/useTerminal.ts +0 -67
- package/client/src/hooks/useTmuxSocket.ts +0 -65
- package/client/src/i18n.ts +0 -213
- package/client/src/main.tsx +0 -17
- package/client/src/settings.tsx +0 -87
- package/client/src/styles.css +0 -723
- package/client/src/types.ts +0 -93
- package/client/src/util.ts +0 -65
- package/client/tsconfig.json +0 -13
- package/client/vite.config.ts +0 -15
- package/fig/fig1.png +0 -0
- package/scripts/prepack.mjs +0 -35
- package/server/package.json +0 -61
- package/server/src/agentHooks.ts +0 -120
- package/server/src/agentOutput.ts +0 -36
- package/server/src/agentState.ts +0 -70
- package/server/src/config.ts +0 -31
- package/server/src/exe.ts +0 -34
- package/server/src/exec.ts +0 -61
- package/server/src/files.ts +0 -330
- package/server/src/foldersStore.ts +0 -114
- package/server/src/index.ts +0 -114
- package/server/src/logger.ts +0 -16
- package/server/src/openBrowser.ts +0 -28
- package/server/src/rest/router.ts +0 -290
- package/server/src/targetCommand.ts +0 -79
- package/server/src/targets.ts +0 -152
- package/server/src/tmux/builder.ts +0 -198
- package/server/src/tmux/formats.ts +0 -95
- package/server/src/tmux/sessions.ts +0 -204
- package/server/src/validate.ts +0 -79
- package/server/src/windowsSsh.ts +0 -239
- package/server/src/winshell/manager.ts +0 -296
- package/server/src/ws/protocol.ts +0 -15
- package/server/src/ws/sshState.ts +0 -36
- package/server/src/ws/terminalSession.ts +0 -207
- package/server/src/ws/wsServer.ts +0 -153
- package/server/src/wsl.ts +0 -38
- package/server/test/agentHooks.test.ts +0 -66
- package/server/test/agentOutput.test.ts +0 -26
- package/server/test/agentState.test.ts +0 -24
- package/server/test/builder.test.ts +0 -162
- package/server/test/files.test.ts +0 -81
- package/server/test/formats.test.ts +0 -123
- package/server/test/monitor.test.ts +0 -25
- package/server/test/validate.test.ts +0 -71
- package/server/test/wsl.test.ts +0 -18
- package/server/tsconfig.json +0 -9
- package/server/vitest.config.ts +0 -12
- package/start.cmd +0 -30
- package/start.command +0 -20
- package/start.sh +0 -20
- package/tsconfig.base.json +0 -19
package/dist/files.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { promises as fsp } from 'node:fs';
|
|
2
|
+
import { basename as posixBasename, dirname as posixDirname } from 'node:path/posix';
|
|
3
|
+
import { commandArgv } from './tmux/builder.js';
|
|
4
|
+
import { TmuxError } from './tmux/sessions.js';
|
|
5
|
+
import { runTargetCommand } from './targetCommand.js';
|
|
6
|
+
const REMOTE_TIMEOUT_MS = 15_000;
|
|
7
|
+
/** Max bytes returned for a file preview. */
|
|
8
|
+
export const FILE_PREVIEW_CAP = 2_000_000;
|
|
9
|
+
/** A NUL anywhere in a preview means the file is binary. */
|
|
10
|
+
const NUL = String.fromCharCode(0);
|
|
11
|
+
const SESSION_DIRECTORY_SCRIPT = [
|
|
12
|
+
'session=$1',
|
|
13
|
+
'requested=$2',
|
|
14
|
+
'cwd=$(tmux display-message -p -t "$session" \'#{pane_current_path}\') || exit $?',
|
|
15
|
+
'if [ -z "$cwd" ]; then echo "empty session cwd" >&2; exit 70; fi',
|
|
16
|
+
'root=$(realpath -- "$cwd") || exit $?',
|
|
17
|
+
'if [ -z "$requested" ]; then requested=$cwd; fi',
|
|
18
|
+
'case "$requested" in /*) ;; *) echo "path must be absolute" >&2; exit 64;; esac',
|
|
19
|
+
'candidate=$(realpath -- "$requested") || exit $?',
|
|
20
|
+
'if [ "$root" = "/" ]; then inside=1; else case "$candidate" in "$root"|"$root"/*) inside=1 ;; *) inside=0 ;; esac; fi',
|
|
21
|
+
'if [ "$inside" != 1 ]; then echo "path is outside the session working directory" >&2; exit 77; fi',
|
|
22
|
+
'if [ ! -d "$candidate" ]; then echo "not a directory" >&2; exit 66; fi',
|
|
23
|
+
'printf "%s\\0%s\\0" "$cwd" "$candidate"',
|
|
24
|
+
'ls -Ap1 -- "$candidate"',
|
|
25
|
+
].join('\n');
|
|
26
|
+
function timeout(t) {
|
|
27
|
+
return t.kind === 'local' ? undefined : REMOTE_TIMEOUT_MS;
|
|
28
|
+
}
|
|
29
|
+
function normalizeRoot(path) {
|
|
30
|
+
if (path === '/')
|
|
31
|
+
return path;
|
|
32
|
+
return path.replace(/\/+$/, '');
|
|
33
|
+
}
|
|
34
|
+
export function isInsideRoot(root, candidate) {
|
|
35
|
+
const normalizedRoot = normalizeRoot(root);
|
|
36
|
+
return normalizedRoot === '/' || candidate === normalizedRoot || candidate.startsWith(`${normalizedRoot}/`);
|
|
37
|
+
}
|
|
38
|
+
async function remoteRealpath(t, path) {
|
|
39
|
+
const r = await run(t, ['realpath', path]);
|
|
40
|
+
if (r.code !== 0) {
|
|
41
|
+
if (/No such file|not found/i.test(r.stderr))
|
|
42
|
+
throw new TmuxError(404, 'path not found');
|
|
43
|
+
if (/Permission denied/i.test(r.stderr))
|
|
44
|
+
throw new TmuxError(403, 'permission denied');
|
|
45
|
+
throw new TmuxError(502, r.stderr.trim().split('\n')[0] || 'cannot resolve path');
|
|
46
|
+
}
|
|
47
|
+
const resolved = r.stdout.trim().split('\n')[0];
|
|
48
|
+
if (!resolved)
|
|
49
|
+
throw new TmuxError(502, 'empty resolved path');
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
async function realpath(t, path) {
|
|
53
|
+
if (t.kind === 'local') {
|
|
54
|
+
try {
|
|
55
|
+
return await fsp.realpath(path);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
throw fsError(e, path);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return remoteRealpath(t, path);
|
|
62
|
+
}
|
|
63
|
+
async function tryRealpath(t, path) {
|
|
64
|
+
try {
|
|
65
|
+
return await realpath(t, path);
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
if (e instanceof TmuxError && e.status === 404)
|
|
69
|
+
return null;
|
|
70
|
+
throw e;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolve a requested target path and verify it stays inside the session's cwd.
|
|
75
|
+
* This keeps the file browser/editor scoped to the agent workspace instead of
|
|
76
|
+
* exposing arbitrary host files through the no-auth local server.
|
|
77
|
+
*/
|
|
78
|
+
export async function resolveScopedPath(t, rootPath, requestedPath, opts = {}) {
|
|
79
|
+
if (!requestedPath.startsWith('/'))
|
|
80
|
+
throw new TmuxError(400, 'path must be absolute');
|
|
81
|
+
const root = await realpath(t, rootPath);
|
|
82
|
+
let candidate = null;
|
|
83
|
+
if (opts.forWrite) {
|
|
84
|
+
candidate = await tryRealpath(t, requestedPath);
|
|
85
|
+
if (!candidate) {
|
|
86
|
+
const name = posixBasename(requestedPath);
|
|
87
|
+
if (!name || name === '.' || name === '..')
|
|
88
|
+
throw new TmuxError(400, 'invalid path');
|
|
89
|
+
const parent = await realpath(t, posixDirname(requestedPath));
|
|
90
|
+
candidate = parent === '/' ? `/${name}` : `${parent}/${name}`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
candidate = await realpath(t, requestedPath);
|
|
95
|
+
}
|
|
96
|
+
if (!isInsideRoot(root, candidate)) {
|
|
97
|
+
throw new TmuxError(403, 'path is outside the session working directory');
|
|
98
|
+
}
|
|
99
|
+
return candidate;
|
|
100
|
+
}
|
|
101
|
+
async function run(t, argv) {
|
|
102
|
+
return runTargetCommand(t, (opts) => commandArgv(t, argv, opts), { timeoutMs: timeout(t) });
|
|
103
|
+
}
|
|
104
|
+
/** The working directory of a session's active pane (tmux #{pane_current_path}). */
|
|
105
|
+
export async function getSessionCwd(t, session) {
|
|
106
|
+
const r = await run(t, ['tmux', 'display-message', '-p', '-t', session, '#{pane_current_path}']);
|
|
107
|
+
if (r.code !== 0) {
|
|
108
|
+
if (/can't find|no server running|session not found/i.test(r.stderr)) {
|
|
109
|
+
throw new TmuxError(404, `session "${session}" not found`);
|
|
110
|
+
}
|
|
111
|
+
throw new TmuxError(502, r.stderr.trim().split('\n')[0] || 'cannot read session cwd');
|
|
112
|
+
}
|
|
113
|
+
const cwd = r.stdout.trim();
|
|
114
|
+
if (!cwd)
|
|
115
|
+
throw new TmuxError(502, 'empty session cwd');
|
|
116
|
+
return cwd;
|
|
117
|
+
}
|
|
118
|
+
function sortEntries(entries) {
|
|
119
|
+
return entries.sort((a, b) => {
|
|
120
|
+
if (a.type !== b.type)
|
|
121
|
+
return a.type === 'dir' ? -1 : 1;
|
|
122
|
+
return a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' });
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function parseRemoteEntries(stdout) {
|
|
126
|
+
return stdout
|
|
127
|
+
.split('\n')
|
|
128
|
+
.filter((l) => l.length > 0)
|
|
129
|
+
.map((line) => {
|
|
130
|
+
const isDir = line.endsWith('/');
|
|
131
|
+
const name = isDir ? line.slice(0, -1) : line;
|
|
132
|
+
return { name, type: isDir ? 'dir' : 'file' };
|
|
133
|
+
})
|
|
134
|
+
.filter((e) => e.name && e.name !== '.' && e.name !== '..');
|
|
135
|
+
}
|
|
136
|
+
function sessionDirectoryError(session, stderr) {
|
|
137
|
+
const first = stderr.trim().split('\n')[0] || 'cannot list directory';
|
|
138
|
+
if (/can't find|no server running|session not found/i.test(stderr)) {
|
|
139
|
+
return new TmuxError(404, `session "${session}" not found`);
|
|
140
|
+
}
|
|
141
|
+
if (/path must be absolute/i.test(stderr))
|
|
142
|
+
return new TmuxError(400, 'path must be absolute');
|
|
143
|
+
if (/not a directory/i.test(stderr))
|
|
144
|
+
return new TmuxError(400, 'not a directory');
|
|
145
|
+
if (/Permission denied/i.test(stderr))
|
|
146
|
+
return new TmuxError(403, 'permission denied');
|
|
147
|
+
if (/outside the session working directory/i.test(stderr)) {
|
|
148
|
+
return new TmuxError(403, 'path is outside the session working directory');
|
|
149
|
+
}
|
|
150
|
+
if (/No such file|not found|realpath:/i.test(stderr))
|
|
151
|
+
return new TmuxError(404, 'path not found');
|
|
152
|
+
return new TmuxError(502, first);
|
|
153
|
+
}
|
|
154
|
+
async function remoteSessionDirectory(t, session, requestedPath) {
|
|
155
|
+
if (requestedPath !== undefined && !requestedPath.startsWith('/')) {
|
|
156
|
+
throw new TmuxError(400, 'path must be absolute');
|
|
157
|
+
}
|
|
158
|
+
const r = await run(t, [
|
|
159
|
+
'sh',
|
|
160
|
+
'-c',
|
|
161
|
+
SESSION_DIRECTORY_SCRIPT,
|
|
162
|
+
'tmuxes-session-directory',
|
|
163
|
+
session,
|
|
164
|
+
requestedPath ?? '',
|
|
165
|
+
]);
|
|
166
|
+
if (r.code !== 0)
|
|
167
|
+
throw sessionDirectoryError(session, r.stderr);
|
|
168
|
+
const firstSep = r.stdout.indexOf(NUL);
|
|
169
|
+
const secondSep = firstSep < 0 ? -1 : r.stdout.indexOf(NUL, firstSep + 1);
|
|
170
|
+
if (firstSep < 0 || secondSep < 0)
|
|
171
|
+
throw new TmuxError(502, 'cannot parse directory listing');
|
|
172
|
+
const cwd = r.stdout.slice(0, firstSep);
|
|
173
|
+
const path = r.stdout.slice(firstSep + 1, secondSep);
|
|
174
|
+
const entries = parseRemoteEntries(r.stdout.slice(secondSep + 1));
|
|
175
|
+
return { cwd, path, entries: sortEntries(entries) };
|
|
176
|
+
}
|
|
177
|
+
export async function listSessionDirectory(t, session, requestedPath) {
|
|
178
|
+
if (t.kind !== 'local')
|
|
179
|
+
return remoteSessionDirectory(t, session, requestedPath);
|
|
180
|
+
const cwd = await getSessionCwd(t, session);
|
|
181
|
+
const path = await resolveScopedPath(t, cwd, requestedPath ?? cwd);
|
|
182
|
+
return { cwd, path, entries: await listDirectory(t, path) };
|
|
183
|
+
}
|
|
184
|
+
/** List a directory ON THE TARGET. Local uses fs; wsl/ssh shell out to `ls`. */
|
|
185
|
+
export async function listDirectory(t, path) {
|
|
186
|
+
if (t.kind === 'local') {
|
|
187
|
+
let dirents;
|
|
188
|
+
try {
|
|
189
|
+
dirents = await fsp.readdir(path, { withFileTypes: true });
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
throw fsError(e, path);
|
|
193
|
+
}
|
|
194
|
+
const entries = [];
|
|
195
|
+
for (const d of dirents) {
|
|
196
|
+
if (d.name.startsWith('.') && (d.name === '.' || d.name === '..'))
|
|
197
|
+
continue;
|
|
198
|
+
let isDir = d.isDirectory();
|
|
199
|
+
if (d.isSymbolicLink()) {
|
|
200
|
+
// Resolve symlinks so linked dirs are navigable.
|
|
201
|
+
try {
|
|
202
|
+
const st = await fsp.stat(path.replace(/\/$/, '') + '/' + d.name);
|
|
203
|
+
isDir = st.isDirectory();
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
isDir = false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
entries.push({ name: d.name, type: isDir ? 'dir' : 'file' });
|
|
210
|
+
}
|
|
211
|
+
return sortEntries(entries);
|
|
212
|
+
}
|
|
213
|
+
// wsl/ssh: `ls -Ap1 -- <path>` → one per line, dirs end with "/".
|
|
214
|
+
const r = await run(t, ['ls', '-Ap1', '--', path]);
|
|
215
|
+
if (r.code !== 0) {
|
|
216
|
+
if (/No such file|not found/i.test(r.stderr))
|
|
217
|
+
throw new TmuxError(404, 'directory not found');
|
|
218
|
+
if (/Permission denied/i.test(r.stderr))
|
|
219
|
+
throw new TmuxError(403, 'permission denied');
|
|
220
|
+
throw new TmuxError(502, r.stderr.trim().split('\n')[0] || 'cannot list directory');
|
|
221
|
+
}
|
|
222
|
+
return sortEntries(parseRemoteEntries(r.stdout));
|
|
223
|
+
}
|
|
224
|
+
/** Read a (capped) file preview ON THE TARGET. */
|
|
225
|
+
export async function readFilePreview(t, path) {
|
|
226
|
+
if (t.kind === 'local') {
|
|
227
|
+
let fh;
|
|
228
|
+
try {
|
|
229
|
+
fh = await fsp.open(path, 'r');
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
throw fsError(e, path);
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const st = await fh.stat();
|
|
236
|
+
if (st.isDirectory())
|
|
237
|
+
throw new TmuxError(400, 'path is a directory');
|
|
238
|
+
const cap = FILE_PREVIEW_CAP;
|
|
239
|
+
const buf = Buffer.alloc(Math.min(cap, Number(st.size)));
|
|
240
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
241
|
+
const slice = buf.subarray(0, bytesRead);
|
|
242
|
+
return {
|
|
243
|
+
path,
|
|
244
|
+
content: slice.toString('utf8'),
|
|
245
|
+
truncated: st.size > cap,
|
|
246
|
+
binary: slice.includes(0),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
await fh.close();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// wsl/ssh: read up to cap+1 bytes; if we got cap+1 the file is larger.
|
|
254
|
+
const r = await run(t, ['head', '-c', String(FILE_PREVIEW_CAP + 1), '--', path]);
|
|
255
|
+
if (r.code !== 0) {
|
|
256
|
+
if (/No such file|not found/i.test(r.stderr))
|
|
257
|
+
throw new TmuxError(404, 'file not found');
|
|
258
|
+
if (/Is a directory/i.test(r.stderr))
|
|
259
|
+
throw new TmuxError(400, 'path is a directory');
|
|
260
|
+
if (/Permission denied/i.test(r.stderr))
|
|
261
|
+
throw new TmuxError(403, 'permission denied');
|
|
262
|
+
throw new TmuxError(502, r.stderr.trim().split('\n')[0] || 'cannot read file');
|
|
263
|
+
}
|
|
264
|
+
const truncated = r.stdout.length > FILE_PREVIEW_CAP;
|
|
265
|
+
const content = truncated ? r.stdout.slice(0, FILE_PREVIEW_CAP) : r.stdout;
|
|
266
|
+
return { path, content, truncated, binary: content.includes(NUL) };
|
|
267
|
+
}
|
|
268
|
+
/** Overwrite a file ON THE TARGET with `content` (UTF-8). Local uses fs;
|
|
269
|
+
* wsl/ssh pipe the bytes into `tee -- <path>` over stdin (tee truncates). */
|
|
270
|
+
export async function writeFile(t, path, content) {
|
|
271
|
+
if (t.kind === 'local') {
|
|
272
|
+
try {
|
|
273
|
+
await fsp.writeFile(path, content, 'utf8');
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
throw fsError(e, path);
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
// `tee` opens the file with O_TRUNC|O_CREAT, so shorter content can't leave a
|
|
281
|
+
// stale tail. Its stdout echo of the content is ignored.
|
|
282
|
+
const r = await runTargetCommand(t, (opts) => commandArgv(t, ['tee', '--', path], opts), {
|
|
283
|
+
timeoutMs: REMOTE_TIMEOUT_MS,
|
|
284
|
+
input: content,
|
|
285
|
+
});
|
|
286
|
+
if (r.code !== 0) {
|
|
287
|
+
if (/No such file|not found/i.test(r.stderr))
|
|
288
|
+
throw new TmuxError(404, 'directory not found');
|
|
289
|
+
if (/Is a directory/i.test(r.stderr))
|
|
290
|
+
throw new TmuxError(400, 'path is a directory');
|
|
291
|
+
if (/Permission denied/i.test(r.stderr))
|
|
292
|
+
throw new TmuxError(403, 'permission denied');
|
|
293
|
+
throw new TmuxError(502, r.stderr.trim().split('\n')[0] || 'cannot write file');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function fsError(e, path) {
|
|
297
|
+
const code = e?.code;
|
|
298
|
+
if (code === 'ENOENT')
|
|
299
|
+
return new TmuxError(404, `not found: ${path}`);
|
|
300
|
+
if (code === 'EACCES')
|
|
301
|
+
return new TmuxError(403, `permission denied: ${path}`);
|
|
302
|
+
if (code === 'ENOTDIR')
|
|
303
|
+
return new TmuxError(400, `not a directory: ${path}`);
|
|
304
|
+
if (code === 'EISDIR')
|
|
305
|
+
return new TmuxError(400, `path is a directory: ${path}`);
|
|
306
|
+
return new TmuxError(502, e instanceof Error ? e.message : 'filesystem error');
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { promises as fsp } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import { config } from './config.js';
|
|
5
|
+
import { sshClientArgs, sshQuote } from './tmux/builder.js';
|
|
6
|
+
import { TmuxError } from './tmux/sessions.js';
|
|
7
|
+
import { runTargetCommand } from './targetCommand.js';
|
|
8
|
+
/**
|
|
9
|
+
* Sidebar folder organization, stored ON THE TARGET so it follows the cluster:
|
|
10
|
+
* every client connecting to the same target reads/writes the same file, so the
|
|
11
|
+
* folder tree syncs across browsers and machines (like the tmux sessions do).
|
|
12
|
+
*
|
|
13
|
+
* Path: $HOME/.config/tmuxes/folders.json on the target.
|
|
14
|
+
* - local / winlocal → the server's own filesystem (fs).
|
|
15
|
+
* - ssh → the remote $HOME (ssh runs commands from $HOME).
|
|
16
|
+
* - wsl → the distro's $HOME (`wsl --cd ~`).
|
|
17
|
+
*/
|
|
18
|
+
const REL = '.config/tmuxes/folders.json';
|
|
19
|
+
const REMOTE_TIMEOUT_MS = 15_000;
|
|
20
|
+
const MAX_BYTES = 500_000;
|
|
21
|
+
const EMPTY = { folders: [], assign: {} };
|
|
22
|
+
function localPath() {
|
|
23
|
+
return join(homedir(), '.config', 'tmuxes', 'folders.json');
|
|
24
|
+
}
|
|
25
|
+
function remoteArgv(t, script, opts = {}) {
|
|
26
|
+
if (t.kind === 'wsl') {
|
|
27
|
+
// --cd ~ → run from the distro home; sh -c handles the redirections.
|
|
28
|
+
return { file: 'wsl.exe', args: ['-d', t.distro ?? '', '--cd', '~', '--exec', 'sh', '-c', script] };
|
|
29
|
+
}
|
|
30
|
+
// ssh: wrap in `sh -c '<script>'` so it works regardless of the remote login
|
|
31
|
+
// shell, and runs from the default cwd ($HOME).
|
|
32
|
+
return {
|
|
33
|
+
file: 'ssh',
|
|
34
|
+
args: [
|
|
35
|
+
...sshClientArgs(t, {
|
|
36
|
+
batchMode: true,
|
|
37
|
+
connectTimeout: config.ssh.connectTimeoutMgmt,
|
|
38
|
+
multiplex: opts.multiplex,
|
|
39
|
+
}).slice(1),
|
|
40
|
+
'sh',
|
|
41
|
+
'-c',
|
|
42
|
+
sshQuote(script),
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function runRemote(t, script, input) {
|
|
47
|
+
return runTargetCommand(t, (opts) => remoteArgv(t, script, opts), {
|
|
48
|
+
timeoutMs: REMOTE_TIMEOUT_MS,
|
|
49
|
+
input,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function readRaw(t) {
|
|
53
|
+
if (t.kind === 'local' || t.kind === 'winlocal') {
|
|
54
|
+
try {
|
|
55
|
+
return await fsp.readFile(localPath(), 'utf8');
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
if (e.code === 'ENOENT')
|
|
59
|
+
return '';
|
|
60
|
+
throw new TmuxError(502, 'cannot read folders');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// `2>/dev/null` so a missing file produces empty stdout + no stderr; a real
|
|
64
|
+
// transport failure (ssh down) prints to stderr → surfaced as 502.
|
|
65
|
+
const r = await runRemote(t, `cat ${REL} 2>/dev/null`);
|
|
66
|
+
if (r.code === 0)
|
|
67
|
+
return r.stdout;
|
|
68
|
+
if (r.stderr.trim())
|
|
69
|
+
throw new TmuxError(502, r.stderr.trim().split('\n')[0]);
|
|
70
|
+
return ''; // missing file
|
|
71
|
+
}
|
|
72
|
+
async function writeRaw(t, raw) {
|
|
73
|
+
if (t.kind === 'local' || t.kind === 'winlocal') {
|
|
74
|
+
await fsp.mkdir(dirname(localPath()), { recursive: true });
|
|
75
|
+
await fsp.writeFile(localPath(), raw, 'utf8');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const r = await runRemote(t, `mkdir -p .config/tmuxes && cat > ${REL}`, raw);
|
|
79
|
+
if (r.code !== 0)
|
|
80
|
+
throw new TmuxError(502, r.stderr.trim().split('\n')[0] || 'cannot write folders');
|
|
81
|
+
}
|
|
82
|
+
export async function readFolders(t) {
|
|
83
|
+
const raw = await readRaw(t);
|
|
84
|
+
if (!raw.trim())
|
|
85
|
+
return EMPTY;
|
|
86
|
+
try {
|
|
87
|
+
const o = JSON.parse(raw);
|
|
88
|
+
if (o && Array.isArray(o.folders) && o.assign && typeof o.assign === 'object') {
|
|
89
|
+
return { folders: o.folders, assign: o.assign };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
/* corrupt file → treat as empty */
|
|
94
|
+
}
|
|
95
|
+
return EMPTY;
|
|
96
|
+
}
|
|
97
|
+
export async function writeFolders(t, payload) {
|
|
98
|
+
const raw = JSON.stringify({ folders: payload.folders, assign: payload.assign });
|
|
99
|
+
if (Buffer.byteLength(raw, 'utf8') > MAX_BYTES)
|
|
100
|
+
throw new TmuxError(413, 'folders payload too large');
|
|
101
|
+
await writeRaw(t, raw);
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=foldersStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foldersStore.js","sourceRoot":"","sources":["../src/foldersStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,GAAG,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;;;;;;GASG;AAEH,MAAM,GAAG,GAAG,6BAA6B,CAAC;AAC1C,MAAM,iBAAiB,GAAG,MAAM,CAAC;AACjC,MAAM,SAAS,GAAG,OAAO,CAAC;AAO1B,MAAM,KAAK,GAAkB,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AAEzD,SAAS,SAAS;IAChB,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,UAAU,CACjB,CAAS,EACT,MAAc,EACd,OAAgC,EAAE;IAElC,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACrB,qEAAqE;QACrE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;IACtG,CAAC;IACD,6EAA6E;IAC7E,gDAAgD;IAChD,OAAO;QACL,IAAI,EAAE,KAAK;QACX,IAAI,EAAE;YACJ,GAAG,aAAa,CAAC,CAAC,EAAE;gBAClB,SAAS,EAAE,IAAI;gBACf,cAAc,EAAE,MAAM,CAAC,GAAG,CAAC,kBAAkB;gBAC7C,SAAS,EAAE,IAAI,CAAC,SAAS;aAC1B,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YACX,IAAI;YACJ,IAAI;YACJ,QAAQ,CAAC,MAAM,CAAC;SACjB;KACF,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,CAAS,EAAE,MAAc,EAAE,KAAc;IAC1D,OAAO,gBAAgB,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAChE,SAAS,EAAE,iBAAiB;QAC5B,KAAK;KACN,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,CAAS;IAC9B,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,OAAO,MAAM,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,EAAE,CAAC;YAC9D,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,4EAA4E;IAC5E,mEAAmE;IACnE,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC,CAAC;IACvD,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IAClC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,OAAO,EAAE,CAAC,CAAC,eAAe;AAC5B,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,CAAS,EAAE,GAAW;IAC5C,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAChD,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,MAAM,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QAC9C,OAAO;IACT,CAAC;IACD,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,oCAAoC,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;IAC7E,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,sBAAsB,CAAC,CAAC;AACvG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,CAAS;IACzC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,OAAO,KAAK,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA2B,CAAC;QACpD,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC9E,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,MAAiC,EAAE,CAAC;QAC7E,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,CAAS,EAAE,OAAsB;IAClE,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjF,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,SAAS;QAAE,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,2BAA2B,CAAC,CAAC;IACtG,MAAM,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACzB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import readline from 'node:readline';
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { config } from './config.js';
|
|
8
|
+
import { log } from './logger.js';
|
|
9
|
+
import { apiRouter } from './rest/router.js';
|
|
10
|
+
import { attachWebSocket } from './ws/wsServer.js';
|
|
11
|
+
import { disposeAll } from './ws/terminalSession.js';
|
|
12
|
+
import { refreshTargets } from './targets.js';
|
|
13
|
+
import { winShell } from './winshell/manager.js';
|
|
14
|
+
import { openBrowser } from './openBrowser.js';
|
|
15
|
+
const app = express();
|
|
16
|
+
app.use(express.json({ limit: '5mb' }));
|
|
17
|
+
app.use('/api', apiRouter);
|
|
18
|
+
// Serve the built client. Published npm package bundles it at <pkg>/public;
|
|
19
|
+
// in the dev monorepo it lives at server/../../client/dist.
|
|
20
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const clientDist = [join(here, '..', 'public'), join(here, '..', '..', 'client', 'dist')].find(existsSync);
|
|
22
|
+
if (clientDist) {
|
|
23
|
+
app.use(express.static(clientDist));
|
|
24
|
+
// SPA fallback for non-API GET routes (avoids path-to-regexp '*' quirks in Express 5).
|
|
25
|
+
app.use((req, res, next) => {
|
|
26
|
+
if (req.method !== 'GET' || req.path.startsWith('/api'))
|
|
27
|
+
return next();
|
|
28
|
+
res.sendFile(join(clientDist, 'index.html'));
|
|
29
|
+
});
|
|
30
|
+
log.info(`serving client from ${clientDist}`);
|
|
31
|
+
}
|
|
32
|
+
const server = createServer(app);
|
|
33
|
+
attachWebSocket(server);
|
|
34
|
+
server.listen(config.port, config.host, () => {
|
|
35
|
+
const url = `http://${config.host}:${config.port}`;
|
|
36
|
+
log.info(`tmuxes listening on ${url}`);
|
|
37
|
+
// Warm the target cache (discovers WSL distros on Windows) ahead of any WS.
|
|
38
|
+
void refreshTargets().catch((e) => log.warn(`initial target discovery failed: ${e}`));
|
|
39
|
+
// One-click launchers set TMUXES_OPEN=1 to pop the browser open.
|
|
40
|
+
if (process.env.TMUXES_OPEN)
|
|
41
|
+
openBrowser(url);
|
|
42
|
+
});
|
|
43
|
+
let shuttingDown = false;
|
|
44
|
+
function shutdown(signal) {
|
|
45
|
+
if (shuttingDown)
|
|
46
|
+
return;
|
|
47
|
+
shuttingDown = true;
|
|
48
|
+
log.info(`received ${signal}, shutting down`);
|
|
49
|
+
// Restore the console we put into raw mode (Windows Ctrl+C handling).
|
|
50
|
+
try {
|
|
51
|
+
if (process.stdin.isTTY)
|
|
52
|
+
process.stdin.setRawMode(false);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
/* ignore */
|
|
56
|
+
}
|
|
57
|
+
disposeAll();
|
|
58
|
+
winShell.disposeAll();
|
|
59
|
+
server.close(() => process.exit(0));
|
|
60
|
+
// Don't wait forever for lingering sockets.
|
|
61
|
+
setTimeout(() => process.exit(0), 3000).unref();
|
|
62
|
+
}
|
|
63
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
64
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
65
|
+
// Console window closed (Windows) / terminal hangup (POSIX) → free the port.
|
|
66
|
+
process.on('SIGHUP', () => shutdown('SIGHUP'));
|
|
67
|
+
// Windows Ctrl+Break.
|
|
68
|
+
if (process.platform === 'win32')
|
|
69
|
+
process.on('SIGBREAK', () => shutdown('SIGBREAK'));
|
|
70
|
+
// On Windows, node-pty's ConPTY backend breaks the normal CTRL_C_EVENT → SIGINT
|
|
71
|
+
// path for the host process (see microsoft/node-pty#190), so `process.on('SIGINT')`
|
|
72
|
+
// can't be relied on. Instead we read the raw Ctrl+C byte (0x03) straight from the
|
|
73
|
+
// console, set up at startup (before any pty is spawned). This bypasses the signal
|
|
74
|
+
// machinery entirely. Ctrl+Break still works via the SIGBREAK handler above.
|
|
75
|
+
if (process.platform === 'win32') {
|
|
76
|
+
const stdin = process.stdin;
|
|
77
|
+
if (stdin.isTTY) {
|
|
78
|
+
try {
|
|
79
|
+
stdin.setRawMode(true); // deliver keys as bytes; Ctrl+C arrives as 0x03
|
|
80
|
+
stdin.resume();
|
|
81
|
+
stdin.on('data', (buf) => {
|
|
82
|
+
if (buf.includes(0x03))
|
|
83
|
+
shutdown('Ctrl+C');
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Fall back to the readline SIGINT bridge (needs explicit terminal mode).
|
|
88
|
+
readline
|
|
89
|
+
.createInterface({ input: stdin, terminal: true })
|
|
90
|
+
.on('SIGINT', () => shutdown('SIGINT'));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// stdin isn't a console TTY (e.g. piped) — best-effort bridge.
|
|
95
|
+
try {
|
|
96
|
+
readline
|
|
97
|
+
.createInterface({ input: stdin, terminal: true })
|
|
98
|
+
.on('SIGINT', () => shutdown('SIGINT'));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
/* nothing more we can do */
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// If the port is already held (e.g. a previous run is still alive), exit with a
|
|
106
|
+
// clear message instead of an unhandled-error stack trace.
|
|
107
|
+
server.on('error', (err) => {
|
|
108
|
+
if (err.code === 'EADDRINUSE') {
|
|
109
|
+
log.error(`port ${config.port} is already in use — another tmuxes instance is still running. ` +
|
|
110
|
+
`Close it (or its window), then start again.`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
log.error(`server error: ${err.message}`);
|
|
114
|
+
}
|
|
115
|
+
process.exit(1);
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Tiny timestamped console logger — no external dependency. */
|
|
2
|
+
function ts() {
|
|
3
|
+
return new Date().toISOString();
|
|
4
|
+
}
|
|
5
|
+
export const log = {
|
|
6
|
+
info(msg, ...rest) {
|
|
7
|
+
console.log(`[${ts()}] ${msg}`, ...rest);
|
|
8
|
+
},
|
|
9
|
+
warn(msg, ...rest) {
|
|
10
|
+
console.warn(`[${ts()}] WARN ${msg}`, ...rest);
|
|
11
|
+
},
|
|
12
|
+
error(msg, ...rest) {
|
|
13
|
+
console.error(`[${ts()}] ERROR ${msg}`, ...rest);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,SAAS,EAAE;IACT,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,IAAI,CAAC,GAAW,EAAE,GAAG,IAAe;QAClC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,CAAC,GAAW,EAAE,GAAG,IAAe;QAClC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,UAAU,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;IACjD,CAAC;IACD,KAAK,CAAC,GAAW,EAAE,GAAG,IAAe;QACnC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,WAAW,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;IACnD,CAAC;CACF,CAAC"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
1
|
+
/** Compatibility shim for the sessions route.
|
|
2
|
+
*
|
|
3
|
+
* Agent status now comes directly from Claude Code / Codex lifecycle hooks via
|
|
4
|
+
* tmux session options, so there is no terminal-output heuristic to annotate.
|
|
5
|
+
*/
|
|
6
|
+
export function annotate(_targetId, sessions) {
|
|
7
|
+
return sessions;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor.js","sourceRoot":"","sources":["../src/monitor.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,SAAiB,EAAE,QAAuB;IACjE,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { isWindows, isMac } from './platform.js';
|
|
3
|
+
import { log } from './logger.js';
|
|
4
|
+
/** Open `url` in the default browser (best-effort, detached). Used by the
|
|
5
|
+
* one-click launchers when TMUXES_OPEN is set. */
|
|
6
|
+
export function openBrowser(url) {
|
|
7
|
+
try {
|
|
8
|
+
let file;
|
|
9
|
+
let args;
|
|
10
|
+
if (isWindows) {
|
|
11
|
+
// `cmd /c start "" "<url>"` — the empty "" is the window title start needs.
|
|
12
|
+
file = 'cmd.exe';
|
|
13
|
+
args = ['/c', 'start', '', url];
|
|
14
|
+
}
|
|
15
|
+
else if (isMac) {
|
|
16
|
+
file = 'open';
|
|
17
|
+
args = [url];
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
file = 'xdg-open';
|
|
21
|
+
args = [url];
|
|
22
|
+
}
|
|
23
|
+
const child = spawn(file, args, { detached: true, stdio: 'ignore' });
|
|
24
|
+
child.on('error', (e) => log.warn(`could not open browser: ${e.message}`));
|
|
25
|
+
child.unref();
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
log.warn(`could not open browser: ${e instanceof Error ? e.message : String(e)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=openBrowser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openBrowser.js","sourceRoot":"","sources":["../src/openBrowser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC;mDACmD;AACnD,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,IAAI,IAAY,CAAC;QACjB,IAAI,IAAc,CAAC;QACnB,IAAI,SAAS,EAAE,CAAC;YACd,4EAA4E;YAC5E,IAAI,GAAG,SAAS,CAAC;YACjB,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QAClC,CAAC;aAAM,IAAI,KAAK,EAAE,CAAC;YACjB,IAAI,GAAG,MAAM,CAAC;YACd,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,UAAU,CAAC;YAClB,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACrE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC3E,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,GAAG,CAAC,IAAI,CAAC,2BAA2B,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC;AACH,CAAC"}
|