doc-fetch-cli 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -0
- package/SECURITY.md +84 -0
- package/bin/doc-fetch.js +37 -0
- package/bin/install.js +171 -0
- package/cmd/docfetch/main.go +54 -0
- package/dist/doc_fetch-1.0.1-py3-none-any.whl +0 -0
- package/dist/doc_fetch-1.0.1.tar.gz +0 -0
- package/doc-fetch +0 -0
- package/doc-fetch_darwin_amd64 +0 -0
- package/doc-fetch_linux_amd64 +0 -0
- package/doc-fetch_windows_amd64.exe +0 -0
- package/doc_fetch/__init__.py +6 -0
- package/doc_fetch/__main__.py +7 -0
- package/doc_fetch/cli.py +113 -0
- package/doc_fetch.egg-info/PKG-INFO +224 -0
- package/doc_fetch.egg-info/SOURCES.txt +19 -0
- package/doc_fetch.egg-info/dependency_links.txt +1 -0
- package/doc_fetch.egg-info/entry_points.txt +2 -0
- package/doc_fetch.egg-info/not-zip-safe +1 -0
- package/doc_fetch.egg-info/top_level.txt +1 -0
- package/docs/usage.md +67 -0
- package/examples/golang-example.sh +12 -0
- package/go.mod +11 -0
- package/go.sum +38 -0
- package/package.json +18 -0
- package/pkg/fetcher/classifier.go +50 -0
- package/pkg/fetcher/describer.go +61 -0
- package/pkg/fetcher/fetcher.go +332 -0
- package/pkg/fetcher/html2md.go +71 -0
- package/pkg/fetcher/llmtxt.go +36 -0
- package/pkg/fetcher/validator.go +109 -0
- package/pkg/fetcher/writer.go +32 -0
- package/pyproject.toml +37 -0
- package/setup.py +158 -0
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# DocFetch - Dynamic Documentation Fetcher š
|
|
2
|
+
|
|
3
|
+
**Transform entire documentation sites into AI-ready, single-file markdown with intelligent LLM.txt indexing**
|
|
4
|
+
|
|
5
|
+
Most AIs can't navigate documentation like humans do. They can't scroll through sections, click sidebar links, or explore related pages. **DocFetch solves this fundamental problem** by converting entire documentation sites into comprehensive, clean markdown files that contain every section and piece of information in a format that LLMs love.
|
|
6
|
+
|
|
7
|
+
## š Why DocFetch is Essential for AI Development
|
|
8
|
+
|
|
9
|
+
### š¤ **AI/LLM Optimization**
|
|
10
|
+
- **Single-file consumption**: No more fragmented context across multiple pages
|
|
11
|
+
- **Clean, structured markdown**: Perfect token efficiency for LLM context windows
|
|
12
|
+
- **Intelligent LLM.txt generation**: AI-friendly index with semantic categorization
|
|
13
|
+
- **Noise removal**: Automatically strips navigation, headers, footers, ads, and buttons
|
|
14
|
+
|
|
15
|
+
### ā” **Developer Productivity**
|
|
16
|
+
- **One command automation**: Replace hours of manual copy-pasting with a single CLI command
|
|
17
|
+
- **Complete documentation access**: Give your AI agents full access to official documentation
|
|
18
|
+
- **Consistent formatting**: Uniform structure across different documentation sites
|
|
19
|
+
- **Version control friendly**: Markdown files work perfectly with Git
|
|
20
|
+
|
|
21
|
+
### šÆ **Smart Content Intelligence**
|
|
22
|
+
- **Automatic page classification**: Identifies APIs, guides, references, and examples
|
|
23
|
+
- **Semantic descriptions**: Generates concise, relevant descriptions for each section
|
|
24
|
+
- **URL preservation**: Maintains original source links for verification
|
|
25
|
+
- **Adaptive content extraction**: Works with diverse documentation site structures
|
|
26
|
+
|
|
27
|
+
### š§ **Production Ready**
|
|
28
|
+
- **Concurrent fetching**: Fast downloads with configurable concurrency
|
|
29
|
+
- **Respectful crawling**: Honors robots.txt and includes rate limiting
|
|
30
|
+
- **Cross-platform**: Works on Windows, macOS, and Linux
|
|
31
|
+
- **Multiple installation options**: NPM, Go install, or direct binary download
|
|
32
|
+
|
|
33
|
+
## š¦ Installation
|
|
34
|
+
|
|
35
|
+
### PyPI (Recommended for Python developers) ⨠NEW
|
|
36
|
+
```bash
|
|
37
|
+
pip install doc-fetch
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### NPM (Recommended for JavaScript/Node.js developers)
|
|
41
|
+
```bash
|
|
42
|
+
npm install -g doc-fetch
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Go (For Go developers)
|
|
46
|
+
```bash
|
|
47
|
+
go install github.com/AlphaTechini/doc-fetch/cmd/docfetch@latest
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Direct Binary Download
|
|
51
|
+
Visit [Releases](https://github.com/AlphaTechini/doc-fetch/releases) and download your platform's binary.
|
|
52
|
+
|
|
53
|
+
## šÆ Usage
|
|
54
|
+
|
|
55
|
+
### Basic Usage
|
|
56
|
+
```bash
|
|
57
|
+
# Fetch entire documentation site to single markdown file
|
|
58
|
+
doc-fetch --url https://golang.org/doc/ --output ./docs/golang-full.md
|
|
59
|
+
|
|
60
|
+
# With LLM.txt generation for AI optimization
|
|
61
|
+
doc-fetch --url https://react.dev/learn --output docs.md --llm-txt
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Advanced Usage
|
|
65
|
+
```bash
|
|
66
|
+
# Comprehensive documentation fetch with all features
|
|
67
|
+
doc-fetch \
|
|
68
|
+
--url https://docs.example.com \
|
|
69
|
+
--output ./internal/docs.md \
|
|
70
|
+
--depth 4 \
|
|
71
|
+
--concurrent 10 \
|
|
72
|
+
--llm-txt \
|
|
73
|
+
--user-agent "MyBot/1.0"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Command Options
|
|
77
|
+
| Flag | Short | Description | Default |
|
|
78
|
+
|------|-------|-------------|---------|
|
|
79
|
+
| `--url` | `-u` | Base URL to fetch documentation from | **Required** |
|
|
80
|
+
| `--output` | `-o` | Output file path | `docs.md` |
|
|
81
|
+
| `--depth` | `-d` | Maximum crawl depth | `2` |
|
|
82
|
+
| `--concurrent` | `-c` | Number of concurrent fetchers | `3` |
|
|
83
|
+
| `--llm-txt` | | Generate AI-friendly llm.txt index | `false` |
|
|
84
|
+
| `--user-agent` | | Custom user agent string | `DocFetch/1.0` |
|
|
85
|
+
|
|
86
|
+
## š Output Files
|
|
87
|
+
|
|
88
|
+
When using `--llm-txt`, DocFetch generates two files:
|
|
89
|
+
|
|
90
|
+
### `docs.md` - Complete Documentation
|
|
91
|
+
```markdown
|
|
92
|
+
# Documentation
|
|
93
|
+
|
|
94
|
+
This file contains documentation fetched by DocFetch.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Getting Started
|
|
99
|
+
|
|
100
|
+
This guide covers installation, setup, and first program...
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Language Specification
|
|
105
|
+
|
|
106
|
+
Complete Go language specification and syntax...
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `docs.llm.txt` - AI-Friendly Index
|
|
110
|
+
```txt
|
|
111
|
+
# llm.txt - AI-friendly documentation index
|
|
112
|
+
|
|
113
|
+
[GUIDE] Getting Started
|
|
114
|
+
https://golang.org/doc/install
|
|
115
|
+
Covers installation, setup, and first program.
|
|
116
|
+
|
|
117
|
+
[REFERENCE] Language Specification
|
|
118
|
+
https://golang.org/ref/spec
|
|
119
|
+
Complete Go language specification and syntax.
|
|
120
|
+
|
|
121
|
+
[API] net/http
|
|
122
|
+
https://pkg.go.dev/net/http
|
|
123
|
+
HTTP client/server implementation.
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## š Real-World Examples
|
|
127
|
+
|
|
128
|
+
### Fetch Go Documentation
|
|
129
|
+
```bash
|
|
130
|
+
doc-fetch --url https://golang.org/doc/ --output ./docs/go-documentation.md --depth 4 --llm-txt
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Fetch React Documentation
|
|
134
|
+
```bash
|
|
135
|
+
doc-fetch --url https://react.dev/learn --output ./docs/react-learn.md --concurrent 10 --llm-txt
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Fetch Your Own Project Docs
|
|
139
|
+
```bash
|
|
140
|
+
doc-fetch --url https://your-project.com/docs/ --output ./internal/docs.md --llm-txt
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## š¤ How LLM.txt Supercharges Your AI
|
|
144
|
+
|
|
145
|
+
The generated `llm.txt` file acts as a **semantic roadmap** for your AI agents:
|
|
146
|
+
|
|
147
|
+
1. **Precise Navigation**: Agents can query specific sections without scanning entire documents
|
|
148
|
+
2. **Context Awareness**: Know whether they're looking at an API reference vs. a tutorial
|
|
149
|
+
3. **Efficient Retrieval**: Jump directly to relevant content based on query intent
|
|
150
|
+
4. **Source Verification**: Always maintain links back to original documentation
|
|
151
|
+
|
|
152
|
+
**Example AI Prompt Enhancement:**
|
|
153
|
+
```
|
|
154
|
+
Instead of: "What does the net/http package do?"
|
|
155
|
+
Your AI can now: "Check the [API] net/http section in llm.txt for HTTP client/server implementation details"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## šļø How It Works
|
|
159
|
+
|
|
160
|
+
1. **Link Discovery**: Parses the base URL to find all internal documentation links
|
|
161
|
+
2. **Content Fetching**: Downloads all pages concurrently with respect for robots.txt
|
|
162
|
+
3. **HTML Cleaning**: Removes non-content elements (navigation, headers, footers, etc.)
|
|
163
|
+
4. **Markdown Conversion**: Converts cleaned HTML to structured markdown
|
|
164
|
+
5. **Intelligent Classification**: Categorizes pages as API, GUIDE, REFERENCE, or EXAMPLE
|
|
165
|
+
6. **Description Generation**: Creates concise, relevant descriptions for each section
|
|
166
|
+
7. **Single File Output**: Combines all documentation into one comprehensive file
|
|
167
|
+
8. **LLM.txt Generation**: Creates AI-friendly index with semantic categorization
|
|
168
|
+
|
|
169
|
+
## š Future Features
|
|
170
|
+
|
|
171
|
+
- **Incremental updates**: Only fetch changed pages on subsequent runs
|
|
172
|
+
- **Custom selectors**: Allow users to specify content areas for different sites
|
|
173
|
+
- **Multiple formats**: Support PDF, JSON, and other output formats
|
|
174
|
+
- **Token counting**: Estimate token usage for LLM context planning
|
|
175
|
+
- **Advanced classification**: Machine learning-based page type detection
|
|
176
|
+
|
|
177
|
+
## š” Why This Exists
|
|
178
|
+
|
|
179
|
+
Traditional documentation sites are designed for **human navigation**, not **AI consumption**. When working with LLMs, you often need to manually copy-paste multiple sections or provide incomplete context. DocFetch automates this process, giving your AI agents complete access to documentation without the manual overhead.
|
|
180
|
+
|
|
181
|
+
**Stop wasting time copying documentation. Start building AI agents with complete knowledge.**
|
|
182
|
+
|
|
183
|
+
## š¤ Contributing
|
|
184
|
+
|
|
185
|
+
Contributions are welcome! Please open an issue or pull request on GitHub.
|
|
186
|
+
|
|
187
|
+
## š License
|
|
188
|
+
|
|
189
|
+
MIT License
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
**Built with ā¤ļø for AI developers who deserve better documentation access**
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Security Features
|
|
4
|
+
|
|
5
|
+
DocFetch includes several built-in security protections:
|
|
6
|
+
|
|
7
|
+
### ā
Path Traversal Protection
|
|
8
|
+
- Output files can only be written within the current working directory
|
|
9
|
+
- Relative paths (`../`) are blocked
|
|
10
|
+
- Absolute paths outside the current directory are rejected
|
|
11
|
+
|
|
12
|
+
### ā
SSRF (Server-Side Request Forgery) Protection
|
|
13
|
+
- Only HTTP/HTTPS URLs are allowed
|
|
14
|
+
- Private IP addresses (192.168.x.x, 10.x.x.x, etc.) are blocked
|
|
15
|
+
- Localhost and loopback addresses are blocked
|
|
16
|
+
- Internal network access is prevented
|
|
17
|
+
|
|
18
|
+
### ā
Rate Limiting
|
|
19
|
+
- Maximum 10 requests per second to avoid overwhelming servers
|
|
20
|
+
- Respectful crawling behavior
|
|
21
|
+
|
|
22
|
+
### ā
Input Validation
|
|
23
|
+
- URL validation and sanitization
|
|
24
|
+
- Output path validation
|
|
25
|
+
- Parameter bounds checking (max depth: 10, max workers: 20)
|
|
26
|
+
|
|
27
|
+
### ā
Content Safety
|
|
28
|
+
- HTML content is cleaned of scripts and dangerous elements
|
|
29
|
+
- XSS patterns are filtered out
|
|
30
|
+
- Only safe markdown is generated
|
|
31
|
+
|
|
32
|
+
## Safe Usage Guidelines
|
|
33
|
+
|
|
34
|
+
### Command Line Usage
|
|
35
|
+
```bash
|
|
36
|
+
# ā
SAFE - relative path in current directory
|
|
37
|
+
doc-fetch --url https://example.com --output docs.md
|
|
38
|
+
|
|
39
|
+
# ā
SAFE - subdirectory in current directory
|
|
40
|
+
doc-fetch --url https://example.com --output ./docs/site.md
|
|
41
|
+
|
|
42
|
+
# ā BLOCKED - path traversal attempt
|
|
43
|
+
doc-fetch --url https://example.com --output ../../etc/passwd
|
|
44
|
+
|
|
45
|
+
# ā BLOCKED - absolute path outside current directory
|
|
46
|
+
doc-fetch --url https://example.com --output /tmp/malicious.md
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### URL Restrictions
|
|
50
|
+
```bash
|
|
51
|
+
# ā
SAFE - public HTTPS site
|
|
52
|
+
doc-fetch --url https://golang.org/doc/ --output docs.md
|
|
53
|
+
|
|
54
|
+
# ā BLOCKED - private IP address
|
|
55
|
+
doc-fetch --url http://192.168.1.1/admin --output docs.md
|
|
56
|
+
|
|
57
|
+
# ā BLOCKED - localhost
|
|
58
|
+
doc-fetch --url http://localhost:8080/api --output docs.md
|
|
59
|
+
|
|
60
|
+
# ā BLOCKED - non-HTTP protocol
|
|
61
|
+
doc-fetch --url file:///etc/passwd --output docs.md
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Reporting Security Issues
|
|
65
|
+
|
|
66
|
+
If you discover a security vulnerability in DocFetch, please:
|
|
67
|
+
|
|
68
|
+
1. **Do not disclose publicly** until it's been addressed
|
|
69
|
+
2. Contact the maintainer directly at [your email]
|
|
70
|
+
3. Provide detailed reproduction steps
|
|
71
|
+
4. Allow reasonable time for patch development
|
|
72
|
+
|
|
73
|
+
## Security Updates
|
|
74
|
+
|
|
75
|
+
Security patches will be released as soon as possible after vulnerability confirmation. Users are encouraged to keep DocFetch updated to the latest version.
|
|
76
|
+
|
|
77
|
+
## Dependencies Security
|
|
78
|
+
|
|
79
|
+
DocFetch uses the following dependencies with known security track records:
|
|
80
|
+
- `github.com/PuerkitoBio/goquery` - HTML parsing
|
|
81
|
+
- `github.com/yuin/goldmark` - Markdown processing
|
|
82
|
+
- Standard Go libraries (`net/http`, `sync`, etc.)
|
|
83
|
+
|
|
84
|
+
All dependencies are regularly audited and kept up-to-date.
|
package/bin/doc-fetch.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawn } = require('child_process');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
// Determine binary path based on platform
|
|
7
|
+
const binDir = path.join(__dirname, '..');
|
|
8
|
+
const binaryName = os.platform() === 'win32' ? 'doc-fetch.exe' : 'doc-fetch';
|
|
9
|
+
const binaryPath = path.join(binDir, binaryName);
|
|
10
|
+
|
|
11
|
+
// Check if binary exists
|
|
12
|
+
if (!require('fs').existsSync(binaryPath)) {
|
|
13
|
+
console.error('ā doc-fetch binary not found!');
|
|
14
|
+
console.error('š” Please run: npm install doc-fetch');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
|
|
20
|
+
// Spawn the Go binary
|
|
21
|
+
const child = spawn(binaryPath, args, {
|
|
22
|
+
stdio: 'inherit'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
child.on('error', (err) => {
|
|
26
|
+
if (err.code === 'ENOENT') {
|
|
27
|
+
console.error('ā doc-fetch binary not found!');
|
|
28
|
+
console.error('š” Please run: npm install doc-fetch');
|
|
29
|
+
} else {
|
|
30
|
+
console.error('ā Failed to start doc-fetch:', err.message);
|
|
31
|
+
}
|
|
32
|
+
process.exit(1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
child.on('exit', (code) => {
|
|
36
|
+
process.exit(code || 0);
|
|
37
|
+
});
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const { pipeline } = require('stream');
|
|
8
|
+
const { promisify } = require('util');
|
|
9
|
+
|
|
10
|
+
const finished = promisify(pipeline);
|
|
11
|
+
|
|
12
|
+
// Security: Validate and sanitize output paths
|
|
13
|
+
function validateOutputPath(filePath) {
|
|
14
|
+
// Resolve to absolute path
|
|
15
|
+
const absPath = path.resolve(filePath);
|
|
16
|
+
|
|
17
|
+
// Get current working directory
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
|
|
20
|
+
// Ensure the path is within the current directory or a subdirectory
|
|
21
|
+
if (!absPath.startsWith(cwd)) {
|
|
22
|
+
throw new Error('Output path must be within current directory or subdirectories');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Block dangerous patterns
|
|
26
|
+
if (filePath.includes('..') || filePath.includes('~')) {
|
|
27
|
+
throw new Error('Relative paths with ".." or "~" are not allowed');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return absPath;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Security: Validate URL
|
|
34
|
+
function validateURL(urlStr) {
|
|
35
|
+
try {
|
|
36
|
+
const url = new URL(urlStr);
|
|
37
|
+
|
|
38
|
+
// Only allow HTTP/HTTPS
|
|
39
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
40
|
+
throw new Error('Only HTTP and HTTPS URLs are allowed');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check for private IP ranges (basic SSRF protection)
|
|
44
|
+
const hostname = url.hostname.toLowerCase();
|
|
45
|
+
const privatePatterns = [
|
|
46
|
+
'127.', // localhost
|
|
47
|
+
'192.168.', // private network
|
|
48
|
+
'10.', // private network
|
|
49
|
+
'172.16.', '172.17.', '172.18.', '172.19.',
|
|
50
|
+
'172.20.', '172.21.', '172.22.', '172.23.',
|
|
51
|
+
'172.24.', '172.25.', '172.26.', '172.27.',
|
|
52
|
+
'172.28.', '172.29.', '172.30.', '172.31.',
|
|
53
|
+
'localhost',
|
|
54
|
+
'::1' // IPv6 localhost
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
for (const pattern of privatePatterns) {
|
|
58
|
+
if (hostname.startsWith(pattern)) {
|
|
59
|
+
throw new Error('Private/internal URLs are not allowed');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return true;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(`Invalid URL: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function getBinaryUrl() {
|
|
70
|
+
const platform = os.platform();
|
|
71
|
+
const arch = os.arch();
|
|
72
|
+
|
|
73
|
+
// Map to Go build targets
|
|
74
|
+
let goos, goarch;
|
|
75
|
+
switch(platform) {
|
|
76
|
+
case 'win32': goos = 'windows'; break;
|
|
77
|
+
case 'darwin': goos = 'darwin'; break;
|
|
78
|
+
default: goos = 'linux';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
switch(arch) {
|
|
82
|
+
case 'x64': goarch = 'amd64'; break;
|
|
83
|
+
case 'arm64': goarch = 'arm64'; break;
|
|
84
|
+
default: goarch = 'amd64';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
url: `https://github.com/AlphaTechini/doc-fetch/releases/download/v1.0.0/doc-fetch_${goos}_${goarch}`,
|
|
89
|
+
filename: platform === 'win32' ? 'doc-fetch.exe' : 'doc-fetch'
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function downloadBinary() {
|
|
94
|
+
const binDir = path.join(__dirname, '..');
|
|
95
|
+
if (!fs.existsSync(binDir)) {
|
|
96
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const { url, filename } = await getBinaryUrl();
|
|
100
|
+
|
|
101
|
+
// Validate the download URL
|
|
102
|
+
validateURL(url);
|
|
103
|
+
|
|
104
|
+
// Validate and sanitize the binary path
|
|
105
|
+
const binaryPath = validateOutputPath(path.join(binDir, filename));
|
|
106
|
+
|
|
107
|
+
console.log('š„ Downloading doc-fetch binary...');
|
|
108
|
+
console.log(` Platform: ${os.platform()} ${os.arch()}`);
|
|
109
|
+
console.log(` URL: ${url}`);
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const response = await new Promise((resolve, reject) => {
|
|
113
|
+
const req = https.get(url, (res) => {
|
|
114
|
+
if (res.statusCode === 404) {
|
|
115
|
+
reject(new Error('Binary not found for your platform'));
|
|
116
|
+
} else if (res.statusCode !== 200) {
|
|
117
|
+
reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
|
|
118
|
+
} else {
|
|
119
|
+
resolve(res);
|
|
120
|
+
}
|
|
121
|
+
}).on('error', reject);
|
|
122
|
+
|
|
123
|
+
// Set timeout for security
|
|
124
|
+
req.setTimeout(30000, () => {
|
|
125
|
+
req.destroy(new Error('Download timeout'));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Create write stream with secure permissions
|
|
130
|
+
const writeStream = fs.createWriteStream(binaryPath, { mode: 0o700 });
|
|
131
|
+
await finished(response, writeStream);
|
|
132
|
+
|
|
133
|
+
console.log('ā
Binary downloaded successfully!');
|
|
134
|
+
return true;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error('ā Failed to download binary:', error.message);
|
|
137
|
+
console.log('š Falling back to Go build from source...');
|
|
138
|
+
|
|
139
|
+
// Fallback: build from source if Go is available
|
|
140
|
+
try {
|
|
141
|
+
execSync('go version', { stdio: 'pipe' });
|
|
142
|
+
console.log('šļø Building from source...');
|
|
143
|
+
|
|
144
|
+
// Validate build path
|
|
145
|
+
const buildPath = validateOutputPath(path.join(binDir, 'doc-fetch'));
|
|
146
|
+
execSync(`go build -o ${buildPath} ./cmd/docfetch`, {
|
|
147
|
+
stdio: 'inherit',
|
|
148
|
+
cwd: path.join(__dirname, '..')
|
|
149
|
+
});
|
|
150
|
+
console.log('ā
Built successfully from source!');
|
|
151
|
+
return true;
|
|
152
|
+
} catch (buildError) {
|
|
153
|
+
console.error('ā Go not found or build failed.');
|
|
154
|
+
console.error('š” Please install Go (https://golang.org/dl/) or download the binary manually.');
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Run the installation
|
|
161
|
+
(async () => {
|
|
162
|
+
try {
|
|
163
|
+
const success = await downloadBinary();
|
|
164
|
+
if (!success) {
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error('Installation failed:', error.message);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
})();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"flag"
|
|
5
|
+
"log"
|
|
6
|
+
"strings"
|
|
7
|
+
|
|
8
|
+
"github.com/AlphaTechini/doc-fetch/pkg/fetcher"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func main() {
|
|
12
|
+
url := flag.String("url", "", "Base URL to fetch documentation from")
|
|
13
|
+
output := flag.String("output", "docs.md", "Output file path")
|
|
14
|
+
depth := flag.Int("depth", 2, "Maximum crawl depth")
|
|
15
|
+
concurrent := flag.Int("concurrent", 3, "Concurrent fetchers")
|
|
16
|
+
userAgent := flag.String("user-agent", "DocFetch/1.0", "Custom user agent")
|
|
17
|
+
llmTxt := flag.Bool("llm-txt", false, "Generate llm.txt index file")
|
|
18
|
+
|
|
19
|
+
flag.Parse()
|
|
20
|
+
|
|
21
|
+
if *url == "" {
|
|
22
|
+
log.Fatal("Error: URL is required\nUsage: doc-fetch --url <base-url> --output <file-path>")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Validate configuration for security
|
|
26
|
+
config := fetcher.Config{
|
|
27
|
+
BaseURL: *url,
|
|
28
|
+
OutputPath: *output,
|
|
29
|
+
MaxDepth: *depth,
|
|
30
|
+
Workers: *concurrent,
|
|
31
|
+
UserAgent: *userAgent,
|
|
32
|
+
GenerateLLMTxt: *llmTxt,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if err := fetcher.ValidateConfig(&config); err != nil {
|
|
36
|
+
log.Fatalf("Configuration error: %v", err)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
err := fetcher.Run(config)
|
|
40
|
+
if err != nil {
|
|
41
|
+
log.Fatalf("Failed to fetch documentation: %v", err)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
log.Printf("Documentation successfully saved to %s", *output)
|
|
45
|
+
if *llmTxt {
|
|
46
|
+
llmTxtPath := *output
|
|
47
|
+
if strings.HasSuffix(*output, ".md") {
|
|
48
|
+
llmTxtPath = strings.TrimSuffix(*output, ".md") + ".llm.txt"
|
|
49
|
+
} else {
|
|
50
|
+
llmTxtPath = *output + ".llm.txt"
|
|
51
|
+
}
|
|
52
|
+
log.Printf("LLM.txt index generated: %s", llmTxtPath)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
Binary file
|
|
Binary file
|
package/doc-fetch
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/doc_fetch/cli.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
DocFetch CLI wrapper for Python.
|
|
4
|
+
|
|
5
|
+
This module provides a Python interface to the Go-based DocFetch binary.
|
|
6
|
+
It handles downloading the appropriate binary for your platform and
|
|
7
|
+
executing it with the provided arguments.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import subprocess
|
|
13
|
+
import platform
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
# Get the directory where this script is located
|
|
17
|
+
SCRIPT_DIR = Path(__file__).parent
|
|
18
|
+
BIN_DIR = SCRIPT_DIR / "bin"
|
|
19
|
+
BINARY_NAME = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_binary_name():
|
|
23
|
+
"""Get the appropriate binary name for the current platform."""
|
|
24
|
+
system = platform.system().lower()
|
|
25
|
+
machine = platform.machine().lower()
|
|
26
|
+
|
|
27
|
+
# Map machine architectures
|
|
28
|
+
arch_map = {
|
|
29
|
+
'x86_64': 'amd64',
|
|
30
|
+
'amd64': 'amd64',
|
|
31
|
+
'arm64': 'arm64',
|
|
32
|
+
'aarch64': 'arm64'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
arch = arch_map.get(machine, 'amd64')
|
|
36
|
+
|
|
37
|
+
if system == 'windows':
|
|
38
|
+
return f'doc-fetch_windows_{arch}.exe'
|
|
39
|
+
elif system == 'darwin':
|
|
40
|
+
return f'doc-fetch_darwin_{arch}'
|
|
41
|
+
else: # linux and others
|
|
42
|
+
return f'doc-fetch_linux_{arch}'
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def download_binary():
|
|
46
|
+
"""Download the appropriate binary from GitHub releases."""
|
|
47
|
+
import urllib.request
|
|
48
|
+
import ssl
|
|
49
|
+
|
|
50
|
+
binary_name = get_binary_name()
|
|
51
|
+
binary_path = BIN_DIR / binary_name
|
|
52
|
+
|
|
53
|
+
# Create bin directory if it doesn't exist
|
|
54
|
+
BIN_DIR.mkdir(exist_ok=True)
|
|
55
|
+
|
|
56
|
+
# URL for the binary
|
|
57
|
+
url = f"https://github.com/AlphaTechini/doc-fetch/releases/download/v1.0.0/{binary_name}"
|
|
58
|
+
|
|
59
|
+
print(f"š„ Downloading doc-fetch binary for {platform.system()} {platform.machine()}...")
|
|
60
|
+
print(f" URL: {url}")
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Create SSL context to handle certificates
|
|
64
|
+
ssl_context = ssl.create_default_context()
|
|
65
|
+
|
|
66
|
+
# Download the binary
|
|
67
|
+
with urllib.request.urlopen(url, context=ssl_context) as response:
|
|
68
|
+
with open(binary_path, 'wb') as f:
|
|
69
|
+
f.write(response.read())
|
|
70
|
+
|
|
71
|
+
# Make executable on Unix-like systems
|
|
72
|
+
if platform.system() != 'Windows':
|
|
73
|
+
os.chmod(binary_path, 0o755)
|
|
74
|
+
|
|
75
|
+
print("ā
Binary downloaded successfully!")
|
|
76
|
+
return binary_path
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f"ā Failed to download binary: {e}")
|
|
80
|
+
print("š” Please ensure you have internet access and can reach GitHub.")
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main():
|
|
85
|
+
"""Main entry point for the doc-fetch CLI."""
|
|
86
|
+
global BINARY_NAME
|
|
87
|
+
|
|
88
|
+
# Get binary path
|
|
89
|
+
binary_name = get_binary_name()
|
|
90
|
+
binary_path = BIN_DIR / binary_name
|
|
91
|
+
|
|
92
|
+
# Download binary if it doesn't exist
|
|
93
|
+
if not binary_path.exists():
|
|
94
|
+
binary_path = download_binary()
|
|
95
|
+
|
|
96
|
+
# Execute the binary with all arguments
|
|
97
|
+
try:
|
|
98
|
+
result = subprocess.run([str(binary_path)] + sys.argv[1:], check=False)
|
|
99
|
+
sys.exit(result.returncode)
|
|
100
|
+
except FileNotFoundError:
|
|
101
|
+
print("ā doc-fetch binary not found!")
|
|
102
|
+
print("š” This shouldn't happen. Please reinstall the package.")
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
except KeyboardInterrupt:
|
|
105
|
+
print("\nā ļø Interrupted by user")
|
|
106
|
+
sys.exit(130)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
print(f"ā Failed to execute doc-fetch: {e}")
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
main()
|