@jagilber-org/index-server 1.28.10 → 1.28.19
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 +109 -1
- package/CONTRIBUTING.md +13 -0
- package/README.md +10 -14
- package/dist/config/featureConfig.js +4 -1
- package/dist/dashboard/client/admin.html +69 -29
- package/dist/dashboard/client/js/admin.embeddings.js +97 -5
- package/dist/dashboard/client/js/admin.instructions.js +1 -1
- package/dist/dashboard/server/AdminPanel.js +38 -0
- package/dist/dashboard/server/ApiRoutes.js +14 -1
- package/dist/dashboard/server/routes/embeddings.routes.js +76 -1
- package/dist/dashboard/server/routes/instructions.routes.js +4 -11
- package/dist/dashboard/server/routes/scripts.routes.js +35 -10
- package/dist/dashboard/server/routes/status.routes.js +77 -0
- package/dist/models/instruction.d.ts +2 -1
- package/dist/models/instruction.js +2 -0
- package/dist/schemas/index-server.code-schema.json +52478 -0
- package/dist/schemas/index.d.ts +7 -164
- package/dist/schemas/index.js +45 -63
- package/dist/schemas/instructionSchema.d.ts +46 -0
- package/dist/schemas/instructionSchema.js +159 -0
- package/{schemas → dist/schemas}/json-schema/instruction-content-type.schema.json +6 -4
- package/{schemas → dist/schemas}/json-schema/instruction-instruction-entry.schema.json +6 -4
- package/dist/schemas/manifest.json +78 -0
- package/dist/server/index-server.js +7 -1
- package/dist/services/bootstrapGating.js +2 -2
- package/dist/services/handlers/instructions.add.js +18 -0
- package/dist/services/handlers/instructions.groom.js +6 -1
- package/dist/services/handlers/instructions.import.js +42 -7
- package/dist/services/handlers.activation.js +3 -1
- package/dist/services/handlers.dashboardConfig.js +2 -1
- package/dist/services/handlers.feedback.d.ts +4 -4
- package/dist/services/handlers.feedback.js +390 -27
- package/dist/services/handlers.instructionSchema.js +73 -31
- package/dist/services/handlers.search.js +11 -6
- package/dist/services/indexLoader.js +7 -0
- package/dist/services/instructionRecordValidation.js +32 -84
- package/dist/services/mcpConfig/flagCatalog.d.ts +1 -1
- package/dist/services/mcpConfig/flagCatalog.js +2 -0
- package/dist/services/mcpConfig/formats.js +2 -6
- package/dist/services/seedBootstrap.contentModel.d.ts +13 -0
- package/dist/services/seedBootstrap.contentModel.js +166 -0
- package/dist/services/seedBootstrap.contentTypes.d.ts +5 -0
- package/dist/services/seedBootstrap.contentTypes.js +76 -0
- package/dist/services/seedBootstrap.d.ts +1 -0
- package/dist/services/seedBootstrap.js +87 -10
- package/dist/services/toolRegistry.js +52 -24
- package/dist/services/toolRegistry.zod.js +84 -37
- package/dist/versioning/schemaVersion.d.ts +1 -1
- package/dist/versioning/schemaVersion.js +1 -13
- package/package.json +17 -3
- package/schemas/index-server.code-schema.json +31019 -25047
- package/schemas/instruction.schema.json +16 -6
- package/schemas/manifest.json +3 -3
- package/scripts/README.md +20 -0
- package/scripts/build/README.md +41 -0
- package/scripts/build/setup-wizard-paths.mjs +27 -0
- package/scripts/build/setup-wizard.mjs +7 -21
- package/scripts/client/README.md +26 -0
- package/scripts/client/index-server-client.ps1 +203 -0
- package/scripts/client/index-server-client.sh +149 -0
- package/scripts/client/powershell-mcp-server.ps1 +83 -0
- package/scripts/client/powershell-mcp-template.ps1 +85 -0
- package/scripts/hooks/README.md +40 -0
- package/server.json +2 -2
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-persisted-admin-session.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-persisted-session-history-entry.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-persisted-web-socket-connection.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-config.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-data.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-manifest.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-metadata.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/instruction-audience-scope.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/instruction-requirement-level.schema.json +0 -0
|
@@ -309,13 +309,47 @@ function createEmbeddingsRoutes(embeddingPathOverride, embeddingStore) {
|
|
|
309
309
|
error: 'No instructions loaded in index',
|
|
310
310
|
});
|
|
311
311
|
}
|
|
312
|
+
// Pre-flight: detect a no-op (full cache hit) so we can return a clearer
|
|
313
|
+
// signal to the dashboard instead of pretending we just computed N items.
|
|
314
|
+
const readiness = (0, embeddingService_js_1.checkModelReadiness)(sem.model, sem.cacheDir, sem.localOnly);
|
|
315
|
+
let preCached = null;
|
|
316
|
+
try {
|
|
317
|
+
if (embeddingStore) {
|
|
318
|
+
preCached = embeddingStore.load();
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
const file = embeddingPathOverride ?? sem.embeddingPath;
|
|
322
|
+
if (file && node_fs_1.default.existsSync(file)) {
|
|
323
|
+
preCached = JSON.parse(node_fs_1.default.readFileSync(file, 'utf8'));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch { /* tolerate */ }
|
|
328
|
+
const currentIds = new Set(state.list.map(i => i.id));
|
|
329
|
+
const cachedIds = preCached?.embeddings ? new Set(Object.keys(preCached.embeddings)) : new Set();
|
|
330
|
+
const cachedHashes = preCached?.entryHashes ?? {};
|
|
331
|
+
const sameModel = preCached?.modelName === sem.model;
|
|
332
|
+
const sameIndex = preCached?.indexHash === state.hash;
|
|
333
|
+
const toCompute = state.list.filter(inst => {
|
|
334
|
+
if (!sameModel)
|
|
335
|
+
return true;
|
|
336
|
+
const h = cachedHashes[inst.id];
|
|
337
|
+
return !h || h !== inst.sourceHash || !cachedIds.has(inst.id);
|
|
338
|
+
});
|
|
339
|
+
const fullHit = sameModel && sameIndex && toCompute.length === 0 && cachedIds.size > 0;
|
|
312
340
|
const start = performance.now();
|
|
313
|
-
const embeddings = await (0, embeddingService_js_1.getInstructionEmbeddings)(state.list, state.hash, sem.embeddingPath, sem.model, sem.cacheDir, sem.device, sem.localOnly);
|
|
341
|
+
const embeddings = await (0, embeddingService_js_1.getInstructionEmbeddings)(state.list, state.hash, sem.embeddingPath, sem.model, sem.cacheDir, sem.device, sem.localOnly, undefined, embeddingStore);
|
|
314
342
|
const elapsed = (performance.now() - start).toFixed(0);
|
|
315
343
|
const count = Object.keys(embeddings).length;
|
|
344
|
+
const computed = fullHit ? 0 : toCompute.length;
|
|
345
|
+
const reused = count - computed;
|
|
316
346
|
return res.json({
|
|
317
347
|
success: true,
|
|
318
348
|
count,
|
|
349
|
+
computed,
|
|
350
|
+
reused,
|
|
351
|
+
cacheHit: fullHit,
|
|
352
|
+
modelDownloaded: !readiness.cached, // best-effort: model wasn't cached before this call
|
|
319
353
|
model: sem.model,
|
|
320
354
|
device: sem.device,
|
|
321
355
|
elapsedMs: Number(elapsed),
|
|
@@ -341,5 +375,46 @@ function createEmbeddingsRoutes(embeddingPathOverride, embeddingStore) {
|
|
|
341
375
|
});
|
|
342
376
|
}
|
|
343
377
|
});
|
|
378
|
+
// POST /embeddings/reset — clear cached embeddings (forces full recompute on next compute).
|
|
379
|
+
// Optionally clears the on-disk model cache so the next compute re-downloads the model.
|
|
380
|
+
router.post('/embeddings/reset', adminAuth_js_1.dashboardAdminAuth, async (req, res) => {
|
|
381
|
+
try {
|
|
382
|
+
const sem = (0, runtimeConfig_js_1.getRuntimeConfig)().semantic;
|
|
383
|
+
const clearModel = req.body && req.body.clearModel === true;
|
|
384
|
+
let embeddingsCleared = false;
|
|
385
|
+
let modelCacheCleared = false;
|
|
386
|
+
// Clear embeddings cache (works for both JSON file and SQLite store).
|
|
387
|
+
if (embeddingStore) {
|
|
388
|
+
embeddingStore.save({ indexHash: '', modelName: '', entryHashes: {}, embeddings: {} });
|
|
389
|
+
embeddingsCleared = true;
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
const file = embeddingPathOverride ?? sem.embeddingPath;
|
|
393
|
+
if (file && node_fs_1.default.existsSync(file)) {
|
|
394
|
+
node_fs_1.default.rmSync(file, { force: true });
|
|
395
|
+
embeddingsCleared = true;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Optionally clear the model cache directory to force a re-download.
|
|
399
|
+
if (clearModel && sem.cacheDir && node_fs_1.default.existsSync(sem.cacheDir)) {
|
|
400
|
+
node_fs_1.default.rmSync(sem.cacheDir, { recursive: true, force: true });
|
|
401
|
+
modelCacheCleared = true;
|
|
402
|
+
}
|
|
403
|
+
return res.json({
|
|
404
|
+
success: true,
|
|
405
|
+
embeddingsCleared,
|
|
406
|
+
modelCacheCleared,
|
|
407
|
+
embeddingPath: embeddingPathOverride ?? sem.embeddingPath,
|
|
408
|
+
cacheDir: sem.cacheDir,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
return res.status(500).json({
|
|
413
|
+
success: false,
|
|
414
|
+
error: 'Failed to reset embeddings',
|
|
415
|
+
message: err.message,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
});
|
|
344
419
|
return router;
|
|
345
420
|
}
|
|
@@ -16,6 +16,7 @@ const registry_js_1 = require("../../../server/registry.js");
|
|
|
16
16
|
const indexContext_js_1 = require("../../../services/indexContext.js");
|
|
17
17
|
const adminAuth_js_1 = require("./adminAuth.js");
|
|
18
18
|
const handlers_search_js_1 = require("../../../services/handlers.search.js");
|
|
19
|
+
const instruction_js_1 = require("../../../models/instruction.js");
|
|
19
20
|
const schemaVersion_js_1 = require("../../../versioning/schemaVersion.js");
|
|
20
21
|
const runtimeConfig_js_1 = require("../../../config/runtimeConfig.js");
|
|
21
22
|
const instructionRecordValidation_js_1 = require("../../../services/instructionRecordValidation.js");
|
|
@@ -123,17 +124,9 @@ function dashboardRequirement(value) {
|
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
function dashboardContentType(value) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
case 'reference':
|
|
130
|
-
case 'example':
|
|
131
|
-
case 'agent':
|
|
132
|
-
case 'instruction':
|
|
133
|
-
return value;
|
|
134
|
-
default:
|
|
135
|
-
return 'instruction';
|
|
136
|
-
}
|
|
127
|
+
return typeof value === 'string' && instruction_js_1.CONTENT_TYPES.includes(value)
|
|
128
|
+
? value
|
|
129
|
+
: 'instruction';
|
|
137
130
|
}
|
|
138
131
|
function createInstructionsRoutes() {
|
|
139
132
|
const router = (0, express_1.Router)();
|
|
@@ -56,18 +56,48 @@ function createScriptsRoutes() {
|
|
|
56
56
|
});
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
// Candidate dirs (first existing wins):
|
|
60
|
+
// 1. Installed package layout: <pkg-root>/scripts/client (resolved from __dirname = dist/dashboard/server/routes)
|
|
61
|
+
// 2. Installed package layout (alt): <pkg-root>/scripts/dist (legacy)
|
|
62
|
+
// 3. Repo dev layout: <cwd>/scripts/client
|
|
63
|
+
// 4. Repo dev layout (legacy): <cwd>/scripts/dist
|
|
64
|
+
const pkgRoot = path_1.default.resolve(__dirname, '..', '..', '..', '..');
|
|
65
|
+
const candidateDirs = [
|
|
66
|
+
path_1.default.join(pkgRoot, 'scripts', 'client'),
|
|
67
|
+
path_1.default.join(pkgRoot, 'scripts', 'dist'),
|
|
68
|
+
path_1.default.join(process.cwd(), 'scripts', 'client'),
|
|
69
|
+
path_1.default.join(process.cwd(), 'scripts', 'dist'),
|
|
70
|
+
];
|
|
71
|
+
let content;
|
|
72
|
+
let lastErr;
|
|
73
|
+
for (const scriptsDir of candidateDirs) {
|
|
61
74
|
const filePath = path_1.default.join(scriptsDir, meta.file); // nosemgrep: javascript.express.security.audit.express-path-join-resolve-traversal.express-path-join-resolve-traversal -- path validated below via startsWith check
|
|
62
75
|
let resolved;
|
|
63
76
|
try {
|
|
64
77
|
resolved = (0, pathContainment_js_1.validatePathContainment)(path_1.default.resolve(filePath), scriptsDir); // nosemgrep: javascript.express.security.audit.express-path-join-resolve-traversal.express-path-join-resolve-traversal -- containment validated by shared helper
|
|
65
78
|
}
|
|
66
79
|
catch {
|
|
67
|
-
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
content = await (0, promises_1.readFile)(resolved, 'utf-8');
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
lastErr = err;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
if (content === undefined) {
|
|
92
|
+
const message = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
93
|
+
if (!lastErr || message.includes('ENOENT')) {
|
|
94
|
+
res.status(404).json({ error: `Script file not found on disk: ${name}` });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
res.status(500).json({ error: `Failed to read script: ${message}` });
|
|
98
|
+
}
|
|
68
99
|
return;
|
|
69
100
|
}
|
|
70
|
-
const content = await (0, promises_1.readFile)(resolved, 'utf-8');
|
|
71
101
|
res.setHeader('Content-Type', meta.contentType);
|
|
72
102
|
res.setHeader('Content-Disposition', `attachment; filename="${meta.file}"`);
|
|
73
103
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
@@ -75,12 +105,7 @@ function createScriptsRoutes() {
|
|
|
75
105
|
}
|
|
76
106
|
catch (err) {
|
|
77
107
|
const message = err instanceof Error ? err.message : String(err);
|
|
78
|
-
|
|
79
|
-
res.status(404).json({ error: `Script file not found on disk: ${name}` });
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
res.status(500).json({ error: `Failed to read script: ${message}` });
|
|
83
|
-
}
|
|
108
|
+
res.status(500).json({ error: `Failed to send script: ${message}` });
|
|
84
109
|
}
|
|
85
110
|
});
|
|
86
111
|
return router;
|
|
@@ -11,6 +11,7 @@ exports.createStatusRoutes = createStatusRoutes;
|
|
|
11
11
|
const express_1 = require("express");
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
14
15
|
const v8_1 = __importDefault(require("v8"));
|
|
15
16
|
const runtimeConfig_js_1 = require("../../../config/runtimeConfig.js");
|
|
16
17
|
const logger_js_1 = require("../../../services/logger.js");
|
|
@@ -73,6 +74,7 @@ function createStatusRoutes(metricsCollector) {
|
|
|
73
74
|
const snapshot = metricsCollector.getCurrentSnapshot();
|
|
74
75
|
const git = getGitCommit();
|
|
75
76
|
const buildTime = getBuildTime();
|
|
77
|
+
const cfg = (0, runtimeConfig_js_1.getRuntimeConfig)();
|
|
76
78
|
// Prevent stale caching of build/version metadata in browsers / proxies
|
|
77
79
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
78
80
|
res.setHeader('Pragma', 'no-cache');
|
|
@@ -84,6 +86,12 @@ function createStatusRoutes(metricsCollector) {
|
|
|
84
86
|
buildTime: buildTime || undefined,
|
|
85
87
|
uptime: snapshot.server.uptime,
|
|
86
88
|
startTime: snapshot.server.startTime,
|
|
89
|
+
paths: {
|
|
90
|
+
instructionsDir: cfg.index.baseDir,
|
|
91
|
+
storageBackend: cfg.storage.backend,
|
|
92
|
+
sqlitePath: cfg.storage.backend === 'sqlite' ? cfg.storage.sqlitePath : undefined,
|
|
93
|
+
backupsDir: cfg.dashboard.admin.backupsDir,
|
|
94
|
+
},
|
|
87
95
|
timestamp: Date.now(),
|
|
88
96
|
});
|
|
89
97
|
}
|
|
@@ -151,6 +159,75 @@ function createStatusRoutes(metricsCollector) {
|
|
|
151
159
|
});
|
|
152
160
|
}
|
|
153
161
|
});
|
|
162
|
+
/**
|
|
163
|
+
* POST /api/system/reveal-path - Open one of the configured paths in the OS
|
|
164
|
+
* file manager. Accepts only a fixed allowlist key (instructions | sqlite |
|
|
165
|
+
* backups) — the server resolves the actual path from runtime config so the
|
|
166
|
+
* client cannot supply an arbitrary filesystem path.
|
|
167
|
+
*
|
|
168
|
+
* Loopback-only by virtue of the dashboard binding to 127.0.0.1.
|
|
169
|
+
*/
|
|
170
|
+
router.post('/system/reveal-path', (req, res) => {
|
|
171
|
+
try {
|
|
172
|
+
const key = String((req.body && req.body.key) || '');
|
|
173
|
+
const cfg = (0, runtimeConfig_js_1.getRuntimeConfig)();
|
|
174
|
+
let target;
|
|
175
|
+
switch (key) {
|
|
176
|
+
case 'instructions':
|
|
177
|
+
target = cfg.index.baseDir;
|
|
178
|
+
break;
|
|
179
|
+
case 'sqlite':
|
|
180
|
+
target = cfg.storage.backend === 'sqlite' ? cfg.storage.sqlitePath : undefined;
|
|
181
|
+
break;
|
|
182
|
+
case 'backups':
|
|
183
|
+
target = cfg.dashboard.admin.backupsDir;
|
|
184
|
+
break;
|
|
185
|
+
default:
|
|
186
|
+
return res.status(400).json({ success: false, error: `unknown key: ${key}` });
|
|
187
|
+
}
|
|
188
|
+
if (!target) {
|
|
189
|
+
return res.status(404).json({ success: false, error: `path not configured for key: ${key}` });
|
|
190
|
+
}
|
|
191
|
+
// For files (e.g. sqlite db) reveal the parent directory instead of trying
|
|
192
|
+
// to open the file itself in the file manager.
|
|
193
|
+
let toOpen = target;
|
|
194
|
+
try {
|
|
195
|
+
if (fs_1.default.existsSync(target) && fs_1.default.statSync(target).isFile()) {
|
|
196
|
+
toOpen = path_1.default.dirname(target);
|
|
197
|
+
}
|
|
198
|
+
else if (!fs_1.default.existsSync(target)) {
|
|
199
|
+
// Fall back to the parent if the leaf does not exist yet.
|
|
200
|
+
toOpen = path_1.default.dirname(target);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch { /* ignore stat errors, attempt original */ }
|
|
204
|
+
// Platform-appropriate "open folder" command. All args are server-derived
|
|
205
|
+
// from runtime config; no user input ever reaches the spawned argv.
|
|
206
|
+
let cmd;
|
|
207
|
+
let args;
|
|
208
|
+
if (process.platform === 'win32') {
|
|
209
|
+
cmd = 'explorer.exe';
|
|
210
|
+
args = [toOpen];
|
|
211
|
+
}
|
|
212
|
+
else if (process.platform === 'darwin') {
|
|
213
|
+
cmd = 'open';
|
|
214
|
+
args = [toOpen];
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
cmd = 'xdg-open';
|
|
218
|
+
args = [toOpen];
|
|
219
|
+
}
|
|
220
|
+
// nosemgrep: javascript.lang.security.audit.detect-child-process.detect-child-process -- args resolved from server-side runtimeConfig allowlist; no user input
|
|
221
|
+
const child = (0, child_process_1.spawn)(cmd, args, { detached: true, stdio: 'ignore' });
|
|
222
|
+
child.on('error', () => { });
|
|
223
|
+
child.unref();
|
|
224
|
+
res.json({ success: true, key, path: toOpen, timestamp: Date.now() });
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
(0, logger_js_1.logError)('[API] reveal-path error:', error);
|
|
228
|
+
res.status(500).json({ success: false, error: 'Failed to reveal path' });
|
|
229
|
+
}
|
|
230
|
+
});
|
|
154
231
|
/**
|
|
155
232
|
* GET /api/system/health - Advanced system health metrics
|
|
156
233
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type AudienceScope = 'individual' | 'group' | 'all';
|
|
2
2
|
export type RequirementLevel = 'mandatory' | 'critical' | 'recommended' | 'optional' | 'deprecated';
|
|
3
|
-
export
|
|
3
|
+
export declare const CONTENT_TYPES: readonly ["agent", "skill", "instruction", "prompt", "workflow", "knowledge", "template", "integration"];
|
|
4
|
+
export type ContentType = typeof CONTENT_TYPES[number];
|
|
4
5
|
export interface InstructionEntry {
|
|
5
6
|
id: string;
|
|
6
7
|
title: string;
|