@danainnovations/cortex-mcp 1.0.135 → 1.0.136

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/dist/cli.js CHANGED
@@ -347,6 +347,39 @@ function getWizardHtml() {
347
347
  font-weight: 400;
348
348
  margin-bottom: 8px;
349
349
  }
350
+ .mcp-section-toggle {
351
+ display: flex;
352
+ align-items: center;
353
+ gap: 6px;
354
+ cursor: pointer;
355
+ user-select: none;
356
+ padding: 0;
357
+ background: none;
358
+ border: none;
359
+ margin-bottom: 2px;
360
+ }
361
+ .mcp-section-toggle .mcp-section-title { margin-bottom: 0; }
362
+ .mcp-section-chevron {
363
+ width: 14px;
364
+ height: 14px;
365
+ color: #6b7780;
366
+ transition: transform 0.2s ease;
367
+ flex-shrink: 0;
368
+ }
369
+ .mcp-section-chevron.open { transform: rotate(90deg); }
370
+ .mcp-section-body {
371
+ overflow: hidden;
372
+ transition: max-height 0.25s ease, opacity 0.2s ease;
373
+ }
374
+ .mcp-section-body.collapsed {
375
+ max-height: 0;
376
+ opacity: 0;
377
+ pointer-events: none;
378
+ }
379
+ .mcp-section-body.expanded {
380
+ max-height: 2000px;
381
+ opacity: 1;
382
+ }
350
383
 
351
384
  /* \u2500\u2500 MCP List \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
352
385
  .mcp-list {
@@ -848,9 +881,9 @@ function getWizardHtml() {
848
881
  <circle cx="24" cy="24" r="6" fill="#00A3E1" opacity="0.8"/>
849
882
  </svg>
850
883
  <h1>Connect Your AI Tools</h1>
851
- <p class="welcome-tagline">One setup. 10+ platforms. All your AI clients.</p>
884
+ <p class="welcome-tagline">One setup. All your tools.</p>
852
885
  <p class="subtitle" style="margin-bottom: 0;">
853
- GitHub, Salesforce, M365, Slack, Asana, and more &mdash; configured in under 2 minutes.
886
+ M365 tools and beyond &mdash; configured in under 2 minutes.
854
887
  </p>
855
888
  </div>
856
889
  <button class="btn btn-accent btn-block" onclick="goToStep('signin')">Get Started</button>
@@ -1023,10 +1056,41 @@ function getWizardHtml() {
1023
1056
  console.error('Failed to load state:', err);
1024
1057
  }
1025
1058
  updateHeaderDots();
1059
+
1060
+ // \u2500\u2500 Resume from progress \u2500\u2500
1061
+ try {
1062
+ var progressResp = await fetch('/api/progress');
1063
+ var progress = await progressResp.json();
1064
+ if (progress.hasConfig && progress.configuredClients.length > 0) {
1065
+ // Config written + clients configured \u2192 skip to connect step
1066
+ goToStep('connect');
1067
+ } else if (progress.hasCredentials) {
1068
+ // Signed in but hasn't configured clients yet \u2192 skip to MCPs
1069
+ goToStep('mcps');
1070
+ }
1071
+ // else: start from welcome (default)
1072
+ } catch (e) {
1073
+ // Can't reach progress endpoint \u2014 start from beginning
1074
+ }
1026
1075
  }
1027
1076
 
1028
1077
  init();
1029
1078
 
1079
+ // \u2500\u2500 Heartbeat \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1080
+ fetch('/api/heartbeat', { method: 'POST' }).catch(function() {});
1081
+ setInterval(function() {
1082
+ fetch('/api/heartbeat', { method: 'POST' }).catch(function() {});
1083
+ }, 5000);
1084
+
1085
+ // \u2500\u2500 Warn before closing tab mid-setup \u2500\u2500\u2500
1086
+ window.addEventListener('beforeunload', function(e) {
1087
+ // Only warn if wizard is not on the done step
1088
+ if (STEP_ORDER[currentStepIndex] !== 'done') {
1089
+ e.preventDefault();
1090
+ e.returnValue = '';
1091
+ }
1092
+ });
1093
+
1030
1094
  // \u2500\u2500 Header Dots \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1031
1095
  function updateHeaderDots() {
1032
1096
  var el = document.getElementById('header-steps');
@@ -1209,31 +1273,37 @@ function getWizardHtml() {
1209
1273
  var personalMcps = enabledMcps.filter(function(m) { return m.authMode === 'personal'; });
1210
1274
 
1211
1275
  var html = '';
1276
+ var chevronSvg = '<svg class="mcp-section-chevron" viewBox="0 0 16 16" fill="currentColor"><path d="M6 3l5 5-5 5z"/></svg>';
1212
1277
 
1213
- if (companyMcps.length > 0) {
1278
+ // Personal integrations first (most relevant to business users)
1279
+ if (personalMcps.length > 0) {
1214
1280
  html += '<div class="mcp-section">';
1215
- html += '<div class="mcp-section-title">Company Integrations</div>';
1216
- html += '<div class="mcp-section-subtitle">Ready to use \\u2014 no account connection needed</div>';
1281
+ html += '<div class="mcp-section-title">Your Integrations</div>';
1282
+ html += '<div class="mcp-section-subtitle">Connect your personal account to use these</div>';
1217
1283
  html += '<div class="mcp-list">';
1218
- html += companyMcps.map(function(mcp) { return renderMcpItem(mcp, false, false); }).join('');
1284
+ html += personalMcps.map(function(mcp) { return renderMcpItem(mcp, false, false); }).join('');
1219
1285
  html += '</div></div>';
1220
1286
  }
1221
1287
 
1222
- if (personalMcps.length > 0) {
1288
+ // Company integrations (collapsible)
1289
+ if (companyMcps.length > 0) {
1223
1290
  html += '<div class="mcp-section">';
1224
- html += '<div class="mcp-section-title">Personal Integrations</div>';
1225
- html += '<div class="mcp-section-subtitle">Connect your personal account to use these</div>';
1291
+ html += '<button class="mcp-section-toggle" onclick="toggleMcpSection(this)">' + chevronSvg + '<span class="mcp-section-title">Company Integrations</span></button>';
1292
+ html += '<div class="mcp-section-body collapsed">';
1293
+ html += '<div class="mcp-section-subtitle" style="margin-top:4px;">Ready to use \\u2014 no account connection needed</div>';
1226
1294
  html += '<div class="mcp-list">';
1227
- html += personalMcps.map(function(mcp) { return renderMcpItem(mcp, false, false); }).join('');
1228
- html += '</div></div>';
1295
+ html += companyMcps.map(function(mcp) { return renderMcpItem(mcp, false, false); }).join('');
1296
+ html += '</div></div></div>';
1229
1297
  }
1230
1298
 
1299
+ // Coming soon (collapsible)
1231
1300
  if (disabledMcps.length > 0) {
1232
1301
  html += '<div class="mcp-section">';
1233
- html += '<div class="mcp-section-title">Coming Soon</div>';
1302
+ html += '<button class="mcp-section-toggle" onclick="toggleMcpSection(this)">' + chevronSvg + '<span class="mcp-section-title">Coming Soon</span></button>';
1303
+ html += '<div class="mcp-section-body collapsed">';
1234
1304
  html += '<div class="mcp-list">';
1235
1305
  html += disabledMcps.map(function(mcp) { return renderMcpItem(mcp, false, true); }).join('');
1236
- html += '</div></div>';
1306
+ html += '</div></div></div>';
1237
1307
  }
1238
1308
 
1239
1309
  container.innerHTML = html;
@@ -1249,6 +1319,20 @@ function getWizardHtml() {
1249
1319
  });
1250
1320
  }
1251
1321
 
1322
+ function toggleMcpSection(btn) {
1323
+ var chevron = btn.querySelector('.mcp-section-chevron');
1324
+ var body = btn.parentElement.querySelector('.mcp-section-body');
1325
+ if (body.classList.contains('collapsed')) {
1326
+ body.classList.remove('collapsed');
1327
+ body.classList.add('expanded');
1328
+ chevron.classList.add('open');
1329
+ } else {
1330
+ body.classList.remove('expanded');
1331
+ body.classList.add('collapsed');
1332
+ chevron.classList.remove('open');
1333
+ }
1334
+ }
1335
+
1252
1336
  async function renderMcps() {
1253
1337
  var container = document.getElementById('mcp-list-container');
1254
1338
 
@@ -1365,27 +1449,33 @@ function getWizardHtml() {
1365
1449
  var mcpUrl = (state.serverUrl || 'https://cortex-bice.vercel.app') + '/mcp/cortex';
1366
1450
  var html = '';
1367
1451
 
1368
- // \u2500\u2500 Section 1: Auto-configured (2-column grid) \u2500\u2500
1369
- if (autoDetected.length > 0) {
1452
+ var chevronSvg = '<svg class="mcp-section-chevron" viewBox="0 0 16 16" fill="currentColor"><path d="M6 3l5 5-5 5z"/></svg>';
1453
+ var primaryTypes = ['claude-desktop', 'claude-code'];
1454
+ var primaryDetected = autoDetected.filter(function(c) { return primaryTypes.indexOf(c.type) !== -1; });
1455
+ var advancedDetected = autoDetected.filter(function(c) { return primaryTypes.indexOf(c.type) === -1; });
1456
+
1457
+ // \u2500\u2500 Primary clients: Claude Desktop + Claude Code (always visible) \u2500\u2500
1458
+ if (primaryDetected.length > 0) {
1370
1459
  html += '<div class="client-section-divider" style="margin-top:0;padding-top:0;border:none;">' +
1371
- '<div class="client-section-title">Auto-configured</div>' +
1372
1460
  '<div class="client-section-subtitle">Select the apps you use \\u2014 we\\u2019ll configure them automatically</div>' +
1373
1461
  '</div>';
1374
1462
  html += '<div class="client-grid">';
1375
- html += autoDetected.map(renderClientItem).join('');
1463
+ html += primaryDetected.map(renderClientItem).join('');
1376
1464
  html += '</div>';
1377
1465
  }
1378
1466
 
1379
- // Show not-found auto clients dimmed
1380
- if (autoNotFound.length > 0) {
1381
- html += '<div class="client-grid" style="margin-top:4px;">';
1382
- html += autoNotFound.map(renderClientItem).join('');
1383
- html += '</div>';
1467
+ // \u2500\u2500 Advanced clients: Cursor, VS Code, Antigravity, Codex (collapsible) \u2500\u2500
1468
+ var advancedAll = advancedDetected.concat(autoNotFound);
1469
+ if (advancedAll.length > 0) {
1470
+ html += '<div class="mcp-section" style="margin-top:12px;">';
1471
+ html += '<button class="mcp-section-toggle" onclick="toggleMcpSection(this)">' + chevronSvg + '<span class="mcp-section-title">Advanced</span></button>';
1472
+ html += '<div class="mcp-section-body collapsed">';
1473
+ html += '<div class="client-section-subtitle" style="margin-top:4px;">If you don\\u2019t know what these are yet, you should be using the Claude Desktop App</div>';
1474
+ html += '<div class="client-grid">';
1475
+ html += advancedAll.map(renderClientItem).join('');
1476
+ html += '</div></div></div>';
1384
1477
  }
1385
1478
 
1386
- // Manual clients (CoWork, Claude.ai) moved to dedicated step 5
1387
- // Additional clients (Perplexity, OpenClaw) moved to Cortex Control Center
1388
-
1389
1479
  el.innerHTML = html;
1390
1480
 
1391
1481
  // Attach click handlers to auto-configured items
@@ -1625,11 +1715,11 @@ function getWizardHtml() {
1625
1715
  el.innerHTML = '<div class="status status-info"><span class="spinner"></span> Loading connections...</div>';
1626
1716
 
1627
1717
  var selectedMcpList = state.availableMcps.filter(function(m) {
1628
- return state.selectedMcps.indexOf(m.name) !== -1;
1718
+ return state.selectedMcps.indexOf(m.name) !== -1 && m.authMode === 'personal';
1629
1719
  });
1630
1720
 
1631
1721
  if (selectedMcpList.length === 0) {
1632
- el.innerHTML = '<div class="status status-info">No integrations selected.</div>';
1722
+ el.innerHTML = '<div class="status status-info">No personal accounts to connect. You\\u2019re all set!</div>';
1633
1723
  return;
1634
1724
  }
1635
1725
 
@@ -1643,18 +1733,7 @@ function getWizardHtml() {
1643
1733
 
1644
1734
  el.innerHTML = selectedMcpList.map(function(mcp) {
1645
1735
  var conn = state.connections.find(function(c) { return c.mcp_name === mcp.name; });
1646
- var isCompany = mcp.authMode !== 'personal';
1647
1736
  var isConnected = conn && !conn.is_company_default && conn.account_email;
1648
- var isCompanyDefault = isCompany;
1649
-
1650
- if (isCompanyDefault) {
1651
- return (
1652
- '<div class="conn-item connected company-default" id="conn-' + mcp.name + '">' +
1653
- '<span class="conn-name">' + escapeHtml(mcp.displayName) + '</span>' +
1654
- '<span class="conn-status connected">&#10003; Company Account</span>' +
1655
- '</div>'
1656
- );
1657
- }
1658
1737
 
1659
1738
  return (
1660
1739
  '<div class="conn-item' + (isConnected ? ' connected' : ' needs-connect') + '" id="conn-' + mcp.name + '" data-personal="true">' +
@@ -1978,7 +2057,7 @@ function detectClients() {
1978
2057
  }
1979
2058
  clients.push({
1980
2059
  type: "codex",
1981
- name: "Codex (OpenAI)",
2060
+ name: "Codex",
1982
2061
  configPath: codexPath,
1983
2062
  detected: codexDetected
1984
2063
  });
@@ -2482,6 +2561,15 @@ function getState() {
2482
2561
  }
2483
2562
  return wizardState;
2484
2563
  }
2564
+ function getProgress() {
2565
+ const creds = readCredentials();
2566
+ const config = readConfig();
2567
+ return {
2568
+ hasCredentials: creds !== null && !!creds.email,
2569
+ hasConfig: config !== null,
2570
+ configuredClients: config && config.configuredClients || []
2571
+ };
2572
+ }
2485
2573
  function parseBody(req) {
2486
2574
  return new Promise((resolve3) => {
2487
2575
  const chunks = [];
@@ -2500,7 +2588,7 @@ function json(res, data, status = 200) {
2500
2588
  res.writeHead(status, { "Content-Type": "application/json" });
2501
2589
  res.end(JSON.stringify(data));
2502
2590
  }
2503
- async function handleApiRoute(path, searchParams, req, res, options, onComplete) {
2591
+ async function handleApiRoute(path, searchParams, req, res, options, onComplete, heartbeat) {
2504
2592
  const method = req.method || "GET";
2505
2593
  if (path === "/api/state" && method === "GET") {
2506
2594
  const creds = readCredentials();
@@ -2761,12 +2849,34 @@ async function handleApiRoute(path, searchParams, req, res, options, onComplete)
2761
2849
  setTimeout(onComplete, 100);
2762
2850
  return true;
2763
2851
  }
2852
+ if (path === "/api/heartbeat" && method === "POST") {
2853
+ if (heartbeat) heartbeat.beat();
2854
+ json(res, { ok: true });
2855
+ return true;
2856
+ }
2857
+ if (path === "/api/progress" && method === "GET") {
2858
+ json(res, getProgress());
2859
+ return true;
2860
+ }
2764
2861
  return false;
2765
2862
  }
2766
2863
 
2767
2864
  // src/wizard/server.ts
2865
+ function createHeartbeatTracker(timeoutMs = 15e3) {
2866
+ let lastBeat = 0;
2867
+ return {
2868
+ beat: () => {
2869
+ lastBeat = Date.now();
2870
+ },
2871
+ isConnected: () => lastBeat > 0 && Date.now() - lastBeat < timeoutMs,
2872
+ _setLastBeat: (ts) => {
2873
+ lastBeat = ts;
2874
+ }
2875
+ };
2876
+ }
2768
2877
  function startWizardServer(options) {
2769
2878
  return new Promise((resolve3, reject) => {
2879
+ const heartbeat = createHeartbeatTracker();
2770
2880
  let completionResolve;
2771
2881
  const completionPromise = new Promise((r) => {
2772
2882
  completionResolve = r;
@@ -2786,7 +2896,8 @@ function startWizardServer(options) {
2786
2896
  req,
2787
2897
  res,
2788
2898
  options,
2789
- onComplete
2899
+ onComplete,
2900
+ heartbeat
2790
2901
  );
2791
2902
  if (!handled) {
2792
2903
  res.writeHead(404, { "Content-Type": "application/json" });
@@ -2817,7 +2928,8 @@ function startWizardServer(options) {
2817
2928
  connections.clear();
2818
2929
  server.close(() => r());
2819
2930
  }),
2820
- waitForCompletion: () => completionPromise
2931
+ waitForCompletion: () => completionPromise,
2932
+ heartbeat
2821
2933
  });
2822
2934
  });
2823
2935
  server.on("error", reject);
@@ -2833,7 +2945,7 @@ async function runSetup() {
2833
2945
  log(" Cortex MCP Setup");
2834
2946
  log(" Starting setup wizard...");
2835
2947
  log("");
2836
- const { port, close, waitForCompletion } = await startWizardServer({
2948
+ const { port, close, waitForCompletion, heartbeat } = await startWizardServer({
2837
2949
  serverUrl: DEFAULT_SERVER_URL
2838
2950
  });
2839
2951
  const url = `http://localhost:${port}`;
@@ -2858,7 +2970,25 @@ async function runSetup() {
2858
2970
  };
2859
2971
  process.on("SIGINT", cleanup);
2860
2972
  process.on("SIGTERM", cleanup);
2973
+ let tabState = "unknown";
2974
+ const heartbeatInterval = setInterval(() => {
2975
+ const connected = heartbeat.isConnected();
2976
+ if (connected && tabState !== "connected") {
2977
+ if (tabState === "disconnected") {
2978
+ log(" \u2713 Browser reconnected \u2014 resuming where you left off.");
2979
+ }
2980
+ tabState = "connected";
2981
+ } else if (!connected && tabState === "connected") {
2982
+ tabState = "disconnected";
2983
+ log("");
2984
+ log(" \u26A0 Browser tab disconnected.");
2985
+ log(` Reopen the wizard: ${url}`);
2986
+ log(" Your progress has been saved \u2014 the wizard will resume where you left off.");
2987
+ log("");
2988
+ }
2989
+ }, 5e3);
2861
2990
  await waitForCompletion();
2991
+ clearInterval(heartbeatInterval);
2862
2992
  await new Promise((r) => setTimeout(r, 300));
2863
2993
  await close();
2864
2994
  log("");
@@ -3869,6 +3999,7 @@ async function startStdioServer(options) {
3869
3999
  "7. Set ALL environment variables on Vercel (vercel__set_env_vars_batch) BEFORE deploying:",
3870
4000
  " - Supabase: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY",
3871
4001
  " - Auth (if set up): NEXT_PUBLIC_CORTEX_URL, NEXT_PUBLIC_CORTEX_CLIENT_ID, CORTEX_CLIENT_ID, CORTEX_CLIENT_SECRET",
4002
+ " - AI (if needed): ANTHROPIC_API_KEY (ctx_ key, sensitive), ANTHROPIC_BASE_URL (Cortex proxy URL)",
3872
4003
  " - Extract all values from tool responses \u2014 never ask the user to provide or copy keys",
3873
4004
  "8. Deploy to Vercel (vercel__deploy) and verify deployment succeeds (vercel__get_deployment)",
3874
4005
  " - If deployment fails, check logs (vercel__get_deployment_logs), fix code, push fix, and retry",
@@ -3937,7 +4068,8 @@ async function startStdioServer(options) {
3937
4068
  "### Phase 2: Plan Infrastructure",
3938
4069
  "6. Determine if the app needs a database (user accounts, persistent data \u2192 yes; static content \u2192 no)",
3939
4070
  "7. Determine if the app needs authentication (multi-user, personalized data \u2192 yes; public content \u2192 no)",
3940
- "8. Present the plan to the user: 'Your app needs: GitHub repo, Vercel deployment, [Supabase DB + auth / no DB]'",
4071
+ "8. Determine if the app needs AI (document parsing, chat, content generation, classification \u2192 yes; purely CRUD/display \u2192 no)",
4072
+ "9. Present the plan to the user: 'Your app needs: GitHub repo, Vercel deployment, [Supabase DB + auth / no DB], [AI features via Cortex / no AI]'",
3941
4073
  "",
3942
4074
  "### Phase 3: Build with Brand",
3943
4075
  "9. Write Next.js + TypeScript code using Sonance Brand components:",
@@ -3956,6 +4088,7 @@ async function startStdioServer(options) {
3956
4088
  "13. Set ALL env vars on Vercel using `vercel__set_env_vars_batch` BEFORE deploying:",
3957
4089
  " - Supabase: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY",
3958
4090
  " - Auth: NEXT_PUBLIC_CORTEX_URL, NEXT_PUBLIC_CORTEX_CLIENT_ID, CORTEX_CLIENT_ID, CORTEX_CLIENT_SECRET",
4091
+ " - AI: ANTHROPIC_API_KEY (ctx_ key, sensitive), ANTHROPIC_BASE_URL (https://cortex-bice.vercel.app)",
3959
4092
  " - Extract ALL values from tool responses \u2014 NEVER ask the user to copy keys",
3960
4093
  "14. Deploy: `vercel__deploy` then verify with `vercel__get_deployment`",
3961
4094
  " - If ERROR: get logs, fix code, push fix, retry until READY",
@@ -3975,8 +4108,17 @@ async function startStdioServer(options) {
3975
4108
  "- Deployment verification",
3976
4109
  "- Quality checks and brand evaluation",
3977
4110
  "",
4111
+ "## AI-Powered Features",
4112
+ "",
4113
+ "When the app needs AI (document parsing, chat, content generation):",
4114
+ "- ANTHROPIC_API_KEY and ANTHROPIC_BASE_URL are auto-provisioned during deploy",
4115
+ "- App code uses `new Anthropic()` with no args \u2014 the SDK reads env vars automatically",
4116
+ "- All requests route through Cortex Apollo proxy (cost tracking, rate limiting)",
4117
+ "- AI calls MUST be server-side only (Next.js API routes), never browser-side",
4118
+ "- NEVER use raw sk-ant- keys \u2014 always use Cortex-provisioned ctx_ keys",
4119
+ "",
3978
4120
  "## Key Rules",
3979
- "- The user should NEVER manually set env vars, copy keys, or configure auth",
4121
+ "- The user should NEVER manually set env vars, copy keys, or configure auth/AI",
3980
4122
  "- Always use Next.js + TypeScript, never plain React or HTML for production apps",
3981
4123
  "- Always use Sonance Brand components \u2014 never write custom Button/Card/Navbar with hardcoded styles"
3982
4124
  ].join("\n");