docs-expert 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kalil0321
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kalil0321/docs-expert/main/.github/banner-dark.svg">
4
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/kalil0321/docs-expert/main/.github/banner-light.svg">
5
+ <img alt="docs-expert" src="https://raw.githubusercontent.com/kalil0321/docs-expert/main/.github/banner-dark.svg" width="600">
6
+ </picture>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <strong>Query any documentation site's AI assistant from the terminal</strong>
11
+ </p>
12
+
13
+ <p align="center">
14
+ <a href="https://www.npmjs.com/package/docs-expert"><img src="https://img.shields.io/npm/v/docs-expert?color=8b5cf6&label=npm" alt="npm"></a>
15
+ <a href="https://github.com/kalil0321/docs-expert"><img src="https://img.shields.io/badge/license-MIT-8b5cf6" alt="license"></a>
16
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D20-8b5cf6" alt="node"></a>
17
+ <a href="https://github.com/kalil0321/docs-expert/actions/workflows/ci.yml"><img src="https://github.com/kalil0321/docs-expert/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
18
+ <a href="https://github.com/kalil0321/docs-expert"><img src="https://raw.githubusercontent.com/kalil0321/docs-expert/main/.github/badges/coverage.svg" alt="coverage"></a>
19
+ </p>
20
+
21
+ ---
22
+
23
+ CLI + TypeScript library that taps into the **AI assistants already embedded** in documentation platforms. No API keys, no scraping, no token costs — just answers from the source.
24
+
25
+ Supports **9 providers** out of the box:
26
+
27
+ | Provider | Flag | Sites |
28
+ |----------|------|-------|
29
+ | Auto-detect | `<url> <question>` | Automatically detects the provider (Mintlify, GitBook, Fern, ReadMe, Inkeep) |
30
+ | [Claude/Anthropic](https://docs.anthropic.com) | `--claude` | Claude Agent SDK, API docs |
31
+ | [Stripe](https://docs.stripe.com) | `--stripe` | Stripe docs |
32
+ | [Vercel](https://vercel.com/docs) | `--vercel` | Vercel docs |
33
+ | [Better Auth](https://better-auth.com) | `--better-auth` | Better Auth docs |
34
+ | [GitBook](https://gitbook.com) | `--gitbook <url>` | Any GitBook-powered site |
35
+ | [Fern](https://buildwithfern.com) | `--fern <url>` | 150+ sites (OpenRouter, Square, ElevenLabs, ...) |
36
+ | [ReadMe](https://readme.com) | `--readme <url>` | Any ReadMe-powered site |
37
+ | [Inkeep](https://inkeep.com) | `--inkeep <url>` | Any Inkeep-powered site (Clerk, ...) — auto-detects API key |
38
+
39
+ ```
40
+ ◆ docs-expert v0.1.0
41
+ query any documentation site's AI assistant
42
+
43
+ ✓ Done.
44
+
45
+ ── Answer ──────────────────────────────────────────────
46
+
47
+ Metronome is a billing platform that transforms your
48
+ customers' usage into precise, tailored invoices...
49
+
50
+ ── Sources ─────────────────────────────────────────────
51
+
52
+ ◆ How Metronome works
53
+ https://docs.metronome.com/guides/get-started/how-metronome-works
54
+ ```
55
+
56
+ ## Install
57
+
58
+ ```bash
59
+ npm install -g docs-expert
60
+ ```
61
+
62
+ ## CLI
63
+
64
+ ```bash
65
+ # Auto-detect provider and query (works with any supported site)
66
+ docs-expert https://docs.metronome.com "What is Metronome?"
67
+ docs-expert https://clerk.com/docs "How do I protect API routes?"
68
+ docs-expert https://openrouter.ai/docs "What models are available?"
69
+
70
+ # Query Claude/Anthropic docs
71
+ docs-expert --claude "How do I use the Agent SDK?"
72
+
73
+ # Query Stripe docs
74
+ docs-expert --stripe "How do I create a payment intent?"
75
+
76
+ # Query Vercel docs
77
+ docs-expert --vercel "How do I deploy a Next.js app?"
78
+
79
+ # Query Better Auth docs
80
+ docs-expert --better-auth "How do I set up email and password auth?"
81
+
82
+ # Query any GitBook site
83
+ docs-expert --gitbook https://docs.gitbook.com "How do I create a space?"
84
+
85
+ # Query any Fern site
86
+ docs-expert --fern https://openrouter.ai/docs "What models are available?"
87
+
88
+ # Query any ReadMe site
89
+ docs-expert --readme https://docs.readme.com "How do I set up API docs?"
90
+
91
+ # Query any Inkeep-powered site (auto-detects API key)
92
+ docs-expert --inkeep https://clerk.com/docs "How do I protect API routes?"
93
+
94
+ # JSON output
95
+ docs-expert --claude --json "What is the Agent SDK?"
96
+
97
+ # Interactive mode
98
+ docs-expert
99
+ ```
100
+
101
+ ## Library
102
+
103
+ ```typescript
104
+ import { ask, askStream, askClaudeDocs, askStripeDocs, askFernDocs, askVercelDocs, resolveProvider } from "docs-expert";
105
+
106
+ // Auto-detect provider
107
+ const { provider } = await resolveProvider("https://clerk.com/docs");
108
+ // provider === "inkeep"
109
+
110
+ // Mintlify (any site)
111
+ const response = await ask("https://docs.metronome.com", "What is Metronome?");
112
+ console.log(response.content);
113
+ console.log(response.searchResults);
114
+
115
+ // Claude docs
116
+ const claude = await askClaudeDocs("How do I use tools?");
117
+
118
+ // Stripe docs
119
+ const stripe = await askStripeDocs("What is a payment intent?");
120
+
121
+ // Fern docs (any site)
122
+ const fern = await askFernDocs("https://openrouter.ai/docs", "What models are available?");
123
+
124
+ // Vercel docs
125
+ const vercel = await askVercelDocs("How do I deploy?");
126
+ ```
127
+
128
+ ### Streaming
129
+
130
+ ```typescript
131
+ import { askStream } from "docs-expert";
132
+
133
+ for await (const event of askStream("https://docs.notte.cc", "What is Notte?")) {
134
+ if (event.type === "text") process.stdout.write(event.text);
135
+ else if (event.type === "done") console.log("\nSources:", event.response.searchResults);
136
+ }
137
+ ```
138
+
139
+ ### Stateful client (multi-turn)
140
+
141
+ ```typescript
142
+ import { createClient } from "docs-expert";
143
+
144
+ const client = createClient("https://docs.metronome.com");
145
+ const r1 = await client.ask("What is Metronome?");
146
+ const r2 = await client.ask("How do I set up billing?"); // has conversation context
147
+ client.clearHistory();
148
+ ```
149
+
150
+ ## How it works
151
+
152
+ docs-expert reverse-engineers the AI assistants that documentation platforms embed in their sites. Each provider has its own API pattern:
153
+
154
+ - **Mintlify** — SSE streaming via `leaves.mintlify.com`, auto-detects subdomain from page HTML
155
+ - **Inkeep** (Claude, Clerk, etc.) — SHA-256 challenge-response auth, OpenAI-compatible chat completions API
156
+ - **Stripe** — Polling-based API at `ai.stripe.com`, creates threads and polls for responses
157
+ - **GitBook** — Next.js Server Actions with RSC streaming, dynamically discovers action hash from JS bundles
158
+ - **Fern** — SSE streaming at `/api/fern-docs/search/v2/chat`, zero auth
159
+ - **ReadMe** — Non-streaming JSON at `/{subdomain}/chatgpt/ask`, zero auth
160
+ - **Vercel** — SSE streaming at `/api/ai-chat`, Vercel AI SDK protocol
161
+ - **Better Auth** — SSE streaming at `/api/docs/chat`, Vercel AI SDK protocol
162
+
163
+ No API keys required. No scraping. No LLM costs. Just the built-in AI that's already there.
164
+
165
+ ## Disclaimer
166
+
167
+ > **This package is not affiliated with, endorsed by, or associated with any of the documentation platforms it supports.**
168
+ >
169
+ > `docs-expert` interacts with publicly accessible AI assistant endpoints — the same ones used by the chat widgets embedded on documentation sites. No authentication is bypassed, no private data is accessed, and no rate limiting is circumvented.
170
+ >
171
+ > This is an independent open-source project built for developer convenience. Use it responsibly and in accordance with the terms of service of the documentation sites you query. The authors assume no liability for misuse.
172
+
173
+ ## License
174
+
175
+ MIT — see [LICENSE](./LICENSE) for details.
176
+
177
+ ---
@@ -0,0 +1,12 @@
1
+ var J=[/"subdomain"\s*:\s*"([a-zA-Z0-9-]+)"/,/mintlify-assets\/_mintlify\/favicons\/([a-zA-Z0-9-]+)\//,/\/api\/assistant\/([a-zA-Z0-9-]+)\//,/data-subdomain="([a-zA-Z0-9-]+)"/,/\/mintlify-assets\/_mintlify\/[^/]+\/([a-zA-Z0-9-]+)\//];async function z(e){let t=e.replace(/\/+$/,""),s=await fetch(t,{headers:{"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}});if(!s.ok)throw new Error(`Failed to fetch docs site (${s.status}): ${s.statusText}`);let n=s.body,r;if(n){let o=n.getReader(),a=new TextDecoder;r="";let u=0;try{for(;u<1e5;){let{done:d,value:c}=await o.read();if(d)break;r+=a.decode(c,{stream:!0}),u+=c.length;for(let i of J){let l=r.match(i);if(l)return o.cancel(),l[1]}}}finally{o.releaseLock()}}else r=await s.text();for(let o of J){let a=r.match(o);if(a)return a[1]}throw new Error(`Could not auto-detect Mintlify subdomain from ${t}. Please provide it manually via the subdomain option.`)}import C from"fs/promises";import Gt from"os";import H from"path";function F(){let e=process.env.DOCS_EXPERT_CACHE_DIR;if(e)return e;let t=Gt.homedir();return H.join(t,".config","docs-expert")}function K(){return H.join(F(),"subdomain-cache.json")}var R=null;async function D(){if(R)return R;let e=K();try{let t=await C.readFile(e,"utf-8"),s=JSON.parse(t);if(s.entries&&typeof s.entries=="object")return R=s,R}catch{}return R={entries:{}},R}async function W(e){R=e;let t=F(),s=K();try{await C.mkdir(t,{recursive:!0}),await C.writeFile(s,JSON.stringify(e,null,0),"utf-8")}catch{}}async function q(e){return(await D()).entries[e]?.subdomain??null}async function X(e,t){let s=await D();s.entries[e]={subdomain:t,cachedAt:Date.now()},await W(s)}async function T(e){let t=await D();delete t.entries[e],await W(t)}function Lt(e){if(!e||!e.includes(":"))return{type:"",data:null};let t=e[0],s=e.slice(2);try{return t==="f"||t==="9"||t==="a"||t==="e"?{type:t,data:JSON.parse(s)}:t==="0"?{type:t,data:JSON.parse(s)}:{type:t,data:s}}catch{return{type:t,data:s}}}async function*Bt(e){let t=e.getReader(),s=new TextDecoder,n="";try{for(;;){let{done:r,value:o}=await t.read();if(r)break;n+=s.decode(o,{stream:!0});let a=n.split(`
2
+ `);n=a.pop();for(let u of a)u&&(yield u)}n&&(yield n)}finally{t.releaseLock()}}async function*$(e){for await(let t of Bt(e)){let s=Lt(t);s.type&&(yield s)}}async function _(e){let t=await q(e);if(t)return{subdomain:t,fromCache:!0};let s=await z(e);return await X(e,s),{subdomain:s,fromCache:!1}}var Jt="https://leaves.mintlify.com/api/assistant",zt="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";function A(e,t){return{id:crypto.randomUUID().replace(/-/g,"").slice(0,16),createdAt:new Date().toISOString(),role:e,content:t,parts:[{type:"text",text:t}]}}function Ht(e,t,s){let n={id:e,messages:t,fp:e,filter:{groups:s.filterGroups??["*"]},currentPath:s.currentPath??"/"};return s.filterVersion&&(n.filter.version=s.filterVersion),n}function Z(e){let t=[],s=e.match(/```suggestions\n([\s\S]*?)\n```/);if(s){for(let r of s[1].trim().split(`
3
+ `)){let o=r.trim().match(/\(([^)]+)\)\[([^\]]+)\]/);o&&t.push(o[2])}return{cleanContent:e.replace(/\n*```suggestions\n[\s\S]*?\n```\n*/g,"").trim(),suggestions:t}}return{cleanContent:e.trim(),suggestions:t}}function V(e,t){return e.map(s=>({...s,href:s.href.startsWith("/")?`${t}${s.href}`:s.href}))}async function U(e,t,s,n){let r=`${Jt}/${e}/message`,o=Ht(e,t,n),a=await fetch(r,{method:"POST",headers:{Accept:"*/*","Content-Type":"application/json",Origin:s,Referer:`${s}/`,"User-Agent":zt},body:JSON.stringify(o)});if(!a.ok)throw new Error(`Mintlify API error (${a.status}): ${a.statusText}`);if(!a.body)throw new Error("No response body received from Mintlify API");return a.body}function Y(e){let t=e.result;return t?.type!=="search"?[]:(t.results??[]).map(n=>{let r=n.metadata??{};return{content:n.content??"",path:n.path??"",title:r.title??"",href:r.href??""}})}async function Q(e,t){let s=[],n="",r=[],o=null;for await(let{type:c,data:i}of $(e))c==="f"&&i&&typeof i=="object"?n=i.messageId??"":c==="0"&&i?s.push(i):c==="a"&&i&&typeof i=="object"?r.push(...Y(i)):c==="e"&&i&&typeof i=="object"&&(o=i.usage??null);let a=s.join(""),{cleanContent:u,suggestions:d}=Z(a);return r=V(r,t),{content:u,messageId:n,searchResults:r,suggestions:d,usage:o}}async function*tt(e,t){let s=[],n="",r=[],o=null;for await(let{type:c,data:i}of $(e))if(c==="f"&&i&&typeof i=="object")n=i.messageId??"";else if(c==="0"&&i)s.push(i),yield{type:"text",text:i};else if(c==="a"&&i&&typeof i=="object"){let l=V(Y(i),t);r.push(...l),l.length&&(yield{type:"searchResults",results:l})}else c==="e"&&i&&typeof i=="object"&&(o=i.usage??null);let a=s.join(""),{cleanContent:u,suggestions:d}=Z(a);yield{type:"done",response:{content:u,messageId:n,searchResults:r,suggestions:d,usage:o}}}async function Ft(e,t){return t.subdomain?{subdomain:t.subdomain,fromCache:!1}:_(e)}function et(e){return e instanceof Error&&e.message.includes("Mintlify API error")}async function st(e,t,s){let{subdomain:n,fromCache:r}=await Ft(e,s);try{return await U(n,t,e,s)}catch(o){if(r&&et(o)){await T(e);let a=await _(e);return U(a.subdomain,t,e,s)}throw o}}async function me(e,t,s={}){let n=e.replace(/\/+$/,""),r=[A("user",t)],o=await st(n,r,s);return Q(o,n)}async function*ye(e,t,s={}){let n=e.replace(/\/+$/,""),r=[A("user",t)],o=await st(n,r,s);yield*tt(o,n)}function we(e,t={}){let s=e.replace(/\/+$/,""),n=t.subdomain,r=!1,o=[];async function a(){if(n)return n;if(t.subdomain)return n=t.subdomain,n;let d=await _(s);return n=d.subdomain,r=d.fromCache,n}async function u(){let d=await a();try{return await U(d,o,s,t)}catch(c){if(r&&et(c))return n=void 0,r=!1,await T(s),u();throw c}}return{get messages(){return[...o]},async ask(d){o.push(A("user",d));let c=await u(),i=await Q(c,s);return o.push(A("assistant",i.content)),i},async*askStream(d){o.push(A("user",d));let c=await u(),i;for await(let l of tt(c,s))l.type==="done"&&(i=l.response),yield l;i&&o.push(A("assistant",i.content))},clearHistory(){o.length=0}}}import nt from"crypto";var rt="338b6cdd7488066de9b9dc40e996d96b11488d29ef05b56d",O="https://api.inkeep.com",v="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36";async function ot(e){let t=e.replace(/\/+$/,""),s=await fetch(t,{headers:{"User-Agent":v},redirect:"follow"});if(!s.ok)throw new Error(`Failed to fetch site (${s.status})`);let n=await s.text(),r=n.match(/apiKey\s*[=:]\s*["']([a-f0-9]{40,50})["']/);if(r)return r[1];let a=n.match(/dpl=([a-zA-Z0-9_-]+)/)?.[1]??"",u=/_next\/static\/chunks\/(\d+)-([a-f0-9]+)\.js/g,d=[],c;for(;(c=u.exec(n))!==null;)d.push(`_next/static/chunks/${c[1]}-${c[2]}.js`);let i=new URL(s.url).origin;for(let l of d){let f=`${i}/${l}${a?`?dpl=${a}`:""}`;try{let h=await fetch(f,{headers:{"User-Agent":v}});if(!h.ok)continue;let p=await h.text();if(!p.includes("inkeep"))continue;let m=p.match(/apiKey\s*[=:]\s*["']([a-f0-9]{40,50})["']/);if(m)return m[1]}catch{continue}}throw new Error("Could not detect Inkeep API key from the site")}function Kt(e){if(e.algorithm!=="SHA-256")throw new Error(`Unsupported algorithm: ${e.algorithm}`);for(let t=0;t<=e.maxnumber;t++)if(nt.createHash("sha256").update(`${e.salt}${t}`).digest("hex")===e.challenge){let n={number:t,algorithm:e.algorithm,challenge:e.challenge,maxnumber:e.maxnumber,salt:e.salt,signature:e.signature};return Buffer.from(JSON.stringify(n)).toString("base64")}throw new Error("Could not solve challenge within maxnumber iterations")}function at(e,t){return{model:"inkeep-qa-expert",messages:[{id:`${Date.now()}-${nt.randomUUID().slice(0,4)}-1`,role:"user",content:e}],stream:t,tools:[{type:"function",function:{name:"provideLinks",description:"Provides links",parameters:{type:"object",properties:{links:{anyOf:[{type:"array",items:{type:"object",properties:{label:{type:["string","null"]},url:{type:"string"},title:{type:["string","null"]},description:{type:["string","null"]}},required:["url"],additionalProperties:!0}},{type:"null"}]},text:{type:"string"}},required:["text"],additionalProperties:!1,$schema:"http://json-schema.org/draft-07/schema#"}}}],tool_choice:"auto"}}async function it(e){let t=await fetch(`${O}/v1/challenge`,{headers:{Accept:"application/json","User-Agent":v,Origin:"https://platform.claude.com",Referer:"https://platform.claude.com/"}});if(!t.ok)throw new Error(`Challenge request failed (${t.status})`);let s=await t.json();return Kt(s)}function ct(e,t){return{Accept:"application/json","Content-Type":"application/json","User-Agent":v,Origin:"https://platform.claude.com",Referer:"https://platform.claude.com/",Authorization:`Bearer ${e}`,"X-Inkeep-Challenge-Solution":t}}async function Wt(e,t=rt){let s=await it(t),n=at(e,!1),r=ct(t,s),o=await fetch(`${O}/v1/chat/completions`,{method:"POST",headers:r,body:JSON.stringify(n)});if(!o.ok)throw new Error(`Inkeep API error (${o.status}): ${o.statusText}`);let a=await o.json(),u="",d=[],c=a.choices;if(c?.[0]){let i=c[0].message;i?.content&&(u=i.content);let l=i?.tool_calls;if(l)for(let f of l){let h=f.function;if(h?.name==="provideLinks"&&h.arguments)try{let p=JSON.parse(h.arguments),m=p.links;if(m)for(let g of m)d.push({content:g.description??"",path:g.url??"",title:g.title??g.label??"",href:g.url??""});!u&&p.text&&(u=p.text)}catch{}}}return{content:u,messageId:"",searchResults:d,suggestions:[],usage:null}}async function*qt(e,t=rt){let s=await it(t),n=at(e,!0),r={...ct(t,s),Accept:"text/event-stream"},o=await fetch(`${O}/v1/chat/completions`,{method:"POST",headers:r,body:JSON.stringify(n)});if(!o.ok)throw new Error(`Inkeep API error (${o.status}): ${o.statusText}`);if(!o.body)throw new Error("No response body");let a=o.body.getReader(),u=new TextDecoder,d="",c=[],i=[],l="";try{for(;;){let{done:f,value:h}=await a.read();if(f)break;d+=u.decode(h,{stream:!0});let p=d.split(`
4
+ `);d=p.pop();for(let m of p){if(!m.startsWith("data: "))continue;let g=m.slice(6);if(g==="[DONE]")break;try{let w=JSON.parse(g).choices;if(!w?.[0])continue;let x=w[0].delta;if(!x)continue;let y=x.content;y&&(c.push(y),yield{type:"text",text:y});let b=x.tool_calls;if(b)for(let Nt of b){let B=Nt.function;B?.arguments&&(l+=B.arguments)}}catch{}}}}finally{a.releaseLock()}if(l)try{let f=JSON.parse(l),h=f.links;if(h){for(let p of h)i.push({content:p.description??"",path:p.url??"",title:p.title??p.label??"",href:p.url??""});i.length&&(yield{type:"searchResults",results:i})}if(c.length===0&&f.text){let p=f.text;c.push(p),yield{type:"text",text:p}}}catch{}yield{type:"done",response:{content:c.join(""),messageId:"",searchResults:i,suggestions:[],usage:null}}}async function be(e,t){let s=await ot(e);return Wt(t,s)}async function*Re(e,t){let s=await ot(e);yield*qt(t,s)}import lt from"crypto";var ut="https://ai.stripe.com",E="https://docs.stripe.com",dt="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",Xt={"Content-Type":"application/json",Origin:E,Referer:`${E}/`,"User-Agent":dt};async function pt(e,t){let s=await fetch(e,{method:"POST",headers:Xt,body:JSON.stringify(t)});if(!s.ok)throw new Error(`Stripe AI error (${s.status}): ${s.statusText}`);return s.json()}function Zt(e){return new Promise(t=>setTimeout(t,e))}async function ft(e,t){let s="";try{let n=await fetch(`${E}/.md`,{headers:{"User-Agent":dt}});n.ok&&(s=await n.text())}catch{}return pt(`${ut}/assistant/thread`,{question:e,message_metadata:{question_type:"chat"},client:"docs",client_id:t,question_metadata:{stripe_doc:{url:"/",title:"Stripe Documentation",prefs:{},content:s}}})}async function*ht(e,t,s=500,n=120){let r=0;for(let o=0;o<n;o++){let a=await pt(`${ut}/smart-docs/get-streaming-ask-summary-state`,{conversation_id:e,offset:r,client:"docs",client_id:t});if(a.content&&(r+=a.content.length,yield a.content),a.is_complete)return;await Zt(s)}}function j(e){return e?e.map(t=>{let s=t.url??"",n=s.startsWith("http")?s:`${E}${s}`;return{content:"",path:s,title:t.title??"",href:n}}):[]}async function ve(e){let t=lt.randomUUID(),s=await ft(e,t);if(s.answerable===!1)return{content:"The AI assistant determined this question is not answerable from Stripe docs.",messageId:s.thread_id,searchResults:j(s.sources),suggestions:[],usage:null};let n=[];for await(let r of ht(s.conversation_id,t))n.push(r);return{content:n.join(""),messageId:s.thread_id,searchResults:j(s.sources),suggestions:[],usage:null}}async function*Ee(e){let t=lt.randomUUID(),s=await ft(e,t),n=j(s.sources);if(n.length&&(yield{type:"searchResults",results:n}),s.answerable===!1){let o="The AI assistant determined this question is not answerable from Stripe docs.";yield{type:"text",text:o},yield{type:"done",response:{content:o,messageId:s.thread_id,searchResults:n,suggestions:[],usage:null}};return}let r=[];for await(let o of ht(s.conversation_id,t))r.push(o),yield{type:"text",text:o};yield{type:"done",response:{content:r.join(""),messageId:s.thread_id,searchResults:n,suggestions:[],usage:null}}}import gt from"crypto";var M="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36";async function mt(e){let t=e.replace(/\/+$/,""),s=await fetch(t,{headers:{"User-Agent":M},redirect:"follow"});if(!s.ok)throw new Error(`Failed to fetch GitBook site (${s.status}): ${s.statusText}`);let n=s.url.replace(/\/+$/,""),r=await s.text(),o=r.match(/apiToken(?:%3A|:)(eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/);if(!o)throw new Error("Could not find GitBook API token in page HTML");let a=o[1],u=JSON.parse(Buffer.from(a.split(".")[1],"base64").toString()),d=u.space??"",c=u.site??"",i=u.organization??"",l=r.match(/basePath(?:%3A|:)([^,%"]+)/),f=l?decodeURIComponent(l[1]).replace(/'/g,""):"/",p=r.match(/"pageId":"([^"]+)"/)?.[1]??"",m=await Vt(r);return{finalUrl:n,apiToken:a,spaceId:d,pageId:p,siteId:c,organizationId:i,basePath:f,actionHash:m}}async function Vt(e){let s=e.match(/dpl=([a-f0-9]{40})/)?.[1]??"",n=e.match(/(https:\/\/static[^/]*\.gitbook\.com)\/_next\//)?.[1]??"https://static-2v.gitbook.com",r=/static\/chunks\/(\d+)-([a-f0-9]+)\.js/g,o=new Set,a;for(;(a=r.exec(e))!==null;)o.add(`static/chunks/${a[1]}-${a[2]}.js`);for(let u of o){let d=`${n}/_next/${u}${s?`?dpl=${s}`:""}`;try{let c=await fetch(d,{headers:{"User-Agent":M}});if(!c.ok)continue;let l=(await c.text()).match(/createServerReference\("([a-f0-9]{40,50})"[^"]*"streamAIChat/);if(l)return l[1]}catch{continue}}return"405c9c6fe927bf660b75e7675a1019681e1eeda4c4"}function yt(e){let t=[];for(let s of e)if(s.nodes){for(let n of s.nodes)if(n.leaves)for(let r of n.leaves)t.push(r.text)}return t.join("")}function wt(e){let t=e.indexOf(":");if(t<0)return null;let s=e.slice(0,t),n=e.slice(t+1);try{return{id:s,data:JSON.parse(n)}}catch{return null}}async function xt(e,t){let s=`${t.finalUrl}/?ask=`,n=`${gt.randomUUID()}R`,r=`${gt.randomUUID()}R`,o=JSON.stringify([{message:e,toolCall:"$undefined",messageContext:{location:{spaceId:t.spaceId,pageId:t.pageId}},previousResponseId:"$undefined",session:{sessionId:n,visitorId:r},tools:[],options:{withLinkPreviews:!0,withToolCalls:!1,asEmbeddable:!1}}]),a=await fetch(s,{method:"POST",headers:{Accept:"text/x-component","Content-Type":"text/plain;charset=UTF-8","Next-Action":t.actionHash,"User-Agent":M},body:o});if(!a.ok)throw new Error(`GitBook API error (${a.status}): ${a.statusText}`);return a}async function Ce(e,t){let s=await mt(e),r=await(await xt(t,s)).text(),o=[],a="",u=-1;for(let c of r.split(`
5
+ `)){let i=wt(c);if(!i)continue;let l=i.data;if(!l||typeof l!="object"||!("event"in l))continue;let f=l.event;if(f.type==="response_document"&&f.blocks){let h=f.stepIndex??0,p=yt(f.blocks);h>=u&&(u=h,a=p)}if(f.type==="response_tool_call"&&f.toolCall?.tool==="search")for(let h of f.toolCall.results??[])h.type==="page"&&h.title&&o.push({content:h.description??"",path:h.pageId??"",title:h.title,href:h.url??""})}return{content:a.trim(),messageId:"",searchResults:o,suggestions:[],usage:null}}async function*De(e,t){let s=await mt(e),n=await xt(t,s);if(!n.body)throw new Error("No response body");let r=n.body.getReader(),o=new TextDecoder,a="",u=[],d=[],c=0,i=-1,l=!1;try{for(;;){let{done:f,value:h}=await r.read();if(f)break;a+=o.decode(h,{stream:!0});let p=a.split(`
6
+ `);a=p.pop();for(let m of p){let g=wt(m);if(!g)continue;let S=g.data;if(!S||typeof S!="object"||!("event"in S))continue;let w=S.event;if(w.type==="response_document"&&w.blocks){let x=w.stepIndex??0;if(x>i&&(i=x,c=0),x===i&&l){let y=yt(w.blocks);if(y.length>c){let b=y.slice(c);u.push(b),c=y.length,yield{type:"text",text:b}}}}if(w.type==="response_tool_call"&&w.toolCall?.tool==="search"){l=!0;let x=[];for(let y of w.toolCall.results??[])if(y.type==="page"&&y.title){let b={content:y.description??"",path:y.pageId??"",title:y.title,href:y.url??""};d.push(b),x.push(b)}x.length&&(yield{type:"searchResults",results:x})}}}}finally{r.releaseLock()}yield{type:"done",response:{content:u.join(""),messageId:"",searchResults:d,suggestions:[],usage:null}}}import P from"crypto";var St="ai-sdk/5.0.120 runtime/node";function bt(e,t){return{url:e,conversationId:P.randomUUID(),queryId:P.randomUUID(),id:P.randomUUID().slice(0,16),filters:[],source:"CHAT",documentUrls:[],messages:[{role:"user",parts:[{type:"text",text:t}],id:P.randomUUID().slice(0,16)}],trigger:"submit-message"}}function Rt(e){try{return new URL(e).hostname}catch{return e.replace(/^https?:\/\//,"").split("/")[0]}}function kt(e){let t=e.replace(/\/+$/,"");return`${new URL(t).origin}/docs/api/fern-docs/search/v2/chat`}function At(e){if(!e.startsWith("data: "))return null;let t=e.slice(6);try{return JSON.parse(t)}catch{return null}}async function Ue(e,t){let s=Rt(e),n=kt(e),r=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":St,"x-fern-host":s},body:JSON.stringify(bt(e,t))});if(!r.ok)throw new Error(`Fern API error (${r.status}): ${r.statusText}`);let o=await r.text(),a=[],u=[];for(let d of o.split(`
7
+ `)){let c=At(d);if(c){if(c.type==="data-sources"){let i=c.data;for(let l of i)u.push({content:"",path:l.url,title:l.title,href:l.url})}c.type==="text-delta"&&c.delta&&a.push(c.delta)}}return{content:a.join(""),messageId:"",searchResults:u,suggestions:[],usage:null}}async function*_e(e,t){let s=Rt(e),n=kt(e),r=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":St,"x-fern-host":s},body:JSON.stringify(bt(e,t))});if(!r.ok)throw new Error(`Fern API error (${r.status}): ${r.statusText}`);if(!r.body)throw new Error("No response body");let o=r.body.getReader(),a=new TextDecoder,u="",d=[],c=[];try{for(;;){let{done:i,value:l}=await o.read();if(i)break;u+=a.decode(l,{stream:!0});let f=u.split(`
8
+ `);u=f.pop();for(let h of f){let p=At(h);if(p){if(p.type==="data-sources"){let m=p.data,g=[];for(let S of m){let w={content:"",path:S.url,title:S.title,href:S.url};c.push(w),g.push(w)}g.length&&(yield{type:"searchResults",results:g})}p.type==="text-delta"&&p.delta&&(d.push(p.delta),yield{type:"text",text:p.delta})}}}}finally{o.releaseLock()}yield{type:"done",response:{content:d.join(""),messageId:"",searchResults:c,suggestions:[],usage:null}}}import vt from"crypto";var Et="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36";async function Yt(e){let t=await fetch(e,{headers:{"User-Agent":Et},redirect:"follow"});if(!t.ok)throw new Error(`Failed to fetch ReadMe site (${t.status}): ${t.statusText}`);let s=await t.text();return(s.match(/"subdomain"\s*:\s*"([^"]+)"/)??s.match(/data-subdomain="([^"]+)"/)??s.match(/subdomain&quot;:&quot;([^&]+)&quot;/))?.[1]??"main"}function Qt(e){let t=[],s=/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g,n;for(;(n=s.exec(e))!==null;)t.push({content:"",path:n[2],title:n[1],href:n[2]});return t}async function te(e,t){let s=e.replace(/\/+$/,""),n=await Yt(s),r=`${s}/${n}/chatgpt/ask`,o=`askAI-${n}-${vt.randomUUID()}`,a=`${Date.now()}-${vt.randomUUID().slice(0,7)}`,u=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":Et},body:JSON.stringify({messages:[{id:a,role:"user",content:t}],conversation_id:o})});if(!u.ok)throw new Error(`ReadMe API error (${u.status}): ${u.statusText}`);let d=await u.text(),c=Qt(d);return{content:d,messageId:"",searchResults:c,suggestions:[],usage:null}}async function*Me(e,t){let s=await te(e,t);s.searchResults.length&&(yield{type:"searchResults",results:s.searchResults}),yield{type:"text",text:s.content},yield{type:"done",response:s}}import ee from"crypto";var Pt="https://vercel.com/api/ai-chat",It="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36";function I(e=16){return ee.randomUUID().replace(/-/g,"").slice(0,e)}function Ct(e){if(!e.startsWith("data: "))return null;try{return JSON.parse(e.slice(6))}catch{return null}}function se(e){let t=[];for(let s of e)if(s.type==="tool-output-available"&&s.output)for(let n of s.output)n.url&&t.push({content:"",path:n.url,title:n.title??"",href:n.url});return t}async function Le(e){let t=await fetch(Pt,{method:"POST",headers:{"Content-Type":"application/json",Origin:"https://vercel.com",Referer:"https://vercel.com/docs","User-Agent":It},body:JSON.stringify({id:I(),currentRoute:"/docs",messages:[{id:I(),role:"user",parts:[{type:"text",text:e}]}],trigger:"submit-message"})});if(!t.ok)throw new Error(`Vercel API error (${t.status}): ${t.statusText}`);let s=await t.text(),n=[],r=[];for(let o of s.split(`
9
+ `)){let a=Ct(o);a&&(r.push(a),a.type==="text-delta"&&a.delta&&n.push(a.delta))}return{content:n.join(""),messageId:"",searchResults:se(r),suggestions:[],usage:null}}async function*Be(e){let t=await fetch(Pt,{method:"POST",headers:{"Content-Type":"application/json",Origin:"https://vercel.com",Referer:"https://vercel.com/docs","User-Agent":It},body:JSON.stringify({id:I(),currentRoute:"/docs",messages:[{id:I(),role:"user",parts:[{type:"text",text:e}]}],trigger:"submit-message"})});if(!t.ok)throw new Error(`Vercel API error (${t.status}): ${t.statusText}`);if(!t.body)throw new Error("No response body");let s=t.body.getReader(),n=new TextDecoder,r="",o=[],a=[];try{for(;;){let{done:u,value:d}=await s.read();if(u)break;r+=n.decode(d,{stream:!0});let c=r.split(`
10
+ `);r=c.pop();for(let i of c){let l=Ct(i);if(l&&(l.type==="text-delta"&&l.delta&&(o.push(l.delta),yield{type:"text",text:l.delta}),l.type==="tool-output-available"&&l.output)){let f=[];for(let h of l.output)if(h.url){let p={content:"",path:h.url,title:h.title??"",href:h.url};a.push(p),f.push(p)}f.length&&(yield{type:"searchResults",results:f})}}}}finally{s.releaseLock()}yield{type:"done",response:{content:o.join(""),messageId:"",searchResults:a,suggestions:[],usage:null}}}import Dt from"crypto";var Tt="https://better-auth.com/api/docs/chat",$t="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36";function Ut(e){if(!e.startsWith("data: "))return null;let t=e.slice(6);if(t==="[DONE]")return{type:"done"};try{return JSON.parse(t)}catch{return null}}async function He(e){let t=await fetch(Tt,{method:"POST",headers:{"Content-Type":"application/json",Origin:"https://better-auth.com",Referer:"https://better-auth.com/docs/introduction","User-Agent":$t},body:JSON.stringify({id:"ai-chat",messages:[{parts:[{type:"text",text:e}],id:Dt.randomUUID().slice(0,16),role:"user"}],trigger:"submit-message"})});if(!t.ok)throw new Error(`Better Auth API error (${t.status}): ${t.statusText}`);let s=await t.text(),n=[],r=[];for(let o of s.split(`
11
+ `)){let a=Ut(o);if(a&&(a.type==="text"&&a.text&&n.push(a.text),a.type==="source"&&a.url&&r.push({content:"",path:a.url,title:a.title??"",href:a.url}),a.type==="error"))throw new Error(a.errorText??"Unknown error from Better Auth AI")}return{content:n.join(""),messageId:"",searchResults:r,suggestions:[],usage:null}}async function*Fe(e){let t=await fetch(Tt,{method:"POST",headers:{"Content-Type":"application/json",Origin:"https://better-auth.com",Referer:"https://better-auth.com/docs/introduction","User-Agent":$t},body:JSON.stringify({id:"ai-chat",messages:[{parts:[{type:"text",text:e}],id:Dt.randomUUID().slice(0,16),role:"user"}],trigger:"submit-message"})});if(!t.ok)throw new Error(`Better Auth API error (${t.status}): ${t.statusText}`);if(!t.body)throw new Error("No response body");let s=t.body.getReader(),n=new TextDecoder,r="",o=[],a=[];try{for(;;){let{done:u,value:d}=await s.read();if(u)break;r+=n.decode(d,{stream:!0});let c=r.split(`
12
+ `);r=c.pop();for(let i of c){let l=Ut(i);if(l){if(l.type==="text"&&l.text&&(o.push(l.text),yield{type:"text",text:l.text}),l.type==="source"&&l.url){let f={content:"",path:l.url,title:l.title??"",href:l.url};a.push(f),yield{type:"searchResults",results:[f]}}if(l.type==="error")throw new Error(l.errorText??"Unknown error from Better Auth AI")}}}}finally{s.releaseLock()}yield{type:"done",response:{content:o.join(""),messageId:"",searchResults:a,suggestions:[],usage:null}}}import N from"fs/promises";import ne from"os";import _t from"path";function Ot(){let e=process.env.DOCS_EXPERT_CACHE_DIR;return e||_t.join(ne.homedir(),".config","docs-expert")}function jt(){return _t.join(Ot(),"provider-cache.json")}var k=null;async function G(){if(k)return k;try{let e=await N.readFile(jt(),"utf-8"),t=JSON.parse(e);if(t.entries&&typeof t.entries=="object")return k=t,k}catch{}return k={entries:{}},k}async function Mt(e){k=e;let t=Ot();try{await N.mkdir(t,{recursive:!0}),await N.writeFile(jt(),JSON.stringify(e,null,0),"utf-8")}catch{}}function L(e){return e.replace(/\/+$/,"")}async function re(e){return(await G()).entries[L(e)]?.provider??null}async function oe(e,t){let s=await G();s.entries[L(e)]={provider:t,cachedAt:Date.now()},await Mt(s)}async function Ze(e){let t=await G();delete t.entries[L(e)],await Mt(t)}async function ae(e){let t;try{t=await(await fetch(e,{headers:{"User-Agent":"docs-expert"},redirect:"follow"})).text()}catch{return"mintlify"}let s=t.toLowerCase(),n={mintlify:0,gitbook:0,fern:0,readme:0,inkeep:0};s.includes("_mintlify")&&(n.mintlify+=10),s.includes("mintlify.app")&&(n.mintlify+=10),s.includes("leaves.mintlify")&&(n.mintlify+=10),s.includes("mintlify")&&(n.mintlify+=2),s.includes("buildwithfern")&&(n.fern+=10),s.includes("fern-docs")&&(n.fern+=10),s.includes("fern.docs")&&(n.fern+=5),s.includes("static.gitbook.com")&&(n.gitbook+=10),s.includes("gitbook.com/_next")&&(n.gitbook+=10),/apitoken[:%]/.test(s)&&s.includes("gitbook")&&(n.gitbook+=8),s.includes("gitbook")&&(n.gitbook+=2),s.includes("powered by readme")&&(n.readme+=10),s.includes("readme-header")&&(n.readme+=8),/"subdomain"\s*:\s*"/.test(t)&&s.includes("readme")&&(n.readme+=8),s.includes("readme.com")&&(n.readme+=2),/inkeep[^"]*\.js/.test(s)&&(n.inkeep+=5),s.includes("inkeep")&&(n.inkeep+=2);let r="mintlify",o=0;for(let[a,u]of Object.entries(n))u>o&&(o=u,r=a);return r}async function Ve(e){let t=await re(e);if(t)return{provider:t,fromCache:!0};let s=await ae(e);return await oe(e,s),{provider:s,fromCache:!1}}export{me as a,ye as b,we as c,Wt as d,qt as e,be as f,Re as g,ve as h,Ee as i,Ce as j,De as k,Ue as l,_e as m,te as n,Me as o,Le as p,Be as q,He as r,Fe as s,Ze as t,ae as u,Ve as v};
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ import{b as E,e as H,g as v,i as N,k as S,m as I,o as _,q as D,s as P,t as Q,v as A}from"./chunk-A7S2BH5H.js";import{text as l,isCancel as d,cancel as p}from"@clack/prompts";import w from"chalk";process.title="docs-expert";var i=w.hex("#8b5cf6"),j=w.hex("#c084fc"),c=w.dim,h=w.bold,R=null;async function T(){return R||(R=(async()=>{let{Marked:o}=await import("marked"),{markedTerminal:n}=await import("marked-terminal");return new o(n({reflowText:!0,width:Math.min(process.stdout.columns||80,100),tab:2}))})()),R}function W(o){return o.replace(/\*\*([^*]+)\*\*/g,(n,r)=>h(r)).replace(/(?<!\*)\*([^*]+)\*(?!\*)/g,(n,r)=>w.italic(r)).replace(/`([^`]+)`/g,(n,r)=>w.cyan(r))}async function J(o){let r=(await T()).parse(o).trimEnd();return W(r)}function K(){console.log(),console.log(` ${j("\u25C6")} ${h("docs-expert")} ${c("v0.1.0")}`),console.log(` ${c("query any documentation site's AI assistant")}`),console.log()}function b(o){let n=Math.min(process.stdout.columns||60,60),r=c("\u2500".repeat(Math.max(n-o.length-4,10)));return` ${c("\u2500\u2500")} ${j(o)} ${r}`}var F=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function z(){let o=0,n=null,r="";return{start(u){r=u,n=setInterval(()=>{let y=i(F[o%F.length]);process.stderr.write(`\r ${y} ${c(r)}`),o++},80)},update(u){r=u},stop(u){n&&clearInterval(n),process.stderr.write(`\r${" ".repeat(process.stdout.columns||60)}\r`),u&&console.error(` ${i("\u2713")} ${c(u)}`)}}}function U(o,n,r){switch(o){case"fern":return I(n,r);case"gitbook":return S(n,r);case"readme":return _(n,r);case"inkeep":return v(n,r);case"mintlify":return E(n,r)}}async function X(){let o=process.argv.slice(2);if(o.includes("--version")||o.includes("-v")){console.log("docs-expert v0.1.0");return}if(o.includes("--help")||o.includes("-h")){console.log(`
3
+ ${h("Usage:")} docs-expert [options] <url> <question>
4
+ docs-expert --claude <question>
5
+ docs-expert --stripe <question>
6
+ docs-expert --gitbook <url> <question>
7
+ docs-expert --fern <url> <question>
8
+ docs-expert --readme <url> <question>
9
+ docs-expert --vercel <question>
10
+ docs-expert --better-auth <question>
11
+ docs-expert --inkeep <url> <question>
12
+
13
+ ${h("Arguments:")}
14
+ url Documentation site URL
15
+ question Question to ask about the docs
16
+
17
+ ${h("Options:")}
18
+ --claude Query Claude/Anthropic docs (via Inkeep API)
19
+ --stripe Query Stripe docs (via Stripe AI)
20
+ --gitbook Query any GitBook-powered docs site
21
+ --fern Query any Fern-powered docs site
22
+ --readme Query any ReadMe-powered docs site
23
+ --vercel Query Vercel docs
24
+ --better-auth Query Better Auth docs
25
+ --inkeep Query any Inkeep-powered docs site (auto-detects API key)
26
+ --json Output raw JSON response
27
+ -v, --version Show version
28
+ -h, --help Show this help message
29
+
30
+ ${h("Examples:")}
31
+ ${c("# Auto-detect provider and query (works with any supported site)")}
32
+ docs-expert https://docs.example.com "How does auth work?"
33
+ docs-expert https://clerk.com/docs "How do I protect API routes?"
34
+
35
+ ${c("# Query Claude/Anthropic docs")}
36
+ docs-expert --claude "How do I use tools in the Agent SDK?"
37
+
38
+ ${c("# Query Stripe docs")}
39
+ docs-expert --stripe "How do I create a payment intent?"
40
+
41
+ ${c("# Query any GitBook docs")}
42
+ docs-expert --gitbook https://docs.gitbook.com "How do I create a space?"
43
+
44
+ ${c("# Query any Fern docs")}
45
+ docs-expert --fern https://openrouter.ai/docs "What models are available?"
46
+
47
+ ${c("# Query any ReadMe docs")}
48
+ docs-expert --readme https://docs.readme.com "How do I set up API docs?"
49
+
50
+ ${c("# Query Vercel docs")}
51
+ docs-expert --vercel "How do I deploy a Next.js app?"
52
+
53
+ ${c("# Query Better Auth docs")}
54
+ docs-expert --better-auth "How do I set up email and password auth?"
55
+
56
+ ${c("# Query any Inkeep-powered docs (Clerk, etc.)")}
57
+ docs-expert --inkeep https://clerk.com/docs "How do I set up auth?"
58
+
59
+ ${c("# Interactive mode (prompts for inputs)")}
60
+ docs-expert
61
+ `);return}let n=o.includes("--json"),r=o.includes("--claude"),u=o.includes("--stripe"),y=o.includes("--gitbook"),B=o.includes("--fern"),G=o.includes("--readme"),x=o.includes("--vercel"),$=o.includes("--better-auth"),L=o.includes("--inkeep"),a=o.filter(e=>!e.startsWith("--")&&!e.startsWith("-"));n||K();let g;if(r||u||x||$){let e=a[0];if(!e){let s=r?"How do I use the Agent SDK?":x?"How do I deploy a Next.js app?":$?"How do I set up email and password auth?":"How do I create a payment intent?",t=await l({message:`${i("\u25C6")} Your question`,placeholder:s});d(t)&&(p("Cancelled."),process.exit(0)),e=t,console.log()}g=r?H(e):x?D(e):$?P(e):N(e)}else if(y){let e=a[0],s=a[1];if(!e){let t=await l({message:`${i("\u25C6")} GitBook docs URL`,placeholder:"https://docs.gitbook.com"});d(t)&&(p("Cancelled."),process.exit(0)),e=t,console.log()}if(!s){let t=await l({message:`${i("\u25C6")} Your question`,placeholder:"How does this work?"});d(t)&&(p("Cancelled."),process.exit(0)),s=t,console.log()}g=S(e,s)}else if(B){let e=a[0],s=a[1];if(!e){let t=await l({message:`${i("\u25C6")} Fern docs URL`,placeholder:"https://openrouter.ai/docs"});d(t)&&(p("Cancelled."),process.exit(0)),e=t,console.log()}if(!s){let t=await l({message:`${i("\u25C6")} Your question`,placeholder:"How does this work?"});d(t)&&(p("Cancelled."),process.exit(0)),s=t,console.log()}g=I(e,s)}else if(G){let e=a[0],s=a[1];if(!e){let t=await l({message:`${i("\u25C6")} ReadMe docs URL`,placeholder:"https://docs.readme.com"});d(t)&&(p("Cancelled."),process.exit(0)),e=t,console.log()}if(!s){let t=await l({message:`${i("\u25C6")} Your question`,placeholder:"How does this work?"});d(t)&&(p("Cancelled."),process.exit(0)),s=t,console.log()}g=_(e,s)}else if(L){let e=a[0],s=a[1];if(!e){let t=await l({message:`${i("\u25C6")} Inkeep-powered docs URL`,placeholder:"https://clerk.com/docs"});d(t)&&(p("Cancelled."),process.exit(0)),e=t,console.log()}if(!s){let t=await l({message:`${i("\u25C6")} Your question`,placeholder:"How does this work?"});d(t)&&(p("Cancelled."),process.exit(0)),s=t,console.log()}g=v(e,s)}else{let e=a[0],s=a[1];if(!e){let f=await l({message:`${i("\u25C6")} Documentation site URL`,placeholder:"https://docs.example.com"});d(f)&&(p("Cancelled."),process.exit(0)),e=f,console.log()}if(!s){let f=await l({message:`${i("\u25C6")} Your question`,placeholder:"How does authentication work?"});d(f)&&(p("Cancelled."),process.exit(0)),s=f,console.log()}let{provider:t,fromCache:V}=await A(e);g=(async function*(){try{yield*U(t,e,s)}catch(f){if(V){await Q(e);let Y=await A(e);yield*U(Y.provider,e,s)}else throw f}})()}let k=z();k.start("Connecting to docs...");let m,q=[],C=!1,M=0,O=300;for await(let e of g)if(e.type==="searchResults"&&!C)C=!0,k.update("Generating answer...");else if(e.type==="text"){q.push(e.text);let s=Date.now();if(s-M>=O){M=s;let t=q.join("").split(/\s+/).length;k.update(`Generating answer... ${c(`${t} words`)}`)}}else e.type==="done"&&(m=e.response);if(m||(k.stop("No response received."),process.exit(1)),k.stop("Done."),n){console.log(JSON.stringify(m,null,2));return}if(console.log(),console.log(b("Answer")),console.log(),console.log(await J(m.content)),console.log(),m.searchResults.length>0){console.log(b("Sources")),console.log();for(let e of m.searchResults)console.log(` ${i("\u25C6")} ${h(e.title)}`),console.log(` ${c(e.href)}`);console.log()}if(m.suggestions.length>0){console.log(b("Suggested")),console.log();for(let e of m.suggestions)console.log(` ${i("\u2192")} ${e}`);console.log()}}X().catch(o=>{console.error(`
62
+ ${w.red("\u2716")} ${o.message}
63
+ `),process.exit(1)});
@@ -0,0 +1,89 @@
1
+ interface SearchResult {
2
+ content: string;
3
+ path: string;
4
+ title: string;
5
+ href: string;
6
+ }
7
+ interface DocsExpertResponse {
8
+ content: string;
9
+ messageId: string;
10
+ searchResults: SearchResult[];
11
+ suggestions: string[];
12
+ usage: Record<string, number> | null;
13
+ }
14
+ interface Message {
15
+ id: string;
16
+ createdAt: string;
17
+ role: "user" | "assistant";
18
+ content: string;
19
+ parts: Array<{
20
+ type: "text";
21
+ text: string;
22
+ }>;
23
+ }
24
+ interface DocsExpertOptions {
25
+ subdomain?: string;
26
+ filterGroups?: string[];
27
+ filterVersion?: string;
28
+ currentPath?: string;
29
+ }
30
+ type StreamEvent = {
31
+ type: "text";
32
+ text: string;
33
+ } | {
34
+ type: "searchResults";
35
+ results: SearchResult[];
36
+ } | {
37
+ type: "done";
38
+ response: DocsExpertResponse;
39
+ };
40
+
41
+ declare function ask(docsUrl: string, question: string, options?: DocsExpertOptions): Promise<DocsExpertResponse>;
42
+ declare function askStream(docsUrl: string, question: string, options?: DocsExpertOptions): AsyncGenerator<StreamEvent>;
43
+ declare function createClient(docsUrl: string, options?: DocsExpertOptions): {
44
+ readonly messages: Message[];
45
+ ask(question: string): Promise<DocsExpertResponse>;
46
+ askStream(question: string): AsyncGenerator<StreamEvent>;
47
+ clearHistory(): void;
48
+ };
49
+
50
+ declare function askClaudeDocs(question: string, apiKey?: string): Promise<DocsExpertResponse>;
51
+ declare function askClaudeDocsStream(question: string, apiKey?: string): AsyncGenerator<StreamEvent>;
52
+ declare function askInkeepDocs(docsUrl: string, question: string): Promise<DocsExpertResponse>;
53
+ declare function askInkeepDocsStream(docsUrl: string, question: string): AsyncGenerator<StreamEvent>;
54
+
55
+ declare function askStripeDocs(question: string): Promise<DocsExpertResponse>;
56
+ declare function askStripeDocsStream(question: string): AsyncGenerator<StreamEvent>;
57
+
58
+ declare function askGitBookDocs(docsUrl: string, question: string): Promise<DocsExpertResponse>;
59
+ declare function askGitBookDocsStream(docsUrl: string, question: string): AsyncGenerator<StreamEvent>;
60
+
61
+ declare function askFernDocs(docsUrl: string, question: string): Promise<DocsExpertResponse>;
62
+ declare function askFernDocsStream(docsUrl: string, question: string): AsyncGenerator<StreamEvent>;
63
+
64
+ declare function askReadMeDocs(docsUrl: string, question: string): Promise<DocsExpertResponse>;
65
+ declare function askReadMeDocsStream(docsUrl: string, question: string): AsyncGenerator<StreamEvent>;
66
+
67
+ declare function askVercelDocs(question: string): Promise<DocsExpertResponse>;
68
+ declare function askVercelDocsStream(question: string): AsyncGenerator<StreamEvent>;
69
+
70
+ declare function askBetterAuthDocs(question: string): Promise<DocsExpertResponse>;
71
+ declare function askBetterAuthDocsStream(question: string): AsyncGenerator<StreamEvent>;
72
+
73
+ type ProviderName = "mintlify" | "gitbook" | "fern" | "readme" | "inkeep";
74
+ /**
75
+ * Score-based provider detection.
76
+ *
77
+ * Each provider gets a score based on how specific the matching signals are.
78
+ * Structural markers (asset paths, API endpoints, script sources) score higher
79
+ * than generic keyword mentions which may appear in page content or as embedded
80
+ * third-party widgets (e.g. Mintlify sites often embed Inkeep for search).
81
+ */
82
+ declare function detectProvider(url: string): Promise<ProviderName>;
83
+ interface ResolveResult {
84
+ provider: ProviderName;
85
+ fromCache: boolean;
86
+ }
87
+ declare function resolveProvider(url: string): Promise<ResolveResult>;
88
+
89
+ export { type DocsExpertOptions, type DocsExpertResponse, type Message, type ProviderName, type SearchResult, type StreamEvent, ask, askBetterAuthDocs, askBetterAuthDocsStream, askClaudeDocs, askClaudeDocsStream, askFernDocs, askFernDocsStream, askGitBookDocs, askGitBookDocsStream, askInkeepDocs, askInkeepDocsStream, askReadMeDocs, askReadMeDocsStream, askStream, askStripeDocs, askStripeDocsStream, askVercelDocs, askVercelDocsStream, createClient, detectProvider, resolveProvider };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{a as e,b as r,c as o,d as s,e as t,f as a,g as c,h as p,i as k,j as m,k as D,l as x,m as S,n as f,o as d,p as i,q as n,r as l,s as v,u,v as B}from"./chunk-A7S2BH5H.js";export{e as ask,l as askBetterAuthDocs,v as askBetterAuthDocsStream,s as askClaudeDocs,t as askClaudeDocsStream,x as askFernDocs,S as askFernDocsStream,m as askGitBookDocs,D as askGitBookDocsStream,a as askInkeepDocs,c as askInkeepDocsStream,f as askReadMeDocs,d as askReadMeDocsStream,r as askStream,p as askStripeDocs,k as askStripeDocsStream,i as askVercelDocs,n as askVercelDocsStream,o as createClient,u as detectProvider,B as resolveProvider};
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "docs-expert",
3
+ "version": "0.1.0",
4
+ "description": "Query any documentation site's AI assistant — CLI + library. Supports Mintlify, GitBook, Fern, ReadMe, Inkeep, Vercel, Stripe, and more.",
5
+ "author": "kalil0321 <kalil.bouzigues@gmail.com>",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "bin": {
11
+ "docs-expert": "dist/cli.js"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/index.js",
16
+ "types": "./dist/index.d.ts"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "sideEffects": false,
23
+ "keywords": [
24
+ "docs",
25
+ "documentation",
26
+ "ai",
27
+ "cli",
28
+ "mintlify",
29
+ "gitbook",
30
+ "fern",
31
+ "readme",
32
+ "inkeep",
33
+ "vercel",
34
+ "stripe",
35
+ "ask-ai",
36
+ "docs-search",
37
+ "developer-tools"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "dev": "tsup --watch",
42
+ "clean": "rimraf dist",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
45
+ "typecheck": "tsc",
46
+ "verify": "npm run typecheck && npm run test && npm run build",
47
+ "prepublishOnly": "npm run verify"
48
+ },
49
+ "dependencies": {
50
+ "@clack/prompts": "^0.9.1",
51
+ "chalk": "^5.6.2",
52
+ "marked": "^15.0.12",
53
+ "marked-terminal": "^7.3.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/marked-terminal": "^6.1.1",
57
+ "@types/node": "^22.12.0",
58
+ "@vitest/coverage-v8": "^3.2.4",
59
+ "rimraf": "^6.0.1",
60
+ "tsup": "^8.3.6",
61
+ "typescript": "^5.7.3",
62
+ "vitest": "^3.0.2"
63
+ },
64
+ "engines": {
65
+ "node": "^20.0.0 || >=22.0.0"
66
+ }
67
+ }