@letrquan/book 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +2504 -0
  2. package/LICENSE +123 -0
  3. package/README.md +1383 -0
  4. package/dist/app-MOXXWEOY.js +18945 -0
  5. package/dist/app-MOXXWEOY.js.map +1 -0
  6. package/dist/chunk-24AXE6SP.js +12 -0
  7. package/dist/chunk-24AXE6SP.js.map +1 -0
  8. package/dist/chunk-3U5IBM24.js +213 -0
  9. package/dist/chunk-3U5IBM24.js.map +1 -0
  10. package/dist/chunk-574C6MQR.js +743 -0
  11. package/dist/chunk-574C6MQR.js.map +1 -0
  12. package/dist/chunk-5GDZ22YP.js +449 -0
  13. package/dist/chunk-5GDZ22YP.js.map +1 -0
  14. package/dist/chunk-5KLVH3PY.js +167 -0
  15. package/dist/chunk-5KLVH3PY.js.map +1 -0
  16. package/dist/chunk-5RDRAO4B.js +92 -0
  17. package/dist/chunk-5RDRAO4B.js.map +1 -0
  18. package/dist/chunk-7N4J557C.js +10 -0
  19. package/dist/chunk-7N4J557C.js.map +1 -0
  20. package/dist/chunk-A7JLBK2Y.js +3506 -0
  21. package/dist/chunk-A7JLBK2Y.js.map +1 -0
  22. package/dist/chunk-ANSJPYGI.js +84 -0
  23. package/dist/chunk-ANSJPYGI.js.map +1 -0
  24. package/dist/chunk-BTZZUCLI.js +264 -0
  25. package/dist/chunk-BTZZUCLI.js.map +1 -0
  26. package/dist/chunk-C7D4NJ2D.js +217 -0
  27. package/dist/chunk-C7D4NJ2D.js.map +1 -0
  28. package/dist/chunk-CAGOB2N4.js +208 -0
  29. package/dist/chunk-CAGOB2N4.js.map +1 -0
  30. package/dist/chunk-DGMQEI6Y.js +98 -0
  31. package/dist/chunk-DGMQEI6Y.js.map +1 -0
  32. package/dist/chunk-E75IPST5.js +289 -0
  33. package/dist/chunk-E75IPST5.js.map +1 -0
  34. package/dist/chunk-EYGD6X2T.js +180 -0
  35. package/dist/chunk-EYGD6X2T.js.map +1 -0
  36. package/dist/chunk-GTVB4HEM.js +765 -0
  37. package/dist/chunk-GTVB4HEM.js.map +1 -0
  38. package/dist/chunk-J7MIZ5CA.js +1907 -0
  39. package/dist/chunk-J7MIZ5CA.js.map +1 -0
  40. package/dist/chunk-KWHQ2XXL.js +626 -0
  41. package/dist/chunk-KWHQ2XXL.js.map +1 -0
  42. package/dist/chunk-L6LK2QIQ.js +657 -0
  43. package/dist/chunk-L6LK2QIQ.js.map +1 -0
  44. package/dist/chunk-MAQXCUR4.js +1007 -0
  45. package/dist/chunk-MAQXCUR4.js.map +1 -0
  46. package/dist/chunk-S4XL7HOM.js +402 -0
  47. package/dist/chunk-S4XL7HOM.js.map +1 -0
  48. package/dist/chunk-URLQAXTR.js +60 -0
  49. package/dist/chunk-URLQAXTR.js.map +1 -0
  50. package/dist/chunk-V4BOYR52.js +3504 -0
  51. package/dist/chunk-V4BOYR52.js.map +1 -0
  52. package/dist/chunk-VZQXX3WW.js +210 -0
  53. package/dist/chunk-VZQXX3WW.js.map +1 -0
  54. package/dist/chunk-WKBO5R4O.js +34 -0
  55. package/dist/chunk-WKBO5R4O.js.map +1 -0
  56. package/dist/chunk-XIFFTATU.js +15753 -0
  57. package/dist/chunk-XIFFTATU.js.map +1 -0
  58. package/dist/command-approvals-6Y57VCWZ.js +28 -0
  59. package/dist/command-approvals-6Y57VCWZ.js.map +1 -0
  60. package/dist/context-ZC5IFEAX.js +25 -0
  61. package/dist/context-ZC5IFEAX.js.map +1 -0
  62. package/dist/hook-approvals-CTUFKEJT.js +22 -0
  63. package/dist/hook-approvals-CTUFKEJT.js.map +1 -0
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.js +2582 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/interactive-assets-CCAAWVKH.js +31 -0
  68. package/dist/interactive-assets-CCAAWVKH.js.map +1 -0
  69. package/dist/job-runner.d.ts +2 -0
  70. package/dist/job-runner.js +235 -0
  71. package/dist/job-runner.js.map +1 -0
  72. package/dist/loader-4IDS2RIE.js +15 -0
  73. package/dist/loader-4IDS2RIE.js.map +1 -0
  74. package/dist/mcp-approvals-WNPH65BK.js +18 -0
  75. package/dist/mcp-approvals-WNPH65BK.js.map +1 -0
  76. package/dist/mcp-config-YIJIES2C.js +16 -0
  77. package/dist/mcp-config-YIJIES2C.js.map +1 -0
  78. package/dist/model-window-store-YFVNXCIP.js +27 -0
  79. package/dist/model-window-store-YFVNXCIP.js.map +1 -0
  80. package/dist/openai-compatible-RHZUBHV6.js +10 -0
  81. package/dist/openai-compatible-RHZUBHV6.js.map +1 -0
  82. package/dist/permission-approvals-76PVREFD.js +14 -0
  83. package/dist/permission-approvals-76PVREFD.js.map +1 -0
  84. package/dist/permissions-CHCKTFQQ.js +28 -0
  85. package/dist/permissions-CHCKTFQQ.js.map +1 -0
  86. package/dist/sandbox-CNVAQDA7.js +23 -0
  87. package/dist/sandbox-CNVAQDA7.js.map +1 -0
  88. package/dist/scrollback-LUKLIBVI.js +136 -0
  89. package/dist/scrollback-LUKLIBVI.js.map +1 -0
  90. package/dist/sdk.d.ts +4574 -0
  91. package/dist/sdk.js +665 -0
  92. package/dist/sdk.js.map +1 -0
  93. package/dist/settings-loader-M2QP5TX2.js +21 -0
  94. package/dist/settings-loader-M2QP5TX2.js.map +1 -0
  95. package/dist/settings-removed-JWWPT4WA.js +19 -0
  96. package/dist/settings-removed-JWWPT4WA.js.map +1 -0
  97. package/dist/shell-selection-V6QAYXPX.js +17 -0
  98. package/dist/shell-selection-V6QAYXPX.js.map +1 -0
  99. package/package.json +143 -0
  100. package/patches/ink+6.8.0.patch +13 -0
  101. package/scripts/apply-ink-patch.mjs +29 -0
  102. package/scripts/ink-patch.mjs +59 -0
  103. package/scripts/verify-ink-patch.mjs +11 -0
@@ -0,0 +1,12 @@
1
+ // src/book-home.ts
2
+ import { homedir } from "os";
3
+ import { join, resolve } from "path";
4
+ function resolveBookHome(env = process.env, systemHome = homedir()) {
5
+ const configured = env.BOOK_HOME?.trim();
6
+ return configured ? resolve(configured) : join(systemHome, ".book");
7
+ }
8
+
9
+ export {
10
+ resolveBookHome
11
+ };
12
+ //# sourceMappingURL=chunk-24AXE6SP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/book-home.ts"],"sourcesContent":["import { homedir } from 'node:os';\r\nimport { join, resolve } from 'node:path';\r\n\r\n/** Resolve Book's user-global state root, allowing evaluation runs to isolate it explicitly. */\r\nexport function resolveBookHome(\r\n env: NodeJS.ProcessEnv = process.env,\r\n systemHome = homedir(),\r\n): string {\r\n const configured = env.BOOK_HOME?.trim();\r\n return configured ? resolve(configured) : join(systemHome, '.book');\r\n}\r\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,MAAM,eAAe;AAGvB,SAAS,gBACd,MAAyB,QAAQ,KACjC,aAAa,QAAQ,GACb;AACR,QAAM,aAAa,IAAI,WAAW,KAAK;AACvC,SAAO,aAAa,QAAQ,UAAU,IAAI,KAAK,YAAY,OAAO;AACpE;","names":[]}
@@ -0,0 +1,213 @@
1
+ import {
2
+ resolveBookHome
3
+ } from "./chunk-24AXE6SP.js";
4
+
5
+ // src/mcp-config.ts
6
+ import { readFileSync, existsSync } from "fs";
7
+ import { join } from "path";
8
+ var SAFE_SERVER_NAME = /^[A-Za-z0-9_-]+$/;
9
+ function isValidMcpServerName(name) {
10
+ return SAFE_SERVER_NAME.test(name) && !name.includes("__");
11
+ }
12
+ var consoleMcpDiagnosticSink = (diagnostic) => {
13
+ console.warn(`\u26A0 ${diagnostic.message}`);
14
+ };
15
+ function expandValue(raw, env) {
16
+ const missing = /* @__PURE__ */ new Set();
17
+ const value = raw.replace(
18
+ /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g,
19
+ (_match, name, fallback) => {
20
+ const configured = env[name];
21
+ if (configured !== void 0) return configured;
22
+ if (fallback !== void 0) return fallback;
23
+ missing.add(name);
24
+ return "";
25
+ }
26
+ );
27
+ return { value, missing: [...missing] };
28
+ }
29
+ function expandRecord(raw, env) {
30
+ const value = {};
31
+ const missing = /* @__PURE__ */ new Set();
32
+ const invalid = [];
33
+ if (raw === void 0) return { value, missing: [], invalid };
34
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
35
+ return { value, missing: [], invalid: ["expected an object"] };
36
+ }
37
+ for (const [key, item] of Object.entries(raw)) {
38
+ if (typeof item !== "string") {
39
+ invalid.push(`${key} must be a string`);
40
+ continue;
41
+ }
42
+ const expanded = expandValue(item, env);
43
+ value[key] = expanded.value;
44
+ for (const name of expanded.missing) missing.add(name);
45
+ }
46
+ return { value, missing: [...missing], invalid };
47
+ }
48
+ function normalizeServerConfig(name, raw, env) {
49
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
50
+ return { error: "expected an object" };
51
+ }
52
+ const source = raw;
53
+ const requestedType = source.type ?? source.transport;
54
+ const type = requestedType === void 0 ? source.url !== void 0 ? "http" : "stdio" : String(requestedType).toLowerCase();
55
+ if (!["stdio", "http", "sse"].includes(type)) {
56
+ return { error: `unsupported transport type "${String(requestedType)}"` };
57
+ }
58
+ const commandExpanded = typeof source.command === "string" ? expandValue(source.command, env) : void 0;
59
+ const urlExpanded = typeof source.url === "string" ? expandValue(source.url, env) : void 0;
60
+ const missing = /* @__PURE__ */ new Set([...commandExpanded?.missing ?? [], ...urlExpanded?.missing ?? []]);
61
+ const args = [];
62
+ if (source.args !== void 0) {
63
+ if (!Array.isArray(source.args)) return { error: "args must be an array" };
64
+ for (const [index, item] of source.args.entries()) {
65
+ if (typeof item !== "string") return { error: `args[${index}] must be a string` };
66
+ const expanded = expandValue(item, env);
67
+ args.push(expanded.value);
68
+ for (const variable of expanded.missing) missing.add(variable);
69
+ }
70
+ }
71
+ const envValues = expandRecord(source.env, env);
72
+ const headerValues = expandRecord(source.headers, env);
73
+ for (const variable of [...envValues.missing, ...headerValues.missing]) missing.add(variable);
74
+ if (envValues.invalid.length > 0) return { error: `env ${envValues.invalid.join(", ")}` };
75
+ if (headerValues.invalid.length > 0)
76
+ return { error: `headers ${headerValues.invalid.join(", ")}` };
77
+ if (missing.size > 0)
78
+ return { error: `missing environment variable(s): ${[...missing].join(", ")}` };
79
+ if (type === "stdio") {
80
+ if (!commandExpanded?.value.trim())
81
+ return { error: "stdio server requires a non-empty command" };
82
+ const cwd = typeof source.cwd === "string" ? expandValue(source.cwd, env) : void 0;
83
+ if (cwd?.missing.length)
84
+ return { error: `missing environment variable(s): ${cwd.missing.join(", ")}` };
85
+ return {
86
+ config: {
87
+ type: "stdio",
88
+ command: commandExpanded.value,
89
+ args,
90
+ env: envValues.value,
91
+ ...cwd?.value ? { cwd: cwd.value } : {}
92
+ }
93
+ };
94
+ }
95
+ if (!urlExpanded?.value.trim()) return { error: `${type} server requires a non-empty url` };
96
+ let parsedUrl;
97
+ try {
98
+ parsedUrl = new URL(urlExpanded.value);
99
+ } catch {
100
+ return { error: "url must be a valid absolute URL" };
101
+ }
102
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
103
+ return { error: "url must use http:// or https://" };
104
+ }
105
+ if (parsedUrl.username || parsedUrl.password)
106
+ return { error: "url must not contain credentials" };
107
+ return {
108
+ config: {
109
+ type,
110
+ url: parsedUrl.toString(),
111
+ headers: headerValues.value
112
+ }
113
+ };
114
+ }
115
+ function resolveMcpServerList(workspace, options = {}) {
116
+ const onDiagnostic = options.onDiagnostic ?? consoleMcpDiagnosticSink;
117
+ const env = options.env ?? process.env;
118
+ const bookHome = options.home ? join(options.home, ".book") : resolveBookHome();
119
+ const sources = [
120
+ { path: join(bookHome, "mcp.json"), source: "user" },
121
+ { path: join(workspace, ".mcp.json"), source: "project" }
122
+ ];
123
+ const servers = /* @__PURE__ */ new Map();
124
+ for (const { path, source } of sources) {
125
+ if (!existsSync(path)) continue;
126
+ let parsed;
127
+ try {
128
+ parsed = JSON.parse(readFileSync(path, "utf-8"));
129
+ } catch {
130
+ onDiagnostic({ level: "warn", message: `Failed to load MCP config: ${path}` });
131
+ continue;
132
+ }
133
+ if (!parsed || typeof parsed !== "object" || !parsed.mcpServers) continue;
134
+ if (typeof parsed.mcpServers !== "object" || Array.isArray(parsed.mcpServers)) {
135
+ onDiagnostic({ level: "warn", message: `Invalid mcpServers object: ${path}` });
136
+ continue;
137
+ }
138
+ for (const [name, rawConfig] of Object.entries(parsed.mcpServers)) {
139
+ if (!isValidMcpServerName(name)) {
140
+ onDiagnostic({
141
+ level: "warn",
142
+ message: `Ignoring invalid MCP server name: ${name} (${path}); use letters, numbers, hyphens, or underscores, without a double underscore`,
143
+ server: name
144
+ });
145
+ continue;
146
+ }
147
+ const normalized = normalizeServerConfig(name, rawConfig, env);
148
+ if (!normalized.config) {
149
+ onDiagnostic({
150
+ level: "warn",
151
+ message: `Ignoring invalid MCP server config: ${name} (${path}): ${normalized.error ?? "invalid configuration"}`,
152
+ server: name
153
+ });
154
+ continue;
155
+ }
156
+ servers.set(name, {
157
+ name,
158
+ source,
159
+ path,
160
+ config: normalized.config
161
+ });
162
+ }
163
+ }
164
+ return [...servers.values()];
165
+ }
166
+ function loadMcpConfig(workspace, home) {
167
+ const servers = {};
168
+ for (const server of resolveMcpServerList(workspace, { home })) {
169
+ servers[server.name] = server.config;
170
+ }
171
+ return servers;
172
+ }
173
+ function formatMcpServerCommand(config) {
174
+ const sensitiveValues = [
175
+ ...Object.values(config.env ?? {}),
176
+ ...Object.values(config.headers ?? {})
177
+ ].filter((value) => value.length >= 4).sort((left, right) => right.length - left.length);
178
+ const redact = (value) => {
179
+ let rendered = value;
180
+ for (const sensitive of sensitiveValues)
181
+ rendered = rendered.replaceAll(sensitive, "<redacted>");
182
+ return rendered;
183
+ };
184
+ if (config.type === "http" || config.type === "sse" || !config.command && config.url) {
185
+ const headers = Object.keys(config.headers ?? {}).sort();
186
+ let endpoint = config.url ?? "";
187
+ let queryConfigured = false;
188
+ try {
189
+ const url = new URL(endpoint);
190
+ queryConfigured = Boolean(url.search);
191
+ url.search = "";
192
+ url.hash = "";
193
+ endpoint = url.toString();
194
+ } catch {
195
+ endpoint = redact(endpoint);
196
+ }
197
+ const metadata = [
198
+ ...queryConfigured ? ["query configured"] : [],
199
+ ...headers.length ? [`headers: ${headers.join(", ")}`] : []
200
+ ];
201
+ return `${config.type === "sse" ? "sse" : "http"} ${endpoint}${metadata.length ? ` (${metadata.join("; ")})` : ""}`;
202
+ }
203
+ return [config.command ?? "", ...config.args ?? []].filter(Boolean).map(redact).join(" ");
204
+ }
205
+
206
+ export {
207
+ isValidMcpServerName,
208
+ consoleMcpDiagnosticSink,
209
+ resolveMcpServerList,
210
+ loadMcpConfig,
211
+ formatMcpServerCommand
212
+ };
213
+ //# sourceMappingURL=chunk-3U5IBM24.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp-config.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'fs';\r\nimport { join } from 'path';\r\nimport { resolveBookHome } from './book-home.js';\r\n\r\nexport type McpTransportType = 'stdio' | 'http' | 'sse';\r\n\r\nexport interface McpServerConfig {\r\n /** Explicit transport. Omitted means stdio when `command` is present. */\r\n type?: McpTransportType;\r\n /** Executable for stdio servers. */\r\n command?: string;\r\n args?: string[];\r\n env?: Record<string, string>;\r\n /** Optional working directory for stdio servers. */\r\n cwd?: string;\r\n /** Endpoint for HTTP/SSE servers. */\r\n url?: string;\r\n /** Additional request headers for HTTP/SSE servers. Values are never shown in diagnostics. */\r\n headers?: Record<string, string>;\r\n}\r\n\r\nconst SAFE_SERVER_NAME = /^[A-Za-z0-9_-]+$/;\r\n\r\nexport function isValidMcpServerName(name: string): boolean {\r\n return SAFE_SERVER_NAME.test(name) && !name.includes('__');\r\n}\r\n\r\ninterface McpConfigDocument {\r\n mcpServers?: Record<string, unknown>;\r\n}\r\n\r\n/** Where a server declaration came from; project declarations require approval. */\r\nexport type McpServerSource = 'user' | 'project';\r\n\r\nexport interface ResolvedMcpServer {\r\n name: string;\r\n config: McpServerConfig;\r\n source: McpServerSource;\r\n /** Config file that defined (or, on a name collision, last overrode) this server. */\r\n path: string;\r\n}\r\n\r\nexport interface McpDiagnostic {\r\n level: 'warn';\r\n message: string;\r\n server?: string;\r\n}\r\n\r\nexport type McpDiagnosticSink = (diagnostic: McpDiagnostic) => void;\r\n\r\n/** Preserves the historical stderr warning behavior for headless/SDK hosts. */\r\nexport const consoleMcpDiagnosticSink: McpDiagnosticSink = (diagnostic) => {\r\n console.warn(`⚠ ${diagnostic.message}`);\r\n};\r\n\r\nexport interface ResolveMcpServersOptions {\r\n home?: string;\r\n onDiagnostic?: McpDiagnosticSink;\r\n /** Environment used for `${VAR}` interpolation; defaults to process.env. */\r\n env?: NodeJS.ProcessEnv;\r\n}\r\n\r\ninterface ExpandedValue {\r\n value: string;\r\n missing: string[];\r\n}\r\n\r\n/**\r\n * Expand the small interpolation language used by the de-facto mcpServers\r\n * config format. We deliberately support only `${NAME}` and `${NAME:-default}`\r\n * here; shell syntax is never evaluated.\r\n */\r\nfunction expandValue(raw: string, env: NodeJS.ProcessEnv): ExpandedValue {\r\n const missing = new Set<string>();\r\n const value = raw.replace(\r\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\\}/g,\r\n (_match, name: string, fallback?: string) => {\r\n const configured = env[name];\r\n if (configured !== undefined) return configured;\r\n if (fallback !== undefined) return fallback;\r\n missing.add(name);\r\n return '';\r\n },\r\n );\r\n return { value, missing: [...missing] };\r\n}\r\n\r\nfunction expandRecord(\r\n raw: unknown,\r\n env: NodeJS.ProcessEnv,\r\n): { value: Record<string, string>; missing: string[]; invalid: string[] } {\r\n const value: Record<string, string> = {};\r\n const missing = new Set<string>();\r\n const invalid: string[] = [];\r\n if (raw === undefined) return { value, missing: [], invalid };\r\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\r\n return { value, missing: [], invalid: ['expected an object'] };\r\n }\r\n for (const [key, item] of Object.entries(raw as Record<string, unknown>)) {\r\n if (typeof item !== 'string') {\r\n invalid.push(`${key} must be a string`);\r\n continue;\r\n }\r\n const expanded = expandValue(item, env);\r\n value[key] = expanded.value;\r\n for (const name of expanded.missing) missing.add(name);\r\n }\r\n return { value, missing: [...missing], invalid };\r\n}\r\n\r\nfunction normalizeServerConfig(\r\n name: string,\r\n raw: unknown,\r\n env: NodeJS.ProcessEnv,\r\n): { config?: McpServerConfig; error?: string } {\r\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\r\n return { error: 'expected an object' };\r\n }\r\n const source = raw as Record<string, unknown>;\r\n const requestedType = source.type ?? source.transport;\r\n const type =\r\n requestedType === undefined\r\n ? source.url !== undefined\r\n ? 'http'\r\n : 'stdio'\r\n : String(requestedType).toLowerCase();\r\n if (!['stdio', 'http', 'sse'].includes(type)) {\r\n return { error: `unsupported transport type \"${String(requestedType)}\"` };\r\n }\r\n\r\n const commandExpanded =\r\n typeof source.command === 'string' ? expandValue(source.command, env) : undefined;\r\n const urlExpanded = typeof source.url === 'string' ? expandValue(source.url, env) : undefined;\r\n const missing = new Set([...(commandExpanded?.missing ?? []), ...(urlExpanded?.missing ?? [])]);\r\n const args: string[] = [];\r\n if (source.args !== undefined) {\r\n if (!Array.isArray(source.args)) return { error: 'args must be an array' };\r\n for (const [index, item] of source.args.entries()) {\r\n if (typeof item !== 'string') return { error: `args[${index}] must be a string` };\r\n const expanded = expandValue(item, env);\r\n args.push(expanded.value);\r\n for (const variable of expanded.missing) missing.add(variable);\r\n }\r\n }\r\n const envValues = expandRecord(source.env, env);\r\n const headerValues = expandRecord(source.headers, env);\r\n for (const variable of [...envValues.missing, ...headerValues.missing]) missing.add(variable);\r\n if (envValues.invalid.length > 0) return { error: `env ${envValues.invalid.join(', ')}` };\r\n if (headerValues.invalid.length > 0)\r\n return { error: `headers ${headerValues.invalid.join(', ')}` };\r\n if (missing.size > 0)\r\n return { error: `missing environment variable(s): ${[...missing].join(', ')}` };\r\n\r\n if (type === 'stdio') {\r\n if (!commandExpanded?.value.trim())\r\n return { error: 'stdio server requires a non-empty command' };\r\n const cwd = typeof source.cwd === 'string' ? expandValue(source.cwd, env) : undefined;\r\n if (cwd?.missing.length)\r\n return { error: `missing environment variable(s): ${cwd.missing.join(', ')}` };\r\n return {\r\n config: {\r\n type: 'stdio',\r\n command: commandExpanded.value,\r\n args,\r\n env: envValues.value,\r\n ...(cwd?.value ? { cwd: cwd.value } : {}),\r\n },\r\n };\r\n }\r\n\r\n if (!urlExpanded?.value.trim()) return { error: `${type} server requires a non-empty url` };\r\n let parsedUrl: URL;\r\n try {\r\n parsedUrl = new URL(urlExpanded.value);\r\n } catch {\r\n return { error: 'url must be a valid absolute URL' };\r\n }\r\n if (!['http:', 'https:'].includes(parsedUrl.protocol)) {\r\n return { error: 'url must use http:// or https://' };\r\n }\r\n if (parsedUrl.username || parsedUrl.password)\r\n return { error: 'url must not contain credentials' };\r\n return {\r\n config: {\r\n type: type as 'http' | 'sse',\r\n url: parsedUrl.toString(),\r\n headers: headerValues.value,\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Resolve the declared MCP servers with provenance.\r\n *\r\n * Sources, later wins on a name collision: <BOOK_HOME>/.book/mcp.json (user),\r\n * then <workspace>/.mcp.json (project). A project entry that overrides a\r\n * user-global name keeps `source: 'project'` so it cannot inherit the trust of\r\n * the name it shadows.\r\n */\r\nexport function resolveMcpServerList(\r\n workspace: string,\r\n options: ResolveMcpServersOptions = {},\r\n): ResolvedMcpServer[] {\r\n const onDiagnostic = options.onDiagnostic ?? consoleMcpDiagnosticSink;\r\n const env = options.env ?? process.env;\r\n const bookHome = options.home ? join(options.home, '.book') : resolveBookHome();\r\n const sources: Array<{ path: string; source: McpServerSource }> = [\r\n { path: join(bookHome, 'mcp.json'), source: 'user' },\r\n { path: join(workspace, '.mcp.json'), source: 'project' },\r\n ];\r\n const servers = new Map<string, ResolvedMcpServer>();\r\n\r\n for (const { path, source } of sources) {\r\n if (!existsSync(path)) continue;\r\n let parsed: McpConfigDocument;\r\n try {\r\n parsed = JSON.parse(readFileSync(path, 'utf-8')) as McpConfigDocument;\r\n } catch {\r\n onDiagnostic({ level: 'warn', message: `Failed to load MCP config: ${path}` });\r\n continue;\r\n }\r\n if (!parsed || typeof parsed !== 'object' || !parsed.mcpServers) continue;\r\n if (typeof parsed.mcpServers !== 'object' || Array.isArray(parsed.mcpServers)) {\r\n onDiagnostic({ level: 'warn', message: `Invalid mcpServers object: ${path}` });\r\n continue;\r\n }\r\n for (const [name, rawConfig] of Object.entries(parsed.mcpServers)) {\r\n if (!isValidMcpServerName(name)) {\r\n onDiagnostic({\r\n level: 'warn',\r\n message: `Ignoring invalid MCP server name: ${name} (${path}); use letters, numbers, hyphens, or underscores, without a double underscore`,\r\n server: name,\r\n });\r\n continue;\r\n }\r\n const normalized = normalizeServerConfig(name, rawConfig, env);\r\n if (!normalized.config) {\r\n onDiagnostic({\r\n level: 'warn',\r\n message: `Ignoring invalid MCP server config: ${name} (${path}): ${normalized.error ?? 'invalid configuration'}`,\r\n server: name,\r\n });\r\n continue;\r\n }\r\n servers.set(name, {\r\n name,\r\n source,\r\n path,\r\n config: normalized.config,\r\n });\r\n }\r\n }\r\n\r\n return [...servers.values()];\r\n}\r\n\r\n/** Merged name → config view used by doctor and ambient-run fingerprinting. */\r\nexport function loadMcpConfig(workspace: string, home?: string): Record<string, McpServerConfig> {\r\n const servers: Record<string, McpServerConfig> = {};\r\n for (const server of resolveMcpServerList(workspace, { home })) {\r\n servers[server.name] = server.config;\r\n }\r\n return servers;\r\n}\r\n\r\n/** Render one server as the exact command line a host is being asked to trust. */\r\nexport function formatMcpServerCommand(config: McpServerConfig): string {\r\n const sensitiveValues = [\r\n ...Object.values(config.env ?? {}),\r\n ...Object.values(config.headers ?? {}),\r\n ]\r\n .filter((value) => value.length >= 4)\r\n .sort((left, right) => right.length - left.length);\r\n const redact = (value: string): string => {\r\n let rendered = value;\r\n for (const sensitive of sensitiveValues)\r\n rendered = rendered.replaceAll(sensitive, '<redacted>');\r\n return rendered;\r\n };\r\n if (config.type === 'http' || config.type === 'sse' || (!config.command && config.url)) {\r\n const headers = Object.keys(config.headers ?? {}).sort();\r\n let endpoint = config.url ?? '';\r\n let queryConfigured = false;\r\n try {\r\n const url = new URL(endpoint);\r\n queryConfigured = Boolean(url.search);\r\n url.search = '';\r\n url.hash = '';\r\n endpoint = url.toString();\r\n } catch {\r\n endpoint = redact(endpoint);\r\n }\r\n const metadata = [\r\n ...(queryConfigured ? ['query configured'] : []),\r\n ...(headers.length ? [`headers: ${headers.join(', ')}`] : []),\r\n ];\r\n return `${config.type === 'sse' ? 'sse' : 'http'} ${endpoint}${metadata.length ? ` (${metadata.join('; ')})` : ''}`;\r\n }\r\n return [config.command ?? '', ...(config.args ?? [])].filter(Boolean).map(redact).join(' ');\r\n}\r\n"],"mappings":"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","names":[]}