Output Configuration
Configure audit report format and output destinations
The [output] section controls how squirrelscan formats and saves audit reports.
Configuration
[output]
format = "console"
path = ""Options
format
Type: "console" | "text" | "json" | "html" | "markdown" | "llm" | "xml"
Default: "console"
Output format for audit reports.
Available Formats:
"console"- Rich terminal output with colors and formatting"text"- Plain text output without colors"json"- Machine-readable JSON for automation"html"- Interactive HTML report in browser"markdown"- Markdown format for documentation"llm"- Compact token-optimized XML/text for coding agents (40% smaller than xml)"xml"- Verbose structured XML for enterprise integration
Note: The sarif format is only available via the report command, not during audit. See Reports documentation for details.
"console" (default)
Rich terminal output optimized for human readability.
Features:
- Colored output (red/yellow/green for fail/warn/pass)
- Progress indicators during crawl
- Summary statistics
- Issue grouping by rule
- Interactive formatting
Example output:
π Crawling https://example.com...
ββββββββββββββββββββββββββββββββββββββββ
β Fetched 100 pages in 12.3s
π Running audit rules...
ββββββββββββββββββββββββββββββββββββββββ
ISSUES FOUND (23)
β core/meta-title (8 pages)
/about - Missing page title
/contact - Title too short (12 chars, min 30)
β οΈ links/broken-links (15 links)
/blog/post-1 β /missing-page (404)
β All other checks passed
ββββββββββββββββββββββββββββββββββββββββ
Health Score: 78/100 (Good)Configuration:
[output]
format = "console"Use cases:
- Local development
- Manual audits
- Terminal-based workflows
- Quick visual feedback
"json"
Machine-readable JSON output for automation and integrations.
Features:
- Health score with group and category scores
- Pass, warning, fail and skipped counts
- Findings grouped by rule, with affected pages
- Checks skipped because a scan limit was hit, listed apart from findings
- Audit status and metadata
Example structure:
{
"meta": {
"version": "0.0.105",
"baseUrl": "https://example.com",
"timestamp": "2026-01-17T10:30:00.000Z",
"totalPages": 100
},
"status": "completed",
"score": {
"overall": 78,
"grade": "C",
"groups": [],
"categories": [{ "name": "Core SEO", "score": 82 }]
},
"summary": { "passed": 1204, "warnings": 15, "failed": 8, "skipped": 2 },
"issues": [
{
"ruleId": "core/meta-title",
"name": "Meta title",
"description": "...",
"category": "Core SEO",
"group": "seo",
"severity": "error",
"checks": [
{
"name": "meta-title",
"status": "fail",
"message": "Missing page title",
"affectedPages": ["https://example.com/about"],
"affectedPagesCount": 1,
"affectedPagesHasMore": false
}
]
}
],
"skippedChecks": [
{
"ruleId": "content/hidden-text",
"name": "Hidden text",
"check": "hidden-text",
"reason": "scan-truncated",
"pages": ["https://example.com/huge-page"],
"pagesCount": 1,
"pagesHasMore": false
}
]
}See the JSON output schema for every key.
Configuration:
[output]
format = "json"
path = "reports/audit.json" # Optional: save to fileUse cases:
- CI/CD pipelines
- Automated testing
- Data analysis
- Integration with other tools
- API responses
CLI usage:
# Output to stdout
squirrel audit https://example.com --format json
# Save to file
squirrel audit https://example.com --format json --output report.json
# Pipe to jq for filtering
squirrel audit https://example.com --format json | jq '.score.overall'"html"
Interactive HTML report that opens in your browser.
Includes:
- Health score and the four group scores
- Issues to fix first, and every finding banded by severity, filterable by group
- A Copy fix prompt button on each finding for your coding agent
- The same findings by page
- Site profile, tech stack and the entity map
It is one standalone file that makes no network request when opened. See the html format for each section and the fix prompts.
Configuration:
[output]
format = "html"
path = "reports/audit.html" # Optional: custom pathDefault behavior:
If path is not specified, HTML report is saved to:
reports/audit-{timestamp}.htmlUse cases:
- Client presentations
- Team sharing
- Visual analysis
- Stakeholder reports
- Archive audits
CLI usage:
# Generate and open in browser
squirrel audit https://example.com --format html
# Save to custom location
squirrel audit https://example.com --format html --output reports/my-audit.html"llm"
Compact token-optimized format for coding agents - hybrid XML/text structure designed to minimize tokens while maintaining structured data.
Features:
- 40-70% smaller than verbose XML format
- 1-space indentation for token efficiency
- Inline attributes instead of nested elements
- Text prefixes (
Desc:,Fix:,Pages:,Items:) - Comma-separated lists for pages and URLs
- Hybrid XML/text structure for easy parsing
Size Comparison:
For a 51-page audit:
- XML format: 209KB
- LLM format: 125KB (40% reduction)
Format Structure:
<?xml version="1.0" encoding="UTF-8"?>
<audit version="0.0.13">
<site url="https://example.com" crawled="42" date="2025-01-18T10:30:00Z"/>
<score overall="87" grade="B">
<cat name="Core SEO" score="100"/>
<cat name="Links" score="85"/>
</score>
<summary passed="83" warnings="13" failed="9"/>
<issues>
<rule id="content/word-count" severity="warning" category="Content" group="seo" status="warn">
Low word count detected
Desc: Pages should have sufficient content for good SEO
Fix: Add more relevant content to improve page depth
Pages (3): /blog/post-1, /blog/post-2, /about
</rule>
</issues>
</audit>Use Cases:
- Piping to Claude Code, Cursor, GPT, or other coding agents
- Token-limited API contexts (cost optimization)
- Agent-based workflows requiring structured output
- AI code review and analysis
CLI usage:
# Direct output during audit
squirrel audit https://example.com --format llm
# Or export from stored audit
squirrel report <audit-id> --format llm
# Pipe directly to a coding agent
squirrel audit https://example.com --format llm | claude "analyze and prioritize fixes"See OUTPUT-FORMAT.md for complete format specification.
"xml"
Verbose structured XML format for enterprise integration, archival, and strict schema validation.
Features:
- 2-space indentation for readability
- Fully nested elements (all metadata in dedicated tags)
- Explicit structure with strict schema compliance
- All information preserved without abbreviation
- Enterprise-ready for XML parsers and validators
Format Structure:
<?xml version="1.0" encoding="UTF-8"?>
<squirrelscan-audit version="0.0.117">
<site>
<url>https://example.com</url>
<pages-crawled>42</pages-crawled>
<audit-date>2025-01-18T10:30:00.000Z</audit-date>
<scan-scope origin="cli" crawled="42" max-pages="100" capped="false"/>
</site>
<health-score overall="87" grade="B">
<category name="Core SEO" score="100"/>
</health-score>
<summary passed="180" warnings="6" failed="2"/>
<issues>
<rule id="content/word-count" severity="warning" category="Content" group="seo">
<name>Word Count</name>
<description>Pages should have sufficient content</description>
<solution>Add more relevant content</solution>
<check name="word-count" status="warn">
<message>Low word count: 150 words (min: 300)</message>
<affected-pages count="2" examples="2" has-more="false">
<page url="https://example.com/blog/post-1"/>
<page url="https://example.com/blog/post-2"/>
</affected-pages>
</check>
</rule>
</issues>
</squirrelscan-audit><scan-scope> says where the audit ran (origin: cli, ci or cloud), how many pages it crawled, the page limit in force (max-pages), and whether that limit stopped the crawl (capped). requested-max-pages appears only when the limit you asked for was lowered, and stop-reason="time" only when the crawl time budget ended the crawl. unfetched appears when the crawl stopped with in-scope URLs still queued: pages it found (in a sitemap, linked from a crawled page, or carried from an earlier audit) but never fetched. crawled plus unfetched is how many pages the crawl knew of. It is a floor: the links on the pages still queued are unknown, so the site may have more. On a rate-limited run, the unfetched count on <rate-limited> already includes these. A report from before scan scope existed has no <scan-scope>. A white-label reportβs root element is <audit>.
Comparison: LLM vs XML
| Aspect | LLM Format | XML Format |
|---|---|---|
| Size (51 pages) | 125KB | 209KB |
| Indentation | 1 space | 2 spaces |
| Structure | Hybrid XML/text | Pure XML |
| Metadata | Inline attributes | Nested elements |
| Best For | Coding agents, tokens | Enterprise, archival |
Use Cases:
- Enterprise data integration and ETL pipelines
- Long-term audit archival with full metadata
- Schema validation and compliance requirements
- XML processing tools and libraries
- Integration with enterprise systems (SAP, Oracle, etc.)
CLI usage:
# Direct output during audit
squirrel audit https://example.com --format xml --output audit.xml
# Or export from stored audit
squirrel report <audit-id> --format xml --output audit.xml
# Validate with xmllint
squirrel audit https://example.com --format xml | xmllint --format -path
Type: string
Default: "" (empty = stdout for console/json, auto-generate for html)
Required: No
Output file path for saving reports.
Behavior:
Empty (default):
consoleformat β stdout (terminal)jsonformat β stdouthtmlformat βreports/audit-{timestamp}.html
Specified:
- Saves to exact path provided
- Creates directories if needed
- Overwrites existing files
Examples:
Save JSON to file:
[output]
format = "json"
path = "reports/latest.json"Save HTML with custom name:
[output]
format = "html"
path = "audit-reports/example-com-2026-01-17.html"Console to file (unusual):
[output]
format = "console"
path = "logs/audit.log"Path expansion:
Relative paths are relative to current working directory:
[output]
path = "reports/audit.json"
# Resolves to: /path/to/project/reports/audit.jsonAbsolute paths work too:
[output]
path = "/Users/you/audits/report.json"Directory creation:
squirrelscan automatically creates parent directories:
[output]
path = "reports/2026/january/audit.json"
# Creates reports/2026/january/ if neededConfiguration examples
console output (default)
Terminal output with colors:
[output]
format = "console"Run:
squirrel audit https://example.comJSON for CI/CD
Save JSON report for automated testing:
[output]
format = "json"
path = "reports/audit.json"Then in CI:
squirrel audit https://staging.example.com
cat reports/audit.json | jq '.score.overall'HTML for sharing
Generate shareable HTML report:
[output]
format = "html"
path = "reports/client-audit.html"Share the file:
squirrel audit https://client.com
# Send reports/client-audit.html to clientMultiple outputs
Use CLI to generate multiple formats:
# Console output
squirrel audit https://example.com
# JSON for automation
squirrel audit https://example.com --format json --output reports/data.json
# HTML for sharing
squirrel audit https://example.com --format html --output reports/visual.htmlCLI override
CLI flags override config file:
Config file:
[output]
format = "console"
path = "reports/default.json"CLI override:
squirrel audit https://example.com --format json --output custom.json
# Uses: format=json, path=custom.jsonPrecedence:
- CLI flags (
--format,--output) - Highest - Config file (
[output]section) - Built-in defaults - Lowest
Format comparison
| Feature | Console | JSON | HTML |
|---|---|---|---|
| Human readable | β | β | β |
| Machine readable | β | β | β |
| Colors | β | β | β |
| Interactive | β | β | β |
| Shareable | β | β | β |
| CI/CD friendly | β | β | β |
| Charts/graphs | β | β | β |
| File size | Small | Medium | Large |
| Parse complexity | N/A | Easy | Medium |
Output schema
JSON output schema
Top-level keys. Keys marked optional are omitted when they do not apply.
{
"meta": {
"version": "string",
"baseUrl": "string",
"timestamp": "ISO 8601 date",
"totalPages": 0,
"maxPages": 0,
"requestedMaxPages": 0,
"stopReason": "time",
"unfetched": 0,
"seedRedirect": {},
"coverage": {},
"auditLevel": {},
"rulesCache": {}
},
"status": "completed|failed|blocked",
"statusReason": "string",
"statusReasonCode": "string",
"rateLimited": { "pages": 0, "hosts": [] },
"refusedFetches": [],
"score": {
"overall": 0,
"grade": "A|B|C|D|F|N/A",
"groups": [
{ "group": "seo", "name": "string", "score": 0, "passed": 0, "warnings": 0, "failed": 0, "total": 0 }
],
"categories": [{ "name": "string", "score": 0 }]
},
"summary": { "passed": 0, "warnings": 0, "failed": 0, "skipped": 0 },
"issues": [
{
"ruleId": "core/meta-title",
"name": "string",
"description": "string",
"solution": "string",
"category": "string",
"group": "string",
"subcategory": "string",
"severity": "error|warning|info",
"checks": [
{
"name": "string",
"status": "fail|warn",
"message": "string",
"affectedPages": ["string"],
"affectedPagesCount": 0,
"affectedPagesHasMore": false,
"items": [],
"details": {}
}
]
}
],
"skippedChecks": [
{
"ruleId": "content/hidden-text",
"name": "string",
"check": "hidden-text",
"reason": "scan-truncated",
"pages": ["string"],
"pagesCount": 0,
"pagesHasMore": false
}
],
"notChecked": [
{
"ruleId": "links/broken-external-links",
"name": "string",
"check": "broken-external-links",
"reason": "external-links-not-checked",
"message": "string"
}
],
"passedRules": [
{
"ruleId": "core/canonical",
"name": "string",
"category": "string",
"scope": "page|site",
"pages": 0
}
],
"editorSummary": {},
"technologies": {},
"entities": {},
"siteProfile": {}
}Always present: meta, status, score, summary, issues. The rest are optional. statusReason and statusReasonCode appear when status is failed or blocked. meta.maxPages, meta.requestedMaxPages, meta.stopReason, meta.unfetched, meta.seedRedirect, meta.coverage, meta.auditLevel and meta.rulesCache appear when they apply. meta.unfetched counts the in-scope URLs the crawl had queued but never fetched when it stopped. Added to the pages this run crawled (meta.coverage.auditedPages on a smart audit, meta.totalPages otherwise) it is a lower bound on the pages the crawl found. On a rate-limited run, rateLimited.unfetched already includes it. meta.auditLevel is the audit level the audit ran at: level (a levelβs name, or custom), basedOn, changes (the settings changed from the level) and settings (every settingβs value). rateLimited, refusedFetches, editorSummary, technologies, entities and siteProfile appear when the audit produced that data. editorSummary.priorities is the editorβs summary ranked to-do list: each entry has title, why, action, effort (quick-win, moderate or significant), ruleIds and technologies.
Issues: issues holds findings only. Every check in it has status fail or warn. affectedPages is a sample, and affectedPagesCount is the true total, so compare the two (or read affectedPagesHasMore) before treating the list as complete.
Skipped checks: skippedChecks lists checks a rule could not finish evaluating, so an absence of issues on those pages does not mean they are clean. It appears only when at least one check was skipped. Skipped checks are never part of issues, and do not count toward passed, warnings, failed or the score.
| Field | Meaning |
|---|---|
ruleId | Rule that skipped, e.g. content/hidden-text |
name | Rule display name |
check | Check name within the rule |
reason | Why it was skipped. Currently only scan-truncated: the page hit the ruleβs scan limit and was only partly scanned (content/hidden-text, content/dev-leakage). More reasons may be added, so treat unknown values as a skip |
pages | Skipped page URLs. A sample when pagesHasMore is true |
pagesCount | True number of skipped pages |
pagesHasMore | pagesCount is larger than pages.length |
summary.skipped is the sum of pagesCount across skippedChecks, and is 0 when none were skipped.
A consumer that closes a finding when a rule stops reporting it should leave the pages in skippedChecks for that rule open, because the rule did not finish looking at them.
Not checked: notChecked lists rules whose check did not run for this audit, so an absence of issues for them means nothing was checked. It appears only when at least one rule was not checked. Like skipped checks, these are never part of issues and never scored.
| Field | Meaning |
|---|---|
ruleId | Rule that was not checked, e.g. links/broken-external-links |
name | Rule display name |
check | Check name within the rule |
reason | external-links-not-checked: external link checking was off (--offline, --no-external-links, [external_links] enabled = false, or switched off for the website or run). external-links-over-cap: more external links than the auditβs limit ([external_links] max_links, by default the page budget or 200, whichever is more); the most-linked were checked. external-links-out-of-time: a cloud auditβs link check ran out of time, or waited too long on one request, before these links were answered. link-targets-not-fetched: the crawl did not follow links (Quick level, Sitemap crawl strategy) and some internal link targets were fetched neither by the crawl nor by the internal link check (one HEAD per target, on by default; [internal_links] enabled = false or --no-internal-links turns it off) |
message | The line every report format shows, e.g. Not checked: Broken External Links. External link checking was off for this audit, so 12 external link(s) on the audited pages were not checked. Turn external link checks on to check them: in the CLI pass --external-links (or drop --no-external-links, --offline, or external_links.enabled = false); in the cloud they are a website setting. |
The text, Markdown and HTML reports print the same Not checked: line under the score, and the LLM and XML reports have a <not-checked> element with one <rule> per entry. A consumer that closes a finding when a rule stops reporting it should leave findings for these rules open.
Passed rules: passedRules appears only with --include-passed, and only when at least one rule passed. Each entry is a rule with at least one passing check and no warning or error. scope is the ruleβs own (page or site). pages counts the distinct pages with a passing check. It is usually 0 for a site rule, and 0 for a page rule whose passing checks carry no page, which the other formats show as pages not recorded. A failed or blocked audit lists none.
Use for:
- Automated analysis
- Data warehousing
- Custom reporting
- Integration testing
HTML report structure
HTML reports include:
1. Overview Section
- Health score gauge
- Total pages/links/images
- Crawl duration
- Issue count by severity
2. Issues Section
- Grouped by rule
- Sorted by severity
- Expandable details
- Quick filters
3. Pages Section
- Full page list
- Status codes
- Title/description
- Check results per page
4. Rules Section
- All rules run
- Pass/fail counts
- Category breakdown
5. Links Section
- Internal link graph
- Broken links list
- Redirect chains
- External links
6. Images Section
- Image list with previews
- Missing alt text
- File size analysis
- Format recommendations
Automation examples
CI/CD pipeline
Extract health score in CI:
# .github/workflows/audit.yml
- name: Run Audit
run: squirrel audit https://staging.example.com --format json --output audit.json
- name: Check Score
run: |
SCORE=$(cat audit.json | jq '.score.overall')
if [ "$SCORE" -lt 70 ]; then
echo "Health score too low: $SCORE"
exit 1
fiCompare audits
Compare two audits using JSON:
# Before changes
squirrel audit https://example.com --format json --output before.json
# Make changes...
# After changes
squirrel audit https://example.com --format json --output after.json
# Compare scores
echo "Before: $(cat before.json | jq '.score.overall')"
echo "After: $(cat after.json | jq '.score.overall')"Generate multiple reports
Run once, output multiple formats:
#!/bin/bash
URL="https://example.com"
DATE=$(date +%Y-%m-%d)
# JSON for data
squirrel audit $URL --format json --output "reports/$DATE/data.json"
# HTML for sharing
squirrel audit $URL --format html --output "reports/$DATE/report.html"
# Console for logs
squirrel audit $URL > "logs/$DATE.log"Troubleshooting
HTML not opening in browser
Solution: Open manually:
squirrel audit https://example.com --format html --output report.html
open report.html # macOS
xdg-open report.html # LinuxJSON output too large
Cause: Large sites generate large JSON files
Solution: Use console format or filter JSON:
# Get only the score block
squirrel audit https://example.com --format json | jq '.score'
# Get only error-severity issues
squirrel audit https://example.com --format json | jq '.issues[] | select(.severity == "error")'Path permission denied
Cause: No write permission to output directory
Solution: Use writable directory or fix permissions:
[output]
path = "~/reports/audit.json" # Home directoryOr:
mkdir -p reports
chmod 755 reportsComplete example
[project]
name = "mysite"
[crawler]
max_pages = 500
[rules]
enable = ["*"]
disable = ["ai/*"]
[output]
# Default to console for development
format = "console"
# Override with CLI for CI/CD:
# --format json --output reports/audit.jsonLocal development:
squirrel audit https://localhost:3000
# Console output to terminalCI/CD:
squirrel audit https://staging.example.com --format json --output audit.json
# JSON saved to file for analysisClient report:
squirrel audit https://client.com --format html --output reports/client-audit.html
# HTML report for sharingRelated
- Crawler Settings - Control what gets crawled
- Rules Configuration - Control what gets checked
- Examples - Common use cases