@aion0/forge 0.4.11 → 0.4.12
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.
- package/README.md +1 -1
- package/RELEASE_NOTES.md +7 -6
- package/app/api/code/route.ts +3 -2
- package/app/api/terminal-bell/route.ts +35 -0
- package/app/icon.svg +106 -0
- package/components/CodeViewer.tsx +4 -0
- package/components/ProjectDetail.tsx +4 -0
- package/components/WebTerminal.tsx +74 -0
- package/next-env.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
# Forge v0.4.
|
|
1
|
+
# Forge v0.4.12
|
|
2
2
|
|
|
3
3
|
Released: 2026-03-23
|
|
4
4
|
|
|
5
|
-
## Changes since v0.4.
|
|
5
|
+
## Changes since v0.4.11
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
- feat: terminal bell notification + Java/Scala file support
|
|
6
9
|
|
|
7
10
|
### Other
|
|
8
|
-
-
|
|
9
|
-
- init version for mobile page
|
|
10
|
-
- change sync period time
|
|
11
|
+
- support more dev language
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.
|
|
14
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.11...v0.4.12
|
package/app/api/code/route.ts
CHANGED
|
@@ -23,7 +23,8 @@ const CODE_EXTS = new Set([
|
|
|
23
23
|
'.css', '.scss', '.html', '.json', '.yaml', '.yml', '.toml',
|
|
24
24
|
'.md', '.txt', '.sh', '.bash', '.zsh', '.fish',
|
|
25
25
|
'.sql', '.graphql', '.proto', '.env', '.gitignore',
|
|
26
|
-
'.xml', '.csv', '.lock',
|
|
26
|
+
'.xml', '.csv', '.lock', '.properties', '.gradle', '.groovy',
|
|
27
|
+
'.scala', '.clj', '.cljs', '.jsp', '.erb', '.vue', '.svelte',
|
|
27
28
|
]);
|
|
28
29
|
|
|
29
30
|
const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.bmp', '.ico', '.avif']);
|
|
@@ -36,7 +37,7 @@ function isCodeFile(name: string): boolean {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
function scanDir(dir: string, base: string, depth: number = 0): FileNode[] {
|
|
39
|
-
if (depth >
|
|
40
|
+
if (depth > 10) return [];
|
|
40
41
|
try {
|
|
41
42
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
42
43
|
const nodes: FileNode[] = [];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { loadSettings } from '@/lib/settings';
|
|
3
|
+
import { addNotification } from '@/lib/notifications';
|
|
4
|
+
|
|
5
|
+
export async function POST(req: Request) {
|
|
6
|
+
const { tabLabel } = await req.json();
|
|
7
|
+
|
|
8
|
+
const label = tabLabel || 'Terminal';
|
|
9
|
+
|
|
10
|
+
// In-app notification
|
|
11
|
+
try {
|
|
12
|
+
addNotification('terminal_bell', `Terminal idle: ${label}`, `Claude appears to have finished in "${label}".`);
|
|
13
|
+
} catch {}
|
|
14
|
+
|
|
15
|
+
// Telegram notification
|
|
16
|
+
const settings = loadSettings();
|
|
17
|
+
const { telegramBotToken, telegramChatId } = settings;
|
|
18
|
+
if (telegramBotToken && telegramChatId) {
|
|
19
|
+
try {
|
|
20
|
+
const chatIds = String(telegramChatId).split(',').map(s => s.trim()).filter(Boolean);
|
|
21
|
+
for (const chatId of chatIds) {
|
|
22
|
+
await fetch(`https://api.telegram.org/bot${telegramBotToken}/sendMessage`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'Content-Type': 'application/json' },
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
chat_id: chatId,
|
|
27
|
+
text: `🔔 Forge — Terminal idle\n\n"${label}" appears to have finished.`,
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return NextResponse.json({ ok: true });
|
|
35
|
+
}
|
package/app/icon.svg
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
|
3
|
+
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
4
|
+
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="1050.000000pt" height="1102.000000pt" viewBox="0 0 1050.000000 1102.000000"
|
|
6
|
+
preserveAspectRatio="xMidYMid meet">
|
|
7
|
+
|
|
8
|
+
<g transform="translate(0.000000,1102.000000) scale(0.100000,-0.100000)"
|
|
9
|
+
fill="#000000" stroke="none">
|
|
10
|
+
<path d="M0 5510 l0 -5510 5250 0 5250 0 0 5510 0 5510 -5250 0 -5250 0 0
|
|
11
|
+
-5510z m2410 3369 c37 -18 164 -120 395 -319 584 -503 1025 -883 1101 -946 39
|
|
12
|
+
-34 111 -96 159 -138 52 -45 96 -76 109 -76 12 0 233 45 492 99 258 55 503
|
|
13
|
+
106 544 115 67 13 83 14 137 1 74 -17 147 -33 398 -85 105 -21 233 -48 285
|
|
14
|
+
-60 237 -52 327 -70 350 -70 25 0 127 84 655 541 61 52 162 139 225 193 63 54
|
|
15
|
+
171 147 240 207 475 410 597 512 643 536 74 38 198 44 279 13 66 -24 145 -92
|
|
16
|
+
181 -153 60 -101 57 -27 57 -1656 l0 -1492 54 -217 c57 -224 150 -590 185
|
|
17
|
+
-722 10 -41 38 -147 62 -235 23 -88 47 -185 54 -215 7 -30 16 -67 20 -82 6
|
|
18
|
+
-25 5 -28 -19 -28 -15 0 -29 6 -32 14 -4 11 -13 12 -35 6 -16 -5 -68 -12 -114
|
|
19
|
+
-15 l-84 -7 -6 29 c-4 15 -9 35 -12 43 -2 8 -29 110 -58 225 -30 116 -75 289
|
|
20
|
+
-100 385 -25 96 -81 316 -125 488 l-80 313 0 1495 c0 1664 5 1554 -65 1554
|
|
21
|
+
-29 0 -49 -13 -123 -77 -88 -77 -479 -412 -617 -528 -40 -33 -213 -181 -385
|
|
22
|
+
-330 -173 -148 -399 -343 -504 -432 -121 -105 -197 -163 -211 -163 -21 0 -115
|
|
23
|
+
19 -525 106 -204 43 -302 64 -500 105 l-155 32 -115 -23 c-63 -12 -155 -31
|
|
24
|
+
-205 -42 -49 -11 -133 -28 -185 -39 -52 -11 -124 -27 -160 -34 -259 -56 -505
|
|
25
|
+
-105 -526 -105 -17 0 -66 36 -162 119 -341 296 -862 743 -1486 1275 -166 142
|
|
26
|
+
-194 156 -230 120 -9 -8 -17 -16 -18 -17 -2 -1 -5 -688 -8 -1527 l-5 -1525
|
|
27
|
+
-53 -195 c-28 -107 -79 -298 -111 -425 -33 -126 -72 -275 -86 -330 -15 -55
|
|
28
|
+
-46 -177 -70 -271 -64 -248 -50 -226 -142 -219 -129 10 -144 11 -149 3 -8 -13
|
|
29
|
+
-54 -9 -54 5 0 6 5 28 11 47 6 19 17 60 24 90 11 50 115 454 165 645 12 44 56
|
|
30
|
+
213 98 375 l77 295 5 1540 5 1540 33 67 c40 81 117 155 193 186 78 31 202 27
|
|
31
|
+
279 -9z m560 -1324 c0 -85 0 -85 -26 -85 -36 0 -81 -29 -94 -59 -6 -14 -14
|
|
32
|
+
-112 -19 -217 -9 -212 -21 -257 -83 -309 l-30 -25 40 -44 c58 -63 73 -120 78
|
|
33
|
+
-301 2 -82 4 -160 4 -173 0 -37 52 -82 94 -82 l36 0 0 -85 0 -85 -47 0 c-107
|
|
34
|
+
1 -190 39 -237 111 -42 63 -56 137 -56 294 0 77 -3 157 -6 177 -9 50 -43 85
|
|
35
|
+
-98 100 l-46 12 0 78 c0 77 0 78 28 84 111 24 115 34 123 268 6 204 13 236 58
|
|
36
|
+
307 45 72 133 113 254 118 l27 1 0 -85z m4810 42 c117 -58 150 -147 150 -406
|
|
37
|
+
0 -192 17 -229 113 -246 l38 -7 -3 -76 -3 -75 -49 -17 c-84 -28 -89 -41 -97
|
|
38
|
+
-254 -4 -101 -10 -199 -14 -218 -17 -79 -79 -154 -153 -185 -19 -8 -66 -15
|
|
39
|
+
-106 -16 l-71 -2 0 80 0 79 45 8 c81 13 86 27 93 239 7 208 18 258 73 316 19
|
|
40
|
+
20 34 37 34 38 0 1 -16 19 -35 40 -52 57 -65 109 -66 254 0 196 -10 259 -43
|
|
41
|
+
287 -15 13 -45 27 -67 30 l-39 7 0 77 c0 54 4 80 13 83 32 12 126 -6 187 -36z
|
|
42
|
+
m-3638 -2314 c233 -236 490 -501 596 -615 72 -77 72 -77 72 -133 l0 -57 -177
|
|
43
|
+
-181 c-455 -467 -688 -697 -710 -697 -23 0 -198 181 -198 206 0 7 159 173 354
|
|
44
|
+
369 195 195 353 357 350 360 -17 18 -186 194 -424 438 -154 159 -282 293 -284
|
|
45
|
+
299 -3 10 197 218 210 218 3 0 98 -93 211 -207z m2473 -1633 l0 -145 -95 -6
|
|
46
|
+
c-121 -9 -1077 -8 -1320 0 l-185 6 -3 134 c-2 90 1 138 9 147 10 12 136 14
|
|
47
|
+
803 12 l791 -3 0 -145z m-4912 -141 c21 -35 42 -73 48 -84 22 -47 71 -129 86
|
|
48
|
+
-143 14 -14 16 -14 25 1 5 9 14 17 20 17 7 0 6 -5 -2 -15 -6 -8 -9 -19 -5 -26
|
|
49
|
+
4 -6 2 -15 -5 -19 -9 -5 -5 -20 13 -56 13 -27 28 -59 32 -72 4 -13 25 -55 47
|
|
50
|
+
-93 38 -65 44 -106 16 -97 -11 4 -40 53 -160 278 -66 123 -173 337 -188 374
|
|
51
|
+
-10 28 -10 29 12 14 13 -9 40 -44 61 -79z m7221 54 c-4 -16 -28 -68 -54 -118
|
|
52
|
+
-26 -49 -80 -153 -120 -230 -40 -77 -90 -170 -112 -207 -21 -36 -38 -71 -38
|
|
53
|
+
-77 0 -13 -27 -15 -34 -2 -3 5 8 41 24 79 17 39 30 78 30 87 0 9 5 13 11 9 8
|
|
54
|
+
-4 9 1 4 17 -4 13 -4 21 0 17 5 -4 14 0 21 10 9 13 10 20 1 31 -10 12 -9 14 4
|
|
55
|
+
9 17 -7 40 30 132 212 74 146 149 240 131 163z m-284 -35 c-7 -18 -33 -75 -57
|
|
56
|
+
-126 -24 -51 -43 -99 -43 -106 0 -7 -7 -19 -15 -26 -9 -7 -13 -20 -10 -30 3
|
|
57
|
+
-10 -5 -26 -21 -41 -14 -13 -46 -59 -71 -101 -30 -53 -49 -75 -59 -71 -8 3
|
|
58
|
+
-14 9 -14 13 0 28 233 461 252 468 4 2 8 8 8 13 0 11 33 47 39 43 2 -2 -2 -18
|
|
59
|
+
-9 -36z m-6543 -260 c99 -190 119 -235 106 -241 -18 -11 -8 -24 -87 113 -87
|
|
60
|
+
148 -108 179 -124 180 -14 0 -36 49 -27 58 3 4 8 -4 12 -16 3 -12 10 -22 15
|
|
61
|
+
-22 5 0 0 17 -11 38 -28 52 -51 111 -51 132 0 10 -5 20 -12 22 -7 3 -8 9 -3
|
|
62
|
+
18 15 24 52 -34 182 -282z m-231 54 c-18 -18 -29 -12 -20 11 3 9 12 13 21 10
|
|
63
|
+
13 -5 13 -8 -1 -21z m6779 -162 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 2
|
|
64
|
+
10 4 10 3 0 8 -4 11 -10z m-751 -568 c-22 -19 -66 -52 -99 -75 -33 -22 -99
|
|
65
|
+
-69 -146 -103 -48 -35 -89 -64 -93 -64 -3 0 -14 -7 -23 -16 -17 -14 -69 -51
|
|
66
|
+
-248 -174 -36 -25 -107 -74 -158 -110 -51 -36 -100 -69 -109 -75 -8 -5 -43
|
|
67
|
+
-30 -78 -55 -34 -25 -75 -53 -92 -62 -17 -9 -34 -21 -37 -25 -15 -20 -72 -53
|
|
68
|
+
-82 -47 -12 7 48 63 69 64 6 0 12 4 12 8 0 5 33 31 72 58 40 27 77 53 83 57
|
|
69
|
+
16 12 252 181 414 295 73 52 180 126 205 142 27 17 67 46 204 147 62 45 106
|
|
70
|
+
71 121 70 22 -1 21 -4 -15 -35z m-5149 -18 c5 -5 30 -23 54 -39 25 -15 50 -32
|
|
71
|
+
55 -37 6 -5 65 -47 131 -93 66 -46 125 -88 130 -92 6 -4 30 -21 55 -37 25 -16
|
|
72
|
+
72 -50 105 -75 58 -45 100 -75 134 -96 9 -5 32 -22 51 -37 20 -14 71 -50 115
|
|
73
|
+
-79 44 -29 97 -71 119 -93 21 -21 36 -34 32 -28 -12 20 10 13 36 -10 14 -13
|
|
74
|
+
35 -30 47 -38 11 -8 21 -20 21 -28 0 -12 -27 2 -75 40 -5 5 -53 37 -105 71
|
|
75
|
+
-134 89 -261 177 -285 197 -11 10 -74 54 -140 99 -66 45 -147 102 -180 126
|
|
76
|
+
-70 52 -161 116 -183 130 -12 7 -140 100 -181 131 -1 1 0 7 3 13 7 11 39 -2
|
|
77
|
+
61 -25z m5388 -81 c-5 -12 -3 -14 8 -8 68 39 -178 -142 -435 -320 -154 -106
|
|
78
|
+
-291 -203 -384 -270 -53 -39 -135 -96 -182 -127 -102 -69 -351 -246 -383 -273
|
|
79
|
+
-52 -44 -168 -104 -167 -88 1 11 75 67 195 148 96 65 153 106 288 206 34 25
|
|
80
|
+
107 76 162 114 93 63 384 273 467 337 20 15 39 28 43 28 3 0 45 29 93 64 78
|
|
81
|
+
57 294 206 299 206 1 0 -1 -7 -4 -17z m-5615 -47 c71 -47 139 -94 159 -111 6
|
|
82
|
+
-5 49 -36 95 -68 46 -32 162 -114 258 -183 96 -69 231 -164 300 -211 122 -85
|
|
83
|
+
260 -185 290 -210 8 -7 24 -19 35 -27 11 -8 17 -17 13 -21 -7 -8 -42 10 -84
|
|
84
|
+
42 -16 12 -76 54 -134 93 -58 39 -143 98 -190 131 -47 33 -96 68 -110 77 -26
|
|
85
|
+
17 -177 125 -195 139 -5 4 -30 21 -55 38 -25 16 -95 66 -156 111 -61 45 -122
|
|
86
|
+
87 -135 94 -25 14 -113 79 -140 105 -9 8 -20 15 -26 15 -5 0 -10 7 -10 15 0
|
|
87
|
+
23 13 19 85 -29z m1347 -667 c11 -17 -1 -21 -15 -4 -8 9 -8 15 -2 15 6 0 14
|
|
88
|
+
-5 17 -11z m2850 1 c10 -16 -258 -206 -276 -195 -15 10 0 25 19 18 9 -3 14 -3
|
|
89
|
+
10 2 -9 9 26 42 53 50 11 4 19 11 19 17 0 6 3 8 7 5 3 -4 16 5 29 18 13 14 27
|
|
90
|
+
25 31 26 4 0 28 15 53 34 53 40 47 37 55 25z m-2795 -49 c-1 -15 -2 -15 -13 0
|
|
91
|
+
-7 9 -19 14 -27 12 -13 -4 -13 -3 -1 5 19 13 41 4 41 -17z m178 -117 c34 -27
|
|
92
|
+
37 -39 8 -28 -9 3 -14 10 -11 14 3 4 -8 10 -23 13 -15 4 -40 19 -57 34 -16 15
|
|
93
|
+
-43 35 -58 43 -16 8 -24 16 -18 18 6 2 8 8 5 14 -7 11 99 -63 154 -108z m-190
|
|
94
|
+
-152 c62 -43 76 -62 47 -62 -8 0 -15 3 -15 8 0 4 -15 13 -32 21 -18 8 -35 17
|
|
95
|
+
-38 20 -3 3 -27 22 -55 40 -27 19 -52 37 -55 41 -3 3 -24 18 -47 33 -25 18
|
|
96
|
+
-40 34 -36 43 5 13 36 -6 231 -144z m452 28 c0 -5 -15 -10 -34 -10 -19 0 -38
|
|
97
|
+
5 -41 10 -4 6 10 10 34 10 23 0 41 -4 41 -10z m685 5 c-25 -8 -675 -12 -675
|
|
98
|
+
-4 0 5 141 9 343 8 188 0 338 -2 332 -4z m705 -5 c-96 -3 -249 -3 -340 0 -113
|
|
99
|
+
4 -58 6 175 6 246 0 291 -2 165 -6z m263 3 c-7 -2 -21 -2 -30 0 -10 3 -4 5 12
|
|
100
|
+
5 17 0 24 -2 18 -5z m165 0 c-21 -2 -57 -2 -80 0 -24 2 -7 4 37 4 44 0 63 -2
|
|
101
|
+
43 -4z m401 -119 c-6 -7 -15 -12 -19 -9 -5 3 -11 1 -15 -5 -4 -6 -11 -7 -17
|
|
102
|
+
-4 -5 4 2 14 18 25 30 20 52 15 33 -7z m-134 -84 c3 -5 -3 -10 -15 -10 -12 0
|
|
103
|
+
-18 5 -15 10 3 6 10 10 15 10 5 0 12 -4 15 -10z m-71 -39 c3 -5 -11 -7 -32 -4
|
|
104
|
+
-49 7 -54 13 -9 13 20 0 38 -4 41 -9z"/>
|
|
105
|
+
</g>
|
|
106
|
+
</svg>
|
|
@@ -112,6 +112,10 @@ const KEYWORDS = new Set([
|
|
|
112
112
|
'true', 'false', 'null', 'undefined', 'void',
|
|
113
113
|
'def', 'self', 'None', 'True', 'False', 'class', 'lambda', 'with', 'as', 'in', 'not', 'and', 'or',
|
|
114
114
|
'func', 'package', 'struct', 'go', 'defer', 'select', 'chan', 'map', 'range',
|
|
115
|
+
'final', 'synchronized', 'volatile', 'transient', 'native',
|
|
116
|
+
'throws', 'int', 'long', 'double', 'float', 'char', 'byte', 'short', 'boolean',
|
|
117
|
+
'override',
|
|
118
|
+
'val', 'object', 'trait', 'sealed', 'implicit', 'lazy', 'match',
|
|
115
119
|
]);
|
|
116
120
|
|
|
117
121
|
function highlightLine(line: string, lang: string): React.ReactNode {
|
|
@@ -13,6 +13,10 @@ const KEYWORDS = new Set([
|
|
|
13
13
|
'public', 'private', 'protected', 'static', 'abstract',
|
|
14
14
|
'true', 'false', 'null', 'undefined', 'void',
|
|
15
15
|
'def', 'self', 'None', 'True', 'False', 'lambda', 'with', 'as', 'in', 'not', 'and', 'or',
|
|
16
|
+
'package', 'final', 'synchronized', 'volatile', 'transient', 'native',
|
|
17
|
+
'throws', 'int', 'long', 'double', 'float', 'char', 'byte', 'short', 'boolean',
|
|
18
|
+
'override', 'struct', 'func', 'go', 'defer', 'select', 'chan', 'range',
|
|
19
|
+
'val', 'var', 'def', 'object', 'trait', 'sealed', 'implicit', 'lazy', 'match',
|
|
16
20
|
]);
|
|
17
21
|
|
|
18
22
|
function highlightLine(line: string): React.ReactNode {
|
|
@@ -37,6 +37,7 @@ interface TabState {
|
|
|
37
37
|
ratios: Record<number, number>;
|
|
38
38
|
activeId: number;
|
|
39
39
|
projectPath?: string;
|
|
40
|
+
bellEnabled?: boolean;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
// ─── Layout persistence ──────────────────────────────────────
|
|
@@ -145,6 +146,11 @@ function firstTerminalId(n: SplitNode): number {
|
|
|
145
146
|
return n.type === 'terminal' ? n.id : firstTerminalId(n.first);
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
function collectPaneIds(tree: SplitNode): number[] {
|
|
150
|
+
if (tree.type === 'terminal') return [tree.id];
|
|
151
|
+
return [...collectPaneIds(tree.first), ...collectPaneIds(tree.second)];
|
|
152
|
+
}
|
|
153
|
+
|
|
148
154
|
function collectSessionNames(tree: SplitNode): string[] {
|
|
149
155
|
if (tree.type === 'terminal') return tree.sessionName ? [tree.sessionName] : [];
|
|
150
156
|
return [...collectSessionNames(tree.first), ...collectSessionNames(tree.second)];
|
|
@@ -158,6 +164,34 @@ function collectAllSessionNames(tabs: TabState[]): string[] {
|
|
|
158
164
|
|
|
159
165
|
const pendingCommands = new Map<number, string>();
|
|
160
166
|
|
|
167
|
+
// ─── Bell notification tracking ─────────────────────────────
|
|
168
|
+
|
|
169
|
+
const bellEnabledPanes = new Set<number>();
|
|
170
|
+
const bellPaneLabels = new Map<number, string>();
|
|
171
|
+
const bellLastFired = new Map<number, number>(); // paneId -> timestamp
|
|
172
|
+
const BELL_COOLDOWN = 30000; // 30s cooldown between bells
|
|
173
|
+
|
|
174
|
+
function fireBellNotification(paneId: number) {
|
|
175
|
+
const now = Date.now();
|
|
176
|
+
const last = bellLastFired.get(paneId) || 0;
|
|
177
|
+
if (now - last < BELL_COOLDOWN) return;
|
|
178
|
+
bellLastFired.set(paneId, now);
|
|
179
|
+
|
|
180
|
+
const label = bellPaneLabels.get(paneId) || 'Terminal';
|
|
181
|
+
|
|
182
|
+
// Browser notification
|
|
183
|
+
if (typeof Notification !== 'undefined' && Notification.permission === 'granted') {
|
|
184
|
+
new Notification('Forge — Terminal Idle', { body: `"${label}" appears to have finished.`, icon: '/icon.png' });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Telegram + in-app via API
|
|
188
|
+
fetch('/api/terminal-bell', {
|
|
189
|
+
method: 'POST',
|
|
190
|
+
headers: { 'Content-Type': 'application/json' },
|
|
191
|
+
body: JSON.stringify({ tabLabel: label }),
|
|
192
|
+
}).catch(() => {});
|
|
193
|
+
}
|
|
194
|
+
|
|
161
195
|
// ─── Global drag lock — suppress terminal fit() during split drag ──
|
|
162
196
|
|
|
163
197
|
let globalDragging = false;
|
|
@@ -498,6 +532,29 @@ const WebTerminal = forwardRef<WebTerminalHandle, WebTerminalProps>(function Web
|
|
|
498
532
|
|
|
499
533
|
const usedSessions = collectAllSessionNames(tabs);
|
|
500
534
|
|
|
535
|
+
// Toggle bell for a tab
|
|
536
|
+
const toggleBell = useCallback((tabId: number) => {
|
|
537
|
+
if (typeof Notification !== 'undefined' && Notification.permission === 'default') {
|
|
538
|
+
Notification.requestPermission();
|
|
539
|
+
}
|
|
540
|
+
setTabs(prev => prev.map(t => t.id === tabId ? { ...t, bellEnabled: !t.bellEnabled } : t));
|
|
541
|
+
}, []);
|
|
542
|
+
|
|
543
|
+
// Sync bell state to module-level sets for MemoTerminalPane to read
|
|
544
|
+
useEffect(() => {
|
|
545
|
+
bellEnabledPanes.clear();
|
|
546
|
+
bellPaneLabels.clear();
|
|
547
|
+
for (const tab of tabs) {
|
|
548
|
+
if (tab.bellEnabled) {
|
|
549
|
+
const paneIds = collectPaneIds(tab.tree);
|
|
550
|
+
for (const id of paneIds) {
|
|
551
|
+
bellEnabledPanes.add(id);
|
|
552
|
+
bellPaneLabels.set(id, tab.label);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}, [tabs]);
|
|
557
|
+
|
|
501
558
|
// Auto-refresh tmux sessions periodically to show detached count
|
|
502
559
|
useEffect(() => {
|
|
503
560
|
if (!hydrated) return;
|
|
@@ -570,6 +627,11 @@ const WebTerminal = forwardRef<WebTerminalHandle, WebTerminalProps>(function Web
|
|
|
570
627
|
{tab.label}
|
|
571
628
|
</span>
|
|
572
629
|
)}
|
|
630
|
+
<button
|
|
631
|
+
onClick={(e) => { e.stopPropagation(); toggleBell(tab.id); }}
|
|
632
|
+
className={`text-[10px] ml-1 ${tab.bellEnabled ? 'text-yellow-400' : 'text-gray-600 hover:text-gray-400'}`}
|
|
633
|
+
title={tab.bellEnabled ? 'Disable notification' : 'Enable notification when idle'}
|
|
634
|
+
>{tab.bellEnabled ? '🔔' : '🔕'}</button>
|
|
573
635
|
{tabs.length > 1 && (
|
|
574
636
|
<button
|
|
575
637
|
onClick={(e) => { e.stopPropagation(); closeTab(tab.id); }}
|
|
@@ -1058,6 +1120,8 @@ const MemoTerminalPane = memo(function TerminalPane({
|
|
|
1058
1120
|
if (!containerRef.current) return;
|
|
1059
1121
|
|
|
1060
1122
|
let disposed = false; // guard against post-cleanup writes (React Strict Mode)
|
|
1123
|
+
let bellIdleTimer = 0;
|
|
1124
|
+
let bellActivityBytes = 0;
|
|
1061
1125
|
|
|
1062
1126
|
// Read terminal theme from CSS variables
|
|
1063
1127
|
const cs = getComputedStyle(document.documentElement);
|
|
@@ -1194,6 +1258,15 @@ const MemoTerminalPane = memo(function TerminalPane({
|
|
|
1194
1258
|
const msg = JSON.parse(event.data);
|
|
1195
1259
|
if (msg.type === 'output') {
|
|
1196
1260
|
try { term.write(msg.data); } catch {};
|
|
1261
|
+
// Bell idle detection
|
|
1262
|
+
bellActivityBytes += (msg.data as string).length;
|
|
1263
|
+
clearTimeout(bellIdleTimer);
|
|
1264
|
+
if (bellActivityBytes >= 200 && bellEnabledPanes.has(id)) {
|
|
1265
|
+
bellIdleTimer = window.setTimeout(() => {
|
|
1266
|
+
bellActivityBytes = 0;
|
|
1267
|
+
fireBellNotification(id);
|
|
1268
|
+
}, 8000);
|
|
1269
|
+
}
|
|
1197
1270
|
} else if (msg.type === 'connected') {
|
|
1198
1271
|
connectedSession = msg.sessionName;
|
|
1199
1272
|
createRetries = 0;
|
|
@@ -1342,6 +1415,7 @@ const MemoTerminalPane = memo(function TerminalPane({
|
|
|
1342
1415
|
visObserver.disconnect();
|
|
1343
1416
|
clearTimeout(resizeTimer);
|
|
1344
1417
|
clearTimeout(reconnectTimer);
|
|
1418
|
+
clearTimeout(bellIdleTimer);
|
|
1345
1419
|
window.removeEventListener('terminal-drag-end', onDragEnd);
|
|
1346
1420
|
resizeObserver.disconnect();
|
|
1347
1421
|
// Strict Mode cleanup: if disposed within 2s of mount and we created a
|
package/next-env.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/types/routes.d.ts";
|
|
3
|
+
import "./.next/dev/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
package/package.json
CHANGED