glad-web 1.0.14 → 1.0.17
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/assets/logo.svg +32 -29
- package/lib/commands/web.js +13 -2
- package/lib/session/pty-manager.js +14 -0
- package/lib/session/session-manager.js +10 -0
- package/lib/web/index.html +53 -26
- package/package.json +1 -1
package/assets/logo.svg
CHANGED
|
@@ -1,40 +1,43 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
|
2
2
|
<defs>
|
|
3
|
+
<!-- Normcore / Cold Slate Background Gradient -->
|
|
3
4
|
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
-
<stop offset="0%" stop-color="#
|
|
5
|
-
<stop offset="
|
|
5
|
+
<stop offset="0%" stop-color="#1E2024" />
|
|
6
|
+
<stop offset="50%" stop-color="#111215" />
|
|
7
|
+
<stop offset="100%" stop-color="#050505" />
|
|
6
8
|
</linearGradient>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<stop offset="
|
|
9
|
+
|
|
10
|
+
<!-- Sunset Coral Gradient (Warm, energetic smile/tongue) -->
|
|
11
|
+
<linearGradient id="faceGrad" gradientUnits="userSpaceOnUse" x1="125" y1="230" x2="387" y2="310">
|
|
12
|
+
<stop offset="0%" stop-color="#FF9E79" />
|
|
13
|
+
<stop offset="60%" stop-color="#FF6F61" />
|
|
14
|
+
<stop offset="100%" stop-color="#D84315" />
|
|
11
15
|
</linearGradient>
|
|
12
16
|
</defs>
|
|
13
17
|
|
|
14
|
-
<!-- Background App Icon Shape -->
|
|
15
|
-
<rect
|
|
18
|
+
<!-- Background App Icon Shape (Cold basalt gradient, fills the icon canvas) -->
|
|
19
|
+
<rect width="512" height="512" fill="url(#bgGrad)" />
|
|
16
20
|
|
|
17
|
-
<!--
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<!-- Mobile screen top bar (notch/speaker) -->
|
|
21
|
-
<line x1="226" y1="96" x2="286" y2="96" stroke="url(#phoneGrad)" stroke-width="8" stroke-linecap="round" />
|
|
21
|
+
<!-- CLI Face (Expanded to fill the icon area, no phone container) -->
|
|
22
|
+
<!-- Left Eye: Terminal I-Beam Cursor (Bold & Centered) -->
|
|
23
|
+
<path d="M 145 101 H 175 M 160 101 V 151 M 145 151 H 175" fill="none" stroke="#007aff" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" />
|
|
22
24
|
|
|
23
|
-
<!--
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
<!-- Right Eye: Project's Send Button Triangle/Paper Plane (Bold & Centered) -->
|
|
26
|
+
<g transform="translate(352, 126) scale(2.4) translate(-12, -12)">
|
|
27
|
+
<line x1="22" y1="2" x2="11" y2="13" stroke="#007aff" stroke-width="5.0" stroke-linecap="round" stroke-linejoin="round" />
|
|
28
|
+
<polygon points="22 2 15 22 11 13 2 9 22 2" fill="none" stroke="#007aff" stroke-width="5.0" stroke-linecap="round" stroke-linejoin="round" />
|
|
29
|
+
</g>
|
|
28
30
|
|
|
29
|
-
<!--
|
|
30
|
-
<path d="M
|
|
31
|
+
<!-- Centered tongue-out mouth -->
|
|
32
|
+
<path d="M 125 230 H 387" fill="none" stroke="url(#faceGrad)" stroke-width="15" stroke-linecap="round" />
|
|
33
|
+
<path d="M 218 230 C 220 250 220 273 226 289 C 232 305 242 313 256 313 C 270 313 280 305 286 289 C 292 273 292 250 294 230" fill="none" stroke="url(#faceGrad)" stroke-width="15" stroke-linecap="round" stroke-linejoin="round" />
|
|
34
|
+
<path d="M 256 290 V 308" fill="none" stroke="url(#faceGrad)" stroke-width="6" stroke-linecap="round" />
|
|
31
35
|
|
|
32
|
-
<!--
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</svg>
|
|
36
|
+
<!-- Git Diff Lines (Expanded & Centered, no filter, 50% opacity for clear mobile legibility) -->
|
|
37
|
+
<!-- Add (+) Line: Faded Green -->
|
|
38
|
+
<line x1="130" y1="361" x2="382" y2="361" stroke="#2EA44F" stroke-width="14" stroke-linecap="round" opacity="0.5" />
|
|
39
|
+
<!-- Delete (-) Line: Faded Red -->
|
|
40
|
+
<line x1="130" y1="386" x2="300" y2="386" stroke="#CF222E" stroke-width="14" stroke-linecap="round" opacity="0.5" />
|
|
41
|
+
<!-- Add (+) Line: Faded Green -->
|
|
42
|
+
<line x1="130" y1="411" x2="350" y2="411" stroke="#2EA44F" stroke-width="14" stroke-linecap="round" opacity="0.5" />
|
|
43
|
+
</svg>
|
package/lib/commands/web.js
CHANGED
|
@@ -344,6 +344,8 @@ async function webCommand(options) {
|
|
|
344
344
|
|
|
345
345
|
ws.sessionId = sessionId;
|
|
346
346
|
const session = sessionManager.get(sessionId);
|
|
347
|
+
const isReconnect = session.hasConnectedWebClient;
|
|
348
|
+
session.hasConnectedWebClient = true;
|
|
347
349
|
if (!session.resizeOwner) {
|
|
348
350
|
session.resizeOwner = ws;
|
|
349
351
|
}
|
|
@@ -352,7 +354,11 @@ async function webCommand(options) {
|
|
|
352
354
|
// Send catchup output. TUI tools may skip the raw circular buffer, so fall
|
|
353
355
|
// back to the rendered/text history snapshot instead of reconnecting blank.
|
|
354
356
|
const catchup = sessionManager.getCatchupOutput(sessionId);
|
|
355
|
-
|
|
357
|
+
ws.needsTuiRedraw = ['antigravity', 'claude-code', 'codex', 'gemini'].includes(session.tool.key)
|
|
358
|
+
&& (isReconnect || (catchup && catchup.source === 'rendered-history'));
|
|
359
|
+
if (ws.needsTuiRedraw) {
|
|
360
|
+
ws.send(JSON.stringify({ type: 'reset' }));
|
|
361
|
+
} else if (catchup && catchup.data) {
|
|
356
362
|
sessionManager.logWsCatchupOutput(sessionId, catchup);
|
|
357
363
|
ws.send(JSON.stringify({ type: 'output', data: catchup.data }));
|
|
358
364
|
}
|
|
@@ -365,7 +371,12 @@ async function webCommand(options) {
|
|
|
365
371
|
}
|
|
366
372
|
if (payload.type === 'resize' && session.resizeOwner === ws) {
|
|
367
373
|
sessionManager.logWsResize(sessionId, payload.cols, payload.rows);
|
|
368
|
-
|
|
374
|
+
if (ws.needsTuiRedraw) {
|
|
375
|
+
ws.needsTuiRedraw = false;
|
|
376
|
+
sessionManager.redraw(sessionId, payload.cols, payload.rows);
|
|
377
|
+
} else {
|
|
378
|
+
sessionManager.resize(sessionId, payload.cols, payload.rows);
|
|
379
|
+
}
|
|
369
380
|
}
|
|
370
381
|
} catch (e) {
|
|
371
382
|
logger.error('WS Message Error: ' + e.message);
|
|
@@ -197,6 +197,20 @@ class PTYManager {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
redraw(cols, rows) {
|
|
201
|
+
if (!this.ptyProcess) return false;
|
|
202
|
+
|
|
203
|
+
const targetCols = Math.max(2, Number.parseInt(cols, 10) || 80);
|
|
204
|
+
const targetRows = Math.max(1, Number.parseInt(rows, 10) || 24);
|
|
205
|
+
const pulseCols = targetCols > 2 ? targetCols - 1 : targetCols + 1;
|
|
206
|
+
|
|
207
|
+
this.ptyProcess.resize(pulseCols, targetRows);
|
|
208
|
+
setTimeout(() => {
|
|
209
|
+
if (this.ptyProcess) this.ptyProcess.resize(targetCols, targetRows);
|
|
210
|
+
}, 30);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
200
214
|
setupLocalResizeListener() {
|
|
201
215
|
if (!process.stdout.isTTY) return;
|
|
202
216
|
this.localResizeListener = () => {
|
|
@@ -97,6 +97,7 @@ class SessionManager extends EventEmitter {
|
|
|
97
97
|
inputSeq: 0,
|
|
98
98
|
completionReadInputSeq: 0,
|
|
99
99
|
resizeOwner: null,
|
|
100
|
+
hasConnectedWebClient: false,
|
|
100
101
|
hasUnreadCompletion: false,
|
|
101
102
|
write: data => this.write(id, data),
|
|
102
103
|
isRunning: () => this.has(id) && ptyManager.isRunning(),
|
|
@@ -137,6 +138,15 @@ class SessionManager extends EventEmitter {
|
|
|
137
138
|
return true;
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
redraw(id, cols, rows) {
|
|
142
|
+
const session = this.get(id);
|
|
143
|
+
if (!session) return false;
|
|
144
|
+
if (session.renderedHistory) {
|
|
145
|
+
session.renderedHistory.resize(cols, rows);
|
|
146
|
+
}
|
|
147
|
+
return session.ptyManager.redraw(cols, rows);
|
|
148
|
+
}
|
|
149
|
+
|
|
140
150
|
rename(id, name) {
|
|
141
151
|
const session = this.get(id);
|
|
142
152
|
if (!session || !name) return null;
|
package/lib/web/index.html
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
#shortcut-rail::-webkit-scrollbar { display: none; }
|
|
53
53
|
.shortcut-group { flex: 0 0 calc(100vw - 28px); display: grid; gap: 8px; scroll-snap-align: start; }
|
|
54
54
|
.simple-shortcuts { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
55
|
-
.complex-shortcuts { grid-template-columns: repeat(
|
|
55
|
+
.complex-shortcuts { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
56
56
|
.key-btn, .command-key { flex: 0 0 auto; min-width: 0; height: 40px; border: 1px solid rgba(255,255,255,0.11); border-radius: 12px; background: rgba(255,255,255,0.08); color: #d1d5db; font-size: 12px; font-weight: 700; letter-spacing: 0; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; white-space: nowrap; }
|
|
57
57
|
.key-btn { font-size: 11px; }
|
|
58
58
|
.shortcut-group .key-btn, .shortcut-group .command-key { width: 100%; }
|
|
@@ -174,9 +174,10 @@
|
|
|
174
174
|
<div class="key-btn" data-key="esc">Esc</div>
|
|
175
175
|
<div class="key-btn" data-key="tab">Tab</div>
|
|
176
176
|
</div>
|
|
177
|
-
<div class="shortcut-group complex-shortcuts">
|
|
177
|
+
<div class="shortcut-group complex-shortcuts" data-role="tool-shortcuts">
|
|
178
|
+
<div class="key-btn special-key" data-key="left">←</div>
|
|
179
|
+
<div class="key-btn special-key" data-key="right">→</div>
|
|
178
180
|
<div class="key-btn" data-key="ctrl-c">Ctrl+C</div>
|
|
179
|
-
<div class="key-btn" data-key="ctrl-y" data-role="tool-action-shortcut" title="Send Ctrl+Y">Ctrl+Y</div>
|
|
180
181
|
<button class="command-key" data-command="/model" title="Send /model">/model</button>
|
|
181
182
|
<button class="command-key" data-command="/resume" title="Send /resume">/resume</button>
|
|
182
183
|
<button class="command-key" data-command="/stat" data-role="usage-shortcut" title="Send /stat">/stat</button>
|
|
@@ -831,28 +832,53 @@
|
|
|
831
832
|
}
|
|
832
833
|
|
|
833
834
|
function updateToolShortcuts(toolKey) {
|
|
834
|
-
const
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
835
|
+
const usesUsageCommand = ['antigravity', 'claude-code'].includes(toolKey);
|
|
836
|
+
const usageCommand = usesUsageCommand ? '/usage' : '/stat';
|
|
837
|
+
const defaultShortcuts = [
|
|
838
|
+
{ key: 'left', label: '←', special: true },
|
|
839
|
+
{ key: 'right', label: '→', special: true },
|
|
840
|
+
{ key: 'ctrl-c', label: 'Ctrl+C' },
|
|
841
|
+
{ command: '/model' },
|
|
842
|
+
{ command: '/resume' },
|
|
843
|
+
{ command: usageCommand }
|
|
844
|
+
];
|
|
845
|
+
const toolShortcuts = {
|
|
846
|
+
codex: [
|
|
847
|
+
{ key: 'ctrl-c', label: 'Ctrl+C' },
|
|
848
|
+
{ key: 'codex-perm', label: '/perm', title: 'Send /perm' },
|
|
849
|
+
{ command: '/model' },
|
|
850
|
+
{ command: '/resume' },
|
|
851
|
+
{ command: '/stat' }
|
|
852
|
+
],
|
|
853
|
+
gemini: [
|
|
854
|
+
{ key: 'ctrl-c', label: 'Ctrl+C' },
|
|
855
|
+
{ key: 'ctrl-y', label: 'Ctrl+Y', title: 'Send Ctrl+Y' },
|
|
856
|
+
{ command: '/model' },
|
|
857
|
+
{ command: '/resume' },
|
|
858
|
+
{ command: '/stat' }
|
|
859
|
+
]
|
|
860
|
+
};
|
|
861
|
+
const shortcuts = toolShortcuts[toolKey] || defaultShortcuts;
|
|
862
|
+
const shortcutGroup = document.querySelector('[data-role="tool-shortcuts"]');
|
|
863
|
+
if (!shortcutGroup) return;
|
|
864
|
+
|
|
865
|
+
shortcutGroup.style.gridTemplateColumns = `repeat(${shortcuts.length}, minmax(0, 1fr))`;
|
|
866
|
+
shortcutGroup.replaceChildren(...shortcuts.map(shortcut => {
|
|
867
|
+
const button = document.createElement(shortcut.command ? 'button' : 'div');
|
|
868
|
+
button.className = shortcut.command
|
|
869
|
+
? 'command-key'
|
|
870
|
+
: `key-btn${shortcut.special ? ' special-key' : ''}`;
|
|
871
|
+
if (shortcut.command) {
|
|
872
|
+
button.dataset.command = shortcut.command;
|
|
873
|
+
button.textContent = shortcut.command;
|
|
874
|
+
button.title = `Send ${shortcut.command}`;
|
|
875
|
+
} else {
|
|
876
|
+
button.dataset.key = shortcut.key;
|
|
877
|
+
button.textContent = shortcut.label;
|
|
878
|
+
button.title = shortcut.title || '';
|
|
879
|
+
}
|
|
880
|
+
return button;
|
|
881
|
+
}));
|
|
856
882
|
}
|
|
857
883
|
|
|
858
884
|
function joinSession(id, sessionName, toolKey = null) {
|
|
@@ -901,6 +927,7 @@
|
|
|
901
927
|
currentSocket.onmessage = (e) => {
|
|
902
928
|
const msg = JSON.parse(e.data);
|
|
903
929
|
if (msg.type === 'output') term.write(msg.data);
|
|
930
|
+
if (msg.type === 'reset') term.reset();
|
|
904
931
|
if (msg.type === 'exit') showLobby();
|
|
905
932
|
};
|
|
906
933
|
}
|
|
@@ -1153,7 +1180,7 @@
|
|
|
1153
1180
|
setTimeout(() => { sendWS('\r'); }, 1000);
|
|
1154
1181
|
return;
|
|
1155
1182
|
}
|
|
1156
|
-
const sequences = { up: '\x1b[A', down: '\x1b[B', enter: '\r', esc: '\x1b', tab: '\t', 'ctrl-c': '\x03', 'ctrl-y': '\x19' };
|
|
1183
|
+
const sequences = { up: '\x1b[A', down: '\x1b[B', left: '\x1b[D', right: '\x1b[C', enter: '\r', esc: '\x1b', tab: '\t', 'ctrl-c': '\x03', 'ctrl-y': '\x19' };
|
|
1157
1184
|
if (sequences[key]) sendWS(sequences[key]);
|
|
1158
1185
|
});
|
|
1159
1186
|
|