@jacebenson/jsn 3.1.5 → 3.3.0
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/bin/jsn.js +14 -2
- package/package.json +6 -4
- package/skills/servicenow/SKILL.md +31 -3
- package/src/app.js +15 -1
- package/src/auth.js +34 -3
- package/src/cli.js +59 -11
- package/src/commands/docs/db.js +140 -0
- package/src/commands/docs/docs.js +166 -0
- package/src/commands/docs/embed.js +46 -0
- package/src/commands/docs/hrr.js +95 -0
- package/src/commands/docs/ingest.js +95 -0
- package/src/commands/docs/public/index.html +178 -0
- package/src/commands/docs/public/vendor/marked.min.js +69 -0
- package/src/commands/docs/public/vendor/purify.min.js +3 -0
- package/src/commands/docs/refresh.js +135 -0
- package/src/commands/docs/search.js +139 -0
- package/src/commands/docs/serve.js +278 -0
- package/src/commands/docs/sync.js +75 -0
- package/src/commands/skill.js +203 -27
- package/src/commands/version.js +2 -2
- package/suppress-warnings.cjs +13 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! @license DOMPurify 3.4.12 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.12/LICENSE */
|
|
2
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,function(){"use strict";function e(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=Array(t);n<t;n++)o[n]=e[n];return o}function t(t,n){return function(e){if(Array.isArray(e))return e}(t)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var o,r,i,a,l=[],c=!0,s=!1;try{if(i=(n=n.call(e)).next,0===t);else for(;!(c=(o=i.call(n)).done)&&(l.push(o.value),l.length!==t);c=!0);}catch(e){s=!0,r=e}finally{try{if(!c&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(s)throw r}}return l}}(t,n)||function(t,n){if(t){if("string"==typeof t)return e(t,n);var o={}.toString.call(t).slice(8,-1);return"Object"===o&&t.constructor&&(o=t.constructor.name),"Map"===o||"Set"===o?Array.from(t):"Arguments"===o||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(o)?e(t,n):void 0}}(t,n)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}const n=Object.entries,o=Object.setPrototypeOf,r=Object.isFrozen,i=Object.getPrototypeOf,a=Object.getOwnPropertyDescriptor;let l=Object.freeze,c=Object.seal,s=Object.create,u="undefined"!=typeof Reflect&&Reflect,f=u.apply,p=u.construct;l||(l=function(e){return e}),c||(c=function(e){return e}),f||(f=function(e,t){for(var n=arguments.length,o=new Array(n>2?n-2:0),r=2;r<n;r++)o[r-2]=arguments[r];return e.apply(t,o)}),p||(p=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o<t;o++)n[o-1]=arguments[o];return new e(...n)});const m=L(Array.prototype.forEach),d=L(Array.prototype.lastIndexOf),h=L(Array.prototype.pop),g=L(Array.prototype.push),y=L(Array.prototype.splice),b=Array.isArray,T=L(String.prototype.toLowerCase),S=L(String.prototype.toString),E=L(String.prototype.match),A=L(String.prototype.replace),N=L(String.prototype.indexOf),_=L(String.prototype.trim),w=L(Number.prototype.toString),O=L(Boolean.prototype.toString),v="undefined"==typeof BigInt?null:L(BigInt.prototype.toString),D="undefined"==typeof Symbol?null:L(Symbol.prototype.toString),R=L(Object.prototype.hasOwnProperty),C=L(Object.prototype.toString),I=L(RegExp.prototype.test),x=(k=TypeError,function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return p(k,t)});var k;function L(e){return function(t){t instanceof RegExp&&(t.lastIndex=0);for(var n=arguments.length,o=new Array(n>1?n-1:0),r=1;r<n;r++)o[r-1]=arguments[r];return f(e,t,o)}}function M(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:T;if(o&&o(e,null),!b(t))return e;let i=t.length;for(;i--;){let o=t[i];if("string"==typeof o){const e=n(o);e!==o&&(r(t)||(t[i]=e),o=e)}e[o]=!0}return e}function z(e){for(let t=0;t<e.length;t++){R(e,t)||(e[t]=null)}return e}function P(e){const o=s(null);for(const i of n(e)){var r=t(i,2);const n=r[0],a=r[1];R(e,n)&&(b(a)?o[n]=z(a):a&&"object"==typeof a&&a.constructor===Object?o[n]=P(a):o[n]=a)}return o}function U(e,t){for(;null!==e;){const n=a(e,t);if(n){if(n.get)return L(n.get);if("function"==typeof n.value)return L(n.value)}e=i(e)}return function(){return null}}const F=l(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","search","section","select","shadow","slot","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),H=l(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","enterkeyhint","exportparts","filter","font","g","glyph","glyphref","hkern","image","inputmode","line","lineargradient","marker","mask","metadata","mpath","part","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),j=l(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),B=l(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),G=l(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),W=l(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),Y=l(["#text"]),q=l(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","command","commandfor","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","exportparts","face","for","headers","height","hidden","high","href","hreflang","id","inert","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","nonce","noshade","novalidate","nowrap","open","optimum","part","pattern","placeholder","playsinline","popover","popovertarget","popovertargetaction","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","slot","span","srclang","start","src","srcset","step","style","summary","tabindex","title","translate","type","usemap","valign","value","width","wrap","xmlns"]),X=l(["accent-height","accumulate","additive","alignment-baseline","amplitude","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clippathunits","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dominant-baseline","dur","edgemode","elevation","end","exponent","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","intercept","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","mask-type","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","slope","specularconstant","specularexponent","spreadmethod","startoffset","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","systemlanguage","tabindex","tablevalues","targetx","targety","transform","transform-origin","text-anchor","text-decoration","text-orientation","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),$=l(["accent","accentunder","align","bevelled","close","columnalign","columnlines","columnspacing","columnspan","denomalign","depth","dir","display","displaystyle","encoding","fence","frame","height","href","id","largeop","length","linethickness","lquote","lspace","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),K=l(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),V=c(/{{[\w\W]*|^[\w\W]*}}/g),Z=c(/<%[\w\W]*|^[\w\W]*%>/g),J=c(/\${[\w\W]*/g),Q=c(/^data-[\-\w.\u00B7-\uFFFF]+$/),ee=c(/^aria-[\-\w]+$/),te=c(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),ne=c(/^(?:\w+script|data):/i),oe=c(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),re=c(/^html$/i),ie=c(/^[a-z][.\w]*(-[.\w]+)+$/i),ae=c(/<[/\w!]/g),le=c(/<[/\w]/g),ce=c(/<\/no(script|embed|frames)/i),se=c(/\/>/i),ue=1,fe=3,pe=7,me=8,de=9,he=11,ge=function(){return"undefined"==typeof window?null:window},ye=function(e,t,n,o){return R(e,t)&&b(e[t])?M(o.base?P(o.base):{},e[t],o.transform):n};var be=function e(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:ge();const o=t=>e(t);if(o.version="3.4.12",o.removed=[],!t||!t.document||t.document.nodeType!==de||!t.Element)return o.isSupported=!1,o;let r=t.document;const i=r,a=i.currentScript;t.DocumentFragment;const u=t.HTMLTemplateElement,f=t.Node,p=t.Element,k=t.NodeFilter,L=t.NamedNodeMap;void 0===L&&(t.NamedNodeMap||t.MozNamedAttrMap),t.HTMLFormElement;const z=t.DOMParser,be=t.trustedTypes,Te=p.prototype,Se=U(Te,"cloneNode"),Ee=U(Te,"remove"),Ae=U(Te,"nextSibling"),Ne=U(Te,"childNodes"),_e=U(Te,"parentNode"),we=U(Te,"shadowRoot"),Oe=U(Te,"attributes"),ve=f&&f.prototype?U(f.prototype,"nodeType"):null,De=f&&f.prototype?U(f.prototype,"nodeName"):null;if("function"==typeof u){const e=r.createElement("template");e.content&&e.content.ownerDocument&&(r=e.content.ownerDocument)}let Re,Ce,Ie="",xe=!1,ke=0;const Le=function(){if(ke>0)throw x('A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.')},Me=function(e){Le(),ke++;try{return Re.createHTML(e)}finally{ke--}},ze=function(){return xe||(Ce=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let n=null;const o="data-tt-policy-suffix";t&&t.hasAttribute(o)&&(n=t.getAttribute(o));const r="dompurify"+(n?"#"+n:"");try{return e.createPolicy(r,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}}(be,a),xe=!0),Ce},Pe=r,Ue=Pe.implementation,Fe=Pe.createNodeIterator,He=Pe.createDocumentFragment,je=Pe.getElementsByTagName,Be=i.importNode;let Ge={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]};o.isSupported="function"==typeof n&&"function"==typeof _e&&Ue&&void 0!==Ue.createHTMLDocument;const We=V,Ye=Z,qe=J,Xe=Q,$e=ee,Ke=ne,Ve=oe,Ze=ie;let Je=te,Qe=null;const et=M({},[...F,...H,...j,...G,...Y]);let tt=null;const nt=M({},[...q,...X,...$,...K]);let ot=Object.seal(s(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),rt=null,it=null;const at=Object.seal(s(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let lt=!0,ct=!0,st=!1,ut=!0,ft=!1,pt=!0,mt=!1,dt=!1,ht=null,gt=null,yt=!1,bt=!1,Tt=!1,St=!1,Et=!0,At=!1;const Nt="user-content-";let _t=!0,wt=!1,Ot={},vt=null;const Dt=M({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","selectedcontent","style","svg","template","thead","title","video","xmp"]);let Rt=null;const Ct=M({},["audio","video","img","source","image","track"]);let It=null;const xt=M({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),kt="http://www.w3.org/1998/Math/MathML",Lt="http://www.w3.org/2000/svg",Mt="http://www.w3.org/1999/xhtml";let zt=Mt,Pt=!1,Ut=null;const Ft=M({},[kt,Lt,Mt],S),Ht=l(["mi","mo","mn","ms","mtext"]);let jt=M({},Ht);const Bt=l(["annotation-xml"]);let Gt=M({},Bt);const Wt=M({},["title","style","font","a","script"]);let Yt=null;const qt=["application/xhtml+xml","text/html"];let Xt=null,$t=null;const Kt=r.createElement("form"),Vt=function(e){return e instanceof RegExp||e instanceof Function},Zt=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if($t&&$t===e)return;e&&"object"==typeof e||(e={}),e=P(e),Yt=-1===qt.indexOf(e.PARSER_MEDIA_TYPE)?"text/html":e.PARSER_MEDIA_TYPE,Xt="application/xhtml+xml"===Yt?S:T,Qe=ye(e,"ALLOWED_TAGS",et,{transform:Xt}),tt=ye(e,"ALLOWED_ATTR",nt,{transform:Xt}),Ut=ye(e,"ALLOWED_NAMESPACES",Ft,{transform:S}),It=ye(e,"ADD_URI_SAFE_ATTR",xt,{transform:Xt,base:xt}),Rt=ye(e,"ADD_DATA_URI_TAGS",Ct,{transform:Xt,base:Ct}),vt=ye(e,"FORBID_CONTENTS",Dt,{transform:Xt}),rt=ye(e,"FORBID_TAGS",P({}),{transform:Xt}),it=ye(e,"FORBID_ATTR",P({}),{transform:Xt}),Ot=!!R(e,"USE_PROFILES")&&(e.USE_PROFILES&&"object"==typeof e.USE_PROFILES?P(e.USE_PROFILES):e.USE_PROFILES),lt=!1!==e.ALLOW_ARIA_ATTR,ct=!1!==e.ALLOW_DATA_ATTR,st=e.ALLOW_UNKNOWN_PROTOCOLS||!1,ut=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,ft=e.SAFE_FOR_TEMPLATES||!1,pt=!1!==e.SAFE_FOR_XML,mt=e.WHOLE_DOCUMENT||!1,bt=e.RETURN_DOM||!1,Tt=e.RETURN_DOM_FRAGMENT||!1,St=e.RETURN_TRUSTED_TYPE||!1,yt=e.FORCE_BODY||!1,Et=!1!==e.SANITIZE_DOM,At=e.SANITIZE_NAMED_PROPS||!1,_t=!1!==e.KEEP_CONTENT,wt=e.IN_PLACE||!1,Je=function(e){try{return I(e,""),!0}catch(e){return!1}}(e.ALLOWED_URI_REGEXP)?e.ALLOWED_URI_REGEXP:te,zt="string"==typeof e.NAMESPACE?e.NAMESPACE:Mt,jt=R(e,"MATHML_TEXT_INTEGRATION_POINTS")&&e.MATHML_TEXT_INTEGRATION_POINTS&&"object"==typeof e.MATHML_TEXT_INTEGRATION_POINTS?P(e.MATHML_TEXT_INTEGRATION_POINTS):M({},Ht),Gt=R(e,"HTML_INTEGRATION_POINTS")&&e.HTML_INTEGRATION_POINTS&&"object"==typeof e.HTML_INTEGRATION_POINTS?P(e.HTML_INTEGRATION_POINTS):M({},Bt);const t=R(e,"CUSTOM_ELEMENT_HANDLING")&&e.CUSTOM_ELEMENT_HANDLING&&"object"==typeof e.CUSTOM_ELEMENT_HANDLING?P(e.CUSTOM_ELEMENT_HANDLING):s(null);if(ot=s(null),R(t,"tagNameCheck")&&Vt(t.tagNameCheck)&&(ot.tagNameCheck=t.tagNameCheck),R(t,"attributeNameCheck")&&Vt(t.attributeNameCheck)&&(ot.attributeNameCheck=t.attributeNameCheck),R(t,"allowCustomizedBuiltInElements")&&"boolean"==typeof t.allowCustomizedBuiltInElements&&(ot.allowCustomizedBuiltInElements=t.allowCustomizedBuiltInElements),c(ot),ft&&(ct=!1),Tt&&(bt=!0),Ot&&(Qe=M({},Y),tt=s(null),!0===Ot.html&&(M(Qe,F),M(tt,q)),!0===Ot.svg&&(M(Qe,H),M(tt,X),M(tt,K)),!0===Ot.svgFilters&&(M(Qe,j),M(tt,X),M(tt,K)),!0===Ot.mathMl&&(M(Qe,G),M(tt,$),M(tt,K))),at.tagCheck=null,at.attributeCheck=null,R(e,"ADD_TAGS")&&("function"==typeof e.ADD_TAGS?at.tagCheck=e.ADD_TAGS:b(e.ADD_TAGS)&&(Qe===et&&(Qe=P(Qe)),M(Qe,e.ADD_TAGS,Xt))),R(e,"ADD_ATTR")&&("function"==typeof e.ADD_ATTR?at.attributeCheck=e.ADD_ATTR:b(e.ADD_ATTR)&&(tt===nt&&(tt=P(tt)),M(tt,e.ADD_ATTR,Xt))),R(e,"ADD_URI_SAFE_ATTR")&&b(e.ADD_URI_SAFE_ATTR)&&M(It,e.ADD_URI_SAFE_ATTR,Xt),R(e,"FORBID_CONTENTS")&&b(e.FORBID_CONTENTS)&&(vt===Dt&&(vt=P(vt)),M(vt,e.FORBID_CONTENTS,Xt)),R(e,"ADD_FORBID_CONTENTS")&&b(e.ADD_FORBID_CONTENTS)&&(vt===Dt&&(vt=P(vt)),M(vt,e.ADD_FORBID_CONTENTS,Xt)),_t&&(Qe["#text"]=!0),mt&&M(Qe,["html","head","body"]),Qe.table&&(M(Qe,["tbody"]),delete rt.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw x('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw x('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');const t=Re;Re=e.TRUSTED_TYPES_POLICY;try{Ie=Me("")}catch(e){throw Re=t,e}}else null===e.TRUSTED_TYPES_POLICY?(Re=void 0,Ie=""):(void 0===Re&&(Re=ze()),Re&&"string"==typeof Ie&&(Ie=Me("")));l&&l(e),$t=e},Jt=M({},[...H,...j,...B]),Qt=M({},[...G,...W]),en=function(e){let t=_e(e);t&&t.tagName||(t={namespaceURI:zt,tagName:"template"});const n=T(e.tagName),o=T(t.tagName);return!!Ut[e.namespaceURI]&&(e.namespaceURI===Lt?function(e,t,n){return t.namespaceURI===Mt?"svg"===e:t.namespaceURI===kt?"svg"===e&&("annotation-xml"===n||jt[n]):Boolean(Jt[e])}(n,t,o):e.namespaceURI===kt?function(e,t,n){return t.namespaceURI===Mt?"math"===e:t.namespaceURI===Lt?"math"===e&&Gt[n]:Boolean(Qt[e])}(n,t,o):e.namespaceURI===Mt?function(e,t,n){return!(t.namespaceURI===Lt&&!Gt[n])&&!(t.namespaceURI===kt&&!jt[n])&&!Qt[e]&&(Wt[e]||!Jt[e])}(n,t,o):!("application/xhtml+xml"!==Yt||!Ut[e.namespaceURI]))},tn=function(e){g(o.removed,{element:e});try{_e(e).removeChild(e)}catch(t){if(Ee(e),!_e(e))throw x("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place")}},nn=function(e){an(e);const t=Ne(e);if(t){const e=[];m(t,t=>{g(e,t)}),m(e,e=>{try{Ee(e)}catch(e){}})}const n=Oe(e);if(n)for(let t=n.length-1;t>=0;--t){const o=n[t],r=o&&o.name;if("string"==typeof r)try{e.removeAttribute(r)}catch(e){}}},on=function(e,t){try{g(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){g(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e)if(bt||Tt)try{tn(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},rn=function(e){const t=Oe(e);if(t)for(let n=t.length-1;n>=0;--n){const o=t[n],r=o&&o.name;if("string"==typeof r&&!tt[Xt(r)])try{e.removeAttribute(r)}catch(e){}}},an=function(e){const t=[e];for(;t.length>0;){const e=t.pop();(ve?ve(e):e.nodeType)===ue&&rn(e);const n=Ne(e);if(n)for(let e=n.length-1;e>=0;--e)t.push(n[e])}},ln=function(e){let t=null,n=null;if(yt)e="<remove></remove>"+e;else{const t=E(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===Yt&&zt===Mt&&(e='<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>'+e+"</body></html>");const o=Re?Me(e):e;if(zt===Mt)try{t=(new z).parseFromString(o,Yt)}catch(e){}if(!t||!t.documentElement){t=Ue.createDocument(zt,"template",null);try{t.documentElement.innerHTML=Pt?Ie:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),zt===Mt?je.call(t,mt?"html":"body")[0]:mt?t.documentElement:i},cn=function(e){return Fe.call(e.ownerDocument||e,e,k.SHOW_ELEMENT|k.SHOW_COMMENT|k.SHOW_TEXT|k.SHOW_PROCESSING_INSTRUCTION|k.SHOW_CDATA_SECTION,null)},sn=function(e){return e=A(e,We," "),e=A(e,Ye," "),e=A(e,qe," ")},un=function(e){var t;e.normalize();const n=Fe.call(e.ownerDocument||e,e,k.SHOW_TEXT|k.SHOW_COMMENT|k.SHOW_CDATA_SECTION|k.SHOW_PROCESSING_INSTRUCTION,null);let o=n.nextNode();for(;o;)o.data=sn(o.data),o=n.nextNode();const r=null===(t=e.querySelectorAll)||void 0===t?void 0:t.call(e,"template");r&&m(r,e=>{pn(e.content)&&un(e.content)})},fn=function(e){const t=De?De(e):null;return"string"==typeof t&&("form"===Xt(t)&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||e.attributes!==Oe(e)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes||e.nodeType!==ve(e)||e.childNodes!==Ne(e)))},pn=function(e){if(!ve||"object"!=typeof e||null===e)return!1;try{return ve(e)===he}catch(e){return!1}},mn=function(e){if(!ve||"object"!=typeof e||null===e)return!1;try{return"number"==typeof ve(e)}catch(e){return!1}};function dn(e,t,n){0!==e.length&&m(e,e=>{e.call(o,t,n,$t)})}const hn=function(e,t){if(dn(Ge.beforeSanitizeElements,e,null),e!==t&&null===_e(e))return!0;if(fn(e))return tn(e),!0;const n=Xt(De?De(e):e.nodeName);if(dn(Ge.uponSanitizeElement,e,{tagName:n,allowedTags:Qe}),e!==t&&null===_e(e))return!0;if(function(e,t){return!!(pt&&e.hasChildNodes()&&!mn(e.firstElementChild)&&I(ae,e.textContent)&&I(ae,e.innerHTML))||!(!pt||e.namespaceURI!==Mt||"style"!==t||!mn(e.firstElementChild))||e.nodeType===pe||!(!pt||e.nodeType!==me||!I(le,e.data))}(e,n))return tn(e),!0;if(rt[n]||!(at.tagCheck instanceof Function&&at.tagCheck(n))&&!Qe[n]){const t=function(e,t){if(!rt[t]&&bn(t)){if(ot.tagNameCheck instanceof RegExp&&I(ot.tagNameCheck,t))return!1;if(ot.tagNameCheck instanceof Function&&ot.tagNameCheck(t))return!1}if(_t&&!vt[t]){const t=_e(e),n=Ne(e);if(n&&t)for(let o=n.length-1;o>=0;--o){const r=wt?n[o]:Se(n[o],!0);t.insertBefore(r,Ae(e))}}return tn(e),!0}(e,n);return!1===t&&dn(Ge.afterSanitizeElements,e,null),t}if((ve?ve(e):e.nodeType)===ue&&!en(e))return tn(e),!0;if(("noscript"===n||"noembed"===n||"noframes"===n)&&I(ce,e.innerHTML))return tn(e),!0;if(ft&&e.nodeType===fe){const t=sn(e.textContent);e.textContent!==t&&(g(o.removed,{element:e.cloneNode()}),e.textContent=t)}return dn(Ge.afterSanitizeElements,e,null),!1},gn=function(e,t,n){if(it[t])return!1;if(pt&&"patchsrc"===t)return!1;if(pt&&"for"===t&&"label"!==e&&"output"!==e)return!1;if(Et&&("id"===t||"name"===t)&&(n in r||n in Kt))return!1;const o=tt[t]||at.attributeCheck instanceof Function&&at.attributeCheck(t,e);if(ct&&I(Xe,t));else if(lt&&I($e,t));else if(o)if(It[t]);else if(I(Je,A(n,Ve,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==N(n,"data:")||!Rt[e]){if(st&&!I(Ke,A(n,Ve,"")));else if(n)return!1}else;else if(!(bn(e)&&(ot.tagNameCheck instanceof RegExp&&I(ot.tagNameCheck,e)||ot.tagNameCheck instanceof Function&&ot.tagNameCheck(e))&&(ot.attributeNameCheck instanceof RegExp&&I(ot.attributeNameCheck,t)||ot.attributeNameCheck instanceof Function&&ot.attributeNameCheck(t,e))||"is"===t&&ot.allowCustomizedBuiltInElements&&(ot.tagNameCheck instanceof RegExp&&I(ot.tagNameCheck,n)||ot.tagNameCheck instanceof Function&&ot.tagNameCheck(n))))return!1;return!0},yn=M({},["annotation-xml","color-profile","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","missing-glyph"]),bn=function(e){return!yn[T(e)]&&I(Ze,e)},Tn=function(e,t,n,o){if(Re&&"object"==typeof be&&"function"==typeof be.getAttributeType&&!n)switch(be.getAttributeType(e,t)){case"TrustedHTML":return Me(o);case"TrustedScriptURL":return function(e){Le(),ke++;try{return Re.createScriptURL(e)}finally{ke--}}(o)}return o},Sn=function(e,t,n,r){try{n?e.setAttributeNS(n,t,r):e.setAttribute(t,r),fn(e)?tn(e):h(o.removed)}catch(n){on(t,e)}},En=function(e){dn(Ge.beforeSanitizeAttributes,e,null);const t=e.attributes;if(!t||fn(e))return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:tt,forceKeepAttr:void 0};let o=t.length;const r=Xt(e.nodeName);for(;o--;){const i=t[o],a=i.name,l=i.namespaceURI,c=i.value,s=Xt(a),u=c;let f="value"===a?u:_(u);n.attrName=s,n.attrValue=f,n.keepAttr=!0,n.forceKeepAttr=void 0,dn(Ge.uponSanitizeAttribute,e,n),f=n.attrValue,!At||"id"!==s&&"name"!==s||0===N(f,Nt)||(on(a,e),f=Nt+f),pt&&I(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,f)?on(a,e):"attributename"===s&&E(f,"href")?on(a,e):n.forceKeepAttr||(n.keepAttr&&(ut||!I(se,f))?(ft&&(f=sn(f)),gn(r,s,f)?(f=Tn(r,s,l,f),f!==u&&Sn(e,a,l,f)):on(a,e)):on(a,e))}dn(Ge.afterSanitizeAttributes,e,null)},An=function(e){let t=null;const n=cn(e);for(dn(Ge.beforeSanitizeShadowDOM,e,null);t=n.nextNode();){dn(Ge.uponSanitizeShadowNode,t,null),hn(t,e),En(t),pn(t.content)&&An(t.content);if((ve?ve(t):t.nodeType)===ue){const e=we(t);pn(e)&&(Nn(e),An(e))}}dn(Ge.afterSanitizeShadowDOM,e,null)},Nn=function(e){const t=[{node:e,shadow:null}];for(;t.length>0;){const e=t.pop();if(e.shadow){An(e.shadow);continue}const n=e.node,o=(ve?ve(n):n.nodeType)===ue,r=Ne(n);if(r)for(let e=r.length-1;e>=0;--e)t.push({node:r[e],shadow:null});if(o){const e=De?De(n):null;if("string"==typeof e&&"template"===Xt(e)){const e=n.content;pn(e)&&t.push({node:e,shadow:null})}}if(o){const e=we(n);pn(e)&&t.push({node:null,shadow:e},{node:e,shadow:null})}}};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,a=null,l=null;if(Pt=!e,Pt&&(e="\x3c!--\x3e"),"string"!=typeof e&&!mn(e)&&"string"!=typeof(e=function(e){switch(typeof e){case"string":return e;case"number":return w(e);case"boolean":return O(e);case"bigint":return v?v(e):"0";case"symbol":return D?D(e):"Symbol()";case"undefined":default:return C(e);case"function":case"object":{if(null===e)return C(e);const t=e,n=U(t,"toString");if("function"==typeof n){const e=n(t);return"string"==typeof e?e:C(e)}return C(e)}}}(e)))throw x("dirty is not a string, aborting");if(!o.isSupported)return e;dt?(Qe=ht,tt=gt):Zt(t),(Ge.uponSanitizeElement.length>0||Ge.uponSanitizeAttribute.length>0)&&(Qe=P(Qe)),Ge.uponSanitizeAttribute.length>0&&(tt=P(tt)),o.removed=[];const c=wt&&"string"!=typeof e&&mn(e);if(c){!function(e){if(!pt)return;const t=[e];for(;t.length>0;){const e=t.pop(),n=ve?ve(e):e.nodeType;if(n===pe||n===me&&I(le,e.data)){try{Ee(e)}catch(e){}continue}if(n===ue){const t=e,n=Xt(De?De(e):e.nodeName);try{t.hasAttribute&&t.hasAttribute("patchsrc")&&t.removeAttribute("patchsrc"),t.hasAttribute&&t.hasAttribute("for")&&"label"!==n&&"output"!==n&&t.removeAttribute("for")}catch(e){}}const o=Ne(e);if(o)for(let e=o.length-1;e>=0;--e)t.push(o[e])}}(e);const t=De?De(e):e.nodeName;if("string"==typeof t){const n=Xt(t);if(!Qe[n]||rt[n])throw nn(e),x("root node is forbidden and cannot be sanitized in-place")}if(fn(e))throw nn(e),x("root node is clobbered and cannot be sanitized in-place");try{Nn(e)}catch(t){throw nn(e),t}}else if(mn(e))n=ln("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),r.nodeType===ue&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r),Nn(r);else{if(!bt&&!ft&&!mt&&-1===e.indexOf("<"))return Re&&St?Me(e):e;if(n=ln(e),!n)return bt?null:St?Ie:""}n&&yt&&tn(n.firstChild);const s=c?e:n,u=cn(s);try{for(;a=u.nextNode();)hn(a,s),En(a),pn(a.content)&&An(a.content)}catch(t){throw c&&(nn(e),m(o.removed,e=>{e.element&&an(e.element)})),t}if(c)return m(o.removed,e=>{e.element&&an(e.element)}),ft&&un(e),e;if(bt){if(ft&&un(n),Tt)for(l=He.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(tt.shadowroot||tt.shadowrootmode)&&(l=Be.call(i,l,!0)),l}let f=mt?n.outerHTML:n.innerHTML;return mt&&Qe["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&I(re,n.ownerDocument.doctype.name)&&(f="<!DOCTYPE "+n.ownerDocument.doctype.name+">\n"+f),ft&&(f=sn(f)),Re&&St?Me(f):f},o.setConfig=function(){Zt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),dt=!0,ht=Qe,gt=tt},o.clearConfig=function(){$t=null,dt=!1,ht=null,gt=null,Re=Ce,Ie=""},o.isValidAttribute=function(e,t,n){$t||Zt({});const o=Xt(e),r=Xt(t);return gn(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&R(Ge,e)&&g(Ge[e],t)},o.removeHook=function(e,t){if(R(Ge,e)){if(void 0!==t){const n=d(Ge[e],t);return-1===n?void 0:y(Ge[e],n,1)[0]}return h(Ge[e])}},o.removeHooks=function(e){R(Ge,e)&&(Ge[e]=[])},o.removeAllHooks=function(){Ge={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},o}();return be});
|
|
3
|
+
//# sourceMappingURL=purify.min.js.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Incremental refresh: sync docs.db to the current state of the markdown folder
|
|
2
|
+
// after a ServiceNow docs update (e.g. `git pull` in the docs repo).
|
|
3
|
+
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import matter from 'gray-matter';
|
|
8
|
+
import { openDocsDb, closeDocsDb, getDocsDbPath } from './db.js';
|
|
9
|
+
import { encodeText, phasesToBytes, docSurface, DEFAULT_DIM } from './hrr.js';
|
|
10
|
+
|
|
11
|
+
export function refreshDocs(opts = {}) {
|
|
12
|
+
const docsDir = opts.docsDir;
|
|
13
|
+
const dbPath = opts.dbPath || getDocsDbPath();
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(dbPath)) {
|
|
16
|
+
throw new Error(`Database not found: ${dbPath}. Run "jsn docs sync" first for the initial build.`);
|
|
17
|
+
}
|
|
18
|
+
if (!fs.existsSync(docsDir)) {
|
|
19
|
+
throw new Error(`Docs directory not found: ${docsDir}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const db = openDocsDb({ dbPath });
|
|
23
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
24
|
+
|
|
25
|
+
const cols = db.prepare('PRAGMA table_info(docs)').all().map((c) => c.name);
|
|
26
|
+
if (!cols.includes('hash')) db.exec('ALTER TABLE docs ADD COLUMN hash TEXT');
|
|
27
|
+
if (!cols.includes('hrr_vector')) db.exec('ALTER TABLE docs ADD COLUMN hrr_vector BLOB');
|
|
28
|
+
|
|
29
|
+
const embeddingsEnabled =
|
|
30
|
+
db.prepare('SELECT COUNT(*) AS n FROM docs WHERE hrr_vector IS NOT NULL').get().n > 0 ||
|
|
31
|
+
!cols.includes('hrr_vector');
|
|
32
|
+
|
|
33
|
+
const isBackfill = db.prepare('SELECT COUNT(*) AS n FROM docs WHERE hash IS NOT NULL').get().n === 0
|
|
34
|
+
&& db.prepare('SELECT COUNT(*) AS n FROM docs').get().n > 0;
|
|
35
|
+
const setHash = db.prepare('UPDATE docs SET hash = ? WHERE id = ?');
|
|
36
|
+
|
|
37
|
+
function* walk(dir) {
|
|
38
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
39
|
+
const full = path.join(dir, entry.name);
|
|
40
|
+
if (entry.isDirectory()) yield* walk(full);
|
|
41
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) yield full;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const existing = new Map();
|
|
46
|
+
for (const row of db.prepare('SELECT id, path, hash FROM docs').all()) {
|
|
47
|
+
existing.set(row.path, { id: row.id, hash: row.hash });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const upsert = db.prepare(`
|
|
51
|
+
INSERT INTO docs (path, title, release, bundle, doc_type, locale, frontmatter, body, size, mtime, hash, hrr_vector)
|
|
52
|
+
VALUES (@path, @title, @release, @bundle, @doc_type, @locale, @frontmatter, @body, @size, @mtime, @hash, @hrr_vector)
|
|
53
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
54
|
+
title=@title, release=@release, bundle=@bundle, doc_type=@doc_type, locale=@locale,
|
|
55
|
+
frontmatter=@frontmatter, body=@body, size=@size, mtime=@mtime, hash=@hash, hrr_vector=@hrr_vector
|
|
56
|
+
`);
|
|
57
|
+
const del = db.prepare('DELETE FROM docs WHERE id = ?');
|
|
58
|
+
|
|
59
|
+
let added = 0, updated = 0, removed = 0, unchanged = 0, errors = 0;
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
const started = Date.now();
|
|
62
|
+
|
|
63
|
+
const files = [...walk(docsDir)];
|
|
64
|
+
|
|
65
|
+
db.exec('BEGIN');
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
try {
|
|
68
|
+
const rel = path.relative(docsDir, file).split(path.sep).join('/');
|
|
69
|
+
seen.add(rel);
|
|
70
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
71
|
+
const hash = createHash('sha256').update(raw).digest('hex');
|
|
72
|
+
|
|
73
|
+
const prev = existing.get(rel);
|
|
74
|
+
if (prev && prev.hash === hash) {
|
|
75
|
+
unchanged++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (isBackfill && prev) {
|
|
80
|
+
setHash.run(hash, prev.id);
|
|
81
|
+
unchanged++;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const stat = fs.statSync(file);
|
|
86
|
+
let data = {}, body = raw;
|
|
87
|
+
try {
|
|
88
|
+
const parsed = matter(raw);
|
|
89
|
+
data = parsed.data || {};
|
|
90
|
+
body = parsed.content || '';
|
|
91
|
+
} catch {
|
|
92
|
+
// keep raw body on malformed frontmatter
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const hrr = embeddingsEnabled
|
|
96
|
+
? phasesToBytes(encodeText(docSurface(data.title ?? null, body), DEFAULT_DIM))
|
|
97
|
+
: null;
|
|
98
|
+
|
|
99
|
+
upsert.run({
|
|
100
|
+
path: rel,
|
|
101
|
+
title: data.title ?? null,
|
|
102
|
+
release: data.release ?? null,
|
|
103
|
+
bundle: data.bundle ?? null,
|
|
104
|
+
doc_type: data.doc_type ?? null,
|
|
105
|
+
locale: data.locale ?? null,
|
|
106
|
+
frontmatter: JSON.stringify(data),
|
|
107
|
+
body,
|
|
108
|
+
size: stat.size,
|
|
109
|
+
mtime: Math.floor(stat.mtimeMs),
|
|
110
|
+
hash,
|
|
111
|
+
hrr_vector: hrr,
|
|
112
|
+
});
|
|
113
|
+
if (prev) updated++; else added++;
|
|
114
|
+
} catch (err) {
|
|
115
|
+
errors++;
|
|
116
|
+
if (errors <= 10) process.stderr.write(`Skipped ${file}: ${err.message}\n`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const [rel, row] of existing) {
|
|
121
|
+
if (!seen.has(rel)) {
|
|
122
|
+
del.run(row.id);
|
|
123
|
+
removed++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
db.exec('COMMIT');
|
|
127
|
+
|
|
128
|
+
db.exec("INSERT INTO docs_fts(docs_fts) VALUES('optimize');");
|
|
129
|
+
db.exec('PRAGMA wal_checkpoint(TRUNCATE)');
|
|
130
|
+
db.prepare("INSERT OR REPLACE INTO meta(key, value) VALUES('synced_at', ?)").run(String(Date.now()));
|
|
131
|
+
closeDocsDb(db);
|
|
132
|
+
|
|
133
|
+
const secs = ((Date.now() - started) / 1000).toFixed(1);
|
|
134
|
+
return { added, updated, removed, unchanged, errors, secs, dbPath, embeddingsEnabled };
|
|
135
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// CLI search against the docs SQLite database.
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
5
|
+
import { getDocsDbPath, hasEmbeddings } from './db.js';
|
|
6
|
+
import { encodeText, similarity, bytesToPhases, DEFAULT_DIM } from './hrr.js';
|
|
7
|
+
|
|
8
|
+
function safeParse(s) {
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(s);
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function searchDocs(opts = {}) {
|
|
17
|
+
const dbPath = opts.dbPath || getDocsDbPath();
|
|
18
|
+
const q = (opts.query || '').toString().trim();
|
|
19
|
+
if (!q) {
|
|
20
|
+
throw new Error('Query is required.');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!fs.existsSync(dbPath)) {
|
|
24
|
+
throw new Error(`Database not found: ${dbPath}. Run "jsn docs sync" first.`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
28
|
+
const limit = Math.min(parseInt(opts.limit, 10) || 20, 100);
|
|
29
|
+
const offset = parseInt(opts.offset, 10) || 0;
|
|
30
|
+
const bundle = opts.bundle ? String(opts.bundle) : null;
|
|
31
|
+
const docType = opts.docType ? String(opts.docType) : null;
|
|
32
|
+
const mode = opts.mode === 'keyword' ? 'keyword' : 'hybrid';
|
|
33
|
+
const useHybrid = mode === 'hybrid' && hasEmbeddings(db);
|
|
34
|
+
|
|
35
|
+
const filters = [];
|
|
36
|
+
const params = { q, limit, offset };
|
|
37
|
+
if (bundle) {
|
|
38
|
+
filters.push('d.bundle = @bundle');
|
|
39
|
+
params.bundle = bundle;
|
|
40
|
+
}
|
|
41
|
+
if (docType) {
|
|
42
|
+
filters.push('d.doc_type = @docType');
|
|
43
|
+
params.docType = docType;
|
|
44
|
+
}
|
|
45
|
+
const where = filters.length ? ' AND ' + filters.join(' AND ') : '';
|
|
46
|
+
|
|
47
|
+
let rows;
|
|
48
|
+
if (!useHybrid) {
|
|
49
|
+
rows = db
|
|
50
|
+
.prepare(
|
|
51
|
+
`SELECT d.id, d.path, d.title, d.bundle, d.doc_type, d.release,
|
|
52
|
+
json_extract(d.frontmatter, '$.canonical_url') AS canonical_url,
|
|
53
|
+
snippet(docs_fts, 1, '[', ']', ' … ', 12) AS snippet,
|
|
54
|
+
bm25(docs_fts) AS score
|
|
55
|
+
FROM docs_fts
|
|
56
|
+
JOIN docs d ON d.id = docs_fts.rowid
|
|
57
|
+
WHERE docs_fts MATCH @q${where}
|
|
58
|
+
ORDER BY score
|
|
59
|
+
LIMIT @limit OFFSET @offset`
|
|
60
|
+
)
|
|
61
|
+
.all(params);
|
|
62
|
+
} else {
|
|
63
|
+
const candParams = { q, cand: (limit + offset) * 4 + 20 };
|
|
64
|
+
if (bundle) candParams.bundle = bundle;
|
|
65
|
+
if (docType) candParams.docType = docType;
|
|
66
|
+
const candidates = db
|
|
67
|
+
.prepare(
|
|
68
|
+
`SELECT d.id, d.path, d.title, d.bundle, d.doc_type, d.release, d.hrr_vector,
|
|
69
|
+
json_extract(d.frontmatter, '$.canonical_url') AS canonical_url,
|
|
70
|
+
snippet(docs_fts, 1, '[', ']', ' … ', 12) AS snippet,
|
|
71
|
+
bm25(docs_fts) AS bm25
|
|
72
|
+
FROM docs_fts
|
|
73
|
+
JOIN docs d ON d.id = docs_fts.rowid
|
|
74
|
+
WHERE docs_fts MATCH @q${where}
|
|
75
|
+
ORDER BY bm25
|
|
76
|
+
LIMIT @cand`
|
|
77
|
+
)
|
|
78
|
+
.all(candParams);
|
|
79
|
+
|
|
80
|
+
if (candidates.length === 0) {
|
|
81
|
+
rows = [];
|
|
82
|
+
} else {
|
|
83
|
+
const hrrDim = (() => {
|
|
84
|
+
try {
|
|
85
|
+
const row = db.prepare("SELECT value FROM meta WHERE key='hrr_dim'").get();
|
|
86
|
+
return row ? parseInt(row.value, 10) : DEFAULT_DIM;
|
|
87
|
+
} catch {
|
|
88
|
+
return DEFAULT_DIM;
|
|
89
|
+
}
|
|
90
|
+
})();
|
|
91
|
+
const queryVec = encodeText(q, hrrDim);
|
|
92
|
+
const queryTokens = new Set(q.toLowerCase().split(/\s+/).filter(Boolean));
|
|
93
|
+
|
|
94
|
+
const bmVals = candidates.map((c) => c.bm25);
|
|
95
|
+
const bmMin = Math.min(...bmVals);
|
|
96
|
+
const bmMax = Math.max(...bmVals);
|
|
97
|
+
const bmRange = bmMax - bmMin || 1;
|
|
98
|
+
|
|
99
|
+
const FTS_W = 0.4, JACCARD_W = 0.3, HRR_W = 0.3;
|
|
100
|
+
|
|
101
|
+
for (const c of candidates) {
|
|
102
|
+
const ftsScore = 1 - (c.bm25 - bmMin) / bmRange;
|
|
103
|
+
const contentTokens = new Set(
|
|
104
|
+
`${c.title || ''} ${c.path || ''}`.toLowerCase().split(/\s+/).filter(Boolean)
|
|
105
|
+
);
|
|
106
|
+
let inter = 0;
|
|
107
|
+
for (const t of queryTokens) if (contentTokens.has(t)) inter++;
|
|
108
|
+
const union = queryTokens.size + contentTokens.size - inter;
|
|
109
|
+
const jaccard = union ? inter / union : 0;
|
|
110
|
+
|
|
111
|
+
let hrrSim = 0.5;
|
|
112
|
+
if (c.hrr_vector) {
|
|
113
|
+
hrrSim = (similarity(queryVec, bytesToPhases(c.hrr_vector)) + 1) / 2;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
c.score = FTS_W * ftsScore + JACCARD_W * jaccard + HRR_W * hrrSim;
|
|
117
|
+
delete c.hrr_vector;
|
|
118
|
+
delete c.bm25;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
122
|
+
rows = candidates.slice(offset, offset + limit);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
db.close();
|
|
127
|
+
|
|
128
|
+
// Post-process canonical_url JSON strings that may contain escaped content.
|
|
129
|
+
for (const row of rows) {
|
|
130
|
+
if (row.canonical_url) row.canonical_url = safeParse(row.canonical_url) || row.canonical_url;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
query: q,
|
|
135
|
+
mode: useHybrid ? 'hybrid' : 'keyword',
|
|
136
|
+
count: rows.length,
|
|
137
|
+
results: rows,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// Built-in HTTP server for docs search (node:http only, no Express).
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import http from 'node:http';
|
|
6
|
+
import { URL } from 'node:url';
|
|
7
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
8
|
+
import { getDocsDbPath, hasEmbeddings } from './db.js';
|
|
9
|
+
import { encodeText, similarity, bytesToPhases, DEFAULT_DIM } from './hrr.js';
|
|
10
|
+
|
|
11
|
+
const PUBLIC_DIR = path.join(import.meta.dirname, 'public');
|
|
12
|
+
|
|
13
|
+
function safeParse(s) {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(s);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function sendJSON(res, status, obj) {
|
|
22
|
+
const body = JSON.stringify(obj, null, 2);
|
|
23
|
+
res.writeHead(status, {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'Content-Length': Buffer.byteLength(body),
|
|
26
|
+
});
|
|
27
|
+
res.end(body);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function sendText(res, status, text, type = 'text/plain') {
|
|
31
|
+
res.writeHead(status, {
|
|
32
|
+
'Content-Type': type,
|
|
33
|
+
'Content-Length': Buffer.byteLength(text),
|
|
34
|
+
});
|
|
35
|
+
res.end(text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function serveStatic(req, res, filePath) {
|
|
39
|
+
if (!fs.existsSync(filePath)) {
|
|
40
|
+
sendText(res, 404, 'Not found');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const stat = fs.statSync(filePath);
|
|
44
|
+
if (stat.isDirectory()) {
|
|
45
|
+
sendText(res, 403, 'Forbidden');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
49
|
+
const mime = {
|
|
50
|
+
'.html': 'text/html',
|
|
51
|
+
'.js': 'application/javascript',
|
|
52
|
+
'.css': 'text/css',
|
|
53
|
+
'.json': 'application/json',
|
|
54
|
+
'.png': 'image/png',
|
|
55
|
+
'.jpg': 'image/jpeg',
|
|
56
|
+
'.svg': 'image/svg+xml',
|
|
57
|
+
}[ext] || 'application/octet-stream';
|
|
58
|
+
|
|
59
|
+
res.writeHead(200, {
|
|
60
|
+
'Content-Type': mime,
|
|
61
|
+
'Content-Length': stat.size,
|
|
62
|
+
});
|
|
63
|
+
fs.createReadStream(filePath).pipe(res);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function serveDocs(opts = {}) {
|
|
67
|
+
const dbPath = opts.dbPath || getDocsDbPath();
|
|
68
|
+
const port = parseInt(opts.port, 10) || 3000;
|
|
69
|
+
const host = opts.host || '127.0.0.1';
|
|
70
|
+
|
|
71
|
+
if (!fs.existsSync(dbPath)) {
|
|
72
|
+
throw new Error(`Database not found: ${dbPath}. Run "jsn docs sync" first.`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
76
|
+
const hasEmb = hasEmbeddings(db);
|
|
77
|
+
const hrrDim = (() => {
|
|
78
|
+
try {
|
|
79
|
+
const row = db.prepare("SELECT value FROM meta WHERE key='hrr_dim'").get();
|
|
80
|
+
return row ? parseInt(row.value, 10) : DEFAULT_DIM;
|
|
81
|
+
} catch {
|
|
82
|
+
return DEFAULT_DIM;
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
|
|
86
|
+
const server = http.createServer((req, res) => {
|
|
87
|
+
try {
|
|
88
|
+
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
89
|
+
const pathname = decodeURIComponent(url.pathname);
|
|
90
|
+
|
|
91
|
+
if (pathname === '/health') {
|
|
92
|
+
return sendJSON(res, 200, { ok: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (pathname === '/stats') {
|
|
96
|
+
const total = db.prepare('SELECT COUNT(*) AS n FROM docs').get().n;
|
|
97
|
+
return sendJSON(res, 200, { documents: total, db: path.resolve(dbPath), embeddings: hasEmb, hrr_dim: hrrDim });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (pathname === '/bundles') {
|
|
101
|
+
const rows = db
|
|
102
|
+
.prepare('SELECT bundle, COUNT(*) AS count FROM docs WHERE bundle IS NOT NULL GROUP BY bundle ORDER BY count DESC')
|
|
103
|
+
.all();
|
|
104
|
+
return sendJSON(res, 200, rows);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (pathname === '/search') {
|
|
108
|
+
const q = (url.searchParams.get('q') || '').trim();
|
|
109
|
+
if (!q) return sendJSON(res, 400, { error: "Missing query parameter 'q'." });
|
|
110
|
+
|
|
111
|
+
const limit = Math.min(parseInt(url.searchParams.get('limit'), 10) || 20, 100);
|
|
112
|
+
const offset = parseInt(url.searchParams.get('offset'), 10) || 0;
|
|
113
|
+
const bundle = url.searchParams.get('bundle') || null;
|
|
114
|
+
const docType = url.searchParams.get('doc_type') || null;
|
|
115
|
+
let mode = (url.searchParams.get('mode') || (hasEmb ? 'hybrid' : 'keyword')).toString();
|
|
116
|
+
if (mode === 'hybrid' && !hasEmb) mode = 'keyword';
|
|
117
|
+
|
|
118
|
+
const filters = [];
|
|
119
|
+
const params = { q, limit, offset };
|
|
120
|
+
if (bundle) {
|
|
121
|
+
filters.push('d.bundle = @bundle');
|
|
122
|
+
params.bundle = bundle;
|
|
123
|
+
}
|
|
124
|
+
if (docType) {
|
|
125
|
+
filters.push('d.doc_type = @docType');
|
|
126
|
+
params.docType = docType;
|
|
127
|
+
}
|
|
128
|
+
const where = filters.length ? ' AND ' + filters.join(' AND ') : '';
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
if (mode === 'keyword') {
|
|
132
|
+
const rows = db
|
|
133
|
+
.prepare(
|
|
134
|
+
`SELECT d.id, d.path, d.title, d.bundle, d.doc_type, d.release,
|
|
135
|
+
json_extract(d.frontmatter, '$.canonical_url') AS canonical_url,
|
|
136
|
+
snippet(docs_fts, 1, '[', ']', ' … ', 12) AS snippet,
|
|
137
|
+
bm25(docs_fts) AS score
|
|
138
|
+
FROM docs_fts
|
|
139
|
+
JOIN docs d ON d.id = docs_fts.rowid
|
|
140
|
+
WHERE docs_fts MATCH @q${where}
|
|
141
|
+
ORDER BY score
|
|
142
|
+
LIMIT @limit OFFSET @offset`
|
|
143
|
+
)
|
|
144
|
+
.all(params);
|
|
145
|
+
return sendJSON(res, 200, { query: q, mode, count: rows.length, results: rows });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const candParams = { q, cand: (limit + offset) * 4 + 20 };
|
|
149
|
+
if (bundle) candParams.bundle = bundle;
|
|
150
|
+
if (docType) candParams.docType = docType;
|
|
151
|
+
const candidates = db
|
|
152
|
+
.prepare(
|
|
153
|
+
`SELECT d.id, d.path, d.title, d.bundle, d.doc_type, d.release, d.hrr_vector,
|
|
154
|
+
json_extract(d.frontmatter, '$.canonical_url') AS canonical_url,
|
|
155
|
+
snippet(docs_fts, 1, '[', ']', ' … ', 12) AS snippet,
|
|
156
|
+
bm25(docs_fts) AS bm25
|
|
157
|
+
FROM docs_fts
|
|
158
|
+
JOIN docs d ON d.id = docs_fts.rowid
|
|
159
|
+
WHERE docs_fts MATCH @q${where}
|
|
160
|
+
ORDER BY bm25
|
|
161
|
+
LIMIT @cand`
|
|
162
|
+
)
|
|
163
|
+
.all(candParams);
|
|
164
|
+
|
|
165
|
+
if (candidates.length === 0) {
|
|
166
|
+
return sendJSON(res, 200, { query: q, mode, count: 0, results: [] });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const queryVec = encodeText(q, hrrDim);
|
|
170
|
+
const queryTokens = new Set(q.toLowerCase().split(/\s+/).filter(Boolean));
|
|
171
|
+
|
|
172
|
+
const bmVals = candidates.map((c) => c.bm25);
|
|
173
|
+
const bmMin = Math.min(...bmVals);
|
|
174
|
+
const bmMax = Math.max(...bmVals);
|
|
175
|
+
const bmRange = bmMax - bmMin || 1;
|
|
176
|
+
|
|
177
|
+
const FTS_W = 0.4, JACCARD_W = 0.3, HRR_W = 0.3;
|
|
178
|
+
|
|
179
|
+
for (const c of candidates) {
|
|
180
|
+
const ftsScore = 1 - (c.bm25 - bmMin) / bmRange;
|
|
181
|
+
const contentTokens = new Set(
|
|
182
|
+
`${c.title || ''} ${c.path || ''}`.toLowerCase().split(/\s+/).filter(Boolean)
|
|
183
|
+
);
|
|
184
|
+
let inter = 0;
|
|
185
|
+
for (const t of queryTokens) if (contentTokens.has(t)) inter++;
|
|
186
|
+
const union = queryTokens.size + contentTokens.size - inter;
|
|
187
|
+
const jaccard = union ? inter / union : 0;
|
|
188
|
+
|
|
189
|
+
let hrrSim = 0.5;
|
|
190
|
+
if (c.hrr_vector) {
|
|
191
|
+
hrrSim = (similarity(queryVec, bytesToPhases(c.hrr_vector)) + 1) / 2;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
c.score = FTS_W * ftsScore + JACCARD_W * jaccard + HRR_W * hrrSim;
|
|
195
|
+
delete c.hrr_vector;
|
|
196
|
+
delete c.bm25;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
200
|
+
const results = candidates.slice(offset, offset + limit);
|
|
201
|
+
return sendJSON(res, 200, { query: q, mode, count: results.length, results });
|
|
202
|
+
} catch (err) {
|
|
203
|
+
return sendJSON(res, 400, { error: `Invalid FTS query: ${err.message}` });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (pathname.startsWith('/doc/')) {
|
|
208
|
+
const id = pathname.slice(5);
|
|
209
|
+
if (!id) return sendJSON(res, 400, { error: "Missing 'id'." });
|
|
210
|
+
const row = db.prepare('SELECT * FROM docs WHERE id = ?').get(id);
|
|
211
|
+
if (!row) return sendJSON(res, 404, { error: 'Not found' });
|
|
212
|
+
row.frontmatter = safeParse(row.frontmatter);
|
|
213
|
+
return sendJSON(res, 200, row);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (pathname === '/doc') {
|
|
217
|
+
const p = url.searchParams.get('path') || '';
|
|
218
|
+
if (!p) return sendJSON(res, 400, { error: "Missing 'path'." });
|
|
219
|
+
const row = db.prepare('SELECT * FROM docs WHERE path = ?').get(p);
|
|
220
|
+
if (!row) return sendJSON(res, 404, { error: 'Not found' });
|
|
221
|
+
row.frontmatter = safeParse(row.frontmatter);
|
|
222
|
+
return sendJSON(res, 200, row);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Static files.
|
|
226
|
+
let filePath = path.join(PUBLIC_DIR, pathname === '/' ? 'index.html' : pathname);
|
|
227
|
+
// Security: prevent escaping public dir.
|
|
228
|
+
filePath = path.resolve(filePath);
|
|
229
|
+
const publicResolved = path.resolve(PUBLIC_DIR);
|
|
230
|
+
if (!filePath.startsWith(publicResolved + path.sep) && filePath !== publicResolved) {
|
|
231
|
+
return sendText(res, 403, 'Forbidden');
|
|
232
|
+
}
|
|
233
|
+
return serveStatic(req, res, filePath);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
sendJSON(res, 500, { error: err.message });
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return new Promise((resolve, reject) => {
|
|
240
|
+
let currentPort = parseInt(port, 10);
|
|
241
|
+
const maxAttempts = 10;
|
|
242
|
+
let attempts = 0;
|
|
243
|
+
let resolved = false;
|
|
244
|
+
|
|
245
|
+
function onListening() {
|
|
246
|
+
if (resolved) return;
|
|
247
|
+
resolved = true;
|
|
248
|
+
server.removeListener('error', onError);
|
|
249
|
+
process.stderr.write(`ServiceNow docs search listening on http://${host}:${currentPort}\n`);
|
|
250
|
+
resolve({ server, port: currentPort, host, dbPath });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function onError(err) {
|
|
254
|
+
if (resolved) return;
|
|
255
|
+
server.removeListener('listening', onListening);
|
|
256
|
+
if (err.code === 'EADDRINUSE' && attempts < maxAttempts) {
|
|
257
|
+
process.stderr.write(`Port ${currentPort} in use, trying ${currentPort + 1}...\n`);
|
|
258
|
+
currentPort++;
|
|
259
|
+
tryListen();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (err.code === 'EADDRINUSE') {
|
|
263
|
+
err.message = `Could not find an open port between ${port} and ${currentPort}. Try specifying one with --port.`;
|
|
264
|
+
}
|
|
265
|
+
resolved = true;
|
|
266
|
+
reject(err);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function tryListen() {
|
|
270
|
+
attempts++;
|
|
271
|
+
server.once('error', onError);
|
|
272
|
+
server.once('listening', onListening);
|
|
273
|
+
server.listen(currentPort, host);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
tryListen();
|
|
277
|
+
});
|
|
278
|
+
}
|