changebook 0.2.0 → 0.3.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/README.md CHANGED
@@ -15,9 +15,12 @@ npx changebook init # login (browser) + register in Claude Code/Codex + sync
15
15
 
16
16
  `init` does four things: opens the browser so you sign in on changebook.app
17
17
  (one Authorize click — no token copy-pasting), registers the MCP server in
18
- Claude Code (user scope) and Codex (`~/.codex/config.toml`), installs the
18
+ **every coding agent it finds on the machine** — Claude Code, Codex, Cursor,
19
+ Windsurf, Claude Desktop and VS Code (Copilot agent mode) — installs the
19
20
  post-commit hook so the atlas updates itself, and writes the product map
20
- into the project's `CLAUDE.md`/`AGENTS.md`.
21
+ into the project's `CLAUDE.md`/`AGENTS.md`. It only touches agents that are
22
+ actually installed, and merges into existing MCP configs without clobbering
23
+ your other servers.
21
24
 
22
25
  ## CLI commands
23
26
 
package/dist/index.js CHANGED
@@ -33,7 +33,7 @@ Usage:
33
33
  changebook hook install|uninstall|status [dir]
34
34
  Git post-commit hook: analyze every new commit automatically
35
35
  changebook sync [dir] Refresh the product map inside CLAUDE.md/AGENTS.md
36
- changebook init [dir] login + register MCP (Claude Code, Codex) + hook + sync
36
+ changebook init [dir] login + register MCP in every agent found + hook + sync
37
37
  changebook open Open the web atlas in the browser
38
38
  changebook serve Run the MCP server on stdio (default with no arguments)
39
39
 
@@ -143,9 +143,9 @@ async function main() {
143
143
  await login();
144
144
  db = new Supabase(); // pick up the freshly stored credentials
145
145
  }
146
- for (const line of await registerAgents())
147
- console.error(line);
148
146
  const dir = arg ?? process.cwd();
147
+ for (const line of await registerAgents(dir))
148
+ console.error(line);
149
149
  // The living-atlas part: analyze every new commit automatically. Not a
150
150
  // git repo (or a foreign hook already there) → explain and move on.
151
151
  try {
package/dist/init.js CHANGED
@@ -1,8 +1,15 @@
1
1
  /**
2
2
  * `changebook init [dir]` — one-command setup for coding agents:
3
3
  * 1. Sign in if needed (browser flow).
4
- * 2. Register the MCP server in Claude Code (user scope) and Codex.
4
+ * 2. Register the MCP server in every agent found on the machine:
5
+ * Claude Code (CLI), Codex, Cursor, Windsurf, Claude Desktop, VS Code.
5
6
  * 3. Sync the product map into the project's CLAUDE.md / AGENTS.md.
7
+ *
8
+ * Every agent runs the SAME stdio server (`changebook serve`); only the config
9
+ * file format and location differ. We only write config for agents that are
10
+ * already present (their dir/config exists), so init never litters the machine
11
+ * with settings for apps the user doesn't have. Existing configs are merged,
12
+ * never clobbered — a foreign JSON we can't parse is left untouched with a hint.
6
13
  */
7
14
  import * as fs from "node:fs";
8
15
  import * as os from "node:os";
@@ -14,63 +21,94 @@ function serverCommand() {
14
21
  const entry = path.join(path.dirname(fileURLToPath(import.meta.url)), "index.js");
15
22
  return { command: process.execPath, args: [entry, "serve"] };
16
23
  }
17
- export async function registerAgents() {
18
- const done = [];
19
- const { command, args } = serverCommand();
24
+ async function registerClaudeCode(command, args) {
20
25
  // On Windows `claude` is a .cmd shim: execFile can't launch it without a
21
26
  // shell (Node throws EINVAL since CVE-2024-27980). Run through the shell there
22
27
  // and quote the path arguments so spaces (e.g. "Program Files") survive; the
23
28
  // rest are constant literals, so there's no injection surface.
24
29
  const isWin = process.platform === "win32";
25
30
  const quote = (s) => (isWin ? `"${s}"` : s);
26
- // Claude Code: user scope, so every project sees the atlas tools.
27
31
  try {
28
- await execFileAsync("claude", [
29
- "mcp",
30
- "add",
31
- "--scope",
32
- "user",
33
- "changebook",
34
- "--",
35
- quote(command),
36
- ...args.map(quote),
37
- ], isWin ? { shell: true } : {});
38
- done.push("Claude Code: MCP server registered (user scope).");
32
+ await execFileAsync("claude", ["mcp", "add", "--scope", "user", "changebook", "--", quote(command), ...args.map(quote)], isWin ? { shell: true } : {});
33
+ return "Claude Code: MCP server registered (user scope).";
39
34
  }
40
35
  catch (error) {
41
36
  const msg = error instanceof Error ? error.message : String(error);
42
- if (/already exists/i.test(msg)) {
43
- done.push("Claude Code: already configured.");
44
- }
45
- else if (/ENOENT|not recognized|command not found/i.test(msg)) {
46
- done.push("Claude Code: `claude` CLI not found — install it and run `changebook init` again.");
47
- }
48
- else {
49
- done.push(`Claude Code: could not register (${msg.split("\n")[0]}).`);
50
- }
37
+ if (/already exists/i.test(msg))
38
+ return "Claude Code: already configured.";
39
+ if (/ENOENT|not recognized|command not found/i.test(msg))
40
+ return "Claude Code: `claude` CLI not found — skipped.";
41
+ return `Claude Code: could not register (${msg.split("\n")[0]}).`;
51
42
  }
52
- // Codex reads ~/.codex/config.toml; append our server if it's not there.
53
- const codexDir = path.join(os.homedir(), ".codex");
54
- const codexConfig = path.join(codexDir, "config.toml");
55
- if (fs.existsSync(codexDir)) {
56
- const current = fs.existsSync(codexConfig)
57
- ? fs.readFileSync(codexConfig, "utf8")
58
- : "";
59
- if (current.includes("[mcp_servers.changebook]")) {
60
- done.push("Codex: already configured.");
61
- }
62
- else {
63
- const argsToml = args.map((a) => JSON.stringify(a)).join(", ");
64
- const block = `\n[mcp_servers.changebook]\n` +
65
- `command = ${JSON.stringify(command)}\n` +
66
- `args = [${argsToml}]\n`;
67
- fs.appendFileSync(codexConfig, block);
68
- done.push("Codex: MCP server added to ~/.codex/config.toml.");
43
+ }
44
+ function registerCodex(command, args) {
45
+ const codexConfig = path.join(os.homedir(), ".codex", "config.toml");
46
+ if (!fs.existsSync(path.dirname(codexConfig)))
47
+ return "Codex: not found — skipped.";
48
+ const current = fs.existsSync(codexConfig) ? fs.readFileSync(codexConfig, "utf8") : "";
49
+ if (current.includes("[mcp_servers.changebook]"))
50
+ return "Codex: already configured.";
51
+ const argsToml = args.map((a) => JSON.stringify(a)).join(", ");
52
+ const block = `\n[mcp_servers.changebook]\n` +
53
+ `command = ${JSON.stringify(command)}\n` +
54
+ `args = [${argsToml}]\n`;
55
+ fs.appendFileSync(codexConfig, block);
56
+ return "Codex: added to ~/.codex/config.toml.";
57
+ }
58
+ /**
59
+ * Merge our server into a JSON MCP config, preserving everything else.
60
+ * `topKey` is "mcpServers" for Cursor/Windsurf/Claude Desktop, "servers" for
61
+ * VS Code. Only writes if the app is present (parent dir exists). An existing
62
+ * file we can't parse is never overwritten.
63
+ */
64
+ function registerJsonAgent(label, configPath, topKey, entry, requireParentDir = true) {
65
+ const parent = path.dirname(configPath);
66
+ if (requireParentDir && !fs.existsSync(parent))
67
+ return `${label}: not found — skipped.`;
68
+ let config = {};
69
+ if (fs.existsSync(configPath)) {
70
+ const raw = fs.readFileSync(configPath, "utf8").trim();
71
+ if (raw) {
72
+ try {
73
+ config = JSON.parse(raw);
74
+ }
75
+ catch {
76
+ return `${label}: config exists but isn't valid JSON — add "changebook" to ${topKey} by hand.`;
77
+ }
69
78
  }
70
79
  }
71
- else {
72
- done.push("Codex: ~/.codex not found — skipped.");
73
- }
80
+ const servers = config[topKey] ?? {};
81
+ if (servers.changebook)
82
+ return `${label}: already configured.`;
83
+ servers.changebook = entry;
84
+ config[topKey] = servers;
85
+ fs.mkdirSync(parent, { recursive: true });
86
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
87
+ return `${label}: registered.`;
88
+ }
89
+ /** Platform path for Claude Desktop's config. */
90
+ function claudeDesktopConfigPath() {
91
+ const home = os.homedir();
92
+ if (process.platform === "darwin")
93
+ return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
94
+ if (process.platform === "win32")
95
+ return path.join(process.env.APPDATA ?? path.join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
96
+ return path.join(home, ".config", "Claude", "claude_desktop_config.json");
97
+ }
98
+ export async function registerAgents(dir) {
99
+ const { command, args } = serverCommand();
100
+ const stdio = { command, args };
101
+ const home = os.homedir();
102
+ const done = [];
103
+ done.push(await registerClaudeCode(command, args));
104
+ done.push(registerCodex(command, args));
105
+ done.push(registerJsonAgent("Cursor", path.join(home, ".cursor", "mcp.json"), "mcpServers", stdio));
106
+ done.push(registerJsonAgent("Windsurf", path.join(home, ".codeium", "windsurf", "mcp_config.json"), "mcpServers", stdio));
107
+ done.push(registerJsonAgent("Claude Desktop", claudeDesktopConfigPath(), "mcpServers", stdio));
108
+ // VS Code (Copilot agent mode) reads a project-scoped .vscode/mcp.json and
109
+ // uses "servers" with an explicit transport type. Only if the project already
110
+ // has a .vscode dir, so init doesn't create one unasked.
111
+ done.push(registerJsonAgent("VS Code", path.join(dir, ".vscode", "mcp.json"), "servers", { type: "stdio", ...stdio }));
74
112
  return done;
75
113
  }
76
114
  //# sourceMappingURL=init.js.map
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "changebook",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
+ "mcpName": "io.github.raulbr90/changebook",
4
5
  "description": "ChangeBook for coding agents: MCP server (product memory for Claude Code/Codex) + CLI to sign in, analyze changes and sync the product map.",
5
6
  "type": "module",
6
7
  "main": "dist/index.js",
@@ -11,7 +12,8 @@
11
12
  "files": [
12
13
  "dist",
13
14
  "!dist/**/*.map",
14
- "README.md"
15
+ "README.md",
16
+ "server.json"
15
17
  ],
16
18
  "license": "MIT",
17
19
  "repository": {
package/server.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.raulbr90/changebook",
4
+ "description": "Query your product's living memory: module map + analyzed change history. Read-only MCP tools.",
5
+ "version": "0.3.0",
6
+ "repository": {
7
+ "url": "https://github.com/raulbr90/appatlas",
8
+ "source": "github"
9
+ },
10
+ "websiteUrl": "https://changebook.dev",
11
+ "packages": [
12
+ {
13
+ "registryType": "npm",
14
+ "registryBaseUrl": "https://registry.npmjs.org",
15
+ "identifier": "changebook",
16
+ "version": "0.3.0",
17
+ "transport": {
18
+ "type": "stdio"
19
+ }
20
+ }
21
+ ]
22
+ }