pagegraph 0.7.0 → 0.8.1
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.
- package/README.md +97 -190
- package/dist/cli.js +4618 -2771
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +23 -1
- package/dist/config.js.map +1 -1
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -34,13 +34,14 @@ bun add -D lighthouse # only for `pagegraph audit` performance evidence
|
|
|
34
34
|
| `pagegraph/audit` | Audit services, scanner protocol, rules, and report schemas | `effect` |
|
|
35
35
|
| `pagegraph` bin | CLI over the same graph | bundled — runs on Bun |
|
|
36
36
|
|
|
37
|
-
The core and React
|
|
38
|
-
peer, and only the entries you import need
|
|
37
|
+
The core and React import graphs have **zero runtime dependencies** — everything they use is a
|
|
38
|
+
peer, and only the entries you import need those peers loaded.
|
|
39
39
|
|
|
40
|
-
The CLI **bundles Effect and
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
The CLI **bundles PageGraph's Effect and TypeSafe provider runtime**, so it does not depend on the
|
|
41
|
+
app's Effect RC; it runs on [Bun](https://bun.sh) (`bunx pagegraph`). Agentic workflows load the
|
|
42
|
+
OpenCode SDK and its private Effect runtime only when invoked. `vite` stays a peer — graph commands
|
|
43
|
+
load your app through Vite at runtime — and `lighthouse` is only needed by `pagegraph audit`.
|
|
44
|
+
Importing `pagegraph/audit` programmatically still needs `effect`.
|
|
44
45
|
|
|
45
46
|
## Quick start
|
|
46
47
|
|
|
@@ -205,6 +206,89 @@ Preview hosts (`indexable: false`) drop `contentSignal` and `directives`;
|
|
|
205
206
|
pagegraph check
|
|
206
207
|
```
|
|
207
208
|
|
|
209
|
+
## Agentic workflows
|
|
210
|
+
|
|
211
|
+
PageGraph can combine the deterministic route graph with a project-owned embedded OpenCode agent,
|
|
212
|
+
live tools exposed by an Executor plugin, and Jev decisions. Scaffold the OpenCode preset once:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
pagegraph init
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The generated `.pagegraph/opencode` directory contains one `seo` agent and customizable `content`,
|
|
219
|
+
`technical`, and `authority` skills. Add your Executor plugin to its `opencode.jsonc`; OpenCode and
|
|
220
|
+
the plugin own model and Executor authentication. PageGraph does not copy credentials or assume
|
|
221
|
+
fixed provider-tool addresses.
|
|
222
|
+
|
|
223
|
+
Enable workflows in `seo.config.ts`:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
export default defineSeoConfig({
|
|
227
|
+
// origin, disallow, and loadGraph as above
|
|
228
|
+
workflows: {
|
|
229
|
+
opencode: {
|
|
230
|
+
configDirectory: ".pagegraph/opencode",
|
|
231
|
+
defaultModel: "openrouter/example/model",
|
|
232
|
+
models: {
|
|
233
|
+
"research.keywords": "openrouter/example/research-model",
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
context: {
|
|
237
|
+
files: ["AGENTS.md"],
|
|
238
|
+
byWorkflow: {
|
|
239
|
+
"research.keywords": ["docs/seo/measurement.md"],
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
runsDirectory: ".pagegraph/runs",
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Run a page-backed workflow:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
pagegraph research keywords \
|
|
251
|
+
--query "transactional email api" \
|
|
252
|
+
--market us \
|
|
253
|
+
--language en
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Every workflow starts from the selected graph and context files, asks the SEO agent to search
|
|
257
|
+
Executor's live catalog and compose suitable tools, then evaluates its structured observations with
|
|
258
|
+
Jev. Tool paths are discovered at runtime; the generated skills contain editable starter recipes,
|
|
259
|
+
not a fixed integration list.
|
|
260
|
+
|
|
261
|
+
| Command | Result |
|
|
262
|
+
| --- | --- |
|
|
263
|
+
| `research keywords` | Query demand, intent, and page ownership opportunities |
|
|
264
|
+
| `research competitors` | Competitor topics, pages, and positioning evidence |
|
|
265
|
+
| `research authority` | Qualified editorial, directory, partner, and community opportunities |
|
|
266
|
+
| `analyze serp` | Search-result intent and page-shape fit |
|
|
267
|
+
| `analyze content` | Substance, answer quality, and intent overlap |
|
|
268
|
+
| `analyze ai-search` | Citation readiness and answer-engine coverage |
|
|
269
|
+
| `plan architecture` | A page-backed information-architecture plan |
|
|
270
|
+
| `improve content` | Evidence-backed source copy edits |
|
|
271
|
+
| `improve metadata` | Title and description edits at the owning source |
|
|
272
|
+
| `improve schema` | Structured-data edits justified by visible content |
|
|
273
|
+
| `improve links` | Contextual internal-link edits from graph and page evidence |
|
|
274
|
+
|
|
275
|
+
The common selectors are repeatable `--page`, `--query`, and `--kind`, plus `--limit`, `--market`,
|
|
276
|
+
`--language`, `--refresh`, `--model`, `--opencode-config`, and `--out`. Relevant workflows also
|
|
277
|
+
accept `--competitor`, `--domain`, or `--device desktop|mobile`. Workflows are noninteractive and
|
|
278
|
+
fail immediately if the agent asks a question or requests permission.
|
|
279
|
+
|
|
280
|
+
The four `improve` workflows write source files by default and require a clean Git tree. Use
|
|
281
|
+
`--dry-run` to keep repository edit and shell tools disabled, or `--allow-dirty` when you explicitly
|
|
282
|
+
want the agent to edit alongside existing changes. PageGraph leaves every diff uncommitted and
|
|
283
|
+
records modified, added, and deleted files after the workflow finishes.
|
|
284
|
+
|
|
285
|
+
Each run writes `.pagegraph/runs/<run-id>/run.json` and `summary.md`. The JSON retains the
|
|
286
|
+
deterministic graph, context, complete OpenCode session export, Executor tool evidence, Jev question
|
|
287
|
+
definitions and answers, Git provenance, changed files, and model provenance. Keep this directory
|
|
288
|
+
ignored: provider output can be large or account-specific. Workflows fail when the configured model,
|
|
289
|
+
active Executor plugin, catalog search plus discovered tool call, or `TYPESAFE_API_KEY` for Jev is
|
|
290
|
+
unavailable.
|
|
291
|
+
|
|
208
292
|
## Typed paths
|
|
209
293
|
|
|
210
294
|
Augment `Register` (the same pattern TanStack Router uses) and `related` /
|
|
@@ -308,8 +392,8 @@ pagegraph links verify <url> # crawl served HTML: depth, orphans, declared-vs
|
|
|
308
392
|
pagegraph links verify <url> --assert-coverage # also assert seo.config.ts coverage on served anchors
|
|
309
393
|
pagegraph links verify <url> --emit-rendered <file> # save the rendered edge set
|
|
310
394
|
pagegraph links candidates # propose contextual links from the declared graph
|
|
311
|
-
pagegraph
|
|
312
|
-
pagegraph
|
|
395
|
+
pagegraph research keywords --query "email api" # combine graph, Executor, and Jev evidence
|
|
396
|
+
pagegraph improve metadata --page /pricing # apply a clean-tree source improvement
|
|
313
397
|
pagegraph sitemap # print sitemap.xml
|
|
314
398
|
pagegraph robots # print robots.txt
|
|
315
399
|
```
|
|
@@ -395,195 +479,18 @@ Pairs already declared as a `related` edge are excluded, and `--rendered` accept
|
|
|
395
479
|
a JSON dump of already-served anchors (`[{ from, to }]` or `{ edges: [...] }`) to
|
|
396
480
|
exclude those too.
|
|
397
481
|
|
|
398
|
-
The output is a reviewable plan: human text by default
|
|
399
|
-
`--json`. Nothing is applied
|
|
400
|
-
|
|
401
|
-
|
|
482
|
+
The output is a deterministic, reviewable plan: human text by default and
|
|
483
|
+
versioned JSON with `--json`. Nothing is applied; `--limit` and `--cluster`
|
|
484
|
+
bound the plan. Use `pagegraph improve links` when you want Executor evidence,
|
|
485
|
+
Jev evaluation, and source edits:
|
|
402
486
|
|
|
403
487
|
```bash
|
|
404
488
|
pagegraph links candidates
|
|
405
489
|
pagegraph links candidates --cluster blog --limit 20
|
|
406
490
|
pagegraph links candidates --rendered rendered.json --json | jq
|
|
407
|
-
pagegraph links
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### Decide with Jev
|
|
411
|
-
|
|
412
|
-
`pagegraph links decide` answers four decisions per candidate — *does the source have a genuine
|
|
413
|
-
reason to link to the destination?*, *is descriptive anchor text already in the copy?*, *which
|
|
414
|
-
direction deserves the link?*, and *how relevant is it?* — and routes anything inside the confidence
|
|
415
|
-
band `(1−t, t)` to a `review` bucket instead of auto-applying. `--budget` (default 4) keeps only the
|
|
416
|
-
top-K candidates per source by relevance.
|
|
417
|
-
|
|
418
|
-
```bash
|
|
419
|
-
pagegraph links decide candidates.json --threshold 0.9
|
|
420
|
-
pagegraph links decide candidates.json --budget 2 --json | jq
|
|
421
|
-
cat candidates.json | pagegraph links decide
|
|
491
|
+
pagegraph improve links --page "/docs/**"
|
|
422
492
|
```
|
|
423
493
|
|
|
424
|
-
Decisions run through [Jev](https://typesafe.ai) via `@effect/ai-typesafe` (model `jev-latest`),
|
|
425
|
-
bundled into the CLI. The API key is read from the **`TYPESAFE_API_KEY` environment variable** — it
|
|
426
|
-
is deliberately not a `seo.config.ts` field, so keys never live in the repo; `seo.config.ts` carries
|
|
427
|
-
non-secret decision settings only. Without a key the command exits 1 with a clear message and an
|
|
428
|
-
empty stdout.
|
|
429
|
-
|
|
430
|
-
### The decision runner
|
|
431
|
-
|
|
432
|
-
`pagegraph decide <family>` generalizes Jev decisions over a batch. Each input answers every decision
|
|
433
|
-
for that input in one provider call; confident answers resolve and anything inside the confidence
|
|
434
|
-
band goes to a `review` bucket. Nothing is applied automatically — the report is the end product.
|
|
435
|
-
|
|
436
|
-
```bash
|
|
437
|
-
pagegraph decide <family> inputs.json --json | jq
|
|
438
|
-
cat inputs.json | pagegraph decide <family> --threshold 0.9
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
A batch is a JSON array, a `{ "inputs": [...] }` envelope, or JSONL (one input per line), from a file
|
|
442
|
-
argument or stdin. Flags are shared across families:
|
|
443
|
-
|
|
444
|
-
| Flag | Default | Meaning |
|
|
445
|
-
| ----------------- | ----------- | ------------------------------------------------------------- |
|
|
446
|
-
| `--json` | false | Emit the versioned report as JSON on stdout |
|
|
447
|
-
| `--model <id>` | `jev-latest` | TypeSafe System One model (`jev-latest`, `jev-preview`, …) |
|
|
448
|
-
| `--threshold <t>` | `0.7` | Confidence boundary; `(1−t, t)` is the review band |
|
|
449
|
-
| `--concurrency <n>` | `4` | In-flight decision calls |
|
|
450
|
-
| `--cache <dir>` | — | Reuse model answers by family + model + input hash |
|
|
451
|
-
| `--review-out <file>` | — | Write only the below-threshold records to JSON |
|
|
452
|
-
|
|
453
|
-
`pagegraph decide links` is the links family alias of `pagegraph links decide` (see
|
|
454
|
-
[Decide with Jev](#decide-with-jev)); it accepts the same candidate array.
|
|
455
|
-
|
|
456
|
-
#### `decide serp` — is our page the shape this SERP rewards?
|
|
457
|
-
|
|
458
|
-
Input: one saved results-page snapshot per input.
|
|
459
|
-
|
|
460
|
-
```json
|
|
461
|
-
{
|
|
462
|
-
"query": "best email api",
|
|
463
|
-
"ourPage": {
|
|
464
|
-
"url": "https://example.com/email-api",
|
|
465
|
-
"title": "Email API for developers",
|
|
466
|
-
"kind": "page",
|
|
467
|
-
"firstWords": "Send transactional email from your product."
|
|
468
|
-
},
|
|
469
|
-
"serpItems": [
|
|
470
|
-
{ "type": "organic", "rank": 1, "domain": "a.example", "title": "Best email APIs", "snippet": "Compare" }
|
|
471
|
-
]
|
|
472
|
-
}
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
| Verdict | Meaning |
|
|
476
|
-
| ---------- | ------------------------------------------------------------------ |
|
|
477
|
-
| `aligned` | Intent and format are both confidently positive. |
|
|
478
|
-
| `mismatch` | At least one of intent/format is confidently wrong. |
|
|
479
|
-
| `review` | Any answer (`ourFormatFit`, `intentMatch`, `titlePatternMatch`) is inside the band. |
|
|
480
|
-
|
|
481
|
-
#### `decide content` — does this page carry enough substance?
|
|
482
|
-
|
|
483
|
-
Input: one page per input, with the evidence the rubric reads.
|
|
484
|
-
|
|
485
|
-
```json
|
|
486
|
-
{
|
|
487
|
-
"url": "https://example.com/email-api",
|
|
488
|
-
"query": "best email api",
|
|
489
|
-
"title": "Transactional email API for product teams",
|
|
490
|
-
"h1": "Send transactional email",
|
|
491
|
-
"first150Words": "The API sends one message per call and reports delivery events.",
|
|
492
|
-
"headings": ["Quickstart", "Events", "Pricing"],
|
|
493
|
-
"wordCount": 1200,
|
|
494
|
-
"structuredData": ["Article", "FAQPage"],
|
|
495
|
-
"categoryLock": "email api",
|
|
496
|
-
"siblingIntents": ["email deliverability", "sms api"],
|
|
497
|
-
"competitorExcerpts": ["A competitor's overview."]
|
|
498
|
-
}
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
| Verdict | Meaning |
|
|
502
|
-
| -------- | --------------------------------------------------------------------------- |
|
|
503
|
-
| `pass` | The value rating is not `thin` and no rubric probability is confidently false. |
|
|
504
|
-
| `flag` | Value is `thin`, or a rubric probability (`originalValue`, `answersFirst`, …) is confidently false. |
|
|
505
|
-
| `review` | Any rubric or value probability is inside the band, or an evidence-dependent rubric (`distinctIntent`, `competitiveSubstance`) is confidently positive without `siblingIntents`/`competitorExcerpts`. |
|
|
506
|
-
|
|
507
|
-
`wordCount` must be a non-negative integer.
|
|
508
|
-
|
|
509
|
-
`value` rates `thin | adequate | strong`.
|
|
510
|
-
|
|
511
|
-
#### `decide fit` — which existing page should target this query?
|
|
512
|
-
|
|
513
|
-
Input: a query plus the site's candidate pages. Candidate ids become the provider labels, so the
|
|
514
|
-
family needs at least two candidates with unique ids of at most 255 characters.
|
|
515
|
-
|
|
516
|
-
```json
|
|
517
|
-
{
|
|
518
|
-
"query": "best email api",
|
|
519
|
-
"candidates": [
|
|
520
|
-
{ "id": "/email-api", "title": "Email API", "excerpt": "Send transactional email." },
|
|
521
|
-
{ "id": "/pricing", "title": "Pricing", "excerpt": "Volume pricing." }
|
|
522
|
-
]
|
|
523
|
-
}
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
| Verdict | Meaning |
|
|
527
|
-
| -------------- | -------------------------------------------------------- |
|
|
528
|
-
| `map` | A page is confidently best; update it. |
|
|
529
|
-
| `cannibalized` | Multiple pages already target the query. |
|
|
530
|
-
| `gap` | No page can serve the query; a new page is needed. |
|
|
531
|
-
| `review` | The conflict or the best page is inside the confidence band. |
|
|
532
|
-
|
|
533
|
-
#### `decide meta` — rank supplied title/description candidates
|
|
534
|
-
|
|
535
|
-
Input: a page plus the candidates to rank. Drafting is out of scope — only the provided candidates
|
|
536
|
-
are judged. Needs at least two candidates with unique ids of at most 255 characters.
|
|
537
|
-
|
|
538
|
-
```json
|
|
539
|
-
{
|
|
540
|
-
"url": "https://example.com/email-api",
|
|
541
|
-
"intent": "choose an email api",
|
|
542
|
-
"categoryLock": "email api",
|
|
543
|
-
"candidates": [
|
|
544
|
-
{ "id": "a", "title": "Email API", "description": "Send transactional email." },
|
|
545
|
-
{ "id": "b", "title": "Email pricing", "description": "Volume pricing." }
|
|
546
|
-
]
|
|
547
|
-
}
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
| Verdict | Meaning |
|
|
551
|
-
| ------------- | ------------------------------------------------------------------------- |
|
|
552
|
-
| `choose:<id>` | A candidate is confidently best with clear intent, length, category, and clickbait checks. |
|
|
553
|
-
| `review` | A concern or an uncertain pick; a human decides. |
|
|
554
|
-
|
|
555
|
-
#### `decide authority` — is this a real link opportunity?
|
|
556
|
-
|
|
557
|
-
Input: one link target per input.
|
|
558
|
-
|
|
559
|
-
```json
|
|
560
|
-
{
|
|
561
|
-
"domain": "blog.example",
|
|
562
|
-
"url": "https://blog.example/email-guide",
|
|
563
|
-
"anchor": "transactional email API",
|
|
564
|
-
"context": "We cover how a transactional email API works end to end.",
|
|
565
|
-
"targetPath": "/email-api",
|
|
566
|
-
"rd": 120,
|
|
567
|
-
"signals": ["editorial", "no-outbound-links"]
|
|
568
|
-
}
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
| Verdict | Meaning |
|
|
572
|
-
| -------- | ------------------------------------------------------------------------ |
|
|
573
|
-
| `accept` | Legitimate, outreach-worthy, and a confident non-`none` fit. |
|
|
574
|
-
| `spam` | Spam is confidently high. |
|
|
575
|
-
| `review` | Any probability is inside the band, or the fit is `none`/uncertain. |
|
|
576
|
-
|
|
577
|
-
`fit` classifies `directory | partner | editorial | community | none`.
|
|
578
|
-
|
|
579
|
-
#### `decide links` — direction, relevance, and budget
|
|
580
|
-
|
|
581
|
-
`pagegraph decide links` shares the links decisions (see [Decide with Jev](#decide-with-jev)) and
|
|
582
|
-
adds `--budget <n>` (default 4): keep the top-K candidates per outbound page by `relevance`
|
|
583
|
-
(`irrelevant | useful | essential`). `direction` classifies `a_to_b | b_to_a | both`; a record also
|
|
584
|
-
carries the resulting `from`/`to` endpoints, so a `b_to_a` recommendation is reported — and
|
|
585
|
-
budgeted — against the page the model actually chose to link *from*.
|
|
586
|
-
|
|
587
494
|
### Contextual-link coverage
|
|
588
495
|
|
|
589
496
|
Declare a contextual-link coverage policy in `seo.config.ts` (a `coverage` array
|