glad-web 1.0.13 → 1.0.15
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 +2 -1
- package/README.zh-CN.md +2 -1
- package/lib/ai-tools/registry.js +9 -0
- package/lib/commands/web.js +18 -6
- package/lib/session/pty-manager.js +15 -1
- package/lib/session/session-manager.js +48 -0
- package/lib/web/index.html +54 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ glad tools detect
|
|
|
122
122
|
|
|
123
123
|
## Supported Tools
|
|
124
124
|
|
|
125
|
-
Glad currently auto-detects the
|
|
125
|
+
Glad currently auto-detects the 21 terminal AI tools defined in the code registry. The names below are the registry `displayName` values used by Glad:
|
|
126
126
|
|
|
127
127
|
| Tool | Detected command |
|
|
128
128
|
| --- | --- |
|
|
@@ -132,6 +132,7 @@ Glad currently auto-detects the 20 terminal AI tools defined in the code registr
|
|
|
132
132
|
| Copilot | `copilot` |
|
|
133
133
|
| Cody | `cody chat` |
|
|
134
134
|
| Gemini | `gemini` |
|
|
135
|
+
| Antigravity | `agy` |
|
|
135
136
|
| Continue | `cn` |
|
|
136
137
|
| Cursor | `cursor-agent` |
|
|
137
138
|
| ChatGPT | `chatgpt` |
|
package/README.zh-CN.md
CHANGED
|
@@ -122,7 +122,7 @@ glad tools detect
|
|
|
122
122
|
|
|
123
123
|
## 支持的工具
|
|
124
124
|
|
|
125
|
-
Glad 当前会自动检测代码注册表中定义的
|
|
125
|
+
Glad 当前会自动检测代码注册表中定义的 21 个终端 AI 工具。下面的名称严格使用 Glad 注册表里的 `displayName`:
|
|
126
126
|
|
|
127
127
|
| 工具 | 检测命令 |
|
|
128
128
|
| --- | --- |
|
|
@@ -132,6 +132,7 @@ Glad 当前会自动检测代码注册表中定义的 20 个终端 AI 工具。
|
|
|
132
132
|
| Copilot | `copilot` |
|
|
133
133
|
| Cody | `cody chat` |
|
|
134
134
|
| Gemini | `gemini` |
|
|
135
|
+
| Antigravity | `agy` |
|
|
135
136
|
| Continue | `cn` |
|
|
136
137
|
| Cursor | `cursor-agent` |
|
|
137
138
|
| ChatGPT | `chatgpt` |
|
package/lib/ai-tools/registry.js
CHANGED
|
@@ -68,6 +68,15 @@ const AI_TOOLS = {
|
|
|
68
68
|
website: 'https://developers.google.com/gemini-code-assist',
|
|
69
69
|
checkInstalled: async () => await commandExists('gemini')
|
|
70
70
|
},
|
|
71
|
+
'antigravity': {
|
|
72
|
+
key: 'antigravity',
|
|
73
|
+
command: 'agy',
|
|
74
|
+
args: [],
|
|
75
|
+
displayName: 'Antigravity',
|
|
76
|
+
description: 'Google Antigravity CLI for local agentic coding workflows',
|
|
77
|
+
website: 'https://antigravity.google/docs/cli-overview',
|
|
78
|
+
checkInstalled: async () => await commandExists('agy')
|
|
79
|
+
},
|
|
71
80
|
'continue': {
|
|
72
81
|
key: 'continue',
|
|
73
82
|
command: 'cn',
|
package/lib/commands/web.js
CHANGED
|
@@ -344,16 +344,23 @@ 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
|
}
|
|
350
352
|
sessionManager.logWsConnected(sessionId, req);
|
|
351
353
|
|
|
352
|
-
// Send catchup buffer
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
354
|
+
// Send catchup output. TUI tools may skip the raw circular buffer, so fall
|
|
355
|
+
// back to the rendered/text history snapshot instead of reconnecting blank.
|
|
356
|
+
const catchup = sessionManager.getCatchupOutput(sessionId);
|
|
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) {
|
|
362
|
+
sessionManager.logWsCatchupOutput(sessionId, catchup);
|
|
363
|
+
ws.send(JSON.stringify({ type: 'output', data: catchup.data }));
|
|
357
364
|
}
|
|
358
365
|
|
|
359
366
|
ws.on('message', (message) => {
|
|
@@ -364,7 +371,12 @@ async function webCommand(options) {
|
|
|
364
371
|
}
|
|
365
372
|
if (payload.type === 'resize' && session.resizeOwner === ws) {
|
|
366
373
|
sessionManager.logWsResize(sessionId, payload.cols, payload.rows);
|
|
367
|
-
|
|
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
|
+
}
|
|
368
380
|
}
|
|
369
381
|
} catch (e) {
|
|
370
382
|
logger.error('WS Message Error: ' + e.message);
|
|
@@ -23,7 +23,7 @@ class PTYManager {
|
|
|
23
23
|
this.onExitCallback = null;
|
|
24
24
|
this.localResizeListener = null;
|
|
25
25
|
|
|
26
|
-
this.tuiTools = ['opencode', 'kilo'];
|
|
26
|
+
this.tuiTools = ['antigravity', 'opencode', 'kilo'];
|
|
27
27
|
this.isTUIMode = this.tuiTools.includes(tool.key);
|
|
28
28
|
|
|
29
29
|
this.recentOutputs = [];
|
|
@@ -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;
|
|
@@ -192,6 +202,33 @@ class SessionManager extends EventEmitter {
|
|
|
192
202
|
};
|
|
193
203
|
}
|
|
194
204
|
|
|
205
|
+
getCatchupOutput(id) {
|
|
206
|
+
const session = this.get(id);
|
|
207
|
+
if (!session) return null;
|
|
208
|
+
|
|
209
|
+
const bufferHistory = session.buffer.getAfter(0);
|
|
210
|
+
if (bufferHistory.length > 0) {
|
|
211
|
+
return {
|
|
212
|
+
source: 'buffer',
|
|
213
|
+
items: bufferHistory.length,
|
|
214
|
+
data: bufferHistory.map(message => message.data).join('')
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const historySource = session.renderedHistory || session.textHistory;
|
|
219
|
+
if (!historySource || typeof historySource.toJSON !== 'function') {
|
|
220
|
+
return { source: 'none', items: 0, data: '' };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const snapshot = historySource.toJSON();
|
|
224
|
+
const text = String(snapshot.text || '');
|
|
225
|
+
return {
|
|
226
|
+
source: session.renderedHistory ? 'rendered-history' : 'text-history',
|
|
227
|
+
items: snapshot.lines || 0,
|
|
228
|
+
data: text ? text + (text.endsWith('\n') ? '' : '\r\n') : ''
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
195
232
|
getDiagnostics(id) {
|
|
196
233
|
const session = this.get(id);
|
|
197
234
|
return session ? this.getSessionDiagnostics(session) : null;
|
|
@@ -234,6 +271,17 @@ class SessionManager extends EventEmitter {
|
|
|
234
271
|
}, { compact: true });
|
|
235
272
|
}
|
|
236
273
|
|
|
274
|
+
logWsCatchupOutput(id, catchup) {
|
|
275
|
+
const session = this.get(id);
|
|
276
|
+
if (!session) return;
|
|
277
|
+
this.logSessionDiagnostics('ws-catchup', session, {
|
|
278
|
+
catchupSource: catchup.source,
|
|
279
|
+
catchupItems: catchup.items,
|
|
280
|
+
catchupBytes: Buffer.byteLength(String(catchup.data || ''), 'utf8'),
|
|
281
|
+
catchupPreview: previewText(catchup.data)
|
|
282
|
+
}, { compact: true });
|
|
283
|
+
}
|
|
284
|
+
|
|
237
285
|
logWsResize(id, cols, rows) {
|
|
238
286
|
const session = this.get(id);
|
|
239
287
|
if (!session) return;
|
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,12 +174,13 @@
|
|
|
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
|
-
<button class="command-key" data-command="/stat" title="Send /stat">/stat</button>
|
|
183
|
+
<button class="command-key" data-command="/stat" data-role="usage-shortcut" title="Send /stat">/stat</button>
|
|
183
184
|
</div>
|
|
184
185
|
</div>
|
|
185
186
|
</div>
|
|
@@ -831,19 +832,53 @@
|
|
|
831
832
|
}
|
|
832
833
|
|
|
833
834
|
function updateToolShortcuts(toolKey) {
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
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
|
+
}));
|
|
847
882
|
}
|
|
848
883
|
|
|
849
884
|
function joinSession(id, sessionName, toolKey = null) {
|
|
@@ -892,6 +927,7 @@
|
|
|
892
927
|
currentSocket.onmessage = (e) => {
|
|
893
928
|
const msg = JSON.parse(e.data);
|
|
894
929
|
if (msg.type === 'output') term.write(msg.data);
|
|
930
|
+
if (msg.type === 'reset') term.reset();
|
|
895
931
|
if (msg.type === 'exit') showLobby();
|
|
896
932
|
};
|
|
897
933
|
}
|
|
@@ -1144,7 +1180,7 @@
|
|
|
1144
1180
|
setTimeout(() => { sendWS('\r'); }, 1000);
|
|
1145
1181
|
return;
|
|
1146
1182
|
}
|
|
1147
|
-
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' };
|
|
1148
1184
|
if (sequences[key]) sendWS(sequences[key]);
|
|
1149
1185
|
});
|
|
1150
1186
|
|