botdocs 0.3.0 ā 0.5.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/README.md +20 -2
- package/dist/src/builder/chunker.d.ts +18 -0
- package/dist/src/builder/chunker.js +62 -1
- package/dist/src/builder/embedder.js +5 -2
- package/dist/src/builder/index-size.d.ts +3 -0
- package/dist/src/builder/index-size.js +16 -0
- package/dist/src/builder/index.js +34 -3
- package/dist/src/builder/markdown-processor.d.ts +8 -1
- package/dist/src/builder/markdown-processor.js +40 -7
- package/dist/src/builder/paths.d.ts +6 -0
- package/dist/src/builder/paths.js +13 -0
- package/dist/src/builder/site-generator.d.ts +19 -2
- package/dist/src/builder/site-generator.js +117 -34
- package/dist/src/builder/template-engine.d.ts +4 -4
- package/dist/src/builder/template-engine.js +5 -1
- package/dist/src/builder/vector-db-builder.d.ts +1 -0
- package/dist/src/builder/vector-db-builder.js +1 -0
- package/dist/src/cli/index.js +61 -18
- package/dist/src/cli/options.d.ts +2 -0
- package/dist/src/cli/options.js +2 -1
- package/dist/src/cli/server.d.ts +6 -0
- package/dist/src/cli/server.js +79 -0
- package/dist/src/cli/watcher.d.ts +7 -0
- package/dist/src/cli/watcher.js +40 -0
- package/dist/src/shared/site-root.d.ts +2 -0
- package/dist/src/shared/site-root.js +9 -0
- package/dist/src/types/config.d.ts +4 -0
- package/dist/src/types/config.js +2 -0
- package/dist/src/types/document.d.ts +1 -1
- package/dist-client/assets/chatbox-Dw_HFrfR.js +8 -0
- package/dist-client/assets/rag-engine-B9wYRzqT.js +1 -0
- package/dist-client/bundle.js +1 -1
- package/dist-client/wasm/ort-wasm-simd-threaded.asyncify.wasm +0 -0
- package/man/botdocs.1 +193 -0
- package/package.json +18 -2
- package/src/styles/chat.css +144 -0
- package/src/styles/themes/classic.css +29 -0
- package/src/styles/themes/material.css +29 -0
- package/src/styles/themes/minimal.css +34 -0
- package/src/styles/themes/modern.css +34 -0
- package/src/styles/themes/slate.css +32 -36
- package/src/templates/layout.html +21 -2
- package/dist-client/assets/chatbox-CmTTiB2Z.js +0 -1
- package/dist-client/assets/rag-engine-L2vj3Y0G.js +0 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var t=Object.defineProperty,e=(e,n,o)=>((e,n,o)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[n]=o)(e,"symbol"!=typeof n?n+"":n,o);import{g as n}from"./chatbox-Dw_HFrfR.js";import"../bundle.js";function o(t,e){if(t.length!==e.length)throw new Error("Vectors must have the same length");let n=0,o=0,s=0;for(let r=0;r<t.length;r++)n+=t[r]*e[r],o+=t[r]*t[r],s+=e[r]*e[r];return o=Math.sqrt(o),s=Math.sqrt(s),0===o||0===s?0:n/(o*s)}const s=new Set(["a","an","and","are","as","at","be","by","for","from","has","he","in","is","it","its","of","on","that","the","to","was","were","will","with"]);function r(t){return(t.toLowerCase().match(/[a-z0-9][\w.-]*/g)??[]).map(t=>t.replace(/[.-]+$/,"")).filter(t=>t.length>0&&!s.has(t))}class i{constructor(t){e(this,"termFreqs",new Map),e(this,"docFreq",new Map),e(this,"docLength",new Map),e(this,"avgDocLength",0),e(this,"docIds",[]);let n=0;for(const e of t){const t=r(e.text);this.docIds.push(e.id),this.docLength.set(e.id,t.length),n+=t.length;const o=new Map;for(const e of t)o.set(e,(o.get(e)??0)+1);this.termFreqs.set(e.id,o);for(const e of o.keys())this.docFreq.set(e,(this.docFreq.get(e)??0)+1)}this.avgDocLength=t.length>0?n/t.length:0}idf(t){const e=this.docFreq.get(t)??0;return Math.log(1+(this.docIds.length-e+.5)/(e+.5))}score(t,e){const n=this.termFreqs.get(e);if(!n)return 0;const o=this.docLength.get(e)??0;let s=0;for(const r of t){const t=n.get(r);if(!t)continue;const e=2.5*t,i=t+1.5*(.25+.75*o/(this.avgDocLength||1));s+=this.idf(r)*(e/i)}return s}scoreAll(t){const e=r(t),n=new Map;for(const o of this.docIds)n.set(o,this.score(e,o));return n}}function c(t){var e;return function(t,e){return!e.startsWith("/")||e.startsWith("//")?e:t+("/"===e?"index.html":e.slice(1))}((null==(e=document.querySelector('meta[name="botdocs-root"]'))?void 0:e.content)??"/",t)}class a{constructor(){e(this,"vectorDB",null),e(this,"bm25Index",null),e(this,"isLoading",!1)}async loadVectorDB(){if(!this.vectorDB)if(this.isLoading)for(;this.isLoading;)await new Promise(t=>setTimeout(t,100));else{this.isLoading=!0;try{console.log("Loading vector database...");const t=await fetch(c("/vector-db.json"));this.setDatabase(await t.json()),console.log(`Loaded ${this.vectorDB.chunks.length} chunks`)}finally{this.isLoading=!1}}}setDatabase(t){this.vectorDB=t,this.bm25Index=null}getBM25Index(){return this.bm25Index||(this.bm25Index=new i(this.vectorDB.chunks.map(t=>({id:t.id,text:t.text})))),this.bm25Index}async search(t,e,n=5,s=0){this.vectorDB||await this.loadVectorDB();const r=this.vectorDB.chunks.map(e=>({chunk:e,score:o(t,e.embedding)})),i=[...r].sort((t,e)=>e.score-t.score),c=new Map(i.map((t,e)=>[t.chunk.id,e])),a=this.getBM25Index().scoreAll(e),h=[...a.entries()].sort((t,e)=>e[1]-t[1]),l=new Map(h.map(([t],e)=>[t,e]));return r.map(({chunk:t,score:e})=>({chunk:t,score:e,rrfScore:1/(60+c.get(t.id)+1)+1/(60+l.get(t.id)+1),bm25Score:a.get(t.id)??0})).filter(t=>t.score>=s).filter(t=>t.bm25Score>0||t.score>=.9).sort((t,e)=>e.rrfScore-t.rrfScore).slice(0,n).map(({chunk:t,score:e})=>({chunk:t,score:e}))}isReady(){return null!==this.vectorDB}getInfo(){return this.vectorDB?{chunkCount:this.vectorDB.chunks.length,dimension:this.vectorDB.dimension,model:this.vectorDB.model}:null}}let h=null;function l(){return h||(h=new a),h}function u(t){const e=[],n=t.split("\n");let o=[],s=null;const r=()=>{if(0===o.length)return;const t=o.join(" ").trim();if(o=[],t)for(const n of function(t){const e=[],n=t.replace(/`[^`]*`/g,t=>(e.push(t),`\0${e.length-1}\0`));return(n.match(/[^.!?]+[.!?]*(?:\s+|$)/g)||[n]).map(t=>t.replace(/\0(\d+)\0/g,(t,n)=>e[Number(n)])).map(t=>t.trim()).filter(Boolean)}(t))n&&e.push({text:n,kind:"prose"})};let i=0;for(;i<n.length;){const t=n[i];if(t.trim().startsWith("```")){r(),s=null;const o=[t];for(i++;i<n.length&&!n[i].trim().startsWith("```");)o.push(n[i]),i++;i<n.length&&o.push(n[i]),e.push({text:o.join("\n"),kind:"code"}),i++;continue}if(/^#{1,6}\s+/.test(t.trim()))r(),s=null,i++;else{if(/^\s*([-*+]|\d+\.)\s+/.test(t)){r();const n=t.trim();n&&(e.push({text:n,kind:"list"}),s=e.length-1),i++;continue}""!==t.trim()?null===s?(o.push(t.trim()),i++):(e[s].text+=" "+t.trim(),i++):(r(),s=null,i++)}}return r(),e}function d(t){let e,n=t;do{e=n,n=n.replace(/!?\[([^\]]*)\]\([^)]*\)/g,"$1")}while(n!==e);return n}let m=!1,f=5,g=.75;const p=.85;async function w(){return n().isModelCached()}async function b(t){if(!m)try{const e=l();await e.loadVectorDB(),t&&t(5);const o=n();await o.initialize(e=>{t&&t(5+.95*e)}),m=!0,t&&t(100)}catch(e){throw console.error("Search initialization failed:",e),e}}async function v(t,e={}){if(!m)throw new Error("Search system not initialized");const s=e.topK??f,r=e.minScore??g;try{const e=n(),i=await e.embed(t),c=l(),a=await c.search(i,t,s,r),h=a.slice(0,3),m=await Promise.all(h.map(t=>async function(t,e,n,s=3){const r=t.trim(),i=u(r);if(0===i.length)return{snippet:r,topSegment:r,topScore:0};const c=await n(i.map(t=>t.text)),a=i.map((t,n)=>({...t,index:n,score:o(e,c[n])})).sort((t,e)=>e.score-t.score);return{snippet:a.slice(0,Math.min(s,a.length)).sort((t,e)=>t.index-e.index).map(t=>t.text).join("\n\n"),topSegment:a[0].text,topScore:a[0].score}}(t.chunk.text,i,t=>e.embedBatch(t)))),f=function(t,e,n,o){if(0===e.length)return[{content:"I couldn't find anything about that in the documentation. Try rephrasing your question or browse the navigation menu to explore available topics."}];const s=/^how (to|do|can)/i.test(t),r=/^what (is|are)/i.test(t),i=/^why/i.test(t),c=[],a=n.map((t,e)=>({result:t,extract:o[e]})).filter(({result:t,extract:e})=>t.score>p&&e.topScore>p);if(a.length>1){let t="**Quick answer**\n\n";for(const{result:e,extract:n}of a){const o=x(e.chunk.metadata);t+=`- ${d(n.topSegment)} ([${y(e.chunk.metadata)}](${o}))\n`}c.push({content:t.trim()})}let h;h=s?"Here's how you can do that:":r?"Let me explain:":i?"Here's what the docs say about that:":1===e.length?"I found this relevant section:":`I found ${n.length} relevant sections:`;if(c.push({content:h}),n.forEach((t,n)=>{const{chunk:s,score:r}=t,i=s.metadata.heading||s.metadata.title,a=x(s.metadata);let h=`### ${i}\n\n${o[n].snippet}\n\nā [View full section](${a})`;r>p&&(h+=" *(highly relevant)*"),1===e.length&&(h+="\n\n*Have a follow-up question? Just ask!*"),c.push({content:h})}),e.length>3){const t=e.length-3;c.push({content:`š” **Found ${t} more related section${t>1?"s":""}** ā check the sources below for more details.`})}const l=e.map(t=>({title:y(t.chunk.metadata),url:x(t.chunk.metadata)})),u=Array.from(new Map(l.map(t=>[t.url,t])).values());return c[c.length-1].citations=u,c}(t,a,h,m);return{messages:f}}catch(i){throw console.error("Search query error:",i),i}}function x(t){let e=c(t.url);return t.headingId&&(e+=`#${t.headingId}`),e}function y(t){return t.heading?`${t.title} ā ${t.heading}`:t.title}function k(){return m}function D(t){f=t}function B(t){g=t}export{b as initializeRAG,w as isModelCached,k as isRAGReady,v as queryRAG,B as setMinScore,D as setTopK};
|
package/dist-client/bundle.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e={},t=function(t,n,o){let r=Promise.resolve();if(n&&n.length>0){let t=function(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:"fulfilled",value:e}),e=>({status:"rejected",reason:e}))))};document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),c=(null==o?void 0:o.nonce)||(null==o?void 0:o.getAttribute("nonce"));r=t(n.map(t=>{if((t=function(e){return"/"+e}(t))in e)return;e[t]=!0;const n=t.endsWith(".css"),o=n?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${t}"]${o}`))return;const r=document.createElement("link");return r.rel=n?"stylesheet":"modulepreload",n||(r.as="script"),r.crossOrigin="",r.href=t,c&&r.setAttribute("nonce",c),document.head.appendChild(r),n?new Promise((e,n)=>{r.addEventListener("load",e),r.addEventListener("error",()=>n(new Error(`Unable to preload CSS for ${t}`)))}):void 0}))}function c(e){const t=new Event("vite:preloadError",{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(e=>{for(const t of e||[])"rejected"===t.status&&c(t.reason);return t().catch(c)})},n="botdocs-theme";function o(){const e="dark"===document.documentElement.getAttribute("data-theme")?"light":"dark";r(e),localStorage.setItem(n,e)}function r(e){document.documentElement.setAttribute("data-theme",e)}document.addEventListener("DOMContentLoaded",()=>{!function(){const e=localStorage.getItem(n),t=window.matchMedia("(prefers-color-scheme: dark)").matches;r(e||(t?"dark":"light"));const c=document.querySelector(".theme-toggle");c&&c.addEventListener("click",o),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",e=>{localStorage.getItem(n)||r(e.matches?"dark":"light")})}(),function(){const e=window.location.pathname;document.querySelectorAll(".sidebar-nav a").forEach(t=>{
|
|
1
|
+
const e={},t=function(t,n,o){let r=Promise.resolve();if(n&&n.length>0){let t=function(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:"fulfilled",value:e}),e=>({status:"rejected",reason:e}))))};document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),c=(null==o?void 0:o.nonce)||(null==o?void 0:o.getAttribute("nonce"));r=t(n.map(t=>{if((t=function(e){return"/"+e}(t))in e)return;e[t]=!0;const n=t.endsWith(".css"),o=n?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${t}"]${o}`))return;const r=document.createElement("link");return r.rel=n?"stylesheet":"modulepreload",n||(r.as="script"),r.crossOrigin="",r.href=t,c&&r.setAttribute("nonce",c),document.head.appendChild(r),n?new Promise((e,n)=>{r.addEventListener("load",e),r.addEventListener("error",()=>n(new Error(`Unable to preload CSS for ${t}`)))}):void 0}))}function c(e){const t=new Event("vite:preloadError",{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(e=>{for(const t of e||[])"rejected"===t.status&&c(t.reason);return t().catch(c)})},n="botdocs-theme";function o(){const e="dark"===document.documentElement.getAttribute("data-theme")?"light":"dark";r(e),localStorage.setItem(n,e)}function r(e){document.documentElement.setAttribute("data-theme",e)}document.addEventListener("DOMContentLoaded",()=>{!function(){const e=localStorage.getItem(n),t=window.matchMedia("(prefers-color-scheme: dark)").matches;r(e||(t?"dark":"light"));const c=document.querySelector(".theme-toggle");c&&c.addEventListener("click",o),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",e=>{localStorage.getItem(n)||r(e.matches?"dark":"light")})}(),function(){const e=window.location.pathname.replace(/\/$/,"/index.html");document.querySelectorAll(".sidebar-nav a").forEach(t=>{t.pathname===e&&t.classList.add("active")}),document.querySelectorAll('a[href^="#"]').forEach(e=>{e.addEventListener("click",t=>{t.preventDefault();const n=e.getAttribute("href");if(n&&n.length>1){const e=document.querySelector(n);e&&e.scrollIntoView({behavior:"smooth"})}})})}();document.getElementById("chat-widget")&&t(async()=>{const{initChat:e}=await import("./assets/chatbox-Dw_HFrfR.js").then(e=>e.c);return{initChat:e}},[]).then(({initChat:e})=>{e()})});export{t as _};
|
|
Binary file
|
package/man/botdocs.1
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
.TH BOTDOCS 1 "2026-08-15" "botdocs 0.4.0" "User Commands"
|
|
2
|
+
.SH NAME
|
|
3
|
+
botdocs \- convert markdown documentation into a static website with AI chatbot
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B botdocs
|
|
6
|
+
.RI [ options ]
|
|
7
|
+
.I input
|
|
8
|
+
.SH DESCRIPTION
|
|
9
|
+
.B botdocs
|
|
10
|
+
converts a directory of markdown files into a fully static documentation website.
|
|
11
|
+
An optional client-side semantic search chatbot is included by default, powered by
|
|
12
|
+
Transformers.js running entirely in the browser with no backend required.
|
|
13
|
+
.PP
|
|
14
|
+
The build pipeline scans markdown files, parses front matter, converts to HTML,
|
|
15
|
+
generates vector embeddings for semantic search, and bundles everything into a
|
|
16
|
+
self-contained static site.
|
|
17
|
+
.PP
|
|
18
|
+
No server-side inference is performed. On first use, visitors to the generated
|
|
19
|
+
site are asked for consent, with a disclosure of what runs locally, before the
|
|
20
|
+
embedding model downloads to their browser.
|
|
21
|
+
.SH ARGUMENTS
|
|
22
|
+
.TP
|
|
23
|
+
.I input
|
|
24
|
+
Input directory containing markdown files. Required.
|
|
25
|
+
.SH OPTIONS
|
|
26
|
+
.TP
|
|
27
|
+
.BR \-o ", " \-\-output " " \fIdir\fR
|
|
28
|
+
Output directory for the generated site. Defaults to
|
|
29
|
+
.BR output .
|
|
30
|
+
.TP
|
|
31
|
+
.B \-\-no\-chat
|
|
32
|
+
Disable the AI chatbot functionality. The generated site will not include
|
|
33
|
+
semantic search or the chat interface.
|
|
34
|
+
.TP
|
|
35
|
+
.BR \-c ", " \-\-config " " \fIfile\fR
|
|
36
|
+
Path to a configuration file. If not specified, botdocs looks for
|
|
37
|
+
.B botdocs.config.json
|
|
38
|
+
in the input directory.
|
|
39
|
+
.TP
|
|
40
|
+
.BR \-t ", " \-\-theme " " \fItheme\fR
|
|
41
|
+
Theme to use for the generated site. Available themes:
|
|
42
|
+
.BR classic " (default), " material ", " minimal ", " slate ", " modern .
|
|
43
|
+
.TP
|
|
44
|
+
.BR \-v ", " \-\-verbose
|
|
45
|
+
Enable verbose logging during the build process.
|
|
46
|
+
.TP
|
|
47
|
+
.BR \-w ", " \-\-watch
|
|
48
|
+
Rebuild when markdown or config files change, and serve the generated site
|
|
49
|
+
on a local preview server for live editing. Press Ctrl+C to stop.
|
|
50
|
+
.TP
|
|
51
|
+
.BR \-p ", " \-\-port " " \fInumber\fR
|
|
52
|
+
Port for the
|
|
53
|
+
.B \-\-watch
|
|
54
|
+
preview server. Defaults to
|
|
55
|
+
.BR 3000 .
|
|
56
|
+
.TP
|
|
57
|
+
.BR \-V ", " \-\-version
|
|
58
|
+
Output the version number.
|
|
59
|
+
.TP
|
|
60
|
+
.BR \-h ", " \-\-help
|
|
61
|
+
Display help information.
|
|
62
|
+
.SH CONFIGURATION
|
|
63
|
+
A
|
|
64
|
+
.B botdocs.config.json
|
|
65
|
+
file can be placed in the input directory to configure the build:
|
|
66
|
+
.PP
|
|
67
|
+
.RS 4
|
|
68
|
+
.nf
|
|
69
|
+
{
|
|
70
|
+
"title": "My Documentation",
|
|
71
|
+
"description": "Project docs",
|
|
72
|
+
"theme": "classic",
|
|
73
|
+
"customCss": "custom.css",
|
|
74
|
+
"attribution": true,
|
|
75
|
+
"baseUrl": "https://example.com/docs/",
|
|
76
|
+
"chat": {
|
|
77
|
+
"enabled": true,
|
|
78
|
+
"welcomeMessage": "Ask me anything about the docs!"
|
|
79
|
+
},
|
|
80
|
+
"build": {
|
|
81
|
+
"chunkSize": 500,
|
|
82
|
+
"chunkOverlap": 50,
|
|
83
|
+
"minChunkSize": 15,
|
|
84
|
+
"topK": 3,
|
|
85
|
+
"minScore": 0.75
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
.fi
|
|
89
|
+
.RE
|
|
90
|
+
.PP
|
|
91
|
+
CLI options override configuration file settings.
|
|
92
|
+
.PP
|
|
93
|
+
.B customCss
|
|
94
|
+
is a path to a CSS file, resolved relative to the config file's directory.
|
|
95
|
+
It is appended after the theme CSS in the bundled stylesheet, so
|
|
96
|
+
same-specificity selectors override the theme without
|
|
97
|
+
.BR !important .
|
|
98
|
+
.PP
|
|
99
|
+
.B attribution
|
|
100
|
+
controls the "Built with Botdocs" footer link and defaults to
|
|
101
|
+
.BR true .
|
|
102
|
+
.PP
|
|
103
|
+
.B baseUrl
|
|
104
|
+
is the canonical URL where the site is hosted (e.g. a GitHub Pages URL).
|
|
105
|
+
When set, each page emits
|
|
106
|
+
.B rel=canonical
|
|
107
|
+
and Open Graph/Twitter card metadata, and a
|
|
108
|
+
.B sitemap.xml
|
|
109
|
+
listing every generated page is written to the output directory.
|
|
110
|
+
When unset, these tags are omitted and no sitemap is generated.
|
|
111
|
+
.PP
|
|
112
|
+
.B build.minChunkSize
|
|
113
|
+
folds chunks smaller than this many estimated tokens into a neighboring
|
|
114
|
+
chunk instead of keeping them as standalone, low-signal retrieval
|
|
115
|
+
candidates (e.g. a heading followed by a single short line). Defaults to
|
|
116
|
+
.BR 15 .
|
|
117
|
+
.PP
|
|
118
|
+
.B build.minScore
|
|
119
|
+
is the minimum cosine similarity (0-1) a chunk must reach to be returned
|
|
120
|
+
from search at all, regardless of
|
|
121
|
+
.BR topK .
|
|
122
|
+
Chat search combines vector similarity with BM25 keyword matching
|
|
123
|
+
(Reciprocal Rank Fusion); this threshold is applied to the raw vector
|
|
124
|
+
score so an off-topic query returns fewer or no results instead of always
|
|
125
|
+
padding out to
|
|
126
|
+
.BR topK .
|
|
127
|
+
Defaults to
|
|
128
|
+
.BR 0.75 ,
|
|
129
|
+
since the e5 embedding model has a fairly high similarity floor even for
|
|
130
|
+
unrelated text.
|
|
131
|
+
.SH EXAMPLES
|
|
132
|
+
Build a documentation site from a directory:
|
|
133
|
+
.PP
|
|
134
|
+
.RS 4
|
|
135
|
+
.nf
|
|
136
|
+
botdocs ./docs
|
|
137
|
+
.fi
|
|
138
|
+
.RE
|
|
139
|
+
.PP
|
|
140
|
+
Build with a custom output directory and verbose logging:
|
|
141
|
+
.PP
|
|
142
|
+
.RS 4
|
|
143
|
+
.nf
|
|
144
|
+
botdocs ./docs -o ./site -v
|
|
145
|
+
.fi
|
|
146
|
+
.RE
|
|
147
|
+
.PP
|
|
148
|
+
Build without the chatbot:
|
|
149
|
+
.PP
|
|
150
|
+
.RS 4
|
|
151
|
+
.nf
|
|
152
|
+
botdocs ./docs --no-chat
|
|
153
|
+
.fi
|
|
154
|
+
.RE
|
|
155
|
+
.PP
|
|
156
|
+
Use the material theme:
|
|
157
|
+
.PP
|
|
158
|
+
.RS 4
|
|
159
|
+
.nf
|
|
160
|
+
botdocs ./docs -t material
|
|
161
|
+
.fi
|
|
162
|
+
.RE
|
|
163
|
+
.PP
|
|
164
|
+
Edit docs with live preview (rebuild on save, serve on port 4000):
|
|
165
|
+
.PP
|
|
166
|
+
.RS 4
|
|
167
|
+
.nf
|
|
168
|
+
botdocs ./docs -w -p 4000
|
|
169
|
+
.fi
|
|
170
|
+
.RE
|
|
171
|
+
.SH EXIT STATUS
|
|
172
|
+
.TP
|
|
173
|
+
.B 0
|
|
174
|
+
Successful build.
|
|
175
|
+
.TP
|
|
176
|
+
.B 1
|
|
177
|
+
Build failed.
|
|
178
|
+
.SH FILES
|
|
179
|
+
.TP
|
|
180
|
+
.I botdocs.config.json
|
|
181
|
+
Optional configuration file in the input directory.
|
|
182
|
+
.TP
|
|
183
|
+
.I output/
|
|
184
|
+
Default output directory containing the generated static site.
|
|
185
|
+
.TP
|
|
186
|
+
.I output/assets/vector\-db.json
|
|
187
|
+
Vector database used by the client-side semantic search.
|
|
188
|
+
.SH AUTHORS
|
|
189
|
+
usr-wwelsh <https://wwel.sh>
|
|
190
|
+
.SH BUGS
|
|
191
|
+
Report bugs at:
|
|
192
|
+
.UR https://github.com/usr-wwelsh/botdocs/issues
|
|
193
|
+
.UE
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botdocs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "CLI tool that converts markdown documentation into a static website with an optional client-side AI chatbot",
|
|
5
5
|
"author": "usr-wwelsh <https://wwel.sh>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
8
8
|
"botdocs": "./bin/botdocs.js"
|
|
9
9
|
},
|
|
10
|
+
"man": "./man/botdocs.1",
|
|
10
11
|
"main": "./dist/src/cli/index.js",
|
|
11
12
|
"type": "module",
|
|
12
13
|
"files": [
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
"bin/",
|
|
16
17
|
"src/templates/",
|
|
17
18
|
"src/styles/",
|
|
19
|
+
"man/",
|
|
18
20
|
"README.md",
|
|
19
21
|
"LICENSE"
|
|
20
22
|
],
|
|
@@ -22,6 +24,11 @@
|
|
|
22
24
|
"build": "tsc",
|
|
23
25
|
"build:client": "vite build",
|
|
24
26
|
"dev": "tsc --watch",
|
|
27
|
+
"lint": "eslint .",
|
|
28
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.client.json && tsc --noEmit -p tsconfig.test.json",
|
|
29
|
+
"test": "tsx --test 'src/**/*.test.ts'",
|
|
30
|
+
"test:integration": "npm run build && tsx --test 'test/integration/**/*.test.ts'",
|
|
31
|
+
"ci": "npm run build && npm run build:client && npm run typecheck && npm run lint && npm test && npm run test:integration",
|
|
25
32
|
"prepublishOnly": "npm run build && npm run build:client"
|
|
26
33
|
},
|
|
27
34
|
"keywords": [
|
|
@@ -35,8 +42,8 @@
|
|
|
35
42
|
"cli"
|
|
36
43
|
],
|
|
37
44
|
"dependencies": {
|
|
45
|
+
"@huggingface/transformers": "^4.2.0",
|
|
38
46
|
"@shikijs/markdown-it": "^1.0.0",
|
|
39
|
-
"@xenova/transformers": "^2.17.0",
|
|
40
47
|
"commander": "^12.0.0",
|
|
41
48
|
"gray-matter": "^4.0.3",
|
|
42
49
|
"markdown-it": "^14.0.0",
|
|
@@ -51,12 +58,21 @@
|
|
|
51
58
|
"shiki": "^1.0.0"
|
|
52
59
|
},
|
|
53
60
|
"devDependencies": {
|
|
61
|
+
"@eslint/js": "^10.0.1",
|
|
54
62
|
"@types/markdown-it": "^14.0.0",
|
|
55
63
|
"@types/node": "^20.11.0",
|
|
64
|
+
"eslint": "^10.8.1",
|
|
56
65
|
"terser": "^5.36.0",
|
|
66
|
+
"tsx": "^4.23.12",
|
|
57
67
|
"typescript": "^5.3.0",
|
|
68
|
+
"typescript-eslint": "^8.67.0",
|
|
58
69
|
"vite": "^6.0.0"
|
|
59
70
|
},
|
|
71
|
+
"overrides": {
|
|
72
|
+
"onnxruntime-node": "^1.27.0",
|
|
73
|
+
"adm-zip": "^0.6.0",
|
|
74
|
+
"sharp": "^0.35.3"
|
|
75
|
+
},
|
|
60
76
|
"engines": {
|
|
61
77
|
"node": ">=18.0.0"
|
|
62
78
|
},
|
package/src/styles/chat.css
CHANGED
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
align-items: center;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
.chat-info,
|
|
71
72
|
.chat-clear,
|
|
72
73
|
.chat-close {
|
|
73
74
|
background: none;
|
|
@@ -89,11 +90,50 @@
|
|
|
89
90
|
font-size: 1.5rem;
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
.chat-info:hover,
|
|
92
94
|
.chat-clear:hover,
|
|
93
95
|
.chat-close:hover {
|
|
94
96
|
background: rgba(255, 255, 255, 0.2);
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
.chat-disclosure {
|
|
100
|
+
padding: 1rem 1.25rem;
|
|
101
|
+
background: var(--bg-tertiary);
|
|
102
|
+
color: var(--text-secondary);
|
|
103
|
+
font-size: 0.8125rem;
|
|
104
|
+
line-height: 1.5;
|
|
105
|
+
border-bottom: 1px solid var(--border-color);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.chat-disclosure p {
|
|
109
|
+
margin: 0 0 0.5rem 0;
|
|
110
|
+
color: var(--text-primary);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.chat-disclosure ul {
|
|
114
|
+
margin: 0 0 0.75rem 0;
|
|
115
|
+
padding-left: 1.25rem;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.chat-disclosure li {
|
|
119
|
+
margin-bottom: 0.375rem;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.chat-disclosure-close {
|
|
123
|
+
padding: 0.375rem 0.875rem;
|
|
124
|
+
background: var(--link-color);
|
|
125
|
+
color: white;
|
|
126
|
+
border: none;
|
|
127
|
+
border-radius: 0.375rem;
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
font-size: 0.8125rem;
|
|
130
|
+
font-weight: 500;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.chat-disclosure-close:hover {
|
|
134
|
+
background: var(--link-hover);
|
|
135
|
+
}
|
|
136
|
+
|
|
97
137
|
.chat-messages {
|
|
98
138
|
flex: 1;
|
|
99
139
|
overflow-y: auto;
|
|
@@ -242,6 +282,19 @@
|
|
|
242
282
|
border-bottom-color: var(--link-color);
|
|
243
283
|
}
|
|
244
284
|
|
|
285
|
+
.message-content ul {
|
|
286
|
+
margin: 0.5rem 0;
|
|
287
|
+
padding-left: 1.25rem;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.message-content li {
|
|
291
|
+
margin-bottom: 0.375rem;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.message-content li:last-child {
|
|
295
|
+
margin-bottom: 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
245
298
|
.message-content hr {
|
|
246
299
|
border: none;
|
|
247
300
|
border-top: 1px solid var(--border-color);
|
|
@@ -398,6 +451,92 @@
|
|
|
398
451
|
transition: width 0.3s ease;
|
|
399
452
|
}
|
|
400
453
|
|
|
454
|
+
.chat-consent p {
|
|
455
|
+
margin: 0 0 0.75rem 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.chat-consent-actions {
|
|
459
|
+
display: flex;
|
|
460
|
+
gap: 0.5rem;
|
|
461
|
+
justify-content: center;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.chat-consent-download,
|
|
465
|
+
.chat-consent-retry {
|
|
466
|
+
padding: 0.5rem 1rem;
|
|
467
|
+
background: var(--link-color);
|
|
468
|
+
color: white;
|
|
469
|
+
border: none;
|
|
470
|
+
border-radius: 0.375rem;
|
|
471
|
+
cursor: pointer;
|
|
472
|
+
font-size: 0.8125rem;
|
|
473
|
+
font-weight: 500;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.chat-consent-download:hover,
|
|
477
|
+
.chat-consent-retry:hover {
|
|
478
|
+
background: var(--link-hover);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.chat-consent-retry {
|
|
482
|
+
margin-left: 0.5rem;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.chat-consent-decline {
|
|
486
|
+
padding: 0.5rem 1rem;
|
|
487
|
+
background: none;
|
|
488
|
+
color: var(--text-secondary);
|
|
489
|
+
border: 1px solid var(--border-color);
|
|
490
|
+
border-radius: 0.375rem;
|
|
491
|
+
cursor: pointer;
|
|
492
|
+
font-size: 0.8125rem;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.chat-consent-decline:hover {
|
|
496
|
+
background: var(--bg-secondary);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/* AI Use Disclosure Banner (full-bleed, breaks out of .main-content padding) */
|
|
500
|
+
.ai-disclosure {
|
|
501
|
+
margin: 3rem -3rem 0 -3rem;
|
|
502
|
+
padding: 1rem 3rem;
|
|
503
|
+
background: var(--link-color);
|
|
504
|
+
color: white;
|
|
505
|
+
text-align: center;
|
|
506
|
+
font-size: 0.875rem;
|
|
507
|
+
font-weight: 500;
|
|
508
|
+
line-height: 1.5;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* Footer Attribution */
|
|
512
|
+
.botdocs-attribution {
|
|
513
|
+
margin-top: 4rem;
|
|
514
|
+
padding-top: 2rem;
|
|
515
|
+
border-top: 1px solid var(--border-color);
|
|
516
|
+
text-align: center;
|
|
517
|
+
font-size: 0.875rem;
|
|
518
|
+
color: var(--text-secondary);
|
|
519
|
+
opacity: 0.8;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.ai-disclosure + .botdocs-attribution {
|
|
523
|
+
margin-top: 1.5rem;
|
|
524
|
+
padding-top: 0;
|
|
525
|
+
border-top: none;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.botdocs-attribution a {
|
|
529
|
+
color: var(--text-secondary);
|
|
530
|
+
text-decoration: none;
|
|
531
|
+
font-weight: 500;
|
|
532
|
+
transition: color 0.2s;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.botdocs-attribution a:hover {
|
|
536
|
+
color: var(--link-color);
|
|
537
|
+
text-decoration: none;
|
|
538
|
+
}
|
|
539
|
+
|
|
401
540
|
/* Mobile responsiveness */
|
|
402
541
|
@media (max-width: 768px) {
|
|
403
542
|
.chat-widget {
|
|
@@ -417,4 +556,9 @@
|
|
|
417
556
|
height: 3.5rem;
|
|
418
557
|
font-size: 1.5rem;
|
|
419
558
|
}
|
|
559
|
+
|
|
560
|
+
.ai-disclosure {
|
|
561
|
+
margin: 2rem -1.5rem 0 -1.5rem;
|
|
562
|
+
padding: 0.875rem 1.5rem;
|
|
563
|
+
}
|
|
420
564
|
}
|
|
@@ -96,6 +96,35 @@ body {
|
|
|
96
96
|
font-weight: 500;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
.nav-list li.nav-group > a {
|
|
100
|
+
font-weight: 600;
|
|
101
|
+
position: relative;
|
|
102
|
+
padding-right: 1.75rem;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.nav-list li.nav-group > a::after {
|
|
106
|
+
content: 'āŗ';
|
|
107
|
+
position: absolute;
|
|
108
|
+
right: 0.75rem;
|
|
109
|
+
transition: transform 0.2s;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.nav-list li.nav-group:has(> .nav-list) > a::after {
|
|
113
|
+
transform: rotate(90deg);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.nav-list .nav-list {
|
|
117
|
+
margin-top: 0.375rem;
|
|
118
|
+
padding-left: 0.75rem;
|
|
119
|
+
border-left: 1px solid var(--border-color);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.nav-list .nav-list a {
|
|
123
|
+
font-weight: 400;
|
|
124
|
+
font-size: 0.9rem;
|
|
125
|
+
padding: 0.375rem 0.75rem;
|
|
126
|
+
}
|
|
127
|
+
|
|
99
128
|
/* Main Content */
|
|
100
129
|
.main-content {
|
|
101
130
|
margin-left: var(--sidebar-width);
|
|
@@ -105,6 +105,35 @@ body {
|
|
|
105
105
|
box-shadow: var(--shadow-1);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
.nav-list li.nav-group > a {
|
|
109
|
+
font-weight: 700;
|
|
110
|
+
position: relative;
|
|
111
|
+
padding-right: 1.75rem;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.nav-list li.nav-group > a::after {
|
|
115
|
+
content: 'āŗ';
|
|
116
|
+
position: absolute;
|
|
117
|
+
right: 1rem;
|
|
118
|
+
transition: transform 0.2s;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.nav-list li.nav-group:has(> .nav-list) > a::after {
|
|
122
|
+
transform: rotate(90deg);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.nav-list .nav-list {
|
|
126
|
+
margin-top: 0.25rem;
|
|
127
|
+
padding-left: 0.75rem;
|
|
128
|
+
border-left: 2px solid var(--bg-tertiary);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.nav-list .nav-list a {
|
|
132
|
+
font-weight: 400;
|
|
133
|
+
font-size: 0.85rem;
|
|
134
|
+
padding: 0.5rem 0.75rem;
|
|
135
|
+
}
|
|
136
|
+
|
|
108
137
|
/* Main Content */
|
|
109
138
|
.main-content {
|
|
110
139
|
margin-left: var(--sidebar-width);
|
|
@@ -109,6 +109,40 @@ body {
|
|
|
109
109
|
border-bottom-color: var(--text-primary);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
.nav-list li.nav-group {
|
|
113
|
+
flex-basis: 100%;
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-wrap: wrap;
|
|
116
|
+
justify-content: center;
|
|
117
|
+
align-items: baseline;
|
|
118
|
+
gap: 0.375rem 1rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.nav-list li.nav-group > a {
|
|
122
|
+
color: var(--text-primary);
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.nav-list li.nav-group > a::after {
|
|
127
|
+
content: 'āŗ';
|
|
128
|
+
margin-left: 0.25rem;
|
|
129
|
+
display: inline-block;
|
|
130
|
+
transition: transform 0.2s;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.nav-list li.nav-group:has(> .nav-list) > a::after {
|
|
134
|
+
transform: rotate(90deg);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.nav-list .nav-list {
|
|
138
|
+
margin: 0;
|
|
139
|
+
gap: 0.375rem 1rem;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.nav-list .nav-list a {
|
|
143
|
+
font-size: 0.8rem;
|
|
144
|
+
}
|
|
145
|
+
|
|
112
146
|
/* Main Content - centered, no sidebar offset */
|
|
113
147
|
.main-content {
|
|
114
148
|
margin-left: 0;
|
|
@@ -122,6 +122,40 @@ body {
|
|
|
122
122
|
box-shadow: 0 4px 12px rgba(100, 108, 255, 0.3);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
.nav-list li.nav-group {
|
|
126
|
+
flex-basis: 100%;
|
|
127
|
+
display: flex;
|
|
128
|
+
flex-wrap: wrap;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: 0.5rem;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.nav-list li.nav-group > a {
|
|
134
|
+
font-weight: 700;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.nav-list li.nav-group > a::after {
|
|
138
|
+
content: 'āŗ';
|
|
139
|
+
margin-left: 0.375rem;
|
|
140
|
+
display: inline-block;
|
|
141
|
+
transition: transform 0.2s;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.nav-list li.nav-group:has(> .nav-list) > a::after {
|
|
145
|
+
transform: rotate(90deg);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.nav-list .nav-list {
|
|
149
|
+
margin: 0;
|
|
150
|
+
gap: 0.375rem;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.nav-list .nav-list a {
|
|
154
|
+
padding: 0.375rem 0.875rem;
|
|
155
|
+
font-size: 0.85rem;
|
|
156
|
+
font-weight: 400;
|
|
157
|
+
}
|
|
158
|
+
|
|
125
159
|
/* Main Content - full width, no sidebar offset */
|
|
126
160
|
.main-content {
|
|
127
161
|
margin-left: 0;
|