diff --git a/.gitignore b/.gitignore index 656e2e629..3ee8c4417 100644 --- a/.gitignore +++ b/.gitignore @@ -30,8 +30,15 @@ www/**/.yarn/* .eslintcache .idea +.vscode .turbo .yarn build/** **/dist **/stats + +# ZCP local-mode runtime state — written by `zcp init` / `zcp serve` +.zcp/ + +# Generated full-text knowledge artifact (built via `zerops-llm-script.ts` prebuild) +knowledge/ diff --git a/apps/docs/content/features/coding-agents.mdx b/apps/docs/content/features/coding-agents.mdx new file mode 100644 index 000000000..6e8efb8ae --- /dev/null +++ b/apps/docs/content/features/coding-agents.mdx @@ -0,0 +1,219 @@ +--- +title: 'Infrastructure for Coding Agents' +description: 'Real project infrastructure for coding agents — runtimes, managed services, private networking, deploys, logs, and verification, exposed through MCP.' +--- + +import Icons from '@theme/Icon'; +import CodingAgentsTopology from '@site/src/components/CodingAgentsTopology'; +import IntroAgentVisual from '@site/src/components/IntroAgentVisual'; + +Zerops was built on the idea of **environment parity** — giving developers the full development lifecycle, from remote development to highly available production, with the observability and developer tools for maximum flexibility, and sensible defaults so the configs stay reasonable. Turns out that's **exactly what coding agents need** to produce and iterate on production-ready applications. + +
+ +## What is it + +An **[MCP server](/zcp/overview)** for your agents — with an optional remote cloud development environment container to run them in — that makes use of the flexibility and processes Zerops provides. There's no "Zerops system prompt" hogging your context window. The MCP server is a thin layer that makes **your agent a Zerops platform power user** — when needed — so it understands: + +- how networking, scaling, debugging, build & deployment, environment variables, and service provisioning work on the platform +- the development loop: start a dev server → implement → deploy to stage → verify + +The depth of the platform underneath is what gives the agent room to actually work. A single agent can scaffold and iterate on projects with multiple runtimes (`app`, `api`, `worker`) backed by multiple [managed services](/features/infrastructure) (object storage `storage`, Postgres `db`, Elasticsearch `search`, NATS `broker`, Valkey `cache`) — just as easily as it can work on a simple Next.js presentational website. + +A **[recipe ecosystem](https://app.zerops.io/recipes)** covering the most popular frameworks — pre-prepared for the whole lifecycle — gives the agent a baseline to start from, or a guide for setting up any stack on Zerops. + + + + + +Most coding-agent platforms went agent-first, then bolted on infrastructure — where it runs, how it reaches the database, how its output becomes production-ready, which third-party services to wire up. **Zerops went platform-first.** The complete development lifecycle existed before coding agents did; we taught them to operate it — **not just how to call APIs, but how to ship software the way a senior developer would.** + + + +

+ +## Main features + +### Your agent, your subscription + +Bring whichever agent you already use. Claude Code today; Codex, Gemini CLI, opencode, and any MCP-capable client next. The container is a regular Ubuntu — install your own CLI tools, drop in your `.claude` / `.cursor` configs, attach your IDE over SSH. **Zerops doesn't resell tokens or proxy your model calls** — sign in with your own Anthropic / OpenAI / Google subscription and the agent talks to its provider directly. + +### An ordinary Zerops project underneath + +The agent operates inside a normal Zerops project — same shape as production. Managed databases (PostgreSQL, MariaDB, ClickHouse), key-value stores (KeyDB, Valkey), search (Elasticsearch, Meilisearch, Typesense), vector store (Qdrant), message queues (NATS, Kafka), object and shared storage, managed Nginx — all on a private network, addressable by hostname. The same pipeline that deploys here can deploy to a separate HA production project with no `zcp` service attached. Not a sandbox the work outgrows. + +### Human ↔ agent handover + +The agent and the human share the same workspace — same filesystem, same SSH key, same Cloud IDE in the browser, same remote-dev attach from your local IDE. Take over mid-session, set something up before the agent runs, debug in the same container the agent just left. Nothing reaches production until you merge the PR — the agent opens it, you gate it. + +### Recipes for any stack + +Runtimes for Bun, Deno, Node.js, Go, Python, Rust, Java, .NET, PHP, Elixir, Gleam — with curated recipes for the framework on top: Next.js, Nuxt, Astro, Svelte, React, Vue, Angular, Solid, Qwik, Analog, NestJS, Laravel, and more. Pick a recipe, select the **AI Agent** environment, and the project comes up with a working dev runtime, a stage runtime, the right managed services wired in, and a coding agent briefed on the Zerops surface. + +

+ +## How it works in practice + +The agent reaches your project's private network one of two ways: + +- **Remote** — a `zcp` service deployed into the project runs the agent. You attach to it with Claude Code's IDE extension, with any IDE that supports remote development (Cursor, Windsurf, VS Code Remote), or by running [`zcli vpn up`](/references/networking/vpn) and ssh-ing into `zcp` to drive the rest of the project from a shell. +- **Local** — install the `zcp` MCP on your machine and run `zcli vpn up` to join the network. From there your IDE, agent, and shell can ssh directly into any container. + +Either way, the agent reaches [managed services by hostname](/references/networking/internal-access) (`db:5432`, `cache:6379`), deploys through the [Zerops pipeline](/features/pipeline), and reads logs and events the same way you would. + + + +The agent runs through two workflows. **Bootstrap** settles which services and runtime targets the app needs. **Develop** is the short work loop — edit code on `appdev`, deploy to `appstage`, check it's reachable, verify behavior against `db:5432` and `cache:6379`, fix from evidence. A session ends with a URL you can open, or a specific blocker — failure category, attempts, what's still needed. Same hostnames, same managed services, same pipeline as production — just at smaller scale. See [Workflows in depth](/zcp/reference/agent-workflow) for both. + +{` `}{`zcp (local or remote)`}{` + │ + ▼ + edit code on `}{`appdev`}{` ◀────────────────────────┐ + └ dev server, hot reload │ + │ │ + ▼ │ + deploy to `}{`appstage`}{` │ + │ │ + ▼ │ + check reachability │ + │ │ + ▼ │ + verify behavior ─── fail ─────┐ │ + │ │ │ + │ pass ▼ │ + ▼ read logs / events │ + URL (proof) fix from evidence ─────┘`} + +Once the loop produces verified work, the agent doesn't push to production. It hands off — through whichever path your team uses: a tag push, a protected-branch merge, a manual `zcli` release, a pull request, or a CI job. **A human gates the release**, and the production deploy runs into a [separate production project](/zcp/security/production-policy) that has no `zcp` service at all. + +{` Dev project GitHub Prod project + +┌─────────────────────┐ ┌──────────────┐ ┌─────────────────────┐ +│ appdev │ │ │ │ app (HA) │ +│ appstage │ │ Pull │ │ │ +│ │ push │ Request │ CI/CD │ db (HA) │ +│ db, cache │ ────▶ │ │ ────▶ │ │ +│ │ │ `}{`+ human`}{` │ │ cache (HA) │ +│ `}{`zcp service`}{` │ │ `}{`review`}{` │ │ │ +│ │ │ │ │ (no zcp service) │ +└─────────────────────┘ └──────────────┘ └─────────────────────┘`} + +

+ +## Where does this fit in the agent landscape + +With the model clear, here's where ZCP sits among adjacent tools. People say "agent infrastructure," "infra for Claude agents," "agent hosting," or "agent sandboxes" to mean different things — usually one of the categories below. + +
+
+

PaaS MCPs

+
+ AWS + Railway + Fly.io + Render +
+

AWS MCP servers · Railway MCP · Fly.io MCP · Render MCP

+
+

Most major PaaS providers have shipped an MCP — provision services, deploy code, list resources, read operational state. Great for "let my agent ship to my Railway account." These are *deploy and operate* surfaces, driven from outside the platform — and they reflect what's underneath: platforms optimized for day-one deploy that tuck the infrastructure behind a wall. Once an agent needs to actually *develop* against that infrastructure — debug a failed health check, follow real logs, hit Postgres from inside the network, inspect what a build is doing, run the dev loop before it ships anything — the wall stops it. That's by design on those platforms, not a missing feature.

+ +

ZCP exposes the full **Bootstrap and Develop** loop as MCP-addressable primitives because Zerops was built around the development lifecycle from the start — not a dev loop retrofitted onto a deploy-first platform.

+
+ +
+
+

Cloud VPS MCPs

+
+ Hetzner + DigitalOcean + Linode + Vultr +
+

Hetzner Cloud MCP · DigitalOcean MCP · Linode MCP · raw SSH/server MCPs

+
+

"Let your agent manage your Hetzner VPS." An MCP gives the agent access to a Linux box — provision it, SSH in, install Docker and Postgres and nginx, set up systemd units, point a domain at it. Maximum control, lowest cost, full Linux to play with. The same reason senior developers reach for managed platforms applies here: even if you *can* harden SSH, write nginx configs, tune `pg_hba.conf`, rotate TLS, set firewall rules, run fail2ban, and stay on top of CVEs across kernel, OpenSSL, Docker, Postgres, and nginx — you usually don't want to. Each layer is its own domain with its own gotchas, each is a security surface that drifts the moment you stop tending it, and every project becomes a snowflake. Hand that work to an agent and you also hand it the blast radius: misconfigured firewall, debug endpoint left open, port 5432 exposed to the internet, keys never rotated. ZCP is the inverse trade-off — the **guardrails** of a managed platform. Private networking, TLS, isolated services, safe defaults, managed backups, no exposed ports unless you opt in — all built in. The agent operates through [project-scoped MCP tools](/zcp/reference/mcp-operations) instead of `sudo` on a fresh Ubuntu box, so the mistakes that matter on a VPS aren't reachable from where it sits.

+
+ +
+
+

Cloud dev environments with AI

+
+ GitHub Codespaces + Gitpod + Coder +
+

GitHub Codespaces + Copilot Workspace · Gitpod · Coder

+
+

A managed Linux container in the cloud with your editor and an AI assistant attached. Solves "where the agent runs" so state survives between sessions and machine setup stops being a question. The `zcp` service is the closest thing in this category — managed Linux container, Cloud IDE, your agent of choice — except it lives *inside* a Zerops project, addressing the project's runtimes, database, and cache by hostname, with deploys and logs as primitives the agent can call.

+
+ +
+
+

Bundled agent platforms

+
+ Replit Agent + Lovable + Bolt.new + v0 + Devin + OpenHands +
+

Replit Agent · Lovable · Bolt.new · v0 · Devin · OpenHands

+
+

The agent, the workspace, the stack, and (often) the hosting target are fused into one closed product. Fast for prototypes. Decisions are made for you. Switching agents usually means switching platforms, and production-grade primitives — a real dev/stage/prod release flow, private networking, observability — tend to be limited or absent. **ZCP doesn't bundle**: the agent and model stay yours on your own subscription, and the platform underneath is normal Zerops with the full stack of managed services, networking, deploys, and observability.

+
+ +
+
+

Local coding agents

+
+ Cursor + Claude Code + Windsurf + Zed + Aider + Cline +
+

Cursor · Windsurf · Zed · Claude Code · Codex CLI · Aider · Cline

+
+

The agent runs on your machine, edits your local checkout, runs local commands. Excellent at code changes. Anything beyond your machine — databases, networking, deploy targets, runtime logs — is yours to wire in. Install the ZCP MCP locally and run `zcli vpn up`, and any of these agents can now reach a real Zerops project alongside your local code: services addressed by hostname, deploys, logs, events.

+
+ +
+
+

Code-execution sandboxes

+
+ E2B + Modal + Daytona + CodeSandbox SDK +
+

E2B · Modal · Daytona · CodeSandbox SDK

+
+

Ephemeral Linux environments that AI *applications* spin up — often per request — to execute generated code safely. The right primitive when you're building a product that needs a model to run untrusted code and hand back the output. They're a building block for AI applications, not somewhere a coding agent settles in to iterate on your project. Different shape from ZCP; they show up here because the names cluster nearby.

+
+ +
+
+

Agent SDKs and frameworks

+
+ Cloudflare Agents + Vercel AI SDK + Mastra + Anthropic Agent SDK +
+

Cloudflare Agents · Vercel AI SDK · Mastra · Anthropic Agent SDK

+
+

Libraries for building and shipping agents to *your* users — tool calling, memory, orchestration, model providers, sometimes hosting. They're how somebody would build a product like ZCP, not where a coding agent runs against a project. Same disambiguation — adjacent on the shelf, different shape.

+
+ +

+ +## Where to start + + diff --git a/apps/docs/content/features/local-remote-development.mdx b/apps/docs/content/features/local-remote-development.mdx new file mode 100644 index 000000000..af7cc8269 --- /dev/null +++ b/apps/docs/content/features/local-remote-development.mdx @@ -0,0 +1,138 @@ +--- +title: Local & Remote Development +description: Three ways to develop on Zerops — local with VPN, Cloud IDE, or your native IDE over SSH. Same network, same managed services, same pipeline as production. +--- + +import Icons from '@theme/Icon'; + +Zerops development is network-first. Keep your editor on your laptop, work fully inside Zerops, or use a native editor over SSH — in each case you join the same project-private network with the same hostnames, credentials, managed services, and deploy pipeline that staging and production use. What changes between environments is policy: credentials, resources, access rules, data, and release authority — not the platform underneath. + +## How development on Zerops works + +Every project ships with a private network. Services reach each other by hostname (`db:5432`, `api:3000`, `redis:6379`), with credentials injected as environment variables. + +The development question is how you get onto that network. Three paths give you the same project-private reach, with different filesystem, tooling, and credential boundaries. + +- **Local + VPN.** Your laptop joins the private network via WireGuard. Editor, toolchain, and processes stay where they are. A hosted workspace is not required. +- **Cloud IDE.** A Linux workspace in the project, accessed through VS Code Server in the dashboard. Zero local setup. +- **Native IDE over SSH.** SSH from your local VS Code, JetBrains Gateway, Cursor, or plain `ssh` into a workspace or directly into a runtime service container. + +Switch paths without reprovisioning the project. A teammate on Local + VPN and another in the Cloud IDE are hitting the same `db:5432`. + +{` Laptop + editor Browser tab Laptop + native IDE + │ │ │ + `}{`zcli vpn up`}{` `}{`VS Code Server`}{` `}{`zcli vpn up`}{` + (WireGuard) on the project + `}{`ssh apidev`}{` + │ │ │ + └──────────────────────┴────────────────────────┘ + │ + ▼ + ┌─────────── project private network ───────────┐ + │ │ + │ `}{`api:3000`}{` `}{`db:5432`}{` `}{`cache:6379`}{` │ + │ │ + └───────────────────────────────────────────────┘`} + +## Local + VPN + +**Best for:** keeping your existing editor, dotfiles, and toolchain — and offloading just the dependencies that are painful to run locally. + +```bash +zcli vpn up + +psql "postgresql://app:secret@db:5432/myapp" +redis-cli -h cache -p 6379 +ssh apidev +``` + +Your laptop can reach project services by hostname: Postgres, Valkey, S3-compatible storage, runtime containers, and any other service in the project. You can keep your normal dev loop and point it at `db:5432` instead of a local mock or Docker Compose dependency. + +You do not need a hosted workspace, `zcli push`, or a new editor for this path. + +**Source control.** Use git on your laptop the way you always have. Commits, pushes, SSH agent against GitHub, your existing credentials — nothing about Zerops sits between you and the remote. + +:::info Complete VPN setup +See the [VPN reference guide](/references/networking/vpn). +::: + +## Cloud IDE in Zerops + +**Best for:** zero local setup, working from a machine that isn't yours, or wanting a pristine environment that doesn't touch your laptop. + +A hosted workspace is a normal Zerops service in the project. It gives you a project-contained Linux environment with private-network access, optional Browser VS Code, `zcli`, git tooling, database CLIs, and shell utilities. + +The workspace can also mount runtime service files, so you can inspect or edit code that runs in another container without moving it to your laptop: + +```bash +ls /var/www/apidev +ls /var/www/frontenddev +vim /var/www/apidev/src/server.ts +``` + +The dev server in the runtime container hot-reloads, the database is the same `db:5432`, and staging deploys still go through the Zerops deploy pipeline. + +The Cloud IDE itself is configurable. Two access methods: + +- **VPN Only.** The IDE is reachable through `zcli vpn up`, no public exposure. Most secure. +- **Public Access.** The IDE is reachable on a `.zerops.app` subdomain, gated by an auto-generated password. + +**Source control.** The hosted workspace isn't on your laptop, so your local SSH keys don't follow it. Two patterns work: + +- **GitHub via `gh`.** The GitHub CLI is preinstalled. Run `gh auth login` once and `gh` handles HTTPS auth, PR creation, and review flows from the container. +- **Token in env vars.** Save a fine-grained personal access token to the workspace service's secret environment variables; `git` reads it through a credential helper. + +Either pattern keeps your token in the workspace service, not in your project's source. + +## Native IDE over SSH + +**Best for:** native editor UX without carrying the toolchain or databases on your laptop. + +Once `zcli vpn up` is connected, every container on the project's private network is reachable from your laptop over SSH. Any editor that speaks Remote SSH can use it: VS Code Remote SSH, JetBrains Gateway, Cursor, Zed, or plain `vim`. + +```bash +ssh apidev # any runtime service in the project +ssh frontenddev +``` + +In this path, a hosted workspace is convenient but not required. You can connect straight to `apidev`, edit the source there, and run the dev server in the same container. A hosted workspace is only a convenience layer when you want shared tools, runtime file mounts, or Cloud IDE. + +**Source control.** SSH agent forwarding works the same way it does to any other machine. Add your local SSH key to your agent (`ssh-add`), and `git push` from inside `apidev` or the workspace service uses your laptop's GitHub key over the forwarded agent socket — no token storage required. + +```ssh-config +# Local ~/.ssh/config +Host apidev.zerops + HostName apidev + ForwardAgent yes +``` + +:::info SSH access details +See the [SSH reference guide](/references/networking/ssh). +::: + +## Picking a path + +| | Local + VPN | Cloud IDE | Native IDE over SSH | +| --------------------- | ------------------ | -------------------------- | --------------------------------- | +| Editor runs | Local | Browser | Local | +| Toolchain | Local | Hosted workspace | Hosted workspace or service container | +| Network entry | WireGuard VPN | Browser into the project | WireGuard VPN, then SSH | +| Hosted workspace required | No | Yes | No (convenience) | +| Git auth | Local credentials | `gh` or token in env | SSH agent forwarding | +| Best for | Keeping your setup | Disposable environments | Native UX, lighter laptop | + +## Why this is not just a cloud IDE + +If you've used GitHub Codespaces or Gitpod, Cloud IDE looks similar: a Linux container in the cloud, accessed from a browser or local IDE. Two structural differences matter. + +**You don't have to be in the editor.** Codespaces and Gitpod are editor-first. Zerops is network-first. Cloud IDE is one way to reach the project's private network; `zcli vpn up` lets you skip the editor environment entirely and consume the project's managed services from your existing local setup. The hosted workspace itself is just another Zerops service — SSH in, install packages, run processes, configure it however you want. + +**Your dev environment runs the same platform as production.** On a CDE, your dev container is one platform and the deployment target is another — making "works on my machine" a fact of life. On Zerops, dev Postgres, queue, cache, or object storage is the same managed service type the app meets later in stage and prod, on the same private network, with the same `zerops.yaml`. Policy differs across environments; the platform underneath doesn't. + +## Where to start + + diff --git a/apps/docs/content/guides/local-development.mdx b/apps/docs/content/guides/local-development.mdx deleted file mode 100644 index d0f8d0292..000000000 --- a/apps/docs/content/guides/local-development.mdx +++ /dev/null @@ -1,131 +0,0 @@ ---- -title: "Local Development with Zerops" -description: "Develop locally with hot reload while connecting to Zerops managed services (DB, cache, storage) via VPN. ZCP generates `.env` with real credentials. Deploy to Zerops with `zerops_deploy` which uses `zcli push` under the hood." ---- - -Develop locally with hot reload while connecting to Zerops managed services (DB, cache, storage) via VPN. ZCP generates `.env` with real credentials. Deploy to Zerops with `zerops_deploy` which uses `zcli push` under the hood. - ---- - -## Setup - -### Prerequisites -- **zcli** installed: `npm i -g @zerops/zcli` or [docs.zerops.io/references/cli](https://docs.zerops.io/references/cli) -- **VPN**: WireGuard (installed by zcli automatically on first `zcli vpn up`) -- **Project-scoped token**: Create in Zerops GUI → Settings → Access Tokens → Custom access per project - -### Configuration -```json -// .mcp.json (in project root) -{ - "mcpServers": { - "zcp": { - "command": "zcp", - "env": { "ZCP_API_KEY": "" } - } - } -} -``` - ---- - -## Workflow - -### 1. Connect to Zerops services -```bash -zcli vpn up -``` -- All services accessible by hostname (e.g., `db`, `cache`) -- One project at a time — switching disconnects the current -- **Env vars NOT available via VPN** — use `.env` file instead - -### 2. Load credentials -ZCP generates `.env` from `zerops_discover`: -``` -db_host=db -db_port=5432 -db_password= -db_connectionString=postgresql://db:@db:5432/db -``` - -How to load: -| Runtime | Method | -|---------|--------| -| Node.js 20+ | `node --env-file .env app.js` | -| Next.js, Vite, Nuxt | Automatic (reads `.env`) | -| PHP/Laravel | Automatic (reads `.env`) | -| Python | `python-dotenv` or `django-environ` | -| Go | `godotenv.Load()` or `source .env && go run .` | -| Java/Spring | `spring-dotenv` or `application.properties` | - -### 3. Develop locally -Start your dev server as usual — hot reload works against Zerops managed services over VPN. - -### 4. Deploy to Zerops -``` -zerops_deploy targetService="appstage" -``` -Uses `zcli push` under the hood. Blocks until build completes. - ---- - -## zerops.yml for Local Mode - -The same `zerops.yml` works for both local push and container deploy: - -```yaml -zerops: - - setup: appstage - build: - base: nodejs@22 - buildCommands: - - npm ci - - npm run build - deployFiles: ./dist - run: - start: node dist/server.js - ports: - - port: 3000 - httpSupport: true - envVariables: - DB_URL: ${db_connectionString} -``` - -`${hostname_varName}` references are resolved by Zerops at container runtime — they work regardless of push source (local or container). - ---- - -## Connection Troubleshooting - -| Symptom | Diagnosis | Fix | -|---------|-----------|-----| -| `nc -zv db 5432` times out | VPN not connected | `zcli vpn up ` | -| VPN connected, still timeout | Wrong project | `zcli vpn up ` | -| Connected but auth fails | Stale .env | Regenerate from `zerops_discover includeEnvs=true` | -| Service unreachable | Service stopped | `zerops_manage action="start" serviceHostname="db"` | - -### Diagnostic sequence -1. `zerops_discover service="db"` — is service RUNNING? -2. `nc -zv db 5432 -w 3` — network reachable? -3. Compare `.env` vs `zerops_discover includeEnvs=true` — credentials current? - ---- - -## Multi-Project - -Each project directory has its own `.mcp.json` + `.zcp/state/`. VPN is one per machine — switch manually: -```bash -zcli vpn up # work on project A -zcli vpn up # auto-disconnects A, connects B -``` - ---- - -## Gotchas - -1. **VPN = network only**: Env vars must come from `.env` file, not VPN connection -2. **`.env` contains secrets**: Add to `.gitignore` immediately — never commit -3. **Deploy = new container**: Local files on Zerops are lost on every deploy. Only `deployFiles` content persists -4. **One VPN project at a time**: Connecting to project B disconnects project A -5. **Object storage (S3)**: Uses HTTPS apiUrl — may work without VPN but not fully verified. Include VPN as fallback -6. **zcli must be installed**: `zerops_deploy` requires zcli in PATH. Error message includes install link if missing diff --git a/apps/docs/content/zcp/concept/how-it-works.mdx b/apps/docs/content/zcp/concept/how-it-works.mdx new file mode 100644 index 000000000..c1124ff8d --- /dev/null +++ b/apps/docs/content/zcp/concept/how-it-works.mdx @@ -0,0 +1,175 @@ +--- +title: 'How it works' +description: 'The work loop: live state, runtime target, service setup, app work, verification, recovery, and delivery.' +--- + +The ZCP MCP setup gives a coding agent a **work loop** for Zerops app work. The agent reads live state, chooses the app runtime and dependencies, changes the app, deploys through Zerops, verifies real behavior, and returns proof or a blocker. + +The loop is carried by project-scoped Zerops operations, Zerops-specific knowledge, and instructions for what to inspect, what to change, when to ask, and what counts as done. You describe the product outcome; the agent uses the loop to make its decisions visible while it works. + +The source of truth is the real project: runtimes, managed services, env references, logs, events, and deploy results. A separate preview sandbox is not the source of truth. + +## The work loop + +```mermaid +flowchart TD + intent["Product intent
Build a task board for my team."] + state["Live state
services, runtime layout, env vars,
logs, events, saved work state"] + scope["Runtime target
which app service changes"] + setup{"Missing or unsuitable
services?"} + provision["Service setup
use existing services or create
missing runtimes/dependencies"] + appwork["App work
code, zerops.yaml, env refs,
migrations, seeds, framework config"] + deploy["Direct deploy through Zerops"] + reachability{"Runtime reachable?
status, logs, HTTP probe"} + behavior{"Requested behavior works?
endpoint, UI flow, worker result,
persisted state"} + evidence["Evidence
build logs, runtime logs,
events, verify output"] + proof["Proof
URL, endpoint result,
UI state, or stored result"] + delivery["Delivery after proof
keep direct deploy, push to git,
or hand off"] + blocker["Blocker
credential, decision,
unsupported fit,
repeated failure"] + + intent --> state --> scope --> setup + setup -->|yes| provision --> appwork + setup -->|no| appwork + appwork --> deploy --> reachability + reachability -->|no, fixable| evidence --> appwork + reachability -->|yes| behavior + behavior -->|no, fixable| evidence + behavior -->|yes| proof --> delivery + setup -->|needs decision| blocker + reachability -->|needs human| blocker + behavior -->|needs human| blocker + + classDef user fill:#f7fbff,stroke:#2d72d9,stroke-width:1.5px,color:#172033; + classDef zcpbox fill:#eef7f1,stroke:#32845a,stroke-width:1.5px,color:#172033; + classDef work fill:#fffaf0,stroke:#c47f17,stroke-width:1.5px,color:#172033; + classDef done fill:#f5f3ff,stroke:#7157d9,stroke-width:1.5px,color:#172033; + classDef stop fill:#fff1f2,stroke:#d33f49,stroke-width:1.5px,color:#172033; + + class intent user; + class state,scope zcpbox; + class setup,provision,appwork,deploy,reachability,behavior,evidence work; + class proof,delivery done; + class blocker stop; +``` + +The loop keeps the agent working from evidence instead of a guessed checklist: current state, Zerops wiring rules, deploy evidence, and the distinction between proof and blocker. + +## What "live state" means + +The MCP tools read Zerops instead of asking you to paste a service inventory into the prompt. Useful state includes: + +- runtime services and managed services, +- whether the app has one runtime, a dev+stage pair, or local files linked to a Zerops runtime, +- env-var keys and Zerops references, +- recent build/deploy events, +- build logs, runtime logs, and verification output, +- saved work state after an interrupted session. + +This is why a short prompt can be enough. The agent can ask what exists, which runtime was last deployed, which checks passed, and where a previous run stopped. + +Chat history is not the source of truth. If the agent sounds confused, starts from an old assumption, or a session was interrupted, the recovery move is: + +```text +Read current project status and tell me where this project stands before changing anything. +``` + +## What the workflow handles + +The workflow handles the things an agent must resolve during app work so you do not have to name them in the prompt. The tools and instructions supply the state, guidance, operations, and verification surface so the agent can work from evidence. + +| What the workflow handles | What the agent gets | What that means for you | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| Live state | Services, env-var keys, Zerops references, recent events, logs, verification output, and saved work state. | You do not have to paste a service inventory or reconstruct what happened. | +| Runtime target | Rules for choosing the app runtime that should receive code changes. | The agent can identify where app work belongs before editing or deploying. | +| Managed services | Knowledge and env wiring patterns for database, cache, queue, search, storage, mail, and similar services. | Product intent can imply real dependencies without a manual wiring checklist. | +| Service setup | Operations for using existing services or creating missing runtimes and dependencies. | Runtime layout and dependencies can be established before app work starts. | +| App wiring | Zerops-specific rules for `zerops.yaml`, env references, ports, commands, public access, and build/deploy behavior. | The app is wired for Zerops rather than for a generic cloud template. | +| Deploy evidence | Build logs, runtime logs, platform events, service status, HTTP checks, and structured verification output. | A failed deploy becomes a diagnosis surface instead of guesswork. | +| Behavior proof | The proof gate is the requested behavior, not only a successful build or reachable root URL. | The final answer can point to what was actually checked. | +| Delivery handoff | Direct proof first; then git push, CI, or human handoff when that is the chosen delivery choice. | Shipping setup follows a verified running result. | + +The exact labels for layouts, delivery modes, and setup routes live in the [Glossary](/zcp/glossary) and [Workflows in depth](/zcp/reference/agent-workflow). + +## Service setup prepares the project layout + +Before app code work starts, the workflow makes three decisions visible: + +- Which runtime service is the app target? +- Which managed services are dependencies? +- Does the existing project layout fit the request? + +If the services already exist, the agent can use them. If a needed runtime or dependency is missing, the tools can create it. If the choice affects cost, product scope, credentials, a destructive action, or which stage/runtime should be used, the agent should stop and ask. + +Service setup is finished when the app runtime and dependencies are known. It is the layout that lets app work happen in the right place. + +## App work changes code and platform wiring together + +After the runtime target is known, the agent gets the platform knowledge needed to make the application change. In practice this often spans both source code and Zerops wiring: + +- app files, +- `zerops.yaml` build and run setup, +- env references to managed services, +- migrations, seeds, and framework config, +- start commands, ports, and public HTTP support, +- local `.env` generation when using local setup. + +That guidance matters because Zerops is its own platform. Service references, build/deploy behavior, public access, scaling, and env resolution do not follow Docker Compose or Kubernetes conventions. + +The first functional deploy goes directly through MCP so the agent has a running result to verify. A repository push or CI handoff can follow, but it should not replace the first proof. + +## Recovery is evidence-driven + +When something fails, the workflow puts the agent on the matching evidence surface — build logs for build failures, prepare/runtime logs for start failures, verify output and request-time logs for behavior failures, transport surfaces for network failures, field-level rejection for config, and the named credential surface for credential failures. The full categories, what to read first, and what to avoid live in [Troubleshooting](/zcp/reference/troubleshooting). + +Retrying the same deploy without new evidence is not progress. The loop pushes toward one of three outcomes: fix from a cause, ask for the missing decision, or report a blocker with the category, evidence read, and attempts made. + +## Verification has two layers + +A successful deploy proves that Zerops accepted the build and started the runtime. Reachability checks prove the service is running and reachable. They still do not prove the product request. + +For a task-board app, useful behavior proof might be: + +- create a task, +- move it between columns, +- refresh the page, +- confirm the task is still there. + +For an API task, proof might be a JSON response from the requested endpoint and stored data behind it. For a worker task, proof might be a processed job and the resulting database or object-storage state. For a staging request, proof belongs on the stage runtime, not only on dev. + +The final answer should make proof inspectable: runtime name, URL or endpoint, behavior checked, and delivery choice if one was set. + +## Delivery happens after proof + +Delivery choice controls how future changes ship after a verified runtime exists: keep direct deploy, push to git, or hand off to CI/release/human action. The user-facing version of this choice and what to tell the agent for each option lives in [Build and ship → Choose delivery after proof](/zcp/workflows/build-with-zcp#choose-delivery-after-proof). + +Packaging a running service turns a deployed runtime into a re-importable bundle for another Zerops project. It is useful for handoff or reuse after proof, not for deploying the next app change; see [Package a running service](/zcp/workflows/package-running-service). + +## Remote and local setup use the same loop + +The same `zcp` binary can run in two places. The control loop stays the same; filesystem and network access change. A human can take over from the same evidence either way: files, runtime target, logs, events, verification result, and delivery state. + +| Setup | What runs where | Practical effect | +| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Remote setup | A `zcp@1` service runs the `zcp` binary inside Zerops. **Include Coding Agent** adds the bundled agent CLI; **Cloud IDE** adds Browser VS Code. | Work happens inside the remote workspace, with private networking and runtime file mounts. | +| Local setup | The `zcp` binary runs on your laptop after `zcp init`, and your local editor or CLI agent talks to it. | App files, deploy source, and git credentials stay local. Managed services are reached over Zerops VPN, and `.env` generation bridges credentials into your local app. | + +Choose setup by where the agent and filesystem should live: [Remote or local setup](/zcp/setup/choose-workspace). + +## Signs of a healthy run + +A well-shaped run should: + +- name the runtime target before editing or deploying, +- use existing services when they fit, +- create missing services only before app work starts, +- read logs, events, and verify output when failure occurs, +- distinguish runtime reachability from requested behavior, +- stop before destructive actions, ambiguous runtime/stage choices, or missing credentials, +- end with proof or a blocker. + +That is the practical difference between "the agent wrote code" and "the app task is done". + +## Next steps + +- [Build and ship](/zcp/workflows/build-with-zcp) — normal app work after setup. +- [Workflows in depth](/zcp/reference/agent-workflow) — process gates, generated files, runtime layouts, delivery labels, and completion evidence. diff --git a/apps/docs/content/zcp/glossary.mdx b/apps/docs/content/zcp/glossary.mdx new file mode 100644 index 000000000..b9012bcdc --- /dev/null +++ b/apps/docs/content/zcp/glossary.mdx @@ -0,0 +1,134 @@ +--- +title: 'Glossary' +description: 'Short definitions for ZCP MCP, remote setup, local setup, workflow, delivery, production, and credential terms.' +--- + +Use these definitions when a page, workflow status, agent handoff, or policy needs exact wording. In normal prompts, describe the outcome you want. + +## Core names + +**ZCP MCP** - Zerops Control Plane MCP: the MCP tool surface that exposes project-scoped Zerops operations to coding agents. + +**MCP server** - the Model Context Protocol server exposed by the `zcp` binary. + +**ZCP MCP tools** - the project-scoped Zerops operations exposed to an agent or MCP-capable client. In MCP clients, this usually appears as the `zerops` server. + +**`zcp` binary** - the executable that can run inside remote setup or on your machine in local setup. + +**`zcp` service** - the service instance in a Zerops project that hosts remote setup. + +**`zcp@1` service** - the Zerops service type used for remote setup. + +**zCLI** - the Zerops command-line client for humans, scripts, VPN, and CI. It is separate from ZCP MCP. + +**zsc** - the in-container Zerops Setup Control utility used from `zerops.yaml`. + +## Setup and workspace + +**Remote setup** - the `zcp` binary running inside a Zerops `zcp@1` service. + +**Include Coding Agent** - remote setup option that adds the bundled agent CLI, currently Claude Code, and preconfigures it to use ZCP MCP tools. + +**Cloud IDE** - browser-based VS Code served by remote setup. + +**Browser VS Code** - dashboard entry point into the Cloud IDE. + +**AI Agent environment** - recipe environment preset that creates app services plus remote setup with **Include Coding Agent** enabled. + +**Local setup** - the `zcp` binary running on your machine after `zcp init`, while your local editor or CLI agent talks to it. + +**Env bridge** - local setup behavior that writes a local `.env` snapshot from Zerops env vars and references so local app code can reach managed services over VPN. + +**Agent client** - the editor, CLI, hosted agent runtime, or custom MCP client that connects to ZCP MCP. + +## Generated files and state + +**Generated workflow block** - the managed section in `CLAUDE.md` between `` and ``. Durable project instructions belong outside it. + +**`.mcp.json`** - local MCP server config. It points the local agent client at `zcp serve` and stores `ZCP_API_KEY`; keep it out of git. + +**`.zcp/state/`** - local workflow metadata for a project directory: known runtimes, pairing, delivery choice, sessions, deploy attempts, verify attempts, and coordination locks. It is not source code. + +**Workflow state** - saved metadata that lets the agent resume, audit, or close a guided run after interruption. + +## Workflow + +**Bootstrap** - workflow phase that reads the current project and settles which runtime services and managed services the app should use before app code work starts. + +**Service setup** - the phase that decides which runtime services and managed services the app should use before app code work starts. + +**Develop** - workflow phase that changes app code/config, deploys, verifies reachability and behavior, and fixes failures from evidence. + +**Runtime target** - the app runtime selected for the current change, such as `appdev`, `appstage`, `app`, or a linked local target. + +**Runtime layout** - which app runtime services the workflow should use: + +- `standard` - dev runtime plus explicit stage runtime. +- `dev` - one mutable development runtime. +- `simple` - one runtime with no dev/stage split. +- `local-stage` - local source directory linked to one Zerops runtime as deploy target. +- `local-only` - local source directory with no linked runtime yet. + +**Managed service** - database, cache, queue, search, storage, mail, or similar dependency. It provides connection details; it is not an app deploy target. + +**Direct deploy** - deploy from the current source to the scoped runtime through ZCP MCP. The first verified running result uses direct deploy before delivery setup is applied. + +**Reachability verification** - checks that the runtime exists, is running, has no recent blocking errors, and can answer an HTTP probe when it is an HTTP service. + +**Behavior verification** - checks that the requested app behavior works on the real URL, endpoint, worker result, or stored state. + +**Proof** - user-inspectable completion evidence, such as a URL, endpoint result, UI state, processed job, or stored result. + +**Blocker** - a clear stop state with evidence: failure category, runtime in scope, what was tried, and what decision or credential is needed. + +## Delivery and production + +**Delivery choice** - what happens after a verified deploy: keep direct deploy, push to git, or hand off to CI/release/human process. + +**Delivery mode** - exact reference label for delivery choice: + +- `auto` - keep direct deploy for future changes. +- `git-push` - commit and push to a configured remote, then observe/verify any tracked build. +- `manual` - external CI, release process, or a human owns future delivery. + +**Git-push capability** - whether remote setup has enough remote URL and credential setup to push from remote setup. It can exist even when the current delivery mode is `auto`. + +**Build integration** - repository-triggered build/deploy path that ZCP MCP may configure or observe, such as a Zerops dashboard webhook or GitHub Actions. It is separate from git-push capability and delivery mode. + +**Package a running service** - workflow that turns one verified runtime and its managed dependencies into a re-importable, git-backed Zerops bundle. + +**Production release** - the release operation that moves verified dev or stage work into a separate production Zerops project. It is set up once per project (production infrastructure) and once per runtime (production deploy trigger), then runs every release. + +**Production boundary** - policy that production should live in a separate Zerops project without a `zcp` service and receive promoted work through CI, release process, or human action. + +## Platform and failure terms + +**Service scaling mode** - Zerops scaling setting such as `HA` or `NON_HA`. Different from runtime layout. + +**Public subdomain access** - Zerops `.zerops.app` URL access for an eligible HTTP runtime. Workers and non-HTTP services do not get useful public URLs. + +**Failure category** - label that points to the first useful evidence surface: + +- `build` - build phase failed. +- `start` - build passed, but runtime start or prepare failed. +- `verify` - runtime exists, but reachability or behavior failed. +- `network` - transport, DNS, VPN, SSH, or service-to-service reach failed. +- `config` - `zerops.yaml`, env vars, setup block, or service settings mismatch. +- `credential` - Zerops, git, SSH, managed-service, or external API credential failed. +- `other` - no known category matched. + +**Confirmation gate** - an operation that pauses until the user explicitly confirms the named target or consequence. + +**Destructive import override** - import action that would replace an existing service stack. ZCP MCP refuses first, names affected services, and requires a matching acknowledgement before proceeding. + +## Credentials + +**Single-project token** - Zerops token that can access exactly one project. ZCP MCP expects this shape. + +**`ZCP_API_KEY`** - single-project Zerops token used by ZCP MCP. + +**`GIT_TOKEN`** - git provider credential used by remote git-push delivery when needed. + +**`ZEROPS_TOKEN`** - Zerops token commonly used by GitHub Actions or external CI that runs `zcli`. + +**External secret** - third-party credential such as Stripe, OpenAI, Mailgun, or GitHub API access. The agent can wire placeholders and env vars, but the secret value remains your responsibility. diff --git a/apps/docs/content/zcp/overview.mdx b/apps/docs/content/zcp/overview.mdx new file mode 100644 index 000000000..fbaf5ebe0 --- /dev/null +++ b/apps/docs/content/zcp/overview.mdx @@ -0,0 +1,152 @@ +--- +title: 'ZCP MCP for Zerops projects' +description: 'How the ZCP MCP server lets a coding agent work with one real Zerops project.' +--- + +import Image from '/src/components/Image'; + +:::info Public preview +ZCP MCP is a public preview and is under active development. If you hit a bug, confusing behavior, or a missing workflow, please report it on Discord; feedback from real projects is especially useful. +::: + +This section covers, in order: + +
+ +
+ +GET STARTED + +

Quickstart and concept

+ +[Quickstart](/zcp/quickstart) for hands-on; [How it works](/zcp/concept/how-it-works) for the work loop. + +
+ +
+ +SETUP + +

Remote or local workspace

+ +[Remote or local setup](/zcp/setup/choose-workspace), [Trust model](/zcp/security/trust-model), and [Production boundary](/zcp/security/production-policy). + +
+ +
+ +WORKFLOWS + +

Build, package, promote

+ +[Build and ship](/zcp/workflows/build-with-zcp), [Package a running service](/zcp/workflows/package-running-service), [Promote to production](/zcp/workflows/promote-to-production). + +
+ +
+ +REFERENCE + +

Troubleshooting and lookup

+ +[Workflows in depth](/zcp/reference/agent-workflow), [ZCP MCP tools](/zcp/reference/mcp-operations), [Troubleshooting](/zcp/reference/troubleshooting), [Glossary](/zcp/glossary). + +
+ +
+ +For the broader feature concept — why coding agents need real project infrastructure rather than a sandbox or generated artifact — start with [Infrastructure for Coding Agents](/features/coding-agents). + +With the generated workflow instructions enabled, an app task ends in proof or a blocker. **Proof** is a deployed runtime plus the URL, endpoint response, UI state, worker result, or stored data that shows the requested behavior works. A **blocker** is the agent reading the relevant Zerops evidence and naming the missing credential, decision, unsupported fit, or repeated failure. + +Your prompt can stay about the product. Name the stack, runtime layout, acceptance criteria, delivery path, external credentials, or risky approval only when those decisions matter. + +## What the agent gets + +
+ +
+ +STATE + +### Current state + +Services, runtime layout, managed dependencies, env-var keys and references, logs, events, deploy history, verification state, and saved work state. + +
+ +
+ +CONTROLS + +### Zerops operations + +Project-scoped tools for discovering services, changing env vars, managing runtimes, deploying, verifying, scaling, public access, and delivery setup. + +
+ +
+ +INSTRUCTIONS + +### Workflow + +The generated instructions combine service setup and app development: inspect state, choose the runtime target, use or create services, wire code and `zerops.yaml`, deploy, verify, and choose delivery. The Zerops work stays behind the product task instead of becoming another checklist. + +
+ +
+ +EVIDENCE + +### Evidence-based completion + +A build or deploy is not the finish line. A completed app task should end with a working URL, endpoint result, UI proof, or a blocker backed by logs, events, and verification evidence. + +
+ +
+ +## What you no longer have to script + +Without this layer, an app prompt often turns into an operations runbook. With MCP tools and workflow instructions enabled, you should not need to paste: + +- the service map, runtime target, dev/stage state, or managed-service inventory, +- database credentials, private hostnames, env-var references, or generated connection strings, +- build logs, runtime logs, event timelines, or a guess about why the last deploy failed, +- a deploy/verify/recovery script for every task, +- a recap after the chat loses context; the agent can read current workflow status. + +**You still own the decisions that need human judgment:** product intent, technology constraints, acceptance criteria, external credentials, repository policy, and approval for destructive actions. + +## Where it runs + +The **same `zcp` binary** runs in both setups. In [remote setup](/zcp/setup/hosted-workspace), Zerops packages it as a `zcp@1` service with Browser VS Code and bundled agent wiring. In [local setup](/zcp/setup/local-agent-bridge), you install it on your machine and connect your own editor or CLI agent. The project surface is the same; the workspace, network access, deploy source, and safety profile differ. + +To start, add remote setup in Zerops or initialize local setup beside your editor or CLI agent. The [Quickstart](/zcp/quickstart) uses remote setup because it needs no local install. + +
+ Remote setup with the zcp service in Zerops and Claude Code running in browser VS Code +
+ +## Your agent, credentials, and workspace + +**Agent account.** Remote setup can include a bundled agent CLI, currently Claude Code, already configured for MCP. Zerops wires the agent to the tools; you still authenticate with your own subscription login or API credentials. + +**Zerops token.** The MCP server connects through `ZCP_API_KEY`, a Zerops token limited to one project. Remote setup gets it from the platform; local setup reads it from `.mcp.json`. Token details live in [Tokens and credentials](/zcp/security/tokens-and-project-access). + +**Workspace freedom.** The `zcp@1` service is still a normal Zerops service. You can install another agent CLI, add private MCP servers or helper tools, edit `CLAUDE.md`, add team dotfiles, and adapt the workspace. Details live in [What remote workspace gives you](/zcp/setup/hosted-workspace#make-customization-persistent). + +:::caution Production boundary +Use this setup for development or staging work. Production should stay in a separate Zerops project and receive released work through your CI or release process; see [Promote to production](/zcp/workflows/promote-to-production) for the practical flow and [Production boundary](/zcp/security/production-policy) for the policy. +::: + +## What stays outside + +This section is not a replacement for Zerops platform references. The Zerops [build and deploy pipeline](/features/pipeline), [permissions](/features/rbac), networking, scaling, and service references remain canonical for platform behavior. diff --git a/apps/docs/content/zcp/quickstart.mdx b/apps/docs/content/zcp/quickstart.mdx new file mode 100644 index 000000000..c41570832 --- /dev/null +++ b/apps/docs/content/zcp/quickstart.mdx @@ -0,0 +1,126 @@ +--- +title: 'Quickstart' +description: 'Try ZCP with a ready-made AI Agent recipe, Browser VS Code, Claude Code, and one product change.' +--- + +import Image from '/src/components/Image'; +import Icons from '@theme/Icon'; + +Use this when you want the fastest hands-on ZCP loop: deploy an **AI Agent** recipe, authorize Claude Code, open Browser VS Code, and ask for one product change. + +Some [Zerops recipes](https://app.zerops.io/recipes) include an **AI Agent** environment. That preset creates app services, managed services, the `zcp@1` workspace, Browser VS Code, and bundled agent wiring in one deploy. + +This quickstart uses [Laravel showcase](https://app.zerops.io/recipes/laravel-showcase?environment=ai-agent) because it includes real managed services. The same flow works for other recipes that offer AI Agent (local variants exist too). + +## Prerequisites + +- A Zerops account with permission to create a project. +- A Claude Code subscription login or API authentication. Zerops wires Claude Code to ZCP, but your agent subscription or model credentials stay yours. + +## 1. Choose the AI Agent recipe + +1. Open the [Zerops recipes catalog](https://app.zerops.io/recipes). +2. Open a recipe with an **AI Agent** environment. For example, open [Laravel showcase](https://app.zerops.io/recipes/laravel-showcase?environment=ai-agent). +3. Select **AI Agent**. +4. Keep **Coding Agent** and **Cloud IDE** enabled. +5. Deploy the recipe. + +
+ Laravel showcase recipe with the AI Agent environment selected and ZCP configuration enabled +
+ +The deploy creates app runtimes, managed dependencies, and a `zcp@1` workspace. In this recipe it appears as the `zcp` service. The agent, terminal, and browser IDE run there. App code still deploys to the app runtimes, not to `zcp`. + +## 2. Authorize Claude Code + +When provisioning finishes, the dashboard opens the Claude Code authentication flow. Use your own Claude Code subscription login or API credentials. + +This is separate from `ZCP_API_KEY`, the Zerops token used by ZCP. Your Claude login or API key is used only by the bundled agent. + +If you close the prompt, open the `zcp` service in the dashboard. Its panel shows the browser workspace, web terminal, SSH access, desktop editor access, and agent authorization state. + +
+ Claude Code authorization dialog shown over the ZCP service control plane and Browser VS Code terminal +
+ +## 3. Open the workspace + +After authentication, continue into **Browser VS Code**. The workspace opens with files, ZCP configuration, terminal access, and the Claude Code panel. + +
+ Browser VS Code workspace with the Claude Code panel open +
+ +You are now inside the remote workspace. The agent can read live state, use the MCP tools, reach private services, and deploy app changes to the runtimes created by the recipe. + +## 4. Ask for a product outcome + +In Claude Code, ask for the app behavior in natural language. A good first prompt is intentionally short: + +```text +Build a task board for my team. +Tasks should stay saved after refresh. +``` + +The agent should deploy, verify, read evidence, and return proof for the app task. Add detail only when it changes the work. + +Add details when they change the product, stack, runtime layout, acceptance criteria, or delivery preference: + +```text +In this Laravel app, add a task board backed by the existing PostgreSQL service. +A user can create a task, refresh the page, and still see it. +``` + +:::note Prompt shape + +Quickstart prompts are short so you can see what ZCP carries behind the request. Longer, detailed prompts are fine for larger engineering work, especially when they change product behavior, stack, runtime layout, acceptance criteria, delivery preference, credentials, or safety approvals. +::: + +## 5. Read the proof + +The final answer should give you a real URL, endpoint, UI state, stored result, or blocker. Open the URL and try the behavior the agent says it verified. + +For the task-board prompt, create a task, refresh the page, and confirm the task is still there. If it disappears, the app is not done; ask the agent to verify that exact behavior again and continue from current evidence rather than starting over. + +If the agent cannot finish, useful output names the blocker: missing credential, missing decision, unsupported runtime choice, or repeated failure it could not recover from. + +## Next steps + + diff --git a/apps/docs/content/zcp/reference/agent-workflow.mdx b/apps/docs/content/zcp/reference/agent-workflow.mdx new file mode 100644 index 000000000..e2ed76ebe --- /dev/null +++ b/apps/docs/content/zcp/reference/agent-workflow.mdx @@ -0,0 +1,173 @@ +--- +title: 'Workflows in depth' +description: 'The bootstrap and develop process behind generated-workflow ZCP MCP agent runs.' +--- + +Use this page when you want the process and exact labels behind a run that follows the generated workflow. Normal prompts should still describe outcomes. The workflow exists so the agent can read the project, prepare the right runtime and services, make the app change, deploy, verify, recover from evidence, and stop with proof or a concrete blocker. + +The useful mental split is: + +| Phase | What it settles | What should be true when it ends | +| ----- | --------------- | -------------------------------- | +| **Bootstrap** | Where the app should run and which services it depends on. | Runtime target and managed dependencies are known. | +| **Develop** | Code, `zerops.yaml`, env wiring, deploy, verification, recovery, and delivery choice. | The requested behavior is proved, or the blocker is concrete. | + +For the loop itself, see [How it works](/zcp/concept/how-it-works). This page catalogs the exact phases, routes, layouts, labels, and gates. + +## Session layers + +Most workflow mistakes come from confusing these layers: + +| Layer | What it is | What changes here | +| ----- | ---------- | ----------------- | +| **Workspace** | Where `zcp` and the agent run: remote `zcp@1` service or local machine. | Agent config, tools, local workflow state. App code should not be deployed to the workspace itself. | +| **Target runtime service** | The app runtime in scope: `appdev`, `appstage`, `app`, or a linked local deploy target. | App files, `zerops.yaml`, deploys, runtime logs, verification target. | +| **Managed services** | Databases, caches, queues, search, storage, mail, and similar dependencies. | Schema/data operations and credentials, never app code deploys. | + +The `zcp` service is the control surface, not the app runtime. + +## What drives the workflow + +The generated workflow is made from four pieces: + +| Piece | Role | +| ----- | ---- | +| **MCP tools** | Project-scoped Zerops operations: discover, deploy, read logs/events, manage env vars, verify, import/export, and related actions. | +| **Generated instructions** | Agent policy for when to inspect, when to ask, when to deploy, what evidence to read, and what counts as done. | +| **Saved workflow state** | Local metadata about bootstrap sessions, runtime pairing, delivery choice, deploy attempts, verify attempts, and interrupted work. | +| **Live Zerops project** | Source of truth for services, status, env refs, logs, events, deploys, runtime files, and public access. | + +The workflow does not replace judgment from the user or the agent. It gives the agent a process and evidence surface so app work does not depend on stale chat memory or a pasted runbook. + +## Bootstrap + +Bootstrap starts before app code changes when the workflow needs to understand or prepare the project layout. + +```mermaid +flowchart TD + start(["Read live project state"]) + interrupted{"Interrupted bootstrap
to resume?"} + runtime{"Runtime services
already exist?"} + known{"Request matches
known recipe or stack?"} + resume(["resume"]) + adopt(["adopt"]) + recipe(["recipe"]) + classic(["classic"]) + done(["Runtime target and dependencies known"]) + + start --> interrupted + interrupted -- yes --> resume --> done + interrupted -- no --> runtime + runtime -- yes --> adopt --> done + runtime -- no --> known + known -- yes --> recipe --> done + known -- no --> classic --> done +``` + +| Route | Use when | Wrong signal | +| ----- | -------- | ------------ | +| `adopt` | Runtime services already exist, including recipe-created projects. | Recreating services that already fit, or targeting the `zcp` service as the app. | +| `recipe` | The project is empty or only has remote setup, and the request matches a known stack recipe. | Treating an unchanged starter as the finished requested product. | +| `classic` | The project is empty and needs a custom service plan. | Writing app code before service ownership and runtime target are known. | +| `resume` | A previous bootstrap was interrupted. | Starting from scratch without checking live services and saved state. | + +Bootstrap ends when the app runtime target and managed dependencies are known. It should also make visible any choice that needs human judgment: cost, credentials, data, runtime layout, production risk, or destructive behavior. + +## Runtime layouts + +Runtime layout describes which app runtime services the workflow should use. Exact layout labels (`standard`, `dev`, `simple`, `local-stage`, `local-only`) and the runtime names they map to live in the [Glossary](/zcp/glossary#runtime-layout). The user-facing three (dev, dev + stage, stage / linked target) are in [Build and ship → Choose the runtime layout](/zcp/workflows/build-with-zcp#choose-the-runtime-layout). Service scaling mode (`HA`/`NON_HA`) is a separate service setting. + +In `standard`, stage is explicit. Work scoped to `appdev` does not silently touch `appstage`; a release or stage verification happens when the user asks for it. + +## Develop + +Develop is the main app-work loop. It begins after bootstrap has a runtime target and dependencies. It closes only when runtime reachability and requested behavior both pass, or when the agent has a blocker that needs a human decision. The loop itself is in [How it works → The work loop](/zcp/concept/how-it-works#the-work-loop). The labeled steps are: + +1. **Name runtime target.** State which runtime is in scope: `appdev`, `appstage`, `app`, or a linked local target. +2. **Change code and config.** Edit app files, `zerops.yaml`, env references, migrations, seeds, framework config, or local `.env` bridge when needed. +3. **Deploy directly first.** The first verified runtime deploy goes through MCP tools. Git or CI handoff comes after proof. +4. **Start or restart if needed.** Dynamic dev runtimes may need an explicit start or restart after deploy. Built-in webserver runtimes do not need a separate dev-server step unless the framework requires one. +5. **Verify runtime reachability.** Check service status, recent error logs, and HTTP readiness when the runtime is an HTTP service. +6. **Verify requested behavior.** Check endpoint body, UI state, job result, persisted data, or another result tied to the user request. +7. **Fix from evidence.** Read failure category, logs, events, and check output. Repeating the same deploy without new evidence is not progress. + +Reachability and requested behavior are separate gates. A green deploy with a broken route is not done. + +## Failure categories + +Failure categories (`build`, `start`, `verify`, `network`, `config`, `credential`, `other`) point the agent to the first useful evidence surface. The full read-first / avoid playbook is in [Troubleshooting → Evidence order](/zcp/reference/troubleshooting#evidence-order); the term definitions are in the [Glossary](/zcp/glossary#failure-category). + +Categorization is what turns retries into evidence-driven fixes. + +## Delivery after proof + +Delivery mode applies after a verified deploy. It does not replace the first proof. + +| Exact mode | User-facing choice | Meaning | +| ---------- | ------------------ | ------- | +| `auto` | Keep direct deploy | The agent keeps deploying future changes directly to the target runtime. | +| `git-push` | Push to git | The agent commits and pushes to a configured remote; any resulting build still needs observation and verification. | +| `manual` | External handoff | CI, release process, or a human owns future delivery; the workflow records evidence but does not initiate the next deploy. | + +Git-push capability, delivery mode, and build integration are separate. A project can have git-push configured while still using direct deploy for a given session. + +Packaging and production release are deliberate handoffs after proof. Packaging turns a verified runtime into a git-backed import bundle. A production release moves verified work into a separate production project through GUI setup, git/CI triggers, or the team's release process. + +## Generated files and state + +Remote setup and `zcp init` create configuration around the MCP server and workflow guidance. + +| File or directory | Created where | Purpose | +| ----------------- | ------------- | ------- | +| `CLAUDE.md` | Remote workspace or local project directory | Claude Code instruction surface. ZCP MCP writes a managed block between `` and ``. User content outside that block is preserved. | +| `.claude/settings.local.json` | Remote workspace or local project directory | Claude Code per-project settings and ZCP MCP tool permissions for that directory. | +| `.mcp.json` | Local project directory | Project-local MCP server config. It contains the local `zcp` command and the project-scoped `ZCP_API_KEY`; keep it out of git. | +| `~/.claude.json` and SSH config | Remote setup | Workspace-level Claude Code and SSH wiring used inside the Zerops-hosted workspace. | +| `.zcp/state/` | Working directory where the MCP server runs | Workflow state and service metadata for that project directory. | + +The managed `CLAUDE.md` block is refreshed by `zcp init` and by the MCP server when the block already exists. Put durable project instructions outside the ZCP markers. Edits inside the managed block are treated as generated content. + +`.zcp/state/` is not application source code and should not be committed. It stores metadata such as known runtime services, local/stage pairing, delivery preference, git-push setup, build integration, first-deploy stamps, workflow sessions, deploy attempts, verify attempts, and local coordination locks. + +ZCP MCP does not use `.zcp/state/` as a stale copy of the Zerops project. Service status, logs, events, runtime files, env-var values, and current platform configuration are read from Zerops or from the local filesystem when tools run. Local `.env` files are generated separately and may contain secrets. + +Do not edit `.zcp/state/` by hand during normal work. Use workflow operations to reset, resume, iterate, or reconfigure state. Deleting it intentionally discards local memory of services and delivery setup for that directory; the tools can rediscover live Zerops state, but delivery preferences and workflow history may need to be set again. + +## Completion evidence + +A completed app task should answer: + +- which bootstrap route was used when setup was needed, +- which runtime target changed, +- which managed services were used or created, +- which deploy passed, +- which reachability check passed, +- which requested behavior passed, +- which URL, endpoint, UI state, worker result, or stored value proves it, +- which delivery choice applies next, +- or which blocker remains and what evidence supports it. + +A clear blocker is acceptable completion only when it names the runtime in scope, failure category, evidence read, fixes tried, and human decision or credential still needed. + +## Confirmation gates + +Two operations pause because the loss is not safely reversible from inside the conversation: **service deletion** (named approval) and **destructive import override** (refuse-then-acknowledge). See [Tokens and credentials → What ZCP enforces for destructive actions](/zcp/security/tokens-and-project-access#what-zcp-enforces-for-destructive-actions) for the user-facing confirmation flow. + +## Auditing a workflow + +A workflow run is well-shaped if the evidence answers: + +| Question | Evidence | +| -------- | -------- | +| Where did bootstrap start? | Live service list, saved workflow state, and bootstrap route. | +| Where did bootstrap end? | Runtime target, managed dependencies, and any human decisions. | +| Which runtime was developed and deployed? | Runtime target, deploy result, events, and logs. | +| What proved reachability? | Verify output, service status, public URL, or HTTP probe. | +| What proved behavior? | Endpoint, UI flow, job result, database/object state, or other requested proof. | +| What controls future delivery? | Delivery mode, git-push state, build integration, package bundle, or production release handoff note. | + +## Related reference + +- [ZCP MCP tools](/zcp/reference/mcp-operations) — operation names and direct tool calls. +- [Troubleshooting](/zcp/reference/troubleshooting) — recovery order when a run gets stuck. +- [Glossary](/zcp/glossary) — exact terms used across the ZCP MCP reference. diff --git a/apps/docs/content/zcp/reference/index.mdx b/apps/docs/content/zcp/reference/index.mdx new file mode 100644 index 000000000..e11240461 --- /dev/null +++ b/apps/docs/content/zcp/reference/index.mdx @@ -0,0 +1,83 @@ +--- +title: "Reference" +description: "Process details, MCP tool names, recovery guidance, and glossary for ZCP MCP." +--- + +import Icons from '@theme/Icon'; + +Reference pages exist for the moments when an exact label, tool name, or recovery step matters. For day-to-day app work, start in [Build and ship](/zcp/workflows/build-with-zcp); for the loop itself, [How it works](/zcp/concept/how-it-works). + + + +## Common scenarios + +
+ +
+ +SCENARIO + +### Debugging a stuck deploy + +[Troubleshooting → Evidence order](/zcp/reference/troubleshooting#evidence-order) names what to read first; [Workflows in depth → Failure categories](/zcp/reference/agent-workflow#failure-categories) maps the labels. + +
+ +
+ +SCENARIO + +### Building a custom MCP client + +[ZCP MCP tools](/zcp/reference/mcp-operations) for the tool surface; [Workflows in depth → What drives the workflow](/zcp/reference/agent-workflow#what-drives-the-workflow) for what the generated workflow adds on top of bare tools. + +
+ +
+ +SCENARIO + +### Auditing a finished run + +[Workflows in depth → Completion evidence](/zcp/reference/agent-workflow#completion-evidence) for what should be in the final answer; [Trust model → Audit evidence](/zcp/security/trust-model#audit-evidence) for what Zerops records platform-side. + +
+ +
diff --git a/apps/docs/content/zcp/reference/mcp-operations.mdx b/apps/docs/content/zcp/reference/mcp-operations.mdx new file mode 100644 index 000000000..582b61ad5 --- /dev/null +++ b/apps/docs/content/zcp/reference/mcp-operations.mdx @@ -0,0 +1,103 @@ +--- +title: 'ZCP MCP tools' +description: 'Use ZCP MCP as a direct MCP tool surface: what the tools do, what they do not decide, and which operations exist.' +--- + +Most app work should be phrased as an outcome: build this, fix that, deploy and prove it. Operation names matter when you configure agent-client policy, debug an MCP integration, build a custom client, or use ZCP MCP pragmatically as a set of project-scoped Zerops tools. + +ZCP MCP is the MCP server exposed by the `zcp` binary. Any MCP-capable client can connect to it and call tools such as `zerops_discover`, `zerops_logs`, `zerops_events`, `zerops_deploy`, or `zerops_verify`. + +## Tool-only use + +The MCP server exposes operations. It does not, by itself, decide the whole app lifecycle. + +Claude Code with the generated workflow uses its instructions to decide sequencing: inspect state before changing things, choose the runtime target, use existing services or create missing ones, deploy, read failure evidence, verify requested behavior, and stop with proof or a blocker. + +Tool-only use is valid when you want a custom MCP client, script, dashboard, policy-gated agent, or narrow operational task. In that setup, your integration owns the sequencing: + +- which project services are in scope, +- when a deploy is allowed, +- what counts as verification, +- which logs or events should be read after failure, +- when to ask a human, +- when to stop. + +Tool-level gates still apply. Service deletion requires explicit named approval, and destructive import override requires an acknowledgement of the exact targets. + +If you use Claude Code but do not want the generated workflow guidance, keep the MCP connection and remove the ZCP-managed workflow block from `CLAUDE.md`, or keep your own policy outside the ZCP markers. What you lose is the generated instruction layer that makes the agent plan around live state, deploy with bounded retries, verify behavior, and report proof or a concrete blocker. + +Generated files and workflow state are documented in [Workflows in depth](/zcp/reference/agent-workflow#generated-files-and-state). + +## What the tools can do + +| Area | Tools | Typical use | +| ---- | ----- | ----------- | +| Discover | `zerops_discover` | Read services, service metadata, env-var keys, and project topology. | +| Observe | `zerops_logs`, `zerops_events`, `zerops_verify`, `zerops_process` | Diagnose deploys, read runtime/build evidence, run health checks, and watch async processes. | +| Deploy | `zerops_deploy` | Push source through Zerops build/deploy pipeline and return build/deploy evidence. | +| Configure | `zerops_env`, `zerops_subdomain`, `zerops_scale`, `zerops_manage` | Change env vars, public subdomain access, scaling, lifecycle, reload/restart, and storage attachment. | +| Import/export | `zerops_import`, `zerops_export`, `zerops_preprocess` | Import project/service YAML, read export YAML, and expand Zerops preprocessor expressions. | +| Workspace | `zerops_mount` | Mount or unmount runtime filesystems in remote setup. | +| Workflow | `zerops_workflow` | Track, recover, or configure workflow sessions and packaging/export flow. | +| Destructive | `zerops_delete` | Delete one named service after explicit user approval. | + +Recipe-authoring and ZCP-internal maintenance operations are intentionally out of scope for this public reference. + +## Read-only operations + +| Operation | Purpose | +| --------- | ------- | +| `zerops_discover` | Read services, ports, env-var keys, and current state. | +| `zerops_export` | Read platform project/service export YAML and service metadata. | +| `zerops_logs` | Read runtime/build logs filtered by service, severity, time, or search. | +| `zerops_events` | Read service activity, deploys, builds, scaling, and failures. | +| `zerops_verify` | Run service health, recent error log, and HTTP-readiness checks. | +| `zerops_knowledge` | Fetch ZCP MCP guidance or platform knowledge for the current state. | +| `zerops_process` | Check a known async process; cancel is a mutating action. | + +## Mutating operations + +| Operation | Purpose | +| --------- | ------- | +| `zerops_deploy` | Ship code through the Zerops build and deploy pipeline. | +| `zerops_env` | Read, set, delete, or generate env vars and local `.env` files. | +| `zerops_manage` | Start, stop, restart, reload, or connect storage. | +| `zerops_scale` | Change CPU, RAM, disk, CPU mode, or container autoscaling where supported. `HA`/`NON_HA` is set at service creation. | +| `zerops_subdomain` | Enable or disable public subdomain access. | +| `zerops_delete` | Delete a service. Explicit named approval is required. | + +## Operational setup + +| Operation | Purpose | +| --------- | ------- | +| `zerops_workflow` | Track, recover, or configure workflow sessions; also carries the package-running-service export flow. | +| `zerops_import` | Import project/service definitions. Destructive override is gated. | +| `zerops_mount` | Mount or unmount runtime filesystems in remote setup. | +| `zerops_preprocess` | Expand Zerops preprocessor expressions. | + +## Remote and local tool differences + +The operation names are the same, but the filesystem and network boundary differ. + +| Area | Remote setup | Local setup | +| ---- | ------------ | ----------- | +| Files | Runtime files through SSHFS or remote containers. | Local working directory. | +| Deploy source | Remote service or batch deploy inside Zerops. | Local `workingDir`. | +| Dev server | Remote setup can run or inspect remote dev processes. | Your local tool owns the dev server. | +| Env bridge | Env vars are already available in Zerops. | `.env` generation resolves Zerops references for local use. | +| Git | Workspace-managed credentials. | User's local git credentials. | + +## Confirmation gates + +Two operations require explicit care: + +- **Service deletion** requires explicit user approval in the current conversation, by service name. +- **Destructive import override** first refuses and names what would be replaced; a second call must acknowledge the same targets. + +See [Tokens and credentials](/zcp/security/tokens-and-project-access#what-zcp-enforces-for-destructive-actions) for the user-facing confirmation flow. + +## Related reference + +- [Workflows in depth](/zcp/reference/agent-workflow) - how a generated-workflow run uses these tools. +- [Troubleshooting](/zcp/reference/troubleshooting) - evidence order when a run gets stuck. +- [Tokens and credentials](/zcp/security/tokens-and-project-access) - token scope, storage, and confirmation gates. diff --git a/apps/docs/content/zcp/reference/troubleshooting.mdx b/apps/docs/content/zcp/reference/troubleshooting.mdx new file mode 100644 index 000000000..a575472fd --- /dev/null +++ b/apps/docs/content/zcp/reference/troubleshooting.mdx @@ -0,0 +1,94 @@ +--- +title: 'Troubleshooting' +description: 'A recovery playbook for stuck ZCP MCP sessions: status first, evidence order, local setup checks, and stop conditions.' +--- + +Use troubleshooting when the agent is confused, a session was interrupted, deploy keeps failing, verification does not match the final answer, or you are taking over manually. + +Start from current state, not chat memory: + +```text +Read current project status and tell me where things stand before changing anything. +``` + +That should make the agent read live services, saved workflow state, recent deploys, logs, events, and verification output before it edits anything else. + +## Find where the run is stuck + +| Stuck point | Ask for | What you should get | +| ----------- | ------- | ------------------- | +| Agent lost context | Current project status and services in scope. | Runtime target, managed services, last deploy, last verify result, and any saved workflow state. | +| Service setup is unclear | Runtime target and dependency plan. | Which existing services will be used, which missing services are needed, and what needs human approval. | +| Deploy failed | Failure category plus build logs, runtime logs, and recent service events. | A cause or next diagnostic step, not another blind deploy. | +| Runtime is reachable but app behavior fails | The failing behavior check and request-time runtime logs. | Endpoint/UI/job/data evidence tied to the product request. | +| Local app cannot reach services | VPN state, generated `.env`, and selected runtime/setup. | Whether MCP auth works separately from private service access. | +| Delivery is ambiguous | Delivery mode, git-push state, build integration, or handoff note. | What will happen after proof: direct deploy, git push, CI, package, or production handoff. | + +Useful prompt: + +```text +Show me the runtime in scope, failure category, evidence read, fixes tried, and the next decision needed. +``` + +## Evidence order + +The useful evidence depends on the failure category surfaced by deploy or verify tools. + +| Category | Read first | Avoid | +| -------- | ---------- | ----- | +| `build` | Build logs, build commands, dependency manifests, deploy file list. | Runtime logs; the runtime did not start yet. | +| `start` | Prepare/runtime logs, start command, ports, env references. | Rebuilding without checking why the process exited. | +| `verify` | Failing check detail, HTTP response, request-time runtime logs, stored state. | Calling a green deploy "done" before behavior passes. | +| `network` | VPN, SSH, DNS, subdomain readiness, service status, transport error. | Editing app code before proving connectivity. | +| `config` | Field-level rejection, `zerops.yaml`, setup name, env references, service settings. | Guessing from service names or dashboard memory. | +| `credential` | The named credential surface: `ZCP_API_KEY`, git, SSH, managed-service, CI, or external API. | Rotating unrelated secrets. | +| `other` | Raw events/logs and the exact phase that failed. | Repeating the same attempt after the same unknown reason. | + +Failure categories come from the deploy/verify evidence surface. They are not a verdict; they tell the agent where the next useful signal is. + +## Local setup checks + +Local setup has two separate connections: + +- MCP uses `ZCP_API_KEY` to talk to the Zerops API. +- Your local app and shell use `zcli vpn up` to reach private service hostnames such as `db` or `cache`. + +That means MCP can work while the app cannot reach the database. + +| Symptom | Check | +| ------- | ----- | +| Agent does not list the `zerops` MCP server. | Relaunch the agent from the directory containing `.mcp.json`. | +| MCP startup says the token reaches multiple projects. | Replace `ZCP_API_KEY` with a single-project token. | +| Re-running `zcp init` made MCP disappear. | Re-add the `ZCP_API_KEY` env block to `.mcp.json` and restart the agent. | +| Local app cannot reach `db`, `cache`, or storage hostnames. | Run `zcli vpn up ` again. | +| Local app reads stale credentials. | Regenerate `.env`; it is a snapshot, not a live sync. | +| `zcp` is not found after install. | Add `~/.local/bin` or the install target to `PATH`, then restart the shell/agent. | + +For local setup details, use [Run locally](/zcp/setup/local-agent-bridge). + +## Manual takeover + +If you take over from the agent, read evidence in this order: + +1. Service list and runtime target. +2. Service-scoped events for the runtime in question. +3. Build logs for build failures, runtime logs for start or request failures. +4. Verify output for reachability and requested behavior. +5. Git history only when delivery uses git-push or CI. + +Do not inspect every service first. Start with the runtime in scope and expand only when the evidence points to a dependency. + +## When to stop + +Stop the loop when: + +- the same failure repeats without new evidence, +- the agent needs an external credential, +- the target runtime or stage choice is ambiguous, +- a destructive action would delete or replace a service, +- production release authority is needed, +- the request no longer fits the current project layout. + +A blocker is acceptable only when it names the runtime in scope, failure category, evidence read, fixes attempted, and the human decision or credential still needed. + +Before destructive recovery, read service-scoped events, logs, deploy/verify result, and git history when delivery uses git-push. Token scope and destructive confirmations are covered in [Tokens and credentials](/zcp/security/tokens-and-project-access). diff --git a/apps/docs/content/zcp/security/production-policy.mdx b/apps/docs/content/zcp/security/production-policy.mdx new file mode 100644 index 000000000..202716c20 --- /dev/null +++ b/apps/docs/content/zcp/security/production-policy.mdx @@ -0,0 +1,93 @@ +--- +title: 'Production boundary' +description: 'Keep ZCP in development or staging. Promote verified work to production through CI, release tooling, or a deliberate human action.' +--- + +Use the MCP setup in development or staging. Keep production in a separate Zerops project without a `zcp` service. Production deploys should come from CI, a release pipeline, or a deliberate human `zcli` push using production credentials. + +Zerops does not prevent you from adding `zcp` to production. The policy exists because ZCP MCP gives a coding agent operational access. In production, that is the wrong blast radius for normal app development. + +The production boundary does not make ZCP output disposable. The intended path is production-shaped dev or stage infrastructure, verified behavior, and then a controlled production release outside the agent loop. + +## Recommended project layout + +| Project | Contains | Who operates it | +| ----------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------ | +| Development / staging project | `zcp` service, agent workspace, dev runtime, optional stage runtime, non-production managed services | Agent plus humans | +| Production project | Production runtimes, production managed services, production env values, production backups and scaling | CI/release process plus humans | + +The two projects use different Zerops tokens. A development token cannot reach production. A production token should not be placed in the development `zcp` service or a local `.mcp.json` used by the agent. + +## Why production is a separate project + +- **The Zerops project is the security boundary.** ZCP binds to one project at startup. Keeping production separate prevents a development agent from touching it by accident. +- **Secrets stay clean.** Production env values, database credentials, object-storage keys, and third-party secrets stay in the production project. +- **Operational policies can differ.** Production often needs HA services, stronger backup retention, stricter scaling, alerts, and release approvals. Development can stay cheaper and more flexible. +- **Audit trails stay readable.** Development experiments and agent retries do not mix with production deploy evidence. + +This separation matters even when the same source repository deploys to both projects. + +## What stage proves + +Stage is the production-like rehearsal inside development or staging. It is where the agent proves the change before the release leaves the ZCP loop. + +Use stage to match production where it matters: + +- same runtime family and version, +- same managed service types, +- same build and start command pattern, +- same deploy route, +- behavior checks against a real running service. + +Stage is not production. It should use non-production data and non-production secrets. A green stage means the change is ready for release, not that the agent should deploy to production itself. + +## The release + +After stage verifies, ZCP's job is done for that change. The production release happens outside the agent loop: + +- CI deploys a merged commit or release tag to the production project. +- A release pipeline runs `zcli push` with a production-scoped `ZEROPS_TOKEN`. +- A human runs `zcli push` against the production project with production credentials. + +The agent can prepare the handoff by pushing code, summarizing verification evidence, and naming the runtime and URL it verified. It should not bridge development to production. + +For the practical workflow, see [Promote to production](/zcp/workflows/promote-to-production). + +## Credential rules + +| Credential | Production policy | +| ----------------------------------- | -------------------------------------------------------------------------------------------------------- | +| Development `ZCP_API_KEY` | Never grants production access. Keep it scoped to the development/staging project. | +| Production `ZEROPS_TOKEN` | Store only in the production CI/release secret store. Do not place it in the development `zcp` service. | +| Agent subscription or model API key | May be used by the agent, but it does not grant Zerops production access by itself. | +| Git credentials | May push source changes, but production deploy authority should stay with the release process. | + +If a production deploy fails, investigate in the production project with production logs, events, backups, and CI output. Do not attach the development agent directly to production as a shortcut. + +## Production separation rules + +| Rule | Why it matters | +| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| Keep the production project without a `zcp` service | Production should not host an agent workspace with operational access. | +| Keep production tokens out of local `.mcp.json` | Local agents should operate development or staging projects, not production. | +| Keep production `ZEROPS_TOKEN` in the release secret store | Development tooling should not deploy to production outside the release process. | +| Treat stage proof as release evidence, not production approval | Stage is the rehearsal; production approval is a separate release decision. | +| Promote through CI, release tooling, or a deliberate human action | Production execution should use production credentials. | + +## Acceptable agent involvement + +The agent can still help before the production release: + +- make the code change in development, +- deploy and verify dev/stage runtimes, +- produce the URL, endpoint result, or UI proof it verified, +- push or prepare the branch your team uses for review, +- summarize release notes and known blockers for the human or CI handoff. + +ZCP's involvement stops at the handoff. Production execution belongs to the release process. + +## Related security + +- [Promote to production](/zcp/workflows/promote-to-production) - practical production release paths after ZCP proof. +- [Trust model](/zcp/security/trust-model) - the boundary that makes this policy enforceable. +- [Tokens and credentials](/zcp/security/tokens-and-project-access) - production and development credentials stay separate. diff --git a/apps/docs/content/zcp/security/tokens-and-project-access.mdx b/apps/docs/content/zcp/security/tokens-and-project-access.mdx new file mode 100644 index 000000000..266dd630e --- /dev/null +++ b/apps/docs/content/zcp/security/tokens-and-project-access.mdx @@ -0,0 +1,162 @@ +--- +title: 'Tokens and credentials' +description: 'ZCP_API_KEY, agent credentials, git credentials, CI tokens, and destructive-action confirmations.' +--- + +ZCP uses a Zerops API token to operate exactly one project. The important rule is simple: `ZCP_API_KEY` is the Zerops credential for ZCP, not an agent login, not a git token, and not a general account token. + +Remote setup gets `ZCP_API_KEY` from Zerops. Local setup reads it from `.mcp.json`. In both setups, ZCP validates the token at startup and refuses tokens that resolve to no project or multiple projects. + +## Credential map + +| Name | What it authorizes | Where it belongs | +| ---------------------------- | -------------------------------------------- | --------------------------------------------------------------------------- | +| `ZCP_API_KEY` | ZCP against one Zerops project | Remote: `zcp` service env injected by Zerops. Local: `.mcp.json` env block. | +| Agent login or model API key | The coding agent itself | The bundled agent in remote setup, or your local agent client. | +| `GIT_TOKEN` | Git push from remote setup to a git provider | Secret env var on the `zcp` service when remote git-push delivery needs it. | +| `ZEROPS_TOKEN` | `zcli` in GitHub Actions or external CI | Separate Zerops delivery token stored in the CI/release secret store. | + +Keeping these names separate prevents most setup and delivery failures. + +## Recommended `ZCP_API_KEY` shape + +Use a Zerops API token with **Custom access per project**, exactly one selected project, and **Full access** for normal agent work. + +Read-only tokens can authenticate, but they fail as soon as the agent needs to deploy, write env vars, restart services, scale, or change public access. Account-wide or multi-project tokens are refused before the agent can operate. + +To generate the token: + +1. Open [Settings -> Access Tokens Management](https://app.zerops.io/settings/token-management). +2. Create a token and name it for the project, for example `zcp-`. +3. Choose **Custom access per project**. +4. Add exactly one project. +5. Set that project to **Full access** for normal ZCP MCP work. +6. Create the token and copy the value. Zerops shows it only at creation time. + +The token's blast radius equals the project and its granted permissions. Other projects, organization settings, and billing stay out of reach. Zerops [Roles & Permissions](/features/rbac#integration-tokens) remain the platform authority. + +## Rejected token shapes + +ZCP validates token shape at startup. + +| Token shape | What happens | Fix | +| ----------------------------------- | ------------------------------------------- | -------------------------------------------------------------------- | +| Account-wide or multi-project token | ZCP refuses to start. | Generate a token scoped to exactly one project. | +| Token with no project access | ZCP refuses to start. | Grant one project or create a new single-project token. | +| Expired or revoked token | ZCP refuses to start or receives HTTP 401. | Replace the token and restart the agent or ZCP process. | +| Read-only project token | Startup may pass, but mutations fail later. | Use full access for normal agent work, or expect read-only behavior. | + +Common messages: + +| Message | Meaning | +| -------------------------------------------------------------- | ------------------------------------------------- | +| `Token accesses N projects; use project-scoped token` | The token can see more than one project. | +| `Token has no project access` | The token authenticates but reaches no project. | +| `No authentication found: set ZCP_API_KEY or log in with zcli` | The `zcp` process did not receive a usable token. | +| `AUTH_TOKEN_EXPIRED` or HTTP 401 | The token expired, was revoked, or is invalid. | + +For ZCP MCP setup, provide `ZCP_API_KEY`. `zcli` login is a diagnostic fallback, not the normal agent setup. + +## Where the token lives - remote vs local {#where-the-token-lives--remote-vs-local} + +ZCP reads `ZCP_API_KEY` from its process environment at startup. It does not write the token somewhere else or exchange it for a derived credential. + +| Setup | Where `ZCP_API_KEY` comes from | Who provisions it | +| -------------------------------------------- | ------------------------------------------------------- | -------------------------------------------------------- | +| [Remote setup](/zcp/setup/hosted-workspace) | The `zcp` service environment | Zerops injects it automatically when the service starts. | +| [Local setup](/zcp/setup/local-agent-bridge) | The `env` block of `.mcp.json` in the app directory | You add it after `zcp init`. | + +In remote setup, do not hand-edit `ZCP_API_KEY`. Replace or rotate it through the Zerops-managed surface so the service keeps the intended project boundary. + +In local setup, `zcp init` writes a token-less `.mcp.json`. Add the token manually: + +```json +{ + "mcpServers": { + "zerops": { + "command": "zcp", + "args": ["serve"], + "env": { + "ZCP_API_KEY": "" + } + } + } +} +``` + +Add `.mcp.json` to `.gitignore`. Each app directory should have its own file and token. Switching projects means switching directories, not editing one shared credential. + +## Agent credentials are separate + +The bundled agent in remote setup may ask you to sign in or provide a model API key. That is not `ZCP_API_KEY`. + +Zerops wires the agent to ZCP. It does not provide your model subscription, store your agent login, or rotate your agent provider credentials. Treat the agent account exactly as you would outside Zerops. + +## Git and CI credentials + +`GIT_TOKEN` matters only when remote setup pushes to a git remote. It authorizes git provider access from the `zcp` service. In local setup, your local git CLI uses your normal SSH key or credential helper, so ZCP does not need `GIT_TOKEN`. + +`ZEROPS_TOKEN` is a Zerops API token used by GitHub Actions or another CI system when that system runs `zcli` against Zerops. It is not a GitHub token. Use a separate delivery token so ZCP sessions and CI/release workflows can be named, rotated, and audited independently. + +For production delivery, `ZEROPS_TOKEN` should reach production only and live only in the production CI or release secret store. + +With GitHub CLI, the secret shape is: + +```bash +gh secret set ZEROPS_TOKEN -b "$ZEROPS_DELIVERY_TOKEN" +``` + +Use the GitHub UI or your CI secret manager instead if your team does not allow local CLI secret writes. + +## Rotation + +Rotate in the Zerops dashboard, then update the surface that consumes the token. + +| Surface | Rotation step | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| Remote `ZCP_API_KEY` | Reconfigure or redeploy the remote workspace through the dashboard-managed flow, then restart the `zcp` service so the process gets the new value. | +| Local `ZCP_API_KEY` | Paste the new token into `.mcp.json`, then restart the local agent client. | +| `ZEROPS_TOKEN` in CI | Replace the repository or CI secret. The next workflow run uses the new value. | +| `GIT_TOKEN` | Replace the git-provider credential stored for remote setup. | + +Rotation is picked up on the next process start or CI run, not in the middle of a live agent session. + +## What ZCP enforces for destructive actions + +A valid token does not remove every guardrail. ZCP MCP adds explicit confirmation for operations where the loss is not safely reversible from inside the conversation. + +| Operation | Gate | +| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Service deletion | Requires explicit approval in the same conversation, including the service name. Remote setup also blocks deleting the `zcp` service it is running in. | +| Wholesale service replacement after failed deploy history | The first request refuses, surfaces what would be replaced, and requires the agent to read failure evidence before asking you to confirm. | + +Deploys, env changes, lifecycle actions, restarts, scaling, and public-access changes do not get an additional ZCP-specific confirmation gate. They are normal operations for a full-access token and should be reviewed through service events, logs, verification output, and team policy. + +Approval from a previous chat does not carry forward. A new conversation needs a new approval. + +## Evidence before destructive recovery + +When a service has recent failure history, ZCP enforces one recovery rule: read the platform evidence before destroying or replacing the service. + +The agent should inspect service events, build logs, runtime logs, and failure summaries, then either fix the cause or show you the evidence before asking for destructive confirmation. The point is to preserve the failure context the next session needs. + +A service waiting for first code deploy is not the same thing as a failed service. The gate is about recorded failure history, not idle state. + +Threat model and boundaries: [Trust model](/zcp/security/trust-model). + +## Credential checks + +- **Account-wide full-access tokens are refused.** ZCP needs one project, not a broad account credential. +- **`zcp init` regenerates `.mcp.json`.** Re-add `ZCP_API_KEY` after rerunning it. +- **`GIT_TOKEN` is not `ZCP_API_KEY`.** One authorizes git provider access; the other authorizes Zerops operations. +- **`ZEROPS_TOKEN` in GitHub Actions is not a GitHub PAT.** It is a Zerops API token for `zcli`. +- **A rotated token needs a restart.** The live ZCP process keeps the old environment value until it starts again. +- **A successful confirmation is still destructive.** Backups, git history, and service events are your recovery evidence; ZCP does not auto-rollback a confirmed deletion or replacement. + +## Related pages + +- [Trust model](/zcp/security/trust-model) - the access boundary this page enforces. +- [What remote workspace gives you](/zcp/setup/hosted-workspace) - automatic token injection. +- [Run locally](/zcp/setup/local-agent-bridge) - local `.mcp.json` token setup. +- [Production boundary](/zcp/security/production-policy) - why production gets separate credentials. +- [GitHub integration](/references/github-integration) - CI secret usage with Zerops. diff --git a/apps/docs/content/zcp/security/trust-model.mdx b/apps/docs/content/zcp/security/trust-model.mdx new file mode 100644 index 000000000..f7fc945f8 --- /dev/null +++ b/apps/docs/content/zcp/security/trust-model.mdx @@ -0,0 +1,110 @@ +--- +title: 'Trust model' +description: 'How MCP access is limited, and how remote and local setup change the surrounding blast radius.' +--- + +The trust model starts with one rule: one ZCP process operates one Zerops project. `ZCP_API_KEY` decides that boundary at startup, and ZCP refuses tokens that resolve to no project or multiple projects. + +That boundary is strong on the Zerops side. It does not make the agent harmless. A valid token can still deploy, change env vars, restart services, read logs, scale services, and change public access when Zerops permissions allow it. Treat it like an operations credential. + +## Boundary summary + +| Question | Answer | +| ------------------------------------ | ------------------------------------------------------------------------------------------- | +| What project can ZCP see? | Exactly one project resolved from `ZCP_API_KEY` at startup. | +| What can ZCP change? | Whatever Zerops RBAC grants that token inside the project. | +| What is outside reach? | Other projects, organization-wide settings, billing, and any operation Zerops RBAC rejects. | +| What network can remote setup reach? | Private services from inside the `zcp` service. | +| What network can local setup reach? | The Zerops API directly, plus private services only when your laptop VPN is up. | +| Who owns the agent login? | You. The agent subscription or model API key is separate from `ZCP_API_KEY`. | + +Zerops [RBAC](/features/rbac) remains the authority. ZCP does not bypass platform permissions; it exposes project operations to the agent only through the token it was given. + +## Remote and local blast radius + +Remote setup and local setup share the same Zerops boundary, but not the same surroundings. + +| Area | Remote setup | Local setup | +| -------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------- | +| Agent location | Inside the `zcp@1` service when **Include Coding Agent** is enabled | On your machine through your editor or CLI agent | +| Files visible to the agent | Workspace files and mounted runtime files | Local files and anything your client permits | +| Private service access | Private network without laptop VPN | `zcli vpn up ` from your machine | +| Local machine exposure | No direct access to your laptop | Agent client runs as your local user | +| Best safety posture | Keep ZCP in a development/staging project and avoid mounting unrelated files | Restrict client permissions, shell access, and filesystem scope | + +Remote setup is contained inside Zerops by design. Local setup is supervise-the-client by design. Either can be right, but local setup requires more attention to your agent client's local permissions. + +## Credential ownership + +There are three separate credential surfaces people often mix together: + +| Credential | Owner | Purpose | +| ----------------------------------- | ---------------------------- | ------------------------------------------------------ | +| `ZCP_API_KEY` | Zerops token | Lets ZCP operate one Zerops project. | +| Agent subscription or model API key | You / the agent provider | Lets the coding agent run. Zerops does not provide it. | +| Git or CI credentials | You / your repository system | Lets finished work push to git or deploy through CI. | + +In both setups, the agent account is still authenticated through the agent's own login flow. For where each credential lives (remote injection vs `.mcp.json`), rotation, rejected token shapes, and `GIT_TOKEN` / `ZEROPS_TOKEN`, see [Tokens and credentials](/zcp/security/tokens-and-project-access). + +## What the token lets the agent do + +The agent can perform normal operational work when the token has permission: + +- discover runtime and managed services, +- create or adjust services when the task requires it, +- read and write service env vars, +- deploy app code to runtime services, +- read build logs, runtime logs, and service events, +- restart, reload, stop, start, or scale services, +- enable or disable public access, +- prepare delivery such as git push or CI handoff. + +This is why development and staging projects are the right place for ZCP. Production should be a separate project without a `zcp` service; see [Production boundary](/zcp/security/production-policy). + +## Remote setup specifics + +- **`zcp` service is not the app.** It is the workspace and control surface. Deploys target app runtimes, not the `zcp` service. +- **The service has operational reach.** A terminal in the `zcp` service can use the same ZCP access as the agent. Review the dashboard's additional changes before deploying the service; they are what give the workspace its operating surface. +- **Network reach is broader than file reach.** The workspace can reach private services, but it only sees runtime files that are mounted into it. +- **Do not hand-edit `ZCP_API_KEY`.** Remote setup gets the value from Zerops. Manual replacement can break the intended one-project boundary. + +## Local setup specifics + +- **The agent inherits local reach.** ZCP MCP is limited to one project, but the local agent client can read files, run commands, and use credentials allowed by your client settings. +- **Each local directory has its own config.** `.mcp.json` and `.zcp/state/` belong to one app directory. Launching from the wrong directory can connect the wrong token or no token. +- **VPN is outside MCP authority.** Bringing up Zerops VPN needs your operating-system approval. The tools cannot grant that for the agent. +- **`.env` files are snapshots.** They contain real project credentials and should stay out of git. + +## Human confirmation gates + +Most project operations do not get an extra ZCP-specific confirmation prompt. Deploys, env changes, restarts, scaling, and public-access changes are normal project operations and are audited through platform evidence. + +ZCP MCP adds hard gates where the loss is not safely reversible from the conversation: **service deletion** (explicit same-conversation approval by service name; remote setup also blocks deleting the `zcp` service it is running in) and **wholesale service replacement after failed deploy history** (refuse-then-acknowledge with failure evidence first). Approval from an old chat does not carry forward. The full enforcement rules are in [Tokens and credentials → What ZCP enforces for destructive actions](/zcp/security/tokens-and-project-access#what-zcp-enforces-for-destructive-actions). + +## Audit evidence + +Zerops records platform-side evidence. It does not record the agent's private reasoning, every shell edit, browser-helper action, or prompt history outside your agent client. + +When taking over from an agent, read evidence in this order: + +1. service-scoped events, +2. build logs, +3. runtime logs, +4. deploy and verification output, +5. git history when delivery uses git-push. + +| Surface | What it proves | What it does not prove | +| -------------- | ------------------------------------------------------------------------- | -------------------------------------------- | +| Service events | Deploy lifecycle, failures, restarts, scaling, and public-access changes. | The exact source edit that caused the event. | +| Build logs | Dependency install, build commands, compile/package failures. | Runtime request behavior after deploy. | +| Runtime logs | Start crashes, port binding, request-time app errors. | Why the build failed. | +| Verify output | Whether reachability and requested behavior passed. | That unrelated app flows work. | +| Git history | Source changes pushed during git-push delivery. | Uncommitted shell edits. | + +Filter by service hostname when possible. Project-level timelines can include unrelated services and older failures. + +## Related security + +- [Tokens and credentials](/zcp/security/tokens-and-project-access) - token scope, storage, rotation, and confirmation gates. +- [Remote or local setup](/zcp/setup/choose-workspace) - compare blast radius before setup. +- [Production boundary](/zcp/security/production-policy) - keep production outside the agent loop. diff --git a/apps/docs/content/zcp/setup/choose-workspace.mdx b/apps/docs/content/zcp/setup/choose-workspace.mdx new file mode 100644 index 000000000..143bb9ccf --- /dev/null +++ b/apps/docs/content/zcp/setup/choose-workspace.mdx @@ -0,0 +1,73 @@ +--- +title: 'Remote or local setup' +description: 'Where the zcp binary and agent workspace run: inside Zerops or on your machine.' +--- + +Use this page to decide where the agent workspace lives: inside Zerops, or on your machine beside the local app directory. + +Both setups connect the agent to one Zerops project and the same project-scoped operations. The tradeoff is where the agent works: filesystem ownership, private-service access, credential location, bundled tooling, and shell blast radius. The runtime layout is a separate app-work decision. + +## What you are choosing + +**Remote setup** always includes the `zcp@1` workspace service. If the chosen **runtime layout** has a development runtime, the agent works with that service too. + +**Local setup** does not need the workspace service or a separate development runtime. It runs beside your **local source tree** and deploys to the Zerops runtime you choose to link. + +Remote setup is the **safer default** for broader agent autonomy because the agent shell, private networking, and workspace files stay **inside Zerops** instead of on your machine. Use local setup when local files, data, desktop tools, git credentials, or a **local-only agent client** need to stay local. + +## Setup comparison + +| | Remote setup | Local setup | +| ----------------------- | ------------------------------------------------- | -------------------------------------------- | +| Workspace service | Always has `zcp@1`. | Not required. | +| Development runtime | Only if the runtime layout includes one. | Not required for local work. | +| `zcp` process | Runs in Zerops. | Runs on your machine. | +| Agent process | Runs in the remote workspace. | Runs in your local editor or CLI. | +| Files the agent edits | Workspace files and mounted runtime files. | Files on your machine. | +| Private service access | Private Zerops network from the workspace. | Zerops VPN from your machine. | +| Token storage | Injected into the workspace service. | `.mcp.json` in the local app directory. | +| Git credentials | Configured inside the workspace service. | Your local git credentials. | +| Safety posture | Broad agent shell permissions stay in Zerops. | Agent shell permissions affect your machine. | + +## Starting points + +Remote setup can start from: + +- **Recipe with AI Agent environment.** A guided path for a known stack. A recipe creates app services, managed services, and the `zcp@1` workspace together. +- **New Zerops setup with remote setup enabled.** Start from blank services or a custom stack and add the `zcp@1` service during creation. +- **Existing development or staging setup.** Add the `zcp` workspace next to services that already exist. Do not add ZCP to production; promote verified work through your release process instead. + +Local setup can start from: + +- **Empty local directory.** The agent creates the app structure from a product request and uses ZCP to select or create Zerops services. +- **Existing app directory.** The app code, editor setup, local data, and git credentials already live on your machine. +- **Recipe prepared for local setup.** The recipe creates the Zerops service baseline, while the agent and files stay local. + +## Runtime layout is separate + +Remote or local only answers where the agent and `zcp` process run. The project can still use: + +- one mutable dev runtime, +- a dev + stage pair, +- a single app runtime, +- local files linked to a stage target. + +That choice belongs to app work: [Build and ship](/zcp/workflows/build-with-zcp#choose-the-runtime-layout). + +Stage is not production. Keep production in a separate Zerops project and promote work through your release process; see [Production boundary](/zcp/security/production-policy). + +## Switching later + +Remote and local setup are not permanent, but switching is a handoff between workspaces, not a sync operation. Before switching, make the handoff explicit: + +- Preserve the current source tree through git or another explicit copy path. +- Choose one deploy source for the next task: the remote workspace, mounted runtime files, or the local app directory. +- Do not edit mounted runtime files and a local repo in parallel unless you have a merge plan. +- Regenerate local `.env` snapshots after Zerops env changes. +- If the local deploy target changed, have the agent inspect the current local link before deploying. + +## Next steps + +- [What remote workspace gives you](/zcp/setup/hosted-workspace) - remote setup, Browser VS Code, bundled agent CLI, and workspace persistence. +- [Run locally](/zcp/setup/local-agent-bridge) - local install, `zcp init`, `.mcp.json`, VPN, env snapshots, and deploy target linking. +- [Trust model](/zcp/security/trust-model) - how the safety boundary changes between remote setup and local setup. diff --git a/apps/docs/content/zcp/setup/hosted-workspace.mdx b/apps/docs/content/zcp/setup/hosted-workspace.mdx new file mode 100644 index 000000000..e49473183 --- /dev/null +++ b/apps/docs/content/zcp/setup/hosted-workspace.mdx @@ -0,0 +1,121 @@ +--- +title: 'What remote workspace gives you' +description: 'What the zcp@1 workspace provides: Claude Code, Browser VS Code, private networking, and ZCP access inside Zerops.' +--- + +A remote workspace puts the agent, terminal, and optional browser IDE inside Zerops, next to private networking and ZCP access. + +The `zcp@1` service runs the same `zcp` binary used by local setup, but broad shell permissions and private service access stay in a clean Zerops service instead of on your laptop. + +Use remote setup when you want the default workspace, a safer boundary for broad agent permissions, or a preconfigured environment with Claude Code and Browser VS Code. + +App code still deploys to your app runtime services. The `zcp` service is the workspace and control surface; it is not the application runtime. + +Taking over is straightforward in this setup. You open the same workspace, terminal, files, and workflow status the agent used, then continue, inspect, or stop the work from there. + +## What it includes + +- **`zcp@1` workspace service.** Runs ZCP inside Zerops and gives the agent project-scoped operations. +- **Platform-injected `ZCP_API_KEY`.** Zerops injects the token into the workspace; you normally do not set it by hand. +- **Bundled Claude Code when enabled.** The **Include Coding Agent** option installs and preconfigures Claude Code. You authenticate with your own Claude subscription login or API key. +- **Browser VS Code when enabled.** The **Cloud IDE** option gives you a browser editor, terminal, and a place to supervise or take over the agent session. +- **Private networking.** The workspace can reach managed services by hostname without laptop VPN. +- **Open workspace model.** You can add other agent CLIs, private MCP servers, helper processes, dotfiles, package installs, or a derived team image. + +For the local alternative, see [Run locally](/zcp/setup/local-agent-bridge). For the tradeoffs, see [Remote or local setup](/zcp/setup/choose-workspace). + +## Choose a starting point + +### First-time trial + +Use the [Quickstart](/zcp/quickstart) when you want the guided recipe route. It covers the recipe catalog, **AI Agent** environment, Claude Code authentication, Browser VS Code, first product prompt, and proof. + +After provisioning, continue with a product prompt in [Build and ship](/zcp/workflows/build-with-zcp). + +### Recipe with AI Agent environment + +Use this when you want a guided stack baseline for development or staging. A recipe with an **AI Agent** environment creates app services, managed services, and the `zcp@1` workspace together. + +Keep **Coding Agent** enabled when you want bundled Claude Code. Keep **Cloud IDE** enabled when you want Browser VS Code. + +### New Zerops setup with remote setup enabled + +Use this when you want blank services or a custom stack. + +1. Open [Add new project](https://app.zerops.io/dashboard/project-add). +2. Enter the project name, region, and tags. +3. Enable the `zcp@1` remote setup service. +4. Keep **Include Coding Agent** enabled if you want bundled Claude Code. +5. Keep **Cloud IDE** enabled if you want browser VS Code. +6. Create the project. + +This gives you the `zcp@1` workspace. App runtimes and managed services may still be created later by normal ZCP app work. + +### Existing development or staging setup + +Use this when runtime or managed services already exist in development or staging. Do not add `zcp` to production; promote verified work through your release process. + +Add a `zcp` service from the dashboard the same way you add another Zerops service. The workspace appears next to the existing services and receives ZCP access from the platform. + +The agent should still read current state before changing anything. Existing services are context, not instructions for the next task. + +## Open the workspace + +For a returning remote workspace, open the existing `zcp` service: + +1. Open the project in the [Zerops dashboard](https://app.zerops.io/). +2. Open the `zcp` service. +3. Use **Browser VS Code** when **Cloud IDE** is enabled. +4. Complete the Claude Code login or API-token flow if prompted. + +After authentication, Claude Code is connected to ZCP and ready to work from the Browser VS Code terminal. If the workspace was already authorized, use the same route to resume, supervise, or take over work. + +First load can take a minute while the image, Cloud IDE, and bundled agent finish starting. If the page opens before the service is ready, wait until the service is running and reload. + +## Use your own editor or tools + +Browser VS Code is the quickest entry point, not the only one. + +Editors that support remote development can connect to the workspace or runtime services over SSH, depending on how your team wants to work. Common options include VS Code Remote SSH, JetBrains Gateway, Cursor, Zed, and plain SSH. + +This keeps the agent and private network inside Zerops while letting you use a desktop editor UI. Broader editor patterns live in [Local & Remote Development](/features/local-remote-development#native-ide-over-ssh). + +You can also install another agent CLI or additional MCP servers inside the `zcp` service. The bundled Claude Code flow is a convenience, not a closed product boundary. + +## Advanced customization guardrails + +Tools installed inside the `zcp` service may see workspace environment variables, private networking, and any runtime files mounted into the workspace. Use trusted and pinned tools. Keep model credentials, git delivery tokens, external API keys, and production credentials out of the workspace unless the current task explicitly needs them. + +## What belongs where + +| Concern | Where it belongs | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| Agent workspace | The `zcp@1` service: agent CLI, browser VS Code, shell tools, MCP servers, helper processes, dotfiles. | +| App code deploys | Your app runtime services; not the `zcp` workspace service. | +| `ZCP_API_KEY` | Injected by Zerops into the `zcp` workspace. | +| Agent account | Your Claude subscription login or model API credential. Zerops wires the agent to ZCP, but the agent account remains yours. | +| Git credentials | Configured inside the workspace when the agent should commit or push from remote setup. | +| Production release | A separate production project and release process. | + +## Runtime file access + +Remote setup can mount runtime service filesystems into the `zcp` workspace after the first setup pass. Each mounted runtime appears as its own folder. Editing a mounted file changes the file inside that runtime service, with no upload step from your laptop. + +Filesystem reach is narrower than network reach. The workspace can reach services over the private network, but it only sees runtime files mounted into it. + +## Make customization persistent + +The `zcp@1` service is a normal Zerops service. One-off shell installs disappear when the service is rebuilt unless you make them part of the service setup. + +Use these patterns: + +- **Small additions:** put package installs, dotfiles, or bootstrap scripts into service init commands. +- **Team-standard workspace:** build a derived image based on `zcp@1` with required tools already present. +- **Helper processes:** run private helpers next to the agent and editor workspace when your team needs internal integrations. + +Keep app runtime build steps with the runtime services. The remote workspace carries the agent, tools, and ZCP access; runtime services own app builds and deploys. + +## Next steps + +- [Build and ship](/zcp/workflows/build-with-zcp) - normal app work after setup. +- [Tokens and credentials](/zcp/security/tokens-and-project-access) - how `ZCP_API_KEY`, git credentials, and CI secrets differ. diff --git a/apps/docs/content/zcp/setup/local-agent-bridge.mdx b/apps/docs/content/zcp/setup/local-agent-bridge.mdx new file mode 100644 index 000000000..80fc61f2d --- /dev/null +++ b/apps/docs/content/zcp/setup/local-agent-bridge.mdx @@ -0,0 +1,175 @@ +--- +title: 'Run locally' +description: 'Install the zcp binary, initialize a local app directory, add ZCP_API_KEY, and connect a local agent to Zerops.' +--- + +Local setup installs the `zcp` binary on your machine and runs it from the app directory where your agent works. + +Use it when the agent should work next to local files, local data, your desktop editor, terminal tools, and git credentials. The MCP server limits Zerops operations to one project, but the agent client runs as your local user, so local approvals and filesystem allowlists matter. + +[Remote setup](/zcp/setup/hosted-workspace) is the safer default when the agent does not need local files or tools. Use local setup when local control is the point. + +:::warning Public preview +Local setup has more moving parts than remote setup and may change faster. The binary install path, `.mcp.json` shape, and files written by `zcp init` are still settling between releases. +::: + +## Choose a local starting point + +Pick the folder that should own app work: + +- **Empty local directory.** Start with no app code yet. The agent can create the app structure and use the MCP tools to select or create Zerops services. +- **Existing app directory.** Use this when app code, local data, editor setup, test fixtures, and git credentials already live on your machine. +- **Recipe prepared for local setup.** Use a recipe to create the Zerops service baseline, then run the agent locally from the directory that should own source changes. + +After that choice, the mechanics are the same: install `zcp`, run `zcp init`, add `ZCP_API_KEY`, start VPN when private service access is needed, and link a runtime when the agent should deploy. + +## What local setup gives the agent + +- **Local files as source.** The agent edits the directory on your machine, and deploys use that working directory. +- **Your editor and terminal.** Framework CLIs, test runners, local data, and local feedback stay under your normal tools. +- **Your git credentials.** Pushes use your local git CLI, SSH agent, or credential helper. +- **Zerops operations.** The MCP tools let the agent discover services, generate env snapshots, deploy to linked runtimes, read logs, and verify. +- **Private service access through VPN.** Your local app and shell reach private service hostnames through `zcli vpn up`. + +Security note: local setup cannot protect your laptop from the agent client. Configure approvals as you would for any local coding agent. + +## Prerequisites + +- A Zerops project. Create one from the [Zerops dashboard](https://app.zerops.io/dashboard/project-add), from a [recipe](https://app.zerops.io/recipes), or use an existing development/staging setup. +- [zCLI](/references/cli) installed and authenticated on your machine. +- A compatible local agent client installed and logged in. +- A **single-project Zerops token**. Multi-project tokens are refused at startup. +- A local directory where the agent should run. + +You do not need MCP just to develop locally against Zerops services as a human. `zcli vpn up` plus your editor is enough. Add MCP when a local coding agent should also understand and operate Zerops. + +## 1. Get `ZCP_API_KEY` + +The MCP server needs a Zerops API token that reaches exactly one project. For normal agent work, use a full-access token; read-only tokens can authenticate but fail on deploys, env changes, lifecycle actions, and other mutations. Token generation, rejected shapes, and rotation are covered in [Tokens and credentials](/zcp/security/tokens-and-project-access). + +## 2. Install `zcp` + +```bash +curl -sSfL https://raw.githubusercontent.com/zeropsio/zcp/main/install.sh | sh +``` + +The installer downloads the latest release for your platform into `~/.local/bin`, or `/usr/local/bin` when run as root. Verify the install: + +```bash +zcp version +``` + +If your shell cannot find `zcp`, add the install directory to `PATH` and reload the shell. + +## 3. Run `zcp init` + +From the local directory the agent should operate: + +```bash +zcp init +``` + +`zcp init` writes local MCP config and agent instructions: + +- `.mcp.json` - MCP server config for this directory. +- `CLAUDE.md` - agent instructions for Zerops work. +- `.claude/settings.local.json` - Claude Code per-project settings when that client is used. +- `~/.config/zerops/aliases` plus a shell-rc sourcing line - helper aliases for launching the agent here. +- `.zcp/state/` - workflow state created when MCP first writes local state. + +Re-running `zcp init` may refresh generated config. `CLAUDE.md` preserves edits outside managed markers, but `.mcp.json` is regenerated from the token-less template. If you rerun it, re-check the `ZCP_API_KEY` block before launching the agent. + +## 4. Add `ZCP_API_KEY` + +`zcp init` writes a token-less `.mcp.json`. Add the project token under the `env` block: + +```json +{ + "mcpServers": { + "zerops": { + "command": "zcp", + "args": ["serve"], + "env": { + "ZCP_API_KEY": "" + } + } + } +} +``` + +Add `.mcp.json` to `.gitignore`. It contains a live Zerops credential and should not leave the machine. Each app directory should have its own `.mcp.json` and token. + +The server name `zerops` is intentional. Do not rename it unless your agent client requires a different name and you understand the prompt/instruction changes. + +## 5. Launch the agent from the app directory + +Start the agent from the directory that contains `.mcp.json`. The client should list `zerops` as an available MCP server. + +Sanity check: + +```text +List the Zerops services through MCP. +``` + +A working connection answers with the runtime and managed services. If not, check the launch directory, token scope, and whether the client loaded `.mcp.json`. + +## 6. Bring up VPN when private services are needed + +The MCP server can talk to the Zerops API without VPN. Your local app, shell, tests, database clients, and framework commands need VPN to reach private hostnames such as `db` or `cache`. + +```bash +zcli vpn up +``` + +VPN setup needs admin or root approval on macOS and Linux. The tools can tell the agent which command is needed, but they cannot approve or start it for you. After the tunnel is up, service hostnames resolve from your machine; rerun the command when local service connections fail. + +## 7. Generate local env when needed + +When the local app needs service credentials, ask the agent for the app work and let the tools generate the env bridge. For a standalone check: + +```text +Generate a .env file for my local app from Zerops env references. +``` + +Env generation needs `zerops.yaml` in the working directory, the runtime or setup the local app should use, a matching `setup:` entry, and non-empty `run.envVariables` under that setup. + +The tools read `run.envVariables`, resolve Zerops references such as `${db_user}`, `${db_password}`, and `${db_hostname}`, and write the resulting values into `.env`. Cross-service references resolve recursively, so a `DATABASE_URL` can land as a complete local connection string. + +If those inputs are missing, the agent should fix `zerops.yaml` or ask for the target runtime/setup. It should not invent env values from service names or dashboard memory. + +The file is a snapshot, not a live sync. Regenerate it after changing env variables in Zerops. VPN is still required for your local app to use private service hostnames from that `.env`. + +Keep `.env` out of git. It contains real connection values. + +## 8. Link a deploy target + +Local setup needs a Zerops runtime when the agent should deploy from your working directory. A stage runtime is the usual target because it lets local work prove itself before the production release. + +If there is exactly one runtime, the agent can use it automatically. If multiple runtimes exist, it should ask which one to link: + +```text +Link this local directory to appstage for deploys. +``` + +Without a linked runtime, the tools can still inspect services and generate env snapshots, but local deploys need a target first. + +## What stays outside + +MCP does not replace your local feedback loop. Vite, Valet, Docker Compose, your IDE runner, framework CLIs, local fixtures, and test data stay under your normal tooling. + +Local setup does not mount Zerops runtime filesystems on your laptop. Remote setup can mount runtime files into the workspace; local setup works from files on your machine. To inspect runtime files from your machine, use [SSH](/references/networking/ssh) directly. + +The MCP server does not own your git credentials. In local setup, your local git CLI, SSH agent, or credential helper handles pushes. + +## Local setup checks + +- **Launch directory matters.** Start the agent from the folder that contains `.mcp.json`. +- **`zcp init` can remove the token from `.mcp.json`.** Re-add `ZCP_API_KEY` after rerunning init. +- **VPN is separate from MCP auth.** MCP may work while your app still cannot reach `db`. +- **`.env` generation depends on `run.envVariables`.** If generation fails, check that the working directory has `zerops.yaml`, the selected `setup:` exists, and env entries live under `run.envVariables`. +- **`.env` is a credential snapshot.** Regenerate after env changes and keep it out of git. + +## Next steps + +- [Build and ship](/zcp/workflows/build-with-zcp) - normal app work after setup. +- [Tokens and credentials](/zcp/security/tokens-and-project-access) - token scope, storage, rotation, and destructive confirmations. diff --git a/apps/docs/content/zcp/workflows/build-with-zcp.mdx b/apps/docs/content/zcp/workflows/build-with-zcp.mdx new file mode 100644 index 000000000..2457ea76f --- /dev/null +++ b/apps/docs/content/zcp/workflows/build-with-zcp.mdx @@ -0,0 +1,138 @@ +--- +title: 'Build and ship' +description: 'The practical choices you control when a coding agent works on a Zerops project.' +--- + +Use Build and ship for the decisions that shape normal app work. ZCP MCP gives the agent Zerops project state, platform guidance, project-scoped operations, deploy evidence, and recovery rules. You still decide what should be built, where it should run, how strict the acceptance criteria are, and what happens after proof. + +```text +Build a task board. +Tasks should stay saved after refresh. +``` + +A prompt can be that short when the outcome is enough. Add detail when it changes behavior, architecture, stack, runtime layout, acceptance criteria, credentials, delivery, packaging, or the production release. + +The expected output is a verified running change, not only generated files. The agent should prove the request against a real runtime, real managed services when used, and the logs, events, and checks that explain what happened. + +## The decisions + +
+
+ Product prompt + Build a task board... +
+ + +You do not run these steps by hand. They are the parts where your intent changes what the agent should do. If you do not specify a choice, the agent should infer from current project state and ask only when the decision changes cost, credentials, runtime layout, delivery, production risk, or destructive behavior. + +## 1. Choose the runtime layout {#choose-the-runtime-layout} + +When the app runtime and dependencies are unclear, the workflow prepares the layout before feature work starts. It reads current state, uses existing services when they fit, creates missing runtimes or managed services when needed, and stops when the agent knows where app code belongs. + +The main user-facing choice is runtime layout. Let the agent infer it from the project, or name it in the prompt when it matters. + +| Runtime layout | Use when | What to tell the agent | +| ------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------- | +| **Dev** | You want one mutable runtime for fast iteration, experiments, or early app work. | `Build a small Node.js API on dev.` | +| **Dev + stage** | You want a development runtime plus a separate runtime for review or release rehearsal. | `Build a Node.js API with PostgreSQL on dev+stage.` | +| **Stage / linked target** | You work from local files or a single target runtime and want the workflow to use that target. | `Use appstage as the deploy target for this local app.` | + +The layout is about app runtimes, not where the `zcp` binary runs. Remote setup can use any of these layouts from the `zcp@1` workspace. Local setup usually works from local files into a linked stage or app runtime. + +Managed services such as PostgreSQL, Valkey, queues, search, storage, or mail are dependencies. The agent gets their state and wiring patterns, but app code deploys to runtime services. + +## 2. Development {#develop-with-live-project-context} + +Development is still a normal coding conversation with the agent. You describe the product behavior, stack constraints, acceptance criteria, and anything the agent must not guess. + +MCP adds the Zerops side of that conversation: current project state, platform knowledge, project-scoped operations, deploy/log evidence, verification checks, recovery rules, and saved work state. Because of that, you usually do not paste service inventory, env wiring, deploy logs, or a "deploy and verify" checklist into the prompt. + +The useful things to name are: + +- the behavior you want and how it should be verified, +- stack, framework, managed-service, or runtime target preferences, +- whether the agent should inspect or continue existing work first, +- approval boundaries for cost, credentials, data, production, or destructive actions. + +Expect the agent to ask when one of those choices is missing. Otherwise, it should use project context while it works and finish with proof or a blocker. + +## 3. Choose delivery after proof {#choose-delivery-after-proof} + +Delivery preference is how app work closes after there is a verified result. Include it in the original prompt or set it later. + +The first functional deploy is still direct so the agent can prove the app runs. Delivery preference decides what happens after that proof and how later sessions should finish similar work. + +| Delivery preference | What it means | What to tell the agent | +| ---------------------- | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| **Keep direct deploy** | The agent keeps deploying directly to the target runtime for fast dev/stage iteration. | `Keep direct deploy for now.` | +| **Push to git** | The agent commits and pushes working changes to the configured repository. | `When the app works, push changes to git@github.com:my-org/task-board.git.` | +| **CI / handoff** | A repository integration, GitHub Actions workflow, release process, or human owns the next deploy. | `Set up GitHub Actions delivery for future deploys after the app works.` | + +The workflow records the delivery choice so later work can follow it. Git credentials, CI secrets, and production credentials are separate from `ZCP_API_KEY`; see [Tokens and credentials](/zcp/security/tokens-and-project-access). + +## 4. Package a verified runtime {#package-a-verified-runtime} + +After a runtime is verified, you can ask the agent to prepare it as a re-importable Zerops project bundle. Use this when the app should become a reusable starter, customer handoff, demo project, or clean staging project. + +The prompt can be one sentence: + +```text +Package appstage as a buildFromGit import bundle, commit it, and push it to git so I can import it into a fresh Zerops project. +``` + +The export workflow prepares `zerops-project-import.yaml` and `zerops.yaml` in the same git repo as the app. The import file contains one runtime with `buildFromGit:` pointing back to that repo, plus managed services needed for Zerops env references to resolve when the bundle is imported into a fresh project. + +The agent may still ask which runtime to package, which half of a dev+stage pair to use, how to classify project env vars, or how to configure git push. Once pushed, the target project can import the bundle from the dashboard or with `zcli project project-import zerops-project-import.yaml`. + +Packaging is not the next deploy of the same app. For the next app change, keep using the normal build/deploy/verify loop. Use packaging when the output you want is a git-backed import bundle; see [Package a running service](/zcp/workflows/package-running-service). + +## 5. Prepare the production release {#prepare-production-release} + +The production release is where authority changes. The agent prepares verified work and release evidence; production execution belongs to Zerops project settings, CI, release tooling, or a deliberate human action with production credentials. + +The useful split is: + +| Job | How often | What happens | +| --- | --- | --- | +| **Production infrastructure** | Once per production project | Export the verified project as YAML in the GUI, edit it for production, then import it as a new project. | +| **Production deploy trigger** | Once per production runtime | Connect the production runtime to git, usually with a tag trigger. | +| **Production release** | Every release | Verify in dev/stage, push source to git, then trigger production through your tag or release process. | + +Production should be a separate Zerops project without a `zcp` service. Production credentials are not `ZCP_API_KEY`; keep them in CI or release tooling. For the full release guide, see [Promote to production](/zcp/workflows/promote-to-production). + +## What the final answer should contain + +For a completed app task, the agent should report: + +- the runtime service it changed, +- the deploy or verification target, +- the URL, endpoint, UI state, job result, or stored data that proves the requested behavior, +- managed services, env vars, or delivery settings it touched, +- the delivery preference, packaging output, or production release state that now applies. + +If the task is incomplete, the final answer should name the blocker, the evidence read, what was tried, and the decision or credential needed from you. diff --git a/apps/docs/content/zcp/workflows/package-running-service.mdx b/apps/docs/content/zcp/workflows/package-running-service.mdx new file mode 100644 index 000000000..f3af93bc0 --- /dev/null +++ b/apps/docs/content/zcp/workflows/package-running-service.mdx @@ -0,0 +1,81 @@ +--- +title: 'Package a running service' +description: 'Turn a running Zerops service into a re-importable bundle.' +--- + +Use packaging when a verified runtime should become a re-importable Zerops bundle. It is a handoff or reuse task after the app already works, not the normal way to deploy the next change. + +You can ask for it directly: + +```text +Package appstage as a buildFromGit import bundle. +Commit it and push it to git. +``` + +The result is a single git repo that contains the app source, [`zerops.yaml`](/zerops-yaml/specification), and a project [import file](/references/import) named `zerops-project-import.yaml`. The import file uses `buildFromGit:` so a fresh Zerops project can rebuild the app from the repo instead of carrying source code inside YAML. + +Packaging starts from a deployed Zerops runtime. If important source changes exist only on your laptop, deploy or push them first so the runtime and git repo match what you want to package. + +## When to package + +Package a runtime when you need: + +- a running runtime and its managed dependencies reproduced in another Zerops project, +- a reusable starter, demo, handoff, customer project, or clean staging baseline, +- a repo commit that carries the Zerops import shape next to the app source, +- the destination project to build from git. + +Do not use packaging for: + +- the next app deploy; use the normal build, deploy, and verify loop, +- the production release; production needs its own infrastructure, credentials, domains, and release trigger, +- moving managed-service data; backups and restores are separate. + +For production setup, follow [Promote to production](/zcp/workflows/promote-to-production). Packaging can help create a reusable app bundle, but it does not decide production infrastructure or release policy. + +## What the agent prepares + +The agent packages **one** runtime. If the project has dev and stage runtimes, it asks which one to use because they can have different env values, start commands, or `setup:` blocks. + +Managed services come along as dependencies when the runtime's `zerops.yaml` needs their env references. You do not pick databases, caches, queues, or search services one by one. + +The workflow then prepares: + +- `zerops-project-import.yaml` - the project and service shape for the destination project, +- `zerops.yaml` - the runtime build and run configuration, +- a git commit and push when git delivery is configured or explicitly requested. + +If the runtime has no usable `zerops.yaml` or git remote, the agent has to fix that first. A bundle is only useful when the destination project can pull source and build it from git. + +## Env vars need review + +Project env vars are the main place where the agent may ask you to decide. Each value gets one bucket: + +| Bucket | Use for | Bundle result | +| --- | --- | --- | +| `infrastructure` | Values derived from managed services, such as database or cache references. | Omitted from project envs; the new managed service provides fresh values. | +| `auto-secret` | App-owned signing or encryption keys that can be regenerated. | Fresh generated secret on import. | +| `external-secret` | Third-party credentials such as Stripe, OpenAI, Mailgun, or GitHub. | `REPLACE_ME` placeholder. | +| `plain-config` | Literal non-secret config such as log level, feature flags, or public app settings. | Copied as-is. | + +Do not treat this as a key-name guessing game. The agent should inspect source code and ask when classification changes behavior. For example, regenerating a Laravel `APP_KEY`, Django `SECRET_KEY`, or session secret can break existing encrypted state. If state continuity matters, carry the existing value instead of generating a new one. + +## What is not included + +| Not included | What to do instead | +| --- | --- | +| Managed-service data | Restore from [Backup](/features/backup) or another migration path. | +| Real third-party secrets | Fill placeholders in the destination project before deploying. | +| Production domains, scaling, backups, and release triggers | Configure them in the destination project. | +| A repeatable production release process | Use your CI or release workflow; see [Promote to production](/zcp/workflows/promote-to-production). | + +## Use the bundle + +After the bundle is pushed, import `zerops-project-import.yaml` in the destination project through the dashboard or with `zcli project project-import zerops-project-import.yaml`. + +Before opening the new project to users: + +- fill `REPLACE_ME` values, +- restore or seed data when needed, +- review service scaling and public access, +- run a normal deploy and verification pass in the destination project. diff --git a/apps/docs/content/zcp/workflows/promote-to-production.mdx b/apps/docs/content/zcp/workflows/promote-to-production.mdx new file mode 100644 index 000000000..74aeb15b4 --- /dev/null +++ b/apps/docs/content/zcp/workflows/promote-to-production.mdx @@ -0,0 +1,131 @@ +--- +title: 'Promote to production' +description: 'Move verified dev or stage work into a production Zerops project without putting the zcp service in production.' +--- + +import Icons from '@theme/Icon'; + +Use this after the agent has proved work in development or stage. The production release is the point where authority changes: the agent can prepare proof, source changes, and setup notes, but production execution belongs to Zerops project settings, CI, release tooling, or a deliberate human action with production credentials. + +Production should be a separate Zerops project without a `zcp` service. That keeps the development agent out of the production blast radius while still letting verified work reach production. + +## The simple production path + +There are three different jobs. Two are setup work; one is the repeatable release operation. + +| Job | How often | Simplest path | +| --- | --- | --- | +| **Create production infrastructure** | Once per production project | Export the verified Zerops project as YAML in the GUI, edit it for production, then import it as a new project. | +| **Connect the production deploy trigger** | Once per production runtime | In the production project, connect the git repository in the Zerops GUI and trigger builds from release tags. | +| **Release an app change** | Every release | Verify in dev/stage, push source to git, then create the release tag or use your team's release process. | + +This page uses the GUI path because it is the clearest path available today. More customized teams can replace the GUI trigger with GitHub Actions or another CI system later. + +## 1. Create production infrastructure + +Do this once when you need a production project for a verified app. + +1. Open the development or staging project that contains the verified runtime. +2. Use **Export project as yaml**. +3. Edit the exported YAML for production. +4. Import the edited YAML as a new Zerops project. + +Before import, review the YAML as production infrastructure, not as a blind copy of the development project: + +- remove the `zcp` service, +- remove dev-only runtimes and tools such as Mailpit or Adminer, +- keep the runtime and managed services that production actually needs, +- choose production `mode` values before creation, especially `HA` for databases that need it, +- set production `minContainers`, autoscaling, and core package, +- replace development secrets with production secrets or placeholders, +- plan managed-service data restore or migrations, +- configure production domains, DNS, public access, SMTP, object storage, backups, queues, search, and cache as needed. + +Before opening production traffic, get familiar with the deeper Zerops production surface: service modes, scaling, deploy pipeline, health checks, backups, public access, and domains. If the app should use a real domain, follow [Public Access Configuration](/references/networking/public-access). + +Project export/import creates the production infrastructure. Do not rely on it as the code delivery mechanism. The app source should be in git, and the first production code deploy should come through the production deploy trigger. + +## 2. Connect the production deploy trigger + +Do this once for each production runtime that should build from git. + +The simplest path is the Zerops GitHub or GitLab integration in the production project: + +1. Open the production runtime service. +2. Connect the git repository. +3. Choose **New tag** as the production trigger. +4. Add a tag filter if your team uses one, for example `v*`. +5. Keep production env vars and secrets in the production Zerops project. + +For stage, a branch trigger can be convenient. For production, a tag trigger is easier to reason about: a normal source push can update stage, while a deliberate release tag updates production. + +If your team needs approvals, custom tests, generated artifacts, or stricter audit, use GitHub Actions or another CI system instead. In that setup, store a production-scoped `ZEROPS_TOKEN` in the CI secret store and run the production deploy from CI. + +## 3. Release an app change + +This is the repeatable operation after production infrastructure and the deploy trigger exist. The exact release command is yours: define it in your repository instructions, agent skill, CI guide, or team release checklist. + +For example, if production deploys from release tags, the user instruction can be as direct as: + +```text +Create the release tag for production and push it to git. +``` + +Use the wording that matches your CI/CD. The release step can be a manual CLI action, a `git push`, a tag push, a pull request, a protected branch merge, or a CI job. That part is your team preference and production setup, not a rule from these docs. + +The important rule is that the agent should name what it pushed and whether that push triggers stage, production, both, or neither. If production release requires a tag, the final answer should say the tag name or the exact tag command. + +## What not to carry into production + +Do not copy these from the development project into production: + +- the `zcp` service, +- development `ZCP_API_KEY`, +- local `.mcp.json`, +- development secrets, +- test data unless it is intentionally migrated, +- dev-only utilities such as Mailpit or Adminer, +- `.zerops.app` preview routing as the final public entry point. + +Use production-specific domains, secrets, backup policy, scaling, and release credentials. + +## What the agent should hand you + +A useful release handoff from the agent contains: + +- runtime and project where the change was verified, +- URL, endpoint, UI state, job result, or stored data that proves the requested behavior, +- commit, branch, PR, or suggested release tag, +- whether the git push triggers stage, production, both, or neither, +- services to keep, remove, or change before importing production infrastructure, +- env vars, managed services, migrations, or delivery settings touched, +- external secrets still needed, +- managed-service data that must be restored or migrated, +- production blockers that need a human decision. + +If the answer only says that files were changed, the release handoff is incomplete. Ask for the verification evidence and the exact release artifact. + +Production hardening belongs in your team's production checklist. This page only defines the handoff from verified dev or stage work into production authority. + +## Related production references + + diff --git a/apps/docs/docusaurus.config.js b/apps/docs/docusaurus.config.js index d7f27217a..293aee9d9 100644 --- a/apps/docs/docusaurus.config.js +++ b/apps/docs/docusaurus.config.js @@ -17,6 +17,18 @@ const config = { favicon: "favicon.ico", organizationName: "zerops", projectName: "zerops/docs", + headTags: [ + { + tagName: "link", + attributes: { + rel: "preload", + href: "/fonts/JetBrainsMono-Regular.woff2", + as: "font", + type: "font/woff2", + crossorigin: "anonymous", + }, + }, + ], plugins: [ './src/plugins/markdown-source', require.resolve("docusaurus-plugin-image-zoom"), @@ -57,6 +69,9 @@ const config = { }, ], themes: ["@docusaurus/theme-mermaid"], + markdown: { + mermaid: true, + }, themeConfig: { mermaid: { theme: { diff --git a/apps/docs/package.json b/apps/docs/package.json index c64f10988..6863e4e89 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -16,7 +16,8 @@ "write-heading-ids": "docusaurus write-heading-ids", "lint": "eslint --ext .js,.jsx,.ts,.tsx . --fix", "lint:content": "eslint --no-eslintrc -c .content.eslintrc.js content --fix", - "diagram2code:generate": "docusaurus diagram2code:generate" + "diagram2code:generate": "docusaurus diagram2code:generate", + "test": "node --test src/plugins/markdown-source/__tests__/*.test.js" }, "dependencies": { "@babel/preset-react": "^7.18.6", diff --git a/apps/docs/sidebars.js b/apps/docs/sidebars.js index 1cf814036..a95176616 100644 --- a/apps/docs/sidebars.js +++ b/apps/docs/sidebars.js @@ -56,6 +56,15 @@ module.exports = { }, ], }, + { + type: 'doc', + id: 'features/coding-agents', + label: 'Infrastructure for Coding Agents', + customProps: { + sidebar_icon: 'sparkles', + }, + className: 'homepage-sidebar-item', + }, { type: 'category', link: { @@ -104,6 +113,15 @@ module.exports = { }, className: 'homepage-sidebar-item', }, + { + type: 'doc', + id: 'features/local-remote-development', + label: 'Local & Remote Development', + customProps: { + sidebar_icon: 'computer-desktop', + }, + className: 'homepage-sidebar-item', + }, { type: 'doc', id: 'features/rbac', @@ -140,23 +158,23 @@ module.exports = { }, className: 'homepage-sidebar-item', }, -// { -// type: 'html', -// value: 'Perfectly suited for', -// customProps: { -// sidebar_is_group_divider: true, -// }, -// className: 'homepage-sidebar-item', -// }, -// { -// type: 'ref', -// id: 'frameworks/laravel', -// label: 'Laravel', -// customProps: { -// sidebar_icon: 'laravel', -// }, -// className: 'homepage-sidebar-item service-sidebar-item', -// }, + // { + // type: 'html', + // value: 'Perfectly suited for', + // customProps: { + // sidebar_is_group_divider: true, + // }, + // className: 'homepage-sidebar-item', + // }, + // { + // type: 'ref', + // id: 'frameworks/laravel', + // label: 'Laravel', + // customProps: { + // sidebar_icon: 'laravel', + // }, + // className: 'homepage-sidebar-item service-sidebar-item', + // }, { type: 'html', value: 'All Supported Services', @@ -290,7 +308,6 @@ module.exports = { }, className: 'homepage-sidebar-item service-sidebar-item', }, - ], }, { @@ -357,76 +374,76 @@ module.exports = { className: 'homepage-sidebar-item service-sidebar-item', }, { - type: "ref", - id: "valkey/overview", - label: "Valkey (Redis)", + type: 'ref', + id: 'valkey/overview', + label: 'Valkey (Redis)', customProps: { - sidebar_icon: "valkey", + sidebar_icon: 'valkey', }, - className: "homepage-sidebar-item service-sidebar-item", + className: 'homepage-sidebar-item service-sidebar-item', }, { - type: "ref", - id: "elasticsearch/overview", - label: "Elasticsearch", + type: 'ref', + id: 'elasticsearch/overview', + label: 'Elasticsearch', customProps: { - sidebar_icon: "elasticsearch", + sidebar_icon: 'elasticsearch', }, - className: "homepage-sidebar-item service-sidebar-item", + className: 'homepage-sidebar-item service-sidebar-item', }, { - type: "ref", - id: "typesense/overview", - label: "Typesense", + type: 'ref', + id: 'typesense/overview', + label: 'Typesense', customProps: { - sidebar_icon: "typesense", + sidebar_icon: 'typesense', }, - className: "homepage-sidebar-item service-sidebar-item", + className: 'homepage-sidebar-item service-sidebar-item', }, { - type: "ref", - id: "meilisearch/overview", - label: "Meilisearch", + type: 'ref', + id: 'meilisearch/overview', + label: 'Meilisearch', customProps: { - sidebar_icon: "meilisearch", + sidebar_icon: 'meilisearch', }, - className: "homepage-sidebar-item service-sidebar-item", + className: 'homepage-sidebar-item service-sidebar-item', }, { - type: "ref", - id: "qdrant/overview", - label: "Qdrant", + type: 'ref', + id: 'qdrant/overview', + label: 'Qdrant', customProps: { - sidebar_icon: "qdrant", + sidebar_icon: 'qdrant', }, - className: "homepage-sidebar-item service-sidebar-item", + className: 'homepage-sidebar-item service-sidebar-item', }, { - type: "ref", - id: "nats/overview", - label: "NATS", + type: 'ref', + id: 'nats/overview', + label: 'NATS', customProps: { - sidebar_icon: "nats", + sidebar_icon: 'nats', }, - className: "homepage-sidebar-item service-sidebar-item", + className: 'homepage-sidebar-item service-sidebar-item', }, { - type: "ref", - id: "kafka/overview", - label: "Kafka", + type: 'ref', + id: 'kafka/overview', + label: 'Kafka', customProps: { - sidebar_icon: "kafka", + sidebar_icon: 'kafka', }, - className: "homepage-sidebar-item service-sidebar-item", + className: 'homepage-sidebar-item service-sidebar-item', }, { - type: "ref", - id: "clickhouse/overview", - label: "Clickhouse", + type: 'ref', + id: 'clickhouse/overview', + label: 'Clickhouse', customProps: { - sidebar_icon: "clickhouse", + sidebar_icon: 'clickhouse', }, - className: "homepage-sidebar-item service-sidebar-item", + className: 'homepage-sidebar-item service-sidebar-item', }, { type: 'ref', @@ -557,7 +574,150 @@ module.exports = { }, className: 'homepage-sidebar-item', }, - ] + ], + }, + { + type: 'category', + label: 'Zerops Control Plane MCP', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'zcp/overview', + label: 'Overview', + customProps: { + sidebar_icon: 'sparkles', + }, + className: 'homepage-sidebar-item', + }, + { + type: 'doc', + id: 'zcp/quickstart', + label: 'Quickstart', + customProps: { + sidebar_icon: 'rocket-launch', + }, + className: 'homepage-sidebar-item', + }, + { + type: 'doc', + id: 'zcp/concept/how-it-works', + label: 'How it works', + customProps: { + sidebar_icon: 'cog-six-tooth', + }, + className: 'homepage-sidebar-item', + }, + { + type: 'category', + label: 'Remote or local setup', + link: { + type: 'doc', + id: 'zcp/setup/choose-workspace', + }, + customProps: { + sidebar_icon: 'computer-desktop', + }, + className: 'homepage-sidebar-item', + items: [ + { + type: 'doc', + id: 'zcp/setup/hosted-workspace', + label: 'What remote workspace gives you', + }, + { + type: 'doc', + id: 'zcp/setup/local-agent-bridge', + label: 'Run locally', + }, + ], + }, + { + type: 'category', + label: 'Build and ship', + link: { + type: 'doc', + id: 'zcp/workflows/build-with-zcp', + }, + customProps: { + sidebar_icon: 'circle-stack', + }, + className: 'homepage-sidebar-item', + items: [ + { + type: 'doc', + id: 'zcp/workflows/package-running-service', + label: 'Package a running service', + }, + { + type: 'doc', + id: 'zcp/workflows/promote-to-production', + label: 'Promote to production', + }, + ], + }, + { + type: 'category', + label: 'Security', + link: { + type: 'doc', + id: 'zcp/security/trust-model', + }, + customProps: { + sidebar_icon: 'users', + }, + className: 'homepage-sidebar-item', + items: [ + { + type: 'doc', + id: 'zcp/security/tokens-and-project-access', + label: 'Tokens and credentials', + }, + { + type: 'doc', + id: 'zcp/security/production-policy', + label: 'Production boundary', + }, + ], + }, + { + type: 'category', + label: 'Reference', + link: { + type: 'doc', + id: 'zcp/reference/index', + }, + customProps: { + sidebar_icon: 'command-line', + }, + className: 'homepage-sidebar-item', + items: [ + { + type: 'doc', + id: 'zcp/reference/agent-workflow', + label: 'Workflows in depth', + }, + { + type: 'doc', + id: 'zcp/reference/mcp-operations', + label: 'MCP tools', + }, + { + type: 'doc', + id: 'zcp/reference/troubleshooting', + label: 'Troubleshooting', + }, + { + type: 'doc', + id: 'zcp/glossary', + label: 'Glossary', + }, + ], + }, + ], }, { type: 'category', @@ -648,7 +808,7 @@ module.exports = { }, className: 'homepage-sidebar-item', }, - ] + ], }, { type: 'category', @@ -724,7 +884,7 @@ module.exports = { }, className: 'homepage-sidebar-item', }, - ] + ], }, { type: 'category', @@ -761,7 +921,7 @@ module.exports = { }, className: 'homepage-sidebar-item', }, - ] + ], }, { type: 'html', @@ -962,15 +1122,15 @@ module.exports = { }, ], }, - { - type: 'doc', - id: 'nodejs/faq', - label: 'FAQ', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'chat-bubble-left-right', - }, + { + type: 'doc', + id: 'nodejs/faq', + label: 'FAQ', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'chat-bubble-left-right', }, + }, ], php: [ { @@ -1228,640 +1388,256 @@ module.exports = { }, ], go: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', - }, + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', }, - { - type: 'doc', - id: 'go/overview', - label: 'Go', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'go', - }, - }, - { - type: 'category', - label: 'Management', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'go/how-to/create', - label: 'Create Go service', - }, - { - type: 'doc', - id: 'go/how-to/upgrade', - label: 'Upgrade Go service', - }, - { - type: 'doc', - id: 'go/how-to/controls', - label: 'Stop, start & delete Go runtime service', - }, - ], + }, + { + type: 'doc', + id: 'go/overview', + label: 'Go', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'go', }, - { - type: 'category', - label: 'Configuration & Environment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'go/how-to/env-variables', - label: 'Manage environment variables', - }, - { - type: 'doc', - id: 'go/how-to/customize-runtime', - label: 'Customize Go runtime', - }, - { - type: 'doc', - id: 'go/how-to/scaling', - label: 'Scale Go runtime service', - }, - ], + }, + { + type: 'category', + label: 'Management', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, }, - { - type: 'category', - label: 'Build & Deployment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + items: [ + { + type: 'doc', + id: 'go/how-to/create', + label: 'Create Go service', }, - items: [ - { - type: 'doc', - id: 'go/how-to/build-pipeline', - label: 'Configure build & deploy pipeline', - }, - { - type: 'doc', - id: 'go/how-to/trigger-pipeline', - label: 'Trigger build pipeline', - }, - { - type: 'doc', - id: 'go/how-to/build-process', - label: 'Build process', - }, - { - type: 'doc', - id: 'go/how-to/deploy-process', - label: 'Deploy process', - }, - ], - }, - { - type: 'category', - label: 'Maintenance & Monitoring', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'go/how-to/upgrade', + label: 'Upgrade Go service', }, - items: [ - { - type: 'doc', - id: 'go/how-to/logs', - label: 'Setup & access logs', - }, - { - type: 'doc', - id: 'go/how-to/filebrowser', - label: 'Browse container files', - }, - { - type: 'doc', - id: 'go/how-to/shared-storage', - label: 'Connect / disconnect shared storage', - }, - ], - }, - ], - rust: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', + { + type: 'doc', + id: 'go/how-to/controls', + label: 'Stop, start & delete Go runtime service', }, + ], + }, + { + type: 'category', + label: 'Configuration & Environment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, }, - { - type: 'doc', - id: 'rust/overview', - label: 'Rust', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'rust', - }, - }, - { - type: 'category', - label: 'Management', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'rust/how-to/create', - label: 'Create Rust service', - }, - { - type: 'doc', - id: 'rust/how-to/upgrade', - label: 'Upgrade Rust service', - }, - { - type: 'doc', - id: 'rust/how-to/controls', - label: 'Stop, start & delete Rust runtime service', - }, - ], - }, - { - type: 'category', - label: 'Configuration & Environment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + items: [ + { + type: 'doc', + id: 'go/how-to/env-variables', + label: 'Manage environment variables', }, - items: [ - { - type: 'doc', - id: 'rust/how-to/env-variables', - label: 'Manage environment variables', - }, - { - type: 'doc', - id: 'rust/how-to/customize-runtime', - label: 'Customize Rust runtime', - }, - { - type: 'doc', - id: 'rust/how-to/scaling', - label: 'Scale Rust runtime service', - }, - ], - }, - { - type: 'category', - label: 'Build & Deployment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'go/how-to/customize-runtime', + label: 'Customize Go runtime', }, - items: [ - { - type: 'doc', - id: 'rust/how-to/build-pipeline', - label: 'Configure build & deploy pipeline', - }, - { - type: 'doc', - id: 'rust/how-to/trigger-pipeline', - label: 'Trigger build pipeline', - }, - { - type: 'doc', - id: 'rust/how-to/build-process', - label: 'Build process', - }, - { - type: 'doc', - id: 'rust/how-to/deploy-process', - label: 'Deploy process', - }, - ], - }, - { - type: 'category', - label: 'Maintenance & Monitoring', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'go/how-to/scaling', + label: 'Scale Go runtime service', }, - items: [ - { - type: 'doc', - id: 'rust/how-to/logs', - label: 'Setup & access logs', - }, - { - type: 'doc', - id: 'rust/how-to/filebrowser', - label: 'Browse container files', - }, - { - type: 'doc', - id: 'rust/how-to/shared-storage', - label: 'Connect / disconnect shared storage', - }, - ], + ], + }, + { + type: 'category', + label: 'Build & Deployment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, }, - ], - dotnet: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', + items: [ + { + type: 'doc', + id: 'go/how-to/build-pipeline', + label: 'Configure build & deploy pipeline', }, - }, - { - type: 'doc', - id: 'dotnet/overview', - label: '.NET', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'dotnet', - }, - }, - { - type: 'category', - label: 'Management', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'dotnet/how-to/create', - label: 'Create .NET service', - }, - { - type: 'doc', - id: 'dotnet/how-to/upgrade', - label: 'Upgrade .NET service', - }, - { - type: 'doc', - id: 'dotnet/how-to/controls', - label: 'Stop, start & delete .NET runtime service', - }, - ], - }, - { - type: 'category', - label: 'Configuration & Environment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'go/how-to/trigger-pipeline', + label: 'Trigger build pipeline', }, - items: [ - { - type: 'doc', - id: 'dotnet/how-to/env-variables', - label: 'Manage environment variables', - }, - { - type: 'doc', - id: 'dotnet/how-to/customize-runtime', - label: 'Customize .NET runtime', - }, - { - type: 'doc', - id: 'dotnet/how-to/scaling', - label: 'Scale .NET runtime service', - }, - ], - }, - { - type: 'category', - label: 'Build & Deployment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'go/how-to/build-process', + label: 'Build process', }, - items: [ - { - type: 'doc', - id: 'dotnet/how-to/build-pipeline', - label: 'Configure build & deploy pipeline', - }, - { - type: 'doc', - id: 'dotnet/how-to/trigger-pipeline', - label: 'Trigger build pipeline', - }, - { - type: 'doc', - id: 'dotnet/how-to/build-process', - label: 'Build process', - }, - { - type: 'doc', - id: 'dotnet/how-to/deploy-process', - label: 'Deploy process', - }, - ], - }, - { - type: 'category', - label: 'Maintenance & Monitoring', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'go/how-to/deploy-process', + label: 'Deploy process', }, - items: [ - { - type: 'doc', - id: 'dotnet/how-to/logs', - label: 'Setup & access logs', - }, - { - type: 'doc', - id: 'dotnet/how-to/filebrowser', - label: 'Browse container files', - }, - { - type: 'doc', - id: 'dotnet/how-to/shared-storage', - label: 'Connect / disconnect shared storage', - }, - ], + ], + }, + { + type: 'category', + label: 'Maintenance & Monitoring', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, }, - ], - java: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', + items: [ + { + type: 'doc', + id: 'go/how-to/logs', + label: 'Setup & access logs', + }, + { + type: 'doc', + id: 'go/how-to/filebrowser', + label: 'Browse container files', + }, + { + type: 'doc', + id: 'go/how-to/shared-storage', + label: 'Connect / disconnect shared storage', }, + ], + }, + ], + rust: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', }, - { - type: 'doc', - id: 'java/overview', - label: 'Java', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'java', - }, - }, - { - type: 'category', - label: 'Management', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'java/how-to/create', - label: 'Create Java service', - }, - { - type: 'doc', - id: 'java/how-to/upgrade', - label: 'Upgrade Java service', - }, - { - type: 'doc', - id: 'java/how-to/controls', - label: 'Stop, start & delete Java runtime service', - }, - ], + }, + { + type: 'doc', + id: 'rust/overview', + label: 'Rust', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'rust', }, - { - type: 'category', - label: 'Configuration & Environment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'java/how-to/env-variables', - label: 'Manage environment variables', - }, - { - type: 'doc', - id: 'java/how-to/customize-runtime', - label: 'Customize Java runtime', - }, - { - type: 'doc', - id: 'java/how-to/scaling', - label: 'Scale Java runtime service', - }, - ], + }, + { + type: 'category', + label: 'Management', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, }, - { - type: 'category', - label: 'Build & Deployment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + items: [ + { + type: 'doc', + id: 'rust/how-to/create', + label: 'Create Rust service', }, - items: [ - { - type: 'doc', - id: 'java/how-to/build-pipeline', - label: 'Configure build & deploy pipeline', - }, - { - type: 'doc', - id: 'java/how-to/trigger-pipeline', - label: 'Trigger build pipeline', - }, - { - type: 'doc', - id: 'java/how-to/build-process', - label: 'Build process', - }, - { - type: 'doc', - id: 'java/how-to/deploy-process', - label: 'Deploy process', - }, - ], - }, - { - type: 'category', - label: 'Maintenance & Monitoring', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'rust/how-to/upgrade', + label: 'Upgrade Rust service', }, - items: [ - { - type: 'doc', - id: 'java/how-to/logs', - label: 'Setup & access logs', - }, - { - type: 'doc', - id: 'java/how-to/filebrowser', - label: 'Browse container files', - }, - { - type: 'doc', - id: 'java/how-to/shared-storage', - label: 'Connect / disconnect shared storage', - }, - ], - }, - ], - nginx: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', + { + type: 'doc', + id: 'rust/how-to/controls', + label: 'Stop, start & delete Rust runtime service', }, + ], + }, + { + type: 'category', + label: 'Configuration & Environment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, }, - { - type: 'doc', - id: 'nginx/overview', - label: 'Nginx Static', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'nginx', - }, - }, - { - type: 'category', - label: 'Management', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'nginx/how-to/create', - label: 'Create Nginx static service', - }, - { - type: 'doc', - id: 'nginx/how-to/upgrade', - label: 'Upgrade Nginx service', - }, - { - type: 'doc', - id: 'nginx/how-to/controls', - label: 'Stop, start & delete Nginx static service', - }, - ], - }, - { - type: 'category', - label: 'Configuration & Environment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + items: [ + { + type: 'doc', + id: 'rust/how-to/env-variables', + label: 'Manage environment variables', }, - items: [ - { - type: 'doc', - id: 'nginx/how-to/env-variables', - label: 'Manage environment variables', - }, - { - type: 'doc', - id: 'nginx/how-to/customize-runtime', - label: 'Customize Nginx static runtime', - }, - { - type: 'doc', - id: 'nginx/how-to/customize-web-server', - label: 'Customize web server', - }, - { - type: 'doc', - id: 'nginx/how-to/scaling', - label: 'Scale Nginx static service', - }, - ], - }, - { - type: 'category', - label: 'Build & Deployment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'rust/how-to/customize-runtime', + label: 'Customize Rust runtime', }, - items: [ - { - type: 'doc', - id: 'nginx/how-to/build-pipeline', - label: 'Configure build & deploy pipeline', - }, - { - type: 'doc', - id: 'nginx/how-to/trigger-pipeline', - label: 'Trigger build pipeline', - }, - { - type: 'doc', - id: 'nginx/how-to/deploy-process', - label: 'Deploy process', - }, - ], - }, - { - type: 'category', - label: 'Maintenance & Monitoring', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'rust/how-to/scaling', + label: 'Scale Rust runtime service', }, - items: [ - { - type: 'doc', - id: 'nginx/how-to/logs', - label: 'Setup & access logs', - }, - { - type: 'doc', - id: 'nginx/how-to/filebrowser', - label: 'Browse container files', - }, - { - type: 'doc', - id: 'nginx/how-to/shared-storage', - label: 'Connect / disconnect shared storage', - }, - ], + ], + }, + { + type: 'category', + label: 'Build & Deployment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, }, - { - type: 'doc', - id: 'nginx/faq', - label: 'FAQ', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'chat-bubble-left-right', + items: [ + { + type: 'doc', + id: 'rust/how-to/build-pipeline', + label: 'Configure build & deploy pipeline', + }, + { + type: 'doc', + id: 'rust/how-to/trigger-pipeline', + label: 'Trigger build pipeline', + }, + { + type: 'doc', + id: 'rust/how-to/build-process', + label: 'Build process', + }, + { + type: 'doc', + id: 'rust/how-to/deploy-process', + label: 'Deploy process', }, + ], + }, + { + type: 'category', + label: 'Maintenance & Monitoring', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, }, - ], - static: [ + items: [ + { + type: 'doc', + id: 'rust/how-to/logs', + label: 'Setup & access logs', + }, + { + type: 'doc', + id: 'rust/how-to/filebrowser', + label: 'Browse container files', + }, + { + type: 'doc', + id: 'rust/how-to/shared-storage', + label: 'Connect / disconnect shared storage', + }, + ], + }, + ], + dotnet: [ { type: 'ref', id: 'homepage', @@ -1873,265 +1649,981 @@ module.exports = { }, { type: 'doc', - id: 'static/overview', - label: 'Static Service', + id: 'dotnet/overview', + label: '.NET', customProps: { sidebar_is_title: true, - sidebar_icon: 'computer-desktop', + sidebar_icon: 'dotnet', }, }, - ], - ubuntu: [ + { + type: 'category', + label: 'Management', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', - }, + type: 'doc', + id: 'dotnet/how-to/create', + label: 'Create .NET service', }, { type: 'doc', - id: 'ubuntu/overview', - label: 'Ubuntu', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'ubuntu', - }, + id: 'dotnet/how-to/upgrade', + label: 'Upgrade .NET service', }, { - type: 'category', - label: 'Management', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'ubuntu/how-to/create', - label: 'Create Ubuntu service', - }, - { - type: 'doc', - id: 'ubuntu/how-to/upgrade', - label: 'Upgrade Ubuntu service', - }, - { - type: 'doc', - id: 'ubuntu/how-to/controls', - label: 'Stop, start & delete Ubuntu runtime service', - }, - ], + type: 'doc', + id: 'dotnet/how-to/controls', + label: 'Stop, start & delete .NET runtime service', + }, + ], + }, + { + type: 'category', + label: 'Configuration & Environment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'dotnet/how-to/env-variables', + label: 'Manage environment variables', + }, + { + type: 'doc', + id: 'dotnet/how-to/customize-runtime', + label: 'Customize .NET runtime', + }, + { + type: 'doc', + id: 'dotnet/how-to/scaling', + label: 'Scale .NET runtime service', + }, + ], + }, + { + type: 'category', + label: 'Build & Deployment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'dotnet/how-to/build-pipeline', + label: 'Configure build & deploy pipeline', + }, + { + type: 'doc', + id: 'dotnet/how-to/trigger-pipeline', + label: 'Trigger build pipeline', + }, + { + type: 'doc', + id: 'dotnet/how-to/build-process', + label: 'Build process', + }, + { + type: 'doc', + id: 'dotnet/how-to/deploy-process', + label: 'Deploy process', + }, + ], + }, + { + type: 'category', + label: 'Maintenance & Monitoring', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'dotnet/how-to/logs', + label: 'Setup & access logs', + }, + { + type: 'doc', + id: 'dotnet/how-to/filebrowser', + label: 'Browse container files', + }, + { + type: 'doc', + id: 'dotnet/how-to/shared-storage', + label: 'Connect / disconnect shared storage', + }, + ], + }, + ], + java: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'java/overview', + label: 'Java', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'java', + }, + }, + { + type: 'category', + label: 'Management', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'java/how-to/create', + label: 'Create Java service', + }, + { + type: 'doc', + id: 'java/how-to/upgrade', + label: 'Upgrade Java service', + }, + { + type: 'doc', + id: 'java/how-to/controls', + label: 'Stop, start & delete Java runtime service', + }, + ], + }, + { + type: 'category', + label: 'Configuration & Environment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'java/how-to/env-variables', + label: 'Manage environment variables', + }, + { + type: 'doc', + id: 'java/how-to/customize-runtime', + label: 'Customize Java runtime', + }, + { + type: 'doc', + id: 'java/how-to/scaling', + label: 'Scale Java runtime service', + }, + ], + }, + { + type: 'category', + label: 'Build & Deployment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'java/how-to/build-pipeline', + label: 'Configure build & deploy pipeline', + }, + { + type: 'doc', + id: 'java/how-to/trigger-pipeline', + label: 'Trigger build pipeline', + }, + { + type: 'doc', + id: 'java/how-to/build-process', + label: 'Build process', + }, + { + type: 'doc', + id: 'java/how-to/deploy-process', + label: 'Deploy process', + }, + ], + }, + { + type: 'category', + label: 'Maintenance & Monitoring', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'java/how-to/logs', + label: 'Setup & access logs', + }, + { + type: 'doc', + id: 'java/how-to/filebrowser', + label: 'Browse container files', + }, + { + type: 'doc', + id: 'java/how-to/shared-storage', + label: 'Connect / disconnect shared storage', + }, + ], + }, + ], + nginx: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'nginx/overview', + label: 'Nginx Static', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'nginx', + }, + }, + { + type: 'category', + label: 'Management', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'nginx/how-to/create', + label: 'Create Nginx static service', + }, + { + type: 'doc', + id: 'nginx/how-to/upgrade', + label: 'Upgrade Nginx service', + }, + { + type: 'doc', + id: 'nginx/how-to/controls', + label: 'Stop, start & delete Nginx static service', + }, + ], + }, + { + type: 'category', + label: 'Configuration & Environment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'nginx/how-to/env-variables', + label: 'Manage environment variables', + }, + { + type: 'doc', + id: 'nginx/how-to/customize-runtime', + label: 'Customize Nginx static runtime', + }, + { + type: 'doc', + id: 'nginx/how-to/customize-web-server', + label: 'Customize web server', + }, + { + type: 'doc', + id: 'nginx/how-to/scaling', + label: 'Scale Nginx static service', + }, + ], + }, + { + type: 'category', + label: 'Build & Deployment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'nginx/how-to/build-pipeline', + label: 'Configure build & deploy pipeline', + }, + { + type: 'doc', + id: 'nginx/how-to/trigger-pipeline', + label: 'Trigger build pipeline', + }, + { + type: 'doc', + id: 'nginx/how-to/deploy-process', + label: 'Deploy process', + }, + ], + }, + { + type: 'category', + label: 'Maintenance & Monitoring', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'nginx/how-to/logs', + label: 'Setup & access logs', + }, + { + type: 'doc', + id: 'nginx/how-to/filebrowser', + label: 'Browse container files', + }, + { + type: 'doc', + id: 'nginx/how-to/shared-storage', + label: 'Connect / disconnect shared storage', + }, + ], + }, + { + type: 'doc', + id: 'nginx/faq', + label: 'FAQ', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'chat-bubble-left-right', + }, + }, + ], + static: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'static/overview', + label: 'Static Service', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'computer-desktop', + }, + }, + ], + ubuntu: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'ubuntu/overview', + label: 'Ubuntu', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'ubuntu', + }, + }, + { + type: 'category', + label: 'Management', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'ubuntu/how-to/create', + label: 'Create Ubuntu service', + }, + { + type: 'doc', + id: 'ubuntu/how-to/upgrade', + label: 'Upgrade Ubuntu service', + }, + { + type: 'doc', + id: 'ubuntu/how-to/controls', + label: 'Stop, start & delete Ubuntu runtime service', + }, + ], + }, + { + type: 'category', + label: 'Configuration & Environment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'ubuntu/how-to/env-variables', + label: 'Manage environment variables', + }, + { + type: 'doc', + id: 'ubuntu/how-to/customize-runtime', + label: 'Customize Ubuntu runtime', + }, + { + type: 'doc', + id: 'ubuntu/how-to/scaling', + label: 'Scale Ubuntu runtime service', + }, + ], + }, + { + type: 'category', + label: 'Build & Deployment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'ubuntu/how-to/build-pipeline', + label: 'Configure build & deploy pipeline', + }, + { + type: 'doc', + id: 'ubuntu/how-to/trigger-pipeline', + label: 'Trigger build pipeline', + }, + { + type: 'doc', + id: 'ubuntu/how-to/build-process', + label: 'Build process', + }, + { + type: 'doc', + id: 'ubuntu/how-to/deploy-process', + label: 'Deploy process', + }, + ], + }, + { + type: 'category', + label: 'Maintenance & Monitoring', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'ubuntu/how-to/logs', + label: 'Setup & access logs', + }, + { + type: 'doc', + id: 'ubuntu/how-to/filebrowser', + label: 'Browse container files', + }, + { + type: 'doc', + id: 'ubuntu/how-to/shared-storage', + label: 'Connect / disconnect shared storage', + }, + ], + }, + ], + alpine: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'alpine/overview', + label: 'Alpine', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'alpine', + }, + }, + { + type: 'category', + label: 'Management', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'alpine/how-to/create', + label: 'Create Alpine service', + }, + { + type: 'doc', + id: 'alpine/how-to/upgrade', + label: 'Upgrade Alpine service', + }, + { + type: 'doc', + id: 'alpine/how-to/controls', + label: 'Stop, start & delete Alpine runtime service', + }, + ], + }, + { + type: 'category', + label: 'Configuration & Environment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'alpine/how-to/env-variables', + label: 'Manage environment variables', + }, + { + type: 'doc', + id: 'alpine/how-to/customize-runtime', + label: 'Customize Alpine runtime', + }, + { + type: 'doc', + id: 'alpine/how-to/scaling', + label: 'Scale Alpine runtime service', + }, + ], + }, + { + type: 'category', + label: 'Build & Deployment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'alpine/how-to/build-pipeline', + label: 'Configure build & deploy pipeline', + }, + { + type: 'doc', + id: 'alpine/how-to/trigger-pipeline', + label: 'Trigger build pipeline', + }, + { + type: 'doc', + id: 'alpine/how-to/build-process', + label: 'Build process', + }, + { + type: 'doc', + id: 'alpine/how-to/deploy-process', + label: 'Deploy process', + }, + ], + }, + { + type: 'category', + label: 'Maintenance & Monitoring', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'alpine/how-to/logs', + label: 'Setup & access logs', + }, + { + type: 'doc', + id: 'alpine/how-to/filebrowser', + label: 'Browse container files', + }, + { + type: 'doc', + id: 'alpine/how-to/shared-storage', + label: 'Connect / disconnect shared storage', + }, + ], + }, + ], + docker: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'docker/overview', + label: 'Docker Service', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'docker', + }, + }, + ], + mariadb: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'mariadb/overview', + label: 'Zerops MariaDB Service', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'mariadb', + }, + }, + { + type: 'category', + label: 'How-to', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'mariadb/how-to/create', + label: 'Create MariaDB service', + }, + { + type: 'doc', + id: 'mariadb/how-to/connect', + label: 'Connect to MariaDB', + }, + { + type: 'doc', + id: 'mariadb/how-to/manage', + label: 'Manage users and databases', + }, + { + type: 'doc', + id: 'mariadb/how-to/export-import-data', + label: 'Export and import data', + }, + { + type: 'doc', + id: 'mariadb/how-to/backup', + label: 'Backup & restore data', + }, + { + type: 'doc', + id: 'mariadb/how-to/scale', + label: 'Scale MariaDB service', + }, + { + type: 'doc', + id: 'mariadb/how-to/control', + label: 'Stop, start and delete MariaDB service', + }, + ], + }, + { + type: 'category', + label: 'Technical details', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'mariadb/tech-details/cluster', + label: 'MariaDB cluster asynchronous behaviour', + }, + { + type: 'doc', + id: 'mariadb/tech-details/limitations', + label: 'Technical limitations of MariaDB cluster', + }, + ], + }, + // { + // type: 'doc', + // id: 'mariadb/faq', + // label: 'FAQ', + // customProps: { + // sidebar_is_title: true, + // sidebar_icon: 'chat-bubble-left-right', + // }, + // }, + ], + postgresql: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'postgresql/overview', + label: 'Zerops PostgreSQL Service', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'postgresql', + }, + }, + { + type: 'category', + label: 'How-to', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'postgresql/how-to/create', + label: 'Create PostgreSQL service', }, { - type: 'category', - label: 'Configuration & Environment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'ubuntu/how-to/env-variables', - label: 'Manage environment variables', - }, - { - type: 'doc', - id: 'ubuntu/how-to/customize-runtime', - label: 'Customize Ubuntu runtime', - }, - { - type: 'doc', - id: 'ubuntu/how-to/scaling', - label: 'Scale Ubuntu runtime service', - }, - ], + type: 'doc', + id: 'postgresql/how-to/connect', + label: 'Connect to PostgreSQL', }, { - type: 'category', - label: 'Build & Deployment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'ubuntu/how-to/build-pipeline', - label: 'Configure build & deploy pipeline', - }, - { - type: 'doc', - id: 'ubuntu/how-to/trigger-pipeline', - label: 'Trigger build pipeline', - }, - { - type: 'doc', - id: 'ubuntu/how-to/build-process', - label: 'Build process', - }, - { - type: 'doc', - id: 'ubuntu/how-to/deploy-process', - label: 'Deploy process', - }, - ], + type: 'doc', + id: 'postgresql/how-to/manage', + label: 'Manage users, databases & plugins', }, { - type: 'category', - label: 'Maintenance & Monitoring', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'ubuntu/how-to/logs', - label: 'Setup & access logs', - }, - { - type: 'doc', - id: 'ubuntu/how-to/filebrowser', - label: 'Browse container files', - }, - { - type: 'doc', - id: 'ubuntu/how-to/shared-storage', - label: 'Connect / disconnect shared storage', - }, - ], + type: 'doc', + id: 'postgresql/how-to/export-import-data', + label: 'Export and import data', + }, + { + type: 'doc', + id: 'postgresql/how-to/backup', + label: 'Backup data', + }, + { + type: 'doc', + id: 'postgresql/how-to/scale', + label: 'Scale PostgreSQL service', + }, + { + type: 'doc', + id: 'postgresql/how-to/control', + label: 'Stop, start and delete PostgreSQL service', }, ], - alpine: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', - }, - }, - { - type: 'doc', - id: 'alpine/overview', - label: 'Alpine', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'alpine', - }, - }, - { - type: 'category', - label: 'Management', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'alpine/how-to/create', - label: 'Create Alpine service', - }, - { - type: 'doc', - id: 'alpine/how-to/upgrade', - label: 'Upgrade Alpine service', - }, - { - type: 'doc', - id: 'alpine/how-to/controls', - label: 'Stop, start & delete Alpine runtime service', - }, - ], - }, - { - type: 'category', - label: 'Configuration & Environment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'alpine/how-to/env-variables', - label: 'Manage environment variables', - }, - { - type: 'doc', - id: 'alpine/how-to/customize-runtime', - label: 'Customize Alpine runtime', - }, - { - type: 'doc', - id: 'alpine/how-to/scaling', - label: 'Scale Alpine runtime service', - }, - ], - }, - { - type: 'category', - label: 'Build & Deployment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'alpine/how-to/build-pipeline', - label: 'Configure build & deploy pipeline', - }, - { - type: 'doc', - id: 'alpine/how-to/trigger-pipeline', - label: 'Trigger build pipeline', - }, - { - type: 'doc', - id: 'alpine/how-to/build-process', - label: 'Build process', - }, - { - type: 'doc', - id: 'alpine/how-to/deploy-process', - label: 'Deploy process', - }, - ], - }, - { - type: 'category', - label: 'Maintenance & Monitoring', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'alpine/how-to/logs', - label: 'Setup & access logs', - }, - { - type: 'doc', - id: 'alpine/how-to/filebrowser', - label: 'Browse container files', - }, - { - type: 'doc', - id: 'alpine/how-to/shared-storage', - label: 'Connect / disconnect shared storage', - }, - ], - }, - ], - docker: [ + }, + // { + // type: "category", + // label: "Technical details", + // collapsible: false, + // customProps: { + // sidebar_is_group_headline: true, + // }, + // items: [ + // { + // type: "doc", + // id: "postgresql/tech-details/cluster", + // label: "PostgreSQL cluster asynchronous behaviour", + // }, + // { + // type: "doc", + // id: "postgresql/tech-details/limitations", + // label: "Technical limitations of PostgreSQL cluster", + // }, + // ], + // }, + { + type: 'doc', + id: 'postgresql/faq', + label: 'FAQ', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'chat-bubble-left-right', + }, + }, + ], + elasticsearch: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'elasticsearch/overview', + label: 'Zerops Elasticsearch Service', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'elasticsearch', + }, + }, + ], + keydb: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'keydb/overview', + label: 'Zerops KeyDB Service', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'keydb', + }, + }, + { + type: 'category', + label: 'How-to', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'keydb/how-to/create', + label: 'Create KeyDB service', + }, + { + type: 'doc', + id: 'keydb/how-to/connect', + label: 'Connect to KeyDB', + }, + { + type: 'doc', + id: 'keydb/how-to/manage', + label: 'Manage users and databases', + }, + { + type: 'doc', + id: 'keydb/how-to/scale', + label: 'Scale KeyDB service', + }, + { + type: 'doc', + id: 'keydb/how-to/control', + label: 'Stop, start and delete KeyDB service', + }, + ], + }, + // { + // type: 'doc', + // id: 'keydb/faq', + // label: 'FAQ', + // customProps: { + // sidebar_is_title: true, + // sidebar_icon: 'chat-bubble-left-right', + // }, + // }, + ], + typesense: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'typesense/overview', + label: 'Zerops Typesense Service', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'typesense', + }, + }, + ], + meilisearch: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'meilisearch/overview', + label: 'Zerops Meilisearch Service', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'meilisearch', + }, + }, + ], + valkey: [ { type: 'ref', id: 'homepage', @@ -2143,15 +2635,15 @@ module.exports = { }, { type: 'doc', - id: 'docker/overview', - label: 'Docker Service', + id: 'valkey/overview', + label: 'Zerops Valkey Service', customProps: { sidebar_is_title: true, - sidebar_icon: 'docker', + sidebar_icon: 'valkey', }, }, ], - mariadb: [ + qdrant: [ { type: 'ref', id: 'homepage', @@ -2163,89 +2655,35 @@ module.exports = { }, { type: 'doc', - id: 'mariadb/overview', - label: 'Zerops MariaDB Service', + id: 'qdrant/overview', + label: 'Zerops Qdrant Service', customProps: { sidebar_is_title: true, - sidebar_icon: 'mariadb', + sidebar_icon: 'qdrant', }, }, + ], + nats: [ { - type: 'category', - label: 'How-to', - collapsible: false, + type: 'ref', + id: 'homepage', + label: 'Back to home', customProps: { - sidebar_is_group_headline: true, + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', }, - items: [ - { - type: 'doc', - id: 'mariadb/how-to/create', - label: 'Create MariaDB service', - }, - { - type: 'doc', - id: 'mariadb/how-to/connect', - label: 'Connect to MariaDB', - }, - { - type: 'doc', - id: 'mariadb/how-to/manage', - label: 'Manage users and databases', - }, - { - type: 'doc', - id: 'mariadb/how-to/export-import-data', - label: 'Export and import data', - }, - { - type: 'doc', - id: 'mariadb/how-to/backup', - label: 'Backup & restore data', - }, - { - type: 'doc', - id: 'mariadb/how-to/scale', - label: 'Scale MariaDB service', - }, - { - type: 'doc', - id: 'mariadb/how-to/control', - label: 'Stop, start and delete MariaDB service', - }, - ], }, { - type: 'category', - label: 'Technical details', - collapsible: false, + type: 'doc', + id: 'nats/overview', + label: 'Zerops NATS Service', customProps: { - sidebar_is_group_headline: true, + sidebar_is_title: true, + sidebar_icon: 'nats', }, - items: [ - { - type: 'doc', - id: 'mariadb/tech-details/cluster', - label: 'MariaDB cluster asynchronous behaviour', - }, - { - type: 'doc', - id: 'mariadb/tech-details/limitations', - label: 'Technical limitations of MariaDB cluster', - }, - ], }, - // { - // type: 'doc', - // id: 'mariadb/faq', - // label: 'FAQ', - // customProps: { - // sidebar_is_title: true, - // sidebar_icon: 'chat-bubble-left-right', - // }, - // }, ], - postgresql: [ + kafka: [ { type: 'ref', id: 'homepage', @@ -2257,11 +2695,51 @@ module.exports = { }, { type: 'doc', - id: 'postgresql/overview', - label: 'Zerops PostgreSQL Service', + id: 'kafka/overview', + label: 'Zerops Kafka Service', customProps: { sidebar_is_title: true, - sidebar_icon: 'postgresql', + sidebar_icon: 'kafka', + }, + }, + ], + clickhouse: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'clickhouse/overview', + label: 'Zerops Clickhouse Service', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'clickhouse', + }, + }, + ], + sharedstorage: [ + { + type: 'ref', + id: 'homepage', + label: 'Back to home', + customProps: { + sidebar_is_back_link: true, + sidebar_icon: 'back-arrow', + }, + }, + { + type: 'doc', + id: 'shared-storage/overview', + label: 'Shared storage overview', + customProps: { + sidebar_is_title: true, + sidebar_icon: 'server', }, }, { @@ -2270,76 +2748,47 @@ module.exports = { collapsible: false, customProps: { sidebar_is_group_headline: true, + sidebar_icon: 'academic-cap-solid', }, items: [ { type: 'doc', - id: 'postgresql/how-to/create', - label: 'Create PostgreSQL service', - }, - { - type: 'doc', - id: 'postgresql/how-to/connect', - label: 'Connect to PostgreSQL', - }, - { - type: 'doc', - id: 'postgresql/how-to/manage', - label: 'Manage users, databases & plugins', + id: 'shared-storage/how-to/create', + label: 'Create shared storage', }, { type: 'doc', - id: 'postgresql/how-to/export-import-data', - label: 'Export and import data', + id: 'shared-storage/how-to/connect', + label: 'Connect shared storage', }, { type: 'doc', - id: 'postgresql/how-to/backup', - label: 'Backup data', + id: 'shared-storage/how-to/use', + label: 'Usage & Limitations', }, { type: 'doc', - id: 'postgresql/how-to/scale', - label: 'Scale PostgreSQL service', + id: 'shared-storage/how-to/manage', + label: 'Manage & Access shared storage', }, { type: 'doc', - id: 'postgresql/how-to/control', - label: 'Stop, start and delete PostgreSQL service', + id: 'shared-storage/how-to/backup', + label: 'Backup shared storage', }, ], }, - // { - // type: "category", - // label: "Technical details", - // collapsible: false, - // customProps: { - // sidebar_is_group_headline: true, - // }, - // items: [ - // { - // type: "doc", - // id: "postgresql/tech-details/cluster", - // label: "PostgreSQL cluster asynchronous behaviour", - // }, - // { - // type: "doc", - // id: "postgresql/tech-details/limitations", - // label: "Technical limitations of PostgreSQL cluster", - // }, - // ], - // }, { type: 'doc', - id: 'postgresql/faq', - label: 'FAQ', + id: 'shared-storage/tech-details', + label: 'Technical details', customProps: { sidebar_is_title: true, - sidebar_icon: 'chat-bubble-left-right', + sidebar_icon: 'document-text', }, }, ], - elasticsearch: [ + objectstorage: [ { type: 'ref', id: 'homepage', @@ -2351,15 +2800,51 @@ module.exports = { }, { type: 'doc', - id: 'elasticsearch/overview', - label: 'Zerops Elasticsearch Service', + id: 'object-storage/overview', + label: 'Object Storage Overview', customProps: { sidebar_is_title: true, - sidebar_icon: 'elasticsearch', + sidebar_icon: 'server', + }, + }, + { + type: 'category', + label: 'How-to', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + sidebar_icon: 'academic-cap-solid', }, + items: [ + { + type: 'doc', + id: 'object-storage/how-to/create', + label: 'Create object storage service', + }, + { + type: 'doc', + id: 'object-storage/how-to/update-bucket', + label: 'Change bucket size or access policy', + }, + { + type: 'doc', + id: 'object-storage/how-to/access', + label: 'Access object storage service', + }, + { + type: 'doc', + id: 'object-storage/how-to/controls', + label: 'Stop, start & delete object storage service', + }, + { + type: 'doc', + id: 'object-storage/how-to/curl-file', + label: 'Download file from a private bucket', + }, + ], }, ], - keydb: [ + deno: [ { type: 'ref', id: 'homepage', @@ -2371,16 +2856,16 @@ module.exports = { }, { type: 'doc', - id: 'keydb/overview', - label: 'Zerops KeyDB Service', + id: 'deno/overview', + label: 'Deno', customProps: { sidebar_is_title: true, - sidebar_icon: 'keydb', + sidebar_icon: 'deno', }, }, { type: 'category', - label: 'How-to', + label: 'Management', collapsible: false, customProps: { sidebar_is_group_headline: true, @@ -2388,62 +2873,103 @@ module.exports = { items: [ { type: 'doc', - id: 'keydb/how-to/create', - label: 'Create KeyDB service', + id: 'deno/how-to/create', + label: 'Create Deno service', }, { type: 'doc', - id: 'keydb/how-to/connect', - label: 'Connect to KeyDB', + id: 'deno/how-to/upgrade', + label: 'Upgrade Deno service', }, { type: 'doc', - id: 'keydb/how-to/manage', - label: 'Manage users and databases', + id: 'deno/how-to/controls', + label: 'Stop, start & delete Deno runtime service', + }, + ], + }, + { + type: 'category', + label: 'Configuration & Environment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'deno/how-to/env-variables', + label: 'Manage environment variables', }, { type: 'doc', - id: 'keydb/how-to/scale', - label: 'Scale KeyDB service', + id: 'deno/how-to/customize-runtime', + label: 'Customize Deno runtime', }, { type: 'doc', - id: 'keydb/how-to/control', - label: 'Stop, start and delete KeyDB service', + id: 'deno/how-to/scaling', + label: 'Scale Deno runtime service', }, ], }, - // { - // type: 'doc', - // id: 'keydb/faq', - // label: 'FAQ', - // customProps: { - // sidebar_is_title: true, - // sidebar_icon: 'chat-bubble-left-right', - // }, - // }, - ], - typesense: [ { - type: 'ref', - id: 'homepage', - label: 'Back to home', + type: 'category', + label: 'Build & Deployment', + collapsible: false, customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', + sidebar_is_group_headline: true, }, + items: [ + { + type: 'doc', + id: 'deno/how-to/build-pipeline', + label: 'Configure build & deploy pipeline', + }, + { + type: 'doc', + id: 'deno/how-to/trigger-pipeline', + label: 'Trigger build pipeline', + }, + { + type: 'doc', + id: 'deno/how-to/build-process', + label: 'Build process', + }, + { + type: 'doc', + id: 'deno/how-to/deploy-process', + label: 'Deploy process', + }, + ], }, { - type: 'doc', - id: 'typesense/overview', - label: 'Zerops Typesense Service', + type: 'category', + label: 'Maintenance & Monitoring', + collapsible: false, customProps: { - sidebar_is_title: true, - sidebar_icon: 'typesense', + sidebar_is_group_headline: true, }, + items: [ + { + type: 'doc', + id: 'deno/how-to/logs', + label: 'Setup & access logs', + }, + { + type: 'doc', + id: 'deno/how-to/filebrowser', + label: 'Browse container files', + }, + { + type: 'doc', + id: 'deno/how-to/shared-storage', + label: 'Connect / disconnect shared storage', + }, + ], }, ], - meilisearch: [ + bun: [ { type: 'ref', id: 'homepage', @@ -2455,55 +2981,120 @@ module.exports = { }, { type: 'doc', - id: 'meilisearch/overview', - label: 'Zerops Meilisearch Service', + id: 'bun/overview', + label: 'Bun', customProps: { sidebar_is_title: true, - sidebar_icon: 'meilisearch', + sidebar_icon: 'bun', }, }, - ], - valkey: [ { - type: 'ref', - id: 'homepage', - label: 'Back to home', + type: 'category', + label: 'Management', + collapsible: false, customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', + sidebar_is_group_headline: true, }, + items: [ + { + type: 'doc', + id: 'bun/how-to/create', + label: 'Create Bun service', + }, + { + type: 'doc', + id: 'bun/how-to/upgrade', + label: 'Upgrade Bun service', + }, + { + type: 'doc', + id: 'bun/how-to/controls', + label: 'Stop, start & delete Bun runtime service', + }, + ], }, { - type: 'doc', - id: 'valkey/overview', - label: 'Zerops Valkey Service', + type: 'category', + label: 'Configuration & Environment', + collapsible: false, customProps: { - sidebar_is_title: true, - sidebar_icon: 'valkey', + sidebar_is_group_headline: true, }, + items: [ + { + type: 'doc', + id: 'bun/how-to/env-variables', + label: 'Manage environment variables', + }, + { + type: 'doc', + id: 'bun/how-to/customize-runtime', + label: 'Customize Bun runtime', + }, + { + type: 'doc', + id: 'bun/how-to/scaling', + label: 'Scale Bun runtime service', + }, + ], }, - ], - qdrant: [ { - type: 'ref', - id: 'homepage', - label: 'Back to home', + type: 'category', + label: 'Build & Deployment', + collapsible: false, customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', + sidebar_is_group_headline: true, }, + items: [ + { + type: 'doc', + id: 'bun/how-to/build-pipeline', + label: 'Configure build & deploy pipeline', + }, + { + type: 'doc', + id: 'bun/how-to/trigger-pipeline', + label: 'Trigger build pipeline', + }, + { + type: 'doc', + id: 'bun/how-to/build-process', + label: 'Build process', + }, + { + type: 'doc', + id: 'bun/how-to/deploy-process', + label: 'Deploy process', + }, + ], }, { - type: 'doc', - id: 'qdrant/overview', - label: 'Zerops Qdrant Service', + type: 'category', + label: 'Maintenance & Monitoring', + collapsible: false, customProps: { - sidebar_is_title: true, - sidebar_icon: 'qdrant', + sidebar_is_group_headline: true, }, + items: [ + { + type: 'doc', + id: 'bun/how-to/logs', + label: 'Setup & access logs', + }, + { + type: 'doc', + id: 'bun/how-to/filebrowser', + label: 'Browse container files', + }, + { + type: 'doc', + id: 'bun/how-to/shared-storage', + label: 'Connect / disconnect shared storage', + }, + ], }, ], - nats: [ + gleam: [ { type: 'ref', id: 'homepage', @@ -2515,120 +3106,120 @@ module.exports = { }, { type: 'doc', - id: 'nats/overview', - label: 'Zerops NATS Service', + id: 'gleam/overview', + label: 'Gleam', customProps: { sidebar_is_title: true, - sidebar_icon: 'nats', - }, - }, - ], - kafka: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', + sidebar_icon: 'gleam', }, }, { - type: 'doc', - id: 'kafka/overview', - label: 'Zerops Kafka Service', + type: 'category', + label: 'Management', + collapsible: false, customProps: { - sidebar_is_title: true, - sidebar_icon: 'kafka', + sidebar_is_group_headline: true, }, - }, - ], - clickhouse: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', + items: [ + { + type: 'doc', + id: 'gleam/how-to/create', + label: 'Create Gleam service', }, - }, - { - type: 'doc', - id: 'clickhouse/overview', - label: 'Zerops Clickhouse Service', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'clickhouse', + { + type: 'doc', + id: 'gleam/how-to/upgrade', + label: 'Upgrade Gleam service', }, - }, - ], - sharedstorage: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', - }, + { + type: 'doc', + id: 'gleam/how-to/controls', + label: 'Stop, start & delete Gleam runtime service', + }, + ], }, { - type: 'doc', - id: 'shared-storage/overview', - label: 'Shared storage overview', + type: 'category', + label: 'Configuration & Environment', + collapsible: false, customProps: { - sidebar_is_title: true, - sidebar_icon: 'server', + sidebar_is_group_headline: true, }, + items: [ + { + type: 'doc', + id: 'gleam/how-to/env-variables', + label: 'Manage environment variables', + }, + { + type: 'doc', + id: 'gleam/how-to/customize-runtime', + label: 'Customize Gleam runtime', + }, + { + type: 'doc', + id: 'gleam/how-to/scaling', + label: 'Scale Gleam runtime service', + }, + ], }, { type: 'category', - label: 'How-to', + label: 'Build & Deployment', collapsible: false, customProps: { sidebar_is_group_headline: true, - sidebar_icon: 'academic-cap-solid', }, items: [ { type: 'doc', - id: 'shared-storage/how-to/create', - label: 'Create shared storage', - }, - { - type: 'doc', - id: 'shared-storage/how-to/connect', - label: 'Connect shared storage', + id: 'gleam/how-to/build-pipeline', + label: 'Configure build & deploy pipeline', }, { type: 'doc', - id: 'shared-storage/how-to/use', - label: 'Usage & Limitations', + id: 'gleam/how-to/trigger-pipeline', + label: 'Trigger build pipeline', }, { type: 'doc', - id: 'shared-storage/how-to/manage', - label: 'Manage & Access shared storage', + id: 'gleam/how-to/build-process', + label: 'Build process', }, { type: 'doc', - id: 'shared-storage/how-to/backup', - label: 'Backup shared storage', + id: 'gleam/how-to/deploy-process', + label: 'Deploy process', }, ], }, { - type: 'doc', - id: 'shared-storage/tech-details', - label: 'Technical details', + type: 'category', + label: 'Maintenance & Monitoring', + collapsible: false, customProps: { - sidebar_is_title: true, - sidebar_icon: 'document-text', + sidebar_is_group_headline: true, }, + items: [ + { + type: 'doc', + id: 'gleam/how-to/logs', + label: 'Setup & access logs', + }, + { + type: 'doc', + id: 'gleam/how-to/filebrowser', + label: 'Browse container files', + }, + { + type: 'doc', + id: 'gleam/how-to/shared-storage', + label: 'Connect / disconnect shared storage', + }, + ], }, ], - objectstorage: [ + elixir: [ { type: 'ref', id: 'homepage', @@ -2640,738 +3231,307 @@ module.exports = { }, { type: 'doc', - id: 'object-storage/overview', - label: 'Object Storage Overview', + id: 'elixir/overview', + label: 'Elixir', customProps: { sidebar_is_title: true, - sidebar_icon: 'server', + sidebar_icon: 'elixir', }, }, { type: 'category', - label: 'How-to', + label: 'Management', collapsible: false, customProps: { sidebar_is_group_headline: true, - sidebar_icon: 'academic-cap-solid', }, items: [ { type: 'doc', - id: 'object-storage/how-to/create', - label: 'Create object storage service', + id: 'elixir/how-to/create', + label: 'Create Elixir service', }, { type: 'doc', - id: 'object-storage/how-to/update-bucket', - label: 'Change bucket size or access policy', + id: 'elixir/how-to/upgrade', + label: 'Upgrade Elixir service', }, { type: 'doc', - id: 'object-storage/how-to/access', - label: 'Access object storage service', + id: 'elixir/how-to/controls', + label: 'Stop, start & delete Elixir runtime service', + }, + ], + }, + { + type: 'category', + label: 'Configuration & Environment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: 'doc', + id: 'elixir/how-to/env-variables', + label: 'Manage environment variables', }, { type: 'doc', - id: 'object-storage/how-to/controls', - label: 'Stop, start & delete object storage service', + id: 'elixir/how-to/customize-runtime', + label: 'Customize Elixir runtime', }, { type: 'doc', - id: 'object-storage/how-to/curl-file', - label: 'Download file from a private bucket', + id: 'elixir/how-to/scaling', + label: 'Scale Elixir runtime service', }, ], }, - ], - deno: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', - }, - }, - { - type: 'doc', - id: 'deno/overview', - label: 'Deno', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'deno', - }, - }, - { - type: 'category', - label: 'Management', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'deno/how-to/create', - label: 'Create Deno service', - }, - { - type: 'doc', - id: 'deno/how-to/upgrade', - label: 'Upgrade Deno service', - }, - { - type: 'doc', - id: 'deno/how-to/controls', - label: 'Stop, start & delete Deno runtime service', - }, - ], - }, - { - type: 'category', - label: 'Configuration & Environment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'deno/how-to/env-variables', - label: 'Manage environment variables', - }, - { - type: 'doc', - id: 'deno/how-to/customize-runtime', - label: 'Customize Deno runtime', - }, - { - type: 'doc', - id: 'deno/how-to/scaling', - label: 'Scale Deno runtime service', - }, - ], - }, - { - type: 'category', - label: 'Build & Deployment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'deno/how-to/build-pipeline', - label: 'Configure build & deploy pipeline', - }, - { - type: 'doc', - id: 'deno/how-to/trigger-pipeline', - label: 'Trigger build pipeline', - }, - { - type: 'doc', - id: 'deno/how-to/build-process', - label: 'Build process', - }, - { - type: 'doc', - id: 'deno/how-to/deploy-process', - label: 'Deploy process', - }, - ], - }, - { - type: 'category', - label: 'Maintenance & Monitoring', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'deno/how-to/logs', - label: 'Setup & access logs', - }, - { - type: 'doc', - id: 'deno/how-to/filebrowser', - label: 'Browse container files', - }, - { - type: 'doc', - id: 'deno/how-to/shared-storage', - label: 'Connect / disconnect shared storage', - }, - ], - }, - ], - bun: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', - }, - }, - { - type: 'doc', - id: 'bun/overview', - label: 'Bun', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'bun', - }, - }, - { - type: 'category', - label: 'Management', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'bun/how-to/create', - label: 'Create Bun service', - }, - { - type: 'doc', - id: 'bun/how-to/upgrade', - label: 'Upgrade Bun service', - }, - { - type: 'doc', - id: 'bun/how-to/controls', - label: 'Stop, start & delete Bun runtime service', - }, - ], - }, - { - type: 'category', - label: 'Configuration & Environment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'bun/how-to/env-variables', - label: 'Manage environment variables', - }, - { - type: 'doc', - id: 'bun/how-to/customize-runtime', - label: 'Customize Bun runtime', - }, - { - type: 'doc', - id: 'bun/how-to/scaling', - label: 'Scale Bun runtime service', - }, - ], - }, - { - type: 'category', - label: 'Build & Deployment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'bun/how-to/build-pipeline', - label: 'Configure build & deploy pipeline', - }, - { - type: 'doc', - id: 'bun/how-to/trigger-pipeline', - label: 'Trigger build pipeline', - }, - { - type: 'doc', - id: 'bun/how-to/build-process', - label: 'Build process', - }, - { - type: 'doc', - id: 'bun/how-to/deploy-process', - label: 'Deploy process', - }, - ], - }, - { - type: 'category', - label: 'Maintenance & Monitoring', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'bun/how-to/logs', - label: 'Setup & access logs', - }, - { - type: 'doc', - id: 'bun/how-to/filebrowser', - label: 'Browse container files', - }, - { - type: 'doc', - id: 'bun/how-to/shared-storage', - label: 'Connect / disconnect shared storage', - }, - ], - }, - ], - gleam: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', - }, - }, - { - type: 'doc', - id: 'gleam/overview', - label: 'Gleam', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'gleam', - }, - }, - { - type: 'category', - label: 'Management', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'gleam/how-to/create', - label: 'Create Gleam service', - }, - { - type: 'doc', - id: 'gleam/how-to/upgrade', - label: 'Upgrade Gleam service', - }, - { - type: 'doc', - id: 'gleam/how-to/controls', - label: 'Stop, start & delete Gleam runtime service', - }, - ], + { + type: 'category', + label: 'Build & Deployment', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, }, - { - type: 'category', - label: 'Configuration & Environment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + items: [ + { + type: 'doc', + id: 'elixir/how-to/build-pipeline', + label: 'Configure build & deploy pipeline', }, - items: [ - { - type: 'doc', - id: 'gleam/how-to/env-variables', - label: 'Manage environment variables', - }, - { - type: 'doc', - id: 'gleam/how-to/customize-runtime', - label: 'Customize Gleam runtime', - }, - { - type: 'doc', - id: 'gleam/how-to/scaling', - label: 'Scale Gleam runtime service', - }, - ], - }, - { - type: 'category', - label: 'Build & Deployment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'elixir/how-to/trigger-pipeline', + label: 'Trigger build pipeline', }, - items: [ - { - type: 'doc', - id: 'gleam/how-to/build-pipeline', - label: 'Configure build & deploy pipeline', - }, - { - type: 'doc', - id: 'gleam/how-to/trigger-pipeline', - label: 'Trigger build pipeline', - }, - { - type: 'doc', - id: 'gleam/how-to/build-process', - label: 'Build process', - }, - { - type: 'doc', - id: 'gleam/how-to/deploy-process', - label: 'Deploy process', - }, - ], - }, - { - type: 'category', - label: 'Maintenance & Monitoring', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'elixir/how-to/build-process', + label: 'Build process', }, - items: [ - { - type: 'doc', - id: 'gleam/how-to/logs', - label: 'Setup & access logs', - }, - { - type: 'doc', - id: 'gleam/how-to/filebrowser', - label: 'Browse container files', - }, - { - type: 'doc', - id: 'gleam/how-to/shared-storage', - label: 'Connect / disconnect shared storage', - }, - ], - }, - ], - elixir: [ - { - type: 'ref', - id: 'homepage', - label: 'Back to home', - customProps: { - sidebar_is_back_link: true, - sidebar_icon: 'back-arrow', + { + type: 'doc', + id: 'elixir/how-to/deploy-process', + label: 'Deploy process', }, + ], + }, + { + type: 'category', + label: 'Maintenance & Monitoring', + collapsible: false, + customProps: { + sidebar_is_group_headline: true, }, - { - type: 'doc', - id: 'elixir/overview', - label: 'Elixir', - customProps: { - sidebar_is_title: true, - sidebar_icon: 'elixir', - }, - }, - { - type: 'category', - label: 'Management', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, - }, - items: [ - { - type: 'doc', - id: 'elixir/how-to/create', - label: 'Create Elixir service', - }, - { - type: 'doc', - id: 'elixir/how-to/upgrade', - label: 'Upgrade Elixir service', - }, - { - type: 'doc', - id: 'elixir/how-to/controls', - label: 'Stop, start & delete Elixir runtime service', - }, - ], - }, - { - type: 'category', - label: 'Configuration & Environment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + items: [ + { + type: 'doc', + id: 'elixir/how-to/logs', + label: 'Setup & access logs', }, - items: [ - { - type: 'doc', - id: 'elixir/how-to/env-variables', - label: 'Manage environment variables', - }, - { - type: 'doc', - id: 'elixir/how-to/customize-runtime', - label: 'Customize Elixir runtime', - }, - { - type: 'doc', - id: 'elixir/how-to/scaling', - label: 'Scale Elixir runtime service', - }, - ], - }, - { - type: 'category', - label: 'Build & Deployment', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'elixir/how-to/filebrowser', + label: 'Browse container files', }, - items: [ - { - type: 'doc', - id: 'elixir/how-to/build-pipeline', - label: 'Configure build & deploy pipeline', - }, - { - type: 'doc', - id: 'elixir/how-to/trigger-pipeline', - label: 'Trigger build pipeline', - }, - { - type: 'doc', - id: 'elixir/how-to/build-process', - label: 'Build process', - }, - { - type: 'doc', - id: 'elixir/how-to/deploy-process', - label: 'Deploy process', - }, - ], - }, - { - type: 'category', - label: 'Maintenance & Monitoring', - collapsible: false, - customProps: { - sidebar_is_group_headline: true, + { + type: 'doc', + id: 'elixir/how-to/shared-storage', + label: 'Connect / disconnect shared storage', }, - items: [ - { - type: 'doc', - id: 'elixir/how-to/logs', - label: 'Setup & access logs', - }, - { - type: 'doc', - id: 'elixir/how-to/filebrowser', - label: 'Browse container files', - }, - { - type: 'doc', - id: 'elixir/how-to/shared-storage', - label: 'Connect / disconnect shared storage', - }, - ], - }, - ], -// laravel: [ -// { -// type: 'ref', -// id: 'homepage', -// label: 'Back to home', -// customProps: { -// sidebar_is_back_link: true, -// sidebar_icon: 'back-arrow', -// }, -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel', -// label: 'Laravel', -// customProps: { -// sidebar_is_title: true, -// sidebar_icon: 'laravel', -// }, -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/introduction', -// label: 'Quickstart Guide', -// customProps: { -// sidebar_icon: 'rocket-launch', -// }, -// }, -// { -// type: 'category', -// label: 'Features', -// collapsible: false, -// customProps: { -// sidebar_is_group_headline: true, -// }, -// items: [ -// { -// type: 'doc', -// id: 'frameworks/laravel/env-variables', -// label: 'Environment Variables', -// }, -//// { -//// type: 'doc', -//// id: 'frameworks/laravel/local-development', -//// label: 'Local Development', -//// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/migrations', -// label: 'Database Migrations', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/redis', -// label: 'Cache & Queue with Redis', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/cron', -// label: 'Schedule Jobs & CRON', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/smtp', -// label: 'SMTP Configuration', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/logs', -// label: 'Logs', -// }, -// ], -// }, -// { -// type: 'html', -// value: 'Recipes', -// customProps: { -// sidebar_is_group_divider: true, -// }, -// className: 'homepage-sidebar-item', -// }, -// { -// type: 'category', -// label: 'Minimal', -// collapsible: true, -// customProps: { -// sidebar_is_group_headline: true, -// }, -// items: [ -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/minimal-local', -// label: 'Local Development', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/minimal-devel', -// label: 'Stage Environment', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/minimal-prod', -// label: 'Production', -// }, -// ], -// }, -// { -// type: 'category', -// label: 'Jetstream', -// collapsible: true, -// customProps: { -// sidebar_is_group_headline: true, -// }, -// items: [ -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/jetstream-local', -// label: 'Local Development', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/jetstream-devel', -// label: 'Stage Environment', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/jetstream-prod', -// label: 'Production', -// }, -// ], -// }, -// { -// type: 'category', -// label: 'Filament', -// collapsible: true, -// customProps: { -// sidebar_is_group_headline: true, -// }, -// items: [ -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/filament-local', -// label: 'Local Development', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/filament-devel', -// label: 'Stage Environment', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/filament-prod', -// label: 'Production', -// }, -// ], -// }, -// { -// type: 'category', -// label: 'Twill CMS', -// collapsible: true, -// customProps: { -// sidebar_is_group_headline: true, -// }, -// items: [ -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/twill-local', -// label: 'Local Development', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/twill-devel', -// label: 'Stage Environment', -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/recipes/twill-prod', -// label: 'Production', -// }, -// ], -// }, -// { -// type: 'doc', -// id: 'frameworks/laravel/faq', -// label: 'FAQ', -// customProps: { -// sidebar_is_title: true, -// sidebar_icon: 'chat-bubble-left-right', -// }, -// }, -// ], -}; \ No newline at end of file + ], + }, + ], + // laravel: [ + // { + // type: 'ref', + // id: 'homepage', + // label: 'Back to home', + // customProps: { + // sidebar_is_back_link: true, + // sidebar_icon: 'back-arrow', + // }, + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel', + // label: 'Laravel', + // customProps: { + // sidebar_is_title: true, + // sidebar_icon: 'laravel', + // }, + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/introduction', + // label: 'Quickstart Guide', + // customProps: { + // sidebar_icon: 'rocket-launch', + // }, + // }, + // { + // type: 'category', + // label: 'Features', + // collapsible: false, + // customProps: { + // sidebar_is_group_headline: true, + // }, + // items: [ + // { + // type: 'doc', + // id: 'frameworks/laravel/env-variables', + // label: 'Environment Variables', + // }, + //// { + //// type: 'doc', + //// id: 'frameworks/laravel/local-development', + //// label: 'Local Development', + //// }, + // { + // type: 'doc', + // id: 'frameworks/laravel/migrations', + // label: 'Database Migrations', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/redis', + // label: 'Cache & Queue with Redis', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/cron', + // label: 'Schedule Jobs & CRON', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/smtp', + // label: 'SMTP Configuration', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/logs', + // label: 'Logs', + // }, + // ], + // }, + // { + // type: 'html', + // value: 'Recipes', + // customProps: { + // sidebar_is_group_divider: true, + // }, + // className: 'homepage-sidebar-item', + // }, + // { + // type: 'category', + // label: 'Minimal', + // collapsible: true, + // customProps: { + // sidebar_is_group_headline: true, + // }, + // items: [ + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/minimal-local', + // label: 'Local Development', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/minimal-devel', + // label: 'Stage Environment', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/minimal-prod', + // label: 'Production', + // }, + // ], + // }, + // { + // type: 'category', + // label: 'Jetstream', + // collapsible: true, + // customProps: { + // sidebar_is_group_headline: true, + // }, + // items: [ + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/jetstream-local', + // label: 'Local Development', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/jetstream-devel', + // label: 'Stage Environment', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/jetstream-prod', + // label: 'Production', + // }, + // ], + // }, + // { + // type: 'category', + // label: 'Filament', + // collapsible: true, + // customProps: { + // sidebar_is_group_headline: true, + // }, + // items: [ + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/filament-local', + // label: 'Local Development', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/filament-devel', + // label: 'Stage Environment', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/filament-prod', + // label: 'Production', + // }, + // ], + // }, + // { + // type: 'category', + // label: 'Twill CMS', + // collapsible: true, + // customProps: { + // sidebar_is_group_headline: true, + // }, + // items: [ + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/twill-local', + // label: 'Local Development', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/twill-devel', + // label: 'Stage Environment', + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/recipes/twill-prod', + // label: 'Production', + // }, + // ], + // }, + // { + // type: 'doc', + // id: 'frameworks/laravel/faq', + // label: 'FAQ', + // customProps: { + // sidebar_is_title: true, + // sidebar_icon: 'chat-bubble-left-right', + // }, + // }, + // ], +}; diff --git a/apps/docs/src/components/AsciiGraph/index.tsx b/apps/docs/src/components/AsciiGraph/index.tsx new file mode 100644 index 000000000..33d336e05 --- /dev/null +++ b/apps/docs/src/components/AsciiGraph/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +type AsciiGraphProps = { + children: React.ReactNode; + className?: string; + ariaLabel?: string; +}; + +const AsciiGraph: React.FC = ({ + children, + className, + ariaLabel, +}) => { + return ( +
+      {children}
+    
+ ); +}; + +export default AsciiGraph; diff --git a/apps/docs/src/components/CodingAgentsTopology/build.cjs b/apps/docs/src/components/CodingAgentsTopology/build.cjs new file mode 100644 index 000000000..2733b03eb --- /dev/null +++ b/apps/docs/src/components/CodingAgentsTopology/build.cjs @@ -0,0 +1,168 @@ +'use strict'; + +// Build functions for the ZCP topology diagram. Pure functions that produce +// a Grid (from grid.cjs). Consumed by: +// - the React component (index.tsx) for the interactive Remote/Local toggle +// - the markdown-source plugin to render the same ASCII into .md output + +const { + make, + text, + textCenter, + vline, + hline, + ch, + joint, + box, + verify, +} = require('./grid.cjs'); + +const W = 80; +const PROJECT_W = 62; + +function buildProject(g, mode) { + const yTop = 3; + const yBottom = 31; + + box(g, { x: 0, y: yTop, w: PROJECT_W, h: yBottom - yTop + 1 }); + textCenter(g, 31, yTop + 1, 'Zerops development project (private network)'); + + box(g, { + x: 2, y: 5, w: 19, h: 5, + lines: ['project ctrl', '+ L3 balancer', '+ firewall'], + }); + box(g, { x: 23, y: 5, w: 12, h: 5, lines: ['stats'], align: 'center' }); + box(g, { x: 37, y: 5, w: 12, h: 5, lines: ['logger'], align: 'center' }); + + box(g, { + x: 2, y: 11, w: 19, h: 4, + lines: ['L7 balancer', '(Nginx)'], + align: 'center', + }); + + joint(g, 11, 9, '┬'); + vline(g, 11, 10, 10); + joint(g, 11, 11, '┴'); + + joint(g, 11, 14, '┬'); + vline(g, 11, 15, 15); + joint(g, 11, 16, '├'); + + if (mode === 'remote') { + hline(g, 16, 12, 46); + joint(g, 29, 16, '┬'); + joint(g, 47, 16, '┐'); + } else { + hline(g, 16, 12, 28); + joint(g, 29, 16, '┐'); + } + + box(g, { + x: 4, y: 17, w: 15, h: 4, + lines: ['appdev:3000', 'dev runtime'], + }); + joint(g, 11, 17, '┴'); + joint(g, 11, 20, '┬'); + vline(g, 11, 21, 21); + + box(g, { + x: 21, y: 17, w: 17, h: 4, + lines: ['appstage:3000', 'stage runtime'], + }); + joint(g, 29, 17, '┴'); + joint(g, 29, 20, '┬'); + vline(g, 29, 21, 21); + + if (mode === 'remote') { + box(g, { + x: 40, y: 17, w: 15, h: 4, + lines: ['zcp service', 'agent + MCP'], + align: 'center', + hl: true, + }); + joint(g, 47, 17, '┴'); + joint(g, 47, 20, '┬'); + vline(g, 47, 21, 21); + textCenter(g, 47, 13, 'control plane'); + textCenter(g, 47, 14, 'ssh into other services'); + textCenter(g, 47, 15, 'dev runtime fs mounted'); + } + + joint(g, 11, 22, '└'); + if (mode === 'remote') { + hline(g, 22, 12, 46); + joint(g, 29, 22, '┼'); + joint(g, 47, 22, '┘'); + } else { + hline(g, 22, 12, 28); + joint(g, 20, 22, '┬'); + joint(g, 29, 22, '┘'); + } + + const downCol = mode === 'remote' ? 29 : 20; + vline(g, downCol, 23, 23); + + joint(g, 14, 24, '┌'); + hline(g, 24, 15, 41); + joint(g, downCol, 24, '┴'); + joint(g, 42, 24, '┐'); + + vline(g, 14, 25, 25); + vline(g, 42, 25, 25); + + box(g, { x: 7, y: 26, w: 15, h: 4, lines: ['db:5432', 'Postgres'] }); + joint(g, 14, 26, '┴'); + + box(g, { x: 35, y: 26, w: 15, h: 4, lines: ['cache:6379', 'Valkey'] }); + joint(g, 42, 26, '┴'); +} + +function buildRemote() { + const g = make(W, 32); + textCenter(g, 31, 0, 'https://my-app.com'); + vline(g, 31, 1, 1); + ch(g, 31, 2, '▼'); + buildProject(g, 'remote'); + verify(g); + return g; +} + +function buildLocal() { + const g = make(W, 44); + textCenter(g, 31, 0, 'https://my-app.com'); + vline(g, 31, 1, 1); + ch(g, 31, 2, '▼'); + buildProject(g, 'local'); + + joint(g, 31, 31, '▲'); + + vline(g, 31, 32, 34); + text(g, 33, 33, 'VPN tunnel'); + + box(g, { + x: 14, y: 35, w: 35, h: 9, + lines: [ + 'IDE / terminal', + '+ agent', + '+ zcp MCP', + '', + '$ curl zerops.io/zcp/install.sh', + '$ zcp init', + '$ zcli vpn up', + ], + align: 'left', + hl: true, + }); + joint(g, 31, 35, '┴'); + + verify(g); + return g; +} + +module.exports = { + buildProject, + buildRemote, + buildLocal, + W, + PROJECT_W, +}; diff --git a/apps/docs/src/components/CodingAgentsTopology/grid.cjs b/apps/docs/src/components/CodingAgentsTopology/grid.cjs new file mode 100644 index 000000000..ccc5eebdb --- /dev/null +++ b/apps/docs/src/components/CodingAgentsTopology/grid.cjs @@ -0,0 +1,149 @@ +'use strict'; + +// Pure grid utilities — used by the React component (CodingAgentsTopology, +// IntroAgentVisual) and by the markdown-source plugin to render the same +// ASCII art as plain text into the .md output. +// +// Mirrors the API of the original grid.tsx but returns plain strings rather +// than React nodes from `render`. The .tsx wrapper (`grid.tsx`) delegates +// here for the grid math and re-implements `render` to emit React nodes +// with `` highlights. + +const SPACE = ' '; + +function make(width, height) { + return Array.from({ length: height }, () => + Array.from({ length: width }, () => ({ ch: SPACE, hl: false })) + ); +} + +function put(g, x, y, ch, hl) { + if (y < 0 || y >= g.length) return; + if (x < 0 || x >= g[0].length) return; + g[y][x] = { ch, hl }; +} + +function text(g, x, y, str, hl = false) { + for (let i = 0; i < str.length; i++) put(g, x + i, y, str[i], hl); +} + +function textCenter(g, cx, y, str, hl = false) { + text(g, cx - Math.floor(str.length / 2), y, str, hl); +} + +function vline(g, x, y1, y2, hl = false) { + const [a, b] = y1 <= y2 ? [y1, y2] : [y2, y1]; + for (let y = a; y <= b; y++) put(g, x, y, '│', hl); +} + +function hline(g, y, x1, x2, hl = false) { + const [a, b] = x1 <= x2 ? [x1, x2] : [x2, x1]; + for (let x = a; x <= b; x++) put(g, x, y, '─', hl); +} + +function ch(g, x, y, char, hl = false) { + put(g, x, y, char, hl); +} + +function joint(g, x, y, char) { + if (y < 0 || y >= g.length || x < 0 || x >= g[0].length) return; + g[y][x] = { ch: char, hl: g[y][x].hl }; +} + +function box(g, o) { + const { x, y, w, h, lines = [], align = 'left', topLabel, hl = false, subtitle = false } = o; + if (hl) { + for (let yy = y + 1; yy < y + h - 1; yy++) { + for (let xx = x + 1; xx < x + w - 1; xx++) { + put(g, xx, yy, SPACE, true); + } + } + } + put(g, x, y, '┌', hl); + put(g, x + w - 1, y, '┐', hl); + put(g, x, y + h - 1, '└', hl); + put(g, x + w - 1, y + h - 1, '┘', hl); + for (let i = 1; i < w - 1; i++) { + put(g, x + i, y, '─', hl); + put(g, x + i, y + h - 1, '─', hl); + } + for (let i = 1; i < h - 1; i++) { + put(g, x, y + i, '│', hl); + put(g, x + w - 1, y + i, '│', hl); + } + if (topLabel) { + const label = ` ${topLabel} `; + for (let i = 0; i < label.length && 2 + i < w - 2; i++) { + put(g, x + 2 + i, y, label[i], hl); + } + } + const inner = w - 2; + for (let i = 0; i < lines.length; i++) { + if (1 + i >= h - 1) break; + const line = lines[i]; + const startCol = + align === 'center' + ? x + 1 + Math.floor((inner - line.length) / 2) + : x + 2; + const accent = subtitle && i > 0; + for (let j = 0; j < line.length; j++) { + if (startCol + j >= x + w - 1) break; + const yy = y + 1 + i; + const xx = startCol + j; + if (yy < 0 || yy >= g.length || xx < 0 || xx >= g[0].length) continue; + g[yy][xx] = { ch: line[j], hl, accent }; + } + } +} + +function highlight(g, x, y, w, h) { + for (let yy = y; yy < y + h; yy++) { + for (let xx = x; xx < x + w; xx++) { + if (yy < 0 || yy >= g.length || xx < 0 || xx >= g[0].length) continue; + g[yy][xx] = { ch: g[yy][xx].ch, hl: true }; + } + } +} + +function verify(g) { + const width = g[0]?.length ?? 0; + for (const row of g) { + if (row.length !== width) throw new Error('Grid rows have inconsistent width'); + } + return { width, height: g.length }; +} + +// Plain-text rendering for the markdown plugin. Drops React span wrappers +// and emits trailing-whitespace-trimmed lines joined with '\n'. +function renderText(g) { + const lines = []; + for (const row of g) { + let end = row.length; + while ( + end > 0 && + row[end - 1].ch === SPACE && + !row[end - 1].hl && + !row[end - 1].accent + ) { + end--; + } + let line = ''; + for (let i = 0; i < end; i++) line += row[i].ch; + lines.push(line); + } + return lines.join('\n'); +} + +module.exports = { + make, + text, + textCenter, + vline, + hline, + ch, + joint, + box, + highlight, + verify, + renderText, +}; diff --git a/apps/docs/src/components/CodingAgentsTopology/grid.tsx b/apps/docs/src/components/CodingAgentsTopology/grid.tsx new file mode 100644 index 000000000..51c813f61 --- /dev/null +++ b/apps/docs/src/components/CodingAgentsTopology/grid.tsx @@ -0,0 +1,83 @@ +// Re-exports the pure grid utilities from grid.cjs and adds a React-flavored +// `render` that wraps highlighted/accent runs in elements. The .cjs +// module is shared with the markdown-source plugin so the same ASCII art +// renders in both the live page and the .md output. +// +// `Grid` is intentionally typed loosely (`any[][]`) because the cell shape +// comes from grid.cjs. Strong typing isn't worth a parallel .d.ts file when +// the consuming React component only passes the grid through. + +import React from 'react'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const grid = require('./grid.cjs'); + +export type Grid = any[][]; + +const SPACE = ' '; + +export const make: (width: number, height: number) => Grid = grid.make; +export const text: (g: Grid, x: number, y: number, str: string, hl?: boolean) => void = grid.text; +export const textCenter: (g: Grid, cx: number, y: number, str: string, hl?: boolean) => void = grid.textCenter; +export const vline: (g: Grid, x: number, y1: number, y2: number, hl?: boolean) => void = grid.vline; +export const hline: (g: Grid, y: number, x1: number, x2: number, hl?: boolean) => void = grid.hline; +export const ch: (g: Grid, x: number, y: number, char: string, hl?: boolean) => void = grid.ch; +export const joint: (g: Grid, x: number, y: number, char: string) => void = grid.joint; + +type BoxOpts = { + x: number; + y: number; + w: number; + h: number; + lines?: string[]; + align?: 'left' | 'center'; + topLabel?: string; + hl?: boolean; + subtitle?: boolean; +}; +export const box: (g: Grid, o: BoxOpts) => void = grid.box; +export const highlight: (g: Grid, x: number, y: number, w: number, h: number) => void = grid.highlight; +export const verify: (g: Grid) => { width: number; height: number } = grid.verify; + +export function render(g: Grid): React.ReactNode[] { + const out: React.ReactNode[] = []; + for (let y = 0; y < g.length; y++) { + if (y > 0) out.push('\n'); + const row = g[y]; + let end = row.length; + while ( + end > 0 && + row[end - 1].ch === SPACE && + !row[end - 1].hl && + !row[end - 1].accent + ) + end--; + let i = 0; + while (i < end) { + const hl = row[i].hl; + const accent = !!row[i].accent; + let j = i; + while (j < end && row[j].hl === hl && !!row[j].accent === accent) j++; + const seg = row + .slice(i, j) + .map((c: { ch: string }) => c.ch) + .join(''); + if (hl) { + out.push( + + {seg} + + ); + } else if (accent) { + out.push( + + {seg} + + ); + } else { + out.push(seg); + } + i = j; + } + } + return out; +} diff --git a/apps/docs/src/components/CodingAgentsTopology/index.tsx b/apps/docs/src/components/CodingAgentsTopology/index.tsx new file mode 100644 index 000000000..4ab233b6b --- /dev/null +++ b/apps/docs/src/components/CodingAgentsTopology/index.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import { render } from './grid'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { buildRemote, buildLocal } = require('./build.cjs'); + +type Mode = 'remote' | 'local'; + + +const CodingAgentsTopology: React.FC = () => { + const [mode, setMode] = useState('remote'); + const diagram = mode === 'remote' ? buildRemote() : buildLocal(); + + return ( +
+
+ + +
+
+        {render(diagram)}
+      
+
+ ); +}; + +export default CodingAgentsTopology; diff --git a/apps/docs/src/components/IntroAgentVisual/build.cjs b/apps/docs/src/components/IntroAgentVisual/build.cjs new file mode 100644 index 000000000..3e85f885a --- /dev/null +++ b/apps/docs/src/components/IntroAgentVisual/build.cjs @@ -0,0 +1,136 @@ +'use strict'; + +// Build function for the agents-to-recipes-to-projects intro visual. +// Pure JS, no React. Consumed by: +// - the React component (index.tsx) for the live page (renders logo images +// absolutely positioned over the ASCII grid) +// - the markdown-source plugin to render the same diagram as plain text +// (logos rendered as their alt text overlaid into the grid) + +const { + make, + text, + textCenter, + vline, + hline, + ch, + joint, + box, + verify, +} = require('../CodingAgentsTopology/grid.cjs'); + +const W = 86; + +const AGENTS = [ + { src: '/img/recipes/claude.png', alt: 'Claude Code' }, + { src: '/img/recipes/codex.svg', alt: 'Codex' }, + { src: '/img/recipes/gemini.svg', alt: 'Gemini CLI' }, + { src: '/img/recipes/opencode.png', alt: 'opencode' }, +]; + +const RECIPES = [ + { src: '/img/recipes/zerops/bun.svg', alt: 'Bun' }, + { src: '/img/recipes/zerops/deno.svg', alt: 'Deno' }, + { src: '/img/recipes/zerops/nodejs.svg', alt: 'Node.js' }, + { src: '/img/recipes/zerops/golang.svg', alt: 'Go' }, + { src: '/img/recipes/zerops/java.svg', alt: 'Java' }, + { src: '/img/recipes/zerops/python.svg', alt: 'Python' }, + { src: '/img/recipes/zerops/rust.svg', alt: 'Rust' }, + { src: '/img/recipes/zerops/gleam.svg', alt: 'Gleam' }, + { src: '/img/recipes/zerops/ruby.svg', alt: 'Ruby' }, + { src: '/img/recipes/zerops/php.svg', alt: 'PHP' }, + { src: '/img/recipes/zerops/dotnet.svg', alt: '.NET' }, + { src: '/img/recipes/zerops/laravel.svg', alt: 'Laravel' }, + { src: '/img/recipes/zerops/nextjs.svg', alt: 'Next.js' }, + { src: '/img/recipes/zerops/nuxt.svg', alt: 'Nuxt' }, + { src: '/img/recipes/zerops/astro.svg', alt: 'Astro' }, + { src: '/img/recipes/zerops/svelte.svg', alt: 'Svelte' }, + { src: '/img/recipes/zerops/react.svg', alt: 'React' }, + { src: '/img/recipes/zerops/vue.svg', alt: 'Vue' }, + { src: '/img/recipes/zerops/angular.png', alt: 'Angular' }, + { src: '/img/recipes/zerops/solid.svg', alt: 'Solid' }, + { src: '/img/recipes/zerops/qwik.svg', alt: 'Qwik' }, + { src: '/img/recipes/zerops/analog.svg', alt: 'Analog' }, + { src: '/img/recipes/zerops/nestjs.svg', alt: 'Nest.js' }, +]; + +function buildLayout() { + const g = make(W, 41); + const logos = []; + + box(g, { x: 2, y: 0, w: 16, h: 8, topLabel: 'ZCP MCP' }); + const AGENT_POS = [ + [6, 2], [13, 2], + [6, 5], [13, 5], + ]; + AGENTS.forEach((a, i) => { + const [col, row] = AGENT_POS[i]; + logos.push({ src: a.src, alt: a.alt, col, row }); + }); + + box(g, { x: 22, y: 0, w: 58, h: 11, topLabel: 'recipes for any stack' }); + const RECIPE_COLS = [26, 33, 40, 47, 54, 61, 68, 75]; + RECIPES.forEach((r, i) => { + const rowIdx = Math.floor(i / 8); + const colIdx = i % 8; + const row = 2 + rowIdx * 3; + logos.push({ src: r.src, alt: r.alt, col: RECIPE_COLS[colIdx], row }); + }); + + joint(g, 17, 4, '├'); + hline(g, 4, 18, 20); + ch(g, 21, 4, '►'); + + textCenter(g, 42, 12, 'zcp with agent has root ssh access to runtime services,'); + textCenter(g, 42, 13, 'runs dev server on dev, deploys to stage, verifies'); + + joint(g, 10, 7, '┬'); + vline(g, 10, 8, 14); + joint(g, 10, 15, '├'); + hline(g, 15, 11, 73); + joint(g, 74, 15, '┐'); + + vline(g, 10, 16, 16); + joint(g, 10, 17, '┼'); + vline(g, 10, 18, 18); + ch(g, 10, 19, '▼'); + + vline(g, 74, 16, 16); + joint(g, 74, 17, '┼'); + vline(g, 74, 18, 18); + ch(g, 74, 19, '▼'); + + box(g, { x: 0, y: 17, w: 60, h: 23, topLabel: 'complex project' }); + + box(g, { x: 3, y: 20, w: 14, h: 4, lines: ['zcp', 'Ubuntu'], align: 'center', hl: true, subtitle: true }); + + box(g, { x: 3, y: 25, w: 14, h: 4, lines: ['appdev', 'Bun'], align: 'center', subtitle: true }); + box(g, { x: 23, y: 25, w: 14, h: 4, lines: ['apidev', 'Golang'], align: 'center', subtitle: true }); + box(g, { x: 43, y: 25, w: 14, h: 4, lines: ['workerdev', 'Python'], align: 'center', subtitle: true }); + + box(g, { x: 3, y: 30, w: 14, h: 4, lines: ['appstage', 'Bun'], align: 'center', subtitle: true }); + box(g, { x: 23, y: 30, w: 14, h: 4, lines: ['apistage', 'Golang'], align: 'center', subtitle: true }); + box(g, { x: 43, y: 30, w: 14, h: 4, lines: ['workerstage', 'Python'], align: 'center', subtitle: true }); + + box(g, { x: 3, y: 35, w: 10, h: 4, lines: ['db', 'Postgres'], align: 'center', subtitle: true }); + box(g, { x: 14, y: 35, w: 10, h: 4, lines: ['search', 'Elastic'], align: 'center', subtitle: true }); + box(g, { x: 25, y: 35, w: 10, h: 4, lines: ['broker', 'NATS'], align: 'center', subtitle: true }); + box(g, { x: 36, y: 35, w: 10, h: 4, lines: ['cache', 'Valkey'], align: 'center', subtitle: true }); + box(g, { x: 47, y: 35, w: 10, h: 4, lines: ['storage', 'S3'], align: 'center', subtitle: true }); + + box(g, { x: 64, y: 17, w: 22, h: 18, topLabel: 'simple project' }); + + box(g, { x: 69, y: 20, w: 12, h: 4, lines: ['zcp', 'Ubuntu'], align: 'center', hl: true, subtitle: true }); + box(g, { x: 69, y: 25, w: 12, h: 4, lines: ['appdev', 'Node.js'], align: 'center', subtitle: true }); + box(g, { x: 69, y: 30, w: 12, h: 4, lines: ['appstage', 'Node.js'], align: 'center', subtitle: true }); + + verify(g); + return { grid: g, logos }; +} + +module.exports = { + buildLayout, + AGENTS, + RECIPES, + W, +}; diff --git a/apps/docs/src/components/IntroAgentVisual/index.tsx b/apps/docs/src/components/IntroAgentVisual/index.tsx new file mode 100644 index 000000000..08c01a917 --- /dev/null +++ b/apps/docs/src/components/IntroAgentVisual/index.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { render } from '../CodingAgentsTopology/grid'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { buildLayout } = require('./build.cjs'); + +type LogoSlot = { src: string; alt: string; col: number; row: number }; + +const IntroAgentVisual: React.FC = () => { + const { grid, logos } = buildLayout() as { + grid: any[][]; + logos: LogoSlot[]; + }; + + return ( +
+
{render(grid)}
+ {logos.map((l, i) => ( + {l.alt} + ))} +
+ ); +}; + +export default IntroAgentVisual; diff --git a/apps/docs/src/css/custom.css b/apps/docs/src/css/custom.css index da54b907d..2bfb0e819 100644 --- a/apps/docs/src/css/custom.css +++ b/apps/docs/src/css/custom.css @@ -53,7 +53,10 @@ @apply cursor-default; } - .sidebar-group-headline > .menu__list-item-collapsible > .menu__link:not([href]) > .menu__link:hover, + .sidebar-group-headline + > .menu__list-item-collapsible + > .menu__link:not([href]) + > .menu__link:hover, .sidebar-group-headline .menu__list-item-collapsible:hover { @apply !bg-transparent; } @@ -178,22 +181,22 @@ html[data-theme='dark'] { } .height-100 { - height: 100%; + height: 100%; } .sidebar-group-headline > .menu__list-item-collapsible > .menu__link { - padding: 0px !important; - color: var(--ifm-menu-color); + padding: 0px !important; + color: var(--ifm-menu-color); } .theme-doc-sidebar-item-category-level-1.sidebar-group-headline { - margin-bottom: 6px; - margin-top: 4px !important; + margin-bottom: 6px; + margin-top: 4px !important; } .sidebar-group-headline > .menu__list-item-collapsible { - cursor: default; - color: #fff; + cursor: default; + color: #fff; } html[data-theme='dark'] .docsearch-btn { @@ -233,3 +236,1183 @@ html[data-theme='dark'] .docsearch-btn:hover { @import url('./components/toc.css'); @import url('./components/copy-page-menu.css'); @import url('./components/tooltip.css'); + +/* ZCP + paired feature pages: force balanced table column widths on tablet + and up. Default markdown tables render as display:block with browser + auto-layout, which collapses one column to ~17% when the other has + bold-led short text the browser treats as max-content. Equal-width + fixed layout keeps long-cell content readable. Below 768px we leave the + default responsive overflow-x behavior alone. */ +@media (min-width: 768px) { + html[class*='docs-doc-id-zcp'] .theme-doc-markdown table, + html[class*='docs-doc-id-features/coding-agents'] .theme-doc-markdown table, + html[class*='docs-doc-id-features/local-remote-development'] + .theme-doc-markdown + table { + display: table; + table-layout: fixed; + width: 100%; + } + html[class*='docs-doc-id-zcp'] .theme-doc-markdown table th, + html[class*='docs-doc-id-zcp'] .theme-doc-markdown table td, + html[class*='docs-doc-id-features/coding-agents'] + .theme-doc-markdown + table + th, + html[class*='docs-doc-id-features/coding-agents'] + .theme-doc-markdown + table + td, + html[class*='docs-doc-id-features/local-remote-development'] + .theme-doc-markdown + table + th, + html[class*='docs-doc-id-features/local-remote-development'] + .theme-doc-markdown + table + td { + overflow-wrap: anywhere; + word-break: normal; + vertical-align: top; + } +} + +@media (max-width: 767px) { + html[class*='docs-doc-id-zcp'] article, + html[class*='docs-doc-id-features/coding-agents'] article, + html[class*='docs-doc-id-features/local-remote-development'] article, + html[class*='docs-doc-id-zcp'] .theme-doc-footer, + html[class*='docs-doc-id-features/coding-agents'] .theme-doc-footer, + html[class*='docs-doc-id-features/local-remote-development'] .theme-doc-footer { + overflow-x: hidden; + } + + html[class*='docs-doc-id-zcp'] article pre, + html[class*='docs-doc-id-features/coding-agents'] article pre, + html[class*='docs-doc-id-features/local-remote-development'] article pre, + html[class*='docs-doc-id-zcp'] article table, + html[class*='docs-doc-id-features/coding-agents'] article table, + html[class*='docs-doc-id-features/local-remote-development'] article table { + max-width: 100%; + overflow-x: auto; + } + + html[class*='docs-doc-id-zcp'] .theme-doc-footer a, + html[class*='docs-doc-id-features/coding-agents'] .theme-doc-footer a, + html[class*='docs-doc-id-features/local-remote-development'] + .theme-doc-footer + a { + max-width: calc(50% - 0.25rem); + min-width: 0; + overflow-wrap: anywhere; + } +} + +.zcp-lifecycle-diagram { + margin: 1.5rem 0 1.75rem; +} + +.zcp-agent-flow { + display: grid; + gap: 0.85rem; + grid-template-columns: 1fr; + margin: 1.25rem 0 1.75rem; +} + +.zcp-agent-flow__node { + background: #ffffff; + border: 1px solid #d7dce5; + border-radius: 8px; + color: #172033; + padding: 0.9rem; + position: relative; +} + +.zcp-agent-flow__node::after { + color: #6b7280; + content: '↓'; + font-size: 1.1rem; + font-weight: 700; + left: 50%; + line-height: 1; + position: absolute; + top: calc(100% + 0.08rem); + transform: translateX(-50%); +} + +.zcp-agent-flow__node:last-child::after { + content: none; +} + +.zcp-agent-flow__node span { + color: #6b7280; + display: block; + font-size: 0.68rem; + font-weight: 650; + letter-spacing: 0; + line-height: 1.1; + margin-bottom: 0.35rem; + text-transform: uppercase; +} + +.zcp-agent-flow__node strong { + display: block; + font-size: 0.9rem; + line-height: 1.25; +} + +.zcp-agent-flow__node p { + color: #4b5563; + font-size: 0.78rem; + line-height: 1.42; + margin: 0.45rem 0 0; +} + +.zcp-agent-flow__node--intent { + background: #f7fbff; + border-color: rgba(45, 114, 217, 0.35); +} + +.zcp-agent-flow__node--agent { + background: #fffaf0; + border-color: rgba(196, 127, 23, 0.35); +} + +.zcp-agent-flow__node--mcp { + background: #f4fbf6; + border-color: rgba(50, 132, 90, 0.35); +} + +.zcp-agent-flow__node--project { + background: #f8fafc; + border-color: rgba(75, 85, 99, 0.28); +} + +.zcp-agent-flow__node--proof { + background: #f7f5ff; + border-color: rgba(113, 87, 217, 0.35); +} + +.zcp-lifecycle-prompt { + align-items: center; + background: #f7fbff; + border: 1px solid #2d72d9; + border-radius: 8px; + color: #172033; + display: flex; + flex-direction: column; + gap: 0.35rem; + margin: 0 auto; + max-width: 22rem; + padding: 0.7rem 0.9rem; + text-align: center; +} + +.zcp-lifecycle-prompt span { + font-size: 0.76rem; + font-weight: 650; + line-height: 1.1; + text-transform: uppercase; +} + +.zcp-lifecycle-prompt code { + background: #eaf2ff; + border: 1px solid rgba(45, 114, 217, 0.22); + border-radius: 6px; + color: #172033; + display: block; + font-size: 0.78rem; + line-height: 1.25; + max-width: 100%; + overflow-wrap: anywhere; + padding: 0.32rem 0.45rem; + white-space: normal; +} + +.zcp-lifecycle-down { + background: #6b7280; + height: 1.35rem; + margin: 0 auto; + position: relative; + width: 2px; +} + +.zcp-lifecycle-down::after { + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 7px solid #6b7280; + bottom: -6px; + content: ''; + left: 50%; + position: absolute; + transform: translateX(-50%); +} + +.zcp-lifecycle-flow { + display: grid; + gap: 1rem; + grid-template-columns: 1fr; + margin-top: 0.8rem; +} + +.zcp-lifecycle-stage { + background: #ffffff; + border: 1px solid #d7dce5; + border-radius: 8px; + color: #172033; + padding: 0.95rem; + position: relative; +} + +.zcp-lifecycle-stage::after { + color: #6b7280; + content: '↓'; + font-size: 1.2rem; + font-weight: 700; + left: 50%; + line-height: 1; + position: absolute; + top: calc(100% + 0.08rem); + transform: translateX(-50%); +} + +.zcp-lifecycle-stage:last-child::after { + content: none; +} + +.zcp-lifecycle-stage h3 { + font-size: 0.95rem; + line-height: 1.25; + margin: 0; +} + +.zcp-lifecycle-stage p { + color: #4b5563; + font-size: 0.84rem; + line-height: 1.45; + margin: 0.45rem 0 0.7rem; +} + +.zcp-lifecycle-stage ul { + display: grid; + gap: 0.42rem; + list-style: none; + margin: 0; + padding: 0; +} + +.zcp-lifecycle-stage li { + border: 1px solid currentColor; + border-radius: 6px; + font-size: 0.78rem; + line-height: 1.25; + padding: 0.38rem 0.48rem; +} + +.zcp-lifecycle-stage--infra { + background: #f4fbf6; + border-color: rgba(50, 132, 90, 0.35); +} + +.zcp-lifecycle-stage--infra li { + color: #236343; +} + +.zcp-lifecycle-stage--dev { + background: #fffaf0; + border-color: rgba(196, 127, 23, 0.35); +} + +.zcp-lifecycle-stage--dev li { + color: #9a5d0d; +} + +.zcp-lifecycle-stage--delivery { + background: #f7f5ff; + border-color: rgba(113, 87, 217, 0.35); +} + +.zcp-lifecycle-stage--delivery li { + color: #5b45b0; +} + +html[data-theme='dark'] .zcp-lifecycle-prompt { + background: #132033; + color: #f8fafc; +} + +html[data-theme='dark'] .zcp-lifecycle-prompt code { + background: #17263d; + color: #dbeafe; +} + +html[data-theme='dark'] .zcp-agent-flow__node { + color: #f8fafc; +} + +html[data-theme='dark'] .zcp-agent-flow__node span, +html[data-theme='dark'] .zcp-agent-flow__node p { + color: #cbd5e1; +} + +html[data-theme='dark'] .zcp-agent-flow__node--intent { + background: rgba(30, 58, 138, 0.22); +} + +html[data-theme='dark'] .zcp-agent-flow__node--agent { + background: rgba(120, 53, 15, 0.2); +} + +html[data-theme='dark'] .zcp-agent-flow__node--mcp { + background: rgba(20, 83, 45, 0.22); +} + +html[data-theme='dark'] .zcp-agent-flow__node--project { + background: rgba(24, 24, 27, 0.72); +} + +html[data-theme='dark'] .zcp-agent-flow__node--proof { + background: rgba(76, 29, 149, 0.22); +} + +html[data-theme='dark'] .zcp-lifecycle-stage { + color: #f8fafc; +} + +html[data-theme='dark'] .zcp-lifecycle-stage p { + color: #cbd5e1; +} + +html[data-theme='dark'] .zcp-lifecycle-stage--infra { + background: rgba(20, 83, 45, 0.22); +} + +html[data-theme='dark'] .zcp-lifecycle-stage--dev { + background: rgba(120, 53, 15, 0.2); +} + +html[data-theme='dark'] .zcp-lifecycle-stage--delivery { + background: rgba(76, 29, 149, 0.22); +} + +@media (min-width: 768px) { + .zcp-agent-flow { + gap: 0.95rem; + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + + .zcp-agent-flow__node::after { + content: '→'; + left: calc(100% + 0.22rem); + top: 50%; + transform: translateY(-50%); + } + + .zcp-lifecycle-prompt { + margin-left: 0; + margin-right: auto; + max-width: none; + width: calc(33.333% - 0.9rem); + } + + .zcp-lifecycle-down { + margin-left: calc(16.666% - 0.45rem); + margin-right: 0; + } + + .zcp-lifecycle-flow { + gap: 1.35rem; + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .zcp-lifecycle-stage::after { + content: '→'; + left: calc(100% + 0.3rem); + top: 50%; + transform: translateY(-50%); + } +} + +/* === coding-agents feature page: claim grid, gap cards, comparison cards, pull quote, hack panel, run-split === */ + +.zcp-claim-grid { + display: grid; + gap: 0.95rem; + grid-template-columns: 1fr; + margin: 1.25rem 0 2rem; +} + +.zcp-claim-card, +.zcp-claim-callout { + background: #ffffff; + border: 1px solid #d7dce5; + border-radius: 8px; + color: #172033; + padding: 1rem; +} + +.zcp-claim-card span, +.zcp-claim-callout span { + color: #6b7280; + display: block; + font-size: 0.68rem; + font-weight: 650; + letter-spacing: 0; + line-height: 1.1; + margin-bottom: 0.45rem; + text-transform: uppercase; +} + +.zcp-claim-card h3, +.zcp-claim-callout h3 { + font-size: 1rem; + line-height: 1.25; + margin: 0; +} + +.zcp-claim-card p, +.zcp-claim-callout p { + color: #4b5563; + font-size: 0.84rem; + line-height: 1.5; + margin: 0.55rem 0 0; +} + +.zcp-claim-card--services { background: #f4fbf6; border-color: rgba(50, 132, 90, 0.35); } +.zcp-claim-card--deploy { background: #f7f5ff; border-color: rgba(113, 87, 217, 0.35); } +.zcp-claim-card--stack { background: #f7fbff; border-color: rgba(45, 114, 217, 0.35); } +.zcp-claim-card--handover { background: #fffaf0; border-color: rgba(196, 127, 23, 0.35); } + +.zcp-claim-callout { + background: #172033; + border-color: #172033; + color: #ffffff; +} +.zcp-claim-callout span, +.zcp-claim-callout p { color: #d1d5db; } +.zcp-claim-callout h3 { color: #ffffff; } + +@media (min-width: 768px) { + .zcp-claim-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .zcp-claim-callout { grid-column: 1 / -1; padding: 1.15rem 1.25rem; } +} + +html[data-theme='dark'] .zcp-claim-card { color: #f8fafc; } +html[data-theme='dark'] .zcp-claim-card p, +html[data-theme='dark'] .zcp-claim-card span { color: #cbd5e1; } +html[data-theme='dark'] .zcp-claim-card--services { background: rgba(20, 83, 45, 0.22); } +html[data-theme='dark'] .zcp-claim-card--deploy { background: rgba(76, 29, 149, 0.22); } +html[data-theme='dark'] .zcp-claim-card--stack { background: rgba(30, 58, 138, 0.22); } +html[data-theme='dark'] .zcp-claim-card--handover { background: rgba(120, 53, 15, 0.2); } + +/* gap objection cards */ + +.zcp-gap-grid { + display: grid; + gap: 0.8rem; + grid-template-columns: 1fr; + margin: 1rem 0 1.75rem; +} + +.zcp-gap-card { + background: #fffaf0; + border: 1px solid rgba(196, 127, 23, 0.35); + border-radius: 8px; + padding: 0.9rem; +} + +.zcp-gap-card h3 { color: #172033; font-size: 0.95rem; line-height: 1.25; margin: 0; } +.zcp-gap-card p { color: #4b5563; font-size: 0.82rem; line-height: 1.45; margin: 0.45rem 0 0; } + +@media (min-width: 768px) { + .zcp-gap-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} + +html[data-theme='dark'] .zcp-gap-card { background: rgba(120, 53, 15, 0.2); } +html[data-theme='dark'] .zcp-gap-card h3 { color: #f8fafc; } +html[data-theme='dark'] .zcp-gap-card p { color: #cbd5e1; } + +/* competitor comparison cards + highlighted summary table */ + +.zcp-compare-grid { + display: grid; + gap: 0.9rem; + grid-template-columns: 1fr; + margin: 1.2rem 0 1.5rem; +} + +.zcp-compare-card { + background: #ffffff; + border: 1px solid #d7dce5; + border-radius: 8px; + padding: 0.95rem; +} + +.zcp-compare-card span { color: #6b7280; display: block; font-size: 0.68rem; font-weight: 650; margin-bottom: 0.4rem; text-transform: uppercase; } +.zcp-compare-card h3 { font-size: 0.98rem; line-height: 1.25; margin: 0; } +.zcp-compare-card p { color: #4b5563; font-size: 0.82rem; line-height: 1.48; margin: 0.5rem 0 0.7rem; } +.zcp-compare-card strong { color: #236343; display: block; font-size: 0.8rem; line-height: 1.35; } + +.zcp-summary-table table tr:last-child { background: #f4fbf6; } +.zcp-summary-table table tr:last-child td { border-top: 1px solid rgba(50, 132, 90, 0.35); font-weight: 600; } + +@media (min-width: 768px) { + .zcp-compare-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .zcp-compare-card--wide { grid-column: 1 / -1; } +} + +html[data-theme='dark'] .zcp-compare-card { background: rgba(24, 24, 27, 0.72); color: #f8fafc; } +html[data-theme='dark'] .zcp-compare-card p, +html[data-theme='dark'] .zcp-compare-card span { color: #cbd5e1; } +html[data-theme='dark'] .zcp-compare-card strong { color: #86efac; } +html[data-theme='dark'] .zcp-summary-table table tr:last-child { background: rgba(20, 83, 45, 0.22); } + +/* project boundary pull quote */ + +.zcp-boundary-quote { + background: #f7fbff; + border: 1px solid rgba(45, 114, 217, 0.35); + border-left: 4px solid #2d72d9; + border-radius: 8px; + margin: 1.25rem 0; + padding: 1rem 1.15rem; +} + +.zcp-boundary-quote p { + color: #172033; + font-size: 1.05rem; + font-weight: 650; + line-height: 1.45; + margin: 0; +} + +html[data-theme='dark'] .zcp-boundary-quote { background: rgba(30, 58, 138, 0.22); } +html[data-theme='dark'] .zcp-boundary-quote p { color: #f8fafc; } + +/* hack-on-the-workspace panel */ + +.zcp-hack-panel { + background: #f8fafc; + border: 1px solid rgba(75, 85, 99, 0.28); + border-radius: 8px; + margin: 1rem 0 1.5rem; + padding: 1rem; +} + +.zcp-hack-panel > div span { color: #6b7280; display: block; font-size: 0.68rem; font-weight: 650; margin-bottom: 0.35rem; text-transform: uppercase; } +.zcp-hack-panel > div strong { color: #172033; display: block; font-size: 1rem; line-height: 1.25; } +.zcp-hack-panel ul { display: grid; gap: 0.45rem; list-style: none; margin: 0.8rem 0 0; padding: 0; } +.zcp-hack-panel li { background: #ffffff; border: 1px solid #d7dce5; border-radius: 6px; color: #4b5563; font-size: 0.82rem; line-height: 1.4; padding: 0.5rem 0.6rem; } + +@media (min-width: 768px) { + .zcp-hack-panel { display: grid; gap: 1rem; grid-template-columns: minmax(0, 0.75fr) minmax(0, 1.25fr); } + .zcp-hack-panel ul { margin-top: 0; } +} + +html[data-theme='dark'] .zcp-hack-panel { background: rgba(24, 24, 27, 0.72); } +html[data-theme='dark'] .zcp-hack-panel > div strong { color: #f8fafc; } +html[data-theme='dark'] .zcp-hack-panel li { background: rgba(15, 23, 42, 0.55); color: #cbd5e1; } + +/* where it runs: remote/local split */ + +.zcp-run-split { display: grid; gap: 0.85rem; grid-template-columns: 1fr; margin: 1rem 0 1.4rem; } +.zcp-run-split article { background: #f7fbff; border: 1px solid rgba(45, 114, 217, 0.35); border-radius: 8px; padding: 0.95rem; } +.zcp-run-split span { color: #6b7280; display: block; font-size: 0.68rem; font-weight: 650; margin-bottom: 0.4rem; text-transform: uppercase; } +.zcp-run-split h3 { color: #172033; font-size: 0.98rem; line-height: 1.25; margin: 0; } +.zcp-run-split p { color: #4b5563; font-size: 0.82rem; line-height: 1.45; margin: 0.5rem 0 0; } + +@media (min-width: 768px) { + .zcp-run-split { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} + +html[data-theme='dark'] .zcp-run-split article { background: rgba(30, 58, 138, 0.22); } +html[data-theme='dark'] .zcp-run-split h3 { color: #f8fafc; } +html[data-theme='dark'] .zcp-run-split p, +html[data-theme='dark'] .zcp-run-split span { color: #cbd5e1; } + +/* substrate cards: structural differentiators */ + +.zcp-substrate-grid { + display: grid; + gap: 0.9rem; + grid-template-columns: 1fr; + margin: 1.1rem 0 1.85rem; +} + +.zcp-substrate-card { + background: #f1f5f9; + border: 1px solid rgba(45, 65, 96, 0.28); + border-radius: 8px; + padding: 1rem; +} + +.zcp-substrate-card span { + color: #475569; + display: block; + font-size: 0.68rem; + font-weight: 650; + letter-spacing: 0; + line-height: 1.1; + margin-bottom: 0.45rem; + text-transform: uppercase; +} + +.zcp-substrate-card h3 { + color: #172033; + font-size: 1rem; + line-height: 1.25; + margin: 0; +} + +.zcp-substrate-card p { + color: #4b5563; + font-size: 0.84rem; + line-height: 1.5; + margin: 0.55rem 0 0; +} + +.zcp-substrate-card p em { + background: rgba(196, 127, 23, 0.18); + border-radius: 3px; + color: #7c3a02; + font-style: normal; + padding: 0 0.22em; +} + +/* substrate cards stack as rows on all viewports — each claim gets full width + so the reader can connect it back to the topology above */ + +html[data-theme='dark'] .zcp-substrate-card { background: rgba(15, 23, 42, 0.7); } +html[data-theme='dark'] .zcp-substrate-card h3 { color: #f8fafc; } +html[data-theme='dark'] .zcp-substrate-card p, +html[data-theme='dark'] .zcp-substrate-card span { color: #cbd5e1; } +html[data-theme='dark'] .zcp-substrate-card p em { background: rgba(196, 127, 23, 0.25); color: #fde68a; } + +/* framed comparison table for the agent-hosting landscape */ + +.zcp-landscape-table { + background: #ffffff; + border: 1px solid rgba(45, 65, 96, 0.18); + border-radius: 10px; + margin: 1.4rem 0 1.8rem; + overflow: hidden; +} + +.zcp-landscape-table table { + border-collapse: collapse; + display: table; + font-size: 0.88rem; + margin: 0; + width: 100%; +} + +.zcp-landscape-table thead th { + background: #f1f5f9; + border: none; + border-bottom: 1px solid rgba(45, 65, 96, 0.18); + color: #475569; + font-size: 0.7rem; + font-weight: 650; + letter-spacing: 0.05em; + padding: 0.7rem 1rem; + text-align: left; + text-transform: uppercase; +} + +.zcp-landscape-table tbody td { + border: none; + border-bottom: 1px solid rgba(45, 65, 96, 0.08); + color: #4b5563; + line-height: 1.45; + padding: 0.75rem 1rem; + vertical-align: top; +} + +.zcp-landscape-table tbody tr:last-child td { + border-bottom: none; +} + +.zcp-landscape-table tbody td:first-child { + color: #172033; + font-weight: 600; + width: 22%; +} + +.zcp-landscape-table tbody td:last-child { + color: #475569; + font-size: 0.82rem; + width: 26%; +} + +.zcp-landscape-table tbody tr:hover { + background: rgba(45, 65, 96, 0.025); +} + +.zcp-landscape-table__zerops { + background: rgba(45, 114, 217, 0.07); + position: relative; +} + +.zcp-landscape-table__zerops:hover { + background: rgba(45, 114, 217, 0.11) !important; +} + +.zcp-landscape-table__zerops td { + color: #1d4ed8 !important; + font-weight: 600; +} + +.zcp-landscape-table__zerops td:first-child { + box-shadow: inset 3px 0 0 #2d72d9; + color: #1d4ed8 !important; +} + +html[data-theme='dark'] .zcp-landscape-table { + background: rgba(15, 23, 42, 0.55); + border-color: rgba(255, 255, 255, 0.08); +} + +html[data-theme='dark'] .zcp-landscape-table thead th { + background: rgba(255, 255, 255, 0.04); + border-bottom-color: rgba(255, 255, 255, 0.08); + color: #94a3b8; +} + +html[data-theme='dark'] .zcp-landscape-table tbody td { + border-bottom-color: rgba(255, 255, 255, 0.06); + color: #cbd5e1; +} + +html[data-theme='dark'] .zcp-landscape-table tbody td:first-child { + color: #f1f5f9; +} + +html[data-theme='dark'] .zcp-landscape-table tbody td:last-child { + color: #94a3b8; +} + +html[data-theme='dark'] .zcp-landscape-table tbody tr:hover { + background: rgba(255, 255, 255, 0.03); +} + +html[data-theme='dark'] .zcp-landscape-table__zerops { + background: rgba(45, 114, 217, 0.18); +} + +html[data-theme='dark'] .zcp-landscape-table__zerops:hover { + background: rgba(45, 114, 217, 0.24) !important; +} + +html[data-theme='dark'] .zcp-landscape-table__zerops td { + color: #93c5fd !important; +} + +/* agent hosting landscape — plain prose sections with a logo strip and an + explicit "ZCP relationship" line per category */ + +.zcp-landscape-section { + border-top: 1px solid rgba(45, 65, 96, 0.12); + margin: 0; + padding: 1.1rem 0 1.25rem; +} + +.zcp-landscape-section:first-of-type { + border-top: none; + padding-top: 1.5rem; +} + +.zcp-landscape-section header { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.7rem 0.85rem; + margin-bottom: 0.55rem; +} + +.zcp-landscape-section h4 { + color: #172033; + font-size: 1rem; + font-weight: 650; + line-height: 1.25; + margin: 0; +} + +.zcp-landscape-section__logos { + align-items: center; + display: inline-flex; + flex-wrap: wrap; + gap: 0.35rem; +} + +.zcp-landscape-section__logos img { + background: rgba(15, 23, 42, 0.05); + border-radius: 5px; + display: inline-block; + height: 22px; + padding: 3px; + width: 22px; +} + +.zcp-landscape-section__examples { + color: #64748b; + flex-basis: 100%; + font-size: 0.78rem; + margin: 0; + order: 99; +} + +.zcp-landscape-section p { + color: #475569; + font-size: 0.92rem; + line-height: 1.6; + margin: 0 0 0.55rem; +} + +.zcp-landscape-section p:last-child { + margin-bottom: 0; +} + +.zcp-landscape-section__relation { + background: rgba(45, 114, 217, 0.06); + border-left: 3px solid #2d72d9; + border-radius: 0 4px 4px 0; + color: #1e293b !important; + font-size: 0.9rem !important; + margin-top: 0.55rem; + padding: 0.55rem 0.85rem !important; +} + +.zcp-landscape-section__relation strong { + color: #1d4ed8; +} + +.zcp-landscape-section__relation code { + background: rgba(45, 114, 217, 0.12); + border-radius: 3px; + color: #1d4ed8; + font-size: 0.86em; + padding: 0.05em 0.3em; +} + +html[data-theme='dark'] .zcp-landscape-section { + border-top-color: rgba(255, 255, 255, 0.08); +} + +html[data-theme='dark'] .zcp-landscape-section h4 { + color: #f1f5f9; +} + +html[data-theme='dark'] .zcp-landscape-section p { + color: #cbd5e1; +} + +html[data-theme='dark'] .zcp-landscape-section__examples { + color: #94a3b8; +} + +html[data-theme='dark'] .zcp-landscape-section__logos img { + background: rgba(255, 255, 255, 0.08); +} + +html[data-theme='dark'] .zcp-landscape-section__relation { + background: rgba(96, 165, 250, 0.12); + border-left-color: #60a5fa; + color: #e2e8f0 !important; +} + +html[data-theme='dark'] .zcp-landscape-section__relation strong { + color: #93c5fd; +} + +html[data-theme='dark'] .zcp-landscape-section__relation code { + background: rgba(96, 165, 250, 0.2); + color: #bfdbfe; +} + +/* service definition yaml panel */ + +.zcp-yaml-intro { + color: #475569; + font-size: 0.86rem; + line-height: 1.55; + margin: 1rem 0 0.5rem; +} + +.zcp-yaml-intro strong { + color: #172033; + font-weight: 650; +} + +html[data-theme='dark'] .zcp-yaml-intro { color: #cbd5e1; } +html[data-theme='dark'] .zcp-yaml-intro strong { color: #f8fafc; } + +/* unified feature grid (replaces claim cards) */ + +.zcp-features-grid { + display: grid; + gap: 0.7rem; + grid-template-columns: 1fr; + margin: 1.1rem 0 1.85rem; +} + +.zcp-feature-card { + background: #ffffff; + border: 1px solid #d7dce5; + border-radius: 8px; + padding: 0.85rem 0.95rem; +} + +.zcp-feature-card h3 { + color: #172033; + font-size: 0.92rem; + line-height: 1.25; + margin: 0; +} + +.zcp-feature-card p { + color: #4b5563; + font-size: 0.8rem; + line-height: 1.5; + margin: 0.4rem 0 0; +} + +.zcp-feature-callout { + background: #172033; + border: 1px solid #172033; + border-radius: 8px; + color: #ffffff; + padding: 0.95rem 1.05rem; +} + +.zcp-feature-callout span { + color: #d1d5db; + display: block; + font-size: 0.66rem; + font-weight: 650; + letter-spacing: 0; + line-height: 1.1; + margin-bottom: 0.35rem; + text-transform: uppercase; +} + +.zcp-feature-callout h3 { + color: #ffffff; + font-size: 1rem; + line-height: 1.25; + margin: 0; +} + +.zcp-feature-callout p { + color: #d1d5db; + font-size: 0.84rem; + line-height: 1.5; + margin: 0.45rem 0 0; +} + +@media (min-width: 768px) { + .zcp-features-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.8rem; } + .zcp-feature-callout { grid-column: 1 / -1; padding: 1.05rem 1.2rem; } +} + +html[data-theme='dark'] .zcp-feature-card { background: rgba(24, 24, 27, 0.72); } +html[data-theme='dark'] .zcp-feature-card h3 { color: #f8fafc; } +html[data-theme='dark'] .zcp-feature-card p { color: #cbd5e1; } + +/* AsciiGraph — blended with page, no code-block chrome */ + +/* Pinned webfont so box-drawing characters (│ ─ ▼ └ ┘) line up at exactly + 1ch on every platform. The system-monospace fallbacks (SF Mono, Consolas, + Roboto Mono) substitute a different font for those glyphs and the diagrams + drift, badly on Android. */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('/fonts/JetBrainsMono-Regular.woff2') format('woff2'); +} + +:root { + --ascii-graph-font: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', 'Courier New', monospace; +} + +.ascii-graph { + background: transparent; + border: none; + color: #475569; + display: block; + font-family: var(--ascii-graph-font); + font-size: 0.88rem; + line-height: 1.4; + /* Disable contextual alternates / ligatures so adjacent glyphs never + collapse or shift. */ + font-variant-ligatures: none; + font-feature-settings: 'liga' 0, 'calt' 0; + margin: 2.5rem auto 3rem; + max-width: 100%; + overflow-x: auto; + padding: 0.5rem 0; + text-align: left; + white-space: pre; + width: fit-content; +} + +.markdown > h2 { + margin-top: 2rem; +} + +.markdown > h3 { + margin-top: 1.75rem; +} + +html[data-theme='dark'] .ascii-graph { + color: #cbd5e1; +} + +.ascii-graph__highlight { + background: rgba(45, 114, 217, 0.14); + border-radius: 2px; + color: #1d4ed8; +} + +html[data-theme='dark'] .ascii-graph__highlight { + background: rgba(96, 165, 250, 0.18); + color: #93c5fd; +} + +.ascii-graph__accent { + opacity: 0.5; +} + +.topology-toggle { + text-align: center; + margin: 1.25rem 0 1.5rem; +} + +.topology-toggle__control { + display: inline-flex; + background: rgba(15, 23, 42, 0.04); + border-radius: 999px; + padding: 3px; + margin-bottom: 0.25rem; +} + +.topology-toggle__button { + appearance: none; + border: 0; + background: transparent; + font-family: var(--ifm-font-family-base); + font-size: 0.85rem; + font-weight: 500; + color: #475569; + padding: 0.35rem 0.95rem; + border-radius: 999px; + cursor: pointer; + transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease; +} + +.topology-toggle__button:hover { + color: #1d4ed8; +} + +.topology-toggle__button--active { + background: #ffffff; + color: #1d4ed8; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08); +} + +.topology-toggle .ascii-graph { + margin-top: 0.25rem; +} + +.topology-toggle__secondary { + margin-top: 0 !important; + padding-top: 0; +} + +.intro-ascii { + position: relative; + /* Match .ascii-graph's centering convention so this lines up with the + other ASCII diagrams on the page. */ + margin: 2.5rem auto 3rem; + width: fit-content; + max-width: 100%; + overflow-x: auto; + /* Lock font + line-height to .ascii-graph values so that 1ch / 1lh on + absolutely-positioned children align with the pre's character grid. */ + font-family: var(--ascii-graph-font); + font-size: 0.88rem; + line-height: 1.4; + font-variant-ligatures: none; + font-feature-settings: 'liga' 0, 'calt' 0; +} + +.intro-ascii__frame { + margin: 0; + padding: 0; +} + +.intro-ascii__logo { + position: absolute; + width: 1.7em; + height: 1.7em; + transform: translate(-50%, -50%); + object-fit: contain; + font: inherit; + pointer-events: auto; +} + +html[data-theme='dark'] .topology-toggle__control { + background: rgba(255, 255, 255, 0.06); +} + +html[data-theme='dark'] .topology-toggle__button { + color: #94a3b8; +} + +html[data-theme='dark'] .topology-toggle__button:hover { + color: #93c5fd; +} + +html[data-theme='dark'] .topology-toggle__button--active { + background: rgba(45, 114, 217, 0.22); + color: #93c5fd; + box-shadow: none; +} + +/* page intro hook — the page's positioning statement */ + +.zcp-intro-hook { + background: rgba(45, 114, 217, 0.06); + border-left: 4px solid #2d72d9; + border-radius: 0 8px 8px 0; + color: #172033; + font-size: 1.05rem; + font-weight: 500; + line-height: 1.55; + margin: 1.5rem 0 2.5rem; + padding: 1.1rem 1.35rem; +} + +.zcp-intro-hook p { + margin: 0; +} + +.zcp-intro-hook p + p { + margin-top: 0.7rem; +} + +.zcp-intro-hook strong { + color: #1d4ed8; + font-weight: 650; +} + +html[data-theme='dark'] .zcp-intro-hook { + background: rgba(96, 165, 250, 0.08); + border-left-color: #60a5fa; + color: #f8fafc; +} + +html[data-theme='dark'] .zcp-intro-hook strong { + color: #93c5fd; +} diff --git a/apps/docs/src/plugins/markdown-source/__tests__/converters.test.js b/apps/docs/src/plugins/markdown-source/__tests__/converters.test.js new file mode 100644 index 000000000..2e5e12eba --- /dev/null +++ b/apps/docs/src/plugins/markdown-source/__tests__/converters.test.js @@ -0,0 +1,513 @@ +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const C = require('../converters'); + +// ---------- Note -------------------------------------------------------- + +test('convertNoteToMarkdown: tip with title -> :::tip[title]', () => { + const input = 'Body text here'; + const out = C.convertNoteToMarkdown(input).trim(); + assert.equal(out, ':::tip[Hello World]\n\nBody text here\n\n:::'); +}); + +test('convertNoteToMarkdown: warning without title -> :::warning', () => { + const input = 'Be careful'; + const out = C.convertNoteToMarkdown(input).trim(); + assert.equal(out, ':::warning\n\nBe careful\n\n:::'); +}); + +test('convertNoteToMarkdown: error type -> :::danger', () => { + const input = 'Something failed'; + const out = C.convertNoteToMarkdown(input).trim(); + assert.match(out, /^:::danger/); +}); + +test('convertNoteToMarkdown: success type -> :::tip', () => { + const input = 'Worked'; + const out = C.convertNoteToMarkdown(input).trim(); + assert.match(out, /^:::tip/); +}); + +test('convertNoteToMarkdown: no type -> :::note', () => { + const input = 'Just a note'; + const out = C.convertNoteToMarkdown(input).trim(); + assert.equal(out, ':::note[Heads up]\n\nJust a note\n\n:::'); +}); + +test('convertNoteToMarkdown: multi-line body with markdown preserved', () => { + const input = ` +Line one +**bold** here + +Another paragraph with \`code\`. +`; + const out = C.convertNoteToMarkdown(input); + assert.match(out, /:::tip\[Multi\]/); + assert.match(out, /Line one/); + assert.match(out, /\*\*bold\*\* here/); + assert.match(out, /Another paragraph with `code`\./); + assert.match(out, /:::\n$/); +}); + +test('convertNoteToMarkdown: multiple Notes in one document', () => { + const input = `first +some text +second`; + const out = C.convertNoteToMarkdown(input); + assert.match(out, /:::tip\[A\]/); + assert.match(out, /:::warning/); + assert.match(out, /some text/); +}); + +// ---------- AsciiGraph ------------------------------------------------- + +test('convertAsciiGraphToMarkdown: unwraps JSX template literals into code fence', () => { + const input = '{`hello world`}'; + const out = C.convertAsciiGraphToMarkdown(input).trim(); + assert.equal(out, '```text\nhello world\n```'); +}); + +test('convertAsciiGraphToMarkdown: strips inline span highlights', () => { + const input = '{`foo `}{`HL`}{` bar`}'; + const out = C.convertAsciiGraphToMarkdown(input).trim(); + assert.equal(out, '```text\nfoo HL bar\n```'); +}); + +test('convertAsciiGraphToMarkdown: preserves multi-line ASCII art', () => { + const input = `{\` \`}{\`zcp\`}{\` + │ + ▼ + edit code\`}`; + const out = C.convertAsciiGraphToMarkdown(input); + assert.match(out, /```text/); + assert.match(out, / zcp/); + assert.match(out, / │/); + assert.match(out, / ▼/); + assert.match(out, / edit code/); + assert.doesNotMatch(out, / { + const input = '{`x`}'; + const out = C.convertAsciiGraphToMarkdown(input); + assert.doesNotMatch(out, /ariaLabel/); +}); + +test('convertAsciiGraphToMarkdown: handles two AsciiGraphs in same document', () => { + const input = '{`one`}\nbetween\n{`two`}'; + const out = C.convertAsciiGraphToMarkdown(input); + assert.match(out, /one/); + assert.match(out, /between/); + assert.match(out, /two/); + assert.equal((out.match(/```text/g) || []).length, 2); +}); + +// ---------- DocCardList ------------------------------------------------ + +test('convertDocCardListToMarkdown: single self-closing item with description', () => { + const input = ``; + const out = C.convertDocCardListToMarkdown(input); + assert.match(out, /- \[Quickstart\]\(\/zcp\/quickstart\) — AI Agent recipe and a real change in five minutes\./); +}); + +test('convertDocCardListToMarkdown: multiple items with Icons[...] inside', () => { + const input = ``; + const out = C.convertDocCardListToMarkdown(input); + assert.match(out, /- \[A\]\(\/a\) — desc A/); + assert.match(out, /- \[B\]\(\/b\) — desc B/); + assert.match(out, /- \[C\]\(\/c\) — desc C/); +}); + +test('convertDocCardListToMarkdown: item without description renders link only', () => { + const input = ``; + const out = C.convertDocCardListToMarkdown(input); + assert.match(out, /- \[X\]\(\/x\)\n/); + assert.doesNotMatch(out, /—/); +}); + +test('convertDocCardListToMarkdown: surrounding text is preserved', () => { + const input = `before + +after`; + const out = C.convertDocCardListToMarkdown(input); + assert.match(out, /before/); + assert.match(out, /after/); + assert.match(out, /- \[X\]\(\/x\)/); +}); + +test('convertDocCardListToMarkdown: items with single-quoted strings containing ]', () => { + // Critical regression test: `customProps: { icon: Icons['x'] }` contains `]`, + // which would terminate a naive lazy regex match. + const input = ``; + const out = C.convertDocCardListToMarkdown(input); + assert.match(out, /- \[P\]\(\/p\) — d/); +}); + +// ---------- Badge ------------------------------------------------------ + +test('convertBadgeToMarkdown: optional -> *[Optional]*', () => { + assert.equal(C.convertBadgeToMarkdown(''), '*[Optional]*'); +}); + +test('convertBadgeToMarkdown: required -> *[Required]*', () => { + assert.equal(C.convertBadgeToMarkdown(''), '*[Required]*'); +}); + +test('convertBadgeToMarkdown: required-some -> longer label', () => { + assert.equal( + C.convertBadgeToMarkdown(''), + '*[Required for some runtimes]*' + ); +}); + +test('convertBadgeToMarkdown: custom with text', () => { + assert.equal( + C.convertBadgeToMarkdown(''), + '*[Beta]*' + ); +}); + +test('convertBadgeToMarkdown: no attributes defaults to optional', () => { + assert.equal(C.convertBadgeToMarkdown(''), '*[Optional]*'); +}); + +// ---------- Button ----------------------------------------------------- + +test('convertButtonToMarkdown: unwraps children', () => { + const input = ''; + assert.equal(C.convertButtonToMarkdown(input), 'click'); +}); + +// ---------- Link ------------------------------------------------------- + +test('convertLinkToMarkdown: plain link', () => { + assert.equal( + C.convertLinkToMarkdown('Read more'), + '[Read more](/features/infrastructure)' + ); +}); + +test('convertLinkToMarkdown: link with inline code', () => { + assert.equal( + C.convertLinkToMarkdown('use zcli'), + '[use `zcli`](/x)' + ); +}); + +test('convertLinkToMarkdown: no href returns children unchanged', () => { + assert.equal( + C.convertLinkToMarkdown('orphan'), + 'orphan' + ); +}); + +// ---------- CustomCard ------------------------------------------------- + +test('convertCustomCardToMarkdown: title + emoji -> ### emoji title', () => { + const input = 'body content'; + const out = C.convertCustomCardToMarkdown(input).trim(); + assert.equal(out, '### 🚀 No Fuss\n\nbody content'); +}); + +test('convertCustomCardToMarkdown: title only', () => { + const input = 'stuff'; + const out = C.convertCustomCardToMarkdown(input).trim(); + assert.equal(out, '### Just Title\n\nstuff'); +}); + +// ---------- DeployButton ----------------------------------------------- + +test('convertDeployButtonToMarkdown: builds recipe URL', () => { + assert.equal( + C.convertDeployButtonToMarkdown(''), + '[Deploy "deno" recipe on Zerops](https://app.zerops.io/recipe/?lf=deno)' + ); +}); + +test('convertDeployButtonToMarkdown: missing link -> empty', () => { + assert.equal( + C.convertDeployButtonToMarkdown(''), + '' + ); +}); + +// ---------- Dropdown --------------------------------------------------- + +test('convertDropdownToMarkdown: single item -> #### heading + body', () => { + const input = 'body here'; + const out = C.convertDropdownToMarkdown(input); + assert.match(out, /#### X/); + assert.match(out, /body here/); +}); + +test('convertDropdownToMarkdown: multiple items', () => { + const input = ` +first body +second body +`; + const out = C.convertDropdownToMarkdown(input); + assert.match(out, /#### One/); + assert.match(out, /first body/); + assert.match(out, /#### Two/); + assert.match(out, /second body/); +}); + +// ---------- Video ------------------------------------------------------ + +test('convertVideoToMarkdown: self-closing with src', () => { + assert.equal( + C.convertVideoToMarkdown('