@clsh/agent 0.0.1

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 (53) hide show
  1. package/dist/auth.d.ts +37 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +62 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/config.d.ts +23 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +119 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/control-mode-parser.d.ts +68 -0
  10. package/dist/control-mode-parser.d.ts.map +1 -0
  11. package/dist/control-mode-parser.js +111 -0
  12. package/dist/control-mode-parser.js.map +1 -0
  13. package/dist/db.d.ts +44 -0
  14. package/dist/db.d.ts.map +1 -0
  15. package/dist/db.js +63 -0
  16. package/dist/db.js.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +114 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/power.d.ts +9 -0
  22. package/dist/power.d.ts.map +1 -0
  23. package/dist/power.js +29 -0
  24. package/dist/power.js.map +1 -0
  25. package/dist/pty-manager.d.ts +110 -0
  26. package/dist/pty-manager.d.ts.map +1 -0
  27. package/dist/pty-manager.js +468 -0
  28. package/dist/pty-manager.js.map +1 -0
  29. package/dist/server.d.ts +24 -0
  30. package/dist/server.d.ts.map +1 -0
  31. package/dist/server.js +160 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/sse-handler.d.ts +13 -0
  34. package/dist/sse-handler.d.ts.map +1 -0
  35. package/dist/sse-handler.js +76 -0
  36. package/dist/sse-handler.js.map +1 -0
  37. package/dist/tmux.d.ts +34 -0
  38. package/dist/tmux.d.ts.map +1 -0
  39. package/dist/tmux.js +114 -0
  40. package/dist/tmux.js.map +1 -0
  41. package/dist/tunnel.d.ts +43 -0
  42. package/dist/tunnel.d.ts.map +1 -0
  43. package/dist/tunnel.js +294 -0
  44. package/dist/tunnel.js.map +1 -0
  45. package/dist/types.d.ts +76 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +2 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/ws-handler.d.ts +13 -0
  50. package/dist/ws-handler.d.ts.map +1 -0
  51. package/dist/ws-handler.js +266 -0
  52. package/dist/ws-handler.js.map +1 -0
  53. package/package.json +47 -0
package/dist/auth.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { type JWTPayload } from 'jose';
2
+ import type { DbStatements } from './db.js';
3
+ /**
4
+ * Generates a cryptographically secure bootstrap token (256-bit, base64url encoded).
5
+ * This token is shown once in the terminal and used for initial authentication.
6
+ */
7
+ export declare function generateBootstrapToken(): string;
8
+ /**
9
+ * Returns the SHA-256 hex digest of the given token.
10
+ * Only the hash is stored in the database -- never the raw token.
11
+ */
12
+ export declare function hashToken(token: string): string;
13
+ /**
14
+ * Verifies a bootstrap token candidate against the database.
15
+ * Tokens expire after 5 minutes — long enough for the user to scan the QR
16
+ * in Safari, add the PWA to their home screen, and re-authenticate, but
17
+ * short enough to limit exposure if the QR code is intercepted.
18
+ */
19
+ export declare function verifyBootstrapToken(statements: DbStatements, candidateToken: string): boolean;
20
+ export interface SessionJWTClaims {
21
+ email?: string;
22
+ authMethod: 'bootstrap';
23
+ }
24
+ /**
25
+ * Creates a signed JWT for an authenticated session.
26
+ * Uses HS256 with an 8-hour expiry and a random JTI for uniqueness.
27
+ */
28
+ export declare function createSessionJWT(claims: SessionJWTClaims, secret: string): Promise<string>;
29
+ export interface VerifiedJWT {
30
+ payload: JWTPayload;
31
+ }
32
+ /**
33
+ * Verifies a JWT token and returns the decoded payload.
34
+ * Throws if the token is invalid, expired, or tampered with.
35
+ */
36
+ export declare function verifyJWT(token: string, secret: string): Promise<VerifiedJWT>;
37
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAKD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,YAAY,EACxB,cAAc,EAAE,MAAM,GACrB,OAAO,CAQT;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,WAAW,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,UAAU,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,WAAW,CAAC,CAStB"}
package/dist/auth.js ADDED
@@ -0,0 +1,62 @@
1
+ import { randomBytes, createHash, randomUUID } from 'node:crypto';
2
+ import { SignJWT, jwtVerify } from 'jose';
3
+ /**
4
+ * Generates a cryptographically secure bootstrap token (256-bit, base64url encoded).
5
+ * This token is shown once in the terminal and used for initial authentication.
6
+ */
7
+ export function generateBootstrapToken() {
8
+ return randomBytes(32).toString('base64url');
9
+ }
10
+ /**
11
+ * Returns the SHA-256 hex digest of the given token.
12
+ * Only the hash is stored in the database -- never the raw token.
13
+ */
14
+ export function hashToken(token) {
15
+ return createHash('sha256').update(token).digest('hex');
16
+ }
17
+ /** How long a bootstrap token remains valid after creation (5 minutes). */
18
+ const BOOTSTRAP_TOKEN_TTL_MS = 5 * 60 * 1000;
19
+ /**
20
+ * Verifies a bootstrap token candidate against the database.
21
+ * Tokens expire after 5 minutes — long enough for the user to scan the QR
22
+ * in Safari, add the PWA to their home screen, and re-authenticate, but
23
+ * short enough to limit exposure if the QR code is intercepted.
24
+ */
25
+ export function verifyBootstrapToken(statements, candidateToken) {
26
+ const hash = hashToken(candidateToken);
27
+ const row = statements.getBootstrapToken.get(hash);
28
+ if (!row)
29
+ return false;
30
+ const createdAt = new Date(row.created_at + 'Z').getTime(); // SQLite datetime is UTC without 'Z'
31
+ const age = Date.now() - createdAt;
32
+ return age < BOOTSTRAP_TOKEN_TTL_MS;
33
+ }
34
+ /**
35
+ * Creates a signed JWT for an authenticated session.
36
+ * Uses HS256 with an 8-hour expiry and a random JTI for uniqueness.
37
+ */
38
+ export async function createSessionJWT(claims, secret) {
39
+ const secretKey = new TextEncoder().encode(secret);
40
+ const jti = randomUUID();
41
+ return new SignJWT({ ...claims })
42
+ .setProtectedHeader({ alg: 'HS256' })
43
+ .setIssuedAt()
44
+ .setExpirationTime('30d')
45
+ .setJti(jti)
46
+ .setIssuer('clsh-agent')
47
+ .setSubject(claims.email ?? 'local')
48
+ .sign(secretKey);
49
+ }
50
+ /**
51
+ * Verifies a JWT token and returns the decoded payload.
52
+ * Throws if the token is invalid, expired, or tampered with.
53
+ */
54
+ export async function verifyJWT(token, secret) {
55
+ const secretKey = new TextEncoder().encode(secret);
56
+ const { payload } = await jwtVerify(token, secretKey, {
57
+ issuer: 'clsh-agent',
58
+ algorithms: ['HS256'],
59
+ });
60
+ return { payload };
61
+ }
62
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAmB,MAAM,MAAM,CAAC;AAG3D;;;GAGG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED,2EAA2E;AAC3E,MAAM,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAAwB,EACxB,cAAsB;IAEtB,MAAM,IAAI,GAAG,SAAS,CAAC,cAAc,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnD,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IAEvB,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,qCAAqC;IACjG,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACnC,OAAO,GAAG,GAAG,sBAAsB,CAAC;AACtC,CAAC;AAOD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAwB,EACxB,MAAc;IAEd,MAAM,SAAS,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IAEzB,OAAO,IAAI,OAAO,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC;SAC9B,kBAAkB,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;SACpC,WAAW,EAAE;SACb,iBAAiB,CAAC,KAAK,CAAC;SACxB,MAAM,CAAC,GAAG,CAAC;SACX,SAAS,CAAC,YAAY,CAAC;SACvB,UAAU,CAAC,MAAM,CAAC,KAAK,IAAI,OAAO,CAAC;SACnC,IAAI,CAAC,SAAS,CAAC,CAAC;AACrB,CAAC;AAMD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,KAAa,EACb,MAAc;IAEd,MAAM,SAAS,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAEnD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE;QACpD,MAAM,EAAE,YAAY;QACpB,UAAU,EAAE,CAAC,OAAO,CAAC;KACtB,CAAC,CAAC;IAEH,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC"}
@@ -0,0 +1,23 @@
1
+ export interface AgentConfig {
2
+ port: number;
3
+ /** Port that the ngrok tunnel should forward to.
4
+ * In dev, set WEB_PORT=4031 so the tunnel hits the Vite dev server
5
+ * (which proxies /api and /ws to the agent). In production this
6
+ * defaults to `port` because the agent serves the built web UI. */
7
+ webPort: number;
8
+ jwtSecret: string;
9
+ ngrokAuthtoken: string | undefined;
10
+ /** Optional static ngrok domain (e.g. "my-name.ngrok-free.app").
11
+ * Free ngrok accounts get one static domain — set NGROK_STATIC_DOMAIN
12
+ * in .env to keep the same URL across restarts. */
13
+ ngrokStaticDomain: string | undefined;
14
+ /** Force a specific tunnel method: 'ngrok', 'ssh', or 'local'.
15
+ * If unset, auto-detects (ngrok → ssh → local). Set TUNNEL=ssh to skip ngrok. */
16
+ tunnelMethod: 'ngrok' | 'ssh' | 'local' | undefined;
17
+ resendApiKey: string | undefined;
18
+ dbPath: string;
19
+ /** Set CLSH_NO_TMUX=1 to disable tmux session persistence even when tmux is available. */
20
+ tmuxDisabled: boolean;
21
+ }
22
+ export declare function loadConfig(): AgentConfig;
23
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb;;;wEAGoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC;;wDAEoD;IACpD,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC;sFACkF;IAClF,YAAY,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,CAAC;IACpD,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,YAAY,EAAE,OAAO,CAAC;CACvB;AAyGD,wBAAgB,UAAU,IAAI,WAAW,CAoBxC"}
package/dist/config.js ADDED
@@ -0,0 +1,119 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { join, resolve } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
5
+ /**
6
+ * Loads variables from a .env file into process.env.
7
+ * Only sets variables that are not already set (process env takes precedence).
8
+ * Silently no-ops if the file doesn't exist.
9
+ *
10
+ * Handles:
11
+ * KEY=value
12
+ * KEY="quoted value"
13
+ * # comments
14
+ * blank lines
15
+ */
16
+ /**
17
+ * Parses .env content and sets unset env vars.
18
+ */
19
+ function parseDotEnvContent(content) {
20
+ for (const line of content.split('\n')) {
21
+ const trimmed = line.trim();
22
+ if (!trimmed || trimmed.startsWith('#'))
23
+ continue;
24
+ const eqIdx = trimmed.indexOf('=');
25
+ if (eqIdx < 0)
26
+ continue;
27
+ const key = trimmed.slice(0, eqIdx).trim();
28
+ if (!key)
29
+ continue;
30
+ // Strip optional surrounding quotes from the value
31
+ const raw = trimmed.slice(eqIdx + 1).trim();
32
+ const value = raw.replace(/^(['"])(.*)\1$/, '$2');
33
+ // Don't override values that are already set in the environment
34
+ if (!(key in process.env)) {
35
+ process.env[key] = value;
36
+ }
37
+ }
38
+ }
39
+ function loadDotEnv() {
40
+ // Try multiple candidate paths for .env:
41
+ // 1. Monorepo root (packages/agent/src/ -> repo root)
42
+ // 2. Current working directory
43
+ const candidates = [
44
+ resolve(import.meta.dirname, '..', '..', '..', '.env'),
45
+ resolve(process.cwd(), '.env'),
46
+ ];
47
+ for (const envPath of candidates) {
48
+ try {
49
+ const content = readFileSync(envPath, 'utf-8');
50
+ parseDotEnvContent(content);
51
+ return; // Use the first .env we find
52
+ }
53
+ catch {
54
+ // Not found at this path, try next
55
+ }
56
+ }
57
+ // No .env found — that's fine, env vars may be set externally or via config.json
58
+ }
59
+ /**
60
+ * Reads ~/.clsh/config.json if it exists.
61
+ * Returns parsed config or empty object.
62
+ */
63
+ function loadConfigFile() {
64
+ try {
65
+ const configPath = join(homedir(), '.clsh', 'config.json');
66
+ const content = readFileSync(configPath, 'utf-8');
67
+ return JSON.parse(content);
68
+ }
69
+ catch {
70
+ return {};
71
+ }
72
+ }
73
+ function getEnv(key) {
74
+ return process.env[key];
75
+ }
76
+ /**
77
+ * Returns a persistent JWT secret stored at ~/.clsh/jwt_secret.
78
+ * Generated once on first run; reused on every subsequent restart so
79
+ * the phone's stored JWT stays valid across `npm run dev` restarts.
80
+ * Override with JWT_SECRET env var if needed.
81
+ */
82
+ function getOrCreateJwtSecret(clshDir) {
83
+ const secretPath = join(clshDir, 'jwt_secret');
84
+ if (existsSync(secretPath)) {
85
+ try {
86
+ const stored = readFileSync(secretPath, 'utf-8').trim();
87
+ if (stored.length > 10)
88
+ return stored;
89
+ }
90
+ catch { /* fall through */ }
91
+ }
92
+ const secret = randomBytes(32).toString('base64url');
93
+ try {
94
+ mkdirSync(clshDir, { recursive: true });
95
+ writeFileSync(secretPath, secret, { mode: 0o600 }); // owner-readable only
96
+ }
97
+ catch { /* ignore write errors */ }
98
+ return secret;
99
+ }
100
+ export function loadConfig() {
101
+ // Priority: env vars > .env > ~/.clsh/config.json > defaults
102
+ loadDotEnv();
103
+ const fileConfig = loadConfigFile();
104
+ const clshDir = join(homedir(), '.clsh');
105
+ const defaultDbPath = join(clshDir, 'clsh.db');
106
+ const port = parseInt(getEnv('PORT') ?? (fileConfig.port != null ? String(fileConfig.port) : '4030'), 10);
107
+ return {
108
+ port,
109
+ webPort: parseInt(getEnv('WEB_PORT') ?? String(port), 10),
110
+ jwtSecret: getEnv('JWT_SECRET') ?? getOrCreateJwtSecret(clshDir),
111
+ tunnelMethod: getEnv('TUNNEL'),
112
+ ngrokAuthtoken: getEnv('NGROK_AUTHTOKEN') ?? fileConfig.ngrokAuthtoken,
113
+ ngrokStaticDomain: getEnv('NGROK_STATIC_DOMAIN') ?? fileConfig.ngrokStaticDomain,
114
+ resendApiKey: getEnv('RESEND_API_KEY'),
115
+ dbPath: getEnv('DB_PATH') ?? defaultDbPath,
116
+ tmuxDisabled: getEnv('CLSH_NO_TMUX') === '1',
117
+ };
118
+ }
119
+ //# 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,68 @@
1
+ /**
2
+ * Parses tmux control mode (-CC) protocol output.
3
+ *
4
+ * In control mode, tmux sends structured text notifications instead of screen
5
+ * redraws. The key notification is %output, which contains the RAW pane output
6
+ * (octal-encoded), giving us both tmux persistence and proper xterm.js scrollback.
7
+ *
8
+ * Protocol format:
9
+ * %output %<paneId> <octal-encoded data>
10
+ * %begin <timestamp> <cmdNumber> <flags>
11
+ * %end <timestamp> <cmdNumber> <flags>
12
+ * %error <timestamp> <cmdNumber> <flags>
13
+ * %exit
14
+ * %session-changed $<id> <name>
15
+ */
16
+ /** Parsed control mode events. */
17
+ export type ControlModeEvent = {
18
+ type: 'output';
19
+ paneId: string;
20
+ data: string;
21
+ } | {
22
+ type: 'begin';
23
+ timestamp: number;
24
+ cmdNumber: number;
25
+ } | {
26
+ type: 'end';
27
+ timestamp: number;
28
+ cmdNumber: number;
29
+ } | {
30
+ type: 'error';
31
+ timestamp: number;
32
+ cmdNumber: number;
33
+ } | {
34
+ type: 'exit';
35
+ };
36
+ /**
37
+ * Decodes tmux octal-encoded string.
38
+ * tmux encodes characters < ASCII 32 and backslash as \NNN (3 octal digits).
39
+ * E.g. \033 → ESC (0x1B), \015 → CR, \012 → LF, \134 → backslash.
40
+ */
41
+ export declare function decodeTmuxOctal(encoded: string): string;
42
+ /**
43
+ * Encodes raw input bytes as hex for tmux `send-keys -H`.
44
+ * Each character becomes a 2-digit hex value separated by spaces.
45
+ */
46
+ export declare function encodeInputAsHex(data: string): string;
47
+ /**
48
+ * Parses a single line of tmux control mode output.
49
+ * Returns null for non-event lines (DCS sequences, empty lines, etc).
50
+ */
51
+ export declare function parseControlLine(line: string): ControlModeEvent | null;
52
+ /**
53
+ * Builds tmux send-keys -H commands for forwarding user input.
54
+ * Chunks large inputs to keep command length reasonable.
55
+ */
56
+ export declare function buildSendKeysCommands(tmuxName: string, data: string): string[];
57
+ /**
58
+ * Line-buffered parser for tmux control mode PTY output.
59
+ * Accumulates raw pty.onData() chunks into complete lines, then parses each.
60
+ */
61
+ export declare class ControlModeLineBuffer {
62
+ private buffer;
63
+ private callback;
64
+ constructor(callback: (event: ControlModeEvent) => void);
65
+ /** Feed raw data from pty.onData(). Parses complete lines and emits events. */
66
+ feed(data: string): void;
67
+ }
68
+ //# sourceMappingURL=control-mode-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-mode-parser.d.ts","sourceRoot":"","sources":["../src/control-mode-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,kCAAkC;AAClC,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAkCtE;AAKD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAU9E;AAED;;;GAGG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAoC;gBAExC,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI;IAIvD,+EAA+E;IAC/E,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CAgBzB"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Parses tmux control mode (-CC) protocol output.
3
+ *
4
+ * In control mode, tmux sends structured text notifications instead of screen
5
+ * redraws. The key notification is %output, which contains the RAW pane output
6
+ * (octal-encoded), giving us both tmux persistence and proper xterm.js scrollback.
7
+ *
8
+ * Protocol format:
9
+ * %output %<paneId> <octal-encoded data>
10
+ * %begin <timestamp> <cmdNumber> <flags>
11
+ * %end <timestamp> <cmdNumber> <flags>
12
+ * %error <timestamp> <cmdNumber> <flags>
13
+ * %exit
14
+ * %session-changed $<id> <name>
15
+ */
16
+ /**
17
+ * Decodes tmux octal-encoded string.
18
+ * tmux encodes characters < ASCII 32 and backslash as \NNN (3 octal digits).
19
+ * E.g. \033 → ESC (0x1B), \015 → CR, \012 → LF, \134 → backslash.
20
+ */
21
+ export function decodeTmuxOctal(encoded) {
22
+ return encoded.replace(/\\(\d{3})/g, (_match, oct) => String.fromCharCode(parseInt(oct, 8)));
23
+ }
24
+ /**
25
+ * Encodes raw input bytes as hex for tmux `send-keys -H`.
26
+ * Each character becomes a 2-digit hex value separated by spaces.
27
+ */
28
+ export function encodeInputAsHex(data) {
29
+ const parts = [];
30
+ for (let i = 0; i < data.length; i++) {
31
+ parts.push(data.charCodeAt(i).toString(16).padStart(2, '0'));
32
+ }
33
+ return parts.join(' ');
34
+ }
35
+ /**
36
+ * Parses a single line of tmux control mode output.
37
+ * Returns null for non-event lines (DCS sequences, empty lines, etc).
38
+ */
39
+ export function parseControlLine(line) {
40
+ if (!line.startsWith('%'))
41
+ return null;
42
+ if (line.startsWith('%output ')) {
43
+ // Format: %output %<paneId> <octal-encoded data>
44
+ const rest = line.substring(8);
45
+ const spaceIdx = rest.indexOf(' ');
46
+ if (spaceIdx === -1)
47
+ return null;
48
+ const paneId = rest.substring(0, spaceIdx);
49
+ const data = decodeTmuxOctal(rest.substring(spaceIdx + 1));
50
+ return { type: 'output', paneId, data };
51
+ }
52
+ if (line.startsWith('%begin ')) {
53
+ const parts = line.split(' ');
54
+ return { type: 'begin', timestamp: parseInt(parts[1], 10), cmdNumber: parseInt(parts[2], 10) };
55
+ }
56
+ if (line.startsWith('%end ')) {
57
+ const parts = line.split(' ');
58
+ return { type: 'end', timestamp: parseInt(parts[1], 10), cmdNumber: parseInt(parts[2], 10) };
59
+ }
60
+ if (line.startsWith('%error ')) {
61
+ const parts = line.split(' ');
62
+ return { type: 'error', timestamp: parseInt(parts[1], 10), cmdNumber: parseInt(parts[2], 10) };
63
+ }
64
+ if (line === '%exit') {
65
+ return { type: 'exit' };
66
+ }
67
+ // Other notifications (%session-changed, %window-add, etc) — ignore
68
+ return null;
69
+ }
70
+ /** Max input bytes per send-keys -H command to avoid overly long commands. */
71
+ const MAX_HEX_CHUNK = 512;
72
+ /**
73
+ * Builds tmux send-keys -H commands for forwarding user input.
74
+ * Chunks large inputs to keep command length reasonable.
75
+ */
76
+ export function buildSendKeysCommands(tmuxName, data) {
77
+ const commands = [];
78
+ for (let offset = 0; offset < data.length; offset += MAX_HEX_CHUNK) {
79
+ const chunk = data.substring(offset, offset + MAX_HEX_CHUNK);
80
+ const hex = encodeInputAsHex(chunk);
81
+ commands.push(`send-keys -t ${tmuxName} -H ${hex}`);
82
+ }
83
+ return commands;
84
+ }
85
+ /**
86
+ * Line-buffered parser for tmux control mode PTY output.
87
+ * Accumulates raw pty.onData() chunks into complete lines, then parses each.
88
+ */
89
+ export class ControlModeLineBuffer {
90
+ buffer = '';
91
+ callback;
92
+ constructor(callback) {
93
+ this.callback = callback;
94
+ }
95
+ /** Feed raw data from pty.onData(). Parses complete lines and emits events. */
96
+ feed(data) {
97
+ this.buffer += data;
98
+ let newlineIdx;
99
+ while ((newlineIdx = this.buffer.indexOf('\n')) !== -1) {
100
+ const line = this.buffer.substring(0, newlineIdx).replace(/\r$/, '');
101
+ this.buffer = this.buffer.substring(newlineIdx + 1);
102
+ if (line === '')
103
+ continue;
104
+ const event = parseControlLine(line);
105
+ if (event) {
106
+ this.callback(event);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ //# sourceMappingURL=control-mode-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-mode-parser.js","sourceRoot":"","sources":["../src/control-mode-parser.ts"],"names":[],"mappings":"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"}
package/dist/db.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ import Database from 'better-sqlite3';
2
+ export interface PtySessionRow {
3
+ id: string;
4
+ tmux_name: string;
5
+ shell: string;
6
+ name: string;
7
+ cwd: string;
8
+ created_at: string;
9
+ }
10
+ export interface DbStatements {
11
+ insertBootstrapToken: Database.Statement<[string, string]>;
12
+ getBootstrapToken: Database.Statement<[string], {
13
+ id: string;
14
+ hash: string;
15
+ created_at: string;
16
+ }>;
17
+ deleteBootstrapToken: Database.Statement<[string]>;
18
+ insertSession: Database.Statement<[string, string, string]>;
19
+ updateSessionLastSeen: Database.Statement<[string]>;
20
+ getSession: Database.Statement<[string], {
21
+ id: string;
22
+ jwt_id: string;
23
+ email: string;
24
+ created_at: string;
25
+ last_seen: string;
26
+ }>;
27
+ insertPtySession: Database.Statement<[string, string, string, string, string]>;
28
+ getPtySession: Database.Statement<[string], PtySessionRow>;
29
+ listPtySessions: Database.Statement<[], PtySessionRow>;
30
+ updatePtySession: Database.Statement<[string, string, string]>;
31
+ deletePtySession: Database.Statement<[string]>;
32
+ deleteAllPtySessions: Database.Statement<[]>;
33
+ }
34
+ export interface DbContext {
35
+ db: Database.Database;
36
+ statements: DbStatements;
37
+ }
38
+ /**
39
+ * Initializes the SQLite database at the given path.
40
+ * Creates the directory if needed, enables WAL mode, and creates all tables.
41
+ * Returns the database instance and prepared statements.
42
+ */
43
+ export declare function initDatabase(dbPath: string): DbContext;
44
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAItC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,oBAAoB,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3D,iBAAiB,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClG,oBAAoB,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACnD,aAAa,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5D,qBAAqB,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACpD,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/H,gBAAgB,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/E,aAAa,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,CAAC;IAC3D,eAAe,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;IACvD,gBAAgB,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/D,gBAAgB,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/C,oBAAoB,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACtB,UAAU,EAAE,YAAY,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAkFtD"}
package/dist/db.js ADDED
@@ -0,0 +1,63 @@
1
+ import Database from 'better-sqlite3';
2
+ import { mkdirSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ /**
5
+ * Initializes the SQLite database at the given path.
6
+ * Creates the directory if needed, enables WAL mode, and creates all tables.
7
+ * Returns the database instance and prepared statements.
8
+ */
9
+ export function initDatabase(dbPath) {
10
+ // Ensure the directory exists
11
+ mkdirSync(dirname(dbPath), { recursive: true });
12
+ const db = new Database(dbPath);
13
+ // Enable WAL mode for better concurrent read performance
14
+ db.pragma('journal_mode = WAL');
15
+ db.pragma('foreign_keys = ON');
16
+ // Create schema
17
+ db.exec(`
18
+ CREATE TABLE IF NOT EXISTS bootstrap_tokens (
19
+ id TEXT PRIMARY KEY,
20
+ hash TEXT NOT NULL UNIQUE,
21
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
22
+ );
23
+
24
+ CREATE TABLE IF NOT EXISTS sessions (
25
+ id TEXT PRIMARY KEY,
26
+ jwt_id TEXT NOT NULL,
27
+ email TEXT NOT NULL DEFAULT '',
28
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
29
+ last_seen TEXT NOT NULL DEFAULT (datetime('now'))
30
+ );
31
+
32
+ CREATE TABLE IF NOT EXISTS allowed_emails (
33
+ email TEXT PRIMARY KEY,
34
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
35
+ );
36
+
37
+ CREATE TABLE IF NOT EXISTS pty_sessions (
38
+ id TEXT PRIMARY KEY,
39
+ tmux_name TEXT NOT NULL UNIQUE,
40
+ shell TEXT NOT NULL DEFAULT 'zsh',
41
+ name TEXT NOT NULL DEFAULT '',
42
+ cwd TEXT NOT NULL DEFAULT '',
43
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
44
+ );
45
+ `);
46
+ // Prepare statements for repeated use
47
+ const statements = {
48
+ insertBootstrapToken: db.prepare('INSERT INTO bootstrap_tokens (id, hash) VALUES (?, ?)'),
49
+ getBootstrapToken: db.prepare('SELECT id, hash, created_at FROM bootstrap_tokens WHERE hash = ?'),
50
+ deleteBootstrapToken: db.prepare('DELETE FROM bootstrap_tokens WHERE hash = ?'),
51
+ insertSession: db.prepare('INSERT INTO sessions (id, jwt_id, email) VALUES (?, ?, ?)'),
52
+ updateSessionLastSeen: db.prepare("UPDATE sessions SET last_seen = datetime('now') WHERE id = ?"),
53
+ getSession: db.prepare('SELECT id, jwt_id, email, created_at, last_seen FROM sessions WHERE id = ?'),
54
+ insertPtySession: db.prepare('INSERT INTO pty_sessions (id, tmux_name, shell, name, cwd) VALUES (?, ?, ?, ?, ?)'),
55
+ getPtySession: db.prepare('SELECT id, tmux_name, shell, name, cwd, created_at FROM pty_sessions WHERE id = ?'),
56
+ listPtySessions: db.prepare('SELECT id, tmux_name, shell, name, cwd, created_at FROM pty_sessions'),
57
+ updatePtySession: db.prepare('UPDATE pty_sessions SET name = ?, cwd = ? WHERE id = ?'),
58
+ deletePtySession: db.prepare('DELETE FROM pty_sessions WHERE id = ?'),
59
+ deleteAllPtySessions: db.prepare('DELETE FROM pty_sessions'),
60
+ };
61
+ return { db, statements };
62
+ }
63
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+BpC;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,8BAA8B;IAC9B,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEhD,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEhC,yDAAyD;IACzD,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAE/B,gBAAgB;IAChB,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BP,CAAC,CAAC;IAEH,sCAAsC;IACtC,MAAM,UAAU,GAAiB;QAC/B,oBAAoB,EAAE,EAAE,CAAC,OAAO,CAC9B,uDAAuD,CACxD;QACD,iBAAiB,EAAE,EAAE,CAAC,OAAO,CAC3B,kEAAkE,CACnE;QACD,oBAAoB,EAAE,EAAE,CAAC,OAAO,CAC9B,6CAA6C,CAC9C;QACD,aAAa,EAAE,EAAE,CAAC,OAAO,CACvB,2DAA2D,CAC5D;QACD,qBAAqB,EAAE,EAAE,CAAC,OAAO,CAC/B,8DAA8D,CAC/D;QACD,UAAU,EAAE,EAAE,CAAC,OAAO,CACpB,4EAA4E,CAC7E;QACD,gBAAgB,EAAE,EAAE,CAAC,OAAO,CAC1B,mFAAmF,CACpF;QACD,aAAa,EAAE,EAAE,CAAC,OAAO,CACvB,mFAAmF,CACpF;QACD,eAAe,EAAE,EAAE,CAAC,OAAO,CACzB,sEAAsE,CACvE;QACD,gBAAgB,EAAE,EAAE,CAAC,OAAO,CAC1B,wDAAwD,CACzD;QACD,gBAAgB,EAAE,EAAE,CAAC,OAAO,CAC1B,uCAAuC,CACxC;QACD,oBAAoB,EAAE,EAAE,CAAC,OAAO,CAC9B,0BAA0B,CAC3B;KACF,CAAC;IAEF,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function main(): Promise<void>;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoCA,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAkF1C"}