@theronap/agnoclast-mcp 0.9.244 → 0.9.246

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/capture.mjs CHANGED
@@ -72,7 +72,7 @@ export function repoFullNameFrom(cwd) {
72
72
  if (!cwd) return null
73
73
  try {
74
74
  const url = execFileSync('git', ['-C', String(cwd), 'config', '--get', 'remote.origin.url'], {
75
- encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'],
75
+ encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true,
76
76
  })
77
77
  return githubFullName(url)
78
78
  } catch {
@@ -108,7 +108,7 @@ export function workingFilesFrom(cwd, max = 40) {
108
108
  const run = (args) => {
109
109
  try {
110
110
  return execFileSync('git', ['-C', String(cwd), ...args], {
111
- encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'],
111
+ encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true,
112
112
  }).split('\n').map((l) => l.trim()).filter(Boolean)
113
113
  } catch {
114
114
  return []
@@ -316,12 +316,16 @@ function captureLogFd() {
316
316
  // feed it the hook payload on its stdin. Returns true if the child was launched (parent may return
317
317
  // immediately), false if spawning failed (caller then does the work in-band). The child sees
318
318
  // CORTEX_CAPTURE_DETACHED=1 so it runs captureWork() directly instead of forking again.
319
+ // ⚠ WINDOWS: `detached` there means DETACHED_PROCESS — this worker has NO console, so every console
320
+ // program it starts (`claude --print`, `git`) would get a brand-new, visible Windows Terminal window
321
+ // titled with its path. Every spawn in this package passes windowsHide (windows_hide.test.mjs).
319
322
  function spawnDetachedWorker(stdinRaw) {
320
323
  try {
321
324
  const bin = join(dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'cortex-mcp.mjs')
322
325
  const child = spawn(process.execPath, [bin, 'capture'], {
323
326
  env: { ...process.env, CORTEX_CAPTURE_DETACHED: '1' },
324
327
  detached: true,
328
+ windowsHide: true,
325
329
  stdio: ['pipe', captureLogFd(), captureLogFd()],
326
330
  })
327
331
  child.on('error', () => {}) // never let an async spawn error crash the hook
@@ -14,7 +14,7 @@ function graphPath(cwd) {
14
14
  }
15
15
 
16
16
  function hasGraphifyBinary() {
17
- const r = spawnSync('graphify', ['--version'], { encoding: 'utf8', timeout: 10_000 })
17
+ const r = spawnSync('graphify', ['--version'], { encoding: 'utf8', timeout: 10_000, windowsHide: true })
18
18
  return !r.error && r.status === 0
19
19
  }
20
20
 
@@ -50,7 +50,7 @@ export function runCodeGraphQuery({ action, question, from, to, node }, cwd = pr
50
50
  const built = buildArgs({ action, question, from, to, node })
51
51
  if (built.error) return { ok: false, text: built.error }
52
52
 
53
- const r = spawnSync('graphify', built.args, { cwd, encoding: 'utf8', timeout: 60_000, maxBuffer: 4 * 1024 * 1024 })
53
+ const r = spawnSync('graphify', built.args, { cwd, encoding: 'utf8', timeout: 60_000, maxBuffer: 4 * 1024 * 1024, windowsHide: true })
54
54
  if (r.error) return { ok: false, text: `graphify failed to run: ${r.error.message}` }
55
55
  const out = (r.stdout || '').trim()
56
56
  const err = (r.stderr || '').trim()
@@ -496,7 +496,7 @@ export function extractSession(transcript, opts = {}) {
496
496
  const r = spawnSync(
497
497
  'claude',
498
498
  ['--print', '--model', process.env.CORTEX_SUMMARY_MODEL ?? 'claude-haiku-4-5'],
499
- { input: prompt, env: edgeSafeEnv(process.env, { CORTEX_SUMMARIZING: '1' }), encoding: 'utf8', timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024 },
499
+ { input: prompt, env: edgeSafeEnv(process.env, { CORTEX_SUMMARIZING: '1' }), encoding: 'utf8', timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024, windowsHide: true },
500
500
  )
501
501
  const why = classifyEdgeFailure(r, timeoutMs)
502
502
  if (why) return edgeSkip(why.reason, why.detail)
@@ -42,7 +42,7 @@ export function extractTyped(transcript, registry, opts = {}) {
42
42
  const r = spawnSync(
43
43
  'claude',
44
44
  ['--print', '--model', opts.model ?? process.env.CORTEX_SUMMARY_MODEL ?? 'claude-haiku-4-5', prompt],
45
- { env: edgeSafeEnv(process.env, { CORTEX_SUMMARIZING: '1' }), encoding: 'utf8', timeout: 120_000, maxBuffer: 8 * 1024 * 1024 },
45
+ { env: edgeSafeEnv(process.env, { CORTEX_SUMMARIZING: '1' }), encoding: 'utf8', timeout: 120_000, maxBuffer: 8 * 1024 * 1024, windowsHide: true },
46
46
  )
47
47
  if (r.status !== 0 || !r.stdout) return null
48
48
  return parseTyped(r.stdout)
@@ -15,7 +15,7 @@ import { fetchCortex, classify, resolveBase, resolveEnvToken } from './diagnose.
15
15
  // job just reads whatever the tree looks like at that moment, no hook into git operations at all.
16
16
 
17
17
  function run(cmd, args, cwd) {
18
- const r = spawnSync(cmd, args, { cwd, encoding: 'utf8', timeout: 120_000 })
18
+ const r = spawnSync(cmd, args, { cwd, encoding: 'utf8', timeout: 120_000, windowsHide: true })
19
19
  return r.status === 0 ? (r.stdout || '').trim() : null
20
20
  }
21
21
 
@@ -78,7 +78,7 @@ export async function runGraphifySync(argv = []) {
78
78
  return 1
79
79
  }
80
80
 
81
- const update = spawnSync('graphify', ['update', '.'], { cwd, encoding: 'utf8', timeout: 300_000, maxBuffer: 8 * 1024 * 1024 })
81
+ const update = spawnSync('graphify', ['update', '.'], { cwd, encoding: 'utf8', timeout: 300_000, maxBuffer: 8 * 1024 * 1024, windowsHide: true })
82
82
  if (update.status !== 0) {
83
83
  process.stderr.write(`cortex-mcp graphify-sync: \`graphify update\` failed:\n${(update.stderr || update.stdout || '').trim()}\n`)
84
84
  return 1
package/lib/login.mjs CHANGED
@@ -27,7 +27,7 @@ function openBrowser(url) {
27
27
  process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open'
28
28
  const args = process.platform === 'win32' ? ['/c', 'start', '""', url] : [url]
29
29
  try {
30
- const child = spawn(cmd, args, { stdio: 'ignore', detached: true })
30
+ const child = spawn(cmd, args, { stdio: 'ignore', detached: true, windowsHide: true })
31
31
  // If the browser cannot be launched we still printed the URL, so this is genuinely non-fatal.
32
32
  child.on('error', () => {})
33
33
  child.unref()
@@ -144,6 +144,7 @@ function runClaude(prompt, model) {
144
144
  encoding: 'utf8',
145
145
  timeout: REVIEW_TIMEOUT_MS,
146
146
  maxBuffer: 1024 * 1024,
147
+ windowsHide: true,
147
148
  })
148
149
  }
149
150
 
@@ -244,6 +244,7 @@ export function spawnObligationWorker(job) {
244
244
  const child = spawn(process.execPath, [bin, 'extract-obligations'], {
245
245
  env: { ...process.env },
246
246
  detached: true,
247
+ windowsHide: true,
247
248
  stdio: ['pipe', fd, fd],
248
249
  })
249
250
  child.on('error', () => {})
package/lib/server.mjs CHANGED
@@ -5057,6 +5057,53 @@ function renderNudge(payload) {
5057
5057
  },
5058
5058
  )
5059
5059
 
5060
+ // share_page — a read-only link to one wiki page (ADR-0090; Theron, 2026-10-19). Same shape as share_record:
5061
+ // NOT auto-allowed (a disclosure, so it prompts), and only when the person asks. Never a confidential page.
5062
+ server.registerTool(
5063
+ 'share_page',
5064
+ {
5065
+ title: 'Make a share link for a wiki page',
5066
+ description:
5067
+ 'Make a read-only link (agnoclast.com/p/…) to ONE wiki page, for anyone — including people outside Agnoclast. ' +
5068
+ 'They see the page as it is when they open it: its title and sections; links to other pages show as plain text, ' +
5069
+ 'and nothing else in the brain is shared. Signed-in viewers can save their own copy; others can join free. ' +
5070
+ 'Allowed for a page you own, or an accessible page you can edit; never a confidential page. Name the page by ' +
5071
+ 'node kind + ref (what read_page shows), optionally its tier. The link is shown ONCE — give it to the person ' +
5072
+ 'straight away. Only when the person asks to share; never on your own initiative.',
5073
+ inputSchema: {
5074
+ kind: z.string().describe('the page node kind (project, person, org, user, …)'),
5075
+ ref: z.string().describe('the page node ref (uuid)'),
5076
+ tier: z.enum(['accessible', 'scoped', 'reports', 'peers', 'down']).optional().describe('which version, when the node has several'),
5077
+ label: z.string().max(80).optional().describe('a label only you see'),
5078
+ expires_in_days: z.number().int().min(1).max(3650).optional().describe('how long the link works; default: no expiry'),
5079
+ },
5080
+ },
5081
+ async ({ kind, ref, tier, label, expires_in_days }) => {
5082
+ let res
5083
+ try {
5084
+ res = await fetchCortex(`${BASE}/api/brain/page-shares`, {
5085
+ method: 'POST',
5086
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
5087
+ body: JSON.stringify({ kind, ref, ...(tier ? { tier } : {}), ...(label ? { label } : {}), ...(expires_in_days ? { expiresInDays: expires_in_days } : {}) }),
5088
+ })
5089
+ } catch (e) {
5090
+ return toolError(`Could not make a share link: ${e.message}`)
5091
+ }
5092
+ if ([403, 404, 409].includes(res.status)) {
5093
+ const b = await res.json().catch(() => null)
5094
+ return toolError(`Not shared: ${b?.detail ?? b?.error ?? 'refused'}`)
5095
+ }
5096
+ if (!res.ok) {
5097
+ const d = classify(res.status, res.headers.get('content-type'), await res.text(), res.headers.get('x-vercel-id'))
5098
+ return toolError(`Could not make a share link: ${d.message}`)
5099
+ }
5100
+ const out = await res.json()
5101
+ return { content: [{ type: 'text', text:
5102
+ `Share link for the page "${out.title}":\n${out.url}\n` +
5103
+ `Read-only, live (they see the page as it is when they open it). This link is shown only once. share id: ${out.share.id}` }] }
5104
+ },
5105
+ )
5106
+
5060
5107
  // set_record_notes — write the NOTES layer of one of your own transcripts (Theron, 2026-10-08: "two
5061
5108
  // layers … a summary and notes from the transcript … the full transcript"). The server keeps the
5062
5109
  // before-state in record history and refuses anything that is not your own kept transcript.
package/lib/uninstall.mjs CHANGED
@@ -221,17 +221,17 @@ export function stripCodexCortexTables(text) {
221
221
  }
222
222
 
223
223
  function safeReaddir(dir) {
224
- try { return execFileSync('ls', ['-1', dir], { encoding: 'utf8' }).split('\n').filter(Boolean) } catch { return [] }
224
+ try { return execFileSync('ls', ['-1', dir], { encoding: 'utf8', windowsHide: true }).split('\n').filter(Boolean) } catch { return [] }
225
225
  }
226
226
  function npxCacheDirs() {
227
227
  const base = join(HOME, '.npm', '_npx')
228
228
  const out = []
229
229
  for (const d of safeReaddir(base)) {
230
230
  const dir = join(base, d)
231
- try { if (execFileSync('grep', ['-rlE', '@theronap/(cortex|agnoclast)-mcp', join(dir, 'package.json')], { encoding: 'utf8' }).trim()) out.push(dir) } catch { /* not one of ours */ }
231
+ try { if (execFileSync('grep', ['-rlE', '@theronap/(cortex|agnoclast)-mcp', join(dir, 'package.json')], { encoding: 'utf8', windowsHide: true }).trim()) out.push(dir) } catch { /* not one of ours */ }
232
232
  }
233
233
  return out
234
234
  }
235
- function tryExec(cmd, args) { try { execFileSync(cmd, args, { stdio: 'ignore' }) } catch { /* best-effort */ } }
236
- function tryExecOut(cmd, args) { try { return execFileSync(cmd, args, { encoding: 'utf8' }) } catch { return null } }
237
- function tryExecIn(cmd, args, input) { try { execFileSync(cmd, args, { input }) } catch { /* best-effort */ } }
235
+ function tryExec(cmd, args) { try { execFileSync(cmd, args, { stdio: 'ignore', windowsHide: true }) } catch { /* best-effort */ } }
236
+ function tryExecOut(cmd, args) { try { return execFileSync(cmd, args, { encoding: 'utf8', windowsHide: true }) } catch { return null } }
237
+ function tryExecIn(cmd, args, input) { try { execFileSync(cmd, args, { input, windowsHide: true }) } catch { /* best-effort */ } }
@@ -383,7 +383,7 @@ export function findTranscript(sessionId, projectsDir = join(homedir(), '.claude
383
383
  function gitRoot(dir) {
384
384
  try {
385
385
  return execFileSync('git', ['-C', dir, 'rev-parse', '--show-toplevel'], {
386
- encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'],
386
+ encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true,
387
387
  }).trim() || null
388
388
  } catch {
389
389
  return null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/agnoclast-mcp",
3
- "version": "0.9.244",
3
+ "version": "0.9.246",
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": {