agentgui 1.0.614 → 1.0.616
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 +0 -1
- package/docs/index.html +0 -1
- package/lib/tool-manager.js +79 -31
- package/lib/ws-handlers-util.js +2 -12
- package/package.json +1 -1
- package/scripts/take-screenshots.sh +14 -1
- package/server.js +6 -16
- package/static/css/tools-popup.css +12 -0
- package/static/js/terminal.js +7 -4
- package/static/js/tools-manager.js +22 -30
package/README.md
CHANGED
|
@@ -87,7 +87,6 @@ Server starts on `http://localhost:3000/gm/`
|
|
|
87
87
|
- SQLite 3
|
|
88
88
|
- Modern browser (Chrome, Firefox, Safari, Edge)
|
|
89
89
|
- At least one supported AI coding agent installed (see table above)
|
|
90
|
-
- Optional: Python 3.9+ for text-to-speech on Windows
|
|
91
90
|
|
|
92
91
|
## Architecture
|
|
93
92
|
|
package/docs/index.html
CHANGED
|
@@ -594,7 +594,6 @@ npm run dev</code></pre>
|
|
|
594
594
|
<li style="padding: 0.5rem 0; color: #4b5563;">✓ Node.js 18+ (LTS recommended)</li>
|
|
595
595
|
<li style="padding: 0.5rem 0; color: #4b5563;">✓ npm or bun package manager</li>
|
|
596
596
|
<li style="padding: 0.5rem 0; color: #4b5563;">✓ At least one AI coding agent (Claude Code, Gemini CLI, etc.)</li>
|
|
597
|
-
<li style="padding: 0.5rem 0; color: #4b5563;">✓ Optional: Python 3.9+ for TTS on Windows</li>
|
|
598
597
|
</ul>
|
|
599
598
|
</div>
|
|
600
599
|
</div>
|
package/lib/tool-manager.js
CHANGED
|
@@ -5,11 +5,15 @@ import path from 'path';
|
|
|
5
5
|
|
|
6
6
|
const isWindows = os.platform() === 'win32';
|
|
7
7
|
const TOOLS = [
|
|
8
|
-
{ id: '
|
|
9
|
-
{ id: '
|
|
10
|
-
{ id: '
|
|
11
|
-
{ id: '
|
|
12
|
-
{ id: 'codex', name: 'Codex CLI', pkg: '@openai/codex',
|
|
8
|
+
{ id: 'cli-claude', name: 'Claude Code', pkg: '@anthropic-ai/claude-code', category: 'cli' },
|
|
9
|
+
{ id: 'cli-opencode', name: 'OpenCode', pkg: 'opencode-ai', category: 'cli' },
|
|
10
|
+
{ id: 'cli-gemini', name: 'Gemini CLI', pkg: '@google/gemini-cli', category: 'cli' },
|
|
11
|
+
{ id: 'cli-kilo', name: 'Kilo Code', pkg: '@kilocode/cli', category: 'cli' },
|
|
12
|
+
{ id: 'cli-codex', name: 'Codex CLI', pkg: '@openai/codex', category: 'cli' },
|
|
13
|
+
{ id: 'gm-cc', name: 'GM Claude', pkg: 'gm-cc', pluginId: 'gm-cc', category: 'plugin' },
|
|
14
|
+
{ id: 'gm-oc', name: 'GM OpenCode', pkg: 'gm-oc', pluginId: 'gm-oc', category: 'plugin' },
|
|
15
|
+
{ id: 'gm-gc', name: 'GM Gemini', pkg: 'gm-gc', pluginId: 'gm', category: 'plugin' },
|
|
16
|
+
{ id: 'gm-kilo', name: 'GM Kilo', pkg: 'gm-kilo', pluginId: 'gm-kilo', category: 'plugin' },
|
|
13
17
|
];
|
|
14
18
|
|
|
15
19
|
const statusCache = new Map();
|
|
@@ -132,6 +136,34 @@ const getPublishedVersion = async (pkg) => {
|
|
|
132
136
|
}
|
|
133
137
|
};
|
|
134
138
|
|
|
139
|
+
const checkCliInstalled = (pkg) => {
|
|
140
|
+
try {
|
|
141
|
+
const cmd = isWindows ? 'where' : 'which';
|
|
142
|
+
const binMap = { '@anthropic-ai/claude-code': 'claude', 'opencode-ai': 'opencode', '@google/gemini-cli': 'gemini', '@kilocode/cli': 'kilo', '@openai/codex': 'codex' };
|
|
143
|
+
const bin = binMap[pkg];
|
|
144
|
+
if (bin) {
|
|
145
|
+
const { execSync } = require('child_process');
|
|
146
|
+
execSync(`${cmd} ${bin}`, { stdio: 'pipe', timeout: 3000 });
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
} catch (_) {}
|
|
150
|
+
return false;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const getCliVersion = (pkg) => {
|
|
154
|
+
try {
|
|
155
|
+
const binMap = { '@anthropic-ai/claude-code': 'claude', 'opencode-ai': 'opencode', '@google/gemini-cli': 'gemini', '@kilocode/cli': 'kilo', '@openai/codex': 'codex' };
|
|
156
|
+
const bin = binMap[pkg];
|
|
157
|
+
if (bin) {
|
|
158
|
+
const { execSync } = require('child_process');
|
|
159
|
+
const out = execSync(`${bin} --version`, { stdio: 'pipe', timeout: 5000, encoding: 'utf8' });
|
|
160
|
+
const match = out.match(/(\d+\.\d+\.\d+)/);
|
|
161
|
+
if (match) return match[1];
|
|
162
|
+
}
|
|
163
|
+
} catch (_) {}
|
|
164
|
+
return null;
|
|
165
|
+
};
|
|
166
|
+
|
|
135
167
|
const checkToolInstalled = (pluginId) => {
|
|
136
168
|
try {
|
|
137
169
|
const homeDir = os.homedir();
|
|
@@ -141,7 +173,6 @@ const checkToolInstalled = (pluginId) => {
|
|
|
141
173
|
if (fs.existsSync(path.join(homeDir, '.config', 'kilo', 'agents', pluginId + '.md'))) return true;
|
|
142
174
|
if (fs.existsSync(path.join(homeDir, '.config', 'opencode', 'agents', 'gm.md'))) return true;
|
|
143
175
|
if (fs.existsSync(path.join(homeDir, '.config', 'kilo', 'agents', 'gm.md'))) return true;
|
|
144
|
-
if (pluginId === 'codex' && fs.existsSync(path.join(homeDir, '.codex', 'plugin.json'))) return true;
|
|
145
176
|
} catch (_) {}
|
|
146
177
|
return false;
|
|
147
178
|
};
|
|
@@ -159,27 +190,20 @@ const compareVersions = (v1, v2) => {
|
|
|
159
190
|
return false;
|
|
160
191
|
};
|
|
161
192
|
|
|
162
|
-
const checkToolViaBunx = async (pkg, pluginId = null) => {
|
|
193
|
+
const checkToolViaBunx = async (pkg, pluginId = null, category = 'plugin') => {
|
|
163
194
|
try {
|
|
164
|
-
const
|
|
165
|
-
const installed = checkToolInstalled(
|
|
166
|
-
const installedVersion = getInstalledVersion(pkg, pluginId);
|
|
195
|
+
const isCli = category === 'cli';
|
|
196
|
+
const installed = isCli ? checkCliInstalled(pkg) : checkToolInstalled(pluginId || pkg);
|
|
197
|
+
const installedVersion = isCli ? getCliVersion(pkg) : getInstalledVersion(pkg, pluginId);
|
|
167
198
|
const publishedVersion = await getPublishedVersion(pkg);
|
|
168
199
|
const needsUpdate = installed && publishedVersion && compareVersions(installedVersion, publishedVersion);
|
|
169
200
|
const isUpToDate = installed && !needsUpdate;
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
installed,
|
|
173
|
-
isUpToDate,
|
|
174
|
-
upgradeNeeded: needsUpdate,
|
|
175
|
-
output: 'version-check',
|
|
176
|
-
installedVersion,
|
|
177
|
-
publishedVersion
|
|
178
|
-
};
|
|
201
|
+
return { installed, isUpToDate, upgradeNeeded: needsUpdate, output: 'version-check', installedVersion, publishedVersion };
|
|
179
202
|
} catch (_) {
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
203
|
+
const isCli = category === 'cli';
|
|
204
|
+
const installed = isCli ? checkCliInstalled(pkg) : checkToolInstalled(pluginId || pkg);
|
|
205
|
+
const installedVersion = isCli ? getCliVersion(pkg) : getInstalledVersion(pkg, pluginId);
|
|
206
|
+
return { installed, isUpToDate: false, upgradeNeeded: false, output: '', installedVersion, publishedVersion: null };
|
|
183
207
|
}
|
|
184
208
|
};
|
|
185
209
|
|
|
@@ -218,9 +242,10 @@ export async function checkToolStatusAsync(toolId) {
|
|
|
218
242
|
};
|
|
219
243
|
}
|
|
220
244
|
|
|
221
|
-
const result = await checkToolViaBunx(tool.pkg, tool.pluginId);
|
|
245
|
+
const result = await checkToolViaBunx(tool.pkg, tool.pluginId, tool.category);
|
|
222
246
|
const status = {
|
|
223
247
|
toolId,
|
|
248
|
+
category: tool.category,
|
|
224
249
|
installed: result.installed,
|
|
225
250
|
isUpToDate: result.isUpToDate,
|
|
226
251
|
upgradeNeeded: result.upgradeNeeded,
|
|
@@ -241,6 +266,29 @@ export async function checkForUpdates(toolId) {
|
|
|
241
266
|
return { needsUpdate: status.upgradeNeeded && status.installed };
|
|
242
267
|
}
|
|
243
268
|
|
|
269
|
+
const spawnNpmInstall = (pkg, onProgress) => new Promise((resolve) => {
|
|
270
|
+
const cmd = isWindows ? 'npm.cmd' : 'npm';
|
|
271
|
+
let completed = false, stderr = '', stdout = '';
|
|
272
|
+
let proc;
|
|
273
|
+
try {
|
|
274
|
+
proc = spawn(cmd, ['install', '-g', pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows });
|
|
275
|
+
} catch (err) {
|
|
276
|
+
return resolve({ success: false, error: `Failed to spawn npm install: ${err.message}` });
|
|
277
|
+
}
|
|
278
|
+
if (!proc) return resolve({ success: false, error: 'Failed to spawn npm process' });
|
|
279
|
+
const timer = setTimeout(() => { if (!completed) { completed = true; try { proc.kill('SIGKILL'); } catch (_) {} resolve({ success: false, error: 'Timeout (5min)' }); } }, 300000);
|
|
280
|
+
const onData = (d) => { if (onProgress) onProgress({ type: 'progress', data: d.toString() }); };
|
|
281
|
+
if (proc.stdout) proc.stdout.on('data', (d) => { stdout += d.toString(); onData(d); });
|
|
282
|
+
if (proc.stderr) proc.stderr.on('data', (d) => { stderr += d.toString(); onData(d); });
|
|
283
|
+
proc.on('close', (code) => {
|
|
284
|
+
clearTimeout(timer);
|
|
285
|
+
if (completed) return;
|
|
286
|
+
completed = true;
|
|
287
|
+
resolve(code === 0 ? { success: true, error: null, pkg } : { success: false, error: (stdout + stderr).substring(0, 1000) || 'Failed' });
|
|
288
|
+
});
|
|
289
|
+
proc.on('error', (err) => { clearTimeout(timer); if (!completed) { completed = true; resolve({ success: false, error: err.message }); } });
|
|
290
|
+
});
|
|
291
|
+
|
|
244
292
|
const spawnBunxProc = (pkg, onProgress) => new Promise((resolve) => {
|
|
245
293
|
const cmd = isWindows ? 'bun.cmd' : 'bun';
|
|
246
294
|
let completed = false, stderr = '', stdout = '';
|
|
@@ -313,22 +361,22 @@ const spawnBunxProc = (pkg, onProgress) => new Promise((resolve) => {
|
|
|
313
361
|
});
|
|
314
362
|
});
|
|
315
363
|
|
|
364
|
+
const spawnForTool = (tool, onProgress) => {
|
|
365
|
+
return tool.category === 'cli' ? spawnNpmInstall(tool.pkg, onProgress) : spawnBunxProc(tool.pkg, onProgress);
|
|
366
|
+
};
|
|
367
|
+
|
|
316
368
|
export async function install(toolId, onProgress) {
|
|
317
369
|
const tool = getTool(toolId);
|
|
318
370
|
if (!tool) return { success: false, error: 'Tool not found' };
|
|
319
371
|
if (installLocks.get(toolId)) return { success: false, error: 'Install in progress' };
|
|
320
372
|
installLocks.set(toolId, true);
|
|
321
373
|
try {
|
|
322
|
-
const result = await
|
|
374
|
+
const result = await spawnForTool(tool, onProgress);
|
|
323
375
|
if (result.success) {
|
|
324
|
-
// Give the filesystem a moment to settle after bun x install
|
|
325
376
|
await new Promise(r => setTimeout(r, 500));
|
|
326
|
-
|
|
327
|
-
// Aggressively clear all version caches to force fresh detection
|
|
328
377
|
statusCache.delete(toolId);
|
|
329
378
|
versionCache.clear();
|
|
330
|
-
|
|
331
|
-
const version = getInstalledVersion(tool.pkg, tool.pluginId);
|
|
379
|
+
const version = tool.category === 'cli' ? getCliVersion(tool.pkg) : getInstalledVersion(tool.pkg, tool.pluginId);
|
|
332
380
|
const freshStatus = await checkToolStatusAsync(toolId);
|
|
333
381
|
return { success: true, error: null, version: version || freshStatus.publishedVersion || 'unknown', ...freshStatus };
|
|
334
382
|
}
|
|
@@ -347,12 +395,12 @@ export async function update(toolId, onProgress) {
|
|
|
347
395
|
|
|
348
396
|
installLocks.set(toolId, true);
|
|
349
397
|
try {
|
|
350
|
-
const result = await
|
|
398
|
+
const result = await spawnForTool(tool, onProgress);
|
|
351
399
|
if (result.success) {
|
|
352
400
|
await new Promise(r => setTimeout(r, 500));
|
|
353
401
|
statusCache.delete(toolId);
|
|
354
402
|
versionCache.clear();
|
|
355
|
-
const version = getInstalledVersion(tool.pkg, tool.pluginId);
|
|
403
|
+
const version = tool.category === 'cli' ? getCliVersion(tool.pkg) : getInstalledVersion(tool.pkg, tool.pluginId);
|
|
356
404
|
const freshStatus = await checkToolStatusAsync(toolId);
|
|
357
405
|
return { success: true, error: null, version: version || freshStatus.publishedVersion || 'unknown', ...freshStatus };
|
|
358
406
|
}
|
package/lib/ws-handlers-util.js
CHANGED
|
@@ -79,19 +79,8 @@ export function register(router, deps) {
|
|
|
79
79
|
try {
|
|
80
80
|
const { getStatus } = await getSpeech();
|
|
81
81
|
const base = getStatus();
|
|
82
|
-
let pythonDetected = false, pythonVersion = null;
|
|
83
|
-
try {
|
|
84
|
-
const { createRequire } = await import('module');
|
|
85
|
-
const r = createRequire(import.meta.url);
|
|
86
|
-
const serverTTS = r('webtalk/server-tts');
|
|
87
|
-
if (typeof serverTTS.detectPython === 'function') {
|
|
88
|
-
const py = serverTTS.detectPython();
|
|
89
|
-
pythonDetected = py.found;
|
|
90
|
-
pythonVersion = py.version || null;
|
|
91
|
-
}
|
|
92
|
-
} catch {}
|
|
93
82
|
return {
|
|
94
|
-
...base,
|
|
83
|
+
...base,
|
|
95
84
|
setupMessage: base.ttsReady ? 'pocket-tts ready' : 'Will setup on first TTS request',
|
|
96
85
|
modelsDownloading: modelDownloadState.downloading,
|
|
97
86
|
modelsComplete: modelDownloadState.complete,
|
|
@@ -261,6 +250,7 @@ export function register(router, deps) {
|
|
|
261
250
|
id: t.id,
|
|
262
251
|
name: t.name,
|
|
263
252
|
pkg: t.pkg,
|
|
253
|
+
category: t.category || 'plugin',
|
|
264
254
|
installed: t.installed,
|
|
265
255
|
status: t.installed ? (t.isUpToDate ? 'installed' : 'needs_update') : 'not_installed',
|
|
266
256
|
isUpToDate: t.isUpToDate,
|
package/package.json
CHANGED
|
@@ -30,8 +30,17 @@ ab screenshot --full "$DOCS_DIR/screenshot-main.png"
|
|
|
30
30
|
echo "Saved screenshot-main.png"
|
|
31
31
|
|
|
32
32
|
ab eval 'document.querySelector(".conversation-item")?.click()'
|
|
33
|
-
sleep
|
|
33
|
+
sleep 3
|
|
34
34
|
ab wait --load networkidle
|
|
35
|
+
sleep 2
|
|
36
|
+
|
|
37
|
+
# Wait for conversation content to render
|
|
38
|
+
ab eval '(async()=>{for(let i=0;i<20;i++){if(document.querySelector("#output .event-block, #output .message-block")){return"ready"}await new Promise(r=>setTimeout(r,500))}return"timeout"})()'
|
|
39
|
+
sleep 1
|
|
40
|
+
|
|
41
|
+
# Scroll down to show action content
|
|
42
|
+
ab eval 'var s=document.getElementById("output-scroll")||document.getElementById("output");if(s){s.scrollTop=Math.min(s.scrollHeight*0.4,800)}'
|
|
43
|
+
sleep 1
|
|
35
44
|
|
|
36
45
|
ab screenshot --full "$DOCS_DIR/screenshot-chat.png"
|
|
37
46
|
echo "Saved screenshot-chat.png"
|
|
@@ -56,6 +65,10 @@ ab screenshot --full "$DOCS_DIR/screenshot-files.png"
|
|
|
56
65
|
echo "Saved screenshot-files.png"
|
|
57
66
|
|
|
58
67
|
ab eval 'document.querySelector(".view-toggle-btn[data-view=\"terminal\"]")?.click()'
|
|
68
|
+
sleep 3
|
|
69
|
+
|
|
70
|
+
# Wait for terminal to have content
|
|
71
|
+
ab eval '(async()=>{for(let i=0;i<10;i++){var t=document.querySelector("#terminalOutput .xterm-screen");if(t&&t.textContent.trim().length>5)return"ready";await new Promise(r=>setTimeout(r,500))}return"timeout"})()'
|
|
59
72
|
sleep 1
|
|
60
73
|
|
|
61
74
|
ab screenshot --full "$DOCS_DIR/screenshot-terminal.png"
|
package/server.js
CHANGED
|
@@ -1838,6 +1838,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1838
1838
|
id: t.id,
|
|
1839
1839
|
name: t.name,
|
|
1840
1840
|
pkg: t.pkg,
|
|
1841
|
+
category: t.category || 'plugin',
|
|
1841
1842
|
installed: t.installed,
|
|
1842
1843
|
status: t.installed ? (t.isUpToDate ? 'installed' : 'needs_update') : 'not_installed',
|
|
1843
1844
|
isUpToDate: t.isUpToDate,
|
|
@@ -2006,10 +2007,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
2006
2007
|
}
|
|
2007
2008
|
|
|
2008
2009
|
if (pathOnly === '/api/tools/update' && req.method === 'POST') {
|
|
2009
|
-
|
|
2010
|
-
|
|
2010
|
+
const allToolIds = ['cli-claude', 'cli-opencode', 'cli-gemini', 'cli-kilo', 'cli-codex', 'gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'];
|
|
2011
|
+
sendJSON(req, res, 200, { updating: true, toolCount: allToolIds.length });
|
|
2012
|
+
broadcastSync({ type: 'tools_update_started', tools: allToolIds });
|
|
2011
2013
|
setImmediate(async () => {
|
|
2012
|
-
const toolIds =
|
|
2014
|
+
const toolIds = allToolIds;
|
|
2013
2015
|
const results = {};
|
|
2014
2016
|
for (const toolId of toolIds) {
|
|
2015
2017
|
try {
|
|
@@ -2936,20 +2938,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
2936
2938
|
try {
|
|
2937
2939
|
const { getStatus } = await getSpeech();
|
|
2938
2940
|
const baseStatus = getStatus();
|
|
2939
|
-
let pythonDetected = false, pythonVersion = null;
|
|
2940
|
-
try {
|
|
2941
|
-
const r = createRequire(import.meta.url);
|
|
2942
|
-
const serverTTS = r('webtalk/server-tts');
|
|
2943
|
-
if (typeof serverTTS.detectPython === 'function') {
|
|
2944
|
-
const pyInfo = serverTTS.detectPython();
|
|
2945
|
-
pythonDetected = pyInfo.found;
|
|
2946
|
-
pythonVersion = pyInfo.version || null;
|
|
2947
|
-
}
|
|
2948
|
-
} catch(e) {}
|
|
2949
2941
|
sendJSON(req, res, 200, {
|
|
2950
2942
|
...baseStatus,
|
|
2951
|
-
pythonDetected,
|
|
2952
|
-
pythonVersion,
|
|
2953
2943
|
setupMessage: baseStatus.ttsReady ? 'pocket-tts ready' : 'Will setup on first TTS request',
|
|
2954
2944
|
modelsDownloading: modelDownloadState.downloading,
|
|
2955
2945
|
modelsComplete: modelDownloadState.complete,
|
|
@@ -4766,7 +4756,7 @@ function onServerReady() {
|
|
|
4766
4756
|
}, 6000);
|
|
4767
4757
|
}).catch(err => console.error('[ACP] Startup error:', err.message));
|
|
4768
4758
|
|
|
4769
|
-
const toolIds = ['
|
|
4759
|
+
const toolIds = ['cli-claude', 'cli-opencode', 'cli-gemini', 'cli-kilo', 'cli-codex', 'gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'];
|
|
4770
4760
|
queries.initializeToolInstallations(toolIds.map(id => ({ id })));
|
|
4771
4761
|
console.log('[TOOLS] Starting background provisioning...');
|
|
4772
4762
|
toolManager.autoProvision((evt) => {
|
|
@@ -458,3 +458,15 @@
|
|
|
458
458
|
opacity: 0.6;
|
|
459
459
|
cursor: not-allowed;
|
|
460
460
|
}
|
|
461
|
+
|
|
462
|
+
.tool-section-header {
|
|
463
|
+
grid-column: 1 / -1;
|
|
464
|
+
font-size: 0.7rem;
|
|
465
|
+
font-weight: 600;
|
|
466
|
+
text-transform: uppercase;
|
|
467
|
+
letter-spacing: 0.05em;
|
|
468
|
+
color: var(--color-text-secondary);
|
|
469
|
+
padding: 0.5rem 0 0.25rem;
|
|
470
|
+
border-bottom: 1px solid var(--color-border);
|
|
471
|
+
margin-bottom: 0.25rem;
|
|
472
|
+
}
|
package/static/js/terminal.js
CHANGED
|
@@ -157,11 +157,14 @@
|
|
|
157
157
|
|
|
158
158
|
window.addEventListener('view-switched', function(e) {
|
|
159
159
|
if (e.detail.view === 'terminal') {
|
|
160
|
-
if (!
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
setTimeout(function() { if (fitAddon) try { fitAddon.fit(); } catch(_) {} }, 100);
|
|
160
|
+
if (!ensureTerm()) {
|
|
161
|
+
setTimeout(function() { window.dispatchEvent(new CustomEvent('view-switched', { detail: { view: 'terminal' } })); }, 200);
|
|
162
|
+
return;
|
|
164
163
|
}
|
|
164
|
+
termActive = true;
|
|
165
|
+
connectAndStart();
|
|
166
|
+
setTimeout(function() { if (fitAddon) try { fitAddon.fit(); } catch(_) {} }, 50);
|
|
167
|
+
setTimeout(function() { if (fitAddon) try { fitAddon.fit(); } catch(_) {} }, 300);
|
|
165
168
|
} else if (termActive) {
|
|
166
169
|
stopTerminal();
|
|
167
170
|
}
|
|
@@ -304,49 +304,41 @@
|
|
|
304
304
|
return;
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
|
|
307
|
+
var cliTools = tools.filter(function(t) { return t.category === 'cli'; });
|
|
308
|
+
var pluginTools = tools.filter(function(t) { return t.category === 'plugin'; });
|
|
309
|
+
var uncategorized = tools.filter(function(t) { return !t.category; });
|
|
310
|
+
|
|
311
|
+
function renderToolCard(tool) {
|
|
308
312
|
var statusClass = getStatusClass(tool);
|
|
309
313
|
var isInstalling = tool.status === 'installing' || tool.status === 'updating';
|
|
310
314
|
var versionInfo = '';
|
|
311
315
|
if (tool.installedVersion || tool.publishedVersion) {
|
|
312
316
|
versionInfo = '<div class="tool-versions">';
|
|
313
|
-
if (tool.installedVersion)
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
if (tool.publishedVersion && tool.installedVersion !== tool.publishedVersion) {
|
|
317
|
-
versionInfo += '<span class="tool-version-item">(v' + esc(tool.publishedVersion) + ' available)</span>';
|
|
318
|
-
}
|
|
317
|
+
if (tool.installedVersion) versionInfo += '<span class="tool-version-item">v' + esc(tool.installedVersion) + '</span>';
|
|
318
|
+
if (tool.publishedVersion && tool.installedVersion !== tool.publishedVersion) versionInfo += '<span class="tool-version-item">(v' + esc(tool.publishedVersion) + ' available)</span>';
|
|
319
319
|
versionInfo += '</div>';
|
|
320
320
|
}
|
|
321
|
-
|
|
322
321
|
return '<div class="tool-item">' +
|
|
323
322
|
'<div style="display: flex; flex-direction: column; gap: 0.3rem;">' +
|
|
324
|
-
'<div class="tool-header">' +
|
|
325
|
-
'<span class="tool-
|
|
326
|
-
'</div>' +
|
|
327
|
-
'<div class="tool-status-indicator ' + statusClass + '">' +
|
|
328
|
-
'<span class="tool-status-dot"></span>' +
|
|
329
|
-
'<span>' + getStatusText(tool) + '</span>' +
|
|
330
|
-
'</div>' +
|
|
323
|
+
'<div class="tool-header"><span class="tool-name">' + esc(tool.name || tool.id) + '</span></div>' +
|
|
324
|
+
'<div class="tool-status-indicator ' + statusClass + '"><span class="tool-status-dot"></span><span>' + getStatusText(tool) + '</span></div>' +
|
|
331
325
|
versionInfo +
|
|
332
|
-
(isInstalling && tool.progress !== undefined ?
|
|
333
|
-
'<div class="tool-progress-container">' +
|
|
334
|
-
'<div class="tool-progress-bar"><div class="tool-progress-fill" style="width:' + Math.min(tool.progress, 100) + '%"></div></div>' +
|
|
335
|
-
'</div>' : '') +
|
|
326
|
+
(isInstalling && tool.progress !== undefined ? '<div class="tool-progress-container"><div class="tool-progress-bar"><div class="tool-progress-fill" style="width:' + Math.min(tool.progress, 100) + '%"></div></div></div>' : '') +
|
|
336
327
|
(tool.error_message ? '<div class="tool-error-message">Error: ' + esc(tool.error_message.substring(0, 40)) + '</div>' : '') +
|
|
337
328
|
'</div>' +
|
|
338
329
|
'<div class="tool-actions">' +
|
|
339
|
-
(tool.status === 'not_installed' ?
|
|
340
|
-
'<button class="tool-btn tool-btn-primary" onclick="window.toolsManager.
|
|
341
|
-
(tool.
|
|
342
|
-
'<button class="tool-btn tool-btn-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
330
|
+
(tool.status === 'not_installed' ? '<button class="tool-btn tool-btn-primary" onclick="window.toolsManager.install(\'' + tool.id + '\')" ' + (operationInProgress.has(tool.id) ? 'disabled' : '') + '>Install</button>' :
|
|
331
|
+
(tool.hasUpdate || tool.status === 'needs_update') ? '<button class="tool-btn tool-btn-primary" onclick="window.toolsManager.update(\'' + tool.id + '\')" ' + (operationInProgress.has(tool.id) ? 'disabled' : '') + '>Update</button>' :
|
|
332
|
+
tool.status === 'failed' ? '<button class="tool-btn tool-btn-primary" onclick="window.toolsManager.install(\'' + tool.id + '\')" ' + (operationInProgress.has(tool.id) ? 'disabled' : '') + '>Retry</button>' :
|
|
333
|
+
'<button class="tool-btn tool-btn-secondary" onclick="window.toolsManager.refresh()" ' + (isRefreshing ? 'disabled' : '') + '>✓</button>') +
|
|
334
|
+
'</div></div>';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
var html = '';
|
|
338
|
+
if (cliTools.length) html += '<div class="tool-section-header">CLI Agents</div>' + cliTools.map(renderToolCard).join('');
|
|
339
|
+
if (pluginTools.length) html += '<div class="tool-section-header">GM Plugins</div>' + pluginTools.map(renderToolCard).join('');
|
|
340
|
+
if (uncategorized.length) html += uncategorized.map(renderToolCard).join('');
|
|
341
|
+
scroll.innerHTML = html;
|
|
350
342
|
}
|
|
351
343
|
|
|
352
344
|
function esc(s) {
|