@karmaniverous/jeeves-watcher 0.4.1 → 0.4.2
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/config.schema.json +9 -2
- package/dist/cjs/index.js +15 -3
- package/dist/cli/jeeves-watcher/index.js +15 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.iife.js +15 -3
- package/dist/index.iife.min.js +1 -1
- package/dist/mjs/index.js +15 -3
- package/package.json +1 -1
package/config.schema.json
CHANGED
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
]
|
|
123
123
|
},
|
|
124
124
|
"maps": {
|
|
125
|
-
"description": "Reusable named JsonMap transformations.",
|
|
125
|
+
"description": "Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory).",
|
|
126
126
|
"allOf": [
|
|
127
127
|
{
|
|
128
128
|
"$ref": "#/definitions/__schema51"
|
|
@@ -597,7 +597,14 @@
|
|
|
597
597
|
"type": "string"
|
|
598
598
|
},
|
|
599
599
|
"additionalProperties": {
|
|
600
|
-
"
|
|
600
|
+
"anyOf": [
|
|
601
|
+
{
|
|
602
|
+
"$ref": "#/definitions/__schema48"
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
"type": "string"
|
|
606
|
+
}
|
|
607
|
+
]
|
|
601
608
|
}
|
|
602
609
|
},
|
|
603
610
|
"__schema52": {
|
package/dist/cjs/index.js
CHANGED
|
@@ -1164,11 +1164,11 @@ const jeevesWatcherConfigSchema = zod.z.object({
|
|
|
1164
1164
|
.array(inferenceRuleSchema)
|
|
1165
1165
|
.optional()
|
|
1166
1166
|
.describe('Rules for inferring metadata from file attributes.'),
|
|
1167
|
-
/** Reusable named JsonMap transformations. */
|
|
1167
|
+
/** Reusable named JsonMap transformations (inline objects or .json file paths). */
|
|
1168
1168
|
maps: zod.z
|
|
1169
|
-
.record(zod.z.string(), jsonmap.jsonMapMapSchema)
|
|
1169
|
+
.record(zod.z.string(), jsonmap.jsonMapMapSchema.or(zod.z.string()))
|
|
1170
1170
|
.optional()
|
|
1171
|
-
.describe('Reusable named JsonMap transformations.'),
|
|
1171
|
+
.describe('Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory).'),
|
|
1172
1172
|
/** Reusable named Handlebars templates (inline strings or .hbs/.handlebars file paths). */
|
|
1173
1173
|
templates: zod.z
|
|
1174
1174
|
.record(zod.z.string(), zod.z.string())
|
|
@@ -1282,6 +1282,18 @@ async function loadConfig(configPath) {
|
|
|
1282
1282
|
}
|
|
1283
1283
|
try {
|
|
1284
1284
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
1285
|
+
// Resolve file-path map references relative to config directory.
|
|
1286
|
+
// After this block, all map values are inline JsonMapMap objects.
|
|
1287
|
+
if (validated.maps) {
|
|
1288
|
+
const configDir = node_path.dirname(result.filepath);
|
|
1289
|
+
for (const [name, value] of Object.entries(validated.maps)) {
|
|
1290
|
+
if (typeof value === 'string') {
|
|
1291
|
+
const mapPath = node_path.resolve(configDir, value);
|
|
1292
|
+
const raw = node_fs.readFileSync(mapPath, 'utf-8');
|
|
1293
|
+
validated.maps[name] = JSON.parse(raw);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1285
1297
|
const withDefaults = applyDefaults(validated);
|
|
1286
1298
|
return substituteEnvVars(withDefaults);
|
|
1287
1299
|
}
|
|
@@ -1166,11 +1166,11 @@ const jeevesWatcherConfigSchema = z.object({
|
|
|
1166
1166
|
.array(inferenceRuleSchema)
|
|
1167
1167
|
.optional()
|
|
1168
1168
|
.describe('Rules for inferring metadata from file attributes.'),
|
|
1169
|
-
/** Reusable named JsonMap transformations. */
|
|
1169
|
+
/** Reusable named JsonMap transformations (inline objects or .json file paths). */
|
|
1170
1170
|
maps: z
|
|
1171
|
-
.record(z.string(), jsonMapMapSchema)
|
|
1171
|
+
.record(z.string(), jsonMapMapSchema.or(z.string()))
|
|
1172
1172
|
.optional()
|
|
1173
|
-
.describe('Reusable named JsonMap transformations.'),
|
|
1173
|
+
.describe('Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory).'),
|
|
1174
1174
|
/** Reusable named Handlebars templates (inline strings or .hbs/.handlebars file paths). */
|
|
1175
1175
|
templates: z
|
|
1176
1176
|
.record(z.string(), z.string())
|
|
@@ -1284,6 +1284,18 @@ async function loadConfig(configPath) {
|
|
|
1284
1284
|
}
|
|
1285
1285
|
try {
|
|
1286
1286
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
1287
|
+
// Resolve file-path map references relative to config directory.
|
|
1288
|
+
// After this block, all map values are inline JsonMapMap objects.
|
|
1289
|
+
if (validated.maps) {
|
|
1290
|
+
const configDir = dirname(result.filepath);
|
|
1291
|
+
for (const [name, value] of Object.entries(validated.maps)) {
|
|
1292
|
+
if (typeof value === 'string') {
|
|
1293
|
+
const mapPath = resolve(configDir, value);
|
|
1294
|
+
const raw = readFileSync(mapPath, 'utf-8');
|
|
1295
|
+
validated.maps[name] = JSON.parse(raw);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1287
1299
|
const withDefaults = applyDefaults(validated);
|
|
1288
1300
|
return substituteEnvVars(withDefaults);
|
|
1289
1301
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -127,7 +127,7 @@ declare const jeevesWatcherConfigSchema: z.ZodObject<{
|
|
|
127
127
|
map: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<_karmaniverous_jsonmap.JsonMapMap, unknown, z.core.$ZodTypeInternals<_karmaniverous_jsonmap.JsonMapMap, unknown>>, z.ZodString]>>;
|
|
128
128
|
template: z.ZodOptional<z.ZodString>;
|
|
129
129
|
}, z.core.$strip>>>;
|
|
130
|
-
maps: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<_karmaniverous_jsonmap.JsonMapMap, unknown, z.core.$ZodTypeInternals<_karmaniverous_jsonmap.JsonMapMap, unknown
|
|
130
|
+
maps: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodType<_karmaniverous_jsonmap.JsonMapMap, unknown, z.core.$ZodTypeInternals<_karmaniverous_jsonmap.JsonMapMap, unknown>>, z.ZodString]>>>;
|
|
131
131
|
templates: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
132
132
|
templateHelpers: z.ZodOptional<z.ZodObject<{
|
|
133
133
|
paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
package/dist/index.iife.js
CHANGED
|
@@ -1135,11 +1135,11 @@
|
|
|
1135
1135
|
.array(inferenceRuleSchema)
|
|
1136
1136
|
.optional()
|
|
1137
1137
|
.describe('Rules for inferring metadata from file attributes.'),
|
|
1138
|
-
/** Reusable named JsonMap transformations. */
|
|
1138
|
+
/** Reusable named JsonMap transformations (inline objects or .json file paths). */
|
|
1139
1139
|
maps: zod.z
|
|
1140
|
-
.record(zod.z.string(), jsonmap.jsonMapMapSchema)
|
|
1140
|
+
.record(zod.z.string(), jsonmap.jsonMapMapSchema.or(zod.z.string()))
|
|
1141
1141
|
.optional()
|
|
1142
|
-
.describe('Reusable named JsonMap transformations.'),
|
|
1142
|
+
.describe('Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory).'),
|
|
1143
1143
|
/** Reusable named Handlebars templates (inline strings or .hbs/.handlebars file paths). */
|
|
1144
1144
|
templates: zod.z
|
|
1145
1145
|
.record(zod.z.string(), zod.z.string())
|
|
@@ -1253,6 +1253,18 @@
|
|
|
1253
1253
|
}
|
|
1254
1254
|
try {
|
|
1255
1255
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
1256
|
+
// Resolve file-path map references relative to config directory.
|
|
1257
|
+
// After this block, all map values are inline JsonMapMap objects.
|
|
1258
|
+
if (validated.maps) {
|
|
1259
|
+
const configDir = node_path.dirname(result.filepath);
|
|
1260
|
+
for (const [name, value] of Object.entries(validated.maps)) {
|
|
1261
|
+
if (typeof value === 'string') {
|
|
1262
|
+
const mapPath = node_path.resolve(configDir, value);
|
|
1263
|
+
const raw = node_fs.readFileSync(mapPath, 'utf-8');
|
|
1264
|
+
validated.maps[name] = JSON.parse(raw);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1256
1268
|
const withDefaults = applyDefaults(validated);
|
|
1257
1269
|
return substituteEnvVars(withDefaults);
|
|
1258
1270
|
}
|
package/dist/index.iife.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t,r,i,n,o,s,a,c,l,h,d,u,g,f,p,m,y,w,b,v,M,S,F,x,k,P,z,j,C){"use strict";function E(e){var t=Object.create(null);return e&&Object.keys(e).forEach((function(r){if("default"!==r){var i=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,i.get?i:{enumerable:!0,get:function(){return e[r]}})}})),t.default=e,Object.freeze(t)}var R=E(F);function T(e){if(e instanceof Error)return e;if("string"==typeof e)return new Error(e);const t=String("object"==typeof e&&null!==e&&"message"in e?e.message:e),r=new Error(t);return r.cause=e,r}function D(e){const t=e.replace(/\\/g,"/"),r=t.search(/[*?\[]/);if(-1===r)return i.resolve(e);const n=t.slice(0,r),o=n.endsWith("/")?n.slice(0,-1):i.dirname(n);return i.resolve(o)}async function*I(e){let t;try{t=(await r.readdir(e,{withFileTypes:!0})).map((e=>({name:e.name,isDirectory:e.isDirectory()})))}catch{return}for(const n of t){const t=i.resolve(e,n.name);if(n.isDirectory)yield*I(t);else try{(await r.stat(t)).isFile()&&(yield t)}catch{}}}async function A(e,t,r,i){const o=await async function(e,t=[]){const r=e.map((e=>e.replace(/\\/g,"/"))),i=t.map((e=>e.replace(/\\/g,"/"))),o=n(r,{dot:!0}),s=i.length?n(i,{dot:!0}):()=>!1,a=Array.from(new Set(e.map(D))),c=new Set;for(const e of a)for await(const t of I(e)){const e=t.replace(/\\/g,"/");s(e)||o(e)&&c.add(t)}return Array.from(c)}(e,t);for(const e of o)await r[i](e);return o.length}function W(e,t=!1){let r=e.replace(/\\/g,"/").toLowerCase();return t&&(r=r.replace(/^([a-z]):/,((e,t)=>t))),r}function N(e,t){const r=W(e,!0),n=s.createHash("sha256").update(r,"utf8").digest("hex");return i.join(t,`${n}.meta.json`)}async function _(e,t){try{const i=await r.readFile(N(e,t),"utf8");return JSON.parse(i)}catch{return null}}async function H(e,t,n){const o=N(e,t);await r.mkdir(i.dirname(o),{recursive:!0}),await r.writeFile(o,JSON.stringify(n,null,2),"utf8")}async function O(e,t){try{await r.rm(N(e,t))}catch{}}const $=["file_path","chunk_index","total_chunks","content_hash","chunk_text"];function q(e){const{processor:r,vectorStore:i,embeddingProvider:n,logger:s,config:a}=e,c=t({logger:!1});var l;return c.get("/status",(l={vectorStore:i,config:a},async()=>{const e=await l.vectorStore.getCollectionInfo();return{status:"ok",uptime:process.uptime(),collection:{name:l.config.vectorStore.collectionName,pointCount:e.pointCount,dimensions:e.dimensions},payloadFields:e.payloadFields}})),c.post("/metadata",function(e){return async(t,r)=>{try{const{path:r,metadata:i}=t.body;return await e.processor.processMetadataUpdate(r,i),{ok:!0}}catch(t){return e.logger.error({err:T(t)},"Metadata update failed"),r.status(500).send({error:"Internal server error"})}}}({processor:r,logger:s})),c.post("/search",function(e){return async(t,r)=>{try{const{query:r,limit:i=10,filter:n}=t.body,o=await e.embeddingProvider.embed([r]);return await e.vectorStore.search(o[0],i,n)}catch(t){return e.logger.error({err:T(t)},"Search failed"),r.status(500).send({error:"Internal server error"})}}}({embeddingProvider:n,vectorStore:i,logger:s})),c.post("/reindex",function(e){return async(t,r)=>{try{const t=await A(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");return await r.status(200).send({ok:!0,filesIndexed:t})}catch(t){return e.logger.error({err:T(t)},"Reindex failed"),await r.status(500).send({error:"Internal server error"})}}}({config:a,processor:r,logger:s})),c.post("/rebuild-metadata",function(e){return async(t,r)=>{try{const t=e.config.metadataDir??".jeeves-metadata",i=[...$];for await(const r of e.vectorStore.scroll()){const e=r.payload,n=e.file_path;if("string"!=typeof n||0===n.length)continue;const s=o.omit(e,i);await H(n,t,s)}return await r.status(200).send({ok:!0})}catch(t){return e.logger.error({err:T(t)},"Rebuild metadata failed"),await r.status(500).send({error:"Internal server error"})}}}({config:a,vectorStore:i,logger:s})),c.post("/config-reindex",function(e){return async(t,r)=>{try{const i=t.body.scope??"rules";return(async()=>{try{if("rules"===i){const t=await A(e.config.watch.paths,e.config.watch.ignored,e.processor,"processRulesUpdate");e.logger.info({scope:i,filesProcessed:t},"Config reindex (rules) completed")}else{const t=await A(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");e.logger.info({scope:i,filesProcessed:t},"Config reindex (full) completed")}}catch(t){e.logger.error({err:T(t),scope:i},"Config reindex failed")}})(),await r.status(200).send({status:"started",scope:i})}catch(t){return e.logger.error({err:T(t)},"Config reindex request failed"),await r.status(500).send({error:"Internal server error"})}}}({config:a,processor:r,logger:s})),c}function L(e){let t=i.resolve(e);const r=i.resolve("/");for(;t!==r;){if(a.existsSync(i.join(t,".git"))&&a.statSync(i.join(t,".git")).isDirectory())return t;const e=i.dirname(t);if(e===t)break;t=e}}function Q(e){const t=i.resolve(e);try{return a.statSync(t).isDirectory()?t:i.dirname(t)}catch{}const r=/[*?[{]/.exec(e);if(!r)return;const n=e.slice(0,r.index).trim(),o=0===n.length?".":n.endsWith("/")||n.endsWith("\\")?n:i.dirname(n),s=i.resolve(o);return a.existsSync(s)?s:void 0}function G(e){const t=[],r=i.join(e,".gitignore");let n;a.existsSync(r)&&t.push(r);try{n=a.readdirSync(e)}catch{return t}for(const r of n){if(".git"===r||"node_modules"===r)continue;const n=i.join(e,r);try{a.statSync(n).isDirectory()&&t.push(...G(n))}catch{}}return t}function B(e){const t=a.readFileSync(e,"utf8");return c().add(t)}class J{repos=new Map;constructor(e){this.scan(e)}scan(e){this.repos.clear();const t=new Set;for(const r of e){const e=Q(r);if(!e)continue;if(t.has(e))continue;t.add(e);const n=L(e);if(!n)continue;if(this.repos.has(n))continue;const o=G(n).map((e=>({dir:i.dirname(e),ig:B(e)})));o.sort(((e,t)=>t.dir.length-e.dir.length)),this.repos.set(n,{root:n,entries:o})}}isIgnored(e){const t=i.resolve(e);for(const[,e]of this.repos){const r=i.relative(e.root,t);if(!(r.startsWith("..")||r.startsWith(i.resolve("/"))||/^[a-zA-Z]:/.test(r)))for(const r of e.entries){const e=i.relative(r.dir,t);if(e.startsWith("..")||/^[a-zA-Z]:/.test(e))continue;const n=e.replace(/\\/g,"/");if(r.ig.ignores(n))return!0}}return!1}invalidate(e){const t=i.resolve(e),r=i.dirname(t);for(const[,e]of this.repos){if(!i.relative(e.root,r).startsWith(".."))return e.entries=e.entries.filter((e=>e.dir!==r)),void(a.existsSync(t)&&(e.entries.push({dir:r,ig:B(t)}),e.entries.sort(((e,t)=>t.dir.length-e.dir.length))))}const n=L(r);if(n&&a.existsSync(t)){const e=[{dir:r,ig:B(t)}];if(this.repos.has(n)){const t=this.repos.get(n);t.entries.push(e[0]),t.entries.sort(((e,t)=>t.dir.length-e.dir.length))}else this.repos.set(n,{root:n,entries:e})}}}const K=p.unified().use(f,{fragment:!0});function U(e){e.registerHelper("adfToMarkdown",(function(t){if(!t||"object"!=typeof t)return"";try{const r=u.fromADF(t);return new e.SafeString(g.toMarkdown(r).trim())}catch{return"\x3c!-- ADF conversion failed --\x3e"}})),e.registerHelper("markdownify",(function(t){if("string"!=typeof t||!t.trim())return"";try{const r=K.parse(t),i=d.toMdast(r);return new e.SafeString(g.toMarkdown(i).trim())}catch{return"\x3c!-- HTML conversion failed --\x3e"}})),e.registerHelper("dateFormat",(function(e,t){if(null==e)return"";const r="string"==typeof t?t:"YYYY-MM-DD";return h(e).format(r)})),e.registerHelper("join",(function(e,t){if(!Array.isArray(e))return"";const r="string"==typeof t?t:", ";return e.join(r)})),e.registerHelper("pluck",(function(e,t){return Array.isArray(e)&&"string"==typeof t?e.map((e=>e&&"object"==typeof e?e[t]:void 0)):[]})),e.registerHelper("lowercase",(e=>"string"==typeof e?e.toLowerCase():"")),e.registerHelper("uppercase",(e=>"string"==typeof e?e.toUpperCase():"")),e.registerHelper("capitalize",(e=>"string"==typeof e?o.capitalize(e):"")),e.registerHelper("title",(e=>"string"==typeof e?o.title(e):"")),e.registerHelper("camel",(e=>"string"==typeof e?o.camel(e):"")),e.registerHelper("snake",(e=>"string"==typeof e?o.snake(e):"")),e.registerHelper("dash",(e=>"string"==typeof e?o.dash(e):"")),e.registerHelper("default",(function(e,t){return e??t??""})),e.registerHelper("eq",(function(e,t){return o.isEqual(e,t)})),e.registerHelper("json",(function(t){return new e.SafeString(JSON.stringify(t,null,2))}))}function V(e,t,r,n=new Set){if(e.endsWith(".hbs")||e.endsWith(".handlebars"))return a.readFileSync(i.resolve(r,e),"utf-8");if(void 0!==t?.[e]){if(n.has(e))throw new Error(`Circular template reference detected: ${e}`);return n.add(e),V(t[e],t,r,n)}return e}function Y(){const e=l.create();return U(e),e}async function Z(e,t,r){for(const n of t){const t=i.resolve(r,n),o=await import(t);"function"==typeof o.default&&o.default(e)}}class X{hbs;compiled=new Map;constructor(e){this.hbs=e}compile(e,t){const r=this.hbs.compile(t);return this.compiled.set(e,r),r}get(e){return this.compiled.get(e)}render(e,t){const r=this.compiled.get(e);return r?r(t):null}}async function ee(e,t,r,i){if(0===e.filter((e=>e.template)).length)return;const n=Y();r?.length&&i&&await Z(n,r,i);const o=new X(n);for(const[r,n]of e.entries()){if(!n.template)continue;const e=V(n.template,t,i??".");o.compile(`rule-${String(r)}`,e)}return o}class te{options;watcher;debounce;constructor(e){this.options=e}start(){this.options.enabled&&(this.watcher=m.watch(this.options.configPath,{ignoreInitial:!0}),this.watcher.on("change",(()=>{this.debounce&&clearTimeout(this.debounce),this.debounce=setTimeout((()=>{this.options.onChange()}),this.options.debounceMs)})),this.watcher.on("error",(e=>{this.options.logger.error({err:T(e)},"Config watcher error")})),this.options.logger.info({configPath:this.options.configPath,debounceMs:this.options.debounceMs},"Config watcher started"))}async stop(){this.debounce&&(clearTimeout(this.debounce),this.debounce=void 0),this.watcher&&(await this.watcher.close(),this.watcher=void 0)}}const re={metadataDir:".jeeves-watcher",shutdownTimeoutMs:1e4},ie={enabled:!0,debounceMs:1e3},ne={host:"127.0.0.1",port:3456},oe={level:"info"},se={debounceMs:300,stabilityThresholdMs:500,usePolling:!1,pollIntervalMs:1e3,respectGitignore:!0},ae={chunkSize:1e3,chunkOverlap:200,dimensions:3072,rateLimitPerMinute:300,concurrency:5},ce=w.z.object({paths:w.z.array(w.z.string()).min(1).describe('Glob patterns for files to watch (e.g., "**/*.md"). At least one required.'),ignored:w.z.array(w.z.string()).optional().describe('Glob patterns to exclude from watching (e.g., "**/node_modules/**").'),pollIntervalMs:w.z.number().optional().describe("Polling interval in milliseconds when usePolling is enabled."),usePolling:w.z.boolean().optional().describe("Use polling instead of native file system events (for network drives)."),debounceMs:w.z.number().optional().describe("Debounce delay in milliseconds for file change events."),stabilityThresholdMs:w.z.number().optional().describe("Time in milliseconds a file must remain unchanged before processing."),respectGitignore:w.z.boolean().optional().describe("Skip files ignored by .gitignore in git repositories. Only applies to repos with a .git directory. Default: true.")}),le=w.z.object({enabled:w.z.boolean().optional().describe("Enable automatic reloading when config file changes."),debounceMs:w.z.number().optional().describe("Debounce delay in milliseconds for config file change detection.")}),he=w.z.object({provider:w.z.string().default("gemini").describe('Embedding provider name (e.g., "gemini", "openai").'),model:w.z.string().default("gemini-embedding-001").describe('Embedding model identifier (e.g., "gemini-embedding-001", "text-embedding-3-small").'),chunkSize:w.z.number().optional().describe("Maximum chunk size in characters for text splitting."),chunkOverlap:w.z.number().optional().describe("Character overlap between consecutive chunks."),dimensions:w.z.number().optional().describe("Embedding vector dimensions (must match model output)."),apiKey:w.z.string().optional().describe("API key for embedding provider (supports ${ENV_VAR} substitution)."),rateLimitPerMinute:w.z.number().optional().describe("Maximum embedding API requests per minute (rate limiting)."),concurrency:w.z.number().optional().describe("Maximum concurrent embedding requests.")}),de=w.z.object({url:w.z.string().describe('Qdrant server URL (e.g., "http://localhost:6333").'),collectionName:w.z.string().describe("Qdrant collection name for vector storage."),apiKey:w.z.string().optional().describe("Qdrant API key for authentication (supports ${ENV_VAR} substitution).")}),ue=w.z.object({host:w.z.string().optional().describe('Host address for API server (e.g., "127.0.0.1", "0.0.0.0").'),port:w.z.number().optional().describe("Port for API server (e.g., 3456).")}),ge=w.z.object({level:w.z.string().optional().describe("Logging level (trace, debug, info, warn, error, fatal)."),file:w.z.string().optional().describe("Path to log file (logs to stdout if omitted).")}),fe=w.z.object({match:w.z.record(w.z.string(),w.z.unknown()).describe("JSON Schema object to match against file attributes."),set:w.z.record(w.z.string(),w.z.unknown()).describe("Metadata fields to set when match succeeds."),map:w.z.union([b.jsonMapMapSchema,w.z.string()]).optional().describe("JsonMap transformation (inline definition, named map reference, or .json file path)."),template:w.z.string().optional().describe("Handlebars content template (inline string, named ref, or .hbs/.handlebars file path).")}),pe=w.z.object({watch:ce.describe("File system watch configuration."),configWatch:le.optional().describe("Configuration file watch settings."),embedding:he.describe("Embedding model configuration."),vectorStore:de.describe("Qdrant vector store configuration."),metadataDir:w.z.string().optional().describe("Directory for persisted metadata sidecar files."),api:ue.optional().describe("API server configuration."),extractors:w.z.record(w.z.string(),w.z.unknown()).optional().describe("Extractor configurations keyed by name."),inferenceRules:w.z.array(fe).optional().describe("Rules for inferring metadata from file attributes."),maps:w.z.record(w.z.string(),b.jsonMapMapSchema).optional().describe("Reusable named JsonMap transformations."),templates:w.z.record(w.z.string(),w.z.string()).optional().describe("Named reusable Handlebars templates (inline strings or .hbs/.handlebars file paths)."),templateHelpers:w.z.object({paths:w.z.array(w.z.string()).optional().describe("File paths to custom helper modules.")}).optional().describe("Custom Handlebars helper registration."),logging:ge.optional().describe("Logging configuration."),shutdownTimeoutMs:w.z.number().optional().describe("Timeout in milliseconds for graceful shutdown."),maxRetries:w.z.number().optional().describe("Maximum consecutive system-level failures before triggering fatal error. Default: Infinity."),maxBackoffMs:w.z.number().optional().describe("Maximum backoff delay in milliseconds for system errors. Default: 60000.")}),me=/\$\{([^}]+)\}/g;function ye(e){if("string"==typeof e)return function(e){return e.replace(me,((e,t)=>{const r=process.env[t];return void 0===r?e:r}))}(e);if(Array.isArray(e))return e.map((e=>ye(e)));if(null!==e&&"object"==typeof e){const t={};for(const[r,i]of Object.entries(e))t[r]=ye(i);return t}return e}const we="jeeves-watcher";async function be(e){const t=y.cosmiconfig(we),r=e?await t.load(e):await t.search();if(!r||r.isEmpty)throw new Error("No jeeves-watcher configuration found. Create a .jeeves-watcherrc or jeeves-watcher.config.{js,ts,json,yaml} file.");try{const e=pe.parse(r.config);return ye((i=e,{...re,...i,watch:{...se,...i.watch},configWatch:{...ie,...i.configWatch},embedding:{...ae,...i.embedding},api:{...ne,...i.api},logging:{...oe,...i.logging}}))}catch(e){if(e instanceof w.ZodError){const t=e.issues.map((e=>`${e.path.join(".")}: ${e.message}`)).join("; ");throw new Error(`Invalid jeeves-watcher configuration: ${t}`)}throw e}var i}function ve(e){return e||{warn(e,t){t?console.warn(e,t):console.warn(e)}}}function Me(e,t){return e<=0?Promise.resolve():new Promise(((r,i)=>{const n=setTimeout((()=>{s(),r()}),e),o=()=>{s(),i(new Error("Retry sleep aborted"))},s=()=>{clearTimeout(n),t&&t.removeEventListener("abort",o)};if(t){if(t.aborted)return void o();t.addEventListener("abort",o,{once:!0})}}))}function Se(e,t,r,i=0){const n=Math.max(0,e-1),o=Math.min(r,t*2**n),s=i>0?1+Math.random()*i:1;return Math.round(o*s)}async function Fe(e,t){const r=Math.max(1,t.attempts);let i;for(let n=1;n<=r;n++)try{return await e(n)}catch(e){i=e;if(n>=r)break;const o=Se(n,t.baseDelayMs,t.maxDelayMs,t.jitter);t.onRetry?.({attempt:n,attempts:r,delayMs:o,error:e}),await Me(o,t.signal)}throw i}const xe=new Map([["mock",function(e){return function(e){return{dimensions:e,embed:t=>Promise.resolve(t.map((t=>{const r=s.createHash("sha256").update(t,"utf8").digest(),i=[];for(let t=0;t<e;t++){const e=r[t%r.length];i.push(e/127.5-1)}return i})))}}(e.dimensions??768)}],["gemini",function(e,t){if(!e.apiKey)throw new Error("Gemini embedding provider requires config.embedding.apiKey");const r=e.dimensions??3072,i=ve(t),n=new v.GoogleGenerativeAIEmbeddings({apiKey:e.apiKey,model:e.model});return{dimensions:r,async embed(t){const o=await Fe((async r=>(r>1&&i.warn({attempt:r,provider:"gemini",model:e.model},"Retrying embedding request"),n.embedDocuments(t))),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:t,delayMs:r,error:n})=>{i.warn({attempt:t,delayMs:r,provider:"gemini",model:e.model,err:T(n)},"Embedding call failed; will retry")}});for(const e of o)if(e.length!==r)throw new Error(`Gemini embedding returned invalid dimensions: expected ${String(r)}, got ${String(e.length)}`);return o}}}]]);function ke(e,t){const r=xe.get(e.provider);if(!r)throw new Error(`Unsupported embedding provider: ${e.provider}`);return r(e,t)}function Pe(e){const t=e?.level??"info";if(e?.file){const r=M.transport({target:"pino/file",options:{destination:e.file,mkdir:!0}});return M({level:t},r)}return M({level:t})}function ze(e){return s.createHash("sha256").update(e,"utf8").digest("hex")}const je="6a6f686e-6761-4c74-ad6a-656576657321";function Ce(e,t){const r=void 0!==t?`${W(e)}#${String(t)}`:W(e);return S.v5(r,je)}const Ee=["content","body","text","snippet","subject","description","summary","transcript"];function Re(e){if(!e||"object"!=typeof e)return JSON.stringify(e);const t=e;for(const e of Ee){const r=t[e];if("string"==typeof r&&r.trim())return r}return JSON.stringify(e)}async function Te(e){const t=await r.readFile(e,"utf8"),{frontmatter:i,body:n}=function(e){const t=e.replace(/^\uFEFF/,"");if(!/^\s*---/.test(t))return{body:e};const r=/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(t);if(!r)return{body:e};const[,i,n]=r,o=x.load(i);return{frontmatter:o&&"object"==typeof o&&!Array.isArray(o)?o:void 0,body:n}}(t);return{text:n,frontmatter:i}}async function De(e){return{text:(await r.readFile(e,"utf8")).replace(/^\uFEFF/,"")}}async function Ie(e){const t=await r.readFile(e,"utf8"),i=R.load(t.replace(/^\uFEFF/,""));i("script, style").remove();return{text:i("body").text().trim()||i.text().trim()}}const Ae=new Map([[".md",Te],[".markdown",Te],[".txt",De],[".text",De],[".json",async function(e){const t=await r.readFile(e,"utf8"),i=JSON.parse(t.replace(/^\uFEFF/,"")),n=i&&"object"==typeof i&&!Array.isArray(i)?i:void 0;return{text:Re(i),json:n}}],[".pdf",async function(e){const t=await r.readFile(e),i=new Uint8Array(t),{extractText:n}=await import("unpdf"),{text:o}=await n(i);return{text:Array.isArray(o)?o.join("\n\n"):o}}],[".docx",async function(e){const t=await r.readFile(e);return{text:(await k.extractRawText({buffer:t})).value}}],[".html",Ie],[".htm",Ie]]);async function We(e,t){const r=Ae.get(t.toLowerCase());return r?r(e):De(e)}function Ne(e,t){return"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,((e,r)=>{const i=o.get(t,r);return null==i?"":"string"==typeof i?i:JSON.stringify(i)}))}function _e(e,t){const r={};for(const[i,n]of Object.entries(e))r[i]=Ne(n,t);return r}async function He(e,t,r,n,s,c){const l={split:(e,t)=>e.split(t),slice:(e,t,r)=>e.slice(t,r),join:(e,t)=>e.join(t),toLowerCase:e=>e.toLowerCase(),replace:(e,t,r)=>e.replace(t,r),get:(e,t)=>o.get(e,t)};let h={},d=null;const u=n??console;for(const[n,{rule:o,validate:g}]of e.entries())if(g(t)){const e=_e(o.set,t);if(h={...h,...e},o.map){let e;if("string"==typeof o.map){if(o.map.endsWith(".json")&&c)try{const t=i.resolve(c,o.map),r=a.readFileSync(t,"utf-8");e=JSON.parse(r)}catch(e){u.warn(`Failed to load map file "${o.map}": ${e instanceof Error?e.message:String(e)}`);continue}else if(e=r?.[o.map],!e){u.warn(`Map reference "${o.map}" not found in named maps. Skipping map transformation.`);continue}}else e=o.map;try{const r=new b.JsonMap(e,l),i=await r.transform(t);i&&"object"==typeof i&&!Array.isArray(i)?h={...h,...i}:u.warn("JsonMap transformation did not return an object; skipping merge.")}catch(e){u.warn(`JsonMap transformation failed: ${e instanceof Error?e.message:String(e)}`)}}if(o.template&&s){const e=`rule-${String(n)}`,r={...t.json??{},...t,...h};try{const t=s.render(e,r);t&&t.trim()?d=t:u.warn(`Template for rule ${String(n)} rendered empty output. Falling back to raw content.`)}catch(e){u.warn(`Template render failed for rule ${String(n)}: ${e instanceof Error?e.message:String(e)}. Falling back to raw content.`)}}}return{metadata:h,renderedContent:d}}function Oe(e,t,r,n){const o=e.replace(/\\/g,"/"),s={file:{path:o,directory:i.dirname(o).replace(/\\/g,"/"),filename:i.basename(o),extension:i.extname(o),sizeBytes:t.size,modified:t.mtime.toISOString()}};return r&&(s.frontmatter=r),n&&(s.json=n),s}function $e(e){const t=function(){const e=new P({allErrors:!0});return z(e),e.addKeyword({keyword:"glob",type:"string",schemaType:"string",validate:(e,t)=>n.isMatch(t,e)}),e}();return e.map(((e,r)=>({rule:e,validate:t.compile({$id:`rule-${String(r)}`,...e.match})})))}async function qe(e,t,n,o,s,a,c){const l=i.extname(e),h=await r.stat(e),d=await We(e,l),u=Oe(e,h,d.frontmatter,d.json),{metadata:g,renderedContent:f}=await He(t,u,o,s,a,c),p=await _(e,n);return{inferred:g,enrichment:p,metadata:{...g,...p??{}},attributes:u,extracted:d,renderedContent:f}}function Le(e,t){const r=[];for(let i=0;i<t;i++)r.push(Ce(e,i));return r}function Qe(e,t=1){if(!e)return t;const r=e.total_chunks;return"number"==typeof r?r:t}class Ge{config;embeddingProvider;vectorStore;compiledRules;logger;templateEngine;constructor(e,t,r,i,n,o){this.config=e,this.embeddingProvider=t,this.vectorStore=r,this.compiledRules=i,this.logger=n,this.templateEngine=o}async processFile(e){try{const t=i.extname(e),{metadata:r,extracted:n,renderedContent:o}=await qe(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger,this.templateEngine,this.config.configDir),s=o??n.text;if(!s.trim())return void this.logger.debug({filePath:e},"Skipping empty file");const a=ze(s),c=Ce(e,0),l=await this.vectorStore.getPayload(c);if(l&&l.content_hash===a)return void this.logger.debug({filePath:e},"Content unchanged, skipping");const h=Qe(l),d=this.config.chunkSize??1e3,u=function(e,t,r){const i=e.toLowerCase();return".md"===i||".markdown"===i?new j.MarkdownTextSplitter({chunkSize:t,chunkOverlap:r}):new j.RecursiveCharacterTextSplitter({chunkSize:t,chunkOverlap:r})}(t,d,this.config.chunkOverlap??200),g=await u.splitText(s),f=await this.embeddingProvider.embed(g),p=g.map(((t,i)=>({id:Ce(e,i),vector:f[i],payload:{...r,file_path:e.replace(/\\/g,"/"),chunk_index:i,total_chunks:g.length,content_hash:a,chunk_text:t}})));if(await this.vectorStore.upsert(p),h>g.length){const t=Le(e,h).slice(g.length);await this.vectorStore.delete(t)}this.logger.info({filePath:e,chunks:g.length},"File processed successfully")}catch(t){this.logger.error({filePath:e,err:T(t)},"Failed to process file")}}async deleteFile(e){try{const t=Ce(e,0),r=await this.vectorStore.getPayload(t),i=Le(e,Qe(r));await this.vectorStore.delete(i),await O(e,this.config.metadataDir),this.logger.info({filePath:e},"File deleted from index")}catch(t){this.logger.error({filePath:e,err:T(t)},"Failed to delete file")}}async processMetadataUpdate(e,t){try{const r={...await _(e,this.config.metadataDir)??{},...t};await H(e,this.config.metadataDir,r);const i=Ce(e,0),n=await this.vectorStore.getPayload(i);if(!n)return null;const o=Qe(n),s=Le(e,o);return await this.vectorStore.setPayload(s,r),this.logger.info({filePath:e,chunks:o},"Metadata updated"),r}catch(t){return this.logger.error({filePath:e,err:T(t)},"Failed to update metadata"),null}}async processRulesUpdate(e){try{const t=Ce(e,0),r=await this.vectorStore.getPayload(t);if(!r)return this.logger.debug({filePath:e},"File not indexed, skipping"),null;const{metadata:i}=await qe(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger,this.templateEngine,this.config.configDir),n=Qe(r),o=Le(e,n);return await this.vectorStore.setPayload(o,i),this.logger.info({filePath:e,chunks:n},"Rules re-applied"),i}catch(t){return this.logger.error({filePath:e,err:T(t)},"Failed to re-apply rules"),null}}updateRules(e,t){this.compiledRules=e,t&&(this.templateEngine=t),this.logger.info({rules:e.length},"Inference rules updated")}}class Be{debounceMs;concurrency;rateLimitPerMinute;started=!1;active=0;debounceTimers=new Map;latestByKey=new Map;normalQueue=[];lowQueue=[];tokens;lastRefillMs=Date.now();drainWaiters=[];constructor(e){this.debounceMs=e.debounceMs,this.concurrency=e.concurrency,this.rateLimitPerMinute=e.rateLimitPerMinute,this.tokens=this.rateLimitPerMinute??Number.POSITIVE_INFINITY}enqueue(e,t){const r=`${e.priority}:${e.path}`;this.latestByKey.set(r,{event:e,fn:t});const i=this.debounceTimers.get(r);i&&clearTimeout(i);const n=setTimeout((()=>{this.debounceTimers.delete(r);const e=this.latestByKey.get(r);e&&(this.latestByKey.delete(r),this.push(e),this.pump())}),this.debounceMs);this.debounceTimers.set(r,n)}process(){this.started=!0,this.pump()}async drain(){this.isIdle()||await new Promise((e=>{this.drainWaiters.push(e)}))}push(e){"low"===e.event.priority?this.lowQueue.push(e):this.normalQueue.push(e)}refillTokens(e){if(void 0===this.rateLimitPerMinute)return;const t=Math.max(0,e-this.lastRefillMs)*(this.rateLimitPerMinute/6e4);this.tokens=Math.min(this.rateLimitPerMinute,this.tokens+t),this.lastRefillMs=e}takeToken(){const e=Date.now();return this.refillTokens(e),!(this.tokens<1)&&(this.tokens-=1,!0)}nextItem(){return this.normalQueue.shift()??this.lowQueue.shift()}pump(){if(this.started){for(;this.active<this.concurrency;){const e=this.nextItem();if(!e)break;if(!this.takeToken()){"low"===e.event.priority?this.lowQueue.unshift(e):this.normalQueue.unshift(e),setTimeout((()=>{this.pump()}),250);break}this.active+=1,Promise.resolve().then((()=>e.fn(e.event))).finally((()=>{this.active-=1,this.pump(),this.maybeResolveDrain()}))}this.maybeResolveDrain()}}isIdle(){return 0===this.active&&0===this.normalQueue.length&&0===this.lowQueue.length&&0===this.debounceTimers.size&&0===this.latestByKey.size}maybeResolveDrain(){if(!this.isIdle())return;const e=this.drainWaiters;this.drainWaiters=[];for(const t of e)t()}}function Je(e){return null==e?"keyword":"number"==typeof e?Number.isInteger(e)?"integer":"float":"boolean"==typeof e?"bool":Array.isArray(e)?"keyword[]":"string"==typeof e&&e.length>256?"text":"keyword"}class Ke{client;collectionName;dims;log;constructor(e,t,r){this.client=new C.QdrantClient({url:e.url,apiKey:e.apiKey,checkCompatibility:!1}),this.collectionName=e.collectionName,this.dims=t,this.log=ve(r)}async ensureCollection(){try{const e=await this.client.getCollections();e.collections.some((e=>e.name===this.collectionName))||await this.client.createCollection(this.collectionName,{vectors:{size:this.dims,distance:"Cosine"}})}catch(e){throw new Error(`Failed to ensure collection "${this.collectionName}": ${String(e)}`)}}async upsert(e){0!==e.length&&await Fe((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.upsert",points:e.length},"Retrying Qdrant upsert"),await this.client.upsert(this.collectionName,{wait:!0,points:e.map((e=>({id:e.id,vector:e.vector,payload:e.payload})))})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:r})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.upsert",err:T(r)},"Qdrant upsert failed; will retry")}})}async delete(e){0!==e.length&&await Fe((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.delete",ids:e.length},"Retrying Qdrant delete"),await this.client.delete(this.collectionName,{wait:!0,points:e})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:r})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.delete",err:T(r)},"Qdrant delete failed; will retry")}})}async setPayload(e,t){0!==e.length&&await this.client.setPayload(this.collectionName,{wait:!0,points:e,payload:t})}async getPayload(e){try{const t=await this.client.retrieve(this.collectionName,{ids:[e],with_payload:!0,with_vector:!1});return 0===t.length?null:t[0].payload}catch{return null}}async getCollectionInfo(){const e=await this.client.getCollection(this.collectionName),t=e.points_count??0,r=e.config.params.vectors,i=void 0!==r&&"size"in r?r.size:0,n={},o=Object.entries(e.payload_schema);if(o.length>0)for(const[e,t]of o)n[e]={type:t.data_type??"unknown"};else t>0&&await this.discoverPayloadFields(n);return{pointCount:t,dimensions:i,payloadFields:n}}async discoverPayloadFields(e,t=100){const r=await this.client.scroll(this.collectionName,{limit:t,with_payload:!0,with_vector:!1});for(const t of r.points){const r=t.payload;if(r)for(const[t,i]of Object.entries(r))t in e||(e[t]={type:Je(i)})}}async search(e,t,r){return(await this.client.search(this.collectionName,{vector:e,limit:t,with_payload:!0,...r?{filter:r}:{}})).map((e=>({id:String(e.id),score:e.score,payload:e.payload})))}async*scroll(e,t=100){let r;for(;;){const i=await this.client.scroll(this.collectionName,{limit:t,with_payload:!0,with_vector:!1,...e?{filter:e}:{},...void 0!==r?{offset:r}:{}});for(const e of i.points)yield{id:String(e.id),payload:e.payload};const n=i.next_page_offset;if(null==n)break;if("string"!=typeof n&&"number"!=typeof n)break;r=n}}}class Ue{consecutiveFailures=0;maxRetries;maxBackoffMs;baseDelayMs;onFatalError;logger;constructor(e){this.maxRetries=e.maxRetries??Number.POSITIVE_INFINITY,this.maxBackoffMs=e.maxBackoffMs??6e4,this.baseDelayMs=e.baseDelayMs??1e3,this.onFatalError=e.onFatalError,this.logger=e.logger}recordSuccess(){this.consecutiveFailures>0&&this.logger.info({previousFailures:this.consecutiveFailures},"System health recovered"),this.consecutiveFailures=0}recordFailure(e){if(this.consecutiveFailures+=1,this.logger.error({consecutiveFailures:this.consecutiveFailures,maxRetries:this.maxRetries,err:T(e)},"System-level failure recorded"),this.consecutiveFailures>=this.maxRetries){if(this.logger.fatal({consecutiveFailures:this.consecutiveFailures},"Maximum retries exceeded, triggering fatal error"),this.onFatalError)return this.onFatalError(e),!1;throw e instanceof Error?e:new Error(`Fatal system error: ${String(e)}`)}return!0}get currentBackoffMs(){if(0===this.consecutiveFailures)return 0;const e=Math.max(0,this.consecutiveFailures-1);return Math.min(this.maxBackoffMs,this.baseDelayMs*2**e)}async backoff(e){const t=this.currentBackoffMs;t<=0||(this.logger.warn({delayMs:t,consecutiveFailures:this.consecutiveFailures},"Backing off before next attempt"),await new Promise(((r,i)=>{const n=setTimeout((()=>{s(),r()}),t),o=()=>{s(),i(new Error("Backoff aborted"))},s=()=>{clearTimeout(n),e&&e.removeEventListener("abort",o)};if(e){if(e.aborted)return void o();e.addEventListener("abort",o,{once:!0})}})))}get failures(){return this.consecutiveFailures}}function Ve(e){const t=e.replace(/\\/g,"/").split("/"),r=[];for(const e of t){if(/[*?{[\]]/.test(e))break;r.push(e)}return r.join("/")||"."}function Ye(e){const t=function(e){const t=e.map((e=>e.replace(/\\/g,"/").toLowerCase()));return[...new Set(t)].sort().filter(((e,t,r)=>{const i=e.endsWith("/")?e:e+"/";return!r.some((t=>t!==e&&i.startsWith(t+"/")))}))}(e.map(Ve)),r=function(e){const t=e.map((e=>e.replace(/\\/g,"/"))),r=n(t,{dot:!0,nocase:!0});return e=>{const t=e.replace(/\\/g,"/");return r(t)}}(e);return{roots:t,matches:r}}class Ze{config;queue;processor;logger;health;gitignoreFilter;globMatches;watcher;constructor(e,t,r,i,n={}){this.config=e,this.queue=t,this.processor=r,this.logger=i,this.gitignoreFilter=n.gitignoreFilter,this.globMatches=()=>!0;const o={maxRetries:n.maxRetries,maxBackoffMs:n.maxBackoffMs,onFatalError:n.onFatalError,logger:i};this.health=new Ue(o)}start(){const{roots:e,matches:t}=Ye(this.config.paths);this.globMatches=t;const r=this.config.ignored?function(e){return e.map((e=>{if("string"!=typeof e)return e;const t=e.replace(/\\/g,"/"),r=n(t,{dot:!0,nocase:!0});return e=>{const t=e.replace(/\\/g,"/");return r(t)}}))}(this.config.ignored):void 0;this.watcher=m.watch(e,{ignored:r,usePolling:this.config.usePolling,interval:this.config.pollIntervalMs,awaitWriteFinish:!!this.config.stabilityThresholdMs&&{stabilityThreshold:this.config.stabilityThresholdMs},ignoreInitial:!1}),this.watcher.on("add",(e=>{this.handleGitignoreChange(e),this.globMatches(e)&&(this.isGitignored(e)||(this.logger.debug({path:e},"File added"),this.queue.enqueue({type:"create",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.processFile(e)))))))})),this.watcher.on("change",(e=>{this.handleGitignoreChange(e),this.globMatches(e)&&(this.isGitignored(e)||(this.logger.debug({path:e},"File changed"),this.queue.enqueue({type:"modify",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.processFile(e)))))))})),this.watcher.on("unlink",(e=>{this.handleGitignoreChange(e),this.globMatches(e)&&(this.isGitignored(e)||(this.logger.debug({path:e},"File removed"),this.queue.enqueue({type:"delete",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.deleteFile(e)))))))})),this.watcher.on("error",(e=>{this.logger.error({err:T(e)},"Watcher error"),this.health.recordFailure(e)})),this.queue.process(),this.logger.info({paths:this.config.paths},"Filesystem watcher started")}async stop(){this.watcher&&(await this.watcher.close(),this.watcher=void 0,this.logger.info("Filesystem watcher stopped"))}get systemHealth(){return this.health}isGitignored(e){if(!this.gitignoreFilter)return!1;const t=this.gitignoreFilter.isIgnored(e);return t&&this.logger.debug({path:e},"Skipping gitignored file"),t}handleGitignoreChange(e){this.gitignoreFilter&&e.endsWith(".gitignore")&&(this.logger.info({path:e},"Gitignore file changed, refreshing filter"),this.gitignoreFilter.invalidate(e))}async wrapProcessing(e){try{await this.health.backoff(),await e(),this.health.recordSuccess()}catch(e){this.health.recordFailure(e)||await this.stop()}}}const Xe={loadConfig:be,createLogger:Pe,createEmbeddingProvider:ke,createVectorStoreClient:(e,t,r)=>new Ke(e,t,r),compileRules:$e,createDocumentProcessor:(e,t,r,i,n,o)=>new Ge(e,t,r,i,n,o),createEventQueue:e=>new Be(e),createFileSystemWatcher:(e,t,r,i,n)=>new Ze(e,t,r,i,n),createApiServer:q};class et{config;configPath;factories;runtimeOptions;logger;watcher;queue;server;processor;configWatcher;constructor(e,t,r={},i={}){this.config=e,this.configPath=t,this.factories={...Xe,...r},this.runtimeOptions=i}async start(){const e=this.factories.createLogger(this.config.logging);this.logger=e;const{embeddingProvider:t,vectorStore:r}=await this.initEmbeddingAndStore(e),n=this.factories.compileRules(this.config.inferenceRules??[]),o=this.configPath?i.dirname(this.configPath):".",s=await ee(this.config.inferenceRules??[],this.config.templates,this.config.templateHelpers?.paths,o),a=this.factories.createDocumentProcessor({metadataDir:this.config.metadataDir??".jeeves-metadata",chunkSize:this.config.embedding.chunkSize,chunkOverlap:this.config.embedding.chunkOverlap,maps:this.config.maps,configDir:o},t,r,n,e,s);this.processor=a,this.queue=this.factories.createEventQueue({debounceMs:this.config.watch.debounceMs??2e3,concurrency:this.config.embedding.concurrency??5,rateLimitPerMinute:this.config.embedding.rateLimitPerMinute}),this.watcher=this.createWatcher(this.queue,a,e),this.server=await this.startApiServer(a,r,t,e),this.watcher.start(),this.startConfigWatch(),e.info("jeeves-watcher started")}async stop(){if(await this.stopConfigWatch(),this.watcher&&await this.watcher.stop(),this.queue){const e=this.config.shutdownTimeoutMs??1e4;await Promise.race([this.queue.drain().then((()=>!0)),new Promise((t=>{setTimeout((()=>{t(!1)}),e)}))])||this.logger?.warn({timeoutMs:e},"Queue drain timeout hit, forcing shutdown")}this.server&&await this.server.close(),this.logger?.info("jeeves-watcher stopped")}async initEmbeddingAndStore(e){let t;try{t=this.factories.createEmbeddingProvider(this.config.embedding,e)}catch(t){throw e.fatal({err:T(t)},"Failed to create embedding provider"),t}const r=this.factories.createVectorStoreClient(this.config.vectorStore,t.dimensions,e);return await r.ensureCollection(),{embeddingProvider:t,vectorStore:r}}createWatcher(e,t,r){const i=this.config.watch.respectGitignore??!0?new J(this.config.watch.paths):void 0;return this.factories.createFileSystemWatcher(this.config.watch,e,t,r,{maxRetries:this.config.maxRetries,maxBackoffMs:this.config.maxBackoffMs,onFatalError:this.runtimeOptions.onFatalError,gitignoreFilter:i})}async startApiServer(e,t,r,i){const n=this.factories.createApiServer({processor:e,vectorStore:t,embeddingProvider:r,queue:this.queue,config:this.config,logger:i});return await n.listen({host:this.config.api?.host??"127.0.0.1",port:this.config.api?.port??3456}),n}startConfigWatch(){const e=this.logger;if(!e)return;const t=this.config.configWatch?.enabled??!0;t&&this.configPath?(this.configWatcher=new te({configPath:this.configPath,enabled:t,debounceMs:this.config.configWatch?.debounceMs??1e4,logger:e,onChange:async()=>this.reloadConfig()}),this.configWatcher.start()):this.configPath||e.debug("Config watch enabled, but no config path was provided")}async stopConfigWatch(){this.configWatcher&&(await this.configWatcher.stop(),this.configWatcher=void 0)}async reloadConfig(){const e=this.logger,t=this.processor;if(e&&t&&this.configPath){e.info({configPath:this.configPath},"Config change detected, reloading...");try{const r=await this.factories.loadConfig(this.configPath);this.config=r;const n=this.factories.compileRules(r.inferenceRules??[]),o=i.dirname(this.configPath),s=await ee(r.inferenceRules??[],r.templates,r.templateHelpers?.paths,o);t.updateRules(n,s),e.info({configPath:this.configPath,rules:n.length},"Config reloaded")}catch(t){e.error({err:T(t)},"Failed to reload config")}}}}e.DocumentProcessor=Ge,e.EventQueue=Be,e.FileSystemWatcher=Ze,e.GitignoreFilter=J,e.JeevesWatcher=et,e.SystemHealth=Ue,e.TemplateEngine=X,e.VectorStoreClient=Ke,e.apiConfigSchema=ue,e.applyRules=He,e.buildAttributes=Oe,e.buildTemplateEngine=ee,e.compileRules=$e,e.configWatchConfigSchema=le,e.contentHash=ze,e.createApiServer=q,e.createEmbeddingProvider=ke,e.createHandlebarsInstance=Y,e.createLogger=Pe,e.deleteMetadata=O,e.embeddingConfigSchema=he,e.extractText=We,e.inferenceRuleSchema=fe,e.jeevesWatcherConfigSchema=pe,e.loadConfig=be,e.loadCustomHelpers=Z,e.loggingConfigSchema=ge,e.metadataPath=N,e.pointId=Ce,e.readMetadata=_,e.registerBuiltinHelpers=U,e.resolveTemplateSource=V,e.startFromConfig=async function(e){const t=await be(e),r=new et(t,e);return function(e){const t=async()=>{await e(),process.exit(0)};process.on("SIGTERM",(()=>{t()})),process.on("SIGINT",(()=>{t()}))}((()=>r.stop())),await r.start(),r},e.vectorStoreConfigSchema=de,e.watchConfigSchema=ce,e.writeMetadata=H}(this["jeeves-watcher"]=this["jeeves-watcher"]||{},Fastify,promises,node_path,picomatch,radash,node_crypto,node_fs,ignore,Handlebars,dayjs,hastUtilToMdast,mdastUtilFromAdf,mdastUtilToMarkdown,rehypeParse,unified,chokidar,cosmiconfig,zod,jsonmap,googleGenai,pino,uuid,cheerio,yaml,mammoth,Ajv,addFormats,textsplitters,jsClientRest);
|
|
1
|
+
!function(e,t,r,i,n,o,s,a,c,l,h,d,u,g,f,p,m,y,w,b,v,M,S,F,x,k,P,z,j,C){"use strict";function E(e){var t=Object.create(null);return e&&Object.keys(e).forEach((function(r){if("default"!==r){var i=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,i.get?i:{enumerable:!0,get:function(){return e[r]}})}})),t.default=e,Object.freeze(t)}var R=E(F);function T(e){if(e instanceof Error)return e;if("string"==typeof e)return new Error(e);const t=String("object"==typeof e&&null!==e&&"message"in e?e.message:e),r=new Error(t);return r.cause=e,r}function D(e){const t=e.replace(/\\/g,"/"),r=t.search(/[*?\[]/);if(-1===r)return i.resolve(e);const n=t.slice(0,r),o=n.endsWith("/")?n.slice(0,-1):i.dirname(n);return i.resolve(o)}async function*I(e){let t;try{t=(await r.readdir(e,{withFileTypes:!0})).map((e=>({name:e.name,isDirectory:e.isDirectory()})))}catch{return}for(const n of t){const t=i.resolve(e,n.name);if(n.isDirectory)yield*I(t);else try{(await r.stat(t)).isFile()&&(yield t)}catch{}}}async function A(e,t,r,i){const o=await async function(e,t=[]){const r=e.map((e=>e.replace(/\\/g,"/"))),i=t.map((e=>e.replace(/\\/g,"/"))),o=n(r,{dot:!0}),s=i.length?n(i,{dot:!0}):()=>!1,a=Array.from(new Set(e.map(D))),c=new Set;for(const e of a)for await(const t of I(e)){const e=t.replace(/\\/g,"/");s(e)||o(e)&&c.add(t)}return Array.from(c)}(e,t);for(const e of o)await r[i](e);return o.length}function W(e,t=!1){let r=e.replace(/\\/g,"/").toLowerCase();return t&&(r=r.replace(/^([a-z]):/,((e,t)=>t))),r}function N(e,t){const r=W(e,!0),n=s.createHash("sha256").update(r,"utf8").digest("hex");return i.join(t,`${n}.meta.json`)}async function O(e,t){try{const i=await r.readFile(N(e,t),"utf8");return JSON.parse(i)}catch{return null}}async function _(e,t,n){const o=N(e,t);await r.mkdir(i.dirname(o),{recursive:!0}),await r.writeFile(o,JSON.stringify(n,null,2),"utf8")}async function H(e,t){try{await r.rm(N(e,t))}catch{}}const $=["file_path","chunk_index","total_chunks","content_hash","chunk_text"];function q(e){const{processor:r,vectorStore:i,embeddingProvider:n,logger:s,config:a}=e,c=t({logger:!1});var l;return c.get("/status",(l={vectorStore:i,config:a},async()=>{const e=await l.vectorStore.getCollectionInfo();return{status:"ok",uptime:process.uptime(),collection:{name:l.config.vectorStore.collectionName,pointCount:e.pointCount,dimensions:e.dimensions},payloadFields:e.payloadFields}})),c.post("/metadata",function(e){return async(t,r)=>{try{const{path:r,metadata:i}=t.body;return await e.processor.processMetadataUpdate(r,i),{ok:!0}}catch(t){return e.logger.error({err:T(t)},"Metadata update failed"),r.status(500).send({error:"Internal server error"})}}}({processor:r,logger:s})),c.post("/search",function(e){return async(t,r)=>{try{const{query:r,limit:i=10,filter:n}=t.body,o=await e.embeddingProvider.embed([r]);return await e.vectorStore.search(o[0],i,n)}catch(t){return e.logger.error({err:T(t)},"Search failed"),r.status(500).send({error:"Internal server error"})}}}({embeddingProvider:n,vectorStore:i,logger:s})),c.post("/reindex",function(e){return async(t,r)=>{try{const t=await A(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");return await r.status(200).send({ok:!0,filesIndexed:t})}catch(t){return e.logger.error({err:T(t)},"Reindex failed"),await r.status(500).send({error:"Internal server error"})}}}({config:a,processor:r,logger:s})),c.post("/rebuild-metadata",function(e){return async(t,r)=>{try{const t=e.config.metadataDir??".jeeves-metadata",i=[...$];for await(const r of e.vectorStore.scroll()){const e=r.payload,n=e.file_path;if("string"!=typeof n||0===n.length)continue;const s=o.omit(e,i);await _(n,t,s)}return await r.status(200).send({ok:!0})}catch(t){return e.logger.error({err:T(t)},"Rebuild metadata failed"),await r.status(500).send({error:"Internal server error"})}}}({config:a,vectorStore:i,logger:s})),c.post("/config-reindex",function(e){return async(t,r)=>{try{const i=t.body.scope??"rules";return(async()=>{try{if("rules"===i){const t=await A(e.config.watch.paths,e.config.watch.ignored,e.processor,"processRulesUpdate");e.logger.info({scope:i,filesProcessed:t},"Config reindex (rules) completed")}else{const t=await A(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");e.logger.info({scope:i,filesProcessed:t},"Config reindex (full) completed")}}catch(t){e.logger.error({err:T(t),scope:i},"Config reindex failed")}})(),await r.status(200).send({status:"started",scope:i})}catch(t){return e.logger.error({err:T(t)},"Config reindex request failed"),await r.status(500).send({error:"Internal server error"})}}}({config:a,processor:r,logger:s})),c}function L(e){let t=i.resolve(e);const r=i.resolve("/");for(;t!==r;){if(a.existsSync(i.join(t,".git"))&&a.statSync(i.join(t,".git")).isDirectory())return t;const e=i.dirname(t);if(e===t)break;t=e}}function Q(e){const t=i.resolve(e);try{return a.statSync(t).isDirectory()?t:i.dirname(t)}catch{}const r=/[*?[{]/.exec(e);if(!r)return;const n=e.slice(0,r.index).trim(),o=0===n.length?".":n.endsWith("/")||n.endsWith("\\")?n:i.dirname(n),s=i.resolve(o);return a.existsSync(s)?s:void 0}function G(e){const t=[],r=i.join(e,".gitignore");let n;a.existsSync(r)&&t.push(r);try{n=a.readdirSync(e)}catch{return t}for(const r of n){if(".git"===r||"node_modules"===r)continue;const n=i.join(e,r);try{a.statSync(n).isDirectory()&&t.push(...G(n))}catch{}}return t}function B(e){const t=a.readFileSync(e,"utf8");return c().add(t)}class J{repos=new Map;constructor(e){this.scan(e)}scan(e){this.repos.clear();const t=new Set;for(const r of e){const e=Q(r);if(!e)continue;if(t.has(e))continue;t.add(e);const n=L(e);if(!n)continue;if(this.repos.has(n))continue;const o=G(n).map((e=>({dir:i.dirname(e),ig:B(e)})));o.sort(((e,t)=>t.dir.length-e.dir.length)),this.repos.set(n,{root:n,entries:o})}}isIgnored(e){const t=i.resolve(e);for(const[,e]of this.repos){const r=i.relative(e.root,t);if(!(r.startsWith("..")||r.startsWith(i.resolve("/"))||/^[a-zA-Z]:/.test(r)))for(const r of e.entries){const e=i.relative(r.dir,t);if(e.startsWith("..")||/^[a-zA-Z]:/.test(e))continue;const n=e.replace(/\\/g,"/");if(r.ig.ignores(n))return!0}}return!1}invalidate(e){const t=i.resolve(e),r=i.dirname(t);for(const[,e]of this.repos){if(!i.relative(e.root,r).startsWith(".."))return e.entries=e.entries.filter((e=>e.dir!==r)),void(a.existsSync(t)&&(e.entries.push({dir:r,ig:B(t)}),e.entries.sort(((e,t)=>t.dir.length-e.dir.length))))}const n=L(r);if(n&&a.existsSync(t)){const e=[{dir:r,ig:B(t)}];if(this.repos.has(n)){const t=this.repos.get(n);t.entries.push(e[0]),t.entries.sort(((e,t)=>t.dir.length-e.dir.length))}else this.repos.set(n,{root:n,entries:e})}}}const K=p.unified().use(f,{fragment:!0});function U(e){e.registerHelper("adfToMarkdown",(function(t){if(!t||"object"!=typeof t)return"";try{const r=u.fromADF(t);return new e.SafeString(g.toMarkdown(r).trim())}catch{return"\x3c!-- ADF conversion failed --\x3e"}})),e.registerHelper("markdownify",(function(t){if("string"!=typeof t||!t.trim())return"";try{const r=K.parse(t),i=d.toMdast(r);return new e.SafeString(g.toMarkdown(i).trim())}catch{return"\x3c!-- HTML conversion failed --\x3e"}})),e.registerHelper("dateFormat",(function(e,t){if(null==e)return"";const r="string"==typeof t?t:"YYYY-MM-DD";return h(e).format(r)})),e.registerHelper("join",(function(e,t){if(!Array.isArray(e))return"";const r="string"==typeof t?t:", ";return e.join(r)})),e.registerHelper("pluck",(function(e,t){return Array.isArray(e)&&"string"==typeof t?e.map((e=>e&&"object"==typeof e?e[t]:void 0)):[]})),e.registerHelper("lowercase",(e=>"string"==typeof e?e.toLowerCase():"")),e.registerHelper("uppercase",(e=>"string"==typeof e?e.toUpperCase():"")),e.registerHelper("capitalize",(e=>"string"==typeof e?o.capitalize(e):"")),e.registerHelper("title",(e=>"string"==typeof e?o.title(e):"")),e.registerHelper("camel",(e=>"string"==typeof e?o.camel(e):"")),e.registerHelper("snake",(e=>"string"==typeof e?o.snake(e):"")),e.registerHelper("dash",(e=>"string"==typeof e?o.dash(e):"")),e.registerHelper("default",(function(e,t){return e??t??""})),e.registerHelper("eq",(function(e,t){return o.isEqual(e,t)})),e.registerHelper("json",(function(t){return new e.SafeString(JSON.stringify(t,null,2))}))}function V(e,t,r,n=new Set){if(e.endsWith(".hbs")||e.endsWith(".handlebars"))return a.readFileSync(i.resolve(r,e),"utf-8");if(void 0!==t?.[e]){if(n.has(e))throw new Error(`Circular template reference detected: ${e}`);return n.add(e),V(t[e],t,r,n)}return e}function Y(){const e=l.create();return U(e),e}async function Z(e,t,r){for(const n of t){const t=i.resolve(r,n),o=await import(t);"function"==typeof o.default&&o.default(e)}}class X{hbs;compiled=new Map;constructor(e){this.hbs=e}compile(e,t){const r=this.hbs.compile(t);return this.compiled.set(e,r),r}get(e){return this.compiled.get(e)}render(e,t){const r=this.compiled.get(e);return r?r(t):null}}async function ee(e,t,r,i){if(0===e.filter((e=>e.template)).length)return;const n=Y();r?.length&&i&&await Z(n,r,i);const o=new X(n);for(const[r,n]of e.entries()){if(!n.template)continue;const e=V(n.template,t,i??".");o.compile(`rule-${String(r)}`,e)}return o}class te{options;watcher;debounce;constructor(e){this.options=e}start(){this.options.enabled&&(this.watcher=m.watch(this.options.configPath,{ignoreInitial:!0}),this.watcher.on("change",(()=>{this.debounce&&clearTimeout(this.debounce),this.debounce=setTimeout((()=>{this.options.onChange()}),this.options.debounceMs)})),this.watcher.on("error",(e=>{this.options.logger.error({err:T(e)},"Config watcher error")})),this.options.logger.info({configPath:this.options.configPath,debounceMs:this.options.debounceMs},"Config watcher started"))}async stop(){this.debounce&&(clearTimeout(this.debounce),this.debounce=void 0),this.watcher&&(await this.watcher.close(),this.watcher=void 0)}}const re={metadataDir:".jeeves-watcher",shutdownTimeoutMs:1e4},ie={enabled:!0,debounceMs:1e3},ne={host:"127.0.0.1",port:3456},oe={level:"info"},se={debounceMs:300,stabilityThresholdMs:500,usePolling:!1,pollIntervalMs:1e3,respectGitignore:!0},ae={chunkSize:1e3,chunkOverlap:200,dimensions:3072,rateLimitPerMinute:300,concurrency:5},ce=w.z.object({paths:w.z.array(w.z.string()).min(1).describe('Glob patterns for files to watch (e.g., "**/*.md"). At least one required.'),ignored:w.z.array(w.z.string()).optional().describe('Glob patterns to exclude from watching (e.g., "**/node_modules/**").'),pollIntervalMs:w.z.number().optional().describe("Polling interval in milliseconds when usePolling is enabled."),usePolling:w.z.boolean().optional().describe("Use polling instead of native file system events (for network drives)."),debounceMs:w.z.number().optional().describe("Debounce delay in milliseconds for file change events."),stabilityThresholdMs:w.z.number().optional().describe("Time in milliseconds a file must remain unchanged before processing."),respectGitignore:w.z.boolean().optional().describe("Skip files ignored by .gitignore in git repositories. Only applies to repos with a .git directory. Default: true.")}),le=w.z.object({enabled:w.z.boolean().optional().describe("Enable automatic reloading when config file changes."),debounceMs:w.z.number().optional().describe("Debounce delay in milliseconds for config file change detection.")}),he=w.z.object({provider:w.z.string().default("gemini").describe('Embedding provider name (e.g., "gemini", "openai").'),model:w.z.string().default("gemini-embedding-001").describe('Embedding model identifier (e.g., "gemini-embedding-001", "text-embedding-3-small").'),chunkSize:w.z.number().optional().describe("Maximum chunk size in characters for text splitting."),chunkOverlap:w.z.number().optional().describe("Character overlap between consecutive chunks."),dimensions:w.z.number().optional().describe("Embedding vector dimensions (must match model output)."),apiKey:w.z.string().optional().describe("API key for embedding provider (supports ${ENV_VAR} substitution)."),rateLimitPerMinute:w.z.number().optional().describe("Maximum embedding API requests per minute (rate limiting)."),concurrency:w.z.number().optional().describe("Maximum concurrent embedding requests.")}),de=w.z.object({url:w.z.string().describe('Qdrant server URL (e.g., "http://localhost:6333").'),collectionName:w.z.string().describe("Qdrant collection name for vector storage."),apiKey:w.z.string().optional().describe("Qdrant API key for authentication (supports ${ENV_VAR} substitution).")}),ue=w.z.object({host:w.z.string().optional().describe('Host address for API server (e.g., "127.0.0.1", "0.0.0.0").'),port:w.z.number().optional().describe("Port for API server (e.g., 3456).")}),ge=w.z.object({level:w.z.string().optional().describe("Logging level (trace, debug, info, warn, error, fatal)."),file:w.z.string().optional().describe("Path to log file (logs to stdout if omitted).")}),fe=w.z.object({match:w.z.record(w.z.string(),w.z.unknown()).describe("JSON Schema object to match against file attributes."),set:w.z.record(w.z.string(),w.z.unknown()).describe("Metadata fields to set when match succeeds."),map:w.z.union([b.jsonMapMapSchema,w.z.string()]).optional().describe("JsonMap transformation (inline definition, named map reference, or .json file path)."),template:w.z.string().optional().describe("Handlebars content template (inline string, named ref, or .hbs/.handlebars file path).")}),pe=w.z.object({watch:ce.describe("File system watch configuration."),configWatch:le.optional().describe("Configuration file watch settings."),embedding:he.describe("Embedding model configuration."),vectorStore:de.describe("Qdrant vector store configuration."),metadataDir:w.z.string().optional().describe("Directory for persisted metadata sidecar files."),api:ue.optional().describe("API server configuration."),extractors:w.z.record(w.z.string(),w.z.unknown()).optional().describe("Extractor configurations keyed by name."),inferenceRules:w.z.array(fe).optional().describe("Rules for inferring metadata from file attributes."),maps:w.z.record(w.z.string(),b.jsonMapMapSchema.or(w.z.string())).optional().describe("Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory)."),templates:w.z.record(w.z.string(),w.z.string()).optional().describe("Named reusable Handlebars templates (inline strings or .hbs/.handlebars file paths)."),templateHelpers:w.z.object({paths:w.z.array(w.z.string()).optional().describe("File paths to custom helper modules.")}).optional().describe("Custom Handlebars helper registration."),logging:ge.optional().describe("Logging configuration."),shutdownTimeoutMs:w.z.number().optional().describe("Timeout in milliseconds for graceful shutdown."),maxRetries:w.z.number().optional().describe("Maximum consecutive system-level failures before triggering fatal error. Default: Infinity."),maxBackoffMs:w.z.number().optional().describe("Maximum backoff delay in milliseconds for system errors. Default: 60000.")}),me=/\$\{([^}]+)\}/g;function ye(e){if("string"==typeof e)return function(e){return e.replace(me,((e,t)=>{const r=process.env[t];return void 0===r?e:r}))}(e);if(Array.isArray(e))return e.map((e=>ye(e)));if(null!==e&&"object"==typeof e){const t={};for(const[r,i]of Object.entries(e))t[r]=ye(i);return t}return e}const we="jeeves-watcher";async function be(e){const t=y.cosmiconfig(we),r=e?await t.load(e):await t.search();if(!r||r.isEmpty)throw new Error("No jeeves-watcher configuration found. Create a .jeeves-watcherrc or jeeves-watcher.config.{js,ts,json,yaml} file.");try{const e=pe.parse(r.config);if(e.maps){const t=i.dirname(r.filepath);for(const[r,n]of Object.entries(e.maps))if("string"==typeof n){const o=i.resolve(t,n),s=a.readFileSync(o,"utf-8");e.maps[r]=JSON.parse(s)}}return ye((n=e,{...re,...n,watch:{...se,...n.watch},configWatch:{...ie,...n.configWatch},embedding:{...ae,...n.embedding},api:{...ne,...n.api},logging:{...oe,...n.logging}}))}catch(e){if(e instanceof w.ZodError){const t=e.issues.map((e=>`${e.path.join(".")}: ${e.message}`)).join("; ");throw new Error(`Invalid jeeves-watcher configuration: ${t}`)}throw e}var n}function ve(e){return e||{warn(e,t){t?console.warn(e,t):console.warn(e)}}}function Me(e,t){return e<=0?Promise.resolve():new Promise(((r,i)=>{const n=setTimeout((()=>{s(),r()}),e),o=()=>{s(),i(new Error("Retry sleep aborted"))},s=()=>{clearTimeout(n),t&&t.removeEventListener("abort",o)};if(t){if(t.aborted)return void o();t.addEventListener("abort",o,{once:!0})}}))}function Se(e,t,r,i=0){const n=Math.max(0,e-1),o=Math.min(r,t*2**n),s=i>0?1+Math.random()*i:1;return Math.round(o*s)}async function Fe(e,t){const r=Math.max(1,t.attempts);let i;for(let n=1;n<=r;n++)try{return await e(n)}catch(e){i=e;if(n>=r)break;const o=Se(n,t.baseDelayMs,t.maxDelayMs,t.jitter);t.onRetry?.({attempt:n,attempts:r,delayMs:o,error:e}),await Me(o,t.signal)}throw i}const xe=new Map([["mock",function(e){return function(e){return{dimensions:e,embed:t=>Promise.resolve(t.map((t=>{const r=s.createHash("sha256").update(t,"utf8").digest(),i=[];for(let t=0;t<e;t++){const e=r[t%r.length];i.push(e/127.5-1)}return i})))}}(e.dimensions??768)}],["gemini",function(e,t){if(!e.apiKey)throw new Error("Gemini embedding provider requires config.embedding.apiKey");const r=e.dimensions??3072,i=ve(t),n=new v.GoogleGenerativeAIEmbeddings({apiKey:e.apiKey,model:e.model});return{dimensions:r,async embed(t){const o=await Fe((async r=>(r>1&&i.warn({attempt:r,provider:"gemini",model:e.model},"Retrying embedding request"),n.embedDocuments(t))),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:t,delayMs:r,error:n})=>{i.warn({attempt:t,delayMs:r,provider:"gemini",model:e.model,err:T(n)},"Embedding call failed; will retry")}});for(const e of o)if(e.length!==r)throw new Error(`Gemini embedding returned invalid dimensions: expected ${String(r)}, got ${String(e.length)}`);return o}}}]]);function ke(e,t){const r=xe.get(e.provider);if(!r)throw new Error(`Unsupported embedding provider: ${e.provider}`);return r(e,t)}function Pe(e){const t=e?.level??"info";if(e?.file){const r=M.transport({target:"pino/file",options:{destination:e.file,mkdir:!0}});return M({level:t},r)}return M({level:t})}function ze(e){return s.createHash("sha256").update(e,"utf8").digest("hex")}const je="6a6f686e-6761-4c74-ad6a-656576657321";function Ce(e,t){const r=void 0!==t?`${W(e)}#${String(t)}`:W(e);return S.v5(r,je)}const Ee=["content","body","text","snippet","subject","description","summary","transcript"];function Re(e){if(!e||"object"!=typeof e)return JSON.stringify(e);const t=e;for(const e of Ee){const r=t[e];if("string"==typeof r&&r.trim())return r}return JSON.stringify(e)}async function Te(e){const t=await r.readFile(e,"utf8"),{frontmatter:i,body:n}=function(e){const t=e.replace(/^\uFEFF/,"");if(!/^\s*---/.test(t))return{body:e};const r=/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(t);if(!r)return{body:e};const[,i,n]=r,o=x.load(i);return{frontmatter:o&&"object"==typeof o&&!Array.isArray(o)?o:void 0,body:n}}(t);return{text:n,frontmatter:i}}async function De(e){return{text:(await r.readFile(e,"utf8")).replace(/^\uFEFF/,"")}}async function Ie(e){const t=await r.readFile(e,"utf8"),i=R.load(t.replace(/^\uFEFF/,""));i("script, style").remove();return{text:i("body").text().trim()||i.text().trim()}}const Ae=new Map([[".md",Te],[".markdown",Te],[".txt",De],[".text",De],[".json",async function(e){const t=await r.readFile(e,"utf8"),i=JSON.parse(t.replace(/^\uFEFF/,"")),n=i&&"object"==typeof i&&!Array.isArray(i)?i:void 0;return{text:Re(i),json:n}}],[".pdf",async function(e){const t=await r.readFile(e),i=new Uint8Array(t),{extractText:n}=await import("unpdf"),{text:o}=await n(i);return{text:Array.isArray(o)?o.join("\n\n"):o}}],[".docx",async function(e){const t=await r.readFile(e);return{text:(await k.extractRawText({buffer:t})).value}}],[".html",Ie],[".htm",Ie]]);async function We(e,t){const r=Ae.get(t.toLowerCase());return r?r(e):De(e)}function Ne(e,t){return"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,((e,r)=>{const i=o.get(t,r);return null==i?"":"string"==typeof i?i:JSON.stringify(i)}))}function Oe(e,t){const r={};for(const[i,n]of Object.entries(e))r[i]=Ne(n,t);return r}async function _e(e,t,r,n,s,c){const l={split:(e,t)=>e.split(t),slice:(e,t,r)=>e.slice(t,r),join:(e,t)=>e.join(t),toLowerCase:e=>e.toLowerCase(),replace:(e,t,r)=>e.replace(t,r),get:(e,t)=>o.get(e,t)};let h={},d=null;const u=n??console;for(const[n,{rule:o,validate:g}]of e.entries())if(g(t)){const e=Oe(o.set,t);if(h={...h,...e},o.map){let e;if("string"==typeof o.map){if(o.map.endsWith(".json")&&c)try{const t=i.resolve(c,o.map),r=a.readFileSync(t,"utf-8");e=JSON.parse(r)}catch(e){u.warn(`Failed to load map file "${o.map}": ${e instanceof Error?e.message:String(e)}`);continue}else if(e=r?.[o.map],!e){u.warn(`Map reference "${o.map}" not found in named maps. Skipping map transformation.`);continue}}else e=o.map;try{const r=new b.JsonMap(e,l),i=await r.transform(t);i&&"object"==typeof i&&!Array.isArray(i)?h={...h,...i}:u.warn("JsonMap transformation did not return an object; skipping merge.")}catch(e){u.warn(`JsonMap transformation failed: ${e instanceof Error?e.message:String(e)}`)}}if(o.template&&s){const e=`rule-${String(n)}`,r={...t.json??{},...t,...h};try{const t=s.render(e,r);t&&t.trim()?d=t:u.warn(`Template for rule ${String(n)} rendered empty output. Falling back to raw content.`)}catch(e){u.warn(`Template render failed for rule ${String(n)}: ${e instanceof Error?e.message:String(e)}. Falling back to raw content.`)}}}return{metadata:h,renderedContent:d}}function He(e,t,r,n){const o=e.replace(/\\/g,"/"),s={file:{path:o,directory:i.dirname(o).replace(/\\/g,"/"),filename:i.basename(o),extension:i.extname(o),sizeBytes:t.size,modified:t.mtime.toISOString()}};return r&&(s.frontmatter=r),n&&(s.json=n),s}function $e(e){const t=function(){const e=new P({allErrors:!0});return z(e),e.addKeyword({keyword:"glob",type:"string",schemaType:"string",validate:(e,t)=>n.isMatch(t,e)}),e}();return e.map(((e,r)=>({rule:e,validate:t.compile({$id:`rule-${String(r)}`,...e.match})})))}async function qe(e,t,n,o,s,a,c){const l=i.extname(e),h=await r.stat(e),d=await We(e,l),u=He(e,h,d.frontmatter,d.json),{metadata:g,renderedContent:f}=await _e(t,u,o,s,a,c),p=await O(e,n);return{inferred:g,enrichment:p,metadata:{...g,...p??{}},attributes:u,extracted:d,renderedContent:f}}function Le(e,t){const r=[];for(let i=0;i<t;i++)r.push(Ce(e,i));return r}function Qe(e,t=1){if(!e)return t;const r=e.total_chunks;return"number"==typeof r?r:t}class Ge{config;embeddingProvider;vectorStore;compiledRules;logger;templateEngine;constructor(e,t,r,i,n,o){this.config=e,this.embeddingProvider=t,this.vectorStore=r,this.compiledRules=i,this.logger=n,this.templateEngine=o}async processFile(e){try{const t=i.extname(e),{metadata:r,extracted:n,renderedContent:o}=await qe(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger,this.templateEngine,this.config.configDir),s=o??n.text;if(!s.trim())return void this.logger.debug({filePath:e},"Skipping empty file");const a=ze(s),c=Ce(e,0),l=await this.vectorStore.getPayload(c);if(l&&l.content_hash===a)return void this.logger.debug({filePath:e},"Content unchanged, skipping");const h=Qe(l),d=this.config.chunkSize??1e3,u=function(e,t,r){const i=e.toLowerCase();return".md"===i||".markdown"===i?new j.MarkdownTextSplitter({chunkSize:t,chunkOverlap:r}):new j.RecursiveCharacterTextSplitter({chunkSize:t,chunkOverlap:r})}(t,d,this.config.chunkOverlap??200),g=await u.splitText(s),f=await this.embeddingProvider.embed(g),p=g.map(((t,i)=>({id:Ce(e,i),vector:f[i],payload:{...r,file_path:e.replace(/\\/g,"/"),chunk_index:i,total_chunks:g.length,content_hash:a,chunk_text:t}})));if(await this.vectorStore.upsert(p),h>g.length){const t=Le(e,h).slice(g.length);await this.vectorStore.delete(t)}this.logger.info({filePath:e,chunks:g.length},"File processed successfully")}catch(t){this.logger.error({filePath:e,err:T(t)},"Failed to process file")}}async deleteFile(e){try{const t=Ce(e,0),r=await this.vectorStore.getPayload(t),i=Le(e,Qe(r));await this.vectorStore.delete(i),await H(e,this.config.metadataDir),this.logger.info({filePath:e},"File deleted from index")}catch(t){this.logger.error({filePath:e,err:T(t)},"Failed to delete file")}}async processMetadataUpdate(e,t){try{const r={...await O(e,this.config.metadataDir)??{},...t};await _(e,this.config.metadataDir,r);const i=Ce(e,0),n=await this.vectorStore.getPayload(i);if(!n)return null;const o=Qe(n),s=Le(e,o);return await this.vectorStore.setPayload(s,r),this.logger.info({filePath:e,chunks:o},"Metadata updated"),r}catch(t){return this.logger.error({filePath:e,err:T(t)},"Failed to update metadata"),null}}async processRulesUpdate(e){try{const t=Ce(e,0),r=await this.vectorStore.getPayload(t);if(!r)return this.logger.debug({filePath:e},"File not indexed, skipping"),null;const{metadata:i}=await qe(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger,this.templateEngine,this.config.configDir),n=Qe(r),o=Le(e,n);return await this.vectorStore.setPayload(o,i),this.logger.info({filePath:e,chunks:n},"Rules re-applied"),i}catch(t){return this.logger.error({filePath:e,err:T(t)},"Failed to re-apply rules"),null}}updateRules(e,t){this.compiledRules=e,t&&(this.templateEngine=t),this.logger.info({rules:e.length},"Inference rules updated")}}class Be{debounceMs;concurrency;rateLimitPerMinute;started=!1;active=0;debounceTimers=new Map;latestByKey=new Map;normalQueue=[];lowQueue=[];tokens;lastRefillMs=Date.now();drainWaiters=[];constructor(e){this.debounceMs=e.debounceMs,this.concurrency=e.concurrency,this.rateLimitPerMinute=e.rateLimitPerMinute,this.tokens=this.rateLimitPerMinute??Number.POSITIVE_INFINITY}enqueue(e,t){const r=`${e.priority}:${e.path}`;this.latestByKey.set(r,{event:e,fn:t});const i=this.debounceTimers.get(r);i&&clearTimeout(i);const n=setTimeout((()=>{this.debounceTimers.delete(r);const e=this.latestByKey.get(r);e&&(this.latestByKey.delete(r),this.push(e),this.pump())}),this.debounceMs);this.debounceTimers.set(r,n)}process(){this.started=!0,this.pump()}async drain(){this.isIdle()||await new Promise((e=>{this.drainWaiters.push(e)}))}push(e){"low"===e.event.priority?this.lowQueue.push(e):this.normalQueue.push(e)}refillTokens(e){if(void 0===this.rateLimitPerMinute)return;const t=Math.max(0,e-this.lastRefillMs)*(this.rateLimitPerMinute/6e4);this.tokens=Math.min(this.rateLimitPerMinute,this.tokens+t),this.lastRefillMs=e}takeToken(){const e=Date.now();return this.refillTokens(e),!(this.tokens<1)&&(this.tokens-=1,!0)}nextItem(){return this.normalQueue.shift()??this.lowQueue.shift()}pump(){if(this.started){for(;this.active<this.concurrency;){const e=this.nextItem();if(!e)break;if(!this.takeToken()){"low"===e.event.priority?this.lowQueue.unshift(e):this.normalQueue.unshift(e),setTimeout((()=>{this.pump()}),250);break}this.active+=1,Promise.resolve().then((()=>e.fn(e.event))).finally((()=>{this.active-=1,this.pump(),this.maybeResolveDrain()}))}this.maybeResolveDrain()}}isIdle(){return 0===this.active&&0===this.normalQueue.length&&0===this.lowQueue.length&&0===this.debounceTimers.size&&0===this.latestByKey.size}maybeResolveDrain(){if(!this.isIdle())return;const e=this.drainWaiters;this.drainWaiters=[];for(const t of e)t()}}function Je(e){return null==e?"keyword":"number"==typeof e?Number.isInteger(e)?"integer":"float":"boolean"==typeof e?"bool":Array.isArray(e)?"keyword[]":"string"==typeof e&&e.length>256?"text":"keyword"}class Ke{client;collectionName;dims;log;constructor(e,t,r){this.client=new C.QdrantClient({url:e.url,apiKey:e.apiKey,checkCompatibility:!1}),this.collectionName=e.collectionName,this.dims=t,this.log=ve(r)}async ensureCollection(){try{const e=await this.client.getCollections();e.collections.some((e=>e.name===this.collectionName))||await this.client.createCollection(this.collectionName,{vectors:{size:this.dims,distance:"Cosine"}})}catch(e){throw new Error(`Failed to ensure collection "${this.collectionName}": ${String(e)}`)}}async upsert(e){0!==e.length&&await Fe((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.upsert",points:e.length},"Retrying Qdrant upsert"),await this.client.upsert(this.collectionName,{wait:!0,points:e.map((e=>({id:e.id,vector:e.vector,payload:e.payload})))})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:r})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.upsert",err:T(r)},"Qdrant upsert failed; will retry")}})}async delete(e){0!==e.length&&await Fe((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.delete",ids:e.length},"Retrying Qdrant delete"),await this.client.delete(this.collectionName,{wait:!0,points:e})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:r})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.delete",err:T(r)},"Qdrant delete failed; will retry")}})}async setPayload(e,t){0!==e.length&&await this.client.setPayload(this.collectionName,{wait:!0,points:e,payload:t})}async getPayload(e){try{const t=await this.client.retrieve(this.collectionName,{ids:[e],with_payload:!0,with_vector:!1});return 0===t.length?null:t[0].payload}catch{return null}}async getCollectionInfo(){const e=await this.client.getCollection(this.collectionName),t=e.points_count??0,r=e.config.params.vectors,i=void 0!==r&&"size"in r?r.size:0,n={},o=Object.entries(e.payload_schema);if(o.length>0)for(const[e,t]of o)n[e]={type:t.data_type??"unknown"};else t>0&&await this.discoverPayloadFields(n);return{pointCount:t,dimensions:i,payloadFields:n}}async discoverPayloadFields(e,t=100){const r=await this.client.scroll(this.collectionName,{limit:t,with_payload:!0,with_vector:!1});for(const t of r.points){const r=t.payload;if(r)for(const[t,i]of Object.entries(r))t in e||(e[t]={type:Je(i)})}}async search(e,t,r){return(await this.client.search(this.collectionName,{vector:e,limit:t,with_payload:!0,...r?{filter:r}:{}})).map((e=>({id:String(e.id),score:e.score,payload:e.payload})))}async*scroll(e,t=100){let r;for(;;){const i=await this.client.scroll(this.collectionName,{limit:t,with_payload:!0,with_vector:!1,...e?{filter:e}:{},...void 0!==r?{offset:r}:{}});for(const e of i.points)yield{id:String(e.id),payload:e.payload};const n=i.next_page_offset;if(null==n)break;if("string"!=typeof n&&"number"!=typeof n)break;r=n}}}class Ue{consecutiveFailures=0;maxRetries;maxBackoffMs;baseDelayMs;onFatalError;logger;constructor(e){this.maxRetries=e.maxRetries??Number.POSITIVE_INFINITY,this.maxBackoffMs=e.maxBackoffMs??6e4,this.baseDelayMs=e.baseDelayMs??1e3,this.onFatalError=e.onFatalError,this.logger=e.logger}recordSuccess(){this.consecutiveFailures>0&&this.logger.info({previousFailures:this.consecutiveFailures},"System health recovered"),this.consecutiveFailures=0}recordFailure(e){if(this.consecutiveFailures+=1,this.logger.error({consecutiveFailures:this.consecutiveFailures,maxRetries:this.maxRetries,err:T(e)},"System-level failure recorded"),this.consecutiveFailures>=this.maxRetries){if(this.logger.fatal({consecutiveFailures:this.consecutiveFailures},"Maximum retries exceeded, triggering fatal error"),this.onFatalError)return this.onFatalError(e),!1;throw e instanceof Error?e:new Error(`Fatal system error: ${String(e)}`)}return!0}get currentBackoffMs(){if(0===this.consecutiveFailures)return 0;const e=Math.max(0,this.consecutiveFailures-1);return Math.min(this.maxBackoffMs,this.baseDelayMs*2**e)}async backoff(e){const t=this.currentBackoffMs;t<=0||(this.logger.warn({delayMs:t,consecutiveFailures:this.consecutiveFailures},"Backing off before next attempt"),await new Promise(((r,i)=>{const n=setTimeout((()=>{s(),r()}),t),o=()=>{s(),i(new Error("Backoff aborted"))},s=()=>{clearTimeout(n),e&&e.removeEventListener("abort",o)};if(e){if(e.aborted)return void o();e.addEventListener("abort",o,{once:!0})}})))}get failures(){return this.consecutiveFailures}}function Ve(e){const t=e.replace(/\\/g,"/").split("/"),r=[];for(const e of t){if(/[*?{[\]]/.test(e))break;r.push(e)}return r.join("/")||"."}function Ye(e){const t=function(e){const t=e.map((e=>e.replace(/\\/g,"/").toLowerCase()));return[...new Set(t)].sort().filter(((e,t,r)=>{const i=e.endsWith("/")?e:e+"/";return!r.some((t=>t!==e&&i.startsWith(t+"/")))}))}(e.map(Ve)),r=function(e){const t=e.map((e=>e.replace(/\\/g,"/"))),r=n(t,{dot:!0,nocase:!0});return e=>{const t=e.replace(/\\/g,"/");return r(t)}}(e);return{roots:t,matches:r}}class Ze{config;queue;processor;logger;health;gitignoreFilter;globMatches;watcher;constructor(e,t,r,i,n={}){this.config=e,this.queue=t,this.processor=r,this.logger=i,this.gitignoreFilter=n.gitignoreFilter,this.globMatches=()=>!0;const o={maxRetries:n.maxRetries,maxBackoffMs:n.maxBackoffMs,onFatalError:n.onFatalError,logger:i};this.health=new Ue(o)}start(){const{roots:e,matches:t}=Ye(this.config.paths);this.globMatches=t;const r=this.config.ignored?function(e){return e.map((e=>{if("string"!=typeof e)return e;const t=e.replace(/\\/g,"/"),r=n(t,{dot:!0,nocase:!0});return e=>{const t=e.replace(/\\/g,"/");return r(t)}}))}(this.config.ignored):void 0;this.watcher=m.watch(e,{ignored:r,usePolling:this.config.usePolling,interval:this.config.pollIntervalMs,awaitWriteFinish:!!this.config.stabilityThresholdMs&&{stabilityThreshold:this.config.stabilityThresholdMs},ignoreInitial:!1}),this.watcher.on("add",(e=>{this.handleGitignoreChange(e),this.globMatches(e)&&(this.isGitignored(e)||(this.logger.debug({path:e},"File added"),this.queue.enqueue({type:"create",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.processFile(e)))))))})),this.watcher.on("change",(e=>{this.handleGitignoreChange(e),this.globMatches(e)&&(this.isGitignored(e)||(this.logger.debug({path:e},"File changed"),this.queue.enqueue({type:"modify",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.processFile(e)))))))})),this.watcher.on("unlink",(e=>{this.handleGitignoreChange(e),this.globMatches(e)&&(this.isGitignored(e)||(this.logger.debug({path:e},"File removed"),this.queue.enqueue({type:"delete",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.deleteFile(e)))))))})),this.watcher.on("error",(e=>{this.logger.error({err:T(e)},"Watcher error"),this.health.recordFailure(e)})),this.queue.process(),this.logger.info({paths:this.config.paths},"Filesystem watcher started")}async stop(){this.watcher&&(await this.watcher.close(),this.watcher=void 0,this.logger.info("Filesystem watcher stopped"))}get systemHealth(){return this.health}isGitignored(e){if(!this.gitignoreFilter)return!1;const t=this.gitignoreFilter.isIgnored(e);return t&&this.logger.debug({path:e},"Skipping gitignored file"),t}handleGitignoreChange(e){this.gitignoreFilter&&e.endsWith(".gitignore")&&(this.logger.info({path:e},"Gitignore file changed, refreshing filter"),this.gitignoreFilter.invalidate(e))}async wrapProcessing(e){try{await this.health.backoff(),await e(),this.health.recordSuccess()}catch(e){this.health.recordFailure(e)||await this.stop()}}}const Xe={loadConfig:be,createLogger:Pe,createEmbeddingProvider:ke,createVectorStoreClient:(e,t,r)=>new Ke(e,t,r),compileRules:$e,createDocumentProcessor:(e,t,r,i,n,o)=>new Ge(e,t,r,i,n,o),createEventQueue:e=>new Be(e),createFileSystemWatcher:(e,t,r,i,n)=>new Ze(e,t,r,i,n),createApiServer:q};class et{config;configPath;factories;runtimeOptions;logger;watcher;queue;server;processor;configWatcher;constructor(e,t,r={},i={}){this.config=e,this.configPath=t,this.factories={...Xe,...r},this.runtimeOptions=i}async start(){const e=this.factories.createLogger(this.config.logging);this.logger=e;const{embeddingProvider:t,vectorStore:r}=await this.initEmbeddingAndStore(e),n=this.factories.compileRules(this.config.inferenceRules??[]),o=this.configPath?i.dirname(this.configPath):".",s=await ee(this.config.inferenceRules??[],this.config.templates,this.config.templateHelpers?.paths,o),a=this.factories.createDocumentProcessor({metadataDir:this.config.metadataDir??".jeeves-metadata",chunkSize:this.config.embedding.chunkSize,chunkOverlap:this.config.embedding.chunkOverlap,maps:this.config.maps,configDir:o},t,r,n,e,s);this.processor=a,this.queue=this.factories.createEventQueue({debounceMs:this.config.watch.debounceMs??2e3,concurrency:this.config.embedding.concurrency??5,rateLimitPerMinute:this.config.embedding.rateLimitPerMinute}),this.watcher=this.createWatcher(this.queue,a,e),this.server=await this.startApiServer(a,r,t,e),this.watcher.start(),this.startConfigWatch(),e.info("jeeves-watcher started")}async stop(){if(await this.stopConfigWatch(),this.watcher&&await this.watcher.stop(),this.queue){const e=this.config.shutdownTimeoutMs??1e4;await Promise.race([this.queue.drain().then((()=>!0)),new Promise((t=>{setTimeout((()=>{t(!1)}),e)}))])||this.logger?.warn({timeoutMs:e},"Queue drain timeout hit, forcing shutdown")}this.server&&await this.server.close(),this.logger?.info("jeeves-watcher stopped")}async initEmbeddingAndStore(e){let t;try{t=this.factories.createEmbeddingProvider(this.config.embedding,e)}catch(t){throw e.fatal({err:T(t)},"Failed to create embedding provider"),t}const r=this.factories.createVectorStoreClient(this.config.vectorStore,t.dimensions,e);return await r.ensureCollection(),{embeddingProvider:t,vectorStore:r}}createWatcher(e,t,r){const i=this.config.watch.respectGitignore??!0?new J(this.config.watch.paths):void 0;return this.factories.createFileSystemWatcher(this.config.watch,e,t,r,{maxRetries:this.config.maxRetries,maxBackoffMs:this.config.maxBackoffMs,onFatalError:this.runtimeOptions.onFatalError,gitignoreFilter:i})}async startApiServer(e,t,r,i){const n=this.factories.createApiServer({processor:e,vectorStore:t,embeddingProvider:r,queue:this.queue,config:this.config,logger:i});return await n.listen({host:this.config.api?.host??"127.0.0.1",port:this.config.api?.port??3456}),n}startConfigWatch(){const e=this.logger;if(!e)return;const t=this.config.configWatch?.enabled??!0;t&&this.configPath?(this.configWatcher=new te({configPath:this.configPath,enabled:t,debounceMs:this.config.configWatch?.debounceMs??1e4,logger:e,onChange:async()=>this.reloadConfig()}),this.configWatcher.start()):this.configPath||e.debug("Config watch enabled, but no config path was provided")}async stopConfigWatch(){this.configWatcher&&(await this.configWatcher.stop(),this.configWatcher=void 0)}async reloadConfig(){const e=this.logger,t=this.processor;if(e&&t&&this.configPath){e.info({configPath:this.configPath},"Config change detected, reloading...");try{const r=await this.factories.loadConfig(this.configPath);this.config=r;const n=this.factories.compileRules(r.inferenceRules??[]),o=i.dirname(this.configPath),s=await ee(r.inferenceRules??[],r.templates,r.templateHelpers?.paths,o);t.updateRules(n,s),e.info({configPath:this.configPath,rules:n.length},"Config reloaded")}catch(t){e.error({err:T(t)},"Failed to reload config")}}}}e.DocumentProcessor=Ge,e.EventQueue=Be,e.FileSystemWatcher=Ze,e.GitignoreFilter=J,e.JeevesWatcher=et,e.SystemHealth=Ue,e.TemplateEngine=X,e.VectorStoreClient=Ke,e.apiConfigSchema=ue,e.applyRules=_e,e.buildAttributes=He,e.buildTemplateEngine=ee,e.compileRules=$e,e.configWatchConfigSchema=le,e.contentHash=ze,e.createApiServer=q,e.createEmbeddingProvider=ke,e.createHandlebarsInstance=Y,e.createLogger=Pe,e.deleteMetadata=H,e.embeddingConfigSchema=he,e.extractText=We,e.inferenceRuleSchema=fe,e.jeevesWatcherConfigSchema=pe,e.loadConfig=be,e.loadCustomHelpers=Z,e.loggingConfigSchema=ge,e.metadataPath=N,e.pointId=Ce,e.readMetadata=O,e.registerBuiltinHelpers=U,e.resolveTemplateSource=V,e.startFromConfig=async function(e){const t=await be(e),r=new et(t,e);return function(e){const t=async()=>{await e(),process.exit(0)};process.on("SIGTERM",(()=>{t()})),process.on("SIGINT",(()=>{t()}))}((()=>r.stop())),await r.start(),r},e.vectorStoreConfigSchema=de,e.watchConfigSchema=ce,e.writeMetadata=_}(this["jeeves-watcher"]=this["jeeves-watcher"]||{},Fastify,promises,node_path,picomatch,radash,node_crypto,node_fs,ignore,Handlebars,dayjs,hastUtilToMdast,mdastUtilFromAdf,mdastUtilToMarkdown,rehypeParse,unified,chokidar,cosmiconfig,zod,jsonmap,googleGenai,pino,uuid,cheerio,yaml,mammoth,Ajv,addFormats,textsplitters,jsClientRest);
|
package/dist/mjs/index.js
CHANGED
|
@@ -1143,11 +1143,11 @@ const jeevesWatcherConfigSchema = z.object({
|
|
|
1143
1143
|
.array(inferenceRuleSchema)
|
|
1144
1144
|
.optional()
|
|
1145
1145
|
.describe('Rules for inferring metadata from file attributes.'),
|
|
1146
|
-
/** Reusable named JsonMap transformations. */
|
|
1146
|
+
/** Reusable named JsonMap transformations (inline objects or .json file paths). */
|
|
1147
1147
|
maps: z
|
|
1148
|
-
.record(z.string(), jsonMapMapSchema)
|
|
1148
|
+
.record(z.string(), jsonMapMapSchema.or(z.string()))
|
|
1149
1149
|
.optional()
|
|
1150
|
-
.describe('Reusable named JsonMap transformations.'),
|
|
1150
|
+
.describe('Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory).'),
|
|
1151
1151
|
/** Reusable named Handlebars templates (inline strings or .hbs/.handlebars file paths). */
|
|
1152
1152
|
templates: z
|
|
1153
1153
|
.record(z.string(), z.string())
|
|
@@ -1261,6 +1261,18 @@ async function loadConfig(configPath) {
|
|
|
1261
1261
|
}
|
|
1262
1262
|
try {
|
|
1263
1263
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
1264
|
+
// Resolve file-path map references relative to config directory.
|
|
1265
|
+
// After this block, all map values are inline JsonMapMap objects.
|
|
1266
|
+
if (validated.maps) {
|
|
1267
|
+
const configDir = dirname(result.filepath);
|
|
1268
|
+
for (const [name, value] of Object.entries(validated.maps)) {
|
|
1269
|
+
if (typeof value === 'string') {
|
|
1270
|
+
const mapPath = resolve(configDir, value);
|
|
1271
|
+
const raw = readFileSync(mapPath, 'utf-8');
|
|
1272
|
+
validated.maps[name] = JSON.parse(raw);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1264
1276
|
const withDefaults = applyDefaults(validated);
|
|
1265
1277
|
return substituteEnvVars(withDefaults);
|
|
1266
1278
|
}
|
package/package.json
CHANGED