@nodemint/projectmind
Advanced tools
+16
-0
@@ -6,2 +6,18 @@ # Changelog | ||
| ## [0.4.3] - 2026-07-03 | ||
| ### Added | ||
| - **`projectmind setup --global`** — registers the MCP server once for every | ||
| future project, instead of per-repo `.mcp.json`. For Claude Code this shells | ||
| out to `claude mcp add --scope user` (the same mechanism codegraph and other | ||
| tools use for global registration) rather than hand-editing its internal | ||
| config; Cursor, Windsurf, and Gemini CLI get the same idempotent merge into | ||
| their global config files (`~/.cursor/mcp.json`, | ||
| `~/.codeium/windsurf/mcp_config.json`, `~/.gemini/settings.json`). | ||
| Prompted by real dogfooding: without this, a fresh project needed | ||
| `projectmind setup` run every time, unlike globally-registered MCP servers. | ||
| Rules files (`CLAUDE.md` etc.) intentionally stay per-project — there's no | ||
| sane "global" convention text — so `--global` only wires the server itself. | ||
| ## [0.4.2] - 2026-07-03 | ||
@@ -8,0 +24,0 @@ |
+1
-1
| { | ||
| "name": "@nodemint/projectmind", | ||
| "version": "0.4.2", | ||
| "version": "0.4.3", | ||
| "mcpName": "io.github.Nodemint-dev/projectmind", | ||
@@ -5,0 +5,0 @@ "publishConfig": { |
+30
-0
@@ -243,2 +243,32 @@ # projectmind | ||
| ### Install once, use everywhere — global setup | ||
| `projectmind setup` above is per-project. Run `projectmind setup --global` | ||
| instead to register the MCP server **once**, so it's available in every | ||
| project you open from then on — no per-repo `.mcp.json`, no re-running setup. | ||
| Supported: Claude Code, Cursor, Windsurf, Gemini CLI. | ||
| ```bash | ||
| projectmind setup --global | ||
| ``` | ||
| For Claude Code this shells out to its own `claude mcp add --scope user` | ||
| (the same mechanism tools like codegraph use for global registration) rather | ||
| than hand-editing its internal config file. For the others it merges into | ||
| their global config (`~/.cursor/mcp.json`, `~/.codeium/windsurf/mcp_config.json`, | ||
| `~/.gemini/settings.json`) the same idempotent way the per-project setup does. | ||
| **Two things this can't do**, by design: | ||
| - **Rules files stay per-project.** `CLAUDE.md` / `.cursorrules` / etc. are | ||
| checked-in project conventions — there's no sane "global" version of them, | ||
| so `--global` only registers the MCP *server*, not the nudge-text. Without | ||
| a project's `.projectmind/` map, `mind_digest` just returns an empty-but-valid | ||
| digest — harmless, but the agent won't be as strongly reminded to call it. | ||
| - **A chat session already open won't see it.** Every MCP client (Claude Code, | ||
| Cursor, etc.) loads its server list once at session start — that's an MCP | ||
| client behavior, not something any server can override. Global scope means | ||
| every *new* session in every *future* project has it automatically; it does | ||
| not retroactively add it to a conversation already in progress. Restart the | ||
| agent once after running `--global`, and you're done for good. | ||
| ## Where projectmind fits (and what it deliberately isn't) | ||
@@ -245,0 +275,0 @@ |
+13
-1
@@ -5,3 +5,3 @@ #!/usr/bin/env node | ||
| import { installHook } from "../hooks/install.js"; | ||
| import { setupAgents, SUPPORTED_AGENTS } from "../setup/index.js"; | ||
| import { setupAgents, setupGlobalAgents, SUPPORTED_AGENTS, SUPPORTED_GLOBAL_AGENTS } from "../setup/index.js"; | ||
| import { watch } from "../watch/index.js"; | ||
@@ -18,2 +18,3 @@ import { savingsSummary, resolvePrice } from "../core/ledger.js"; | ||
| setup [--agent <name>] Wire the MCP server + rules into agents (${SUPPORTED_AGENTS.join(", ")}, or all) | ||
| setup --global [--agent <name>] Register the MCP server once, for every future project (${SUPPORTED_GLOBAL_AGENTS.join(", ")}) | ||
| digest Print the compact digest (what the agent reads first) | ||
@@ -41,2 +42,3 @@ context [--files a,b] [--node id] [--term t] [--depth 1] Task-scoped subgraph | ||
| --agent <name> (setup) target one agent; default all | ||
| --global (setup) register once for every future project, instead of just this repo | ||
| --files <a,b,c> (context) comma-separated files you're working on | ||
@@ -60,2 +62,3 @@ --node <id> (context) seed the subgraph from this node | ||
| else if (a === "--agent") opts.agent = argv[++i]; | ||
| else if (a === "--global") opts.global = true; | ||
| else if (a === "--files") opts.files = argv[++i]; | ||
@@ -115,2 +118,11 @@ else if (a === "--node") opts.node = argv[++i]; | ||
| const agents = opts.agent || "all"; | ||
| if (opts.global) { | ||
| const results = setupGlobalAgents(agents); | ||
| out(`Registered projectmind globally (every future project will have it, no per-repo setup needed):`); | ||
| for (const x of results) { | ||
| out(` ${x.status.padEnd(18)} ${x.file}${x.error ? ` — ${x.error}` : ""}`); | ||
| } | ||
| out("\nRestart your agent for this to take effect — including in any session already open."); | ||
| break; | ||
| } | ||
| const results = setupAgents(r, agents); | ||
@@ -117,0 +129,0 @@ out(`Wired projectmind into agents at ${r}:`); |
@@ -182,3 +182,3 @@ #!/usr/bin/env node | ||
| const server = new Server( | ||
| { name: "projectmind", version: "0.4.2" }, | ||
| { name: "projectmind", version: "0.4.3" }, | ||
| { capabilities: { tools: {} } } | ||
@@ -185,0 +185,0 @@ ); |
+66
-8
@@ -5,4 +5,13 @@ // Multi-agent wiring. Writes the MCP server config and a workflow rules block | ||
| // rather than destroy it. Rules files get a marked, idempotent block. | ||
| // | ||
| // Two scopes: | ||
| // - project (default): writes .mcp.json etc. in the current repo only. | ||
| // - global: registers projectmind once, for every future project — the same | ||
| // "install once, works everywhere" model as codegraph's user-scoped MCP | ||
| // entry. No rules-file text is global (CLAUDE.md/AGENTS.md are inherently | ||
| // per-repo), only the MCP server registration. | ||
| import fs from "node:fs"; | ||
| import os from "node:os"; | ||
| import path from "node:path"; | ||
| import { execFileSync } from "node:child_process"; | ||
@@ -60,4 +69,5 @@ // `mcp` is a subcommand of the main bin, so plain npx works (same invocation | ||
| function mergeJsonConfig(r, spec) { | ||
| const file = path.join(r, spec.file); | ||
| // file: absolute path. label: what to report back (may differ from `file`, | ||
| // e.g. "~/.cursor/mcp.json" for readability). | ||
| function mergeJsonConfig(file, key, label = file) { | ||
| let data = {}; | ||
@@ -70,11 +80,11 @@ if (fs.existsSync(file)) { | ||
| try { fs.copyFileSync(file, backup); } catch { /* ignore */ } | ||
| return { file: spec.file, status: "skipped-unparseable", backup }; | ||
| return { file: label, status: "skipped-unparseable", backup }; | ||
| } | ||
| } | ||
| data[spec.key] = data[spec.key] || {}; | ||
| if (data[spec.key].projectmind) return { file: spec.file, status: "already" }; | ||
| data[spec.key].projectmind = { ...MCP_ENTRY }; | ||
| data[key] = data[key] || {}; | ||
| if (data[key].projectmind) return { file: label, status: "already" }; | ||
| data[key].projectmind = { ...MCP_ENTRY }; | ||
| fs.mkdirSync(path.dirname(file), { recursive: true }); | ||
| fs.writeFileSync(file, JSON.stringify(data, null, 2) + "\n"); | ||
| return { file: spec.file, status: "written" }; | ||
| return { file: label, status: "written" }; | ||
| } | ||
@@ -100,3 +110,3 @@ | ||
| if (!a) continue; | ||
| if (a.json) results.push({ agent: key, label: a.label, ...mergeJsonConfig(r, a.json) }); | ||
| if (a.json) results.push({ agent: key, label: a.label, ...mergeJsonConfig(path.join(r, a.json.file), a.json.key, a.json.file) }); | ||
| if (a.rules) results.push({ agent: key, label: a.label, ...appendRulesBlock(r, a.rules) }); | ||
@@ -106,1 +116,49 @@ } | ||
| } | ||
| // --------------------------------------------------------------------------- | ||
| // Global (user) scope — register once, available in every future project | ||
| // without a per-project .mcp.json. Only the MCP server entry is global; rules | ||
| // files (CLAUDE.md, AGENTS.md, ...) are inherently per-repo and untouched. | ||
| // --------------------------------------------------------------------------- | ||
| export const SUPPORTED_GLOBAL_AGENTS = ["claude", "cursor", "windsurf", "gemini"]; | ||
| function globalJsonPath(agent, homeDir) { | ||
| switch (agent) { | ||
| case "cursor": return path.join(homeDir, ".cursor", "mcp.json"); | ||
| case "windsurf": return path.join(homeDir, ".codeium", "windsurf", "mcp_config.json"); | ||
| case "gemini": return path.join(homeDir, ".gemini", "settings.json"); | ||
| default: return null; | ||
| } | ||
| } | ||
| // Claude Code has no documented global config file to hand-edit safely, but | ||
| // ships a first-class CLI verb for exactly this (`claude mcp add --scope | ||
| // user`) — shell out to it rather than guess an internal file format. | ||
| function addClaudeUserScope(execFile = execFileSync) { | ||
| const label = "claude mcp (user scope)"; | ||
| try { | ||
| execFile("claude", ["mcp", "add", "projectmind", "--scope", "user", "--", "npx", "-y", "@nodemint/projectmind", "mcp"], { stdio: "pipe" }); | ||
| return { file: label, status: "written" }; | ||
| } catch (err) { | ||
| const msg = String(err?.stderr || err?.message || ""); | ||
| if (/already exists/i.test(msg)) return { file: label, status: "already" }; | ||
| return { file: label, status: "failed", error: (msg.split("\n").find(Boolean) || "is the `claude` CLI installed and on PATH?").trim() }; | ||
| } | ||
| } | ||
| // homeDir is overridable for tests; defaults to the real home directory. | ||
| export function setupGlobalAgents(agents = "all", { homeDir = os.homedir(), execFile = execFileSync } = {}) { | ||
| const keys = agents === "all" || !agents ? SUPPORTED_GLOBAL_AGENTS : [].concat(agents).filter((k) => SUPPORTED_GLOBAL_AGENTS.includes(k)); | ||
| const results = []; | ||
| for (const key of keys) { | ||
| if (key === "claude") { | ||
| results.push({ agent: "claude", label: "Claude Code", ...addClaudeUserScope(execFile) }); | ||
| continue; | ||
| } | ||
| const file = globalJsonPath(key, homeDir); | ||
| if (!file) continue; | ||
| const label = key === "cursor" ? "~/.cursor/mcp.json" : key === "windsurf" ? "~/.codeium/windsurf/mcp_config.json" : "~/.gemini/settings.json"; | ||
| results.push({ agent: key, label: AGENTS[key]?.label || key, ...mergeJsonConfig(file, "mcpServers", label) }); | ||
| } | ||
| return results; | ||
| } |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
103937
6.5%1700
4.04%413
7.83%2
100%