treesap 0.1.13 → 0.2.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 (147) hide show
  1. package/README.md +31 -192
  2. package/dist/app.d.ts +28 -0
  3. package/dist/app.d.ts.map +1 -0
  4. package/dist/app.js +184 -0
  5. package/dist/app.js.map +1 -0
  6. package/dist/context.d.ts +36 -0
  7. package/dist/context.d.ts.map +1 -0
  8. package/dist/context.js +95 -0
  9. package/dist/context.js.map +1 -0
  10. package/dist/index.d.ts +5 -7
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +5 -9
  13. package/dist/index.js.map +1 -1
  14. package/dist/middleware/cors.d.ts +11 -0
  15. package/dist/middleware/cors.d.ts.map +1 -0
  16. package/dist/middleware/cors.js +34 -0
  17. package/dist/middleware/cors.js.map +1 -0
  18. package/dist/middleware/serve-static.d.ts +6 -0
  19. package/dist/middleware/serve-static.d.ts.map +1 -0
  20. package/dist/middleware/serve-static.js +68 -0
  21. package/dist/middleware/serve-static.js.map +1 -0
  22. package/dist/node.d.ts +8 -0
  23. package/dist/node.d.ts.map +1 -0
  24. package/dist/node.js +52 -0
  25. package/dist/node.js.map +1 -0
  26. package/dist/path.d.ts +10 -0
  27. package/dist/path.d.ts.map +1 -0
  28. package/dist/path.js +45 -0
  29. package/dist/path.js.map +1 -0
  30. package/dist/vite.d.ts +31 -0
  31. package/dist/vite.d.ts.map +1 -0
  32. package/dist/vite.js +278 -0
  33. package/dist/vite.js.map +1 -0
  34. package/package.json +33 -40
  35. package/dist/cli.d.ts +0 -3
  36. package/dist/cli.d.ts.map +0 -1
  37. package/dist/cli.js +0 -137
  38. package/dist/cli.js.map +0 -1
  39. package/dist/components/BaseHead.d.ts +0 -5
  40. package/dist/components/BaseHead.d.ts.map +0 -1
  41. package/dist/components/BaseHead.js +0 -161
  42. package/dist/components/BaseHead.js.map +0 -1
  43. package/dist/components/ChatInput.d.ts +0 -7
  44. package/dist/components/ChatInput.d.ts.map +0 -1
  45. package/dist/components/ChatInput.js +0 -11
  46. package/dist/components/ChatInput.js.map +0 -1
  47. package/dist/components/Sidebar.d.ts +0 -8
  48. package/dist/components/Sidebar.d.ts.map +0 -1
  49. package/dist/components/Sidebar.js +0 -7
  50. package/dist/components/Sidebar.js.map +0 -1
  51. package/dist/components/SimpleLivePreview.d.ts +0 -7
  52. package/dist/components/SimpleLivePreview.d.ts.map +0 -1
  53. package/dist/components/SimpleLivePreview.js +0 -7
  54. package/dist/components/SimpleLivePreview.js.map +0 -1
  55. package/dist/components/Terminal.d.ts +0 -7
  56. package/dist/components/Terminal.d.ts.map +0 -1
  57. package/dist/components/Terminal.js +0 -14
  58. package/dist/components/Terminal.js.map +0 -1
  59. package/dist/components/VoiceRecorder.d.ts +0 -4
  60. package/dist/components/VoiceRecorder.d.ts.map +0 -1
  61. package/dist/components/VoiceRecorder.js +0 -5
  62. package/dist/components/VoiceRecorder.js.map +0 -1
  63. package/dist/components/icons/GeminiLogo.d.ts +0 -7
  64. package/dist/components/icons/GeminiLogo.d.ts.map +0 -1
  65. package/dist/components/icons/GeminiLogo.js +0 -5
  66. package/dist/components/icons/GeminiLogo.js.map +0 -1
  67. package/dist/components/icons/OllamaLogo.d.ts +0 -2
  68. package/dist/components/icons/OllamaLogo.d.ts.map +0 -1
  69. package/dist/components/icons/OllamaLogo.js +0 -5
  70. package/dist/components/icons/OllamaLogo.js.map +0 -1
  71. package/dist/layouts/Layout.d.ts +0 -9
  72. package/dist/layouts/Layout.d.ts.map +0 -1
  73. package/dist/layouts/Layout.js +0 -9
  74. package/dist/layouts/Layout.js.map +0 -1
  75. package/dist/layouts/NotFoundLayout.d.ts +0 -2
  76. package/dist/layouts/NotFoundLayout.d.ts.map +0 -1
  77. package/dist/layouts/NotFoundLayout.js +0 -6
  78. package/dist/layouts/NotFoundLayout.js.map +0 -1
  79. package/dist/pages/Code.d.ts +0 -7
  80. package/dist/pages/Code.d.ts.map +0 -1
  81. package/dist/pages/Code.js +0 -8
  82. package/dist/pages/Code.js.map +0 -1
  83. package/dist/pages/Home.d.ts +0 -7
  84. package/dist/pages/Home.d.ts.map +0 -1
  85. package/dist/pages/Home.js +0 -8
  86. package/dist/pages/Home.js.map +0 -1
  87. package/dist/pages/Welcome.d.ts +0 -2
  88. package/dist/pages/Welcome.d.ts.map +0 -1
  89. package/dist/pages/Welcome.js +0 -6
  90. package/dist/pages/Welcome.js.map +0 -1
  91. package/dist/server.d.ts +0 -11
  92. package/dist/server.d.ts.map +0 -1
  93. package/dist/server.js +0 -434
  94. package/dist/server.js.map +0 -1
  95. package/dist/services/dev-server.d.ts +0 -29
  96. package/dist/services/dev-server.d.ts.map +0 -1
  97. package/dist/services/dev-server.js +0 -201
  98. package/dist/services/dev-server.js.map +0 -1
  99. package/dist/services/terminal.d.ts +0 -46
  100. package/dist/services/terminal.d.ts.map +0 -1
  101. package/dist/services/terminal.js +0 -264
  102. package/dist/services/terminal.js.map +0 -1
  103. package/dist/services/websocket.d.ts +0 -48
  104. package/dist/services/websocket.d.ts.map +0 -1
  105. package/dist/services/websocket.js +0 -332
  106. package/dist/services/websocket.js.map +0 -1
  107. package/dist/static/components/ChatInput.js +0 -237
  108. package/dist/static/components/Sidebar.js +0 -225
  109. package/dist/static/components/SimpleLivePreview.js +0 -305
  110. package/dist/static/components/Terminal.js +0 -461
  111. package/dist/static/components/TerminalTabs.js +0 -383
  112. package/dist/static/favicon.svg +0 -14
  113. package/dist/static/signals/LivePreviewSignal.js +0 -71
  114. package/dist/static/signals/SidebarSignal.js +0 -123
  115. package/dist/static/signals/TerminalSignal.js +0 -273
  116. package/dist/static/styles/main.css +0 -1761
  117. package/src/cli.ts +0 -155
  118. package/src/components/BaseHead.ts +0 -164
  119. package/src/components/ChatInput.tsx +0 -56
  120. package/src/components/Sidebar.tsx +0 -99
  121. package/src/components/SimpleLivePreview.tsx +0 -40
  122. package/src/components/Terminal.tsx +0 -40
  123. package/src/components/VoiceRecorder.tsx +0 -33
  124. package/src/components/icons/GeminiLogo.tsx +0 -10
  125. package/src/components/icons/OllamaLogo.tsx +0 -5
  126. package/src/index.tsx +0 -12
  127. package/src/layouts/Layout.tsx +0 -41
  128. package/src/layouts/NotFoundLayout.tsx +0 -15
  129. package/src/pages/Code.tsx +0 -34
  130. package/src/pages/Welcome.tsx +0 -56
  131. package/src/server.tsx +0 -519
  132. package/src/services/dev-server.ts +0 -234
  133. package/src/services/terminal.ts +0 -325
  134. package/src/services/websocket.ts +0 -405
  135. package/src/static/components/ChatInput.js +0 -237
  136. package/src/static/components/Sidebar.js +0 -225
  137. package/src/static/components/SimpleLivePreview.js +0 -305
  138. package/src/static/components/Terminal.js +0 -461
  139. package/src/static/components/TerminalTabs.js +0 -383
  140. package/src/static/favicon.svg +0 -14
  141. package/src/static/signals/LivePreviewSignal.js +0 -71
  142. package/src/static/signals/SidebarSignal.js +0 -123
  143. package/src/static/signals/TerminalSignal.js +0 -273
  144. package/src/static/styles/main.css +0 -1761
  145. package/src/styles/input.css +0 -3
  146. package/tailwind.config.ts +0 -22
  147. package/tsconfig.json +0 -37
@@ -1,234 +0,0 @@
1
- import process from "node:process";
2
- import { spawn, type ChildProcess } from "node:child_process";
3
-
4
- export interface DevServerStatus {
5
- running: boolean;
6
- pid?: number;
7
- startTime?: Date;
8
- port?: number;
9
- command?: string;
10
- logs: string[];
11
- errors: string[];
12
- }
13
-
14
- export class DevServerManager {
15
- private childProcess: ChildProcess | null = null;
16
- private status: DevServerStatus;
17
- private logBuffer: string[] = [];
18
- private errorBuffer: string[] = [];
19
- private maxBufferSize = 1000;
20
-
21
- constructor(private command: string, private port?: number) {
22
- this.status = {
23
- running: false,
24
- command: command,
25
- port: port,
26
- logs: [],
27
- errors: []
28
- };
29
- }
30
-
31
- async start(): Promise<void> {
32
- if (this.childProcess) {
33
- console.log("Dev server is already running");
34
- return;
35
- }
36
-
37
- try {
38
- console.log(`🚀 Starting dev server: ${this.command}`);
39
-
40
- // Split command into parts for spawn
41
- const commandParts = this.command.split(' ');
42
- const cmd = commandParts[0];
43
- const args = commandParts.slice(1);
44
-
45
- this.childProcess = spawn(cmd, args, {
46
- stdio: ['pipe', 'pipe', 'pipe'],
47
- cwd: process.cwd(),
48
- shell: process.platform === 'win32'
49
- });
50
-
51
- this.status.running = true;
52
- this.status.pid = this.childProcess.pid;
53
- this.status.startTime = new Date();
54
-
55
- // Handle stdout
56
- if (this.childProcess.stdout) {
57
- this.childProcess.stdout.on('data', (data) => {
58
- const output = data.toString();
59
- this.addLog(output);
60
- });
61
- }
62
-
63
- // Handle stderr
64
- if (this.childProcess.stderr) {
65
- this.childProcess.stderr.on('data', (data) => {
66
- const output = data.toString();
67
- this.addError(output);
68
- });
69
- }
70
-
71
- // Handle process exit
72
- this.childProcess.on('exit', (code) => {
73
- console.log(`Dev server exited with code ${code}`);
74
- this.status.running = false;
75
- this.status.pid = undefined;
76
- this.childProcess = null;
77
- });
78
-
79
- this.childProcess.on('error', (error) => {
80
- console.error("Dev server error:", error);
81
- this.status.running = false;
82
- this.status.pid = undefined;
83
- this.childProcess = null;
84
- });
85
-
86
- } catch (error) {
87
- console.error("Failed to start dev server:", error);
88
- this.status.running = false;
89
- this.addError(`Failed to start: ${error instanceof Error ? error.message : String(error)}`);
90
- throw error;
91
- }
92
- }
93
-
94
- async stop(): Promise<void> {
95
- if (!this.childProcess) {
96
- console.log("Dev server is not running");
97
- return;
98
- }
99
-
100
- try {
101
- this.childProcess.kill("SIGTERM");
102
-
103
- // Wait for process to exit with timeout
104
- await new Promise<void>((resolve, reject) => {
105
- const timeout = setTimeout(() => {
106
- if (this.childProcess) {
107
- this.childProcess.kill("SIGKILL");
108
- }
109
- reject(new Error("Process did not exit gracefully"));
110
- }, 5000);
111
-
112
- this.childProcess!.on('exit', () => {
113
- clearTimeout(timeout);
114
- resolve();
115
- });
116
- });
117
-
118
- this.childProcess = null;
119
- this.status.running = false;
120
- this.status.pid = undefined;
121
-
122
- } catch (error) {
123
- console.error("Failed to stop dev server:", error);
124
- this.addError(`Failed to stop: ${error instanceof Error ? error.message : String(error)}`);
125
- }
126
- }
127
-
128
- async restart(): Promise<void> {
129
- console.log("🔄 Restarting dev server...");
130
- await this.stop();
131
- // Small delay to ensure cleanup
132
- await new Promise(resolve => setTimeout(resolve, 1000));
133
- await this.start();
134
- }
135
-
136
- getStatus(): DevServerStatus {
137
- return {
138
- ...this.status,
139
- logs: [...this.logBuffer],
140
- errors: [...this.errorBuffer]
141
- };
142
- }
143
-
144
- getLogs(_since?: Date): string[] {
145
- // For now, return all logs. In future, could filter by timestamp
146
- return [...this.logBuffer];
147
- }
148
-
149
- sendCommand(command: string): boolean {
150
- if (!this.childProcess || !this.childProcess.stdin) {
151
- return false;
152
- }
153
-
154
- try {
155
- this.childProcess.stdin.write(command + '\n');
156
- return true;
157
- } catch (error) {
158
- console.error('Failed to send command to subprocess:', error);
159
- return false;
160
- }
161
- }
162
-
163
-
164
- private addLog(message: string): void {
165
- const timestamp = new Date().toISOString();
166
- const logEntry = `[${timestamp}] ${message}`;
167
-
168
- this.logBuffer.push(logEntry);
169
- if (this.logBuffer.length > this.maxBufferSize) {
170
- this.logBuffer.shift();
171
- }
172
-
173
- }
174
-
175
- private addError(message: string): void {
176
- const timestamp = new Date().toISOString();
177
- const errorEntry = `[${timestamp}] ${message}`;
178
-
179
- this.errorBuffer.push(errorEntry);
180
- if (this.errorBuffer.length > this.maxBufferSize) {
181
- this.errorBuffer.shift();
182
- }
183
-
184
- }
185
-
186
- // Cleanup on process exit
187
- setupGracefulShutdown(): void {
188
- const cleanup = async () => {
189
- if (this.childProcess) {
190
- await this.stop();
191
- }
192
- process.exit(0);
193
- };
194
-
195
- // Handle Ctrl+C (SIGINT)
196
- process.on('SIGINT', () => {
197
- console.log('\n🛑 Shutting down...');
198
- // Forward signal to child process if it exists
199
- if (this.childProcess && this.childProcess.pid) {
200
- try {
201
- this.childProcess.kill('SIGINT');
202
- } catch {
203
- // Silently handle signal forwarding errors
204
- }
205
- }
206
- cleanup().catch(() => {});
207
- });
208
-
209
- // Handle termination signal
210
- process.on('SIGTERM', () => {
211
- console.log('\n🛑 Terminating...');
212
- // Forward signal to child process if it exists
213
- if (this.childProcess && this.childProcess.pid) {
214
- try {
215
- this.childProcess.kill('SIGTERM');
216
- } catch {
217
- // Silently handle signal forwarding errors
218
- }
219
- }
220
- cleanup().catch(() => {});
221
- });
222
-
223
- // Handle process exit (synchronous cleanup only)
224
- process.on('exit', () => {
225
- if (this.childProcess) {
226
- try {
227
- this.childProcess.kill('SIGTERM');
228
- } catch {
229
- // Silently handle cleanup errors during exit
230
- }
231
- }
232
- });
233
- }
234
- }
@@ -1,325 +0,0 @@
1
- import { spawn, ChildProcess } from 'child_process';
2
- import { EventEmitter } from 'events';
3
- import * as pty from 'node-pty';
4
- import * as fs from 'node:fs';
5
- import * as path from 'node:path';
6
- import * as os from 'node:os';
7
-
8
- export interface TerminalSession {
9
- id: string;
10
- process: pty.IPty;
11
- eventEmitter: EventEmitter;
12
- createdAt: Date;
13
- lastActivity: Date;
14
- cwd?: string;
15
- env?: Record<string, string>;
16
- cols?: number;
17
- rows?: number;
18
- }
19
-
20
- export interface PersistedSessionData {
21
- id: string;
22
- createdAt: string;
23
- lastActivity: string;
24
- cwd: string;
25
- env: Record<string, string>;
26
- cols: number;
27
- rows: number;
28
- }
29
-
30
- export class TerminalService {
31
- private static sessions = new Map<string, TerminalSession>();
32
- private static readonly SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
33
- private static readonly PERSISTENCE_DIR = path.join(os.tmpdir(), '.treesap-terminals');
34
- private static readonly SESSIONS_FILE = path.join(this.PERSISTENCE_DIR, 'sessions.json');
35
-
36
- static createSession(sessionId: string, options?: { cwd?: string; cols?: number; rows?: number }): TerminalSession {
37
- // Clean up any existing session with the same ID
38
- this.destroySession(sessionId);
39
-
40
- const eventEmitter = new EventEmitter();
41
- // Increase max listeners to handle multiple terminal tabs and connections
42
- eventEmitter.setMaxListeners(20);
43
-
44
- // Use provided options or defaults
45
- const cwd = options?.cwd || process.cwd();
46
- const cols = options?.cols || 80;
47
- const rows = options?.rows || 24;
48
- const env: Record<string, string> = {};
49
-
50
- // Filter out undefined values from process.env
51
- for (const [key, value] of Object.entries(process.env)) {
52
- if (value !== undefined) {
53
- env[key] = value;
54
- }
55
- }
56
-
57
- // Create a PTY process for proper terminal behavior
58
- const ptyProcess = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : process.env.SHELL || '/bin/bash', [], {
59
- name: 'xterm-256color',
60
- cols,
61
- rows,
62
- cwd,
63
- env
64
- });
65
-
66
- const session: TerminalSession = {
67
- id: sessionId,
68
- process: ptyProcess,
69
- eventEmitter,
70
- createdAt: new Date(),
71
- lastActivity: new Date(),
72
- cwd,
73
- env,
74
- cols,
75
- rows
76
- };
77
-
78
- // Handle process output
79
- ptyProcess.onData((data: string) => {
80
- session.lastActivity = new Date();
81
- eventEmitter.emit('output', {
82
- type: 'output',
83
- content: data
84
- });
85
- });
86
-
87
- ptyProcess.onExit((e: { exitCode: number; signal?: number }) => {
88
- eventEmitter.emit('output', {
89
- type: 'exit',
90
- code: e.exitCode
91
- });
92
- this.destroySession(sessionId);
93
- });
94
-
95
- this.sessions.set(sessionId, session);
96
-
97
- // Set up session cleanup
98
- this.scheduleSessionCleanup(sessionId);
99
-
100
- // Persist session data
101
- this.persistSessionData(session);
102
-
103
- return session;
104
- }
105
-
106
- static getSession(sessionId: string): TerminalSession | undefined {
107
- return this.sessions.get(sessionId);
108
- }
109
-
110
- static executeCommand(sessionId: string, command: string): boolean {
111
- const session = this.getSession(sessionId);
112
- if (!session) {
113
- return false;
114
- }
115
-
116
- try {
117
- session.lastActivity = new Date();
118
- session.process.write(command + '\n');
119
- return true;
120
- } catch (error) {
121
- console.error(`Error executing command in session ${sessionId}:`, error);
122
- return false;
123
- }
124
- }
125
-
126
- static destroySession(sessionId: string): boolean {
127
- const session = this.sessions.get(sessionId);
128
- if (!session) {
129
- return false;
130
- }
131
-
132
- try {
133
- // Clean up the PTY process
134
- session.process.kill();
135
-
136
- // Remove event listeners
137
- session.eventEmitter.removeAllListeners();
138
-
139
- // Remove from sessions map
140
- this.sessions.delete(sessionId);
141
-
142
- // Remove from persistent storage
143
- this.removePersistedSession(sessionId);
144
-
145
- return true;
146
- } catch (error) {
147
- console.error(`Error destroying session ${sessionId}:`, error);
148
- return false;
149
- }
150
- }
151
-
152
- static getAllSessions(): TerminalSession[] {
153
- return Array.from(this.sessions.values());
154
- }
155
-
156
- static cleanupExpiredSessions(): number {
157
- const now = new Date();
158
- let cleanedCount = 0;
159
-
160
- for (const [sessionId, session] of this.sessions.entries()) {
161
- const timeSinceLastActivity = now.getTime() - session.lastActivity.getTime();
162
-
163
- if (timeSinceLastActivity > this.SESSION_TIMEOUT) {
164
- this.destroySession(sessionId);
165
- cleanedCount++;
166
- }
167
- }
168
-
169
- return cleanedCount;
170
- }
171
-
172
- private static scheduleSessionCleanup(sessionId: string): void {
173
- setTimeout(() => {
174
- const session = this.getSession(sessionId);
175
- if (session) {
176
- const timeSinceLastActivity = new Date().getTime() - session.lastActivity.getTime();
177
- if (timeSinceLastActivity >= this.SESSION_TIMEOUT) {
178
- console.log(`Cleaning up expired terminal session: ${sessionId}`);
179
- this.destroySession(sessionId);
180
- } else {
181
- // Reschedule cleanup
182
- this.scheduleSessionCleanup(sessionId);
183
- }
184
- }
185
- }, this.SESSION_TIMEOUT);
186
- }
187
-
188
- static setupGlobalCleanup(): void {
189
- // Load persisted sessions on startup
190
- this.loadPersistedSessions();
191
-
192
- // Cleanup all sessions on process exit
193
- const cleanup = () => {
194
- console.log('Cleaning up all terminal sessions...');
195
- for (const sessionId of this.sessions.keys()) {
196
- this.destroySession(sessionId);
197
- }
198
- };
199
-
200
- process.on('SIGINT', cleanup);
201
- process.on('SIGTERM', cleanup);
202
- process.on('exit', cleanup);
203
-
204
- // Periodic cleanup of expired sessions
205
- setInterval(() => {
206
- const cleaned = this.cleanupExpiredSessions();
207
- if (cleaned > 0) {
208
- console.log(`Cleaned up ${cleaned} expired terminal sessions`);
209
- }
210
- }, 5 * 60 * 1000); // Every 5 minutes
211
- }
212
-
213
- // Persistence methods
214
- private static ensurePersistenceDir(): void {
215
- try {
216
- if (!fs.existsSync(this.PERSISTENCE_DIR)) {
217
- fs.mkdirSync(this.PERSISTENCE_DIR, { recursive: true });
218
- }
219
- } catch (error) {
220
- console.error('Error creating persistence directory:', error);
221
- }
222
- }
223
-
224
- private static persistSessionData(session: TerminalSession): void {
225
- try {
226
- this.ensurePersistenceDir();
227
-
228
- const sessionData: PersistedSessionData = {
229
- id: session.id,
230
- createdAt: session.createdAt.toISOString(),
231
- lastActivity: session.lastActivity.toISOString(),
232
- cwd: session.cwd || process.cwd(),
233
- env: session.env || (() => {
234
- const env: Record<string, string> = {};
235
- for (const [key, value] of Object.entries(process.env)) {
236
- if (value !== undefined) {
237
- env[key] = value;
238
- }
239
- }
240
- return env;
241
- })(),
242
- cols: session.cols || 80,
243
- rows: session.rows || 24
244
- };
245
-
246
- let existingData: PersistedSessionData[] = [];
247
- if (fs.existsSync(this.SESSIONS_FILE)) {
248
- const content = fs.readFileSync(this.SESSIONS_FILE, 'utf8');
249
- if (content.trim()) {
250
- existingData = JSON.parse(content);
251
- }
252
- }
253
-
254
- // Remove existing session data if present
255
- existingData = existingData.filter(s => s.id !== session.id);
256
-
257
- // Add new session data
258
- existingData.push(sessionData);
259
-
260
- fs.writeFileSync(this.SESSIONS_FILE, JSON.stringify(existingData, null, 2));
261
- } catch (error) {
262
- console.error('Error persisting session data:', error);
263
- }
264
- }
265
-
266
- private static removePersistedSession(sessionId: string): void {
267
- try {
268
- if (!fs.existsSync(this.SESSIONS_FILE)) return;
269
-
270
- const content = fs.readFileSync(this.SESSIONS_FILE, 'utf8');
271
- if (!content.trim()) return;
272
-
273
- let existingData: PersistedSessionData[] = JSON.parse(content);
274
- existingData = existingData.filter(s => s.id !== sessionId);
275
-
276
- fs.writeFileSync(this.SESSIONS_FILE, JSON.stringify(existingData, null, 2));
277
- } catch (error) {
278
- console.error('Error removing persisted session:', error);
279
- }
280
- }
281
-
282
- private static loadPersistedSessions(): void {
283
- try {
284
- if (!fs.existsSync(this.SESSIONS_FILE)) return;
285
-
286
- const content = fs.readFileSync(this.SESSIONS_FILE, 'utf8');
287
- if (!content.trim()) return;
288
-
289
- const persistedSessions: PersistedSessionData[] = JSON.parse(content);
290
-
291
- console.log(`Found ${persistedSessions.length} persisted terminal session(s)`);
292
-
293
- for (const sessionData of persistedSessions) {
294
- // Check if session is not too old
295
- const lastActivity = new Date(sessionData.lastActivity);
296
- const timeSinceLastActivity = Date.now() - lastActivity.getTime();
297
-
298
- if (timeSinceLastActivity < this.SESSION_TIMEOUT) {
299
- console.log(`Restoring terminal session: ${sessionData.id}`);
300
-
301
- // Create new session with the persisted options
302
- this.createSession(sessionData.id, {
303
- cwd: sessionData.cwd,
304
- cols: sessionData.cols,
305
- rows: sessionData.rows
306
- });
307
- } else {
308
- console.log(`Skipping expired session: ${sessionData.id}`);
309
- this.removePersistedSession(sessionData.id);
310
- }
311
- }
312
- } catch (error) {
313
- console.error('Error loading persisted sessions:', error);
314
- }
315
- }
316
-
317
- // Update session activity and persist
318
- static updateSessionActivity(sessionId: string): void {
319
- const session = this.getSession(sessionId);
320
- if (session) {
321
- session.lastActivity = new Date();
322
- this.persistSessionData(session);
323
- }
324
- }
325
- }