@theronap/cortex-mcp 0.4.3 → 0.4.5

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.
@@ -16,7 +16,7 @@
16
16
  * Get your token from the Cortex console → Connect your AI.
17
17
  */
18
18
 
19
- const VERSION = '0.4.3'
19
+ const VERSION = '0.4.5'
20
20
  const cmd = process.argv[2]
21
21
  const rest = process.argv.slice(3)
22
22
 
@@ -35,6 +35,7 @@ if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
35
35
  `Subcommands:\n` +
36
36
  ` setup <token> wire MCP server + capture hook into ~/.claude config\n` +
37
37
  ` doctor live health check — confirm your token works (no restart needed)\n` +
38
+ ` status one-line connected/not-connected check (used by the SessionStart hook)\n` +
38
39
  ` capture Stop-hook capturer (invoked by Claude Code)\n` +
39
40
  ` (no args) run the MCP server (used by your Claude config)\n\n` +
40
41
  `Get your token from the Cortex console → Connect your AI.\n`,
@@ -59,6 +60,12 @@ if (cmd === 'setup') {
59
60
  process.exitCode = await runDoctor()
60
61
  const { closeFetch } = await import('../lib/diagnose.mjs')
61
62
  await closeFetch()
63
+ } else if (cmd === 'status') {
64
+ // One-line SessionStart health signal (wired by setup). Always exit 0.
65
+ const { runStatus } = await import('../lib/doctor.mjs')
66
+ await runStatus()
67
+ const { closeFetch } = await import('../lib/diagnose.mjs')
68
+ await closeFetch()
62
69
  } else if (cmd === 'capture') {
63
70
  const { runCapture } = await import('../lib/capture.mjs')
64
71
  await runCapture()
package/lib/capture.mjs CHANGED
@@ -1,6 +1,21 @@
1
1
  import { readFileSync } from 'fs'
2
+ import { homedir } from 'os'
3
+ import { resolve } from 'path'
2
4
  import { fetchCortex, classify, resolveBase } from './diagnose.mjs'
3
5
 
6
+ // Project name from the hook cwd — cross-platform. Windows hooks send backslash paths,
7
+ // and splitting on '/' alone turned the WHOLE path into one garbage project slug
8
+ // ("c-users-webst-onedrive-…", three-machine dry-run finding 2026-06-09). A session run
9
+ // from the home directory itself is 'general', not a project named after the user.
10
+ export function projectFrom(cwd) {
11
+ if (!cwd) return 'general'
12
+ try {
13
+ if (resolve(String(cwd)) === homedir()) return 'general'
14
+ } catch { /* unresolvable path — fall through to basename */ }
15
+ const base = String(cwd).split(/[\\/]+/).filter((s) => s && !/^[A-Za-z]:$/.test(s)).pop()
16
+ return base ?? 'general'
17
+ }
18
+
4
19
  // Claude Code Stop hook → POSTs a session digest to Cortex cloud, which
5
20
  // summarizes server-side and upserts ONE record per session. Node-native
6
21
  // (no bun, no repo clone). Always exits 0 — capture must never break a session.
@@ -36,7 +51,7 @@ export async function runCapture() {
36
51
 
37
52
  let hook = {}
38
53
  try { hook = JSON.parse(readStdin()) } catch { /* no/invalid stdin */ }
39
- const repo = hook.cwd?.split('/').filter(Boolean).pop() ?? 'general'
54
+ const repo = projectFrom(hook.cwd)
40
55
  const transcript = hook.transcript_path ? transcriptTail(hook.transcript_path) : ''
41
56
 
42
57
  if (!transcript && !hook.session_id) { process.stderr.write('cortex: empty session, skipping\n'); return }
package/lib/doctor.mjs CHANGED
@@ -24,6 +24,32 @@ function resolveToken() {
24
24
  return { token: null, source: null }
25
25
  }
26
26
 
27
+ // `status` — the one-line SessionStart variant of doctor: a visible "is Cortex capturing?"
28
+ // signal inside Claude Code itself (three-machine dry-run finding 2026-06-09: with no
29
+ // indicator, a user can't tell whether their sessions are flowing to the org).
30
+ // Always returns 0 — a status line must never break a session start.
31
+ export async function runStatus() {
32
+ const base = resolveBase(process.env.CORTEX_URL)
33
+ const out = (m) => process.stdout.write(m + '\n')
34
+ const { token } = resolveToken()
35
+ if (!token) {
36
+ out('Cortex: NOT connected — no token found. Run: npx -y @theronap/cortex-mcp setup <token>')
37
+ return 0
38
+ }
39
+ try {
40
+ const r = await checkToken(token, base)
41
+ if (r.ok) {
42
+ const n = typeof r.projectCount === 'number' ? ` · ${r.projectCount} project${r.projectCount === 1 ? '' : 's'} visible` : ''
43
+ out(`Cortex: connected — sessions on this machine are captured to your org${n}.`)
44
+ } else {
45
+ out(`Cortex: NOT connected — ${r.diagnosis?.message ?? 'check failed'}. Run: npx -y @theronap/cortex-mcp doctor`)
46
+ }
47
+ } catch (e) {
48
+ out(`Cortex: status check failed (${e?.message ?? String(e)}) — run doctor.`)
49
+ }
50
+ return 0
51
+ }
52
+
27
53
  export async function runDoctor() {
28
54
  const base = resolveBase(process.env.CORTEX_URL)
29
55
  const out = (m) => process.stdout.write(m + '\n')
package/lib/setup.mjs CHANGED
@@ -100,9 +100,24 @@ export async function runSetup(argv, version) {
100
100
  grp.hooks = grp.hooks ?? []
101
101
  grp.hooks.push({ type: 'command', command: captureCmd })
102
102
 
103
+ // Connected-status SessionStart hook: one line inside Claude Code itself saying whether
104
+ // this machine's sessions are flowing to the org (dry-run finding: silence is unreadable).
105
+ // `status` reads the token from ~/.claude.json, so the command carries no secret.
106
+ s.hooks.SessionStart = Array.isArray(s.hooks.SessionStart) ? s.hooks.SessionStart : []
107
+ const statusCmd = `npx -y ${spec} status`
108
+ for (const sg of s.hooks.SessionStart) {
109
+ if (Array.isArray(sg.hooks)) {
110
+ sg.hooks = sg.hooks.filter((h) => !/cortex-mcp(@[^ ]*)? status/.test(h.command ?? ''))
111
+ }
112
+ }
113
+ let sgrp = s.hooks.SessionStart.find((g) => (g.matcher ?? '') === '')
114
+ if (!sgrp) { sgrp = { matcher: '', hooks: [] }; s.hooks.SessionStart.push(sgrp) }
115
+ sgrp.hooks = sgrp.hooks ?? []
116
+ sgrp.hooks.push({ type: 'command', command: statusCmd })
117
+
103
118
  ensureDir(settingsJson)
104
119
  writeFileSync(settingsJson, JSON.stringify(s, null, 2))
105
- log(` ✓ Capture hook → ${settingsJson}${bak ? ' (backup saved)' : ''}`)
120
+ log(` ✓ Capture hook + status line → ${settingsJson}${bak ? ' (backup saved)' : ''}`)
106
121
  } catch (e) {
107
122
  process.stderr.write(` ✗ failed to update ${settingsJson}: ${e.message}\n`)
108
123
  process.exit(1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Connect your AI assistant to Cortex — your org's projects, activity, gaps, and directives, scoped to you.",
5
5
  "type": "module",
6
6
  "bin": {