@seflless/ghosttown 1.6.2 → 1.10.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.
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Session Management Types
3
+ *
4
+ * Core types for the custom PTY session management system that replaces tmux.
5
+ * Sessions persist across reconnections and server restarts.
6
+ */
7
+ /**
8
+ * Unique identifier for a session (UUID v4).
9
+ * This ID is stable and survives renames, reconnections, and server restarts.
10
+ */
11
+ export type SessionId = string;
12
+ /**
13
+ * Share permission level for a session connection.
14
+ */
15
+ export type SharePermission = 'read-only' | 'read-write';
16
+ /**
17
+ * Configuration for sharing a session with others.
18
+ */
19
+ export interface SharingConfig {
20
+ /** Whether sharing is currently enabled */
21
+ enabled: boolean;
22
+ /** Secure token for share access (32-byte random, base64url) */
23
+ token: string;
24
+ /** Permission level for shared access */
25
+ permissions: SharePermission;
26
+ /** Unix timestamp when share expires, null for never */
27
+ expiresAt: number | null;
28
+ /** Maximum number of concurrent viewers, null for unlimited */
29
+ maxViewers: number | null;
30
+ }
31
+ /**
32
+ * Process state for a session.
33
+ * The pid is null after server restart (process was killed).
34
+ */
35
+ export interface ProcessState {
36
+ /** Process ID, null if process is not running */
37
+ pid: number | null;
38
+ /** Shell executable path (e.g., '/bin/zsh') */
39
+ shell: string;
40
+ /** Shell arguments */
41
+ args: string[];
42
+ /** Current working directory */
43
+ cwd: string;
44
+ /** Environment variables */
45
+ env: Record<string, string>;
46
+ }
47
+ /**
48
+ * Terminal state for a session.
49
+ */
50
+ export interface TerminalState {
51
+ /** Number of columns */
52
+ cols: number;
53
+ /** Number of rows */
54
+ rows: number;
55
+ /** Cursor X position */
56
+ cursorX: number;
57
+ /** Cursor Y position */
58
+ cursorY: number;
59
+ /** Cursor style */
60
+ cursorStyle: 'block' | 'underline' | 'bar';
61
+ /** Whether cursor is visible */
62
+ cursorVisible: boolean;
63
+ }
64
+ /**
65
+ * Core session state that survives server restarts.
66
+ * Persisted to disk as JSON.
67
+ */
68
+ export interface Session {
69
+ /** Unique identifier (UUID v4) */
70
+ id: SessionId;
71
+ /** User-facing display name */
72
+ displayName: string;
73
+ /** Unix timestamp when session was created */
74
+ createdAt: number;
75
+ /** Unix timestamp of last activity */
76
+ lastActivity: number;
77
+ /** Process state */
78
+ process: ProcessState;
79
+ /** Terminal state */
80
+ terminal: TerminalState;
81
+ /** Path to scrollback file (relative to session directory) */
82
+ scrollbackFile: string;
83
+ /** Total number of lines in scrollback history */
84
+ scrollbackLength: number;
85
+ /** Sharing configuration, null if not shared */
86
+ sharing: SharingConfig | null;
87
+ }
88
+ /**
89
+ * Connection to a session (in-memory, not persisted).
90
+ */
91
+ export interface Connection {
92
+ /** Unique connection ID */
93
+ id: string;
94
+ /** Session being connected to */
95
+ sessionId: SessionId;
96
+ /** User identifier */
97
+ userId: string;
98
+ /** Connection type */
99
+ type: 'owner' | 'viewer';
100
+ /** Permission level */
101
+ permissions: SharePermission;
102
+ /** Unix timestamp when connected */
103
+ connectedAt: number;
104
+ }
105
+ /**
106
+ * Options for creating a new session.
107
+ */
108
+ export interface CreateSessionOptions {
109
+ /** Display name (auto-generated if not provided) */
110
+ name?: string;
111
+ /** Shell executable (defaults to user's shell) */
112
+ shell?: string;
113
+ /** Shell arguments */
114
+ args?: string[];
115
+ /** Working directory (defaults to home directory) */
116
+ cwd?: string;
117
+ /** Environment variables to add/override */
118
+ env?: Record<string, string>;
119
+ /** Terminal columns (default: 80) */
120
+ cols?: number;
121
+ /** Terminal rows (default: 24) */
122
+ rows?: number;
123
+ /**
124
+ * Whether to start the PTY process immediately (default: true).
125
+ * Set to false for web sessions where output should wait for client connection.
126
+ */
127
+ startProcess?: boolean;
128
+ }
129
+ /**
130
+ * Options for connecting to a session.
131
+ */
132
+ export interface ConnectOptions {
133
+ /** Session ID to connect to */
134
+ sessionId: SessionId;
135
+ /** Share token (required for viewer access) */
136
+ shareToken?: string;
137
+ /** User identifier */
138
+ userId: string;
139
+ }
140
+ /**
141
+ * Session info returned from list operations.
142
+ */
143
+ export interface SessionInfo {
144
+ /** Session ID */
145
+ id: SessionId;
146
+ /** Display name */
147
+ displayName: string;
148
+ /** Unix timestamp of last activity */
149
+ lastActivity: number;
150
+ /** Whether a process is currently running */
151
+ isRunning: boolean;
152
+ /** Number of active connections */
153
+ connectionCount: number;
154
+ /** Whether session is being shared */
155
+ isShared: boolean;
156
+ }
157
+ /**
158
+ * Result of creating a share link.
159
+ */
160
+ export interface ShareLink {
161
+ /** Local URL for LAN access */
162
+ localUrl: string;
163
+ /** Public URL for internet access (if tunneling enabled) */
164
+ publicUrl?: string;
165
+ /** Share token */
166
+ token: string;
167
+ /** Unix timestamp when share expires */
168
+ expiresAt: number | null;
169
+ }
170
+ /**
171
+ * Options for creating a share link.
172
+ */
173
+ export interface CreateShareOptions {
174
+ /** Permission level (default: 'read-only') */
175
+ permissions?: SharePermission;
176
+ /** Expiration time in milliseconds from now */
177
+ expiresIn?: number;
178
+ /** Maximum concurrent viewers */
179
+ maxViewers?: number;
180
+ }
181
+ /**
182
+ * Events emitted by SessionManager.
183
+ */
184
+ export interface SessionManagerEvents {
185
+ /** Emitted when a new session is created */
186
+ 'session:created': (session: Session) => void;
187
+ /** Emitted when a session is deleted */
188
+ 'session:deleted': (sessionId: SessionId) => void;
189
+ /** Emitted when a session's process exits */
190
+ 'session:exit': (sessionId: SessionId, exitCode: number) => void;
191
+ /** Emitted when a connection is established */
192
+ 'connection:open': (connection: Connection) => void;
193
+ /** Emitted when a connection is closed */
194
+ 'connection:close': (connectionId: string) => void;
195
+ /** Emitted when session output is received */
196
+ 'session:output': (sessionId: SessionId, data: string) => void;
197
+ }
198
+ /**
199
+ * Storage paths for session data.
200
+ */
201
+ export interface SessionPaths {
202
+ /** Base directory for all session data */
203
+ baseDir: string;
204
+ /** Directory for a specific session */
205
+ sessionDir: (sessionId: SessionId) => string;
206
+ /** Path to session metadata file */
207
+ metadataFile: (sessionId: SessionId) => string;
208
+ /** Path to scrollback file */
209
+ scrollbackFile: (sessionId: SessionId) => string;
210
+ }
211
+ /**
212
+ * Configuration for SessionManager.
213
+ */
214
+ export interface SessionManagerConfig {
215
+ /** Base directory for session storage (default: ~/.config/ghosttown/sessions) */
216
+ storageDir?: string;
217
+ /** Maximum scrollback lines per session (default: 10000) */
218
+ scrollbackLimit?: number;
219
+ /** Default shell (default: $SHELL or /bin/sh) */
220
+ defaultShell?: string;
221
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Session Management Types
3
+ *
4
+ * Core types for the custom PTY session management system that replaces tmux.
5
+ * Sessions persist across reconnections and server restarts.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Session Management Types
3
+ *
4
+ * Core types for the custom PTY session management system that replaces tmux.
5
+ * Sessions persist across reconnections and server restarts.
6
+ */
7
+
8
+ /**
9
+ * Unique identifier for a session (UUID v4).
10
+ * This ID is stable and survives renames, reconnections, and server restarts.
11
+ */
12
+ export type SessionId = string;
13
+
14
+ /**
15
+ * Share permission level for a session connection.
16
+ */
17
+ export type SharePermission = 'read-only' | 'read-write';
18
+
19
+ /**
20
+ * Configuration for sharing a session with others.
21
+ */
22
+ export interface SharingConfig {
23
+ /** Whether sharing is currently enabled */
24
+ enabled: boolean;
25
+ /** Secure token for share access (32-byte random, base64url) */
26
+ token: string;
27
+ /** Permission level for shared access */
28
+ permissions: SharePermission;
29
+ /** Unix timestamp when share expires, null for never */
30
+ expiresAt: number | null;
31
+ /** Maximum number of concurrent viewers, null for unlimited */
32
+ maxViewers: number | null;
33
+ }
34
+
35
+ /**
36
+ * Process state for a session.
37
+ * The pid is null after server restart (process was killed).
38
+ */
39
+ export interface ProcessState {
40
+ /** Process ID, null if process is not running */
41
+ pid: number | null;
42
+ /** Shell executable path (e.g., '/bin/zsh') */
43
+ shell: string;
44
+ /** Shell arguments */
45
+ args: string[];
46
+ /** Current working directory */
47
+ cwd: string;
48
+ /** Environment variables */
49
+ env: Record<string, string>;
50
+ }
51
+
52
+ /**
53
+ * Terminal state for a session.
54
+ */
55
+ export interface TerminalState {
56
+ /** Number of columns */
57
+ cols: number;
58
+ /** Number of rows */
59
+ rows: number;
60
+ /** Cursor X position */
61
+ cursorX: number;
62
+ /** Cursor Y position */
63
+ cursorY: number;
64
+ /** Cursor style */
65
+ cursorStyle: 'block' | 'underline' | 'bar';
66
+ /** Whether cursor is visible */
67
+ cursorVisible: boolean;
68
+ }
69
+
70
+ /**
71
+ * Core session state that survives server restarts.
72
+ * Persisted to disk as JSON.
73
+ */
74
+ export interface Session {
75
+ /** Unique identifier (UUID v4) */
76
+ id: SessionId;
77
+ /** User-facing display name */
78
+ displayName: string;
79
+ /** Unix timestamp when session was created */
80
+ createdAt: number;
81
+ /** Unix timestamp of last activity */
82
+ lastActivity: number;
83
+ /** Process state */
84
+ process: ProcessState;
85
+ /** Terminal state */
86
+ terminal: TerminalState;
87
+ /** Path to scrollback file (relative to session directory) */
88
+ scrollbackFile: string;
89
+ /** Total number of lines in scrollback history */
90
+ scrollbackLength: number;
91
+ /** Sharing configuration, null if not shared */
92
+ sharing: SharingConfig | null;
93
+ }
94
+
95
+ /**
96
+ * Connection to a session (in-memory, not persisted).
97
+ */
98
+ export interface Connection {
99
+ /** Unique connection ID */
100
+ id: string;
101
+ /** Session being connected to */
102
+ sessionId: SessionId;
103
+ /** User identifier */
104
+ userId: string;
105
+ /** Connection type */
106
+ type: 'owner' | 'viewer';
107
+ /** Permission level */
108
+ permissions: SharePermission;
109
+ /** Unix timestamp when connected */
110
+ connectedAt: number;
111
+ }
112
+
113
+ /**
114
+ * Options for creating a new session.
115
+ */
116
+ export interface CreateSessionOptions {
117
+ /** Display name (auto-generated if not provided) */
118
+ name?: string;
119
+ /** Shell executable (defaults to user's shell) */
120
+ shell?: string;
121
+ /** Shell arguments */
122
+ args?: string[];
123
+ /** Working directory (defaults to home directory) */
124
+ cwd?: string;
125
+ /** Environment variables to add/override */
126
+ env?: Record<string, string>;
127
+ /** Terminal columns (default: 80) */
128
+ cols?: number;
129
+ /** Terminal rows (default: 24) */
130
+ rows?: number;
131
+ /**
132
+ * Whether to start the PTY process immediately (default: true).
133
+ * Set to false for web sessions where output should wait for client connection.
134
+ */
135
+ startProcess?: boolean;
136
+ }
137
+
138
+ /**
139
+ * Options for connecting to a session.
140
+ */
141
+ export interface ConnectOptions {
142
+ /** Session ID to connect to */
143
+ sessionId: SessionId;
144
+ /** Share token (required for viewer access) */
145
+ shareToken?: string;
146
+ /** User identifier */
147
+ userId: string;
148
+ }
149
+
150
+ /**
151
+ * Session info returned from list operations.
152
+ */
153
+ export interface SessionInfo {
154
+ /** Session ID */
155
+ id: SessionId;
156
+ /** Display name */
157
+ displayName: string;
158
+ /** Unix timestamp of last activity */
159
+ lastActivity: number;
160
+ /** Whether a process is currently running */
161
+ isRunning: boolean;
162
+ /** Number of active connections */
163
+ connectionCount: number;
164
+ /** Whether session is being shared */
165
+ isShared: boolean;
166
+ }
167
+
168
+ /**
169
+ * Result of creating a share link.
170
+ */
171
+ export interface ShareLink {
172
+ /** Local URL for LAN access */
173
+ localUrl: string;
174
+ /** Public URL for internet access (if tunneling enabled) */
175
+ publicUrl?: string;
176
+ /** Share token */
177
+ token: string;
178
+ /** Unix timestamp when share expires */
179
+ expiresAt: number | null;
180
+ }
181
+
182
+ /**
183
+ * Options for creating a share link.
184
+ */
185
+ export interface CreateShareOptions {
186
+ /** Permission level (default: 'read-only') */
187
+ permissions?: SharePermission;
188
+ /** Expiration time in milliseconds from now */
189
+ expiresIn?: number;
190
+ /** Maximum concurrent viewers */
191
+ maxViewers?: number;
192
+ }
193
+
194
+ /**
195
+ * Events emitted by SessionManager.
196
+ */
197
+ export interface SessionManagerEvents {
198
+ /** Emitted when a new session is created */
199
+ 'session:created': (session: Session) => void;
200
+ /** Emitted when a session is deleted */
201
+ 'session:deleted': (sessionId: SessionId) => void;
202
+ /** Emitted when a session's process exits */
203
+ 'session:exit': (sessionId: SessionId, exitCode: number) => void;
204
+ /** Emitted when a connection is established */
205
+ 'connection:open': (connection: Connection) => void;
206
+ /** Emitted when a connection is closed */
207
+ 'connection:close': (connectionId: string) => void;
208
+ /** Emitted when session output is received */
209
+ 'session:output': (sessionId: SessionId, data: string) => void;
210
+ }
211
+
212
+ /**
213
+ * Storage paths for session data.
214
+ */
215
+ export interface SessionPaths {
216
+ /** Base directory for all session data */
217
+ baseDir: string;
218
+ /** Directory for a specific session */
219
+ sessionDir: (sessionId: SessionId) => string;
220
+ /** Path to session metadata file */
221
+ metadataFile: (sessionId: SessionId) => string;
222
+ /** Path to scrollback file */
223
+ scrollbackFile: (sessionId: SessionId) => string;
224
+ }
225
+
226
+ /**
227
+ * Configuration for SessionManager.
228
+ */
229
+ export interface SessionManagerConfig {
230
+ /** Base directory for session storage (default: ~/.config/ghosttown/sessions) */
231
+ storageDir?: string;
232
+ /** Maximum scrollback lines per session (default: 10000) */
233
+ scrollbackLimit?: number;
234
+ /** Default shell (default: $SHELL or /bin/sh) */
235
+ defaultShell?: string;
236
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Session Utilities
3
+ *
4
+ * Helper functions for working with SessionManager sessions.
5
+ * Simplified for UUID-based session IDs (no longer depends on tmux).
6
+ */
7
+
8
+ /**
9
+ * @typedef {Object} SessionInfo
10
+ * @property {string} id - UUID
11
+ * @property {string} displayName - User-facing name
12
+ */
13
+
14
+ /**
15
+ * Validate a session display name.
16
+ *
17
+ * @param {string} name - The name to validate
18
+ * @returns {{ valid: boolean, error?: string }} Validation result
19
+ */
20
+ export function validateSessionName(name) {
21
+ if (!name || name.length === 0) {
22
+ return { valid: false, error: 'Session name cannot be empty' };
23
+ }
24
+ if (name.length > 50) {
25
+ return { valid: false, error: 'Session name too long (max 50 chars)' };
26
+ }
27
+ // Reserved name for system use
28
+ if (name === 'ghosttown-host') {
29
+ return { valid: false, error: "'ghosttown-host' is a reserved name" };
30
+ }
31
+ // Allow letters, numbers, hyphens, underscores for URL safety
32
+ // Note: Purely numeric names are now OK since we use UUIDs for session identity
33
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
34
+ return {
35
+ valid: false,
36
+ error: 'Session name can only contain letters, numbers, hyphens, and underscores',
37
+ };
38
+ }
39
+ return { valid: true };
40
+ }
41
+
42
+ /**
43
+ * Find a session by display name from a list.
44
+ *
45
+ * @param {SessionInfo[]} sessions - List from SessionManager.listSessions()
46
+ * @param {string} displayName - The display name to search for
47
+ * @returns {SessionInfo|null} The matching session, or null if not found
48
+ */
49
+ export function findByDisplayName(sessions, displayName) {
50
+ return sessions.find((s) => s.displayName === displayName) || null;
51
+ }
52
+
53
+ /**
54
+ * Find a session by ID or display name.
55
+ * Useful for CLI commands that accept either.
56
+ *
57
+ * @param {SessionInfo[]} sessions - List from SessionManager.listSessions()
58
+ * @param {string} identifier - UUID, short UUID prefix, or display name
59
+ * @returns {SessionInfo|null} The matching session, or null if not found
60
+ */
61
+ export function findSession(sessions, identifier) {
62
+ if (!identifier) {
63
+ return null;
64
+ }
65
+
66
+ // Try exact ID match first
67
+ const byId = sessions.find((s) => s.id === identifier);
68
+ if (byId) {
69
+ return byId;
70
+ }
71
+
72
+ // Try short ID prefix match (for convenience)
73
+ const byPrefix = sessions.find((s) => s.id.startsWith(identifier));
74
+ if (byPrefix) {
75
+ return byPrefix;
76
+ }
77
+
78
+ // Try by display name
79
+ return findByDisplayName(sessions, identifier);
80
+ }
81
+
82
+ /**
83
+ * Check if a display name already exists in the session list.
84
+ *
85
+ * @param {SessionInfo[]} sessions - List from SessionManager.listSessions()
86
+ * @param {string} displayName - The display name to check
87
+ * @returns {boolean} True if the name already exists
88
+ */
89
+ export function displayNameExists(sessions, displayName) {
90
+ return findByDisplayName(sessions, displayName) !== null;
91
+ }