@jagilber-org/index-server 1.26.4 → 1.26.11
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 +34 -0
- package/dist/config/runtimeConfig.js +4 -1
- package/dist/dashboard/client/admin.html +26 -26
- package/dist/lib/mcpStdioLogging.d.ts +12 -0
- package/dist/lib/mcpStdioLogging.js +39 -0
- package/dist/services/logger.js +45 -16
- package/dist/services/mcpLogBridge.d.ts +4 -1
- package/dist/services/mcpLogBridge.js +26 -4
- package/package.json +2 -1
- package/scripts/setup-wizard.mjs +268 -16
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,26 @@ The format is based on Keep a Changelog and this project adheres to Semantic Ver
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.26.11] - 2026-04-30
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Release**: `server.json` now ends with exactly one trailing LF. The v1.26.10 release was published with two trailing LFs (artefact of an earlier `Set-Content -NoNewline` pipeline), which caused the mirror PR to fail CI on the `end-of-file-fixer` framework hook.
|
|
14
|
+
- **Pre-commit (prevention)**: `scripts/pre-commit.mjs` now invokes the `pre-commit` framework on staged files for an explicit fast-hook allowlist (`end-of-file-fixer`, `trailing-whitespace`, `check-json`, `check-yaml`, `check-merge-conflict`, `detect-private-key`). Previously these framework hooks only ran in CI, so auto-fixable formatting issues silently passed local commits and broke PR builds. Slow security hooks (`gitleaks`, `semgrep`, `ggshield`, `detect-secrets`) deliberately remain in their own CI workflows / pre-push stage. Honours `SKIP_PRE_COMMIT_FRAMEWORK=1` and skips gracefully when `pre-commit` is not on PATH (#266).
|
|
15
|
+
|
|
16
|
+
## [1.26.10] - 2026-04-30
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **CI**: `.github/workflows/precommit.yml` checkout now uses `fetch-depth: 0` so `pre-commit run --from-ref BASE --to-ref HEAD` (diff-only mode introduced in v1.26.9) can resolve the PR base SHA. Previous default `fetch-depth: 1` caused `git diff BASE..HEAD` to exit 3 with `CalledProcessError` on every PR (#264).
|
|
21
|
+
- **Tests**: `skippedTestsAudit.spec.ts` env-check allowlist now recognizes platform identifiers (`isWindows`, `isLinux`, `isMac`, `isDarwin`, `process.platform`) so the new `ggshieldWithRetry.spec.ts` Windows-skip from v1.26.9 passes the audit (#264).
|
|
22
|
+
|
|
23
|
+
## [1.26.6] - 2026-04-28
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **Setup Wizard**: Resolve server entry point for `npx` installs so the wizard can locate and launch the server correctly.
|
|
28
|
+
|
|
9
29
|
## [1.26.1] - 2026-04-27
|
|
10
30
|
|
|
11
31
|
### Fixed
|
|
@@ -1441,3 +1461,17 @@ No client changes required. Enable `INDEX_SERVER_MEMOIZE=1` (and optionally `IND
|
|
|
1441
1461
|
## [1.26.2] - 2026-04-27
|
|
1442
1462
|
|
|
1443
1463
|
## [1.26.4] - 2026-04-28
|
|
1464
|
+
|
|
1465
|
+
## [1.26.5] - 2026-04-28
|
|
1466
|
+
|
|
1467
|
+
## [1.26.7] - 2026-04-30
|
|
1468
|
+
|
|
1469
|
+
### Added
|
|
1470
|
+
|
|
1471
|
+
- fix(wizard): correct Build prompt logic
|
|
1472
|
+
|
|
1473
|
+
## [1.26.8] - 2026-04-30
|
|
1474
|
+
|
|
1475
|
+
### Added
|
|
1476
|
+
|
|
1477
|
+
- Test stability + version parity tooling (PR #261)
|
|
@@ -266,8 +266,11 @@ function applyProfileDefaults(profile) {
|
|
|
266
266
|
if (process.env[key] === undefined)
|
|
267
267
|
process.env[key] = value;
|
|
268
268
|
};
|
|
269
|
-
// All profiles enable dashboard by default
|
|
269
|
+
// All profiles enable dashboard and file logging by default.
|
|
270
|
+
// File logging uses the sentinel value '1' which resolves to logs/mcp-server.log.
|
|
271
|
+
// This ensures logs are always available on disk without requiring mcp.json changes.
|
|
270
272
|
setDefault('INDEX_SERVER_DASHBOARD', '1');
|
|
273
|
+
setDefault('INDEX_SERVER_LOG_FILE', '1');
|
|
271
274
|
if (profile === 'enhanced') {
|
|
272
275
|
setDefault('INDEX_SERVER_SEMANTIC_ENABLED', '1');
|
|
273
276
|
setDefault('INDEX_SERVER_SEMANTIC_LOCAL_ONLY', '0');
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
|
-
<meta name="dashboard-build-version" content="1.26.
|
|
4
|
+
<meta name="dashboard-build-version" content="1.26.11-9badd8dd">
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>Index Server Admin</title>
|
|
8
|
-
<link rel="stylesheet" href="css/admin.css?v=1.26.
|
|
9
|
-
<script defer src="js/admin.utils.js?v=1.26.
|
|
10
|
-
<script defer src="js/admin.auth.js?v=1.26.
|
|
11
|
-
<script defer src="js/admin.overview.js?v=1.26.
|
|
12
|
-
<script defer src="js/admin.sessions.js?v=1.26.
|
|
13
|
-
<script defer src="js/admin.monitor.js?v=1.26.
|
|
14
|
-
<script defer src="js/admin.graph.js?v=1.26.
|
|
8
|
+
<link rel="stylesheet" href="css/admin.css?v=1.26.11-9badd8dd">
|
|
9
|
+
<script defer src="js/admin.utils.js?v=1.26.11-9badd8dd"></script>
|
|
10
|
+
<script defer src="js/admin.auth.js?v=1.26.11-9badd8dd"></script>
|
|
11
|
+
<script defer src="js/admin.overview.js?v=1.26.11-9badd8dd"></script>
|
|
12
|
+
<script defer src="js/admin.sessions.js?v=1.26.11-9badd8dd"></script>
|
|
13
|
+
<script defer src="js/admin.monitor.js?v=1.26.11-9badd8dd"></script>
|
|
14
|
+
<script defer src="js/admin.graph.js?v=1.26.11-9badd8dd"></script>
|
|
15
15
|
<script defer src="js/marked.umd.js"></script>
|
|
16
|
-
<script defer src="js/admin.instructions.js?v=1.26.
|
|
17
|
-
<script defer src="js/admin.logs.js?v=1.26.
|
|
18
|
-
<script defer src="js/admin.maintenance.js?v=1.26.
|
|
19
|
-
<script defer src="js/admin.config.js?v=1.26.
|
|
20
|
-
<script defer src="js/admin.performance.js?v=1.26.
|
|
21
|
-
<script defer src="js/admin.instances.js?v=1.26.
|
|
22
|
-
<script defer src="js/admin.embeddings.js?v=1.26.
|
|
23
|
-
<script defer src="js/admin.messaging.js?v=1.26.
|
|
24
|
-
<script defer src="js/admin.sqlite.js?v=1.26.
|
|
25
|
-
<script defer src="js/admin.boot.js?v=1.26.
|
|
26
|
-
<script defer src="js/admin.feedback.js?v=1.26.
|
|
16
|
+
<script defer src="js/admin.instructions.js?v=1.26.11-9badd8dd"></script>
|
|
17
|
+
<script defer src="js/admin.logs.js?v=1.26.11-9badd8dd"></script>
|
|
18
|
+
<script defer src="js/admin.maintenance.js?v=1.26.11-9badd8dd"></script>
|
|
19
|
+
<script defer src="js/admin.config.js?v=1.26.11-9badd8dd"></script>
|
|
20
|
+
<script defer src="js/admin.performance.js?v=1.26.11-9badd8dd"></script>
|
|
21
|
+
<script defer src="js/admin.instances.js?v=1.26.11-9badd8dd"></script>
|
|
22
|
+
<script defer src="js/admin.embeddings.js?v=1.26.11-9badd8dd"></script>
|
|
23
|
+
<script defer src="js/admin.messaging.js?v=1.26.11-9badd8dd"></script>
|
|
24
|
+
<script defer src="js/admin.sqlite.js?v=1.26.11-9badd8dd"></script>
|
|
25
|
+
<script defer src="js/admin.boot.js?v=1.26.11-9badd8dd"></script>
|
|
26
|
+
<script defer src="js/admin.feedback.js?v=1.26.11-9badd8dd"></script>
|
|
27
27
|
</head>
|
|
28
28
|
<body>
|
|
29
29
|
<div class="admin-container admin-root">
|
|
@@ -879,10 +879,10 @@
|
|
|
879
879
|
}
|
|
880
880
|
}
|
|
881
881
|
|
|
882
|
-
// Graph logic was extracted to js/admin.graph.js?v=1.26.
|
|
882
|
+
// Graph logic was extracted to js/admin.graph.js?v=1.26.11-9badd8dd
|
|
883
883
|
// Functions available globally: reloadGraphMermaid, initGraphScopeDefaults, copyMermaidSource, toggleGraphEdit, applyGraphEdit, cancelGraphEdit, refreshDrillCategories, loadDrillInstructions, clearSelections
|
|
884
884
|
|
|
885
|
-
<!-- overview functions moved to js/admin.overview.js?v=1.26.
|
|
885
|
+
<!-- overview functions moved to js/admin.overview.js?v=1.26.11-9badd8dd -->
|
|
886
886
|
|
|
887
887
|
// Lightweight overview-level maintenance display (optional)
|
|
888
888
|
// Intentionally minimal to avoid blocking overview rendering.
|
|
@@ -1067,7 +1067,7 @@
|
|
|
1067
1067
|
}
|
|
1068
1068
|
|
|
1069
1069
|
// --- Backup / Restore ---
|
|
1070
|
-
// Extracted to js/admin.maintenance.js?v=1.26.
|
|
1070
|
+
// Extracted to js/admin.maintenance.js?v=1.26.11-9badd8dd
|
|
1071
1071
|
|
|
1072
1072
|
async function performBackup() {
|
|
1073
1073
|
try {
|
|
@@ -1133,7 +1133,7 @@
|
|
|
1133
1133
|
}
|
|
1134
1134
|
|
|
1135
1135
|
async function loadConfiguration() {
|
|
1136
|
-
// Primary implementation in js/admin.config.js?v=1.26.
|
|
1136
|
+
// Primary implementation in js/admin.config.js?v=1.26.11-9badd8dd (loaded via defer).
|
|
1137
1137
|
// This inline fallback only fires if the external script failed to load.
|
|
1138
1138
|
if (window.__configExternalLoaded) return;
|
|
1139
1139
|
try {
|
|
@@ -1193,10 +1193,10 @@
|
|
|
1193
1193
|
return false;
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
|
-
// Monitoring functions moved to js/admin.monitor.js?v=1.26.
|
|
1196
|
+
// Monitoring functions moved to js/admin.monitor.js?v=1.26.11-9badd8dd
|
|
1197
1197
|
|
|
1198
1198
|
// ===== Log Viewer =====
|
|
1199
|
-
// Extracted to js/admin.logs.js?v=1.26.
|
|
1199
|
+
// Extracted to js/admin.logs.js?v=1.26.11-9badd8dd
|
|
1200
1200
|
|
|
1201
1201
|
// ===== Instruction Management =====
|
|
1202
1202
|
let instructionEditing = null;
|
|
@@ -1693,7 +1693,7 @@
|
|
|
1693
1693
|
setInterval(fetchResourceTrends, 10000);
|
|
1694
1694
|
})();
|
|
1695
1695
|
|
|
1696
|
-
// Instruction management logic extracted to js/admin.instructions.js?v=1.26.
|
|
1696
|
+
// Instruction management logic extracted to js/admin.instructions.js?v=1.26.11-9badd8dd
|
|
1697
1697
|
// Functions exposed globally: loadInstructions, renderInstructionList, editInstruction, saveInstruction, deleteInstruction, etc.
|
|
1698
1698
|
|
|
1699
1699
|
function startAutoRefresh() {
|
|
@@ -112,6 +112,7 @@ export declare class McpStdioLogger {
|
|
|
112
112
|
private readonly _serverName;
|
|
113
113
|
private readonly _maxBufferSize;
|
|
114
114
|
private readonly _inferLevel;
|
|
115
|
+
private readonly _exitHandler;
|
|
115
116
|
constructor(options?: McpStdioLoggerOptions);
|
|
116
117
|
/**
|
|
117
118
|
* Start intercepting process.stderr.write.
|
|
@@ -152,6 +153,17 @@ export declare class McpStdioLogger {
|
|
|
152
153
|
* without triggering the MCP routing/buffering pipeline.
|
|
153
154
|
*/
|
|
154
155
|
writeOriginalStderr(data: string): void;
|
|
156
|
+
/**
|
|
157
|
+
* Drain the pre-handshake buffer to the ORIGINAL stderr without requiring
|
|
158
|
+
* an MCP server activation. Used when the process is about to exit before
|
|
159
|
+
* the MCP handshake completes (e.g., `--help`, `--init-cert`, or a fatal
|
|
160
|
+
* startup error). Without this, buffered diagnostic lines would be silently
|
|
161
|
+
* discarded — see issue #234.
|
|
162
|
+
*
|
|
163
|
+
* Safe to call repeatedly; a no-op when the buffer is empty. Does not change
|
|
164
|
+
* `isActive` and does not require a registered server.
|
|
165
|
+
*/
|
|
166
|
+
flushToStderr(): void;
|
|
155
167
|
/**
|
|
156
168
|
* Restore original stderr and deactivate the bridge.
|
|
157
169
|
* Useful for testing cleanup or graceful shutdown.
|
|
@@ -115,6 +115,7 @@ class McpStdioLogger {
|
|
|
115
115
|
_serverName;
|
|
116
116
|
_maxBufferSize;
|
|
117
117
|
_inferLevel;
|
|
118
|
+
_exitHandler;
|
|
118
119
|
constructor(options = {}) {
|
|
119
120
|
this._serverName = options.serverName ?? 'mcp-server';
|
|
120
121
|
this._maxBufferSize = options.maxBufferSize ?? 500;
|
|
@@ -123,6 +124,16 @@ class McpStdioLogger {
|
|
|
123
124
|
if (options.interceptImmediately !== false) {
|
|
124
125
|
this.interceptStderr();
|
|
125
126
|
}
|
|
127
|
+
// Issue #234: if the process exits before the MCP handshake completes
|
|
128
|
+
// (e.g. --help, --init-cert, fatal startup error), drain buffered stderr
|
|
129
|
+
// so the user actually sees what was logged. Inactive bridge only —
|
|
130
|
+
// once activated, the buffer is already replayed via sendLoggingMessage.
|
|
131
|
+
this._exitHandler = () => {
|
|
132
|
+
if (!this._active && this._buffer.length > 0) {
|
|
133
|
+
this.flushToStderr();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
process.on('exit', this._exitHandler);
|
|
126
137
|
}
|
|
127
138
|
// -----------------------------------------------------------------------
|
|
128
139
|
// Public API
|
|
@@ -241,6 +252,30 @@ class McpStdioLogger {
|
|
|
241
252
|
}
|
|
242
253
|
catch { /* ignore */ }
|
|
243
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Drain the pre-handshake buffer to the ORIGINAL stderr without requiring
|
|
257
|
+
* an MCP server activation. Used when the process is about to exit before
|
|
258
|
+
* the MCP handshake completes (e.g., `--help`, `--init-cert`, or a fatal
|
|
259
|
+
* startup error). Without this, buffered diagnostic lines would be silently
|
|
260
|
+
* discarded — see issue #234.
|
|
261
|
+
*
|
|
262
|
+
* Safe to call repeatedly; a no-op when the buffer is empty. Does not change
|
|
263
|
+
* `isActive` and does not require a registered server.
|
|
264
|
+
*/
|
|
265
|
+
flushToStderr() {
|
|
266
|
+
if (this._buffer.length === 0)
|
|
267
|
+
return;
|
|
268
|
+
for (const entry of this._buffer) {
|
|
269
|
+
const data = entry.data;
|
|
270
|
+
try {
|
|
271
|
+
this._originalStderrWrite(data.endsWith('\n') ? data : data + '\n');
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
/* ignore — best-effort drain */
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
this._buffer.length = 0;
|
|
278
|
+
}
|
|
244
279
|
/**
|
|
245
280
|
* Restore original stderr and deactivate the bridge.
|
|
246
281
|
* Useful for testing cleanup or graceful shutdown.
|
|
@@ -250,6 +285,10 @@ class McpStdioLogger {
|
|
|
250
285
|
this._active = false;
|
|
251
286
|
this._intercepting = false;
|
|
252
287
|
this._buffer.length = 0;
|
|
288
|
+
try {
|
|
289
|
+
process.removeListener('exit', this._exitHandler);
|
|
290
|
+
}
|
|
291
|
+
catch { /* ignore */ }
|
|
253
292
|
}
|
|
254
293
|
/**
|
|
255
294
|
* Get the number of currently buffered lines (pre-handshake).
|
package/dist/services/logger.js
CHANGED
|
@@ -26,6 +26,7 @@ const LEVEL_PRIORITY = {
|
|
|
26
26
|
*/
|
|
27
27
|
function newCorrelationId() { return crypto_1.default.randomBytes(8).toString('hex'); }
|
|
28
28
|
let logFileHandle = null;
|
|
29
|
+
let logFilePath;
|
|
29
30
|
function loggingCfg() {
|
|
30
31
|
return (0, runtimeConfig_1.getRuntimeConfig)().logging;
|
|
31
32
|
}
|
|
@@ -59,12 +60,25 @@ function shouldEmit(level) {
|
|
|
59
60
|
const recordPriority = LEVEL_PRIORITY[level] ?? LEVEL_PRIORITY.INFO;
|
|
60
61
|
return recordPriority >= threshold;
|
|
61
62
|
}
|
|
63
|
+
// Single exit handler — registered once to avoid listener accumulation on re-init
|
|
64
|
+
let exitHandlerRegistered = false;
|
|
62
65
|
// Initialize file logging if INDEX_SERVER_LOG_FILE is specified
|
|
63
66
|
function initializeFileLogging() {
|
|
64
67
|
const cfg = loggingCfg();
|
|
65
68
|
const logFile = cfg.file;
|
|
66
|
-
if (!logFile
|
|
67
|
-
return;
|
|
69
|
+
if (!logFile)
|
|
70
|
+
return;
|
|
71
|
+
// If file handle exists but path changed (e.g., test reconfiguration), close old and reopen
|
|
72
|
+
if (logFileHandle && logFilePath !== logFile) {
|
|
73
|
+
try {
|
|
74
|
+
logFileHandle.end();
|
|
75
|
+
}
|
|
76
|
+
catch { /* ignore */ }
|
|
77
|
+
logFileHandle = null;
|
|
78
|
+
logFilePath = undefined;
|
|
79
|
+
}
|
|
80
|
+
if (logFileHandle)
|
|
81
|
+
return; // Already initialized for this path
|
|
68
82
|
try {
|
|
69
83
|
// Ensure log directory exists
|
|
70
84
|
const logDir = path_1.default.dirname(logFile);
|
|
@@ -76,6 +90,17 @@ function initializeFileLogging() {
|
|
|
76
90
|
flags: 'a',
|
|
77
91
|
encoding: 'utf8'
|
|
78
92
|
});
|
|
93
|
+
logFilePath = logFile;
|
|
94
|
+
// Graceful fallback: if the stream hits EPERM/EACCES, disable file logging
|
|
95
|
+
logFileHandle.on('error', (err) => {
|
|
96
|
+
process.stderr.write(`[logger] File logging error (${err.code ?? err.message}), falling back to stderr-only\n`);
|
|
97
|
+
try {
|
|
98
|
+
logFileHandle?.end();
|
|
99
|
+
}
|
|
100
|
+
catch { /* ignore */ }
|
|
101
|
+
logFileHandle = null;
|
|
102
|
+
logFilePath = undefined;
|
|
103
|
+
});
|
|
79
104
|
// NDJSON session start record
|
|
80
105
|
const sessionStart = {
|
|
81
106
|
ts: new Date().toISOString(),
|
|
@@ -85,19 +110,22 @@ function initializeFileLogging() {
|
|
|
85
110
|
};
|
|
86
111
|
const startLine = JSON.stringify(sessionStart);
|
|
87
112
|
logFileHandle.write(startLine + '\n');
|
|
88
|
-
// Cleanup on process exit
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
// Cleanup on process exit — register only once
|
|
114
|
+
if (!exitHandlerRegistered) {
|
|
115
|
+
exitHandlerRegistered = true;
|
|
116
|
+
process.on('exit', () => {
|
|
117
|
+
if (logFileHandle && !logFileHandle.destroyed) {
|
|
118
|
+
const sessionEnd = {
|
|
119
|
+
ts: new Date().toISOString(),
|
|
120
|
+
level: 'INFO',
|
|
121
|
+
msg: '[logger] Session ended',
|
|
122
|
+
pid: process.pid,
|
|
123
|
+
};
|
|
124
|
+
logFileHandle.write(JSON.stringify(sessionEnd) + '\n');
|
|
125
|
+
logFileHandle.end();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
101
129
|
// Emit NDJSON init diagnostic
|
|
102
130
|
try {
|
|
103
131
|
const stats = fs_1.default.existsSync(logFile) ? fs_1.default.statSync(logFile) : null;
|
|
@@ -153,8 +181,9 @@ function emit(rec) {
|
|
|
153
181
|
if (!shouldEmit(rec.level))
|
|
154
182
|
return;
|
|
155
183
|
// Initialize file logging on first emit (lazy initialization)
|
|
184
|
+
// or reinitialize if the configured path has changed (e.g., test reconfiguration)
|
|
156
185
|
const cfg = loggingCfg();
|
|
157
|
-
if (!logFileHandle
|
|
186
|
+
if (cfg.file && (!logFileHandle || logFilePath !== cfg.file)) {
|
|
158
187
|
initializeFileLogging();
|
|
159
188
|
}
|
|
160
189
|
// Always include pid for multi-process identification
|
|
@@ -23,15 +23,18 @@ export declare function registerMcpServer(server: any): void;
|
|
|
23
23
|
* Activate the bridge so subsequent log calls are routed via MCP protocol.
|
|
24
24
|
* Replays any buffered pre-handshake stderr lines through the protocol.
|
|
25
25
|
* Called from `emitReadyGlobal()` after the handshake completes.
|
|
26
|
+
*
|
|
27
|
+
* No-op when STDERR_BRIDGE_ENABLED is false (default).
|
|
26
28
|
*/
|
|
27
29
|
export declare function activateMcpLogBridge(): void;
|
|
28
30
|
/**
|
|
29
31
|
* Returns true if the bridge is active and logs will be sent via MCP protocol.
|
|
32
|
+
* Always false when STDERR_BRIDGE_ENABLED is false (default).
|
|
30
33
|
*/
|
|
31
34
|
export declare function isMcpLogBridgeActive(): boolean;
|
|
32
35
|
/**
|
|
33
36
|
* Send a log message through the MCP `notifications/message` protocol.
|
|
34
|
-
* No-op if the bridge is not yet active.
|
|
37
|
+
* No-op if the bridge is not yet active or not enabled.
|
|
35
38
|
*
|
|
36
39
|
* @param level - The index-server log level (TRACE, DEBUG, INFO, WARN, ERROR)
|
|
37
40
|
* @param data - The log payload (typically the NDJSON string)
|
|
@@ -29,10 +29,25 @@ const LEVEL_MAP = {
|
|
|
29
29
|
WARN: 'warning',
|
|
30
30
|
ERROR: 'error',
|
|
31
31
|
};
|
|
32
|
-
//
|
|
32
|
+
// Bridge default: DISABLED.
|
|
33
|
+
//
|
|
34
|
+
// History: enabling-by-default routed all logs through MCP `notifications/message`
|
|
35
|
+
// and suppressed raw stderr. VS Code Insiders does not surface those notifications
|
|
36
|
+
// in any visible output channel, so the net effect for that client was complete
|
|
37
|
+
// log silence (regression first observed after commit 5de6662 / v1.26.4).
|
|
38
|
+
//
|
|
39
|
+
// Default is now off: stderr flows raw, which every MCP-aware client (including
|
|
40
|
+
// VS Code Insiders' `[server stderr]` rendering) handles. Set
|
|
41
|
+
// INDEX_SERVER_ENABLE_STDERR_BRIDGE=1 to opt in to the protocol-level routing
|
|
42
|
+
// (preferred for clients that render `notifications/message` with proper severity).
|
|
43
|
+
//
|
|
44
|
+
// The legacy INDEX_SERVER_DISABLE_STDERR_BRIDGE variable is now a no-op (the
|
|
45
|
+
// new default already matches what setting it did).
|
|
46
|
+
const STDERR_BRIDGE_ENABLED = process.env.INDEX_SERVER_ENABLE_STDERR_BRIDGE === '1';
|
|
47
|
+
// Singleton instance — intercepts stderr only when the bridge is opted in.
|
|
33
48
|
const _logger = new mcpStdioLogging_1.McpStdioLogger({
|
|
34
49
|
serverName: 'index-server',
|
|
35
|
-
interceptImmediately:
|
|
50
|
+
interceptImmediately: STDERR_BRIDGE_ENABLED,
|
|
36
51
|
maxBufferSize: 500,
|
|
37
52
|
});
|
|
38
53
|
/**
|
|
@@ -47,24 +62,31 @@ function registerMcpServer(server) {
|
|
|
47
62
|
* Activate the bridge so subsequent log calls are routed via MCP protocol.
|
|
48
63
|
* Replays any buffered pre-handshake stderr lines through the protocol.
|
|
49
64
|
* Called from `emitReadyGlobal()` after the handshake completes.
|
|
65
|
+
*
|
|
66
|
+
* No-op when STDERR_BRIDGE_ENABLED is false (default).
|
|
50
67
|
*/
|
|
51
68
|
function activateMcpLogBridge() {
|
|
69
|
+
if (!STDERR_BRIDGE_ENABLED)
|
|
70
|
+
return;
|
|
52
71
|
_logger.activate();
|
|
53
72
|
}
|
|
54
73
|
/**
|
|
55
74
|
* Returns true if the bridge is active and logs will be sent via MCP protocol.
|
|
75
|
+
* Always false when STDERR_BRIDGE_ENABLED is false (default).
|
|
56
76
|
*/
|
|
57
77
|
function isMcpLogBridgeActive() {
|
|
58
|
-
return _logger.isActive;
|
|
78
|
+
return STDERR_BRIDGE_ENABLED && _logger.isActive;
|
|
59
79
|
}
|
|
60
80
|
/**
|
|
61
81
|
* Send a log message through the MCP `notifications/message` protocol.
|
|
62
|
-
* No-op if the bridge is not yet active.
|
|
82
|
+
* No-op if the bridge is not yet active or not enabled.
|
|
63
83
|
*
|
|
64
84
|
* @param level - The index-server log level (TRACE, DEBUG, INFO, WARN, ERROR)
|
|
65
85
|
* @param data - The log payload (typically the NDJSON string)
|
|
66
86
|
*/
|
|
67
87
|
function sendMcpLog(level, data) {
|
|
88
|
+
if (!STDERR_BRIDGE_ENABLED)
|
|
89
|
+
return;
|
|
68
90
|
_logger.log(LEVEL_MAP[level] ?? 'info', data);
|
|
69
91
|
}
|
|
70
92
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jagilber-org/index-server",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.11",
|
|
4
4
|
"mcpName": "io.github.jagilber-org/index-server",
|
|
5
5
|
"description": "MCP instruction indexing server for AI assistant governance — search, CRUD, schema validation, usage tracking, and cross-repo knowledge promotion.",
|
|
6
6
|
"publishConfig": {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"manifest:generate": "node scripts/generate-manifest.mjs",
|
|
51
51
|
"contract:tools": "node scripts/generate-tools-snapshot.mjs",
|
|
52
52
|
"verify:manifest": "node scripts/verify-manifest.mjs",
|
|
53
|
+
"check:version-parity": "node scripts/check-version-parity.mjs",
|
|
53
54
|
"test": "npm run test:fast",
|
|
54
55
|
"test:all": "vitest run",
|
|
55
56
|
"test:watch": "vitest",
|
package/scripts/setup-wizard.mjs
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import fs from 'fs';
|
|
21
21
|
import path from 'path';
|
|
22
22
|
import os from 'os';
|
|
23
|
+
import { execFileSync } from 'child_process';
|
|
23
24
|
import { fileURLToPath } from 'url';
|
|
24
25
|
import { select, input, confirm, checkbox } from '@inquirer/prompts';
|
|
25
26
|
|
|
@@ -27,12 +28,90 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
27
28
|
const ROOT = path.resolve(__dirname, '..');
|
|
28
29
|
const IS_WINDOWS = process.platform === 'win32';
|
|
29
30
|
|
|
31
|
+
// --------------------------------------------------------------------------
|
|
32
|
+
// Launch spec resolver — determines how to invoke index-server at runtime.
|
|
33
|
+
//
|
|
34
|
+
// Returns { command, args, cwd, source } where source indicates the mode:
|
|
35
|
+
// 'local' — dist/ found at config.root (dev checkout)
|
|
36
|
+
// 'packaged' — dist/ found in the package ROOT but not config.root (npx install)
|
|
37
|
+
// 'npx' — fallback when no dist/ found anywhere
|
|
38
|
+
// --------------------------------------------------------------------------
|
|
39
|
+
function resolveServerLaunch(config) {
|
|
40
|
+
const entryRelative = 'dist/server/index-server.js';
|
|
41
|
+
const localEntry = path.join(config.root, entryRelative);
|
|
42
|
+
const packagedEntry = path.join(ROOT, entryRelative);
|
|
43
|
+
|
|
44
|
+
// Case 1: config.root is the repo checkout with dist/ present
|
|
45
|
+
if (fs.existsSync(localEntry)) {
|
|
46
|
+
return {
|
|
47
|
+
command: 'node',
|
|
48
|
+
args: [entryRelative],
|
|
49
|
+
cwd: config.root,
|
|
50
|
+
source: 'local',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Case 2: dist/ exists in the package root (npx cache) but not in config.root
|
|
55
|
+
if (fs.existsSync(packagedEntry)) {
|
|
56
|
+
return {
|
|
57
|
+
command: 'node',
|
|
58
|
+
args: [fwd(packagedEntry)],
|
|
59
|
+
cwd: config.root,
|
|
60
|
+
source: 'packaged',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Case 3: no dist/ anywhere — use npx as last resort
|
|
65
|
+
let pkgName = '@jagilber-org/index-server';
|
|
66
|
+
let pkgVersion = '';
|
|
67
|
+
try {
|
|
68
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
69
|
+
if (pkg.name) pkgName = pkg.name;
|
|
70
|
+
if (pkg.version) pkgVersion = `@${pkg.version}`;
|
|
71
|
+
} catch {
|
|
72
|
+
console.warn('⚠ Could not read package.json — npx will use latest published version');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
command: 'npx',
|
|
77
|
+
args: ['-y', `${pkgName}${pkgVersion}`],
|
|
78
|
+
cwd: config.root,
|
|
79
|
+
source: 'npx',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
30
83
|
// --------------------------------------------------------------------------
|
|
31
84
|
// Path helpers
|
|
32
85
|
// --------------------------------------------------------------------------
|
|
33
86
|
/** Normalize to forward slashes for mcp.json compatibility. */
|
|
34
87
|
function fwd(p) { return p.replace(/\\/g, '/'); }
|
|
35
88
|
|
|
89
|
+
/** Locate the npm CLI script for execFileSync(node, [npmCli, ...]). Returns null if not found. */
|
|
90
|
+
function findNpmCli() {
|
|
91
|
+
// npm_execpath is set when running via npm/npx
|
|
92
|
+
if (process.env.npm_execpath) return process.env.npm_execpath;
|
|
93
|
+
// Resolve npm relative to the Node.js installation
|
|
94
|
+
const candidates = [
|
|
95
|
+
path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
96
|
+
path.join(path.dirname(process.execPath), '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
97
|
+
];
|
|
98
|
+
for (const c of candidates) {
|
|
99
|
+
if (fs.existsSync(c)) return c;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Run an npm command. Uses npm CLI script if found, otherwise shells out to `npm` on PATH. */
|
|
105
|
+
function runNpm(args, opts = {}) {
|
|
106
|
+
const npmCli = findNpmCli();
|
|
107
|
+
if (npmCli) {
|
|
108
|
+
return execFileSync(process.execPath, [npmCli, ...args], opts);
|
|
109
|
+
}
|
|
110
|
+
// Fallback: invoke `npm` directly via execFileSync (uses npm.cmd on Windows).
|
|
111
|
+
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
112
|
+
return execFileSync(npmBin, args, opts);
|
|
113
|
+
}
|
|
114
|
+
|
|
36
115
|
/** Resolve a sub-path under a root, always absolute and forward-slashed. */
|
|
37
116
|
function resolveUnder(root, ...segments) { return fwd(path.resolve(root, ...segments)); }
|
|
38
117
|
|
|
@@ -120,6 +199,7 @@ function parseNonInteractiveArgs() {
|
|
|
120
199
|
scope: 'repo', // 'global' or 'repo'
|
|
121
200
|
write: false, // write to real config files
|
|
122
201
|
preview: true, // show preview before writing
|
|
202
|
+
deploy: true, // deploy runtime to root when needed
|
|
123
203
|
};
|
|
124
204
|
|
|
125
205
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -136,6 +216,7 @@ function parseNonInteractiveArgs() {
|
|
|
136
216
|
else if (args[i] === '--scope' && args[i + 1]) config.scope = args[++i];
|
|
137
217
|
else if (args[i] === '--write') config.write = true;
|
|
138
218
|
else if (args[i] === '--no-preview') config.preview = false;
|
|
219
|
+
else if (args[i] === '--no-deploy') config.deploy = false;
|
|
139
220
|
}
|
|
140
221
|
|
|
141
222
|
// Profile overrides
|
|
@@ -253,7 +334,7 @@ async function runInteractiveWizard() {
|
|
|
253
334
|
default: 'repo',
|
|
254
335
|
});
|
|
255
336
|
|
|
256
|
-
return { profile, root, serverName, port, host, mutation, logLevel, generateCerts, targets, scope, write: true, preview: true };
|
|
337
|
+
return { profile, root, serverName, port, host, mutation, logLevel, generateCerts, targets, scope, write: true, preview: true, deploy: true };
|
|
257
338
|
}
|
|
258
339
|
|
|
259
340
|
// --------------------------------------------------------------------------
|
|
@@ -362,15 +443,17 @@ function getEnvCatalog(config, paths) {
|
|
|
362
443
|
function generateMcpJson(config, paths) {
|
|
363
444
|
const catalog = getEnvCatalog(config, paths);
|
|
364
445
|
const indent = '\t\t\t\t';
|
|
446
|
+
const launch = resolveServerLaunch(config);
|
|
447
|
+
const argsJson = JSON.stringify(launch.args);
|
|
365
448
|
|
|
366
449
|
const lines = [
|
|
367
450
|
'{',
|
|
368
451
|
'\t"servers": {',
|
|
369
452
|
`\t\t"${config.serverName}": {`,
|
|
370
453
|
'\t\t\t"type": "stdio",',
|
|
371
|
-
`\t\t\t"cwd": "${fwd(
|
|
372
|
-
|
|
373
|
-
|
|
454
|
+
`\t\t\t"cwd": "${fwd(launch.cwd)}",`,
|
|
455
|
+
`\t\t\t"command": "${launch.command}",`,
|
|
456
|
+
`\t\t\t"args": ${argsJson},`,
|
|
374
457
|
'\t\t\t"env": {',
|
|
375
458
|
];
|
|
376
459
|
|
|
@@ -412,11 +495,16 @@ function generateCopilotCliJson(config, paths) {
|
|
|
412
495
|
if (entry.section) continue;
|
|
413
496
|
if (entry.active) env[entry.key] = entry.value;
|
|
414
497
|
}
|
|
498
|
+
const launch = resolveServerLaunch(config);
|
|
499
|
+
// copilot-cli doesn't reliably inherit cwd — use absolute args for local/packaged
|
|
500
|
+
const args = launch.source === 'local'
|
|
501
|
+
? [fwd(path.resolve(launch.cwd, launch.args[0]))]
|
|
502
|
+
: launch.args;
|
|
415
503
|
const obj = {
|
|
416
504
|
mcpServers: {
|
|
417
505
|
[config.serverName]: {
|
|
418
|
-
command:
|
|
419
|
-
args
|
|
506
|
+
command: launch.command,
|
|
507
|
+
args,
|
|
420
508
|
env,
|
|
421
509
|
},
|
|
422
510
|
},
|
|
@@ -434,11 +522,16 @@ function generateClaudeDesktopJson(config, paths) {
|
|
|
434
522
|
if (entry.section) continue;
|
|
435
523
|
if (entry.active) env[entry.key] = entry.value;
|
|
436
524
|
}
|
|
525
|
+
const launch = resolveServerLaunch(config);
|
|
526
|
+
// Claude Desktop doesn't support cwd — use absolute args for local/packaged
|
|
527
|
+
const args = launch.source === 'local'
|
|
528
|
+
? [fwd(path.resolve(launch.cwd, launch.args[0]))]
|
|
529
|
+
: launch.args;
|
|
437
530
|
const obj = {
|
|
438
531
|
mcpServers: {
|
|
439
532
|
[config.serverName]: {
|
|
440
|
-
command:
|
|
441
|
-
args
|
|
533
|
+
command: launch.command,
|
|
534
|
+
args,
|
|
442
535
|
env,
|
|
443
536
|
},
|
|
444
537
|
},
|
|
@@ -620,6 +713,143 @@ function printFolderSummary(paths, profile) {
|
|
|
620
713
|
console.log('└────────────────────┴────────────────────────────────────────────────┘');
|
|
621
714
|
}
|
|
622
715
|
|
|
716
|
+
// --------------------------------------------------------------------------
|
|
717
|
+
// Deploy runtime to target root (when different from package root)
|
|
718
|
+
// --------------------------------------------------------------------------
|
|
719
|
+
async function deployRuntime(config) {
|
|
720
|
+
if (config.deploy === false) return;
|
|
721
|
+
|
|
722
|
+
let sourceRoot, targetRoot;
|
|
723
|
+
try {
|
|
724
|
+
sourceRoot = fs.realpathSync(ROOT);
|
|
725
|
+
targetRoot = fs.realpathSync(config.root);
|
|
726
|
+
} catch {
|
|
727
|
+
sourceRoot = path.resolve(ROOT);
|
|
728
|
+
targetRoot = path.resolve(config.root);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Skip when running from the target directory (dev clone / already deployed)
|
|
732
|
+
if (sourceRoot.toLowerCase() === targetRoot.toLowerCase()) return;
|
|
733
|
+
|
|
734
|
+
const entryPoint = path.join(targetRoot, 'dist', 'server', 'index-server.js');
|
|
735
|
+
const targetPkg = path.join(targetRoot, 'package.json');
|
|
736
|
+
|
|
737
|
+
// Read source version for comparison
|
|
738
|
+
let sourceVersion = 'unknown';
|
|
739
|
+
try {
|
|
740
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
741
|
+
sourceVersion = pkg.version || 'unknown';
|
|
742
|
+
} catch { /* ok */ }
|
|
743
|
+
|
|
744
|
+
// Check if already deployed at this version
|
|
745
|
+
if (fs.existsSync(entryPoint) && fs.existsSync(targetPkg)) {
|
|
746
|
+
try {
|
|
747
|
+
const existing = JSON.parse(fs.readFileSync(targetPkg, 'utf8'));
|
|
748
|
+
if (existing.version === sourceVersion) {
|
|
749
|
+
console.log(`\n✅ Runtime v${sourceVersion} already deployed at ${config.root}`);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
console.log(`\n📦 Upgrading runtime: ${existing.version} → ${sourceVersion}`);
|
|
753
|
+
} catch { /* ok - redeploy */ }
|
|
754
|
+
} else {
|
|
755
|
+
console.log(`\n📦 Deploying runtime v${sourceVersion} to ${config.root}...`);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const pkgName = '@jagilber-org/index-server';
|
|
759
|
+
|
|
760
|
+
// Strategy: npm install the exact package version into the target directory
|
|
761
|
+
// This gives a proper node_modules tree regardless of npx cache layout
|
|
762
|
+
try {
|
|
763
|
+
fs.mkdirSync(targetRoot, { recursive: true });
|
|
764
|
+
|
|
765
|
+
// Write a minimal package.json if none exists (npm install needs it)
|
|
766
|
+
if (!fs.existsSync(targetPkg)) {
|
|
767
|
+
const minPkg = {
|
|
768
|
+
name: 'index-server-runtime',
|
|
769
|
+
version: '1.0.0',
|
|
770
|
+
private: true,
|
|
771
|
+
type: 'commonjs',
|
|
772
|
+
scripts: { start: 'node node_modules/@jagilber-org/index-server/dist/server/index-server.js' },
|
|
773
|
+
};
|
|
774
|
+
fs.writeFileSync(targetPkg, JSON.stringify(minPkg, null, 2), 'utf8');
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
console.log(' Installing package (this may take a moment)...');
|
|
778
|
+
|
|
779
|
+
// Strategy: pack the current package into a tarball, then install it.
|
|
780
|
+
// This works regardless of whether the version is published to npm,
|
|
781
|
+
// and produces a proper self-contained node_modules tree.
|
|
782
|
+
const packOutput = runNpm(
|
|
783
|
+
['pack', '--pack-destination', targetRoot],
|
|
784
|
+
{ cwd: ROOT, stdio: ['pipe', 'pipe', 'inherit'], timeout: 30_000 }
|
|
785
|
+
).toString().trim();
|
|
786
|
+
const tarballName = packOutput.split('\n').pop();
|
|
787
|
+
|
|
788
|
+
const tarballPath = path.join(targetRoot, tarballName);
|
|
789
|
+
|
|
790
|
+
try {
|
|
791
|
+
// Install from the local tarball
|
|
792
|
+
runNpm(
|
|
793
|
+
['install', tarballPath, '--omit=dev', '--no-fund', '--no-audit'],
|
|
794
|
+
{ cwd: targetRoot, stdio: 'inherit', timeout: 120_000 }
|
|
795
|
+
);
|
|
796
|
+
} finally {
|
|
797
|
+
// Clean up tarball
|
|
798
|
+
try { fs.unlinkSync(tarballPath); } catch { /* ok */ }
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Create convenience symlinks/junctions so "dist/" at root resolves
|
|
802
|
+
const installedDist = path.join(targetRoot, 'node_modules', pkgName, 'dist');
|
|
803
|
+
const targetDist = path.join(targetRoot, 'dist');
|
|
804
|
+
if (fs.existsSync(installedDist)) {
|
|
805
|
+
// Remove stale junction/directory before (re)creating — handles broken junctions on Windows
|
|
806
|
+
try { fs.rmSync(targetDist, { recursive: true, force: true }); } catch { /* ok if absent */ }
|
|
807
|
+
try {
|
|
808
|
+
// On Windows, directory junctions don't require elevated privileges
|
|
809
|
+
fs.symlinkSync(installedDist, targetDist, 'junction');
|
|
810
|
+
} catch {
|
|
811
|
+
// Fallback: copy dist recursively
|
|
812
|
+
fs.cpSync(installedDist, targetDist, { recursive: true });
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Copy schemas — refresh on redeploy/upgrade
|
|
817
|
+
const installedSchemas = path.join(targetRoot, 'node_modules', pkgName, 'schemas');
|
|
818
|
+
const targetSchemas = path.join(targetRoot, 'schemas');
|
|
819
|
+
if (fs.existsSync(installedSchemas)) {
|
|
820
|
+
try { fs.rmSync(targetSchemas, { recursive: true, force: true }); } catch { /* ok if absent */ }
|
|
821
|
+
fs.cpSync(installedSchemas, targetSchemas, { recursive: true });
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Update the runtime package.json with correct version/start script
|
|
825
|
+
try {
|
|
826
|
+
const runtimePkg = JSON.parse(fs.readFileSync(targetPkg, 'utf8'));
|
|
827
|
+
runtimePkg.version = sourceVersion;
|
|
828
|
+
runtimePkg.scripts = runtimePkg.scripts || {};
|
|
829
|
+
runtimePkg.scripts.start = 'node dist/server/index-server.js';
|
|
830
|
+
fs.writeFileSync(targetPkg, JSON.stringify(runtimePkg, null, 2) + '\n', 'utf8');
|
|
831
|
+
} catch { /* ok */ }
|
|
832
|
+
|
|
833
|
+
// Verify the deployed server is actually runnable
|
|
834
|
+
const deployedEntry = path.join(targetRoot, 'dist', 'server', 'index-server.js');
|
|
835
|
+
if (!fs.existsSync(deployedEntry)) {
|
|
836
|
+
throw new Error(
|
|
837
|
+
`dist/server/index-server.js not found after deployment. ` +
|
|
838
|
+
`Build the project first: cd "${ROOT}" && npm run build`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
console.log(` ✅ Runtime deployed to ${config.root}`);
|
|
843
|
+
} catch (err) {
|
|
844
|
+
console.error(`\n❌ Runtime deployment failed: ${err.message}`);
|
|
845
|
+
console.error(' To deploy manually, run:');
|
|
846
|
+
console.error(` cd "${config.root}" && npm install ${pkgName}@${sourceVersion}`);
|
|
847
|
+
console.error(' Then create a symlink: dist -> node_modules/@jagilber-org/index-server/dist');
|
|
848
|
+
// Exit with error so CI can detect deployment failures
|
|
849
|
+
process.exitCode = 1;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
623
853
|
// --------------------------------------------------------------------------
|
|
624
854
|
// Main
|
|
625
855
|
// --------------------------------------------------------------------------
|
|
@@ -646,7 +876,8 @@ Non-interactive mode:
|
|
|
646
876
|
--target <list> Comma-separated targets: vscode,copilot-cli,claude
|
|
647
877
|
--scope <s> global | repo (default: repo)
|
|
648
878
|
--write Write directly to real config files (with backup)
|
|
649
|
-
--no-preview Skip config preview in non-interactive mode
|
|
879
|
+
--no-preview Skip config preview in non-interactive mode
|
|
880
|
+
--no-deploy Skip runtime deployment to target root`);
|
|
650
881
|
process.exit(0);
|
|
651
882
|
}
|
|
652
883
|
|
|
@@ -718,11 +949,13 @@ Non-interactive mode:
|
|
|
718
949
|
}
|
|
719
950
|
}
|
|
720
951
|
|
|
952
|
+
// ── Deploy runtime if target root differs from package root ─────────
|
|
953
|
+
await deployRuntime(config);
|
|
954
|
+
|
|
721
955
|
// ── Generate TLS certs ──────────────────────────────────────────────
|
|
722
956
|
if (config.generateCerts) {
|
|
723
957
|
console.log('\n🔐 Generating TLS certificates...');
|
|
724
958
|
try {
|
|
725
|
-
const { execFileSync } = await import('child_process');
|
|
726
959
|
const certDir = path.join(config.root, 'certs');
|
|
727
960
|
execFileSync(
|
|
728
961
|
process.execPath,
|
|
@@ -737,16 +970,27 @@ Non-interactive mode:
|
|
|
737
970
|
|
|
738
971
|
// ── Next steps ──────────────────────────────────────────────────────
|
|
739
972
|
const proto = (config.profile === 'enhanced' || config.profile === 'experimental') ? 'https' : 'http';
|
|
973
|
+
const launch = resolveServerLaunch(config);
|
|
740
974
|
console.log('\n╔════════════════════════════════════════════════════════════════╗');
|
|
741
975
|
console.log('║ Next Steps ║');
|
|
742
976
|
console.log('╚════════════════════════════════════════════════════════════════╝\n');
|
|
743
|
-
|
|
744
|
-
|
|
977
|
+
|
|
978
|
+
let step = 1;
|
|
979
|
+
// Note: `packaged` source means dist/ ships with the wizard package — nothing to
|
|
980
|
+
// build. The packaged-runtime info banner below covers it. Issue #260: do NOT
|
|
981
|
+
// print "npm run build" here because config.root is typically a data-only
|
|
982
|
+
// directory with no package.json (npm run build → ENOENT).
|
|
983
|
+
if (launch.source === 'npx') {
|
|
984
|
+
console.log(` ${step}. The server will be fetched via npx on first start.\n`);
|
|
985
|
+
step++;
|
|
986
|
+
}
|
|
745
987
|
|
|
746
988
|
if (config.write) {
|
|
747
|
-
console.log(
|
|
989
|
+
console.log(` ${step}. Config files have been written. Restart your MCP client.\n`);
|
|
990
|
+
step++;
|
|
748
991
|
} else {
|
|
749
|
-
console.log(
|
|
992
|
+
console.log(` ${step}. Copy generated config into your MCP client settings.`);
|
|
993
|
+
|
|
750
994
|
for (const ct of configTargets) {
|
|
751
995
|
const genPath = ct.format === 'vscode' || ct.format === 'vscode-global'
|
|
752
996
|
? path.join(config.root, '.vscode', 'mcp.json.generated')
|
|
@@ -754,15 +998,18 @@ Non-interactive mode:
|
|
|
754
998
|
console.log(` ${ct.target}: ${genPath}`);
|
|
755
999
|
}
|
|
756
1000
|
console.log('');
|
|
1001
|
+
step++;
|
|
757
1002
|
}
|
|
758
1003
|
|
|
759
|
-
console.log(`
|
|
1004
|
+
console.log(` ${step}. Open the dashboard:`);
|
|
760
1005
|
console.log(` ${proto}://localhost:${config.port}\n`);
|
|
1006
|
+
step++;
|
|
761
1007
|
|
|
762
1008
|
if (config.profile === 'enhanced' || config.profile === 'experimental') {
|
|
763
|
-
console.log(
|
|
1009
|
+
console.log(` ${step}. First-time semantic search:`);
|
|
764
1010
|
console.log(' The MiniLM model (~90MB) will download on first query.');
|
|
765
1011
|
console.log(` Model cache: ${paths.modelCache}\n`);
|
|
1012
|
+
step++;
|
|
766
1013
|
}
|
|
767
1014
|
|
|
768
1015
|
if (config.profile === 'experimental') {
|
|
@@ -770,6 +1017,11 @@ Non-interactive mode:
|
|
|
770
1017
|
console.log(` ${paths.sqliteDb}\n`);
|
|
771
1018
|
}
|
|
772
1019
|
|
|
1020
|
+
if (launch.source === 'packaged') {
|
|
1021
|
+
console.log(' ℹ️ Using packaged runtime from current installation.');
|
|
1022
|
+
console.log(' Rerun without --no-deploy for a self-contained install.\n');
|
|
1023
|
+
}
|
|
1024
|
+
|
|
773
1025
|
console.log(` Targets: ${(config.targets || ['vscode']).join(', ')} | Scope: ${config.scope || 'repo'}`);
|
|
774
1026
|
console.log(` Profile: ${config.profile} | Root: ${fwd(config.root)}`);
|
|
775
1027
|
console.log('');
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/jagilber-org/index-server",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.26.
|
|
9
|
+
"version": "1.26.11",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@jagilber-org/index-server",
|
|
14
|
-
"version": "1.26.
|
|
14
|
+
"version": "1.26.11",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|