@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.
Files changed (73) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/CONTRIBUTING.md +13 -0
  3. package/README.md +10 -14
  4. package/dist/config/featureConfig.js +4 -1
  5. package/dist/dashboard/client/admin.html +69 -29
  6. package/dist/dashboard/client/js/admin.embeddings.js +97 -5
  7. package/dist/dashboard/client/js/admin.instructions.js +1 -1
  8. package/dist/dashboard/server/AdminPanel.js +38 -0
  9. package/dist/dashboard/server/ApiRoutes.js +14 -1
  10. package/dist/dashboard/server/routes/embeddings.routes.js +76 -1
  11. package/dist/dashboard/server/routes/instructions.routes.js +4 -11
  12. package/dist/dashboard/server/routes/scripts.routes.js +35 -10
  13. package/dist/dashboard/server/routes/status.routes.js +77 -0
  14. package/dist/models/instruction.d.ts +2 -1
  15. package/dist/models/instruction.js +2 -0
  16. package/dist/schemas/index-server.code-schema.json +52478 -0
  17. package/dist/schemas/index.d.ts +7 -164
  18. package/dist/schemas/index.js +45 -63
  19. package/dist/schemas/instructionSchema.d.ts +46 -0
  20. package/dist/schemas/instructionSchema.js +159 -0
  21. package/{schemas → dist/schemas}/json-schema/instruction-content-type.schema.json +6 -4
  22. package/{schemas → dist/schemas}/json-schema/instruction-instruction-entry.schema.json +6 -4
  23. package/dist/schemas/manifest.json +78 -0
  24. package/dist/server/index-server.js +7 -1
  25. package/dist/services/bootstrapGating.js +2 -2
  26. package/dist/services/handlers/instructions.add.js +18 -0
  27. package/dist/services/handlers/instructions.groom.js +6 -1
  28. package/dist/services/handlers/instructions.import.js +42 -7
  29. package/dist/services/handlers.activation.js +3 -1
  30. package/dist/services/handlers.dashboardConfig.js +2 -1
  31. package/dist/services/handlers.feedback.d.ts +4 -4
  32. package/dist/services/handlers.feedback.js +390 -27
  33. package/dist/services/handlers.instructionSchema.js +73 -31
  34. package/dist/services/handlers.search.js +11 -6
  35. package/dist/services/indexLoader.js +7 -0
  36. package/dist/services/instructionRecordValidation.js +32 -84
  37. package/dist/services/mcpConfig/flagCatalog.d.ts +1 -1
  38. package/dist/services/mcpConfig/flagCatalog.js +2 -0
  39. package/dist/services/mcpConfig/formats.js +2 -6
  40. package/dist/services/seedBootstrap.contentModel.d.ts +13 -0
  41. package/dist/services/seedBootstrap.contentModel.js +166 -0
  42. package/dist/services/seedBootstrap.contentTypes.d.ts +5 -0
  43. package/dist/services/seedBootstrap.contentTypes.js +76 -0
  44. package/dist/services/seedBootstrap.d.ts +1 -0
  45. package/dist/services/seedBootstrap.js +87 -10
  46. package/dist/services/toolRegistry.js +52 -24
  47. package/dist/services/toolRegistry.zod.js +84 -37
  48. package/dist/versioning/schemaVersion.d.ts +1 -1
  49. package/dist/versioning/schemaVersion.js +1 -13
  50. package/package.json +17 -3
  51. package/schemas/index-server.code-schema.json +31019 -25047
  52. package/schemas/instruction.schema.json +16 -6
  53. package/schemas/manifest.json +3 -3
  54. package/scripts/README.md +20 -0
  55. package/scripts/build/README.md +41 -0
  56. package/scripts/build/setup-wizard-paths.mjs +27 -0
  57. package/scripts/build/setup-wizard.mjs +7 -21
  58. package/scripts/client/README.md +26 -0
  59. package/scripts/client/index-server-client.ps1 +203 -0
  60. package/scripts/client/index-server-client.sh +149 -0
  61. package/scripts/client/powershell-mcp-server.ps1 +83 -0
  62. package/scripts/client/powershell-mcp-template.ps1 +85 -0
  63. package/scripts/hooks/README.md +40 -0
  64. package/server.json +2 -2
  65. /package/{schemas → dist/schemas}/json-schema/SessionPersistence-persisted-admin-session.schema.json +0 -0
  66. /package/{schemas → dist/schemas}/json-schema/SessionPersistence-persisted-session-history-entry.schema.json +0 -0
  67. /package/{schemas → dist/schemas}/json-schema/SessionPersistence-persisted-web-socket-connection.schema.json +0 -0
  68. /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-config.schema.json +0 -0
  69. /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-data.schema.json +0 -0
  70. /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-manifest.schema.json +0 -0
  71. /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-metadata.schema.json +0 -0
  72. /package/{schemas → dist/schemas}/json-schema/instruction-audience-scope.schema.json +0 -0
  73. /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
- switch (value) {
127
- case 'template':
128
- case 'workflow':
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
- try {
60
- const scriptsDir = path_1.default.join(process.cwd(), 'scripts', 'dist');
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
- res.status(400).json({ error: 'Invalid script path' });
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
- if (message.includes('ENOENT')) {
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 type ContentType = 'instruction' | 'template' | 'workflow' | 'reference' | 'example' | 'agent';
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;
@@ -1,2 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONTENT_TYPES = void 0;
4
+ exports.CONTENT_TYPES = ['agent', 'skill', 'instruction', 'prompt', 'workflow', 'knowledge', 'template', 'integration'];