@jagilber-org/index-server 1.22.0 → 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.
- package/CHANGELOG.md +87 -2
- package/CODE_OF_CONDUCT.md +2 -0
- package/CONTRIBUTING.md +32 -2
- package/README.md +83 -20
- package/SECURITY.md +17 -5
- package/dist/config/dashboardConfig.d.ts +3 -0
- package/dist/config/dashboardConfig.js +3 -0
- package/dist/config/defaultValues.d.ts +1 -1
- package/dist/config/defaultValues.js +1 -1
- package/dist/config/featureConfig.d.ts +2 -0
- package/dist/config/featureConfig.js +6 -1
- package/dist/config/runtimeConfig.d.ts +1 -1
- package/dist/config/runtimeConfig.js +8 -9
- package/dist/dashboard/client/admin.html +173 -54
- package/dist/dashboard/client/css/admin.css +151 -0
- package/dist/dashboard/client/js/admin.auth.js +25 -11
- package/dist/dashboard/client/js/admin.config.js +1 -1
- package/dist/dashboard/client/js/admin.feedback.js +328 -0
- package/dist/dashboard/client/js/admin.graph.js +120 -18
- package/dist/dashboard/client/js/admin.instructions.js +27 -13
- package/dist/dashboard/client/js/admin.logs.js +1 -5
- package/dist/dashboard/client/js/admin.maintenance.js +53 -8
- package/dist/dashboard/client/js/admin.messaging.js +1 -4
- package/dist/dashboard/client/js/admin.overview.js +5 -1
- package/dist/dashboard/client/js/admin.sessions.js +1 -1
- package/dist/dashboard/client/js/admin.utils.js +43 -1
- package/dist/dashboard/client/js/mermaid.min.js +813 -537
- package/dist/dashboard/export/DataExporter.js +2 -1
- package/dist/dashboard/server/AdminPanel.d.ts +3 -0
- package/dist/dashboard/server/AdminPanel.js +132 -35
- package/dist/dashboard/server/ApiRoutes.js +40 -9
- package/dist/dashboard/server/DashboardServer.js +1 -1
- package/dist/dashboard/server/FileMetricsStorage.d.ts +19 -0
- package/dist/dashboard/server/FileMetricsStorage.js +52 -5
- package/dist/dashboard/server/HttpTransport.js +6 -0
- package/dist/dashboard/server/InstanceManager.js +7 -2
- package/dist/dashboard/server/KnowledgeStore.js +7 -2
- package/dist/dashboard/server/MetricsCollector.d.ts +16 -0
- package/dist/dashboard/server/MetricsCollector.js +113 -17
- package/dist/dashboard/server/legacyDashboardHtml.js +7 -2
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +8 -3
- package/dist/dashboard/server/routes/admin.feedback.routes.d.ts +15 -0
- package/dist/dashboard/server/routes/admin.feedback.routes.js +188 -0
- package/dist/dashboard/server/routes/admin.routes.js +35 -27
- package/dist/dashboard/server/routes/alerts.routes.js +4 -3
- package/dist/dashboard/server/routes/api.feedback.routes.js +2 -1
- package/dist/dashboard/server/routes/api.usage.routes.js +8 -7
- package/dist/dashboard/server/routes/embeddings.routes.d.ts +2 -1
- package/dist/dashboard/server/routes/embeddings.routes.js +18 -9
- package/dist/dashboard/server/routes/graph.routes.js +10 -13
- package/dist/dashboard/server/routes/index.d.ts +1 -0
- package/dist/dashboard/server/routes/index.js +74 -39
- package/dist/dashboard/server/routes/instances.routes.js +2 -1
- package/dist/dashboard/server/routes/instructions.routes.js +46 -27
- package/dist/dashboard/server/routes/knowledge.routes.js +4 -3
- package/dist/dashboard/server/routes/logs.routes.js +5 -4
- package/dist/dashboard/server/routes/messaging.routes.js +15 -14
- package/dist/dashboard/server/routes/metrics.routes.js +14 -13
- package/dist/dashboard/server/routes/scripts.routes.js +6 -3
- package/dist/dashboard/server/routes/status.routes.js +25 -6
- package/dist/dashboard/server/routes/synthetic.routes.js +3 -2
- package/dist/dashboard/server/routes/usage.routes.js +2 -1
- package/dist/dashboard/server/utils/escapeHtml.d.ts +1 -0
- package/dist/dashboard/server/utils/escapeHtml.js +11 -0
- package/dist/dashboard/server/utils/pathContainment.d.ts +1 -0
- package/dist/dashboard/server/utils/pathContainment.js +15 -0
- package/dist/dashboard/server/wsInit.js +2 -2
- package/dist/lib/mcpStdioLogging.d.ts +165 -0
- package/dist/lib/mcpStdioLogging.js +287 -0
- package/dist/schemas/index.d.ts +37 -2
- package/dist/schemas/index.js +27 -3
- package/dist/server/backgroundServicesStartup.d.ts +7 -1
- package/dist/server/backgroundServicesStartup.js +25 -8
- package/dist/server/certInit.d.ts +97 -0
- package/dist/server/certInit.js +359 -0
- package/dist/server/certInit.types.d.ts +92 -0
- package/dist/server/certInit.types.js +34 -0
- package/dist/server/handshake/fallbackFrames.d.ts +31 -0
- package/dist/server/handshake/fallbackFrames.js +38 -0
- package/dist/server/handshake/initializeDetector.d.ts +31 -0
- package/dist/server/handshake/initializeDetector.js +88 -0
- package/dist/server/handshake/protocol.d.ts +15 -0
- package/dist/server/handshake/protocol.js +37 -0
- package/dist/server/handshake/readyEmitter.d.ts +6 -0
- package/dist/server/handshake/readyEmitter.js +88 -0
- package/dist/server/handshake/safetyFallbacks.d.ts +1 -0
- package/dist/server/handshake/safetyFallbacks.js +134 -0
- package/dist/server/handshake/stdinSniffer.d.ts +1 -0
- package/dist/server/handshake/stdinSniffer.js +260 -0
- package/dist/server/handshake/tracing.d.ts +16 -0
- package/dist/server/handshake/tracing.js +95 -0
- package/dist/server/handshakeManager.d.ts +23 -23
- package/dist/server/handshakeManager.js +36 -466
- package/dist/server/index-server.d.ts +23 -0
- package/dist/server/index-server.js +194 -9
- package/dist/server/mcpReadOnlySurfaces.d.ts +44 -0
- package/dist/server/mcpReadOnlySurfaces.js +297 -0
- package/dist/server/sdkServer.js +69 -7
- package/dist/server/transport.d.ts +5 -6
- package/dist/server/transport.js +46 -64
- package/dist/server/transportFactory.d.ts +3 -9
- package/dist/server/transportFactory.js +18 -380
- package/dist/services/atomicFs.d.ts +3 -0
- package/dist/services/atomicFs.js +171 -13
- package/dist/services/auditLog.d.ts +17 -2
- package/dist/services/auditLog.js +75 -14
- package/dist/services/bootstrapGating.js +1 -1
- package/dist/services/categoryRules.d.ts +10 -0
- package/dist/services/categoryRules.js +17 -0
- package/dist/services/classificationService.js +7 -5
- package/dist/services/embeddingService.d.ts +27 -11
- package/dist/services/embeddingService.js +51 -14
- package/dist/services/feedbackStorage.d.ts +39 -0
- package/dist/services/feedbackStorage.js +88 -0
- package/dist/services/handlers/instructions.add.js +429 -317
- package/dist/services/handlers/instructions.groom.js +128 -31
- package/dist/services/handlers/instructions.import.js +56 -23
- package/dist/services/handlers/instructions.patch.js +43 -32
- package/dist/services/handlers/instructions.query.js +20 -29
- package/dist/services/handlers/instructions.shared.d.ts +54 -0
- package/dist/services/handlers/instructions.shared.js +126 -1
- package/dist/services/handlers.activation.js +83 -81
- package/dist/services/handlers.dashboardConfig.d.ts +2 -2
- package/dist/services/handlers.dashboardConfig.js +1 -2
- package/dist/services/handlers.diagnostics.js +75 -54
- package/dist/services/handlers.feedback.d.ts +4 -11
- package/dist/services/handlers.feedback.js +11 -333
- package/dist/services/handlers.gates.js +69 -37
- package/dist/services/handlers.graph.js +2 -2
- package/dist/services/handlers.help.js +2 -2
- package/dist/services/handlers.instructionSchema.js +4 -2
- package/dist/services/handlers.integrity.js +42 -22
- package/dist/services/handlers.messaging.js +1 -1
- package/dist/services/handlers.metrics.js +51 -6
- package/dist/services/handlers.prompt.js +10 -2
- package/dist/services/handlers.search.js +94 -44
- package/dist/services/handlers.trace.js +1 -1
- package/dist/services/handlers.usage.js +38 -7
- package/dist/services/indexContext.d.ts +21 -1
- package/dist/services/indexContext.js +267 -82
- package/dist/services/indexLoader.d.ts +1 -0
- package/dist/services/indexLoader.js +28 -8
- package/dist/services/instructionRecordValidation.d.ts +39 -0
- package/dist/services/instructionRecordValidation.js +388 -0
- package/dist/services/instructions.dispatcher.js +4 -4
- package/dist/services/loaderSchemaValidator.d.ts +15 -0
- package/dist/services/loaderSchemaValidator.js +69 -0
- package/dist/services/logger.js +11 -2
- package/dist/services/mcpLogBridge.d.ts +49 -0
- package/dist/services/mcpLogBridge.js +83 -0
- package/dist/services/ownershipService.js +18 -8
- package/dist/services/performanceBaseline.js +23 -22
- package/dist/services/promptReviewService.d.ts +3 -1
- package/dist/services/promptReviewService.js +41 -13
- package/dist/services/regexSafety.d.ts +6 -0
- package/dist/services/regexSafety.js +46 -0
- package/dist/services/seedBootstrap.js +4 -4
- package/dist/services/storage/factory.d.ts +14 -1
- package/dist/services/storage/factory.js +61 -1
- package/dist/services/storage/jsonEmbeddingStore.d.ts +15 -0
- package/dist/services/storage/jsonEmbeddingStore.js +83 -0
- package/dist/services/storage/jsonFileStore.d.ts +3 -1
- package/dist/services/storage/jsonFileStore.js +8 -6
- package/dist/services/storage/migrationEngine.d.ts +13 -0
- package/dist/services/storage/migrationEngine.js +31 -0
- package/dist/services/storage/sqliteEmbeddingStore.d.ts +30 -0
- package/dist/services/storage/sqliteEmbeddingStore.js +222 -0
- package/dist/services/storage/sqliteStore.d.ts +3 -1
- package/dist/services/storage/sqliteStore.js +2 -2
- package/dist/services/storage/types.d.ts +48 -1
- package/dist/services/toolRegistry.js +77 -67
- package/dist/services/toolRegistry.zod.js +89 -86
- package/dist/services/tracing.js +5 -4
- package/dist/utils/envUtils.d.ts +4 -0
- package/dist/utils/envUtils.js +7 -0
- package/dist/utils/memoryMonitor.js +11 -10
- package/package.json +11 -4
- package/schemas/instruction.schema.json +38 -1
- package/scripts/copy-dashboard-assets.mjs +1 -1
- package/scripts/dist/README.md +1 -1
- package/scripts/setup-wizard.mjs +781 -0
- package/server.json +1 -0
- package/dist/externalClientLib.d.ts +0 -1
- package/dist/externalClientLib.js +0 -2
- package/dist/portableClientWrapper.d.ts +0 -1
- package/dist/portableClientWrapper.js +0 -2
- package/dist/services/indexingService.d.ts +0 -1
- 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
|
+
});
|