agentgui 1.0.199 → 1.0.200

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.199",
3
+ "version": "1.0.200",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -180,6 +180,120 @@ function discoverAgents() {
180
180
 
181
181
  const discoveredAgents = discoverAgents();
182
182
 
183
+ const PROVIDER_CONFIGS = {
184
+ 'anthropic': {
185
+ name: 'Anthropic', configPaths: [
186
+ path.join(os.homedir(), '.claude.json'),
187
+ path.join(os.homedir(), '.config', 'claude', 'settings.json'),
188
+ path.join(os.homedir(), '.anthropic.json')
189
+ ],
190
+ configFormat: (apiKey, model) => ({ api_key: apiKey, default_model: model })
191
+ },
192
+ 'openai': {
193
+ name: 'OpenAI', configPaths: [
194
+ path.join(os.homedir(), '.openai.json'),
195
+ path.join(os.homedir(), '.config', 'openai', 'api-key')
196
+ ],
197
+ configFormat: (apiKey, model) => ({ apiKey, defaultModel: model })
198
+ },
199
+ 'google': {
200
+ name: 'Google Gemini', configPaths: [
201
+ path.join(os.homedir(), '.gemini.json'),
202
+ path.join(os.homedir(), '.config', 'gemini', 'credentials.json')
203
+ ],
204
+ configFormat: (apiKey, model) => ({ api_key: apiKey, default_model: model })
205
+ },
206
+ 'openrouter': {
207
+ name: 'OpenRouter', configPaths: [
208
+ path.join(os.homedir(), '.openrouter.json'),
209
+ path.join(os.homedir(), '.config', 'openrouter', 'config.json')
210
+ ],
211
+ configFormat: (apiKey, model) => ({ api_key: apiKey, default_model: model })
212
+ },
213
+ 'github': {
214
+ name: 'GitHub Models', configPaths: [
215
+ path.join(os.homedir(), '.github.json'),
216
+ path.join(os.homedir(), '.config', 'github-copilot.json')
217
+ ],
218
+ configFormat: (apiKey, model) => ({ github_token: apiKey, default_model: model })
219
+ },
220
+ 'azure': {
221
+ name: 'Azure OpenAI', configPaths: [
222
+ path.join(os.homedir(), '.azure.json'),
223
+ path.join(os.homedir(), '.config', 'azure-openai', 'config.json')
224
+ ],
225
+ configFormat: (apiKey, model) => ({ api_key: apiKey, endpoint: '', default_model: model })
226
+ },
227
+ 'anthropic-claude-code': {
228
+ name: 'Claude Code Max', configPaths: [
229
+ path.join(os.homedir(), '.claude', 'max.json'),
230
+ path.join(os.homedir(), '.config', 'claude-code', 'max.json')
231
+ ],
232
+ configFormat: (apiKey, model) => ({ api_key: apiKey, plan: 'max', default_model: model })
233
+ },
234
+ 'opencode': {
235
+ name: 'OpenCode', configPaths: [
236
+ path.join(os.homedir(), '.opencode', 'config.json'),
237
+ path.join(os.homedir(), '.config', 'opencode', 'config.json')
238
+ ],
239
+ configFormat: (apiKey, model) => ({ api_key: apiKey, default_model: model, providers: ['anthropic', 'openai', 'google'] })
240
+ },
241
+ 'proxypilot': {
242
+ name: 'ProxyPilot', configPaths: [
243
+ path.join(os.homedir(), '.proxypilot', 'config.json'),
244
+ path.join(os.homedir(), '.config', 'proxypilot', 'config.json')
245
+ ],
246
+ configFormat: (apiKey, model) => ({ api_key: apiKey, default_model: model })
247
+ }
248
+ };
249
+
250
+ function maskKey(key) {
251
+ if (!key || key.length < 8) return '****';
252
+ return '****' + key.slice(-4);
253
+ }
254
+
255
+ function getProviderConfigs() {
256
+ const configs = {};
257
+ for (const [providerId, config] of Object.entries(PROVIDER_CONFIGS)) {
258
+ for (const configPath of config.configPaths) {
259
+ try {
260
+ if (fs.existsSync(configPath)) {
261
+ const content = fs.readFileSync(configPath, 'utf8');
262
+ const parsed = JSON.parse(content);
263
+ const rawKey = parsed.api_key || parsed.apiKey || parsed.github_token || '';
264
+ configs[providerId] = {
265
+ name: config.name,
266
+ apiKey: maskKey(rawKey),
267
+ hasKey: !!rawKey,
268
+ defaultModel: parsed.default_model || parsed.defaultModel || '',
269
+ path: configPath
270
+ };
271
+ break;
272
+ }
273
+ } catch (_) {}
274
+ }
275
+ if (!configs[providerId]) {
276
+ configs[providerId] = { name: config.name, apiKey: '', hasKey: false, defaultModel: '', path: '' };
277
+ }
278
+ }
279
+ return configs;
280
+ }
281
+
282
+ function saveProviderConfig(providerId, apiKey, defaultModel) {
283
+ const config = PROVIDER_CONFIGS[providerId];
284
+ if (!config) throw new Error('Unknown provider: ' + providerId);
285
+ const configPath = config.configPaths[0];
286
+ const configDir = path.dirname(configPath);
287
+ if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
288
+ let existing = {};
289
+ try {
290
+ if (fs.existsSync(configPath)) existing = JSON.parse(fs.readFileSync(configPath, 'utf8'));
291
+ } catch (_) {}
292
+ const merged = { ...existing, ...config.configFormat(apiKey, defaultModel) };
293
+ fs.writeFileSync(configPath, JSON.stringify(merged, null, 2), { mode: 0o600 });
294
+ return configPath;
295
+ }
296
+
183
297
  function parseBody(req) {
184
298
  return new Promise((resolve, reject) => {
185
299
  let body = '';
@@ -755,6 +869,33 @@ const server = http.createServer(async (req, res) => {
755
869
  return;
756
870
  }
757
871
 
872
+ if (pathOnly === '/api/auth/configs' && req.method === 'GET') {
873
+ const configs = getProviderConfigs();
874
+ sendJSON(req, res, 200, configs);
875
+ return;
876
+ }
877
+
878
+ if (pathOnly === '/api/auth/save-config' && req.method === 'POST') {
879
+ try {
880
+ const body = await parseBody(req);
881
+ const { providerId, apiKey, defaultModel } = body || {};
882
+ if (typeof providerId !== 'string' || !providerId.length || providerId.length > 100) {
883
+ sendJSON(req, res, 400, { error: 'Invalid providerId' }); return;
884
+ }
885
+ if (typeof apiKey !== 'string' || !apiKey.length || apiKey.length > 10000) {
886
+ sendJSON(req, res, 400, { error: 'Invalid apiKey' }); return;
887
+ }
888
+ if (defaultModel !== undefined && (typeof defaultModel !== 'string' || defaultModel.length > 200)) {
889
+ sendJSON(req, res, 400, { error: 'Invalid defaultModel' }); return;
890
+ }
891
+ const configPath = saveProviderConfig(providerId, apiKey, defaultModel || '');
892
+ sendJSON(req, res, 200, { success: true, path: configPath });
893
+ } catch (err) {
894
+ sendJSON(req, res, 400, { error: err.message });
895
+ }
896
+ return;
897
+ }
898
+
758
899
  if (pathOnly === '/api/import/claude-code' && req.method === 'GET') {
759
900
  const result = queries.importClaudeCodeConversations();
760
901
  sendJSON(req, res, 200, { imported: result });
package/static/index.html CHANGED
@@ -452,7 +452,7 @@
452
452
  transition: background-color 0.15s, color 0.15s;
453
453
  }
454
454
  .header-icon-btn:hover { background-color: var(--color-bg-primary); color: var(--color-text-primary); }
455
- .header-icon-btn svg { width: 18px; height: 18px; }
455
+ .header-icon-btn svg { width: 20px; height: 20px; }
456
456
  #scriptStartBtn { color: var(--color-success); }
457
457
  #scriptStartBtn:hover { background-color: rgba(16,185,129,0.1); color: var(--color-success); }
458
458
  .script-dev-btn { color: var(--color-info); }
@@ -466,7 +466,7 @@
466
466
  .agent-auth-btn:hover { background-color: var(--color-bg-primary); }
467
467
  .agent-auth-dropdown {
468
468
  position: absolute; top: 100%; right: 0; z-index: 100;
469
- min-width: 200px; padding: 0.25rem 0;
469
+ min-width: 260px; padding: 0.25rem 0;
470
470
  background: var(--color-bg-secondary); border: 1px solid var(--color-border);
471
471
  border-radius: 0.5rem; box-shadow: 0 4px 12px rgba(0,0,0,0.15);
472
472
  display: none;
@@ -485,6 +485,11 @@
485
485
  .agent-auth-dot.ok { background: var(--color-success); }
486
486
  .agent-auth-dot.missing { background: var(--color-warning); }
487
487
  .agent-auth-dot.unknown { background: var(--color-text-secondary); }
488
+ .agent-auth-section-header {
489
+ padding: 0.375rem 0.75rem; font-size: 0.6875rem; font-weight: 600;
490
+ text-transform: uppercase; letter-spacing: 0.05em;
491
+ color: var(--color-text-secondary); user-select: none;
492
+ }
488
493
 
489
494
  .terminal-container {
490
495
  flex: 1; display: flex; flex-direction: column; overflow: hidden; background: #1e1e1e;
@@ -2,103 +2,154 @@
2
2
  var BASE = window.__BASE_URL || '';
3
3
  var btn = document.getElementById('agentAuthBtn');
4
4
  var dropdown = document.getElementById('agentAuthDropdown');
5
- var agents = [];
6
- var authRunning = false;
5
+ var agents = [], providers = {}, authRunning = false, editingProvider = null;
7
6
  var AUTH_CONV_ID = '__agent_auth__';
8
7
 
9
8
  function init() {
10
9
  if (!btn || !dropdown) return;
10
+ btn.style.display = 'flex';
11
11
  btn.addEventListener('click', toggleDropdown);
12
12
  document.addEventListener('click', function(e) {
13
- if (!btn.contains(e.target)) closeDropdown();
13
+ if (!btn.contains(e.target) && !dropdown.contains(e.target)) closeDropdown();
14
14
  });
15
- window.addEventListener('conversation-selected', function() { fetchAuthStatus(); });
15
+ window.addEventListener('conversation-selected', function() { refresh(); });
16
16
  window.addEventListener('ws-message', onWsMessage);
17
- fetchAuthStatus();
17
+ refresh();
18
18
  }
19
19
 
20
+ function refresh() { fetchAuthStatus(); fetchProviderConfigs(); }
21
+
20
22
  function fetchAuthStatus() {
21
- fetch(BASE + '/api/agents/auth-status')
22
- .then(function(r) { return r.json(); })
23
- .then(function(data) {
24
- agents = data.agents || [];
25
- updateButton();
26
- renderDropdown();
27
- })
23
+ fetch(BASE + '/api/agents/auth-status').then(function(r) { return r.json(); })
24
+ .then(function(data) { agents = data.agents || []; updateButton(); renderDropdown(); })
25
+ .catch(function() {});
26
+ }
27
+
28
+ function fetchProviderConfigs() {
29
+ fetch(BASE + '/api/auth/configs').then(function(r) { return r.json(); })
30
+ .then(function(data) { providers = data || {}; updateButton(); renderDropdown(); })
28
31
  .catch(function() {});
29
32
  }
30
33
 
31
34
  function updateButton() {
32
- if (agents.length === 0) { btn.style.display = 'none'; return; }
33
35
  btn.style.display = 'flex';
34
- var allOk = agents.every(function(a) { return a.authenticated; });
35
- var anyMissing = agents.some(function(a) { return !a.authenticated; });
36
- btn.classList.toggle('auth-ok', allOk);
37
- btn.classList.toggle('auth-warn', anyMissing);
36
+ var agentOk = agents.length === 0 || agents.every(function(a) { return a.authenticated; });
37
+ var pkeys = Object.keys(providers);
38
+ var provOk = pkeys.length === 0 || pkeys.some(function(k) { return providers[k].hasKey; });
39
+ var anyWarn = agents.some(function(a) { return !a.authenticated; }) ||
40
+ pkeys.some(function(k) { return !providers[k].hasKey; });
41
+ btn.classList.toggle('auth-ok', agentOk && provOk && (agents.length > 0 || pkeys.length > 0));
42
+ btn.classList.toggle('auth-warn', anyWarn);
38
43
  }
39
44
 
40
45
  function renderDropdown() {
41
46
  dropdown.innerHTML = '';
42
- agents.forEach(function(agent) {
43
- var item = document.createElement('button');
44
- item.className = 'agent-auth-item';
45
- var dotClass = agent.authenticated ? 'ok' : (agent.detail === 'unknown' ? 'unknown' : 'missing');
46
- item.innerHTML = '<span class="agent-auth-dot ' + dotClass + '"></span>' +
47
- '<span>' + escapeHtml(agent.name) + '</span>' +
48
- '<span style="margin-left:auto;font-size:0.7rem;color:var(--color-text-secondary)">' + escapeHtml(agent.detail) + '</span>';
49
- item.addEventListener('click', function(e) {
50
- e.stopPropagation();
51
- closeDropdown();
52
- triggerAuth(agent.id);
47
+ if (agents.length > 0) {
48
+ appendHeader('Agent CLI Auth');
49
+ agents.forEach(function(agent) {
50
+ var dotClass = agent.authenticated ? 'ok' : (agent.detail === 'unknown' ? 'unknown' : 'missing');
51
+ var item = makeItem(dotClass, agent.name, agent.detail);
52
+ item.addEventListener('click', function(e) { e.stopPropagation(); closeDropdown(); triggerAuth(agent.id); });
53
+ dropdown.appendChild(item);
54
+ });
55
+ }
56
+ var pkeys = Object.keys(providers);
57
+ if (pkeys.length > 0) {
58
+ if (agents.length > 0) appendSep();
59
+ appendHeader('Provider Keys');
60
+ pkeys.forEach(function(pid) {
61
+ var p = providers[pid];
62
+ var item = makeItem(p.hasKey ? 'ok' : 'missing', p.name || pid, p.hasKey ? p.apiKey : 'not set');
63
+ item.style.flexWrap = 'wrap';
64
+ item.addEventListener('click', function(e) { e.stopPropagation(); toggleEdit(pid); });
65
+ dropdown.appendChild(item);
66
+ if (editingProvider === pid) dropdown.appendChild(makeEditForm(pid));
53
67
  });
54
- dropdown.appendChild(item);
68
+ }
69
+ }
70
+
71
+ function appendHeader(text) {
72
+ var h = document.createElement('div');
73
+ h.className = 'agent-auth-section-header';
74
+ h.textContent = text;
75
+ dropdown.appendChild(h);
76
+ }
77
+
78
+ function appendSep() {
79
+ var s = document.createElement('div');
80
+ s.style.cssText = 'height:1px;background:var(--color-border);margin:0.25rem 0;';
81
+ dropdown.appendChild(s);
82
+ }
83
+
84
+ function makeItem(dotClass, name, detail) {
85
+ var el = document.createElement('button');
86
+ el.className = 'agent-auth-item';
87
+ el.innerHTML = '<span class="agent-auth-dot ' + dotClass + '"></span><span>' + esc(name) +
88
+ '</span><span style="margin-left:auto;font-size:0.7rem;color:var(--color-text-secondary)">' + esc(detail) + '</span>';
89
+ return el;
90
+ }
91
+
92
+ function makeEditForm(pid) {
93
+ var form = document.createElement('div');
94
+ form.style.cssText = 'width:100%;padding:0.375rem 0.75rem;display:flex;gap:0.375rem;';
95
+ var input = document.createElement('input');
96
+ input.type = 'password'; input.placeholder = 'API key';
97
+ input.style.cssText = 'flex:1;min-width:0;padding:0.25rem 0.5rem;font-size:0.75rem;border:1px solid var(--color-border);border-radius:0.25rem;background:var(--color-bg-primary);color:var(--color-text-primary);outline:none;';
98
+ input.addEventListener('click', function(e) { e.stopPropagation(); });
99
+ var saveBtn = document.createElement('button');
100
+ saveBtn.textContent = 'Save';
101
+ saveBtn.style.cssText = 'padding:0.25rem 0.5rem;font-size:0.7rem;font-weight:600;background:var(--color-primary);color:white;border:none;border-radius:0.25rem;cursor:pointer;flex-shrink:0;';
102
+ saveBtn.addEventListener('click', function(e) {
103
+ e.stopPropagation();
104
+ var key = input.value.trim();
105
+ if (!key) return;
106
+ saveBtn.disabled = true; saveBtn.textContent = '...';
107
+ saveProviderKey(pid, key);
55
108
  });
109
+ form.appendChild(input); form.appendChild(saveBtn);
110
+ setTimeout(function() { input.focus(); }, 50);
111
+ return form;
112
+ }
113
+
114
+ function toggleEdit(pid) { editingProvider = editingProvider === pid ? null : pid; renderDropdown(); }
115
+
116
+ function saveProviderKey(providerId, apiKey) {
117
+ fetch(BASE + '/api/auth/save-config', {
118
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
119
+ body: JSON.stringify({ providerId: providerId, apiKey: apiKey, defaultModel: '' })
120
+ }).then(function(r) { return r.json(); }).then(function(data) {
121
+ if (data.success) { editingProvider = null; fetchProviderConfigs(); }
122
+ }).catch(function() { editingProvider = null; renderDropdown(); });
56
123
  }
57
124
 
58
125
  function toggleDropdown(e) {
59
126
  e.stopPropagation();
127
+ if (!dropdown.classList.contains('open')) { editingProvider = null; refresh(); }
60
128
  dropdown.classList.toggle('open');
61
129
  }
62
130
 
63
- function closeDropdown() {
64
- dropdown.classList.remove('open');
65
- }
131
+ function closeDropdown() { dropdown.classList.remove('open'); editingProvider = null; }
66
132
 
67
133
  function triggerAuth(agentId) {
68
134
  if (authRunning) return;
69
135
  fetch(BASE + '/api/agents/' + agentId + '/auth', {
70
- method: 'POST',
71
- headers: { 'Content-Type': 'application/json' },
72
- body: '{}'
73
- })
74
- .then(function(r) { return r.json(); })
75
- .then(function(data) {
136
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}'
137
+ }).then(function(r) { return r.json(); }).then(function(data) {
76
138
  if (data.ok) {
77
- authRunning = true;
78
- showTerminalTab();
79
- switchToTerminalView();
139
+ authRunning = true; showTerminalTab(); switchToTerminalView();
80
140
  var term = getTerminal();
81
- if (term) {
82
- term.clear();
83
- term.writeln('\x1b[36m[authenticating ' + agentId + ']\x1b[0m\r\n');
84
- }
141
+ if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + agentId + ']\x1b[0m\r\n'); }
85
142
  }
86
- })
87
- .catch(function() {});
143
+ }).catch(function() {});
88
144
  }
89
145
 
90
146
  function onWsMessage(e) {
91
147
  var data = e.detail;
92
148
  if (!data || data.conversationId !== AUTH_CONV_ID) return;
93
149
  if (data.type === 'script_started') {
94
- authRunning = true;
95
- showTerminalTab();
96
- switchToTerminalView();
150
+ authRunning = true; showTerminalTab(); switchToTerminalView();
97
151
  var term = getTerminal();
98
- if (term) {
99
- term.clear();
100
- term.writeln('\x1b[36m[authenticating ' + (data.agentId || '') + ']\x1b[0m\r\n');
101
- }
152
+ if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + (data.agentId || '') + ']\x1b[0m\r\n'); }
102
153
  } else if (data.type === 'script_output') {
103
154
  showTerminalTab();
104
155
  var term = getTerminal();
@@ -108,37 +159,20 @@
108
159
  var term = getTerminal();
109
160
  var msg = data.error ? data.error : ('exited with code ' + (data.code || 0));
110
161
  if (term) term.writeln('\r\n\x1b[90m[auth ' + msg + ']\x1b[0m');
111
- setTimeout(fetchAuthStatus, 1000);
162
+ setTimeout(refresh, 1000);
112
163
  }
113
164
  }
114
165
 
115
- function showTerminalTab() {
116
- var tabBtn = document.getElementById('terminalTabBtn');
117
- if (tabBtn) tabBtn.style.display = '';
118
- }
119
-
166
+ function showTerminalTab() { var t = document.getElementById('terminalTabBtn'); if (t) t.style.display = ''; }
120
167
  function switchToTerminalView() {
121
168
  var bar = document.getElementById('viewToggleBar');
122
169
  if (!bar) return;
123
- var termBtn = bar.querySelector('[data-view="terminal"]');
124
- if (termBtn) termBtn.click();
125
- }
126
-
127
- function getTerminal() {
128
- return window.scriptRunner ? window.scriptRunner.getTerminal() : null;
129
- }
130
-
131
- function escapeHtml(s) {
132
- var d = document.createElement('div');
133
- d.textContent = s;
134
- return d.innerHTML;
135
- }
136
-
137
- if (document.readyState === 'loading') {
138
- document.addEventListener('DOMContentLoaded', init);
139
- } else {
140
- init();
170
+ var t = bar.querySelector('[data-view="terminal"]'); if (t) t.click();
141
171
  }
172
+ function getTerminal() { return window.scriptRunner ? window.scriptRunner.getTerminal() : null; }
173
+ function esc(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
142
174
 
143
- window.agentAuth = { refresh: fetchAuthStatus };
175
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
176
+ else init();
177
+ window.agentAuth = { refresh: refresh };
144
178
  })();