@danainnovations/cortex-mcp 1.0.4 → 1.0.7

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
@@ -10,29 +10,33 @@ import * as readline from "readline";
10
10
  var DEFAULT_SERVER_URL = "https://cortex-bice.vercel.app";
11
11
  var PROTOCOL_VERSION = "2024-11-05";
12
12
  var AVAILABLE_MCPS = [
13
+ {
14
+ name: "asana",
15
+ displayName: "Asana",
16
+ description: "Projects, tasks, teams, workspaces (17 tools)",
17
+ serverName: "cortex-asana",
18
+ authMode: "personal"
19
+ },
13
20
  {
14
21
  name: "github",
15
22
  displayName: "GitHub",
16
23
  description: "Repos, PRs, issues, branches, code review (30 tools)",
17
- serverName: "cortex-github"
24
+ serverName: "cortex-github",
25
+ authMode: "company"
18
26
  },
19
27
  {
20
28
  name: "vercel",
21
29
  displayName: "Vercel",
22
30
  description: "Deployments, projects, env vars (15 tools)",
23
- serverName: "cortex-vercel"
31
+ serverName: "cortex-vercel",
32
+ authMode: "company"
24
33
  },
25
34
  {
26
35
  name: "supabase",
27
36
  displayName: "Supabase",
28
37
  description: "Database, migrations, edge functions (20+ tools)",
29
- serverName: "cortex-supabase"
30
- },
31
- {
32
- name: "sonance_brand",
33
- displayName: "Sonance Brand",
34
- description: "Brand design system, product data",
35
- serverName: "cortex-sonance-brand"
38
+ serverName: "cortex-supabase",
39
+ authMode: "company"
36
40
  }
37
41
  ];
38
42
  var MCP_NAMES = AVAILABLE_MCPS.map((m) => m.name);
@@ -40,6 +44,7 @@ var DEFAULT_MCPS = [...MCP_NAMES];
40
44
  var DEFAULT_API_KEY = "ctx_07d37a81_9f7be06af38d04753090a4034f907a65ec06cd675ed26f65653898388e2d1709";
41
45
  var CONFIG_DIR_NAME = ".cortex-mcp";
42
46
  var CONFIG_FILE_NAME = "config.json";
47
+ var CREDENTIALS_FILE_NAME = "credentials.json";
43
48
 
44
49
  // src/config/storage.ts
45
50
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -214,7 +219,7 @@ function generateStdioSnippet(apiKey) {
214
219
  mcpServers: {
215
220
  cortex: {
216
221
  command: "npx",
217
- args: ["-y", "@danainnovations/cortex-mcp", "serve"],
222
+ args: ["-y", "@danainnovations/cortex-mcp@latest", "serve"],
218
223
  env: { CORTEX_API_KEY: apiKey }
219
224
  }
220
225
  }
@@ -267,6 +272,352 @@ function configureClient(clientType, serverUrl, apiKey, mcps) {
267
272
  }
268
273
  }
269
274
 
275
+ // src/auth/credentials.ts
276
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
277
+ import { join as join3 } from "path";
278
+ function getCredentialsPath() {
279
+ return join3(getHomeDir(), CONFIG_DIR_NAME, CREDENTIALS_FILE_NAME);
280
+ }
281
+ function readCredentials() {
282
+ const path = getCredentialsPath();
283
+ if (!existsSync3(path)) return null;
284
+ try {
285
+ const raw = readFileSync3(path, "utf-8");
286
+ return JSON.parse(raw);
287
+ } catch {
288
+ return null;
289
+ }
290
+ }
291
+ function writeCredentials(creds) {
292
+ const dir = join3(getHomeDir(), CONFIG_DIR_NAME);
293
+ if (!existsSync3(dir)) {
294
+ mkdirSync3(dir, { recursive: true, mode: 448 });
295
+ }
296
+ const path = getCredentialsPath();
297
+ writeFileSync3(path, JSON.stringify(creds, null, 2) + "\n", {
298
+ mode: 384
299
+ });
300
+ }
301
+ function deleteCredentials() {
302
+ const path = getCredentialsPath();
303
+ if (existsSync3(path)) {
304
+ unlinkSync(path);
305
+ }
306
+ }
307
+ function getEffectiveApiKey() {
308
+ const envKey = process.env.CORTEX_API_KEY;
309
+ if (envKey) return envKey;
310
+ const creds = readCredentials();
311
+ if (creds?.apiKey) return creds.apiKey;
312
+ return DEFAULT_API_KEY;
313
+ }
314
+
315
+ // src/utils/browser.ts
316
+ import { exec } from "child_process";
317
+ function openBrowser(url) {
318
+ return new Promise((resolve) => {
319
+ const platform2 = getPlatform();
320
+ const cmd = platform2 === "macos" ? "open" : platform2 === "windows" ? "start" : "xdg-open";
321
+ exec(`${cmd} "${url}"`, () => resolve());
322
+ });
323
+ }
324
+
325
+ // src/cli/connect.ts
326
+ function log(msg) {
327
+ process.stderr.write(msg + "\n");
328
+ }
329
+ function sleep(ms) {
330
+ return new Promise((resolve) => setTimeout(resolve, ms));
331
+ }
332
+ async function connectProvider(providerName, displayName, apiKey, serverUrl) {
333
+ let sessionId;
334
+ let authUrl;
335
+ let expiresIn;
336
+ try {
337
+ const resp = await fetch(
338
+ `${serverUrl}/api/v1/oauth/connect/${providerName}/initiate`,
339
+ {
340
+ method: "POST",
341
+ headers: {
342
+ "Content-Type": "application/json",
343
+ "x-api-key": apiKey
344
+ }
345
+ }
346
+ );
347
+ if (!resp.ok) {
348
+ const err = await resp.json().catch(() => ({}));
349
+ log(` Error: ${err.detail || `HTTP ${resp.status}`}`);
350
+ return false;
351
+ }
352
+ const data = await resp.json();
353
+ sessionId = data.session_id;
354
+ authUrl = data.authorization_url;
355
+ expiresIn = data.expires_in;
356
+ } catch (err) {
357
+ const msg = err instanceof Error ? err.message : String(err);
358
+ log(` Error: Could not reach Cortex server at ${serverUrl}`);
359
+ log(` ${msg}`);
360
+ return false;
361
+ }
362
+ log(` Opening browser for ${displayName} authorization...`);
363
+ await openBrowser(authUrl);
364
+ log(" If the browser didn't open, visit:");
365
+ log(` ${authUrl}`);
366
+ log("");
367
+ const deadline = Date.now() + expiresIn * 1e3;
368
+ process.stderr.write(" Waiting for authorization");
369
+ while (Date.now() < deadline) {
370
+ await sleep(3e3);
371
+ try {
372
+ const resp = await fetch(
373
+ `${serverUrl}/api/v1/oauth/connect/poll/${sessionId}`
374
+ );
375
+ const result = await resp.json();
376
+ if (result.status === "completed") {
377
+ process.stderr.write("\n\n");
378
+ const who = result.account_email || "unknown";
379
+ log(` Connected as ${who}`);
380
+ log(` ${displayName} tools will now use your personal account.`);
381
+ log("");
382
+ return true;
383
+ }
384
+ if (result.status === "expired") {
385
+ process.stderr.write("\n");
386
+ log(" Authorization session expired. Please try again.");
387
+ return false;
388
+ }
389
+ if (result.status === "error") {
390
+ process.stderr.write("\n");
391
+ log(
392
+ ` Connection failed: ${result.error_message || "unknown error"}`
393
+ );
394
+ return false;
395
+ }
396
+ process.stderr.write(".");
397
+ } catch {
398
+ process.stderr.write("!");
399
+ }
400
+ }
401
+ process.stderr.write("\n");
402
+ log(" Timeout waiting for authorization. Please try again.");
403
+ return false;
404
+ }
405
+ async function runConnect(provider) {
406
+ const creds = readCredentials();
407
+ if (!creds) {
408
+ log("");
409
+ log(" You must be logged in first.");
410
+ log(" Run: cortex-mcp login");
411
+ log("");
412
+ process.exit(1);
413
+ }
414
+ const mcp = AVAILABLE_MCPS.find(
415
+ (m) => m.name === provider || m.displayName.toLowerCase() === provider.toLowerCase()
416
+ );
417
+ if (!mcp) {
418
+ log("");
419
+ log(` Unknown provider: ${provider}`);
420
+ log(` Available: ${AVAILABLE_MCPS.map((m) => m.name).join(", ")}`);
421
+ log("");
422
+ process.exit(1);
423
+ }
424
+ const config = readConfig();
425
+ const serverUrl = config?.server || DEFAULT_SERVER_URL;
426
+ log("");
427
+ log(` Cortex MCP \u2014 Connect ${mcp.displayName}`);
428
+ log(` Link your personal ${mcp.displayName} account`);
429
+ log("");
430
+ const success = await connectProvider(
431
+ mcp.name,
432
+ mcp.displayName,
433
+ creds.apiKey,
434
+ serverUrl
435
+ );
436
+ if (!success) {
437
+ process.exit(1);
438
+ }
439
+ }
440
+
441
+ // src/cli/login.ts
442
+ function log2(msg) {
443
+ process.stderr.write(msg + "\n");
444
+ }
445
+ function sleep2(ms) {
446
+ return new Promise((resolve) => setTimeout(resolve, ms));
447
+ }
448
+ async function loginWithSSO(serverUrl) {
449
+ let sessionId;
450
+ let authUrl;
451
+ let expiresIn;
452
+ try {
453
+ const resp = await fetch(`${serverUrl}/api/v1/auth/employee/initiate`, {
454
+ method: "POST",
455
+ headers: { "Content-Type": "application/json" }
456
+ });
457
+ if (!resp.ok) {
458
+ log2(` Error: Failed to start authentication (HTTP ${resp.status})`);
459
+ return null;
460
+ }
461
+ const data = await resp.json();
462
+ sessionId = data.session_id;
463
+ authUrl = data.auth_url;
464
+ expiresIn = data.expires_in;
465
+ } catch (err) {
466
+ const msg = err instanceof Error ? err.message : String(err);
467
+ log2(` Error: Could not reach Cortex server at ${serverUrl}`);
468
+ log2(` ${msg}`);
469
+ return null;
470
+ }
471
+ log2(" Opening browser for Okta SSO login...");
472
+ await openBrowser(authUrl);
473
+ log2(" If the browser didn't open, visit:");
474
+ log2(` ${authUrl}`);
475
+ log2("");
476
+ const deadline = Date.now() + expiresIn * 1e3;
477
+ process.stderr.write(" Waiting for authentication");
478
+ while (Date.now() < deadline) {
479
+ await sleep2(3e3);
480
+ try {
481
+ const resp = await fetch(
482
+ `${serverUrl}/api/v1/auth/employee/poll/${sessionId}`
483
+ );
484
+ const result = await resp.json();
485
+ if (result.status === "completed") {
486
+ process.stderr.write("\n\n");
487
+ const email = result.employee_email || result.user_info?.email || "unknown";
488
+ const name = result.employee_name || result.user_info?.name || "";
489
+ const apiKey = result.api_key || "";
490
+ return { apiKey, email, name: name || void 0 };
491
+ }
492
+ if (result.status === "expired") {
493
+ process.stderr.write("\n");
494
+ log2(" Authentication session expired. Please try again.");
495
+ return null;
496
+ }
497
+ if (result.status === "error") {
498
+ process.stderr.write("\n");
499
+ log2(` Authentication failed: ${result.error_message || "unknown error"}`);
500
+ return null;
501
+ }
502
+ process.stderr.write(".");
503
+ } catch {
504
+ process.stderr.write("!");
505
+ }
506
+ }
507
+ process.stderr.write("\n");
508
+ log2(" Timeout waiting for authentication. Please try again.");
509
+ return null;
510
+ }
511
+ async function showConnectionsAndAutoConnect(apiKey, serverUrl) {
512
+ let existingConnections = [];
513
+ try {
514
+ const resp = await fetch(`${serverUrl}/api/v1/oauth/connections`, {
515
+ headers: { "x-api-key": apiKey }
516
+ });
517
+ if (resp.ok) {
518
+ const data = await resp.json();
519
+ existingConnections = data.connections || [];
520
+ }
521
+ } catch {
522
+ }
523
+ log2("");
524
+ log2(" MCP Connections");
525
+ log2(" " + "\u2500".repeat(45));
526
+ const personalMcps = [];
527
+ for (const mcp of AVAILABLE_MCPS) {
528
+ const conn = existingConnections.find((c) => c.mcp_name === mcp.name);
529
+ if (mcp.authMode === "company") {
530
+ log2(` ${mcp.displayName.padEnd(15)} company default`);
531
+ } else {
532
+ if (conn && !conn.is_company_default && conn.account_email) {
533
+ log2(` ${mcp.displayName.padEnd(15)} ${conn.account_email}`);
534
+ } else {
535
+ log2(
536
+ ` ${mcp.displayName.padEnd(15)} not connected (personal account required)`
537
+ );
538
+ personalMcps.push({ name: mcp.name, displayName: mcp.displayName });
539
+ }
540
+ }
541
+ }
542
+ log2("");
543
+ if (personalMcps.length === 0) {
544
+ log2(" All accounts connected!");
545
+ log2("");
546
+ return;
547
+ }
548
+ for (const mcp of personalMcps) {
549
+ log2(
550
+ ` ${mcp.displayName} requires a personal connection to access your data.`
551
+ );
552
+ log2(` Connecting now...`);
553
+ log2("");
554
+ const success = await connectProvider(
555
+ mcp.name,
556
+ mcp.displayName,
557
+ apiKey,
558
+ serverUrl
559
+ );
560
+ if (!success) {
561
+ log2(
562
+ ` You can connect later with: cortex-mcp connect ${mcp.name}`
563
+ );
564
+ log2("");
565
+ }
566
+ }
567
+ }
568
+ async function runLogin() {
569
+ const existing = readCredentials();
570
+ if (existing) {
571
+ log2("");
572
+ log2(` Already logged in as ${existing.name || existing.email}`);
573
+ log2(" Run 'cortex-mcp logout' first to switch accounts.");
574
+ log2("");
575
+ return;
576
+ }
577
+ const config = readConfig();
578
+ const serverUrl = config?.server || DEFAULT_SERVER_URL;
579
+ log2("");
580
+ log2(" Cortex MCP Login");
581
+ log2(" Sign in with your company Okta SSO account");
582
+ log2("");
583
+ const result = await loginWithSSO(serverUrl);
584
+ if (!result) {
585
+ process.exit(1);
586
+ }
587
+ const { apiKey, email, name } = result;
588
+ writeCredentials({
589
+ apiKey,
590
+ email,
591
+ name,
592
+ authenticatedAt: (/* @__PURE__ */ new Date()).toISOString()
593
+ });
594
+ log2(` Authenticated as ${name || email}${name ? ` (${email})` : ""}`);
595
+ log2(" Personal API key saved.");
596
+ const existingConfig = readConfig();
597
+ if (existingConfig && existingConfig.configuredClients && existingConfig.configuredClients.length > 0) {
598
+ log2("");
599
+ log2(" Updating configured clients:");
600
+ for (const clientType of existingConfig.configuredClients) {
601
+ try {
602
+ configureClient(
603
+ clientType,
604
+ existingConfig.server || DEFAULT_SERVER_URL,
605
+ apiKey,
606
+ existingConfig.mcps
607
+ );
608
+ log2(` ${clientType}: updated`);
609
+ } catch {
610
+ log2(` ${clientType}: failed to update`);
611
+ }
612
+ }
613
+ existingConfig.apiKey = apiKey;
614
+ writeConfig(existingConfig);
615
+ }
616
+ await showConnectionsAndAutoConnect(apiKey, serverUrl);
617
+ log2(" Done! Restart your AI clients to apply.");
618
+ log2("");
619
+ }
620
+
270
621
  // src/cli/setup.ts
271
622
  var rl = readline.createInterface({
272
623
  input: process.stdin,
@@ -277,28 +628,29 @@ function ask(question) {
277
628
  rl.question(question, (answer) => resolve(answer.trim()));
278
629
  });
279
630
  }
280
- function log(msg) {
631
+ function log3(msg) {
281
632
  process.stderr.write(msg + "\n");
282
633
  }
283
634
  async function runSetup() {
284
- const apiKey = DEFAULT_API_KEY;
285
- log("");
286
- log(" Cortex MCP Setup");
287
- log(" Connect your AI tools to GitHub, Vercel, Supabase & Sonance Brand");
288
- log("");
289
- log(" Step 1: Select MCPs");
290
- log(" Available MCPs (all enabled by default):\n");
635
+ let apiKey = getEffectiveApiKey();
636
+ let creds = readCredentials();
637
+ log3("");
638
+ log3(" Cortex MCP Setup");
639
+ log3(" Connect your AI tools to Asana, GitHub, Vercel & Supabase");
640
+ log3("");
641
+ log3(" Step 1: Select MCPs");
642
+ log3(" Available MCPs (all enabled by default):\n");
291
643
  for (const mcp of AVAILABLE_MCPS) {
292
- log(` - ${mcp.displayName.padEnd(15)} ${mcp.description}`);
644
+ log3(` - ${mcp.displayName.padEnd(15)} ${mcp.description}`);
293
645
  }
294
- log("");
646
+ log3("");
295
647
  const mcpInput = await ask(
296
648
  ` MCPs to enable (comma-separated, or press Enter for all): `
297
649
  );
298
650
  let selectedMcps;
299
651
  if (!mcpInput) {
300
652
  selectedMcps = [...DEFAULT_MCPS];
301
- log(" All MCPs enabled.\n");
653
+ log3(" All MCPs enabled.\n");
302
654
  } else {
303
655
  selectedMcps = mcpInput.split(",").map((s) => s.trim().toLowerCase()).filter(
304
656
  (s) => AVAILABLE_MCPS.some(
@@ -313,58 +665,103 @@ async function runSetup() {
313
665
  });
314
666
  if (selectedMcps.length === 0) {
315
667
  selectedMcps = [...DEFAULT_MCPS];
316
- log(" No valid MCPs recognized. Enabling all.\n");
668
+ log3(" No valid MCPs recognized. Enabling all.\n");
317
669
  } else {
318
- log(` Enabled: ${selectedMcps.join(", ")}
670
+ log3(` Enabled: ${selectedMcps.join(", ")}
319
671
  `);
320
672
  }
321
673
  }
322
- log(" Step 2: Configure AI Clients\n");
674
+ log3(" Step 2: Configure AI Clients\n");
323
675
  const clients = detectClients();
324
676
  const configuredClients = [];
325
677
  for (const client of clients) {
326
678
  if (!client.detected) {
327
- log(` ${client.name}: not detected, skipping`);
679
+ log3(` ${client.name}: not detected, skipping`);
328
680
  continue;
329
681
  }
330
682
  const answer = await ask(` Configure ${client.name}? (Y/n): `);
331
683
  if (answer.toLowerCase() === "n") {
332
- log(` Skipping ${client.name}`);
684
+ log3(` Skipping ${client.name}`);
333
685
  continue;
334
686
  }
335
687
  try {
336
688
  if (client.type === "claude-desktop") {
337
689
  configureClaudeDesktop(DEFAULT_SERVER_URL, apiKey, selectedMcps);
338
- log(` ${client.name}: configured`);
690
+ log3(` ${client.name}: configured`);
339
691
  configuredClients.push(client.type);
340
692
  } else if (client.type === "claude-code") {
341
693
  configureClaudeCode(DEFAULT_SERVER_URL, apiKey, selectedMcps);
342
- log(` ${client.name}: configured`);
694
+ log3(` ${client.name}: configured`);
343
695
  configuredClients.push(client.type);
344
696
  }
345
697
  } catch (err) {
346
698
  const msg = err instanceof Error ? err.message : String(err);
347
- log(` ${client.name}: failed \u2014 ${msg}`);
699
+ log3(` ${client.name}: failed \u2014 ${msg}`);
348
700
  }
349
701
  }
350
- log("");
702
+ log3("");
351
703
  const stdioAnswer = await ask(
352
704
  " Do you also need a config snippet for OpenClaw or other stdio clients? (y/N): "
353
705
  );
354
706
  if (stdioAnswer.toLowerCase() === "y") {
355
- log("\n Add this to your client's MCP config:\n");
356
- log(generateStdioSnippet(apiKey));
357
- log("");
707
+ log3("\n Add this to your client's MCP config:\n");
708
+ log3(generateStdioSnippet(apiKey));
709
+ log3("");
358
710
  configuredClients.push("stdio");
359
711
  }
360
712
  const config = createConfig(apiKey, selectedMcps);
361
713
  config.configuredClients = configuredClients;
362
714
  writeConfig(config);
363
- log("");
364
- log(" Setup complete! Restart your AI clients to see the new tools.");
365
- log(` Config saved to ~/.cortex-mcp/config.json`);
366
- log("");
367
- rl.close();
715
+ log3("");
716
+ log3(" Step 3: Sign In\n");
717
+ if (creds) {
718
+ log3(` Already signed in as ${creds.name || creds.email}`);
719
+ apiKey = creds.apiKey;
720
+ } else {
721
+ log3(" Sign in with your company Okta SSO account.");
722
+ log3("");
723
+ rl.close();
724
+ const result = await loginWithSSO(DEFAULT_SERVER_URL);
725
+ if (!result) {
726
+ log3(" Login failed. Run 'cortex-mcp login' to try again.");
727
+ process.exit(1);
728
+ }
729
+ const { apiKey: newKey, email, name } = result;
730
+ apiKey = newKey;
731
+ writeCredentials({
732
+ apiKey: newKey,
733
+ email,
734
+ name,
735
+ authenticatedAt: (/* @__PURE__ */ new Date()).toISOString()
736
+ });
737
+ log3(` Authenticated as ${name || email}${name ? ` (${email})` : ""}`);
738
+ log3(" Personal API key saved.");
739
+ if (configuredClients.length > 0) {
740
+ log3("");
741
+ log3(" Updating clients with personal key:");
742
+ for (const clientType of configuredClients) {
743
+ try {
744
+ if (clientType === "claude-desktop") {
745
+ configureClaudeDesktop(DEFAULT_SERVER_URL, newKey, selectedMcps);
746
+ } else if (clientType === "claude-code") {
747
+ configureClaudeCode(DEFAULT_SERVER_URL, newKey, selectedMcps);
748
+ }
749
+ log3(` ${clientType}: updated`);
750
+ } catch {
751
+ log3(` ${clientType}: failed to update`);
752
+ }
753
+ }
754
+ }
755
+ config.apiKey = newKey;
756
+ writeConfig(config);
757
+ creds = readCredentials();
758
+ }
759
+ log3("");
760
+ log3(" Step 4: Connect Accounts\n");
761
+ await showConnectionsAndAutoConnect(apiKey, DEFAULT_SERVER_URL);
762
+ log3(" Setup complete! Restart your AI clients to see the new tools.");
763
+ log3(` Config saved to ~/.cortex-mcp/config.json`);
764
+ log3("");
368
765
  }
369
766
 
370
767
  // src/cli/configure.ts
@@ -374,7 +771,7 @@ var VALID_CLIENTS = {
374
771
  stdio: "stdio"
375
772
  };
376
773
  async function runConfigure(options) {
377
- const key = options.key || DEFAULT_API_KEY;
774
+ const key = options.key || getEffectiveApiKey();
378
775
  const { client } = options;
379
776
  const clientType = VALID_CLIENTS[client];
380
777
  if (!clientType) {
@@ -557,18 +954,9 @@ async function startStdioServer(options) {
557
954
 
558
955
  // src/cli/serve.ts
559
956
  async function runServe() {
560
- let apiKey = process.env.CORTEX_API_KEY;
561
- let serverUrl = DEFAULT_SERVER_URL;
562
- if (!apiKey) {
563
- const config = readConfig();
564
- if (config) {
565
- apiKey = config.apiKey;
566
- serverUrl = config.server || DEFAULT_SERVER_URL;
567
- }
568
- }
569
- if (!apiKey) {
570
- apiKey = DEFAULT_API_KEY;
571
- }
957
+ const config = readConfig();
958
+ let serverUrl = config?.server || DEFAULT_SERVER_URL;
959
+ let apiKey = getEffectiveApiKey();
572
960
  await startStdioServer({ serverUrl, apiKey });
573
961
  }
574
962
 
@@ -580,9 +968,16 @@ function runStatus() {
580
968
  return;
581
969
  }
582
970
  const maskedKey = config.apiKey.slice(0, 8) + "****" + config.apiKey.slice(-4);
971
+ const creds = readCredentials();
583
972
  console.log("");
584
973
  console.log(" Cortex MCP Status");
585
974
  console.log(" -----------------");
975
+ if (creds) {
976
+ console.log(` User: ${creds.name || creds.email}`);
977
+ console.log(` Email: ${creds.email}`);
978
+ } else {
979
+ console.log(" User: not logged in (using shared key)");
980
+ }
586
981
  console.log(` Config: ${getConfigPath()}`);
587
982
  console.log(` Server: ${config.server}`);
588
983
  console.log(` API Key: ${maskedKey}`);
@@ -606,7 +1001,7 @@ function runStatus() {
606
1001
  }
607
1002
 
608
1003
  // src/cli/reset.ts
609
- import { existsSync as existsSync3, unlinkSync, rmdirSync } from "fs";
1004
+ import { existsSync as existsSync4, unlinkSync as unlinkSync2, rmdirSync } from "fs";
610
1005
  function runReset() {
611
1006
  console.log("");
612
1007
  console.log(" Resetting Cortex MCP configuration...");
@@ -619,8 +1014,8 @@ function runReset() {
619
1014
  ` Claude Code: ${codeReset ? "entries removed" : "no entries found"}`
620
1015
  );
621
1016
  const configPath = getConfigPath();
622
- if (existsSync3(configPath)) {
623
- unlinkSync(configPath);
1017
+ if (existsSync4(configPath)) {
1018
+ unlinkSync2(configPath);
624
1019
  console.log(` Config file: removed`);
625
1020
  }
626
1021
  const configDir = getConfigDir();
@@ -630,13 +1025,181 @@ function runReset() {
630
1025
  }
631
1026
  console.log("");
632
1027
  console.log(" Done. Restart your AI clients to apply changes.");
1028
+ console.log(" Note: Login credentials are preserved. Run 'cortex-mcp logout' to sign out.");
633
1029
  console.log("");
634
1030
  }
635
1031
 
1032
+ // src/cli/logout.ts
1033
+ function runLogout() {
1034
+ const creds = readCredentials();
1035
+ if (!creds) {
1036
+ console.log("");
1037
+ console.log(" Not currently logged in.");
1038
+ console.log("");
1039
+ return;
1040
+ }
1041
+ deleteCredentials();
1042
+ console.log("");
1043
+ console.log(` Logged out (was: ${creds.email})`);
1044
+ console.log(" MCP tools will use the shared default key.");
1045
+ console.log(" Run 'cortex-mcp login' to sign in again.");
1046
+ console.log("");
1047
+ }
1048
+
1049
+ // src/cli/whoami.ts
1050
+ function runWhoami() {
1051
+ const creds = readCredentials();
1052
+ if (!creds) {
1053
+ console.log("");
1054
+ console.log(" Not logged in. Using shared default API key.");
1055
+ console.log(" Run 'cortex-mcp login' to authenticate.");
1056
+ console.log("");
1057
+ return;
1058
+ }
1059
+ console.log("");
1060
+ console.log(" Cortex MCP Account");
1061
+ console.log(" ------------------");
1062
+ if (creds.name) {
1063
+ console.log(` Name: ${creds.name}`);
1064
+ }
1065
+ console.log(` Email: ${creds.email}`);
1066
+ console.log(` Authenticated: ${creds.authenticatedAt}`);
1067
+ console.log(
1068
+ ` API Key: ${creds.apiKey.slice(0, 8)}****${creds.apiKey.slice(-4)}`
1069
+ );
1070
+ console.log("");
1071
+ }
1072
+
1073
+ // src/cli/connections.ts
1074
+ function log4(msg) {
1075
+ process.stderr.write(msg + "\n");
1076
+ }
1077
+ async function runConnections() {
1078
+ const creds = readCredentials();
1079
+ if (!creds) {
1080
+ log4("");
1081
+ log4(" Not logged in. Run: cortex-mcp login");
1082
+ log4("");
1083
+ process.exit(1);
1084
+ }
1085
+ const config = readConfig();
1086
+ const serverUrl = config?.server || DEFAULT_SERVER_URL;
1087
+ try {
1088
+ const resp = await fetch(`${serverUrl}/api/v1/oauth/connections`, {
1089
+ headers: { "x-api-key": creds.apiKey }
1090
+ });
1091
+ if (!resp.ok) {
1092
+ log4("");
1093
+ log4(" Error fetching connections.");
1094
+ log4("");
1095
+ process.exit(1);
1096
+ }
1097
+ const data = await resp.json();
1098
+ const connections = data.connections || [];
1099
+ log4("");
1100
+ log4(" Cortex MCP Connections");
1101
+ log4(" ----------------------");
1102
+ for (const mcp of AVAILABLE_MCPS) {
1103
+ const conn = connections.find((c) => c.mcp_name === mcp.name);
1104
+ let statusText;
1105
+ if (!conn) {
1106
+ statusText = "not connected";
1107
+ } else if (conn.is_company_default) {
1108
+ statusText = "(company default)";
1109
+ } else {
1110
+ statusText = conn.account_email || "connected";
1111
+ }
1112
+ log4(` ${mcp.displayName.padEnd(15)} ${statusText}`);
1113
+ }
1114
+ log4("");
1115
+ log4(" Connect an account: cortex-mcp connect <provider>");
1116
+ log4("");
1117
+ } catch {
1118
+ log4("");
1119
+ log4(" Error: Could not reach Cortex server.");
1120
+ log4("");
1121
+ process.exit(1);
1122
+ }
1123
+ }
1124
+
1125
+ // src/cli/disconnect.ts
1126
+ function log5(msg) {
1127
+ process.stderr.write(msg + "\n");
1128
+ }
1129
+ async function runDisconnect(provider) {
1130
+ const creds = readCredentials();
1131
+ if (!creds) {
1132
+ log5("");
1133
+ log5(" Not logged in. Run: cortex-mcp login");
1134
+ log5("");
1135
+ process.exit(1);
1136
+ }
1137
+ const mcp = AVAILABLE_MCPS.find(
1138
+ (m) => m.name === provider || m.displayName.toLowerCase() === provider.toLowerCase()
1139
+ );
1140
+ if (!mcp) {
1141
+ log5("");
1142
+ log5(` Unknown provider: ${provider}`);
1143
+ log5(` Available: ${AVAILABLE_MCPS.map((m) => m.name).join(", ")}`);
1144
+ log5("");
1145
+ process.exit(1);
1146
+ }
1147
+ const providerName = mcp.name;
1148
+ const displayName = mcp.displayName;
1149
+ const config = readConfig();
1150
+ const serverUrl = config?.server || DEFAULT_SERVER_URL;
1151
+ try {
1152
+ const listResp = await fetch(`${serverUrl}/api/v1/oauth/connections`, {
1153
+ headers: { "x-api-key": creds.apiKey }
1154
+ });
1155
+ if (!listResp.ok) {
1156
+ log5("");
1157
+ log5(" Error fetching connections.");
1158
+ log5("");
1159
+ process.exit(1);
1160
+ }
1161
+ const listData = await listResp.json();
1162
+ const match = (listData.connections || []).find(
1163
+ (c) => c.mcp_name === providerName && !c.is_company_default
1164
+ );
1165
+ if (!match) {
1166
+ log5("");
1167
+ log5(` No personal connection found for ${displayName}.`);
1168
+ log5("");
1169
+ process.exit(1);
1170
+ }
1171
+ const resp = await fetch(
1172
+ `${serverUrl}/api/v1/oauth/connections/${match.id}/disconnect`,
1173
+ {
1174
+ method: "POST",
1175
+ headers: { "x-api-key": creds.apiKey }
1176
+ }
1177
+ );
1178
+ if (resp.ok) {
1179
+ log5("");
1180
+ log5(` Disconnected from ${displayName}.`);
1181
+ log5(
1182
+ ` ${displayName} tools will fall back to the company default token.`
1183
+ );
1184
+ log5("");
1185
+ } else {
1186
+ log5("");
1187
+ log5(" Error disconnecting. Please try again.");
1188
+ log5("");
1189
+ process.exit(1);
1190
+ }
1191
+ } catch {
1192
+ log5("");
1193
+ log5(" Error: Could not reach Cortex server.");
1194
+ log5("");
1195
+ process.exit(1);
1196
+ }
1197
+ }
1198
+
636
1199
  // bin/cli.ts
637
1200
  var program = new Command();
638
1201
  program.name("cortex-mcp").description(
639
- "Connect your AI tools to GitHub, Vercel, Supabase & Sonance Brand via Cortex"
1202
+ "Connect your AI tools to Asana, GitHub, Vercel & Supabase via Cortex"
640
1203
  ).version("1.0.0");
641
1204
  program.command("setup").description("Interactive setup wizard \u2014 configure MCPs and AI clients").action(async () => {
642
1205
  try {
@@ -669,5 +1232,49 @@ program.command("status").description("Show current Cortex MCP configuration").a
669
1232
  program.command("reset").description("Remove all Cortex MCP entries from AI clients").action(() => {
670
1233
  runReset();
671
1234
  });
1235
+ program.command("login").description("Sign in with your company Okta SSO account").action(async () => {
1236
+ try {
1237
+ await runLogin();
1238
+ } catch (err) {
1239
+ if (err.code === "ERR_USE_AFTER_CLOSE") {
1240
+ process.exit(0);
1241
+ }
1242
+ console.error("Login failed:", err);
1243
+ process.exit(1);
1244
+ }
1245
+ });
1246
+ program.command("logout").description("Sign out and remove stored credentials").action(() => {
1247
+ runLogout();
1248
+ });
1249
+ program.command("whoami").description("Show current authenticated user").action(() => {
1250
+ runWhoami();
1251
+ });
1252
+ program.command("connect <provider>").description("Connect your personal account to an MCP service (e.g., asana)").action(async (provider) => {
1253
+ try {
1254
+ await runConnect(provider);
1255
+ } catch (err) {
1256
+ if (err.code === "ERR_USE_AFTER_CLOSE") {
1257
+ process.exit(0);
1258
+ }
1259
+ console.error("Connect failed:", err);
1260
+ process.exit(1);
1261
+ }
1262
+ });
1263
+ program.command("connections").description("List your OAuth connections to MCP services").action(async () => {
1264
+ try {
1265
+ await runConnections();
1266
+ } catch (err) {
1267
+ console.error("Failed:", err);
1268
+ process.exit(1);
1269
+ }
1270
+ });
1271
+ program.command("disconnect <provider>").description("Remove your personal OAuth connection to an MCP service").action(async (provider) => {
1272
+ try {
1273
+ await runDisconnect(provider);
1274
+ } catch (err) {
1275
+ console.error("Disconnect failed:", err);
1276
+ process.exit(1);
1277
+ }
1278
+ });
672
1279
  program.parse();
673
1280
  //# sourceMappingURL=cli.js.map