@theronap/agnoclast-mcp 0.9.96

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.
Files changed (44) hide show
  1. package/README.md +47 -0
  2. package/bin/cortex-mcp.mjs +223 -0
  3. package/lib/capture.mjs +470 -0
  4. package/lib/code_graph_cli.mjs +59 -0
  5. package/lib/context_log.mjs +92 -0
  6. package/lib/diagnose.mjs +360 -0
  7. package/lib/docs_scan.mjs +171 -0
  8. package/lib/doctor.mjs +117 -0
  9. package/lib/edge_extract.mjs +156 -0
  10. package/lib/editors/_fsutil.mjs +31 -0
  11. package/lib/editors/antigravity.mjs +130 -0
  12. package/lib/editors/claude.mjs +202 -0
  13. package/lib/editors/codex.mjs +111 -0
  14. package/lib/editors/cursor.mjs +77 -0
  15. package/lib/editors/index.mjs +42 -0
  16. package/lib/extract_typed.mjs +68 -0
  17. package/lib/graphify_sync.mjs +134 -0
  18. package/lib/grep_cli.mjs +82 -0
  19. package/lib/hydrate.mjs +181 -0
  20. package/lib/imessage_send.mjs +88 -0
  21. package/lib/ingest_folder.mjs +170 -0
  22. package/lib/install.mjs +163 -0
  23. package/lib/login.mjs +148 -0
  24. package/lib/managed.mjs +49 -0
  25. package/lib/migrate_key.mjs +139 -0
  26. package/lib/presence.mjs +226 -0
  27. package/lib/publish_targets.mjs +51 -0
  28. package/lib/red_link_triage.mjs +37 -0
  29. package/lib/redact.mjs +40 -0
  30. package/lib/rename_notice.mjs +31 -0
  31. package/lib/resolve.mjs +153 -0
  32. package/lib/server.mjs +2986 -0
  33. package/lib/session_key.mjs +37 -0
  34. package/lib/setup.mjs +215 -0
  35. package/lib/skills.mjs +374 -0
  36. package/lib/statusline.mjs +67 -0
  37. package/lib/uninstall.mjs +237 -0
  38. package/lib/use_brain.mjs +82 -0
  39. package/lib/with_token.mjs +66 -0
  40. package/package.json +36 -0
  41. package/skills/author-docs/SKILL.md +74 -0
  42. package/skills/context/SKILL.md +25 -0
  43. package/skills/log/SKILL.md +114 -0
  44. package/skills/walkthrough/SKILL.md +189 -0
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # cortex-mcp
2
+
3
+ Connect your AI assistant to **Agnoclast** — your org's projects, recent activity, gaps, and directives, scoped to exactly what you're permitted to see.
4
+
5
+ ## Setup
6
+
7
+ 1. Get your personal token from the Agnoclast console → **Connect your AI**.
8
+ 2. Add this to your Claude Code config (`~/.claude.json`, under `mcpServers`):
9
+
10
+ ```json
11
+ {
12
+ "mcpServers": {
13
+ "cortex": {
14
+ "command": "npx",
15
+ "args": ["-y", "@theronap/cortex-mcp"],
16
+ "env": { "CORTEX_TOKEN": "your-personal-token" }
17
+ }
18
+ }
19
+ }
20
+ ```
21
+
22
+ 3. Restart Claude Code. Your AI now sees your org context automatically, the managed startup skill
23
+ will prefer query-centered `session_context` on substantive session opens, and each session start
24
+ will log the exact baseline Agnoclast context to `~/.cortex/context-snapshots/`.
25
+
26
+ No clone, no path, no build step — `npx` fetches and runs it.
27
+
28
+ ## Tools
29
+
30
+ - **my_context** — your projects, recent activity, gaps, and directives.
31
+ - **session_context** — opening-query-centered context seeded from the most relevant node and its neighborhood.
32
+ - **search_org** — search your visible activity and projects by keyword.
33
+ - **project_status** — status of a specific project by key.
34
+
35
+ ## Startup snapshots
36
+
37
+ Every Claude session start runs `snapshot-context`, which fetches the exact `my_context` payload and writes:
38
+ - `~/.cortex/context-snapshots/latest.md`
39
+ - `~/.cortex/context-snapshots/<timestamp>.md`
40
+ - `~/.cortex/context-snapshots/index.jsonl`
41
+
42
+ ## Environment
43
+
44
+ | Var | Required | Default |
45
+ |-----|----------|---------|
46
+ | `CORTEX_TOKEN` | yes | — |
47
+ | `CORTEX_URL` | no | `https://cortex-console.vercel.app` |
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cortex-mcp — connect your AI assistant to Agnoclast.
4
+ *
5
+ * Subcommands:
6
+ * (none) run the MCP server (stdio) — used by your Claude config
7
+ * setup <TOKEN> wire BOTH the MCP server + capture hook into your Claude config
8
+ * doctor live health check — is the token actually working? (no restart needed)
9
+ * capture the Stop-hook capturer (invoked by Claude Code, not by hand)
10
+ * ingest-folder <path> ingest a local markdown folder as your authored records
11
+ * snapshot-context save the exact startup context served by Agnoclast to a local log file
12
+ * --version | -v
13
+ * --help | -h
14
+ *
15
+ * Zero-install onboarding:
16
+ * npx -y @theronap/cortex-mcp setup <your-token>
17
+ *
18
+ * Get your token from the Agnoclast console → Connect your AI.
19
+ */
20
+
21
+ import { readFileSync } from 'node:fs'
22
+ import { fileURLToPath } from 'node:url'
23
+ import { dirname, join } from 'node:path'
24
+ // VERSION = package.json, the single source of truth. NEVER hardcode it: a stale constant here is
25
+ // exactly what shipped 0.9.6 as "0.9.5", so `setup` pinned the wrong version into every config and
26
+ // no amount of cache-clearing/@version could fix an install. Derive it so it can never drift again.
27
+ const VERSION = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8')).version
28
+ const cmd = process.argv[2]
29
+ const rest = process.argv.slice(3)
30
+
31
+ if (cmd === '--version' || cmd === '-v') {
32
+ process.stdout.write(`cortex-mcp ${VERSION}\n`)
33
+ process.exit(0)
34
+ }
35
+
36
+ if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
37
+ process.stdout.write(
38
+ `cortex-mcp ${VERSION} — connect your AI assistant to Agnoclast\n\n` +
39
+ `Onboard (one command, no token to copy):\n` +
40
+ ` npx -y @theronap/cortex-mcp login\n\n` +
41
+ `This opens your browser, you click Approve, and it wires everything up. Restart\n` +
42
+ `Claude Code after, and your AI sees your Agnoclast context while your sessions flow\n` +
43
+ `into the org automatically.\n\n` +
44
+ `Subcommands:\n` +
45
+ ` login [--label <name>] browser-approved sign-in — gets a token for you, then runs setup\n` +
46
+ ` setup <token> wire MCP server + capture hook into ~/.claude config (single editor)\n` +
47
+ ` install [<token>] [--editor auto|all|<id,...>] wire Agnoclast into EVERY detected editor + write the capability manifest\n` +
48
+ ` repair re-run setup at the latest version using your existing token (no token needed)\n` +
49
+ ` uninstall remove ALL Agnoclast wiring (MCP, hooks, skills, launchd, cron). --dry-run to preview, --purge to also wipe ~/.cortex + npx cache\n` +
50
+ ` doctor live health check — confirm your token works (no restart needed)\n` +
51
+ ` status one-line connected/not-connected check (used by the SessionStart hook)\n` +
52
+ ` use-brain [<brain>] where your session captures are saved — no arg shows the current setting\n` +
53
+ ` skills install/repair the managed Agnoclast skills — bundled + org-published (also wired by setup)\n` +
54
+ ` skills push <file> publish a SKILL.md to your org (owner/manager/admin)\n` +
55
+ ` docs-scan detect new/changed local docs pending Agnoclast authoring (used by /cortex-author-docs)\n` +
56
+ ` graphify-sync [path] [--brain <name-or-id>] rebuild the local code graph + log a timeline event\n` +
57
+ ` snapshot-context save the exact startup context Agnoclast served to a local snapshot\n` +
58
+ ` hydrate UserPromptSubmit hook — inject query-centered context on the first substantive turn\n` +
59
+ ` migrate-key [--dry-run] move the MCP config key to its new name, allow rules first\n` +
60
+ ` with-token -- <cmd> run <cmd> with your token in its environment (for cron/launchd wrappers)\n` +
61
+ ` statusline ambient presence line for the Claude Code statusline (local read only)\n` +
62
+ ` capture Stop-hook capturer (invoked by Claude Code)\n` +
63
+ ` ingest-folder <path> ingest a local markdown folder as your authored records\n` +
64
+ ` (no args) run the MCP server (used by your Claude config)\n\n` +
65
+ `Get your token from the Agnoclast console → Connect your AI.\n`,
66
+ )
67
+ process.exit(0)
68
+ }
69
+
70
+ // The network-using subcommands do their work, release the global fetch (undici) sockets, then
71
+ // let the process exit NATURALLY — no process.exit(). Forcing exit on Windows tears libuv down
72
+ // abruptly and, if a threadpool/pipe handle is mid-flight (DNS, the stdout flush), trips an
73
+ // assertion (`!(handle->flags & UV_HANDLE_CLOSING)`, src\win\async.c) AFTER the real output.
74
+ // A graceful drain lets libuv finish those handles first, so the assertion never fires. The MCP
75
+ // server (default branch) runs forever and never reaches an exit path. if/else so the CLI
76
+ // commands don't fall through into the server.
77
+ if (cmd === 'login') {
78
+ // Browser-approved sign-in: no token to copy. Ends by calling runSetup with the token it
79
+ // collected, so there is still exactly ONE code path that writes a credential to disk.
80
+ const { runLogin } = await import('../lib/login.mjs')
81
+ await runLogin(rest, VERSION)
82
+ const { closeFetch } = await import('../lib/diagnose.mjs')
83
+ await closeFetch()
84
+ } else if (cmd === 'setup') {
85
+ const { runSetup } = await import('../lib/setup.mjs')
86
+ await runSetup(rest, VERSION)
87
+ const { closeFetch } = await import('../lib/diagnose.mjs')
88
+ await closeFetch()
89
+ } else if (cmd === 'install') {
90
+ // The cross-editor hub installer: wire Agnoclast into every detected editor via the adapter
91
+ // registry, then write the capability manifest (~/.cortex/editors.json). Superset of `setup`.
92
+ const { runInstall } = await import('../lib/install.mjs')
93
+ await runInstall(rest, VERSION)
94
+ const { closeFetch } = await import('../lib/diagnose.mjs')
95
+ await closeFetch()
96
+ } else if (cmd === 'uninstall' || cmd === 'remove') {
97
+ // Full reverse of setup: strip every Agnoclast touch-point (MCP entries, hooks, skills, launchd, cron).
98
+ // --dry-run prints the plan and changes nothing; --purge also removes ~/.cortex, the npx cache, and
99
+ // backups. No network — safe to run even when the token is dead or the server is unreachable.
100
+ const { runUninstall } = await import('../lib/uninstall.mjs')
101
+ runUninstall(rest)
102
+ } else if (cmd === 'repair' || cmd === 'update') {
103
+ // Re-run setup at THIS version using the already-wired token (no token arg needed). Fixes a
104
+ // machine set up with an older version: re-pins MCP + hooks, reinstalls skills to the flat path.
105
+ const { runRepair } = await import('../lib/setup.mjs')
106
+ await runRepair(VERSION)
107
+ const { closeFetch } = await import('../lib/diagnose.mjs')
108
+ await closeFetch()
109
+ } else if (cmd === 'doctor') {
110
+ const { runDoctor } = await import('../lib/doctor.mjs')
111
+ process.exitCode = await runDoctor()
112
+ const { closeFetch } = await import('../lib/diagnose.mjs')
113
+ await closeFetch()
114
+ } else if (cmd === 'status') {
115
+ // One-line SessionStart health signal (wired by setup). Always exit 0.
116
+ const { runStatus } = await import('../lib/doctor.mjs')
117
+ await runStatus()
118
+ const { closeFetch } = await import('../lib/diagnose.mjs')
119
+ await closeFetch()
120
+ } else if (cmd === 'capture') {
121
+ const { runCapture } = await import('../lib/capture.mjs')
122
+ await runCapture()
123
+ const { closeFetch } = await import('../lib/diagnose.mjs')
124
+ await closeFetch()
125
+ } else if (cmd === 'resolve') {
126
+ // Entity identity dedup: judge the server-flagged fuzzy duplicate pairs locally via `claude -p`.
127
+ const { runResolve } = await import('../lib/resolve.mjs')
128
+ await runResolve(rest)
129
+ const { closeFetch } = await import('../lib/diagnose.mjs')
130
+ await closeFetch()
131
+ } else if (cmd === 'materialize') {
132
+ // EXCISED 2026-07-02 (legacy-materializer incident): the digest pipeline deleted live-authored
133
+ // pages. Pages come from live authoring now; this stub keeps old hook invocations harmless.
134
+ process.stderr.write('cortex-mcp: `materialize` was removed — pages come from live authoring (the `author` tool + /log). Nothing to do.\n')
135
+ } else if (cmd === 'ingest-folder') {
136
+ const { runIngestFolder } = await import('../lib/ingest_folder.mjs')
137
+ await runIngestFolder(rest)
138
+ const { closeFetch } = await import('../lib/diagnose.mjs')
139
+ await closeFetch()
140
+ } else if (cmd === 'grep') {
141
+ // Ranked keyword (fts) search by default; --substring/--literal for exact lookups. Thin client of /api/grep.
142
+ const { runGrep } = await import('../lib/grep_cli.mjs')
143
+ process.exitCode = await runGrep(rest)
144
+ const { closeFetch } = await import('../lib/diagnose.mjs')
145
+ await closeFetch()
146
+ } else if (cmd === 'graphify-sync') {
147
+ // Local producer: `graphify update` + log an evidence-tier timeline event. Run from a cron/
148
+ // launchd job per repo, not a git hook (shared-checkout hazard — see reference memory).
149
+ const { runGraphifySync } = await import('../lib/graphify_sync.mjs')
150
+ process.exitCode = await runGraphifySync(rest)
151
+ const { closeFetch } = await import('../lib/diagnose.mjs')
152
+ await closeFetch()
153
+ } else if (cmd === 'snapshot-context') {
154
+ const { runSnapshotContext } = await import('../lib/context_log.mjs')
155
+ process.exitCode = await runSnapshotContext()
156
+ const { closeFetch } = await import('../lib/diagnose.mjs')
157
+ await closeFetch()
158
+ } else if (cmd === 'hydrate') {
159
+ // UserPromptSubmit hook (① discovery): hydrate the model with query-centered Agnoclast context on the
160
+ // FIRST substantive turn, before it answers — then never again this session (topic-shift refresh stays
161
+ // the cortex-context skill's job). Synchronous by necessity, but once-per-session + 8s + fail-open.
162
+ const { runHydrate } = await import('../lib/hydrate.mjs')
163
+ process.exitCode = await runHydrate()
164
+ const { closeFetch } = await import('../lib/diagnose.mjs')
165
+ await closeFetch()
166
+ } else if (cmd === 'migrate-key') {
167
+ // ADR-0033 §5 C2. Guarded: adds the new namespace's allow rules and VERIFIES them from disk
168
+ // before flipping mcpServers.cortex -> .agnoclast, so a seat that never received C1 cannot end up
169
+ // with renamed tools and no permissions. Idempotent; --dry-run reports the plan and changes
170
+ // nothing. NOT wired into any hook yet — activating it is the C2 release itself.
171
+ const { runKeyMigration } = await import('../lib/migrate_key.mjs')
172
+ const res = runKeyMigration({ dryRun: rest.includes('--dry-run') })
173
+ process.stdout.write(JSON.stringify(res, null, 2) + '\n')
174
+ process.exitCode = res.status === 'aborted' ? 1 : 0
175
+ } else if (cmd === 'with-token') {
176
+ // Run a command with the resolved token in its environment. Exists so generated helper scripts
177
+ // (launchd/cron wrappers) never inline their own config parse — those files are frozen on disk and
178
+ // a config change strands them silently. See lib/with_token.mjs.
179
+ const { runWithToken } = await import('../lib/with_token.mjs')
180
+ process.exitCode = runWithToken(rest)
181
+ } else if (cmd === 'statusline') {
182
+ // Ambient presence: read one local breadcrumb and print a line. No network, no auth — it redraws
183
+ // constantly, so anything more expensive than a file read does not belong here.
184
+ const { runStatusline } = await import('../lib/statusline.mjs')
185
+ process.exitCode = runStatusline()
186
+ } else if (cmd === 'skills') {
187
+ // Install / repair the managed Agnoclast skills — bundled + org-published (`skills push` publishes).
188
+ // Org sync is network-fail-soft so the SessionStart hook stays safe offline.
189
+ const { runSkills } = await import('../lib/skills.mjs')
190
+ process.exitCode = await runSkills(rest)
191
+ const { closeFetch } = await import('../lib/diagnose.mjs')
192
+ await closeFetch()
193
+ } else if (cmd === 'docs-scan') {
194
+ // Documentation ingestion, detection half: hash-diff registered doc roots for new/changed *.md.
195
+ // The cortex-author-docs skill authors the pending docs into wiki pages. No network.
196
+ const { runDocsScan } = await import('../lib/docs_scan.mjs')
197
+ process.exitCode = await runDocsScan(rest)
198
+ } else if (cmd === 'use-brain') {
199
+ // Set (or show) which brain this machine's unattended session captures land in. Registered here,
200
+ // BEFORE the default branch — an unrecognised subcommand falls through to "start the MCP server",
201
+ // which is how `connect-calendar` once silently became the file-watching daemon on a pilot user's
202
+ // machine and reported that daemon's errors instead of its own.
203
+ const { runUseBrain } = await import('../lib/use_brain.mjs')
204
+ process.exitCode = await runUseBrain(rest)
205
+ const { closeFetch } = await import('../lib/diagnose.mjs')
206
+ await closeFetch()
207
+ } else if (cmd === 'precompact') {
208
+ // RETIRED — deliberately kept as a silent no-op, do not delete yet.
209
+ //
210
+ // The reminder it used to print never reached a model: PreCompact takes a blocking `decision` and has
211
+ // no additionalContext channel, so its stdout went nowhere (verified 2026-08-10 against 7,232 local
212
+ // transcripts — see the PreCompact block in lib/editors/claude.mjs for the measurement and control).
213
+ //
214
+ // Why this branch survives the removal: seats installed before this release still have
215
+ // `PreCompact: npx -y @theronap/cortex-mcp@latest precompact` in ~/.claude/settings.json, and @latest
216
+ // resolves to THIS build. Deleting the branch would turn a harmless no-op into an unknown-command
217
+ // error on every compaction for anyone who has not re-run install. mergeClaudeSettings unwires them
218
+ // on their next install/repair; drop this branch a release after that has had time to propagate.
219
+ } else {
220
+ // Default: run the MCP server (stays alive; never exits).
221
+ const { runServer } = await import('../lib/server.mjs')
222
+ await runServer(VERSION)
223
+ }