colana 1.0.0-beta.76 → 1.0.0-beta.78

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/bin/admin.js CHANGED
@@ -156,12 +156,14 @@ async function createKey(flags) {
156
156
  console.error(`\n ${icons.fail} ${c.error}ERROR: --name is required.${c.reset}\n`);
157
157
  console.error(' Usage: colana admin create-key --name "Sarah" --type staff [--email x] [--expires 2026-12-31]\n');
158
158
  process.exit(1);
159
+ return;
159
160
  }
160
161
 
161
162
  const validTypes = ['admin', 'staff', 'beta'];
162
163
  if (!validTypes.includes(type)) {
163
164
  console.error(`\n ${icons.fail} ${c.error}ERROR: Invalid type "${type}". Must be one of: ${validTypes.join(', ')}${c.reset}\n`);
164
165
  process.exit(1);
166
+ return;
165
167
  }
166
168
 
167
169
  // Prefer server API (avoids sql.js in-memory DB conflict)
@@ -174,6 +176,7 @@ async function createKey(flags) {
174
176
  } catch (err) {
175
177
  console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset} ${c.muted}(via server)${c.reset}\n`);
176
178
  process.exit(1);
179
+ return;
177
180
  }
178
181
  } else {
179
182
  // Fallback: direct DB access (only safe when server is NOT running)
@@ -184,6 +187,7 @@ async function createKey(flags) {
184
187
  } catch (err) {
185
188
  console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset}\n`);
186
189
  process.exit(1);
190
+ return;
187
191
  }
188
192
  }
189
193
 
@@ -225,6 +229,7 @@ async function listKeysCmd(flags) {
225
229
  } catch (err) {
226
230
  console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset} ${c.muted}(via server)${c.reset}\n`);
227
231
  process.exit(1);
232
+ return;
228
233
  }
229
234
  } else {
230
235
  await ensureDb();
@@ -232,7 +237,7 @@ async function listKeysCmd(flags) {
232
237
  result = listKeys(filters);
233
238
  }
234
239
 
235
- if (!result.keys.length) {
240
+ if (!result || !result.keys || !result.keys.length) {
236
241
  console.log('\n No keys found.\n');
237
242
  return;
238
243
  }
@@ -263,6 +268,7 @@ async function revokeKeyCmd(positional) {
263
268
  console.error(`\n ${icons.fail} ${c.error}ERROR: Key ID is required.${c.reset}\n`);
264
269
  console.error(' Usage: colana admin revoke-key <id>\n');
265
270
  process.exit(1);
271
+ return;
266
272
  }
267
273
 
268
274
  // Prefer server API
@@ -275,6 +281,7 @@ async function revokeKeyCmd(positional) {
275
281
  } catch (err) {
276
282
  console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset} ${c.muted}(via server)${c.reset}\n`);
277
283
  process.exit(1);
284
+ return;
278
285
  }
279
286
  } else {
280
287
  await ensureDb();
@@ -285,6 +292,7 @@ async function revokeKeyCmd(positional) {
285
292
  } catch (err) {
286
293
  console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset}\n`);
287
294
  process.exit(1);
295
+ return;
288
296
  }
289
297
  }
290
298
  }
@@ -381,7 +389,11 @@ async function main() {
381
389
  console.error(` ${icons.fail} Unknown command: ${c.value}${command}${c.reset}`);
382
390
  console.error(` ${c.muted}Run "colana admin help" for usage.${c.reset}`);
383
391
  process.exit(1);
392
+ return;
384
393
  }
385
394
  }
386
395
 
387
- main();
396
+ main().catch((err) => {
397
+ console.error(` ${icons.fail} ${c.error}${err.message || err}${c.reset}`);
398
+ process.exit(1);
399
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "colana",
3
- "version": "1.0.0-beta.76",
3
+ "version": "1.0.0-beta.78",
4
4
  "description": "Agent-First. Multiplied. Multi-agent command center for AI coding agents.",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
package/public/app.js CHANGED
@@ -325,8 +325,8 @@ function renderPersonalStrip() {
325
325
  const preview = document.getElementById('personal-preview');
326
326
  const launchBtn = document.getElementById('personal-launch-btn');
327
327
 
328
- // Discovery state: not installed or not configured
329
- const needsSetup = !pa.installed || !pa.configured;
328
+ // Discovery state: not installed AND not configured (truly needs setup)
329
+ const needsSetup = !pa.installed && !pa.configured;
330
330
  strip.classList.toggle('needs-setup', needsSetup && pa.status !== 'running' && pa.status !== 'waiting');
331
331
 
332
332
  // Status dot
@@ -342,7 +342,7 @@ function renderPersonalStrip() {
342
342
 
343
343
  // Preview text
344
344
  if (preview) {
345
- if (!pa.installed) {
345
+ if (!pa.installed && !pa.configured) {
346
346
  preview.textContent = 'Personal AI — Set up';
347
347
  } else if (!pa.chatEnabled) {
348
348
  preview.textContent = 'Personal AI — Add API key';
@@ -660,8 +660,8 @@ async function openPersonalPanel() {
660
660
  const clearChatBtn = document.getElementById('personal-panel-clear-chat');
661
661
  const composer = document.getElementById('personal-panel-composer');
662
662
 
663
- // --- Inline Setup Mode: installed but no API key ---
664
- if (pa.installed && !pa.chatEnabled) {
663
+ // --- Inline Setup Mode: installed/configured but no API key ---
664
+ if ((pa.installed || pa.configured) && !pa.chatEnabled) {
665
665
  if (modeToggle) modeToggle.style.display = 'none';
666
666
  if (clearChatBtn) clearChatBtn.style.display = 'none';
667
667
  if (composer) composer.classList.add('hidden');
@@ -830,7 +830,7 @@ async function launchPersonalAgent() {
830
830
  // shows "Add API key" with a "Set up" button that opens the panel with an
831
831
  // inline key input card. Don't spawn (would hit preflight GATEWAY_AUTH_MISSING)
832
832
  // and don't auto-open anything — let the user initiate via the strip/button.
833
- if (state.personalAgent.installed && !state.personalAgent.chatEnabled) {
833
+ if ((state.personalAgent.installed || state.personalAgent.configured) && !state.personalAgent.chatEnabled) {
834
834
  return;
835
835
  }
836
836
  // Guard: don't launch if another launch call is already in-flight
@@ -1287,9 +1287,10 @@ function renderPersonalPanelSetup() {
1287
1287
  closePersonalPanel();
1288
1288
  openSettingsModal();
1289
1289
  setTimeout(() => {
1290
- const section = document.getElementById('settings-personal-agents');
1290
+ const section = document.getElementById('settings-personal-api-keys')
1291
+ || document.getElementById('settings-personal-agents');
1291
1292
  if (section) section.scrollIntoView({ behavior: 'smooth', block: 'center' });
1292
- }, 300);
1293
+ }, 400);
1293
1294
  };
1294
1295
  settingsLink.appendChild(settingsAnchor);
1295
1296
  card.appendChild(settingsLink);
@@ -1561,20 +1562,17 @@ async function sendPersonalChatMessage() {
1561
1562
  addKeyBtn.textContent = 'Add API Key';
1562
1563
  addKeyBtn.addEventListener('click', async (e) => {
1563
1564
  e.preventDefault();
1564
- resetModelConfigReady();
1565
1565
  openSettingsModal();
1566
- await _modelConfigReady;
1567
- // Scroll to the key input (Quick Setup on fresh install, or auth row otherwise)
1568
- const keyInput = document.getElementById('openclaw-auth-key-input')
1569
- || document.querySelector('.openclaw-quick-setup input[type="password"]');
1570
- const modelSection = document.getElementById('openclaw-model-config')
1571
- || document.getElementById('settings-openclaw-model');
1572
- if (keyInput) {
1573
- keyInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
1574
- setTimeout(() => keyInput.focus(), 300);
1575
- } else if (modelSection) {
1576
- modelSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
1577
- }
1566
+ // Scroll to the dedicated API Keys section
1567
+ setTimeout(() => {
1568
+ const apiKeysSection = document.getElementById('settings-personal-api-keys');
1569
+ if (apiKeysSection) {
1570
+ apiKeysSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
1571
+ // Focus the first key input for quick entry
1572
+ const firstInput = apiKeysSection.querySelector('.personal-api-key-input');
1573
+ if (firstInput) setTimeout(() => firstInput.focus(), 300);
1574
+ }
1575
+ }, 400);
1578
1576
  });
1579
1577
  assistantBubble.appendChild(addKeyBtn);
1580
1578
 
@@ -7221,6 +7219,9 @@ async function openSettingsModal() {
7221
7219
  // Integration Hub status (non-blocking)
7222
7220
  renderIntegrationsGrid();
7223
7221
 
7222
+ // Personal Agent API keys (non-blocking)
7223
+ renderPersonalApiKeys();
7224
+
7224
7225
  // OpenClaw model config (non-blocking)
7225
7226
  loadOpenClawModelConfig();
7226
7227
  } catch (error) {
@@ -7283,6 +7284,145 @@ async function installCliAgent(provider, btn) {
7283
7284
  }
7284
7285
  }
7285
7286
 
7287
+ // =========================================================================
7288
+ // Personal Agent API Key Management (Settings)
7289
+ // =========================================================================
7290
+
7291
+ /**
7292
+ * Render the API Keys section in Settings > Personal AI Agents.
7293
+ * Always visible — shows provider key status and input for adding/updating keys.
7294
+ * Uses the same /openclaw/models/auth endpoint as the inline panel setup.
7295
+ */
7296
+ async function renderPersonalApiKeys() {
7297
+ const container = document.getElementById('settings-personal-api-keys');
7298
+ if (!container) return;
7299
+
7300
+ const providers = [
7301
+ { id: 'anthropic', label: 'Anthropic', envVar: 'ANTHROPIC_API_KEY', placeholder: 'sk-ant-...', defaultModel: 'anthropic/claude-sonnet-4-20250514' },
7302
+ { id: 'openai', label: 'OpenAI', envVar: 'OPENAI_API_KEY', placeholder: 'sk-...', defaultModel: 'openai/gpt-4o' },
7303
+ { id: 'google', label: 'Google', envVar: 'GOOGLE_API_KEY', placeholder: 'AIza...', defaultModel: 'google/gemini-2.0-flash' },
7304
+ { id: 'groq', label: 'Groq', envVar: 'GROQ_API_KEY', placeholder: 'gsk_...', defaultModel: 'groq/llama-3.3-70b-versatile' },
7305
+ { id: 'xai', label: 'xAI', envVar: 'XAI_API_KEY', placeholder: 'xai-...', defaultModel: 'xai/grok-3-mini' },
7306
+ { id: 'mistral', label: 'Mistral', envVar: 'MISTRAL_API_KEY', placeholder: 'Paste key...', defaultModel: 'mistral/mistral-large-latest' },
7307
+ ];
7308
+
7309
+ // Fetch current auth status to show which providers have keys configured
7310
+ let authProviders = null;
7311
+ try {
7312
+ const data = await api('/openclaw/models/status');
7313
+ authProviders = data.authProviders;
7314
+ } catch { /* best-effort */ }
7315
+
7316
+ container.textContent = '';
7317
+
7318
+ const grid = document.createElement('div');
7319
+ grid.className = 'personal-api-keys-grid';
7320
+
7321
+ for (const p of providers) {
7322
+ // Check if this provider has a key configured (from authProviders or env)
7323
+ const hasKey = authProviders
7324
+ ? (authProviders[p.id]?.authenticated === true || authProviders[p.id]?.status === 'authenticated')
7325
+ : false;
7326
+
7327
+ const row = document.createElement('div');
7328
+ row.className = 'personal-api-key-row';
7329
+
7330
+ const infoDiv = document.createElement('div');
7331
+ infoDiv.className = 'personal-api-key-info';
7332
+
7333
+ const nameSpan = document.createElement('span');
7334
+ nameSpan.className = 'personal-api-key-name';
7335
+ nameSpan.textContent = p.label;
7336
+ infoDiv.appendChild(nameSpan);
7337
+
7338
+ const statusSpan = document.createElement('span');
7339
+ statusSpan.className = 'personal-api-key-status';
7340
+ if (hasKey) {
7341
+ statusSpan.classList.add('configured');
7342
+ statusSpan.textContent = 'Configured';
7343
+ } else {
7344
+ statusSpan.textContent = 'Not configured';
7345
+ }
7346
+ infoDiv.appendChild(statusSpan);
7347
+
7348
+ const actionDiv = document.createElement('div');
7349
+ actionDiv.className = 'personal-api-key-action';
7350
+
7351
+ const keyInput = document.createElement('input');
7352
+ keyInput.type = 'password';
7353
+ keyInput.className = 'personal-api-key-input';
7354
+ keyInput.placeholder = hasKey ? 'Enter new key to update...' : p.placeholder;
7355
+
7356
+ const saveBtn = document.createElement('button');
7357
+ saveBtn.type = 'button';
7358
+ saveBtn.className = 'btn btn-sm btn-primary personal-api-key-save';
7359
+ saveBtn.textContent = hasKey ? 'Update' : 'Save';
7360
+
7361
+ saveBtn.onclick = async () => {
7362
+ const keyValue = keyInput.value.trim();
7363
+ if (!keyValue) { showToast('Enter an API key', 'error'); return; }
7364
+ saveBtn.disabled = true;
7365
+ saveBtn.textContent = 'Saving...';
7366
+
7367
+ try {
7368
+ await api('/openclaw/models/auth', {
7369
+ method: 'POST',
7370
+ body: JSON.stringify({ provider: p.id, apiKey: keyValue }),
7371
+ });
7372
+
7373
+ // Auto-set a default model if this is the first key being added
7374
+ if (!hasKey && p.defaultModel) {
7375
+ saveBtn.textContent = 'Setting model...';
7376
+ try {
7377
+ await api('/openclaw/models/set', {
7378
+ method: 'POST',
7379
+ body: JSON.stringify({ model: p.defaultModel }),
7380
+ });
7381
+ } catch {
7382
+ // Model set failed — non-fatal, user can set manually
7383
+ }
7384
+ }
7385
+
7386
+ showToast(`${p.label} API key saved`, 'success');
7387
+ keyInput.value = '';
7388
+
7389
+ // Refresh the section and model config after gateway picks up the key
7390
+ setTimeout(() => {
7391
+ renderPersonalApiKeys();
7392
+ loadOpenClawModelConfig();
7393
+ loadPersonalAgentStatus();
7394
+ }, 2500);
7395
+ } catch (err) {
7396
+ showToast('Failed to save: ' + err.message, 'error');
7397
+ } finally {
7398
+ saveBtn.disabled = false;
7399
+ saveBtn.textContent = hasKey ? 'Update' : 'Save';
7400
+ }
7401
+ };
7402
+
7403
+ // Submit on Enter key
7404
+ keyInput.addEventListener('keydown', (e) => {
7405
+ if (e.key === 'Enter') { e.preventDefault(); saveBtn.click(); }
7406
+ });
7407
+
7408
+ actionDiv.appendChild(keyInput);
7409
+ actionDiv.appendChild(saveBtn);
7410
+
7411
+ row.appendChild(infoDiv);
7412
+ row.appendChild(actionDiv);
7413
+ grid.appendChild(row);
7414
+ }
7415
+
7416
+ container.appendChild(grid);
7417
+
7418
+ // Hint text
7419
+ const hint = document.createElement('p');
7420
+ hint.className = 'settings-hint';
7421
+ hint.style.cssText = 'margin-top: 8px; font-size: 0.78rem;';
7422
+ hint.textContent = 'Keys are encrypted and stored locally. Only one provider key is needed to get started.';
7423
+ container.appendChild(hint);
7424
+ }
7425
+
7286
7426
  // =========================================================================
7287
7427
  // OpenClaw Model Configuration
7288
7428
  // =========================================================================
@@ -7902,7 +8042,7 @@ async function loadPersonalAgentStatus() {
7902
8042
  const hint = document.createElement('p');
7903
8043
  hint.className = 'settings-hint personal-auth-hint';
7904
8044
  hint.style.cssText = 'margin-top: 6px; color: var(--warning, #e8a838); font-size: 0.82rem;';
7905
- hint.textContent = 'Configure a model API key in Model Configuration below to enable chat.';
8045
+ hint.textContent = 'Add an API key in the API Keys section below to enable chat.';
7906
8046
  container.appendChild(hint);
7907
8047
  }
7908
8048
 
@@ -10547,10 +10687,10 @@ function initEventListeners() {
10547
10687
  const pa = state.personalAgent;
10548
10688
  if (pa.panelOpen) {
10549
10689
  closePersonalPanel();
10550
- } else if (pa.installed && !pa.chatEnabled) {
10551
- // Installed but no API key — open panel with inline setup
10690
+ } else if ((pa.installed || pa.configured) && !pa.chatEnabled) {
10691
+ // Installed/configured but no API key — open panel with inline setup
10552
10692
  openPersonalPanel();
10553
- } else if (pa.sessionId) {
10693
+ } else if (pa.sessionId || pa.chatEnabled) {
10554
10694
  openPersonalPanel();
10555
10695
  }
10556
10696
  });
@@ -10561,8 +10701,8 @@ function initEventListeners() {
10561
10701
  personalLaunchBtn.addEventListener('click', (e) => {
10562
10702
  e.stopPropagation();
10563
10703
  const pa = state.personalAgent;
10564
- if (!pa.installed) {
10565
- // Not installed — open Settings scrolled to Personal AI Agents section
10704
+ if (!pa.installed && !pa.configured) {
10705
+ // Not installed and not configured — open Settings scrolled to Personal AI Agents section
10566
10706
  openSettingsModal();
10567
10707
  setTimeout(() => {
10568
10708
  const section = document.getElementById('settings-personal-agents');
@@ -10571,7 +10711,7 @@ function initEventListeners() {
10571
10711
  return;
10572
10712
  }
10573
10713
  if (!pa.chatEnabled) {
10574
- // Installed but no API key — open panel with inline setup
10714
+ // Installed/configured but no API key — open panel with inline setup
10575
10715
  openPersonalPanel();
10576
10716
  return;
10577
10717
  }
package/public/index.html CHANGED
@@ -910,6 +910,12 @@
910
910
  <!-- Populated dynamically by app.js — personal agents only -->
911
911
  </div>
912
912
 
913
+ <div class="settings-section-title">API Keys</div>
914
+ <p class="settings-hint" style="margin-bottom: 10px;">Add or update API keys to connect your Personal Agent to AI providers.</p>
915
+ <div id="settings-personal-api-keys" class="personal-api-keys-section">
916
+ <!-- Populated dynamically by app.js -->
917
+ </div>
918
+
913
919
  <div class="settings-section-title">Model Configuration</div>
914
920
  <p class="settings-hint" style="margin-bottom: 10px;">Configure the default AI model for your Personal Agent.</p>
915
921
  <div id="settings-openclaw-model" class="openclaw-model-config">
package/public/styles.css CHANGED
@@ -7751,6 +7751,91 @@ select.form-input option {
7751
7751
  background: var(--bg-secondary);
7752
7752
  }
7753
7753
 
7754
+ /* --- Settings: Personal Agent API Keys --- */
7755
+ .personal-api-keys-section {
7756
+ margin: 8px 0;
7757
+ }
7758
+
7759
+ .personal-api-keys-grid {
7760
+ display: flex;
7761
+ flex-direction: column;
7762
+ gap: 6px;
7763
+ }
7764
+
7765
+ .personal-api-key-row {
7766
+ display: flex;
7767
+ align-items: center;
7768
+ justify-content: space-between;
7769
+ gap: 12px;
7770
+ padding: 8px 12px;
7771
+ background: var(--bg-secondary, #1a1a2e);
7772
+ border: 1px solid var(--border-color, #2a2a4a);
7773
+ border-radius: 8px;
7774
+ transition: border-color 0.15s ease;
7775
+ }
7776
+
7777
+ .personal-api-key-row:hover {
7778
+ border-color: var(--border-hover, #3a3a5a);
7779
+ }
7780
+
7781
+ .personal-api-key-info {
7782
+ display: flex;
7783
+ flex-direction: column;
7784
+ gap: 2px;
7785
+ min-width: 100px;
7786
+ flex-shrink: 0;
7787
+ }
7788
+
7789
+ .personal-api-key-name {
7790
+ font-size: 0.88rem;
7791
+ font-weight: 600;
7792
+ color: var(--text-primary, #e0e0e0);
7793
+ }
7794
+
7795
+ .personal-api-key-status {
7796
+ font-size: 0.75rem;
7797
+ color: var(--text-muted, #888);
7798
+ }
7799
+
7800
+ .personal-api-key-status.configured {
7801
+ color: var(--success, #4caf50);
7802
+ }
7803
+
7804
+ .personal-api-key-action {
7805
+ display: flex;
7806
+ align-items: center;
7807
+ gap: 6px;
7808
+ flex: 1;
7809
+ min-width: 0;
7810
+ }
7811
+
7812
+ .personal-api-key-input {
7813
+ flex: 1;
7814
+ min-width: 0;
7815
+ padding: 6px 10px;
7816
+ border-radius: 6px;
7817
+ border: 1px solid var(--border-color, #2a2a4a);
7818
+ background: var(--bg-primary, #0d0d1a);
7819
+ color: var(--text-primary, #e0e0e0);
7820
+ font-size: 0.82rem;
7821
+ font-family: inherit;
7822
+ transition: border-color 0.15s ease;
7823
+ }
7824
+
7825
+ .personal-api-key-input:focus {
7826
+ outline: none;
7827
+ border-color: var(--accent, #7c4dff);
7828
+ }
7829
+
7830
+ .personal-api-key-input::placeholder {
7831
+ color: var(--text-muted, #666);
7832
+ }
7833
+
7834
+ .personal-api-key-save {
7835
+ flex-shrink: 0;
7836
+ white-space: nowrap;
7837
+ }
7838
+
7754
7839
  /* --- Settings: OpenClaw Model Config --- */
7755
7840
  .openclaw-model-config {
7756
7841
  margin: 8px 0;