@company-os/terminal-server 1.0.0 → 1.1.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/README.md +33 -47
- package/dist/__tests__/auth.test.d.ts +2 -0
- package/dist/__tests__/auth.test.d.ts.map +1 -0
- package/dist/__tests__/auth.test.js +61 -0
- package/dist/__tests__/auth.test.js.map +1 -0
- package/dist/__tests__/spawn-config.test.d.ts +2 -0
- package/dist/__tests__/spawn-config.test.d.ts.map +1 -0
- package/dist/__tests__/spawn-config.test.js +79 -0
- package/dist/__tests__/spawn-config.test.js.map +1 -0
- package/dist/__tests__/validation.test.d.ts +2 -0
- package/dist/__tests__/validation.test.d.ts.map +1 -0
- package/dist/__tests__/validation.test.js +102 -0
- package/dist/__tests__/validation.test.js.map +1 -0
- package/dist/auth.d.ts +8 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +21 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +77 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +426 -904
- package/dist/index.js.map +1 -0
- package/dist/spawn-config.d.ts +16 -0
- package/dist/spawn-config.d.ts.map +1 -0
- package/dist/spawn-config.js +14 -0
- package/dist/spawn-config.js.map +1 -0
- package/dist/transcript-sync.d.ts +24 -0
- package/dist/transcript-sync.d.ts.map +1 -0
- package/dist/transcript-sync.js +176 -0
- package/dist/transcript-sync.js.map +1 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +16 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +135 -0
- package/dist/validation.js.map +1 -0
- package/package.json +16 -16
package/README.md
CHANGED
|
@@ -1,60 +1,46 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @companyos/terminal-server
|
|
2
2
|
|
|
3
|
-
Local
|
|
3
|
+
Local WebSocket server that spawns and manages pseudo-terminal (PTY) sessions for AI CLI tools (Claude Code, Codex, Gemini) and interactive shells.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Depends On
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
- `node-pty` -- pseudo-terminal spawning
|
|
8
|
+
- `ws` -- WebSocket server
|
|
9
|
+
- `zod` -- env var validation
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Key Exports
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
- `SERVER_CONFIG` -- resolved server configuration (port, origins, max sessions, idle timeout)
|
|
14
|
+
- `buildSpawnConfig` / `SpawnConfig` / `CreateMessage` -- PTY spawn configuration builder
|
|
15
|
+
- `ServerMessage` / `ClientMessage` -- WebSocket protocol types
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
- **C++ build toolchain** (required by `node-pty`):
|
|
17
|
-
- macOS: Xcode Command Line Tools (`xcode-select --install`)
|
|
18
|
-
- Windows: Visual Studio Build Tools (`npm install -g windows-build-tools`)
|
|
19
|
-
- Linux: `build-essential` (`sudo apt install build-essential`)
|
|
17
|
+
## Endpoints
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
| Path | Method | Description |
|
|
20
|
+
|--------------|--------|--------------------------------------------------|
|
|
21
|
+
| `/health` | GET | Server status and active session count |
|
|
22
|
+
| `/status` | GET | Claude CLI availability check |
|
|
23
|
+
| `/sessions` | GET | Lists Claude Code sessions from `~/.claude/` |
|
|
24
|
+
| `/clis` | GET | Detected CLI tools (claude, codex, gemini) |
|
|
25
|
+
| `ws://` | WS | Terminal PTY session (create, input, resize, ping) |
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
## Scripts
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```
|
|
29
|
+
| Script | Command | Description |
|
|
30
|
+
|---------|----------------------|-----------------------------|
|
|
31
|
+
| `dev` | `npx tsx src/index.ts` | Run with ts-node (dev mode) |
|
|
32
|
+
| `build` | `tsc -b` | Compile TypeScript |
|
|
33
|
+
| `start` | `node dist/index.js` | Run compiled server |
|
|
31
34
|
|
|
32
35
|
## Configuration
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|---|---|---|
|
|
36
|
-
| `TERMINAL_SERVER_PORT` | `3002` | Port to listen on |
|
|
37
|
-
| `ALLOWED_ORIGINS` | `http://localhost:3000,http://localhost:3002,https://app.company-os.ai` | Comma-separated allowed origins |
|
|
38
|
-
| `COMPANYOS_WORKSPACE_ROOT` | Current directory | Working directory for new shells |
|
|
37
|
+
Environment variables (all optional with defaults):
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
**node-pty fails to install**
|
|
49
|
-
|
|
50
|
-
You're missing the C++ build toolchain. See Requirements above.
|
|
51
|
-
|
|
52
|
-
**Browser says "Connect your terminal" but server is running**
|
|
53
|
-
|
|
54
|
-
Check the server output — it prints the port it's listening on. If it's not 3002, the browser won't find it (it defaults to 3002).
|
|
55
|
-
|
|
56
|
-
## Security model
|
|
57
|
-
|
|
58
|
-
- Binds to **localhost only** — not accessible from the network
|
|
59
|
-
- **Origin allowlist** — only connections from listed origins are accepted; missing origins are rejected
|
|
60
|
-
- Your shell runs on your machine; CompanyOS servers never see your terminal traffic
|
|
39
|
+
| Variable | Default | Description |
|
|
40
|
+
|-----------------------------------|---------|--------------------------------|
|
|
41
|
+
| `TERMINAL_SERVER_PORT` | `3002` | HTTP/WS listen port |
|
|
42
|
+
| `ALLOWED_ORIGINS` | (list) | Comma-separated allowed origins |
|
|
43
|
+
| `TERMINAL_SERVER_MAX_SESSIONS` | `5` | Max concurrent PTY sessions |
|
|
44
|
+
| `TERMINAL_SERVER_IDLE_TIMEOUT_MS` | `1800000` | Session idle timeout (30min) |
|
|
45
|
+
| `TERMINAL_SERVER_TOKEN` | (none) | Auth token for WS connections |
|
|
46
|
+
| `COMPANYOS_WORKSPACE_ROOT` | `$HOME` | Default working directory |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { getAuthConfig, verifyToken, extractTokenFromRequest } from '../auth.js';
|
|
3
|
+
describe('auth', () => {
|
|
4
|
+
const originalEnv = process.env.TERMINAL_SERVER_TOKEN;
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
if (originalEnv === undefined) {
|
|
7
|
+
delete process.env.TERMINAL_SERVER_TOKEN;
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
process.env.TERMINAL_SERVER_TOKEN = originalEnv;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
describe('getAuthConfig', () => {
|
|
14
|
+
it('returns null token when env var is not set', () => {
|
|
15
|
+
delete process.env.TERMINAL_SERVER_TOKEN;
|
|
16
|
+
expect(getAuthConfig().token).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
it('returns token from env var', () => {
|
|
19
|
+
process.env.TERMINAL_SERVER_TOKEN = 'test-secret-123';
|
|
20
|
+
expect(getAuthConfig().token).toBe('test-secret-123');
|
|
21
|
+
});
|
|
22
|
+
it('returns null for empty string', () => {
|
|
23
|
+
process.env.TERMINAL_SERVER_TOKEN = '';
|
|
24
|
+
expect(getAuthConfig().token).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('verifyToken', () => {
|
|
28
|
+
it('returns true for matching tokens', () => {
|
|
29
|
+
expect(verifyToken('abc123', 'abc123')).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
it('returns false for non-matching tokens of same length', () => {
|
|
32
|
+
expect(verifyToken('abc123', 'xyz789')).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it('returns false for tokens of different lengths', () => {
|
|
35
|
+
expect(verifyToken('short', 'muchlongertoken')).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
it('handles long hex tokens', () => {
|
|
38
|
+
const token = 'a'.repeat(64);
|
|
39
|
+
expect(verifyToken(token, token)).toBe(true);
|
|
40
|
+
expect(verifyToken(token, 'b'.repeat(64))).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('extractTokenFromRequest', () => {
|
|
44
|
+
function mockReq(url, host = 'localhost:3002') {
|
|
45
|
+
return { url, headers: { host } };
|
|
46
|
+
}
|
|
47
|
+
it('extracts token from query string', () => {
|
|
48
|
+
expect(extractTokenFromRequest(mockReq('/?token=mytoken'))).toBe('mytoken');
|
|
49
|
+
});
|
|
50
|
+
it('returns null when no token param', () => {
|
|
51
|
+
expect(extractTokenFromRequest(mockReq('/'))).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
it('returns null for missing url', () => {
|
|
54
|
+
expect(extractTokenFromRequest({ headers: {} })).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
it('handles url with path and other params', () => {
|
|
57
|
+
expect(extractTokenFromRequest(mockReq('/ws?foo=bar&token=secret&baz=1'))).toBe('secret');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=auth.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../src/__tests__/auth.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/spawn-config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { buildSpawnConfig } from '../spawn-config.js';
|
|
3
|
+
import { getDefaultShell, getWorkingDirectory } from '../config.js';
|
|
4
|
+
describe('buildSpawnConfig', () => {
|
|
5
|
+
it('uses default shell when no command specified', () => {
|
|
6
|
+
const config = buildSpawnConfig({ type: 'create', cwd: '/tmp' });
|
|
7
|
+
expect(config.command).toBe(getDefaultShell());
|
|
8
|
+
expect(config.args).toEqual([]);
|
|
9
|
+
});
|
|
10
|
+
it('uses specified command and args', () => {
|
|
11
|
+
const config = buildSpawnConfig({
|
|
12
|
+
type: 'create',
|
|
13
|
+
cwd: '/tmp',
|
|
14
|
+
command: 'claude',
|
|
15
|
+
args: ['--mcp-config', '/path/to/mcp.json'],
|
|
16
|
+
});
|
|
17
|
+
expect(config.command).toBe('claude');
|
|
18
|
+
expect(config.args).toEqual(['--mcp-config', '/path/to/mcp.json']);
|
|
19
|
+
});
|
|
20
|
+
it('uses specified command with empty args if none provided', () => {
|
|
21
|
+
const config = buildSpawnConfig({ type: 'create', command: 'codex' });
|
|
22
|
+
expect(config.command).toBe('codex');
|
|
23
|
+
expect(config.args).toEqual([]);
|
|
24
|
+
});
|
|
25
|
+
it('uses provided cwd', () => {
|
|
26
|
+
const config = buildSpawnConfig({ type: 'create', cwd: '/home/user/project' });
|
|
27
|
+
expect(config.cwd).toBe('/home/user/project');
|
|
28
|
+
});
|
|
29
|
+
it('falls back to working directory when no cwd provided', () => {
|
|
30
|
+
const config = buildSpawnConfig({ type: 'create' });
|
|
31
|
+
expect(config.cwd).toBe(getWorkingDirectory());
|
|
32
|
+
});
|
|
33
|
+
it('falls back to default shell when command is empty string', () => {
|
|
34
|
+
const config = buildSpawnConfig({ type: 'create', command: '' });
|
|
35
|
+
expect(config.command).toBe(getDefaultShell());
|
|
36
|
+
});
|
|
37
|
+
it('uses empty args array when args is undefined', () => {
|
|
38
|
+
const config = buildSpawnConfig({ type: 'create', args: undefined });
|
|
39
|
+
expect(config.args).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
it('adds --resume flag when resumeSessionId is provided', () => {
|
|
42
|
+
const config = buildSpawnConfig({
|
|
43
|
+
type: 'create',
|
|
44
|
+
command: 'claude',
|
|
45
|
+
resumeSessionId: 'abc-123',
|
|
46
|
+
});
|
|
47
|
+
expect(config.command).toBe('claude');
|
|
48
|
+
expect(config.args).toEqual(['--resume', 'abc-123']);
|
|
49
|
+
});
|
|
50
|
+
it('appends --resume to existing args', () => {
|
|
51
|
+
const config = buildSpawnConfig({
|
|
52
|
+
type: 'create',
|
|
53
|
+
command: 'claude',
|
|
54
|
+
args: ['--verbose'],
|
|
55
|
+
resumeSessionId: 'abc-123',
|
|
56
|
+
});
|
|
57
|
+
expect(config.args).toEqual(['--verbose', '--resume', 'abc-123']);
|
|
58
|
+
});
|
|
59
|
+
it('does not duplicate --resume if already in args', () => {
|
|
60
|
+
const config = buildSpawnConfig({
|
|
61
|
+
type: 'create',
|
|
62
|
+
command: 'claude',
|
|
63
|
+
args: ['--resume', 'existing-id'],
|
|
64
|
+
resumeSessionId: 'abc-123',
|
|
65
|
+
});
|
|
66
|
+
expect(config.args).toEqual(['--resume', 'existing-id']);
|
|
67
|
+
});
|
|
68
|
+
it('does not mutate original args array', () => {
|
|
69
|
+
const original = ['--verbose'];
|
|
70
|
+
buildSpawnConfig({
|
|
71
|
+
type: 'create',
|
|
72
|
+
command: 'claude',
|
|
73
|
+
args: original,
|
|
74
|
+
resumeSessionId: 'abc-123',
|
|
75
|
+
});
|
|
76
|
+
expect(original).toEqual(['--verbose']);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
//# sourceMappingURL=spawn-config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-config.test.js","sourceRoot":"","sources":["../../src/__tests__/spawn-config.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/validation.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { validateClientMessage, ALLOWED_COMMANDS } from "../validation.js";
|
|
3
|
+
describe("command allowlist", () => {
|
|
4
|
+
it("allows known CLI commands", () => {
|
|
5
|
+
for (const cmd of ["claude", "codex", "gemini", "zsh", "bash", "fish", "sh"]) {
|
|
6
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", command: cmd }));
|
|
7
|
+
expect(result.valid, `${cmd} should be allowed`).toBe(true);
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
it("allows create with no command (default shell)", () => {
|
|
11
|
+
const result = validateClientMessage(JSON.stringify({ type: "create" }));
|
|
12
|
+
expect(result.valid).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
it("rejects unknown commands", () => {
|
|
15
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", command: "rm" }));
|
|
16
|
+
expect(result.valid).toBe(false);
|
|
17
|
+
expect(result.error).toContain("not allowed");
|
|
18
|
+
});
|
|
19
|
+
it("rejects absolute paths", () => {
|
|
20
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", command: "/usr/bin/python3" }));
|
|
21
|
+
expect(result.valid).toBe(false);
|
|
22
|
+
expect(result.error).toContain("path");
|
|
23
|
+
});
|
|
24
|
+
it("rejects commands with path separators", () => {
|
|
25
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", command: "../evil" }));
|
|
26
|
+
expect(result.valid).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
it("exports ALLOWED_COMMANDS set", () => {
|
|
29
|
+
expect(ALLOWED_COMMANDS).toBeInstanceOf(Set);
|
|
30
|
+
expect(ALLOWED_COMMANDS.has("claude")).toBe(true);
|
|
31
|
+
expect(ALLOWED_COMMANDS.has("rm")).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe("cwd validation", () => {
|
|
35
|
+
it("accepts absolute paths", () => {
|
|
36
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", cwd: "/home/user/project" }));
|
|
37
|
+
expect(result.valid).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
it("rejects relative paths", () => {
|
|
40
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", cwd: "relative/path" }));
|
|
41
|
+
expect(result.valid).toBe(false);
|
|
42
|
+
expect(result.error).toContain("absolute path");
|
|
43
|
+
});
|
|
44
|
+
it("rejects path traversal", () => {
|
|
45
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", cwd: "/home/user/../etc" }));
|
|
46
|
+
expect(result.valid).toBe(false);
|
|
47
|
+
expect(result.error).toContain("..");
|
|
48
|
+
});
|
|
49
|
+
it("allows create with no cwd", () => {
|
|
50
|
+
const result = validateClientMessage(JSON.stringify({ type: "create" }));
|
|
51
|
+
expect(result.valid).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe("shell args sanitization", () => {
|
|
55
|
+
it("rejects -c for shell commands", () => {
|
|
56
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", command: "bash", args: ["-c", "rm -rf /"] }));
|
|
57
|
+
expect(result.valid).toBe(false);
|
|
58
|
+
expect(result.error).toContain("-c");
|
|
59
|
+
expect(result.error).toContain("bash");
|
|
60
|
+
});
|
|
61
|
+
it("rejects -e for shell commands", () => {
|
|
62
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", command: "zsh", args: ["-e", "evil"] }));
|
|
63
|
+
expect(result.valid).toBe(false);
|
|
64
|
+
expect(result.error).toContain("-e");
|
|
65
|
+
});
|
|
66
|
+
it("rejects --execute for shell commands", () => {
|
|
67
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", command: "sh", args: ["--execute", "evil"] }));
|
|
68
|
+
expect(result.valid).toBe(false);
|
|
69
|
+
expect(result.error).toContain("--execute");
|
|
70
|
+
});
|
|
71
|
+
it("allows -c for non-shell commands like claude", () => {
|
|
72
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", command: "claude", args: ["-c", "continue"] }));
|
|
73
|
+
expect(result.valid).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
it("allows safe args for shell commands", () => {
|
|
76
|
+
const result = validateClientMessage(JSON.stringify({ type: "create", command: "bash", args: ["--norc"] }));
|
|
77
|
+
expect(result.valid).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe("existing validation", () => {
|
|
81
|
+
it("rejects invalid JSON", () => {
|
|
82
|
+
const result = validateClientMessage("not json");
|
|
83
|
+
expect(result.valid).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
it("rejects unknown message types", () => {
|
|
86
|
+
const result = validateClientMessage(JSON.stringify({ type: "unknown" }));
|
|
87
|
+
expect(result.valid).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
it("validates ping messages", () => {
|
|
90
|
+
const result = validateClientMessage(JSON.stringify({ type: "ping" }));
|
|
91
|
+
expect(result.valid).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
it("validates input messages", () => {
|
|
94
|
+
const result = validateClientMessage(JSON.stringify({ type: "input", data: "hello" }));
|
|
95
|
+
expect(result.valid).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
it("validates resize messages", () => {
|
|
98
|
+
const result = validateClientMessage(JSON.stringify({ type: "resize", cols: 80, rows: 24 }));
|
|
99
|
+
expect(result.valid).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=validation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.test.js","sourceRoot":"","sources":["../../src/__tests__/validation.test.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
export interface AuthConfig {
|
|
3
|
+
token: string | null;
|
|
4
|
+
}
|
|
5
|
+
export declare function getAuthConfig(): AuthConfig;
|
|
6
|
+
export declare function verifyToken(provided: string, expected: string): boolean;
|
|
7
|
+
export declare function extractTokenFromRequest(req: IncomingMessage): string | null;
|
|
8
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAIjD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,aAAa,IAAI,UAAU,CAE1C;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGvE;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAO3E"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { URL } from 'node:url';
|
|
3
|
+
import { getAuthToken } from './config.js';
|
|
4
|
+
export function getAuthConfig() {
|
|
5
|
+
return { token: getAuthToken() };
|
|
6
|
+
}
|
|
7
|
+
export function verifyToken(provided, expected) {
|
|
8
|
+
if (provided.length !== expected.length)
|
|
9
|
+
return false;
|
|
10
|
+
return timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
|
|
11
|
+
}
|
|
12
|
+
export function extractTokenFromRequest(req) {
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`);
|
|
15
|
+
return url.searchParams.get('token');
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM3C,MAAM,UAAU,aAAa;IAC3B,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,QAAgB;IAC5D,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACtD,OAAO,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,GAAoB;IAC1D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC,CAAC;QAChF,OAAO,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const SERVER_CONFIG: {
|
|
2
|
+
readonly defaultPort: number;
|
|
3
|
+
readonly allowedOrigins: string[];
|
|
4
|
+
readonly maxSessions: number;
|
|
5
|
+
readonly idleTimeoutMs: number;
|
|
6
|
+
};
|
|
7
|
+
export declare const PTY_CONFIG: {
|
|
8
|
+
readonly termName: "xterm-256color";
|
|
9
|
+
readonly cols: 80;
|
|
10
|
+
readonly rows: 24;
|
|
11
|
+
};
|
|
12
|
+
export declare function getDefaultShell(): string;
|
|
13
|
+
export declare function getWorkingDirectory(): string;
|
|
14
|
+
export declare function generateSessionId(): string;
|
|
15
|
+
export declare function getAuthToken(): string | null;
|
|
16
|
+
export interface CliInfo {
|
|
17
|
+
id: string;
|
|
18
|
+
label: string;
|
|
19
|
+
command: string;
|
|
20
|
+
detected: boolean;
|
|
21
|
+
resumeSupported: boolean;
|
|
22
|
+
installCommand: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function detectClis(): CliInfo[];
|
|
25
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,aAAa;;;;;CAKhB,CAAC;AAEX,eAAO,MAAM,UAAU;;;;CAIb,CAAC;AAEX,wBAAgB,eAAe,IAAI,MAAM,CAKxC;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAU5C;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAG5C;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;CACxB;AAWD,wBAAgB,UAAU,IAAI,OAAO,EAAE,CAWtC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as os from 'os';
|
|
2
|
+
import { execFileSync } from 'child_process';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
const EnvSchema = z.object({
|
|
5
|
+
TERMINAL_SERVER_PORT: z.coerce.number().int().positive().default(3002),
|
|
6
|
+
ALLOWED_ORIGINS: z
|
|
7
|
+
.string()
|
|
8
|
+
.default('http://localhost:3000,http://localhost:3002,http://localhost:5173,https://app.company-os.ai')
|
|
9
|
+
.transform((s) => s.split(',')),
|
|
10
|
+
TERMINAL_SERVER_MAX_SESSIONS: z.coerce.number().int().positive().default(5),
|
|
11
|
+
TERMINAL_SERVER_IDLE_TIMEOUT_MS: z.coerce.number().int().nonnegative().default(1_800_000),
|
|
12
|
+
TERMINAL_SERVER_TOKEN: z.string().min(1).nullish().transform((v) => v ?? null),
|
|
13
|
+
COMPANYOS_WORKSPACE_ROOT: z.string().optional(),
|
|
14
|
+
});
|
|
15
|
+
function loadEnv() {
|
|
16
|
+
return EnvSchema.parse(process.env);
|
|
17
|
+
}
|
|
18
|
+
const env = loadEnv();
|
|
19
|
+
export const SERVER_CONFIG = {
|
|
20
|
+
defaultPort: env.TERMINAL_SERVER_PORT,
|
|
21
|
+
allowedOrigins: env.ALLOWED_ORIGINS,
|
|
22
|
+
maxSessions: env.TERMINAL_SERVER_MAX_SESSIONS,
|
|
23
|
+
idleTimeoutMs: env.TERMINAL_SERVER_IDLE_TIMEOUT_MS,
|
|
24
|
+
};
|
|
25
|
+
export const PTY_CONFIG = {
|
|
26
|
+
termName: 'xterm-256color',
|
|
27
|
+
cols: 80,
|
|
28
|
+
rows: 24,
|
|
29
|
+
};
|
|
30
|
+
export function getDefaultShell() {
|
|
31
|
+
if (os.platform() === 'win32') {
|
|
32
|
+
return process.env.COMSPEC || 'cmd.exe';
|
|
33
|
+
}
|
|
34
|
+
return process.env.SHELL || '/bin/zsh';
|
|
35
|
+
}
|
|
36
|
+
export function getWorkingDirectory() {
|
|
37
|
+
if (env.COMPANYOS_WORKSPACE_ROOT)
|
|
38
|
+
return env.COMPANYOS_WORKSPACE_ROOT;
|
|
39
|
+
// Default to the repo root so embedded terminals pick up .mcp.json and CLAUDE.md
|
|
40
|
+
try {
|
|
41
|
+
return execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
42
|
+
encoding: 'utf-8',
|
|
43
|
+
timeout: 2000,
|
|
44
|
+
}).trim();
|
|
45
|
+
}
|
|
46
|
+
catch { /* not in a git repo */ }
|
|
47
|
+
return os.homedir();
|
|
48
|
+
}
|
|
49
|
+
export function generateSessionId() {
|
|
50
|
+
return `session-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
51
|
+
}
|
|
52
|
+
export function getAuthToken() {
|
|
53
|
+
const raw = process.env.TERMINAL_SERVER_TOKEN;
|
|
54
|
+
return raw && raw.length > 0 ? raw : null;
|
|
55
|
+
}
|
|
56
|
+
const CLI_REGISTRY = [
|
|
57
|
+
{ id: "claude", label: "Claude Code", command: "claude", resumeSupported: true, installCommand: "npm install -g @anthropic-ai/claude-code" },
|
|
58
|
+
{ id: "codex", label: "Codex", command: "codex", resumeSupported: true, installCommand: "npm install -g @openai/codex" },
|
|
59
|
+
{ id: "gemini", label: "Gemini", command: "gemini", resumeSupported: false, installCommand: "npm install -g @google/gemini-cli" },
|
|
60
|
+
{ id: "aider", label: "Aider", command: "aider", resumeSupported: false, installCommand: "pip install aider-chat" },
|
|
61
|
+
];
|
|
62
|
+
let cachedClis = null;
|
|
63
|
+
export function detectClis() {
|
|
64
|
+
if (cachedClis)
|
|
65
|
+
return cachedClis;
|
|
66
|
+
cachedClis = CLI_REGISTRY.map((cli) => {
|
|
67
|
+
let detected = false;
|
|
68
|
+
try {
|
|
69
|
+
execFileSync('which', [cli.command], { encoding: 'utf-8', timeout: 2000 });
|
|
70
|
+
detected = true;
|
|
71
|
+
}
|
|
72
|
+
catch { /* not found */ }
|
|
73
|
+
return { ...cli, detected };
|
|
74
|
+
});
|
|
75
|
+
return cachedClis;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { SERVER_CONFIG } from './config.js';
|
|
3
|
+
export { SERVER_CONFIG };
|
|
4
|
+
export { buildSpawnConfig } from './spawn-config.js';
|
|
5
|
+
export type { CreateMessage, SpawnConfig } from './spawn-config.js';
|
|
6
|
+
export type { ServerMessage, ClientMessage } from './types.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAUA,OAAO,EAAE,aAAa,EAA6C,MAAM,aAAa,CAAC;AA2fvF,OAAO,EAAE,aAAa,EAAE,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|