@theronap/cortex-mcp 0.6.0 → 0.8.0

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/lib/server.mjs CHANGED
@@ -52,13 +52,32 @@ export async function runServer(version) {
52
52
  {
53
53
  title: 'Search the org',
54
54
  description: 'Search your visible work activity and projects by keyword.',
55
- inputSchema: { query: z.string().describe('keyword to search for') },
55
+ inputSchema: { query: z.string().describe('keyword to search for — people or work activity') },
56
56
  },
57
57
  async ({ query }) => {
58
- const text = await fetchContext()
59
- const q = query.toLowerCase()
60
- const lines = text.split('\n').filter((l) => l.toLowerCase().includes(q))
61
- return { content: [{ type: 'text', text: lines.length ? `Matches for "${query}":\n${lines.join('\n')}` : `No visible results for "${query}".` }] }
58
+ // Real search via /api/search: records (RLS-scoped retrieve) + graph entities (people),
59
+ // instead of substring-grepping the cached context. People were previously invisible to search.
60
+ const res = await fetchCortex(`${BASE}/api/search?q=${encodeURIComponent(query)}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
61
+ if (!res.ok) {
62
+ const body = await res.text()
63
+ throw new Error(classify(res.status, res.headers.get('content-type'), body, res.headers.get('x-vercel-id')).message)
64
+ }
65
+ const { people = [], records = [] } = await res.json()
66
+ if (!people.length && !records.length) return { content: [{ type: 'text', text: `No visible results for "${query}".` }] }
67
+ const lines = []
68
+ if (people.length) {
69
+ lines.push('People:')
70
+ for (const p of people) {
71
+ const meta = [p.title, p.company].filter(Boolean).join(', ')
72
+ lines.push(`- ${p.name}${meta ? ` (${meta})` : ''} — mentioned in ${p.mentions} record${p.mentions === 1 ? '' : 's'}`)
73
+ }
74
+ }
75
+ if (records.length) {
76
+ if (lines.length) lines.push('')
77
+ lines.push('Activity:')
78
+ for (const r of records) lines.push(`- [${r.source}] ${r.title}${r.project ? ` (${r.project})` : ''}`)
79
+ }
80
+ return { content: [{ type: 'text', text: `Results for "${query}":\n${lines.join('\n')}` }] }
62
81
  },
63
82
  )
64
83
 
package/lib/setup.mjs CHANGED
@@ -134,7 +134,7 @@ export async function runSetup(argv, version) {
134
134
  process.exit(1)
135
135
  }
136
136
 
137
- // ── 3. Managed skills repository (~/.claude/skills/cortex/) ──────────────
137
+ // ── 3. Managed skills installed flat into every agent CLI present (Claude + Codex) ──
138
138
  try {
139
139
  installSkills({ quiet: false })
140
140
  } catch (e) {
package/lib/skills.mjs CHANGED
@@ -3,24 +3,32 @@ import { homedir } from 'os'
3
3
  import { join, dirname } from 'path'
4
4
  import { fileURLToPath } from 'url'
5
5
 
6
- // Managed Cortex skills repository.
6
+ // Managed Cortex skills.
7
7
  //
8
- // Cortex owns a namespace under the user's Claude skills dir: ~/.claude/skills/cortex/
9
- // cortex/
10
- // README.md explains the namespace is Cortex-managed
11
- // .cortex-manifest.json ← what's managed + content hashes (machine-readable)
12
- // core/<name>/SKILL.md ← MANDATORY, self-healing skills shipped in this package
13
- // (org/ and shared/ are reserved for server-synced skills — not yet populated)
8
+ // Cortex ships mandatory, self-healing skills (e.g. cortex-log) and installs them into EVERY agent
9
+ // CLI present on the machine, at the flat layout each one discovers:
10
+ // ~/.claude/skills/<name>/SKILL.md (Claude Code)
11
+ // ~/.codex/skills/<name>/SKILL.md (OpenAI Codex CLI only if ~/.codex exists)
14
12
  //
15
- // "Inalterable" in practice: the bundled core skills are the source of truth. On every install AND
16
- // on every session start (the SessionStart --repair hook), any core skill whose on-disk content
17
- // drifted from the bundled source is RESTORED the user's version is backed up to <file>.user-bak
18
- // first, so nothing is lost, but the canonical skill always wins. Idempotent: identical content is a
19
- // no-op (no backup, no write, no churn).
13
+ // IMPORTANT: skills MUST be one level under <cli>/skills/ both Claude Code and Codex discover
14
+ // skills as <cli>/skills/<name>/SKILL.md. An earlier layout nested them under skills/cortex/core/,
15
+ // which is installed but INVISIBLE to discovery (the skill never appears as a slash command).
16
+ //
17
+ // "Inalterable" in practice: the bundled skills are the source of truth. On every install AND on
18
+ // every Claude session start (the SessionStart --repair hook), any managed skill whose on-disk
19
+ // content drifted is RESTORED — the user's version is backed up to SKILL.md.user-bak first, so
20
+ // nothing is lost, but the canonical skill always wins. Identical content is a no-op.
20
21
 
21
22
  const HERE = dirname(fileURLToPath(import.meta.url))
22
23
  const BUNDLED = join(HERE, '..', 'skills') // packages/cortex-mcp/skills/<name>/SKILL.md
23
24
 
25
+ // Agent CLIs we install skills into. Claude is primary; Codex is included whenever it's present.
26
+ // Both use the same flat <cli>/skills/<name>/SKILL.md discovery layout.
27
+ const CLIS = [
28
+ { id: 'Claude Code', dir: join(homedir(), '.claude') },
29
+ { id: 'Codex', dir: join(homedir(), '.codex') },
30
+ ]
31
+
24
32
  // djb2 — tiny, dependency-free content fingerprint for the manifest (drift detection, not security).
25
33
  function hash(s) {
26
34
  let h = 5381
@@ -32,90 +40,100 @@ function ensureDir(path) {
32
40
  if (!existsSync(path)) mkdirSync(path, { recursive: true })
33
41
  }
34
42
 
35
- // Discover bundled core skills: each subdir of skills/ that contains a SKILL.md.
43
+ // The invocation name (slash command) comes from the SKILL.md `name:` frontmatter, not the folder.
44
+ // Use it as the install directory so the layout matches what the user types (/cortex-log).
45
+ function frontmatterName(source, fallback) {
46
+ const m = source.match(/^---[\s\S]*?\bname:\s*([^\n#]+)/)
47
+ return m ? m[1].trim() : fallback
48
+ }
49
+
50
+ // Discover bundled skills: each subdir of skills/ that contains a SKILL.md.
36
51
  function bundledSkills() {
37
52
  if (!existsSync(BUNDLED)) return []
38
53
  return readdirSync(BUNDLED, { withFileTypes: true })
39
54
  .filter((d) => d.isDirectory() && existsSync(join(BUNDLED, d.name, 'SKILL.md')))
40
- .map((d) => ({ name: d.name, src: join(BUNDLED, d.name, 'SKILL.md') }))
55
+ .map((d) => {
56
+ const src = join(BUNDLED, d.name, 'SKILL.md')
57
+ const source = readFileSync(src, 'utf8')
58
+ return { name: frontmatterName(source, d.name), src, source }
59
+ })
41
60
  }
42
61
 
43
- const README = `# Cortex-managed skills
44
-
45
- This folder is owned by Cortex (\`@theronap/cortex-mcp\`). Skills under \`core/\` are **mandatory and
46
- self-healing**: if you edit or delete one, Cortex restores it on your next Claude session (your edited
47
- copy is saved as \`SKILL.md.user-bak\` first). To change a core skill, change it upstream in Cortex.
48
-
49
- - \`core/\` — required Cortex skills, shipped and repaired by this package
50
- - \`org/\` — your organization's skills (reserved; synced from Cortex)
51
- - \`shared/\` — skills shared across the org (reserved; synced from Cortex)
52
-
53
- Managed by: \`npx -y @theronap/cortex-mcp skills\` · repaired automatically each session.
54
- `
55
-
56
- /**
57
- * Install / repair the managed Cortex skills.
58
- * @param {{ quiet?: boolean }} opts quiet → only emit on actual change (for the SessionStart hook)
59
- * @returns {{ installed: string[], repaired: string[], unchanged: string[] }}
60
- */
61
- export function installSkills(opts = {}) {
62
- const quiet = !!opts.quiet
63
- const root = join(homedir(), '.claude', 'skills', 'cortex')
64
- const coreDir = join(root, 'core')
65
- const log = (m) => { if (!quiet) process.stdout.write(m + '\n') }
66
-
67
- const skills = bundledSkills()
62
+ // Install/repair all bundled skills into one CLI's skills root. Returns per-CLI change lists.
63
+ function installInto(skillsRoot, skills) {
64
+ ensureDir(skillsRoot)
68
65
  const result = { installed: [], repaired: [], unchanged: [] }
69
- if (!skills.length) return result // nothing bundled (shouldn't happen) — never error
70
-
71
- ensureDir(coreDir)
72
- // Reserved namespaces so the structure is discoverable even before server sync exists.
73
- ensureDir(join(root, 'org'))
74
- ensureDir(join(root, 'shared'))
75
- writeFileSync(join(root, 'README.md'), README)
76
-
77
66
  const manifest = { managed: [], updated_by: 'cortex-mcp', skills: {} }
78
67
 
79
68
  for (const sk of skills) {
80
- const source = readFileSync(sk.src, 'utf8')
81
- const dest = join(coreDir, sk.name, 'SKILL.md')
82
- manifest.managed.push(`core/${sk.name}/SKILL.md`)
83
- manifest.skills[sk.name] = hash(source)
69
+ const dest = join(skillsRoot, sk.name, 'SKILL.md')
70
+ manifest.managed.push(`${sk.name}/SKILL.md`)
71
+ manifest.skills[sk.name] = hash(sk.source)
84
72
 
85
73
  if (!existsSync(dest)) {
86
74
  ensureDir(dirname(dest))
87
- writeFileSync(dest, source)
75
+ writeFileSync(dest, sk.source)
88
76
  result.installed.push(sk.name)
89
77
  continue
90
78
  }
91
79
  const current = readFileSync(dest, 'utf8')
92
- if (current === source) { result.unchanged.push(sk.name); continue }
80
+ if (current === sk.source) { result.unchanged.push(sk.name); continue }
93
81
 
94
82
  // Drift: preserve the user's version, then restore canonical.
95
83
  try { copyFileSync(dest, `${dest}.user-bak`) } catch { /* best-effort backup */ }
96
- writeFileSync(dest, source)
84
+ writeFileSync(dest, sk.source)
97
85
  result.repaired.push(sk.name)
98
86
  }
99
87
 
100
- writeFileSync(join(root, '.cortex-manifest.json'), JSON.stringify(manifest, null, 2))
88
+ writeFileSync(join(skillsRoot, '.cortex-skills.json'), JSON.stringify(manifest, null, 2))
89
+ return result
90
+ }
91
+
92
+ /**
93
+ * Install / repair the managed Cortex skills into every agent CLI present on the machine.
94
+ * @param {{ quiet?: boolean }} opts quiet → only emit on actual change (for the SessionStart hook)
95
+ * @returns {{ installed: string[], repaired: string[], unchanged: string[], targets: string[] }}
96
+ */
97
+ export function installSkills(opts = {}) {
98
+ const quiet = !!opts.quiet
99
+ const log = (m) => { if (!quiet) process.stdout.write(m + '\n') }
100
+
101
+ const skills = bundledSkills()
102
+ const summary = { installed: [], repaired: [], unchanged: [], targets: [] }
103
+ if (!skills.length) return summary // nothing bundled (shouldn't happen) — never error
104
+
105
+ const targets = CLIS.filter((c) => existsSync(c.dir))
106
+ if (!targets.length) return summary // no agent CLI on this machine
107
+
108
+ for (const cli of targets) {
109
+ const skillsRoot = join(cli.dir, 'skills')
110
+ const r = installInto(skillsRoot, skills)
111
+ summary.targets.push(cli.id)
112
+ summary.installed.push(...r.installed)
113
+ summary.repaired.push(...r.repaired)
114
+ summary.unchanged.push(...r.unchanged)
115
+ const changed = [...r.installed, ...r.repaired]
116
+ if (changed.length) log(` ✓ ${cli.id}: ${changed.join(', ')} → ${skillsRoot}`)
117
+ }
101
118
 
102
- if (result.installed.length) log(` ✓ Cortex skills installed: ${result.installed.join(', ')} → ${coreDir}`)
103
- if (result.repaired.length) log(` ✓ Cortex skills restored (backed up your copy): ${result.repaired.join(', ')}`)
104
- if (quiet && (result.installed.length || result.repaired.length)) {
119
+ if (quiet && (summary.installed.length || summary.repaired.length)) {
105
120
  // SessionStart surfaces one line in Claude Code so a silent self-heal isn't invisible.
106
- process.stdout.write(`Cortex: synced ${result.installed.length + result.repaired.length} managed skill(s).\n`)
121
+ process.stdout.write(`Cortex: synced managed skill(s) into ${summary.targets.join(' + ')}.\n`)
107
122
  }
108
- return result
123
+ return summary
109
124
  }
110
125
 
111
126
  // CLI entry: `cortex-mcp skills [--repair] [--quiet]`. (--repair and plain install are the same
112
127
  // idempotent operation; --repair is just the name the SessionStart hook uses for intent.)
113
128
  export async function runSkills(argv = []) {
114
129
  const quiet = argv.includes('--quiet')
115
- if (!quiet) process.stdout.write('\nCortex skills — installing managed repository…\n')
130
+ if (!quiet) process.stdout.write('\nCortex skills — installing managed skills…\n')
116
131
  const r = installSkills({ quiet })
117
- if (!quiet && !r.installed.length && !r.repaired.length) {
118
- process.stdout.write(` Up to date (${r.unchanged.join(', ') || 'none'}).\n`)
132
+ if (!quiet) {
133
+ if (!r.targets.length) process.stdout.write(' ! No agent CLI found (~/.claude or ~/.codex). Nothing to install.\n')
134
+ else if (!r.installed.length && !r.repaired.length) {
135
+ process.stdout.write(` ✓ Up to date in ${r.targets.join(' + ')} (${[...new Set(r.unchanged)].join(', ') || 'none'}).\n`)
136
+ }
119
137
  }
120
138
  return 0
121
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
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": {