@karmaniverous/jeeves-watcher 0.5.0-1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -280
- package/package.json +63 -92
- package/LICENSE +0 -28
- package/dist/cjs/index.js +0 -5050
- package/dist/index.iife.js +0 -5021
- package/dist/index.iife.min.js +0 -1
- package/dist/plugin/index.js +0 -267
- package/dist/plugin/openclaw.plugin.json +0 -24
- package/dist/skills/jeeves-watcher/SKILL.md +0 -413
- package/dist/skills/jeeves-watcher-admin/SKILL.md +0 -200
- /package/dist/{mjs/index.js → index.js} +0 -0
package/dist/index.iife.min.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
!function(e,t,r,i,n,s,o,a,c,l,u,h,f,d,g,p,m,y,b,w,v,M,S,z,x,k,j,P,F,R,O,E){"use strict";function A(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 C=A(w);function D(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 T(e,t){return e<=0?Promise.resolve():new Promise(((r,i)=>{const n=setTimeout((()=>{o(),r()}),e),s=()=>{o(),i(new Error("Retry sleep aborted"))},o=()=>{clearTimeout(n),t&&t.removeEventListener("abort",s)};if(t){if(t.aborted)return void s();t.addEventListener("abort",s,{once:!0})}}))}function N(e,t,r,i=0){const n=Math.max(0,e-1),s=Math.min(r,t*2**n),o=i>0?1+Math.random()*i:1;return Math.round(s*o)}async function I(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 s=N(n,t.baseDelayMs,t.maxDelayMs,t.jitter);t.onRetry?.({attempt:n,attempts:r,delayMs:s,error:e}),await T(s,t.signal)}throw i}function H(e){return e.replace(/\\/g,"/")}function $(e){const t=H(e),r=t.search(/[*?[]/);if(-1===r)return i.resolve(e);const n=t.slice(0,r),s=n.endsWith("/")?n.slice(0,-1):i.dirname(n);return i.resolve(s)}async function*_(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*_(t);else try{(await r.stat(t)).isFile()&&(yield t)}catch{}}}async function W(e,t,r,i){const s=await async function(e,t=[]){const r=e.map((e=>H(e))),i=t.map((e=>H(e))),s=n(r,{dot:!0}),o=i.length?n(i,{dot:!0}):()=>!1,a=Array.from(new Set(e.map($))),c=new Set;for(const e of a)for await(const t of _(e)){const e=H(t);o(e)||s(e)&&c.add(t)}return Array.from(c)}(e,t);for(const e of s)await r[i](e);return s.length}async function L(e,t,r){await I((async()=>{const r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok)throw new Error(`Non-OK response: ${String(r.status)}`)}),{attempts:3,baseDelayMs:1e3,maxDelayMs:4e3,onRetry:({attempt:t,error:i})=>{r.warn({attempt:t,err:D(i),url:e},"Reindex callback failed; will retry")}})}async function J(e,t){const{config:r,processor:i,logger:n,reindexTracker:s,valuesManager:o}=e;s?.start(t);const a=Date.now();let c=0,l=0;try{if("full"===t&&o&&o.clearAll(),"issues"===t&&e.issuesManager){const t=e.issuesManager.getAll(),r=Object.keys(t);c=0;for(const e of r)try{await i.processFile(e),c++}catch(t){l++,n.warn({filePath:e,err:D(t)},"Failed to reprocess issue file")}}else c=await W(r.watch.paths,r.watch.ignored,i,"processFile");const u=Date.now()-a;return n.info({scope:t,filesProcessed:c,durationMs:u},`Reindex (${t}) completed`),s?.complete(),r.reindex?.callbackUrl&&await L(r.reindex.callbackUrl,{scope:t,filesProcessed:c,durationMs:u,errors:[]},n),{filesProcessed:c,durationMs:u,errors:l}}catch(e){l=1;const i=Date.now()-a;return n.error({err:D(e),scope:t},"Reindex failed"),s?.complete(),r.reindex?.callbackUrl&&await L(r.reindex.callbackUrl,{scope:t,filesProcessed:0,durationMs:i,errors:[D(e).message]},n),{filesProcessed:0,durationMs:i,errors:l}}}const q=s.z.object({paths:s.z.array(s.z.string()).min(1).describe('Glob patterns for files to watch (e.g., "**/*.md"). At least one required.'),ignored:s.z.array(s.z.string()).optional().describe('Glob patterns to exclude from watching (e.g., "**/node_modules/**").'),pollIntervalMs:s.z.number().optional().describe("Polling interval in milliseconds when usePolling is enabled."),usePolling:s.z.boolean().optional().describe("Use polling instead of native file system events (for network drives)."),debounceMs:s.z.number().optional().describe("Debounce delay in milliseconds for file change events."),stabilityThresholdMs:s.z.number().optional().describe("Time in milliseconds a file must remain unchanged before processing."),respectGitignore:s.z.boolean().optional().describe("Skip files ignored by .gitignore in git repositories. Only applies to repos with a .git directory. Default: true.")}),B=s.z.object({enabled:s.z.boolean().optional().describe("Enable automatic reloading when config file changes."),debounceMs:s.z.number().optional().describe("Debounce delay in milliseconds for config file change detection."),reindex:s.z.union([s.z.literal("issues").describe("Re-process only files with recorded issues."),s.z.literal("full").describe("Full reindex of all watched files.")]).optional().describe("Reindex scope triggered on config change. Default: issues.")}),G=s.z.object({host:s.z.string().optional().describe('Host address for API server (e.g., "127.0.0.1", "0.0.0.0").'),port:s.z.number().optional().describe("Port for API server (e.g., 3456).")}),Q=s.z.object({level:s.z.string().optional().describe("Logging level (trace, debug, info, warn, error, fatal)."),file:s.z.string().optional().describe("Path to log file (logs to stdout if omitted).")}),U=s.z.record(s.z.string(),s.z.unknown()),K=s.z.object({type:s.z.literal("object").optional().describe('JSON Schema type (always "object" for schema definitions).'),properties:s.z.record(s.z.string(),U).optional().describe("Map of property names to JSON Schema property definitions.")}),V=s.z.union([K,s.z.string().describe("File path to a JSON schema file.")]),Y=s.z.union([s.z.string().describe("Named reference to a global schema."),K]),Z=s.z.object({name:s.z.string().min(1).describe("Unique name identifying this inference rule."),description:s.z.string().min(1).describe("Human-readable description of what this rule does."),match:s.z.record(s.z.string(),s.z.unknown()).describe("JSON Schema object to match against file attributes."),schema:s.z.array(Y).optional().describe("Array of schema references (named schema refs or inline objects) merged left-to-right."),map:s.z.union([o.jsonMapMapSchema,s.z.string()]).optional().describe("JsonMap transformation (inline definition, named map reference, or .json file path)."),template:s.z.string().optional().describe("Handlebars content template (inline string, named ref, or .hbs/.handlebars file path).")}),X=s.z.object({provider:s.z.string().default("gemini").describe('Embedding provider name (e.g., "gemini", "openai").'),model:s.z.string().default("gemini-embedding-001").describe('Embedding model identifier (e.g., "gemini-embedding-001", "text-embedding-3-small").'),chunkSize:s.z.number().optional().describe("Maximum chunk size in characters for text splitting."),chunkOverlap:s.z.number().optional().describe("Character overlap between consecutive chunks."),dimensions:s.z.number().optional().describe("Embedding vector dimensions (must match model output)."),apiKey:s.z.string().optional().describe("API key for embedding provider (supports ${ENV_VAR} substitution)."),rateLimitPerMinute:s.z.number().optional().describe("Maximum embedding API requests per minute (rate limiting)."),concurrency:s.z.number().optional().describe("Maximum concurrent embedding requests.")}),ee=s.z.object({url:s.z.string().describe('Qdrant server URL (e.g., "http://localhost:6333").'),collectionName:s.z.string().describe("Qdrant collection name for vector storage."),apiKey:s.z.string().optional().describe("Qdrant API key for authentication (supports ${ENV_VAR} substitution).")}),te=s.z.object({description:s.z.string().optional().describe("Human-readable description of this deployment's organizational strategy and content domains."),schemas:s.z.record(s.z.string(),V).optional().describe("Global named schema definitions (inline objects or file paths) referenced by inference rules."),watch:q.describe("File system watch configuration."),configWatch:B.optional().describe("Configuration file watch settings."),embedding:X.describe("Embedding model configuration."),vectorStore:ee.describe("Qdrant vector store configuration."),metadataDir:s.z.string().optional().describe("Directory for persisted metadata sidecar files."),api:G.optional().describe("API server configuration."),extractors:s.z.record(s.z.string(),s.z.unknown()).optional().describe("Extractor configurations keyed by name."),stateDir:s.z.string().optional().describe("Directory for persistent state files (issues.json, values.json). Defaults to metadataDir."),inferenceRules:s.z.array(Z).optional().describe("Rules for inferring metadata from file attributes."),maps:s.z.record(s.z.string(),s.z.union([o.jsonMapMapSchema,s.z.string(),s.z.object({map:o.jsonMapMapSchema.or(s.z.string()),description:s.z.string().optional()})])).optional().describe("Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory)."),templates:s.z.record(s.z.string(),s.z.union([s.z.string(),s.z.object({template:s.z.string(),description:s.z.string().optional()})])).optional().describe("Named reusable Handlebars templates (inline strings or .hbs/.handlebars file paths)."),templateHelpers:s.z.record(s.z.string(),s.z.object({path:s.z.string(),description:s.z.string().optional()})).optional().describe("Custom Handlebars helper registration."),mapHelpers:s.z.record(s.z.string(),s.z.object({path:s.z.string(),description:s.z.string().optional()})).optional().describe("Custom JsonMap lib function registration."),reindex:s.z.object({callbackUrl:s.z.url().optional()}).optional().describe("Reindex configuration."),slots:s.z.record(s.z.string(),s.z.unknown()).optional().describe("Named Qdrant filter patterns for skill-activated behaviors."),search:s.z.object({scoreThresholds:s.z.object({strong:s.z.number().min(-1).max(1),relevant:s.z.number().min(-1).max(1),noise:s.z.number().min(-1).max(1)}).optional()}).optional().describe("Search configuration including score thresholds."),logging:Q.optional().describe("Logging configuration."),shutdownTimeoutMs:s.z.number().optional().describe("Timeout in milliseconds for graceful shutdown."),maxRetries:s.z.number().optional().describe("Maximum consecutive system-level failures before triggering fatal error. Default: Infinity."),maxBackoffMs:s.z.number().optional().describe("Maximum backoff delay in milliseconds for system errors. Default: 60000.")});function re(e,t){let r={...e};if(t){const e=function(e,t){if(!t)return e??[];if(!e)return t;const r=[...e];for(const e of t){const t=e.name;if(!t){r.push(e);continue}const i=r.findIndex((e=>e.name===t));i>=0?r[i]=e:r.push(e)}return r}(r.inferenceRules,t.inferenceRules);r={...r,...t,inferenceRules:e}}const i=te.safeParse(r),n=[];if(!i.success){for(const e of i.error.issues)n.push({path:e.path.join("."),message:e.message});return{candidateRaw:r,errors:n}}return{candidateRaw:r,parsed:i.data,errors:n}}function ie(e,t,r){return async(i,n)=>{try{return await e(i,n)}catch(e){return t.error({err:D(e)},`${r} failed`),n.status(500).send({error:"Internal server error"})}}}function ne(e){!function(e){const t=new Set,r=[];for(const i of e)t.has(i.name)?r.push(i.name):t.add(i.name);if(r.length>0)throw new Error(`Duplicate inference rule names found: ${r.join(", ")}. Rule names must be unique.`)}(e);const t=function(){const e=new a({allErrors:!0});return c(e),e.addKeyword({keyword:"glob",type:"string",schemaType:"string",validate:(e,t)=>n.isMatch(t,e)}),e}();return e.map((e=>({rule:e,validate:t.compile({$id:e.name,...e.match})})))}function se(e,t,r){if(!e)return{};const i={};for(const[t,n]of Object.entries(e))i[t]={...n,...r?.[t]?.exports?{exports:r[t].exports}:{}};return t&&(i._exports=t),i}function oe(e){try{const t=u.readFileSync(e,"utf-8");return e.endsWith(".json")?JSON.parse(t):t}catch{return e}}function ae(e){return async(t,r)=>{try{const{path:r,resolve:i}=t.body;let n=function(e){const{config:t,valuesManager:r,issuesManager:i,helperExports:n,helperIntrospection:s}=e,o=(t.inferenceRules??[]).map((e=>({...e,values:r.getForRule(e.name)})));return{description:t.description??"",search:t.search??{},schemas:t.schemas??{},inferenceRules:o,mapHelpers:se(t.mapHelpers,n?.mapHelpers,s?.mapHelpers),templateHelpers:se(t.templateHelpers,n?.templateHelpers,s?.templateHelpers),maps:t.maps??{},templates:t.templates??{},slots:t.slots??{},issues:i.getAll()}}({config:e.config,valuesManager:e.valuesManager,issuesManager:e.issuesManager,helperIntrospection:e.helperIntrospection});i&&i.length>0&&(n=function(e,t){const r={...e};if(Array.isArray(r.inferenceRules)&&(r.inferenceRules=r.inferenceRules.map((e=>{let i={...e};if(t.includes("files")&&"string"==typeof e.map&&e.map.endsWith(".json")&&(i={...i,map:oe(e.map)}),t.includes("globals")&&Array.isArray(e.schema)&&"object"==typeof r.schemas){const t=r.schemas,n=e.schema.map((e=>"string"==typeof e&&t[e]?t[e]:e));i={...i,schema:n}}return i}))),t.includes("files")&&r.maps&&"object"==typeof r.maps){const e={...r.maps};for(const[t,r]of Object.entries(e))"string"==typeof r&&(e[t]=oe(r));r.maps=e}if(t.includes("files")&&r.templates&&"object"==typeof r.templates){const e={...r.templates};for(const[t,r]of Object.entries(e))"string"==typeof r&&(e[t]=oe(r));r.templates=e}return r}(n,i));const s=l.JSONPath({path:r,json:n});return{result:s,count:s.length}}catch(t){const i=D(t);return e.logger.error({err:i},"Config query failed"),r.status(400).send({error:i.message||"Query failed"})}}}const ce=Z.extend({values:s.z.record(s.z.string(),s.z.array(s.z.unknown())).optional()}),le=s.z.object({description:s.z.string().optional(),search:s.z.object({scoreThresholds:s.z.object({strong:s.z.number(),relevant:s.z.number(),noise:s.z.number()}).optional()}).optional(),schemas:s.z.array(s.z.unknown()),inferenceRules:s.z.array(ce),maps:s.z.record(s.z.string(),s.z.unknown()).optional(),templates:s.z.record(s.z.string(),s.z.unknown()).optional(),templateHelpers:s.z.record(s.z.string(),s.z.unknown()).optional(),mapHelpers:s.z.record(s.z.string(),s.z.unknown()).optional(),slots:s.z.record(s.z.string(),s.z.unknown()).optional(),issues:s.z.record(s.z.string(),s.z.array(s.z.unknown())).optional()});async function ue(e,t,r){const n={};for(const[s,{path:o}]of Object.entries(e)){const e=i.resolve(t,o),a=await import(f.pathToFileURL(e).href),c="object"==typeof a.default&&null!==a.default?a.default:a,l={};for(const[e,t]of Object.entries(c))r&&!r(t)||(l[e]=t);n[s]=l}return n}function he(e,t){return"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,((e,r)=>{const i=h.get(t,r);return null==i?"":"string"==typeof i?i:JSON.stringify(i)}))}function fe(e,t){if("string"==typeof e){const r=t?i.resolve(t,e):e,n=u.readFileSync(r,"utf-8");return JSON.parse(n)}return e}function de(e,t={}){const{globalSchemas:r={},configDir:i}=t,n={properties:{}};for(const t of e){let e;if("string"==typeof t){const n=r[t];if(!n)throw new Error(`Schema reference "${t}" not found in global schemas`);e=fe(n,i)}else e=t;if(e.properties)for(const[t,r]of Object.entries(e.properties)){const e=n.properties[t];n.properties[t]=e?{...e,...r}:r}}return n}function ge(e,t){if(null!=e)switch(t){case"string":return"string"==typeof e?e:"number"==typeof e||"boolean"==typeof e?String(e):"object"==typeof e?JSON.stringify(e):void 0;case"integer":if(""===e)return;if("string"==typeof e){const t=e.trim(),r=parseInt(t,10);return Number.isInteger(r)&&r.toString()===t?r:void 0}return"number"==typeof e&&Number.isInteger(e)?e:void 0;case"number":{if(""===e)return;const t="string"==typeof e?parseFloat(e):Number(e);return Number.isFinite(t)?t:void 0}case"boolean":if(""===e)return;if("boolean"==typeof e)return e;if("string"==typeof e){const t=e.toLowerCase();if("true"===t)return!0;if("false"===t)return!1}return;case"array":if(""===e)return;if(Array.isArray(e))return e;if("string"==typeof e)try{const t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return;case"object":if(""===e)return;if("string"==typeof e){try{const t=JSON.parse(e);if("object"==typeof t&&null!==t&&!Array.isArray(t))return t}catch{}return}return"object"!=typeof e||Array.isArray(e)?void 0:e;default:return e}}function pe(e,t){const r={};for(const[i,n]of Object.entries(e.properties)){const e=n.set;if(void 0===e)continue;const s=ge(he(e,t),n.type);void 0!==s&&(r[i]=s)}return r}function me(e,t){for(const[r,i]of Object.entries(e.properties))if(!i.type)throw new Error(`Property "${r}" in rule "${t}" has no declared type. Every property must have a type.`)}async function ye(e,t,r,n,s,a,c,l){const f=function(e,t){const r=new Map,n=t=>{const n=e?i.resolve(e,t):t;if(!r.has(n)){const e=u.readFileSync(n,"utf-8");r.set(n,JSON.parse(e))}return r.get(n)};return{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)=>h.get(e,t),lookupJson:(e,t,r)=>{const i=n(e)[t];return null==i?null:r?h.get(i,r)??null:i},mapLookup:(e,t,r)=>{if(!Array.isArray(t))return[];const i=n(e),s=[];for(const e of t){if("string"!=typeof e)continue;const t=i[e];if(null==t)continue;const n=h.get(t,r);if(null!=n)if(Array.isArray(n))for(const e of n)s.push(e);else s.push(n)}return s},...t}}(a,c);let d={},g=null;const p=[],m=n??console;for(const[,{rule:n,validate:c}]of e.entries())if(c(t)){if(p.push(n.name),n.schema&&n.schema.length>0)try{const e=de(n.schema,{globalSchemas:l,configDir:a});me(e,n.name);const r=pe(e,t);d={...d,...r}}catch(e){m.warn(`Schema processing failed for rule "${n.name}": ${e instanceof Error?e.message:String(e)}`);continue}if(n.map){let e;if("string"==typeof n.map){if(n.map.endsWith(".json")&&a)try{const t=i.resolve(a,n.map),r=u.readFileSync(t,"utf-8");e=JSON.parse(r)}catch(e){m.warn(`Failed to load map file "${n.map}": ${e instanceof Error?e.message:String(e)}`);continue}else if(e=r?.[n.map],!e){m.warn(`Map reference "${n.map}" not found in named maps. Skipping map transformation.`);continue}}else e=n.map;try{const r=new o.JsonMap(e,f),i=await r.transform(t);i&&"object"==typeof i&&!Array.isArray(i)?d={...d,...i}:m.warn("JsonMap transformation did not return an object; skipping merge.")}catch(e){m.warn(`JsonMap transformation failed: ${e instanceof Error?e.message:String(e)}`)}}if(n.template&&s){const e=n.name,r={...t.json??{},...t,...d};try{const t=s.render(e,r);t&&t.trim()?g=t:m.warn(`Template for rule "${n.name}" rendered empty output. Falling back to raw content.`)}catch(e){m.warn(`Template render failed for rule "${n.name}": ${e instanceof Error?e.message:String(e)}. Falling back to raw content.`)}}}return{metadata:d,renderedContent:g,matchedRules:p}}function be(e,t,r,n){const s=H(e),o={file:{path:s,directory:H(i.dirname(s)),filename:i.basename(s),extension:i.extname(s),sizeBytes:t.size,modified:t.mtime.toISOString()}};return r&&(o.frontmatter=r),n&&(o.json=n),o}function we(e){return ie((async t=>{const{config:r,testPaths:i}=t.body,{candidateRaw:n,parsed:s,errors:o}=re(e.config,r);if(o.length>0)return{valid:!1,errors:o};const a=function(e){const t=[];for(const r of["mapHelpers","templateHelpers"]){const i=e[r];if(i)for(const[e,n]of Object.entries(i))if(n.path)if(u.existsSync(n.path))try{u.readFileSync(n.path,"utf-8")}catch(i){t.push({path:`${r}.${e}.path`,message:`Failed to read: ${D(i).message}`})}else t.push({path:`${r}.${e}.path`,message:`File not found: ${n.path}`})}return t}(n);if(a.length>0)return{valid:!1,errors:a};if(s?.inferenceRules)for(const e of s.inferenceRules)if(e.schema&&0!==e.schema.length)try{me(de(e.schema,{globalSchemas:s.schemas??{}}),e.name)}catch(t){return{valid:!1,errors:[{path:`inferenceRules[${e.name}]`,message:D(t).message}]}}const c=[];if(i&&s?.inferenceRules){const e=ne(s.inferenceRules);for(const t of i)try{const r=be(t,u.statSync(t)),i=await ye(e,r);c.push({path:t,matchedRules:i.matchedRules,metadata:i.metadata})}catch{c.push({path:t,matchedRules:[],metadata:{},error:"File not found"})}}return{valid:!0,...c.length>0?{testResults:c}:{}}}),e.logger,"Config validate")}function ve(e,t){switch(t){case"string":return"string"==typeof e;case"integer":return"number"==typeof e&&Number.isInteger(e);case"number":return"number"==typeof e&&Number.isFinite(e);case"boolean":return"boolean"==typeof e;case"array":return Array.isArray(e);case"object":return function(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}(e);default:return!0}}function Me(e){return null===e?"null":Array.isArray(e)?"array":"number"==typeof e?Number.isInteger(e)?"integer":"number":typeof e}function Se(e){return ie((async(t,r)=>{const{path:n,metadata:s}=t.body,o=function(e,t,r){const n=ne(e.inferenceRules??[]),s=H(t),o={file:{path:s,directory:H(i.dirname(s)),filename:i.basename(s),extension:i.extname(s),sizeBytes:0,modified:new Date(0).toISOString()}},a=n.filter((e=>e.validate(o))),c=a.map((e=>e.rule.name)),l=a.flatMap((e=>e.rule.schema??[]));if(0===l.length)return{ok:!0,matchedRules:c};const u=de(l,{globalSchemas:e.schemas??{}}),h=[];for(const[t,i]of Object.entries(r)){if(null==i)continue;if(!Object.hasOwn(u.properties,t))continue;const r=u.properties[t].type;if(r&&!ve(i,r)){const n=Me(i),s=a.filter((i=>{if(!i.rule.schema)return!1;const n=de(i.rule.schema,{globalSchemas:e.schemas??{}});return Object.hasOwn(n.properties,t)&&n.properties[t].type===r})),o=s.length>0?s[0].rule.name:c[0]??"unknown";h.push({property:t,expected:r,received:n,rule:o,message:`Property '${t}' is declared as ${r} in ${o} schema, received ${n}`})}}return h.length>0?{ok:!1,matchedRules:c,error:"Validation failed",details:h}:{ok:!0,matchedRules:c}}(e.config,n,s);return o.ok?(await e.processor.processMetadataUpdate(n,s),{ok:!0,matched_rules:o.matchedRules}):r.code(400).send({error:o.error,details:o.details})}),e.logger,"Metadata update")}function ze(e,t=!1){let r=e.replace(/\\/g,"/").toLowerCase();return t&&(r=r.replace(/^([a-z]):/,((e,t)=>t))),r}function xe(e,t){const r=ze(e,!0),n=d.createHash("sha256").update(r,"utf8").digest("hex");return i.join(t,`${n}.meta.json`)}async function ke(e,t){try{const i=await r.readFile(xe(e,t),"utf8");return JSON.parse(i)}catch{return null}}async function je(e,t,n){const s=xe(e,t);await r.mkdir(i.dirname(s),{recursive:!0}),await r.writeFile(s,JSON.stringify(n,null,2),"utf8")}async function Pe(e,t){try{await r.rm(xe(e,t))}catch{}}const Fe=["file_path","chunk_index","total_chunks","content_hash","chunk_text"],Re="file_path",Oe="chunk_index",Ee="total_chunks",Ae="content_hash",Ce="chunk_text";class De{_active=!1;_scope;_startedAt;start(e){this._active=!0,this._scope=e,this._startedAt=(new Date).toISOString()}complete(){this._active=!1,this._scope=void 0,this._startedAt=void 0}getStatus(){return this._active?{active:!0,scope:this._scope,startedAt:this._startedAt}:{active:!1}}}function Te(e){const{processor:o,vectorStore:a,embeddingProvider:c,logger:l,config:u,issuesManager:f,valuesManager:d,configPath:g,helperIntrospection:p}=e,m=e.reindexTracker??new De,y=t({logger:!1});var b;return y.get("/status",(b={vectorStore:a,collectionName:u.vectorStore.collectionName,reindexTracker:m},async()=>{const e=await b.vectorStore.getCollectionInfo();return{status:"ok",uptime:process.uptime(),collection:{name:b.collectionName,pointCount:e.pointCount,dimensions:e.dimensions},reindex:b.reindexTracker.getStatus()}})),y.post("/metadata",Se({processor:o,config:u,logger:l})),y.post("/search",function(e){return ie((async t=>{const{query:r,limit:i=10,offset:n,filter:s}=t.body,o=await e.embeddingProvider.embed([r]);return await e.vectorStore.search(o[0],i,s,n)}),e.logger,"Search")}({embeddingProvider:c,vectorStore:a,logger:l})),y.post("/reindex",function(e){return ie((async(t,r)=>{const i=await W(e.watch.paths,e.watch.ignored,e.processor,"processFile");return await r.status(200).send({ok:!0,filesIndexed:i})}),e.logger,"Reindex")}({watch:u.watch,processor:o,logger:l})),y.post("/rebuild-metadata",function(e){return ie((async(t,r)=>{const i=e.metadataDir??".jeeves-metadata",n=[...Fe];for await(const t of e.vectorStore.scroll()){const e=t.payload,r=e[Re];if("string"!=typeof r||0===r.length)continue;const s=h.omit(e,n);await je(r,i,s)}return await r.status(200).send({ok:!0})}),e.logger,"Rebuild metadata")}({metadataDir:u.metadataDir,vectorStore:a,logger:l})),y.post("/config-reindex",function(e){return ie((async(t,r)=>{const i=t.body.scope??"issues";return J({config:e.config,processor:e.processor,logger:e.logger,reindexTracker:e.reindexTracker},i),await r.status(200).send({status:"started",scope:i})}),e.logger,"Config reindex request")}({config:u,processor:o,logger:l,reindexTracker:m})),y.get("/issues",function(e){return()=>{const t=e.issuesManager.getAll();return{count:Object.keys(t).length,issues:t}}}({issuesManager:f})),y.get("/config/schema",(async(e,t)=>{const r=s.z.toJSONSchema(le);t.send(r)})),y.post("/config/match",function(e){const{config:t,logger:r}=e,s=ne(t.inferenceRules??[]),o=n(t.watch.paths,{dot:!0}),a=t.watch.ignored?.length?n(t.watch.ignored,{dot:!0}):null;return ie((async(e,t)=>{const r=e.body;if(!Array.isArray(r.paths))return void t.code(400).send({error:'Request body must include "paths" array'});const n={matches:r.paths.map((e=>{const t=H(e),r={file:{path:t,directory:H(i.dirname(t)),filename:i.basename(t),extension:i.extname(t),sizeBytes:0,modified:new Date(0).toISOString()}},n=[];for(const e of s)e.validate(r)&&n.push(e.rule.name);return{rules:n,watched:o(t)&&!a?.(t)}}))};t.send(n)}),r,"Config match")}({config:u,logger:l})),y.post("/config/query",ae({config:u,valuesManager:d,issuesManager:f,logger:l,helperIntrospection:p})),y.post("/config/validate",we({config:u,logger:l})),y.post("/config/apply",function(e){return ie((async(t,i)=>{const{config:n}=t.body,{candidateRaw:s,errors:o}=re(e.config,n);if(o.length>0)return await i.status(400).send({valid:!1,errors:o});await r.writeFile(e.configPath,JSON.stringify(s,null,2),"utf-8");const a=e.config.configWatch?.reindex??"issues";return e.triggerReindex&&e.triggerReindex(a),{applied:!0,reindexTriggered:!!e.triggerReindex,scope:a}}),e.logger,"Config apply")}({config:u,configPath:g,reindexTracker:m,logger:l,triggerReindex:e=>{J({config:u,processor:o,logger:l,reindexTracker:m,valuesManager:d,issuesManager:f},e)}})),y}class Ne{filePath;cache=null;logger;constructor(e){this.filePath=e.filePath,this.logger=e.logger,u.mkdirSync(i.dirname(this.filePath),{recursive:!0})}load(){if(this.cache)return this.cache;try{if(u.existsSync(this.filePath)){const e=u.readFileSync(this.filePath,"utf-8");this.cache=JSON.parse(e)}else this.cache=this.createEmpty()}catch{this.logger.warn({filePath:this.filePath},"Failed to read JSON store file, starting fresh"),this.cache=this.createEmpty()}return this.cache}save(){u.writeFileSync(this.filePath,JSON.stringify(this.cache,null,2),"utf-8")}}class Ie extends Ne{constructor(e,t){super({filePath:i.join(e,"issues.json"),logger:t})}createEmpty(){return{}}record(e,t,r,i){const n=this.load(),s={type:t,message:r,timestamp:Math.floor(Date.now()/1e3),...i};n[e]||(n[e]=[]),n[e].push(s),this.save(),this.logger.debug({filePath:e,type:t},"Issue recorded")}clear(e){const t=this.load();e in t&&(Reflect.deleteProperty(t,e),this.save(),this.logger.debug({filePath:e},"Issue cleared"))}clearAll(){this.cache={},this.save(),this.logger.debug("All issues cleared")}getAll(){return{...this.load()}}}const He=s.z.object({type:s.z.union([s.z.literal("type_collision").describe("Conflicting metadata types from multiple rules."),s.z.literal("interpolation_error").describe("Template variable resolution failure.")]).describe("Error category: type_collision (conflicting metadata types), interpolation_error (template rendering failure)."),property:s.z.string().optional(),rules:s.z.array(s.z.string()).optional(),rule:s.z.string().optional(),types:s.z.array(s.z.string()).optional(),message:s.z.string(),timestamp:s.z.union([s.z.number(),s.z.string()])});class $e extends Ne{constructor(e,t){super({filePath:i.join(e,"values.json"),logger:t})}createEmpty(){return{}}isTrackable(e){const t=typeof e;return"string"===t||"number"===t||"boolean"===t}update(e,t){const r=this.load();r[e]??={};const i=r[e];for(const[e,r]of Object.entries(t)){if(!this.isTrackable(r))continue;i[e]??=[];const t=i[e];t.includes(r)||(t.push(r),t.sort(((e,t)=>typeof e==typeof t?String(e).localeCompare(String(t)):typeof e<typeof t?-1:1)))}this.save()}clearAll(){this.cache={},this.save(),this.logger.debug("All values cleared")}getAll(){return{...this.load()}}getForRule(e){return{...this.load()[e]}}}class _e{options;watcher;debounce;constructor(e){this.options=e}start(){this.options.enabled&&(this.watcher=g.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:D(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 We={metadataDir:".jeeves-watcher",shutdownTimeoutMs:1e4},Le={enabled:!0,debounceMs:1e3},Je={host:"127.0.0.1",port:3456},qe={level:"info"},Be={debounceMs:300,stabilityThresholdMs:500,usePolling:!1,pollIntervalMs:1e3,respectGitignore:!0},Ge={chunkSize:1e3,chunkOverlap:200,dimensions:3072,rateLimitPerMinute:300,concurrency:5},Qe=/\$\{([^}]+)\}/g;function Ue(e){if("string"==typeof e)return function(e){return e.replace(Qe,((e,t)=>{const r=process.env[t];return void 0===r?e:r}))}(e);if(Array.isArray(e))return e.map((e=>Ue(e)));if(null!==e&&"object"==typeof e){const t={};for(const[r,i]of Object.entries(e))t[r]=Ue(i);return t}return e}const Ke="jeeves-watcher";async function Ve(e){const t=p.cosmiconfig(Ke),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=te.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 s=i.resolve(t,n),o=u.readFileSync(s,"utf-8");e.maps[r]=JSON.parse(o)}}return Ue((n=e,{...We,...n,watch:{...Be,...n.watch},configWatch:{...Le,...n.configWatch},embedding:{...Ge,...n.embedding},api:{...Je,...n.api},logging:{...qe,...n.logging}}))}catch(e){if(e instanceof s.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 Ye(e){return e||{warn(e,t){t?console.warn(e,t):console.warn(e)}}}const Ze=new Map([["mock",function(e){return function(e){return{dimensions:e,embed:t=>Promise.resolve(t.map((t=>{const r=d.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=Ye(t),n=new m.GoogleGenerativeAIEmbeddings({apiKey:e.apiKey,model:e.model});return{dimensions:r,async embed(t){const s=await I((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:D(n)},"Embedding call failed; will retry")}});for(const e of s)if(e.length!==r)throw new Error(`Gemini embedding returned invalid dimensions: expected ${String(r)}, got ${String(e.length)}`);return s}}}]]);function Xe(e,t,r){const i=new Map(Ze);if(r)for(const[e,t]of r)i.set(e,t);const n=i.get(e.provider);if(!n)throw new Error(`Unsupported embedding provider: ${e.provider}`);return n(e,t)}function et(e){const t=e?.level??"info";if(e?.file){const r=y.transport({target:"pino/file",options:{destination:e.file,mkdir:!0}});return y({level:t},r)}return y({level:t})}function tt(e){return d.createHash("sha256").update(e,"utf8").digest("hex")}const rt="6a6f686e-6761-4c74-ad6a-656576657321";function it(e,t){const r=void 0!==t?`${ze(e)}#${String(t)}`:ze(e);return b.v5(r,rt)}function nt(e,t,r,i){if("string"==typeof r){const n=r,s=i??{};return void e.error({...s,err:D(t)},n)}const n=r,s="string"==typeof i?i:"";e.error({...n,err:D(t)},s)}const st=["content","body","text","snippet","subject","description","summary","transcript"];function ot(e){if(!e||"object"!=typeof e)return JSON.stringify(e);const t=e;for(const e of st){const r=t[e];if("string"==typeof r&&r.trim())return r}return JSON.stringify(e)}async function at(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,s=v.load(i);return{frontmatter:s&&"object"==typeof s&&!Array.isArray(s)?s:void 0,body:n}}(t);return{text:n,frontmatter:i}}async function ct(e){return{text:(await r.readFile(e,"utf8")).replace(/^\uFEFF/,"")}}async function lt(e){const t=await r.readFile(e,"utf8"),i=C.load(t.replace(/^\uFEFF/,""));i("script, style").remove();return{text:i("body").text().trim()||i.text().trim()}}const ut=new Map([[".md",at],[".markdown",at],[".txt",ct],[".text",ct],[".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:ot(i),json:n}}],[".pdf",async function(e){const t=await r.readFile(e),i=new Uint8Array(t),{extractText:n}=await import("unpdf"),{text:s}=await n(i);return{text:Array.isArray(s)?s.join("\n\n"):s}}],[".docx",async function(e){const t=await r.readFile(e);return{text:(await M.extractRawText({buffer:t})).value}}],[".html",lt],[".htm",lt]]);async function ht(e,t,r){const i=new Map(ut);if(r)for(const[e,t]of r)i.set(e,t);const n=i.get(t.toLowerCase());return n?n(e):ct(e)}async function ft(e){const{filePath:t,compiledRules:n,metadataDir:s,maps:o,logger:a,templateEngine:c,configDir:l,customMapLib:u,globalSchemas:h}=e,f=i.extname(t),d=await r.stat(t),g=await ht(t,f),p=be(t,d,g.frontmatter,g.json),{metadata:m,renderedContent:y,matchedRules:b}=await ye(n,p,o,a,c,l,u,h),w=await ke(t,s);return{inferred:m,enrichment:w,metadata:{...m,...w??{}},attributes:p,extracted:g,renderedContent:y,matchedRules:b}}function dt(e,t){const r=[];for(let i=0;i<t;i++)r.push(it(e,i));return r}function gt(e,t=1){if(!e)return t;const r=e[Ee];return"number"==typeof r?r:t}class pt{config;embeddingProvider;vectorStore;compiledRules;logger;templateEngine;issuesManager;valuesManager;constructor({config:e,embeddingProvider:t,vectorStore:r,compiledRules:i,logger:n,templateEngine:s,issuesManager:o,valuesManager:a}){this.config=e,this.embeddingProvider=t,this.vectorStore=r,this.compiledRules=i,this.logger=n,this.templateEngine=s,this.issuesManager=o,this.valuesManager=a}async processFile(e){try{const t=i.extname(e),{metadata:r,extracted:n,renderedContent:s,matchedRules:o}=await ft({filePath:e,compiledRules:this.compiledRules,metadataDir:this.config.metadataDir,maps:this.config.maps,logger:this.logger,templateEngine:this.templateEngine,configDir:this.config.configDir,customMapLib:this.config.customMapLib,globalSchemas:this.config.globalSchemas}),a=s??n.text;if(!a.trim())return void this.logger.debug({filePath:e},"Skipping empty file");const c=tt(a),l=it(e,0),u=await this.vectorStore.getPayload(l);if(u&&u.content_hash===c)return void this.logger.debug({filePath:e},"Content unchanged, skipping");const h=this.config.chunkSize??1e3,f=function(e,t,r){const i=e.toLowerCase();return".md"===i||".markdown"===i?new S.MarkdownTextSplitter({chunkSize:t,chunkOverlap:r}):new S.RecursiveCharacterTextSplitter({chunkSize:t,chunkOverlap:r})}(t,h,this.config.chunkOverlap??200),d={...r,matched_rules:o};if(await async function(e,t,r,i,n){const{embeddingProvider:s,vectorStore:o,splitter:a,logger:c}=e,l=gt(n),u=tt(t),h=await a.splitText(t),f=await s.embed(h),d=h.map(((e,t)=>({id:it(r,t),vector:f[t],payload:{...i,[Re]:H(r),[Oe]:t,[Ee]:h.length,[Ae]:u,[Ce]:e}})));if(await o.upsert(d),l>h.length){const e=dt(r,l).slice(h.length);await o.delete(e)}c.info({filePath:r,chunks:h.length},"File processed successfully")}({embeddingProvider:this.embeddingProvider,vectorStore:this.vectorStore,splitter:f,logger:this.logger},a,e,d,u),this.issuesManager?.clear(e),this.valuesManager)for(const e of o)this.valuesManager.update(e,r)}catch(t){nt(this.logger,t,{filePath:e},"Failed to process file")}}async deleteFile(e){try{const t=it(e,0),r=await this.vectorStore.getPayload(t),i=dt(e,gt(r));await this.vectorStore.delete(i),await Pe(e,this.config.metadataDir),this.logger.info({filePath:e},"File deleted from index")}catch(t){nt(this.logger,t,{filePath:e},"Failed to delete file")}}async processMetadataUpdate(e,t){try{const r={...await ke(e,this.config.metadataDir)??{},...t};await je(e,this.config.metadataDir,r);const i=it(e,0),n=await this.vectorStore.getPayload(i);if(!n)return null;const s=gt(n),o=dt(e,s);return await this.vectorStore.setPayload(o,r),this.logger.info({filePath:e,chunks:s},"Metadata updated"),r}catch(t){return nt(this.logger,t,{filePath:e},"Failed to update metadata"),null}}async processRulesUpdate(e){try{const t=it(e,0),r=await this.vectorStore.getPayload(t);if(!r)return this.logger.debug({filePath:e},"File not indexed, skipping"),null;const{metadata:i,matchedRules:n}=await ft({filePath:e,compiledRules:this.compiledRules,metadataDir:this.config.metadataDir,maps:this.config.maps,logger:this.logger,templateEngine:this.templateEngine,configDir:this.config.configDir,customMapLib:this.config.customMapLib,globalSchemas:this.config.globalSchemas}),s={...i,matched_rules:n},o=gt(r),a=dt(e,o);return await this.vectorStore.setPayload(a,s),this.issuesManager?.clear(e),this.logger.info({filePath:e,chunks:o},"Rules re-applied"),s}catch(t){return nt(this.logger,t,{filePath:e},"Failed to re-apply rules"),null}}updateRules(e,t,r){this.compiledRules=e,t&&(this.templateEngine=t),void 0!==r&&(this.config={...this.config,customMapLib:r}),this.logger.info({rules:e.length},"Inference rules updated")}}class mt{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 yt(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 bt{client;collectionName;dims;log;constructor(e,t,r){this.client=new z.QdrantClient({url:e.url,apiKey:e.apiKey,checkCompatibility:!1}),this.collectionName=e.collectionName,this.dims=t,this.log=Ye(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 retryOperation(e,t){await I((async r=>{r>1&&this.log.warn({attempt:r,operation:`qdrant.${e}`},`Retrying Qdrant ${e}`),await t()}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:t,delayMs:r,error:i})=>{this.log.warn({attempt:t,delayMs:r,operation:`qdrant.${e}`,err:D(i)},`Qdrant ${e} failed; will retry`)}})}async upsert(e){0!==e.length&&await this.retryOperation("upsert",(async()=>{await this.client.upsert(this.collectionName,{wait:!0,points:e.map((e=>({id:e.id,vector:e.vector,payload:e.payload})))})}))}async delete(e){0!==e.length&&await this.retryOperation("delete",(async()=>{await this.client.delete(this.collectionName,{wait:!0,points:e})}))}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={},s=Object.entries(e.payload_schema);if(s.length>0)for(const[e,t]of s)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:yt(i)})}}async search(e,t,r,i){return(await this.client.search(this.collectionName,{vector:e,limit:t,with_payload:!0,...r?{filter:r}:{},...void 0!==i?{offset:i}:{}})).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 wt{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:D(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((()=>{o(),r()}),t),s=()=>{o(),i(new Error("Backoff aborted"))},o=()=>{clearTimeout(n),e&&e.removeEventListener("abort",s)};if(e){if(e.aborted)return void s();e.addEventListener("abort",s,{once:!0})}})))}get failures(){return this.consecutiveFailures}}function vt(e){const t=H(e).split("/"),r=[];for(const e of t){if(/[*?{[\]]/.test(e))break;r.push(e)}return r.join("/")||"."}function Mt(e){const t=function(e){const t=e.map((e=>H(e).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(vt)),r=function(e){const t=e.map((e=>H(e))),r=n(t,{dot:!0,nocase:!0});return e=>{const t=H(e);return r(t)}}(e);return{roots:t,matches:r}}class St{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 s={maxRetries:n.maxRetries,maxBackoffMs:n.maxBackoffMs,onFatalError:n.onFatalError,logger:i};this.health=new wt(s)}start(){const{roots:e,matches:t}=Mt(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=H(e),r=n(t,{dot:!0,nocase:!0});return e=>{const t=H(e);return r(t)}}))}(this.config.ignored):void 0;this.watcher=g.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:D(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 zt={loadConfig:Ve,createLogger:et,createEmbeddingProvider:Xe,createVectorStoreClient:(e,t,r)=>new bt(e,t,r),compileRules:ne,createDocumentProcessor:e=>new pt(e),createEventQueue:e=>new mt(e),createFileSystemWatcher:(e,t,r,i,n)=>new St(e,t,r,i,n),createApiServer:Te};function xt(e){let t=i.resolve(e);const r=i.resolve("/");for(;t!==r;){if(u.existsSync(i.join(t,".git"))&&u.statSync(i.join(t,".git")).isDirectory())return t;const e=i.dirname(t);if(e===t)break;t=e}}function kt(e){const t=i.resolve(e);try{return u.statSync(t).isDirectory()?t:i.dirname(t)}catch{}const r=/[*?[{]/.exec(e);if(!r)return;const n=e.slice(0,r.index).trim(),s=0===n.length?".":n.endsWith("/")||n.endsWith("\\")?n:i.dirname(n),o=i.resolve(s);return u.existsSync(o)?o:void 0}function jt(e){const t=[],r=i.join(e,".gitignore");let n;u.existsSync(r)&&t.push(r);try{n=u.readdirSync(e)}catch{return t}for(const r of n){if(".git"===r||"node_modules"===r)continue;const n=i.join(e,r);try{u.statSync(n).isDirectory()&&t.push(...jt(n))}catch{}}return t}function Pt(e){const t=u.readFileSync(e,"utf8");return x().add(t)}class Ft{repos=new Map;constructor(e){this.scan(e)}scan(e){this.repos.clear();const t=new Set;for(const r of e){const e=kt(r);if(!e)continue;if(t.has(e))continue;t.add(e);const n=xt(e);if(!n)continue;if(this.repos.has(n))continue;const s=jt(n).map((e=>({dir:i.dirname(e),ig:Pt(e)})));s.sort(((e,t)=>t.dir.length-e.dir.length)),this.repos.set(n,{root:n,entries:s})}}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(u.existsSync(t)&&(e.entries.push({dir:r,ig:Pt(t)}),e.entries.sort(((e,t)=>t.dir.length-e.dir.length))))}const n=xt(r);if(n&&u.existsSync(t)){const e=[{dir:r,ig:Pt(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})}}}function Rt(e){const t={},r=/\/\*\*([\s\S]*?)\*\/\s*export\s+(?:function|const)\s+(\w+)/g;let i;for(;null!==(i=r.exec(e));){const[,e,r]=i;if(!r||!e)continue;const n=e.split("\n").map((e=>e.replace(/^\s*\*\s?/,"").trim())).filter((e=>e.length>0&&!e.startsWith("@")));t[r]=n.join(" ")}return t}async function Ot(e,t){const r={},i=await ue({[t]:{path:e}},"",(e=>"function"==typeof e));let n={};for(const t of[".ts",".js",""]){const r=e.replace(/\.[jt]s$/,"")+t;try{if(n=Rt(u.readFileSync(""===t?e:r,"utf-8")),Object.keys(n).length>0)break}catch{}}const s=i[t]??{};for(const e of Object.keys(s))r[`${t}_${e}`]=n[e]??"";return{exports:r}}const Et=E.unified().use(O,{fragment:!0});function At(e){e.registerHelper("adfToMarkdown",(function(t){if(!t||"object"!=typeof t)return"";try{const r=F.fromADF(t);return new e.SafeString(R.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=Et.parse(t),i=P.toMdast(r);return new e.SafeString(R.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 j(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?h.capitalize(e):"")),e.registerHelper("title",(e=>"string"==typeof e?h.title(e):"")),e.registerHelper("camel",(e=>"string"==typeof e?h.camel(e):"")),e.registerHelper("snake",(e=>"string"==typeof e?h.snake(e):"")),e.registerHelper("dash",(e=>"string"==typeof e?h.dash(e):"")),e.registerHelper("default",(function(e,t){return e??t??""})),e.registerHelper("eq",(function(e,t){return h.isEqual(e,t)})),e.registerHelper("json",(function(t){return new e.SafeString(JSON.stringify(t,null,2))}))}function Ct(e,t,r,n=new Set){if(e.endsWith(".hbs")||e.endsWith(".handlebars"))return u.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),Ct(t[e],t,r,n)}return e}function Dt(){const e=k.create();return At(e),e}async function Tt(e,t,r){for(const[n,{path:s}]of Object.entries(t)){const t=i.resolve(r,s),o=await import(f.pathToFileURL(t).href);if("function"==typeof o.default){const t=new Set(Object.keys(e.helpers));o.default(e,n);const r=Object.keys(e.helpers);for(const i of r)if(!t.has(i)&&!i.startsWith(`${n}_`)){const t=e.helpers[i];e.registerHelper(`${n}_${i}`,t),e.unregisterHelper(i)}}}}class Nt{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 It(e,t,r,i){if(0===e.filter((e=>e.template)).length)return;const n=Dt();r&&Object.keys(r).length>0&&i&&await Tt(n,r,i);const s=new Nt(n),o=t?Object.fromEntries(Object.entries(t).map((([e,t])=>{return[e,(r=t,"string"==typeof r?r:r.template)];var r}))):void 0;for(const t of e){if(!t.template)continue;const e=Ct(t.template,o,i??".");s.compile(t.name,e)}return s}function Ht(e){if(!e)return;const t={};for(const[r,i]of Object.entries(e))t[r]="string"==typeof i?i:i&&"object"==typeof i&&"map"in i?i.map:i;return t}async function $t(e,t){const r=await It(e.inferenceRules??[],e.templates,e.templateHelpers,t),i=e.mapHelpers&&t?await async function(e,t){const r=await ue(e,t,(e=>"function"==typeof e)),i={};for(const[e,t]of Object.entries(r))for(const[r,n]of Object.entries(t))i[`${e}_${r}`]=n;return i}(e.mapHelpers,t):void 0;return{templateEngine:r,customMapLib:i}}async function _t(e,t){return await async function(e,t){const r={mapHelpers:{},templateHelpers:{}};if(e.mapHelpers)for(const[n,{path:s}]of Object.entries(e.mapHelpers)){const e=i.resolve(t,s);r.mapHelpers[n]=await Ot(e,n)}if(e.templateHelpers)for(const[n,{path:s}]of Object.entries(e.templateHelpers)){const e=i.resolve(t,s);r.templateHelpers[n]=await Ot(e,n)}return r}({mapHelpers:e.mapHelpers,templateHelpers:e.templateHelpers},t)}function Wt(e){return e?i.dirname(e):"."}class Lt{config;configPath;factories;runtimeOptions;logger;watcher;queue;server;processor;configWatcher;issuesManager;valuesManager;helperIntrospection;constructor(e,t,r={},i={}){this.config=e,this.configPath=t,this.factories={...zt,...r},this.runtimeOptions=i}async start(){const e=this.factories.createLogger(this.config.logging);this.logger=e;const{embeddingProvider:t,vectorStore:r}=await async function(e,t,r){let i;try{i=t.createEmbeddingProvider(e.embedding,r)}catch(e){throw r.fatal({err:D(e)},"Failed to create embedding provider"),e}const n=t.createVectorStoreClient(e.vectorStore,i.dimensions,r);return await n.ensureCollection(),{embeddingProvider:i,vectorStore:n}}(this.config,this.factories,e),i=this.factories.compileRules(this.config.inferenceRules??[]),n=Wt(this.configPath),{templateEngine:s,customMapLib:o}=await $t(this.config,n);this.helperIntrospection=await _t(this.config,n);const a=function(e,t,r){return{metadataDir:e.metadataDir??".jeeves-metadata",chunkSize:e.embedding.chunkSize,chunkOverlap:e.embedding.chunkOverlap,maps:Ht(e.maps),configDir:t,customMapLib:r,globalSchemas:e.schemas}}(this.config,n,o),c=this.factories.createDocumentProcessor({config:a,embeddingProvider:t,vectorStore:r,compiledRules:i,logger:e,templateEngine:s});this.processor=c,this.queue=this.factories.createEventQueue({debounceMs:this.config.watch.debounceMs??2e3,concurrency:this.config.embedding.concurrency??5,rateLimitPerMinute:this.config.embedding.rateLimitPerMinute}),this.watcher=function(e,t,r,i,n,s){const o=e.watch.respectGitignore??1?new Ft(e.watch.paths):void 0;return t.createFileSystemWatcher(e.watch,r,i,n,{maxRetries:e.maxRetries??s.maxRetries,maxBackoffMs:e.maxBackoffMs??s.maxBackoffMs,onFatalError:s.onFatalError,gitignoreFilter:o})}(this.config,this.factories,this.queue,c,e,this.runtimeOptions);const l=this.config.stateDir??this.config.metadataDir??".jeeves-metadata";this.issuesManager=new Ie(l,e),this.valuesManager=new $e(l,e),this.server=await this.startApiServer(c,r,t,e,this.issuesManager,this.valuesManager,this.helperIntrospection),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 startApiServer(e,t,r,i,n,s,o){const a=this.factories.createApiServer({processor:e,vectorStore:t,embeddingProvider:r,queue:this.queue,config:this.config,logger:i,issuesManager:n,valuesManager:s,configPath:this.configPath??"",helperIntrospection:o});return await a.listen({host:this.config.api?.host??"127.0.0.1",port:this.config.api?.port??3456}),a}startConfigWatch(){const e=this.logger;if(!e)return;const t=this.config.configWatch?.enabled??!0;t&&this.configPath?(this.configWatcher=new _e({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 i=this.factories.compileRules(r.inferenceRules??[]),n=Wt(this.configPath),{templateEngine:s,customMapLib:o}=await $t(r,n);t.updateRules(i,s,o),e.info({configPath:this.configPath,rules:i.length},"Config reloaded");const a=r.configWatch?.reindex??"issues";e.info({scope:a},`Config watch triggering ${a} reindex`),await J({config:r,processor:t,logger:e,valuesManager:this.valuesManager,issuesManager:this.issuesManager},a)}catch(t){e.error({err:D(t)},"Failed to reload config")}}}}e.DocumentProcessor=pt,e.EventQueue=mt,e.FileSystemWatcher=St,e.GitignoreFilter=Ft,e.IssuesManager=Ie,e.JeevesWatcher=Lt,e.ReindexTracker=De,e.SystemHealth=wt,e.TemplateEngine=Nt,e.ValuesManager=$e,e.VectorStoreClient=bt,e.apiConfigSchema=G,e.applyRules=ye,e.buildAttributes=be,e.buildTemplateEngine=It,e.compileRules=ne,e.configWatchConfigSchema=B,e.contentHash=tt,e.createApiServer=Te,e.createEmbeddingProvider=Xe,e.createHandlebarsInstance=Dt,e.createLogger=et,e.deleteMetadata=Pe,e.embeddingConfigSchema=X,e.extractText=ht,e.inferenceRuleSchema=Z,e.issueRecordSchema=He,e.jeevesWatcherConfigSchema=te,e.loadConfig=Ve,e.loadCustomHelpers=Tt,e.loggingConfigSchema=Q,e.metadataPath=xe,e.pointId=it,e.readMetadata=ke,e.registerBuiltinHelpers=At,e.resolveTemplateSource=Ct,e.startFromConfig=async function(e){const t=await Ve(e),r=new Lt(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=ee,e.watchConfigSchema=q,e.writeMetadata=je}(this["jeeves-watcher"]=this["jeeves-watcher"]||{},Fastify,promises,node_path,picomatch,zod,jsonmap,Ajv,addFormats,jsonpathPlus,node_fs,radash,node_url,node_crypto,chokidar,cosmiconfig,googleGenai,pino,uuid,cheerio,yaml,mammoth,textsplitters,jsClientRest,ignore,Handlebars,dayjs,hastUtilToMdast,mdastUtilFromAdf,mdastUtilToMarkdown,rehypeParse,unified);
|
package/dist/plugin/index.js
DELETED
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module plugin/helpers
|
|
3
|
-
* Shared types and utility functions for the OpenClaw plugin tool registrations.
|
|
4
|
-
*/
|
|
5
|
-
const DEFAULT_API_URL = 'http://127.0.0.1:3458';
|
|
6
|
-
/** Resolve the watcher API base URL from plugin config. */
|
|
7
|
-
function getApiUrl(api) {
|
|
8
|
-
const url = api.config?.plugins?.entries?.['jeeves-watcher']?.config?.apiUrl;
|
|
9
|
-
return typeof url === 'string' ? url : DEFAULT_API_URL;
|
|
10
|
-
}
|
|
11
|
-
/** Format a successful tool result. */
|
|
12
|
-
function ok(data) {
|
|
13
|
-
return {
|
|
14
|
-
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
/** Format an error tool result. */
|
|
18
|
-
function fail(error) {
|
|
19
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
-
return {
|
|
21
|
-
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
22
|
-
isError: true,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
/** Fetch JSON from a URL, throwing on non-OK responses. */
|
|
26
|
-
async function fetchJson(url, init) {
|
|
27
|
-
const res = await fetch(url, init);
|
|
28
|
-
if (!res.ok) {
|
|
29
|
-
throw new Error(`HTTP ${String(res.status)}: ${await res.text()}`);
|
|
30
|
-
}
|
|
31
|
-
return res.json();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @module plugin
|
|
36
|
-
* OpenClaw plugin entry point. Registers all jeeves-watcher tools.
|
|
37
|
-
*/
|
|
38
|
-
/** Register all jeeves-watcher tools with the OpenClaw plugin API. */
|
|
39
|
-
function register(api) {
|
|
40
|
-
const baseUrl = getApiUrl(api);
|
|
41
|
-
api.registerTool({
|
|
42
|
-
name: 'watcher_status',
|
|
43
|
-
description: 'Get jeeves-watcher service health, uptime, and collection statistics.',
|
|
44
|
-
parameters: { type: 'object', properties: {} },
|
|
45
|
-
execute: async () => {
|
|
46
|
-
try {
|
|
47
|
-
return ok(await fetchJson(`${baseUrl}/status`));
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
return fail(error);
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
}, { optional: true });
|
|
54
|
-
api.registerTool({
|
|
55
|
-
name: 'watcher_search',
|
|
56
|
-
description: 'Semantic search over indexed documents. Supports Qdrant filters.',
|
|
57
|
-
parameters: {
|
|
58
|
-
type: 'object',
|
|
59
|
-
required: ['query'],
|
|
60
|
-
properties: {
|
|
61
|
-
query: { type: 'string', description: 'Search query text.' },
|
|
62
|
-
limit: {
|
|
63
|
-
type: 'number',
|
|
64
|
-
description: 'Max results (default 10).',
|
|
65
|
-
},
|
|
66
|
-
offset: {
|
|
67
|
-
type: 'number',
|
|
68
|
-
description: 'Number of results to skip for pagination.',
|
|
69
|
-
},
|
|
70
|
-
filter: {
|
|
71
|
-
type: 'object',
|
|
72
|
-
description: 'Qdrant filter object.',
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
execute: async (_id, params) => {
|
|
77
|
-
try {
|
|
78
|
-
const body = { query: params.query };
|
|
79
|
-
if (params.limit !== undefined)
|
|
80
|
-
body.limit = params.limit;
|
|
81
|
-
if (params.offset !== undefined)
|
|
82
|
-
body.offset = params.offset;
|
|
83
|
-
if (params.filter !== undefined)
|
|
84
|
-
body.filter = params.filter;
|
|
85
|
-
return ok(await fetchJson(`${baseUrl}/search`, {
|
|
86
|
-
method: 'POST',
|
|
87
|
-
headers: { 'Content-Type': 'application/json' },
|
|
88
|
-
body: JSON.stringify(body),
|
|
89
|
-
}));
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
return fail(error);
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
}, { optional: true });
|
|
96
|
-
api.registerTool({
|
|
97
|
-
name: 'watcher_enrich',
|
|
98
|
-
description: 'Set or update metadata on a document by file path.',
|
|
99
|
-
parameters: {
|
|
100
|
-
type: 'object',
|
|
101
|
-
required: ['path', 'metadata'],
|
|
102
|
-
properties: {
|
|
103
|
-
path: {
|
|
104
|
-
type: 'string',
|
|
105
|
-
description: 'Relative file path of the document.',
|
|
106
|
-
},
|
|
107
|
-
metadata: {
|
|
108
|
-
type: 'object',
|
|
109
|
-
description: 'Key-value metadata to set on the document.',
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
execute: async (_id, params) => {
|
|
114
|
-
try {
|
|
115
|
-
return ok(await fetchJson(`${baseUrl}/metadata`, {
|
|
116
|
-
method: 'POST',
|
|
117
|
-
headers: { 'Content-Type': 'application/json' },
|
|
118
|
-
body: JSON.stringify({
|
|
119
|
-
path: params.path,
|
|
120
|
-
metadata: params.metadata,
|
|
121
|
-
}),
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
return fail(error);
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
}, { optional: true });
|
|
129
|
-
api.registerTool({
|
|
130
|
-
name: 'watcher_query',
|
|
131
|
-
description: 'Query the merged virtual document via JSONPath.',
|
|
132
|
-
parameters: {
|
|
133
|
-
type: 'object',
|
|
134
|
-
required: ['path'],
|
|
135
|
-
properties: {
|
|
136
|
-
path: {
|
|
137
|
-
type: 'string',
|
|
138
|
-
description: 'JSONPath expression.',
|
|
139
|
-
},
|
|
140
|
-
resolve: {
|
|
141
|
-
type: 'array',
|
|
142
|
-
items: { type: 'string', enum: ['files', 'globals'] },
|
|
143
|
-
description: 'Resolution scopes to include (e.g., ["files"], ["globals"], or both).',
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
execute: async (_id, params) => {
|
|
148
|
-
try {
|
|
149
|
-
const body = { path: params.path };
|
|
150
|
-
if (params.resolve !== undefined)
|
|
151
|
-
body.resolve = params.resolve;
|
|
152
|
-
return ok(await fetchJson(`${baseUrl}/config/query`, {
|
|
153
|
-
method: 'POST',
|
|
154
|
-
headers: { 'Content-Type': 'application/json' },
|
|
155
|
-
body: JSON.stringify(body),
|
|
156
|
-
}));
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
return fail(error);
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
}, { optional: true });
|
|
163
|
-
api.registerTool({
|
|
164
|
-
name: 'watcher_validate',
|
|
165
|
-
description: 'Validate a candidate config (or current config if omitted). Optionally test file paths against the config to preview rule matching and metadata output.',
|
|
166
|
-
parameters: {
|
|
167
|
-
type: 'object',
|
|
168
|
-
properties: {
|
|
169
|
-
config: {
|
|
170
|
-
type: 'object',
|
|
171
|
-
description: 'Candidate config (partial or full). Omit to validate current config.',
|
|
172
|
-
},
|
|
173
|
-
testPaths: {
|
|
174
|
-
type: 'array',
|
|
175
|
-
items: { type: 'string' },
|
|
176
|
-
description: 'File paths to test against the config for dry-run preview.',
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
execute: async (_id, params) => {
|
|
181
|
-
try {
|
|
182
|
-
const body = {};
|
|
183
|
-
if (params.config !== undefined)
|
|
184
|
-
body.config = params.config;
|
|
185
|
-
if (params.testPaths !== undefined)
|
|
186
|
-
body.testPaths = params.testPaths;
|
|
187
|
-
return ok(await fetchJson(`${baseUrl}/config/validate`, {
|
|
188
|
-
method: 'POST',
|
|
189
|
-
headers: { 'Content-Type': 'application/json' },
|
|
190
|
-
body: JSON.stringify(body),
|
|
191
|
-
}));
|
|
192
|
-
}
|
|
193
|
-
catch (error) {
|
|
194
|
-
return fail(error);
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
}, { optional: true });
|
|
198
|
-
api.registerTool({
|
|
199
|
-
name: 'watcher_config_apply',
|
|
200
|
-
description: 'Apply a full or partial config. Validates, writes to disk, and triggers configured reindex behavior.',
|
|
201
|
-
parameters: {
|
|
202
|
-
type: 'object',
|
|
203
|
-
required: ['config'],
|
|
204
|
-
properties: {
|
|
205
|
-
config: {
|
|
206
|
-
type: 'object',
|
|
207
|
-
description: 'Full or partial config to apply.',
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
execute: async (_id, params) => {
|
|
212
|
-
try {
|
|
213
|
-
return ok(await fetchJson(`${baseUrl}/config/apply`, {
|
|
214
|
-
method: 'POST',
|
|
215
|
-
headers: { 'Content-Type': 'application/json' },
|
|
216
|
-
body: JSON.stringify({ config: params.config }),
|
|
217
|
-
}));
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
return fail(error);
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
}, { optional: true });
|
|
224
|
-
api.registerTool({
|
|
225
|
-
name: 'watcher_reindex',
|
|
226
|
-
description: 'Trigger a reindex of the watched files.',
|
|
227
|
-
parameters: {
|
|
228
|
-
type: 'object',
|
|
229
|
-
properties: {
|
|
230
|
-
scope: {
|
|
231
|
-
type: 'string',
|
|
232
|
-
enum: ['rules', 'full'],
|
|
233
|
-
description: 'Reindex scope: "rules" (default) re-applies inference rules; "full" re-embeds everything.',
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
execute: async (_id, params) => {
|
|
238
|
-
try {
|
|
239
|
-
return ok(await fetchJson(`${baseUrl}/config-reindex`, {
|
|
240
|
-
method: 'POST',
|
|
241
|
-
headers: { 'Content-Type': 'application/json' },
|
|
242
|
-
body: JSON.stringify({
|
|
243
|
-
scope: params.scope ?? 'rules',
|
|
244
|
-
}),
|
|
245
|
-
}));
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
return fail(error);
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
}, { optional: true });
|
|
252
|
-
api.registerTool({
|
|
253
|
-
name: 'watcher_issues',
|
|
254
|
-
description: 'Get runtime embedding failures. Shows files that failed processing and why.',
|
|
255
|
-
parameters: { type: 'object', properties: {} },
|
|
256
|
-
execute: async () => {
|
|
257
|
-
try {
|
|
258
|
-
return ok(await fetchJson(`${baseUrl}/issues`));
|
|
259
|
-
}
|
|
260
|
-
catch (error) {
|
|
261
|
-
return fail(error);
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
}, { optional: true });
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export { register as default };
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "jeeves-watcher",
|
|
3
|
-
"name": "Jeeves Watcher",
|
|
4
|
-
"description": "Semantic search and metadata enrichment via a jeeves-watcher instance.",
|
|
5
|
-
"version": "0.5.0",
|
|
6
|
-
"skills": ["dist/skills/jeeves-watcher", "dist/skills/jeeves-watcher-admin"],
|
|
7
|
-
"configSchema": {
|
|
8
|
-
"type": "object",
|
|
9
|
-
"additionalProperties": false,
|
|
10
|
-
"properties": {
|
|
11
|
-
"apiUrl": {
|
|
12
|
-
"type": "string",
|
|
13
|
-
"description": "jeeves-watcher API base URL",
|
|
14
|
-
"default": "http://127.0.0.1:3458"
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"uiHints": {
|
|
19
|
-
"apiUrl": {
|
|
20
|
-
"label": "Watcher API URL",
|
|
21
|
-
"placeholder": "http://127.0.0.1:3458"
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|