treesap 0.1.13 → 0.2.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 (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,41 +0,0 @@
1
- interface BaseLayoutProps {
2
- title?: string;
3
- description?: string;
4
- head?: any;
5
- children: any;
6
- }
7
-
8
- export default function BaseLayout(props: BaseLayoutProps) {
9
- return (
10
- <>
11
- <div dangerouslySetInnerHTML={{ __html: '<!DOCTYPE html>' }} />
12
- <html lang="en">
13
- <head>
14
- <title>{props.title || "Treesap"}</title>
15
- <meta name="description" content={props.description || "A modern web application"} />
16
- <meta charset="UTF-8" />
17
- <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" />
18
- <link rel="stylesheet" href="/styles/main.css" />
19
- {/* Sapling Islands */}
20
- <script type="module" src="https://sapling-is.land"></script>
21
- <style
22
- dangerouslySetInnerHTML={{
23
- __html: `
24
- sapling-island{display:contents}
25
- `,
26
- }}
27
- />
28
- <script
29
- src="https://cdn.jsdelivr.net/npm/iconify-icon@2.1.0/dist/iconify-icon.min.js"
30
- defer
31
- >
32
- </script>
33
- {props.head}
34
- </head>
35
- <body class="bg-white text-black">
36
- {props.children}
37
- </body>
38
- </html>
39
- </>
40
- );
41
- }
@@ -1,15 +0,0 @@
1
- import Layout from "./Layout.js";
2
-
3
- export default async function NotFoundLayout() {
4
- return (
5
- <Layout>
6
- <main
7
- class="flex-1 flex flex-col justify-center items-center min-h-screen"
8
- >
9
- <h1 class="text-4xl font-bold font-heading leading-tight mb-8">
10
- Page not found
11
- </h1>
12
- </main>
13
- </Layout>
14
- );
15
- }
@@ -1,34 +0,0 @@
1
- import Layout from "../layouts/Layout.js";
2
- import { Sidebar } from "../components/Sidebar.js";
3
- import { SimpleLivePreview } from "../components/SimpleLivePreview.js";
4
-
5
- interface TerminalProps {
6
- previewPort?: number;
7
- workingDirectory?: string;
8
- }
9
-
10
- export function Code({ previewPort = 1234, workingDirectory }: TerminalProps) {
11
- return (
12
- <Layout title="Code Editor">
13
- <div id="code-container" class="h-screen flex bg-[#1e1e1e] relative">
14
- {/* Mobile toggle button */}
15
- <button
16
- type="button"
17
- id="mobile-sidebar-toggle"
18
- class="fixed top-4 left-4 z-60 p-3 bg-[#2d2d30] border border-[#3c3c3c] rounded-lg shadow-xl hover:bg-[#3c3c3c] transition-all md:hidden"
19
- title="Toggle Sidebar"
20
- >
21
- <iconify-icon icon="tabler:menu-2" width="20" height="20" class="text-[#cccccc]"></iconify-icon>
22
- </button>
23
-
24
- {/* Sidebar */}
25
- <Sidebar id="sidebar" previewPort={previewPort} workingDirectory={workingDirectory} />
26
-
27
- {/* Main Content - Live Preview */}
28
- <div class="flex-1 md:flex-1">
29
- <SimpleLivePreview id="live-preview" previewPort={previewPort} />
30
- </div>
31
- </div>
32
- </Layout>
33
- );
34
- }
@@ -1,56 +0,0 @@
1
- import Layout from "../layouts/Layout.js";
2
-
3
- export function Welcome() {
4
- return (
5
- <Layout title="Welcome to Treesap">
6
- <div class="h-screen flex flex-col items-center justify-center bg-[#1e1e1e] text-white p-8">
7
- {/* Header with logo and title */}
8
- <div class="text-center mb-12">
9
- <div class="flex items-center justify-center mb-4">
10
- <div class="w-12 h-12 bg-gradient-to-br from-green-400 to-green-600 rounded-lg flex items-center justify-center mr-4">
11
- <span class="text-2xl">🌳</span>
12
- </div>
13
- <div class="text-left">
14
- <h1 class="text-3xl font-semibold text-white">Treesap</h1>
15
- <p class="text-gray-400 text-sm">Free • Development Environment</p>
16
- </div>
17
- </div>
18
- </div>
19
-
20
- {/* Main action buttons */}
21
- <div class="flex gap-8 mb-16">
22
- <a
23
- href="/code"
24
- class="flex flex-col items-center p-6 bg-[#2a2a2a] hover:bg-[#3a3a3a] rounded-lg transition-colors group min-w-[140px]"
25
- >
26
- <div class="w-8 h-8 mb-3 text-gray-300 group-hover:text-white transition-colors">
27
- <iconify-icon icon="tabler:folder-open" width="32" height="32"></iconify-icon>
28
- </div>
29
- <span class="text-gray-300 group-hover:text-white transition-colors">Open project</span>
30
- </a>
31
-
32
- <button
33
- class="flex flex-col items-center p-6 bg-[#2a2a2a] hover:bg-[#3a3a3a] rounded-lg transition-colors group min-w-[140px] opacity-50 cursor-not-allowed"
34
- disabled
35
- >
36
- <div class="w-8 h-8 mb-3 text-gray-500">
37
- <iconify-icon icon="tabler:git-branch" width="32" height="32"></iconify-icon>
38
- </div>
39
- <span class="text-gray-500">Clone repo</span>
40
- </button>
41
-
42
- <button
43
- class="flex flex-col items-center p-6 bg-[#2a2a2a] hover:bg-[#3a3a3a] rounded-lg transition-colors group min-w-[140px] opacity-50 cursor-not-allowed"
44
- disabled
45
- >
46
- <div class="w-8 h-8 mb-3 text-gray-500">
47
- <iconify-icon icon="tabler:terminal" width="32" height="32"></iconify-icon>
48
- </div>
49
- <span class="text-gray-500">Connect via SSH</span>
50
- </button>
51
- </div>
52
-
53
- </div>
54
- </Layout>
55
- );
56
- }
package/src/server.tsx DELETED
@@ -1,519 +0,0 @@
1
-
2
- import { Hono, type Context } from "hono";
3
- import { serveStatic } from "@hono/node-server/serve-static";
4
- import NotFoundLayout from "./layouts/NotFoundLayout.js";
5
- import { Welcome } from "./pages/Welcome.js";
6
- import { Code } from "./pages/Code.js";
7
- import { DevServerManager } from "./services/dev-server.js";
8
- import { TerminalService } from "./services/terminal.js";
9
- import { WebSocketTerminalService } from "./services/websocket.js";
10
- import * as path from 'node:path';
11
- import process from "node:process";
12
- import { fileURLToPath } from 'node:url';
13
- import type { Server } from 'http';
14
-
15
- export interface TreesapConfig {
16
- port?: number;
17
- projectRoot?: string;
18
- previewPort?: number;
19
- devCommand?: string;
20
- devPort?: number;
21
- }
22
-
23
- export async function startServer(config: TreesapConfig & { autoStartDev?: boolean } = {}) {
24
- const {
25
- port = 1234,
26
- projectRoot = process.cwd(),
27
- previewPort = 5173,
28
- devCommand,
29
- devPort,
30
- autoStartDev = false
31
- } = config;
32
-
33
- // Change the current working directory to the project root
34
- process.chdir(projectRoot);
35
-
36
- // Initialize dev server manager if dev command is provided
37
- let devServerManager: DevServerManager | null = null;
38
- if (devCommand) {
39
- devServerManager = new DevServerManager(devCommand, devPort);
40
- devServerManager.setupGracefulShutdown();
41
-
42
- // Auto-start dev server if enabled
43
- if (autoStartDev) {
44
- devServerManager.start().catch(error => {
45
- console.error("Failed to auto-start dev server:", error);
46
- });
47
- }
48
- }
49
-
50
- const app = new Hono();
51
-
52
- // Resolve module static directory (so consumers can see bundled assets)
53
- const moduleDir = path.dirname(fileURLToPath(import.meta.url));
54
- const moduleStaticRoot = path.join(moduleDir, 'static');
55
-
56
- // Welcome page (default)
57
- app.get("/", (c: Context) => {
58
- // Additional cache prevention headers
59
- c.header('Cache-Control', 'no-cache, no-store, must-revalidate, private');
60
- c.header('Pragma', 'no-cache');
61
- c.header('Expires', '0');
62
- return c.html(<Welcome />);
63
- });
64
-
65
- // Code page
66
- app.get("/code", (c: Context) => {
67
- // Additional cache prevention headers
68
- c.header('Cache-Control', 'no-cache, no-store, must-revalidate, private');
69
- c.header('Pragma', 'no-cache');
70
- c.header('Expires', '0');
71
- return c.html(<Code previewPort={previewPort} />);
72
- });
73
-
74
- // Claude page
75
- app.get("/claude", (c: Context) => {
76
- // Additional cache prevention headers
77
- c.header('Cache-Control', 'no-cache, no-store, must-revalidate, private');
78
- c.header('Pragma', 'no-cache');
79
- c.header('Expires', '0');
80
- return c.html(<Code previewPort={previewPort} workingDirectory={projectRoot} />);
81
- });
82
-
83
-
84
- // Dev server management endpoints
85
- app.get("/api/dev-status", (c: Context) => {
86
- if (!devServerManager) {
87
- return c.json({ error: "No dev server configured" }, 400);
88
- }
89
- return c.json(devServerManager.getStatus());
90
- });
91
-
92
- app.post("/api/dev-restart", async (c: Context) => {
93
- if (!devServerManager) {
94
- return c.json({ error: "No dev server configured" }, 400);
95
- }
96
-
97
- try {
98
- await devServerManager.restart();
99
- return c.json({ success: true, message: "Dev server restarted" });
100
- } catch (error) {
101
- return c.json({
102
- error: "Failed to restart dev server",
103
- details: error instanceof Error ? error.message : String(error)
104
- }, 500);
105
- }
106
- });
107
-
108
- app.post("/api/dev-start", async (c: Context) => {
109
- if (!devServerManager) {
110
- return c.json({ error: "No dev server configured" }, 400);
111
- }
112
-
113
- try {
114
- await devServerManager.start();
115
- return c.json({ success: true, message: "Dev server started" });
116
- } catch (error) {
117
- return c.json({
118
- error: "Failed to start dev server",
119
- details: error instanceof Error ? error.message : String(error)
120
- }, 500);
121
- }
122
- });
123
-
124
- app.post("/api/dev-stop", async (c: Context) => {
125
- if (!devServerManager) {
126
- return c.json({ error: "No dev server configured" }, 400);
127
- }
128
-
129
- try {
130
- await devServerManager.stop();
131
- return c.json({ success: true, message: "Dev server stopped" });
132
- } catch (error) {
133
- return c.json({
134
- error: "Failed to stop dev server",
135
- details: error instanceof Error ? error.message : String(error)
136
- }, 500);
137
- }
138
- });
139
-
140
- app.get("/api/dev-logs", (c: Context) => {
141
- if (!devServerManager) {
142
- return c.json({ error: "No dev server configured" }, 400);
143
- }
144
-
145
- const logs = devServerManager.getLogs();
146
- return c.json({ logs });
147
- });
148
-
149
- // List active terminal sessions with WebSocket client info
150
- app.get("/api/terminal/sessions", (c: Context) => {
151
- const sessions = TerminalService.getAllSessions();
152
- const wsActiveSessions = WebSocketTerminalService.getActiveSessions();
153
-
154
- return c.json({
155
- sessions: sessions.map(session => {
156
- const wsInfo = wsActiveSessions.find(ws => ws.sessionId === session.id);
157
- return {
158
- id: session.id,
159
- createdAt: session.createdAt,
160
- lastActivity: session.lastActivity,
161
- connectedClients: wsInfo ? wsInfo.clientCount : 0
162
- };
163
- }),
164
- totalConnectedClients: WebSocketTerminalService.getConnectedClients()
165
- });
166
- });
167
-
168
- // Get specific terminal session status
169
- app.get("/api/terminal/sessions/:sessionId/status", (c: Context) => {
170
- const sessionId = c.req.param('sessionId');
171
- const session = TerminalService.getSession(sessionId);
172
-
173
- if (!session) {
174
- return c.json({ error: "Session not found" }, 404);
175
- }
176
-
177
- const clients = WebSocketTerminalService.getSessionClients(sessionId);
178
-
179
- return c.json({
180
- id: session.id,
181
- createdAt: session.createdAt,
182
- lastActivity: session.lastActivity,
183
- connectedClients: clients.length,
184
- clientIds: clients
185
- });
186
- });
187
-
188
- // Send command to terminal via API
189
- app.post("/api/terminal/sessions/:sessionId/command", async (c: Context) => {
190
- const sessionId = c.req.param('sessionId');
191
- const body = await c.req.json();
192
- const { command } = body;
193
-
194
- if (!command) {
195
- return c.json({ error: "Command is required" }, 400);
196
- }
197
-
198
- // Get or create terminal session
199
- let session = TerminalService.getSession(sessionId);
200
- if (!session) {
201
- session = TerminalService.createSession(sessionId);
202
- }
203
-
204
- const success = WebSocketTerminalService.sendCommandToSession(sessionId, command);
205
-
206
- if (success) {
207
- return c.json({
208
- success: true,
209
- message: `Command sent to session ${sessionId}`,
210
- connectedClients: WebSocketTerminalService.getSessionClients(sessionId).length
211
- });
212
- } else {
213
- return c.json({ error: "Failed to send command to terminal" }, 500);
214
- }
215
- });
216
-
217
- // Get recent output from terminal session (for API clients)
218
- app.get("/api/terminal/sessions/:sessionId/output", async (c: Context) => {
219
- const sessionId = c.req.param('sessionId');
220
- const session = TerminalService.getSession(sessionId);
221
-
222
- if (!session) {
223
- return c.json({ error: "Session not found" }, 404);
224
- }
225
-
226
- // Note: This is a basic implementation. For production, you'd want to
227
- // store recent output history in the TerminalService
228
- return c.json({
229
- sessionId,
230
- message: "Output streaming available via WebSocket connection",
231
- connectedClients: WebSocketTerminalService.getSessionClients(sessionId).length,
232
- websocketUrl: `ws://${c.req.header('host')}/terminal/ws`
233
- });
234
- });
235
-
236
- // Terminal endpoints
237
- app.post("/terminal/execute/:sessionId", async (c: Context) => {
238
- const sessionId = c.req.param('sessionId');
239
- const body = await c.req.json();
240
- const { command } = body;
241
-
242
- if (!command) {
243
- return c.json({ error: "Command is required" }, 400);
244
- }
245
-
246
- // Get or create terminal session
247
- let session = TerminalService.getSession(sessionId);
248
- if (!session) {
249
- session = TerminalService.createSession(sessionId);
250
- }
251
-
252
- const success = TerminalService.executeCommand(sessionId, command);
253
-
254
- if (success) {
255
- return c.json({ success: true });
256
- } else {
257
- return c.json({ error: "Failed to execute command" }, 500);
258
- }
259
- });
260
-
261
- app.post("/terminal/input/:sessionId", async (c: Context) => {
262
- const sessionId = c.req.param('sessionId');
263
- const body = await c.req.json();
264
- const { input } = body;
265
-
266
- if (input === undefined) {
267
- return c.json({ error: "Input is required" }, 400);
268
- }
269
-
270
- // Get or create terminal session
271
- let session = TerminalService.getSession(sessionId);
272
- if (!session) {
273
- session = TerminalService.createSession(sessionId);
274
- }
275
-
276
- // Send input directly to PTY
277
- try {
278
- session.lastActivity = new Date();
279
- session.process.write(input);
280
- return c.json({ success: true });
281
- } catch (error) {
282
- console.error(`Error sending input to session ${sessionId}:`, error);
283
- return c.json({ error: "Failed to send input" }, 500);
284
- }
285
- });
286
-
287
- app.get("/terminal/stream/:sessionId", async (c: Context) => {
288
- const sessionId = c.req.param('sessionId');
289
-
290
- // Get or create terminal session
291
- let session = TerminalService.getSession(sessionId);
292
- if (!session) {
293
- session = TerminalService.createSession(sessionId);
294
- }
295
-
296
- // Set SSE headers
297
- c.header('Content-Type', 'text/event-stream');
298
- c.header('Cache-Control', 'no-cache');
299
- c.header('Connection', 'keep-alive');
300
- c.header('Access-Control-Allow-Origin', '*');
301
-
302
- const stream = new ReadableStream({
303
- start(controller) {
304
- const encoder = new TextEncoder();
305
-
306
- // Send initial connection event
307
- controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'connected' })}\n\n`));
308
-
309
- // Set up output listener
310
- const handleOutput = (data: any) => {
311
- try {
312
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
313
- } catch (error) {
314
- console.error('Error sending terminal output:', error);
315
- // Remove listener on error to prevent memory leaks
316
- session!.eventEmitter.removeListener('output', handleOutput);
317
- }
318
- };
319
-
320
- session!.eventEmitter.on('output', handleOutput);
321
-
322
- // Handle client disconnect
323
- const handleDisconnect = () => {
324
- try {
325
- session!.eventEmitter.removeListener('output', handleOutput);
326
- } catch (error) {
327
- console.error('Error removing output listener:', error);
328
- }
329
- };
330
-
331
- // Clean up on stream close
332
- return () => {
333
- handleDisconnect();
334
- };
335
- }
336
- });
337
-
338
- return new Response(stream, {
339
- headers: {
340
- 'Content-Type': 'text/event-stream',
341
- 'Cache-Control': 'no-cache',
342
- 'Connection': 'keep-alive',
343
- 'Access-Control-Allow-Origin': '*'
344
- }
345
- });
346
- });
347
-
348
- // Delete terminal session
349
- app.delete("/terminal/session/:sessionId", async (c: Context) => {
350
- const sessionId = c.req.param('sessionId');
351
-
352
- if (!sessionId) {
353
- return c.json({ error: "Session ID is required" }, 400);
354
- }
355
-
356
- const success = TerminalService.destroySession(sessionId);
357
-
358
- if (success) {
359
-
360
- return c.json({ message: `Terminal session ${sessionId} destroyed successfully` });
361
- } else {
362
- console.log(`Terminal session not found: ${sessionId}`);
363
- return c.json({ error: `Terminal session ${sessionId} not found` }, 404);
364
- }
365
- });
366
-
367
- // Claude Code subprocess management
368
- let claudeCodeManager: DevServerManager | null = null;
369
-
370
- app.post("/api/claude-start", async (c: Context) => {
371
- try {
372
- if (claudeCodeManager && claudeCodeManager.getStatus().running) {
373
- return c.json({ error: "Claude Code is already running" }, 400);
374
- }
375
-
376
- claudeCodeManager = new DevServerManager("claude");
377
- claudeCodeManager.setupGracefulShutdown();
378
- await claudeCodeManager.start();
379
-
380
- return c.json({ success: true, message: "Claude Code started" });
381
- } catch (error) {
382
- return c.json({
383
- error: "Failed to start Claude Code",
384
- details: error instanceof Error ? error.message : String(error)
385
- }, 500);
386
- }
387
- });
388
-
389
- app.get("/api/claude-status", (c: Context) => {
390
- if (!claudeCodeManager) {
391
- return c.json({ running: false });
392
- }
393
- return c.json(claudeCodeManager.getStatus());
394
- });
395
-
396
- app.post("/api/claude-stop", async (c: Context) => {
397
- if (!claudeCodeManager) {
398
- return c.json({ error: "No Claude Code process running" }, 400);
399
- }
400
-
401
- try {
402
- await claudeCodeManager.stop();
403
- claudeCodeManager = null;
404
- return c.json({ success: true, message: "Claude Code stopped" });
405
- } catch (error) {
406
- return c.json({
407
- error: "Failed to stop Claude Code",
408
- details: error instanceof Error ? error.message : String(error)
409
- }, 500);
410
- }
411
- });
412
-
413
- app.post("/api/claude-send", async (c: Context) => {
414
- if (!claudeCodeManager) {
415
- return c.json({ error: "Claude Code process not running" }, 400);
416
- }
417
-
418
- try {
419
- const body = await c.req.json();
420
- const { command } = body;
421
-
422
- if (!command) {
423
- return c.json({ error: "Command is required" }, 400);
424
- }
425
-
426
- // Send command to Claude Code stdin
427
- const success = claudeCodeManager.sendCommand(command);
428
-
429
- if (success) {
430
- return c.json({ success: true, message: "Command sent to Claude Code" });
431
- } else {
432
- return c.json({ error: "Failed to send command - process may not be running" }, 500);
433
- }
434
- } catch (error) {
435
- return c.json({
436
- error: "Failed to send command to Claude Code",
437
- details: error instanceof Error ? error.message : String(error)
438
- }, 500);
439
- }
440
- });
441
-
442
- // Serve static files from the module's bundled static directory
443
- app.get("*", serveStatic({ root: moduleStaticRoot }));
444
-
445
-
446
- // 404 Handler
447
- app.notFound((c: Context) => c.html(<NotFoundLayout />));
448
-
449
- const { serve } = await import('@hono/node-server');
450
-
451
- // Start the server and initialize WebSocket
452
- const server = serve({
453
- fetch: app.fetch,
454
- port,
455
- }) as Server;
456
-
457
- // Initialize WebSocket service
458
- WebSocketTerminalService.initialize(server);
459
-
460
- // Setup global graceful shutdown for all subprocess managers
461
- const setupGlobalShutdown = () => {
462
- const cleanup = async () => {
463
- console.log('\nšŸ›‘ Shutting down server and all subprocesses...');
464
-
465
- // Clean up WebSocket connections
466
- WebSocketTerminalService.cleanup();
467
-
468
- // Stop Claude Code process if running
469
- if (claudeCodeManager) {
470
- try {
471
- await claudeCodeManager.stop();
472
- claudeCodeManager = null;
473
- } catch (error) {
474
- console.error('Error stopping Claude Code process:', error);
475
- }
476
- }
477
-
478
- // Stop dev server if running
479
- if (devServerManager) {
480
- try {
481
- await devServerManager.stop();
482
- } catch (error) {
483
- console.error('Error stopping dev server:', error);
484
- }
485
- }
486
-
487
- process.exit(0);
488
- };
489
-
490
- // Handle Ctrl+C (SIGINT)
491
- process.on('SIGINT', () => {
492
- cleanup().catch(() => process.exit(1));
493
- });
494
-
495
- // Handle termination signal
496
- process.on('SIGTERM', () => {
497
- cleanup().catch(() => process.exit(1));
498
- });
499
-
500
- // Handle process exit (synchronous cleanup only)
501
- process.on('exit', () => {
502
- if (claudeCodeManager) {
503
- try {
504
- claudeCodeManager.stop();
505
- } catch {
506
- // Silently handle cleanup errors during exit
507
- }
508
- }
509
- });
510
- };
511
-
512
- setupGlobalShutdown();
513
-
514
- // Initialize terminal service cleanup
515
- TerminalService.setupGlobalCleanup();
516
-
517
- console.log(`\n🌳 Treesap server running at http://localhost:${port}`);
518
- console.log(`šŸ”Œ WebSocket terminal server available at ws://localhost:${port}/terminal/ws\n`);
519
- }