agentgui 1.0.614 → 1.0.615

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.
@@ -5,11 +5,15 @@ import path from 'path';
5
5
 
6
6
  const isWindows = os.platform() === 'win32';
7
7
  const TOOLS = [
8
- { id: 'gm-cc', name: 'Claude Code', pkg: 'gm-cc', pluginId: 'gm-cc' },
9
- { id: 'gm-oc', name: 'OpenCode', pkg: 'gm-oc', pluginId: 'gm-oc' },
10
- { id: 'gm-gc', name: 'Gemini CLI', pkg: 'gm-gc', pluginId: 'gm' },
11
- { id: 'gm-kilo', name: 'Kilo', pkg: 'gm-kilo', pluginId: 'gm-kilo' },
12
- { id: 'codex', name: 'Codex CLI', pkg: '@openai/codex', pluginId: '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 actualPluginId = pluginId || pkg;
165
- const installed = checkToolInstalled(actualPluginId);
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 actualPluginId = pluginId || pkg;
181
- const installedVersion = getInstalledVersion(pkg, pluginId);
182
- return { installed: checkToolInstalled(actualPluginId), isUpToDate: false, upgradeNeeded: false, output: '', installedVersion, publishedVersion: null };
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 spawnBunxProc(tool.pkg, onProgress);
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 spawnBunxProc(tool.pkg, onProgress);
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
  }
@@ -261,6 +261,7 @@ export function register(router, deps) {
261
261
  id: t.id,
262
262
  name: t.name,
263
263
  pkg: t.pkg,
264
+ category: t.category || 'plugin',
264
265
  installed: t.installed,
265
266
  status: t.installed ? (t.isUpToDate ? 'installed' : 'needs_update') : 'not_installed',
266
267
  isUpToDate: t.isUpToDate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.614",
3
+ "version": "1.0.615",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
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
- sendJSON(req, res, 200, { updating: true, toolCount: 4 });
2010
- broadcastSync({ type: 'tools_update_started', tools: ['gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'] });
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 = ['gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'];
2014
+ const toolIds = allToolIds;
2013
2015
  const results = {};
2014
2016
  for (const toolId of toolIds) {
2015
2017
  try {
@@ -4766,7 +4768,7 @@ function onServerReady() {
4766
4768
  }, 6000);
4767
4769
  }).catch(err => console.error('[ACP] Startup error:', err.message));
4768
4770
 
4769
- const toolIds = ['gm-oc', 'gm-gc', 'gm-kilo', 'gm-cc', 'codex'];
4771
+ const toolIds = ['cli-claude', 'cli-opencode', 'cli-gemini', 'cli-kilo', 'cli-codex', 'gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'];
4770
4772
  queries.initializeToolInstallations(toolIds.map(id => ({ id })));
4771
4773
  console.log('[TOOLS] Starting background provisioning...');
4772
4774
  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
+ }
@@ -157,11 +157,14 @@
157
157
 
158
158
  window.addEventListener('view-switched', function(e) {
159
159
  if (e.detail.view === 'terminal') {
160
- if (!termActive) {
161
- termActive = true;
162
- connectAndStart();
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
- scroll.innerHTML = tools.map(function(tool) {
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
- versionInfo += '<span class="tool-version-item">v' + esc(tool.installedVersion) + '</span>';
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-name">' + esc(tool.name || tool.id) + '</span>' +
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.install(\'' + tool.id + '\')" ' + (operationInProgress.has(tool.id) ? 'disabled' : '') + '>Install</button>' :
341
- (tool.hasUpdate || tool.status === 'needs_update') ?
342
- '<button class="tool-btn tool-btn-primary" onclick="window.toolsManager.update(\'' + tool.id + '\')" ' + (operationInProgress.has(tool.id) ? 'disabled' : '') + '>Update</button>' :
343
- tool.status === 'failed' ?
344
- '<button class="tool-btn tool-btn-primary" onclick="window.toolsManager.install(\'' + tool.id + '\')" ' + (operationInProgress.has(tool.id) ? 'disabled' : '') + '>Retry</button>' :
345
- '<button class="tool-btn tool-btn-secondary" onclick="window.toolsManager.refresh()" ' + (isRefreshing ? 'disabled' : '') + '>✓</button>'
346
- ) +
347
- '</div>' +
348
- '</div>';
349
- }).join('');
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) {