@jagilber-org/index-server 1.22.1 → 1.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/CHANGELOG.md +87 -2
  2. package/CODE_OF_CONDUCT.md +2 -0
  3. package/CONTRIBUTING.md +32 -2
  4. package/README.md +82 -19
  5. package/SECURITY.md +17 -5
  6. package/dist/config/dashboardConfig.d.ts +3 -0
  7. package/dist/config/dashboardConfig.js +3 -0
  8. package/dist/config/defaultValues.d.ts +1 -1
  9. package/dist/config/defaultValues.js +1 -1
  10. package/dist/config/featureConfig.d.ts +2 -0
  11. package/dist/config/featureConfig.js +6 -1
  12. package/dist/config/runtimeConfig.d.ts +1 -1
  13. package/dist/config/runtimeConfig.js +8 -9
  14. package/dist/dashboard/client/admin.html +170 -53
  15. package/dist/dashboard/client/css/admin.css +132 -0
  16. package/dist/dashboard/client/js/admin.auth.js +25 -11
  17. package/dist/dashboard/client/js/admin.config.js +1 -1
  18. package/dist/dashboard/client/js/admin.feedback.js +328 -0
  19. package/dist/dashboard/client/js/admin.graph.js +120 -18
  20. package/dist/dashboard/client/js/admin.instructions.js +27 -13
  21. package/dist/dashboard/client/js/admin.logs.js +1 -5
  22. package/dist/dashboard/client/js/admin.maintenance.js +53 -8
  23. package/dist/dashboard/client/js/admin.messaging.js +1 -4
  24. package/dist/dashboard/client/js/admin.overview.js +5 -1
  25. package/dist/dashboard/client/js/admin.sessions.js +1 -1
  26. package/dist/dashboard/client/js/admin.utils.js +43 -1
  27. package/dist/dashboard/client/js/mermaid.min.js +813 -537
  28. package/dist/dashboard/export/DataExporter.js +2 -1
  29. package/dist/dashboard/server/AdminPanel.d.ts +3 -0
  30. package/dist/dashboard/server/AdminPanel.js +132 -35
  31. package/dist/dashboard/server/ApiRoutes.js +40 -9
  32. package/dist/dashboard/server/DashboardServer.js +1 -1
  33. package/dist/dashboard/server/FileMetricsStorage.d.ts +19 -0
  34. package/dist/dashboard/server/FileMetricsStorage.js +52 -5
  35. package/dist/dashboard/server/HttpTransport.js +6 -0
  36. package/dist/dashboard/server/InstanceManager.js +7 -2
  37. package/dist/dashboard/server/KnowledgeStore.js +7 -2
  38. package/dist/dashboard/server/MetricsCollector.d.ts +16 -0
  39. package/dist/dashboard/server/MetricsCollector.js +113 -17
  40. package/dist/dashboard/server/legacyDashboardHtml.js +7 -2
  41. package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
  42. package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +8 -3
  43. package/dist/dashboard/server/routes/admin.feedback.routes.d.ts +15 -0
  44. package/dist/dashboard/server/routes/admin.feedback.routes.js +188 -0
  45. package/dist/dashboard/server/routes/admin.routes.js +35 -27
  46. package/dist/dashboard/server/routes/alerts.routes.js +4 -3
  47. package/dist/dashboard/server/routes/api.feedback.routes.js +2 -1
  48. package/dist/dashboard/server/routes/api.usage.routes.js +8 -7
  49. package/dist/dashboard/server/routes/embeddings.routes.d.ts +2 -1
  50. package/dist/dashboard/server/routes/embeddings.routes.js +18 -9
  51. package/dist/dashboard/server/routes/graph.routes.js +10 -13
  52. package/dist/dashboard/server/routes/index.d.ts +1 -0
  53. package/dist/dashboard/server/routes/index.js +74 -39
  54. package/dist/dashboard/server/routes/instances.routes.js +2 -1
  55. package/dist/dashboard/server/routes/instructions.routes.js +46 -27
  56. package/dist/dashboard/server/routes/knowledge.routes.js +4 -3
  57. package/dist/dashboard/server/routes/logs.routes.js +5 -4
  58. package/dist/dashboard/server/routes/messaging.routes.js +15 -14
  59. package/dist/dashboard/server/routes/metrics.routes.js +14 -13
  60. package/dist/dashboard/server/routes/scripts.routes.js +6 -3
  61. package/dist/dashboard/server/routes/status.routes.js +5 -4
  62. package/dist/dashboard/server/routes/synthetic.routes.js +3 -2
  63. package/dist/dashboard/server/routes/usage.routes.js +2 -1
  64. package/dist/dashboard/server/utils/escapeHtml.d.ts +1 -0
  65. package/dist/dashboard/server/utils/escapeHtml.js +11 -0
  66. package/dist/dashboard/server/utils/pathContainment.d.ts +1 -0
  67. package/dist/dashboard/server/utils/pathContainment.js +15 -0
  68. package/dist/dashboard/server/wsInit.js +2 -2
  69. package/dist/lib/mcpStdioLogging.d.ts +165 -0
  70. package/dist/lib/mcpStdioLogging.js +287 -0
  71. package/dist/schemas/index.d.ts +37 -2
  72. package/dist/schemas/index.js +27 -3
  73. package/dist/server/backgroundServicesStartup.d.ts +7 -1
  74. package/dist/server/backgroundServicesStartup.js +25 -8
  75. package/dist/server/certInit.d.ts +97 -0
  76. package/dist/server/certInit.js +359 -0
  77. package/dist/server/certInit.types.d.ts +92 -0
  78. package/dist/server/certInit.types.js +34 -0
  79. package/dist/server/handshake/fallbackFrames.d.ts +31 -0
  80. package/dist/server/handshake/fallbackFrames.js +38 -0
  81. package/dist/server/handshake/initializeDetector.d.ts +31 -0
  82. package/dist/server/handshake/initializeDetector.js +88 -0
  83. package/dist/server/handshake/protocol.d.ts +15 -0
  84. package/dist/server/handshake/protocol.js +37 -0
  85. package/dist/server/handshake/readyEmitter.d.ts +6 -0
  86. package/dist/server/handshake/readyEmitter.js +88 -0
  87. package/dist/server/handshake/safetyFallbacks.d.ts +1 -0
  88. package/dist/server/handshake/safetyFallbacks.js +134 -0
  89. package/dist/server/handshake/stdinSniffer.d.ts +1 -0
  90. package/dist/server/handshake/stdinSniffer.js +260 -0
  91. package/dist/server/handshake/tracing.d.ts +16 -0
  92. package/dist/server/handshake/tracing.js +95 -0
  93. package/dist/server/handshakeManager.d.ts +23 -23
  94. package/dist/server/handshakeManager.js +36 -466
  95. package/dist/server/index-server.d.ts +23 -0
  96. package/dist/server/index-server.js +194 -9
  97. package/dist/server/mcpReadOnlySurfaces.d.ts +44 -0
  98. package/dist/server/mcpReadOnlySurfaces.js +297 -0
  99. package/dist/server/sdkServer.js +69 -7
  100. package/dist/server/transport.d.ts +5 -6
  101. package/dist/server/transport.js +46 -64
  102. package/dist/server/transportFactory.d.ts +3 -9
  103. package/dist/server/transportFactory.js +18 -380
  104. package/dist/services/atomicFs.d.ts +3 -0
  105. package/dist/services/atomicFs.js +171 -13
  106. package/dist/services/auditLog.d.ts +17 -2
  107. package/dist/services/auditLog.js +75 -14
  108. package/dist/services/bootstrapGating.js +1 -1
  109. package/dist/services/categoryRules.d.ts +10 -0
  110. package/dist/services/categoryRules.js +17 -0
  111. package/dist/services/classificationService.js +7 -5
  112. package/dist/services/embeddingService.d.ts +27 -11
  113. package/dist/services/embeddingService.js +51 -14
  114. package/dist/services/feedbackStorage.d.ts +39 -0
  115. package/dist/services/feedbackStorage.js +88 -0
  116. package/dist/services/handlers/instructions.add.js +429 -317
  117. package/dist/services/handlers/instructions.groom.js +128 -31
  118. package/dist/services/handlers/instructions.import.js +56 -23
  119. package/dist/services/handlers/instructions.patch.js +43 -32
  120. package/dist/services/handlers/instructions.query.js +20 -29
  121. package/dist/services/handlers/instructions.shared.d.ts +54 -0
  122. package/dist/services/handlers/instructions.shared.js +126 -1
  123. package/dist/services/handlers.activation.js +83 -81
  124. package/dist/services/handlers.dashboardConfig.d.ts +2 -2
  125. package/dist/services/handlers.dashboardConfig.js +1 -2
  126. package/dist/services/handlers.diagnostics.js +75 -54
  127. package/dist/services/handlers.feedback.d.ts +4 -11
  128. package/dist/services/handlers.feedback.js +11 -333
  129. package/dist/services/handlers.gates.js +69 -37
  130. package/dist/services/handlers.graph.js +2 -2
  131. package/dist/services/handlers.help.js +2 -2
  132. package/dist/services/handlers.instructionSchema.js +4 -2
  133. package/dist/services/handlers.integrity.js +42 -22
  134. package/dist/services/handlers.messaging.js +1 -1
  135. package/dist/services/handlers.metrics.js +51 -6
  136. package/dist/services/handlers.prompt.js +10 -2
  137. package/dist/services/handlers.search.js +94 -44
  138. package/dist/services/handlers.trace.js +1 -1
  139. package/dist/services/handlers.usage.js +38 -7
  140. package/dist/services/indexContext.d.ts +21 -1
  141. package/dist/services/indexContext.js +263 -78
  142. package/dist/services/indexLoader.d.ts +1 -0
  143. package/dist/services/indexLoader.js +28 -8
  144. package/dist/services/instructionRecordValidation.d.ts +39 -0
  145. package/dist/services/instructionRecordValidation.js +388 -0
  146. package/dist/services/instructions.dispatcher.js +4 -4
  147. package/dist/services/loaderSchemaValidator.d.ts +15 -0
  148. package/dist/services/loaderSchemaValidator.js +69 -0
  149. package/dist/services/logger.js +11 -2
  150. package/dist/services/mcpLogBridge.d.ts +49 -0
  151. package/dist/services/mcpLogBridge.js +83 -0
  152. package/dist/services/ownershipService.js +18 -8
  153. package/dist/services/performanceBaseline.js +23 -22
  154. package/dist/services/promptReviewService.d.ts +3 -1
  155. package/dist/services/promptReviewService.js +41 -13
  156. package/dist/services/regexSafety.d.ts +6 -0
  157. package/dist/services/regexSafety.js +46 -0
  158. package/dist/services/seedBootstrap.js +1 -1
  159. package/dist/services/storage/factory.d.ts +14 -1
  160. package/dist/services/storage/factory.js +61 -1
  161. package/dist/services/storage/jsonEmbeddingStore.d.ts +15 -0
  162. package/dist/services/storage/jsonEmbeddingStore.js +83 -0
  163. package/dist/services/storage/jsonFileStore.d.ts +3 -1
  164. package/dist/services/storage/jsonFileStore.js +8 -6
  165. package/dist/services/storage/migrationEngine.d.ts +13 -0
  166. package/dist/services/storage/migrationEngine.js +31 -0
  167. package/dist/services/storage/sqliteEmbeddingStore.d.ts +30 -0
  168. package/dist/services/storage/sqliteEmbeddingStore.js +222 -0
  169. package/dist/services/storage/sqliteStore.d.ts +3 -1
  170. package/dist/services/storage/sqliteStore.js +2 -2
  171. package/dist/services/storage/types.d.ts +48 -1
  172. package/dist/services/toolRegistry.js +77 -67
  173. package/dist/services/toolRegistry.zod.js +89 -86
  174. package/dist/services/tracing.js +5 -4
  175. package/dist/utils/envUtils.d.ts +4 -0
  176. package/dist/utils/envUtils.js +7 -0
  177. package/dist/utils/memoryMonitor.js +11 -10
  178. package/package.json +11 -4
  179. package/schemas/instruction.schema.json +38 -1
  180. package/scripts/copy-dashboard-assets.mjs +1 -1
  181. package/scripts/dist/README.md +1 -1
  182. package/scripts/setup-wizard.mjs +781 -0
  183. package/server.json +1 -0
  184. package/dist/externalClientLib.d.ts +0 -1
  185. package/dist/externalClientLib.js +0 -2
  186. package/dist/portableClientWrapper.d.ts +0 -1
  187. package/dist/portableClientWrapper.js +0 -2
  188. package/dist/services/indexingService.d.ts +0 -1
  189. package/dist/services/indexingService.js +0 -2
@@ -0,0 +1,781 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * setup-wizard.mjs — Interactive configuration wizard for Index Server.
4
+ *
5
+ * Guides users through profile selection and initial setup, then generates:
6
+ * - .env file with all active settings
7
+ * - .vscode/mcp.json snippet with fully documented env vars (active + commented reference)
8
+ *
9
+ * Profiles:
10
+ * default — HTTP dashboard, JSON storage, keyword search
11
+ * enhanced — HTTPS dashboard, JSON storage, semantic search, mutation, file logging
12
+ * experimental — HTTPS dashboard, SQLite storage, semantic search, debug logging
13
+ *
14
+ * Usage:
15
+ * npx @jagilber-org/index-server --setup
16
+ * npm run setup
17
+ * node scripts/setup-wizard.mjs
18
+ * node scripts/setup-wizard.mjs --non-interactive --profile enhanced --root C:/mcp/index-server
19
+ */
20
+ import fs from 'fs';
21
+ import path from 'path';
22
+ import os from 'os';
23
+ import { fileURLToPath } from 'url';
24
+ import { select, input, confirm, checkbox } from '@inquirer/prompts';
25
+
26
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
+ const ROOT = path.resolve(__dirname, '..');
28
+ const IS_WINDOWS = process.platform === 'win32';
29
+
30
+ // --------------------------------------------------------------------------
31
+ // Path helpers
32
+ // --------------------------------------------------------------------------
33
+ /** Normalize to forward slashes for mcp.json compatibility. */
34
+ function fwd(p) { return p.replace(/\\/g, '/'); }
35
+
36
+ /** Resolve a sub-path under a root, always absolute and forward-slashed. */
37
+ function resolveUnder(root, ...segments) { return fwd(path.resolve(root, ...segments)); }
38
+
39
+ // --------------------------------------------------------------------------
40
+ // Profile definitions
41
+ // --------------------------------------------------------------------------
42
+ const PROFILES = {
43
+ default: {
44
+ label: 'Default — HTTP, JSON storage, keyword search',
45
+ description: [
46
+ ' Transport : stdio (MCP)',
47
+ ' Dashboard : HTTP on localhost:8787',
48
+ ' Storage : JSON files',
49
+ ' Search : keyword only',
50
+ ' Mutation : on (read/write)',
51
+ ' Logging : info to stderr',
52
+ ],
53
+ },
54
+ enhanced: {
55
+ label: 'Enhanced — HTTPS, semantic search, mutation enabled',
56
+ description: [
57
+ ' Transport : stdio (MCP)',
58
+ ' Dashboard : HTTPS with self-signed certs',
59
+ ' Storage : JSON files',
60
+ ' Search : semantic (MiniLM model, ~90MB download)',
61
+ ' Mutation : on (read/write)',
62
+ ' Logging : info + file log',
63
+ ' Metrics : file-based storage',
64
+ ],
65
+ },
66
+ experimental: {
67
+ label: 'Experimental — HTTPS, SQLite storage, semantic search',
68
+ description: [
69
+ ' Transport : stdio (MCP)',
70
+ ' Dashboard : HTTPS with self-signed certs',
71
+ ' Storage : SQLite with WAL mode',
72
+ ' Search : semantic (MiniLM model, ~90MB download)',
73
+ ' Mutation : on (read/write)',
74
+ ' Logging : debug + file log',
75
+ ' Metrics : file-based storage',
76
+ ' ⚠️ SQLite backend is experimental',
77
+ ],
78
+ },
79
+ };
80
+
81
+ // --------------------------------------------------------------------------
82
+ // Resolve all paths for a given root
83
+ // --------------------------------------------------------------------------
84
+ function resolvePaths(root) {
85
+ return {
86
+ instructions: resolveUnder(root, 'instructions'),
87
+ feedback: resolveUnder(root, 'feedback'),
88
+ backups: resolveUnder(root, 'backups'),
89
+ state: resolveUnder(root, 'data', 'state'),
90
+ auditLog: resolveUnder(root, 'logs', 'instruction-transactions.log.jsonl'),
91
+ logFile: resolveUnder(root, 'logs', 'mcp-server.log'),
92
+ metrics: resolveUnder(root, 'metrics'),
93
+ messaging: resolveUnder(root, 'data', 'messaging'),
94
+ embeddings: resolveUnder(root, 'data', 'embeddings.json'),
95
+ modelCache: resolveUnder(root, 'data', 'models'),
96
+ sqliteDb: resolveUnder(root, 'data', 'index.db'),
97
+ certs: resolveUnder(root, 'certs'),
98
+ flags: resolveUnder(root, 'flags.json'),
99
+ };
100
+ }
101
+
102
+ // --------------------------------------------------------------------------
103
+ // Non-interactive mode
104
+ // --------------------------------------------------------------------------
105
+ function parseNonInteractiveArgs() {
106
+ const args = process.argv.slice(2);
107
+ if (!args.includes('--non-interactive')) return null;
108
+
109
+ const config = {
110
+ profile: 'default',
111
+ root: ROOT,
112
+ port: 8787,
113
+ host: '127.0.0.1',
114
+ tls: false,
115
+ mutation: true,
116
+ logLevel: 'info',
117
+ generateCerts: false,
118
+ serverName: 'index-server',
119
+ targets: ['vscode'], // 'vscode', 'copilot-cli', 'claude'
120
+ scope: 'repo', // 'global' or 'repo'
121
+ write: false, // write to real config files
122
+ preview: true, // show preview before writing
123
+ };
124
+
125
+ for (let i = 0; i < args.length; i++) {
126
+ if (args[i] === '--profile' && args[i + 1]) config.profile = args[++i];
127
+ else if (args[i] === '--root' && args[i + 1]) config.root = path.resolve(args[++i]);
128
+ else if (args[i] === '--port' && args[i + 1]) config.port = parseInt(args[++i], 10);
129
+ else if (args[i] === '--host' && args[i + 1]) config.host = args[++i];
130
+ else if (args[i] === '--tls') config.tls = true;
131
+ else if (args[i] === '--mutation') config.mutation = true;
132
+ else if (args[i] === '--log-level' && args[i + 1]) config.logLevel = args[++i];
133
+ else if (args[i] === '--generate-certs') config.generateCerts = true;
134
+ else if (args[i] === '--server-name' && args[i + 1]) config.serverName = args[++i];
135
+ else if (args[i] === '--target' && args[i + 1]) config.targets = args[++i].split(',').map(t => t.trim());
136
+ else if (args[i] === '--scope' && args[i + 1]) config.scope = args[++i];
137
+ else if (args[i] === '--write') config.write = true;
138
+ else if (args[i] === '--no-preview') config.preview = false;
139
+ }
140
+
141
+ // Profile overrides
142
+ if (config.profile === 'enhanced' || config.profile === 'experimental') {
143
+ config.tls = true;
144
+ config.mutation = true;
145
+ config.generateCerts = true;
146
+ }
147
+ if (config.profile === 'experimental') {
148
+ config.logLevel = 'debug';
149
+ }
150
+
151
+ return config;
152
+ }
153
+
154
+ // --------------------------------------------------------------------------
155
+ // Interactive wizard
156
+ // --------------------------------------------------------------------------
157
+ async function runInteractiveWizard() {
158
+ console.log('\n╔════════════════════════════════════════════════════════════════╗');
159
+ console.log('║ Index Server — Configuration Wizard ║');
160
+ console.log('║ MCP instruction indexing for AI governance ║');
161
+ console.log('╚════════════════════════════════════════════════════════════════╝\n');
162
+
163
+ // Step 1: Profile
164
+ const profileKeys = Object.keys(PROFILES);
165
+ const profile = await select({
166
+ message: 'Choose a configuration profile',
167
+ choices: profileKeys.map(k => ({
168
+ name: PROFILES[k].label,
169
+ value: k,
170
+ description: PROFILES[k].description.join('\n'),
171
+ })),
172
+ default: 'default',
173
+ });
174
+
175
+ // Step 2: Root directory
176
+ const defaultRoot = IS_WINDOWS ? 'C:\\mcp\\index-server' : '/opt/index-server';
177
+ const root = path.resolve(await input({
178
+ message: 'Base directory (all data paths resolve under this root)',
179
+ default: defaultRoot,
180
+ }));
181
+
182
+ // Step 3: Server name for mcp.json entry
183
+ const serverName = await input({
184
+ message: 'MCP server name (used in mcp.json)',
185
+ default: 'index-server',
186
+ });
187
+
188
+ // Step 4: Dashboard port
189
+ const port = parseInt(await input({
190
+ message: 'Dashboard port',
191
+ default: '8787',
192
+ validate: (v) => /^\d+$/.test(v) && +v > 0 && +v < 65536 ? true : 'Enter a valid port (1-65535)',
193
+ }), 10);
194
+
195
+ // Step 5: Dashboard host
196
+ const host = await select({
197
+ message: 'Dashboard host',
198
+ choices: [
199
+ { name: '127.0.0.1 — localhost only (recommended)', value: '127.0.0.1' },
200
+ { name: '0.0.0.0 — all network interfaces', value: '0.0.0.0' },
201
+ ],
202
+ default: '127.0.0.1',
203
+ });
204
+
205
+ // Step 6: TLS certs (Enhanced/Experimental)
206
+ let generateCerts = false;
207
+ if (profile === 'enhanced' || profile === 'experimental') {
208
+ generateCerts = await confirm({
209
+ message: 'Generate self-signed TLS certificates now?',
210
+ default: true,
211
+ });
212
+ }
213
+
214
+ // Step 7: Mutation
215
+ let mutation = true;
216
+ if (profile === 'default') {
217
+ mutation = await confirm({
218
+ message: 'Enable mutation (write operations)?',
219
+ default: true,
220
+ });
221
+ }
222
+
223
+ // Step 8: Log level
224
+ const defaultLogLevel = profile === 'experimental' ? 'debug' : 'info';
225
+ const logLevel = await select({
226
+ message: 'Log level',
227
+ choices: ['error', 'warn', 'info', 'debug', 'trace'].map(l => ({
228
+ name: l,
229
+ value: l,
230
+ })),
231
+ default: defaultLogLevel,
232
+ });
233
+
234
+ // Step 9: Target MCP clients
235
+ const targets = await checkbox({
236
+ message: 'Which MCP client configs should be generated?',
237
+ choices: [
238
+ { name: 'VS Code (.vscode/mcp.json)', value: 'vscode', checked: true },
239
+ { name: 'Copilot CLI (~/.copilot/mcp-config.json)', value: 'copilot-cli' },
240
+ { name: 'Claude Desktop (claude_desktop_config.json)', value: 'claude' },
241
+ ],
242
+ });
243
+ // Ensure at least one target
244
+ if (targets.length === 0) targets.push('vscode');
245
+
246
+ // Step 10: Scope (global vs workspace/repo)
247
+ const scope = await select({
248
+ message: 'Configuration scope',
249
+ choices: [
250
+ { name: 'Workspace/repo — .vscode/mcp.json in current directory', value: 'repo' },
251
+ { name: 'Global — user-level config (applies to all workspaces)', value: 'global' },
252
+ ],
253
+ default: 'repo',
254
+ });
255
+
256
+ return { profile, root, serverName, port, host, mutation, logLevel, generateCerts, targets, scope, write: true, preview: true };
257
+ }
258
+
259
+ // --------------------------------------------------------------------------
260
+ // Complete env var catalog (usage-ordered, grouped by category)
261
+ // - key: env var name
262
+ // - desc: single-line description for mcp.json comment
263
+ // - profiles: which profiles set it active (non-commented)
264
+ // - value: function(config, paths) => string value
265
+ // --------------------------------------------------------------------------
266
+ function getEnvCatalog(config, paths) {
267
+ const p = config.profile;
268
+ const isEnhanced = p === 'enhanced' || p === 'experimental';
269
+ const isSqlite = p === 'experimental';
270
+ const tls = config.tls;
271
+
272
+ return [
273
+ // ── Core Paths ─────────────────────────────────────────────────────────
274
+ { section: 'Core Paths — where your data lives' },
275
+ { key: 'INDEX_SERVER_PROFILE', desc: 'Configuration profile: default | enhanced | experimental', active: true, value: p },
276
+ { key: 'INDEX_SERVER_DIR', desc: 'Instruction catalog directory (your knowledge base)', active: true, value: paths.instructions },
277
+ { key: 'INDEX_SERVER_FEEDBACK_DIR', desc: 'Feedback entries storage directory', active: true, value: paths.feedback },
278
+ { key: 'INDEX_SERVER_BACKUPS_DIR', desc: 'Backup snapshots directory', active: true, value: paths.backups },
279
+ { key: 'INDEX_SERVER_STATE_DIR', desc: 'Runtime state files directory', active: true, value: paths.state },
280
+ { key: 'INDEX_SERVER_MESSAGING_DIR',desc: 'Message queue storage directory', active: true, value: paths.messaging },
281
+
282
+ // ── Dashboard (HTTP Admin UI) ──────────────────────────────────────────
283
+ { section: 'Dashboard — HTTP/HTTPS admin interface' },
284
+ { key: 'INDEX_SERVER_DASHBOARD', desc: 'Enable the web dashboard (0=off, 1=on)', active: true, value: '1' },
285
+ { key: 'INDEX_SERVER_DASHBOARD_PORT', desc: 'Dashboard listen port', active: true, value: String(config.port) },
286
+ { key: 'INDEX_SERVER_DASHBOARD_HOST', desc: 'Dashboard bind address (127.0.0.1=local, 0.0.0.0=all)', active: true, value: config.host },
287
+ { key: 'INDEX_SERVER_DASHBOARD_GRAPH', desc: 'Enable instruction graph visualization (0=off, 1=on)', active: false, value: '0' },
288
+
289
+ // ── Security & Mutation ────────────────────────────────────────────────
290
+ { section: 'Security — mutation control, TLS, authentication' },
291
+ { key: 'INDEX_SERVER_MUTATION', desc: 'Enable write operations: add, update, delete (0=off, 1=on)', active: true, value: config.mutation ? '1' : '0' },
292
+ { key: 'INDEX_SERVER_ADMIN_API_KEY', desc: 'Dashboard admin API key (set a strong random value)', active: false, value: '' },
293
+ { key: 'INDEX_SERVER_DASHBOARD_TLS', desc: 'Enable HTTPS for dashboard (0=off, 1=on)', active: tls, value: tls ? '1' : '0' },
294
+ { key: 'INDEX_SERVER_DASHBOARD_TLS_CERT', desc: 'Path to TLS certificate file (.crt/.pem)', active: tls, value: tls ? resolveUnder(paths.certs, 'server.crt') : '' },
295
+ { key: 'INDEX_SERVER_DASHBOARD_TLS_KEY', desc: 'Path to TLS private key file (.key/.pem)', active: tls, value: tls ? resolveUnder(paths.certs, 'server.key') : '' },
296
+ { key: 'INDEX_SERVER_DASHBOARD_TLS_CA', desc: 'Path to CA certificate for client verification (optional)', active: false, value: '' },
297
+
298
+ // ── Semantic Search & Embeddings ───────────────────────────────────────
299
+ { section: 'Semantic Search — AI-powered instruction search' },
300
+ { key: 'INDEX_SERVER_SEMANTIC_ENABLED', desc: 'Enable semantic (vector) search (0=off, 1=on)', active: isEnhanced, value: isEnhanced ? '1' : '0' },
301
+ { key: 'INDEX_SERVER_SEMANTIC_MODEL', desc: 'HuggingFace model name for embeddings', active: false, value: 'Xenova/all-MiniLM-L6-v2' },
302
+ { key: 'INDEX_SERVER_SEMANTIC_DEVICE', desc: 'Compute device: cpu | cuda | dml (Windows ML)', active: false, value: 'cpu' },
303
+ { key: 'INDEX_SERVER_SEMANTIC_CACHE_DIR', desc: 'Directory for downloaded model files (~90MB)', active: isEnhanced, value: paths.modelCache },
304
+ { key: 'INDEX_SERVER_EMBEDDING_PATH', desc: 'Cached embeddings file (grows with catalog size)', active: isEnhanced, value: paths.embeddings },
305
+ { key: 'INDEX_SERVER_SEMANTIC_LOCAL_ONLY', desc: 'Block remote model downloads (0=allow download, 1=local only)', active: isEnhanced, value: isEnhanced ? '0' : '1' },
306
+
307
+ // ── Storage Backend ────────────────────────────────────────────────────
308
+ { section: 'Storage Backend — JSON (default) or SQLite (experimental)' },
309
+ { key: 'INDEX_SERVER_STORAGE_BACKEND', desc: 'Storage engine: json | sqlite', active: isSqlite, value: isSqlite ? 'sqlite' : 'json' },
310
+ { key: 'INDEX_SERVER_SQLITE_PATH', desc: 'SQLite database file path', active: isSqlite, value: paths.sqliteDb },
311
+ { key: 'INDEX_SERVER_SQLITE_WAL', desc: 'Enable Write-Ahead Logging for SQLite (0=off, 1=on)', active: isSqlite, value: '1' },
312
+ { key: 'INDEX_SERVER_SQLITE_MIGRATE_ON_START', desc: 'Auto-migrate JSON to SQLite on startup (0=off, 1=on)', active: isSqlite, value: '1' },
313
+
314
+ // ── Logging & Diagnostics ──────────────────────────────────────────────
315
+ { section: 'Logging — log level, file output, diagnostics' },
316
+ { key: 'INDEX_SERVER_LOG_LEVEL', desc: 'Log level: error | warn | info | debug | trace', active: true, value: config.logLevel },
317
+ { key: 'INDEX_SERVER_LOG_FILE', desc: 'Enable file logging (0=off, 1=default path, or absolute path)', active: isEnhanced, value: isEnhanced ? '1' : '0' },
318
+ { key: 'INDEX_SERVER_VERBOSE_LOGGING', desc: 'Verbose stderr output (0=off, 1=on)', active: false, value: '0' },
319
+ { key: 'INDEX_SERVER_LOG_JSON', desc: 'JSON-formatted log output (0=off, 1=on)', active: false, value: '0' },
320
+ { key: 'INDEX_SERVER_LOG_DIAG', desc: 'Diagnostic startup logging (0=off, 1=on)', active: false, value: '0' },
321
+ { key: 'INDEX_SERVER_AUDIT_LOG', desc: 'Audit log path (1=default, 0=disabled, or absolute path)', active: true, value: paths.auditLog },
322
+
323
+ // ── Backup & Recovery ──────────────────────────────────────────────────
324
+ { section: 'Backup — automatic backup scheduling' },
325
+ { key: 'INDEX_SERVER_AUTO_BACKUP', desc: 'Enable automatic backups (0=off, 1=on; defaults to on when mutation is enabled)', active: false, value: config.mutation ? '1' : '0' },
326
+ { key: 'INDEX_SERVER_AUTO_BACKUP_INTERVAL_MS', desc: 'Backup interval in ms (default: 3600000 = 1 hour)', active: false, value: '3600000' },
327
+ { key: 'INDEX_SERVER_AUTO_BACKUP_MAX_COUNT', desc: 'Max backup snapshots to retain (default: 10)', active: false, value: '10' },
328
+ { key: 'INDEX_SERVER_BACKUP_BEFORE_BULK_DELETE',desc: 'Auto-backup before bulk delete operations (0=off, 1=on)', active: false, value: '1' },
329
+
330
+ // ── Features & Flags ───────────────────────────────────────────────────
331
+ { section: 'Features — feature flags and capabilities' },
332
+ { key: 'INDEX_SERVER_FEATURES', desc: 'Comma-separated feature flags: usage,window,hotness,drift,risk', active: isEnhanced, value: isEnhanced ? 'usage' : '' },
333
+ { key: 'INDEX_SERVER_METRICS_FILE_STORAGE', desc: 'Persist metrics to disk (0=off, 1=on)', active: isEnhanced, value: isEnhanced ? '1' : '0' },
334
+ { key: 'INDEX_SERVER_METRICS_DIR', desc: 'Metrics storage directory', active: isEnhanced, value: paths.metrics },
335
+ { key: 'INDEX_SERVER_FLAGS_FILE', desc: 'Feature flags JSON file path', active: false, value: paths.flags },
336
+
337
+ // ── Server & Transport ─────────────────────────────────────────────────
338
+ { section: 'Server — MCP transport and instance mode' },
339
+ { key: 'INDEX_SERVER_MODE', desc: 'Instance mode: standalone | leader | follower | auto', active: false, value: 'standalone' },
340
+ { key: 'INDEX_SERVER_DISABLE_EARLY_STDIN_BUFFER',desc: 'Disable stdin handshake hardening (0=off, 1=on)', active: false, value: '0' },
341
+ { key: 'INDEX_SERVER_IDLE_KEEPALIVE_MS', desc: 'Keepalive interval in ms (default: 30000)', active: false, value: '30000' },
342
+ { key: 'INDEX_SERVER_POLL_MS', desc: 'Index filesystem poll interval in ms (default: 10000)', active: false, value: '10000' },
343
+
344
+ // ── Advanced Tuning ────────────────────────────────────────────────────
345
+ { section: 'Advanced — tuning, limits, governance (most users can skip)' },
346
+ { key: 'INDEX_SERVER_BODY_WARN_LENGTH', desc: 'Max instruction body length in chars (default: 100000)', active: false, value: '100000' },
347
+ { key: 'INDEX_SERVER_AUTO_SPLIT_OVERSIZED', desc: 'Auto-split oversized entries on load (0=off, 1=on)', active: false, value: '0' },
348
+ { key: 'INDEX_SERVER_READ_RETRIES', desc: 'File read retry attempts (default: 3)', active: false, value: '3' },
349
+ { key: 'INDEX_SERVER_MAX_BULK_DELETE', desc: 'Max entries in a single bulk delete (default: 5)', active: false, value: '5' },
350
+ { key: 'INDEX_SERVER_FEEDBACK_MAX_ENTRIES', desc: 'Max feedback entries before rotation (default: 1000)', active: false, value: '1000' },
351
+ { key: 'INDEX_SERVER_MESSAGING_MAX', desc: 'Max messages in queue (default: 10000)', active: false, value: '10000' },
352
+ { key: 'INDEX_SERVER_MAX_CONNECTIONS', desc: 'Max concurrent dashboard connections (default: 100)', active: false, value: '100' },
353
+ { key: 'INDEX_SERVER_CACHE_MODE', desc: 'Index cache mode: normal | memoize | memoize+hash | reload | reload+memo', active: false, value: 'normal' },
354
+ { key: 'INDEX_SERVER_WORKSPACE', desc: 'Workspace identifier for multi-tenant setups', active: false, value: '' },
355
+ { key: 'INDEX_SERVER_AGENT_ID', desc: 'Agent identifier for audit trails', active: false, value: '' },
356
+ ];
357
+ }
358
+
359
+ // --------------------------------------------------------------------------
360
+ // Generate .vscode/mcp.json snippet (JSONC with comments)
361
+ // --------------------------------------------------------------------------
362
+ function generateMcpJson(config, paths) {
363
+ const catalog = getEnvCatalog(config, paths);
364
+ const indent = '\t\t\t\t';
365
+
366
+ const lines = [
367
+ '{',
368
+ '\t"servers": {',
369
+ `\t\t"${config.serverName}": {`,
370
+ '\t\t\t"type": "stdio",',
371
+ `\t\t\t"cwd": "${fwd(config.root)}",`,
372
+ '\t\t\t"command": "node",',
373
+ '\t\t\t"args": ["dist/server/index-server.js"],',
374
+ '\t\t\t"env": {',
375
+ ];
376
+
377
+ let firstSection = true;
378
+ for (const entry of catalog) {
379
+ if (entry.section) {
380
+ if (!firstSection) lines.push('');
381
+ lines.push(`${indent}// ── ${entry.section} ${'─'.repeat(Math.max(0, 58 - entry.section.length))}`);
382
+ firstSection = false;
383
+ continue;
384
+ }
385
+
386
+ const comment = `// ${entry.desc}`;
387
+ if (entry.active) {
388
+ lines.push(`${indent}${comment}`);
389
+ lines.push(`${indent}"${entry.key}": "${entry.value}",`);
390
+ } else {
391
+ lines.push(`${indent}${comment}`);
392
+ lines.push(`${indent}// "${entry.key}": "${entry.value}",`);
393
+ }
394
+ }
395
+
396
+ lines.push('\t\t\t}');
397
+ lines.push('\t\t}');
398
+ lines.push('\t},');
399
+ lines.push('\t"inputs": []');
400
+ lines.push('}');
401
+
402
+ return lines.join('\n');
403
+ }
404
+
405
+ // --------------------------------------------------------------------------
406
+ // Generate Copilot CLI mcp-config.json format
407
+ // --------------------------------------------------------------------------
408
+ function generateCopilotCliJson(config, paths) {
409
+ const catalog = getEnvCatalog(config, paths);
410
+ const env = {};
411
+ for (const entry of catalog) {
412
+ if (entry.section) continue;
413
+ if (entry.active) env[entry.key] = entry.value;
414
+ }
415
+ const obj = {
416
+ mcpServers: {
417
+ [config.serverName]: {
418
+ command: 'node',
419
+ args: [path.join(fwd(config.root), 'dist/server/index-server.js')],
420
+ env,
421
+ },
422
+ },
423
+ };
424
+ return JSON.stringify(obj, null, 2);
425
+ }
426
+
427
+ // --------------------------------------------------------------------------
428
+ // Generate Claude Desktop config JSON format
429
+ // --------------------------------------------------------------------------
430
+ function generateClaudeDesktopJson(config, paths) {
431
+ const catalog = getEnvCatalog(config, paths);
432
+ const env = {};
433
+ for (const entry of catalog) {
434
+ if (entry.section) continue;
435
+ if (entry.active) env[entry.key] = entry.value;
436
+ }
437
+ const obj = {
438
+ mcpServers: {
439
+ [config.serverName]: {
440
+ command: 'node',
441
+ args: [path.join(fwd(config.root), 'dist/server/index-server.js')],
442
+ env,
443
+ },
444
+ },
445
+ };
446
+ return JSON.stringify(obj, null, 2);
447
+ }
448
+
449
+ // --------------------------------------------------------------------------
450
+ // Resolve target config file paths based on scope and OS
451
+ // --------------------------------------------------------------------------
452
+ function resolveConfigPaths(config) {
453
+ const home = os.homedir();
454
+ const results = [];
455
+ const isWin = process.platform === 'win32';
456
+
457
+ for (const target of (config.targets || ['vscode'])) {
458
+ if (target === 'vscode') {
459
+ if (config.scope === 'global') {
460
+ const dir = isWin
461
+ ? path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Code', 'User')
462
+ : path.join(home, '.config', 'Code', 'User');
463
+ results.push({ target, path: path.join(dir, 'settings.json'), format: 'vscode-global' });
464
+ } else {
465
+ results.push({ target, path: path.join(config.root, '.vscode', 'mcp.json'), format: 'vscode' });
466
+ }
467
+ } else if (target === 'copilot-cli') {
468
+ const dir = isWin
469
+ ? path.join(process.env.USERPROFILE || home, '.copilot')
470
+ : path.join(home, '.copilot');
471
+ results.push({ target, path: path.join(dir, 'mcp-config.json'), format: 'copilot-cli' });
472
+ } else if (target === 'claude') {
473
+ const dir = isWin
474
+ ? path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Claude')
475
+ : path.join(home, 'Library', 'Application Support', 'Claude');
476
+ results.push({ target, path: path.join(dir, 'claude_desktop_config.json'), format: 'claude' });
477
+ }
478
+ }
479
+ return results;
480
+ }
481
+
482
+ // --------------------------------------------------------------------------
483
+ // Generate config content for a given format
484
+ // --------------------------------------------------------------------------
485
+ function generateConfigForTarget(format, config, paths) {
486
+ switch (format) {
487
+ case 'vscode':
488
+ case 'vscode-global':
489
+ return generateMcpJson(config, paths);
490
+ case 'copilot-cli':
491
+ return generateCopilotCliJson(config, paths);
492
+ case 'claude':
493
+ return generateClaudeDesktopJson(config, paths);
494
+ default:
495
+ return generateMcpJson(config, paths);
496
+ }
497
+ }
498
+
499
+ // --------------------------------------------------------------------------
500
+ // Preview all generated configs
501
+ // --------------------------------------------------------------------------
502
+ function previewConfigs(configTargets, config, paths) {
503
+ console.log('\n┌─────────────────────────────────────────────────────────────────────┐');
504
+ console.log('│ 📋 Configuration Preview │');
505
+ console.log('└─────────────────────────────────────────────────────────────────────┘');
506
+ for (const ct of configTargets) {
507
+ const content = generateConfigForTarget(ct.format, config, paths);
508
+ console.log(`\n── ${ct.target} → ${ct.path} ──\n`);
509
+ console.log(content);
510
+ }
511
+ console.log('');
512
+ }
513
+
514
+ // --------------------------------------------------------------------------
515
+ // Write config to real file (merge if existing)
516
+ // --------------------------------------------------------------------------
517
+ function writeConfigFile(targetInfo, content) {
518
+ const dir = path.dirname(targetInfo.path);
519
+ if (!fs.existsSync(dir)) {
520
+ fs.mkdirSync(dir, { recursive: true });
521
+ }
522
+
523
+ if (targetInfo.format === 'vscode-global' && fs.existsSync(targetInfo.path)) {
524
+ // Write sidecar to avoid corrupting settings.json
525
+ const sidecarPath = targetInfo.path.replace('settings.json', 'mcp.json.generated');
526
+ fs.writeFileSync(sidecarPath, content, 'utf8');
527
+ console.log(` ✅ Generated: ${sidecarPath}`);
528
+ console.log(` ℹ️ Merge the "mcp" key into your settings.json manually.`);
529
+ return;
530
+ }
531
+
532
+ if (fs.existsSync(targetInfo.path)) {
533
+ // Backup existing file before overwriting
534
+ const backup = targetInfo.path + '.backup.' + Date.now();
535
+ fs.copyFileSync(targetInfo.path, backup);
536
+ console.log(` 📦 Backed up existing: ${backup}`);
537
+
538
+ // For JSON files, try to merge the mcpServers key
539
+ try {
540
+ const existing = JSON.parse(fs.readFileSync(targetInfo.path, 'utf8'));
541
+ const generated = JSON.parse(content);
542
+
543
+ if (targetInfo.format === 'copilot-cli' || targetInfo.format === 'claude') {
544
+ existing.mcpServers = { ...existing.mcpServers, ...generated.mcpServers };
545
+ fs.writeFileSync(targetInfo.path, JSON.stringify(existing, null, 2), 'utf8'); // lgtm[js/file-system-race] — setup wizard writes user-provided config target; race acceptable in CLI tooling
546
+ console.log(` ✅ Merged into: ${targetInfo.path}`);
547
+ return;
548
+ }
549
+ } catch {
550
+ // Not valid JSON — fall through to overwrite
551
+ }
552
+ }
553
+
554
+ fs.writeFileSync(targetInfo.path, content, 'utf8'); // lgtm[js/file-system-race] — setup wizard writes user-provided config target; race acceptable in CLI tooling
555
+ console.log(` ✅ Written: ${targetInfo.path}`);
556
+ }
557
+
558
+ // --------------------------------------------------------------------------
559
+ // Generate .env file
560
+ // --------------------------------------------------------------------------
561
+ function generateEnvFile(config, paths) {
562
+ const catalog = getEnvCatalog(config, paths);
563
+ const lines = [
564
+ '# Index Server Configuration',
565
+ `# Profile: ${config.profile}`,
566
+ `# Generated by setup wizard on ${new Date().toISOString()}`,
567
+ `# Root: ${fwd(config.root)}`,
568
+ '#',
569
+ ];
570
+
571
+ for (const entry of catalog) {
572
+ if (entry.section) {
573
+ lines.push('', `# ── ${entry.section}`);
574
+ continue;
575
+ }
576
+ if (entry.active) {
577
+ lines.push(`${entry.key}=${entry.value}`);
578
+ } else {
579
+ lines.push(`# ${entry.key}=${entry.value}`);
580
+ }
581
+ }
582
+
583
+ lines.push('');
584
+ return lines.join('\n');
585
+ }
586
+
587
+ // --------------------------------------------------------------------------
588
+ // Print folder summary table
589
+ // --------------------------------------------------------------------------
590
+ function printFolderSummary(paths, profile) {
591
+ console.log('\n┌─────────────────────────────────────────────────────────────────────┐');
592
+ console.log('│ 📂 Index Locations │');
593
+ console.log('├────────────────────┬────────────────────────────────────────────────┤');
594
+
595
+ const rows = [
596
+ ['Instructions', paths.instructions],
597
+ ['Feedback', paths.feedback],
598
+ ['Backups', paths.backups],
599
+ ['State', paths.state],
600
+ ['Messages', paths.messaging],
601
+ ['Audit Log', paths.auditLog],
602
+ ['Log File', paths.logFile],
603
+ ['Metrics', paths.metrics],
604
+ ];
605
+
606
+ if (profile === 'enhanced' || profile === 'experimental') {
607
+ rows.push(
608
+ ['Model Cache', paths.modelCache],
609
+ ['Embeddings', paths.embeddings],
610
+ );
611
+ }
612
+ if (profile === 'experimental') {
613
+ rows.push(['SQLite DB', paths.sqliteDb]);
614
+ }
615
+
616
+ for (const [label, value] of rows) {
617
+ const paddedLabel = label.padEnd(18);
618
+ console.log(`│ ${paddedLabel} │ ${value.padEnd(46)}│`);
619
+ }
620
+ console.log('└────────────────────┴────────────────────────────────────────────────┘');
621
+ }
622
+
623
+ // --------------------------------------------------------------------------
624
+ // Main
625
+ // --------------------------------------------------------------------------
626
+ async function main() {
627
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
628
+ console.log(`Usage: setup-wizard.mjs [options]
629
+
630
+ Interactive mode:
631
+ npx @jagilber-org/index-server --setup
632
+ npm run setup
633
+ node scripts/setup-wizard.mjs
634
+
635
+ Non-interactive mode:
636
+ node scripts/setup-wizard.mjs --non-interactive [options]
637
+ --profile <name> default | enhanced | experimental
638
+ --root <dir> Base directory for all data paths
639
+ --port <n> Dashboard port (default: 8787)
640
+ --host <addr> Dashboard host (default: 127.0.0.1)
641
+ --tls Enable TLS dashboard settings in generated config
642
+ --mutation Enable write operations
643
+ --log-level <lvl> Log level: error|warn|info|debug|trace
644
+ --generate-certs Generate self-signed TLS certificates
645
+ --server-name <n> MCP server name in mcp.json (default: index-server)
646
+ --target <list> Comma-separated targets: vscode,copilot-cli,claude
647
+ --scope <s> global | repo (default: repo)
648
+ --write Write directly to real config files (with backup)
649
+ --no-preview Skip config preview in non-interactive mode`);
650
+ process.exit(0);
651
+ }
652
+
653
+ let config = parseNonInteractiveArgs();
654
+ if (!config) {
655
+ config = await runInteractiveWizard();
656
+ }
657
+
658
+ const paths = resolvePaths(config.root);
659
+
660
+ // ── Print folder summary ────────────────────────────────────────────
661
+ printFolderSummary(paths, config.profile);
662
+
663
+ // ── Generate .env file ──────────────────────────────────────────────
664
+ const envContent = generateEnvFile(config, paths);
665
+ const envPath = path.join(config.root, '.env');
666
+
667
+ try {
668
+ fs.mkdirSync(config.root, { recursive: true });
669
+ } catch { /* exists */ }
670
+
671
+ if (fs.existsSync(envPath)) { // lgtm[js/file-system-race]
672
+ const genPath = path.join(config.root, '.env.generated');
673
+ fs.writeFileSync(genPath, envContent, 'utf8');
674
+ console.log(`\n⚠️ .env already exists. Written to: ${genPath}`);
675
+ } else {
676
+ fs.writeFileSync(envPath, envContent, 'utf8'); // lgtm[js/file-system-race] — setup wizard writes .env to user-supplied path; race acceptable in CLI tooling
677
+ console.log(`\n✅ .env written to: ${envPath}`);
678
+ }
679
+
680
+ // ── Multi-target config generation ──────────────────────────────────
681
+ const configTargets = resolveConfigPaths(config);
682
+
683
+ // Preview
684
+ if (config.preview !== false) {
685
+ previewConfigs(configTargets, config, paths);
686
+ }
687
+
688
+ // Write to real files or sidecar
689
+ if (config.write) {
690
+ console.log('📁 Writing configuration files...\n');
691
+ for (const ct of configTargets) {
692
+ const content = generateConfigForTarget(ct.format, config, paths);
693
+ writeConfigFile(ct, content);
694
+ }
695
+ } else {
696
+ // Legacy sidecar behavior for backward compatibility
697
+ const mcpContent = generateMcpJson(config, paths);
698
+ const mcpDir = path.join(config.root, '.vscode');
699
+ const mcpPath = path.join(mcpDir, 'mcp.json.generated');
700
+
701
+ try {
702
+ fs.mkdirSync(mcpDir, { recursive: true });
703
+ } catch { /* exists */ }
704
+ fs.writeFileSync(mcpPath, mcpContent, 'utf8');
705
+ console.log(`✅ mcp.json snippet written to: ${mcpPath}`);
706
+ console.log(' Copy its contents into your .vscode/mcp.json or VS Code user settings.');
707
+
708
+ // Also generate Copilot CLI / Claude if requested
709
+ for (const ct of configTargets) {
710
+ if (ct.format !== 'vscode' && ct.format !== 'vscode-global') {
711
+ const content = generateConfigForTarget(ct.format, config, paths);
712
+ const genPath = ct.path + '.generated';
713
+ const dir = path.dirname(genPath);
714
+ try { fs.mkdirSync(dir, { recursive: true }); } catch { /* exists */ }
715
+ fs.writeFileSync(genPath, content, 'utf8');
716
+ console.log(`✅ ${ct.target} config written to: ${genPath}`);
717
+ }
718
+ }
719
+ }
720
+
721
+ // ── Generate TLS certs ──────────────────────────────────────────────
722
+ if (config.generateCerts) {
723
+ console.log('\n🔐 Generating TLS certificates...');
724
+ try {
725
+ const { execFileSync } = await import('child_process');
726
+ const certDir = path.join(config.root, 'certs');
727
+ execFileSync(
728
+ process.execPath,
729
+ [path.join(ROOT, 'scripts', 'generate-certs.mjs'), '--hostname', 'localhost', '--output', certDir],
730
+ { stdio: 'inherit' }
731
+ );
732
+ } catch {
733
+ console.error('❌ Certificate generation failed. Run manually:');
734
+ console.error(` node scripts/generate-certs.mjs --output "${path.join(config.root, 'certs')}"`);
735
+ }
736
+ }
737
+
738
+ // ── Next steps ──────────────────────────────────────────────────────
739
+ const proto = (config.profile === 'enhanced' || config.profile === 'experimental') ? 'https' : 'http';
740
+ console.log('\n╔════════════════════════════════════════════════════════════════╗');
741
+ console.log('║ Next Steps ║');
742
+ console.log('╚════════════════════════════════════════════════════════════════╝\n');
743
+ console.log(' 1. Build the server:');
744
+ console.log(' npm run build\n');
745
+
746
+ if (config.write) {
747
+ console.log(' 2. Config files have been written. Restart your MCP client.\n');
748
+ } else {
749
+ console.log(' 2. Copy generated config into your MCP client settings.');
750
+ for (const ct of configTargets) {
751
+ const genPath = ct.format === 'vscode' || ct.format === 'vscode-global'
752
+ ? path.join(config.root, '.vscode', 'mcp.json.generated')
753
+ : ct.path + '.generated';
754
+ console.log(` ${ct.target}: ${genPath}`);
755
+ }
756
+ console.log('');
757
+ }
758
+
759
+ console.log(` 3. Open the dashboard:`);
760
+ console.log(` ${proto}://localhost:${config.port}\n`);
761
+
762
+ if (config.profile === 'enhanced' || config.profile === 'experimental') {
763
+ console.log(' 4. First-time semantic search:');
764
+ console.log(' The MiniLM model (~90MB) will download on first query.');
765
+ console.log(` Model cache: ${paths.modelCache}\n`);
766
+ }
767
+
768
+ if (config.profile === 'experimental') {
769
+ console.log(' ⚠️ SQLite backend is experimental. Your data is in:');
770
+ console.log(` ${paths.sqliteDb}\n`);
771
+ }
772
+
773
+ console.log(` Targets: ${(config.targets || ['vscode']).join(', ')} | Scope: ${config.scope || 'repo'}`);
774
+ console.log(` Profile: ${config.profile} | Root: ${fwd(config.root)}`);
775
+ console.log('');
776
+ }
777
+
778
+ main().catch(err => {
779
+ console.error('Setup wizard error:', err);
780
+ process.exit(1);
781
+ });