@productbrain/mcp 0.0.1-beta.1159 → 0.0.1-beta.11608

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/dist/index.js CHANGED
@@ -1,20 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- SERVER_VERSION,
3
+ createProductBrainServer
4
+ } from "./chunk-FSXEWXW6.js";
5
+ import {
4
6
  bootstrap,
5
- createProductBrainServer,
6
7
  getAgentSessionId,
7
8
  getWorkspaceId,
8
- initFeatureFlags,
9
9
  orphanAgentSession,
10
10
  recoverSessionState
11
- } from "./chunk-WG7H45D3.js";
12
- import {
13
- getPostHogClient,
14
- initAnalytics,
15
- shutdownAnalytics,
16
- trackSessionStarted
17
- } from "./chunk-YMF3IQ5E.js";
11
+ } from "./chunk-Z6D2YH32.js";
18
12
 
19
13
  // src/index.ts
20
14
  import { readFileSync } from "fs";
@@ -45,10 +39,6 @@ async function gracefulShutdown() {
45
39
  if (getAgentSessionId()) await withTimeout(orphanAgentSession(), 1500);
46
40
  } catch {
47
41
  }
48
- try {
49
- await withTimeout(shutdownAnalytics(), 1e3);
50
- } catch {
51
- }
52
42
  clearTimeout(hardExit);
53
43
  process.exit(0);
54
44
  }
@@ -62,12 +52,9 @@ process.on("unhandledRejection", (reason) => {
62
52
  process.on("uncaughtException", (err) => {
63
53
  process.stderr.write(`[MCP] Uncaught exception: ${err.stack ?? err.message}
64
54
  `);
65
- getPostHogClient()?.capture({ distinctId: "mcp-server", event: "mcp_process_crashed", properties: { message: err.message, stack: err.stack } });
66
55
  gracefulShutdown();
67
56
  });
68
57
  bootstrap();
69
- initAnalytics();
70
- initFeatureFlags(getPostHogClient());
71
58
  var server = createProductBrainServer();
72
59
  var transport = new StdioServerTransport();
73
60
  transport.onerror = (error) => {
@@ -80,8 +67,7 @@ process.stderr.write("[MCP] Server connected.\n");
80
67
  process.stdin.on("end", () => {
81
68
  gracefulShutdown();
82
69
  });
83
- getWorkspaceId().then(async (wsId) => {
84
- trackSessionStarted(wsId, SERVER_VERSION);
70
+ getWorkspaceId().then(async () => {
85
71
  try {
86
72
  await recoverSessionState();
87
73
  } catch {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\n\nimport {\n getWorkspaceId,\n bootstrap,\n orphanAgentSession,\n getAgentSessionId,\n recoverSessionState,\n} from \"./client.js\";\nimport { initAnalytics, trackSessionStarted, shutdownAnalytics, getPostHogClient } from \"./analytics.js\";\nimport { initFeatureFlags } from \"./featureFlags.js\";\nimport { createProductBrainServer, SERVER_VERSION } from \"./server.js\";\n\ntry {\n const envPath = resolve(process.cwd(), \".env.mcp\");\n for (const line of readFileSync(envPath, \"utf-8\").split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIdx = trimmed.indexOf(\"=\");\n if (eqIdx === -1) continue;\n process.env[trimmed.slice(0, eqIdx)] ??= trimmed.slice(eqIdx + 1);\n }\n} catch {\n // .env.mcp not found — rely on env vars being set by the launcher\n}\n\nconst withTimeout = <T>(p: Promise<T>, ms: number): Promise<T | void> =>\n Promise.race([p, new Promise<void>((r) => setTimeout(r, ms))]);\n\nlet shuttingDown = false;\nasync function gracefulShutdown() {\n if (shuttingDown) return;\n shuttingDown = true;\n process.stderr.write(\"[MCP] Graceful shutdown initiated.\\n\");\n // Hard-exit guard — fires if async cleanup exceeds budget.\n // Still exits 0: SIGTERM/stdin-close is an intentional shutdown regardless.\n const hardExit = setTimeout(() => {\n process.stderr.write(\"[MCP] Hard exit after shutdown timeout.\\n\");\n process.exit(0);\n }, 3_000);\n try {\n if (getAgentSessionId()) await withTimeout(orphanAgentSession(), 1_500);\n } catch {\n /* best-effort */\n }\n try {\n await withTimeout(shutdownAnalytics(), 1_000);\n } catch {\n /* best-effort */\n }\n clearTimeout(hardExit);\n process.exit(0);\n}\n\n// Register signal handlers BEFORE server.connect so they are always active\nprocess.on(\"SIGINT\", gracefulShutdown);\nprocess.on(\"SIGTERM\", gracefulShutdown);\nprocess.on(\"unhandledRejection\", (reason) => {\n const msg = reason instanceof Error ? reason.message : String(reason);\n process.stderr.write(`[MCP] Unhandled rejection: ${msg}\\n`);\n});\nprocess.on(\"uncaughtException\", (err) => {\n process.stderr.write(`[MCP] Uncaught exception: ${err.stack ?? err.message}\\n`);\n // Track crash via PostHog so it surfaces in the analytics dashboard even without Sentry.\n getPostHogClient()?.capture({ distinctId: \"mcp-server\", event: \"mcp_process_crashed\", properties: { message: err.message, stack: err.stack } });\n gracefulShutdown();\n});\n\nbootstrap();\ninitAnalytics();\ninitFeatureFlags(getPostHogClient());\n\nconst server = createProductBrainServer();\nconst transport = new StdioServerTransport();\ntransport.onerror = (error) => {\n process.stderr.write(`[MCP] Transport error: ${error.message}\\n`);\n};\nprocess.stderr.write(\"[MCP] Starting server.\\n\");\nawait server.connect(transport);\nprocess.stderr.write(\"[MCP] Server connected.\\n\");\n\nprocess.stdin.on(\"end\", () => {\n gracefulShutdown();\n});\n\n// Do not auto-start agent sessions here: a second concurrent startAgentSession (e.g. from\n// `session action=start` right after connect) can supersede the first session while client\n// state still holds the stale id — markOriented then fails with \"superseded, not active\".\n// Call `session action=start` or `start` explicitly (STD-135).\ngetWorkspaceId()\n .then(async (wsId) => {\n trackSessionStarted(wsId, SERVER_VERSION);\n try {\n await recoverSessionState();\n } catch {\n /* best-effort — fresh stdio has nothing to recover */\n }\n })\n .catch(() => {\n process.stderr.write(\"[MCP] Workspace resolution deferred — will retry on first tool call.\\n\");\n });\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,4BAA4B;AAarC,IAAI;AACF,QAAM,UAAU,QAAQ,QAAQ,IAAI,GAAG,UAAU;AACjD,aAAW,QAAQ,aAAa,SAAS,OAAO,EAAE,MAAM,IAAI,GAAG;AAC7D,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,UAAM,QAAQ,QAAQ,QAAQ,GAAG;AACjC,QAAI,UAAU,GAAI;AAClB,YAAQ,IAAI,QAAQ,MAAM,GAAG,KAAK,CAAC,MAAM,QAAQ,MAAM,QAAQ,CAAC;AAAA,EAClE;AACF,QAAQ;AAER;AAEA,IAAM,cAAc,CAAI,GAAe,OACrC,QAAQ,KAAK,CAAC,GAAG,IAAI,QAAc,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC,CAAC,CAAC;AAE/D,IAAI,eAAe;AACnB,eAAe,mBAAmB;AAChC,MAAI,aAAc;AAClB,iBAAe;AACf,UAAQ,OAAO,MAAM,sCAAsC;AAG3D,QAAM,WAAW,WAAW,MAAM;AAChC,YAAQ,OAAO,MAAM,2CAA2C;AAChE,YAAQ,KAAK,CAAC;AAAA,EAChB,GAAG,GAAK;AACR,MAAI;AACF,QAAI,kBAAkB,EAAG,OAAM,YAAY,mBAAmB,GAAG,IAAK;AAAA,EACxE,QAAQ;AAAA,EAER;AACA,MAAI;AACF,UAAM,YAAY,kBAAkB,GAAG,GAAK;AAAA,EAC9C,QAAQ;AAAA,EAER;AACA,eAAa,QAAQ;AACrB,UAAQ,KAAK,CAAC;AAChB;AAGA,QAAQ,GAAG,UAAU,gBAAgB;AACrC,QAAQ,GAAG,WAAW,gBAAgB;AACtC,QAAQ,GAAG,sBAAsB,CAAC,WAAW;AAC3C,QAAM,MAAM,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AACpE,UAAQ,OAAO,MAAM,8BAA8B,GAAG;AAAA,CAAI;AAC5D,CAAC;AACD,QAAQ,GAAG,qBAAqB,CAAC,QAAQ;AACvC,UAAQ,OAAO,MAAM,6BAA6B,IAAI,SAAS,IAAI,OAAO;AAAA,CAAI;AAE9E,mBAAiB,GAAG,QAAQ,EAAE,YAAY,cAAc,OAAO,uBAAuB,YAAY,EAAE,SAAS,IAAI,SAAS,OAAO,IAAI,MAAM,EAAE,CAAC;AAC9I,mBAAiB;AACnB,CAAC;AAED,UAAU;AACV,cAAc;AACd,iBAAiB,iBAAiB,CAAC;AAEnC,IAAM,SAAS,yBAAyB;AACxC,IAAM,YAAY,IAAI,qBAAqB;AAC3C,UAAU,UAAU,CAAC,UAAU;AAC7B,UAAQ,OAAO,MAAM,0BAA0B,MAAM,OAAO;AAAA,CAAI;AAClE;AACA,QAAQ,OAAO,MAAM,0BAA0B;AAC/C,MAAM,OAAO,QAAQ,SAAS;AAC9B,QAAQ,OAAO,MAAM,2BAA2B;AAEhD,QAAQ,MAAM,GAAG,OAAO,MAAM;AAC5B,mBAAiB;AACnB,CAAC;AAMD,eAAe,EACZ,KAAK,OAAO,SAAS;AACpB,sBAAoB,MAAM,cAAc;AACxC,MAAI;AACF,UAAM,oBAAoB;AAAA,EAC5B,QAAQ;AAAA,EAER;AACF,CAAC,EACA,MAAM,MAAM;AACX,UAAQ,OAAO,MAAM,6EAAwE;AAC/F,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\n\nimport {\n getWorkspaceId,\n bootstrap,\n orphanAgentSession,\n getAgentSessionId,\n recoverSessionState,\n} from \"./client.js\";\nimport { createProductBrainServer } from \"./server.js\";\n\ntry {\n const envPath = resolve(process.cwd(), \".env.mcp\");\n for (const line of readFileSync(envPath, \"utf-8\").split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIdx = trimmed.indexOf(\"=\");\n if (eqIdx === -1) continue;\n process.env[trimmed.slice(0, eqIdx)] ??= trimmed.slice(eqIdx + 1);\n }\n} catch {\n // .env.mcp not found — rely on env vars being set by the launcher\n}\n\nconst withTimeout = <T>(p: Promise<T>, ms: number): Promise<T | void> =>\n Promise.race([p, new Promise<void>((r) => setTimeout(r, ms))]);\n\nlet shuttingDown = false;\nasync function gracefulShutdown() {\n if (shuttingDown) return;\n shuttingDown = true;\n process.stderr.write(\"[MCP] Graceful shutdown initiated.\\n\");\n // Hard-exit guard — fires if async cleanup exceeds budget.\n // Still exits 0: SIGTERM/stdin-close is an intentional shutdown regardless.\n const hardExit = setTimeout(() => {\n process.stderr.write(\"[MCP] Hard exit after shutdown timeout.\\n\");\n process.exit(0);\n }, 3_000);\n try {\n if (getAgentSessionId()) await withTimeout(orphanAgentSession(), 1_500);\n } catch {\n /* best-effort */\n }\n clearTimeout(hardExit);\n process.exit(0);\n}\n\n// Register signal handlers BEFORE server.connect so they are always active\nprocess.on(\"SIGINT\", gracefulShutdown);\nprocess.on(\"SIGTERM\", gracefulShutdown);\nprocess.on(\"unhandledRejection\", (reason) => {\n const msg = reason instanceof Error ? reason.message : String(reason);\n process.stderr.write(`[MCP] Unhandled rejection: ${msg}\\n`);\n});\nprocess.on(\"uncaughtException\", (err) => {\n process.stderr.write(`[MCP] Uncaught exception: ${err.stack ?? err.message}\\n`);\n gracefulShutdown();\n});\n\nbootstrap();\n\nconst server = createProductBrainServer();\nconst transport = new StdioServerTransport();\ntransport.onerror = (error) => {\n process.stderr.write(`[MCP] Transport error: ${error.message}\\n`);\n};\nprocess.stderr.write(\"[MCP] Starting server.\\n\");\nawait server.connect(transport);\nprocess.stderr.write(\"[MCP] Server connected.\\n\");\n\nprocess.stdin.on(\"end\", () => {\n gracefulShutdown();\n});\n\n// Do not auto-start agent sessions here: a second concurrent startAgentSession (e.g. from\n// `session action=start` right after connect) can supersede the first session while client\n// state still holds the stale id — markOriented then fails with \"superseded, not active\".\n// Call `session action=start` or `start` explicitly (STD-135).\ngetWorkspaceId()\n .then(async () => {\n try {\n await recoverSessionState();\n } catch {\n /* best-effort — fresh stdio has nothing to recover */\n }\n })\n .catch(() => {\n process.stderr.write(\"[MCP] Workspace resolution deferred — will retry on first tool call.\\n\");\n });\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,4BAA4B;AAWrC,IAAI;AACF,QAAM,UAAU,QAAQ,QAAQ,IAAI,GAAG,UAAU;AACjD,aAAW,QAAQ,aAAa,SAAS,OAAO,EAAE,MAAM,IAAI,GAAG;AAC7D,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,UAAM,QAAQ,QAAQ,QAAQ,GAAG;AACjC,QAAI,UAAU,GAAI;AAClB,YAAQ,IAAI,QAAQ,MAAM,GAAG,KAAK,CAAC,MAAM,QAAQ,MAAM,QAAQ,CAAC;AAAA,EAClE;AACF,QAAQ;AAER;AAEA,IAAM,cAAc,CAAI,GAAe,OACrC,QAAQ,KAAK,CAAC,GAAG,IAAI,QAAc,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC,CAAC,CAAC;AAE/D,IAAI,eAAe;AACnB,eAAe,mBAAmB;AAChC,MAAI,aAAc;AAClB,iBAAe;AACf,UAAQ,OAAO,MAAM,sCAAsC;AAG3D,QAAM,WAAW,WAAW,MAAM;AAChC,YAAQ,OAAO,MAAM,2CAA2C;AAChE,YAAQ,KAAK,CAAC;AAAA,EAChB,GAAG,GAAK;AACR,MAAI;AACF,QAAI,kBAAkB,EAAG,OAAM,YAAY,mBAAmB,GAAG,IAAK;AAAA,EACxE,QAAQ;AAAA,EAER;AACA,eAAa,QAAQ;AACrB,UAAQ,KAAK,CAAC;AAChB;AAGA,QAAQ,GAAG,UAAU,gBAAgB;AACrC,QAAQ,GAAG,WAAW,gBAAgB;AACtC,QAAQ,GAAG,sBAAsB,CAAC,WAAW;AAC3C,QAAM,MAAM,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AACpE,UAAQ,OAAO,MAAM,8BAA8B,GAAG;AAAA,CAAI;AAC5D,CAAC;AACD,QAAQ,GAAG,qBAAqB,CAAC,QAAQ;AACvC,UAAQ,OAAO,MAAM,6BAA6B,IAAI,SAAS,IAAI,OAAO;AAAA,CAAI;AAC9E,mBAAiB;AACnB,CAAC;AAED,UAAU;AAEV,IAAM,SAAS,yBAAyB;AACxC,IAAM,YAAY,IAAI,qBAAqB;AAC3C,UAAU,UAAU,CAAC,UAAU;AAC7B,UAAQ,OAAO,MAAM,0BAA0B,MAAM,OAAO;AAAA,CAAI;AAClE;AACA,QAAQ,OAAO,MAAM,0BAA0B;AAC/C,MAAM,OAAO,QAAQ,SAAS;AAC9B,QAAQ,OAAO,MAAM,2BAA2B;AAEhD,QAAQ,MAAM,GAAG,OAAO,MAAM;AAC5B,mBAAiB;AACnB,CAAC;AAMD,eAAe,EACZ,KAAK,YAAY;AAChB,MAAI;AACF,UAAM,oBAAoB;AAAA,EAC5B,QAAQ;AAAA,EAER;AACF,CAAC,EACA,MAAM,MAAM;AACX,UAAQ,OAAO,MAAM,6EAAwE;AAC/F,CAAC;","names":[]}
@@ -1,20 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ DEFAULT_CLOUD_URL,
3
4
  MCP_NPX_PACKAGE,
4
- initAnalytics,
5
+ PASTE_NOW,
6
+ apiKeysUrl,
5
7
  resolveClient,
6
- shutdownAnalytics,
7
- trackSetupCompleted,
8
- trackSetupStarted,
8
+ warnOnProdFallthrough,
9
9
  writeClientConfig
10
- } from "./chunk-YMF3IQ5E.js";
10
+ } from "./chunk-Z6D2YH32.js";
11
11
 
12
12
  // src/cli/setup.ts
13
13
  import { execSync } from "child_process";
14
14
  import { createInterface } from "readline";
15
- import { existsSync, writeFileSync, mkdirSync } from "fs";
16
- import { join } from "path";
17
- var APP_URL = process.env.PRODUCTBRAIN_APP_URL ?? "https://work.productbrain.io";
18
15
  function bold(s) {
19
16
  return `\x1B[1m${s}\x1B[0m`;
20
17
  }
@@ -69,9 +66,14 @@ function promptChoice(question, choices) {
69
66
  });
70
67
  });
71
68
  }
72
- var DEFAULT_CLOUD_URL = "https://gateway.productbrain.io";
69
+ function resolveSetupSiteUrl() {
70
+ const explicit = process.env.CONVEX_SITE_URL ?? process.env.PRODUCTBRAIN_URL;
71
+ const url = explicit ?? DEFAULT_CLOUD_URL;
72
+ warnOnProdFallthrough(url, { explicit: explicit != null });
73
+ return url;
74
+ }
73
75
  async function verifyWorkspace(apiKey) {
74
- const siteUrl = process.env.CONVEX_SITE_URL ?? process.env.PRODUCTBRAIN_URL ?? DEFAULT_CLOUD_URL;
76
+ const siteUrl = resolveSetupSiteUrl();
75
77
  try {
76
78
  const res = await fetch(`${siteUrl.replace(/\/$/, "")}/api/aki`, {
77
79
  method: "POST",
@@ -89,27 +91,24 @@ async function verifyWorkspace(apiKey) {
89
91
  }
90
92
  }
91
93
  async function runSetup() {
92
- initAnalytics();
93
- trackSetupStarted();
94
94
  log("");
95
95
  log(bold(` Product${orange("Brain")} Setup`));
96
96
  log(dim(" Connect your AI assistant to your chain\n"));
97
- const apiKeysUrl = `${APP_URL}/settings/api-keys`;
97
+ const keysUrl = apiKeysUrl();
98
98
  log(` ${dim("1. Get your API key from Settings \u2192 API Keys")}`);
99
- log(` ${dim(apiKeysUrl)}
99
+ log(` ${dim(keysUrl)}
100
100
  `);
101
101
  const openNow = await prompt(` Open this URL in your browser? [Y/n]: `);
102
102
  if (openNow.toLowerCase() !== "n" && openNow.toLowerCase() !== "no") {
103
- openBrowser(apiKeysUrl);
103
+ openBrowser(keysUrl);
104
104
  }
105
105
  log("");
106
106
  log(` ${dim("2. Generate a key (if you don't have one), then copy it.\n")}`);
107
107
  const apiKey = await prompt(` Paste your API key (pb_sk_...): `);
108
108
  if (!apiKey || !apiKey.startsWith("pb_sk_")) {
109
109
  log(` ${orange("!")} Invalid key format. Keys start with pb_sk_.`);
110
- log(` Get one at ${apiKeysUrl}
110
+ log(` Get one at ${keysUrl}
111
111
  `);
112
- await shutdownAnalytics();
113
112
  process.exit(1);
114
113
  }
115
114
  log(` ${green("\u2713")} Key received`);
@@ -117,7 +116,7 @@ async function runSetup() {
117
116
  if (workspace) {
118
117
  log(` ${green("\u2713")} Connected to workspace: ${bold(workspace.name)} ${dim(`(${workspace.slug})`)}`);
119
118
  } else {
120
- log(` ${orange("!")} Could not verify workspace. Check your key at ${apiKeysUrl}`);
119
+ log(` ${orange("!")} Could not verify workspace. Check your key at ${keysUrl}`);
121
120
  }
122
121
  log("");
123
122
  const CLIENT_NAMES = ["Cursor", "Claude Desktop"];
@@ -125,50 +124,37 @@ async function runSetup() {
125
124
  const choice = await promptChoice("Where do you want to set up Product Brain?", options);
126
125
  if (choice === 2) {
127
126
  printConfigSnippet(apiKey);
128
- trackSetupCompleted("Other", "snippet_shown");
129
127
  } else {
130
128
  const client = resolveClient(CLIENT_NAMES[choice]);
131
129
  if (client) {
132
- const outcome = await writeConfig(client, apiKey);
133
- trackSetupCompleted(CLIENT_NAMES[choice], outcome);
130
+ await writeConfig(client, apiKey);
134
131
  } else {
135
132
  log(` ${orange("!")} ${CLIENT_NAMES[choice]} config path not available on this platform.`);
136
133
  printConfigSnippet(apiKey);
137
- trackSetupCompleted(CLIENT_NAMES[choice], "write_error");
138
134
  }
139
135
  }
140
- if (choice === 0) {
141
- await offerCursorRulesInstall();
142
- printDeeplink(apiKey);
143
- }
144
- if (choice === 1) {
145
- printClaudeSnippet();
146
- }
136
+ if (choice === 0) printDeeplink(apiKey);
147
137
  log("");
148
138
  log(
149
- ` ${green("\u2713")} Done! Restart your AI assistant and try: ${bold('"Start PB"')}`
139
+ ` ${green("\u2713")} Done! Restart your AI assistant and try: ${bold(`"${PASTE_NOW}"`)}`
150
140
  );
151
141
  printHelpLink();
152
- await shutdownAnalytics();
153
142
  }
154
143
  async function writeConfig(client, apiKey) {
155
144
  try {
156
145
  const wrote = await writeClientConfig(client, apiKey);
157
146
  if (wrote) {
158
147
  log(` ${green("\u2713")} Wrote config to ${dim(client.configPath)}`);
159
- return "config_written";
160
148
  } else {
161
149
  log(` ${dim("\u2139")} ${client.name} already configured \u2014 skipped`);
162
- return "config_existed";
163
150
  }
164
151
  } catch (err) {
165
152
  log(` ${orange("!")} Could not write ${client.name} config: ${err.message}`);
166
153
  printConfigSnippet(apiKey);
167
- return "write_error";
168
154
  }
169
155
  }
170
156
  function printHelpLink() {
171
- log(` ${dim(`Need help? See ${APP_URL}/settings/api-keys`)}`);
157
+ log(` ${dim(`Need help? See ${apiKeysUrl()}`)}`);
172
158
  log("");
173
159
  }
174
160
  function printConfigSnippet(apiKey) {
@@ -192,79 +178,6 @@ function printConfigSnippet(apiKey) {
192
178
  }
193
179
  log("");
194
180
  }
195
- var CURSOR_RULE_FILENAME = "product-brain.mdc";
196
- var CURSOR_RULE_CONTENT = `---
197
- description: Product Brain MCP \u2014 single source of truth for product knowledge
198
- globs:
199
- alwaysApply: true
200
- ---
201
-
202
- # Product Brain MCP
203
-
204
- Product Brain is your product knowledge base. The Chain is the single source of truth.
205
-
206
- Every entry is either a **draft** (captured but not accepted) or **accepted** (on the Chain, SSOT).
207
- Accepting onto the Chain is the compounding act.
208
-
209
- ## Quick Start
210
-
211
- Say **"Start PB"** or **"Start Product Brain"** to begin. This single call:
212
- - Orients you to the workspace (readiness, gaps, planned work)
213
- - Unlocks write tools for the session
214
- - Surfaces your next recommended action
215
-
216
- ## Tool Workflow
217
-
218
- 1. **Start here**: \`start\` \u2014 workspace context + next action
219
- 2. **Search**: \`entries action=search\` \u2014 find entries across all collections
220
- 3. **Drill in**: \`entries action=get\` \u2014 full record with data, labels, relations
221
- 4. **Context**: \`context action=gather\` \u2014 related knowledge around an entry or task
222
- 5. **Capture**: \`capture\` \u2014 create knowledge with auto-linking + quality score
223
- 6. **Accept**: \`commit-entry\` \u2014 promote drafts to SSOT when confirmation is still required
224
- 7. **Connect**: \`graph action=suggest\` then \`relations action=create\` to build the graph
225
-
226
- ## Bulk Knowledge Input
227
-
228
- When given a document or batch of knowledge to capture:
229
- 1. Scan the input \u2014 identify all collections needed
230
- 2. Call \`collections action=list\` \u2014 compare against what exists
231
- 3. Propose missing collections to the user for confirmation
232
- 4. Call \`collections action=create\` for each confirmed collection
233
- 5. Then capture entries into the correct collections
234
-
235
- Never stuff entries into the wrong collection. Never silently skip knowledge.
236
-
237
- ## Rules
238
-
239
- - In Open mode, user-authored captures can be accepted immediately unless the user asks to keep them as drafts.
240
- - In consensus/role modes, only call \`commit-entry\` when the user confirms.
241
- - Use \`graph action=suggest\` after capturing to discover and create relations.
242
- - Collections are dynamic \u2014 use \`collections action=create\` when the workspace needs new ones.
243
- - When lost, fetch \`productbrain://orientation\` for the full system map.
244
- `;
245
- function isCursorProject() {
246
- return existsSync(join(process.cwd(), ".cursor")) || existsSync(join(process.cwd(), ".cursorignore"));
247
- }
248
- async function offerCursorRulesInstall() {
249
- if (!isCursorProject()) return;
250
- const answer = await prompt(`
251
- Install Product Brain rule for Cursor? [Y/n]: `);
252
- if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
253
- log(dim(" Skipped rule install."));
254
- return;
255
- }
256
- const rulesDir = join(process.cwd(), ".cursor", "rules");
257
- const rulePath = join(rulesDir, CURSOR_RULE_FILENAME);
258
- if (existsSync(rulePath)) {
259
- log(` ${dim("\u2139")} Rule already exists at ${dim(rulePath)} \u2014 skipped`);
260
- return;
261
- }
262
- if (!existsSync(rulesDir)) {
263
- mkdirSync(rulesDir, { recursive: true });
264
- }
265
- writeFileSync(rulePath, CURSOR_RULE_CONTENT, "utf-8");
266
- log(` ${green("\u2713")} Installed rule at ${dim(rulePath)}`);
267
- }
268
181
  function buildDeeplink(apiKey) {
269
182
  const config = JSON.stringify({
270
183
  command: "npx",
@@ -280,18 +193,8 @@ function printDeeplink(apiKey) {
280
193
  log(` ${dim("One-click install for Cursor (paste in browser):")}`);
281
194
  log(` ${link}`);
282
195
  }
283
- function printClaudeSnippet() {
284
- log("");
285
- log(bold(" For Claude Code / CLAUDE.md:"));
286
- log(dim(" Add this line to your ~/.claude/CLAUDE.md:"));
287
- log("");
288
- log(` When Product Brain MCP is available, say "Start PB" at the beginning`);
289
- log(` of each session to orient to the workspace and unlock write tools.`);
290
- log(` In Open mode, user-authored captures can be accepted immediately unless the user asks to keep drafts.`);
291
- log(` In consensus/role modes, only accept when the user confirms.`);
292
- log("");
293
- }
294
196
  export {
197
+ resolveSetupSiteUrl,
295
198
  runSetup
296
199
  };
297
- //# sourceMappingURL=setup-PC43F5AO.js.map
200
+ //# sourceMappingURL=setup-YVFC4IQA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/setup.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * `npx @productbrain/mcp@beta setup`\n *\n * Guided onboarding: get API key from the app, paste it, write MCP config.\n */\n\nimport { execSync } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport { resolveClient, writeClientConfig, MCP_NPX_PACKAGE, type McpClientInfo } from \"./config-writer.js\";\nimport { DEFAULT_CLOUD_URL } from \"../client.js\";\nimport { apiKeysUrl } from \"../lib/appUrl.js\";\nimport { warnOnProdFallthrough } from \"../prod-fallthrough.js\";\nimport { PASTE_NOW } from \"../lib/ruleListRender.js\"; // TASK-1006: the one paste sentence\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// ── Workspace Verification ───────────────────────────────────────────────\n\n/**\n * Resolve the gateway URL the setup flow probes. TEN-2382: setup is an\n * independent entry point (no bootstrap), so it warns on silent prod\n * fall-through here. DEFAULT_CLOUD_URL is imported from client.ts (the\n * in-package SSOT) — no local re-declare.\n */\nexport function resolveSetupSiteUrl(): string {\n const explicit = process.env.CONVEX_SITE_URL ?? process.env.PRODUCTBRAIN_URL;\n const url = explicit ?? DEFAULT_CLOUD_URL;\n warnOnProdFallthrough(url, { explicit: explicit != null });\n return url;\n}\n\nasync function verifyWorkspace(\n apiKey: string,\n): Promise<{ name: string; slug: string } | null> {\n const siteUrl = resolveSetupSiteUrl();\n\n try {\n const res = await fetch(`${siteUrl.replace(/\\/$/, \"\")}/api/aki`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ fn: \"resolveWorkspace\", args: {} }),\n });\n\n if (!res.ok) return null;\n const json = (await res.json()) as {\n data?: { name: string; slug: string } | null;\n error?: string;\n };\n return json.data ?? null;\n } catch {\n return null;\n }\n}\n\n// ── Main ────────────────────────────────────────────────────────────────\n\nexport async function runSetup() {\n log(\"\");\n log(bold(` Product${orange(\"Brain\")} Setup`));\n log(dim(\" Connect your AI assistant to your chain\\n\"));\n\n const keysUrl = apiKeysUrl();\n\n log(` ${dim(\"1. Get your API key from Settings → API Keys\")}`);\n log(` ${dim(keysUrl)}\\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(keysUrl);\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 ${keysUrl}\\n`);\n process.exit(1);\n }\n\n log(` ${green(\"✓\")} Key received`);\n\n const workspace = await verifyWorkspace(apiKey);\n if (workspace) {\n log(` ${green(\"✓\")} Connected to workspace: ${bold(workspace.name)} ${dim(`(${workspace.slug})`)}`);\n } else {\n log(` ${orange(\"!\")} Could not verify workspace. Check your key at ${keysUrl}`);\n }\n log(\"\");\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 } else {\n const client = resolveClient(CLIENT_NAMES[choice]);\n if (client) {\n await writeConfig(client, apiKey);\n } else {\n log(` ${orange(\"!\")} ${CLIENT_NAMES[choice]} config path not available on this platform.`);\n printConfigSnippet(apiKey);\n }\n }\n\n // TASK-670 (WP-820, DEC-1863): no Cursor rule file and no CLAUDE.md snippet; the connector's\n // session start teaches the agent how to work here, one source.\n if (choice === 0) printDeeplink(apiKey);\n\n log(\"\");\n log(\n ` ${green(\"✓\")} Done! Restart your AI assistant and try: ${bold(`\"${PASTE_NOW}\"`)}`,\n );\n printHelpLink();\n}\n\nasync function writeConfig(\n client: McpClientInfo,\n apiKey: string,\n): Promise<void> {\n try {\n const wrote = await writeClientConfig(client, apiKey);\n if (wrote) {\n log(` ${green(\"✓\")} Wrote config to ${dim(client.configPath)}`);\n } else {\n log(` ${dim(\"ℹ\")} ${client.name} already configured — skipped`);\n }\n } catch (err: any) {\n log(` ${orange(\"!\")} Could not write ${client.name} config: ${err.message}`);\n printConfigSnippet(apiKey);\n }\n}\n\nfunction printHelpLink() {\n log(` ${dim(`Need help? See ${apiKeysUrl()}`)}`);\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\", MCP_NPX_PACKAGE],\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\nfunction buildDeeplink(apiKey: string): string {\n const config = JSON.stringify({\n command: \"npx\",\n args: [\"-y\", MCP_NPX_PACKAGE],\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\n"],"mappings":"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","names":[]}