@wotaso/seo-blog-admin-cli
v0.2.2
Published
Secure CLI for SEO Drafts agents, projects, and publishing integrations.
Readme
SEO Blog Admin CLI
Tenant-side and admin CLI for SEO Blog Admin publishing integrations.
Use it from tenant repositories without copying local JavaScript files:
npx --yes @wotaso/[email protected] init-static --site-slug example --site-origin https://example.com --content-dir src/content/blog
npx --yes @wotaso/[email protected] publish-static
npx --yes @wotaso/[email protected] publish-cms --cms wordpressScoped agent API
Automation agents should use revocable sda_live_ tokens and the fixed /api/v1/agent/v1 surface. Agent secrets are never accepted as command-line arguments or printed. Verify and store a token through stdin:
printf '%s' "$SEO_BLOG_AGENT_TOKEN" |
npx --yes @wotaso/[email protected] agent login --token-stdin
npx --yes @wotaso/[email protected] agent status
npx --yes @wotaso/[email protected] agent projects list
npx --yes @wotaso/[email protected] agent posts list --status pending_review --site-slug example
npx --yes @wotaso/[email protected] agent posts get 00000000-0000-4000-8000-000000000000 \
--body-offset 0 --body-limit 60000
npx --yes @wotaso/[email protected] agent projects refresh-links example \
--max-posts 3 --max-links-per-post 3Owners and admins first sign in with a mobile session, then manage least-privilege tokens. Newly created secrets are stored in the user config (0600) by default and are shown only as a prefix:
npx --yes @wotaso/[email protected] login-password \
--email [email protected] --password-stdin
npx --yes @wotaso/[email protected] agent tokens create \
--label "content-review-agent" \
--scopes projects:read,posts:read,posts:write,posts:approve \
--projects example \
--expires-in-days 30
npx --yes @wotaso/[email protected] agent tokens list
npx --yes @wotaso/[email protected] agent tokens revoke 00000000-0000-4000-8000-000000000000Write and review commands take a bounded JSON object from a file or stdin. Include the API-returned expectedContentVersion and expectedStateVersion for post mutations; project-context writes require expectedContextVersion. The CLI never retries mutations because the API does not promise idempotency:
npx --yes @wotaso/[email protected] agent posts update "$POST_ID" --input update.json
npx --yes @wotaso/[email protected] agent posts schedule "$POST_ID" --input schedule.json
npx --yes @wotaso/[email protected] agent posts approve "$POST_ID" --input approval.json
npx --yes @wotaso/[email protected] agent posts check-publish "$POST_ID" --input versions.json
npx --yes @wotaso/[email protected] agent opportunities generate-review-draft "$OPPORTUNITY_ID"posts get returns at most one explicitly requested Markdown chunk. Continue with the returned nextBodyOffset until it is null; if contentVersion or stateVersion changes between chunks, discard the partial body and reload from offset 0.
Content edits and scheduling are separate operations. posts update rejects scheduledAt; posts schedule accepts only the two expected versions plus a timestamp or null. Approval likewise rejects scheduling fields, clears any pre-existing automation schedule, and requires the approved version to be scheduled afterward.
Available read surfaces include projects, posts and revisions, opportunities, insights, integration health, billing, and the current token’s audit events. Write scopes cover project context, post import/update, separately scoped scheduling and approval, review-state changes, publish-health checks, opportunities, and qualified draft generation.
agent projects refresh-links discovers only links between already verified live posts. It changes no public page directly: bounded same-URL revisions enter the normal review queue, preserve the prior live snapshot for decline/rollback, and publish only after explicit approval and scheduling.
Publisher config and egress safety
New seo-blog.config.json files contain "version": 1; legacy files without a version are read as version 1. Every field is runtime validated and unknown fields fail closed. Post slugs and locales are validated before path construction, writes must remain below the configured content/asset root, and existing symlinks are never traversed.
Repository config cannot freely choose a secret variable or an arbitrary CMS destination:
- Provider credentials use fixed names such as
WORDPRESS_APP_PASSWORDandWEBFLOW_TOKEN. - Additional names must be explicitly listed in
SEO_BLOG_CMS_ALLOWED_SECRET_ENVS. - A CMS endpoint read from repo config must match
SEO_BLOG_CMS_ALLOWED_HOSTS. - CMS responses, social-card downloads, and deployment confirmations have timeouts, response limits, bounded redirects, and private-address checks.
- HTTPS is required. Loopback/private network, custom egress, custom secret names, and writes outside the current repository require explicit local environment opt-ins and are refused when
CI=trueor in GitHub Actions.
Local-only exceptions are SEO_BLOG_ALLOW_PRIVATE_NETWORK=true, SEO_BLOG_ALLOW_CUSTOM_EGRESS=true, SEO_BLOG_ALLOW_CUSTOM_SECRET_ENVS=true, and SEO_BLOG_ALLOW_OUTSIDE_WORKSPACE=true. Do not set them in shared runners.
Publisher health check
Run the check inside the tenant website repository:
SEO_BLOG_PUBLISH_TOKEN=... npx --yes @wotaso/[email protected] healthIt validates the checked-in publisher config, tenant token, canonical project identity, output route, and the real daily runner. The generated GitHub Actions workflow runs this check before every publish pull. A manual local run is useful for diagnosis, but the project only receives a green daily-runner check after a successful CI heartbeat. The heartbeat expires after 36 hours, so a missing daily job becomes visible without creating a synthetic test post.
For a CMS or tenant server cron, invoke the same command from the scheduled environment:
SEO_BLOG_SCHEDULED=true npx --yes @wotaso/[email protected] health --mode cmsProvider credentials and the publish token remain in that runner's secret store. The command reports only whether expected environment variables exist, never their values.
For static hosts behind Cloudflare, publish-static writes a temporary publish-result manifest. Run cache revalidation only after the host deployment has started. The command waits for cache-busted article URLs to become reachable, purges only the article route variants plus the blog index and discovery files, and warms those URLs again:
SEO_BLOG_PUBLISH_TOKEN=... \
npx --yes @wotaso/[email protected] cache-revalidate --if-configuredThe preferred path uses the project’s Cloudflare OAuth connection and existing publisher token, so website repositories do not need Cloudflare credentials. --if-configured keeps publishing green until the project connection exists. A zone-scoped CLOUDFLARE_API_TOKEN plus CLOUDFLARE_ZONE_ID remains available as an advanced fallback. Neither path clears the full zone.
Admin/setup agents can log in and rotate a tenant-scoped publish token directly into GitHub Actions secrets:
npx --yes @wotaso/[email protected] login-password \
--email [email protected] \
--password-stdin
npx --yes @wotaso/[email protected] rotate-token flashes \
--repo Wotaso/Flashes-WebsiteProjects can always be created and remain stored without consuming weekly capacity. Allocate exact weekly counts per language market; setting every count to zero pauses generation without deleting data:
npx --yes @wotaso/[email protected] create-project https://example.com
npx --yes @wotaso/[email protected] activate-project example
npx --yes @wotaso/[email protected] pause-project example
npx --yes @wotaso/[email protected] set-locales example --locales de-DE:DE,en-US:US
npx --yes @wotaso/[email protected] set-weekly-allocation example --allocation de-DE:DE=5,en-US:US=2
npx --yes @wotaso/[email protected] set-image-style example \
--background-color '#0E1021' \
--text-color '#F7F7F5' \
--primary-color '#B6FF5C'The image palette is project-scoped. Text and background must meet a 4.5:1 contrast ratio; generated hero images ask the image model itself to render one short article headline in these colors.
Free workspaces include two review-ready drafts per calendar month and one project. Paid workspaces choose one shared allowance from 1 to 30 posts or pages per week. The monthly price is EUR 13 for the core workspace, plus EUR 6 per weekly slot, plus EUR 4 per active project above the first.
Active-project capacity unlocks with the selected weekly allowance: 1–5 slots include one project, 6–9 include two, 10–14 include three, 15–19 include four, 20–24 include five, 25–29 include six, and 30 includes seven. Projects with no allocation stay paused. Content languages do not cost extra, but every generated language version uses one shared weekly slot.
Publish tokens belong only in CI, server-side jobs, or CMS secret stores. Do not place them in browser bundles, mobile apps, public files, or committed environment files.
Native CMS providers include WordPress, Webflow, Wix, Contentful, Sanity, Strapi, Ghost, Shopify, and HubSpot. Framer Server API publishing is available as beta on Node.js 22+.
FRAMER_API_KEY=... FRAMER_PROJECT_URL=... FRAMER_COLLECTION=Blog \
npx --yes @wotaso/[email protected] publish-cms --cms framer
WIX_API_KEY=... WIX_SITE_ID=... WIX_MEMBER_ID=... \
npx --yes @wotaso/[email protected] publish-cms --cms wixSquarespace uses the reviewed Markdown/HTML export or signed-webhook path because its public APIs do not expose general blog-post publishing. Never store CMS credentials in seo-blog.config.json.
