changebook 0.2.0 → 0.3.1
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/LICENSE +1 -1
- package/README.md +5 -2
- package/dist/index.js +3 -3
- package/dist/init.js +83 -45
- package/package.json +4 -7
- package/server.json +18 -0
package/LICENSE
CHANGED
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
const codexConfig = path.join(
|
|
55
|
-
if (fs.existsSync(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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.
|
|
3
|
+
"version": "0.3.1",
|
|
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,14 +12,10 @@
|
|
|
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
|
-
"repository": {
|
|
18
|
-
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/raulbr90/appatlas.git",
|
|
20
|
-
"directory": "changebook-mcp"
|
|
21
|
-
},
|
|
22
19
|
"homepage": "https://changebook.dev",
|
|
23
20
|
"keywords": [
|
|
24
21
|
"mcp",
|
package/server.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
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.1",
|
|
6
|
+
"websiteUrl": "https://changebook.dev",
|
|
7
|
+
"packages": [
|
|
8
|
+
{
|
|
9
|
+
"registryType": "npm",
|
|
10
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
11
|
+
"identifier": "changebook",
|
|
12
|
+
"version": "0.3.1",
|
|
13
|
+
"transport": {
|
|
14
|
+
"type": "stdio"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|