Skip to content

Commit 787aed8

Browse files
committed
fix: reduce batch size to prevent SDK timeouts and rewrite README
1 parent 880132d commit 787aed8

6 files changed

Lines changed: 138 additions & 87 deletions

File tree

.docs.mv2.GG3ypp

3.82 MB
Binary file not shown.

README.md

Lines changed: 81 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div align="center">
22

3-
<img src="s_maw.svg" alt="maw" width="400">
3+
<img src="https://raw.githubusercontent.com/memvid/maw/main/s_maw.svg" alt="maw" width="400">
44

55
**Crawl any site. Search it forever.**
66

@@ -14,174 +14,172 @@
1414

1515
---
1616

17-
Feed the maw. It never forgets.
18-
1917
```bash
2018
npx @memvid/maw https://stripe.com/docs
2119
```
2220

23-
That's it. The entire Stripe docs are now in a 40MB file you can search and ask questions to. Offline. Forever.
24-
25-
## Why?
21+
30 seconds later you have the entire Stripe documentation in a single 40MB file. Search it. Ask it questions. Works offline. Works forever.
2622

27-
Because you shouldn't need to keep 47 browser tabs open or bookmark links you'll never read again. Crawl once, query forever.
28-
29-
```bash
30-
# later, when you actually need it
31-
maw ask stripe.mv2 "how do webhooks work?"
32-
```
23+
No more bookmarking docs you'll forget about. No more 47 browser tabs. No more "I read this somewhere but can't find it."
3324

3425
## Install
3526

3627
```bash
3728
npm i -g @memvid/maw
3829
```
3930

40-
Or just use `npx @memvid/maw` without installing.
31+
Or just `npx @memvid/maw` if you don't want to install anything.
4132

42-
## What it does
33+
## The basics
4334

44-
```
45-
maw https://react.dev → react.mv2 (312 pages, 18s)
46-
maw https://docs.python.org → python.mv2 (2,847 pages, 4min)
47-
maw . → repo.mv2 (your local git repo)
48-
maw https://news.ycombinator.com/item?id=12345 → just that page
35+
```bash
36+
maw https://react.dev # crawls entire site → maw.mv2
37+
maw find maw.mv2 "useEffect" # instant search
38+
maw ask maw.mv2 "when should I use useCallback vs useMemo?" # AI answers
4939
```
5040

51-
Smart defaults:
52-
- **Single page URL?** Fetches just that page
53-
- **Domain root?** Crawls the whole site
54-
- **Local path?** Reads your git repo
55-
- **Protected site?** Auto-switches to stealth browser
41+
That last one needs `OPENAI_API_KEY` in your env. The first two work out of the box.
5642

57-
## Commands
43+
## What can you crawl?
5844

59-
### Crawl
45+
Basically anything.
6046

6147
```bash
62-
maw <url> # → maw.mv2
63-
maw <url> -o docs.mv2 # custom output
64-
maw <url> docs.mv2 # same thing (appends if exists)
65-
maw <url> --depth 5 --max-pages 500 # go deeper
48+
maw https://docs.python.org # documentation (2,847 pages, ~4 min)
49+
maw https://paulgraham.com # blogs
50+
maw . # your local codebase
51+
maw https://github.com/user/repo # any git repo
52+
maw "https://news.ycombinator.com/item?id=12345" # single pages
6653
```
6754

68-
### Query
55+
It figures out the right approach automatically:
56+
- Single page URL → fetches just that page
57+
- Domain root → crawls everything it can find
58+
- Local path → reads your files directly
59+
- Cloudflare/bot protection → switches to stealth browser
6960

61+
## Commands
62+
63+
**Crawl**
7064
```bash
71-
maw find docs.mv2 "authentication" # full-text search
72-
maw ask docs.mv2 "how does X work?" # AI answer (needs OPENAI_API_KEY)
73-
maw list docs.mv2 # see what's inside
65+
maw <url> # saves to maw.mv2
66+
maw <url> -o docs.mv2 # custom output file
67+
maw <url> docs.mv2 # same (appends if file exists)
68+
maw <url> --depth 5 --max-pages 500 # go deeper
7469
```
7570

76-
### Preview
77-
71+
**Search**
7872
```bash
79-
maw preview stripe.com # shows sitemap, estimated page count
73+
maw find docs.mv2 "authentication" # keyword search
74+
maw ask docs.mv2 "how do I do X?" # AI-powered answers
75+
maw list docs.mv2 # see what's in there
8076
```
8177

82-
### Export
78+
**Preview before crawling**
79+
```bash
80+
maw preview stripe.com # shows sitemap, page count estimate
81+
```
8382

83+
**Export**
8484
```bash
85-
maw export docs.mv2 -f markdown -o docs.md
86-
maw export docs.mv2 -f json -o docs.json
85+
maw export docs.mv2 -f markdown # dump everything to markdown
86+
maw export docs.mv2 -f json # or json
8787
```
8888

89-
## Embeddings
89+
## Semantic search
90+
91+
By default you get keyword search (BM25). It's fast and works well for most things.
9092

9193
Want semantic search? Add `--embed`:
9294

9395
```bash
94-
maw https://docs.whatever.com --embed openai
96+
maw https://kubernetes.io/docs --embed openai
9597
```
9698

97-
Uses OpenAI embeddings for semantic search. Costs ~$0.01 per 1000 pages. Without it, you get BM25 keyword search (still good, just different).
99+
Costs about $0.01 per 1000 pages. Your queries will understand meaning, not just keywords.
98100

99-
## How it works
100-
101-
Most sites work with a simple fetch. When that fails (Cloudflare, JS-heavy SPAs), maw falls back to a real browser. When *that* fails (aggressive anti-bot), it uses stealth mode.
101+
## How it handles protected sites
102102

103103
```
104-
fetch (fast) → playwright (slower) → rebrowser (stealth)
105-
↓ ↓ ↓
106-
works? blocked? blocked?
107-
↓ ↓ ↓
108-
done retry done
104+
fetch (fast) → playwright (real browser) → rebrowser (stealth)
109105
```
110106

111-
90% of sites never need the browser. The 10% that do, just work.
107+
90% of sites work with a simple fetch. The other 10% get a real browser. If that's blocked too, stealth mode usually gets through.
108+
109+
You don't have to think about this. It just tries each approach until something works.
112110

113-
## Options
111+
## All the flags
114112

115-
| Flag | Description | Default |
116-
|------|-------------|---------|
113+
| Flag | What it does | Default |
114+
|------|--------------|---------|
117115
| `-o, --output <file>` | Output file | `maw.mv2` |
118-
| `-d, --depth <n>` | Crawl depth | `2` |
119-
| `-m, --max-pages <n>` | Max pages to crawl | `150` |
116+
| `-d, --depth <n>` | How deep to crawl | `2` |
117+
| `-m, --max-pages <n>` | Stop after this many pages | `150` |
120118
| `-c, --concurrency <n>` | Parallel requests | `10` |
121-
| `-r, --rate-limit <n>` | Requests per second | `10` |
122-
| `--include <regex>` | Only crawl matching URLs | - |
123-
| `--exclude <regex>` | Skip matching URLs | - |
119+
| `-r, --rate-limit <n>` | Max requests/second | `10` |
120+
| `--include <regex>` | Only crawl URLs matching this | - |
121+
| `--exclude <regex>` | Skip URLs matching this | - |
124122
| `--browser` | Force browser mode | - |
125123
| `--stealth` | Force stealth mode | - |
126-
| `--embed [model]` | Enable embeddings | - |
124+
| `--embed [model]` | Enable semantic embeddings | - |
127125
| `--no-robots` | Ignore robots.txt | - |
128-
| `--no-sitemap` | Skip sitemap discovery | - |
126+
| `--no-sitemap` | Don't use sitemap.xml | - |
129127

130128
## Examples
131129

132130
```bash
133-
# documentation sites
131+
# grab some docs
134132
maw https://react.dev
135133
maw https://docs.python.org
136134
maw https://stripe.com/docs
137135

138-
# news/blogs
136+
# archive a blog
139137
maw https://paulgraham.com/articles.html
140-
maw "https://news.ycombinator.com/item?id=40000000"
141138

142-
# your own repos
143-
maw . -o my-project.mv2
144-
maw https://github.com/user/repo
139+
# your own code
140+
maw .
141+
maw https://github.com/your/repo
145142

146-
# combine multiple sources
143+
# combine sources into one file
147144
maw https://react.dev https://nextjs.org -o frontend.mv2
148145

149-
# deep crawl with embeddings
146+
# big crawl with semantic search
150147
maw https://kubernetes.io/docs --depth 4 --max-pages 1000 --embed openai
151148
```
152149

153150
## Limits
154151

155-
Files up to **50MB** work without any API key. That's roughly 500-2000 pages depending on content.
152+
Up to **50MB** works without an API key. That's roughly 500-2000 pages depending on how much text is on each page.
156153

157-
For bigger crawls, get a key at [memvid.com](https://memvid.com).
154+
Need more? Get a key at [memvid.com](https://memvid.com).
158155

159156
## FAQ
160157

161158
**Is this legal?**
162159

163-
Respects robots.txt by default. Use `--no-robots` at your own discretion.
160+
Respects robots.txt by default. What you do with `--no-robots` is your business.
164161

165-
**Why .mv2?**
162+
**What's an .mv2 file?**
166163

167-
It's a [memvid](https://memvid.com) file — single-file database with full-text search, embeddings, and temporal queries baked in. Think SQLite but for documents.
164+
A [memvid](https://memvid.com) file. Single-file database with search built in. Like SQLite but for documents and memory.
168165

169-
**Can I use it programmatically?**
166+
**Programmatic usage?**
170167

171168
```javascript
172-
import { crawl, query } from 'maw'
169+
import { maw, find, ask } from '@memvid/maw'
173170

174-
await crawl('https://example.com', { output: 'site.mv2' })
175-
const results = await query('site.mv2', 'search term')
171+
await maw(['https://example.com'], { output: 'site.mv2' })
172+
const results = await find('site.mv2', 'search term')
173+
const answer = await ask('site.mv2', 'explain this to me')
176174
```
177175

178-
**What about rate limiting?**
176+
**Will I get rate limited?**
179177

180-
Default is 10 req/sec with automatic backoff. Most sites won't notice you. If you're hitting APIs, consider `--rate-limit 2`.
178+
Default is 10 req/sec with backoff. Most sites won't notice. If you're worried, use `--rate-limit 2`.
181179

182-
**Does it handle JavaScript-rendered content?**
180+
**JS-rendered content?**
183181

184-
Yes. If fetch fails, it automatically tries Playwright. For heavily protected sites, use `--stealth`.
182+
Works. Falls back to a real browser automatically when needed.
185183

186184
---
187185

bin/maw.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { maw, find, ask, list, preview, exportDocs } from '../src/index.js';
1010
import { setLogMode } from '../src/utils/logger.js';
1111
import * as ui from '../src/utils/ui.js';
1212

13-
const VERSION = '1.0.4';
13+
const VERSION = '1.0.5';
1414

1515
// Global error handlers to prevent crashes
1616
process.on('uncaughtException', (err) => {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@memvid/maw",
3-
"version": "1.0.4",
3+
"version": "1.0.5",
44
"description": "Crawl any site. Search it forever.",
55
"type": "module",
66
"main": "dist/index.js",

src/ingestor/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ export async function ingestToMv2(
126126
let skippedDupes = 0;
127127
let stoppedAtLimit = false;
128128
let estimatedFileSize = 0;
129-
// Batch size for ingestion (default 10)
130-
const batchSize = options.batchSize || 10;
129+
// Batch size for ingestion (default 3 - larger batches can cause SDK timeouts)
130+
const batchSize = options.batchSize || 3;
131131
const batch: Array<{ title: string; label: string; text: string; metadata: Record<string, any> }> = [];
132132

133133
// Size limit (default 40MB to stay safely under 50MB free tier with buffer)

test-sdk.mjs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { create, use } from '@memvid/sdk';
2+
import { existsSync } from 'fs';
3+
4+
async function test() {
5+
const filePath = '/tmp/test-append.mv2';
6+
7+
// Create new or open existing
8+
let mem;
9+
if (existsSync(filePath)) {
10+
console.log('Opening existing memory...');
11+
mem = await use('basic', filePath);
12+
} else {
13+
console.log('Creating memory...');
14+
mem = await create(filePath, 'basic');
15+
}
16+
17+
// Generate 10 docs with realistic content (like web pages)
18+
const docs = [];
19+
for (let i = 0; i < 10; i++) {
20+
docs.push({
21+
title: `Page ${i + 1} - Sample Web Page`,
22+
label: 'web',
23+
text: `# Page ${i + 1}\n\nThis is sample content for page ${i + 1}. `.repeat(50) +
24+
`\n\nThis is a longer document with more text to simulate real web pages.`,
25+
metadata: {
26+
url: `https://example.com/page-${i + 1}`,
27+
crawledAt: new Date().toISOString(),
28+
},
29+
});
30+
}
31+
32+
console.log(`Trying putMany with ${docs.length} docs...`);
33+
const start = Date.now();
34+
try {
35+
const ids = await mem.putMany(docs);
36+
console.log('putMany() succeeded in ' + (Date.now() - start) + 'ms');
37+
} catch (e) {
38+
console.error('putMany() failed:', e.message);
39+
}
40+
41+
// Now test find
42+
console.log('\nTesting find...');
43+
try {
44+
const results = await mem.find('sample content', { k: 5 });
45+
console.log('find() returned', results?.hits?.length || 0, 'results');
46+
} catch (e) {
47+
console.error('find() failed:', e.message);
48+
}
49+
50+
console.log('Done');
51+
}
52+
53+
test().catch(console.error);

0 commit comments

Comments
 (0)