@involvex/bun-crawl 0.0.0-stage → 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.
@@ -0,0 +1,452 @@
1
+ # bun-crawl Implementation Plan
2
+
3
+ ## Project Overview
4
+
5
+ **Name:** `bun-crawl` (CLI binary name)
6
+ **Package Manager:** Bun
7
+ **CLI Framework:** `citty`
8
+ **Language:** TypeScript
9
+
10
+ ---
11
+
12
+ ## Directory Structure
13
+
14
+ ```
15
+ bun-crawl/
16
+ ├── src/
17
+ │ ├── index.ts # Entry point, exports CLI
18
+ │ ├── cli.ts # CLI definition (citty)
19
+ │ ├── core/
20
+ │ │ ├── fetcher.ts # HTTP fetch wrapper with config
21
+ │ │ ├── cleaner.ts # linkedom + @mozilla/readability
22
+ │ │ ├── markdown.ts # turndown configuration
23
+ │ │ └── crawler.ts # HTMLRewriter-based link extraction & recursion
24
+ │ ├── ai/
25
+ │ │ ├── interface.ts # AIExtractor interface
26
+ │ │ ├── null-ai.ts # NoOpAIExtractor (pass-through)
27
+ │ │ └── zen-ai.ts # Template/Stub for OpenCode Zen (commented)
28
+ │ └── types.ts # Shared types/interfaces
29
+ ├── package.json
30
+ ├── bunfig.toml
31
+ ├── tsconfig.json
32
+ └── README.md
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Phase 1: Project Setup
38
+
39
+ ### 1.1 Initialize Bun Project
40
+
41
+ ```bash
42
+ bun init -y
43
+ ```
44
+
45
+ ### 1.2 Install Dependencies
46
+
47
+ ```bash
48
+ # Production dependencies
49
+ bun add citty turndown @mozilla/readability linkedom
50
+
51
+ # Dev dependencies
52
+ bun add -d @types/turndown @types/mozilla-readability typescript
53
+ ```
54
+
55
+ ### 1.3 Configure `package.json`
56
+
57
+ ```json
58
+ {
59
+ "name": "bun-crawl",
60
+ "version": "0.1.0",
61
+ "type": "module",
62
+ "main": "src/index.ts",
63
+ "bin": {
64
+ "bun-crawl": "./src/cli.ts"
65
+ },
66
+ "scripts": {
67
+ "dev": "bun run src/cli.ts",
68
+ "build": "bun build --compile --minify ./src/index.ts --outfile bun-crawl",
69
+ "test": "bun test"
70
+ },
71
+ "dependencies": {
72
+ "citty": "^0.1.6",
73
+ "turndown": "^7.2.0",
74
+ "@mozilla/readability": "^0.5.0",
75
+ "linkedom": "^0.18.0"
76
+ },
77
+ "devDependencies": {
78
+ "@types/turndown": "^5.0.4",
79
+ "@types/mozilla-readability": "^0.1.0",
80
+ "typescript": "^5.5.0"
81
+ }
82
+ }
83
+ ```
84
+
85
+ ### 1.4 Configure `tsconfig.json`
86
+
87
+ ```json
88
+ {
89
+ "compilerOptions": {
90
+ "target": "ES2022",
91
+ "module": "ESNext",
92
+ "moduleResolution": "bundler",
93
+ "lib": ["ES2022"],
94
+ "strict": true,
95
+ "esModuleInterop": true,
96
+ "skipLibCheck": true,
97
+ "forceConsistentCasingInFileNames": true,
98
+ "resolveJsonModule": true,
99
+ "declaration": true,
100
+ "outDir": "./dist"
101
+ },
102
+ "include": ["src/**/*"],
103
+ "exclude": ["node_modules", "dist"]
104
+ }
105
+ ```
106
+
107
+ ### 1.5 Configure `bunfig.toml`
108
+
109
+ ```toml
110
+ [install]
111
+ cache = true
112
+
113
+ [run]
114
+ env = { NODE_ENV = "development" }
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Phase 2: Core Processing Pipeline
120
+
121
+ ### 2.1 Shared Types (`src/types.ts`)
122
+
123
+ ```typescript
124
+ export interface ScrapeResult {
125
+ url: string
126
+ title: string
127
+ markdown: string
128
+ html: string
129
+ metadata: {
130
+ description?: string
131
+ author?: string
132
+ publishedTime?: string
133
+ [key: string]: unknown
134
+ }
135
+ }
136
+
137
+ export interface CrawlOptions {
138
+ depth: number
139
+ limit: number
140
+ sameDomain: boolean
141
+ }
142
+
143
+ export interface CrawlResult {
144
+ pages: ScrapeResult[]
145
+ errors: Array<{url: string; error: string}>
146
+ stats: {
147
+ total: number
148
+ succeeded: number
149
+ failed: number
150
+ }
151
+ }
152
+
153
+ export interface FetcherOptions {
154
+ timeout?: number
155
+ userAgent?: string
156
+ headers?: Record<string, string>
157
+ followRedirects?: boolean
158
+ }
159
+ ```
160
+
161
+ ### 2.2 Fetcher (`src/core/fetcher.ts`)
162
+
163
+ - Wrapper around native `fetch()`
164
+ - Configurable timeout, user-agent, headers
165
+ - Error handling with descriptive messages
166
+ - Returns raw HTML + response metadata
167
+
168
+ ```typescript
169
+ export class Fetcher {
170
+ constructor(private options: FetcherOptions = {}) {}
171
+
172
+ async fetch(
173
+ url: string,
174
+ ): Promise<{html: string; url: string; status: number}> {
175
+ // Implementation with AbortController for timeout
176
+ }
177
+ }
178
+ ```
179
+
180
+ ### 2.3 Cleaner (`src/core/cleaner.ts`)
181
+
182
+ - Uses `linkedom` to create DOM from HTML
183
+ - Uses `@mozilla/readability` to extract main content
184
+ - Returns cleaned HTML + metadata (title, byline, etc.)
185
+
186
+ ```typescript
187
+ export class Cleaner {
188
+ clean(
189
+ html: string,
190
+ url: string,
191
+ ): {content: string; title: string; metadata: Record<string, unknown>} {
192
+ // JSDOM via linkedom + Readability
193
+ }
194
+ }
195
+ ```
196
+
197
+ ### 2.4 Markdown Converter (`src/core/markdown.ts`)
198
+
199
+ - Configures `turndown` with sensible defaults
200
+ - Handles: tables, code blocks (fenced), links, images, headings
201
+ - Options for: heading style, code block style, bullet list marker
202
+
203
+ ```typescript
204
+ export class MarkdownConverter {
205
+ private turndown: TurndownService
206
+
207
+ constructor(options?: TurndownOptions) {}
208
+
209
+ convert(html: string): string {
210
+ return this.turndown.turndown(html)
211
+ }
212
+ }
213
+ ```
214
+
215
+ ### 2.5 Crawler (`src/core/crawler.ts`)
216
+
217
+ - Uses Bun's native `HTMLRewriter` for streaming link extraction
218
+ - Recursive crawling with depth/limit control
219
+ - Same-domain filtering
220
+ - Deduplication of URLs
221
+ - Concurrent fetching with configurable concurrency
222
+
223
+ ```typescript
224
+ export class Crawler {
225
+ constructor(
226
+ private fetcher: Fetcher,
227
+ private cleaner: Cleaner,
228
+ private converter: MarkdownConverter,
229
+ private options: CrawlOptions,
230
+ ) {}
231
+
232
+ async crawl(startUrl: string): Promise<CrawlResult> {
233
+ // BFS with HTMLRewriter for link extraction
234
+ }
235
+
236
+ private extractLinks(html: string, baseUrl: string): string[] {
237
+ // HTMLRewriter implementation
238
+ }
239
+ }
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Phase 3: AI Extractor Pattern
245
+
246
+ ### 3.1 Interface (`src/ai/interface.ts`)
247
+
248
+ ```typescript
249
+ export interface AIExtractor {
250
+ extract<T = string>(markdown: string, schemaPrompt: string): Promise<T>
251
+ }
252
+
253
+ export interface AIExtractorConfig {
254
+ // Base config for all extractors
255
+ }
256
+ ```
257
+
258
+ ### 3.2 NullAIExtractor (`src/ai/null-ai.ts`) - Default
259
+
260
+ ```typescript
261
+ export class NullAIExtractor implements AIExtractor {
262
+ async extract<T = string>(
263
+ markdown: string,
264
+ _schemaPrompt: string,
265
+ ): Promise<T> {
266
+ return markdown as T // Pass-through
267
+ }
268
+ }
269
+ ```
270
+
271
+ ### 3.3 ZenAIExtractor Template (`src/ai/zen-ai.ts`) - Stub/Template
272
+
273
+ ```typescript
274
+ // TODO: Implement when OpenCode Zen API is available
275
+ // This is a template for future OpenAI-compatible Chat Completions integration
276
+
277
+ /*
278
+ export interface ZenAIConfig {
279
+ endpoint: string; // e.g. "https://api.opencode.ai/v1/chat/completions"
280
+ apiKey: string;
281
+ model?: string; // e.g. "gpt-4o", "claude-3.5-sonnet"
282
+ temperature?: number;
283
+ maxTokens?: number;
284
+ }
285
+
286
+ export class ZenAIExtractor implements AIExtractor {
287
+ constructor(private config: ZenAIConfig) {}
288
+
289
+ async extract<T = string>(markdown: string, schemaPrompt: string): Promise<T> {
290
+ const response = await fetch(this.config.endpoint, {
291
+ method: 'POST',
292
+ headers: {
293
+ 'Authorization': `Bearer ${this.config.apiKey}`,
294
+ 'Content-Type': 'application/json',
295
+ },
296
+ body: JSON.stringify({
297
+ model: this.config.model || 'gpt-4o',
298
+ messages: [
299
+ { role: 'system', content: schemaPrompt },
300
+ { role: 'user', content: markdown }
301
+ ],
302
+ temperature: this.config.temperature ?? 0,
303
+ max_tokens: this.config.maxTokens ?? 4000,
304
+ }),
305
+ });
306
+
307
+ const data = await response.json();
308
+ return data.choices[0]?.message?.content as T;
309
+ }
310
+ }
311
+ */
312
+
313
+ // Export placeholder for now
314
+ export class ZenAIExtractor {
315
+ constructor() {
316
+ throw new Error(
317
+ 'ZenAIExtractor not yet implemented. See src/ai/zen-ai.ts for template.',
318
+ )
319
+ }
320
+ }
321
+ ```
322
+
323
+ ---
324
+
325
+ ## Phase 4: CLI Interface (`src/cli.ts`)
326
+
327
+ ### Commands Structure
328
+
329
+ ```typescript
330
+ // bun-crawl scrape <url> [options]
331
+ // bun-crawl crawl <url> [options]
332
+ // bun-crawl --help / --version
333
+ ```
334
+
335
+ ### Scrape Command Options
336
+
337
+ | Flag | Short | Description | Default |
338
+ | -------------- | ----- | ------------------------------------ | --------------- |
339
+ | `--output` | `-o` | Output file path (stdout if omitted) | stdout |
340
+ | `--format` | `-f` | Output format: `markdown`, `json` | markdown |
341
+ | `--timeout` | `-t` | Request timeout in ms | 30000 |
342
+ | `--user-agent` | `-u` | Custom User-Agent | bun-crawl/0.1.0 |
343
+
344
+ ### Crawl Command Options
345
+
346
+ | Flag | Short | Description | Default |
347
+ | --------------- | ----- | ----------------------------------- | -------------- |
348
+ | `--depth` | `-d` | Max crawl depth | 2 |
349
+ | `--limit` | `-l` | Max pages to crawl | 20 |
350
+ | `--out-dir` | `-o` | Output directory for markdown files | ./crawl-output |
351
+ | `--same-domain` | | Stay on same domain | true |
352
+ | `--concurrency` | `-c` | Concurrent requests | 5 |
353
+
354
+ ### Global Options
355
+
356
+ | Flag | Description |
357
+ | ----------- | ---------------------------- |
358
+ | `--json` | Output machine-readable JSON |
359
+ | `--verbose` | Verbose logging |
360
+
361
+ ---
362
+
363
+ ## Phase 5: Entry Point (`src/index.ts`)
364
+
365
+ ```typescript
366
+ // Main exports for programmatic use
367
+ export {Fetcher} from './core/fetcher'
368
+ export {Cleaner} from './core/cleaner'
369
+ export {MarkdownConverter} from './core/markdown'
370
+ export {Crawler} from './core/crawler'
371
+ export {NullAIExtractor} from './ai/null-ai'
372
+ export {ZenAIExtractor} from './ai/zen-ai'
373
+ export type {AIExtractor} from './ai/interface'
374
+ export type {
375
+ ScrapeResult,
376
+ CrawlResult,
377
+ CrawlOptions,
378
+ FetcherOptions,
379
+ } from './types'
380
+ ```
381
+
382
+ ---
383
+
384
+ ## Phase 6: Build & Distribution
385
+
386
+ ### Build Script
387
+
388
+ ```json
389
+ "scripts": {
390
+ "build": "bun build --compile --minify ./src/index.ts --outfile bun-crawl"
391
+ }
392
+ ```
393
+
394
+ ### Output
395
+
396
+ - Single binary: `./bun-crawl` (Linux/macOS) or `./bun-crawl.exe` (Windows)
397
+ - Self-contained, no Node.js/Bun runtime required
398
+ - Cross-compilation via `bun build --compile --target=...`
399
+
400
+ ---
401
+
402
+ ## Implementation Order
403
+
404
+ 1. ✅ **Setup**: `package.json`, `tsconfig.json`, `bunfig.toml`
405
+ 2. ✅ **Types**: `src/types.ts`
406
+ 3. ✅ **Core**: `fetcher.ts` → `cleaner.ts` → `markdown.ts` → `crawler.ts`
407
+ 4. ✅ **AI**: `interface.ts` → `null-ai.ts` → `zen-ai.ts` (stub)
408
+ 5. ✅ **CLI**: `cli.ts` with both commands (custom parser replacing citty)
409
+ 6. ✅ **Entry**: `index.ts` exports
410
+ 7. ✅ **Build**: Test compilation - standalone binary works
411
+
412
+ ---
413
+
414
+ ## Testing Strategy
415
+
416
+ - Unit tests for each core module
417
+ - Integration test for scrape command
418
+ - Integration test for crawl command
419
+ - Snapshot tests for markdown output
420
+
421
+ ---
422
+
423
+ ## Future Extensions (Post-MVP)
424
+
425
+ 1. **ZenAIExtractor** full implementation
426
+ 2. **Playwright fallback** for JS-heavy sites
427
+ 3. **Sitemap.xml** discovery
428
+ 4. **Robots.txt** compliance
429
+ 5. **Rate limiting** per domain
430
+ 6. **Cache layer** (file-based or Redis)
431
+ 7. **Plugin system** for custom extractors
432
+
433
+ ---
434
+
435
+ ## Clarifications for Review
436
+
437
+ 1. ✅ **CLI Name**: `bun-crawl` (binary & command)
438
+ 2. ✅ **Framework**: Custom argument parser (citty's option parsing had issues with subcommands)
439
+ 3. ✅ **ZenAIExtractor**: Template/stub only (commented implementation)
440
+ 4. ✅ **Default output format for crawl**: Individual `.md` files per page in output directory
441
+ 5. ✅ **Concurrency default**: 5 (works well)
442
+ 6. ✅ **User-Agent string**: `bun-crawl/0.1.0`
443
+
444
+ ---
445
+
446
+ ## Suggested Next Steps
447
+
448
+ 1. Add unit tests for core modules
449
+ 2. Add integration tests for CLI commands
450
+ 3. Add README.md with usage examples
451
+ 4. Set up CI/CD for automated builds
452
+ 5. Consider adding Playwright fallback for JS-heavy sites
package/Plan.md ADDED
@@ -0,0 +1,83 @@
1
+ # Bun-Crawl
2
+
3
+ ---
4
+
5
+ ## Architektur & Modul-Design
6
+
7
+ Die Pipeline folgt dem **Strategy-Pattern**. Der Scraper-Core übergibt das Ergebnis an eine Pipeline, deren letzter Schritt optional die AI-Extraktion ist.
8
+
9
+ ```
10
+ [URL] -> [Fetcher] -> [Readability + Turndown] -> [Markdown Output]
11
+ │
12
+ ▼ (Optional / Später)
13
+ [OpenCode Zen API] -> [JSON Output]
14
+
15
+ ```
16
+
17
+ ### Ordnerstruktur
18
+
19
+ ```
20
+ firebun/
21
+ ├── src/
22
+ │ ├── cli.ts # CLI Interface (Commander / Citty)
23
+ │ ├── core/
24
+ │ │ ├── fetcher.ts # HTTP Requests (optional Playwright Fallback)
25
+ │ │ ├── cleaner.ts # Linkedom + @mozilla/readability
26
+ │ │ ├── markdown.ts # Turndown Konvertierung
27
+ │ │ └── crawler.ts # HTMLRewriter für Rekursion
28
+ │ ├── ai/
29
+ │ │ ├── interface.ts # AI Extractor Interface
30
+ │ │ └── null-ai.ts # Standart-Pass-Through (ohne AI)
31
+ │ │ └── zen-ai.ts # (Später) OpenCode Zen Provider
32
+ │ └── index.ts
33
+ ├── package.json
34
+ └── bunfig.toml
35
+
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Implementierungsplan
41
+
42
+ ### Phase 1: Projekt-Setup
43
+
44
+ - Initalisierung: `bun init`
45
+ - Dependencies: `bun add turndown @mozilla/readability linkedom citty`
46
+ - Dev-Dependencies: `bun add -d @types/turndown @types/mozilla-readability`
47
+
48
+ ### Phase 2: Core Processing Pipeline (Ohne AI)
49
+
50
+ - **Fetcher (`src/core/fetcher.ts`):** `fetch()`-Wrapper mit konfigurierbaren User-Agents und Timeouts.
51
+ - **Cleaner (`src/core/cleaner.ts`):** DOM-Parsing mit `linkedom` und Content-Extraktion via `@mozilla/readability`.
52
+ - **Markdown (`src/core/markdown.ts`):** Konfiguration von `turndown` zur sauberen Formatierung von Tabellen, Code-Blöcken und Links.
53
+
54
+ ### Phase 3: Crawl-Engine & CLI
55
+
56
+ - **Link-Parser (`src/core/crawler.ts`):** Nutzung von Buns nativem `HTMLRewriter`, um im HTTP-Stream alle internen Links (`<a href="...">`) zu sammeln und Rekursion (Tiefe/Limit) zu steuern.
57
+ - **CLI-Interface (`src/cli.ts`):** Flag-Parsing (`--depth`, `--out-dir`, `--json`).
58
+
59
+ ### Phase 4: Vorbereitung der OpenCode Zen Schnittstelle
60
+
61
+ - Erstellen von `src/ai/interface.ts`:
62
+
63
+ ```typescript
64
+ export interface AIExtractor {
65
+ extract<T>(markdown: string, schemaPrompt: string): Promise<T | string>
66
+ }
67
+ ```
68
+
69
+ - `NullAIExtractor` implementieren, der lediglich das rohe Markdown unverändert zurückgibt.
70
+ - Für die spätere Anbindung muss nur ein `ZenAIExtractor` hinzugefügt werden, der per `fetch()` an den OpenAI-kompatiblen OpenCode Zen Endpoint sendet (`/v1/chat/completions`).
71
+
72
+ ### Phase 5: Compilation
73
+
74
+ - Script in `package.json`:
75
+
76
+ ```json
77
+ "scripts": {
78
+ "build": "bun build --compile --minify ./src/index.ts --outfile firebun"
79
+ }
80
+
81
+ ```
82
+
83
+ ---
package/README.md CHANGED
@@ -1,3 +1,181 @@
1
- # Temporary Holding Version
1
+ # bun-crawl
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Open-source Firecrawl alternative built with Bun. Scrape and crawl websites into clean Markdown.
4
+
5
+ ## Features
6
+
7
+ - **Fast** - Native Bun runtime, no Node.js required
8
+ - **Clean output** - Mozilla Readability + Turndown for article extraction
9
+ - **Recursive crawling** - Depth-limited, same-domain filtering, concurrent requests
10
+ - **Standalone binary** - Single executable, no runtime dependencies
11
+ - **AI-ready** - Extractor interface for LLM-based content processing
12
+
13
+ ## Installation
14
+
15
+ ### Pre-built Binary (Recommended)
16
+
17
+ Download the latest release for your platform.
18
+
19
+ ### From Source
20
+
21
+ ```bash
22
+ git clone https://github.com/yourusername/bun-crawl
23
+ cd bun-crawl
24
+ bun install
25
+ bun run build
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### Scrape a Single URL
31
+
32
+ ```bash
33
+ # Output to stdout (markdown)
34
+ bun-crawl scrape https://example.com
35
+
36
+ # Save to file
37
+ bun-crawl scrape https://example.com -o page.md
38
+
39
+ # JSON output with metadata
40
+ bun-crawl scrape https://example.com --format json
41
+ bun-crawl scrape https://example.com --json
42
+
43
+ # Custom timeout and user agent
44
+ bun-crawl scrape https://example.com -t 60000 -u "MyBot/1.0"
45
+ ```
46
+
47
+ ### Crawl a Website Recursively
48
+
49
+ ```bash
50
+ # Crawl with defaults (depth 2, limit 20 pages)
51
+ bun-crawl crawl https://example.com
52
+
53
+ # Custom depth and limit
54
+ bun-crawl crawl https://example.com -d 3 -l 50
55
+
56
+ # Custom output directory
57
+ bun-crawl crawl https://example.com -O ./my-output
58
+
59
+ # JSON summary only
60
+ bun-crawl crawl https://example.com --json
61
+
62
+ # Stay on same domain (default), custom concurrency
63
+ bun-crawl crawl https://example.com --same-domain -c 10
64
+ ```
65
+
66
+ ### Options Reference
67
+
68
+ #### `scrape` Command
69
+
70
+ | Flag | Short | Description | Default |
71
+ | -------------- | ----- | ------------------------------------ | --------------- |
72
+ | `--output` | `-o` | Output file path (stdout if omitted) | stdout |
73
+ | `--format` | `-f` | Output format: `markdown`, `json` | markdown |
74
+ | `--timeout` | `-t` | Request timeout in milliseconds | 30000 |
75
+ | `--user-agent` | `-u` | Custom User-Agent string | bun-crawl/0.1.0 |
76
+ | `--json` | | Output machine-readable JSON | false |
77
+
78
+ #### `crawl` Command
79
+
80
+ | Flag | Short | Description | Default |
81
+ | --------------- | ----- | ------------------------------------ | --------------- |
82
+ | `--depth` | `-d` | Maximum crawl depth | 2 |
83
+ | `--limit` | `-l` | Maximum pages to crawl | 20 |
84
+ | `--out-dir` | `-O` | Output directory for markdown files | ./crawl-output |
85
+ | `--same-domain` | | Stay on the same domain | true |
86
+ | `--concurrency` | `-c` | Concurrent requests | 5 |
87
+ | `--timeout` | `-t` | Request timeout in milliseconds | 30000 |
88
+ | `--user-agent` | `-u` | Custom User-Agent string | bun-crawl/0.1.0 |
89
+ | `--json` | | Output machine-readable JSON summary | false |
90
+
91
+ ## Programmatic API
92
+
93
+ ```typescript
94
+ import {
95
+ Fetcher,
96
+ Cleaner,
97
+ MarkdownConverter,
98
+ Crawler,
99
+ NullAIExtractor,
100
+ } from 'bun-crawl'
101
+
102
+ const fetcher = new Fetcher({timeout: 30000, userAgent: 'MyBot/1.0'})
103
+ const cleaner = new Cleaner()
104
+ const converter = new MarkdownConverter()
105
+ const ai = new NullAIExtractor()
106
+
107
+ // Scrape single page
108
+ const {html, url, status} = await fetcher.fetch('https://example.com')
109
+ const {content, title, metadata} = cleaner.clean(html, url)
110
+ const markdown = await ai.extract(converter.convert(content), '')
111
+
112
+ // Crawl multiple pages
113
+ const crawler = new Crawler(fetcher, cleaner, converter, {
114
+ depth: 2,
115
+ limit: 20,
116
+ sameDomain: true,
117
+ concurrency: 5,
118
+ })
119
+ const result = await crawler.crawl('https://example.com')
120
+ ```
121
+
122
+ ## AI Extractors
123
+
124
+ `bun-crawl` uses a pluggable AI extractor interface. The default `NullAIExtractor` passes content through unchanged.
125
+
126
+ ```typescript
127
+ import {AIExtractor} from 'bun-crawl'
128
+
129
+ class MyAIExtractor implements AIExtractor {
130
+ async extract<T = string>(
131
+ markdown: string,
132
+ schemaPrompt: string,
133
+ ): Promise<T> {
134
+ // Call your LLM API here
135
+ return markdown as T
136
+ }
137
+ }
138
+ ```
139
+
140
+ A `ZenAIExtractor` template is included for future OpenCode Zen integration.
141
+
142
+ ## Building
143
+
144
+ ```bash
145
+ # Development
146
+ bun run dev scrape https://example.com
147
+
148
+ # Production binary
149
+ bun run build
150
+ # Output: ./bun-crawl (Linux/macOS) or ./bun-crawl.exe (Windows)
151
+ ```
152
+
153
+ ### Cross-compilation
154
+
155
+ ```bash
156
+ bun build --compile --target=linux-x64 ./src/cli.ts --outfile bun-crawl-linux
157
+ bun build --compile --target=darwin-x64 ./src/cli.ts --outfile bun-crawl-macos
158
+ bun build --compile --target=windows-x64 ./src/cli.ts --outfile bun-crawl.exe
159
+ ```
160
+
161
+ ## Project Structure
162
+
163
+ ```
164
+ src/
165
+ ├── cli.ts # CLI entry point
166
+ ├── index.ts # Public API exports
167
+ ├── types.ts # TypeScript interfaces
168
+ ├── core/
169
+ │ ├── fetcher.ts # HTTP fetch wrapper
170
+ │ ├── cleaner.ts # Readability content extraction
171
+ │ ├── markdown.ts # Turndown HTML→Markdown
172
+ │ └── crawler.ts # Recursive crawler (HTMLRewriter)
173
+ └── ai/
174
+ ├── interface.ts # AIExtractor interface
175
+ ├── null-ai.ts # Pass-through extractor
176
+ └── zen-ai.ts # Template for Zen integration
177
+ ```
178
+
179
+ ## License
180
+
181
+ MIT