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 +21 -0
- package/README.md +177 -0
- package/dist/chunk-A7S2BH5H.js +12 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +63 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.js +1 -0
- package/package.json +67 -0
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":"([^&]+)"/))?.[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)});
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|