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 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.
@@ -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
package/doc-fetch ADDED
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,6 @@
1
+ """
2
+ DocFetch - Dynamic documentation fetching CLI for AI/LLM consumption.
3
+
4
+ This package provides a Python wrapper around the Go-based DocFetch binary,
5
+ enabling easy installation and usage via pip.
6
+ """
@@ -0,0 +1,7 @@
1
+ """
2
+ Module entry point for doc-fetch.
3
+ """
4
+ from .cli import main
5
+
6
+ if __name__ == "__main__":
7
+ main()
@@ -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()