zubo 0.1.19 → 0.1.21

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.
@@ -928,7 +928,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
928
928
  <span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg></span> Dashboard
929
929
  </a>
930
930
  <a href="#memory" onclick="showPanel('memory')">
931
- <span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2C8.5 2 6 4.5 6 7c0 1.5.5 2.8 1.4 3.8C6.5 12 6 13.5 6 15c0 3.5 2.5 7 6 7s6-3.5 6-7c0-1.5-.5-3-1.4-4.2C17.5 9.8 18 8.5 18 7c0-2.5-2.5-5-6-5z"/><path d="M9 10h6"/><path d="M9 14h6"/></svg></span> Memory
931
+ <span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2C8.5 2 6 4.5 6 7c0 1.5.5 2.8 1.4 3.8C6.5 12 6 13.5 6 15c0 3.5 2.5 7 6 7s6-3.5 6-7c0-1.5-.5-3-1.4-4.2C17.5 9.8 18 8.5 18 7c0-2.5-2.5-5-6-5z"/><path d="M9 10h6"/><path d="M9 14h6"/></svg></span> Knowledge
932
932
  </a>
933
933
  <a href="#skills" onclick="showPanel('skills')">
934
934
  <span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg></span> Skills
@@ -940,7 +940,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
940
940
  <span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 16.98h1.67c2.49 0 4.5-2.01 4.5-4.49 0-2.48-2.01-4.49-4.5-4.49h-.16C19.17 5.35 16.68 3 13.67 3 11.23 3 9.14 4.56 8.34 6.78c-.3-.05-.6-.08-.91-.08-2.76 0-5 2.24-5 5s2.24 5 5 5h1.48"/><polyline points="12 13 12 21"/><polyline points="9 18 12 21 15 18"/></svg></span> Webhooks
941
941
  </a>
942
942
  <a href="#mcp" onclick="showPanel('mcp')">
943
- <span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 12h4"/><path d="M14 12h4"/><circle cx="8" cy="12" r="1"/><circle cx="16" cy="12" r="1"/></svg></span> MCP
943
+ <span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 12h4"/><path d="M14 12h4"/><circle cx="8" cy="12" r="1"/><circle cx="16" cy="12" r="1"/></svg></span> Extensions
944
944
  </a>
945
945
  <div class="sidebar-divider"></div>
946
946
  <div class="sidebar-section">Settings</div>
@@ -989,11 +989,11 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
989
989
  <div class="suggestion-chips-group">
990
990
  <div class="chip-row">
991
991
  <button class="suggestion-chip" onclick="useSuggestion(this)">What can you do?</button>
992
- <button class="suggestion-chip" onclick="useSuggestion(this)">Check my schedule</button>
992
+ <button class="suggestion-chip" onclick="useSuggestion(this)">Set a reminder for tomorrow</button>
993
993
  </div>
994
994
  <div class="chip-row">
995
- <button class="suggestion-chip" onclick="useSuggestion(this)">Summarize recent emails</button>
996
- <button class="suggestion-chip" onclick="useSuggestion(this)">Set a reminder</button>
995
+ <button class="suggestion-chip" onclick="useSuggestion(this)">Help me write an email</button>
996
+ <button class="suggestion-chip" onclick="useSuggestion(this)">Explain something to me</button>
997
997
  </div>
998
998
  </div>
999
999
  </div>
@@ -1099,6 +1099,9 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1099
1099
  <!-- MEMORY PANEL -->
1100
1100
  <div id="panel-memory" class="panel">
1101
1101
  <div class="panel-body">
1102
+ <div style="margin-bottom:20px;">
1103
+ <p class="settings-desc" style="margin-bottom:0;">Everything Zubo remembers about you and your conversations. Edit the persistent memory file directly, or search through individual memory chunks below.</p>
1104
+ </div>
1102
1105
  <div class="editor-wrap">
1103
1106
  <div class="editor-toolbar">
1104
1107
  <button class="btn btn-primary" onclick="saveMemory()">Save MEMORY.md</button>
@@ -1122,6 +1125,9 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1122
1125
  <!-- SKILLS PANEL (with Browse/Registry tab) -->
1123
1126
  <div id="panel-skills" class="panel">
1124
1127
  <div class="panel-body">
1128
+ <div style="margin-bottom:16px;">
1129
+ <p class="settings-desc" style="margin-bottom:0;">Skills are custom capabilities you can add to Zubo. Browse the community registry to install new ones, or build your own in TypeScript.</p>
1130
+ </div>
1125
1131
  <div class="tab-bar" id="skills-tabs">
1126
1132
  <button class="tab active" onclick="switchTab('skills','installed')">Installed</button>
1127
1133
  <button class="tab" onclick="switchTab('skills','browse')">Browse Registry</button>
@@ -1206,6 +1212,9 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1206
1212
  <!-- WORKFLOWS PANEL -->
1207
1213
  <div id="panel-workflows" class="panel">
1208
1214
  <div class="panel-body">
1215
+ <div style="margin-bottom:16px;">
1216
+ <p class="settings-desc" style="margin-bottom:0;">Automate multi-step tasks by chaining actions together. Use pre-built recipes, ask Zubo to create one in chat, or build visually with drag-and-drop.</p>
1217
+ </div>
1209
1218
  <div class="tab-bar" id="workflows-tabs">
1210
1219
  <button class="tab active" onclick="switchTab('workflows','recipes')">Recipes</button>
1211
1220
  <button class="tab" onclick="switchTab('workflows','custom')">Custom Workflows</button>
@@ -1305,6 +1314,9 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1305
1314
  <!-- MCP MARKETPLACE PANEL -->
1306
1315
  <div id="panel-mcp" class="panel">
1307
1316
  <div class="panel-body">
1317
+ <div style="margin-bottom:16px;">
1318
+ <p class="settings-desc" style="margin-bottom:0;">Extensions use the <strong>MCP (Model Context Protocol)</strong> standard to give Zubo new tools — like accessing files, databases, GitHub, and more. Browse the marketplace or add your own.</p>
1319
+ </div>
1308
1320
  <div class="tab-bar" id="mcp-tabs">
1309
1321
  <button class="tab active" onclick="switchTab('mcp','installed')">Installed</button>
1310
1322
  <button class="tab" onclick="switchTab('mcp','marketplace')">Marketplace</button>
@@ -1314,22 +1326,22 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1314
1326
  <div id="mcp-installed-list" style="display:flex;flex-direction:column;gap:12px;"></div>
1315
1327
  <div id="mcp-installed-empty" class="empty-state-card" style="display:none;">
1316
1328
  <div class="empty-icon"><svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 12h4"/><path d="M14 12h4"/></svg></div>
1317
- <h4>No MCP servers installed</h4>
1318
- <p>Browse the marketplace to add powerful tool servers.</p>
1329
+ <h4>No extensions installed</h4>
1330
+ <p>Browse the marketplace to add powerful extensions.</p>
1319
1331
  <button class="btn btn-primary" onclick="switchTab('mcp','marketplace')">Browse Marketplace</button>
1320
1332
  </div>
1321
1333
  </div>
1322
1334
 
1323
1335
  <div class="tab-content" id="mcp-tab-marketplace">
1324
1336
  <div class="search-bar">
1325
- <input id="mcp-marketplace-search" type="text" placeholder="Search MCP servers (e.g. filesystem, github, database...)" onkeydown="if(event.key==='Enter')searchMcpMarketplace()">
1337
+ <input id="mcp-marketplace-search" type="text" placeholder="Search extensions (e.g. filesystem, github, database...)" onkeydown="if(event.key==='Enter')searchMcpMarketplace()">
1326
1338
  <button class="btn btn-primary" onclick="searchMcpMarketplace()">Search</button>
1327
1339
  </div>
1328
1340
  <div class="mcp-marketplace-grid" id="mcp-marketplace-results"></div>
1329
1341
  <div id="mcp-marketplace-empty" class="empty-state-card">
1330
1342
  <div class="empty-icon"><svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>
1331
- <h4>Discover MCP Servers</h4>
1332
- <p>Search the official MCP registry to find and install tool servers.</p>
1343
+ <h4>Discover Extensions</h4>
1344
+ <p>Search the marketplace to find and install extensions.</p>
1333
1345
  </div>
1334
1346
  </div>
1335
1347
  </div>
@@ -1398,12 +1410,12 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1398
1410
  <button class="tab active" onclick="switchTab('settings','general')">General</button>
1399
1411
  <button class="tab" onclick="switchTab('settings','providers')">Providers</button>
1400
1412
  <button class="tab" onclick="switchTab('settings','channels')">Channels</button>
1401
- <button class="tab" onclick="switchTab('settings','mcp')">MCP</button>
1402
- <button class="tab" onclick="switchTab('settings','routing')">Routing</button>
1413
+ <button class="tab" onclick="switchTab('settings','mcp')">Extensions</button>
1414
+ <button class="tab" onclick="switchTab('settings','routing')">Cost Savings</button>
1403
1415
  <button class="tab" onclick="switchTab('settings','data')">Data</button>
1404
- <button class="tab" onclick="switchTab('settings','secrets')">Secrets</button>
1405
- <button class="tab" onclick="switchTab('settings','system')">System Prompt</button>
1406
- <button class="tab" onclick="switchTab('settings','cron')">Cron</button>
1416
+ <button class="tab" onclick="switchTab('settings','secrets')">API Keys</button>
1417
+ <button class="tab" onclick="switchTab('settings','system')">Personality</button>
1418
+ <button class="tab" onclick="switchTab('settings','cron')">Scheduled Tasks</button>
1407
1419
  <button class="tab" onclick="switchTab('settings','logs')">Logs</button>
1408
1420
  <button class="tab" onclick="switchTab('settings','privacy')">Privacy</button>
1409
1421
  <button class="tab" onclick="switchTab('settings','budget')">Budget</button>
@@ -1413,11 +1425,11 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1413
1425
  <!-- General Tab -->
1414
1426
  <div class="tab-content active" id="settings-tab-general">
1415
1427
  <div class="settings-section">
1416
- <h3 class="settings-title" data-tooltip="Select which AI model powers Zubo">LLM Provider</h3>
1417
- <p class="settings-desc">Select which provider and model Zubo uses.</p>
1428
+ <h3 class="settings-title" data-tooltip="Choose which AI powers your agent">AI Model</h3>
1429
+ <p class="settings-desc">Choose which AI service and model your agent uses for conversations.</p>
1418
1430
  <div class="settings-grid">
1419
1431
  <div class="settings-field">
1420
- <label class="settings-label" data-tooltip="Cloud AI service" for="settings-provider">Provider</label>
1432
+ <label class="settings-label" data-tooltip="AI service (e.g. Anthropic, OpenAI)" for="settings-provider">Provider</label>
1421
1433
  <select id="settings-provider" class="settings-select" onchange="onProviderChange()"></select>
1422
1434
  </div>
1423
1435
  <div class="settings-field">
@@ -1433,11 +1445,11 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1433
1445
  </div>
1434
1446
 
1435
1447
  <div class="settings-section">
1436
- <h3 class="settings-title" data-tooltip="Background task frequency">Heartbeat Interval</h3>
1437
- <p class="settings-desc">How often the background heartbeat runs. Default: 30 minutes.</p>
1448
+ <h3 class="settings-title" data-tooltip="How often Zubo checks for tasks">Background Check Interval</h3>
1449
+ <p class="settings-desc">How often Zubo checks for reminders, scheduled tasks, and updates. Default: every 30 minutes.</p>
1438
1450
  <div class="settings-grid">
1439
1451
  <div class="settings-field">
1440
- <label class="settings-label" data-tooltip="Minutes between heartbeats" for="settings-heartbeat">Interval (minutes)</label>
1452
+ <label class="settings-label" data-tooltip="Minutes between checks" for="settings-heartbeat">Interval (minutes)</label>
1441
1453
  <input id="settings-heartbeat" type="number" class="settings-input" min="1" max="1440" step="1" placeholder="30">
1442
1454
  </div>
1443
1455
  </div>
@@ -1456,8 +1468,8 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1456
1468
  <!-- Providers Tab -->
1457
1469
  <div class="tab-content" id="settings-tab-providers">
1458
1470
  <div class="settings-section">
1459
- <h3 class="settings-title">LLM Providers</h3>
1460
- <p class="settings-desc">Configure AI model providers. Set one as active for immediate use.</p>
1471
+ <h3 class="settings-title">AI Providers</h3>
1472
+ <p class="settings-desc">Configure AI services. Set one as active for your agent to use.</p>
1461
1473
  <div id="providers-list" style="display:flex;flex-direction:column;gap:12px;"></div>
1462
1474
  <div style="margin-top:20px;">
1463
1475
  <button class="btn btn-primary" onclick="showAddProviderForm()">Add Provider</button>
@@ -1468,22 +1480,23 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1468
1480
  <div class="settings-field">
1469
1481
  <label class="settings-label" for="new-provider-name">Provider</label>
1470
1482
  <select id="new-provider-name" class="settings-select" onchange="onNewProviderSelect()">
1471
- <option value="">-- Select --</option>
1472
- <option value="anthropic">Anthropic</option>
1473
- <option value="openai">OpenAI</option>
1474
- <option value="groq">Groq</option>
1475
- <option value="together">Together</option>
1476
- <option value="openrouter">OpenRouter</option>
1483
+ <option value="">-- Select a provider --</option>
1484
+ <option value="anthropic">Anthropic (Claude)</option>
1485
+ <option value="openai">OpenAI (GPT)</option>
1486
+ <option value="groq">Groq (fast, free tier)</option>
1487
+ <option value="together">Together AI</option>
1488
+ <option value="openrouter">OpenRouter (many models)</option>
1477
1489
  <option value="deepseek">DeepSeek</option>
1478
1490
  <option value="xai">xAI (Grok)</option>
1479
- <option value="ollama">Ollama (local)</option>
1480
- <option value="lmstudio">LM Studio (local)</option>
1481
- <option value="custom">Custom (OpenAI-compat)</option>
1491
+ <option value="ollama">Ollama (free, runs locally)</option>
1492
+ <option value="lmstudio">LM Studio (free, runs locally)</option>
1493
+ <option value="custom">Custom (advanced)</option>
1482
1494
  </select>
1483
1495
  </div>
1484
1496
  <div class="settings-field">
1485
1497
  <label class="settings-label" for="new-provider-key">API Key</label>
1486
1498
  <input id="new-provider-key" type="password" class="settings-input" placeholder="sk-...">
1499
+ <span id="provider-key-help" class="settings-hint" style="font-size:11px;color:var(--text-faint);margin-top:4px;display:none;"></span>
1487
1500
  </div>
1488
1501
  <div class="settings-field">
1489
1502
  <label class="settings-label" for="new-provider-model">Model</label>
@@ -1532,14 +1545,14 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1532
1545
  <!-- MCP Tab -->
1533
1546
  <div class="tab-content" id="settings-tab-mcp">
1534
1547
  <div class="settings-section">
1535
- <h3 class="settings-title">MCP Servers</h3>
1536
- <p class="settings-desc">Model Context Protocol servers extend Zubo with additional tools. Changes apply immediately.</p>
1548
+ <h3 class="settings-title">Extensions</h3>
1549
+ <p class="settings-desc">Extensions give Zubo new abilities like accessing files, databases, and APIs. Install from the marketplace or add manually.</p>
1537
1550
  <div id="mcp-servers-list" style="display:flex;flex-direction:column;gap:12px;"></div>
1538
1551
  <div style="margin-top:20px;">
1539
- <button class="btn btn-primary" onclick="showAddMcpForm()">Add MCP Server</button>
1552
+ <button class="btn btn-primary" onclick="showAddMcpForm()">Add Extension</button>
1540
1553
  </div>
1541
1554
  <div id="mcp-add-form" style="display:none;margin-top:16px;padding:16px;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);">
1542
- <h4 style="margin-bottom:12px;font-family:var(--display);font-weight:600;">Add MCP Server</h4>
1555
+ <h4 style="margin-bottom:12px;font-family:var(--display);font-weight:600;">Add Extension</h4>
1543
1556
  <div class="settings-grid">
1544
1557
  <div class="settings-field">
1545
1558
  <label class="settings-label" for="mcp-name">Name</label>
@@ -1570,8 +1583,8 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1570
1583
  <!-- Routing Tab -->
1571
1584
  <div class="tab-content" id="settings-tab-routing">
1572
1585
  <div class="settings-section">
1573
- <h3 class="settings-title" data-tooltip="Route simple queries to a cheaper/faster model">Smart Routing</h3>
1574
- <p class="settings-desc">Automatically route simple queries to a fast, cheap model and complex ones to your primary model. Saves cost without sacrificing quality.</p>
1586
+ <h3 class="settings-title" data-tooltip="Use a cheaper AI for simple questions">Smart Cost Savings</h3>
1587
+ <p class="settings-desc">Use a cheaper, faster AI for simple questions and your main AI for complex ones. Saves money without sacrificing quality.</p>
1575
1588
  <div class="settings-grid">
1576
1589
  <div class="settings-field">
1577
1590
  <label class="settings-label" for="sr-enabled">Enabled</label>
@@ -1615,8 +1628,8 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1615
1628
  <!-- Secrets Tab -->
1616
1629
  <div class="tab-content" id="settings-tab-secrets">
1617
1630
  <div class="settings-section" style="max-width:700px;">
1618
- <h3 class="settings-title">Secrets &amp; API Keys</h3>
1619
- <p class="settings-desc">Manage API keys and credentials for integrations. Values are stored encrypted in your local database and never sent to external services by Zubo.</p>
1631
+ <h3 class="settings-title">API Keys &amp; Credentials</h3>
1632
+ <p class="settings-desc">Manage API keys for integrations. Your keys are encrypted and stored securely on your device Zubo never sends them anywhere.</p>
1620
1633
  <div style="display:flex;gap:10px;margin-bottom:16px;flex-wrap:wrap;">
1621
1634
  <button class="btn btn-primary" onclick="showAddSecretForm()">Add Secret</button>
1622
1635
  <button class="btn btn-ghost" onclick="loadSecrets()">Refresh</button>
@@ -1648,6 +1661,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1648
1661
 
1649
1662
  <!-- System Prompt Tab -->
1650
1663
  <div class="tab-content" id="settings-tab-system">
1664
+ <p class="settings-desc" style="margin-bottom:12px;">Customize your agent&#39;s personality and instructions. This controls how Zubo thinks and responds. Be careful — incorrect changes may cause errors.</p>
1651
1665
  <div class="editor-wrap" style="min-height:calc(100vh - 220px);">
1652
1666
  <div class="editor-toolbar">
1653
1667
  <button class="btn btn-primary" onclick="saveSystem()">Save</button>
@@ -1728,7 +1742,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1728
1742
  <div class="cards" id="budget-summary-cards"></div>
1729
1743
  <div class="settings-section" style="margin-top:24px;">
1730
1744
  <h3 class="settings-title">Budget Limits</h3>
1731
- <p class="settings-desc">Set spending limits to control costs. The agent will pause when limits are reached.</p>
1745
+ <p class="settings-desc">Set spending limits to control costs. Zubo will stop responding when limits are reached — you can raise them anytime. Typical usage: ~100 messages/day costs $0.50–$2.00 depending on your AI model.</p>
1732
1746
  <div class="settings-grid">
1733
1747
  <div class="settings-field">
1734
1748
  <label class="settings-label" for="budget-daily">Daily Limit (USD)</label>
@@ -1907,7 +1921,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1907
1921
  <script>
1908
1922
  // --- Panel routing ---
1909
1923
  var panelNames = ['agent','history','dashboard','memory','skills','workflows','webhooks','mcp','integrations','settings'];
1910
- var panelTitles = { agent:'Chat', history:'History', dashboard:'Dashboard', memory:'Memory', skills:'Skills', workflows:'Workflows', webhooks:'Webhooks', mcp:'MCP', integrations:'Integrations', settings:'Settings' };
1924
+ var panelTitles = { agent:'Chat', history:'History', dashboard:'Dashboard', memory:'Knowledge', skills:'Skills', workflows:'Workflows', webhooks:'Webhooks', mcp:'Extensions', integrations:'Integrations', settings:'Settings' };
1911
1925
 
1912
1926
  // Legacy panel name mapping (old names -> new names + tab)
1913
1927
  var legacyPanelMap = {
@@ -2002,6 +2016,8 @@ function switchTab(panelId, tabName) {
2002
2016
  if (tabName === 'marketplace') {
2003
2017
  var s = document.getElementById('mcp-marketplace-search');
2004
2018
  if (s) s.focus();
2019
+ var grid = document.getElementById('mcp-marketplace-results');
2020
+ if (grid && !grid.hasChildNodes()) searchMcpMarketplace();
2005
2021
  }
2006
2022
  }
2007
2023
  if (panelId === 'settings' && tabName === 'digests') loadDigestConfig();
@@ -3788,10 +3804,28 @@ function onNewProviderSelect() {
3788
3804
  document.getElementById('new-provider-url-field').style.display = showUrl ? '' : 'none';
3789
3805
  // Local providers don't need API key
3790
3806
  var keyInput = document.getElementById('new-provider-key');
3807
+ var helpEl = document.getElementById('provider-key-help');
3791
3808
  if (name === 'ollama' || name === 'lmstudio') {
3792
- keyInput.placeholder = 'Optional for local providers';
3809
+ keyInput.placeholder = 'Not needed for local models';
3810
+ helpEl.style.display = 'none';
3793
3811
  } else {
3794
3812
  keyInput.placeholder = 'sk-...';
3813
+ // Show where to get the API key
3814
+ var keyLinks = {
3815
+ anthropic: 'console.anthropic.com/settings/keys',
3816
+ openai: 'platform.openai.com/api-keys',
3817
+ groq: 'console.groq.com/keys',
3818
+ together: 'api.together.xyz/settings/api-keys',
3819
+ openrouter: 'openrouter.ai/keys',
3820
+ deepseek: 'platform.deepseek.com/api_keys',
3821
+ xai: 'console.x.ai'
3822
+ };
3823
+ if (keyLinks[name]) {
3824
+ helpEl.textContent = 'Get your key at ' + keyLinks[name];
3825
+ helpEl.style.display = 'block';
3826
+ } else {
3827
+ helpEl.style.display = 'none';
3828
+ }
3795
3829
  }
3796
3830
  }
3797
3831
 
@@ -3855,7 +3889,7 @@ function loadMcpServers() {
3855
3889
  if (!servers.length) {
3856
3890
  var empty = document.createElement('div');
3857
3891
  empty.className = 'empty-state';
3858
- empty.textContent = 'No MCP servers configured. Add one to extend Zubo with new tools.';
3892
+ empty.textContent = 'No extensions configured. Add one to extend Zubo with new tools.';
3859
3893
  list.appendChild(empty);
3860
3894
  return;
3861
3895
  }
@@ -3919,7 +3953,7 @@ function restartMcpServer(name) {
3919
3953
  }
3920
3954
 
3921
3955
  function removeMcpServer(name) {
3922
- if (!confirm('Remove MCP server "' + name + '"? This will disconnect it.')) return;
3956
+ if (!confirm('Remove extension "' + name + '"? This will disconnect it.')) return;
3923
3957
  api('/mcp/servers/' + encodeURIComponent(name), { method: 'DELETE' }).then(function(data) {
3924
3958
  if (data.ok) {
3925
3959
  toast(name + ' removed');
@@ -4361,19 +4395,19 @@ function renderCmdResults(query) {
4361
4395
  var subTabs = [
4362
4396
  { title: 'Analytics', action: function() { showPanel('dashboard'); switchTab('dashboard','analytics'); } },
4363
4397
  { title: 'Performance', action: function() { showPanel('dashboard'); switchTab('dashboard','performance'); } },
4364
- { title: 'System Prompt', action: function() { showPanel('settings'); switchTab('settings','system'); } },
4365
- { title: 'Cron Jobs', action: function() { showPanel('settings'); switchTab('settings','cron'); } },
4398
+ { title: 'Personality', action: function() { showPanel('settings'); switchTab('settings','system'); } },
4399
+ { title: 'Scheduled Tasks', action: function() { showPanel('settings'); switchTab('settings','cron'); } },
4366
4400
  { title: 'Logs', action: function() { showPanel('settings'); switchTab('settings','logs'); } },
4367
4401
  { title: 'Privacy & Data', action: function() { showPanel('settings'); switchTab('settings','privacy'); } },
4368
4402
  { title: 'Budget', action: function() { showPanel('settings'); switchTab('settings','budget'); } },
4369
- { title: 'Secrets', action: function() { showPanel('settings'); switchTab('settings','secrets'); } },
4403
+ { title: 'API Keys', action: function() { showPanel('settings'); switchTab('settings','secrets'); } },
4370
4404
  { title: 'Channels', action: function() { showPanel('settings'); switchTab('settings','channels'); } },
4371
4405
  { title: 'Providers', action: function() { showPanel('settings'); switchTab('settings','providers'); } },
4372
- { title: 'MCP Servers', action: function() { showPanel('settings'); switchTab('settings','mcp'); } },
4373
- { title: 'Smart Routing', action: function() { showPanel('settings'); switchTab('settings','routing'); } },
4406
+ { title: 'Extensions', action: function() { showPanel('settings'); switchTab('settings','mcp'); } },
4407
+ { title: 'Cost Savings', action: function() { showPanel('settings'); switchTab('settings','routing'); } },
4374
4408
  { title: 'Browse Registry', action: function() { showPanel('skills'); switchTab('skills','browse'); } },
4375
4409
  { title: 'Visual Builder', action: function() { showPanel('workflows'); switchTab('workflows','visual'); } },
4376
- { title: 'MCP Marketplace', action: function() { showPanel('mcp'); switchTab('mcp','marketplace'); } },
4410
+ { title: 'Extensions Marketplace', action: function() { showPanel('mcp'); switchTab('mcp','marketplace'); } },
4377
4411
  { title: 'Email Digests', action: function() { showPanel('settings'); switchTab('settings','digests'); } },
4378
4412
  ];
4379
4413
  subTabs.forEach(function(st) { items.push({ name: st.title.toLowerCase(), title: st.title, action: st.action }); });
@@ -4514,7 +4548,7 @@ function clearChatMessages() {
4514
4548
  subtext.textContent = 'Ask me anything, or try a suggestion below';
4515
4549
  var chipsGroup = document.createElement('div');
4516
4550
  chipsGroup.className = 'suggestion-chips-group';
4517
- var rows = [['What can you do?','Check my schedule'],['Summarize recent emails','Set a reminder']];
4551
+ var rows = [['What can you do?','Set a reminder for tomorrow'],['Help me write an email','Explain something to me']];
4518
4552
  rows.forEach(function(rowLabels) {
4519
4553
  var row = document.createElement('div');
4520
4554
  row.className = 'chip-row';
@@ -4580,8 +4614,8 @@ function loadConversationStats() {
4580
4614
  var stats = [
4581
4615
  { label: 'Conversations', value: data.totalConversations || 0 },
4582
4616
  { label: 'Messages', value: data.totalMessages || 0 },
4583
- { label: 'Channels', value: Object.keys(data.messagesByChannel || {}).length },
4584
- { label: 'Recent (24h)', value: data.recentActivity || 0 },
4617
+ { label: 'Channels', value: (data.messagesByChannel || []).length },
4618
+ { label: 'Recent (24h)', value: (data.recentActivity || []).reduce(function(s, r) { return s + (r.count || 0); }, 0) },
4585
4619
  ];
4586
4620
  stats.forEach(function(s) {
4587
4621
  var card = document.createElement('div');
@@ -4823,6 +4857,13 @@ function loadWebhooks() {
4823
4857
  urlEl.setAttribute('data-tooltip', 'Click to copy');
4824
4858
  urlEl.onclick = function() { copyToClipboard(whUrl); };
4825
4859
  card.appendChild(urlEl);
4860
+ // Localhost warning
4861
+ if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
4862
+ var warn = document.createElement('div');
4863
+ warn.style.cssText = 'font-size:11px;color:var(--yellow,#f5a623);margin-top:4px;padding:6px 8px;background:rgba(245,166,35,0.08);border-radius:4px;line-height:1.4;';
4864
+ warn.textContent = 'This URL uses localhost and won\\'t be reachable by external services. Use a tunnel (ngrok, cloudflared) or deploy to a public server to receive webhooks.';
4865
+ card.appendChild(warn);
4866
+ }
4826
4867
  // Stats
4827
4868
  var stats = document.createElement('div');
4828
4869
  stats.className = 'webhook-card-stats';
@@ -5024,9 +5065,10 @@ function searchMcpMarketplace() {
5024
5065
  card.appendChild(desc);
5025
5066
  var acts = document.createElement('div');
5026
5067
  acts.className = 'mcp-server-card-actions';
5027
- if (s.repository) {
5068
+ var repoUrl = typeof s.repository === 'string' ? s.repository : (s.repository && s.repository.url ? s.repository.url : '');
5069
+ if (repoUrl) {
5028
5070
  var link = document.createElement('a');
5029
- link.href = s.repository;
5071
+ link.href = repoUrl;
5030
5072
  link.target = '_blank';
5031
5073
  link.style.cssText = 'font-size:12px;color:var(--text-muted);';
5032
5074
  link.textContent = 'View Repo';
@@ -68,8 +68,17 @@ export function createTelegramAdapter(
68
68
  });
69
69
  });
70
70
 
71
- bot.catch((err) => {
71
+ bot.catch(async (err) => {
72
72
  logger.error("Telegram bot error", { error: err.message });
73
+ // Try to notify the user that something went wrong
74
+ try {
75
+ const ctx = err.ctx;
76
+ if (ctx?.chat?.id) {
77
+ await ctx.reply("Sorry, something went wrong. Please try again.").catch(() => {});
78
+ }
79
+ } catch {
80
+ // Can't reply — don't crash
81
+ }
73
82
  });
74
83
 
75
84
  return {
@@ -17,6 +17,24 @@ function escapeHtml(s: string): string {
17
17
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
18
18
  }
19
19
 
20
+ /** Convert raw error messages to user-friendly messages. Prevents leaking internal details. */
21
+ function friendlyError(err: any): string {
22
+ const msg = err?.message ?? String(err);
23
+ if (msg.includes("401") || msg.includes("Unauthorized") || msg.includes("invalid"))
24
+ return "Authentication failed. Check your API key in Settings > API Keys.";
25
+ if (msg.includes("429") || msg.includes("rate limit"))
26
+ return "Too many messages too quickly. Wait a moment and try again.";
27
+ if (msg.includes("404") || msg.includes("not found"))
28
+ return "The AI model wasn't found. Check your model name in Settings > AI Model.";
29
+ if (msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("Connection refused"))
30
+ return "Can't reach the AI service. Check your internet connection or try again.";
31
+ if (msg.includes("timed out") || msg.includes("timeout"))
32
+ return "The request took too long. The AI service may be busy — try again in a moment.";
33
+ if (msg.includes("context") || msg.includes("too long"))
34
+ return "Your message is too long. Try splitting it into shorter questions.";
35
+ return "Something went wrong. Try again, or check Settings if this keeps happening.";
36
+ }
37
+
20
38
  /** Add security headers to all HTTP responses */
21
39
  function addSecurityHeaders(res: Response): Response {
22
40
  const headers = new Headers(res.headers);
@@ -509,7 +527,7 @@ async function handleDashboardApi(url: URL, req: Request): Promise<Response | nu
509
527
  const { loadConfig } = await import("../config/loader");
510
528
  const { createProvider } = await import("../llm/factory");
511
529
  const config = await loadConfig();
512
- const llm = createProvider(config);
530
+ const llm = await createProvider(config);
513
531
  const res = await llm.chat({
514
532
  system: "You are a test.",
515
533
  messages: [{ role: "user", content: [{ type: "text", text: "Say OK" }] }],
@@ -1204,7 +1222,7 @@ async function handleDashboardApi(url: URL, req: Request): Promise<Response | nu
1204
1222
  const { loadConfig } = await import("../config/loader");
1205
1223
  const { createProvider } = await import("../llm/factory");
1206
1224
  const config = await loadConfig();
1207
- const webhookLlm = createProvider(config);
1225
+ const webhookLlm = await createProvider(config);
1208
1226
  const { agentLoop } = await import("../agent/loop");
1209
1227
  const sessionKey = `webhook:${webhookName}`;
1210
1228
  const result = await agentLoop(webhookLlm, sessionKey, message);
@@ -1736,7 +1754,7 @@ async function handleDashboardApi(url: URL, req: Request): Promise<Response | nu
1736
1754
  const router = (globalThis as any).__zuboRouter;
1737
1755
  if (router) {
1738
1756
  const freshConfig = await loadConfig();
1739
- const newLlm = createProvider(freshConfig);
1757
+ const newLlm = await createProvider(freshConfig);
1740
1758
  router.setLlm(newLlm);
1741
1759
  }
1742
1760
 
@@ -1948,9 +1966,9 @@ async function handleDashboardApi(url: URL, req: Request): Promise<Response | nu
1948
1966
  }
1949
1967
 
1950
1968
  // GET /api/dashboard/conversations/:id/messages — get messages for a conversation
1951
- const convMsgMatch = path.match(/^\/conversations\/([a-f0-9-]+)\/messages$/);
1969
+ const convMsgMatch = path.match(/^\/conversations\/([^/]+)\/messages$/);
1952
1970
  if (convMsgMatch && req.method === "GET") {
1953
- const threadId = convMsgMatch[1];
1971
+ const threadId = decodeURIComponent(convMsgMatch[1]);
1954
1972
  try {
1955
1973
  const db = getDb();
1956
1974
  const limit = Math.min(parseInt(url.searchParams.get("limit") ?? "50", 10), 200);
@@ -2494,9 +2512,23 @@ export function createWebChatAdapter(
2494
2512
  db.run(`CREATE TABLE IF NOT EXISTS threads (
2495
2513
  id TEXT PRIMARY KEY,
2496
2514
  title TEXT NOT NULL DEFAULT 'New conversation',
2515
+ channel TEXT DEFAULT 'webchat',
2516
+ message_count INTEGER NOT NULL DEFAULT 0,
2517
+ summary TEXT,
2497
2518
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
2498
2519
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2499
2520
  )`);
2521
+ // Migrate existing tables missing new columns
2522
+ try { db.run("ALTER TABLE threads ADD COLUMN channel TEXT DEFAULT 'webchat'"); } catch {}
2523
+ try { db.run("ALTER TABLE threads ADD COLUMN message_count INTEGER NOT NULL DEFAULT 0"); } catch {}
2524
+ try { db.run("ALTER TABLE threads ADD COLUMN summary TEXT"); } catch {}
2525
+ // Backfill threads from conversation_messages for pre-existing data
2526
+ try {
2527
+ db.run(`INSERT OR IGNORE INTO threads (id, title, channel, message_count, created_at, updated_at)
2528
+ SELECT thread_id, thread_id, COALESCE(channel, 'webchat'),
2529
+ COUNT(*), MIN(timestamp), MAX(timestamp)
2530
+ FROM conversation_messages GROUP BY thread_id`);
2531
+ } catch {}
2500
2532
  } catch (err: any) {
2501
2533
  logger.warn("Failed to create threads table", { error: (err as Error).message });
2502
2534
  }
@@ -2877,7 +2909,7 @@ async function handleRequest(
2877
2909
  return Response.json({ reply });
2878
2910
  } catch (err: any) {
2879
2911
  return Response.json(
2880
- { error: err.message },
2912
+ { error: friendlyError(err) },
2881
2913
  { status: 500 }
2882
2914
  );
2883
2915
  }
@@ -2934,7 +2966,7 @@ async function handleRequest(
2934
2966
  send("done", { reply });
2935
2967
  close();
2936
2968
  }).catch((err) => {
2937
- send("error", { error: err.message });
2969
+ send("error", { error: friendlyError(err) });
2938
2970
  close();
2939
2971
  });
2940
2972
  return;
@@ -2949,7 +2981,7 @@ async function handleRequest(
2949
2981
  send("done", { reply });
2950
2982
  close();
2951
2983
  }).catch((err) => {
2952
- send("error", { error: err.message });
2984
+ send("error", { error: friendlyError(err) });
2953
2985
  close();
2954
2986
  });
2955
2987
  },
@@ -11,7 +11,7 @@ export class ClaudeCodeProvider implements LlmProvider {
11
11
  model: string;
12
12
  contextWindow = 200_000;
13
13
 
14
- constructor(model: string = "claude-sonnet-4-5-20250929") {
14
+ constructor(model: string = "default") {
15
15
  this.model = model;
16
16
  }
17
17
 
@@ -55,7 +55,6 @@ export class ClaudeCodeProvider implements LlmProvider {
55
55
  const prompt = parts.join("\n\n");
56
56
 
57
57
  const args = ["claude", "-p", prompt, "--output-format", "json"];
58
- if (this.model) args.push("--model", this.model);
59
58
 
60
59
  try {
61
60
  const proc = Bun.spawn(args, {
package/src/llm/codex.ts CHANGED
@@ -11,7 +11,7 @@ export class CodexProvider implements LlmProvider {
11
11
  model: string;
12
12
  contextWindow = 200_000;
13
13
 
14
- constructor(model: string = "o4-mini") {
14
+ constructor(model: string = "default") {
15
15
  this.model = model;
16
16
  }
17
17
 
@@ -51,8 +51,8 @@ export class CodexProvider implements LlmProvider {
51
51
 
52
52
  const prompt = parts.join("\n\n");
53
53
 
54
- const args = ["codex", "-q", prompt];
55
- if (this.model) args.push("--model", this.model);
54
+ const args = ["codex", "exec"];
55
+ args.push(prompt);
56
56
 
57
57
  try {
58
58
  const proc = Bun.spawn(args, {