PHPackages                             wiserwebsolutions/laravel-boarddocs-scraper - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [PDF &amp; Document Generation](/categories/documents)
4. /
5. wiserwebsolutions/laravel-boarddocs-scraper

ActiveLibrary[PDF &amp; Document Generation](/categories/documents)

wiserwebsolutions/laravel-boarddocs-scraper
===========================================

A Laravel package that scans public BoardDocs sites for agendas and attachments, exposes a fluent unofficial API, and exports self-contained meeting PDFs with remote links rewritten as in-document anchors.

v3.6.1(2w ago)0115MITPHPPHP ^8.2

Since Aug 6Pushed 1mo agoCompare

[ Source](https://github.com/WiserWebSolutions/laravel-boarddocs-scraper)[ Packagist](https://packagist.org/packages/wiserwebsolutions/laravel-boarddocs-scraper)[ RSS](/packages/wiserwebsolutions-laravel-boarddocs-scraper/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (12)Versions (16)Used By (0)

Laravel BoardDocs Scraper
=========================

[](#laravel-boarddocs-scraper)

> **AI-authored notice**The initial commit of this project was made by an AI assistant that was asked to convert specific components of a private Python script into a publishable Laravel package.

A Laravel package that scans **public** [BoardDocs](https://go.boarddocs.com) sites for meeting agendas and attachments, exposes a fluent, Laravel-flavored "unofficial API," and exports **self-contained meeting PDFs** — with remote BoardDocs links rewritten into in-document anchors — plus a JSONL search index that plugs into the [Laravel AI SDK](https://laravel.com/docs/13.x/ai-sdk).

It is a PHP/Laravel port of the Python [AbandonBoard](https://github.com/) exporter, so a district can archive its agendas before losing BoardDocs access. **Public data only** — the private/login features of the original are intentionally omitted.

```
BoardDocs()->site('pa/phoe')
    ->committees()->first()
    ->agenda()->withAttachments()
    ->toPdf()
    ->save();
```

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11, 12, or 13
- `tecnickcom/tcpdf` + `setasign/fpdi` (installed automatically) for PDF assembly

Installation
------------

[](#installation)

```
composer require graboyes/laravel-boarddocs-scraper
php artisan vendor:publish --tag=boarddocs-config
```

Set your default site in `.env`:

```
BOARDDOCS_SITE="pa/phoe"
```

The fluent API
--------------

[](#the-fluent-api)

The `BoardDocs()` helper (or the `BoardDocs` facade) is the entry point. Committee and meeting lists are returned as Laravel Collections and are cached, so `->first()`, `->firstWhere()`, `->map()` etc. all work as expected.

```
use BoardDocsScraper\Facades\BoardDocs;

// Discover committees (cached)
$committees = BoardDocs::site('pa/phoe')->committees();      // Collection
$board = BoardDocs::site('pa/phoe')->committeeNamed('Directors');

// Meetings for a committee (newest first, cached)
$meetings = $board->meetings();                              // Collection
$latest   = $board->latest();                                // ?Meeting

// Agenda for a meeting
$agenda = $latest->agenda();
$agenda->items();          // Collection
$agenda->attachments();    // Collection (metadata; no download)
$agenda->text();           // plain-text agenda summary

// Render a self-contained PDF
$pdf = $agenda->withAttachments()->toPdf();
$pdf->pageCount();                     // int
$pdf->attachments();                   // SavedAttachment[]
$pdf->save();                          // -> "boarddocs/pa-phoe/Public//-Agenda.pdf"
$pdf->save('custom/path.pdf', 'r2');   // any Laravel disk
return $pdf->response();               // inline PDF HTTP response
```

The one-liner from the top works too:

```
BoardDocs()->site('pa/phoe')->committees()->first()->agenda()->withAttachments()->toPdf();
```

The low-level HTTP client (the raw "unofficial API") is available if you need it:

```
$client = BoardDocs::client('pa/phoe');
$client->discoverCommittees();
$client->listMeetings($committeeId);
$client->fetchPrintAgendaHtml($meetingId, $committeeId);
```

Self-contained meeting PDFs
---------------------------

[](#self-contained-meeting-pdfs)

With `self_contained` enabled (default), each meeting PDF contains the printed agenda followed by every PDF attachment merged inline, one **bookmark per attachment**, and non-PDF attachments embedded as file attachments. When `remap_links` is on, any remote BoardDocs link in the agenda is rewritten into an **internal PDF anchor** that jumps to the merged attachment's page — so the document stays fully navigable offline, even if a district's BoardDocs subscription ends.

Two rendering engines are available (configure `boarddocs.pdf.engine`):

EngineFidelityLink remapDependencies`tcpdf` (default)Good (agenda pages are simple)✅ Yespure PHP`browsershot`High (real Chrome)❌ No (attachments still merged)`spatie/browsershot` + Node + ChromiumScanning &amp; the search index
-------------------------------

[](#scanning--the-search-index)

```
# Export a whole site (all committees), updating output/index.jsonl
php artisan boarddocs:scan --site=pa/phoe

# Scope it
php artisan boarddocs:scan --site=pa/phoe --committee=CTNNDT5F7A3B --limit=5 --since=2024-01-01
php artisan boarddocs:scan --dry-run          # list without downloading
php artisan boarddocs:scan --no-attachments   # agenda-only PDFs
php artisan boarddocs:scan --engine=browsershot

# Search the exported index
php artisan boarddocs:search budget transportation --committee=Finance --limit=10
```

`index.jsonl` has the same shape as the original project (one meeting per line):

```
{"path":"pa-phoe/Public/Policy Committee/2026-06-01-Agenda.pdf","district":"pa-phoe",
 "visibility":"Public","committee":"Policy Committee","date":"2026-06-01","page_count":7,
 "agenda_text":"…","attachments":[{"title":"May 18, 2026 Policy Minutes.pdf","page":4}]}
```

Meetings whose PDF already exists are skipped, except those within `scan.refresh_recent_days` (default 7) which are re-exported.

Laravel AI SDK integration
--------------------------

[](#laravel-ai-sdk-integration)

The package ships ready-to-use [AI SDK tools](https://laravel.com/docs/13.x/ai-sdk) so an agent can search and consume the archive. Install the SDK to use them:

```
composer require laravel/ai
```

Use the bundled agent directly:

```
use BoardDocsScraper\Ai\BoardDocsAgent;

$answer = (new BoardDocsAgent)->prompt('What did the Policy Committee decide about edtech?');
```

…or register the individual tools on your own agent:

```
use BoardDocsScraper\Ai\Tools\SearchAgendasTool;
use BoardDocsScraper\Ai\Tools\GetMeetingTool;
use BoardDocsScraper\Ai\Tools\ListCommitteesTool;

class MyAgent implements Agent, HasTools
{
    use Promptable;

    public function tools(): iterable
    {
        return [new SearchAgendasTool, new GetMeetingTool, new ListCommitteesTool];
    }
}
```

- **SearchAgendasTool** — keyword search over agenda text + attachment titles; returns meetings with snippets and the `path` to fetch full details.
- **GetMeetingTool** — full indexed record (agenda text, attachments + pages) for one `path`.
- **ListCommitteesTool** — live committee list for a site.

### Vector store search (optional)

[](#vector-store-search-optional)

By default `BoardDocsAgent` searches the local `index.jsonl` via `SearchAgendasTool`(free, deterministic keyword search). You can instead delegate search to a [vector store](https://laravel.com/docs/13.x/ai-sdk#vector-stores) so the model performs semantic retrieval over the actual meeting PDFs — useful when relevant content lives in scanned attachments or phrasing that keyword search misses.

1. Create a store once (OpenAI or Gemini only) and wire up its ID:

    ```
    php artisan boarddocs:vector-store:create "BoardDocs Agendas" --write-env
    ```

    This creates the store, then writes `BOARDDOCS_AI_SEARCH_DRIVER=vector` and `BOARDDOCS_VECTOR_STORE_ID` into `.env` for you. Omit `--write-env` to just print the values and add them yourself; pass `--provider=openai` (or `gemini`) to pin a provider other than your app's default. Equivalent by hand:

    ```
    use Laravel\Ai\Stores;

    $store = Stores::create('BoardDocs Agendas');
    // put $store->id in .env as BOARDDOCS_VECTOR_STORE_ID
    ```
2. Run `php artisan boarddocs:scan` as usual. Whenever the vector driver is active, the scan uploads each newly exported (or refreshed) meeting PDF into the store — tagged with `path`/`committee`/`date`/`page_count` metadata so results can be mapped back to `GetMeetingTool` — and backfills any already-exported PDFs that were never synced.

With the driver set to `vector`, `BoardDocsAgent` registers `Laravel\Ai\Providers\Tools\FileSearch` against that store instead of `SearchAgendasTool`(falling back to `SearchAgendasTool` automatically if `vector_store.id` isn't set). `GetMeetingTool` and `ListCommitteesTool` are unaffected by the driver either way.

Configuration
-------------

[](#configuration)

See `config/boarddocs.php`. Highlights:

KeyPurpose`site`, `base_url`Default site slug and host`http.request_delay`Polite delay between requests (seconds)`cache.store`, `cache.ttl`Cache store + TTL for committee/meeting/agenda data`output.disk`, `output.path`, `output.index`Where PDFs and `index.jsonl` are written`pdf.engine``tcpdf` or `browsershot``pdf.self_contained`, `pdf.remap_links`, `pdf.embed_non_pdf`Self-contained PDF behavior`scan.refresh_recent_days`Re-export window for recent meetings`ai.search_driver``jsonl` (default, local keyword search) or `vector``ai.vector_store.id`, `ai.vector_store.provider`Vector store used when `ai.search_driver` is `vector`Testing
-------

[](#testing)

```
composer install
vendor/bin/pest
```

`laravel/ai` is a dev dependency (`composer install` pulls it in) so the `tests/Feature/Ai`suite — `BoardDocsAgent`, the AI SDK tool classes, `VectorStoreSync`, and `boarddocs:vector-store:create` — is fully exercised against the SDK's fakes (`Stores::fake()`) by default. If `laravel/ai` isn't present (e.g. `composer install --no-dev`), that suite skips itself with a clear message instead of failing.

License
-------

[](#license)

MIT

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance94

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity54

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

15

Last Release

14d ago

Major Versions

v0.3.0 → v1.0.02026-08-10

v1.0.0 → v2.0.02026-08-10

v2.0.0 → v3.0.02026-08-10

### Community

Maintainers

![](https://www.gravatar.com/avatar/e3b045b66d2ab5c78da1cb707f906fd94c5d804382728dcdb94187e85676485d?d=identicon)[danswiser](/maintainers/danswiser)

---

Top Contributors

[![danswiser](https://avatars.githubusercontent.com/u/6931029?v=4)](https://github.com/danswiser "danswiser (4 commits)")

---

Tags

laravelpdfaiAgendascraperboarddocsschool-board

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/wiserwebsolutions-laravel-boarddocs-scraper/health.svg)

```
[![Health](https://phpackages.com/badges/wiserwebsolutions-laravel-boarddocs-scraper/health.svg)](https://phpackages.com/packages/wiserwebsolutions-laravel-boarddocs-scraper)
```

###  Alternatives

[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

80427.1M249](/packages/laravel-mcp)[laravel/ai

The official AI SDK for Laravel.

1.1k4.6M341](/packages/laravel-ai)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k31.8M163](/packages/laravel-cashier)[spatie/laravel-export

Create a static site bundle from a Laravel app

679153.2k7](/packages/spatie-laravel-export)[craftcms/cms

Craft CMS

3.6k3.7M3.5k](/packages/craftcms-cms)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
