Commit graph

47 commits

Author SHA1 Message Date
ced92de67c
feat: optional arborist provenance sink (opt-in, default off)
Adds neopig/arborist_sink.py: when enabled, every crawled page neopig
stores is also committed to an arborist content-addressed store — a
Merkle document_root + append-only audit chain — alongside neopig's
existing md5/FileVault storage. Verifiable dedup + FTS5 + tamper-evident
provenance; replaces nothing.

Strictly opt-in: a hard no-op unless NEOPIG_ARBORIST_ENABLED is set AND
arborist is importable. neopig behaves byte-identically without it. This
keeps neopig public-domain by default — arborist is AGPL, pulled only
when an operator opts in. neopig never touches arborist's tables; all
writes go through arborist.embed (no-raw-SQL rule preserved). Sync
SQLite writes run off the event loop via to_thread, lock-serialized,
and failures are swallowed so the mirror can never break a crawl.

Wired into NeoPig.__init__ (self.arborist_sink) and the store_page hook
in the crawl path. Phase-0 scope: page text only; media-manifest edges
are a follow-on. Tests cover disabled-by-default, flag-without-arborist,
the page->Document mapping, and enabled end-to-end + content-idempotence.
2026-05-22 13:06:59 -04:00
c8b0ecbd87 add canonical permacomputer preamble to all source files 2026-02-25 17:40:12 -05:00
027f5afc57 Add public domain license and headers to all source files
- Add LICENSE file (public domain, permacomputer values)
- Add license headers to all 27 Python source files
- Restore full about_ch9_p2 with "terminated 🟣" content
- Translate about_ch9_p2 to all 26 languages

The permacomputer is community-owned infrastructure optimized around:
TRUTH, FREEDOM, HARMONY, LOVE
2026-01-08 15:38:28 -05:00
58c93f217a Reorganize project structure: move modules to neopig/, scripts to scripts/, data to data/ 2026-01-06 14:06:43 -05:00
f124828c16 21 side quests scattered through the codebase
1-20 are findable. 21 is the absence itself.

fnord
2026-01-05 21:10:02 -05:00
81662f1781 Mungday chaos: Sign Maker easter eggs, fix OVER_9000, England flag
Setting Orange, 5th of Chaos, YOLD 3192 - Celebrate Mungday!

- Add Sign Maker comments throughout codebase
- Fix OVER_9000 = 9001 (it's OVER 9000, not exactly 9000)
- Change English flag from 🇺🇸 to 🏴󠁧󠁢󠁥󠁮󠁧󠁿 (St. George's Cross)
- Fnord. You didn't see this commit.
2026-01-05 20:18:23 -05:00
e5972a3171 Add instant replay for crawl jobs and NEOPIG_NO_VCS flag
- Add /api/crawl/jobs/{job_id}/replay SSE endpoint for replaying crawls
- Replay streams media at original discovery pace with speed multiplier
- Add Replay button to crawl jobs UI (works even when crawling disabled)
- Track active replays to prevent double-clicking same job
- Live feed page handles ?replay=JOB_ID parameter for replay mode
- Add NEOPIG_NO_VCS=1 env var to skip VCS repository detection
- Fix hljs.highlightAll() call when highlight.js not loaded
2026-01-04 10:47:11 -05:00
66d2f4d1d9 Port 31337 default, code file display fixes
- Change default port from 8000 to 31337 across all files
- About page: and → &, beast → pig
- Search results: SVG placeholder for code files (shows extension)
- Download button: remove .bin fallback, use original extension
2026-01-02 15:35:39 -05:00
0696e92ae3 Add VCS repo cloning support (git/hg/svn/fossil)
Smart source detection: like Hydra for RSS feeds, auto-detect
repository URLs and clone instead of HTTP crawling.

- repo.py: detect_vcs(), clone_repo(), walk_files(), symlink_to_vault()
- Symlink storage: files in repo_vault/ symlink to content-addressed vault/
- Database: repo_uri, repo_path, commit_hash, vcs_type columns
- Auto-detect GitHub, GitLab, Bitbucket, sr.ht, hg.mozilla.org, etc.
2026-01-02 14:37:53 -05:00
17feb3d50b Add --host argument to neopig.py --serve for binding to specific interfaces 2026-01-02 10:54:00 -05:00
59e7ad5669 Add job purge: delete all traces of a crawl job
Order of operations (critical for data integrity):
1. Get job info and collect hashes/URIs to delete
2. Delete MediaSource records (breaks references)
3. Delete Page records
4. Delete orphan Media records (not referenced by other jobs)
5. Delete orphan media files from vault
6. Delete screenshot files for deleted pages
7. Delete CrawlJob record
8. Delete state files

CLI: python neopig.py --purge-job JOB_ID
API: DELETE /api/crawl/jobs/{job_id}?purge=true (default)
UI: Delete button now purges by default
2026-01-01 16:47:59 -05:00
6491a2763f Fix fetch_results typo and handle None paths in Uri.path
- async_web_fetcher: fetch_results -> fetch_tasks (undefined variable)
- neopig: Handle None Uri.path with (parsed.path or '').lstrip('/')
2026-01-01 16:26:08 -05:00
648842301f Use Uri.hostname instead of Uri.host 2026-01-01 16:10:34 -05:00
e00034c39b Hydra: persist feeds per-domain, self-heal on every crawl
- Store discovered RSS/Atom/Sitemap feed URLs in hydra-{domain}.json
- ALWAYS check known feeds on every crawl (self-healing)
- --hydra flag discovers NEW feeds, adds them to persistent list
- fetch_feeds() accepts feed_urls param for known feeds
- Tracks which feeds actually return items for persistence
2026-01-01 15:54:04 -05:00
ffdbdaad68 Add hydra state persistence for delta URL detection
- Hydra state file (hydra-{domain}.json) never rotates
- Tracks seen URLs with first_seen timestamp, title, published date
- _filter_new_hydra_urls() returns only NEW URLs not seen before
- Enables detecting new posts when sites publish via RSS/Atom/Sitemap
2026-01-01 15:49:04 -05:00
abc3c101da Add hydra mode for RSS/Atom/Sitemap feed discovery
- --hydra CLI flag and UI checkbox (off by default)
- discover_feeds(): finds RSS/Atom links and common sitemap URLs
- parse_rss(), parse_atom(), parse_sitemap(): extract URLs from feeds
- fetch_feeds(): async method to fetch and parse all discovered feeds
- hydra_urls injected into crawl queue with high priority (score 100)
- Supports nested sitemaps (sitemap index files)
2026-01-01 15:34:53 -05:00
d05bb5993b Move job logging to neopig.py - always log for both CLI and web UI 2026-01-01 09:01:14 -05:00
66538a6ab4 Add crawl ETA estimates to UI based on pages/sec rate 2025-12-31 16:19:13 -05:00
0bfc1a0292 Fix hash_to_path calls passing ext as depth parameter 2025-12-31 16:12:39 -05:00
99c836e2c8 Unify filevault: merge storage.py into single sync+async module
- filevault.py: Vault (sync) + AsyncVault (async) in one file
- Delete storage.py (redundant async wrapper)
- Both classes share identical API: store, get, exists, delete, stats
- Add content_hash() and hash_to_path() utility functions
- Update all imports: neopig.py, serp.py, test files
- Update README with unified documentation
- 72 lines saved, cleaner architecture
2025-12-31 15:44:39 -05:00
b0542fccc6 Improve depth CLI help and add max depth validation
- Clarify depth help: 0=single page, 1=page+links, -1=unlimited
- Reject depth >18, suggest using -1 for unlimited crawling
- Update CLAUDE.md with depth documentation
2025-12-31 15:37:18 -05:00
8d669e206c Comprehensive README rewrite with full API documentation
- README.md: Complete rewrite in About page style with origin story
- Documents all storage systems: filevault, ImageVault, domain_vault
- Full database schema and method reference
- Screenshot capture with all engines and config options
- HTML to Markdown converter with content detection
- CLI reference for neopig.py, archive.py, serp.py
- All API endpoints documented
2025-12-31 15:21:55 -05:00
2c63076d0d Fix None comparison in markdown backfill progress tracking 2025-12-30 16:21:53 -05:00
7daae6c0dc Fix UNIQUE constraint error when pages have identical screenshots 2025-12-30 16:20:15 -05:00
3fa3c9722b Screenshot backfill: query content_length for dynamic delay 2025-12-30 16:17:44 -05:00
c3e8fb564a Screenshots: full_page=True by default, fix backfill success detection, add About nav link 2025-12-30 16:12:21 -05:00
3481fd3098 Screenshot backfill: process ALL pages in DB, not just crawled domain 2025-12-30 16:07:12 -05:00
482d559f00 Screenshot backfill: DB-backed progress tracking like markdown ETL 2025-12-30 15:33:17 -05:00
ad05d1a308 Screenshot backfill: 1x CPU workers in normal mode (not just 1) 2025-12-30 15:31:17 -05:00
cd56e23a75 Screenshot backfill: 3x CPU workers in fast mode 2025-12-30 15:30:25 -05:00
f4996f9c71 Parallel screenshot backfill with --fast mode (2x CPU workers) 2025-12-30 15:29:48 -05:00
9054c45ac8 Fix Uri.host -> Uri.hostname 2025-12-30 15:27:15 -05:00
deaf796ce1 Auto-backfill missing screenshots after crawl completes 2025-12-30 15:26:10 -05:00
16f869d7de Enable screenshots by default, --no-screenshot to disable 2025-12-30 15:21:41 -05:00
ee9c3cfc74 ETL backfill with jobs table, background flusher, smooth progress
- backfill_jobs table tracks ETL progress with atomic increments
- Background flusher thread syncs progress every 50ms (workers never block)
- Thread-local DB connections for page writes (WAL mode)
- tqdm smoothing=0.1 for stable rate display (30-40 pages/sec)
- 4 processes x 6 threads = 24 workers burning all CPUs
- Zero lost counts - final flush verified against return values
2025-12-30 14:43:25 -05:00
78ec0230b8 Fix progress bar to show 100% at completion 2025-12-30 14:23:07 -05:00
1346590d39 ETL-style parallel markdown backfill with real-time progress
- ProcessPoolExecutor: 1 process per CPU core (bypasses GIL)
- ThreadPoolExecutor: 6 threads per process for I/O throughput
- Thread-local SQLite connections with WAL mode
- Manager().Value() for cross-process progress counter
- Real-time tqdm updates polling shared counter every 50ms
- ~19 pages/sec on 4-core system (4887 pages in 2 min)
2025-12-30 14:22:29 -05:00
293ed64a3b modified: .gitignore
modified:   database.py
	modified:   html2md.py
	modified:   neopig.py
	modified:   screenshot.py
	modified:   serp.py
2025-12-30 11:35:25 -05:00
3952978ba4 Screenshot backfill improvements and oversized image handling
- Filter completed URIs at query level for faster backfill resume
- Split oversized screenshots (>65000px) into multiple JPEG chunks
- Progress bar shows total including skipped, advances on errors
- Suppress PIL DecompressionBombWarning for large screenshots
- Cleaner error logging (truncate verbose wkhtmltoimage output)
- Per-page capture logs moved to debug level
2025-12-30 11:05:10 -05:00
7b4c8b0241 modified: .gitignore
modified:   archive.py
	modified:   html2md.py
	modified:   neopig.py
	modified:   screenshot.py
2025-12-30 09:50:54 -05:00
416b3cb760 Add self-extracting archives with bundled neopig
- Bootstrap C program extracts serve.py and runs from tarball
- Archives now include neopig source files for self-contained crawling
- --upgrade-neopig flag to update neopig in existing archives
- html2md.py: smart HTML-to-markdown converter for forums/blogs/Q&A
- Fix vault path defaults (data/vault instead of vault)
- Streaming tar.gz creation without temp copies
- URL rewriting for local media references in archives
2025-12-30 06:55:20 -05:00
a1a58fbb50 Add backfill-markdown, download button, fix image hydration
- backfill-markdown: regenerate markdown with absolute URLs for a domain
- Download button on /view page with slugified filename from alt/title
- html2text baseurl resolves relative URLs during crawl
- Fallback URL lookup by filename for older crawls
- Video hover-to-play on all video elements
- Avatar CSS float:left for text wrapping
- FileResponse inline disposition (view not download)
- Dynamic screenshot delay based on content length
2025-12-29 15:05:39 -05:00
222c23bb14 modified: CLAUDE.md
modified:   archive.py
	modified:   database.py
	modified:   neopig.py
	modified:   screenshot.py
	modified:   serp.py
2025-12-29 12:13:33 -05:00
21e344f675 Add archive.py with --serve flag for live crawl watching
- archive.py: New site archiver wrapper with embedded serve.py
- --serve flag starts SERP server alongside crawl for live viewing
- --fast mode for sites without robots.txt (no crawl delay)
- Symlink-based storage: domain views link to hash vault
- Tarball resolves symlinks to include only domain content
- Fixed bytes_downloaded/bytes_stored accounting for screenshots
- Data directory (data/) for databases and state files
- Standardized on - separator in filenames
2025-12-29 11:26:02 -05:00
444317a71b modified: async_web_fetcher.py
modified:   database.py
	modified:   neopig.py
2025-12-29 05:08:31 -05:00
bc11c516b6 Add filevault system with async wrapper and convert sync ops to async
New files:
- filevault.py: Hash-based file storage with use_pairs option (v1.1.0)
- async_filevault.py: Async wrapper using asyncio.to_thread()
- domain_vault.py: Triple vault system for web archival (HTML, Media, Linkpeek)
- screenshot.py: Async screenshot capture using uri2png
- tests/unit/: Comprehensive test suite (85 tests)

Sync-to-async conversions:
- storage.py: Wrap Path operations in asyncio.to_thread()
- domain_vault.py: Wrap exists(), mkdir(), rglob(), os.walk() in asyncio.to_thread()
- screenshot.py: Wrap read_bytes(), write_bytes(), unlink() in asyncio.to_thread()

All sync filesystem operations now run in thread pool to avoid blocking async loop.
2025-12-22 20:18:20 -05:00
6dab26d6fa Initial neopig: async media crawler with MD5 deduplication
- Multi-target concurrent crawling support
- Content-addressable vault storage (MD5 hash)
- SQLite database with page context for searchability
- Live SERP with real-time polling feed (/live)
- URI naming convention (media_uri, page_uri)
- GIF and image object-fit: contain for proper display
2025-12-21 17:26:42 -05:00