SEO On-Page Audit
MIT↓ 0 downloadsFetches a single target page and audits its on-page SEO — title, meta, headings, canonical, Open Graph, structured data, alt text, internal links, word count — then writes a prioritized, fix-by-fix action list you can hand to a developer.
Topology
Disclosures
Everything below runs on your machine or inside the sandbox when you use this workflow. Mismatches between these declarations and the actual code block publishing.
Host hooks
Commands executed on YOUR host machine by Sandcastle lifecycle hooks.
cp .env.example .env
Sandbox hooks
Commands executed inside the sandbox container.
None declared.
Network access
Fetches the single {{TARGET_URL}} page via curl inside the sandbox. No other hosts are contacted.
Shell expansion
Prompt files contain !`command` blocks — the agent CLI executes these commands at prompt-load time. They are highlighted amber in the prompt files below.
Files
On-page SEO audit: {{TARGET_URL}}
You are a technical SEO specialist. Audit the on-page SEO of a single URL and write a prioritized, actionable fix list. Be specific: quote the actual markup you found, and give the exact change to make.
The page to audit
!curl -sL --max-time 30 {{TARGET_URL}}
Your task
Using the fetched HTML above (and only what it contains), audit {{TARGET_URL}} across these on-page factors:
- Title tag — present? length (aim ~50–60 chars)? unique, keyword-led, compelling?
- Meta description — present? length (~140–160 chars)? does it earn the click?
- Headings — exactly one
<h1>? logicalh2/h3hierarchy? keyword coverage? - Canonical — is there a
<link rel="canonical">? does it point where it should? - Open Graph & Twitter cards —
og:title,og:description,og:image,twitter:cardpresent? - Structured data — any JSON-LD / schema.org markup? which types? valid-looking?
- Image alt text — do
<img>tags have descriptivealtattributes? which are missing? - Internal links — are there internal links with descriptive anchor text? any orphan feel?
- Word count & content depth — roughly how much body copy? thin or substantive?
Output
Write your findings to seo/audit.md as a prioritized list (highest-impact,
easiest-win issues first). For each issue use this shape:
- Issue — what is wrong or missing (quote the markup).
- Why it matters — the SEO impact in one sentence.
- Fix — the exact change, with example markup where useful.
Start the file with a one-line verdict and a short scorecard table (factor →
pass / warn / fail). If the fetch returned little usable HTML, say so explicitly
rather than inventing findings. Commit the file with a message like
seo: on-page audit for {{TARGET_URL}}.
When seo/audit.md is written and committed, output <promise>COMPLETE</promise>.
Diff vs the stock Sandcastle 0.12.0 template Dockerfile — green lines were added by the author, red lines were removed from stock.
+# Sandbox image for the SEO On-Page Audit workflow.+# Node 22 + git + curl (the auditor fetches the target page) + the Claude Code+# CLI, running as a non-root `agent` user.FROM node:22-bookworm-# System dependencies.RUN apt-get update && apt-get install -y --no-install-recommends \git \curl \jq \ca-certificates \&& rm -rf /var/lib/apt/lists/*# Claude Code CLI (the agent runtime).RUN npm install -g @anthropic-ai/claude-code# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to# the host user via --build-arg to avoid permission errors on bind mounts.+# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it+# (the stock Sandcastle template pattern) — groupadd/useradd would collide with+# the existing IDs on a default build.ARG AGENT_UID=1000ARG AGENT_GID=1000-RUN groupadd --gid ${AGENT_GID} agent \- && useradd --uid ${AGENT_UID} --gid ${AGENT_GID} --create-home --shell /bin/bash agent+RUN groupmod -o -g ${AGENT_GID} node \+ && usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node-USER agent-WORKDIR /workspace+USER ${AGENT_UID}:${AGENT_GID}+WORKDIR /home/agent++# Sandcastle bind-mounts the worktree and sets the working directory at+# container start; the container just needs to stay alive until then.+ENTRYPOINT ["sleep", "infinity"]
Show full Dockerfile (highlighted)
# Sandbox image for the SEO On-Page Audit workflow.
# Node 22 + git + curl (the auditor fetches the target page) + the Claude Code
# CLI, running as a non-root `agent` user.
FROM node:22-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
jq \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Claude Code CLI (the agent runtime).
RUN npm install -g @anthropic-ai/claude-code
# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to
# the host user via --build-arg to avoid permission errors on bind mounts.
# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it
# (the stock Sandcastle template pattern) — groupadd/useradd would collide with
# the existing IDs on a default build.
ARG AGENT_UID=1000
ARG AGENT_GID=1000
RUN groupmod -o -g ${AGENT_GID} node \
&& usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node
USER ${AGENT_UID}:${AGENT_GID}
WORKDIR /home/agent
# Sandcastle bind-mounts the worktree and sets the working directory at
# container start; the container just needs to stay alive until then.
ENTRYPOINT ["sleep", "infinity"]
# Auth for the Claude Code auditor agent.
# Run `claude setup-token` on your host to generate a token, then paste it here
# in your local .sandcastle/.env (never commit the real .env).
CLAUDE_CODE_OAUTH_TOKEN=
import { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
// The page to audit. Point this at any public URL you want an on-page SEO
// teardown of. It is substituted into audit-prompt.md as {{TARGET_URL}} and
// fetched with `curl` inside the sandbox.
const TARGET_URL = "https://example.com";
// Single Sonnet auditor, one pass. The host hook seeds .env into the worktree
// so CLAUDE_CODE_OAUTH_TOKEN is available. The prompt fetches TARGET_URL via a
// `curl` shell-expansion block, then writes the prioritized fix list to
// seo/audit.md.
const audit = await run({
name: "seo-onpage-audit",
agent: claudeCode("claude-sonnet-4-6"),
sandbox: docker(),
promptFile: ".sandcastle/audit-prompt.md",
promptArgs: { TARGET_URL },
maxIterations: 1,
hooks: {
host: {
onWorktreeReady: [{ command: "cp .env.example .env" }],
},
},
});
console.log(`SEO audit written in ${audit.commits.length} commit(s) on ${audit.branch}.`);
README
SEO On-Page Audit
Point it at a URL and get a developer-ready, prioritized on-page SEO fix list — without pasting page source into a chat window or clicking through five separate audit tools. One Claude Code agent fetches the live page and grades every on-page factor that actually moves rankings.
What it does
You give it a single TARGET_URL. A Claude Code auditor fetches the live
HTML and audits it end to end: the title tag and meta description (presence,
length, click-worthiness), heading structure (one h1, sane hierarchy), the
canonical link, Open Graph and Twitter card tags, JSON-LD / schema.org
structured data, image alt text, internal linking and anchor text, and overall
content depth.
The output is seo/audit.md: a one-line verdict, a pass/warn/fail scorecard, and
a prioritized list of issues. Each issue names the exact markup that is wrong,
explains the SEO impact in a sentence, and gives the precise fix — highest-impact,
easiest wins first.
How it works
main.ts runs a single Sonnet auditor for one iteration on the docker sandbox.
The prompt pulls the page with a !`curl -sL --max-time 30 {{TARGET_URL}}`
shell-expansion block — which is why the manifest discloses usesShellExpansion
and network access to that one URL. A host hook (cp .env.example .env) seeds
your token into the worktree before the run. The topology is a single node:
fetch page → audit → fix list.
Requirements
Set CLAUDE_CODE_OAUTH_TOKEN in .sandcastle/.env (run claude setup-token).
Edit TARGET_URL at the top of .sandcastle/main.ts to the page you want
audited. Build the image once with npx @ai-hero/sandcastle docker build-image,
then run npx tsx .sandcastle/main.ts. The only network call is the single
curl to your target URL; nothing else leaves the sandbox but the model
request.