ForageFacebook 2.0.0__tar.gz → 2.2.0__tar.gz

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.
Files changed (47) hide show
  1. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/.github/dependabot.yml +10 -0
  2. foragefacebook-2.2.0/.github/workflows/ci.yml +60 -0
  3. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/.github/workflows/publish.yml +28 -10
  4. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/.gitignore +5 -0
  5. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/AGENTS.md +10 -8
  6. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/CHANGELOG.md +36 -1
  7. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/CONTRIBUTING.md +6 -4
  8. foragefacebook-2.2.0/Makefile +14 -0
  9. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/PKG-INFO +69 -27
  10. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/README.md +68 -25
  11. foragefacebook-2.2.0/docs/capabilities.md +37 -0
  12. foragefacebook-2.2.0/docs/research/2026-09-20-cli-portfolio-audit.md +869 -0
  13. foragefacebook-2.2.0/docs/research/2026-09-20-cli-portfolio-repairs.md +106 -0
  14. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/pyproject.toml +1 -2
  15. foragefacebook-2.2.0/scripts/live-e2e.py +57 -0
  16. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/scripts/release.sh +6 -2
  17. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/__init__.py +1 -1
  18. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/auth.py +13 -2
  19. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/cli.py +144 -6
  20. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/exporter.py +3 -0
  21. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/models.py +15 -0
  22. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/parser.py +118 -124
  23. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/scraper.py +145 -29
  24. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/uv.lock +2 -110
  25. foragefacebook-2.0.0/.github/workflows/ci.yml +0 -91
  26. foragefacebook-2.0.0/tests/__init__.py +0 -1
  27. foragefacebook-2.0.0/tests/conftest.py +0 -176
  28. foragefacebook-2.0.0/tests/fixtures/comment_simple.html +0 -7
  29. foragefacebook-2.0.0/tests/fixtures/comment_with_replies.html +0 -23
  30. foragefacebook-2.0.0/tests/fixtures/post_long_content.html +0 -13
  31. foragefacebook-2.0.0/tests/fixtures/post_realistic.html +0 -54
  32. foragefacebook-2.0.0/tests/fixtures/post_simple.html +0 -13
  33. foragefacebook-2.0.0/tests/fixtures/post_sponsored.html +0 -7
  34. foragefacebook-2.0.0/tests/fixtures/post_with_emoji.html +0 -12
  35. foragefacebook-2.0.0/tests/fixtures/post_with_media.html +0 -15
  36. foragefacebook-2.0.0/tests/fixtures/post_with_see_more.html +0 -12
  37. foragefacebook-2.0.0/tests/test_auth.py +0 -48
  38. foragefacebook-2.0.0/tests/test_cli.py +0 -161
  39. foragefacebook-2.0.0/tests/test_exporter.py +0 -669
  40. foragefacebook-2.0.0/tests/test_marketplace.py +0 -52
  41. foragefacebook-2.0.0/tests/test_models.py +0 -158
  42. foragefacebook-2.0.0/tests/test_parser.py +0 -362
  43. foragefacebook-2.0.0/tests/test_scraper.py +0 -460
  44. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/.github/FUNDING.yml +0 -0
  45. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/CLAUDE.md +0 -0
  46. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/LICENSE +0 -0
  47. {foragefacebook-2.0.0 → foragefacebook-2.2.0}/SECURITY.md +0 -0
@@ -4,6 +4,11 @@ updates:
4
4
  directory: "/"
5
5
  schedule:
6
6
  interval: "weekly"
7
+ cooldown:
8
+ default-days: 7
9
+ groups:
10
+ dependencies:
11
+ patterns: ["*"]
7
12
  open-pull-requests-limit: 5
8
13
  labels:
9
14
  - "dependencies"
@@ -12,6 +17,11 @@ updates:
12
17
  directory: "/"
13
18
  schedule:
14
19
  interval: "weekly"
20
+ cooldown:
21
+ default-days: 7
22
+ groups:
23
+ dependencies:
24
+ patterns: ["*"]
15
25
  open-pull-requests-limit: 5
16
26
  labels:
17
27
  - "dependencies"
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: CI
3
+
4
+ on:
5
+ push:
6
+ branches: [master, main]
7
+ pull_request:
8
+ branches: [master, main]
9
+ workflow_call:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: ci-${{ github.workflow }}-${{ github.ref }}
16
+ cancel-in-progress: ${{ github.ref_type != 'tag' }}
17
+
18
+ jobs:
19
+ quality:
20
+ runs-on: ubuntu-latest
21
+ timeout-minutes: 15
22
+ steps:
23
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+ with:
25
+ persist-credentials: false
26
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
27
+ with:
28
+ version: "0.12.17"
29
+ python-version: "3.14"
30
+ - run: uv sync --locked --all-extras --no-config
31
+ - name: Check source and lockfile
32
+ run: |
33
+ uv lock --check --no-config
34
+ uv run --locked --no-config ruff check src/ scripts/
35
+ uv run --locked --no-config ruff format --check src/ scripts/
36
+ uv run --locked --no-config ty check src/forage/
37
+ - name: Audit workflows and release helper
38
+ run: |
39
+ go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
40
+ uvx --no-config zizmor==1.30.1 --no-progress .github/workflows
41
+ shellcheck scripts/release.sh
42
+ go run mvdan.cc/sh/v3/cmd/shfmt@v3.14.1 -i 2 -d scripts/release.sh
43
+ - name: Build and install wheel
44
+ run: |
45
+ uv build --no-config
46
+ uv venv /tmp/forage-install-smoke
47
+ uv pip install --no-config --python /tmp/forage-install-smoke/bin/python dist/*.whl
48
+ /tmp/forage-install-smoke/bin/forage --version
49
+ /tmp/forage-install-smoke/bin/forage --help
50
+
51
+ check:
52
+ if: always()
53
+ needs: [quality]
54
+ runs-on: ubuntu-latest
55
+ timeout-minutes: 2
56
+ steps:
57
+ - name: Require all checks
58
+ env:
59
+ QUALITY_RESULT: ${{ needs.quality.result }}
60
+ run: test "$QUALITY_RESULT" = success
@@ -1,3 +1,4 @@
1
+ ---
1
2
  name: Publish to PyPI
2
3
 
3
4
  on:
@@ -13,34 +14,50 @@ on:
13
14
  permissions:
14
15
  contents: read
15
16
 
17
+ concurrency:
18
+ group: publish-${{ github.ref }}
19
+ cancel-in-progress: false
20
+
16
21
  jobs:
22
+ checks:
23
+ # actionlint 1.7.12 does not yet parse the new $/ syntax. This resolves the same commit.
24
+ uses: ./.github/workflows/ci.yml # zizmor: ignore[self-repository]
17
25
  build:
26
+ needs: checks
27
+ timeout-minutes: 10
18
28
  runs-on: ubuntu-latest
19
29
 
20
30
  steps:
21
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
31
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
32
  with:
23
33
  persist-credentials: false
24
34
 
25
35
  - name: Install uv
26
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
36
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
27
37
  with:
28
38
  enable-cache: false
39
+ version: "0.12.17"
40
+ python-version: "3.14"
29
41
 
30
- - name: Set up Python
31
- run: uv python install 3.12
42
+ - name: Verify release version
43
+ if: github.event_name == 'release'
44
+ env:
45
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
46
+ run: |
47
+ uv run --no-project --no-config python -c 'import os, tomllib; from pathlib import Path; version = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]; assert os.environ["RELEASE_TAG"] == "v" + version, "tag/package version mismatch"'
32
48
 
33
49
  - name: Build package
34
- run: uv build
50
+ run: uv build --no-config
35
51
 
36
52
  - name: Upload artifacts
37
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
53
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
38
54
  with:
39
55
  name: dist
40
56
  path: dist/
41
57
 
42
58
  publish-test-pypi:
43
59
  needs: build
60
+ timeout-minutes: 10
44
61
  runs-on: ubuntu-latest
45
62
  if: github.event_name == 'workflow_dispatch' && github.event.inputs.test_pypi == 'true'
46
63
 
@@ -53,18 +70,19 @@ jobs:
53
70
 
54
71
  steps:
55
72
  - name: Download artifacts
56
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
73
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
57
74
  with:
58
75
  name: dist
59
76
  path: dist/
60
77
 
61
78
  - name: Publish to Test PyPI
62
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
79
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
63
80
  with:
64
81
  repository-url: https://test.pypi.org/legacy/
65
82
 
66
83
  publish-pypi:
67
84
  needs: build
85
+ timeout-minutes: 10
68
86
  runs-on: ubuntu-latest
69
87
  if: github.event_name == 'release'
70
88
 
@@ -77,10 +95,10 @@ jobs:
77
95
 
78
96
  steps:
79
97
  - name: Download artifacts
80
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
98
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
81
99
  with:
82
100
  name: dist
83
101
  path: dist/
84
102
 
85
103
  - name: Publish to PyPI
86
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
104
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -60,3 +60,8 @@ output/
60
60
  .env
61
61
  .env.local
62
62
  .env.*.local
63
+
64
+ # CLI end-to-end runner
65
+ /node_modules/
66
+ /.e2e/
67
+ !package-lock.json
@@ -12,11 +12,10 @@ Forage is a CLI tool for scraping private Facebook groups to analyze community d
12
12
  uv sync # Install deps (always use uv, not pip/python)
13
13
  uv run forage --help # Run CLI
14
14
  uv run ty check src/ # Type check (Astral's ty)
15
- uv run pytest # Run all tests
16
- uv run pytest tests/test_parser.py # Run a single test file
17
- uv run pytest -k "test_name" # Run a single test by name
18
- uv run ruff check src/ tests/ # Lint
19
- uv run ruff format src/ tests/ # Auto-format (CI enforces this)
15
+ make check # Lint, type check, and build
16
+ make test # Read-only live Facebook checks
17
+ uv run ruff check src/ scripts/ # Lint
18
+ uv run ruff format src/ scripts/ # Format
20
19
  ```
21
20
 
22
21
  ## Manual Testing
@@ -59,7 +58,10 @@ Random viewport sizes, `human_delay()` with variance, real Chromium browser, ses
59
58
 
60
59
  ### Test Structure
61
60
 
62
- Tests in `tests/` with HTML fixtures in `tests/fixtures/` (realistic Facebook HTML snippets). `conftest.py` provides `create_mock_element()` for creating mock Playwright `ElementHandle` objects from HTML strings.
61
+ Use `make test` for real CLI reads through the saved Facebook session. Install Chromium first.
62
+ Set `FORAGE_E2E_GROUP` to test a group; otherwise the test searches Marketplace for computers.
63
+ Keep unit tests, mock tests, and fake-server suites out of this repo. CI runs static checks and package builds.
64
+ Live tests must not log in, change credentials, or write to Facebook.
63
65
 
64
66
  ## Fragile Code — Check Here First When Things Break
65
67
 
@@ -99,9 +101,9 @@ Steps:
99
101
  1. Update `version` in both `pyproject.toml` and `src/forage/__init__.py`
100
102
  2. Run `uv lock --no-config` — refresh locked editable package metadata without leaking user-level uv settings
101
103
  3. Update `CHANGELOG.md` — add version section under `## [Unreleased]`, update comparison links at bottom
102
- 4. Run `uv lock --check --no-config` and `uv run ruff format src/ tests/` — CI enforces formatting
104
+ 4. Run `uv lock --check --no-config` and `uv run ruff format src/ scripts/` — CI enforces formatting
103
105
  5. Commit: `git commit -m "chore: release vX.Y.Z"`
104
- 6. Push: `git push origin master`
106
+ 6. Push a feature branch and open a PR. Merge only after all required checks pass. Never push directly to `master`.
105
107
  7. Tag: `git tag -a vX.Y.Z -m "Release vX.Y.Z"` then `git push origin vX.Y.Z`
106
108
  8. Create GitHub release: `gh release create vX.Y.Z --title "vX.Y.Z" --notes "..."`
107
109
  — This triggers `.github/workflows/publish.yml` which publishes to PyPI via OIDC (no tokens)
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.2.0] - 2026-10-10
11
+
12
+ ### Changed
13
+
14
+ - Replace unit and synthetic browser tests with read-only live CLI checks.
15
+ - Keep lint, type checks, and package build/install checks in CI.
16
+ - Remove pytest and the Node test harness.
17
+ - Add common `make check`, `make test`, and `make build` entry points.
18
+ - Add `forage version` beside the existing `--version` flag.
19
+
20
+ ## [2.1.0] - 2026-09-20
21
+
22
+ ### Fixed
23
+
24
+ - Preserve native comment IDs and scope fallback IDs to their post to prevent SQLite collisions.
25
+ - Preserve full visible post text, expand supported “See more” controls, and exclude nested comments.
26
+ - Parse compact comment counts and retain query-based post permalinks.
27
+ - Include the full `--until` day, reject invalid date ranges before login, and preserve “Yesterday” clock times.
28
+ - Report missing feeds and Marketplace authentication failures instead of misleading empty results or timeouts.
29
+ - Bind Marketplace coordinates to the requested listing ID; exclude unbound locations.
30
+ - Skip full parsing for group posts whose reliable permalink ID is already present.
31
+ - Count accepted Marketplace listings toward `--limit`; keep a separate `--max-candidates` cost bound.
32
+ - Store session directories and authentication state with owner-only POSIX permissions.
33
+ - Reject missing CSV/SQLite output arguments before browser work.
34
+
35
+ ### Added
36
+
37
+ - JSON collection diagnostics and explicit content/comment completeness indicators.
38
+ - `forage doctor` for local setup checks without Facebook access.
39
+ - `forage export` to reuse existing exporters with saved group JSON.
40
+ - Network-blocked Chromium regression tests and a documented capability contract.
41
+
42
+
10
43
  ## [2.0.0] - 2026-08-29
11
44
 
12
45
  ### Added
@@ -214,7 +247,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
214
247
  - Sensitive files excluded via `.gitignore`
215
248
  - Security guidelines in SECURITY.md
216
249
 
217
- [Unreleased]: https://github.com/jwmoss/forage/compare/v2.0.0...HEAD
250
+ [Unreleased]: https://github.com/jwmoss/forage/compare/v2.2.0...HEAD
251
+ [2.2.0]: https://github.com/jwmoss/forage/compare/v2.1.0...v2.2.0
252
+ [2.1.0]: https://github.com/jwmoss/forage/compare/v2.0.0...v2.1.0
218
253
  [2.0.0]: https://github.com/jwmoss/forage/compare/v1.2.1...v2.0.0
219
254
  [1.2.1]: https://github.com/jwmoss/forage/compare/v1.2.0...v1.2.1
220
255
  [1.2.0]: https://github.com/jwmoss/forage/compare/v1.1.0...v1.2.0
@@ -22,7 +22,8 @@ Thank you for your interest in contributing to Forage!
22
22
 
23
23
  4. Run tests:
24
24
  ```bash
25
- uv run pytest tests/ -v
25
+ make check
26
+ make test
26
27
  ```
27
28
 
28
29
  5. Run type checker:
@@ -55,15 +56,16 @@ Example: `feat: add SQLite export option`
55
56
  1. Fork the repository
56
57
  2. Create a feature branch (`git checkout -b feat/my-feature`)
57
58
  3. Make your changes
58
- 4. Ensure tests pass (`uv run pytest tests/`)
59
+ 4. Ensure tests pass (`make check`; run `make test` with a saved session)
59
60
  5. Ensure type checking passes (`uv run ty check src/forage/`)
60
61
  6. Commit with a descriptive message
61
62
  7. Push and open a pull request
62
63
 
63
64
  ## Testing
64
65
 
65
- - Write tests for new functionality
66
- - Use pytest fixtures for common test data
66
+ - Verify behavior through the real CLI against Facebook.
67
+ - Keep live checks read-only and use the existing session.
68
+ - Do not add unit tests, mock tests, or fake-server suites.
67
69
  - Mock external dependencies (Playwright, network)
68
70
  - Aim for good coverage of edge cases
69
71
 
@@ -0,0 +1,14 @@
1
+ .PHONY: check test build
2
+
3
+ check:
4
+ uv lock --check --no-config
5
+ uv run --locked --no-config ruff check src/ scripts/
6
+ uv run --locked --no-config ruff format --check src/ scripts/
7
+ uv run --locked --no-config ty check src/forage/
8
+ uv build --no-config
9
+
10
+ test:
11
+ uv run --locked --no-config python scripts/live-e2e.py
12
+
13
+ build:
14
+ uv build --no-config
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ForageFacebook
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: CLI tool to scrape posts, comments, and reactions from private Facebook groups
5
5
  Project-URL: Homepage, https://github.com/jwmoss/forage
6
6
  Project-URL: Repository, https://github.com/jwmoss/forage
@@ -28,7 +28,6 @@ Requires-Dist: playwright>=1.40
28
28
  Requires-Dist: pydantic>=2.0
29
29
  Requires-Dist: rich>=13.0
30
30
  Provides-Extra: dev
31
- Requires-Dist: pytest>=8.0; extra == 'dev'
32
31
  Requires-Dist: ruff>=0.8; extra == 'dev'
33
32
  Requires-Dist: ty>=0.0.1a6; extra == 'dev'
34
33
  Description-Content-Type: text/markdown
@@ -38,7 +37,9 @@ Description-Content-Type: text/markdown
38
37
  [![CI](https://github.com/jwmoss/forage/actions/workflows/ci.yml/badge.svg)](https://github.com/jwmoss/forage/actions/workflows/ci.yml)
39
38
  [![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg)](LICENSE)
40
39
 
41
- CLI tool to scrape posts, comments, and reactions from private Facebook groups using browser automation.
40
+ CLI tool to collect Facebook group posts and search Marketplace electronics through browser automation.
41
+
42
+ Forage uses browser pages, not a public REST API. Results are partial observations. See the [capability contract](docs/capabilities.md).
42
43
 
43
44
  ## Table of Contents
44
45
 
@@ -124,7 +125,7 @@ uv run forage scrape your-group-slug --min-reactions 5
124
125
  uv run forage scrape your-group-slug --top-comments 10
125
126
 
126
127
  # Watch the browser (debugging)
127
- uv run forage scrape your-group-slug --no-headless -v
128
+ uv run forage -v scrape your-group-slug --no-headless
128
129
 
129
130
  # Slower scraping to avoid rate limits
130
131
  uv run forage scrape your-group-slug --delay 5.0
@@ -148,6 +149,9 @@ Global Flags:
148
149
  Commands:
149
150
  login Open browser for interactive Facebook login
150
151
  scrape Scrape posts from a Facebook group
152
+ marketplace Search Marketplace electronics
153
+ doctor Check local browser and session setup
154
+ export Convert saved group JSON without Facebook access
151
155
  ```
152
156
 
153
157
  #### scrape flags
@@ -156,7 +160,7 @@ Commands:
156
160
  |------|---------|-------------|
157
161
  | `--days` | `7` | Posts from last N days |
158
162
  | `--since` | - | Start date (ISO 8601: YYYY-MM-DD) |
159
- | `--until` | - | End date (ISO 8601: YYYY-MM-DD) |
163
+ | `--until` | - | Inclusive end date (YYYY-MM-DD), in the local timezone |
160
164
  | `--limit` | `0` | Max posts (0 = unlimited) |
161
165
  | `--delay` | `2.0` | Seconds between page loads |
162
166
  | `--min-reactions` | `0` | Min reactions for comments |
@@ -169,6 +173,46 @@ Commands:
169
173
  | `--no-headless` | `false` | Show browser window |
170
174
  | `--browser` | `chromium` | Browser: chromium, firefox, webkit |
171
175
 
176
+ ### Marketplace search
177
+
178
+ ```bash
179
+ uv run forage marketplace "rtx 4070" --city wilmington --radius 40 --limit 20 -o listings.json
180
+ ```
181
+
182
+ `--limit` counts accepted listings. `--max-candidates` bounds the search cost, including rejected candidates; its default is 200.
183
+ Forage excludes distant listings and listings whose coordinates it cannot verify. City is the search center, not a city-only filter.
184
+ The current parser supports electronics with dollar prices or “Free” labels. Search relevance follows Facebook's results.
185
+
186
+ ### Local diagnosis and saved exports
187
+
188
+ ```bash
189
+ # Check browser installation and session permissions without contacting Facebook
190
+ uv run forage doctor
191
+
192
+ # Convert an existing group result without another scrape or login
193
+ uv run forage export posts.json --format sqlite --output posts.db
194
+ uv run forage export posts.json --format csv --output posts.csv
195
+ uv run forage export posts.json --format llm --output compact.json
196
+ ```
197
+
198
+ `doctor` checks local files. It does not prove that a session remains valid. Install the selected browser before login:
199
+
200
+ ```bash
201
+ uv run playwright install chromium
202
+ # For --browser firefox or webkit, install that browser instead.
203
+ ```
204
+
205
+ JSON and LLM JSON include collection diagnostics: stop reason, candidate count, rejected count, parse failures, and unknown timestamps.
206
+ `partial: true` means the scraper cannot prove exhaustive coverage. A verified empty-page message produces `stop_reason: "empty"`.
207
+ `content_truncated` identifies an unexpanded post. `comments_complete: false` means comment coverage is unverified.
208
+ Keep the original JSON if you need these diagnostics beside CSV or SQLite output.
209
+
210
+ Dates use the local system and browser timezone. `--until` includes the entire selected day. Reversed dates fail before login.
211
+ The scraper retains posts with unknown timestamps and reports their count.
212
+
213
+ Exit codes: `0` for a completed command, `1` for collection or output errors, `2` for invalid arguments,
214
+ `3` for required authentication, and `4` for a recognized unavailable group. Empty results alone do not prove an empty group.
215
+
172
216
  ### SQLite Export
173
217
 
174
218
  Export directly to SQLite for easier analysis:
@@ -274,10 +318,23 @@ uv sync --extra dev
274
318
  # Run type checker
275
319
  uv run ty check src/
276
320
 
277
- # Run tests
278
- uv run pytest
321
+ # Run static checks and build the package
322
+ make check
323
+
324
+ # Install Chromium for live reads
325
+ uv run playwright install chromium
326
+
327
+ # Read Marketplace through the saved Facebook session
328
+ make test
329
+
330
+ # Read one post from a group you can access
331
+ FORAGE_E2E_GROUP=your-group-slug make test
279
332
  ```
280
333
 
334
+ Live tests run the real CLI and Facebook pages. They do not use fixtures or mock servers.
335
+ They need an existing login session. CI runs static checks and package build/install checks.
336
+
337
+
281
338
  ## Architecture
282
339
 
283
340
  ```
@@ -295,29 +352,14 @@ src/forage/
295
352
  - Facebook's HTML structure changes frequently
296
353
  - Rate limiting may require slower scraping
297
354
  - Individual reaction types not broken out (only total)
298
- - Session cookies expire after ~30 days
355
+ - Session lifetime depends on Facebook; use `forage login` when authentication fails
356
+ - English page labels define the supported parser scope
357
+ - Previously overwritten SQLite comments require a new scrape; upgrades cannot recover them
299
358
 
300
359
  ## Roadmap
301
360
 
302
- Planned features and improvements:
303
-
304
- ### High Priority
305
- - [ ] **Cookie import** - Import cookies from browser extensions (EditThisCookie, Netscape format)
306
- - [ ] **Incremental scraping** - Only fetch posts newer than last scrape
307
- - [ ] **Progress persistence** - Resume interrupted scrapes
308
-
309
- ### Medium Priority
310
- - [ ] **Multiple groups** - Scrape multiple groups in one command
311
- - [ ] **Media extraction** - Download images/videos from posts
312
- - [ ] **Reaction breakdown** - Extract individual reaction types (like, love, etc.)
313
- - [ ] **Author statistics** - Aggregate stats per author
314
- - [ ] **Scheduled scraping** - Cron-friendly mode with locking
315
-
316
- ### Nice to Have
317
- - [ ] **Web UI** - Local web interface for browsing scraped data
318
- - [ ] **Webhook notifications** - Notify on new posts matching criteria
319
- - [ ] **Public group support** - Scrape without login for public groups
320
- - [ ] **Parallel scraping** - Speed up multi-group scrapes
361
+ Prioritize sanitized DOM fixtures and verified selector updates. Add incremental collection only after identity and completeness checks support it.
362
+ Use shell loops for batches, system scheduling for recurring commands, and SQLite or jq for analysis.
321
363
 
322
364
  ### Contributing
323
365
 
@@ -3,7 +3,9 @@
3
3
  [![CI](https://github.com/jwmoss/forage/actions/workflows/ci.yml/badge.svg)](https://github.com/jwmoss/forage/actions/workflows/ci.yml)
4
4
  [![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg)](LICENSE)
5
5
 
6
- CLI tool to scrape posts, comments, and reactions from private Facebook groups using browser automation.
6
+ CLI tool to collect Facebook group posts and search Marketplace electronics through browser automation.
7
+
8
+ Forage uses browser pages, not a public REST API. Results are partial observations. See the [capability contract](docs/capabilities.md).
7
9
 
8
10
  ## Table of Contents
9
11
 
@@ -89,7 +91,7 @@ uv run forage scrape your-group-slug --min-reactions 5
89
91
  uv run forage scrape your-group-slug --top-comments 10
90
92
 
91
93
  # Watch the browser (debugging)
92
- uv run forage scrape your-group-slug --no-headless -v
94
+ uv run forage -v scrape your-group-slug --no-headless
93
95
 
94
96
  # Slower scraping to avoid rate limits
95
97
  uv run forage scrape your-group-slug --delay 5.0
@@ -113,6 +115,9 @@ Global Flags:
113
115
  Commands:
114
116
  login Open browser for interactive Facebook login
115
117
  scrape Scrape posts from a Facebook group
118
+ marketplace Search Marketplace electronics
119
+ doctor Check local browser and session setup
120
+ export Convert saved group JSON without Facebook access
116
121
  ```
117
122
 
118
123
  #### scrape flags
@@ -121,7 +126,7 @@ Commands:
121
126
  |------|---------|-------------|
122
127
  | `--days` | `7` | Posts from last N days |
123
128
  | `--since` | - | Start date (ISO 8601: YYYY-MM-DD) |
124
- | `--until` | - | End date (ISO 8601: YYYY-MM-DD) |
129
+ | `--until` | - | Inclusive end date (YYYY-MM-DD), in the local timezone |
125
130
  | `--limit` | `0` | Max posts (0 = unlimited) |
126
131
  | `--delay` | `2.0` | Seconds between page loads |
127
132
  | `--min-reactions` | `0` | Min reactions for comments |
@@ -134,6 +139,46 @@ Commands:
134
139
  | `--no-headless` | `false` | Show browser window |
135
140
  | `--browser` | `chromium` | Browser: chromium, firefox, webkit |
136
141
 
142
+ ### Marketplace search
143
+
144
+ ```bash
145
+ uv run forage marketplace "rtx 4070" --city wilmington --radius 40 --limit 20 -o listings.json
146
+ ```
147
+
148
+ `--limit` counts accepted listings. `--max-candidates` bounds the search cost, including rejected candidates; its default is 200.
149
+ Forage excludes distant listings and listings whose coordinates it cannot verify. City is the search center, not a city-only filter.
150
+ The current parser supports electronics with dollar prices or “Free” labels. Search relevance follows Facebook's results.
151
+
152
+ ### Local diagnosis and saved exports
153
+
154
+ ```bash
155
+ # Check browser installation and session permissions without contacting Facebook
156
+ uv run forage doctor
157
+
158
+ # Convert an existing group result without another scrape or login
159
+ uv run forage export posts.json --format sqlite --output posts.db
160
+ uv run forage export posts.json --format csv --output posts.csv
161
+ uv run forage export posts.json --format llm --output compact.json
162
+ ```
163
+
164
+ `doctor` checks local files. It does not prove that a session remains valid. Install the selected browser before login:
165
+
166
+ ```bash
167
+ uv run playwright install chromium
168
+ # For --browser firefox or webkit, install that browser instead.
169
+ ```
170
+
171
+ JSON and LLM JSON include collection diagnostics: stop reason, candidate count, rejected count, parse failures, and unknown timestamps.
172
+ `partial: true` means the scraper cannot prove exhaustive coverage. A verified empty-page message produces `stop_reason: "empty"`.
173
+ `content_truncated` identifies an unexpanded post. `comments_complete: false` means comment coverage is unverified.
174
+ Keep the original JSON if you need these diagnostics beside CSV or SQLite output.
175
+
176
+ Dates use the local system and browser timezone. `--until` includes the entire selected day. Reversed dates fail before login.
177
+ The scraper retains posts with unknown timestamps and reports their count.
178
+
179
+ Exit codes: `0` for a completed command, `1` for collection or output errors, `2` for invalid arguments,
180
+ `3` for required authentication, and `4` for a recognized unavailable group. Empty results alone do not prove an empty group.
181
+
137
182
  ### SQLite Export
138
183
 
139
184
  Export directly to SQLite for easier analysis:
@@ -239,10 +284,23 @@ uv sync --extra dev
239
284
  # Run type checker
240
285
  uv run ty check src/
241
286
 
242
- # Run tests
243
- uv run pytest
287
+ # Run static checks and build the package
288
+ make check
289
+
290
+ # Install Chromium for live reads
291
+ uv run playwright install chromium
292
+
293
+ # Read Marketplace through the saved Facebook session
294
+ make test
295
+
296
+ # Read one post from a group you can access
297
+ FORAGE_E2E_GROUP=your-group-slug make test
244
298
  ```
245
299
 
300
+ Live tests run the real CLI and Facebook pages. They do not use fixtures or mock servers.
301
+ They need an existing login session. CI runs static checks and package build/install checks.
302
+
303
+
246
304
  ## Architecture
247
305
 
248
306
  ```
@@ -260,29 +318,14 @@ src/forage/
260
318
  - Facebook's HTML structure changes frequently
261
319
  - Rate limiting may require slower scraping
262
320
  - Individual reaction types not broken out (only total)
263
- - Session cookies expire after ~30 days
321
+ - Session lifetime depends on Facebook; use `forage login` when authentication fails
322
+ - English page labels define the supported parser scope
323
+ - Previously overwritten SQLite comments require a new scrape; upgrades cannot recover them
264
324
 
265
325
  ## Roadmap
266
326
 
267
- Planned features and improvements:
268
-
269
- ### High Priority
270
- - [ ] **Cookie import** - Import cookies from browser extensions (EditThisCookie, Netscape format)
271
- - [ ] **Incremental scraping** - Only fetch posts newer than last scrape
272
- - [ ] **Progress persistence** - Resume interrupted scrapes
273
-
274
- ### Medium Priority
275
- - [ ] **Multiple groups** - Scrape multiple groups in one command
276
- - [ ] **Media extraction** - Download images/videos from posts
277
- - [ ] **Reaction breakdown** - Extract individual reaction types (like, love, etc.)
278
- - [ ] **Author statistics** - Aggregate stats per author
279
- - [ ] **Scheduled scraping** - Cron-friendly mode with locking
280
-
281
- ### Nice to Have
282
- - [ ] **Web UI** - Local web interface for browsing scraped data
283
- - [ ] **Webhook notifications** - Notify on new posts matching criteria
284
- - [ ] **Public group support** - Scrape without login for public groups
285
- - [ ] **Parallel scraping** - Speed up multi-group scrapes
327
+ Prioritize sanitized DOM fixtures and verified selector updates. Add incremental collection only after identity and completeness checks support it.
328
+ Use shell loops for batches, system scheduling for recurring commands, and SQLite or jq for analysis.
286
329
 
287
330
  ### Contributing
288
331