@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.
Files changed (42) hide show
  1. package/README.md +33 -47
  2. package/dist/__tests__/auth.test.d.ts +2 -0
  3. package/dist/__tests__/auth.test.d.ts.map +1 -0
  4. package/dist/__tests__/auth.test.js +61 -0
  5. package/dist/__tests__/auth.test.js.map +1 -0
  6. package/dist/__tests__/spawn-config.test.d.ts +2 -0
  7. package/dist/__tests__/spawn-config.test.d.ts.map +1 -0
  8. package/dist/__tests__/spawn-config.test.js +79 -0
  9. package/dist/__tests__/spawn-config.test.js.map +1 -0
  10. package/dist/__tests__/validation.test.d.ts +2 -0
  11. package/dist/__tests__/validation.test.d.ts.map +1 -0
  12. package/dist/__tests__/validation.test.js +102 -0
  13. package/dist/__tests__/validation.test.js.map +1 -0
  14. package/dist/auth.d.ts +8 -0
  15. package/dist/auth.d.ts.map +1 -0
  16. package/dist/auth.js +21 -0
  17. package/dist/auth.js.map +1 -0
  18. package/dist/config.d.ts +25 -0
  19. package/dist/config.d.ts.map +1 -0
  20. package/dist/config.js +77 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/index.d.ts +7 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +426 -904
  25. package/dist/index.js.map +1 -0
  26. package/dist/spawn-config.d.ts +16 -0
  27. package/dist/spawn-config.d.ts.map +1 -0
  28. package/dist/spawn-config.js +14 -0
  29. package/dist/spawn-config.js.map +1 -0
  30. package/dist/transcript-sync.d.ts +24 -0
  31. package/dist/transcript-sync.d.ts.map +1 -0
  32. package/dist/transcript-sync.js +176 -0
  33. package/dist/transcript-sync.js.map +1 -0
  34. package/dist/types.d.ts +38 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +2 -0
  37. package/dist/types.js.map +1 -0
  38. package/dist/validation.d.ts +16 -0
  39. package/dist/validation.d.ts.map +1 -0
  40. package/dist/validation.js +135 -0
  41. package/dist/validation.js.map +1 -0
  42. package/package.json +16 -16
package/README.md CHANGED
@@ -1,60 +1,46 @@
1
- # @company-os/terminal-server
1
+ # @companyos/terminal-server
2
2
 
3
- Local terminal server for [CompanyOS](https://app.company-os.ai). Run it on your machine to get a live terminal alongside the chat panel in your browser.
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
- ## Quick start
5
+ ## Depends On
6
6
 
7
- ```bash
8
- npx @company-os/terminal-server
9
- ```
7
+ - `node-pty` -- pseudo-terminal spawning
8
+ - `ws` -- WebSocket server
9
+ - `zod` -- env var validation
10
10
 
11
- Then open [app.company-os.ai](https://app.company-os.ai), click the terminal toggle in chat, and click **Reconnect**.
11
+ ## Key Exports
12
12
 
13
- ## Requirements
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
- - **Node.js 18+**
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
- ## How it works
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
- The server runs on `localhost:3002` and exposes a WebSocket endpoint. Your browser connects to `ws://localhost:3002` directly — CompanyOS never proxies or receives your shell access.
27
+ ## Scripts
24
28
 
25
- ```
26
- Browser (app.company-os.ai) ──ws://localhost:3002──► terminal-server (your machine)
27
-
28
-
29
- PTY (shell)
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
- | Environment variable | Default | Description |
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
- ## Troubleshooting
41
-
42
- **Port 3002 is in use**
43
-
44
- ```bash
45
- TERMINAL_SERVER_PORT=3003 npx @company-os/terminal-server
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=spawn-config.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validation.test.d.ts.map
@@ -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
@@ -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"}
@@ -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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"}
@@ -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"}