treesap 0.1.6 → 0.1.8

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.
@@ -1113,12 +1113,20 @@ video {
1113
1113
  z-index: 50;
1114
1114
  }
1115
1115
 
1116
- .mb-4 {
1117
- margin-bottom: 1rem;
1116
+ .mb-12 {
1117
+ margin-bottom: 3rem;
1118
+ }
1119
+
1120
+ .mb-16 {
1121
+ margin-bottom: 4rem;
1122
+ }
1123
+
1124
+ .mb-3 {
1125
+ margin-bottom: 0.75rem;
1118
1126
  }
1119
1127
 
1120
- .mb-6 {
1121
- margin-bottom: 1.5rem;
1128
+ .mb-4 {
1129
+ margin-bottom: 1rem;
1122
1130
  }
1123
1131
 
1124
1132
  .mb-8 {
@@ -1133,22 +1141,30 @@ video {
1133
1141
  margin-left: 0.5rem;
1134
1142
  }
1135
1143
 
1144
+ .mr-1 {
1145
+ margin-right: 0.25rem;
1146
+ }
1147
+
1136
1148
  .mr-2 {
1137
1149
  margin-right: 0.5rem;
1138
1150
  }
1139
1151
 
1140
- .block {
1141
- display: block;
1152
+ .mr-4 {
1153
+ margin-right: 1rem;
1142
1154
  }
1143
1155
 
1144
- .inline-block {
1145
- display: inline-block;
1156
+ .block {
1157
+ display: block;
1146
1158
  }
1147
1159
 
1148
1160
  .flex {
1149
1161
  display: flex;
1150
1162
  }
1151
1163
 
1164
+ .inline-flex {
1165
+ display: inline-flex;
1166
+ }
1167
+
1152
1168
  .hidden {
1153
1169
  display: none;
1154
1170
  }
@@ -1157,6 +1173,14 @@ video {
1157
1173
  height: 2.5rem;
1158
1174
  }
1159
1175
 
1176
+ .h-12 {
1177
+ height: 3rem;
1178
+ }
1179
+
1180
+ .h-8 {
1181
+ height: 2rem;
1182
+ }
1183
+
1160
1184
  .h-full {
1161
1185
  height: 100%;
1162
1186
  }
@@ -1173,12 +1197,20 @@ video {
1173
1197
  width: 2.5rem;
1174
1198
  }
1175
1199
 
1200
+ .w-12 {
1201
+ width: 3rem;
1202
+ }
1203
+
1204
+ .w-8 {
1205
+ width: 2rem;
1206
+ }
1207
+
1176
1208
  .w-full {
1177
1209
  width: 100%;
1178
1210
  }
1179
1211
 
1180
- .max-w-2xl {
1181
- max-width: 42rem;
1212
+ .min-w-\[140px\] {
1213
+ min-width: 140px;
1182
1214
  }
1183
1215
 
1184
1216
  .flex-1 {
@@ -1219,14 +1251,16 @@ video {
1219
1251
  justify-content: center;
1220
1252
  }
1221
1253
 
1254
+ .gap-1 {
1255
+ gap: 0.25rem;
1256
+ }
1257
+
1222
1258
  .gap-2 {
1223
1259
  gap: 0.5rem;
1224
1260
  }
1225
1261
 
1226
- .space-y-4 > :not([hidden]) ~ :not([hidden]) {
1227
- --tw-space-y-reverse: 0;
1228
- margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
1229
- margin-bottom: calc(1rem * var(--tw-space-y-reverse));
1262
+ .gap-8 {
1263
+ gap: 2rem;
1230
1264
  }
1231
1265
 
1232
1266
  .overflow-hidden {
@@ -1249,6 +1283,11 @@ video {
1249
1283
  border-radius: 0.375rem;
1250
1284
  }
1251
1285
 
1286
+ .rounded-t-sm {
1287
+ border-top-left-radius: 0.125rem;
1288
+ border-top-right-radius: 0.125rem;
1289
+ }
1290
+
1252
1291
  .border {
1253
1292
  border-width: 1px;
1254
1293
  }
@@ -1265,6 +1304,15 @@ video {
1265
1304
  border-right-width: 1px;
1266
1305
  }
1267
1306
 
1307
+ .border-t-2 {
1308
+ border-top-width: 2px;
1309
+ }
1310
+
1311
+ .border-\[\#0e639c\] {
1312
+ --tw-border-opacity: 1;
1313
+ border-color: rgb(14 99 156 / var(--tw-border-opacity, 1));
1314
+ }
1315
+
1268
1316
  .border-\[\#3c3c3c\] {
1269
1317
  --tw-border-opacity: 1;
1270
1318
  border-color: rgb(60 60 60 / var(--tw-border-opacity, 1));
@@ -1280,11 +1328,6 @@ video {
1280
1328
  border-color: rgb(156 163 175 / var(--tw-border-opacity, 1));
1281
1329
  }
1282
1330
 
1283
- .border-green-600 {
1284
- --tw-border-opacity: 1;
1285
- border-color: rgb(22 163 74 / var(--tw-border-opacity, 1));
1286
- }
1287
-
1288
1331
  .bg-\[\#1e1e1e\] {
1289
1332
  --tw-bg-opacity: 1;
1290
1333
  background-color: rgb(30 30 30 / var(--tw-bg-opacity, 1));
@@ -1295,6 +1338,11 @@ video {
1295
1338
  background-color: rgb(37 37 38 / var(--tw-bg-opacity, 1));
1296
1339
  }
1297
1340
 
1341
+ .bg-\[\#2a2a2a\] {
1342
+ --tw-bg-opacity: 1;
1343
+ background-color: rgb(42 42 42 / var(--tw-bg-opacity, 1));
1344
+ }
1345
+
1298
1346
  .bg-\[\#2d2d30\] {
1299
1347
  --tw-bg-opacity: 1;
1300
1348
  background-color: rgb(45 45 48 / var(--tw-bg-opacity, 1));
@@ -1305,26 +1353,11 @@ video {
1305
1353
  background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));
1306
1354
  }
1307
1355
 
1308
- .bg-gray-200 {
1309
- --tw-bg-opacity: 1;
1310
- background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
1311
- }
1312
-
1313
- .bg-gray-50 {
1314
- --tw-bg-opacity: 1;
1315
- background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
1316
- }
1317
-
1318
1356
  .bg-gray-900 {
1319
1357
  --tw-bg-opacity: 1;
1320
1358
  background-color: rgb(17 24 39 / var(--tw-bg-opacity, 1));
1321
1359
  }
1322
1360
 
1323
- .bg-green-600 {
1324
- --tw-bg-opacity: 1;
1325
- background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1));
1326
- }
1327
-
1328
1361
  .bg-transparent {
1329
1362
  background-color: transparent;
1330
1363
  }
@@ -1334,6 +1367,24 @@ video {
1334
1367
  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
1335
1368
  }
1336
1369
 
1370
+ .bg-gradient-to-br {
1371
+ background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
1372
+ }
1373
+
1374
+ .from-green-400 {
1375
+ --tw-gradient-from: #4ade80 var(--tw-gradient-from-position);
1376
+ --tw-gradient-to: rgb(74 222 128 / 0) var(--tw-gradient-to-position);
1377
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
1378
+ }
1379
+
1380
+ .to-green-600 {
1381
+ --tw-gradient-to: #16a34a var(--tw-gradient-to-position);
1382
+ }
1383
+
1384
+ .p-0\.5 {
1385
+ padding: 0.125rem;
1386
+ }
1387
+
1337
1388
  .p-1 {
1338
1389
  padding: 0.25rem;
1339
1390
  }
@@ -1350,6 +1401,10 @@ video {
1350
1401
  padding: 1rem;
1351
1402
  }
1352
1403
 
1404
+ .p-6 {
1405
+ padding: 1.5rem;
1406
+ }
1407
+
1353
1408
  .p-8 {
1354
1409
  padding: 2rem;
1355
1410
  }
@@ -1364,11 +1419,6 @@ video {
1364
1419
  padding-right: 0.75rem;
1365
1420
  }
1366
1421
 
1367
- .px-6 {
1368
- padding-left: 1.5rem;
1369
- padding-right: 1.5rem;
1370
- }
1371
-
1372
1422
  .py-1 {
1373
1423
  padding-top: 0.25rem;
1374
1424
  padding-bottom: 0.25rem;
@@ -1379,9 +1429,8 @@ video {
1379
1429
  padding-bottom: 0.5rem;
1380
1430
  }
1381
1431
 
1382
- .py-3 {
1383
- padding-top: 0.75rem;
1384
- padding-bottom: 0.75rem;
1432
+ .text-left {
1433
+ text-align: left;
1385
1434
  }
1386
1435
 
1387
1436
  .text-center {
@@ -1392,26 +1441,26 @@ video {
1392
1441
  font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
1393
1442
  }
1394
1443
 
1444
+ .text-2xl {
1445
+ font-size: 1.5rem;
1446
+ line-height: 2rem;
1447
+ }
1448
+
1449
+ .text-3xl {
1450
+ font-size: 1.875rem;
1451
+ line-height: 2.25rem;
1452
+ }
1453
+
1395
1454
  .text-4xl {
1396
1455
  font-size: 2.25rem;
1397
1456
  line-height: 2.5rem;
1398
1457
  }
1399
1458
 
1400
- .text-6xl {
1401
- font-size: 3.75rem;
1402
- line-height: 1;
1403
- }
1404
-
1405
1459
  .text-sm {
1406
1460
  font-size: 0.875rem;
1407
1461
  line-height: 1.25rem;
1408
1462
  }
1409
1463
 
1410
- .text-xl {
1411
- font-size: 1.25rem;
1412
- line-height: 1.75rem;
1413
- }
1414
-
1415
1464
  .font-bold {
1416
1465
  font-weight: 700;
1417
1466
  }
@@ -1438,14 +1487,19 @@ video {
1438
1487
  color: rgb(0 0 0 / var(--tw-text-opacity, 1));
1439
1488
  }
1440
1489
 
1441
- .text-gray-500 {
1490
+ .text-gray-300 {
1442
1491
  --tw-text-opacity: 1;
1443
- color: rgb(107 114 128 / var(--tw-text-opacity, 1));
1492
+ color: rgb(209 213 219 / var(--tw-text-opacity, 1));
1493
+ }
1494
+
1495
+ .text-gray-400 {
1496
+ --tw-text-opacity: 1;
1497
+ color: rgb(156 163 175 / var(--tw-text-opacity, 1));
1444
1498
  }
1445
1499
 
1446
- .text-gray-600 {
1500
+ .text-gray-500 {
1447
1501
  --tw-text-opacity: 1;
1448
- color: rgb(75 85 99 / var(--tw-text-opacity, 1));
1502
+ color: rgb(107 114 128 / var(--tw-text-opacity, 1));
1449
1503
  }
1450
1504
 
1451
1505
  .text-gray-800 {
@@ -1458,10 +1512,8 @@ video {
1458
1512
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
1459
1513
  }
1460
1514
 
1461
- .shadow-lg {
1462
- --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
1463
- --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
1464
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1515
+ .opacity-50 {
1516
+ opacity: 0.5;
1465
1517
  }
1466
1518
 
1467
1519
  .shadow-xl {
@@ -1500,6 +1552,16 @@ video {
1500
1552
  border-color: rgb(14 99 156 / var(--tw-border-opacity, 1));
1501
1553
  }
1502
1554
 
1555
+ .hover\:bg-\[\#2d2d30\]:hover {
1556
+ --tw-bg-opacity: 1;
1557
+ background-color: rgb(45 45 48 / var(--tw-bg-opacity, 1));
1558
+ }
1559
+
1560
+ .hover\:bg-\[\#3a3a3a\]:hover {
1561
+ --tw-bg-opacity: 1;
1562
+ background-color: rgb(58 58 58 / var(--tw-bg-opacity, 1));
1563
+ }
1564
+
1503
1565
  .hover\:bg-\[\#3c3c3c\]:hover {
1504
1566
  --tw-bg-opacity: 1;
1505
1567
  background-color: rgb(60 60 60 / var(--tw-bg-opacity, 1));
@@ -1515,11 +1577,6 @@ video {
1515
1577
  background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1));
1516
1578
  }
1517
1579
 
1518
- .hover\:bg-green-700:hover {
1519
- --tw-bg-opacity: 1;
1520
- background-color: rgb(21 128 61 / var(--tw-bg-opacity, 1));
1521
- }
1522
-
1523
1580
  .hover\:text-white:hover {
1524
1581
  --tw-text-opacity: 1;
1525
1582
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
@@ -1539,3 +1596,8 @@ video {
1539
1596
  .disabled\:opacity-50:disabled {
1540
1597
  opacity: 0.5;
1541
1598
  }
1599
+
1600
+ .group:hover .group-hover\:text-white {
1601
+ --tw-text-opacity: 1;
1602
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
1603
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "treesap",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "AI Agent Framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,9 +1,11 @@
1
1
  interface TerminalProps {
2
2
  id?: string;
3
- sessionId?: string;
3
+ index?: number;
4
4
  }
5
5
 
6
- export function Terminal({ id = "terminal", sessionId }: TerminalProps) {
6
+ export function Terminal({ id, index = 1 }: TerminalProps) {
7
+ const terminalId = id || `terminal-${index}`;
8
+ const sessionId = `terminal-${index}`;
7
9
  return (
8
10
  <sapling-island loading="visible">
9
11
  <template>
@@ -11,23 +13,27 @@ export function Terminal({ id = "terminal", sessionId }: TerminalProps) {
11
13
  <script type="module" src="/components/Terminal.js"></script>
12
14
  </template>
13
15
 
14
- <div id={id} class="h-full bg-gray-900 flex font-sans overflow-hidden">
16
+ <div id={terminalId} class="h-full bg-gray-900 flex font-sans overflow-hidden">
15
17
  {/* Terminal Interface */}
16
18
  <div class="w-full flex flex-col bg-gray-900 relative">
17
19
  {/* Terminal header */}
18
- <div id={`${id}-container`} class="h-full flex flex-col">
20
+ <div id={`${terminalId}-container`} class="h-full flex flex-col">
19
21
 
20
22
  {/* Xterm.js terminal container */}
21
23
  <div class="flex-1 overflow-hidden">
22
- <div id={`${id}-xterm`} class="h-full w-full"></div>
24
+ <div id={`${terminalId}-xterm`} class="h-full w-full"></div>
23
25
  </div>
24
26
  </div>
25
27
  </div>
26
28
  </div>
27
29
 
28
30
  <script dangerouslySetInnerHTML={{__html: `
29
- // Pass sessionId to terminal if provided
30
- window.terminalSessionId_${id.replace(/-/g, '_')} = '${sessionId || ''}';
31
+ // Pass terminal data to JavaScript
32
+ window.terminalData_${terminalId.replace(/-/g, '_')} = {
33
+ terminalId: '${terminalId}',
34
+ sessionId: '${sessionId}',
35
+ index: ${index}
36
+ };
31
37
  `}}></script>
32
38
  </sapling-island>
33
39
  );
@@ -13,21 +13,16 @@ export function Code({ previewPort = 1234, workingDirectory }: TerminalProps) {
13
13
  <div id="code-container" class="h-screen flex bg-[#1e1e1e]">
14
14
  {/* Left Pane - Tabbed Sidebar */}
15
15
  <div id="sidebar-pane" class="w-2/5-plus border-r border-[#3c3c3c] transition-all duration-300 flex flex-col bg-[#252526]">
16
- {/* Back to Home */}
17
- <div class="p-3 border-b border-[#3c3c3c]">
18
- <a href="/" class="text-[#cccccc] hover:text-white text-sm">← Back to Home</a>
19
- </div>
20
-
21
16
  {/* Preview Controls */}
22
17
  <div class="p-3 border-b border-[#3c3c3c] bg-[#2d2d30]">
23
18
  <div class="flex items-center gap-2">
24
- <button
25
- id="live-preview-hide-sidebar-btn"
26
- class="p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex items-center text-[#cccccc] hover:text-white"
27
- title="Hide Sidebar"
19
+ <a
20
+ href="/"
21
+ class="p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white"
22
+ title="Back to Home"
28
23
  >
29
- <iconify-icon id="live-preview-hide-sidebar-icon" icon="ph:sidebar-simple" width="16" height="16"></iconify-icon>
30
- </button>
24
+ <iconify-icon icon="tabler:arrow-left" width="16" height="16"></iconify-icon>
25
+ </a>
31
26
  <button
32
27
  type="button"
33
28
  id="live-preview-refresh-btn"
@@ -77,11 +72,80 @@ export function Code({ previewPort = 1234, workingDirectory }: TerminalProps) {
77
72
  {/* Tab Content */}
78
73
  <div class="flex-1 overflow-hidden bg-[#1e1e1e]">
79
74
  {/* Terminal Tab Content (Active) */}
80
- <div class="h-full p-4">
81
- <TerminalComponent
82
- id="terminal"
83
- sessionId="shared-terminal-session"
84
- />
75
+ <div class="h-full flex flex-col">
76
+
77
+ {/* Terminal Tabs Bar */}
78
+ <div class="border-b border-[#3c3c3c] bg-[#252526] px-3 py-1">
79
+ <div class="flex items-center gap-1">
80
+ {/* Terminal Tab 1 */}
81
+ <button
82
+ id="terminal-tab-1"
83
+ class="terminal-tab flex items-center px-3 py-1 text-sm text-white bg-[#1e1e1e] border-t-2 border-[#0e639c] rounded-t-sm hover:bg-[#2d2d30] transition-colors"
84
+ data-terminal-index="1"
85
+ >
86
+ <iconify-icon icon="tabler:terminal-2" width="14" height="14" class="mr-1"></iconify-icon>
87
+ Terminal 1
88
+ <button class="ml-2 hover:bg-[#3c3c3c] rounded p-0.5 text-[#cccccc] hover:text-white terminal-close-btn" data-terminal-index="1" style="display: none;">
89
+ <iconify-icon icon="tabler:x" width="12" height="12"></iconify-icon>
90
+ </button>
91
+ </button>
92
+
93
+ {/* Terminal Tab 2 */}
94
+ <button
95
+ id="terminal-tab-2"
96
+ class="terminal-tab flex items-center px-3 py-1 text-sm text-[#cccccc] hover:text-white hover:bg-[#2d2d30] transition-colors rounded-t-sm"
97
+ data-terminal-index="2"
98
+ style="display: none;"
99
+ >
100
+ <iconify-icon icon="tabler:terminal-2" width="14" height="14" class="mr-1"></iconify-icon>
101
+ Terminal 2
102
+ <button class="ml-2 hover:bg-[#3c3c3c] rounded p-0.5 text-[#cccccc] hover:text-white terminal-close-btn" data-terminal-index="2">
103
+ <iconify-icon icon="tabler:x" width="12" height="12"></iconify-icon>
104
+ </button>
105
+ </button>
106
+
107
+ {/* Terminal Tab 3 */}
108
+ <button
109
+ id="terminal-tab-3"
110
+ class="terminal-tab flex items-center px-3 py-1 text-sm text-[#cccccc] hover:text-white hover:bg-[#2d2d30] transition-colors rounded-t-sm"
111
+ data-terminal-index="3"
112
+ style="display: none;"
113
+ >
114
+ <iconify-icon icon="tabler:terminal-2" width="14" height="14" class="mr-1"></iconify-icon>
115
+ Terminal 3
116
+ <button class="ml-2 hover:bg-[#3c3c3c] rounded p-0.5 text-[#cccccc] hover:text-white terminal-close-btn" data-terminal-index="3">
117
+ <iconify-icon icon="tabler:x" width="12" height="12"></iconify-icon>
118
+ </button>
119
+ </button>
120
+
121
+ {/* Add Terminal Button */}
122
+ <button
123
+ id="add-terminal-btn"
124
+ class="flex items-center px-2 py-1 text-sm text-[#cccccc] hover:text-white hover:bg-[#2d2d30] transition-colors rounded"
125
+ title="New Terminal"
126
+ >
127
+ <iconify-icon icon="tabler:plus" width="14" height="14"></iconify-icon>
128
+ </button>
129
+ </div>
130
+ </div>
131
+
132
+ {/* Terminal Content Area */}
133
+ <div class="flex-1 overflow-hidden relative">
134
+ {/* Terminal 1 Container */}
135
+ <div id="terminal-container-1" class="h-full p-4 terminal-container" data-terminal-index="1">
136
+ <TerminalComponent index={1} />
137
+ </div>
138
+
139
+ {/* Terminal 2 Container */}
140
+ <div id="terminal-container-2" class="h-full p-4 terminal-container" data-terminal-index="2" style="display: none;">
141
+ <TerminalComponent index={2} />
142
+ </div>
143
+
144
+ {/* Terminal 3 Container */}
145
+ <div id="terminal-container-3" class="h-full p-4 terminal-container" data-terminal-index="3" style="display: none;">
146
+ <TerminalComponent index={3} />
147
+ </div>
148
+ </div>
85
149
  </div>
86
150
  </div>
87
151
  </div>
@@ -89,6 +153,9 @@ export function Code({ previewPort = 1234, workingDirectory }: TerminalProps) {
89
153
  {/* Right Pane - Live Preview */}
90
154
  <SimpleLivePreview id="live-preview" previewPort={previewPort} />
91
155
  </div>
156
+
157
+ {/* Terminal Tabs Management Script */}
158
+ <script type="module" src="/components/TerminalTabs.js"></script>
92
159
  </Layout>
93
160
  );
94
161
  }
@@ -3,28 +3,53 @@ import Layout from "../layouts/Layout.js";
3
3
  export function Welcome() {
4
4
  return (
5
5
  <Layout title="Welcome to Treesap">
6
- <div class="h-screen flex flex-col items-center justify-center bg-gray-50 p-8">
7
- <div class="text-center max-w-2xl">
8
- <h1 class="text-6xl font-bold text-gray-800 mb-4">🌳</h1>
9
- <h2 class="text-4xl font-bold text-gray-800 mb-6">Welcome to Treesap</h2>
10
- <p class="text-xl text-gray-600 mb-8">
11
- Your integrated development environment with terminal and live preview.
12
- </p>
13
-
14
- <div class="space-y-4">
15
- <a
16
- href="/code"
17
- class="inline-block bg-green-600 hover:bg-green-700 text-white font-semibold py-3 px-6 rounded-lg transition-colors shadow-lg border-2 border-green-600"
18
- style="background-color: #16a34a !important; color: white !important; text-decoration: none !important;"
19
- >
20
- Start Coding →
21
- </a>
22
-
23
- <div class="text-gray-500 text-sm">
24
- or navigate to <code class="bg-gray-200 px-2 py-1 rounded">/code</code> to begin
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>
25
16
  </div>
26
17
  </div>
27
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
+
28
53
  </div>
29
54
  </Layout>
30
55
  );
package/src/server.tsx CHANGED
@@ -235,6 +235,8 @@ export async function startServer(config: TreesapConfig & { autoStartDev?: boole
235
235
  controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
236
236
  } catch (error) {
237
237
  console.error('Error sending terminal output:', error);
238
+ // Remove listener on error to prevent memory leaks
239
+ session!.eventEmitter.removeListener('output', handleOutput);
238
240
  }
239
241
  };
240
242
 
@@ -242,7 +244,11 @@ export async function startServer(config: TreesapConfig & { autoStartDev?: boole
242
244
 
243
245
  // Handle client disconnect
244
246
  const handleDisconnect = () => {
245
- session!.eventEmitter.removeListener('output', handleOutput);
247
+ try {
248
+ session!.eventEmitter.removeListener('output', handleOutput);
249
+ } catch (error) {
250
+ console.error('Error removing output listener:', error);
251
+ }
246
252
  };
247
253
 
248
254
  // Clean up on stream close
@@ -262,6 +268,25 @@ export async function startServer(config: TreesapConfig & { autoStartDev?: boole
262
268
  });
263
269
  });
264
270
 
271
+ // Delete terminal session
272
+ app.delete("/terminal/session/:sessionId", async (c: Context) => {
273
+ const sessionId = c.req.param('sessionId');
274
+
275
+ if (!sessionId) {
276
+ return c.json({ error: "Session ID is required" }, 400);
277
+ }
278
+
279
+ const success = TerminalService.destroySession(sessionId);
280
+
281
+ if (success) {
282
+
283
+ return c.json({ message: `Terminal session ${sessionId} destroyed successfully` });
284
+ } else {
285
+ console.log(`Terminal session not found: ${sessionId}`);
286
+ return c.json({ error: `Terminal session ${sessionId} not found` }, 404);
287
+ }
288
+ });
289
+
265
290
  // Claude Code subprocess management
266
291
  let claudeCodeManager: DevServerManager | null = null;
267
292
 
@@ -19,6 +19,8 @@ export class TerminalService {
19
19
  this.destroySession(sessionId);
20
20
 
21
21
  const eventEmitter = new EventEmitter();
22
+ // Increase max listeners to handle multiple terminal tabs and connections
23
+ eventEmitter.setMaxListeners(20);
22
24
 
23
25
  // Create a PTY process for proper terminal behavior
24
26
  const ptyProcess = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : process.env.SHELL || '/bin/bash', [], {