tmuxes 0.1.7 → 0.1.9
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/.node-version +1 -0
- package/.nvmrc +1 -0
- 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 +4 -0
- package/.tmp-npm-cache/_cacache/index-v5/65/22/dd66711f62681fce09aabb2357a2907b4a0c778ac5227c4baf9603fd86e8 +4 -0
- package/.tmp-npm-cache/_update-notifier-last-checked +0 -0
- package/AGENTS.md +15 -0
- package/CLAUDE.md +3 -0
- package/LICENSE +21 -21
- package/README.en.md +304 -0
- package/README.md +301 -289
- package/SECURITY.md +31 -0
- package/{public → client}/index.html +12 -13
- package/client/package.json +29 -0
- package/client/src/App.tsx +123 -0
- package/client/src/activity.ts +5 -0
- package/client/src/api.ts +130 -0
- package/client/src/attention.tsx +157 -0
- package/client/src/components/FileExplorer.tsx +156 -0
- package/client/src/components/FileViewer.tsx +194 -0
- package/client/src/components/SessionRow.tsx +108 -0
- package/client/src/components/SessionTree.tsx +197 -0
- package/client/src/components/SettingsButton.tsx +122 -0
- package/client/src/components/Sidebar.tsx +96 -0
- package/client/src/components/StatusBanner.tsx +31 -0
- package/client/src/components/TargetGroup.tsx +275 -0
- package/client/src/components/TerminalPanel.tsx +192 -0
- package/client/src/folders.ts +245 -0
- package/client/src/hooks/useTerminal.ts +67 -0
- package/client/src/hooks/useTmuxSocket.ts +65 -0
- package/client/src/i18n.ts +213 -0
- package/client/src/main.tsx +17 -0
- package/client/src/settings.tsx +87 -0
- package/client/src/styles.css +723 -0
- package/client/src/types.ts +93 -0
- package/client/src/util.ts +65 -0
- package/client/tsconfig.json +13 -0
- package/client/vite.config.ts +15 -0
- package/fig/fig1.png +0 -0
- package/package.json +28 -61
- package/scripts/prepack.mjs +35 -0
- package/{bin → server/bin}/tmuxes.js +36 -36
- package/server/package.json +61 -0
- package/server/src/agentHooks.ts +120 -0
- package/server/src/agentOutput.ts +36 -0
- package/server/src/agentState.ts +70 -0
- package/server/src/config.ts +31 -0
- package/server/src/exe.ts +34 -0
- package/server/src/exec.ts +61 -0
- package/server/src/files.ts +330 -0
- package/server/src/foldersStore.ts +114 -0
- package/server/src/index.ts +114 -0
- package/server/src/logger.ts +16 -0
- package/{dist/monitor.js → server/src/monitor.ts} +10 -9
- package/server/src/openBrowser.ts +28 -0
- package/{dist/platform.js → server/src/platform.ts} +4 -5
- package/server/src/rest/router.ts +290 -0
- package/server/src/targetCommand.ts +79 -0
- package/server/src/targets.ts +152 -0
- package/server/src/tmux/builder.ts +198 -0
- package/server/src/tmux/formats.ts +95 -0
- package/server/src/tmux/sessions.ts +204 -0
- package/server/src/validate.ts +79 -0
- package/server/src/windowsSsh.ts +239 -0
- package/server/src/winshell/manager.ts +296 -0
- package/server/src/ws/protocol.ts +15 -0
- package/server/src/ws/sshState.ts +36 -0
- package/server/src/ws/terminalSession.ts +207 -0
- package/server/src/ws/wsServer.ts +153 -0
- package/server/src/wsl.ts +38 -0
- package/server/test/agentHooks.test.ts +66 -0
- package/server/test/agentOutput.test.ts +26 -0
- package/server/test/agentState.test.ts +24 -0
- package/server/test/builder.test.ts +162 -0
- package/server/test/files.test.ts +81 -0
- package/server/test/formats.test.ts +123 -0
- package/server/test/monitor.test.ts +25 -0
- package/server/test/validate.test.ts +71 -0
- package/server/test/wsl.test.ts +18 -0
- package/server/tsconfig.json +9 -0
- package/server/vitest.config.ts +12 -0
- package/start.cmd +30 -0
- package/start.command +20 -0
- package/start.sh +20 -0
- package/tsconfig.base.json +19 -0
- package/dist/agentHooks.js +0 -91
- package/dist/agentHooks.js.map +0 -1
- package/dist/agentOutput.js +0 -30
- package/dist/agentOutput.js.map +0 -1
- package/dist/agentState.js +0 -45
- package/dist/agentState.js.map +0 -1
- package/dist/config.js +0 -32
- package/dist/config.js.map +0 -1
- package/dist/exe.js +0 -37
- package/dist/exe.js.map +0 -1
- package/dist/exec.js +0 -43
- package/dist/exec.js.map +0 -1
- package/dist/files.js +0 -243
- package/dist/files.js.map +0 -1
- package/dist/foldersStore.js +0 -103
- package/dist/foldersStore.js.map +0 -1
- package/dist/index.js +0 -117
- package/dist/index.js.map +0 -1
- package/dist/logger.js +0 -16
- package/dist/logger.js.map +0 -1
- package/dist/monitor.js.map +0 -1
- package/dist/openBrowser.js +0 -31
- package/dist/openBrowser.js.map +0 -1
- package/dist/platform.js.map +0 -1
- package/dist/rest/router.js +0 -190
- package/dist/rest/router.js.map +0 -1
- package/dist/targetCommand.js +0 -41
- package/dist/targetCommand.js.map +0 -1
- package/dist/targets.js +0 -131
- package/dist/targets.js.map +0 -1
- package/dist/tmux/builder.js +0 -173
- package/dist/tmux/builder.js.map +0 -1
- package/dist/tmux/formats.js +0 -61
- package/dist/tmux/formats.js.map +0 -1
- package/dist/tmux/sessions.js +0 -157
- package/dist/tmux/sessions.js.map +0 -1
- package/dist/validate.js +0 -65
- package/dist/validate.js.map +0 -1
- package/dist/winshell/manager.js +0 -267
- package/dist/winshell/manager.js.map +0 -1
- package/dist/ws/protocol.js +0 -4
- package/dist/ws/protocol.js.map +0 -1
- package/dist/ws/sshState.js +0 -35
- package/dist/ws/sshState.js.map +0 -1
- package/dist/ws/terminalSession.js +0 -204
- package/dist/ws/terminalSession.js.map +0 -1
- package/dist/ws/wsServer.js +0 -151
- package/dist/ws/wsServer.js.map +0 -1
- package/dist/wsl.js +0 -35
- package/dist/wsl.js.map +0 -1
- package/public/assets/index-BpVrfoZw.js +0 -44
- package/public/assets/index-D_X5SnGx.css +0 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { mkdtemp, mkdir, realpath, rm, symlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { isInsideRoot, resolveScopedPath } from '../src/files.js';
|
|
6
|
+
import type { Target } from '../src/targets.js';
|
|
7
|
+
|
|
8
|
+
const local: Target = { id: 'local', kind: 'local', label: 'Local' };
|
|
9
|
+
const describePosixLocal = process.platform === 'win32' ? describe.skip : describe;
|
|
10
|
+
|
|
11
|
+
async function withTemp<T>(fn: (dir: string) => Promise<T>): Promise<T> {
|
|
12
|
+
const dir = await mkdtemp(join(tmpdir(), 'tmuxes-files-'));
|
|
13
|
+
try {
|
|
14
|
+
return await fn(dir);
|
|
15
|
+
} finally {
|
|
16
|
+
await rm(dir, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('isInsideRoot', () => {
|
|
21
|
+
it('matches root exactly or descendants only', () => {
|
|
22
|
+
expect(isInsideRoot('/home/me/proj', '/home/me/proj')).toBe(true);
|
|
23
|
+
expect(isInsideRoot('/home/me/proj', '/home/me/proj/src/a.ts')).toBe(true);
|
|
24
|
+
expect(isInsideRoot('/home/me/proj', '/home/me/project2/a.ts')).toBe(false);
|
|
25
|
+
expect(isInsideRoot('/', '/etc/passwd')).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describePosixLocal('resolveScopedPath', () => {
|
|
30
|
+
it('allows existing files inside the session root', async () => {
|
|
31
|
+
await withTemp(async (dir) => {
|
|
32
|
+
const root = join(dir, 'root');
|
|
33
|
+
const src = join(root, 'src');
|
|
34
|
+
const file = join(src, 'a.txt');
|
|
35
|
+
await mkdir(src, { recursive: true });
|
|
36
|
+
await writeFile(file, 'ok');
|
|
37
|
+
|
|
38
|
+
await expect(resolveScopedPath(local, root, file)).resolves.toBe(await realpath(file));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('allows creating a new file inside the session root', async () => {
|
|
43
|
+
await withTemp(async (dir) => {
|
|
44
|
+
const root = join(dir, 'root');
|
|
45
|
+
await mkdir(root);
|
|
46
|
+
|
|
47
|
+
await expect(resolveScopedPath(local, root, join(root, 'new.txt'), { forWrite: true })).resolves.toBe(
|
|
48
|
+
join(await realpath(root), 'new.txt'),
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('rejects existing paths outside the session root', async () => {
|
|
54
|
+
await withTemp(async (dir) => {
|
|
55
|
+
const root = join(dir, 'root');
|
|
56
|
+
const outside = join(dir, 'outside');
|
|
57
|
+
const file = join(outside, 'secret.txt');
|
|
58
|
+
await mkdir(root);
|
|
59
|
+
await mkdir(outside);
|
|
60
|
+
await writeFile(file, 'secret');
|
|
61
|
+
|
|
62
|
+
await expect(resolveScopedPath(local, root, file)).rejects.toMatchObject({
|
|
63
|
+
status: 403,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('rejects symlink escapes from the session root', async () => {
|
|
69
|
+
await withTemp(async (dir) => {
|
|
70
|
+
const root = join(dir, 'root');
|
|
71
|
+
const outside = join(dir, 'outside');
|
|
72
|
+
await mkdir(root);
|
|
73
|
+
await mkdir(outside);
|
|
74
|
+
await symlink(outside, join(root, 'link'));
|
|
75
|
+
|
|
76
|
+
await expect(resolveScopedPath(local, root, join(root, 'link', 'new.txt'), { forWrite: true })).rejects.toMatchObject({
|
|
77
|
+
status: 403,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
SESSION_FORMAT,
|
|
4
|
+
WINDOW_FORMAT,
|
|
5
|
+
parseSessions,
|
|
6
|
+
parseWindows,
|
|
7
|
+
isEmptySessionsError,
|
|
8
|
+
} from '../src/tmux/formats.js';
|
|
9
|
+
|
|
10
|
+
const SEP = '|';
|
|
11
|
+
|
|
12
|
+
describe('parseSessions', () => {
|
|
13
|
+
it('parses delimited session lines (name field last)', () => {
|
|
14
|
+
const out = [
|
|
15
|
+
['3', '1', '1700000000', '1700000050', '', 'work'].join(SEP),
|
|
16
|
+
['1', '0', '1700000100', '1700000150', '', 'my proj'].join(SEP), // name with a space survives
|
|
17
|
+
].join('\n');
|
|
18
|
+
expect(parseSessions(out)).toEqual([
|
|
19
|
+
{ name: 'work', windows: 3, attached: true, created: 1700000000, lastActivity: 1700000050 },
|
|
20
|
+
{ name: 'my proj', windows: 1, attached: false, created: 1700000100, lastActivity: 1700000150 },
|
|
21
|
+
]);
|
|
22
|
+
});
|
|
23
|
+
it('parses hook-derived agent state', () => {
|
|
24
|
+
const out = [
|
|
25
|
+
'1',
|
|
26
|
+
'0',
|
|
27
|
+
'1700000100',
|
|
28
|
+
'1700000150',
|
|
29
|
+
'codex:waiting:decision:PermissionRequest:1700000200.42',
|
|
30
|
+
'agent',
|
|
31
|
+
].join(SEP);
|
|
32
|
+
expect(parseSessions(out)).toEqual([
|
|
33
|
+
{
|
|
34
|
+
name: 'agent',
|
|
35
|
+
windows: 1,
|
|
36
|
+
attached: false,
|
|
37
|
+
created: 1700000100,
|
|
38
|
+
lastActivity: 1700000150,
|
|
39
|
+
agentKind: 'codex',
|
|
40
|
+
agentState: 'waiting',
|
|
41
|
+
attentionReason: 'decision',
|
|
42
|
+
agentEvent: 'PermissionRequest',
|
|
43
|
+
agentNonce: '1700000200.42',
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
it('keeps a separator that appears inside a name', () => {
|
|
48
|
+
const out = ['2', '0', '1700000200', '1700000250', 'claude:idle:done:Stop:1', 'a|b'].join(SEP);
|
|
49
|
+
expect(parseSessions(out)).toEqual([
|
|
50
|
+
{
|
|
51
|
+
name: 'a|b',
|
|
52
|
+
windows: 2,
|
|
53
|
+
attached: false,
|
|
54
|
+
created: 1700000200,
|
|
55
|
+
lastActivity: 1700000250,
|
|
56
|
+
agentKind: 'claude',
|
|
57
|
+
agentState: 'idle',
|
|
58
|
+
attentionReason: 'done',
|
|
59
|
+
agentEvent: 'Stop',
|
|
60
|
+
agentNonce: '1',
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
it('parses hook-derived error state', () => {
|
|
65
|
+
const out = ['1', '0', '1700000200', '1700000250', 'codex:idle:error:CodexStreamDisconnected:1', 'agent'].join(SEP);
|
|
66
|
+
expect(parseSessions(out)).toEqual([
|
|
67
|
+
{
|
|
68
|
+
name: 'agent',
|
|
69
|
+
windows: 1,
|
|
70
|
+
attached: false,
|
|
71
|
+
created: 1700000200,
|
|
72
|
+
lastActivity: 1700000250,
|
|
73
|
+
agentKind: 'codex',
|
|
74
|
+
agentState: 'idle',
|
|
75
|
+
attentionReason: 'error',
|
|
76
|
+
agentEvent: 'CodexStreamDisconnected',
|
|
77
|
+
agentNonce: '1',
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
it('returns [] for empty output', () => {
|
|
82
|
+
expect(parseSessions('')).toEqual([]);
|
|
83
|
+
expect(parseSessions('\n')).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
it('places the free-form name token last', () => {
|
|
86
|
+
expect(SESSION_FORMAT.split(SEP)).toEqual([
|
|
87
|
+
'#{session_windows}',
|
|
88
|
+
'#{session_attached}',
|
|
89
|
+
'#{session_created}',
|
|
90
|
+
'#{session_activity}',
|
|
91
|
+
'#{@tmuxes_agent}',
|
|
92
|
+
'#{session_name}',
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('parseWindows', () => {
|
|
98
|
+
it('parses window lines', () => {
|
|
99
|
+
const out = [
|
|
100
|
+
['0', '2', '1', 'bash'].join(SEP),
|
|
101
|
+
['1', '1', '0', 'logs'].join(SEP),
|
|
102
|
+
].join('\n');
|
|
103
|
+
expect(parseWindows(out)).toEqual([
|
|
104
|
+
{ index: 0, name: 'bash', panes: 2, active: true },
|
|
105
|
+
{ index: 1, name: 'logs', panes: 1, active: false },
|
|
106
|
+
]);
|
|
107
|
+
expect(WINDOW_FORMAT.split(SEP)[0]).toBe('#{window_index}');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('isEmptySessionsError', () => {
|
|
112
|
+
it('treats no-server / no-sessions as empty', () => {
|
|
113
|
+
expect(isEmptySessionsError('no server running on /tmp/tmux-1000/default')).toBe(true);
|
|
114
|
+
expect(isEmptySessionsError('no sessions')).toBe(true);
|
|
115
|
+
expect(isEmptySessionsError('error connecting to /tmp/tmux-1000/default (No such file)')).toBe(
|
|
116
|
+
true,
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
it('does not swallow real ssh errors', () => {
|
|
120
|
+
expect(isEmptySessionsError('Permission denied (publickey).')).toBe(false);
|
|
121
|
+
expect(isEmptySessionsError('ssh: connect to host x port 22: Connection timed out')).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { annotate } from '../src/monitor.js';
|
|
3
|
+
import type { SessionInfo } from '../src/tmux/formats.js';
|
|
4
|
+
|
|
5
|
+
function session(name: string): SessionInfo {
|
|
6
|
+
return {
|
|
7
|
+
name,
|
|
8
|
+
windows: 1,
|
|
9
|
+
attached: false,
|
|
10
|
+
created: 1_700_000_000,
|
|
11
|
+
lastActivity: 10,
|
|
12
|
+
agentKind: 'codex',
|
|
13
|
+
agentState: 'idle',
|
|
14
|
+
attentionReason: 'done',
|
|
15
|
+
agentEvent: 'Stop',
|
|
16
|
+
agentNonce: '123',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('annotate', () => {
|
|
21
|
+
it('passes through hook-derived agent state without terminal heuristics', () => {
|
|
22
|
+
const sessions = [session('work')];
|
|
23
|
+
expect(annotate('local', sessions)).toBe(sessions);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
isValidSessionName,
|
|
4
|
+
isValidHost,
|
|
5
|
+
isValidUser,
|
|
6
|
+
isValidPort,
|
|
7
|
+
isValidDimension,
|
|
8
|
+
parseHostSpec,
|
|
9
|
+
} from '../src/validate.js';
|
|
10
|
+
|
|
11
|
+
describe('isValidSessionName', () => {
|
|
12
|
+
it('accepts plain names', () => {
|
|
13
|
+
expect(isValidSessionName('work')).toBe(true);
|
|
14
|
+
expect(isValidSessionName('my-session_2')).toBe(true);
|
|
15
|
+
expect(isValidSessionName('a'.repeat(64))).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
it('rejects tmux delimiters and metachars', () => {
|
|
18
|
+
expect(isValidSessionName('has.dot')).toBe(false); // '.' is a target delimiter
|
|
19
|
+
expect(isValidSessionName('has:colon')).toBe(false); // ':' is a target delimiter
|
|
20
|
+
expect(isValidSessionName('with space')).toBe(false);
|
|
21
|
+
expect(isValidSessionName('rm -rf /')).toBe(false);
|
|
22
|
+
expect(isValidSessionName('$(whoami)')).toBe(false);
|
|
23
|
+
expect(isValidSessionName('')).toBe(false);
|
|
24
|
+
expect(isValidSessionName('a'.repeat(65))).toBe(false);
|
|
25
|
+
expect(isValidSessionName(42 as unknown)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('host / user / port', () => {
|
|
30
|
+
it('validates hosts', () => {
|
|
31
|
+
expect(isValidHost('example.com')).toBe(true);
|
|
32
|
+
expect(isValidHost('10.0.0.1')).toBe(true);
|
|
33
|
+
expect(isValidHost('bad host')).toBe(false);
|
|
34
|
+
expect(isValidHost('a;b')).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it('validates users', () => {
|
|
37
|
+
expect(isValidUser('alice')).toBe(true);
|
|
38
|
+
expect(isValidUser('al ice')).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
it('validates ports', () => {
|
|
41
|
+
expect(isValidPort(22)).toBe(true);
|
|
42
|
+
expect(isValidPort(65535)).toBe(true);
|
|
43
|
+
expect(isValidPort(0)).toBe(false);
|
|
44
|
+
expect(isValidPort(70000)).toBe(false);
|
|
45
|
+
expect(isValidPort(22.5)).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
it('validates dimensions', () => {
|
|
48
|
+
expect(isValidDimension(80)).toBe(true);
|
|
49
|
+
expect(isValidDimension(1000)).toBe(true);
|
|
50
|
+
expect(isValidDimension(0)).toBe(false);
|
|
51
|
+
expect(isValidDimension(1001)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('parseHostSpec', () => {
|
|
56
|
+
it('parses user@host:port', () => {
|
|
57
|
+
expect(parseHostSpec('alice@web1:2222')).toEqual({ user: 'alice', host: 'web1', port: 2222 });
|
|
58
|
+
});
|
|
59
|
+
it('parses host only', () => {
|
|
60
|
+
expect(parseHostSpec('db2')).toEqual({ user: undefined, host: 'db2', port: undefined });
|
|
61
|
+
});
|
|
62
|
+
it('parses user@host', () => {
|
|
63
|
+
expect(parseHostSpec('bob@db2')).toEqual({ user: 'bob', host: 'db2', port: undefined });
|
|
64
|
+
});
|
|
65
|
+
it('rejects garbage', () => {
|
|
66
|
+
expect(parseHostSpec('')).toBeNull();
|
|
67
|
+
expect(parseHostSpec('a b@c')).toBeNull();
|
|
68
|
+
expect(parseHostSpec('host:notaport')).toBeNull();
|
|
69
|
+
expect(parseHostSpec('host:99999')).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseWslList } from '../src/wsl.js';
|
|
3
|
+
|
|
4
|
+
const BOM = String.fromCharCode(0xfeff);
|
|
5
|
+
|
|
6
|
+
describe('parseWslList', () => {
|
|
7
|
+
it('parses CRLF list, strips BOM, filters docker system distros', () => {
|
|
8
|
+
const out = `${BOM}Ubuntu\r\nDebian\r\ndocker-desktop\r\ndocker-desktop-data\r\nkali-linux\r\n`;
|
|
9
|
+
expect(parseWslList(out)).toEqual(['Ubuntu', 'Debian', 'kali-linux']);
|
|
10
|
+
});
|
|
11
|
+
it('handles names with version suffixes and blank lines', () => {
|
|
12
|
+
expect(parseWslList('Ubuntu-22.04\n\nUbuntu-24.04\n')).toEqual(['Ubuntu-22.04', 'Ubuntu-24.04']);
|
|
13
|
+
});
|
|
14
|
+
it('returns [] for empty output', () => {
|
|
15
|
+
expect(parseWslList('')).toEqual([]);
|
|
16
|
+
expect(parseWslList(BOM + '\r\n')).toEqual([]);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
// Source uses NodeNext-style ".js" import specifiers; map them to ".ts".
|
|
5
|
+
resolve: {
|
|
6
|
+
extensionAlias: { '.js': ['.ts', '.js'] },
|
|
7
|
+
},
|
|
8
|
+
test: {
|
|
9
|
+
include: ['test/**/*.test.ts'],
|
|
10
|
+
environment: 'node',
|
|
11
|
+
},
|
|
12
|
+
});
|
package/start.cmd
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
REM One-click launcher for Windows. Double-click in Explorer, or run in
|
|
3
|
+
REM Windows Terminal. Builds if needed, starts the server, opens the browser.
|
|
4
|
+
setlocal
|
|
5
|
+
cd /d "%~dp0"
|
|
6
|
+
|
|
7
|
+
where npm >nul 2>nul
|
|
8
|
+
if errorlevel 1 (
|
|
9
|
+
echo [tmuxes] Node.js / npm not found. Install Node 18+ from https://nodejs.org and retry.
|
|
10
|
+
pause
|
|
11
|
+
exit /b 1
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if not exist node_modules (
|
|
15
|
+
echo [tmuxes] Installing dependencies...
|
|
16
|
+
call npm install
|
|
17
|
+
if errorlevel 1 ( echo [tmuxes] npm install failed. & pause & exit /b 1 )
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
echo [tmuxes] Building...
|
|
21
|
+
call npm run build
|
|
22
|
+
if errorlevel 1 ( echo [tmuxes] build failed. & pause & exit /b 1 )
|
|
23
|
+
|
|
24
|
+
echo [tmuxes] Starting on http://127.0.0.1:7420 (a browser window will open)...
|
|
25
|
+
REM Run node DIRECTLY (not via npm) so the server is the console's own child:
|
|
26
|
+
REM closing this window (or Ctrl+C) reaches it, it shuts down, and the port is
|
|
27
|
+
REM released. Going through `npm start` can orphan the node process, leaving
|
|
28
|
+
REM 7420 held until the next reboot.
|
|
29
|
+
set TMUXES_OPEN=1
|
|
30
|
+
node "%~dp0server\dist\index.js"
|
package/start.command
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# One-click launcher for macOS / Linux. Builds if needed, starts the server,
|
|
3
|
+
# and opens the browser. (macOS users: the double-clickable copy is start.command.)
|
|
4
|
+
set -e
|
|
5
|
+
cd "$(dirname "$0")"
|
|
6
|
+
|
|
7
|
+
if ! command -v npm >/dev/null 2>&1; then
|
|
8
|
+
echo "[tmuxes] Node.js / npm not found. Install Node 18+ (e.g. 'brew install node') and retry."
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
12
|
+
echo "[tmuxes] tmux not found. Install it (macOS: 'brew install tmux', Debian/Ubuntu: 'sudo apt install tmux')."
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
[ -d node_modules ] || { echo "[tmuxes] Installing dependencies..."; npm install; }
|
|
17
|
+
echo "[tmuxes] Building..."
|
|
18
|
+
npm run build
|
|
19
|
+
echo "[tmuxes] Starting on http://127.0.0.1:7420 (a browser window will open)..."
|
|
20
|
+
TMUXES_OPEN=1 npm start
|
package/start.sh
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# One-click launcher for macOS / Linux. Builds if needed, starts the server,
|
|
3
|
+
# and opens the browser. (macOS users: the double-clickable copy is start.command.)
|
|
4
|
+
set -e
|
|
5
|
+
cd "$(dirname "$0")"
|
|
6
|
+
|
|
7
|
+
if ! command -v npm >/dev/null 2>&1; then
|
|
8
|
+
echo "[tmuxes] Node.js / npm not found. Install Node 18+ (e.g. 'brew install node') and retry."
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
12
|
+
echo "[tmuxes] tmux not found. Install it (macOS: 'brew install tmux', Debian/Ubuntu: 'sudo apt install tmux')."
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
[ -d node_modules ] || { echo "[tmuxes] Installing dependencies..."; npm install; }
|
|
17
|
+
echo "[tmuxes] Building..."
|
|
18
|
+
npm run build
|
|
19
|
+
echo "[tmuxes] Starting on http://127.0.0.1:7420 (a browser window will open)..."
|
|
20
|
+
TMUXES_OPEN=1 npm start
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022"],
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noUnusedLocals": true,
|
|
9
|
+
"noUnusedParameters": true,
|
|
10
|
+
"noFallthroughCasesInSwitch": true,
|
|
11
|
+
"noImplicitReturns": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"declaration": false,
|
|
17
|
+
"sourceMap": true
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/agentHooks.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { agentHookCommand, } from './agentState.js';
|
|
2
|
-
function isDisabled() {
|
|
3
|
-
const v = process.env.TMUXES_NO_AUTOHOOK;
|
|
4
|
-
return v === '1' || v === 'true';
|
|
5
|
-
}
|
|
6
|
-
function baseName(token) {
|
|
7
|
-
return (token.split(/[\\/]/).pop() || '').toLowerCase().replace(/\.(exe|cmd|bat)$/, '');
|
|
8
|
-
}
|
|
9
|
-
export function detectAgentKind(command) {
|
|
10
|
-
if (isDisabled())
|
|
11
|
-
return undefined;
|
|
12
|
-
const trimmed = command.trim();
|
|
13
|
-
if (!trimmed)
|
|
14
|
-
return undefined;
|
|
15
|
-
const m = /^(\S+)(\s+[\s\S]*)?$/.exec(trimmed);
|
|
16
|
-
if (!m)
|
|
17
|
-
return undefined;
|
|
18
|
-
switch (baseName(m[1])) {
|
|
19
|
-
case 'claude':
|
|
20
|
-
return 'claude';
|
|
21
|
-
case 'codex':
|
|
22
|
-
return 'codex';
|
|
23
|
-
default:
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function hook(kind, state, reason, event) {
|
|
28
|
-
return { type: 'command', command: agentHookCommand(kind, state, reason, event) };
|
|
29
|
-
}
|
|
30
|
-
function claudeSettings() {
|
|
31
|
-
return JSON.stringify({
|
|
32
|
-
hooks: {
|
|
33
|
-
UserPromptSubmit: [{ hooks: [hook('claude', 'running', '', 'UserPromptSubmit')] }],
|
|
34
|
-
PreToolUse: [{ matcher: '', hooks: [hook('claude', 'running', '', 'PreToolUse')] }],
|
|
35
|
-
PostToolUse: [{ matcher: '', hooks: [hook('claude', 'running', '', 'PostToolUse')] }],
|
|
36
|
-
PermissionRequest: [
|
|
37
|
-
{ matcher: '', hooks: [hook('claude', 'waiting', 'decision', 'PermissionRequest')] },
|
|
38
|
-
],
|
|
39
|
-
Notification: [
|
|
40
|
-
{
|
|
41
|
-
matcher: 'permission_prompt',
|
|
42
|
-
hooks: [hook('claude', 'waiting', 'decision', 'Notification.permission_prompt')],
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
matcher: 'elicitation_dialog',
|
|
46
|
-
hooks: [hook('claude', 'waiting', 'decision', 'Notification.elicitation_dialog')],
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
Stop: [{ hooks: [hook('claude', 'idle', 'done', 'Stop')] }],
|
|
50
|
-
StopFailure: [{ hooks: [hook('claude', 'idle', 'error', 'StopFailure')] }],
|
|
51
|
-
SessionEnd: [{ hooks: [hook('claude', 'idle', 'done', 'SessionEnd')] }],
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function tomlString(v) {
|
|
56
|
-
return `"${v.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
57
|
-
}
|
|
58
|
-
function codexHookConfig(event, cmd, opts = {}) {
|
|
59
|
-
const matcher = opts.matcher === undefined ? '' : `matcher=${tomlString(opts.matcher)},`;
|
|
60
|
-
return `hooks.${event}=[{${matcher}hooks=[{type="command",command=${tomlString(cmd)}}]}]`;
|
|
61
|
-
}
|
|
62
|
-
function codexConfigArgs() {
|
|
63
|
-
const configs = [
|
|
64
|
-
codexHookConfig('UserPromptSubmit', agentHookCommand('codex', 'running', '', 'UserPromptSubmit')),
|
|
65
|
-
codexHookConfig('PreToolUse', agentHookCommand('codex', 'running', '', 'PreToolUse'), {
|
|
66
|
-
matcher: '',
|
|
67
|
-
}),
|
|
68
|
-
codexHookConfig('PostToolUse', agentHookCommand('codex', 'running', '', 'PostToolUse'), {
|
|
69
|
-
matcher: '',
|
|
70
|
-
}),
|
|
71
|
-
codexHookConfig('PermissionRequest', agentHookCommand('codex', 'waiting', 'decision', 'PermissionRequest'), { matcher: '' }),
|
|
72
|
-
codexHookConfig('Stop', agentHookCommand('codex', 'idle', 'done', 'Stop')),
|
|
73
|
-
];
|
|
74
|
-
return configs.map((c) => `-c '${c}'`).join(' ');
|
|
75
|
-
}
|
|
76
|
-
export function augmentAgentCommand(command) {
|
|
77
|
-
const kind = detectAgentKind(command);
|
|
78
|
-
if (!kind)
|
|
79
|
-
return { command };
|
|
80
|
-
const trimmed = command.trim();
|
|
81
|
-
const m = /^(\S+)(\s+[\s\S]*)?$/.exec(trimmed);
|
|
82
|
-
if (!m)
|
|
83
|
-
return { command };
|
|
84
|
-
const prog = m[1];
|
|
85
|
-
const rest = m[2] ?? '';
|
|
86
|
-
if (kind === 'claude') {
|
|
87
|
-
return { kind, command: `${prog} --settings '${claudeSettings()}'${rest}` };
|
|
88
|
-
}
|
|
89
|
-
return { kind, command: `${prog} ${codexConfigArgs()}${rest}` };
|
|
90
|
-
}
|
|
91
|
-
//# sourceMappingURL=agentHooks.js.map
|
package/dist/agentHooks.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agentHooks.js","sourceRoot":"","sources":["../src/agentHooks.ts"],"names":[],"mappings":"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"}
|
package/dist/agentOutput.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
const ERROR_PATTERNS = [
|
|
2
|
-
{
|
|
3
|
-
event: 'CodexStreamDisconnected',
|
|
4
|
-
kinds: ['codex'],
|
|
5
|
-
re: /stream disconnected before completion.*error sending request for url.*\/codex\/responses/i,
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
event: 'StreamDisconnected',
|
|
9
|
-
re: /stream disconnected before completion/i,
|
|
10
|
-
},
|
|
11
|
-
];
|
|
12
|
-
function normalizeTerminalText(text) {
|
|
13
|
-
return text
|
|
14
|
-
.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, '')
|
|
15
|
-
.replace(/\s+/g, ' ')
|
|
16
|
-
.trim();
|
|
17
|
-
}
|
|
18
|
-
export function classifyAgentTerminalError(text, kind) {
|
|
19
|
-
const normalized = normalizeTerminalText(text);
|
|
20
|
-
if (!normalized)
|
|
21
|
-
return undefined;
|
|
22
|
-
for (const pattern of ERROR_PATTERNS) {
|
|
23
|
-
if (pattern.kinds && !pattern.kinds.includes(kind))
|
|
24
|
-
continue;
|
|
25
|
-
if (pattern.re.test(normalized))
|
|
26
|
-
return pattern.event;
|
|
27
|
-
}
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
//# sourceMappingURL=agentOutput.js.map
|
package/dist/agentOutput.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agentOutput.js","sourceRoot":"","sources":["../src/agentOutput.ts"],"names":[],"mappings":"AAQA,MAAM,cAAc,GAA2B;IAC7C;QACE,KAAK,EAAE,yBAAyB;QAChC,KAAK,EAAE,CAAC,OAAO,CAAC;QAChB,EAAE,EAAE,2FAA2F;KAChG;IACD;QACE,KAAK,EAAE,oBAAoB;QAC3B,EAAE,EAAE,wCAAwC;KAC7C;CACF,CAAC;AAEF,SAAS,qBAAqB,CAAC,IAAY;IACzC,OAAO,IAAI;SACR,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC;SACzC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,IAAY,EAAE,IAAe;IACtE,MAAM,UAAU,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IAClC,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,SAAS;QAC7D,IAAI,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,OAAO,OAAO,CAAC,KAAK,CAAC;IACxD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/agentState.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export const AGENT_OPTION = '@tmuxes_agent';
|
|
2
|
-
function isAgentKind(v) {
|
|
3
|
-
return v === 'claude' || v === 'codex';
|
|
4
|
-
}
|
|
5
|
-
function isAgentState(v) {
|
|
6
|
-
return v === 'running' || v === 'waiting' || v === 'idle';
|
|
7
|
-
}
|
|
8
|
-
function isAttentionReason(v) {
|
|
9
|
-
return v === 'decision' || v === 'done' || v === 'error';
|
|
10
|
-
}
|
|
11
|
-
function cleanToken(v) {
|
|
12
|
-
return v.replace(/[^A-Za-z0-9_.-]/g, '_');
|
|
13
|
-
}
|
|
14
|
-
export function agentValue(kind, state, reason, event, nonce) {
|
|
15
|
-
return [
|
|
16
|
-
kind,
|
|
17
|
-
state,
|
|
18
|
-
reason,
|
|
19
|
-
cleanToken(event),
|
|
20
|
-
nonce.replace(/:/g, '_'),
|
|
21
|
-
].join(':');
|
|
22
|
-
}
|
|
23
|
-
export function agentInitialValue(kind) {
|
|
24
|
-
return agentValue(kind, 'idle', '', 'launch', String(Date.now()));
|
|
25
|
-
}
|
|
26
|
-
export function agentHookCommand(kind, state, reason, event) {
|
|
27
|
-
return `tmux set-option -q ${AGENT_OPTION} ${agentValue(kind, state, reason, event, '$(date +%s).$$')}`;
|
|
28
|
-
}
|
|
29
|
-
export function parseAgentValue(raw) {
|
|
30
|
-
if (!raw)
|
|
31
|
-
return undefined;
|
|
32
|
-
const [kind, state, reason, event, ...nonceParts] = raw.split(':');
|
|
33
|
-
if (!isAgentKind(kind) || !isAgentState(state))
|
|
34
|
-
return undefined;
|
|
35
|
-
const snap = { agentKind: kind, agentState: state };
|
|
36
|
-
if (isAttentionReason(reason))
|
|
37
|
-
snap.attentionReason = reason;
|
|
38
|
-
if (event)
|
|
39
|
-
snap.agentEvent = event;
|
|
40
|
-
const nonce = nonceParts.join(':');
|
|
41
|
-
if (nonce)
|
|
42
|
-
snap.agentNonce = nonce;
|
|
43
|
-
return snap;
|
|
44
|
-
}
|
|
45
|
-
//# sourceMappingURL=agentState.js.map
|
package/dist/agentState.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agentState.js","sourceRoot":"","sources":["../src/agentState.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAC;AAE5C,SAAS,WAAW,CAAC,CAAS;IAC5B,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,OAAO,CAAC;AACzC,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,MAAM,CAAC;AAC5D,CAAC;AAED,SAAS,iBAAiB,CAAC,CAAS;IAClC,OAAO,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,OAAO,CAAC;AAC3D,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,IAAe,EACf,KAAiB,EACjB,MAA4B,EAC5B,KAAa,EACb,KAAa;IAEb,OAAO;QACL,IAAI;QACJ,KAAK;QACL,MAAM;QACN,UAAU,CAAC,KAAK,CAAC;QACjB,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;KACzB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAe;IAC/C,OAAO,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,IAAe,EACf,KAAiB,EACjB,MAA4B,EAC5B,KAAa;IAEb,OAAO,sBAAsB,YAAY,IAAI,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,CAAC,EAAE,CAAC;AAC1G,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAuB;IACrD,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACjE,MAAM,IAAI,GAAkB,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACnE,IAAI,iBAAiB,CAAC,MAAM,CAAC;QAAE,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC;IAC7D,IAAI,KAAK;QAAE,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IACnC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,KAAK;QAAE,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IACnC,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/config.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/** Central configuration. Localhost-only by design — see README/SECURITY. */
|
|
2
|
-
export const config = {
|
|
3
|
-
/** Bind address. Intentionally not configurable: this is a no-auth local shell UI. */
|
|
4
|
-
host: '127.0.0.1',
|
|
5
|
-
port: Number(process.env.TMUXES_PORT ?? 7420),
|
|
6
|
-
/** ssh timeouts (seconds). */
|
|
7
|
-
ssh: {
|
|
8
|
-
/** Management calls fail fast so the UI never hangs. */
|
|
9
|
-
connectTimeoutMgmt: 8,
|
|
10
|
-
/** Interactive attach is allowed a little longer. */
|
|
11
|
-
connectTimeoutTty: 10,
|
|
12
|
-
/** Keep the OpenSSH control master alive for long-lived SSH reuse. */
|
|
13
|
-
controlPersist: 'yes',
|
|
14
|
-
},
|
|
15
|
-
/**
|
|
16
|
-
* Allowed WebSocket Origins. The WS upgrade bypasses Express middleware, so
|
|
17
|
-
* we enforce this in the upgrade handler to block DNS-rebind / cross-site WS
|
|
18
|
-
* hijack. Empty/absent Origin (non-browser clients) is allowed.
|
|
19
|
-
*/
|
|
20
|
-
isAllowedOrigin(origin) {
|
|
21
|
-
if (!origin)
|
|
22
|
-
return true;
|
|
23
|
-
try {
|
|
24
|
-
const { hostname } = new URL(origin);
|
|
25
|
-
return hostname === '127.0.0.1' || hostname === 'localhost' || hostname === '[::1]';
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,sFAAsF;IACtF,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC;IAE7C,8BAA8B;IAC9B,GAAG,EAAE;QACH,wDAAwD;QACxD,kBAAkB,EAAE,CAAC;QACrB,qDAAqD;QACrD,iBAAiB,EAAE,EAAE;QACrB,sEAAsE;QACtE,cAAc,EAAE,KAAK;KACtB;IAED;;;;OAIG;IACH,eAAe,CAAC,MAA0B;QACxC,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;YACrC,OAAO,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,OAAO,CAAC;QACtF,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACO,CAAC"}
|