@productbrain/mcp 0.0.1-beta.1223 → 0.0.1-beta.12232
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/.env.mcp.example +1 -1
- package/README.md +67 -0
- package/dist/chunk-5OQ7KOPB.js +11945 -0
- package/dist/chunk-5OQ7KOPB.js.map +1 -0
- package/dist/chunk-TZLNCSXM.js +1798 -0
- package/dist/chunk-TZLNCSXM.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/http.js +388 -330
- package/dist/http.js.map +1 -1
- package/dist/index.js +5 -19
- package/dist/index.js.map +1 -1
- package/dist/setup-LDN5VOSR.js +204 -0
- package/dist/setup-LDN5VOSR.js.map +1 -0
- package/dist/views/src/entry-cards/index.html +85 -60
- package/dist/views/src/graph-constellation/index.html +72 -47
- package/package.json +4 -5
- package/dist/chunk-M5XN5T3N.js +0 -15297
- package/dist/chunk-M5XN5T3N.js.map +0 -1
- package/dist/chunk-YMF3IQ5E.js +0 -465
- package/dist/chunk-YMF3IQ5E.js.map +0 -1
- package/dist/setup-PC43F5AO.js +0 -297
- package/dist/setup-PC43F5AO.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
createProductBrainServer
|
|
4
|
+
} from "./chunk-5OQ7KOPB.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-
|
|
12
|
-
import {
|
|
13
|
-
getPostHogClient,
|
|
14
|
-
initAnalytics,
|
|
15
|
-
shutdownAnalytics,
|
|
16
|
-
trackSessionStarted
|
|
17
|
-
} from "./chunk-YMF3IQ5E.js";
|
|
11
|
+
} from "./chunk-TZLNCSXM.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 (
|
|
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 {
|
|
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":[]}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
AGENT_KEY_PREFIX,
|
|
4
|
+
API_KEY_FORMAT_LINE,
|
|
5
|
+
API_KEY_PREFIX,
|
|
6
|
+
DEFAULT_CLOUD_URL,
|
|
7
|
+
MCP_NPX_PACKAGE,
|
|
8
|
+
PASTE_NOW,
|
|
9
|
+
apiKeysUrl,
|
|
10
|
+
isApiKeyShaped,
|
|
11
|
+
resolveClient,
|
|
12
|
+
warnOnProdFallthrough,
|
|
13
|
+
writeClientConfig
|
|
14
|
+
} from "./chunk-TZLNCSXM.js";
|
|
15
|
+
|
|
16
|
+
// src/cli/setup.ts
|
|
17
|
+
import { execSync } from "child_process";
|
|
18
|
+
import { createInterface } from "readline";
|
|
19
|
+
function bold(s) {
|
|
20
|
+
return `\x1B[1m${s}\x1B[0m`;
|
|
21
|
+
}
|
|
22
|
+
function green(s) {
|
|
23
|
+
return `\x1B[32m${s}\x1B[0m`;
|
|
24
|
+
}
|
|
25
|
+
function dim(s) {
|
|
26
|
+
return `\x1B[2m${s}\x1B[0m`;
|
|
27
|
+
}
|
|
28
|
+
function orange(s) {
|
|
29
|
+
return `\x1B[33m${s}\x1B[0m`;
|
|
30
|
+
}
|
|
31
|
+
function log(msg) {
|
|
32
|
+
process.stdout.write(`${msg}
|
|
33
|
+
`);
|
|
34
|
+
}
|
|
35
|
+
function openBrowser(url) {
|
|
36
|
+
const platform = process.platform;
|
|
37
|
+
try {
|
|
38
|
+
if (platform === "darwin") execSync(`open "${url}"`);
|
|
39
|
+
else if (platform === "win32") execSync(`start "" "${url}"`);
|
|
40
|
+
else execSync(`xdg-open "${url}"`);
|
|
41
|
+
} catch {
|
|
42
|
+
log(dim(` Could not open browser automatically.`));
|
|
43
|
+
log(` Open this URL manually: ${url}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function prompt(question) {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
49
|
+
rl.question(question, (answer) => {
|
|
50
|
+
rl.close();
|
|
51
|
+
resolve(answer.trim());
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function promptChoice(question, choices) {
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
log("");
|
|
58
|
+
log(bold(question));
|
|
59
|
+
choices.forEach((c, i) => log(` ${i + 1}) ${c}`));
|
|
60
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
61
|
+
rl.question(`
|
|
62
|
+
${dim("Choice [1]:")} `, (line) => {
|
|
63
|
+
rl.close();
|
|
64
|
+
const n = parseInt(line.trim(), 10);
|
|
65
|
+
if (isNaN(n) || n < 1 || n > choices.length) {
|
|
66
|
+
resolve(0);
|
|
67
|
+
} else {
|
|
68
|
+
resolve(n - 1);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function resolveSetupSiteUrl() {
|
|
74
|
+
const explicit = process.env.CONVEX_SITE_URL ?? process.env.PRODUCTBRAIN_URL;
|
|
75
|
+
const url = explicit ?? DEFAULT_CLOUD_URL;
|
|
76
|
+
warnOnProdFallthrough(url, { explicit: explicit != null });
|
|
77
|
+
return url;
|
|
78
|
+
}
|
|
79
|
+
async function verifyWorkspace(apiKey) {
|
|
80
|
+
const siteUrl = resolveSetupSiteUrl();
|
|
81
|
+
try {
|
|
82
|
+
const res = await fetch(`${siteUrl.replace(/\/$/, "")}/api/aki`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
Authorization: `Bearer ${apiKey}`
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify({ fn: "resolveWorkspace", args: {} })
|
|
89
|
+
});
|
|
90
|
+
if (!res.ok) return null;
|
|
91
|
+
const json = await res.json();
|
|
92
|
+
return json.data ?? null;
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function runSetup() {
|
|
98
|
+
log("");
|
|
99
|
+
log(bold(` Product${orange("Brain")} Setup`));
|
|
100
|
+
log(dim(" Connect your AI assistant to your chain\n"));
|
|
101
|
+
const keysUrl = apiKeysUrl();
|
|
102
|
+
log(` ${dim("1. Get your API key from Settings \u2192 API Keys")}`);
|
|
103
|
+
log(` ${dim(keysUrl)}
|
|
104
|
+
`);
|
|
105
|
+
const openNow = await prompt(` Open this URL in your browser? [Y/n]: `);
|
|
106
|
+
if (openNow.toLowerCase() !== "n" && openNow.toLowerCase() !== "no") {
|
|
107
|
+
openBrowser(keysUrl);
|
|
108
|
+
}
|
|
109
|
+
log("");
|
|
110
|
+
log(` ${dim("2. Generate a key (if you don't have one), then copy it.\n")}`);
|
|
111
|
+
const apiKey = await prompt(` Paste your API key (${API_KEY_PREFIX} or ${AGENT_KEY_PREFIX}): `);
|
|
112
|
+
if (!apiKey || !isApiKeyShaped(apiKey)) {
|
|
113
|
+
log(` ${orange("!")} ${API_KEY_FORMAT_LINE}`);
|
|
114
|
+
log(` Get one at ${keysUrl}
|
|
115
|
+
`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
log(` ${green("\u2713")} Key received`);
|
|
119
|
+
const workspace = await verifyWorkspace(apiKey);
|
|
120
|
+
if (workspace) {
|
|
121
|
+
log(` ${green("\u2713")} Connected to workspace: ${bold(workspace.name)} ${dim(`(${workspace.slug})`)}`);
|
|
122
|
+
} else {
|
|
123
|
+
log(` ${orange("!")} Could not verify workspace. Check your key at ${keysUrl}`);
|
|
124
|
+
}
|
|
125
|
+
log("");
|
|
126
|
+
const CLIENT_NAMES = ["Cursor", "Claude Desktop"];
|
|
127
|
+
const options = [...CLIENT_NAMES, "Other"];
|
|
128
|
+
const choice = await promptChoice("Where do you want to set up Product Brain?", options);
|
|
129
|
+
if (choice === 2) {
|
|
130
|
+
printConfigSnippet(apiKey);
|
|
131
|
+
} else {
|
|
132
|
+
const client = resolveClient(CLIENT_NAMES[choice]);
|
|
133
|
+
if (client) {
|
|
134
|
+
await writeConfig(client, apiKey);
|
|
135
|
+
} else {
|
|
136
|
+
log(` ${orange("!")} ${CLIENT_NAMES[choice]} config path not available on this platform.`);
|
|
137
|
+
printConfigSnippet(apiKey);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (choice === 0) printDeeplink(apiKey);
|
|
141
|
+
log("");
|
|
142
|
+
log(
|
|
143
|
+
` ${green("\u2713")} Done! Restart your AI assistant and try: ${bold(`"${PASTE_NOW}"`)}`
|
|
144
|
+
);
|
|
145
|
+
printHelpLink();
|
|
146
|
+
}
|
|
147
|
+
async function writeConfig(client, apiKey) {
|
|
148
|
+
try {
|
|
149
|
+
const wrote = await writeClientConfig(client, apiKey);
|
|
150
|
+
if (wrote) {
|
|
151
|
+
log(` ${green("\u2713")} Wrote config to ${dim(client.configPath)}`);
|
|
152
|
+
} else {
|
|
153
|
+
log(` ${dim("\u2139")} ${client.name} already configured \u2014 skipped`);
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
log(` ${orange("!")} Could not write ${client.name} config: ${err.message}`);
|
|
157
|
+
printConfigSnippet(apiKey);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function printHelpLink() {
|
|
161
|
+
log(` ${dim(`Need help? See ${apiKeysUrl()}`)}`);
|
|
162
|
+
log("");
|
|
163
|
+
}
|
|
164
|
+
function printConfigSnippet(apiKey) {
|
|
165
|
+
log("");
|
|
166
|
+
log(bold(" Add this to your MCP client config:\n"));
|
|
167
|
+
const snippet = JSON.stringify(
|
|
168
|
+
{
|
|
169
|
+
mcpServers: {
|
|
170
|
+
"Product Brain": {
|
|
171
|
+
command: "npx",
|
|
172
|
+
args: ["-y", MCP_NPX_PACKAGE],
|
|
173
|
+
env: { PRODUCTBRAIN_API_KEY: apiKey }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
null,
|
|
178
|
+
2
|
|
179
|
+
);
|
|
180
|
+
for (const line of snippet.split("\n")) {
|
|
181
|
+
log(` ${line}`);
|
|
182
|
+
}
|
|
183
|
+
log("");
|
|
184
|
+
}
|
|
185
|
+
function buildDeeplink(apiKey) {
|
|
186
|
+
const config = JSON.stringify({
|
|
187
|
+
command: "npx",
|
|
188
|
+
args: ["-y", MCP_NPX_PACKAGE],
|
|
189
|
+
env: { PRODUCTBRAIN_API_KEY: apiKey }
|
|
190
|
+
});
|
|
191
|
+
const encoded = Buffer.from(config).toString("base64url");
|
|
192
|
+
return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent("Product Brain")}&config=${encoded}`;
|
|
193
|
+
}
|
|
194
|
+
function printDeeplink(apiKey) {
|
|
195
|
+
const link = buildDeeplink(apiKey);
|
|
196
|
+
log("");
|
|
197
|
+
log(` ${dim("One-click install for Cursor (paste in browser):")}`);
|
|
198
|
+
log(` ${link}`);
|
|
199
|
+
}
|
|
200
|
+
export {
|
|
201
|
+
resolveSetupSiteUrl,
|
|
202
|
+
runSetup
|
|
203
|
+
};
|
|
204
|
+
//# sourceMappingURL=setup-LDN5VOSR.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\nimport { AGENT_KEY_PREFIX, API_KEY_FORMAT_LINE, API_KEY_PREFIX, isApiKeyShaped } from \"../lib/tokenConstants.js\";\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 (${API_KEY_PREFIX} or ${AGENT_KEY_PREFIX}): `);\n\n if (!apiKey || !isApiKeyShaped(apiKey)) {\n log(` ${orange(\"!\")} ${API_KEY_FORMAT_LINE}`);\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":[]}
|