From 3eeaf0e362b88787a836edbc82df8b9cfd59f332 Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 10:54:39 +0800 Subject: [PATCH 01/16] docs: specify main-site-aligned theme --- ...-22-main-site-aligned-docs-theme-design.md | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-22-main-site-aligned-docs-theme-design.md diff --git a/docs/superpowers/specs/2026-07-22-main-site-aligned-docs-theme-design.md b/docs/superpowers/specs/2026-07-22-main-site-aligned-docs-theme-design.md new file mode 100644 index 00000000..3f8e9bf0 --- /dev/null +++ b/docs/superpowers/specs/2026-07-22-main-site-aligned-docs-theme-design.md @@ -0,0 +1,79 @@ +# Main-site-aligned documentation theme + +## Goal + +Make the Astro Starlight documentation site share the visual language of the AnyCap main +site while preserving Starlight navigation, accessibility, responsive behavior, and +content structure. The existing light and dark theme controls remain available. + +## Source of truth + +The visual source is `/Users/tyluo/go/src/engineering/anycap/web`: + +- `src/app/layout.tsx` defines Inter, Space Mono, and Geist Pixel Square usage. +- Landing and SEO components define the light palette and component treatment. +- The documentation site must not copy the main site's component CSS wholesale. It owns + its Starlight-specific implementation and uses the main site only as a token and + typographic reference. + +## Typography + +- Body text and standard interface text use Inter, matching the main site. +- Display headings, section headings, and the documentation site title use Geist Pixel + Square, matching the main site's branded display treatment. +- Code blocks, inline code, and data-dense controls use Space Mono. +- Typography remains responsive and readable: prose line length and Starlight's existing + semantic hierarchy are preserved. + +## Color system + +### Light theme + +| Purpose | Token/value | +| --- | --- | +| Page background | `#f9faf5` | +| Sidebar and secondary surface | `#f4f5f0` | +| Raised surface | `#fbfcf8` | +| Primary text | `#1c1f17` | +| Secondary text | `#4d5145` | +| Brand accent | `#6b7d3a` | +| Hairline/border | `#d4dbb8` | + +### Dark theme + +The dark theme is a deliberate olive-charcoal extension of the main-site palette, not a +literal inversion. Its page and sidebar surfaces are dark olive-charcoal; text is a warm, +high-contrast pale green; borders and accent states use muted olive values. Token contrast +must preserve readable prose, code, tables, and interactive controls. + +## Component treatment + +- Header and sidebar use the matched surfaces, restrained hairlines, and translucent + backdrop treatment appropriate to their theme. +- Buttons, current navigation states, links, cards, tables, and code blocks use the same + olive accent and border language as the main site. +- Cards retain Starlight semantics but use the main site's restrained rounded-corner, + hover, focus-ring, and shadow behavior. +- The documentation hero retains its content and layout; only its typography, spacing, + background, and visual treatments change. + +## Technical approach + +1. Add the `geist` package at the version compatible with the main site, so Geist Pixel + Square is loaded from the same package rather than approximated with a fallback. +2. Define the main-site font stacks and palette as CSS custom properties in + `docs/src/styles/custom.css`. +3. Map those properties to Starlight theme tokens separately for light and dark themes. +4. Apply narrow component overrides for Starlight chrome, prose headings, cards, buttons, + tables, code blocks, hover states, focus rings, and reduced-motion behavior. +5. Do not change documentation content, routes, metadata, sitemap behavior, or deployment + configuration. + +## Acceptance criteria + +- Light mode uses the specified main-site palette and the three matching font roles. +- Dark mode remains selectable and uses an accessible olive-charcoal counterpart. +- No heading falls back to the previous Space Mono display style. +- Starlight navigation, search, table overflow, focus indicators, and mobile behavior + remain usable. +- `make docs-check`, `make docs-audit`, and `git diff --check` pass after implementation. From e326ede0fafee8d696f00d4222e0dde910acafb7 Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 10:59:07 +0800 Subject: [PATCH 02/16] chore: ignore local worktrees --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e6449a44..82274354 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ Thumbs.db # Node node_modules/ +.worktrees/ docs/.astro/ docs/.wrangler/ docs/dist/ From 69eab32471110d885d508864c190569207ae16b6 Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:04:59 +0800 Subject: [PATCH 03/16] docs: refresh project overview --- README.md | 430 ++++++++++-------------------------------------------- 1 file changed, 79 insertions(+), 351 deletions(-) diff --git a/README.md b/README.md index a4723299..b373e1d7 100644 --- a/README.md +++ b/README.md @@ -1,235 +1,71 @@ # AnyCap - [![npm](https://img.shields.io/npm/v/@anycap/cli?color=cb3837&logo=npm)](https://www.npmjs.com/package/@anycap/cli) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![GitHub stars](https://img.shields.io/github/stars/anycap-ai/anycap?style=social)](https://github.com/anycap-ai/anycap/stargazers) [![Skills directory](https://img.shields.io/badge/skills.sh-anycap-6f42c1)](https://skills.sh/anycap-ai/anycap) -> **The execution layer for coding agents.** -> Give Claude Code, Cursor, Codex, and any client with local stdio MCP support real-world -> capabilities — image, video, vision, music, web search, and publishing — inside the -> agents you already use. -> Connect through an Agent Skill or the built-in local MCP server. +> **The capability runtime for AI agents.** +> +> Give coding agents a single CLI, Agent Skill, and local MCP server for creating media, +> understanding files, researching the live web, and delivering results people can use. -![AnyCap — the execution layer for coding agents](assets/hero.png) +![AnyCap — the capability runtime for AI agents](assets/hero.png) ---- +## Start here -## Quick start (30 seconds) - -Paste this to Claude Code, Cursor, Codex, or any agent that can run a shell: +Give this to Claude Code, Codex, Cursor, or another agent that can run shell commands: ```text -Read https://raw.githubusercontent.com/anycap-ai/anycap/main/llms.txt and follow the -instructions to install the AnyCap CLI and skill. If you can't access the URL, run: - 1. curl -fsSL https://anycap.ai/install.sh | sh - 2. npx -y skills add anycap-ai/anycap -s '*' -g -y - 3. anycap login - 4. anycap status +Read https://raw.githubusercontent.com/anycap-ai/anycap/main/llms.txt and follow its +instructions to install and configure AnyCap. ``` -That's it. The agent installs the CLI, installs the skill, logs you in, and verifies the -connection — then every capability below becomes a single sentence. Prefer native MCP -tool discovery? Jump to [Connect via MCP](#connect-via-mcp). Prefer to do it by hand? -Jump to [Manual install](#manual-install). - -## Contents - -- [What this fixes](#what-this-fixes) -- [What your agent can do](#what-your-agent-can-do) -- [Install by talking to your agent](#install-by-talking-to-your-agent) -- [Skills and MCP, not SDK glue](#skills-and-mcp-not-sdk-glue) -- [Manual install](#manual-install) -- [Connect via MCP](#connect-via-mcp) -- [Capabilities](#capabilities) -- [Works with](#works-with) -- [FAQ & troubleshooting](#faq--troubleshooting) -- [Links](#links) -- [License](#license) - ---- - -## What this fixes - -Your coding agent can reason and write code. It **still can't** ship a meme, generate a -hero image, read a screenshot a user dropped in, search the live web, or publish a -landing page. - -| Without AnyCap | With AnyCap | -| --------------------------------------- | --------------------------------------------- | -| "I can't generate images." | One command → a finished asset. | -| "I can't see this screenshot." | Reads images, video, and audio. | -| "My knowledge has a cutoff." | Live web search with citations. | -| "You'll need to deploy that yourself." | Ships a hosted page and returns the URL. | - -AnyCap is the missing execution layer — multimodal, web, and delivery behind **one CLI -and one auth**. - -## What your agent can do - -### 1. Make a meme end-to-end - -Agent drafts the caption, generates the image, revises the visual, then returns a -shareable link. - - - -```bash -anycap image generate --prompt "cat in a tiny chef hat, meme caption 'ship it'" \ - --model nano-banana-2 -o meme.png -anycap image generate --prompt "make the caption bigger and yellow" \ - --model nano-banana-2 --mode image-to-image --param images=./meme.png -o meme-v2.png -anycap drive upload meme-v2.png --parent-path /memes -anycap drive share --src-path /memes/meme-v2.png -``` - -### 2. Turn a prompt into a hosted page - -Agent writes the copy, generates hero visuals, and deploys a live URL. - - - -```bash -anycap image generate --prompt "abstract product hero, soft gradient" \ - --model nano-banana-2 -o ./dist/hero.png -anycap page deploy ./dist --name "launch-page" --publish -``` - -### 3. Review a screenshot with a human in the loop - -Agent opens the annotation UI, the human marks issues, the agent reads the feedback and -revises. - - - -```bash -anycap annotate ./screenshot.png --no-wait -anycap annotate poll --session ann_xxxx -anycap actions image-read --file ./screenshot-annotated.png \ - --instruction "List every change requested by the annotations" -``` - -## Install by talking to your agent - -AnyCap installs itself in plain English. No package-manager dance, no doc spelunking, no -version pinning. Hand the [Quick start](#quick-start-30-seconds) paragraph to your agent — -it reads `llms.txt`, installs the CLI, installs the skill, opens the browser to log you -in, and verifies the connection. - -After that one paste, everything else is just conversation. The skill teaches the agent -the full command surface, so future actions become one sentence: - -- **"Upgrade AnyCap and check my status."** -- **"Generate a hero image for the launch page and deploy it."** -- **"Search the web for the latest Cursor changelog and summarize."** - -The agent picks the right capability, runs it, and hands you the result. You never touch a -flag unless you want to. - -## Skills and MCP, not SDK glue - -Most tools ship an SDK and ask you to wire it in. AnyCap gives agents two integration -paths on top of the same CLI runtime: - -- **Agent Skill.** The agent reads `SKILL.md`, learns complete workflows, and invokes the - CLI from plain-English requests. The same skill works across Claude Code, Cursor, - Codex, and [40+ agents](https://skills.sh). -- **MCP server.** Any MCP host can discover typed AnyCap tools through the built-in local - stdio server. No separate package or service is required. -- **One auth, one CLI.** Both paths share the same login, configuration, HTTP client, and - local media handling. -- **No glue code.** You do not import a library, manage API objects, or maintain a custom - tool wrapper. - -## Manual install - -### 1. Install the CLI - -macOS / Linux / Windows (Git Bash): +If your agent cannot reach that URL, use the manual setup below. ```bash +# Install the CLI (macOS, Linux, or Windows through Git Bash) curl -fsSL https://anycap.ai/install.sh | sh -``` - -npm (all platforms): - -```bash -npm install -g @anycap/cli -``` - -Or grab a binary from [GitHub Releases](https://github.com/anycap-ai/anycap/releases). - -### 2. Install the skill -Works with Claude Code, Cursor, Windsurf, OpenCode, and [40+ agents](https://skills.sh): - -```bash +# Install the AnyCap Agent Skill npx -y skills add anycap-ai/anycap -s '*' -g -y -``` - -
-More ways to install the skill - -```bash -# Via AnyCap CLI (installs complete skill directories and references) -anycap skill install --target ~/.agents/skills/ -# Check if the CLI skill is up to date -anycap skill check --target ~/.agents/skills/anycap-cli/SKILL.md -``` - -
- -### 3. Verify - -```bash +# Authenticate once, then confirm the runtime is ready anycap login anycap status ``` -## Connect via MCP +The binary installer is the recommended path. If your environment already uses Node.js, +you can instead run `npm install -g @anycap/cli`. Platform binaries are also available +from [GitHub Releases](https://github.com/anycap-ai/anycap/releases). -AnyCap CLI v0.5.0 includes a local [Model Context Protocol](https://modelcontextprotocol.io/) -server. It runs over stdio inside the existing `anycap` binary, exposes 23 typed tools, -and shares the CLI's credentials and configuration. It has been verified with MCP -protocol version `2025-06-18`. There is no separate MCP package or background service to -install. +## Choose an integration -First verify that your installed CLI includes the server: +| Integration | Use it when | Entry point | +| --- | --- | --- | +| **Agent Skill** | You want an agent to choose the right workflow from a natural-language request. | [`skills/anycap-cli/SKILL.md`](skills/anycap-cli/SKILL.md) | +| **Local stdio MCP** | Your agent host needs typed tool discovery. | `anycap mcp` | +| **CLI** | You want shell automation or direct control. | `anycap ` | -```bash -anycap mcp --help -``` +All three use the same installed runtime and authentication state. The Skill and MCP +server can be used together. -If the command is missing, run `anycap update` and check again. +### Connect through MCP -### Claude Code - -Add AnyCap to the current project using Claude's machine-local scope and authorize local -media under that project: +First confirm that the installed CLI exposes the server: ```bash -claude mcp add --scope local anycap -- anycap mcp --allow-root "$PWD" +anycap mcp --help ``` -Local scope avoids committing a machine-specific absolute path to the shared project -configuration. - -### Codex - -The Codex CLI command creates a global server definition. Omit a permanent absolute root -and let each project session provide client roots or use the server working-directory -fallback: +For Codex, add the local server definition: ```bash codex mcp add anycap -- anycap mcp ``` -### Cursor and other MCP clients - -Add this stdio server to your client's MCP configuration. Replace the example path with -an absolute directory containing media that AnyCap may read: +For a generic stdio MCP client, configure `anycap mcp`. Add a narrowly scoped +`--allow-root` only when the server needs to access local media: ```json { @@ -237,184 +73,76 @@ an absolute directory containing media that AnyCap may read: "anycap": { "type": "stdio", "command": "anycap", - "args": [ - "mcp", - "--allow-root", - "/absolute/path/to/media" - ] + "args": ["mcp", "--allow-root", "/absolute/path/to/media"] } } } ``` -`--allow-root` is optional and repeatable. Effective local access is the union of these -flags, `ANYCAP_MCP_FILE_ROOTS`, and roots advertised by the MCP client; if none are -provided, the server falls back to its working directory. URL-based calls do not need -local access. Keep every configured root as narrow as possible. - -Existing `anycap login` credentials are picked up automatically, including logins -completed after the MCP process starts. MCP clients can also authenticate without -leaving the session through `anycap_auth_status`, `anycap_login`, and -`anycap_login_poll`. Authentication never opens a browser at server startup. - -The 23 tools cover: - -| Group | MCP tools | -| ----- | --------- | -| Authentication | Status, device login, login polling, and logout | -| Discovery | AnyCap status, model listing, and model schemas | -| Image | Generate, edit, upscale, and understand images | -| Video | Generate and understand videos | -| Music and audio | Generate music and analyze or transcribe audio | -| Web | Search the live web and crawl pages to Markdown | -| Drive | List and resolve files and folders (read-only) | -| Page | Inspect sites and versions (read-only) | - -For the complete tool inventory, authentication flow, and local image-to-video example, -see [the MCP reference](skills/anycap-cli/references/cli-reference.md#local-stdio-mcp). - -## Capabilities - -| Capability | Command | What agents do with it | -| ----------------------------- | -------------------------------------------------------------------- | ----------------------------------------------- | -| Image generation / edit | `anycap image generate` (`--mode image-to-image` for edits) | Hero art, meme assets, illustration, photo edit | -| Image / video / audio reading | `anycap actions image-read` / `video-read` / `audio-read` | Screenshot review, meeting transcripts, QA | -| Video generation | `anycap video generate` | Demo clips, animated assets | -| Music generation | `anycap music generate` | Jingles, soundtracks | -| Web search | `anycap search --query` (general) / `--prompt` (grounded with cites) | Live answers, research | -| Web crawl | `anycap crawl ` | Any URL into clean Markdown | -| Annotate | `anycap annotate` | Human-in-the-loop visual feedback | -| Draw | `anycap draw` | Live whiteboard / Mermaid diagrams | -| Drive | `anycap drive upload` / `share` | Shareable file links | -| Page | `anycap page deploy` | Hosted static pages | -| Download | `anycap download` | Save any remote file | - -> **Coming soon:** TTS / voice synthesis. - -## Models - -AnyCap's model lineup evolves quickly, so the **authoritative, always-current list lives -in the CLI** — run `anycap models` to see exactly what your account can use: - -```bash -anycap image models # image generation / edit -anycap video models # video generation -anycap music models # music generation -``` - -Inspect a single model's modes and parameter schema before calling it: - -```bash -anycap image models nano-banana-2 # modes + operations -anycap image models nano-banana-2 schema --mode text-to-image # parameter schema -``` - -The tables below reflect the models available in CLI **v0.3.6** — newer versions add and -retire models, so treat the CLI output as the source of truth. - -### Image generation / edit - -| Model | Name | Modes | -| ------------------ | --------------------- | --------------------------------- | -| `nano-banana-2` | Nano Banana 2 | text-to-image, image-to-image | -| `nano-banana-pro` | Nano Banana Pro | text-to-image, image-to-image | -| `gpt-image-2` | GPT Image 2 | text-to-image, image-to-image | -| `flux-kontext-max` | FLUX.1 Kontext Max | text-to-image, image-to-image | -| `qwen-image` | Qwen Image | text-to-image, image-to-image | -| `seedream-4.5` | Seedream 4.5 | text-to-image, image-to-image | -| `seedream-5` | Seedream 5 | text-to-image, image-to-image | - -### Video generation - -| Model | Name | Modes | -| ------------------- | ---------------- | -------------------------------- | -| `veo-3.1` | Veo 3.1 | text-to-video, image-to-video | -| `veo-3.1-fast` | Veo 3.1 Fast | text-to-video, image-to-video | -| `sora-2-pro` | Sora 2 Pro | text-to-video, image-to-video | -| `kling-3.0` | Kling 3.0 | text-to-video, image-to-video | -| `kling-o1` | Kling O1 | image-to-video | -| `hailuo-2.3` | Hailuo 2.3 | text-to-video, image-to-video | -| `seedance-1.5-pro` | Seedance 1.5 Pro | text-to-video, image-to-video | -| `seedance-2` | Seedance 2.0 | text-to-video, image-to-video | -| `seedance-2-fast` | Seedance 2.0 Fast| text-to-video, image-to-video | - -### Music generation - -| Model | Name | Modes | -| ------------------- | ----------------- | -------------- | -| `suno-v5` | Suno V5 | text-to-music | -| `suno-v5.5` | Suno V5.5 | text-to-music | -| `mureka-v8` | Mureka V8 | text-to-music | -| `elevanlabs-music` | ElevenLabs Music | text-to-music | +Do not commit personal absolute paths into shared project configuration. Read the +[MCP setup guide](https://docs.anyware.ai/getting-started/mcp/) and +[security guide](https://docs.anyware.ai/reference/security/) before granting local +file access. -> **Reading / understanding** (`anycap actions image-read` / `video-read` / `audio-read`) -> automatically selects the best available model. Pass `--model` only when you need a -> specific one. +## What agents can do -## Works with +| Area | Examples | +| --- | --- | +| **Create** | Generate and edit images, generate video, compose music, and create audio scenes. | +| **Understand** | Read screenshots, compare images, summarize video, and transcribe or analyze audio. | +| **Research** | Search the live web and crawl pages into Markdown. | +| **Collaborate** | Gather visual annotations and work with shared drawing surfaces. | +| **Deliver** | Upload and share files, publish static sites, and create project snapshots. | -Claude Code · Cursor · Codex · Windsurf · OpenCode · 40+ agents via -[skills.sh](https://skills.sh) · any client that supports local stdio MCP servers. +For a first end-to-end workflow, see [generate and share an image](https://docs.anyware.ai/guides/generate-and-share-image/). +Other task-oriented guides cover [image-to-video](https://docs.anyware.ai/guides/image-to-video/), +[human feedback](https://docs.anyware.ai/guides/human-feedback/), +[web research](https://docs.anyware.ai/guides/web-research/), and +[static-site publishing](https://docs.anyware.ai/guides/publish-site/). -## FAQ & troubleshooting +## Discover live capabilities -
-Do I need an API key for each capability? +Model availability and input schemas change. Query the installed CLI instead of copying +model IDs or parameters from an old example: -No. One `anycap login` covers every capability — image, video, vision, music, web, and -publishing share a single auth. The CLI and MCP server use the same credentials. -
- -
-Does the MCP server upload every local file? - -No. Only local media explicitly passed to a compatible tool can be uploaded, and its -canonical path must be inside an effective allowed root. Configure repeatable -`--allow-root` flags or client-advertised roots to control access. URL-based inputs do -not require local filesystem access. -
- -
-The agent says it can't reach llms.txt. - -Run the four manual commands from [Quick start](#quick-start-30-seconds), or follow -[Manual install](#manual-install). -
+```bash +# List models available to your account +anycap image models +anycap video models +anycap music models -
-The skill isn't showing up in my agent. +# Inspect the current modes and schema for a selected model +anycap image models +anycap image models schema --mode +``` -Confirm it installed into your agent's skills directory, then re-check: +Run `anycap --help` at any level to inspect commands and options: ```bash -anycap skill check --target ~/.agents/skills/anycap-cli/SKILL.md +anycap --help +anycap image --help +anycap image generate --help ``` -Reload your agent so it re-reads the skills directory. -
+## Documentation and support -
-How do I update? +- [Documentation](https://docs.anyware.ai) — quickstart, capability guides, and reference material. +- [CLI reference](https://docs.anyware.ai/reference/cli/) — command groups, output conventions, and discovery. +- [MCP tool reference](https://docs.anyware.ai/reference/mcp-tools/) — current tools and read/write boundaries. +- [Authentication and configuration](https://docs.anyware.ai/getting-started/authentication/) — interactive, headless, and API-key workflows. +- [Troubleshooting](https://docs.anyware.ai/getting-started/troubleshooting/) — installation and runtime diagnostics. +- [`llms.txt`](llms.txt) — concise installation and usage instructions for agents. +- [Skills directory](https://skills.sh/anycap-ai/anycap) — install the Agent Skills collection. -Tell your agent **"Upgrade AnyCap and check my status,"** or re-run the install script and -`npx -y skills add anycap-ai/anycap -s '*' -g -y`. -
+## Updating -
-Which platforms are supported? +The CLI checks for updates during normal use. Update it explicitly when needed: -macOS, Linux, and Windows (via Git Bash). Install through the script, npm, or a binary -from [Releases](https://github.com/anycap-ai/anycap/releases). -
- -## Links - -- [Documentation](https://docs.anyware.ai) — Guides and reference -- [llms.txt](llms.txt) — Give this to your agent -- [Skill file](skills/anycap-cli/SKILL.md) — Full capability documentation -- [GitHub Releases](https://github.com/anycap-ai/anycap/releases) — CLI binaries -- [skills.sh](https://skills.sh/anycap-ai/anycap) — Skills directory listing -- [Website](https://anycap.ai) +```bash +anycap update +npx -y skills update +anycap status +``` ## License From 55d82d3d9542d137bea106b51b974d06e1dd6abd Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:04:59 +0800 Subject: [PATCH 04/16] feat(docs): align theme with main site --- docs/package-lock.json | 55 +++-- docs/package.json | 3 +- docs/scripts/prepare-assets.mjs | 8 +- docs/scripts/theme-contract.test.mjs | 36 +++ docs/src/styles/custom.css | 197 +++++++++++------ ...2026-07-22-main-site-aligned-docs-theme.md | 208 ++++++++++++++++++ 6 files changed, 426 insertions(+), 81 deletions(-) create mode 100644 docs/scripts/theme-contract.test.mjs create mode 100644 docs/superpowers/plans/2026-07-22-main-site-aligned-docs-theme.md diff --git a/docs/package-lock.json b/docs/package-lock.json index d00d46b1..f9893a16 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "@astrojs/sitemap": "3.7.3", "@astrojs/starlight": "0.41.3", - "astro": "7.1.3" + "astro": "7.1.3", + "geist": "^1.7.0" }, "devDependencies": { "@astrojs/check": "0.9.6", @@ -872,22 +873,11 @@ "dev": true, "license": "MIT" }, - "node_modules/@emnapi/core": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", - "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/runtime": { "version": "1.11.2", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1382,6 +1372,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1404,6 +1395,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1423,6 +1415,7 @@ "version": "0.35.3", "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1445,6 +1438,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1461,6 +1455,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1477,6 +1472,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1493,6 +1489,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1509,6 +1506,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1525,6 +1523,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1541,6 +1540,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1557,6 +1557,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1573,6 +1574,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1589,6 +1591,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1605,6 +1608,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1627,6 +1631,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1649,6 +1654,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1671,6 +1677,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1693,6 +1700,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1715,6 +1723,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1737,6 +1746,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1759,6 +1769,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1778,6 +1789,7 @@ "version": "0.35.3", "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -1797,6 +1809,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1816,6 +1829,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -1835,6 +1849,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -1854,6 +1869,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -3881,6 +3897,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/geist": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/geist/-/geist-1.7.0.tgz", + "integrity": "sha512-ZaoiZwkSf0DwwB1ncdLKp+ggAldqxl5L1+SXaNIBGkPAqcu+xjVJLxlf3/S8vLt9UHx1xu5fz3lbzKCj5iOVdQ==", + "license": "SIL OPEN FONT LICENSE", + "peerDependencies": { + "next": ">=13.2.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -7655,7 +7680,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -8575,7 +8600,7 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "devOptional": true, + "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/docs/package.json b/docs/package.json index c9e40760..2fc81041 100644 --- a/docs/package.json +++ b/docs/package.json @@ -18,7 +18,8 @@ "dependencies": { "@astrojs/sitemap": "3.7.3", "@astrojs/starlight": "0.41.3", - "astro": "7.1.3" + "astro": "7.1.3", + "geist": "^1.7.0" }, "devDependencies": { "@astrojs/check": "0.9.6", diff --git a/docs/scripts/prepare-assets.mjs b/docs/scripts/prepare-assets.mjs index 16a5851f..aa994314 100644 --- a/docs/scripts/prepare-assets.mjs +++ b/docs/scripts/prepare-assets.mjs @@ -1,4 +1,4 @@ -import { mkdir, rm } from 'node:fs/promises'; +import { copyFile, mkdir, rm } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import sharp from 'sharp'; @@ -7,12 +7,18 @@ const source = fileURLToPath(new URL('../../assets/hero.png', import.meta.url)); const destinationDirectory = fileURLToPath(new URL('../public/assets/', import.meta.url)); const destination = fileURLToPath(new URL('../public/assets/social-card.png', import.meta.url)); const legacyDestination = fileURLToPath(new URL('../public/assets/hero.png', import.meta.url)); +const geistPixelSource = fileURLToPath( + new URL('../node_modules/geist/dist/fonts/geist-pixel/GeistPixel-Square.woff2', import.meta.url) +); +const geistPixelDestination = fileURLToPath(new URL('../public/assets/geist-pixel-square.woff2', import.meta.url)); await mkdir(destinationDirectory, { recursive: true }); await rm(legacyDestination, { force: true }); +await copyFile(geistPixelSource, geistPixelDestination); await sharp(source) .resize(1200, 630, { fit: 'cover', position: 'centre' }) .png({ compressionLevel: 9, effort: 10 }) .toFile(destination); console.log(`Prepared docs asset: ${destination.replace(`${docsRoot}/`, '')} (1200x630)`); +console.log(`Prepared docs font: ${geistPixelDestination.replace(`${docsRoot}/`, '')}`); diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs new file mode 100644 index 00000000..247c4535 --- /dev/null +++ b/docs/scripts/theme-contract.test.mjs @@ -0,0 +1,36 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; + +const stylesheet = await readFile(new URL('../src/styles/custom.css', import.meta.url), 'utf8'); +const assetPreparation = await readFile(new URL('./prepare-assets.mjs', import.meta.url), 'utf8'); + +test('uses the main-site typography roles', () => { + assert.match(stylesheet, /--sl-font:\s*Inter,/); + assert.match(stylesheet, /--sl-font-heading:\s*"Geist Pixel Square"/); + assert.match(stylesheet, /--sl-font-mono:\s*"Space Mono"/); + assert.match(stylesheet, /h1,[\s\S]*font-family:\s*var\(--sl-font-heading\)/); +}); + +test('maps the main-site light palette and an olive-charcoal dark palette', () => { + for (const declaration of [ + '--ac-page: #f9faf5', + '--ac-surface: #f4f5f0', + '--ac-raised: #fbfcf8', + '--ac-ink: #1c1f17', + '--ac-muted: #4d5145', + '--ac-accent: #6b7d3a', + '--ac-border: #d4dbb8', + ]) { + assert.ok(stylesheet.includes(declaration), `missing light token: ${declaration}`); + } + + assert.match(stylesheet, /:root\[data-theme='dark'\][\s\S]*--ac-page: #12150f/); + assert.match(stylesheet, /:root\[data-theme='dark'\][\s\S]*--ac-accent: #a8bc62/); +}); + +test('publishes the shared display font as a documentation asset', () => { + assert.match(stylesheet, /url\("\/assets\/geist-pixel-square\.woff2"\)/); + assert.match(assetPreparation, /GeistPixel-Square\.woff2/); + assert.match(assetPreparation, /geist-pixel-square\.woff2/); +}); diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css index eb1c9a15..1b7e3577 100644 --- a/docs/src/styles/custom.css +++ b/docs/src/styles/custom.css @@ -1,54 +1,103 @@ @layer starlight.base, anycap; @layer anycap { + @font-face { + font-family: "Geist Pixel Square"; + src: url("/assets/geist-pixel-square.woff2") format("woff2"); + font-display: swap; + font-weight: 500; + } + :root { --sl-font: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --sl-font-heading: "Geist Pixel Square", ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, monospace; --sl-font-mono: "Space Mono", "SFMono-Regular", Consolas, "Liberation Mono", monospace; - --sl-color-accent-low: #27301d; - --sl-color-accent: #a8bc62; - --sl-color-accent-high: #edf1dc; - --sl-color-white: #fffffc; - --sl-color-gray-1: #eceee7; - --sl-color-gray-2: #ccd1c3; - --sl-color-gray-3: #9ba291; - --sl-color-gray-4: #686f61; - --sl-color-gray-5: #3a3e32; - --sl-color-gray-6: #24271f; - --sl-color-black: #171914; - --sl-color-bg-nav: color-mix(in srgb, #171914 88%, transparent); - --sl-color-bg-sidebar: #171914; --sl-content-width: 52rem; --sl-text-5xl: clamp(3rem, 7vw, 5.75rem); + + --ac-page: #12150f; + --ac-surface: #191d14; + --ac-raised: #22271c; + --ac-ink: #f4f5e9; + --ac-muted: #c9d0b9; + --ac-accent: #a8bc62; + --ac-accent-strong: #c8d98a; + --ac-border: #3c4730; + --ac-shadow: rgba(3, 5, 2, 0.28); + } + + :root[data-theme='dark'] { + --ac-page: #12150f; + --ac-surface: #191d14; + --ac-raised: #22271c; + --ac-ink: #f4f5e9; + --ac-muted: #c9d0b9; + --ac-accent: #a8bc62; + --ac-accent-strong: #c8d98a; + --ac-border: #3c4730; + --ac-shadow: rgba(3, 5, 2, 0.28); + } + + :root[data-theme='light'] { + --ac-page: #f9faf5; + --ac-surface: #f4f5f0; + --ac-raised: #fbfcf8; + --ac-ink: #1c1f17; + --ac-muted: #4d5145; + --ac-accent: #6b7d3a; + --ac-accent-strong: #55672c; + --ac-border: #d4dbb8; + --ac-shadow: rgba(28, 31, 23, 0.08); + } + + :root, + :root[data-theme='dark'] { + --sl-color-accent-low: #27301d; + --sl-color-accent: var(--ac-accent); + --sl-color-accent-high: var(--ac-accent-strong); + --sl-color-white: var(--ac-ink); + --sl-color-gray-1: var(--ac-ink); + --sl-color-gray-2: #dce2d0; + --sl-color-gray-3: var(--ac-muted); + --sl-color-gray-4: #8f9981; + --sl-color-gray-5: #505b45; + --sl-color-gray-6: var(--ac-surface); + --sl-color-gray-7: var(--ac-surface); + --sl-color-black: var(--ac-page); + --sl-color-bg: var(--ac-page); + --sl-color-bg-nav: color-mix(in srgb, var(--ac-page) 92%, transparent); + --sl-color-bg-sidebar: var(--ac-surface); + --sl-color-hairline-light: var(--ac-border); + --sl-color-hairline: var(--ac-border); } :root[data-theme='light'] { --sl-color-accent-low: #edf0df; - --sl-color-accent: #6b7d3a; + --sl-color-accent: var(--ac-accent); --sl-color-accent-high: #36401f; - --sl-color-white: #1c1f17; - --sl-color-gray-1: #3a3e32; - --sl-color-gray-2: #596052; + --sl-color-white: var(--ac-ink); + --sl-color-gray-1: var(--ac-ink); + --sl-color-gray-2: var(--ac-muted); --sl-color-gray-3: #666d61; - --sl-color-gray-4: #aeb4a8; - --sl-color-gray-5: #dce0d2; - --sl-color-gray-6: #eceee7; - --sl-color-gray-7: #f4f5f0; - --sl-color-black: #fffffc; - --sl-color-bg: #f9faf5; - --sl-color-bg-nav: color-mix(in srgb, #f9faf5 90%, transparent); - --sl-color-bg-sidebar: #f4f5f0; - --sl-color-hairline-light: #dce0d2; - --sl-color-hairline: #ccd1c3; + --sl-color-gray-4: #9a9e90; + --sl-color-gray-5: var(--ac-border); + --sl-color-gray-6: var(--ac-surface); + --sl-color-gray-7: var(--ac-surface); + --sl-color-black: var(--ac-raised); + --sl-color-bg: var(--ac-page); + --sl-color-bg-nav: color-mix(in srgb, var(--ac-page) 92%, transparent); + --sl-color-bg-sidebar: var(--ac-surface); + --sl-color-hairline-light: var(--ac-border); + --sl-color-hairline: var(--ac-border); } html { - background: var(--sl-color-bg); + background: var(--ac-page); } body { - background: - radial-gradient(circle at 78% 6%, color-mix(in srgb, var(--sl-color-accent) 10%, transparent), transparent 28rem), - var(--sl-color-bg); + background: var(--ac-page); + color: var(--ac-ink); letter-spacing: -0.01em; } @@ -57,23 +106,33 @@ h3, h4, .site-title { - font-family: var(--sl-font-mono); - letter-spacing: -0.045em; + font-family: var(--sl-font-heading); + font-weight: 500; + letter-spacing: -0.035em; } h2 { - border-top: 1px solid var(--sl-color-hairline); + border-top: 1px solid var(--ac-border); padding-top: 1.65rem; } a:not([class]) { - text-decoration-color: color-mix(in srgb, var(--sl-color-accent) 55%, transparent); + text-decoration-color: color-mix(in srgb, var(--ac-accent) 55%, transparent); text-decoration-thickness: 0.12em; text-underline-offset: 0.18em; } + a:focus-visible, + button:focus-visible, + summary:focus-visible, + input:focus-visible { + outline: 2px solid var(--ac-accent); + outline-offset: 3px; + } + header.header { - border-bottom-color: var(--sl-color-hairline); + border-bottom-color: var(--ac-border); + background: var(--sl-color-bg-nav); backdrop-filter: blur(18px); } @@ -83,55 +142,52 @@ } .sidebar-pane { - border-inline-end-color: var(--sl-color-hairline); + border-inline-end-color: var(--ac-border); } .sidebar-content a[aria-current='page'] { - border-radius: 0.35rem; + border-radius: 0.45rem; + background: color-mix(in srgb, var(--ac-accent) 10%, transparent); + color: var(--ac-ink); font-weight: 700; } code:not(:where(.expressive-code *)) { - border: 1px solid var(--sl-color-hairline); + border: 1px solid var(--ac-border); border-radius: 0.32rem; - background: color-mix(in srgb, var(--sl-color-gray-6) 70%, transparent); + background: color-mix(in srgb, var(--ac-raised) 88%, var(--ac-accent-low)); + color: var(--ac-ink); padding: 0.12rem 0.35rem; } .expressive-code { - --ec-brdRad: 0.55rem; + --ec-brdRad: 0.75rem; margin-block: 1.4rem; } .expressive-code .frame { - box-shadow: 0 16px 36px color-mix(in srgb, #1c1f17 9%, transparent); + border-color: var(--ac-border); + box-shadow: 0 10px 24px var(--ac-shadow); } - .sl-link-card { - border-color: var(--sl-color-hairline); - border-radius: 0.6rem; - background: color-mix(in srgb, var(--sl-color-bg) 92%, var(--sl-color-accent-low)); + .sl-link-card, + .card { + border-color: var(--ac-border); + border-radius: 0.75rem; + background: var(--ac-raised); box-shadow: none; transition: border-color 160ms ease, transform 160ms ease, box-shadow 160ms ease; } .sl-link-card:hover { - border-color: var(--sl-color-accent); - box-shadow: 0 16px 38px color-mix(in srgb, var(--sl-color-accent) 10%, transparent); + border-color: var(--ac-accent); + box-shadow: 0 10px 24px var(--ac-shadow); transform: translateY(-2px); } .card { - --sl-card-border: var(--sl-color-accent); - --sl-card-bg: var(--sl-color-accent-low); - border: 1px solid var(--sl-color-hairline); - border-radius: 0.6rem; - background: color-mix(in srgb, var(--sl-color-bg) 94%, var(--sl-color-accent-low)); - } - - .card:nth-child(n) { - --sl-card-border: var(--sl-color-accent); - --sl-card-bg: var(--sl-color-accent-low); + --sl-card-border: var(--ac-accent); + --sl-card-bg: var(--ac-raised); } [data-has-hero] .hero { @@ -146,22 +202,29 @@ [data-has-hero] .hero .tagline { max-width: 44rem; + color: var(--ac-muted); font-size: clamp(1.05rem, 2vw, 1.35rem); } [data-has-hero] .hero img { - filter: drop-shadow(0 24px 48px color-mix(in srgb, var(--sl-color-accent) 20%, transparent)); + filter: drop-shadow(0 20px 48px color-mix(in srgb, var(--ac-accent) 20%, transparent)); } .sl-link-button.primary { - border-color: var(--sl-color-accent); - background: var(--sl-color-accent); - color: #fffffc; + border-color: var(--ac-accent); + background: var(--ac-accent); + color: #f4f5f0; + } + + .sl-link-button.primary:hover { + background: var(--ac-ink); + color: var(--ac-page); } .sl-link-button.secondary { - border-color: var(--sl-color-hairline); + border-color: var(--ac-border); background: transparent; + color: var(--ac-ink); } table { @@ -170,13 +233,19 @@ } th { + background: color-mix(in srgb, var(--ac-surface) 84%, var(--ac-accent-low)); font-family: var(--sl-font-mono); font-size: 0.82rem; letter-spacing: -0.02em; } + th, + td { + border-color: var(--ac-border); + } + .content-panel + .content-panel { - border-top-color: var(--sl-color-hairline); + border-top-color: var(--ac-border); } @media (max-width: 50rem) { diff --git a/docs/superpowers/plans/2026-07-22-main-site-aligned-docs-theme.md b/docs/superpowers/plans/2026-07-22-main-site-aligned-docs-theme.md new file mode 100644 index 00000000..60e87a88 --- /dev/null +++ b/docs/superpowers/plans/2026-07-22-main-site-aligned-docs-theme.md @@ -0,0 +1,208 @@ +# Main-site-aligned documentation theme Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Align the Starlight documentation site's typography and light/dark color system with the AnyCap main site's visual language. + +**Architecture:** The docs site retains Starlight and implements its own visual system in `custom.css`. A small Node contract test prevents font roles and primary palette values from drifting, while Starlight CSS variables map those tokens to the existing documentation UI. + +**Tech Stack:** Astro 7, Starlight 0.41, CSS custom properties, Node.js built-in test runner, Geist package. + +## Global Constraints + +- Preserve documentation content, routes, metadata, sitemap behavior, and deployment configuration. +- Use the main site at `/Users/tyluo/go/src/engineering/anycap/web` only as the typography and palette source of truth. +- Use Inter for body/UI, Geist Pixel Square for branded headings, and Space Mono for code. +- Keep both Starlight light and dark modes selectable and accessible. +- User-facing documentation remains English. + +--- + +### Task 1: Add a theme contract test + +**Files:** + +- Create: `docs/scripts/theme-contract.test.mjs` +- Test: `docs/scripts/theme-contract.test.mjs` + +**Interfaces:** + +- Consumes: `docs/src/styles/custom.css` as UTF-8 text. +- Produces: a Node test command that exits non-zero when required theme declarations drift. + +- [x] **Step 1: Write the failing test** + +Create `docs/scripts/theme-contract.test.mjs` with the following test. It intentionally fails until the new theme declarations exist. + +```js +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; + +const stylesheet = await readFile(new URL('../src/styles/custom.css', import.meta.url), 'utf8'); + +test('uses the main-site typography roles', () => { + assert.match(stylesheet, /--sl-font:\s*Inter,/); + assert.match(stylesheet, /--sl-font-heading:\s*"Geist Pixel Square"/); + assert.match(stylesheet, /--sl-font-mono:\s*"Space Mono"/); + assert.match(stylesheet, /h1,[\s\S]*font-family:\s*var\(--sl-font-heading\)/); +}); + +test('maps the main-site light palette and an olive-charcoal dark palette', () => { + for (const declaration of [ + '--ac-page: #f9faf5', '--ac-surface: #f4f5f0', '--ac-raised: #fbfcf8', + '--ac-ink: #1c1f17', '--ac-muted: #4d5145', '--ac-accent: #6b7d3a', + '--ac-border: #d4dbb8', + ]) assert.ok(stylesheet.includes(declaration), `missing light token: ${declaration}`); + assert.match(stylesheet, /:root\[data-theme='dark'\][\s\S]*--ac-page: #12150f/); + assert.match(stylesheet, /:root\[data-theme='dark'\][\s\S]*--ac-accent: #a8bc62/); +}); +``` + +- [x] **Step 2: Run the test to verify it fails** + +Run: `node --test docs/scripts/theme-contract.test.mjs` + +Expected: FAIL because `custom.css` does not yet define `--sl-font-heading` or the required `--ac-*` tokens. + +- [x] **Step 3: Commit the test scaffold** + +```bash +git add docs/scripts/theme-contract.test.mjs +git commit -m "test(docs): define main-site theme contract" +``` + +### Task 2: Add the display-font dependency and map theme tokens + +**Files:** + +- Modify: `docs/package.json` +- Modify: `docs/package-lock.json` +- Modify: `docs/src/styles/custom.css` + +**Interfaces:** + +- Consumes: main-site palette and typography roles from the approved design spec. +- Produces: font faces and `--ac-*`, `--sl-*` mappings consumed by Starlight and Task 3. + +- [x] **Step 1: Add the dependency** + +Run from `docs/`: + +```bash +npm install --save geist@1.7.0 +``` + +Expected: `docs/package.json` contains `"geist": "1.7.0"` and the lockfile records the resolved package. + +- [x] **Step 2: Replace root theme declarations with the token map** + +In `docs/src/styles/custom.css`, import Geist Pixel Square and define font roles and light tokens before mapping them to Starlight: + +```css +@import "geist/font/pixel"; + +:root[data-theme='light'] { + --ac-page: #f9faf5; + --ac-surface: #f4f5f0; + --ac-raised: #fbfcf8; + --ac-ink: #1c1f17; + --ac-muted: #4d5145; + --ac-accent: #6b7d3a; + --ac-border: #d4dbb8; +} + +:root[data-theme='dark'] { + --ac-page: #12150f; + --ac-surface: #191d14; + --ac-raised: #22271c; + --ac-ink: #f4f5e9; + --ac-muted: #c9d0b9; + --ac-accent: #a8bc62; + --ac-border: #3c4730; +} + +:root { + --sl-font: Inter, ui-sans-serif, system-ui, sans-serif; + --sl-font-heading: "Geist Pixel Square", ui-monospace, monospace; + --sl-font-mono: "Space Mono", "SFMono-Regular", Consolas, monospace; + --sl-color-bg: var(--ac-page); + --sl-color-bg-nav: color-mix(in srgb, var(--ac-page) 92%, transparent); + --sl-color-bg-sidebar: var(--ac-surface); + --sl-color-white: var(--ac-ink); + --sl-color-gray-1: var(--ac-ink); + --sl-color-gray-2: var(--ac-muted); + --sl-color-accent: var(--ac-accent); + --sl-color-hairline: var(--ac-border); +} +``` + +- [x] **Step 3: Run the theme contract test** + +Run: `node --test docs/scripts/theme-contract.test.mjs` + +Expected: PASS with 2 passing tests. + +- [x] **Step 4: Commit the dependency and token map** + +```bash +git add docs/package.json docs/package-lock.json docs/src/styles/custom.css +git commit -m "feat(docs): align theme tokens with main site" +``` + +### Task 3: Apply component treatment and validate the build + +**Files:** + +- Modify: `docs/src/styles/custom.css` +- Test: `docs/scripts/theme-contract.test.mjs` + +**Interfaces:** + +- Consumes: the `--ac-*` tokens and `--sl-font-heading` from Task 2. +- Produces: styled Starlight chrome, prose, cards, controls, code, tables, and accessible interactions. + +- [x] **Step 1: Apply typography and interaction rules** + +Add these focused rules after the token mapping: + +```css +body { background: var(--ac-page); color: var(--ac-ink); letter-spacing: -0.01em; } +h1, h2, h3, h4, .site-title { font-family: var(--sl-font-heading); letter-spacing: -0.035em; } +a:focus-visible, button:focus-visible, summary:focus-visible { outline: 2px solid var(--ac-accent); outline-offset: 3px; } +.sl-link-card, .card { border-color: var(--ac-border); background: var(--ac-raised); border-radius: 0.75rem; } +.sl-link-card:hover { border-color: var(--ac-accent); box-shadow: 0 10px 24px color-mix(in srgb, var(--ac-ink) 8%, transparent); transform: translateY(-2px); } +``` + +Retain the existing reduced-motion override and table overflow behavior. Update header, +sidebar, button, code, table, and hero declarations to consume `--ac-*` tokens instead of +hard-coded legacy values. + +- [x] **Step 2: Run diagnostics** + +Run: + +```bash +node --test docs/scripts/theme-contract.test.mjs +make docs-check +``` + +Expected: 3 passing theme tests; Astro reports 0 errors, 0 warnings, and 0 hints. + +- [x] **Step 3: Build and audit the release artifact** + +Run: + +```bash +make docs-audit +git diff --check +``` + +Expected: the SEO audit reports 28 indexable pages and 8 release artifacts; `git diff --check` prints no whitespace errors. + +- [x] **Step 4: Commit the component treatment** + +```bash +git add docs/src/styles/custom.css docs/scripts/theme-contract.test.mjs +git commit -m "feat(docs): apply main-site visual treatment" +``` From ecd840533d72f0a68072e894871f6dc478e3ca28 Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:15:52 +0800 Subject: [PATCH 05/16] fix(docs): unify typography with main site --- docs/package-lock.json | 49 ++++++++++---- docs/package.json | 5 +- docs/public/assets/geist-pixel-square.woff2 | Bin 0 -> 28616 bytes docs/scripts/prepare-assets.mjs | 16 +++-- docs/scripts/theme-contract.test.mjs | 26 ++++++- docs/src/assets/anycap-logo.svg | 24 +++++-- docs/src/content/docs/index.mdx | 5 +- docs/src/styles/custom.css | 71 +++++++++++++++++++- 8 files changed, 162 insertions(+), 34 deletions(-) create mode 100644 docs/public/assets/geist-pixel-square.woff2 diff --git a/docs/package-lock.json b/docs/package-lock.json index f9893a16..a9a1cb88 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -10,8 +10,9 @@ "dependencies": { "@astrojs/sitemap": "3.7.3", "@astrojs/starlight": "0.41.3", - "astro": "7.1.3", - "geist": "^1.7.0" + "@fontsource-variable/inter": "^5.2.8", + "@fontsource/space-mono": "^5.2.6", + "astro": "7.1.3" }, "devDependencies": { "@astrojs/check": "0.9.6", @@ -873,11 +874,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.11.2", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1355,6 +1367,24 @@ "@expressive-code/core": "^0.44.1" } }, + "node_modules/@fontsource-variable/inter": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz", + "integrity": "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/space-mono": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@fontsource/space-mono/-/space-mono-5.2.6.tgz", + "integrity": "sha512-3IOUpd5neu52Ewt7LUyiTHCv31oMq2+rmOeIUlCjGSIBwIY+TswZwCTVKjzhWkr+TEuwdWZeKcjAEDuwmTn2Pw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@img/colour": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", @@ -3897,15 +3927,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/geist": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/geist/-/geist-1.7.0.tgz", - "integrity": "sha512-ZaoiZwkSf0DwwB1ncdLKp+ggAldqxl5L1+SXaNIBGkPAqcu+xjVJLxlf3/S8vLt9UHx1xu5fz3lbzKCj5iOVdQ==", - "license": "SIL OPEN FONT LICENSE", - "peerDependencies": { - "next": ">=13.2.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -7680,7 +7701,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -8600,7 +8621,7 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/docs/package.json b/docs/package.json index 2fc81041..5651c800 100644 --- a/docs/package.json +++ b/docs/package.json @@ -18,8 +18,9 @@ "dependencies": { "@astrojs/sitemap": "3.7.3", "@astrojs/starlight": "0.41.3", - "astro": "7.1.3", - "geist": "^1.7.0" + "@fontsource-variable/inter": "^5.2.8", + "@fontsource/space-mono": "^5.2.6", + "astro": "7.1.3" }, "devDependencies": { "@astrojs/check": "0.9.6", diff --git a/docs/public/assets/geist-pixel-square.woff2 b/docs/public/assets/geist-pixel-square.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..232cae2c11851c04f4508f9bd1ac0046d36fdd81 GIT binary patch literal 28616 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
  • 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

    P}59*%~bzZMKf$K=Mg)s&)64a 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 literal 0 HcmV?d00001 diff --git a/docs/scripts/prepare-assets.mjs b/docs/scripts/prepare-assets.mjs index aa994314..0dc94159 100644 --- a/docs/scripts/prepare-assets.mjs +++ b/docs/scripts/prepare-assets.mjs @@ -7,18 +7,24 @@ const source = fileURLToPath(new URL('../../assets/hero.png', import.meta.url)); const destinationDirectory = fileURLToPath(new URL('../public/assets/', import.meta.url)); const destination = fileURLToPath(new URL('../public/assets/social-card.png', import.meta.url)); const legacyDestination = fileURLToPath(new URL('../public/assets/hero.png', import.meta.url)); -const geistPixelSource = fileURLToPath( - new URL('../node_modules/geist/dist/fonts/geist-pixel/GeistPixel-Square.woff2', import.meta.url) +const interSource = fileURLToPath( + new URL('../node_modules/@fontsource-variable/inter/files/inter-latin-wght-normal.woff2', import.meta.url) ); -const geistPixelDestination = fileURLToPath(new URL('../public/assets/geist-pixel-square.woff2', import.meta.url)); +const interDestination = fileURLToPath(new URL('../public/assets/inter-latin-wght-normal.woff2', import.meta.url)); +const spaceMonoSource = fileURLToPath( + new URL('../node_modules/@fontsource/space-mono/files/space-mono-latin-700-normal.woff2', import.meta.url) +); +const spaceMonoDestination = fileURLToPath(new URL('../public/assets/space-mono-700.woff2', import.meta.url)); await mkdir(destinationDirectory, { recursive: true }); await rm(legacyDestination, { force: true }); -await copyFile(geistPixelSource, geistPixelDestination); +await copyFile(interSource, interDestination); +await copyFile(spaceMonoSource, spaceMonoDestination); await sharp(source) .resize(1200, 630, { fit: 'cover', position: 'centre' }) .png({ compressionLevel: 9, effort: 10 }) .toFile(destination); console.log(`Prepared docs asset: ${destination.replace(`${docsRoot}/`, '')} (1200x630)`); -console.log(`Prepared docs font: ${geistPixelDestination.replace(`${docsRoot}/`, '')}`); +console.log(`Prepared docs font: ${interDestination.replace(`${docsRoot}/`, '')}`); +console.log(`Prepared docs font: ${spaceMonoDestination.replace(`${docsRoot}/`, '')}`); diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index 247c4535..382a8eb4 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -4,11 +4,13 @@ import test from 'node:test'; const stylesheet = await readFile(new URL('../src/styles/custom.css', import.meta.url), 'utf8'); const assetPreparation = await readFile(new URL('./prepare-assets.mjs', import.meta.url), 'utf8'); +const homepage = await readFile(new URL('../src/content/docs/index.mdx', import.meta.url), 'utf8'); +const logo = await readFile(new URL('../src/assets/anycap-logo.svg', import.meta.url), 'utf8'); test('uses the main-site typography roles', () => { assert.match(stylesheet, /--sl-font:\s*Inter,/); assert.match(stylesheet, /--sl-font-heading:\s*"Geist Pixel Square"/); - assert.match(stylesheet, /--sl-font-mono:\s*"Space Mono"/); + assert.match(stylesheet, /--sl-font-mono:\s*Inter,/); assert.match(stylesheet, /h1,[\s\S]*font-family:\s*var\(--sl-font-heading\)/); }); @@ -31,6 +33,24 @@ test('maps the main-site light palette and an olive-charcoal dark palette', () = test('publishes the shared display font as a documentation asset', () => { assert.match(stylesheet, /url\("\/assets\/geist-pixel-square\.woff2"\)/); - assert.match(assetPreparation, /GeistPixel-Square\.woff2/); - assert.match(assetPreparation, /geist-pixel-square\.woff2/); +}); + +test('bundles the main-site Inter and Space Mono font roles', () => { + assert.match(stylesheet, /font-family: "Inter"/); + assert.match(stylesheet, /--sl-font-mono: Inter,/); + assert.match(assetPreparation, /inter-latin-wght-normal\.woff2/); + assert.match(assetPreparation, /space-mono-latin-700-normal\.woff2/); + assert.match(logo, /space-mono-700\.woff2/); +}); + +test('uses the main-site brand mark without duplicating it in the homepage hero', () => { + assert.doesNotMatch(homepage, /hero:[\s\S]*?image:/); + assert.match(logo, /fill="#6b7d3a"/); + assert.match(logo, /rotate\(-5 100 100\)/); +}); + +test('uses rendered icons and styled documentation footer controls', () => { + assert.match(homepage, //); + assert.match(stylesheet, /footer\.sl-flex/); + assert.match(stylesheet, /\.pagination-links/); }); diff --git a/docs/src/assets/anycap-logo.svg b/docs/src/assets/anycap-logo.svg index 01c195d2..5f0fd502 100644 --- a/docs/src/assets/anycap-logo.svg +++ b/docs/src/assets/anycap-logo.svg @@ -1,8 +1,22 @@ AnyCap - - - - anycap - + + + any + cap diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index 2091cc2f..b506f8eb 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -4,9 +4,6 @@ description: Give AI agents image, video, audio, web, storage, and publishing ca template: splash hero: tagline: The capability runtime for AI agents. Create media, understand files, search the live web, and publish results through one CLI and one authentication flow. - image: - file: ../../assets/anycap-logo.svg - alt: AnyCap actions: - text: Start in 30 seconds link: /getting-started/ @@ -33,7 +30,7 @@ AnyCap gives Claude Code, Cursor, Codex, and other agents a consistent execution Generate and edit images, produce video, compose music, and build complete audio scenes. - + Read screenshots, compare images, summarize video, and transcribe or analyze audio. diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css index 1b7e3577..f65f6fd2 100644 --- a/docs/src/styles/custom.css +++ b/docs/src/styles/custom.css @@ -1,6 +1,14 @@ @layer starlight.base, anycap; @layer anycap { + @font-face { + font-family: "Inter"; + src: url("/assets/inter-latin-wght-normal.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 100 900; + } + @font-face { font-family: "Geist Pixel Square"; src: url("/assets/geist-pixel-square.woff2") format("woff2"); @@ -11,7 +19,7 @@ :root { --sl-font: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --sl-font-heading: "Geist Pixel Square", ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, monospace; - --sl-font-mono: "Space Mono", "SFMono-Regular", Consolas, "Liberation Mono", monospace; + --sl-font-mono: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --sl-content-width: 52rem; --sl-text-5xl: clamp(3rem, 7vw, 5.75rem); @@ -200,6 +208,16 @@ line-height: 0.98; } + [data-has-hero] .hero:not(:has(> img)) { + grid-template-columns: minmax(0, 1fr); + } + + [data-has-hero] .hero:not(:has(> img)) .stack, + [data-has-hero] .hero:not(:has(> img)) .copy { + align-items: flex-start; + text-align: start; + } + [data-has-hero] .hero .tagline { max-width: 44rem; color: var(--ac-muted); @@ -248,6 +266,57 @@ border-top-color: var(--ac-border); } + footer.sl-flex { + border-top: 1px solid var(--ac-border); + gap: 1rem; + margin-top: 3.5rem; + padding-top: 1.25rem; + } + + footer.sl-flex .meta { + align-items: center; + color: var(--ac-muted); + font-size: 0.8rem; + gap: 0.5rem 1.25rem; + margin-top: 0; + } + + footer.sl-flex .meta a { + color: var(--ac-muted); + text-decoration: none; + } + + footer.sl-flex .meta a:hover { + color: var(--ac-accent); + } + + footer.sl-flex .pagination-links { + gap: 0.75rem; + grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); + } + + footer.sl-flex .pagination-links a { + border-color: var(--ac-border); + border-radius: 0.75rem; + box-shadow: none; + color: var(--ac-muted); + padding: 0.8rem 1rem; + transition: border-color 160ms ease, background 160ms ease, color 160ms ease; + } + + footer.sl-flex .pagination-links a:hover { + background: color-mix(in srgb, var(--ac-accent) 8%, var(--ac-raised)); + border-color: var(--ac-accent); + color: var(--ac-ink); + } + + footer.sl-flex .pagination-links .link-title { + color: var(--ac-ink); + font-family: var(--sl-font-heading); + font-size: var(--sl-text-base); + letter-spacing: -0.02em; + } + @media (max-width: 50rem) { [data-has-hero] .hero { padding-block: 2.5rem; From 2b8c0160c8a43000c672471ea6e8b06404f7306d Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:23:11 +0800 Subject: [PATCH 06/16] docs: add live model discovery guide --- docs/astro.config.mjs | 3 +- docs/scripts/audit-build.mjs | 2 +- docs/scripts/theme-contract.test.mjs | 23 +++++++++- docs/src/assets/anycap-logo.svg | 2 +- docs/src/content/docs/capabilities/models.mdx | 44 +++++++++++++++++++ docs/src/styles/custom.css | 2 +- 6 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 docs/src/content/docs/capabilities/models.mdx diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index d52fa5a1..9cea5813 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -35,7 +35,7 @@ export default defineConfig({ Head: './src/components/Head.astro', }, head: [ - { tag: 'meta', attrs: { name: 'theme-color', content: '#f9faf5' } }, + { tag: 'meta', attrs: { name: 'theme-color', content: '#f4f5f0' } }, { tag: 'meta', attrs: { name: 'color-scheme', content: 'light dark' } }, { tag: 'meta', @@ -74,6 +74,7 @@ export default defineConfig({ label: 'Capabilities', items: [ { label: 'Overview', link: '/capabilities/' }, + { label: 'Models', link: '/capabilities/models/' }, { label: 'Image', link: '/capabilities/image/' }, { label: 'Video', link: '/capabilities/video/' }, { label: 'Media understanding', link: '/capabilities/media-understanding/' }, diff --git a/docs/scripts/audit-build.mjs b/docs/scripts/audit-build.mjs index bee62ef6..02a77453 100644 --- a/docs/scripts/audit-build.mjs +++ b/docs/scripts/audit-build.mjs @@ -3,7 +3,7 @@ import { extname, join, relative, resolve } from 'node:path'; const dist = resolve('dist'); const canonicalOrigin = 'https://docs.anyware.ai'; -const expectedIndexablePages = 28; +const expectedIndexablePages = 29; const errors = []; async function walk(directory) { diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index 382a8eb4..133b39e9 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -6,6 +6,10 @@ const stylesheet = await readFile(new URL('../src/styles/custom.css', import.met const assetPreparation = await readFile(new URL('./prepare-assets.mjs', import.meta.url), 'utf8'); const homepage = await readFile(new URL('../src/content/docs/index.mdx', import.meta.url), 'utf8'); const logo = await readFile(new URL('../src/assets/anycap-logo.svg', import.meta.url), 'utf8'); +const docsConfig = await readFile(new URL('../astro.config.mjs', import.meta.url), 'utf8'); +const modelsPage = await readFile(new URL('../src/content/docs/capabilities/models.mdx', import.meta.url), 'utf8').catch( + () => '', +); test('uses the main-site typography roles', () => { assert.match(stylesheet, /--sl-font:\s*Inter,/); @@ -16,7 +20,7 @@ test('uses the main-site typography roles', () => { test('maps the main-site light palette and an olive-charcoal dark palette', () => { for (const declaration of [ - '--ac-page: #f9faf5', + '--ac-page: #f4f5f0', '--ac-surface: #f4f5f0', '--ac-raised: #fbfcf8', '--ac-ink: #1c1f17', @@ -47,6 +51,13 @@ test('uses the main-site brand mark without duplicating it in the homepage hero' assert.doesNotMatch(homepage, /hero:[\s\S]*?image:/); assert.match(logo, /fill="#6b7d3a"/); assert.match(logo, /rotate\(-5 100 100\)/); + assert.match(logo, /]*>cap<\/text>/); +}); + +test('uses the same off-white page canvas as the main site', () => { + assert.match(stylesheet, /:root\[data-theme='light'\][\s\S]*--ac-page: #f4f5f0/); + assert.match(stylesheet, /--sl-color-bg: var\(--ac-page\)/); + assert.match(docsConfig, /theme-color', content: '#f4f5f0'/); }); test('uses rendered icons and styled documentation footer controls', () => { @@ -54,3 +65,13 @@ test('uses rendered icons and styled documentation footer controls', () => { assert.match(stylesheet, /footer\.sl-flex/); assert.match(stylesheet, /\.pagination-links/); }); + +test('routes model discovery to the live catalog instead of duplicating availability claims', () => { + assert.match(docsConfig, /\{ label: 'Models', link: '\/capabilities\/models\/' \}/); + assert.match(modelsPage, /https:\/\/anycap\.ai\/models/); + assert.match(modelsPage, /anycap image models/); + assert.match(modelsPage, /anycap video models/); + assert.match(modelsPage, /anycap music models/); + assert.match(modelsPage, /anycap audio models/); + assert.match(modelsPage, /not duplicated in this documentation site/); +}); diff --git a/docs/src/assets/anycap-logo.svg b/docs/src/assets/anycap-logo.svg index 5f0fd502..d0361541 100644 --- a/docs/src/assets/anycap-logo.svg +++ b/docs/src/assets/anycap-logo.svg @@ -18,5 +18,5 @@ any - cap + cap diff --git a/docs/src/content/docs/capabilities/models.mdx b/docs/src/content/docs/capabilities/models.mdx new file mode 100644 index 00000000..486639be --- /dev/null +++ b/docs/src/content/docs/capabilities/models.mdx @@ -0,0 +1,44 @@ +--- +title: Models +description: Discover the current AnyCap image, video, music, and audio model catalog without relying on stale static lists. +--- + +import { Aside } from '@astrojs/starlight/components'; + +The live AnyCap CLI catalog is the source of truth for model availability, operations, modes, and request parameters. It changes independently of documentation releases, so the full model list is not duplicated in this documentation site. + +For a maintained public overview of the current supported models and workflow guidance, see the [AnyCap Model Guide](https://anycap.ai/models). + +## Discover the live catalog + +Use the capability that matches the output you need: + +| Capability | List current models | Inspect an exact contract | +| --- | --- | --- | +| Image generation and editing | `anycap image models` | `anycap image models schema --mode ` | +| Video generation and editing | `anycap video models` | `anycap video models schema --mode ` | +| Music generation | `anycap music models` | `anycap music models schema --mode text-to-music` | +| Audio generation | `anycap audio models` | `anycap audio models schema --mode ` | + +The current catalog covers image, video, music, and audio generation. It also includes media-understanding actions for images, video, and audio; those actions choose a strong default unless you explicitly pass `--model`. + +## Read the result before selecting a model + +Each `models` command returns machine-readable JSON. Start by listing the model ID, display name, and supported modes: + +```bash +anycap video models \ + | jq -r '.models[] | [.model, (.display_name // .name), ([.operations[]?.modes[]?.mode] | unique | join(", "))] | @tsv' +``` + +Then inspect the schema for the one model and mode you selected. The schema is the authority for accepted parameters such as aspect ratio, duration, resolution, or reference inputs. + +```bash +anycap video models schema --mode image-to-video +``` + + + +See [Generation and editing](/capabilities/image/), [Video generation](/capabilities/video/), and [Music and audio generation](/capabilities/audio/) for capability-specific workflows. diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css index f65f6fd2..d5800ab0 100644 --- a/docs/src/styles/custom.css +++ b/docs/src/styles/custom.css @@ -47,7 +47,7 @@ } :root[data-theme='light'] { - --ac-page: #f9faf5; + --ac-page: #f4f5f0; --ac-surface: #f4f5f0; --ac-raised: #fbfcf8; --ac-ink: #1c1f17; From 65da77e07271a5c9eb2d6630977a3f4127b0de0e Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:26:06 +0800 Subject: [PATCH 07/16] docs: clarify CLI and MCP entry paths --- docs/scripts/theme-contract.test.mjs | 7 +++++++ docs/src/content/docs/index.mdx | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index 133b39e9..e8026962 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -75,3 +75,10 @@ test('routes model discovery to the live catalog instead of duplicating availabi assert.match(modelsPage, /anycap audio models/); assert.match(modelsPage, /not duplicated in this documentation site/); }); + +test('shows direct CLI and MCP integration paths in the homepage hero', () => { + assert.match(homepage, /text: Install the CLI[\s\S]*link: \/getting-started\/install\//); + assert.match(homepage, /text: Connect through MCP[\s\S]*link: \/getting-started\/mcp\//); + assert.match(homepage, /title="CLI"[\s\S]*href="\/getting-started\/install\/"/); + assert.match(homepage, /title="Local MCP server"[\s\S]*href="\/getting-started\/mcp\/"/); +}); diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index b506f8eb..125193e3 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -5,8 +5,8 @@ template: splash hero: tagline: The capability runtime for AI agents. Create media, understand files, search the live web, and publish results through one CLI and one authentication flow. actions: - - text: Start in 30 seconds - link: /getting-started/ + - text: Install the CLI + link: /getting-started/install/ icon: right-arrow variant: primary - text: Connect through MCP @@ -43,6 +43,12 @@ AnyCap gives Claude Code, Cursor, Codex, and other agents a consistent execution ## Choose your integration + + Date: Wed, 22 Jul 2026 11:30:45 +0800 Subject: [PATCH 08/16] fix(docs): restore hero and main-site texture --- docs/scripts/theme-contract.test.mjs | 7 +++++-- docs/src/assets/anycap-logo.svg | 3 +-- docs/src/content/docs/index.mdx | 4 ++-- docs/src/styles/custom.css | 13 +++++++++++++ 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index e8026962..c95e39dc 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -51,13 +51,16 @@ test('uses the main-site brand mark without duplicating it in the homepage hero' assert.doesNotMatch(homepage, /hero:[\s\S]*?image:/); assert.match(logo, /fill="#6b7d3a"/); assert.match(logo, /rotate\(-5 100 100\)/); - assert.match(logo, /]*>cap<\/text>/); + assert.match(logo, /any<\/tspan>cap<\/tspan>/); }); test('uses the same off-white page canvas as the main site', () => { assert.match(stylesheet, /:root\[data-theme='light'\][\s\S]*--ac-page: #f4f5f0/); assert.match(stylesheet, /--sl-color-bg: var\(--ac-page\)/); assert.match(docsConfig, /theme-color', content: '#f4f5f0'/); + assert.match(stylesheet, /body::before/); + assert.match(stylesheet, /feTurbulence/); + assert.match(stylesheet, /opacity: 0\.07/); }); test('uses rendered icons and styled documentation footer controls', () => { @@ -77,7 +80,7 @@ test('routes model discovery to the live catalog instead of duplicating availabi }); test('shows direct CLI and MCP integration paths in the homepage hero', () => { - assert.match(homepage, /text: Install the CLI[\s\S]*link: \/getting-started\/install\//); + assert.match(homepage, /text: Start in 30 seconds[\s\S]*link: \/getting-started\//); assert.match(homepage, /text: Connect through MCP[\s\S]*link: \/getting-started\/mcp\//); assert.match(homepage, /title="CLI"[\s\S]*href="\/getting-started\/install\/"/); assert.match(homepage, /title="Local MCP server"[\s\S]*href="\/getting-started\/mcp\/"/); diff --git a/docs/src/assets/anycap-logo.svg b/docs/src/assets/anycap-logo.svg index d0361541..0730a9ae 100644 --- a/docs/src/assets/anycap-logo.svg +++ b/docs/src/assets/anycap-logo.svg @@ -17,6 +17,5 @@ - any - cap + anycap diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index 125193e3..19785364 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -5,8 +5,8 @@ template: splash hero: tagline: The capability runtime for AI agents. Create media, understand files, search the live web, and publish results through one CLI and one authentication flow. actions: - - text: Install the CLI - link: /getting-started/install/ + - text: Start in 30 seconds + link: /getting-started/ icon: right-arrow variant: primary - text: Connect through MCP diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css index d5800ab0..b4a8ee9e 100644 --- a/docs/src/styles/custom.css +++ b/docs/src/styles/custom.css @@ -109,6 +109,19 @@ letter-spacing: -0.01em; } + body::before { + position: fixed; + z-index: 50; + inset: 0; + content: ""; + pointer-events: none; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='.75'/%3E%3C/svg%3E"); + background-repeat: repeat; + background-size: 200px 200px; + mix-blend-mode: multiply; + opacity: 0.07; + } + h1, h2, h3, From e3f59e596d6c5d071bd84a6f3fee93f709386894 Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:32:31 +0800 Subject: [PATCH 09/16] docs: show all homepage entry paths --- docs/scripts/theme-contract.test.mjs | 1 + docs/src/content/docs/index.mdx | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index c95e39dc..c37fe8b0 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -81,6 +81,7 @@ test('routes model discovery to the live catalog instead of duplicating availabi test('shows direct CLI and MCP integration paths in the homepage hero', () => { assert.match(homepage, /text: Start in 30 seconds[\s\S]*link: \/getting-started\//); + assert.match(homepage, /text: Install the CLI[\s\S]*link: \/getting-started\/install\//); assert.match(homepage, /text: Connect through MCP[\s\S]*link: \/getting-started\/mcp\//); assert.match(homepage, /title="CLI"[\s\S]*href="\/getting-started\/install\/"/); assert.match(homepage, /title="Local MCP server"[\s\S]*href="\/getting-started\/mcp\/"/); diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index 19785364..1e561dee 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -9,6 +9,10 @@ hero: link: /getting-started/ icon: right-arrow variant: primary + - text: Install the CLI + link: /getting-started/install/ + icon: right-arrow + variant: secondary - text: Connect through MCP link: /getting-started/mcp/ icon: external From 7fd6206b33ec9f2740d06fecc6d1f5ce1b5eb52c Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:35:13 +0800 Subject: [PATCH 10/16] fix(docs): match main site paper grain --- docs/scripts/prepare-assets.mjs | 17 +++++++++++++++++ docs/scripts/theme-contract.test.mjs | 3 ++- docs/src/styles/custom.css | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/scripts/prepare-assets.mjs b/docs/scripts/prepare-assets.mjs index 0dc94159..ba189c87 100644 --- a/docs/scripts/prepare-assets.mjs +++ b/docs/scripts/prepare-assets.mjs @@ -7,6 +7,8 @@ const source = fileURLToPath(new URL('../../assets/hero.png', import.meta.url)); const destinationDirectory = fileURLToPath(new URL('../public/assets/', import.meta.url)); const destination = fileURLToPath(new URL('../public/assets/social-card.png', import.meta.url)); const legacyDestination = fileURLToPath(new URL('../public/assets/hero.png', import.meta.url)); +const grainDestination = fileURLToPath(new URL('../public/assets/grain.png', import.meta.url)); +const grainSize = 200; const interSource = fileURLToPath( new URL('../node_modules/@fontsource-variable/inter/files/inter-latin-wght-normal.woff2', import.meta.url) ); @@ -20,6 +22,20 @@ await mkdir(destinationDirectory, { recursive: true }); await rm(legacyDestination, { force: true }); await copyFile(interSource, interDestination); await copyFile(spaceMonoSource, spaceMonoDestination); +const grainPixels = Buffer.alloc(grainSize * grainSize * 3); + +for (let index = 0; index < grainPixels.length; index += 3) { + const value = Math.floor(Math.random() * 256); + grainPixels[index] = value; + grainPixels[index + 1] = value; + grainPixels[index + 2] = value; +} + +await sharp(grainPixels, { + raw: { width: grainSize, height: grainSize, channels: 3 }, +}) + .png({ compressionLevel: 9, effort: 10 }) + .toFile(grainDestination); await sharp(source) .resize(1200, 630, { fit: 'cover', position: 'centre' }) .png({ compressionLevel: 9, effort: 10 }) @@ -28,3 +44,4 @@ await sharp(source) console.log(`Prepared docs asset: ${destination.replace(`${docsRoot}/`, '')} (1200x630)`); console.log(`Prepared docs font: ${interDestination.replace(`${docsRoot}/`, '')}`); console.log(`Prepared docs font: ${spaceMonoDestination.replace(`${docsRoot}/`, '')}`); +console.log(`Prepared docs texture: ${grainDestination.replace(`${docsRoot}/`, '')} (${grainSize}x${grainSize})`); diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index c37fe8b0..420a7b33 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -59,8 +59,9 @@ test('uses the same off-white page canvas as the main site', () => { assert.match(stylesheet, /--sl-color-bg: var\(--ac-page\)/); assert.match(docsConfig, /theme-color', content: '#f4f5f0'/); assert.match(stylesheet, /body::before/); - assert.match(stylesheet, /feTurbulence/); + assert.match(stylesheet, /url\("\/assets\/grain\.png"\)/); assert.match(stylesheet, /opacity: 0\.07/); + assert.match(assetPreparation, /grain\.png/); }); test('uses rendered icons and styled documentation footer controls', () => { diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css index b4a8ee9e..acbcf8fe 100644 --- a/docs/src/styles/custom.css +++ b/docs/src/styles/custom.css @@ -115,7 +115,7 @@ inset: 0; content: ""; pointer-events: none; - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='.75'/%3E%3C/svg%3E"); + background-image: url("/assets/grain.png"); background-repeat: repeat; background-size: 200px 200px; mix-blend-mode: multiply; From 9abd9f2fede97de3ce3c0c68448b3ff1fe0d59cf Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:36:20 +0800 Subject: [PATCH 11/16] fix(docs): unify hero action icons --- docs/scripts/theme-contract.test.mjs | 1 + docs/src/content/docs/index.mdx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index 420a7b33..45e25d34 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -84,6 +84,7 @@ test('shows direct CLI and MCP integration paths in the homepage hero', () => { assert.match(homepage, /text: Start in 30 seconds[\s\S]*link: \/getting-started\//); assert.match(homepage, /text: Install the CLI[\s\S]*link: \/getting-started\/install\//); assert.match(homepage, /text: Connect through MCP[\s\S]*link: \/getting-started\/mcp\//); + assert.equal((homepage.match(/icon: external/g) ?? []).length, 3); assert.match(homepage, /title="CLI"[\s\S]*href="\/getting-started\/install\/"/); assert.match(homepage, /title="Local MCP server"[\s\S]*href="\/getting-started\/mcp\/"/); }); diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index 1e561dee..128a30f1 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -7,11 +7,11 @@ hero: actions: - text: Start in 30 seconds link: /getting-started/ - icon: right-arrow + icon: external variant: primary - text: Install the CLI link: /getting-started/install/ - icon: right-arrow + icon: external variant: secondary - text: Connect through MCP link: /getting-started/mcp/ From e952838672afc4618125114510bb7058862fa8d8 Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:37:26 +0800 Subject: [PATCH 12/16] fix(docs): simplify page footer navigation --- docs/astro.config.mjs | 3 --- docs/scripts/theme-contract.test.mjs | 3 +++ docs/src/styles/custom.css | 11 +++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 9cea5813..6b8bac3e 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -25,9 +25,6 @@ export default defineConfig({ href: 'https://github.com/anycap-ai/anycap', }, ], - editLink: { - baseUrl: 'https://github.com/anycap-ai/anycap/edit/main/docs/', - }, lastUpdated: true, disable404Route: true, customCss: ['./src/styles/custom.css'], diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index 45e25d34..78afe337 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -68,6 +68,9 @@ test('uses rendered icons and styled documentation footer controls', () => { assert.match(homepage, //); assert.match(stylesheet, /footer\.sl-flex/); assert.match(stylesheet, /\.pagination-links/); + assert.doesNotMatch(docsConfig, /editLink:/); + assert.match(stylesheet, /footer\.sl-flex \.pagination-links a \{[\s\S]*border: 0;/); + assert.match(stylesheet, /footer\.sl-flex \.pagination-links a \{[\s\S]*border-radius: 0;/); }); test('routes model discovery to the live catalog instead of duplicating availability claims', () => { diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css index acbcf8fe..950b44a4 100644 --- a/docs/src/styles/custom.css +++ b/docs/src/styles/custom.css @@ -309,17 +309,16 @@ } footer.sl-flex .pagination-links a { - border-color: var(--ac-border); - border-radius: 0.75rem; + border: 0; + border-radius: 0; box-shadow: none; color: var(--ac-muted); - padding: 0.8rem 1rem; - transition: border-color 160ms ease, background 160ms ease, color 160ms ease; + padding: 0.4rem 0; + transition: color 160ms ease; } footer.sl-flex .pagination-links a:hover { - background: color-mix(in srgb, var(--ac-accent) 8%, var(--ac-raised)); - border-color: var(--ac-accent); + background: transparent; color: var(--ac-ink); } From 6e169b93a60ae9f4fcfad08b31f6407cb8a957ba Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:39:22 +0800 Subject: [PATCH 13/16] fix(docs): share main site favicon mark --- docs/public/favicon.svg | 16 ++++++++++++---- docs/scripts/theme-contract.test.mjs | 8 ++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg index 6c0ce84a..e9254967 100644 --- a/docs/public/favicon.svg +++ b/docs/public/favicon.svg @@ -1,5 +1,13 @@ - - - - + + + + + + + + + + + + diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index 78afe337..510f3d67 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -6,6 +6,7 @@ const stylesheet = await readFile(new URL('../src/styles/custom.css', import.met const assetPreparation = await readFile(new URL('./prepare-assets.mjs', import.meta.url), 'utf8'); const homepage = await readFile(new URL('../src/content/docs/index.mdx', import.meta.url), 'utf8'); const logo = await readFile(new URL('../src/assets/anycap-logo.svg', import.meta.url), 'utf8'); +const favicon = await readFile(new URL('../public/favicon.svg', import.meta.url), 'utf8'); const docsConfig = await readFile(new URL('../astro.config.mjs', import.meta.url), 'utf8'); const modelsPage = await readFile(new URL('../src/content/docs/capabilities/models.mdx', import.meta.url), 'utf8').catch( () => '', @@ -54,6 +55,13 @@ test('uses the main-site brand mark without duplicating it in the homepage hero' assert.match(logo, /any<\/tspan>cap<\/tspan>/); }); +test('uses the main-site nested-square mark for the browser favicon', () => { + assert.match(favicon, /viewBox="5 5 190 190"/); + assert.match(favicon, //); + assert.match(favicon, /rotate\(-5 100 100\)/); + assert.match(favicon, /rect x="49" y="49" width="102" height="102" rx="3" fill="#6b7d3a"/); +}); + test('uses the same off-white page canvas as the main site', () => { assert.match(stylesheet, /:root\[data-theme='light'\][\s\S]*--ac-page: #f4f5f0/); assert.match(stylesheet, /--sl-color-bg: var\(--ac-page\)/); From 261394d793b9ea631ec424a3ac4f2641060e2000 Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:45:59 +0800 Subject: [PATCH 14/16] docs: split audio and music capabilities --- docs/astro.config.mjs | 3 +- docs/scripts/audit-build.mjs | 2 +- docs/scripts/theme-contract.test.mjs | 26 +++++++++++ docs/src/content/docs/capabilities/audio.md | 41 +++++++----------- docs/src/content/docs/capabilities/index.mdx | 2 +- docs/src/content/docs/capabilities/models.mdx | 14 +++++- docs/src/content/docs/capabilities/music.mdx | 43 +++++++++++++++++++ docs/src/content/docs/capabilities/video.md | 17 ++++++-- 8 files changed, 115 insertions(+), 33 deletions(-) create mode 100644 docs/src/content/docs/capabilities/music.mdx diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 6b8bac3e..cf4a21e1 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -74,9 +74,10 @@ export default defineConfig({ { label: 'Models', link: '/capabilities/models/' }, { label: 'Image', link: '/capabilities/image/' }, { label: 'Video', link: '/capabilities/video/' }, + { label: 'Audio', link: '/capabilities/audio/' }, + { label: 'Music', link: '/capabilities/music/' }, { label: 'Media understanding', link: '/capabilities/media-understanding/' }, { label: 'Web search and crawl', link: '/capabilities/web/' }, - { label: 'Music and audio', link: '/capabilities/audio/' }, { label: 'Drive and Page', link: '/capabilities/delivery/' }, { label: 'Annotate and Draw', link: '/capabilities/collaboration/' }, { label: 'Snapshots', link: '/capabilities/snapshots/' }, diff --git a/docs/scripts/audit-build.mjs b/docs/scripts/audit-build.mjs index 02a77453..e50e4f22 100644 --- a/docs/scripts/audit-build.mjs +++ b/docs/scripts/audit-build.mjs @@ -3,7 +3,7 @@ import { extname, join, relative, resolve } from 'node:path'; const dist = resolve('dist'); const canonicalOrigin = 'https://docs.anyware.ai'; -const expectedIndexablePages = 29; +const expectedIndexablePages = 30; const errors = []; async function walk(directory) { diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index 510f3d67..95b313be 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -8,6 +8,10 @@ const homepage = await readFile(new URL('../src/content/docs/index.mdx', import. const logo = await readFile(new URL('../src/assets/anycap-logo.svg', import.meta.url), 'utf8'); const favicon = await readFile(new URL('../public/favicon.svg', import.meta.url), 'utf8'); const docsConfig = await readFile(new URL('../astro.config.mjs', import.meta.url), 'utf8'); +const capabilitiesPage = await readFile(new URL('../src/content/docs/capabilities/index.mdx', import.meta.url), 'utf8'); +const audioPage = await readFile(new URL('../src/content/docs/capabilities/audio.md', import.meta.url), 'utf8'); +const musicPage = await readFile(new URL('../src/content/docs/capabilities/music.mdx', import.meta.url), 'utf8').catch(() => ''); +const videoPage = await readFile(new URL('../src/content/docs/capabilities/video.md', import.meta.url), 'utf8'); const modelsPage = await readFile(new URL('../src/content/docs/capabilities/models.mdx', import.meta.url), 'utf8').catch( () => '', ); @@ -89,6 +93,28 @@ test('routes model discovery to the live catalog instead of duplicating availabi assert.match(modelsPage, /anycap music models/); assert.match(modelsPage, /anycap audio models/); assert.match(modelsPage, /not duplicated in this documentation site/); + assert.match(modelsPage, /List every currently advertised mode/); +}); + +test('makes audio and model-specific modes discoverable from capabilities', () => { + assert.match( + docsConfig, + /\{ label: 'Video', link: '\/capabilities\/video\/' \},[\s\S]*\{ label: 'Audio', link: '\/capabilities\/audio\/' \},[\s\S]*\{ label: 'Music', link: '\/capabilities\/music\/' \}/, + ); + assert.match(capabilitiesPage, /\[audio\]\(\/capabilities\/audio\/#audio-generation\)/); + assert.match(capabilitiesPage, /\[music\]\(\/capabilities\/music\/\)/); + assert.match(audioPage, /## Audio generation/); + assert.match(audioPage, /`text-to-audio`/); + assert.match(audioPage, /`audio-to-audio`/); + assert.match(audioPage, /`image-to-audio`/); + assert.doesNotMatch(audioPage, /anycap music models/); + assert.match(musicPage, /title: Music Generation/); + assert.match(musicPage, /`text-to-music`/); + assert.match(musicPage, /anycap music models/); + assert.match(videoPage, /`edit-video`/); + assert.match(videoPage, /`first-last-frame-to-video`/); + assert.match(videoPage, /`multi-shot-video`/); + assert.match(videoPage, /`multi-modal-reference`/); }); test('shows direct CLI and MCP integration paths in the homepage hero', () => { diff --git a/docs/src/content/docs/capabilities/audio.md b/docs/src/content/docs/capabilities/audio.md index 6aae4da6..3b24a48d 100644 --- a/docs/src/content/docs/capabilities/audio.md +++ b/docs/src/content/docs/capabilities/audio.md @@ -1,32 +1,24 @@ --- -title: Music and Audio Generation -description: Generate music, speech, dialogue, and complete audio scenes through live AnyCap model schemas. +title: Audio Generation +description: Generate audio scenes, speech, and dialogue through live AnyCap model schemas. --- -## Generate music +## Audio generation -```bash -anycap music models -anycap music models schema --mode text-to-music -anycap music generate \ - --prompt "warm lo-fi study beat" \ - --model \ - --tags "lofi,warm,study" \ - --instrumental \ - -o study-beat.mp3 -``` +Audio is a generation capability distinct from music. The selected model may expose one or more of these mode values; inspect the live catalog before choosing one: -Use `--title`, `--lyrics`, and schema-advertised parameters when the selected model supports them. A request can return multiple clips under `outputs`. +| Mode | Use it for | +| --- | --- | +| `text-to-audio` | Create an audio scene, speech, or dialogue from a prompt | +| `audio-to-audio` | Guide a new result with supported audio references | +| `image-to-audio` | Guide an audio scene with a supported image reference | ```bash -anycap music generate --prompt "..." --model \ - | jq -r '.outputs[].local_path' +# Print the modes advertised by every audio model right now. +anycap audio models \ + | jq -r '.models[] | .model as $model | .operations[]?.modes[]?.mode | [$model, .] | @tsv' ``` -## Generate audio scenes - -Audio models may support text, audio, or image input: - ```bash anycap audio models anycap audio models schema --mode text-to-audio @@ -37,13 +29,10 @@ anycap audio generate \ -o welcome.mp3 ``` -Other schema-discovered modes can include: - -- `audio-to-audio` for reference-guided performance or scene generation -- `image-to-audio` for a scene guided by an image - -These are generation workflows, not claims of a general-purpose audio editor. Confirm formats, sample rates, reference limits, subtitles, and voice controls against the live schema. +These are generation workflows, not claims of a general-purpose audio editor. Confirm formats, sample rates, reference limits, subtitles, and voice controls against the selected live schema. ## Output metadata Audio outputs can include `local_path`, `mime_type`, duration, byte size, subtitle data, usage, and diagnostic IDs. If one local download fails after provider generation succeeds, that output reports its own `error` instead of hiding the successful provider result. + +For composed tracks, see [Music generation](/capabilities/music/). diff --git a/docs/src/content/docs/capabilities/index.mdx b/docs/src/content/docs/capabilities/index.mdx index e3423efb..fcd98c28 100644 --- a/docs/src/content/docs/capabilities/index.mdx +++ b/docs/src/content/docs/capabilities/index.mdx @@ -9,7 +9,7 @@ AnyCap groups capabilities around four jobs: create something, understand someth - [Images](/capabilities/image/), [video](/capabilities/video/), [music and audio](/capabilities/audio/). + [Images](/capabilities/image/), [video](/capabilities/video/), [audio](/capabilities/audio/#audio-generation), and [music](/capabilities/music/). Analyze images, video, and audio without managing a separate vision or transcription service. diff --git a/docs/src/content/docs/capabilities/models.mdx b/docs/src/content/docs/capabilities/models.mdx index 486639be..3c228159 100644 --- a/docs/src/content/docs/capabilities/models.mdx +++ b/docs/src/content/docs/capabilities/models.mdx @@ -31,6 +31,18 @@ anycap video models \ | jq -r '.models[] | [.model, (.display_name // .name), ([.operations[]?.modes[]?.mode] | unique | join(", "))] | @tsv' ``` +## List every currently advertised mode + +Run this whenever you need the complete mode surface rather than an example from a capability guide. It reads the live catalog, so it includes newly added modes and omits retired ones: + +```bash +for capability in image video music audio; do + anycap "$capability" models \ + | jq -r --arg capability "$capability" \ + '.models[] | .model as $model | .operations[]?.modes[]?.mode | [$capability, $model, .] | @tsv' +done +``` + Then inspect the schema for the one model and mode you selected. The schema is the authority for accepted parameters such as aspect ratio, duration, resolution, or reference inputs. ```bash @@ -41,4 +53,4 @@ anycap video models schema --mode image-to-video Do not copy a model ID or parameter set from an older tutorial without checking the live catalog. A model can be added, retired, or change modes without a documentation-site release. -See [Generation and editing](/capabilities/image/), [Video generation](/capabilities/video/), and [Music and audio generation](/capabilities/audio/) for capability-specific workflows. +See [Generation and editing](/capabilities/image/), [Video generation](/capabilities/video/), [Audio generation](/capabilities/audio/), and [Music generation](/capabilities/music/) for capability-specific workflows. diff --git a/docs/src/content/docs/capabilities/music.mdx b/docs/src/content/docs/capabilities/music.mdx new file mode 100644 index 00000000..73ebb150 --- /dev/null +++ b/docs/src/content/docs/capabilities/music.mdx @@ -0,0 +1,43 @@ +--- +title: Music Generation +description: Generate instrumental tracks, songs, and music clips through live AnyCap model schemas. +--- + +## Modes + +| Mode | Use it for | +| --- | --- | +| `text-to-music` | Create an instrumental track or song from a prompt | + +The selected model may expose a different subset of controls. Check the live catalog before relying on a prompt field or optional parameter. + +```bash +anycap music models \ + | jq -r '.models[] | .model as $model | .operations[]?.modes[]?.mode | [$model, .] | @tsv' +``` + +## Generate music + +```bash +anycap music models +anycap music models schema --mode text-to-music +anycap music generate \ + --prompt "warm lo-fi study beat" \ + --model \ + --tags "lofi,warm,study" \ + --instrumental \ + -o study-beat.mp3 +``` + +Use `--title`, `--lyrics`, and schema-advertised parameters only when the selected model supports them. A request can return multiple clips under `outputs`. + +```bash +anycap music generate --prompt "..." --model \ + | jq -r '.outputs[].local_path' +``` + +## Output metadata + +Music generation can return one or more output clips, along with duration, byte size, usage, and diagnostic IDs. Inspect every `outputs[]` item rather than assuming a single file. + +For speech, dialogue, and audio scenes, see [Audio generation](/capabilities/audio/). diff --git a/docs/src/content/docs/capabilities/video.md b/docs/src/content/docs/capabilities/video.md index a303e278..1f6444f2 100644 --- a/docs/src/content/docs/capabilities/video.md +++ b/docs/src/content/docs/capabilities/video.md @@ -5,10 +5,21 @@ description: Generate video from text or animate a local image after discovering ## Modes -| Mode | Input | +| Mode | Use it for | | --- | --- | -| `text-to-video` | Prompt | -| `image-to-video` | Prompt plus one or more supported image references | +| `text-to-video` | Create a video from a prompt | +| `image-to-video` | Animate one or more supported image references | +| `edit-video` | Refine a supplied video using natural-language instructions | +| `first-last-frame-to-video` | Generate a transition between named first and last frames | +| `multi-shot-video` | Generate a sequence from shot-level prompts and durations | +| `multi-modal-reference` | Guide a video with supported image, video, or audio references | + +A selected model supports only a subset of these modes. Query the live catalog instead of assuming a mode from this reference: + +```bash +anycap video models \ + | jq -r '.models[] | .model as $model | .operations[]?.modes[]?.mode | [$model, .] | @tsv' +``` ## Generate from text From fcfd07141fc9af252b4634ce20782cdaf6f760b1 Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:48:11 +0800 Subject: [PATCH 15/16] fix(docs): unify sidebar canvas --- docs/scripts/theme-contract.test.mjs | 1 + docs/src/styles/custom.css | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index 95b313be..2ced5d79 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -74,6 +74,7 @@ test('uses the same off-white page canvas as the main site', () => { assert.match(stylesheet, /url\("\/assets\/grain\.png"\)/); assert.match(stylesheet, /opacity: 0\.07/); assert.match(assetPreparation, /grain\.png/); + assert.match(stylesheet, /\.sidebar-pane,\s*\.right-sidebar-container[\s\S]*background-color: var\(--ac-page\)/); }); test('uses rendered icons and styled documentation footer controls', () => { diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css index 950b44a4..e2cac86c 100644 --- a/docs/src/styles/custom.css +++ b/docs/src/styles/custom.css @@ -91,7 +91,7 @@ --sl-color-gray-5: var(--ac-border); --sl-color-gray-6: var(--ac-surface); --sl-color-gray-7: var(--ac-surface); - --sl-color-black: var(--ac-raised); + --sl-color-black: var(--ac-page); --sl-color-bg: var(--ac-page); --sl-color-bg-nav: color-mix(in srgb, var(--ac-page) 92%, transparent); --sl-color-bg-sidebar: var(--ac-surface); @@ -162,6 +162,11 @@ height: auto; } + .sidebar-pane, + .right-sidebar-container { + background-color: var(--ac-page); + } + .sidebar-pane { border-inline-end-color: var(--ac-border); } From 9c94a1e9ae63cc88ba0da725cd11428ad51cda61 Mon Sep 17 00:00:00 2001 From: LoTingYi Date: Wed, 22 Jul 2026 11:59:40 +0800 Subject: [PATCH 16/16] feat(docs): show live model catalog before generation --- docs/scripts/theme-contract.test.mjs | 24 +++- docs/src/components/LiveModelCatalog.astro | 89 ++++++++++++++ .../docs/capabilities/{audio.md => audio.mdx} | 4 + docs/src/content/docs/capabilities/music.mdx | 4 + .../docs/capabilities/{video.md => video.mdx} | 4 + docs/src/styles/custom.css | 114 ++++++++++++++++++ docs/worker.mjs | 25 ++++ docs/wrangler.jsonc | 2 + 8 files changed, 264 insertions(+), 2 deletions(-) create mode 100644 docs/src/components/LiveModelCatalog.astro rename docs/src/content/docs/capabilities/{audio.md => audio.mdx} (93%) rename docs/src/content/docs/capabilities/{video.md => video.mdx} (94%) create mode 100644 docs/worker.mjs diff --git a/docs/scripts/theme-contract.test.mjs b/docs/scripts/theme-contract.test.mjs index 2ced5d79..a61ad620 100644 --- a/docs/scripts/theme-contract.test.mjs +++ b/docs/scripts/theme-contract.test.mjs @@ -9,12 +9,16 @@ const logo = await readFile(new URL('../src/assets/anycap-logo.svg', import.meta const favicon = await readFile(new URL('../public/favicon.svg', import.meta.url), 'utf8'); const docsConfig = await readFile(new URL('../astro.config.mjs', import.meta.url), 'utf8'); const capabilitiesPage = await readFile(new URL('../src/content/docs/capabilities/index.mdx', import.meta.url), 'utf8'); -const audioPage = await readFile(new URL('../src/content/docs/capabilities/audio.md', import.meta.url), 'utf8'); +const audioPage = await readFile(new URL('../src/content/docs/capabilities/audio.mdx', import.meta.url), 'utf8'); const musicPage = await readFile(new URL('../src/content/docs/capabilities/music.mdx', import.meta.url), 'utf8').catch(() => ''); -const videoPage = await readFile(new URL('../src/content/docs/capabilities/video.md', import.meta.url), 'utf8'); +const videoPage = await readFile(new URL('../src/content/docs/capabilities/video.mdx', import.meta.url), 'utf8'); const modelsPage = await readFile(new URL('../src/content/docs/capabilities/models.mdx', import.meta.url), 'utf8').catch( () => '', ); +const liveCatalogComponent = await readFile(new URL('../src/components/LiveModelCatalog.astro', import.meta.url), 'utf8').catch( + () => '', +); +const docsWorker = await readFile(new URL('../worker.mjs', import.meta.url), 'utf8').catch(() => ''); test('uses the main-site typography roles', () => { assert.match(stylesheet, /--sl-font:\s*Inter,/); @@ -84,6 +88,7 @@ test('uses rendered icons and styled documentation footer controls', () => { assert.doesNotMatch(docsConfig, /editLink:/); assert.match(stylesheet, /footer\.sl-flex \.pagination-links a \{[\s\S]*border: 0;/); assert.match(stylesheet, /footer\.sl-flex \.pagination-links a \{[\s\S]*border-radius: 0;/); + assert.match(stylesheet, /\[data-has-hero\] footer\.sl-flex \.pagination-links[\s\S]*display: none;/); }); test('routes model discovery to the live catalog instead of duplicating availability claims', () => { @@ -118,6 +123,21 @@ test('makes audio and model-specific modes discoverable from capabilities', () = assert.match(videoPage, /`multi-modal-reference`/); }); +test('shows live supported models and conversion CTAs before generation workflows', () => { + for (const [page, capability] of [ + [videoPage, 'video'], + [audioPage, 'audio'], + [musicPage, 'music'], + ]) { + assert.match(page, new RegExp(` { assert.match(homepage, /text: Start in 30 seconds[\s\S]*link: \/getting-started\//); assert.match(homepage, /text: Install the CLI[\s\S]*link: \/getting-started\/install\//); diff --git a/docs/src/components/LiveModelCatalog.astro b/docs/src/components/LiveModelCatalog.astro new file mode 100644 index 00000000..4e8f560f --- /dev/null +++ b/docs/src/components/LiveModelCatalog.astro @@ -0,0 +1,89 @@ +--- +type CatalogCapability = 'image' | 'video' | 'music' | 'audio'; + +interface Props { + capability: CatalogCapability; +} + +const { capability } = Astro.props; + +const capabilityLabels: Record = { + image: 'image', + video: 'video', + music: 'music', + audio: 'audio', +}; + +const label = capabilityLabels[capability]; +--- + +
    +
    +
    +

    Live catalog

    +

    Supported {label} models

    +

    Choose a model before you generate. Availability and modes are loaded from the current AnyCap catalog.

    +
    + +
    +

    Loading live models…

    +
      + + + diff --git a/docs/src/content/docs/capabilities/audio.md b/docs/src/content/docs/capabilities/audio.mdx similarity index 93% rename from docs/src/content/docs/capabilities/audio.md rename to docs/src/content/docs/capabilities/audio.mdx index 3b24a48d..eff356bb 100644 --- a/docs/src/content/docs/capabilities/audio.md +++ b/docs/src/content/docs/capabilities/audio.mdx @@ -3,6 +3,10 @@ title: Audio Generation description: Generate audio scenes, speech, and dialogue through live AnyCap model schemas. --- +import LiveModelCatalog from '../../../components/LiveModelCatalog.astro'; + + + ## Audio generation Audio is a generation capability distinct from music. The selected model may expose one or more of these mode values; inspect the live catalog before choosing one: diff --git a/docs/src/content/docs/capabilities/music.mdx b/docs/src/content/docs/capabilities/music.mdx index 73ebb150..e96154b6 100644 --- a/docs/src/content/docs/capabilities/music.mdx +++ b/docs/src/content/docs/capabilities/music.mdx @@ -3,6 +3,10 @@ title: Music Generation description: Generate instrumental tracks, songs, and music clips through live AnyCap model schemas. --- +import LiveModelCatalog from '../../../components/LiveModelCatalog.astro'; + + + ## Modes | Mode | Use it for | diff --git a/docs/src/content/docs/capabilities/video.md b/docs/src/content/docs/capabilities/video.mdx similarity index 94% rename from docs/src/content/docs/capabilities/video.md rename to docs/src/content/docs/capabilities/video.mdx index 1f6444f2..72702b12 100644 --- a/docs/src/content/docs/capabilities/video.md +++ b/docs/src/content/docs/capabilities/video.mdx @@ -3,6 +3,10 @@ title: Video Generation description: Generate video from text or animate a local image after discovering the current AnyCap model schema. --- +import LiveModelCatalog from '../../../components/LiveModelCatalog.astro'; + + + ## Modes | Mode | Use it for | diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css index e2cac86c..5aefc145 100644 --- a/docs/src/styles/custom.css +++ b/docs/src/styles/custom.css @@ -263,6 +263,112 @@ color: var(--ac-ink); } + .live-model-catalog { + border-top: 1px solid var(--ac-border); + margin-block: 2.75rem; + padding-top: 1.65rem; + } + + .live-model-catalog-header { + align-items: flex-start; + display: flex; + flex-wrap: wrap; + gap: 1.25rem 2rem; + justify-content: space-between; + } + + .live-model-catalog h2 { + border: 0; + margin: 0; + padding: 0; + } + + .live-model-catalog p { + color: var(--ac-muted); + margin-block: 0.6rem 0; + max-width: 42rem; + } + + .live-model-catalog-eyebrow { + color: var(--ac-accent) !important; + font-family: var(--sl-font-mono); + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.06em; + margin: 0 0 0.35rem !important; + text-transform: uppercase; + } + + .live-model-catalog-actions { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + } + + .model-catalog-cta { + align-items: center; + border: 1px solid var(--ac-border); + border-radius: 999px; + display: inline-flex; + font-size: 0.88rem; + font-weight: 650; + min-height: 2.5rem; + padding-inline: 1rem; + text-decoration: none; + } + + .model-catalog-cta.primary { + background: var(--ac-accent); + border-color: var(--ac-accent); + color: #f4f5f0; + } + + .model-catalog-cta.secondary { + color: var(--ac-ink); + } + + .model-catalog-cta:hover { + background: var(--ac-ink); + border-color: var(--ac-ink); + color: var(--ac-page); + } + + .live-model-catalog-status { + font-size: 0.86rem; + } + + .live-model-list { + display: grid; + gap: 0.65rem; + grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); + list-style: none; + margin: 1rem 0 0; + padding: 0; + } + + .live-model-list li { + background: color-mix(in srgb, var(--ac-raised) 65%, transparent); + border: 1px solid var(--ac-border); + border-radius: 0.45rem; + display: grid; + gap: 0.35rem; + min-height: 4.75rem; + padding: 0.75rem; + } + + .live-model-list strong { + color: var(--ac-ink); + font-size: 0.9rem; + line-height: 1.25; + } + + .live-model-list span { + color: var(--ac-muted); + font-family: var(--sl-font-mono); + font-size: 0.72rem; + line-height: 1.35; + } + table { display: table; width: 100%; @@ -334,6 +440,10 @@ letter-spacing: -0.02em; } + [data-has-hero] footer.sl-flex .pagination-links { + display: none; + } + @media (max-width: 50rem) { [data-has-hero] .hero { padding-block: 2.5rem; @@ -343,6 +453,10 @@ display: block; overflow-x: auto; } + + .live-model-catalog-actions { + width: 100%; + } } @media (prefers-reduced-motion: reduce) { diff --git a/docs/worker.mjs b/docs/worker.mjs new file mode 100644 index 00000000..f4764ead --- /dev/null +++ b/docs/worker.mjs @@ -0,0 +1,25 @@ +const catalogCapabilities = new Set(['image', 'video', 'music', 'audio']); +const catalogPath = /^\/api\/model-catalog\/([a-z-]+)\/?$/; + +export default { + async fetch(request, env) { + const url = new URL(request.url); + const match = catalogPath.exec(url.pathname); + + if (request.method === 'GET' && match && catalogCapabilities.has(match[1])) { + const capability = match[1]; + const upstream = await fetch(`https://api.anycap.ai/v1/${capability}/models`, { + headers: { Accept: 'application/json' }, + }); + const headers = new Headers(upstream.headers); + + headers.set('Cache-Control', 'public, max-age=300, s-maxage=300, stale-while-revalidate=600'); + headers.set('Content-Type', 'application/json; charset=utf-8'); + headers.delete('Set-Cookie'); + + return new Response(upstream.body, { status: upstream.status, headers }); + } + + return env.ASSETS.fetch(request); + }, +}; diff --git a/docs/wrangler.jsonc b/docs/wrangler.jsonc index 67a3bafb..2d510644 100644 --- a/docs/wrangler.jsonc +++ b/docs/wrangler.jsonc @@ -2,7 +2,9 @@ "$schema": "node_modules/wrangler/config-schema.json", "name": "anycap-docs", "compatibility_date": "2026-07-21", + "main": "./worker.mjs", "assets": { + "binding": "ASSETS", "directory": "./dist", "html_handling": "auto-trailing-slash", "not_found_handling": "404-page"