@productbrain/mcp 0.0.1-beta.0 → 0.0.1-beta.10

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.
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ initAnalytics,
4
+ shutdownAnalytics,
5
+ trackSetupCompleted,
6
+ trackSetupStarted
7
+ } from "./chunk-XBMI6QHR.js";
8
+
9
+ // src/cli/setup.ts
10
+ import { execSync } from "child_process";
11
+ import { createInterface } from "readline";
12
+ import { existsSync as existsSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
13
+ import { join as join2 } from "path";
14
+
15
+ // src/cli/config-writer.ts
16
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
17
+ import { join, dirname } from "path";
18
+ import { homedir, platform } from "os";
19
+ var SERVER_ENTRY_KEY = "Product Brain";
20
+ var LEGACY_ENTRY_KEY = "productbrain";
21
+ function buildServerEntry(apiKey) {
22
+ return {
23
+ command: "npx",
24
+ args: ["-y", "@productbrain/mcp@beta"],
25
+ env: { PRODUCTBRAIN_API_KEY: apiKey }
26
+ };
27
+ }
28
+ function getCursorConfigPath() {
29
+ return join(process.cwd(), ".cursor", "mcp.json");
30
+ }
31
+ function getClaudeDesktopConfigPath() {
32
+ const os = platform();
33
+ if (os === "darwin") {
34
+ return join(
35
+ homedir(),
36
+ "Library",
37
+ "Application Support",
38
+ "Claude",
39
+ "claude_desktop_config.json"
40
+ );
41
+ }
42
+ if (os === "win32") {
43
+ const appData = process.env.APPDATA ?? join(homedir(), "AppData", "Roaming");
44
+ return join(appData, "Claude", "claude_desktop_config.json");
45
+ }
46
+ return null;
47
+ }
48
+ function resolveClient(name) {
49
+ if (name === "Cursor") {
50
+ return { name, configPath: getCursorConfigPath() };
51
+ }
52
+ const configPath = getClaudeDesktopConfigPath();
53
+ return configPath ? { name, configPath } : null;
54
+ }
55
+ function readJsonSafe(path) {
56
+ if (!existsSync(path)) return {};
57
+ try {
58
+ return JSON.parse(readFileSync(path, "utf-8"));
59
+ } catch {
60
+ return {};
61
+ }
62
+ }
63
+ async function writeClientConfig(client, apiKey) {
64
+ const config = readJsonSafe(client.configPath);
65
+ const serversKey = "mcpServers";
66
+ if (!config[serversKey]) config[serversKey] = {};
67
+ if (config[serversKey][SERVER_ENTRY_KEY]) {
68
+ return false;
69
+ }
70
+ if (config[serversKey][LEGACY_ENTRY_KEY]) {
71
+ const legacy = config[serversKey][LEGACY_ENTRY_KEY];
72
+ config[serversKey][SERVER_ENTRY_KEY] = {
73
+ ...buildServerEntry(apiKey),
74
+ env: { ...legacy.env, PRODUCTBRAIN_API_KEY: legacy.env?.PRODUCTBRAIN_API_KEY ?? apiKey }
75
+ };
76
+ delete config[serversKey][LEGACY_ENTRY_KEY];
77
+ } else {
78
+ config[serversKey][SERVER_ENTRY_KEY] = buildServerEntry(apiKey);
79
+ }
80
+ const dir = dirname(client.configPath);
81
+ if (!existsSync(dir)) {
82
+ mkdirSync(dir, { recursive: true });
83
+ }
84
+ writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
85
+ return true;
86
+ }
87
+
88
+ // src/cli/setup.ts
89
+ var APP_URL = process.env.PRODUCTBRAIN_APP_URL ?? "https://productbrain.io";
90
+ function bold(s) {
91
+ return `\x1B[1m${s}\x1B[0m`;
92
+ }
93
+ function green(s) {
94
+ return `\x1B[32m${s}\x1B[0m`;
95
+ }
96
+ function dim(s) {
97
+ return `\x1B[2m${s}\x1B[0m`;
98
+ }
99
+ function orange(s) {
100
+ return `\x1B[33m${s}\x1B[0m`;
101
+ }
102
+ function log(msg) {
103
+ process.stdout.write(`${msg}
104
+ `);
105
+ }
106
+ function openBrowser(url) {
107
+ const platform2 = process.platform;
108
+ try {
109
+ if (platform2 === "darwin") execSync(`open "${url}"`);
110
+ else if (platform2 === "win32") execSync(`start "" "${url}"`);
111
+ else execSync(`xdg-open "${url}"`);
112
+ } catch {
113
+ log(dim(` Could not open browser automatically.`));
114
+ log(` Open this URL manually: ${url}`);
115
+ }
116
+ }
117
+ function prompt(question) {
118
+ return new Promise((resolve) => {
119
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
120
+ rl.question(question, (answer) => {
121
+ rl.close();
122
+ resolve(answer.trim());
123
+ });
124
+ });
125
+ }
126
+ function promptChoice(question, choices) {
127
+ return new Promise((resolve) => {
128
+ log("");
129
+ log(bold(question));
130
+ choices.forEach((c, i) => log(` ${i + 1}) ${c}`));
131
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
132
+ rl.question(`
133
+ ${dim("Choice [1]:")} `, (line) => {
134
+ rl.close();
135
+ const n = parseInt(line.trim(), 10);
136
+ if (isNaN(n) || n < 1 || n > choices.length) {
137
+ resolve(0);
138
+ } else {
139
+ resolve(n - 1);
140
+ }
141
+ });
142
+ });
143
+ }
144
+ async function runSetup() {
145
+ initAnalytics();
146
+ trackSetupStarted();
147
+ log("");
148
+ log(bold(` Product${orange("Brain")} Setup`));
149
+ log(dim(" Connect your AI assistant to your chain\n"));
150
+ const apiKeysUrl = `${APP_URL}/settings/api-keys`;
151
+ log(` ${dim("1. Get your API key from Settings \u2192 API Keys")}`);
152
+ log(` ${dim(apiKeysUrl)}
153
+ `);
154
+ const openNow = await prompt(` Open this URL in your browser? [Y/n]: `);
155
+ if (openNow.toLowerCase() !== "n" && openNow.toLowerCase() !== "no") {
156
+ openBrowser(apiKeysUrl);
157
+ }
158
+ log("");
159
+ log(` ${dim("2. Generate a key (if you don't have one), then copy it.\n")}`);
160
+ const apiKey = await prompt(` Paste your API key (pb_sk_...): `);
161
+ if (!apiKey || !apiKey.startsWith("pb_sk_")) {
162
+ log(` ${orange("!")} Invalid key format. Keys start with pb_sk_.`);
163
+ log(` Get one at ${apiKeysUrl}
164
+ `);
165
+ await shutdownAnalytics();
166
+ process.exit(1);
167
+ }
168
+ log(` ${green("\u2713")} Key received
169
+ `);
170
+ const CLIENT_NAMES = ["Cursor", "Claude Desktop"];
171
+ const options = [...CLIENT_NAMES, "Other"];
172
+ const choice = await promptChoice("Where do you want to set up Product Brain?", options);
173
+ if (choice === 2) {
174
+ printConfigSnippet(apiKey);
175
+ trackSetupCompleted("Other", "snippet_shown");
176
+ } else {
177
+ const client = resolveClient(CLIENT_NAMES[choice]);
178
+ if (client) {
179
+ const outcome = await writeConfig(client, apiKey);
180
+ trackSetupCompleted(CLIENT_NAMES[choice], outcome);
181
+ } else {
182
+ log(` ${orange("!")} ${CLIENT_NAMES[choice]} config path not available on this platform.`);
183
+ printConfigSnippet(apiKey);
184
+ trackSetupCompleted(CLIENT_NAMES[choice], "write_error");
185
+ }
186
+ }
187
+ if (choice === 0) {
188
+ await offerCursorRulesInstall();
189
+ printDeeplink(apiKey);
190
+ }
191
+ if (choice === 1) {
192
+ printClaudeSnippet();
193
+ }
194
+ log("");
195
+ log(
196
+ ` ${green("\u2713")} Done! Restart your AI assistant and try: ${bold('"Start PB"')}`
197
+ );
198
+ printHelpLink();
199
+ await shutdownAnalytics();
200
+ }
201
+ async function writeConfig(client, apiKey) {
202
+ try {
203
+ const wrote = await writeClientConfig(client, apiKey);
204
+ if (wrote) {
205
+ log(` ${green("\u2713")} Wrote config to ${dim(client.configPath)}`);
206
+ return "config_written";
207
+ } else {
208
+ log(` ${dim("\u2139")} ${client.name} already configured \u2014 skipped`);
209
+ return "config_existed";
210
+ }
211
+ } catch (err) {
212
+ log(` ${orange("!")} Could not write ${client.name} config: ${err.message}`);
213
+ printConfigSnippet(apiKey);
214
+ return "write_error";
215
+ }
216
+ }
217
+ function printHelpLink() {
218
+ log(` ${dim(`Need help? See ${APP_URL}/settings/api-keys`)}`);
219
+ log("");
220
+ }
221
+ function printConfigSnippet(apiKey) {
222
+ log("");
223
+ log(bold(" Add this to your MCP client config:\n"));
224
+ const snippet = JSON.stringify(
225
+ {
226
+ mcpServers: {
227
+ "Product Brain": {
228
+ command: "npx",
229
+ args: ["-y", "@productbrain/mcp@beta"],
230
+ env: { PRODUCTBRAIN_API_KEY: apiKey }
231
+ }
232
+ }
233
+ },
234
+ null,
235
+ 2
236
+ );
237
+ for (const line of snippet.split("\n")) {
238
+ log(` ${line}`);
239
+ }
240
+ log("");
241
+ }
242
+ var CURSOR_RULE_FILENAME = "product-brain.mdc";
243
+ var CURSOR_RULE_CONTENT = `---
244
+ description: Product Brain MCP \u2014 single source of truth for product knowledge
245
+ globs:
246
+ alwaysApply: true
247
+ ---
248
+
249
+ # Product Brain MCP
250
+
251
+ Product Brain is your product knowledge base. The Chain is the single source of truth.
252
+
253
+ Every entry is either a **draft** (captured but not committed) or **committed** (on the Chain, SSOT).
254
+ Committing to the Chain is the compounding act.
255
+
256
+ ## Quick Start
257
+
258
+ Say **"Start PB"** or **"Start Product Brain"** to begin. This single call:
259
+ - Orients you to the workspace (readiness, gaps, planned work)
260
+ - Unlocks write tools for the session
261
+ - Surfaces your next recommended action
262
+
263
+ ## Tool Workflow
264
+
265
+ 1. **Start here**: \`orient\` or \`start\` \u2014 workspace context + next action
266
+ 2. **Search**: \`search\` \u2014 find entries across all collections
267
+ 3. **Drill in**: \`get-entry\` \u2014 full record with data, labels, relations
268
+ 4. **Context**: \`gather-context\` \u2014 related knowledge around an entry or task
269
+ 5. **Capture**: \`capture\` \u2014 create a draft with auto-linking + quality score
270
+ 6. **Commit**: \`commit-entry\` \u2014 promote draft to SSOT (only when user confirms)
271
+ 7. **Connect**: \`suggest-links\` then \`relate-entries\` to build the graph
272
+
273
+ ## Rules
274
+
275
+ - Always capture as draft first. Only call \`commit-entry\` when the user confirms.
276
+ - Use \`suggest-links\` after capturing to discover and create relations.
277
+ - When lost, fetch \`productbrain://orientation\` for the full system map.
278
+ `;
279
+ function isCursorProject() {
280
+ return existsSync2(join2(process.cwd(), ".cursor")) || existsSync2(join2(process.cwd(), ".cursorignore"));
281
+ }
282
+ async function offerCursorRulesInstall() {
283
+ if (!isCursorProject()) return;
284
+ const answer = await prompt(`
285
+ Install Product Brain rule for Cursor? [Y/n]: `);
286
+ if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
287
+ log(dim(" Skipped rule install."));
288
+ return;
289
+ }
290
+ const rulesDir = join2(process.cwd(), ".cursor", "rules");
291
+ const rulePath = join2(rulesDir, CURSOR_RULE_FILENAME);
292
+ if (existsSync2(rulePath)) {
293
+ log(` ${dim("\u2139")} Rule already exists at ${dim(rulePath)} \u2014 skipped`);
294
+ return;
295
+ }
296
+ if (!existsSync2(rulesDir)) {
297
+ mkdirSync2(rulesDir, { recursive: true });
298
+ }
299
+ writeFileSync2(rulePath, CURSOR_RULE_CONTENT, "utf-8");
300
+ log(` ${green("\u2713")} Installed rule at ${dim(rulePath)}`);
301
+ }
302
+ function buildDeeplink(apiKey) {
303
+ const config = JSON.stringify({
304
+ command: "npx",
305
+ args: ["-y", "@productbrain/mcp@beta"],
306
+ env: { PRODUCTBRAIN_API_KEY: apiKey }
307
+ });
308
+ const encoded = Buffer.from(config).toString("base64url");
309
+ return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent("Product Brain")}&config=${encoded}`;
310
+ }
311
+ function printDeeplink(apiKey) {
312
+ const link = buildDeeplink(apiKey);
313
+ log("");
314
+ log(` ${dim("One-click install for Cursor (paste in browser):")}`);
315
+ log(` ${link}`);
316
+ }
317
+ function printClaudeSnippet() {
318
+ log("");
319
+ log(bold(" For Claude Code / CLAUDE.md:"));
320
+ log(dim(" Add this line to your ~/.claude/CLAUDE.md:"));
321
+ log("");
322
+ log(` When Product Brain MCP is available, say "Start PB" at the beginning`);
323
+ log(` of each session to orient to the workspace and unlock write tools.`);
324
+ log(` Always capture as draft first; only commit when the user confirms.`);
325
+ log("");
326
+ }
327
+ export {
328
+ runSetup
329
+ };
330
+ //# sourceMappingURL=setup-6KVGMTRP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/setup.ts","../src/cli/config-writer.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * `npx @productbrain/mcp setup`\n *\n * Guided onboarding: get API key from the app, paste it, write MCP config,\n * and optionally install Cursor rules/skills (additive-only).\n */\n\nimport { execSync } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport { existsSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { resolveClient, writeClientConfig, type McpClientInfo } from \"./config-writer.js\";\nimport { initAnalytics, trackSetupStarted, trackSetupCompleted, shutdownAnalytics } from \"../analytics.js\";\n\nconst APP_URL =\n process.env.PRODUCTBRAIN_APP_URL ?? \"https://productbrain.io\";\n\n// ── Helpers ─────────────────────────────────────────────────────────────\n\nfunction bold(s: string) {\n return `\\x1b[1m${s}\\x1b[0m`;\n}\nfunction green(s: string) {\n return `\\x1b[32m${s}\\x1b[0m`;\n}\nfunction dim(s: string) {\n return `\\x1b[2m${s}\\x1b[0m`;\n}\nfunction orange(s: string) {\n return `\\x1b[33m${s}\\x1b[0m`;\n}\n\nfunction log(msg: string) {\n process.stdout.write(`${msg}\\n`);\n}\n\nfunction openBrowser(url: string) {\n const platform = process.platform;\n try {\n if (platform === \"darwin\") execSync(`open \"${url}\"`);\n else if (platform === \"win32\") execSync(`start \"\" \"${url}\"`);\n else execSync(`xdg-open \"${url}\"`);\n } catch {\n log(dim(` Could not open browser automatically.`));\n log(` Open this URL manually: ${url}`);\n }\n}\n\nfunction prompt(question: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\nfunction promptChoice(question: string, choices: string[]): Promise<number> {\n return new Promise((resolve) => {\n log(\"\");\n log(bold(question));\n choices.forEach((c, i) => log(` ${i + 1}) ${c}`));\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(`\\n ${dim(\"Choice [1]:\")} `, (line) => {\n rl.close();\n const n = parseInt(line.trim(), 10);\n if (isNaN(n) || n < 1 || n > choices.length) {\n resolve(0);\n } else {\n resolve(n - 1);\n }\n });\n });\n}\n\n// ── Main ────────────────────────────────────────────────────────────────\n\nexport async function runSetup() {\n initAnalytics();\n trackSetupStarted();\n\n log(\"\");\n log(bold(` Product${orange(\"Brain\")} Setup`));\n log(dim(\" Connect your AI assistant to your chain\\n\"));\n\n const apiKeysUrl = `${APP_URL}/settings/api-keys`;\n\n log(` ${dim(\"1. Get your API key from Settings → API Keys\")}`);\n log(` ${dim(apiKeysUrl)}\\n`);\n\n const openNow = await prompt(` Open this URL in your browser? [Y/n]: `);\n if (openNow.toLowerCase() !== \"n\" && openNow.toLowerCase() !== \"no\") {\n openBrowser(apiKeysUrl);\n }\n\n log(\"\");\n log(` ${dim(\"2. Generate a key (if you don't have one), then copy it.\\n\")}`);\n\n const apiKey = await prompt(` Paste your API key (pb_sk_...): `);\n\n if (!apiKey || !apiKey.startsWith(\"pb_sk_\")) {\n log(` ${orange(\"!\")} Invalid key format. Keys start with pb_sk_.`);\n log(` Get one at ${apiKeysUrl}\\n`);\n await shutdownAnalytics();\n process.exit(1);\n }\n\n log(` ${green(\"✓\")} Key received\\n`);\n\n const CLIENT_NAMES = [\"Cursor\", \"Claude Desktop\"] as const;\n const options = [...CLIENT_NAMES, \"Other\"];\n\n const choice = await promptChoice(\"Where do you want to set up Product Brain?\", options);\n\n if (choice === 2) {\n printConfigSnippet(apiKey);\n trackSetupCompleted(\"Other\", \"snippet_shown\");\n } else {\n const client = resolveClient(CLIENT_NAMES[choice]);\n if (client) {\n const outcome = await writeConfig(client, apiKey);\n trackSetupCompleted(CLIENT_NAMES[choice], outcome);\n } else {\n log(` ${orange(\"!\")} ${CLIENT_NAMES[choice]} config path not available on this platform.`);\n printConfigSnippet(apiKey);\n trackSetupCompleted(CLIENT_NAMES[choice], \"write_error\");\n }\n }\n\n // Cursor-specific: offer to install rule (additive-only)\n if (choice === 0) {\n await offerCursorRulesInstall();\n printDeeplink(apiKey);\n }\n\n // Claude-specific: print snippet (never write to CLAUDE.md)\n if (choice === 1) {\n printClaudeSnippet();\n }\n\n log(\"\");\n log(\n ` ${green(\"✓\")} Done! Restart your AI assistant and try: ${bold('\"Start PB\"')}`,\n );\n printHelpLink();\n await shutdownAnalytics();\n}\n\nasync function writeConfig(\n client: McpClientInfo,\n apiKey: string,\n): Promise<\"config_written\" | \"config_existed\" | \"write_error\"> {\n try {\n const wrote = await writeClientConfig(client, apiKey);\n if (wrote) {\n log(` ${green(\"✓\")} Wrote config to ${dim(client.configPath)}`);\n return \"config_written\";\n } else {\n log(` ${dim(\"ℹ\")} ${client.name} already configured — skipped`);\n return \"config_existed\";\n }\n } catch (err: any) {\n log(` ${orange(\"!\")} Could not write ${client.name} config: ${err.message}`);\n printConfigSnippet(apiKey);\n return \"write_error\";\n }\n}\n\nfunction printHelpLink() {\n log(` ${dim(`Need help? See ${APP_URL}/settings/api-keys`)}`);\n log(\"\");\n}\n\nfunction printConfigSnippet(apiKey: string) {\n log(\"\");\n log(bold(\" Add this to your MCP client config:\\n\"));\n const snippet = JSON.stringify(\n {\n mcpServers: {\n \"Product Brain\": {\n command: \"npx\",\n args: [\"-y\", \"@productbrain/mcp@beta\"],\n env: { PRODUCTBRAIN_API_KEY: apiKey },\n },\n },\n },\n null,\n 2,\n );\n for (const line of snippet.split(\"\\n\")) {\n log(` ${line}`);\n }\n log(\"\");\n}\n\n// ── Cursor Rules/Skills Install (additive-only) ─────────────────────────\n\nconst CURSOR_RULE_FILENAME = \"product-brain.mdc\";\n\nconst CURSOR_RULE_CONTENT = `---\ndescription: Product Brain MCP — single source of truth for product knowledge\nglobs:\nalwaysApply: true\n---\n\n# Product Brain MCP\n\nProduct Brain is your product knowledge base. The Chain is the single source of truth.\n\nEvery entry is either a **draft** (captured but not committed) or **committed** (on the Chain, SSOT).\nCommitting to the Chain is the compounding act.\n\n## Quick Start\n\nSay **\"Start PB\"** or **\"Start Product Brain\"** to begin. This single call:\n- Orients you to the workspace (readiness, gaps, planned work)\n- Unlocks write tools for the session\n- Surfaces your next recommended action\n\n## Tool Workflow\n\n1. **Start here**: \\`orient\\` or \\`start\\` — workspace context + next action\n2. **Search**: \\`search\\` — find entries across all collections\n3. **Drill in**: \\`get-entry\\` — full record with data, labels, relations\n4. **Context**: \\`gather-context\\` — related knowledge around an entry or task\n5. **Capture**: \\`capture\\` — create a draft with auto-linking + quality score\n6. **Commit**: \\`commit-entry\\` — promote draft to SSOT (only when user confirms)\n7. **Connect**: \\`suggest-links\\` then \\`relate-entries\\` to build the graph\n\n## Rules\n\n- Always capture as draft first. Only call \\`commit-entry\\` when the user confirms.\n- Use \\`suggest-links\\` after capturing to discover and create relations.\n- When lost, fetch \\`productbrain://orientation\\` for the full system map.\n`;\n\nfunction isCursorProject(): boolean {\n return existsSync(join(process.cwd(), \".cursor\")) || existsSync(join(process.cwd(), \".cursorignore\"));\n}\n\nasync function offerCursorRulesInstall(): Promise<void> {\n if (!isCursorProject()) return;\n\n const answer = await prompt(`\\n Install Product Brain rule for Cursor? [Y/n]: `);\n if (answer.toLowerCase() === \"n\" || answer.toLowerCase() === \"no\") {\n log(dim(\" Skipped rule install.\"));\n return;\n }\n\n const rulesDir = join(process.cwd(), \".cursor\", \"rules\");\n const rulePath = join(rulesDir, CURSOR_RULE_FILENAME);\n\n if (existsSync(rulePath)) {\n log(` ${dim(\"ℹ\")} Rule already exists at ${dim(rulePath)} — skipped`);\n return;\n }\n\n if (!existsSync(rulesDir)) {\n mkdirSync(rulesDir, { recursive: true });\n }\n\n writeFileSync(rulePath, CURSOR_RULE_CONTENT, \"utf-8\");\n log(` ${green(\"✓\")} Installed rule at ${dim(rulePath)}`);\n}\n\nfunction buildDeeplink(apiKey: string): string {\n const config = JSON.stringify({\n command: \"npx\",\n args: [\"-y\", \"@productbrain/mcp@beta\"],\n env: { PRODUCTBRAIN_API_KEY: apiKey },\n });\n const encoded = Buffer.from(config).toString(\"base64url\");\n return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(\"Product Brain\")}&config=${encoded}`;\n}\n\nfunction printDeeplink(apiKey: string): void {\n const link = buildDeeplink(apiKey);\n log(\"\");\n log(` ${dim(\"One-click install for Cursor (paste in browser):\")}`);\n log(` ${link}`);\n}\n\nfunction printClaudeSnippet(): void {\n log(\"\");\n log(bold(\" For Claude Code / CLAUDE.md:\"));\n log(dim(\" Add this line to your ~/.claude/CLAUDE.md:\"));\n log(\"\");\n log(` When Product Brain MCP is available, say \"Start PB\" at the beginning`);\n log(` of each session to orient to the workspace and unlock write tools.`);\n log(` Always capture as draft first; only commit when the user confirms.`);\n log(\"\");\n}\n","/**\n * Multi-client MCP config detection and writer.\n *\n * Supports:\n * - Cursor: .cursor/mcp.json in cwd (project-level)\n * - Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)\n * %APPDATA%/Claude/claude_desktop_config.json (Windows)\n *\n * The writer reads existing config, merges the new server entry (never\n * overwrites existing entries), and writes back. Falls back to printing\n * a snippet for unsupported OS or unknown formats.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir, platform } from \"node:os\";\n\nexport interface McpClientInfo {\n name: string;\n configPath: string;\n}\n\nconst SERVER_ENTRY_KEY = \"Product Brain\";\nconst LEGACY_ENTRY_KEY = \"productbrain\";\n\nfunction buildServerEntry(apiKey: string) {\n return {\n command: \"npx\",\n args: [\"-y\", \"@productbrain/mcp@beta\"],\n env: { PRODUCTBRAIN_API_KEY: apiKey },\n };\n}\n\n// ── Detection ───────────────────────────────────────────────────────────\n\nfunction getCursorConfigPath(): string {\n return join(process.cwd(), \".cursor\", \"mcp.json\");\n}\n\nfunction getClaudeDesktopConfigPath(): string | null {\n const os = platform();\n if (os === \"darwin\") {\n return join(\n homedir(),\n \"Library\",\n \"Application Support\",\n \"Claude\",\n \"claude_desktop_config.json\",\n );\n }\n if (os === \"win32\") {\n const appData = process.env.APPDATA ?? join(homedir(), \"AppData\", \"Roaming\");\n return join(appData, \"Claude\", \"claude_desktop_config.json\");\n }\n // Linux: no official Claude Desktop location yet\n return null;\n}\n\nexport function resolveClient(name: \"Cursor\" | \"Claude Desktop\"): McpClientInfo | null {\n if (name === \"Cursor\") {\n return { name, configPath: getCursorConfigPath() };\n }\n const configPath = getClaudeDesktopConfigPath();\n return configPath ? { name, configPath } : null;\n}\n\n// ── Writing ─────────────────────────────────────────────────────────────\n\nfunction readJsonSafe(path: string): Record<string, any> {\n if (!existsSync(path)) return {};\n try {\n return JSON.parse(readFileSync(path, \"utf-8\"));\n } catch {\n return {};\n }\n}\n\n/**\n * Write or merge the Product Brain server entry into a client config file.\n * Migrates legacy \"productbrain\" key to \"Product Brain\" when present.\n * Returns true if the config was written, false if already present.\n */\nexport async function writeClientConfig(\n client: McpClientInfo,\n apiKey: string,\n): Promise<boolean> {\n const config = readJsonSafe(client.configPath);\n\n const serversKey = \"mcpServers\";\n if (!config[serversKey]) config[serversKey] = {};\n\n // Don't overwrite an existing Product Brain entry\n if (config[serversKey][SERVER_ENTRY_KEY]) {\n return false;\n }\n\n // Migrate legacy \"productbrain\" key to \"Product Brain\", preserving existing env\n if (config[serversKey][LEGACY_ENTRY_KEY]) {\n const legacy = config[serversKey][LEGACY_ENTRY_KEY];\n config[serversKey][SERVER_ENTRY_KEY] = {\n ...buildServerEntry(apiKey),\n env: { ...legacy.env, PRODUCTBRAIN_API_KEY: legacy.env?.PRODUCTBRAIN_API_KEY ?? apiKey },\n };\n delete config[serversKey][LEGACY_ENTRY_KEY];\n } else {\n config[serversKey][SERVER_ENTRY_KEY] = buildServerEntry(apiKey);\n }\n\n const dir = dirname(client.configPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(client.configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n return true;\n}\n"],"mappings":"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","names":["existsSync","writeFileSync","mkdirSync","join","platform","existsSync","join","mkdirSync","writeFileSync"]}
@@ -0,0 +1,14 @@
1
+ import {
2
+ checkEntryQuality,
3
+ formatQualityReport,
4
+ registerSmartCaptureTools,
5
+ runContradictionCheck
6
+ } from "./chunk-ZMGVB2VY.js";
7
+ import "./chunk-XBMI6QHR.js";
8
+ export {
9
+ checkEntryQuality,
10
+ formatQualityReport,
11
+ registerSmartCaptureTools,
12
+ runContradictionCheck
13
+ };
14
+ //# sourceMappingURL=smart-capture-YUVDO42L.js.map
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "@productbrain/mcp",
3
- "version": "0.0.1-beta.0",
4
- "description": "ProductBrain MCP server single source of truth for product knowledge in Cursor, Claude Desktop, and any MCP-compatible AI assistant.",
3
+ "version": "0.0.1-beta.10",
4
+ "description": "Product BrainMCP server for AI-assisted product knowledge management",
5
5
  "type": "module",
6
+ "engines": {
7
+ "node": ">=18"
8
+ },
6
9
  "bin": {
7
10
  "productbrain": "dist/cli/index.js",
11
+ "mcp": "dist/cli/index.js",
8
12
  "synergyos-mcp": "dist/index.js"
9
13
  },
10
14
  "files": [
@@ -12,38 +16,15 @@
12
16
  "README.md",
13
17
  ".env.mcp.example"
14
18
  ],
15
- "engines": {
16
- "node": ">=18.0.0"
17
- },
18
19
  "scripts": {
19
20
  "build": "tsup",
20
21
  "start": "node dist/index.js",
21
22
  "dev": "tsx src/index.ts",
22
23
  "typecheck": "tsc --noEmit",
23
24
  "prepublishOnly": "npm run build",
24
- "publish:beta": "npm publish --tag=beta --access=public",
25
+ "publish:beta": "npm publish --tag=beta",
25
26
  "version:prerelease": "npm version prerelease"
26
27
  },
27
- "keywords": [
28
- "productbrain",
29
- "synergyos",
30
- "mcp",
31
- "model-context-protocol",
32
- "cursor",
33
- "claude-desktop",
34
- "product-management",
35
- "knowledge-base",
36
- "glossary",
37
- "business-rules",
38
- "terminology"
39
- ],
40
- "author": "SynergyOS.ai",
41
- "license": "MIT",
42
- "repository": {
43
- "type": "git",
44
- "url": "git+https://github.com/synergyai-os/productbrain.git"
45
- },
46
- "homepage": "https://github.com/synergyai-os/productbrain#readme",
47
28
  "dependencies": {
48
29
  "@modelcontextprotocol/sdk": "^1.12.1",
49
30
  "convex": "^1.32.0",
@@ -55,5 +36,8 @@
55
36
  "tsup": "^8.0.0",
56
37
  "tsx": "^4.0.0",
57
38
  "typescript": "^5.0.0"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
58
42
  }
59
43
  }