From 0ef33245a43eef0f53f9d84b59c943b6ea0c24ff Mon Sep 17 00:00:00 2001 From: DroidAgent <154886644+echobt@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:41:04 +0000 Subject: [PATCH 1/2] docs: restructure the public IA and fix duplicate page titles The docs site worked but was harder to navigate than it needed to be. Two problems in particular: every product shipped both a `X/index` hub and a separate `X/overview` page competing as the entry point, and 26 pages rendered a second `

` because a body heading duplicated the frontmatter title. Information architecture - One hub per product. `chat|code|bot|design/overview` are merged into the matching `index` page and removed; redirects cover the old URLs. - `docs.json` groups are re-cut around what a reader is trying to do: each product tab now runs hub, task guides, and a Help group. - Groups carry `root` pages so a sidebar title opens something real. - Added redirects for paths readers and search engines still hold: `/chat/artifacts`, `/code/goals`, `/troubleshooting`, `/getting-started/desktop-app`, and the merged overviews. Titles - Removed the body `#` heading from the 24 problem pages and `status.mdx`, where it duplicated the frontmatter title. The live pages rendered two `

` elements; the canonical one is the page title. Content - Added `chat/troubleshooting.mdx`. Chat was the only product tab without one while Code, Bot, and CLI each had a page. - Rewrote `platform.mdx` from a placeholder into a real answer about what does and does not exist, and `problems/index.mdx` around status families and the two different 429s. - Gave `api/overview.mdx` and `api/errors.mdx` practical guidance on which problem fields are safe to render. - Every page now closes with a Related or Next section, so no page is a dead end. Home gained a reference grid and a product chooser. Housekeeping - Removed the FernDesk sync workflow, script, test, and docs. Mintlify is the source of truth, so there is no downstream mirror to sync; the workflow would have pushed this rewrite to a second system. - `check-docs-site.mjs` now walks a group's `root` as well as its `pages`, with a regression test. A dead group root would otherwise publish as a 404 sidebar title. - README and AGENTS record the one-hub-per-product rule and the new check. Validation: check-docs-site, its shell suite, docs-ui, and mint validate all pass. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +- .github/workflows/ferndesk-sync.yml | 97 --- .gitignore | 6 +- AGENTS.md | 4 + README.md | 34 +- api/errors.mdx | 47 +- api/overview.mdx | 32 +- bot/desktop.mdx | 2 +- bot/how-it-works.mdx | 7 + bot/index.mdx | 80 ++- bot/overview.mdx | 58 -- bot/troubleshooting.mdx | 7 + changelog.mdx | 6 + chat/images.mdx | 7 + chat/index.mdx | 81 ++- chat/overview.mdx | 59 -- chat/plans.mdx | 7 + chat/projects.mdx | 7 + chat/sharing.mdx | 7 + chat/tools.mdx | 7 + chat/troubleshooting.mdx | 66 ++ cli/index.mdx | 7 + code/how-it-works.mdx | 7 + code/index.mdx | 59 +- code/overview.mdx | 51 -- code/troubleshooting.mdx | 7 + design/index.mdx | 51 +- design/overview.mdx | 43 -- docs.json | 167 ++++-- getting-started/accounts.mdx | 7 + getting-started/downloads.mdx | 7 + getting-started/plans.mdx | 7 + getting-started/quickstart.mdx | 9 + getting-started/settings.mdx | 7 + getting-started/troubleshooting.mdx | 8 + index.mdx | 40 +- platform.mdx | 45 +- problems/bad_request.mdx | 2 - problems/concurrency_limit_reached.mdx | 2 - problems/conflict.mdx | 2 - problems/content_policy.mdx | 2 - problems/entitlement_required.mdx | 2 - problems/forbidden.mdx | 2 - problems/gone.mdx | 2 - problems/idempotency_key_reuse.mdx | 2 - problems/index.mdx | 43 +- problems/internal.mdx | 2 - problems/invalid_credential.mdx | 2 - problems/invalid_state.mdx | 2 - problems/jurisdiction_restricted.mdx | 2 - problems/no_capacity.mdx | 2 - problems/not_found.mdx | 2 - problems/payload_too_large.mdx | 2 - problems/quota_exceeded.mdx | 2 - problems/rate_limited.mdx | 2 - problems/safety_intervention.mdx | 2 - problems/service_unavailable.mdx | 2 - problems/unauthenticated.mdx | 2 - problems/unsupported_media_type.mdx | 2 - problems/upstream_failure.mdx | 2 - problems/upstream_timeout.mdx | 2 - problems/validation_failed.mdx | 2 - scripts/FERNDESK.md | 91 --- scripts/check-docs-site.mjs | 3 + scripts/ferndesk_sync.py | 696 ---------------------- scripts/tests/check-docs-site.test.sh | 64 ++ scripts/tests/ferndesk-sync-retry.test.py | 350 ----------- security/overview.mdx | 8 + status.mdx | 8 +- 69 files changed, 805 insertions(+), 1652 deletions(-) delete mode 100644 .github/workflows/ferndesk-sync.yml delete mode 100644 bot/overview.mdx delete mode 100644 chat/overview.mdx create mode 100644 chat/troubleshooting.mdx delete mode 100644 code/overview.mdx delete mode 100644 design/overview.mdx delete mode 100644 scripts/FERNDESK.md delete mode 100755 scripts/ferndesk_sync.py delete mode 100644 scripts/tests/ferndesk-sync-retry.test.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e19510..993c320 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,6 @@ jobs: node-version: 22 - run: node scripts/check-docs-site.mjs - run: bash scripts/tests/check-docs-site.test.sh - - name: Check the FernDesk write-path retry policy - run: python3 scripts/tests/ferndesk-sync-retry.test.py + - run: node scripts/tests/docs-ui.test.mjs - name: Validate the Mintlify build run: npm exec --yes --package=mint@4.2.876 -- mint validate diff --git a/.github/workflows/ferndesk-sync.yml b/.github/workflows/ferndesk-sync.yml deleted file mode 100644 index 1734c78..0000000 --- a/.github/workflows/ferndesk-sync.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: FernDesk sync - -# Lightest durable path: after docs land on main (and on explicit dispatch from -# backend production deploy), upsert Mintlify MDX into FernDesk Production. -# Staging target is available for previews; Compliance owns the always-à-jour gate. -# -# Factory Droid (custom:deepseek/deepseek-v4.1-flash, effort medium) is the -# agent path for non-mechanical content work / gap fills — see -# scripts/FERNDESK.md. This workflow is the mechanical always-on sync. - -on: - push: - branches: [main] - paths: - - "**/*.mdx" - - "**/*.md" - - docs.json - - scripts/ferndesk_sync.py - - .github/workflows/ferndesk-sync.yml - repository_dispatch: - types: [ferndesk-sync] - workflow_dispatch: - inputs: - target: - description: FernDesk section target - type: choice - options: [production, staging] - default: production - dry_run: - description: Plan only (FERNDESK_DRY_RUN=1) - type: boolean - default: false - -permissions: - contents: read - -concurrency: - group: ferndesk-sync-${{ github.event.inputs.target || 'production' }} - cancel-in-progress: false - -jobs: - sync: - name: Sync Mintlify → FernDesk - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Resolve target - id: t - run: | - set -euo pipefail - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "target=${{ inputs.target }}" >> "$GITHUB_OUTPUT" - echo "dry=${{ inputs.dry_run }}" >> "$GITHUB_OUTPUT" - elif [ "${{ github.event_name }}" = "repository_dispatch" ]; then - echo "target=${{ github.event.client_payload.target || 'production' }}" >> "$GITHUB_OUTPUT" - echo "dry=false" >> "$GITHUB_OUTPUT" - else - echo "target=production" >> "$GITHUB_OUTPUT" - echo "dry=false" >> "$GITHUB_OUTPUT" - fi - - - name: Install curl_cffi - run: | - set -euo pipefail - python3 -m pip install --upgrade pip - python3 -m pip install curl_cffi - - - name: Restore FernDesk slug cache - uses: actions/cache@v4 - with: - path: | - .ferndesk-slug-cache-production.json - .ferndesk-slug-cache-staging.json - key: ferndesk-slug-${{ steps.t.outputs.target }}-${{ github.run_id }} - restore-keys: | - ferndesk-slug-${{ steps.t.outputs.target }}- - - - name: Sync - env: - FERNDESK_API_KEY: ${{ secrets.FERNDESK_API_KEY }} - FERNDESK_TARGET: ${{ steps.t.outputs.target }} - FERNDESK_DRY_RUN: ${{ steps.t.outputs.dry == 'true' && '1' || '0' }} - DOCS_ROOT: ${{ github.workspace }} - run: | - set -euo pipefail - if [ -z "${FERNDESK_API_KEY:-}" ]; then - echo "FERNDESK_API_KEY secret missing — add it under repo Actions secrets (never commit)." >&2 - exit 1 - fi - python3 scripts/ferndesk_sync.py - if [ -f ferndesk-sync-summary.json ]; then - echo '### FernDesk sync' >> "$GITHUB_STEP_SUMMARY" - echo '```json' >> "$GITHUB_STEP_SUMMARY" - cat ferndesk-sync-summary.json >> "$GITHUB_STEP_SUMMARY" - echo '```' >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/.gitignore b/.gitignore index 627aaa5..2c0f18a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,5 @@ .mintlify/ node_modules/ -# Python bytecode from scripts/tests +# Python bytecode from scripts __pycache__/ - -# FernDesk sync local caches (never commit API-derived IDs casually) -.ferndesk-slug-cache*.json -ferndesk-sync-summary.json diff --git a/AGENTS.md b/AGENTS.md index 7d5477d..2c314bc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,8 +10,12 @@ repository root, not in `apps/docs`. Preview and validation commands are in - Preserve the Home + Documentation navbar and ink-on-cream CTAs. - Keep every navigation slug backed by a page and every problem page's `type` URL on `https://docs.cortex.foundation/problems/{code}`. +- One hub page per product (`/index.mdx`). Do not add a second + overview page beside it, and give every page a `title`, a `description`, + and a closing **Related** or **Next** section. - Run `node scripts/check-docs-site.mjs` and `bash scripts/tests/check-docs-site.test.sh`, then + `node scripts/tests/docs-ui.test.mjs` and `npm exec --yes --package=mint@4.2.876 -- mint validate` before committing. - For error-code or endpoint changes, also run the checker with a backend checkout as its first argument. Coordinate the two PRs; the backend owns diff --git a/README.md b/README.md index f0540a1..06797e3 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ the content directory set to the repository root (not `apps/docs`). Every Cortex custom domain that CNAMEs to a third-party hostname is a **Direct CNAME (DNS only)** — `docs.cortex.foundation` (Mintlify), -`status.cortex.foundation` (status page), the FernDesk **Connect domain**, and -the `software.cortex.foundation` / `releases.cortex.foundation` R2 custom +`status.cortex.foundation` (status page), and the +`software.cortex.foundation` / `releases.cortex.foundation` R2 custom domains owned by [`CortexLM/backend`](https://github.com/CortexLM/backend) (`docs/software-cdn.md`). @@ -36,11 +36,37 @@ domains owned by > **Use a Direct CNAME (DNS only). Do not enable orange-cloud Proxied** — that > causes Cloudflare **Cross-User Banned** (Error 1014). +## Structure + +Mintlify is the single source of truth for the public docs. There is no +downstream mirror to sync: the site publishes from this repository, branch +`main`, and `docs.json` is the whole navigation. + +| Tab | Holds | Entry point | +| --- | --- | --- | +| Get started | Quickstart, what Cortex is, accounts, plans, settings, downloads, help | `getting-started/quickstart` | +| Chat | The conversation product — projects, Library, plans, models, tools, research, media | `chat/index` | +| Code | The coding agent — sessions, modes, GitHub, runtimes, review | `code/index` | +| Bot | The computer-using agent — computer, tools, approvals, routines, skills | `bot/index` | +| CLI | The terminal front-end to Code — install, TUI, slash commands, sessions, headless, extend | `cli/index` | +| Design | Canvases and the Design library | `design/index` | +| API | The RFC 9457 problem format and the catalog of error codes | `api/overview` | +| Changelog | Dated release notes, and the deferred Platform API | `changelog` | + +Each product tab opens on a single hub page (`/index.mdx`). Do not +re-introduce a second overview page beside it — one entry point per product, +with the task guides beneath it in the sidebar. + +Every page ends with a **Related** or **Next** section so a reader is never at +a dead end, and every page carries a `title` and a `description` in its +frontmatter. Product pages also carry an `image` for link previews. + ## Checks ```bash node scripts/check-docs-site.mjs bash scripts/tests/check-docs-site.test.sh +node scripts/tests/docs-ui.test.mjs npm exec --yes --package=mint@4.2.876 -- mint validate ``` @@ -71,6 +97,10 @@ that adds the code. The full check fails when: Do not invent endpoints. There is no inference Platform API section here — see `platform.mdx`. Sign in via the app; this tree has no auth stack. +`scripts/check-docs-site.mjs` enforces all of the above, including that every +`docs.json` navigation slug resolves to a page. Run it before pushing; a +sidebar link with no MDX behind it would otherwise publish as a 404. + ## Visuals Brand green `#1F4945` is for doodle accents, illustration focus rings, and diff --git a/api/errors.mdx b/api/errors.mdx index 225d4fc..12fd1d7 100644 --- a/api/errors.mdx +++ b/api/errors.mdx @@ -1,9 +1,9 @@ --- title: "Errors" -description: "RFC 9457 problem+json. Branch on code. type URIs live on this site." +description: "Every Cortex failure is an RFC 9457 problem document. Branch on code, read request_id, and know which fields are safe to render." --- -Every failure is `application/problem+json`: +Every failure Cortex returns is an `application/problem+json` document: ```json { @@ -16,14 +16,39 @@ Every failure is `application/problem+json`: } ``` -- **`code`** is the contract. Mirror: `@cortex/api-types` `ErrorCode`. -- **`title` / `detail`** are English, for logs and humans reading raw JSON. - The web app renders catalog copy. Three codes may show `detail` as a - secondary line: `validation_failed`, `bad_request`, `invalid_state`. -- **`type`** is dereferenceable: [Problem catalog](/problems). -- **`request_id`** is always present and safe to show. +## Fields -User-facing `detail` names a product surface, never a vendor ("The audio -service is temporarily unavailable.", not a subprocessor's name). +| Field | Always present | What to do with it | +| --- | --- | --- | +| **`code`** | Yes | **This is the contract.** Branch on `code`, never on `title` or `detail`. The same code means the same thing on every surface. | +| **`type`** | Yes | A dereferenceable URL for the code — [Problem catalog](/problems). Stable; safe to link. | +| **`status`** | Yes | The HTTP status. Present in the body as well so a logged document is self-describing. | +| **`request_id`** | Yes | Always present, always safe to show or log. Quote it when reporting a problem. | +| **`title`** | Yes | English, for logs and humans reading raw JSON. Do not render it to end users. | +| **`detail`** | Sometimes | English, for logs and humans reading raw JSON. Do not render it to end users. | -Full catalog: [Problems](/problems). +## Rendering rules + +The web app renders catalog copy for a code rather than the wire text. If you are building a client against these documents, do the same. + +- **Render your own copy** for `code`. The catalog page for each code explains the situation in product language. +- **Do not render `detail`.** It is diagnostic text, and it may name an internal component or a specific resource id. +- **Three codes may show `detail` as a secondary line**: `validation_failed`, `bad_request`, and `invalid_state`. Even there it is supplementary, not the message. +- **User-facing text never names a vendor.** `detail` names a Cortex surface — *The audio service is temporarily unavailable.* — never a subprocessor. + +## Handling a code + +| Situation | What to do | +| --- | --- | +| The catalog marks the code **Retryable: yes** | Retry after the delay the response suggests, then back off. | +| The catalog marks it **Retryable: no** | Retrying the same request will fail the same way. Change the request, the plan, or the state. | +| You see `rate_limited` or `quota_exceeded` | Both are HTTP 429 but mean different things — transport throttling versus a plan window. See [Plans and quotas](/getting-started/plans). | +| You see `internal` or an upstream code | Keep the `request_id`. Check [status.cortex.foundation](https://status.cortex.foundation) if it repeats. | + +Full list with statuses and retry guidance: [Problem catalog](/problems). + +## Related + +- [Problem catalog](/problems) — every code, status, and retry answer. +- [Public API](/api/overview) — what this tab covers. +- [Troubleshooting](/getting-started/troubleshooting) — what to do about a message you see in the product. diff --git a/api/overview.mdx b/api/overview.mdx index 3cc9c64..7ea9148 100644 --- a/api/overview.mdx +++ b/api/overview.mdx @@ -1,22 +1,36 @@ --- title: "Public API" -description: "Problem type URIs. Sign in via the app. The inference Platform API is coming soon." +description: "What this site documents about the Cortex API — the RFC 9457 problem format and its type URIs. Sign in via the app." --- -This site is **end-user product documentation** for Cortex Chat, Code, Bot, and Design. +Cortex is used through its apps, not through a public API you write against. This tab documents the one part of the wire contract that is public and stable: the shape of an error. -**Sign in at [cortex.foundation](https://cortex.foundation).** The CLI and desktop app use the same account. This site does not document login, sessions, or identity-provider wire protocol. +**Sign in at [cortex.foundation](https://cortex.foundation).** The CLI and the desktop apps use the same account. This site does not document login, sessions, or identity-provider wire protocol. ## What is public here -- [Problem catalog](/problems) — RFC 9457 `type` URIs (`https://docs.cortex.foundation/problems/{code}`) -- [Errors](/api/errors) — how a problem document is shaped -- Product guides: [Chat](/chat), [Code](/code), [Bot](/bot), [Design](/design) +| Page | What it covers | +| --- | --- | +| [Problem catalog](/problems) | Every `type` URI — `https://docs.cortex.foundation/problems/{code}` — with its HTTP status and whether retrying helps | +| [Errors](/api/errors) | How a problem document is shaped and which fields you can rely on | +| [Platform API](/platform) | Why there is no inference API yet | + +Product guides live under their own tabs: [Chat](/chat), [Code](/code), [Bot](/bot), [CLI](/cli), [Design](/design). + +## Why the error format is public + +Every Cortex surface — the web app, the desktop apps, and the CLI — reads the same problem documents. When something fails, the message you see names a Cortex surface and carries a stable `code`. Publishing the codes means the message in the product, the message in the terminal, and this catalog all say the same thing. Connect apps and GitHub from **Settings** and **Customize** in the product. There is no public plugin webhook catalog on this site. ## What is not here -- Authentication, session cookies, device codes, or identity-provider internals -- The app's private session API -- An inference Platform API — [Coming soon](/platform), with **no endpoints** +- Authentication, session cookies, device codes, or identity-provider internals. +- The app's private session API. It is not a supported integration surface. +- An inference Platform API. See [Platform API](/platform) — no endpoints, SDKs, or keys exist. + +## Related + +- [Errors](/api/errors) — the problem document format. +- [Problem catalog](/problems) — all codes in one table. +- [Troubleshooting](/getting-started/troubleshooting) — what to do when you see one. diff --git a/bot/desktop.mdx b/bot/desktop.mdx index 5406096..92c4f8d 100644 --- a/bot/desktop.mdx +++ b/bot/desktop.mdx @@ -59,5 +59,5 @@ There is no Chat or Code entry in this sidebar. For those, use the web app or th ## Related -- [Bot overview](/bot/overview) +- [Cortex Bot](/bot) — what a bot is and how to create your first one. - [Downloads](/getting-started/downloads) — every way to run Cortex. diff --git a/bot/how-it-works.mdx b/bot/how-it-works.mdx index 4ee9b9e..d74689e 100644 --- a/bot/how-it-works.mdx +++ b/bot/how-it-works.mdx @@ -53,3 +53,10 @@ The planner describes the control it wants — *the Save button in the dialog* | Machine | Snippet sandbox | Headless guest | Guest **with** a desktop | See [How Chat works](/chat/how-it-works) and [How Code works](/code/how-it-works). + +## Related + +- [Tools](/bot/tools) — the catalog behind the loop. +- [Approvals](/bot/approvals) — what parks, and what does not. +- [Computer](/bot/computer) — the guest the loop runs on. +- [Bot troubleshooting](/bot/troubleshooting) — turns that stop, and why. diff --git a/bot/index.mdx b/bot/index.mdx index a16c5ce..a6cbee1 100644 --- a/bot/index.mdx +++ b/bot/index.mdx @@ -1,29 +1,72 @@ --- -title: "Bot guides" -description: "Cortex Bot — a bot with its own computer. Browses, clicks, and works on a desktop you can watch; parks consequential steps behind approvals; runs routines on your local weekday hours." +title: "Cortex Bot" +description: "A bot with its own computer. Browses, clicks, and works on a desktop you can watch; parks consequential steps behind approvals; runs routines on your local weekday hours." image: "/images/banners/bot.png" --- -Cortex Bot is an always-on teammate. Each bot can have a computer — a screen, a browser, files — and you watch it work. Your bots are listed once, in the sidebar; the pane is the open bot, conversation in the middle and its computer on the right. Bots belong to a signed-in account. +Cortex Bot is an always-on teammate. Where Chat answers and Code changes a repository, a bot **does things on a desktop**: opens a browser, clicks through a flow, reads files, runs a shell command, and reports back in a few short messages. You can watch its screen while it works, and it asks before it does anything consequential. - - Cortex Bot public page — a bot with its own computer +Bots belong to a signed-in account. Your bots are listed once, in the sidebar; the pane is the open bot — conversation in the middle, its computer on the right. + + + Cortex Bot feature grid — A real desktop not a transcript, Your bots listed once, Plugins you chose, One product one sign-in +## What a bot is + +| Commitment | What it means | +| --- | --- | +| **A real desktop, not a transcript** | Each bot can have a computer: a screen, a browser, files. You watch it work instead of pasting a web page into chat. | +| **Your bots listed once** | One list in the sidebar, in the web app and the [Bot desktop app](/bot/desktop). | +| **Plugins you chose** | A bot never inherits a connection you did not assign to it. | +| **One product, one sign-in** | The same account as Chat and Code. | + ## Start here Sign in, create a bot, open its computer, give it a first task. - - One list of bots, first-run setup, who can drive a bot. + + A live desktop on a Cloud guest — screen, browser, files. A desktop app built around agents, routines, and channels — no Chat or Code sidebar. +## Create your first bot + + + + Bots are member objects. A guest sees the public product page at `/bot`. + + + An account with no bots yet gets the **setup form**, not an empty list. Give the bot a name and, if it helps, a description of the job. + + + On the Computer rail choose **Open desktop**. The Cloud guest starts — the rail reads **Connecting** while it does — and the live desktop appears: screen, browser, files. + + + Describe the job in the conversation. + + ```text + Open our status page, check whether any component is degraded, and tell me what you find. + ``` + + The bot works on its desktop while you watch. It reports back in short messages rather than a transcript of every click. + + + When the bot reaches a consequential action — changing something in a connected app, or a dangerous computer step — it parks and shows **Allow / Deny / Always**. Nothing runs until you choose. See [Approvals](/bot/approvals). + + + Add a **routine** for work you want repeated. Routines fire in your local weekday hours. See [Routines](/bot/routines). + + + Choose **Sleep** on the Computer rail when you are done. The next task wakes the guest before the bot is prompted. + + + ## Core guides @@ -47,7 +90,28 @@ Cortex Bot is an always-on teammate. Each bot can have a computer — a screen, +## Give the bot tools + +Under **Customize → Connectors**, connect an app and make it available to **Cortex Bot** (or to both Chat and Bot). A bot never inherits a connection you did not assign. See [Tools and connectors](/chat/tools). + +## Good first tasks + +| Task | Why it suits a bot | +| --- | --- | +| Check a dashboard and summarise what changed | Browsing plus a short report | +| Fill a recurring form from a document you attach | Desktop work with a clear stopping point | +| Watch a page and notify you when something appears | A routine plus `notify` | +| Pull a report from a connected app every Monday | A routine with an approved connection | + ## Advanced - [How Bot works](/bot/how-it-works) — user-visible bubbles only, no round counter, waking a hibernated guest. -- [Troubleshooting](/bot/troubleshooting) — Connecting versus Stream Unavailable, parked steps, the setup form. +- [Bot troubleshooting](/bot/troubleshooting) — Connecting versus Stream Unavailable, parked steps, the setup form. +- [Security and privacy](/security/overview) — what Cortex stores and where a bot's computer runs. + +## Next + +1. [Bot quickstart](/bot/quickstart) — create a bot and give it a first task. +2. [Computer](/bot/computer) — open the desktop, and choose a host. +3. [Approvals](/bot/approvals) — decide what a bot may do without asking. +4. [Routines](/bot/routines) — put repeated work on a schedule. diff --git a/bot/overview.mdx b/bot/overview.mdx deleted file mode 100644 index a76d2f4..0000000 --- a/bot/overview.mdx +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: "Cortex Bot" -description: "A bot with its own computer, listed once in the sidebar. An account with no bots gets the setup form, not an empty list. Guests see the public product page." ---- - -Cortex Bot is a computer-using agent. Where Chat answers and Code changes a repository, a bot **does things on a desktop**: opens a browser, clicks through a flow, reads files, runs a shell command, and reports back in a few short messages. You can watch its screen while it works, and it asks before it does anything consequential. - - - Cortex Bot feature grid — A real desktop not a transcript, Your bots listed once, Plugins you chose, One product one sign-in - - -## The shape of the product - -| Commitment | What it means | -| --- | --- | -| **A real desktop, not a transcript** | Each bot can have a computer: a screen, a browser, files. You watch it work instead of pasting a web page into chat. | -| **Your bots, listed once** | The account's bots live in the sidebar. The pane is the open bot — conversation in the middle, its computer on the right. | -| **Plugins you chose** | A connected app reaches Chat, Bot, or both — you pick. A bot never inherits a tool you did not assign. | -| **One product, one sign-in** | Create a bot after you sign in. A guest session cannot own something it could never come back to revoke. | - -## First run - -An account with no bots gets the **setup form**, not an empty list. Name the bot, decide what it is for, and create it. There are no pre-made bots with invented names waiting in the list. - -## The open bot - -| Area | What is there | -| --- | --- | -| **Sidebar** | Your bots, listed once | -| **Centre** | The conversation — short bubbles from the bot, your replies, and parked approvals | -| **Computer rail (right)** | The live desktop, with **Open desktop** and **Sleep**, plus routines | - -## Who can drive a bot - -Guests see the public product page at `/bot` and the **This space is reserved** gate on deep links. Signed-in members create and drive bots. Bots are private to the account — see [Sharing](/chat/sharing). - -## Bot compared with Chat and Code - -| | Chat | Code | Bot | -| --- | --- | --- | --- | -| Best at | Questions, writing, research | Changing a repository | Doing tasks on a desktop, on a schedule | -| Runs in | A snippet sandbox | An isolated guest, headless | An isolated guest **with a desktop** | -| You see | A streamed reply | A turn timeline | Short user-visible messages plus the live screen | -| Guardrail | Tool round budget | Ask / Plan / Agent | Allow / Deny / Always approvals | - -## Where Bot lives - -| Surface | Host for the computer | -| --- | --- | -| Web app (`/bot`) | Cloud | -| [Bot desktop app](/bot/desktop) | Cloud, This PC | -| SSH | Shell only, no desktop | - -## Next - -- [Bot quickstart](/bot/quickstart) -- [Computer](/bot/computer) · [Tools](/bot/tools) · [Approvals](/bot/approvals) -- [Routines](/bot/routines) · [Skills](/bot/skills) diff --git a/bot/troubleshooting.mdx b/bot/troubleshooting.mdx index 4a2bcb4..532065f 100644 --- a/bot/troubleshooting.mdx +++ b/bot/troubleshooting.mdx @@ -57,3 +57,10 @@ Bot uses the same problem documents as the rest of Cortex. Common ones: | [`invalid_state`](/problems/invalid_state) | The bot or its computer is not in a state that allows the action | Full list: [Problem catalog](/problems). + +## Related + +- [Computer](/bot/computer) — the guest states behind Connecting and Stream Unavailable. +- [Approvals](/bot/approvals) — why a parked step waits. +- [Routines](/bot/routines) — local weekday timing. +- [Troubleshooting](/getting-started/troubleshooting) — the same guidance across all products. diff --git a/changelog.mdx b/changelog.mdx index 9f8e737..a86ff2d 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -47,3 +47,9 @@ Dated notes on what the products ship. Each entry names a change the app or the ## How this page is kept An entry names a change the product already ships, with a date, and links the surface. Product previews stay labelled as previews. The inference Platform API is not announced here until that product exists — see [Coming soon](/platform). + +## Related + +- [System status](/status) — outages and maintenance, which are not changelog entries. +- [CLI](/cli) — install and update, including `cortex upgrade`. +- [Platform API](/platform) — why no inference API is announced here yet. diff --git a/chat/images.mdx b/chat/images.mdx index 8a85ea4..d462266 100644 --- a/chat/images.mdx +++ b/chat/images.mdx @@ -49,3 +49,10 @@ A generated picture is a Library file like any other. **Check origin** is a diff - One complete sentence beats a list of keywords: *A lighthouse on a granite headland at dusk, long exposure, soft teal sky.* - Reuse the seed to iterate on a picture you like while changing one thing at a time. - Use the negative prompt for the things the model keeps adding that you do not want. + +## Related + +- [Models](/chat/models) — Cortex-Image-1 beside the chat models. +- [Library](/chat/library) — where generated pictures land. +- [Plans and quotas](/getting-started/plans) — how `images_per_day` behaves. +- [Origin check](/chat/origin-check) — ask whether a picture reads as generated. diff --git a/chat/index.mdx b/chat/index.mdx index 39ac813..1262e86 100644 --- a/chat/index.mdx +++ b/chat/index.mdx @@ -1,10 +1,12 @@ --- -title: "Chat guides" -description: "Everything for Cortex Chat — conversations, projects, Library, plans, models, tools, research, images, and how a turn actually runs." +title: "Cortex Chat" +description: "The conversation product at cortex.foundation — projects, Library, plans, models, tools, Deep Research, images, and how a turn runs." image: "/images/banners/chat.png" --- -Cortex Chat is the conversation product at [cortex.foundation](https://cortex.foundation). Ask questions, write, research with citations, work with documents and images, and keep related work together in projects. Chat works before you sign in; projects, files, memory, plans, and connections belong to a signed-in account. +Cortex Chat is the assistant for research, writing, and everyday questions. It streams answers as they are written, keeps documents and threads together in projects, and hands off to Code or Bot when the job is a repository or a computer rather than a conversation. + +Chat works before you sign in. Projects, files, memory, plans, and connections belong to a signed-in account. Cortex Chat home in the dark theme @@ -16,14 +18,54 @@ Cortex Chat is the conversation product at [cortex.foundation](https://cortex.fo First message, attach a file, pick a model, keep the thread in a project. - - What Chat is for, what lives where in the sidebar, and when to switch to Code or Bot. + + Standing instructions, chats, and files kept together. - - Guest allowance versus a signed-in member, and what each can do. + + Built-in tools, connected apps, GitHub, and MCP servers. +## Finding your way around + +The sidebar holds everything Chat can open. Three of its entries are worth knowing before you start clicking. + +| Entry | What it opens | +| --- | --- | +| **Chat · Code · Bot** | The product switcher. Chat is the default; Code and Bot need a signed-in account. | +| **Projects** | Standing instructions plus the chats and files that belong together. See [Projects](/chat/projects). | +| **Artifacts** | Opens the [Library](/chat/library) — your uploads and everything the assistant produced. | +| **Customize** | **Connectors**: connect apps and choose whether each reaches Chat, Bot, or both. See [Tools](/chat/tools). | +| **Recents** | Your latest conversations. Guests see the current session only. | + +**New chat** is an action, not a selected row. **Pinned** collects the projects you use most. + +### The composer + +- **+** opens **Attach file**, **Image**, and **Check origin**. +- The **model chip** — for example *Cortex 1 Mini · Preview · Thinking Low* — switches model, sets the Thinking level, and toggles **Deep Research**. +- **Enter** sends, **Shift + Enter** adds a line, **Esc** stops a response. +- While a turn runs the placeholder reads **Cortex is responding…** and the send button becomes a stop control. + +### The four cards on Chat home + +They are shortcuts, not separate products. + +| Card | What it does | +| --- | --- | +| **Code** — *The Cortex agent working inside your repos* | Opens Cortex Code | +| **Research** — *Cited answers from live sources* | Prefills the composer and turns on Deep Research | +| **Docs** — *Drafts that structure themselves* | Opens a new chat with a document canvas beside it | +| **Agents** — *Background runs on schedules* | Opens Cortex Bot | + +### A thread + +Every reply carries **Copy**, **Listen** (when voice is available), and thumbs up or down. The thread header offers **Start a plan** and **Move to project**. Replies render markdown — headings, tables, and syntax-highlighted code blocks with their own **Copy** button. + + + A Cortex Chat thread with a reply in the conversation pane + + ## Core guides @@ -66,11 +108,32 @@ Cortex Chat is the conversation product at [cortex.foundation](https://cortex.fo ## Context that carries over -- [Memory](/chat/memory) — standing preferences and facts, reviewable and deletable one by one. -- [Canvases](/chat/canvases) — a document sidecar beside the conversation, drafted by Cortex or by you. + + + Standing preferences and facts, reviewable and deletable one by one. + + + A document sidecar beside the conversation, drafted by Cortex or by you. + + + +## When to switch +Chat does not run Code's workspace shell or Bot's computer. Chat's Python tool is a snippet sandbox with no workspace, and it cannot see a repository. + +| Stay in Chat | Open Code | Open Bot | +| --- | --- | --- | +| Questions, writing, research, pictures, origin checks, documents, plans | A repository, an isolated Cloud guest, diffs, a pull request | An always-on teammate with a computer, routines, approvals | ## Under the hood - [How Chat works](/chat/how-it-works) — the tool loop, compaction, and Chat's round budget. - [Streaming](/chat/streaming) — tokens, then tools, then the finished reply; what happens on a dropped connection. - [Settings](/getting-started/settings) — theme, language, shortcuts, defaults, privacy. +- [Chat troubleshooting](/chat/troubleshooting) — messages that are not errors, and what to do next. + +## Next + +1. [Chat quickstart](/chat/quickstart) — from an open tab to a useful first conversation. +2. [Projects](/chat/projects) — instructions your chats start from. +3. [Models and thinking](/chat/models) — pick a model per conversation. +4. [Tools and connectors](/chat/tools) — connect an app or an MCP server. diff --git a/chat/overview.mdx b/chat/overview.mdx deleted file mode 100644 index 5102c9e..0000000 --- a/chat/overview.mdx +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: "Overview" -description: "What Cortex Chat is for, how the sidebar is organised, what a thread offers, and when to switch to Code or Bot." ---- - -Cortex Chat is the assistant for research, writing, and everyday questions. It streams answers as they are written, keeps documents and threads together in projects, and hands off to Code or Bot when the job is a repository or a computer rather than a conversation. - - - A Cortex Chat thread with a reply in the conversation pane - - -## The sidebar - -| Entry | What it opens | -| --- | --- | -| **Chat · Code · Bot** | The product switcher. Chat is the default; Code and Bot need a signed-in account. | -| **New chat** | Starts a thread. It is an action, not a selected row. | -| **Projects** | Standing instructions plus the chats and files that belong together. See [Projects](/chat/projects). | -| **Artifacts** | Opens the [Library](/chat/library) — your uploads and everything the assistant produced. | -| **Customize** | **Connectors**: connect apps and choose whether each reaches Chat, Bot, or both. See [Tools](/chat/tools). | -| **Pinned** | Projects you pinned for quick access. | -| **Recents** | Your latest conversations. Guests see the current session only. | -| **Guest / your account** | Sign in, or open [Settings](/getting-started/settings). | - -## The composer - -- **+** opens **Attach file**, **Image**, and **Check origin**. -- The **model chip** (for example *Cortex 1 Mini · Preview · Thinking Low*) switches model, sets the Thinking level, and toggles **Deep Research**. -- **Enter** sends; **Shift + Enter** adds a line; **Esc** stops a response. -- While a turn runs, the placeholder reads **Cortex is responding…** and the send button becomes a stop control. - -## Home starting points - -The four cards on Chat home are shortcuts, not separate products: - -| Card | What it does | -| --- | --- | -| **Code** — *The Cortex agent working inside your repos* | Opens Cortex Code | -| **Research** — *Cited answers from live sources* | Prefills the composer and turns on Deep Research | -| **Docs** — *Drafts that structure themselves* | Opens a new chat with a document canvas beside it | -| **Agents** — *Background runs on schedules* | Opens Cortex Bot | - -## A thread - -Every reply carries **Copy**, **Listen** (when voice is available), and thumbs up / down. The thread header offers **Start a plan** — turn this conversation into a task list — and **Move to project**. Replies render markdown: headings, tables, and syntax-highlighted code blocks with their own **Copy** button. A small line under the composer reminds you that Cortex is an AI and can make mistakes. - -## When to switch - -| Stay in Chat | Open Code | Open Bot | -| --- | --- | --- | -| Questions, writing, research, pictures, origin checks, documents, plans | A repository, an isolated Cloud guest, diffs, a pull request | An always-on teammate with a computer, routines, approvals | - -Chat does not run Code's workspace shell or Bot's computer. Chat's Python tool is a snippet sandbox with no workspace; it cannot see a repository. How the loop runs: [How Chat works](/chat/how-it-works). - -## Next - -- [Chat quickstart](/chat/quickstart) — from open to a useful first conversation. -- [Projects](/chat/projects) · [Library](/chat/library) · [Plans](/chat/plans) -- [Models](/chat/models) · [Tools and connectors](/chat/tools) diff --git a/chat/plans.mdx b/chat/plans.mdx index 4ff6b3b..03234b4 100644 --- a/chat/plans.mdx +++ b/chat/plans.mdx @@ -47,3 +47,10 @@ Choose **New plan**. Give it a title and, optionally, steps. Bullets and numbers ## Guests and members Plans persist on a signed-in account. A guest can open the dialog, but a plan needs somewhere to live once the tab closes — sign in first. See [Accounts](/getting-started/accounts). + +## Related + +- [Projects](/chat/projects) — instructions that apply to every chat in them. +- [Canvases](/chat/canvases) — a document beside one conversation. +- [Deep Research](/chat/deep-research) — the research plan, which is not a task list. +- [CLI goals](/cli/goal) — a long-horizon objective in the terminal. diff --git a/chat/projects.mdx b/chat/projects.mdx index 3c0e324..68b469e 100644 --- a/chat/projects.mdx +++ b/chat/projects.mdx @@ -61,3 +61,10 @@ Add documents to a project so every chat in it can use them. Files come from you Cortex Chat is per account. Another account cannot see your projects, and there is no teammate invite on a thread. Plus and Pro plans list **Projects and saved work** among their features — check [Plans](/getting-started/plans) for what your plan includes. + +## Related + +- [Library](/chat/library) — the file store projects draw from. +- [Memory](/chat/memory) — account-wide preferences, as opposed to project instructions. +- [Plans](/chat/plans) — task lists that start from a chat. +- [Sharing and teams](/chat/sharing) — what is per account, and where team features live. diff --git a/chat/sharing.mdx b/chat/sharing.mdx index 4a906ed..e46dc33 100644 --- a/chat/sharing.mdx +++ b/chat/sharing.mdx @@ -35,3 +35,10 @@ Do not expect a sharing control to appear in the thread header — the two actio ## Export and deletion Your data is yours to take or remove. **Settings → Data & privacy** offers **Export my data** and **Delete all data**; a signed-in account confirms with an authenticator app. See [Security](/security/overview) and [Accounts](/getting-started/accounts). + +## Related + +- [Accounts](/getting-started/accounts) — guest versus member. +- [Projects](/chat/projects) — private to your account, with no invite control. +- [Plans and quotas](/getting-started/plans) — where team workspace features are listed. +- [CLI sessions](/cli/sessions) — sharing a session transcript from the terminal. diff --git a/chat/tools.mdx b/chat/tools.mdx index 53afb63..f3d5595 100644 --- a/chat/tools.mdx +++ b/chat/tools.mdx @@ -80,3 +80,10 @@ Cortex Security's GitHub App — pull-request review — is a third, separate in No. Connected-app content is read at question time and discarded. See [Security](/security/overview). + +## Related + +- [How Chat works](/chat/how-it-works) — how tools enter a turn. +- [Connect GitHub](/code/github) — the repository connection, which lives in Code. +- [Cortex Security](/code/security) — the third GitHub install, for pull-request review. +- [Extend the CLI](/cli/extend) — MCP servers for the terminal agent. diff --git a/chat/troubleshooting.mdx b/chat/troubleshooting.mdx new file mode 100644 index 0000000..a46228b --- /dev/null +++ b/chat/troubleshooting.mdx @@ -0,0 +1,66 @@ +--- +title: "Chat troubleshooting" +description: "What Chat shows when a turn, a file, a model, or a connection does not behave — and what to do about it." +--- + +Chat says what is wrong in plain words rather than spinning. This page maps those messages to causes and fixes. For the whole app, see [Troubleshooting](/getting-started/troubleshooting); for the terminal, [CLI troubleshooting](/cli/troubleshooting). + +## The reply + +| What you see | Cause | Fix | +| --- | --- | --- | +| A reply cut short, then the thread reconnects | The connection dropped mid-reply | **Do not resend.** Run state lives in the thread; the reply that already started continues. See [Streaming](/chat/streaming). | +| The send button stays a stop control | A turn is in flight | Wait, or press **Esc** to stop it. The partial reply stays. | +| A long thread answers more vaguely than it did | The thread was compacted to fit the model's context window | Start a new chat, or put the standing detail in a [project](/chat/projects) so it is not repeated. See [How Chat works](/chat/how-it-works). | +| A turn fails with [`service_unavailable`](/problems/service_unavailable) | Compaction could not summarise the thread | Retry. Compaction fails closed rather than dropping your earlier turns. | + +## Files and images + +| What you see | Cause | Fix | +| --- | --- | --- | +| A file will not attach | Type or size check failed at upload | A rejected file is not attached and does not enter Library. Try a supported type or a smaller file. | +| An error square where an image should be | The Library fetch or the generation failed | Retry the request. It is never an endless spinner. | +| **Improving prompt…** on an image card | The picture is queued; Chat is completing the scene description first | Wait — the card becomes a skeleton, then the image. | +| A generated picture is missing from the thread | It saved to Library but the fetch failed | Open [Library](/chat/library) and attach it from there. | +| Image generation is unavailable | The deployment has image generation switched off | The request fails closed before any quota is spent. See [Image generation](/chat/images). | + +## Models and research + +| What you see | Cause | Fix | +| --- | --- | --- | +| The model on the chip is not the one you picked | Peak-hour fallback, or the conversation kept the model it was created with | Check the chip: it always names the model serving the conversation. See [Models](/chat/models). | +| A model is missing from the chip | Availability depends on your plan | Compare plans under **Settings → Plan & billing**. See [Plans](/getting-started/plans). | +| **Deep Research** is missing | You are in Code or Bot | Deep Research is a Chat feature. Switch to Chat. | +| A research run stops early | A per-run cap was reached | Questions, sources, and parallelism are capped per run. See [Deep Research](/chat/deep-research). | +| Origin check says it cannot run | The check is unavailable on this deployment | Ask again later. Cortex says so rather than guessing — do not read silence as a verdict. See [Origin check](/chat/origin-check). | + +## Voice + +| What you see | Cause | Fix | +| --- | --- | --- | +| No mic in the composer | The deployment cannot transcribe | Nothing to configure — the capability is off, not broken. | +| No **Listen** under an answer | The deployment cannot speak | Same. Voice controls appear only when the deployment behind them can do the work. | + +See [Voice](/chat/voice). + +## Quotas and access + +| What you see | Cause | Fix | +| --- | --- | --- | +| **Sign in to continue** above the composer | The guest allowance for today or this month is used up | Sign in to continue on Free, or wait for the reset. | +| [`quota_exceeded`](/problems/quota_exceeded) | A daily or monthly window is exhausted | The error carries when it resets. See [Plans and quotas](/getting-started/plans). | +| [`entitlement_required`](/problems/entitlement_required) | The plan does not include that capability at all | Compare plans under **Settings → Plan & billing**. | +| [`rate_limited`](/problems/rate_limited) | Too many requests in a short burst | Pause, then retry. | +| Projects, Library, or Memory are missing | You are a guest | Those need a signed-in account. See [Accounts](/getting-started/accounts). | +| A connected app cannot be used in Chat | The connection is enabled for Bot only | **Customize → Connectors** → enable it for **Cortex Chat**. See [Tools and connectors](/chat/tools). | +| No **Connect** control for an app | That connection is not available on this deployment, or you are a guest | Sign in; if it is still missing, it is not offered here. | + +## Still stuck + +Keep the `request_id` from the error, the time, and the surface you were on. Check [status.cortex.foundation](https://status.cortex.foundation) if Chat is failing alongside other products — see [System status](/status). Full error semantics: [Problem catalog](/problems). + +## Related + +- [How Chat works](/chat/how-it-works) — the tool loop and its round budget. +- [Streaming](/chat/streaming) — what a dropped connection does and does not lose. +- [Troubleshooting](/getting-started/troubleshooting) — the same guidance across all products. diff --git a/cli/index.mdx b/cli/index.mdx index df83506..fb055e6 100644 --- a/cli/index.mdx +++ b/cli/index.mdx @@ -92,3 +92,10 @@ cortex - Everything documented here is backed by the shipped CLI. `cortex --help` and `cortex --help` are generated from the same definitions and are always authoritative. - The CLI talks to the Cortex API at `api.cortex.foundation`. Releases are published to `software.cortex.foundation`. - Source and issues: [github.com/CortexLM/cli](https://github.com/CortexLM/cli), Apache-2.0. + +## Next + +1. [Install the CLI](/cli/install) — checksums, version pins, and channels. +2. [Sign in](/cli/sign-in) — the browser flow, device codes, and CI keys. +3. [CLI quickstart](/cli/quickstart) — a working Code session in the terminal. +4. [Modes and permissions](/cli/modes-and-permissions) — decide how much the agent may do. diff --git a/code/how-it-works.mdx b/code/how-it-works.mdx index 8ddecbe..2f707bc 100644 --- a/code/how-it-works.mdx +++ b/code/how-it-works.mdx @@ -60,3 +60,10 @@ The CLI drives the same session API with streaming tokens and first-class tool r | Transcript | Streamed reply | Turn timeline | User-visible bubbles | See [How Chat works](/chat/how-it-works) and [How Bot works](/bot/how-it-works). + +## Related + +- [Sessions](/code/sessions) — reading the timeline this loop produces. +- [Ask, Plan, Agent](/code/interactions) — the modes that gate mutating tools. +- [Cloud runtimes](/code/cloud) — the guest the tools execute in. +- [Code troubleshooting](/code/troubleshooting) — when the agent will not edit. diff --git a/code/index.mdx b/code/index.mdx index 24ad607..938ced1 100644 --- a/code/index.mdx +++ b/code/index.mdx @@ -1,11 +1,30 @@ --- -title: "Code guides" -description: "Cortex Code — a coding agent that works inside your repository on an isolated runtime. Sessions, Ask / Plan / Agent, GitHub, Cloud guests, desktop hosts, the CLI, and pull-request review." +title: "Cortex Code" +description: "A coding agent that works inside your repository on an isolated runtime. Sessions, Ask / Plan / Agent, GitHub, Cloud guests, desktop hosts, and pull-request review." image: "/images/banners/code.png" --- Cortex Code is a coding agent, not a chat with a different icon. You point it at a repository, describe the change, and it works through the task inside an isolated runtime — reading, editing, running your build and tests — while you watch a timeline of what it did and decide how much it may do on its own. +It is available in the web app, the desktop app, and the CLI, with one account across all three. + + + Cortex Code feature grid — One repository per runtime, Reviews that stay failed when they fail, No pasted diff, The same account as Chat + + +## What Code is + +| Commitment | What it means for you | +| --- | --- | +| **One repository per runtime** | Each session gets an isolated guest on the same farm as Cortex Bot, without a desktop. The environment is installed by the agent, not guessed from a filename. | +| **Reviews stay failed when they fail** | A Cortex Security review is one pass over a diff. A failed review is stored as failed — never rewritten as an approval. | +| **No pasted diff** | Code writes the change in the repository; Cortex Security reads the pull request in place. Neither asks you to paste a patch into a box. | +| **The same account as Chat** | Sign in on cortex.foundation. No second identity. | + +## Who can open it + +Code is a member surface. A guest visiting `/code` sees the public product page, and deep links show **This space is reserved** with **Continue in Chat** or **Sign in**. Once signed in, `/code` opens Code Home: connect GitHub, pick a repository, start a session. See [Accounts](/getting-started/accounts). + Cortex Code public page — a coding agent that works inside the repository @@ -16,14 +35,26 @@ Cortex Code is a coding agent, not a chat with a different icon. You point it at Sign in, connect GitHub, start a Cloud session, review the timeline. - - What Code is, who can open it, and what it deliberately is not. + + A timeline of the work — files read, edits, commands, plans. The same Code sessions from your terminal, scripts, and CI. +## Where it runs + +| Surface | Host | Notes | +| --- | --- | --- | +| Web app | **Cloud** | Web Code is Cloud-only | +| Desktop app | Cloud, **This PC**, **SSH** | [Code on desktop](/code/desktop) | +| CLI | Cloud by default; This PC and SSH by opt-in | [Code in the terminal](/code/cli) | + +## Modes in one line + +**Ask** reads and explains. **Plan** proposes without changing anything. **Agent** edits files and runs commands. Pick per turn; the session row holds the default. See [Ask, Plan, Agent](/code/interactions). + ## Core guides @@ -52,8 +83,26 @@ Cortex Code is a coding agent, not a chat with a different icon. You point it at +## What Code is not + +| Not this | Use instead | +| --- | --- | +| A Chat transcript | A [session](/code/sessions) is a turn timeline — files read, edits, commands, plans | +| Chat's Python sandbox | [Cloud runtimes](/code/cloud) — an isolated guest with your repository | +| A Chat plugin | [Connect GitHub](/code/github) — the repository connection lives in Code | +| The public `/security` page | [Cortex Security](/code/security) — the signed-in review inbox at `/code/security` | +| A place to store secrets | There is no Secrets page. Environment for a guest belongs to that guest; `/code/secrets` sends you home. | + ## Advanced - [Cortex Security](/code/security) — GitHub App pull-request review that stays failed when it fails. - [How Code works](/code/how-it-works) — workspace tools execute inside the attached guest; modes withhold mutating tools. -- [Troubleshooting](/code/troubleshooting) — gates, Reconnect, Cloud availability, and what a failed review means. +- [Code troubleshooting](/code/troubleshooting) — gates, Reconnect, Cloud availability, and what a failed review means. +- [Security and privacy](/security/overview) — what Cortex stores and where code runs. + +## Next + +1. [Code quickstart](/code/quickstart) — sign in, connect GitHub, start a Cloud session. +2. [Sessions](/code/sessions) — read the timeline. +3. [Ask, Plan, Agent](/code/interactions) — decide how much a turn may change. +4. [Code in the terminal](/code/cli) — the same sessions from the CLI. diff --git a/code/overview.mdx b/code/overview.mdx deleted file mode 100644 index c2685e1..0000000 --- a/code/overview.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: "Cortex Code" -description: "A coding agent that works inside the repository — sessions, isolated runtimes, and pull-request reviews. Web Code is Cloud-only; This PC and SSH live on desktop and the CLI." ---- - -Cortex Code changes repositories. You describe the work, the agent carries it out inside a runtime prepared for that repository, and every step lands on a timeline you can read and stop. It is available in the web app, the desktop app, and the CLI, with one account across all three. - - - Cortex Code feature grid — One repository per runtime, Reviews that stay failed when they fail, No pasted diff, The same account as Chat - - -## What Code is - -| Commitment | What it means for you | -| --- | --- | -| **One repository per runtime** | Each session gets an isolated guest on the same farm as Cortex Bot, without a desktop. The environment is installed by the agent, not guessed from a filename. | -| **Reviews stay failed when they fail** | A Cortex Security review is one pass over a diff. A failed review is stored as failed — never rewritten as an approval. | -| **No pasted diff** | Code writes the change in the repository; Cortex Security reads the pull request in place. Neither asks you to paste a patch into a box. | -| **The same account as Chat** | Sign in on cortex.foundation. No second identity. | - -## Who can open it - -Code is a member surface. A guest visiting `/code` sees the public product page, and deep links show **This space is reserved** with **Continue in Chat** or **Sign in**. Once signed in, `/code` opens Code Home: connect GitHub, pick a repository, start a session. See [Accounts](/getting-started/accounts). - -## Where it runs - -| Surface | Host | Notes | -| --- | --- | --- | -| Web app | **Cloud** | Web Code is Cloud-only | -| Desktop app | Cloud, **This PC**, **SSH** | [Code on desktop](/code/desktop) | -| CLI | Cloud by default; This PC and SSH by opt-in | [Code in the terminal](/code/cli) | - -## What Code is not - -| Not this | Use instead | -| --- | --- | -| A Chat transcript | A [session](/code/sessions) is a turn timeline — files read, edits, commands, plans | -| Chat's Python sandbox | [Cloud runtimes](/code/cloud) — an isolated guest with your repository | -| A Chat plugin | [Connect GitHub](/code/github) — the repository connection lives in Code | -| The public `/security` page | [Cortex Security](/code/security) — the signed-in review inbox at `/code/security` | -| A place to store secrets | There is no Secrets page. Environment for a guest belongs to that guest; `/code/secrets` sends you home. | - -## Modes in one line - -**Ask** reads and explains. **Plan** proposes without changing anything. **Agent** edits files and runs commands. Pick per turn; the session row holds the default. See [Ask, Plan, Agent](/code/interactions). - -## Next - -- [Code quickstart](/code/quickstart) — first session in the web app. -- [Sessions](/code/sessions) — reading the timeline. -- [CLI](/cli) — the terminal front-end to the same sessions. diff --git a/code/troubleshooting.mdx b/code/troubleshooting.mdx index 197ee0b..5d30ce7 100644 --- a/code/troubleshooting.mdx +++ b/code/troubleshooting.mdx @@ -60,3 +60,10 @@ Every failure is a problem document with a stable `code` and a `request_id`. Com | [`upstream_timeout`](/problems/upstream_timeout) | The runtime did not answer in time; retry | Full list: [Problem catalog](/problems). + +## Related + +- [Code](/code) — what Code is and who can open it. +- [Ask, Plan, Agent](/code/interactions) — the modes that withhold mutating tools. +- [How Code works](/code/how-it-works) — where workspace tools execute. +- [Troubleshooting](/getting-started/troubleshooting) — the same guidance across all products. diff --git a/design/index.mdx b/design/index.mdx index 1a843c1..cdcd379 100644 --- a/design/index.mdx +++ b/design/index.mdx @@ -1,26 +1,46 @@ --- -title: "Design guides" -description: "Cortex Design — persistable canvases hosted from Chat or Code and edited in the Design desktop app. Frames, notes, options, and a Library of components, pages, and tokens." +title: "Cortex Design" +description: "Persistable canvases hosted from Chat or Code and edited in the Design desktop app. Frames, notes, options, and a Library of components, pages, and tokens." image: "/images/banners/design.png" --- Cortex Design is where visual work lives. A canvas is a board of frames, notes, and options that you host from a Chat conversation or a Code session and then open in the **Design desktop app** to work on. There is no Design mode in the web shell: the browser creates and lists canvases, the desktop app is where you edit them. - - - What a canvas is, who can create one, and what the web app does not host. + + + Frames, notes, options, and the host binding to Chat or Code. Default title Untitled canvas. Install the working UI, open a board, edit frames and notes. - - Frames, notes, options, and the host binding to Chat or Code. Default title Untitled canvas. - Components, pages, and tokens you reuse across canvases. Thumbnails are uploaded. +## The shape of the product + +| Fact | What it means | +| --- | --- | +| **Hosted from Chat or Code** | Both products have design tools that create and write to a canvas. Bot does not. | +| **Edited on desktop** | The working UI is a separate desktop app. There is no `/design` route in the web app and no Design segment on the Chat · Code · Bot switcher. | +| **Private to your account** | Sign in first. Another account's canvases are not visible. | +| **Not a Chat sidecar** | A Chat canvas is a document beside a conversation. A Design canvas is a board of frames. Different objects. | + +## Start from Chat or Code + + + + Canvases are member objects. + + + Ask Chat — *lay these three screens out on a canvas* — or ask a Code session to draw the architecture. The design tools create the board, titled **Untitled canvas**, and write frames to it. + + + Install the [Design desktop app](/design/desktop), sign in, and open the board. Edit frames, notes, and the Library drawer there. + + + ## Design and the other products | Object | Product | What it is | @@ -30,10 +50,15 @@ Cortex Design is where visual work lives. A canvas is a board of frames, notes, | [Plan](/chat/plans) | Chat | A task list | | Code file | Code | A file inside a session's guest | -Chat and Code can host a Design canvas through their design tools; Bot does not receive those tools. +Chat and Code can host a Design canvas through their design tools; Bot does not receive those tools. A [Design library](/design/library) item and a file in Chat's [Library](/chat/library) are different objects that share a word. + +## Related + +- [Downloads](/getting-started/downloads) — every desktop app and the CLI in one place. +- [Security and privacy](/security/overview) — what Cortex stores and where your work runs. -## Where to start +## Next -1. Sign in — canvases belong to a member account and are private to it. -2. Ask Chat or Code to put something on a canvas. The object is created with the title **Untitled canvas**. -3. Install the [Design desktop app](/design/desktop) and open the board there. +1. [Canvases](/design/canvases) — what lives on a board and how it is hosted. +2. [Design desktop](/design/desktop) — install the app and open your first canvas. +3. [Design library](/design/library) — components, pages, and tokens. diff --git a/design/overview.mdx b/design/overview.mdx deleted file mode 100644 index a6f47bc..0000000 --- a/design/overview.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: "Cortex Design" -description: "Persistable canvases from Chat or Code, edited in a separate desktop app. No fourth mode in the web shell." ---- - -A Design canvas is a board: frames, options, notes, and a Library drawer of reusable components, pages, and tokens. It is created when Chat or Code writes to it and persists on your account. You work on it in the **Design desktop app**. - -## The shape of the product - -| Fact | What it means | -| --- | --- | -| **Hosted from Chat or Code** | Both products have design tools that create and write to a canvas. Bot does not. | -| **Edited on desktop** | The working UI is a separate desktop app. There is no `/design` route in the web app and no Design segment on the Chat · Code · Bot switcher. | -| **Private to your account** | Sign in first. Another account's canvases are not visible. | -| **Not a Chat sidecar** | A Chat canvas is a document beside a conversation. A Design canvas is a board of frames. Different objects. | - -## Start from Chat or Code - - - - Canvases are member objects. - - - Ask Chat — *lay these three screens out on a canvas* — or ask a Code session to draw the architecture. The design tools create the board, titled **Untitled canvas**, and write frames to it. - - - Install the [Design desktop app](/design/desktop), sign in, and open the board. Edit frames, notes, and the Library drawer there. - - - -## What the web app will not do - -- Show a Design segment on the product switcher. -- Open a working canvas in the browser. -- Let a guest create a canvas. - -Hosting from Chat or Code still creates the object; the desktop app is where you work. - -## Next - -- [Canvases](/design/canvases) — what lives on the board. -- [Library](/design/library) — components, pages, tokens. -- [Design desktop](/design/desktop) — install and open. diff --git a/docs.json b/docs.json index 44fde85..0ca15dd 100644 --- a/docs.json +++ b/docs.json @@ -85,6 +85,10 @@ "label": "Downloads", "href": "/getting-started/downloads" }, + { + "label": "Troubleshooting", + "href": "/getting-started/troubleshooting" + }, { "label": "Changelog", "href": "/changelog" @@ -125,7 +129,7 @@ "404": { "redirect": false, "title": "Page not found", - "description": "That path is not in Cortex Docs. Try [Get started](/getting-started/quickstart), [Chat](/chat), [Code](/code), [Bot](/bot), [CLI](/cli), or [Design](/design). The inference Platform API is not documented here." + "description": "That path is not in Cortex Docs. Try [Get started](/getting-started/quickstart), [Chat](/chat), [Code](/code), [Bot](/bot), [CLI](/cli), or [Design](/design). If a product message sent you here, the [problem catalog](/problems) explains it. The inference Platform API is not documented here." } }, "seo": { @@ -139,6 +143,22 @@ } }, "redirects": [ + { + "source": "/chat/overview", + "destination": "/chat" + }, + { + "source": "/code/overview", + "destination": "/code" + }, + { + "source": "/bot/overview", + "destination": "/bot" + }, + { + "source": "/design/overview", + "destination": "/design" + }, { "source": "/chat/conversations", "destination": "/chat/projects" @@ -147,10 +167,26 @@ "source": "/chat/plugins", "destination": "/chat/tools" }, + { + "source": "/chat/artifacts", + "destination": "/chat/library" + }, { "source": "/getting-started/cli", "destination": "/cli/install" }, + { + "source": "/getting-started/desktop-app", + "destination": "/getting-started/desktop" + }, + { + "source": "/code/goals", + "destination": "/cli/goal" + }, + { + "source": "/troubleshooting", + "destination": "/getting-started/troubleshooting" + }, { "source": "/api/authentication", "destination": "/getting-started/accounts" @@ -195,8 +231,8 @@ "groups": [ { "group": "Start here", + "root": "getting-started/quickstart", "pages": [ - "getting-started/quickstart", "getting-started/what-is-cortex", "getting-started/accounts", "getting-started/plans", @@ -206,16 +242,16 @@ }, { "group": "Downloads", + "root": "getting-started/downloads", "pages": [ - "getting-started/downloads", "getting-started/desktop" ], "icon": "download" }, { "group": "Help", + "root": "getting-started/troubleshooting", "pages": [ - "getting-started/troubleshooting", "status", "security/overview" ], @@ -227,25 +263,24 @@ "tab": "Chat", "groups": [ { - "group": "Getting started", + "group": "Chat", + "root": "chat/index", "pages": [ - "chat/index", - "chat/overview", - "chat/quickstart" + "chat/quickstart", + "chat/projects", + "chat/library", + "chat/plans", + "chat/sharing" ], - "icon": "rocket" + "icon": "message-square" }, { - "group": "Core guides", + "group": "Models and tools", "pages": [ - "chat/projects", - "chat/library", - "chat/plans", "chat/models", - "chat/tools", - "chat/sharing" + "chat/tools" ], - "icon": "book-open" + "icon": "cpu" }, { "group": "Research and media", @@ -255,7 +290,7 @@ "chat/origin-check", "chat/voice" ], - "icon": "search" + "icon": "telescope" }, { "group": "Context that carries over", @@ -266,12 +301,19 @@ "icon": "brain" }, { - "group": "Under the hood", + "group": "How Chat works", "pages": [ "chat/how-it-works", "chat/streaming" ], "icon": "sliders-horizontal" + }, + { + "group": "Help", + "pages": [ + "chat/troubleshooting" + ], + "icon": "life-buoy" } ] }, @@ -279,40 +321,45 @@ "tab": "Code", "groups": [ { - "group": "Getting started", + "group": "Code", + "root": "code/index", "pages": [ - "code/index", - "code/overview", - "code/quickstart" + "code/quickstart", + "code/sessions", + "code/interactions" ], - "icon": "rocket" + "icon": "code" }, { - "group": "Core guides", + "group": "Repositories", "pages": [ - "code/sessions", - "code/interactions", "code/github", - "code/cloud" + "code/security" ], - "icon": "book-open" + "icon": "github" }, { - "group": "Desktop and terminal", + "group": "Where it runs", "pages": [ + "code/cloud", "code/desktop", "code/cli" ], - "icon": "monitor" + "icon": "cloud" }, { - "group": "Advanced", + "group": "How Code works", "pages": [ - "code/security", - "code/how-it-works", - "code/troubleshooting" + "code/how-it-works" ], "icon": "sliders-horizontal" + }, + { + "group": "Help", + "pages": [ + "code/troubleshooting" + ], + "icon": "life-buoy" } ] }, @@ -320,24 +367,23 @@ "tab": "Bot", "groups": [ { - "group": "Getting started", + "group": "Bot", + "root": "bot/index", "pages": [ - "bot/index", - "bot/overview", - "bot/quickstart" + "bot/quickstart", + "bot/computer", + "bot/tools" ], - "icon": "rocket" + "icon": "bot" }, { - "group": "Core guides", + "group": "Control and scheduling", "pages": [ - "bot/computer", - "bot/tools", "bot/approvals", "bot/routines", "bot/skills" ], - "icon": "book-open" + "icon": "shield-check" }, { "group": "Desktop", @@ -347,12 +393,18 @@ "icon": "monitor" }, { - "group": "Advanced", + "group": "How Bot works", "pages": [ - "bot/how-it-works", - "bot/troubleshooting" + "bot/how-it-works" ], "icon": "sliders-horizontal" + }, + { + "group": "Help", + "pages": [ + "bot/troubleshooting" + ], + "icon": "life-buoy" } ] }, @@ -360,9 +412,9 @@ "tab": "CLI", "groups": [ { - "group": "Getting started", + "group": "Get started", + "root": "cli/index", "pages": [ - "cli/index", "cli/install", "cli/sign-in", "cli/quickstart" @@ -409,21 +461,14 @@ "tab": "Design", "groups": [ { - "group": "Getting started", - "pages": [ - "design/index", - "design/overview", - "design/desktop" - ], - "icon": "rocket" - }, - { - "group": "Core guides", + "group": "Design", + "root": "design/index", "pages": [ "design/canvases", + "design/desktop", "design/library" ], - "icon": "book-open" + "icon": "layout-template" } ] }, @@ -432,16 +477,16 @@ "groups": [ { "group": "Public", + "root": "api/overview", "pages": [ - "api/overview", "api/errors" ], "icon": "braces" }, { "group": "Problems", + "root": "problems/index", "pages": [ - "problems/index", "problems/validation_failed", "problems/bad_request", "problems/idempotency_key_reuse", diff --git a/getting-started/accounts.mdx b/getting-started/accounts.mdx index 6f2a6f2..8220bcf 100644 --- a/getting-started/accounts.mdx +++ b/getting-started/accounts.mdx @@ -59,3 +59,10 @@ Cortex Chat is per account, not a shared workspace. There is no teammate invite | **Sign in to continue** in the composer banner | The guest allowance for today or this month is used up. Sign in to continue on the Free plan. | | Two-factor prompt on export or deletion | Expected. A signed-in account confirms destructive actions with an authenticator app. | | The CLI asks you to sign in again | The stored session expired or was cleared. Run `cortex login`. | + +## Next + +- [Plans and quotas](/getting-started/plans) — what each plan includes and how limits behave. +- [Settings](/getting-started/settings) — theme, language, models, privacy, billing. +- [Sharing and teams](/chat/sharing) — what is per account, and where team features live. +- [CLI sign-in](/cli/sign-in) — the terminal side of the same account. diff --git a/getting-started/downloads.mdx b/getting-started/downloads.mdx index 39301ca..0afd773 100644 --- a/getting-started/downloads.mdx +++ b/getting-started/downloads.mdx @@ -68,3 +68,10 @@ Full steps, version pins, channels, and updates: [Install the CLI](/cli/install) | Web app | Always current | | Desktop apps | The updater feed on `releases.cortex.foundation` | | CLI | `cortex upgrade` (or `cortex upgrade --check` to report only) | + +## Next + +- [Desktop app](/getting-started/desktop) — what This PC and SSH add for Code. +- [Bot desktop](/bot/desktop) — the app built around agents, routines, and channels. +- [Design desktop](/design/desktop) — the working UI for canvases. +- [Install the CLI](/cli/install) — checksums, version pins, and channels. diff --git a/getting-started/plans.mdx b/getting-started/plans.mdx index 9c58b9b..c3f3303 100644 --- a/getting-started/plans.mdx +++ b/getting-started/plans.mdx @@ -61,3 +61,10 @@ Model availability depends on your plan. During peak hours new chats may fall ba Through 7 September 2026 a signed-in member could claim one month of Cortex Pro from the product. That window has closed; claiming twice never stacked. + +## Next + +- [Accounts](/getting-started/accounts) — guest versus member, and what each unlocks. +- [Models and thinking](/chat/models) — which models a plan can serve. +- [Image generation](/chat/images) — the `images_per_day` window. +- [CLI troubleshooting](/cli/troubleshooting) — quota messages in the terminal. diff --git a/getting-started/quickstart.mdx b/getting-started/quickstart.mdx index a7ba2bc..347c2e1 100644 --- a/getting-started/quickstart.mdx +++ b/getting-started/quickstart.mdx @@ -91,3 +91,12 @@ The browser runs Chat, Code Cloud, and Bot Cloud. **This PC** and **SSH** hosts Not on this site. The inference Platform API is [coming soon](/platform) and has no endpoints to document yet. The [API tab](/api/overview) covers the problem-document format the app already uses. + +## Next + +1. [Chat quickstart](/chat/quickstart) — projects, files, models, and research. +2. [Code quickstart](/code/quickstart) — a Cloud session on one of your repositories. +3. [Bot quickstart](/bot/quickstart) — a bot with its own computer. +4. [CLI quickstart](/cli/quickstart) — the same Code sessions from a terminal. +5. [Accounts](/getting-started/accounts) — what signing in unlocks, and what it does not. +6. [Troubleshooting](/getting-started/troubleshooting) — when a message is not an error. diff --git a/getting-started/settings.mdx b/getting-started/settings.mdx index ecc05c5..bbb2e71 100644 --- a/getting-started/settings.mdx +++ b/getting-started/settings.mdx @@ -81,3 +81,10 @@ Your current plan, today's and this month's usage with reset timers, the plan la | **CLI** | `/settings` in the TUI, `cortex config`, and `~/.cortex/config.toml`. See [CLI configuration](/cli/configuration). | | **Connected apps** | **Customize → Connectors** in the sidebar, and **Settings → Integrations**. See [Tools and connectors](/chat/tools). | | **Cortex Security policy** | `/code/security/policy` in the app. See [Cortex Security](/code/security). | + +## Next + +- [Accounts](/getting-started/accounts) — what signing in unlocks. +- [Plans and quotas](/getting-started/plans) — the usage panel under Plan & billing. +- [Security and privacy](/security/overview) — cookies, storage, and deletion. +- [Chat troubleshooting](/chat/troubleshooting) — when a setting does not seem to take. diff --git a/getting-started/troubleshooting.mdx b/getting-started/troubleshooting.mdx index 9c576f2..903d164 100644 --- a/getting-started/troubleshooting.mdx +++ b/getting-started/troubleshooting.mdx @@ -78,3 +78,11 @@ If Chat, Code, Bot, or the API are all failing at once, check **[status.cortex.f ## Reporting a problem Keep the `request_id` from the error, the time, and the surface you were on. From the CLI, `cortex feedback bug "what happened" --include-logs` attaches diagnostics. + +## Related + +- [Chat troubleshooting](/chat/troubleshooting) — turns, files, models, and connections. +- [Code troubleshooting](/code/troubleshooting) — gates, Reconnect, and runtimes. +- [Bot troubleshooting](/bot/troubleshooting) — computers, parked steps, and routines. +- [CLI troubleshooting](/cli/troubleshooting) — the terminal. +- [Problem catalog](/problems) — every code, its status, and whether retrying helps. diff --git a/index.mdx b/index.mdx index da41e90..31413a8 100644 --- a/index.mdx +++ b/index.mdx @@ -1,12 +1,12 @@ --- title: "Guides for Chat, Code, Bot, Design, and the CLI" -description: "Product docs for the Cortex app — how to chat, ship code, run bots, design with your team, and drive Code from a terminal. Platform API lives elsewhere." +description: "Product docs for the Cortex app — how to chat, ship code, run bots, design canvases, and drive Code from a terminal. Platform API lives elsewhere." image: "/images/banners/getting-started.png" --- docs.cortex.foundation -Product docs for the Cortex app — how to chat, ship code, run bots, design with your team, and drive Code from a terminal. One account, one domain. **Platform API lives elsewhere.** +Product docs for the Cortex app — how to chat, ship code, run bots, design canvases, and drive Code from a terminal. One account, one domain. **Platform API lives elsewhere.** Cortex Chat home with recent conversations in the sidebar and the composer ready @@ -17,6 +17,8 @@ Product docs for the Cortex app — how to chat, ship code, run bots, design wit Download Cortex

+## Start here + A first chat as a guest, then sign in for Code, Bot, and Design. Accounts, plans, settings. @@ -33,17 +35,19 @@ Product docs for the Cortex app — how to chat, ship code, run bots, design wit Persistable canvases hosted from Chat or Code, edited in the Design desktop app. - - What Cortex stores, cookies and consent, where code runs, export and deletion. + + Cortex Code in the terminal — install, sign in, the TUI, slash commands, `/goal`, headless runs. +## Reference + - - Cortex Code in the terminal — install, sign in, the TUI, slash commands, `/goal`, headless runs. + + What a message means across Chat, Code, and Bot — gates, quotas, reconnects. - - What shipped in Chat, Code, Bot, Design, and the CLI — dated, with the surface it changed. + + What Cortex stores, cookies and consent, where code runs, export and deletion. The problem-document format and the public catalog of error codes. @@ -51,8 +55,26 @@ Product docs for the Cortex app — how to chat, ship code, run bots, design wit Live availability for Web, API, Chat, Code, and Bot — incidents and maintenance. + + What shipped in Chat, Code, Bot, Design, and the CLI — dated, with the surface it changed. + + + Not available yet. No endpoints, SDKs, or keys — and what to use instead. + +## Which product do I want? + +| You want to… | Open | +| --- | --- | +| Ask questions, write, research with citations, work with documents and pictures | [Chat](/chat) | +| Change a repository, run tests, open a pull request | [Code](/code) | +| Delegate ongoing work to an agent that browses, clicks, and runs on a schedule | [Bot](/bot) | +| Drive Code from your own terminal, a script, or CI | [CLI](/cli) | +| Lay out frames, notes, and a component library on a board | [Design](/design) | + +Chat works before you sign in. Code, Bot, and Design need an account — see [Accounts](/getting-started/accounts). + -The inference **Platform API** (a public model-inference API) is not documented on this site and is not implemented in this repository. See [Coming soon](/platform). +The inference **Platform API** (a public model-inference API) is not documented on this site and is not implemented in this repository. See [Platform API](/platform). diff --git a/platform.mdx b/platform.mdx index de24953..5935b78 100644 --- a/platform.mdx +++ b/platform.mdx @@ -1,19 +1,42 @@ --- title: "Platform API" -description: "Coming soon. Not implemented in this repository." +description: "Coming soon. There is no public inference API to document yet — no endpoints, SDKs, or API keys." --- -# Coming soon +Cortex does not have a public inference API today. There is no completions +endpoint, no SDK, and no API key to request. This page exists so the +navigation does not grow a reference for something that is not there. -A separate, credit-metered **inference Platform API** (model inference the -way a completions API would work) is **out of scope** for this site and for -the Cortex app repository. +## What is available now -This page is a navigation placeholder so the IA does not grow a fake -reference. It documents **no endpoints**, because none exist here to -document. +| You want to… | Use | +| --- | --- | +| Ask questions, write, and research | [Cortex Chat](/chat) | +| Change a repository | [Cortex Code](/code) | +| Delegate ongoing work to an agent with a computer | [Cortex Bot](/bot) | +| Drive Code from a terminal, a script, or CI | [Cortex CLI](/cli) | +| Build a client against the app's error contract | [Public API](/api/overview) and the [problem catalog](/problems) | -Until that product is explicitly in scope: +## What is not available -- Use Cortex Chat, Code, Bot, and Design in the [app](https://cortex.foundation). -- Do not add Platform sections, SDKs, or API keys for inference metering. +- **Model inference as a service.** No endpoint takes a prompt and returns a + completion. +- **Platform API keys.** Metered inference keys are not issued, so there is + nothing to generate in Settings. +- **An SDK.** No client library wraps a Platform API. + +The [API tab](/api/overview) documents the RFC 9457 problem format the apps +and the CLI already use. That is the whole public API surface. + +## When this changes + +A Platform API would be announced on the [changelog](/changelog) and at +[cortex.foundation](https://cortex.foundation) first. Until an announcement +exists, treat any page or tool claiming to sell Platform API access as +unofficial. + +## Related + +- [Public API](/api/overview) — what this site does document. +- [Problem catalog](/problems) — the `type` URIs every error points at. +- [Changelog](/changelog) — where a Platform launch would appear. diff --git a/problems/bad_request.mdx b/problems/bad_request.mdx index c743efc..fdb346f 100644 --- a/problems/bad_request.mdx +++ b/problems/bad_request.mdx @@ -3,8 +3,6 @@ title: "bad_request" description: "Bad request. HTTP 400." --- -# `bad_request` - | | | | --- | --- | | HTTP | `400` | diff --git a/problems/concurrency_limit_reached.mdx b/problems/concurrency_limit_reached.mdx index 957c8ce..bb52ac6 100644 --- a/problems/concurrency_limit_reached.mdx +++ b/problems/concurrency_limit_reached.mdx @@ -3,8 +3,6 @@ title: "concurrency_limit_reached" description: "Too many runs at once. HTTP 429." --- -# `concurrency_limit_reached` - | | | | --- | --- | | HTTP | `429` | diff --git a/problems/conflict.mdx b/problems/conflict.mdx index ec7c3c3..081bb57 100644 --- a/problems/conflict.mdx +++ b/problems/conflict.mdx @@ -3,8 +3,6 @@ title: "conflict" description: "Conflict. HTTP 409." --- -# `conflict` - | | | | --- | --- | | HTTP | `409` | diff --git a/problems/content_policy.mdx b/problems/content_policy.mdx index 74a82d3..0fb97d6 100644 --- a/problems/content_policy.mdx +++ b/problems/content_policy.mdx @@ -3,8 +3,6 @@ title: "content_policy" description: "Blocked by content policy. HTTP 403." --- -# `content_policy` - | | | | --- | --- | | HTTP | `403` | diff --git a/problems/entitlement_required.mdx b/problems/entitlement_required.mdx index b6c4247..271f778 100644 --- a/problems/entitlement_required.mdx +++ b/problems/entitlement_required.mdx @@ -3,8 +3,6 @@ title: "entitlement_required" description: "Your plan does not include this. HTTP 403." --- -# `entitlement_required` - | | | | --- | --- | | HTTP | `403` | diff --git a/problems/forbidden.mdx b/problems/forbidden.mdx index 94b8ad0..c8ac9b8 100644 --- a/problems/forbidden.mdx +++ b/problems/forbidden.mdx @@ -3,8 +3,6 @@ title: "forbidden" description: "Not permitted. HTTP 403." --- -# `forbidden` - | | | | --- | --- | | HTTP | `403` | diff --git a/problems/gone.mdx b/problems/gone.mdx index 4c7493c..64c7ec5 100644 --- a/problems/gone.mdx +++ b/problems/gone.mdx @@ -3,8 +3,6 @@ title: "gone" description: "No longer available. HTTP 410." --- -# `gone` - | | | | --- | --- | | HTTP | `410` | diff --git a/problems/idempotency_key_reuse.mdx b/problems/idempotency_key_reuse.mdx index 06c8925..2ca696b 100644 --- a/problems/idempotency_key_reuse.mdx +++ b/problems/idempotency_key_reuse.mdx @@ -3,8 +3,6 @@ title: "idempotency_key_reuse" description: "Idempotency key reused with a different body. HTTP 400." --- -# `idempotency_key_reuse` - | | | | --- | --- | | HTTP | `400` | diff --git a/problems/index.mdx b/problems/index.mdx index d8d0abc..6e0f794 100644 --- a/problems/index.mdx +++ b/problems/index.mdx @@ -1,15 +1,26 @@ --- title: "Problem catalog" -description: "Dereferenceable type URIs for RFC 9457 documents from api.cortex.foundation." +description: "Every Cortex error code, its HTTP status, whether retrying helps, and the page that explains it." --- -`PROBLEM_TYPE_BASE` is `https://docs.cortex.foundation/problems`. Each -`ErrorCode` on the wire has a page at `/problems/{code}` whose path is the -snake_case `code` (not kebab-case). +Every Cortex failure carries a `code` and a `type`. The `type` is a URL that resolves to a page on this site, so an error message can point at its own explanation. -These pages are the `type` member of a problem document. They are generated -from `crates/cortex-core/src/error.rs` and must stay in lockstep — CI fails -if a code is missing. +The base is `https://docs.cortex.foundation/problems`. Each code has a page at `/problems/{code}`, and the path is the snake_case `code` — `rate_limited`, not `rate-limited`. + +## Start with the status + +| Status | Usually means | Where to look | +| --- | --- | --- | +| **400 / 422** | The request was malformed or the resource forbids it | [`validation_failed`](/problems/validation_failed), [`bad_request`](/problems/bad_request), [`invalid_state`](/problems/invalid_state) | +| **401 / 403** | Sign-in, permission, or plan | [`unauthenticated`](/problems/unauthenticated), [`invalid_credential`](/problems/invalid_credential), [`forbidden`](/problems/forbidden), [`entitlement_required`](/problems/entitlement_required) | +| **404 / 409 / 410** | The resource moved, changed, or was deleted | [`not_found`](/problems/not_found), [`conflict`](/problems/conflict), [`gone`](/problems/gone) | +| **413 / 415** | The payload is too big or the wrong type | [`payload_too_large`](/problems/payload_too_large), [`unsupported_media_type`](/problems/unsupported_media_type) | +| **429** | Too fast, or out of plan quota | [`rate_limited`](/problems/rate_limited), [`quota_exceeded`](/problems/quota_exceeded), [`concurrency_limit_reached`](/problems/concurrency_limit_reached) | +| **500–504** | Our side | [`internal`](/problems/internal), [`service_unavailable`](/problems/service_unavailable), [`no_capacity`](/problems/no_capacity), [`upstream_failure`](/problems/upstream_failure), [`upstream_timeout`](/problems/upstream_timeout) | + +**Retryable: yes** means the same request can succeed later. **Retryable: no** means retrying changes nothing — the request, the plan, or the state has to change first. + +## All codes | Code | HTTP | Retryable | | --- | --- | --- | @@ -22,6 +33,9 @@ if a code is missing. | [`invalid_credential`](/problems/invalid_credential) | 401 | no | | [`forbidden`](/problems/forbidden) | 403 | no | | [`entitlement_required`](/problems/entitlement_required) | 403 | no | +| [`safety_intervention`](/problems/safety_intervention) | 403 | no | +| [`content_policy`](/problems/content_policy) | 403 | no | +| [`jurisdiction_restricted`](/problems/jurisdiction_restricted) | 403 | no | | [`not_found`](/problems/not_found) | 404 | no | | [`conflict`](/problems/conflict) | 409 | yes | | [`gone`](/problems/gone) | 410 | no | @@ -29,11 +43,18 @@ if a code is missing. | [`quota_exceeded`](/problems/quota_exceeded) | 429 | no | | [`rate_limited`](/problems/rate_limited) | 429 | yes | | [`concurrency_limit_reached`](/problems/concurrency_limit_reached) | 429 | no | -| [`safety_intervention`](/problems/safety_intervention) | 403 | no | -| [`content_policy`](/problems/content_policy) | 403 | no | -| [`jurisdiction_restricted`](/problems/jurisdiction_restricted) | 403 | no | | [`internal`](/problems/internal) | 500 | yes | | [`upstream_failure`](/problems/upstream_failure) | 502 | yes | -| [`upstream_timeout`](/problems/upstream_timeout) | 504 | yes | | [`service_unavailable`](/problems/service_unavailable) | 503 | yes | | [`no_capacity`](/problems/no_capacity) | 503 | yes | +| [`upstream_timeout`](/problems/upstream_timeout) | 504 | yes | + +## Two 429s that mean different things + +[`rate_limited`](/problems/rate_limited) is transport throttling — you are sending requests too fast, and waiting fixes it. [`quota_exceeded`](/problems/quota_exceeded) is a plan window running out — waiting for the reset or changing plan fixes it. Read the `code`, not the status. + +## Related + +- [Errors](/api/errors) — the shape of a problem document and which fields to render. +- [Public API](/api/overview) — what this tab covers. +- [Troubleshooting](/getting-started/troubleshooting) — what a message in the product means. diff --git a/problems/internal.mdx b/problems/internal.mdx index 0a5ebe4..cfab1dd 100644 --- a/problems/internal.mdx +++ b/problems/internal.mdx @@ -3,8 +3,6 @@ title: "internal" description: "Something went wrong on our side. HTTP 500." --- -# `internal` - | | | | --- | --- | | HTTP | `500` | diff --git a/problems/invalid_credential.mdx b/problems/invalid_credential.mdx index 2cb903f..c5cd290 100644 --- a/problems/invalid_credential.mdx +++ b/problems/invalid_credential.mdx @@ -3,8 +3,6 @@ title: "invalid_credential" description: "Credential is invalid or expired. HTTP 401." --- -# `invalid_credential` - | | | | --- | --- | | HTTP | `401` | diff --git a/problems/invalid_state.mdx b/problems/invalid_state.mdx index 7267904..8c5fed6 100644 --- a/problems/invalid_state.mdx +++ b/problems/invalid_state.mdx @@ -3,8 +3,6 @@ title: "invalid_state" description: "Not allowed in the current state. HTTP 422." --- -# `invalid_state` - | | | | --- | --- | | HTTP | `422` | diff --git a/problems/jurisdiction_restricted.mdx b/problems/jurisdiction_restricted.mdx index 3bf3479..32c3255 100644 --- a/problems/jurisdiction_restricted.mdx +++ b/problems/jurisdiction_restricted.mdx @@ -3,8 +3,6 @@ title: "jurisdiction_restricted" description: "Not available in your region. HTTP 403." --- -# `jurisdiction_restricted` - | | | | --- | --- | | HTTP | `403` | diff --git a/problems/no_capacity.mdx b/problems/no_capacity.mdx index b7cf089..8f54caa 100644 --- a/problems/no_capacity.mdx +++ b/problems/no_capacity.mdx @@ -3,8 +3,6 @@ title: "no_capacity" description: "No capacity for this model right now. HTTP 503." --- -# `no_capacity` - | | | | --- | --- | | HTTP | `503` | diff --git a/problems/not_found.mdx b/problems/not_found.mdx index 99cd00c..f5ebbb8 100644 --- a/problems/not_found.mdx +++ b/problems/not_found.mdx @@ -3,8 +3,6 @@ title: "not_found" description: "Not found. HTTP 404." --- -# `not_found` - | | | | --- | --- | | HTTP | `404` | diff --git a/problems/payload_too_large.mdx b/problems/payload_too_large.mdx index 2eb527c..33b91e5 100644 --- a/problems/payload_too_large.mdx +++ b/problems/payload_too_large.mdx @@ -3,8 +3,6 @@ title: "payload_too_large" description: "Payload too large. HTTP 413." --- -# `payload_too_large` - | | | | --- | --- | | HTTP | `413` | diff --git a/problems/quota_exceeded.mdx b/problems/quota_exceeded.mdx index 74a4088..a45ffaf 100644 --- a/problems/quota_exceeded.mdx +++ b/problems/quota_exceeded.mdx @@ -3,8 +3,6 @@ title: "quota_exceeded" description: "Plan limit reached. HTTP 429." --- -# `quota_exceeded` - | | | | --- | --- | | HTTP | `429` | diff --git a/problems/rate_limited.mdx b/problems/rate_limited.mdx index 66faa06..1b7fc4b 100644 --- a/problems/rate_limited.mdx +++ b/problems/rate_limited.mdx @@ -3,8 +3,6 @@ title: "rate_limited" description: "Too many requests. HTTP 429." --- -# `rate_limited` - | | | | --- | --- | | HTTP | `429` | diff --git a/problems/safety_intervention.mdx b/problems/safety_intervention.mdx index c4b2eb5..2d4e6b9 100644 --- a/problems/safety_intervention.mdx +++ b/problems/safety_intervention.mdx @@ -3,8 +3,6 @@ title: "safety_intervention" description: "We want to make sure you are safe. HTTP 403." --- -# `safety_intervention` - | | | | --- | --- | | HTTP | `403` | diff --git a/problems/service_unavailable.mdx b/problems/service_unavailable.mdx index 05ce191..fb39e80 100644 --- a/problems/service_unavailable.mdx +++ b/problems/service_unavailable.mdx @@ -3,8 +3,6 @@ title: "service_unavailable" description: "Temporarily unavailable. HTTP 503." --- -# `service_unavailable` - | | | | --- | --- | | HTTP | `503` | diff --git a/problems/unauthenticated.mdx b/problems/unauthenticated.mdx index 26be2f8..124c6b6 100644 --- a/problems/unauthenticated.mdx +++ b/problems/unauthenticated.mdx @@ -3,8 +3,6 @@ title: "unauthenticated" description: "Authentication required. HTTP 401." --- -# `unauthenticated` - | | | | --- | --- | | HTTP | `401` | diff --git a/problems/unsupported_media_type.mdx b/problems/unsupported_media_type.mdx index b26118f..ff2923d 100644 --- a/problems/unsupported_media_type.mdx +++ b/problems/unsupported_media_type.mdx @@ -3,8 +3,6 @@ title: "unsupported_media_type" description: "Unsupported media type. HTTP 415." --- -# `unsupported_media_type` - | | | | --- | --- | | HTTP | `415` | diff --git a/problems/upstream_failure.mdx b/problems/upstream_failure.mdx index 1f14635..cf61c5c 100644 --- a/problems/upstream_failure.mdx +++ b/problems/upstream_failure.mdx @@ -3,8 +3,6 @@ title: "upstream_failure" description: "An upstream service failed. HTTP 502." --- -# `upstream_failure` - | | | | --- | --- | | HTTP | `502` | diff --git a/problems/upstream_timeout.mdx b/problems/upstream_timeout.mdx index 5b5ef42..d8f866d 100644 --- a/problems/upstream_timeout.mdx +++ b/problems/upstream_timeout.mdx @@ -3,8 +3,6 @@ title: "upstream_timeout" description: "An upstream service timed out. HTTP 504." --- -# `upstream_timeout` - | | | | --- | --- | | HTTP | `504` | diff --git a/problems/validation_failed.mdx b/problems/validation_failed.mdx index c7574cd..50a8a4f 100644 --- a/problems/validation_failed.mdx +++ b/problems/validation_failed.mdx @@ -3,8 +3,6 @@ title: "validation_failed" description: "Validation failed. HTTP 422." --- -# `validation_failed` - | | | | --- | --- | | HTTP | `422` | diff --git a/scripts/FERNDESK.md b/scripts/FERNDESK.md deleted file mode 100644 index 09ce17a..0000000 --- a/scripts/FERNDESK.md +++ /dev/null @@ -1,91 +0,0 @@ -# FernDesk sync (Mintlify → help center) - -## Staging vs Production - -FernDesk sections (not tags) are the env boundary: - -| Section | Use | -|---------|-----| -| **Production** | Public docs.cortex.foundation content (after custom domain connect) | -| **Staging** | Pre-prod / draft mirrors; unpublished or staging-only articles | - -Collections are mirrored under both sections (`Getting Started`, `Chat`, `Code`, `Bot`, …). -Article `keywords` carry `env:production|staging` plus a Mintlify path fingerprint. - -## Mechanical sync (durable) - -```bash -export FERNDESK_API_KEY=… # secret only — never paste in chat/PRs -FERNDESK_TARGET=production python3 scripts/ferndesk_sync.py -``` - -GitHub Action: `.github/workflows/ferndesk-sync.yml` - -- `push` to `main` (docs paths) -- `workflow_dispatch` (choose production|staging) -- `repository_dispatch` type `ferndesk-sync` (backend prod deploy hook) - -Repo secret required: `FERNDESK_API_KEY`. - -## Write-path rate limits (429) - -`POST /articles` answers `429` / `{"code":"rate_limited"}` under load. The write -path (article create, update, publish, and collection create) backs off -exponentially — 5s doubling to a 180s cap, the slower ladder 429 needs; 5xx and -CF 1010 keep 3s doubling to 90s — and waits for `Retry-After` (delta-seconds or -HTTP-date) whenever the server sends it and it is larger. - -Retries stay bounded on three axes, so a rate-limited run finishes and reports -instead of hanging: - -| Env | Default | Meaning | -|-----|---------|---------| -| `FERNDESK_WRITE_RETRIES` | `12` | attempts per write | -| `FERNDESK_WRITE_DEADLINE` | `1800` | seconds of retrying for one write | -| `FERNDESK_WRITE_BUDGET` | `5400` | seconds of retry time for the whole run (`0` disables) | - -Reads share the 12-attempt default. A `Retry-After` above 300s is treated as -unusable and the exponential ladder is used instead. Hard 4xx (400/401/404) -fail immediately without retrying. - -One page that exhausts its retries does not abort the rest of the run: the sync -continues, then logs a `FAILURES` line, records `failed` / `failed_slugs` in the -SUMMARY, and exits `1`. Nothing is cached for a failed write, so the next run -retries that page cleanly. Re-run the sync (or the workflow) once the limit -clears. - -A create that trips a slug conflict (409/422, or a message naming the slug) is -recovered by looking the article up and PATCHing it, so a stale cache or a -pagination miss does not surface as a failure. - -## Factory Droid path (agent) - -When Manager Deploy lands **prod** and content needs judgment (rewrites, gap fill, migration QA), launch Factory Droid only: - -- model: `custom:deepseek/deepseek-v4.1-flash` -- effort: **medium** -- **never** Cursor Cloud / CloudAgent, **never** `deepseek-v4-pro` - -Prompt the Droid to run `scripts/ferndesk_sync.py` against the tip of `CortexLM/docs` main with `FERNDESK_TARGET=production`, then report the SUMMARY JSON. Do not paste the API key into the prompt — use the Droid/host secret store. - -## Safety - -- Sync **upserts** by slug; it does **not** delete FernDesk-only articles. -- Mintlify remains the source of truth in git until cutover is complete. -- `python3 scripts/tests/ferndesk-sync-retry.test.py` covers the write-path - retry policy offline (faked transport, virtual clock). - -## Cloudflare / GitHub Actions - -FernDesk sits behind Cloudflare. GHA's stock Python `urllib` TLS fingerprint triggers -**Error 1010** (`browser_signature_banned`). The sync script uses `curl_cffi` with -`impersonate="chrome"` in CI (Chrome-like User-Agent + Accept; retries 403/1010 with -backoff). Local runs fall back to urllib only if `curl_cffi` is not installed. - -Separately: the FernDesk UI still needs **Connect domain** for `docs.cortex.foundation` -HTTPS. That custom-domain 403 is unrelated to CF 1010 on `api.ferndesk.com`. - -When that record goes in, use a **Direct CNAME (DNS only)** — **do not enable -orange-cloud Proxied**; that causes Cloudflare **Cross-User Banned** (Error 1014). -The same rule holds for every other Cortex custom domain that CNAMEs to a -third-party host: [`README.md`](../README.md#custom-domain-dns). diff --git a/scripts/check-docs-site.mjs b/scripts/check-docs-site.mjs index 523be5d..f7447c2 100644 --- a/scripts/check-docs-site.mjs +++ b/scripts/check-docs-site.mjs @@ -153,6 +153,9 @@ function collectNavPages(node, out = []) { if (typeof node !== 'object') return out; if (typeof node.page === 'string') pushNavSlug(node.page, out); if (typeof node.href === 'string') pushNavSlug(node.href, out); + // A group's `root` is the page its title opens. It is not repeated in + // `pages`, so walking only `pages` would leave a dead root link green. + if (typeof node.root === 'string') pushNavSlug(node.root, out); if (Array.isArray(node.pages)) collectNavPages(node.pages, out); if (Array.isArray(node.groups)) collectNavPages(node.groups, out); if (Array.isArray(node.tabs)) collectNavPages(node.tabs, out); diff --git a/scripts/ferndesk_sync.py b/scripts/ferndesk_sync.py deleted file mode 100755 index 69c6c1a..0000000 --- a/scripts/ferndesk_sync.py +++ /dev/null @@ -1,696 +0,0 @@ -#!/usr/bin/env python3 -"""Sync CortexLM/docs (Mintlify MDX) → FernDesk help center. - -Env: - FERNDESK_API_KEY required (Bearer). Never print/log the value. - FERNDESK_TARGET production|staging (default: production) - FERNDESK_DRY_RUN 1 = plan only (still needs API unless FERNDESK_DRY_LOCAL=1) - FERNDESK_DRY_LOCAL 1 = discover pages only, no API - FERNDESK_FULL_SCAN 1 = rebuild slug cache by listing all articles - FERNDESK_SLUG_CACHE path to slug→id cache (default .ferndesk-slug-cache.json) - FERNDESK_WRITE_RETRIES attempts per article/collection write (default 12) - FERNDESK_WRITE_DEADLINE seconds of retrying allowed per write (default 1800) - FERNDESK_WRITE_BUDGET seconds of retry time for the whole run (default 5400) - DOCS_ROOT docs repo root (default: cwd) - -Idempotent upsert by slug. Never deletes FernDesk-only articles (safe migration). -""" -from __future__ import annotations - -import datetime -import email.utils -import hashlib -import json -import os -import re -import sys -import time -import urllib.error -import urllib.parse -import urllib.request -from pathlib import Path - -API = "https://api.ferndesk.com/v1" - -FOLDER_TO_COLLECTION = { - "getting-started": "Getting Started", - "chat": "Chat", - "code": "Code", - "bot": "Bot", - "cli": "Cli", - "design": "Design", - "api": "Api", - "security": "Security", - "problems": "Problems", - "changelog": "Changelog", -} - -ROOT_FILE_COLLECTION = { - "index.mdx": "Index", - "platform.mdx": "Platform", - "changelog.mdx": "Changelog", - "status.mdx": "Getting Started", -} - -SKIP_NAMES = {"docs.json", "README.md", "AGENTS.md", "LICENSE", "custom.css"} - - -def log(msg: str) -> None: - print(msg, flush=True) - - -# Chrome-like UA so CF sees a browser-ish client alongside TLS impersonation. -_CHROME_UA = ( - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " - "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" -) -_ACCEPT = "application/json, text/plain, */*" - - -def _in_ci() -> bool: - return os.environ.get("CI") == "true" or os.environ.get("GITHUB_ACTIONS") == "true" - - -def _http_client(): - """Prefer curl_cffi Chrome TLS fingerprint; urllib only outside CI if missing.""" - try: - from curl_cffi import requests as cffi_requests # type: ignore - - return ("curl_cffi", cffi_requests) - except ImportError: - if _in_ci(): - raise RuntimeError( - "curl_cffi is required in CI (Cloudflare Error 1010 bans GHA urllib TLS). " - "Install with: pip install curl_cffi" - ) from None - return ("urllib", None) - - -def _is_cf_1010(status: int, body: str) -> bool: - if status != 403: - return False - b = body.lower() - return "1010" in b or "browser_signature_banned" in b or "error 1010" in b - - -# Transient failures worth retrying. 429 is the article-write rate limit -# (`{"code":"rate_limited"}`); 5xx are origin hiccups behind Cloudflare. -_RETRY_STATUSES = (429, 502, 503, 504) -_WRITE_METHODS = {"POST", "PATCH", "PUT", "DELETE"} -_BACKOFF_BASE = 3.0 -_BACKOFF_CAP = 90.0 -# 429 needs a longer cool-down than 5xx/CF 1010 — FernDesk rate limits recover -# slowly once the article writes start tripping them. -_RATE_LIMIT_BACKOFF_BASE = 5.0 -_RATE_LIMIT_BACKOFF_CAP = 180.0 -# A server-supplied Retry-After past this cap is treated as unusable and the -# exponential ladder is used instead — an unbounded header must not park CI. -_RETRY_AFTER_CAP = 300.0 -# Attempts per request. Writes additionally carry a per-write deadline and a -# run-wide budget so a rate-limited run always finishes and reports. -_DEFAULT_RETRIES = 12 -_DEFAULT_WRITE_RETRIES = 12 - - -def _env_number(name: str, default: float, minimum: float = 0.0) -> float: - raw = os.environ.get(name) - if raw is None or not raw.strip(): - return default - try: - value = float(raw) - except ValueError: - log(f"WARN {name}={raw!r} is not a number; using {default:g}") - return default - if value < minimum: - log(f"WARN {name}={raw!r} below minimum {minimum:g}; using {minimum:g}") - return minimum - return value - - -def _header(headers, name: str) -> str | None: - """Read a response header from curl_cffi/urllib without trusting either API.""" - if headers is None: - return None - try: - value = headers.get(name) - except Exception: - return None - return value if isinstance(value, str) else None - - -def _retry_after_seconds(value: str | None) -> float | None: - """Retry-After as delta-seconds or HTTP-date; None when absent or malformed.""" - if not value or not value.strip(): - return None - raw = value.strip() - try: - return max(0.0, float(raw)) - except ValueError: - pass - try: - when = email.utils.parsedate_to_datetime(raw) - except (TypeError, ValueError): - return None - if when is None: - return None - if when.tzinfo is None: - when = when.replace(tzinfo=datetime.timezone.utc) - return max(0.0, (when - datetime.datetime.now(datetime.timezone.utc)).total_seconds()) - - -def _retry_delay(attempt: int, retry_after: float | None, status: int | None = None) -> tuple[float, str]: - """Wait before the next attempt: server's Retry-After when usable, else backoff. - - 429 gets the slower ladder main introduced (5s doubling to 180s); other - transient statuses and CF 1010 keep the shorter one (3s doubling to 90s). - """ - if status == 429: - backoff = min(_RATE_LIMIT_BACKOFF_CAP, _RATE_LIMIT_BACKOFF_BASE * (2**attempt)) - else: - backoff = min(_BACKOFF_CAP, _BACKOFF_BASE * (2**attempt)) - if retry_after is None: - return backoff, "backoff" - if retry_after > _RETRY_AFTER_CAP: - return backoff, f"backoff (Retry-After {retry_after:.0f}s over {_RETRY_AFTER_CAP:.0f}s cap)" - return max(backoff, retry_after), "Retry-After" - - -# Retry time spent on writes so far. A sustained 429 storm across ~100 pages -# could otherwise run past the job timeout and lose the SUMMARY entirely, so -# the run stops retrying once the budget is gone and reports what landed. -_write_budget_spent = 0.0 - - -def _write_budget_left() -> float | None: - """Seconds of write retry time left, or None when the budget is disabled.""" - limit = _env_number("FERNDESK_WRITE_BUDGET", 5400.0, 0.0) - if limit <= 0: - return None - return max(0.0, limit - _write_budget_spent) - - -def api( - key: str, - path: str, - method: str = "GET", - body: dict | None = None, - retries: int | None = None, - deadline: float | None = None, - label: str | None = None, -): - headers = { - "Authorization": f"Bearer {key}", - "Accept": _ACCEPT, - "User-Agent": _CHROME_UA, - } - data = None - if body is not None: - data = json.dumps(body).encode() - headers["Content-Type"] = "application/json" - - client_kind, cffi_requests = _http_client() - last = None - url = API + path - tag = f" [{label}]" if label else "" - - # Writes are the constrained path (POST /articles 429 `rate_limited`), so - # they additionally get a wall-clock deadline and share a run-wide budget; - # reads just get the attempt count. - if retries is None: - retries = int( - _env_number("FERNDESK_WRITE_RETRIES", _DEFAULT_WRITE_RETRIES, 1.0) - if method in _WRITE_METHODS - else _DEFAULT_RETRIES - ) - if deadline is None and method in _WRITE_METHODS: - deadline = _env_number("FERNDESK_WRITE_DEADLINE", 1800.0, 1.0) - started = time.monotonic() - retry_after: float | None = None - retry_status: int | None = None - status_note = "transient failure" - write = method in _WRITE_METHODS - budget_left = _write_budget_left() if write else None - - for i in range(retries): - if i: - wait, why = _retry_delay(i - 1, retry_after, retry_status) - if deadline is not None and time.monotonic() - started + wait > deadline: - last = f"{last} (deadline {deadline:.0f}s exceeded after {i} attempts)" - break - if budget_left is not None: - if wait > budget_left: - last = ( - f"{last} (write retry budget exhausted after {i} attempts; " - "rerun the sync once the rate limit clears)" - ) - break - budget_left -= wait - globals()["_write_budget_spent"] = _write_budget_spent + wait - log( - f"{method} {path}{tag} retry {i}/{retries - 1} " - f"{status_note}; sleep {wait:.1f}s ({why})" - ) - time.sleep(wait) - try: - if client_kind == "curl_cffi": - resp = cffi_requests.request( - method, - url, - headers=headers, - data=data, - timeout=(15, 60), # connect, read — avoid multi-minute hangs - impersonate="chrome", - ) - txt = (resp.text or "")[:400] - if resp.status_code >= 400: - last = f"{method} {path} -> {resp.status_code} {txt}" - if resp.status_code in _RETRY_STATUSES or _is_cf_1010( - resp.status_code, txt - ): - retry_after = _retry_after_seconds(_header(resp.headers, "Retry-After")) - retry_status = resp.status_code - status_note = ( - "cf1010/403" - if _is_cf_1010(resp.status_code, txt) - else f"HTTP {resp.status_code}" - ) - continue - raise RuntimeError(last) - raw = resp.content or b"" - return json.loads(raw) if raw else {} - - # urllib fallback (local/dev only — GHA TLS fingerprint is banned by CF) - req = urllib.request.Request(url, data=data, headers=headers, method=method) - with urllib.request.urlopen(req, timeout=90) as r: - raw = r.read() - return json.loads(raw) if raw else {} - except urllib.error.HTTPError as e: - txt = e.read().decode("utf-8", "replace")[:400] - last = f"{method} {path} -> {e.code} {txt}" - if e.code in _RETRY_STATUSES or _is_cf_1010(e.code, txt): - retry_after = _retry_after_seconds(_header(e.headers, "Retry-After")) - retry_status = e.code - status_note = "cf1010/403" if _is_cf_1010(e.code, txt) else f"HTTP {e.code}" - continue - raise RuntimeError(last) from e - except urllib.error.URLError as e: - last = f"{method} {path} -> URLError {e}" - retry_after = None - retry_status = None - status_note = f"transport error ({e})" - continue - except RuntimeError: - raise - except Exception as e: - last = f"{method} {path} -> {type(e).__name__} {e}" - retry_after = None - retry_status = None - status_note = f"transport error ({e})" - continue - raise RuntimeError(last or "retries exhausted") - - -def list_all(key: str, path: str, max_pages: int = 100) -> list: - """Paginate FernDesk list endpoints with anti-loop guards. - - FernDesk has been observed to return has_more/next_cursor forever on - /collections (GHA ran 13k+ pages / ~278k rows). Guard with: - - max_pages hard cap - - repeated next_cursor detection - - stop when a page adds no new item ids - """ - out: list = [] - cursor = None - page_n = 0 - seen_cursors: set[str] = set() - seen_ids: set = set() - while True: - page_n += 1 - if page_n > max_pages: - log(f"list_all {path} stop: max_pages={max_pages} so_far={len(out)}") - break - p = path - if "limit=" not in path and "pageSize=" not in path: - sep0 = "&" if "?" in p else "?" - p = f"{p}{sep0}limit=100" - if cursor: - sep = "&" if "?" in p else "?" - p = f"{p}{sep}cursor={urllib.parse.quote(cursor)}" - log(f"list_all {path} page={page_n} so_far={len(out)}") - page = api(key, p) - if isinstance(page, list): - log(f"list_all {path} done count={len(page)} (array)") - return page - batch = page.get("results") or page.get("items") or [] - new_ids = 0 - for item in batch: - out.append(item) - iid = item.get("id") if isinstance(item, dict) else None - if iid is not None and iid not in seen_ids: - seen_ids.add(iid) - new_ids += 1 - if not page.get("has_more") or not page.get("next_cursor"): - break - nxt = str(page["next_cursor"]) - if nxt in seen_cursors: - log(f"list_all {path} stop: repeated cursor so_far={len(out)}") - break - if batch and new_ids == 0: - log(f"list_all {path} stop: no new ids so_far={len(out)}") - break - seen_cursors.add(nxt) - cursor = nxt - time.sleep(0.2) - log(f"list_all {path} done count={len(out)}") - return out - - -def strip_frontmatter(text: str) -> tuple[dict, str]: - meta: dict = {} - if text.startswith("---"): - end = text.find("\n---", 3) - if end != -1: - block = text[3:end].strip() - body = text[end + 4 :].lstrip("\n") - for line in block.splitlines(): - if ":" in line: - k, v = line.split(":", 1) - meta[k.strip()] = v.strip().strip("\"'") - return meta, body - return meta, text - - -def mdx_to_markdown(body: str) -> str: - lines = [ln for ln in body.splitlines() if not ln.startswith("import ")] - text = "\n".join(lines) - text = re.sub(r"<[A-Z][A-Za-z0-9]*(\s[^>]*)?/>", "", text) - text = re.sub(r"]*)?>", "", text) - return text.strip() + "\n" - - -def content_fingerprint(md: str) -> str: - return hashlib.sha256(md.encode()).hexdigest()[:16] - - -def discover_pages(docs_root: Path) -> list[dict]: - pages = [] - for path in sorted(docs_root.rglob("*")): - if not path.is_file() or path.suffix not in {".mdx", ".md"}: - continue - if path.name in SKIP_NAMES: - continue - rel = path.relative_to(docs_root).as_posix() - if rel.startswith(".") or "/." in rel: - continue - if any(p in rel.split("/") for p in ("scripts", "images", "logo", "node_modules", ".github")): - continue - meta, body = strip_frontmatter(path.read_text(encoding="utf-8", errors="replace")) - md = mdx_to_markdown(body) - raw_title = meta.get("title") or path.stem.replace("-", " ").replace("_", " ").title() - # FernDesk 400s on bare snake_case problem codes as titles (COR-444). - if "_" in raw_title and " " not in raw_title: - desc = (meta.get("description") or "").split(".")[0].strip() - nice = raw_title.replace("_", " ").title() - title = f"{desc} ({raw_title})" if desc else nice - else: - title = raw_title.replace("_", " ") if "_" in raw_title else raw_title - slug = rel.rsplit(".", 1)[0] - if slug.endswith("/index"): - slug = slug[: -len("/index")] or "index" - parts = rel.split("/") - if len(parts) == 1: - coll = ROOT_FILE_COLLECTION.get(parts[0], "Index") - else: - coll = FOLDER_TO_COLLECTION.get(parts[0]) - if not coll: - continue - fp = content_fingerprint(md) - pages.append( - { - "path": rel, - "title": title, - # FernDesk rejects underscores in slugs (POST 400); normalize. - "slug": slug.replace("/", "-").replace("_", "-"), - "collection": coll, - "markdown": md, - "fp": fp, - "keywords": f"source:mintlify;path:{rel};fp:{fp};env:{{ENV}}", - } - ) - return pages - - -def ensure_section(key: str, name: str) -> dict: - log(f"ensure_section {name!r}") - for s in list_all(key, "/sections"): - if s.get("name") == name: - return s - return api(key, "/sections", "POST", {"name": name, "slug": name.lower()}) - - -def ensure_collection(key: str, title: str, section_id: str, existing: list) -> dict: - for c in existing: - if c.get("sectionId") == section_id and c.get("title") == title and not c.get("parentCollectionId"): - return c - c = api(key, "/collections", "POST", {"title": title, "sectionId": section_id}) - existing.append(c) - time.sleep(0.25) - return c - - -def main() -> int: - key = os.environ.get("FERNDESK_API_KEY") - target = os.environ.get("FERNDESK_TARGET", "production").strip().lower() - if target not in ("production", "staging"): - log("ERROR: FERNDESK_TARGET must be production|staging") - return 2 - dry = os.environ.get("FERNDESK_DRY_RUN") == "1" - dry_local = os.environ.get("FERNDESK_DRY_LOCAL") == "1" - docs_root = Path(os.environ.get("DOCS_ROOT") or Path.cwd()).resolve() - section_name = "Production" if target == "production" else "Staging" - publish = target == "production" - - log(f"docs_root={docs_root} target={target} section={section_name} dry={dry} dry_local={dry_local}") - pages = discover_pages(docs_root) - log(f"discovered {len(pages)} mintlify pages") - - if dry_local: - from collections import Counter - log("by_collection " + json.dumps(Counter(p["collection"] for p in pages))) - for page in pages[:15]: - log(f" {page['collection']}: {page['slug']}") - if len(pages) > 15: - log(f" … +{len(pages)-15} more") - summary = {"target": target, "pages": len(pages), "mode": "dry-local"} - log("SUMMARY " + json.dumps(summary)) - return 0 - - if not key: - log("ERROR: FERNDESK_API_KEY missing") - return 2 - - section = ensure_section(key, section_name) - # Scope to section — unscoped /collections paginated forever in GHA (COR-444). - colls = list_all(key, f"/collections?sectionId={urllib.parse.quote(str(section['id']))}") - cache_path = Path(os.environ.get("FERNDESK_SLUG_CACHE", f".ferndesk-slug-cache-{target}.json")) - by_slug: dict = {} - cache_loaded = False - if cache_path.exists() and os.environ.get("FERNDESK_FULL_SCAN") != "1": - try: - by_slug = json.loads(cache_path.read_text()) - cache_loaded = True - log(f"loaded slug cache {len(by_slug)} from {cache_path}") - except Exception: - by_slug = {} - if (not cache_loaded) or os.environ.get("FERNDESK_FULL_SCAN") == "1": - log("scanning articles for section (set FERNDESK_FULL_SCAN=1 to force)…") - articles = [ - a - for a in list_all( - key, - f"/articles?sectionId={urllib.parse.quote(str(section['id']))}", - max_pages=200, - ) - if a.get("sectionId") == section["id"] - ] - by_slug = { - a.get("slug"): { - "id": a["id"], - "status": a.get("status"), - "keywords": a.get("keywords") or "", - } - for a in articles - if a.get("slug") - } - cache_path.write_text(json.dumps(by_slug, indent=2) + "\n") - log(f"wrote slug cache {len(by_slug)}") - - created = updated = skipped = failed = 0 - failures: list[dict] = [] - for page in pages: - page["keywords"] = page["keywords"].replace("{ENV}", target) - coll = ensure_collection(key, page["collection"], section["id"], colls) - existing = by_slug.get(page["slug"]) - body_common = { - "title": page["title"], - "markdown": page["markdown"], - "collectionId": coll["id"], - "sectionId": section["id"], - "slug": page["slug"], - "keywords": page["keywords"], - "metaDescription": f"Cortex docs ({target}): {page['title']}", - } - if existing: - eid = existing["id"] if isinstance(existing, dict) else existing - estatus = existing.get("status") if isinstance(existing, dict) else None - ekw = (existing.get("keywords") or "") if isinstance(existing, dict) else "" - if dry: - log(f"DRY update {page['slug']} -> {eid}") - skipped += 1 - continue - # Skip PATCH when Mintlify fingerprint already present (saves write quota). - if page["fp"] and f"fp:{page['fp']}" in ekw: - skipped += 1 - log(f"skip unchanged {page['slug']}") - continue - try: - api(key, f"/articles/{eid}", "PATCH", body_common, label=page["slug"]) - if publish and estatus != "published": - time.sleep(0.15) - api(key, f"/articles/{eid}/publish", "POST", {}, label=page["slug"]) - except RuntimeError as e: - # One stuck article must not abort the remaining pages; the - # failure still fails the run and lands in SUMMARY. - failed += 1 - failures.append({"slug": page["slug"], "op": "update", "error": str(e)}) - log(f"ERROR update {page['slug']} failed after retries: {e}") - continue - by_slug[page["slug"]] = { - "id": eid, - "status": "published" if publish else (estatus or "draft"), - "keywords": page["keywords"], - } - cache_path.write_text(json.dumps(by_slug, indent=2) + "\n") - updated += 1 - log(f"updated {page['slug']}") - time.sleep(1.2) - continue - - if dry: - log(f"DRY create {page['slug']} in {page['collection']}") - created += 1 - continue - # Lookup-before-create: avoid POST when slug already exists but missed cache. - looked = None - try: - for a in list_all( - key, - f"/articles?sectionId={urllib.parse.quote(str(section['id']))}&slug={urllib.parse.quote(page['slug'])}", - max_pages=3, - ): - if a.get("slug") == page["slug"]: - looked = a - break - except RuntimeError as e: - log(f"slug lookup failed for {page['slug']}: {e}") - if looked: - try: - api(key, f"/articles/{looked['id']}", "PATCH", body_common, label=page["slug"]) - if publish and looked.get("status") != "published": - time.sleep(0.3) - api(key, f"/articles/{looked['id']}/publish", "POST", {}, label=page["slug"]) - except RuntimeError as e: - failed += 1 - failures.append({"slug": page["slug"], "op": "lookup-update", "error": str(e)}) - log(f"ERROR lookup-update {page['slug']} failed after retries: {e}") - continue - by_slug[page["slug"]] = { - "id": looked["id"], - "status": looked.get("status") or "published", - "keywords": page["keywords"], - } - cache_path.write_text(json.dumps(by_slug, indent=2) + "\n") - updated += 1 - log(f"lookup-update {page['slug']}") - time.sleep(1.5) - continue - - try: - art = api( - key, - "/articles", - "POST", - {**body_common, "publish": publish}, - label=page["slug"], - ) - except RuntimeError as e: - # Slug may already exist (pagination/cache miss) — recover via lookup + PATCH. - msg = str(e) - if "409" in msg or "already" in msg.lower() or "slug" in msg.lower() or "422" in msg: - log(f"create conflict for {page['slug']}; looking up existing…") - found = None - for a in list_all( - key, - f"/articles?sectionId={urllib.parse.quote(str(section['id']))}&slug={urllib.parse.quote(page['slug'])}", - max_pages=5, - ): - if a.get("slug") == page["slug"]: - found = a - break - if found: - art = found - api(key, f"/articles/{art['id']}", "PATCH", body_common, label=page["slug"]) - if publish and art.get("status") != "published": - time.sleep(0.3) - api(key, f"/articles/{art['id']}/publish", "POST", {}, label=page["slug"]) - by_slug[page["slug"]] = { - "id": art["id"], - "status": art.get("status") or "published", - } - cache_path.write_text(json.dumps(by_slug, indent=2) + "\n") - updated += 1 - log(f"recovered-update {page['slug']}") - time.sleep(1.5) - continue - # Still stuck: one page must not abort the remaining pages, but the - # failure is recorded and fails the run. - failed += 1 - failures.append({"slug": page["slug"], "op": "create", "error": str(e)}) - log(f"ERROR create {page['slug']} failed after retries: {e}") - continue - by_slug[page["slug"]] = { - "id": art.get("id"), - "status": art.get("status") or ("published" if publish else "draft"), - } - cache_path.write_text(json.dumps(by_slug, indent=2) + "\n") - created += 1 - log(f"created {page['slug']} {art.get('id')}") - time.sleep(1.5) - - summary = { - "target": target, - "section_id": section["id"], - "pages": len(pages), - "created": created, - "updated": updated, - "skipped": skipped, - "failed": failed, - } - if failures: - summary["failed_slugs"] = [f["slug"] for f in failures] - log("FAILURES " + json.dumps(failures)) - log("SUMMARY " + json.dumps(summary)) - Path(os.environ.get("FERNDESK_SUMMARY_PATH") or "ferndesk-sync-summary.json").write_text( - json.dumps(summary, indent=2) + "\n" - ) - if failures: - log( - f"ERROR {failed} of {len(pages)} pages failed after retries " - "(see FAILURES above); rerun the sync once the rate limit clears" - ) - return 1 - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/tests/check-docs-site.test.sh b/scripts/tests/check-docs-site.test.sh index f908130..e3de33d 100755 --- a/scripts/tests/check-docs-site.test.sh +++ b/scripts/tests/check-docs-site.test.sh @@ -265,6 +265,70 @@ JSON must_fail "$missingnav" "missing-mintlify-page" must_fail "$missingnav" "also-missing-mintlify-page" +# A group `root` is a page the sidebar title opens, and it is not repeated in +# `pages`. Walking only `pages` would publish a dead group title. +missingroot="$tmp/missing-root" +seed "$missingroot" +cat > "$missingroot/site/docs.json" <<'JSON' +{ + "name": "Cortex", + "logo": { "href": "https://docs.cortex.foundation" }, + "navbar": { + "links": [ + { "label": "Home", "href": "/" }, + { "label": "Documentation", "href": "/problems/not_found" } + ] + }, + "navigation": { + "tabs": [ + { + "tab": "API", + "groups": [ + { + "group": "Problems", + "root": "missing-root-page", + "pages": ["problems/not_found", "problems/internal"] + } + ] + } + ] + } +} +JSON +must_fail "$missingroot" "missing-root-page" + +# The same group with a root that does exist must pass. +goodroot="$tmp/good-root" +seed "$goodroot" +cat > "$goodroot/site/docs.json" <<'JSON' +{ + "name": "Cortex", + "logo": { "href": "https://docs.cortex.foundation" }, + "navbar": { + "links": [ + { "label": "Home", "href": "/" }, + { "label": "Documentation", "href": "/problems/not_found" } + ] + }, + "navigation": { + "tabs": [ + { + "tab": "API", + "groups": [ + { + "group": "Problems", + "root": "problems/not_found", + "pages": ["problems/internal"] + } + ] + } + ] + } +} +JSON +out="$(CORTEX_CHECK_ROOT="$goodroot/site" node "$script" "$goodroot" 2>&1)" || + fail "a group root backed by an MDX page should pass, got: $out" + # Retired fake-app SVG plates must not appear in public MDX. frames="$tmp/fake-app-frame" seed "$frames" diff --git a/scripts/tests/ferndesk-sync-retry.test.py b/scripts/tests/ferndesk-sync-retry.test.py deleted file mode 100644 index d041ff0..0000000 --- a/scripts/tests/ferndesk-sync-retry.test.py +++ /dev/null @@ -1,350 +0,0 @@ -#!/usr/bin/env python3 -"""Guards the FernDesk sync write-path retry policy (COR-444 residual). - -`POST /articles` answers 429 `rate_limited` under load, so the write path has to -back off, honour `Retry-After`, and stay bounded. Runs offline: the HTTP client -is faked and `time.sleep` is virtual, so no network and no real waits. -""" -from __future__ import annotations - -import importlib.util -import json -import os -import sys -import tempfile -from contextlib import contextmanager -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[2] -_spec = importlib.util.spec_from_file_location( - "ferndesk_sync", ROOT / "scripts" / "ferndesk_sync.py" -) -fs = importlib.util.module_from_spec(_spec) -_spec.loader.exec_module(fs) - -failures: list[str] = [] - - -def check(name: str, cond: bool, detail: object = "") -> None: - if cond: - print(f"ok {name}") - else: - failures.append(name) - print(f"FAIL {name} {detail}") - - -@contextmanager -def env(**pairs: str): - saved = {k: os.environ.get(k) for k in pairs} - os.environ.update({k: str(v) for k, v in pairs.items()}) - try: - yield - finally: - for k, v in saved.items(): - if v is None: - os.environ.pop(k, None) - else: - os.environ[k] = v - - -class FakeTime: - """Virtual clock: `sleep` advances `monotonic` so deadlines are testable.""" - - def __init__(self) -> None: - self.now = 0.0 - self.slept: list[float] = [] - - def sleep(self, seconds: float) -> None: - self.slept.append(seconds) - self.now += seconds - - def monotonic(self) -> float: - return self.now - - -class FakeResponse: - def __init__(self, status_code: int, payload: object = None, headers=None) -> None: - self.status_code = status_code - self.headers = headers or {} - body = b"" if payload is None else json.dumps(payload).encode() - self.content = body - self.text = body.decode() - - -class FakeClient: - """Scripted transport; records every request it is asked to make.""" - - def __init__(self, script) -> None: - self.script = script - self.calls: list[tuple[str, str]] = [] - - def request(self, method, url, **kwargs): - self.calls.append((method, url)) - return self.script(method, url, len(self.calls)) - - -def install(script, monkeypatch_time: bool = True): - """Point the sync module at a fake transport and (optionally) a virtual clock.""" - client = FakeClient(script) - logs: list[str] = [] - saved = (fs._http_client, fs.log, fs.time if monkeypatch_time else None) - fake_time = FakeTime() - fs._http_client = lambda: ("curl_cffi", client) - fs.log = lambda msg: logs.append(str(msg)) - if monkeypatch_time: - fs.time = fake_time - return client, logs, fake_time, saved - - -def restore(saved) -> None: - fs._http_client, fs.log, saved_time = saved - if saved_time is not None: - fs.time = saved_time - fs._write_budget_spent = 0.0 - - -# --- Retry-After parsing ----------------------------------------------------- -check("retry-after absent is None", fs._retry_after_seconds(None) is None) -check("retry-after blank is None", fs._retry_after_seconds(" ") is None) -check("retry-after garbage is None", fs._retry_after_seconds("soon") is None) -check("retry-after seconds parsed", fs._retry_after_seconds("45") == 45.0) -check("retry-after negative clamps to 0", fs._retry_after_seconds("-3") == 0.0) -_date = fs.email.utils.formatdate(fs.time.time() + 120, usegmt=True) -_parsed = fs._retry_after_seconds(_date) -check( - "retry-after HTTP-date parsed", - _parsed is not None and 100 <= _parsed <= 130, - _parsed, -) -check("header helper tolerates None", fs._header(None, "Retry-After") is None) -check("header helper rejects non-str", fs._header({"Retry-After": 5}, "Retry-After") is None) - -# --- Delay ladder ------------------------------------------------------------ -_wait, why = fs._retry_delay(0, None, 503) -check("first backoff is the base", _wait == fs._BACKOFF_BASE and why == "backoff", (_wait, why)) -_wait, _ = fs._retry_delay(1, None, 503) -check("backoff grows exponentially", _wait == fs._BACKOFF_BASE * 2, _wait) -_wait, _ = fs._retry_delay(9, None, 503) -check("backoff is capped", _wait == fs._BACKOFF_CAP, _wait) -_wait, why = fs._retry_delay(0, None, 429) -check("429 uses the slower ladder", _wait == fs._RATE_LIMIT_BACKOFF_BASE, (_wait, why)) -_wait, _ = fs._retry_delay(6, None, 429) -check("429 ladder reaches its higher cap", _wait == fs._RATE_LIMIT_BACKOFF_CAP, _wait) -_wait, why = fs._retry_delay(0, 45.0, 429) -check("retry-after wins when larger", _wait == 45.0 and why == "Retry-After", (_wait, why)) -_wait, why = fs._retry_delay(0, 1.0, 429) -check("server floor never undercut", _wait == fs._RATE_LIMIT_BACKOFF_BASE, _wait) -_wait, why = fs._retry_delay(0, fs._RETRY_AFTER_CAP + 1, 429) -check("absurd retry-after falls back to backoff", _wait == fs._RATE_LIMIT_BACKOFF_BASE and "cap" in why, (_wait, why)) - -# --- 429 then success on the write path ------------------------------------- -def flaky_then_ok(method, url, n): - if n < 3: - return FakeResponse(429, {"error": "Too many requests", "code": "rate_limited"}, - {"Retry-After": "20"}) - return FakeResponse(201, {"id": "art-1", "status": "published"}) - - -client, logs, fake_time, saved = install(flaky_then_ok) -try: - with env(FERNDESK_WRITE_RETRIES="5", FERNDESK_WRITE_DEADLINE="600"): - art = fs.api("k", "/articles", "POST", {"title": "t"}, label="getting-started-quickstart") - check("write retries through 429 and succeeds", art.get("id") == "art-1", art) - check("write made three attempts", len(client.calls) == 3, client.calls) - check("retry-after honoured on every wait", fake_time.slept == [20.0, 20.0], fake_time.slept) - check("write method is POST throughout", {m for m, _ in client.calls} == {"POST"}, client.calls) - check("logs name the article slug", any("getting-started-quickstart" in line for line in logs), logs) - check("logs report the rate limit", any("HTTP 429" in line for line in logs), logs) - check("logs report the wait reason", any("Retry-After" in line for line in logs), logs) -finally: - restore(saved) - -# --- exhausted retries raise with the body ---------------------------------- -client, logs, fake_time, saved = install(lambda m, u, n: FakeResponse(429, {"code": "rate_limited"}, {})) -try: - with env(FERNDESK_WRITE_RETRIES="4"): - raised = None - try: - fs.api("k", "/articles", "POST", {}, label="slug-x") - except RuntimeError as e: - raised = str(e) - check("exhausted retries raise", raised is not None, raised) - check("error keeps the last status", raised and "429" in raised, raised) - check("error keeps the api code", raised and "rate_limited" in raised, raised) - check("attempts match the configured budget", len(client.calls) == 4, len(client.calls)) -finally: - restore(saved) - -# --- deadline stops the ladder ---------------------------------------------- -client, logs, fake_time, saved = install(lambda m, u, n: FakeResponse(503, {"code": "unavailable"}, {})) -try: - with env(FERNDESK_WRITE_RETRIES="20"): - raised = None - try: - fs.api("k", "/articles", "POST", {}, deadline=10) - except RuntimeError as e: - raised = str(e) - check("deadline aborts a long ladder", raised is not None and "deadline" in raised, raised) - check("deadline stops before the cap", len(client.calls) == 3, len(client.calls)) - check("virtual clock stayed within the deadline", fake_time.now <= 10, fake_time.now) -finally: - restore(saved) - -# --- 5xx still retried, hard 4xx is not ------------------------------------- -client, logs, fake_time, saved = install( - lambda m, u, n: FakeResponse(400, {"error": "bad request", "code": "invalid_request"}, {}) -) -try: - with env(FERNDESK_WRITE_RETRIES="5"): - raised = None - try: - fs.api("k", "/articles", "POST", {}) - except RuntimeError as e: - raised = str(e) - check("400 fails fast without retrying", len(client.calls) == 1, len(client.calls)) - check("400 surfaces its code", raised and "invalid_request" in raised, raised) -finally: - restore(saved) - -# --- reads keep the lighter budget ------------------------------------------ -client, logs, fake_time, saved = install(lambda m, u, n: FakeResponse(429, {"code": "rate_limited"}, {})) -try: - with env(FERNDESK_WRITE_RETRIES="99"): - try: - fs.api("k", "/articles") - except RuntimeError: - pass - check("reads keep the shared default budget", len(client.calls) == fs._DEFAULT_RETRIES, len(client.calls)) -finally: - restore(saved) - -# --- run-wide write budget -------------------------------------------------- -client, logs, fake_time, saved = install( - lambda m, u, n: FakeResponse(429, {"code": "rate_limited"}, {"Retry-After": "30"}) -) -try: - with env(FERNDESK_WRITE_RETRIES="50", FERNDESK_WRITE_BUDGET="100"): - raised = None - try: - fs.api("k", "/articles", "POST", {}, label="budget-a") - except RuntimeError as e: - raised = str(e) - check("run budget stops a rate-limit storm", raised is not None and "budget" in raised, raised) - check("run budget spends no more than allowed", fake_time.now <= 100, fake_time.now) - spent = fs._write_budget_spent - check("run budget is charged for waits", 0 < spent <= 100, spent) - check("run budget is shared across writes", spent > 0, spent) -finally: - restore(saved) - -# --- budget disabled means retry until the per-write deadline ---------------- -client, logs, fake_time, saved = install( - lambda m, u, n: FakeResponse(429, {"code": "rate_limited"}, {"Retry-After": "30"}) -) -try: - with env(FERNDESK_WRITE_RETRIES="50", FERNDESK_WRITE_BUDGET="0"): - raised = None - try: - fs.api("k", "/articles", "POST", {}, deadline=120) - except RuntimeError as e: - raised = str(e) - check("budget 0 disables the run-wide cap", raised is not None and "deadline" in raised, raised) -finally: - restore(saved) - -# --- shared fixtures for the end-to-end runs --------------------------------- -docs_root = Path(tempfile.mkdtemp(prefix="ferndesk-docs-")) -(docs_root / "getting-started").mkdir(parents=True) -(docs_root / "getting-started" / "quickstart.mdx").write_text( - "---\ntitle: Quickstart\n---\n\nHello.\n", encoding="utf-8" -) - -SECTIONS = [{"id": "sec-1", "name": "Staging"}] -COLLECTIONS = [{"id": "col-1", "sectionId": "sec-1", "title": "Getting Started"}] - -# --- slug lookup / create conflict recovery (lookup-before-create + PATCH) ------- -def conflict_routes(method, url, n): - if "/sections" in url: - return FakeResponse(200, SECTIONS) - if "/collections" in url: - return FakeResponse(200, COLLECTIONS) - if "/articles" in url and method == "POST": - return FakeResponse(409, {"error": "slug already exists", "code": "conflict"}) - if "/articles" in url and "slug=" in url: - return FakeResponse(200, {"results": [{"id": "art-9", "slug": "getting-started-quickstart", - "status": "draft", "sectionId": "sec-1"}], - "has_more": False}) - if "/articles" in url and method == "PATCH": - return FakeResponse(200, {"id": "art-9", "status": "draft"}) - if "/articles" in url: - return FakeResponse(200, {"results": [], "has_more": False}) - return FakeResponse(404, {"code": "not_found"}) - - -summary_path = docs_root / "summary-conflict.json" -cache_path = docs_root / "cache-conflict.json" -client, logs, fake_time, saved = install(conflict_routes) -try: - with env( - FERNDESK_API_KEY="test-key", - FERNDESK_TARGET="staging", - FERNDESK_SLUG_CACHE=str(cache_path), - FERNDESK_SUMMARY_PATH=str(summary_path), - DOCS_ROOT=str(docs_root), - FERNDESK_FULL_SCAN="1", - ): - code = fs.main() - summary = json.loads(summary_path.read_text()) - check("conflict recovery keeps the run green", code == 0, code) - check("conflict recovery counts an update", summary.get("updated") == 1, summary) - check("conflict recovery reports no failures", summary.get("failed") == 0, summary) - check("conflict recovery caches the found id", json.loads(cache_path.read_text()).get( - "getting-started-quickstart", {}).get("id") == "art-9", cache_path.read_text()) - check("conflict recovery is logged", any(("recovered-update" in line or "lookup-update" in line) for line in logs), logs) -finally: - restore(saved) - -# --- end-to-end: a permanently rate-limited page fails the run honestly ----- -def sync_routes(method, url, n): - if "/sections" in url: - return FakeResponse(200, SECTIONS) - if "/collections" in url: - return FakeResponse(200, COLLECTIONS) - if "/articles" in url and method == "POST": - # The residual COR-444 case: article creation stays rate limited. - return FakeResponse(429, {"error": "Too many requests", "code": "rate_limited"}, - {"Retry-After": "5"}) - if "/articles" in url: - # Slug scan lists the section before the write path runs. - return FakeResponse(200, {"results": [], "has_more": False}) - return FakeResponse(404, {"code": "not_found"}) - - -summary_path = docs_root / "summary.json" -cache_path = docs_root / "cache.json" -client, logs, fake_time, saved = install(sync_routes) -try: - with env( - FERNDESK_API_KEY="test-key", - FERNDESK_TARGET="staging", - FERNDESK_WRITE_RETRIES="3", - FERNDESK_SLUG_CACHE=str(cache_path), - FERNDESK_SUMMARY_PATH=str(summary_path), - DOCS_ROOT=str(docs_root), - FERNDESK_FULL_SCAN="1", - ): - code = fs.main() - summary = json.loads(summary_path.read_text()) - check("a stuck page fails the run", code == 1, code) - check("summary counts the failure", summary.get("failed") == 1, summary) - check("summary names the stuck slug", summary.get("failed_slugs") == ["getting-started-quickstart"], summary) - check("summary still reports the page total", summary.get("pages") == 1, summary) - check("no article id cached for a failed write", json.loads(cache_path.read_text()) == {}, cache_path.read_text()) - check("FAILURES line is logged", any(line.startswith("FAILURES ") for line in logs), logs) -finally: - restore(saved) - -if failures: - print(f"ferndesk-sync-retry: {len(failures)} check(s) failed", file=sys.stderr) - sys.exit(1) -print("ferndesk-sync-retry: ok") diff --git a/security/overview.mdx b/security/overview.mdx index 274605a..f14d824 100644 --- a/security/overview.mdx +++ b/security/overview.mdx @@ -65,3 +65,11 @@ GitHub App review of pull requests is a **product**, at `/code/security` in the ## What this page does not claim Cortex does not claim SOC 2, GDPR certification, or HIPAA compliance in product copy, and neither does this site. Legal documents live at [cortex.foundation/privacy](https://cortex.foundation/privacy), [cortex.foundation/terms](https://cortex.foundation/terms), and [cortex.foundation/ai-disclosures](https://cortex.foundation/ai-disclosures). + +## Related + +- [Accounts](/getting-started/accounts) — what a signed-in account holds. +- [Settings](/getting-started/settings) — the Data & privacy controls in one place. +- [Approvals](/bot/approvals) — how a bot asks before consequential steps. +- [Modes and permissions](/cli/modes-and-permissions) — the CLI's sandbox and approval policy. +- [Errors](/api/errors) — why user-facing text never names a vendor. diff --git a/status.mdx b/status.mdx index 09697c5..a81a568 100644 --- a/status.mdx +++ b/status.mdx @@ -7,8 +7,6 @@ description: "Live availability for Cortex Web, API, Chat, Code, and Bot — inc Cortex -# System status - Cortex publishes live component health, incident history, and scheduled maintenance on the public status page.

@@ -52,3 +50,9 @@ User-facing error text names a Cortex surface, never a vendor. Status updates ar ## Brand Status communications use Cortex green (`#1F4945`) and the Cortex logo. The status site is hosted under the `cortex.foundation` domain so it stays available when you need it most. + +## Related + +- [Troubleshooting](/getting-started/troubleshooting) — what a message in the product means. +- [Chat](/chat) · [Code](/code) · [Bot](/bot) · [CLI](/cli) — the surfaces status reports on. +- [Problem catalog](/problems) — the error codes behind a failed request. From 4bb57c47a005fa8034f5dbdc9f40ba91f8a97658 Mon Sep 17 00:00:00 2001 From: DroidAgent <154886644+echobt@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:48:49 +0000 Subject: [PATCH 2/2] docs: make pages findable by the labels the product uses Follow-up polish on the IA pass. Two things made pages harder to find than they needed to be: the sidebar and the docs disagreed on names, and several descriptions were long enough to be truncated before they said anything useful. Search - Added `keywords` to every content page, mapping the label a reader sees in the product to the page that documents it. The sidebar says **Artifacts** and the page is Library; it says **Planning** and the page is Plans; it says **Agents** and the page is Cortex Bot. Searching either term now finds the page. No content page relied on this before. - Gave `Sessions` and `Canvases` product-qualified titles, because each name covered two different pages and the pair was indistinguishable in search. `sidebarTitle` keeps the sidebar short, so the pages still read "Sessions" and "Canvases" where space is tight. Metadata - Trimmed 19 descriptions to fit under 160 characters. Longer text is cut off in search results and link previews, so the trailing half was doing no work. No claim changed; where the cut cost a possessive, it was restored. - Fixed the image inventory: `images/product/README.md` still pointed at the four overview pages this branch removed, and `bot-public.png` had gone unused when the Bot pages were merged. The capture is placed again, so every product screenshot is now used on exactly one page. Docs - README and AGENTS record the description limit, the unique-title rule, and the keyword convention, so the next page follows them. Validation: check-docs-site, its shell suite, docs-ui, and mint validate all pass. Re-checked structurally: no orphans, no dead navigation, no broken internal links, no duplicate titles. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- AGENTS.md | 4 ++++ README.md | 11 +++++++++++ api/errors.mdx | 1 + api/overview.mdx | 1 + bot/approvals.mdx | 1 + bot/computer.mdx | 3 ++- bot/desktop.mdx | 1 + bot/how-it-works.mdx | 3 ++- bot/index.mdx | 7 ++++++- bot/quickstart.mdx | 1 + bot/routines.mdx | 3 ++- bot/skills.mdx | 1 + bot/tools.mdx | 3 ++- bot/troubleshooting.mdx | 1 + changelog.mdx | 1 + chat/canvases.mdx | 4 +++- chat/deep-research.mdx | 1 + chat/how-it-works.mdx | 3 ++- chat/images.mdx | 1 + chat/index.mdx | 1 + chat/library.mdx | 1 + chat/memory.mdx | 1 + chat/models.mdx | 1 + chat/origin-check.mdx | 1 + chat/plans.mdx | 1 + chat/projects.mdx | 1 + chat/quickstart.mdx | 1 + chat/sharing.mdx | 1 + chat/streaming.mdx | 1 + chat/tools.mdx | 3 ++- chat/troubleshooting.mdx | 1 + chat/voice.mdx | 1 + cli/configuration.mdx | 1 + cli/extend.mdx | 1 + cli/goal.mdx | 3 ++- cli/headless.mdx | 1 + cli/index.mdx | 3 ++- cli/install.mdx | 1 + cli/keyboard.mdx | 1 + cli/modes-and-permissions.mdx | 3 ++- cli/quickstart.mdx | 1 + cli/sessions.mdx | 4 +++- cli/sign-in.mdx | 3 ++- cli/slash-commands.mdx | 1 + cli/troubleshooting.mdx | 3 ++- cli/tui.mdx | 3 ++- code/cli.mdx | 3 ++- code/cloud.mdx | 1 + code/desktop.mdx | 1 + code/github.mdx | 3 ++- code/how-it-works.mdx | 3 ++- code/index.mdx | 3 ++- code/interactions.mdx | 3 ++- code/quickstart.mdx | 1 + code/security.mdx | 3 ++- code/sessions.mdx | 4 +++- code/troubleshooting.mdx | 1 + design/canvases.mdx | 4 +++- design/desktop.mdx | 1 + design/index.mdx | 1 + design/library.mdx | 1 + getting-started/accounts.mdx | 1 + getting-started/desktop.mdx | 1 + getting-started/downloads.mdx | 1 + getting-started/plans.mdx | 1 + getting-started/quickstart.mdx | 1 + getting-started/settings.mdx | 1 + getting-started/troubleshooting.mdx | 1 + getting-started/what-is-cortex.mdx | 1 + images/product/README.md | 6 +++--- index.mdx | 1 + platform.mdx | 1 + problems/index.mdx | 1 + security/overview.mdx | 1 + status.mdx | 1 + 75 files changed, 121 insertions(+), 26 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2c314bc..7be4f2c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,10 @@ repository root, not in `apps/docs`. Preview and validation commands are in - One hub page per product (`/index.mdx`). Do not add a second overview page beside it, and give every page a `title`, a `description`, and a closing **Related** or **Next** section. +- Keep `description` under 160 characters so it is not truncated in search + results and link previews. Where the product's sidebar label differs from + the page title (the sidebar says **Artifacts**, the page is **Library**), + add the label to `keywords` so either term finds the page. - Run `node scripts/check-docs-site.mjs` and `bash scripts/tests/check-docs-site.test.sh`, then `node scripts/tests/docs-ui.test.mjs` and diff --git a/README.md b/README.md index 06797e3..5a997d4 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,17 @@ Every page ends with a **Related** or **Next** section so a reader is never at a dead end, and every page carries a `title` and a `description` in its frontmatter. Product pages also carry an `image` for link previews. +Keep `description` under 160 characters — longer text is truncated in search +results and link previews. Titles are unique across the site; where two pages +would otherwise collide (`Sessions` in Code and in the CLI, `Canvases` in Chat +and in Design) the title carries the product and `sidebarTitle` keeps the +sidebar short. + +Where the product's sidebar label differs from the page title, list the label +in `keywords` so either term finds the page. The sidebar says **Artifacts**; +the page is [Library](/chat/library). It says **Planning**; the page is +[Plans](/chat/plans). It says **Agents**; the page is [Cortex Bot](/bot). + ## Checks ```bash diff --git a/api/errors.mdx b/api/errors.mdx index 12fd1d7..2c4211b 100644 --- a/api/errors.mdx +++ b/api/errors.mdx @@ -1,6 +1,7 @@ --- title: "Errors" description: "Every Cortex failure is an RFC 9457 problem document. Branch on code, read request_id, and know which fields are safe to render." +keywords: ["problem json", "error handling", "status codes"] --- Every failure Cortex returns is an `application/problem+json` document: diff --git a/api/overview.mdx b/api/overview.mdx index 7ea9148..0612162 100644 --- a/api/overview.mdx +++ b/api/overview.mdx @@ -1,6 +1,7 @@ --- title: "Public API" description: "What this site documents about the Cortex API — the RFC 9457 problem format and its type URIs. Sign in via the app." +keywords: ["reference", "error format", "RFC 9457"] --- Cortex is used through its apps, not through a public API you write against. This tab documents the one part of the wire contract that is public and stable: the shape of an error. diff --git a/bot/approvals.mdx b/bot/approvals.mdx index 68f69fb..f460c5e 100644 --- a/bot/approvals.mdx +++ b/bot/approvals.mdx @@ -1,6 +1,7 @@ --- title: "Approvals" description: "Allow / Deny / Always. A bot parks consequential steps — connected-app changes and dangerous computer actions — and nothing runs until you choose." +keywords: ["permissions", "Allow", "Deny", "Always", "parked step"] --- A bot can act, so it asks first. When it reaches a step that changes something in a connected app or could damage its computer, it **parks** the action and shows an approval widget in the conversation. The widget names the tool and what it is about to do. diff --git a/bot/computer.mdx b/bot/computer.mdx index 873b032..4903ab3 100644 --- a/bot/computer.mdx +++ b/bot/computer.mdx @@ -1,6 +1,7 @@ --- title: "Computer" -description: "The Computer rail is a live desktop on a Cloud guest — screen, browser, shell — not a slideshow of screenshots. Web is Cloud-only; This PC lives on the Bot desktop app." +description: "The Computer rail is a live desktop on a Cloud guest — screen, browser, shell — not a slideshow of screenshots. Web is Cloud-only." +keywords: ["desktop", "guest", "This PC", "SSH", "sleep"] --- Every bot can have a computer. In the web app it is an isolated Cloud guest with a desktop, and the **Computer rail** on the right of the open bot shows that desktop live while the bot works. diff --git a/bot/desktop.mdx b/bot/desktop.mdx index 92c4f8d..1c58d65 100644 --- a/bot/desktop.mdx +++ b/bot/desktop.mdx @@ -1,6 +1,7 @@ --- title: "Bot desktop" description: "The Cortex Bot desktop app — a home for your agents, routines, and channels, with This PC as a host for a bot's computer. It has no Chat or Code sidebar." +keywords: ["app", "download", "channels"] --- Cortex ships two desktop apps because the two jobs are different. The [Cortex desktop app](/getting-started/desktop) carries **Chat and Code**. The **Cortex Bot desktop app** is built around bots: its sidebar is **agents, routines, and channels**, and it adds **This PC** as a host for a bot's computer. It does not carry Chat or Code. diff --git a/bot/how-it-works.mdx b/bot/how-it-works.mdx index d74689e..1549be1 100644 --- a/bot/how-it-works.mdx +++ b/bot/how-it-works.mdx @@ -1,6 +1,7 @@ --- title: "How Bot works" -description: "What you see is a few short bubbles, not the inner loop. No round counter; a 15-minute turn deadline; six identical tool calls stop a turn. An active turn wakes a hibernated guest first." +description: "You see a few short bubbles, not the inner loop. No round counter; a 15-minute turn deadline; six identical tool calls stop a turn." +keywords: ["loop", "deadline", "guard", "hibernation"] --- A bot's turn is a tool loop on a computer. Most of it is invisible by design: the conversation shows only the messages the bot chose to send you, the questions it asked, the notifications it raised, and the credentials it requested. The screen on the Computer rail shows the rest. diff --git a/bot/index.mdx b/bot/index.mdx index a6cbee1..d098a5b 100644 --- a/bot/index.mdx +++ b/bot/index.mdx @@ -1,13 +1,18 @@ --- title: "Cortex Bot" -description: "A bot with its own computer. Browses, clicks, and works on a desktop you can watch; parks consequential steps behind approvals; runs routines on your local weekday hours." +description: "A bot with its own computer. Browses, clicks, and works on a desktop you can watch; parks consequential steps behind approvals; runs routines on schedule." image: "/images/banners/bot.png" +keywords: ["Agents", "computer use", "teammate", "automation"] --- Cortex Bot is an always-on teammate. Where Chat answers and Code changes a repository, a bot **does things on a desktop**: opens a browser, clicks through a flow, reads files, runs a shell command, and reports back in a few short messages. You can watch its screen while it works, and it asks before it does anything consequential. Bots belong to a signed-in account. Your bots are listed once, in the sidebar; the pane is the open bot — conversation in the middle, its computer on the right. + + Cortex Bot public page — a bot with its own computer + + Cortex Bot feature grid — A real desktop not a transcript, Your bots listed once, Plugins you chose, One product one sign-in diff --git a/bot/quickstart.mdx b/bot/quickstart.mdx index 210c83d..8494c0e 100644 --- a/bot/quickstart.mdx +++ b/bot/quickstart.mdx @@ -2,6 +2,7 @@ title: "Bot quickstart" description: "Sign in, create your first bot, open its computer, give it a task, and approve its first consequential step." image: "/images/banners/bot.png" +keywords: ["tutorial", "first bot", "getting started", "create bot"] --- This quickstart uses the web app, where a bot's computer runs in the Cloud. The [Bot desktop app](/bot/desktop) adds **This PC**. diff --git a/bot/routines.mdx b/bot/routines.mdx index a837d1b..34445a5 100644 --- a/bot/routines.mdx +++ b/bot/routines.mdx @@ -1,6 +1,7 @@ --- title: "Routines" -description: "Scheduled work on a bot you own. Routines fire in the owner's local weekday hours, pause and resume from the Computer rail, and follow your account across devices." +description: "Scheduled work on a bot you own. Routines fire in your local weekday hours, pause and resume from the Computer rail." +keywords: ["schedule", "recurring", "cron", "automation", "timers"] --- A routine is work a bot repeats on a schedule: every weekday morning, pull the report; on Fridays, check the release page; every hour during business hours, watch the queue. Routines belong to a bot you own and fire in **your** local weekday hours. diff --git a/bot/skills.mdx b/bot/skills.mdx index 8e591b1..827a8d0 100644 --- a/bot/skills.mdx +++ b/bot/skills.mdx @@ -1,6 +1,7 @@ --- title: "Skills" description: "Skills are instruction bundles a bot can follow. Enable first-party skills on a bot you own; the bot loads one when the task calls for it." +keywords: ["instructions", "bundles", "procedures"] --- A skill tells a bot **how** a particular job is done — the checklist for a weekly report, the steps for a support triage, the way your team wants a summary written. Instead of carrying every procedure in its context all the time, the bot loads a skill when the task calls for it. diff --git a/bot/tools.mdx b/bot/tools.mdx index f964664..980d933 100644 --- a/bot/tools.mdx +++ b/bot/tools.mdx @@ -1,6 +1,7 @@ --- title: "Bot tools" -description: "The parent tool catalog a bot receives — files and shell, computer use, browser, web, memory and skills, user-visible messages, nested tasks, agents, artifacts, and the plugins you enabled for Bot." +description: "The parent catalog a bot receives — files and shell, computer use, browser, web, memory and skills, messaging, nested tasks, agents, and your plugins." +keywords: ["capabilities", "catalog", "computer use", "browser"] --- A bot receives the **parent catalog**: everything below, plus the connected apps you enabled for Bot. It is not a subset of Chat's tools or Code's workspace catalog — it is its own set, built around having a computer. diff --git a/bot/troubleshooting.mdx b/bot/troubleshooting.mdx index 532065f..251013a 100644 --- a/bot/troubleshooting.mdx +++ b/bot/troubleshooting.mdx @@ -1,6 +1,7 @@ --- title: "Bot troubleshooting" description: "Connecting versus Stream Unavailable, parked steps that do not run, the setup form, missing tools, and turns that stop." +keywords: ["errors", "problems", "fixes", "not working"] --- Bot names its states plainly. This page maps them to causes and fixes. For the rest of the app see [Troubleshooting](/getting-started/troubleshooting). diff --git a/changelog.mdx b/changelog.mdx index a86ff2d..a47438a 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -2,6 +2,7 @@ title: "Changelog" description: "What shipped in Cortex Chat, Code, Bot, Design, and the CLI — dated, with a link to the surface it changed." image: "/images/banners/changelog.png" +keywords: ["release notes", "updates", "what is new"] --- Dated notes on what the products ship. Each entry names a change the app or the CLI already carries and links the surface it touched. Announcements from the Foundation are on [cortex.foundation/news](https://cortex.foundation/news). diff --git a/chat/canvases.mdx b/chat/canvases.mdx index ab885b4..fbf9fe0 100644 --- a/chat/canvases.mdx +++ b/chat/canvases.mdx @@ -1,6 +1,8 @@ --- -title: "Canvases" +title: "Chat canvases" +sidebarTitle: "Canvases" description: "A document that sits beside the conversation. Draft it yourself or ask Cortex to — and keep editing after the chat moves on." +keywords: ["document", "sidecar", "editor", "draft"] --- A Chat canvas is a document panel beside a conversation. Cortex can draft it, you can edit it, and it keeps your edits when you close it. It is the right place for a launch announcement, a spec, or anything you want to shape over several turns instead of scrolling back through replies. diff --git a/chat/deep-research.mdx b/chat/deep-research.mdx index 9890668..1dce74e 100644 --- a/chat/deep-research.mdx +++ b/chat/deep-research.mdx @@ -1,6 +1,7 @@ --- title: "Deep Research" description: "Plan the questions, read live sources, and write a cited report. A Chat feature — not a Code tool and not a Bot skill." +keywords: ["research", "citations", "sources", "report"] --- Deep Research turns a conversation into a research run. Instead of one answer, Cortex plans a set of questions, reads live sources for each in parallel, and writes a report with citations. You review the plan before it runs and read the report on the thread when it is done. diff --git a/chat/how-it-works.mdx b/chat/how-it-works.mdx index 3ddb541..b12a524 100644 --- a/chat/how-it-works.mdx +++ b/chat/how-it-works.mdx @@ -1,6 +1,7 @@ --- title: "How Chat works" -description: "A Chat turn is one tool-calling loop: the model asks for tools, Chat runs them, results flow back, and the loop ends when the model stops or hits Chat's round budget." +description: "A Chat turn is one tool-calling loop: the model asks for tools, Chat runs them, results flow back, and the loop ends when the model stops." +keywords: ["tool loop", "context", "compaction", "rounds"] --- A Chat turn is a real tool loop. The model emits tool calls, Chat executes them, the results re-enter the context, and the loop continues until the model stops or Chat reaches its round budget. What you see is the streamed result of that loop: tokens, then tools, then the finished reply. diff --git a/chat/images.mdx b/chat/images.mdx index d462266..7415e05 100644 --- a/chat/images.mdx +++ b/chat/images.mdx @@ -1,6 +1,7 @@ --- title: "Image generation" description: "Ask Chat for a picture and Cortex-Image-1 paints it. Generated images land in Library. On Free and Guest, images are a daily quota — not a paywall." +keywords: ["pictures", "Cortex-Image-1", "generation", "text to image"] --- Ask for a picture in plain language, or choose **+ → Image** on the composer. Chat loads its image skill, turns your ask into a complete scene description, and calls **Cortex-Image-1**. The finished picture is a Library file you can reuse in any chat or project. diff --git a/chat/index.mdx b/chat/index.mdx index 1262e86..e2eff6d 100644 --- a/chat/index.mdx +++ b/chat/index.mdx @@ -2,6 +2,7 @@ title: "Cortex Chat" description: "The conversation product at cortex.foundation — projects, Library, plans, models, tools, Deep Research, images, and how a turn runs." image: "/images/banners/chat.png" +keywords: ["assistant", "conversations", "chat app"] --- Cortex Chat is the assistant for research, writing, and everyday questions. It streams answers as they are written, keeps documents and threads together in projects, and hands off to Code or Bot when the job is a repository or a computer rather than a conversation. diff --git a/chat/library.mdx b/chat/library.mdx index 9543ad8..f8c15a6 100644 --- a/chat/library.mdx +++ b/chat/library.mdx @@ -1,6 +1,7 @@ --- title: "Library" description: "Everything you uploaded to a chat or a project, and everything the assistant produced. Files stay in Library when a chat is deleted." +keywords: ["Artifacts", "files", "uploads", "attachments", "file store"] --- Library is the file store on your account. Anything you attach in a chat, add to a project, or upload directly lands here, and so does everything the assistant generates — pictures from Cortex-Image-1 included. Open it from **Artifacts** in the sidebar. diff --git a/chat/memory.mdx b/chat/memory.mdx index 93bc855..253652f 100644 --- a/chat/memory.mdx +++ b/chat/memory.mdx @@ -1,6 +1,7 @@ --- title: "Memory" description: "Cortex remembers preferences and facts you share so answers get more personal over time. Review or delete individual memories under Settings → Data & privacy." +keywords: ["preferences", "personalisation", "saved facts", "forget"] --- Memory is a short list of standing preferences and facts about you that Chat keeps on your account and reads into every conversation. It is how Cortex remembers that you prefer British spelling, work in euros, or want answers to start with the conclusion — without you repeating it. diff --git a/chat/models.mdx b/chat/models.mdx index edc10c5..e41b4d5 100644 --- a/chat/models.mdx +++ b/chat/models.mdx @@ -1,6 +1,7 @@ --- title: "Models and thinking" description: "Pick a model per conversation from the composer chip, set how long Cortex thinks before answering, and know what happens at peak hours." +keywords: ["Cortex 1 Mini", "Cortex Teutonic-1", "thinking level", "model picker", "context window"] --- Every conversation runs on a Cortex model. The chip under the composer — *Cortex 1 Mini · Preview · Thinking Low* by default — is where you change it. The choice belongs to the conversation: existing threads keep the model they were created with, and new chats start from the default. diff --git a/chat/origin-check.mdx b/chat/origin-check.mdx index f69a34b..eb43819 100644 --- a/chat/origin-check.mdx +++ b/chat/origin-check.mdx @@ -1,6 +1,7 @@ --- title: "Origin check" description: "Attach media and ask whether it is authentic. Cortex waits for a verdict, and says so plainly when the check cannot run." +keywords: ["authenticity", "AI detection", "verify media"] --- **Check origin** answers one question about a picture or a video you attach: does it look authentic, or does it carry the marks of generation or manipulation? It is an explicit ask on attached media — not something that runs silently on every image in a thread. diff --git a/chat/plans.mdx b/chat/plans.mdx index 03234b4..8150301 100644 --- a/chat/plans.mdx +++ b/chat/plans.mdx @@ -1,6 +1,7 @@ --- title: "Plans" description: "A plan is a task list the model can build and keep up to date while you work. Start one from any chat, or from Planning." +keywords: ["Planning", "task list", "checklist", "steps"] --- A plan turns a long answer into steps you can tick off. Start one from a thread with **Start a plan**, or from the **Planning** page with **New plan**. Plans are kept up to date as the work moves, and a plan started from a chat outlives that chat if you delete it. diff --git a/chat/projects.mdx b/chat/projects.mdx index 68b469e..c09d016 100644 --- a/chat/projects.mdx +++ b/chat/projects.mdx @@ -1,6 +1,7 @@ --- title: "Projects" description: "A project keeps its own instructions, its chats, and its files together. Everything you say in a project's chats is read with those instructions in front of it." +keywords: ["instructions", "folders", "workspace"] --- A **project** is a folder with standing instructions. Create one for a piece of work you keep coming back to, give it instructions once, and every chat inside it starts from them. Projects belong to a signed-in account. diff --git a/chat/quickstart.mdx b/chat/quickstart.mdx index 3c5ef12..0809d10 100644 --- a/chat/quickstart.mdx +++ b/chat/quickstart.mdx @@ -2,6 +2,7 @@ title: "Chat quickstart" description: "From an open tab to a useful first conversation — send, attach, pick a model, and keep the thread in a project." image: "/images/banners/chat.png" +keywords: ["tutorial", "first chat", "getting started", "first message"] --- This walk-through takes about five minutes. Steps 1–3 work as a guest; step 4 needs a signed-in account. diff --git a/chat/sharing.mdx b/chat/sharing.mdx index e46dc33..4aa9587 100644 --- a/chat/sharing.mdx +++ b/chat/sharing.mdx @@ -1,6 +1,7 @@ --- title: "Sharing and teams" description: "Cortex Chat is per account. What that means for threads, projects, plans, and files — and where team features live." +keywords: ["teams", "invite", "collaboration", "privacy"] --- Cortex Chat is a **per-account** product. Your threads, projects, Library, memory, and plans belong to your signed-in account and follow you across devices. Another account cannot see them, and there is no “invite a teammate to this thread” control. diff --git a/chat/streaming.mdx b/chat/streaming.mdx index f875c65..14e5f4a 100644 --- a/chat/streaming.mdx +++ b/chat/streaming.mdx @@ -1,6 +1,7 @@ --- title: "Streaming" description: "Answers appear as they are written — tokens, then tools, then the finished reply. If the connection drops, the thread reconnects; you do not resend." +keywords: ["tokens", "live reply", "reconnect"] --- Chat streams. You see the answer being written rather than a block of text after the turn ends, and you can stop it at any point. diff --git a/chat/tools.mdx b/chat/tools.mdx index f3d5595..bd44762 100644 --- a/chat/tools.mdx +++ b/chat/tools.mdx @@ -1,6 +1,7 @@ --- title: "Tools and connectors" -description: "Built-in Chat tools, connected apps under Customize → Connectors, GitHub in Settings → Integrations, and MCP servers — and which product each connection reaches." +description: "Built-in Chat tools, connected apps under Customize → Connectors, GitHub in Settings → Integrations, and MCP servers." +keywords: ["Customize", "Connectors", "integrations", "plugins", "MCP", "connected apps"] --- A Chat turn can call tools. Some are built in — web search, Python snippets, image generation, memory, canvases. Others come from apps you connect, and from MCP servers you attach. This page explains where each lives and how to scope it. diff --git a/chat/troubleshooting.mdx b/chat/troubleshooting.mdx index a46228b..2262c4d 100644 --- a/chat/troubleshooting.mdx +++ b/chat/troubleshooting.mdx @@ -1,6 +1,7 @@ --- title: "Chat troubleshooting" description: "What Chat shows when a turn, a file, a model, or a connection does not behave — and what to do about it." +keywords: ["errors", "problems", "fixes", "not working"] --- Chat says what is wrong in plain words rather than spinning. This page maps those messages to causes and fixes. For the whole app, see [Troubleshooting](/getting-started/troubleshooting); for the terminal, [CLI troubleshooting](/cli/troubleshooting). diff --git a/chat/voice.mdx b/chat/voice.mdx index 63f22da..b774b63 100644 --- a/chat/voice.mdx +++ b/chat/voice.mdx @@ -1,6 +1,7 @@ --- title: "Voice" description: "Dictate with the mic and hear an answer with Listen. Both controls appear only when the deployment can transcribe or speak." +keywords: ["dictation", "speech", "listen", "microphone", "text to speech"] --- Chat has two voice controls: a **mic** in the composer for dictation, and **Listen** under each answer to hear it read aloud. Each appears only when the deployment behind it can actually do the work. diff --git a/cli/configuration.mdx b/cli/configuration.mdx index 803c065..de063a4 100644 --- a/cli/configuration.mdx +++ b/cli/configuration.mdx @@ -1,6 +1,7 @@ --- title: "Configuration" description: "config.toml keys, profiles, precedence, the environment variables the CLI reads, where data lives on disk, and how to select the Cloud, This PC, or SSH host." +keywords: ["config.toml", "settings", "environment variables", "profiles"] --- Cortex reads a **global** config file, an optional **project** config file, and any overrides you pass on the command line — in that order, later sources winning. Environment variables sit between the files and the flags. diff --git a/cli/extend.mdx b/cli/extend.mdx index e30a7a6..1596992 100644 --- a/cli/extend.mdx +++ b/cli/extend.mdx @@ -1,6 +1,7 @@ --- title: "Extend the CLI" description: "MCP servers, skills, agents and subagents, hooks, plugins, custom commands, and themes — every extension point in the Cortex CLI." +keywords: ["MCP", "plugins", "hooks", "skills", "agents", "themes"] --- The CLI ships with the tools a coding agent needs. You extend it in six ways: connect **MCP servers** for more tools, write **skills** for how a job is done, define **agents** for who is working, run **hooks** on lifecycle events, install **plugins**, and add **custom commands**. diff --git a/cli/goal.mdx b/cli/goal.mdx index f1d99fb..11e611a 100644 --- a/cli/goal.mdx +++ b/cli/goal.mdx @@ -1,6 +1,7 @@ --- title: "Goals" -description: "/goal keeps a long-horizon objective attached to the session. It survives the end of a turn, context compaction, and resume — the agent keeps planning, acting, and verifying until the work is evidence-complete." +description: "/goal attaches a long-horizon objective to the session. It survives compaction and resume while the agent keeps planning, acting, and verifying." +keywords: ["objective", "long running", "/goal"] --- A normal turn ends when the model stops. A **goal** does not: `/goal` attaches an objective to the session, and the agent keeps working toward it across turns — planning, acting, verifying — until the work is **evidence-complete**, until you pause or clear it, or until the budget runs out. The goal survives the end of a turn, context compaction, and closing and resuming the TUI. diff --git a/cli/headless.mdx b/cli/headless.mdx index f8e35cf..7ef9f59 100644 --- a/cli/headless.mdx +++ b/cli/headless.mdx @@ -1,6 +1,7 @@ --- title: "Headless and one-shot runs" description: "cortex exec for CI and scripts — autonomy levels, JSON output, turn and time limits — and cortex run for a single streamed request from your own shell." +keywords: ["CI", "automation", "exec", "scripts", "JSON output"] --- Two entry points work without a terminal, so they are safe in pipelines where the TUI refuses to start: diff --git a/cli/index.mdx b/cli/index.mdx index fb055e6..df7cba6 100644 --- a/cli/index.mdx +++ b/cli/index.mdx @@ -1,7 +1,8 @@ --- title: "Cortex CLI" -description: "Cortex Code in your terminal — an interactive session view, one-shot runs, headless mode for CI, and the extension points: MCP servers, skills, agents, hooks, plugins." +description: "Cortex Code in your terminal — an interactive session view, one-shot runs, headless mode for CI, and the extension points." image: "/images/banners/code.png" +keywords: ["terminal", "command line", "cortex command"] --- The Cortex CLI is **Cortex Code for the terminal**. One binary, `cortex`, gives you an interactive TUI with a live timeline and approvals, one-shot runs from your own shell, and a headless mode for scripts and CI. It uses the same account and the same Code sessions as the web and desktop apps, runs on the Cloud runtime by default, and never runs the model on your laptop. diff --git a/cli/install.mdx b/cli/install.mdx index 27d13ae..2754c66 100644 --- a/cli/install.mdx +++ b/cli/install.mdx @@ -1,6 +1,7 @@ --- title: "Install the CLI" description: "Install Cortex CLI on macOS, Linux, or Windows. The installer verifies a SHA-256 checksum before it writes anything; cortex upgrade keeps it current." +keywords: ["setup", "download", "upgrade", "update", "checksum"] --- The installer downloads the release build for your platform from [software.cortex.foundation](https://software.cortex.foundation), verifies its SHA-256 checksum against the release manifest, and installs the binary. Nothing is written until the checksum matches. diff --git a/cli/keyboard.mdx b/cli/keyboard.mdx index fcbd426..d3fe21f 100644 --- a/cli/keyboard.mdx +++ b/cli/keyboard.mdx @@ -1,6 +1,7 @@ --- title: "Keyboard shortcuts" description: "Every key binding in the Cortex CLI, grouped by where it applies. Press Ctrl+X in the TUI for the in-app overlay, or ? for help." +keywords: ["shortcuts", "keybindings", "hotkeys"] --- Press **Ctrl+X** at any time for the shortcuts overlay, or **?** / **F1** for help. The footer always shows one hint for the current context. diff --git a/cli/modes-and-permissions.mdx b/cli/modes-and-permissions.mdx index a7f6553..e7a6f56 100644 --- a/cli/modes-and-permissions.mdx +++ b/cli/modes-and-permissions.mdx @@ -1,6 +1,7 @@ --- title: "Modes and permissions" -description: "Agent / Plan / Ask, the Spec lock, approval prompts, the sandbox, the permission table, and the model picker's effort radios — every control over what the CLI may do." +description: "Agent / Plan / Ask, the Spec lock, approval prompts, the sandbox, the permission table, and the model picker's effort radios." +keywords: ["Agent", "Plan", "Ask", "sandbox", "approvals", "autonomy"] --- Every CLI session runs under three controls: a **mode** that decides which tools the model is even offered, an **approval policy** that decides when you are asked, and a **sandbox** that decides what a tool may touch when it runs. Effort, on the model picker, decides how hard the model thinks. diff --git a/cli/quickstart.mdx b/cli/quickstart.mdx index b44439b..407c345 100644 --- a/cli/quickstart.mdx +++ b/cli/quickstart.mdx @@ -2,6 +2,7 @@ title: "CLI quickstart" description: "From nothing installed to a working Cortex Code session in the terminal — install, sign in, first turn, AGENTS.md, and how much autonomy to grant." image: "/images/banners/code.png" +keywords: ["tutorial", "first run", "getting started"] --- Ten minutes, end to end. The TUI and `cortex exec` run on the **Cloud** Code runtime by default, so a fresh install completes a turn without any extra configuration. diff --git a/cli/sessions.mdx b/cli/sessions.mdx index 30bc42e..78223ff 100644 --- a/cli/sessions.mdx +++ b/cli/sessions.mdx @@ -1,6 +1,8 @@ --- -title: "Sessions" +title: "CLI sessions" +sidebarTitle: "Sessions" description: "Every interactive run is a session written to disk as you go — list, resume, rewind, fork, export, import, share, lock, and clean up." +keywords: ["resume", "fork", "rewind", "export", "share", "history"] --- Every interactive run is a session: the transcript, the tool calls, and the context Cortex built up along the way. Sessions are written to disk as you go, so you can close the terminal and pick the work back up later. The CLI also remembers the server-side Code session id for each workspace, so terminal turns continue the same coding session you see in the web app. diff --git a/cli/sign-in.mdx b/cli/sign-in.mdx index 6d8ad44..965b00e 100644 --- a/cli/sign-in.mdx +++ b/cli/sign-in.mdx @@ -1,6 +1,7 @@ --- title: "Sign in" -description: "cortex login opens the browser flow; a device code covers machines without one; API keys and tokens cover CI. Credentials live in your OS keyring, never in a file in the repository." +description: "cortex login opens the browser flow; a device code covers machines without one; API keys cover CI. Credentials live in your OS keyring." +keywords: ["login", "authentication", "API key", "device code", "token"] --- The CLI uses the same Cortex account as the web app. Sign in once per machine; the session is stored in your operating system's keyring. diff --git a/cli/slash-commands.mdx b/cli/slash-commands.mdx index b956052..2504a7b 100644 --- a/cli/slash-commands.mdx +++ b/cli/slash-commands.mdx @@ -1,6 +1,7 @@ --- title: "Slash commands" description: "Everything you can type after / in the Cortex CLI composer — model, mode, permissions, plan, goal, sessions, files, MCP, diagnostics — and how to add your own." +keywords: ["commands", "/help", "/model", "/goal", "palette"] --- Type `/` in the composer to open the command list. It shows the most-used commands first and filters as you type; the trailer *N more — keep typing to filter* means there is more below. A line beginning with `/` that is not a known command is sent to the agent as an ordinary message. diff --git a/cli/troubleshooting.mdx b/cli/troubleshooting.mdx index 9ded827..d068b7c 100644 --- a/cli/troubleshooting.mdx +++ b/cli/troubleshooting.mdx @@ -1,6 +1,7 @@ --- title: "CLI troubleshooting" -description: "Start with four commands, then work through the product-facing messages — the coding service, hosts that refuse, the agent that will not edit, MCP, quotas, timeouts, disk." +description: "Start with four commands, then work through the messages — the coding service, hosts that refuse, the agent that will not edit, MCP, quotas, timeouts." +keywords: ["errors", "problems", "fixes", "not working"] --- ## Start here diff --git a/cli/tui.mdx b/cli/tui.mdx index 2073eec..3b62ac4 100644 --- a/cli/tui.mdx +++ b/cli/tui.mdx @@ -1,6 +1,7 @@ --- title: "The TUI" -description: "What is on screen when you run cortex — the timeline and its rows, the composer, the status line, the footer, queueing follow-ups, interrupting, and the panels behind Ctrl+K." +description: "What is on screen when you run cortex — the timeline and its rows, the composer, the status line, queueing follow-ups, and the panels behind Ctrl+K." +keywords: ["interface", "screen", "layout", "panels"] --- `cortex` with a terminal attached starts the interactive UI. It takes the full viewport by default (the alternate screen) and gives you three things: a **timeline** of the conversation and the agent's work, a **composer** at the bottom, and a **footer** with the model, the mode, and the most useful shortcut. diff --git a/code/cli.mdx b/code/cli.mdx index eec5c96..daf9636 100644 --- a/code/cli.mdx +++ b/code/cli.mdx @@ -1,6 +1,7 @@ --- title: "Code in the terminal" -description: "The Cortex CLI drives the same Code sessions from a terminal — interactive, one-shot, or headless in CI. Same account, Cloud by default, This PC and SSH by opt-in." +description: "The Cortex CLI drives the same Code sessions from a terminal — interactive, one-shot, or headless in CI. Same account, Cloud by default." +keywords: ["terminal", "command line"] --- The Cortex CLI is Cortex Code for the terminal. One binary, `cortex`, gives you an interactive session view, one-shot runs from your shell, and a headless mode for scripts and CI. It talks to the same Code sessions as the web and desktop apps and signs in with the same account. The full documentation lives in the [CLI tab](/cli); this page is the map from Code concepts to CLI commands. diff --git a/code/cloud.mdx b/code/cloud.mdx index d806be6..64853bb 100644 --- a/code/cloud.mdx +++ b/code/cloud.mdx @@ -1,6 +1,7 @@ --- title: "Cloud runtimes" description: "Every web Code session runs in its own isolated Cloud guest, prepared by the agent for that repository. Not the Chat Python sandbox, not the API process." +keywords: ["runtime", "guest", "sandbox", "environment"] --- Web Code runs your repository in an **isolated Cloud guest** — a headless virtual machine on the same farm as Bot computers. The guest is started for the session, attached, and prepared by the agent. Workspace tools execute inside it and nowhere else. diff --git a/code/desktop.mdx b/code/desktop.mdx index fa4b905..9a0fa87 100644 --- a/code/desktop.mdx +++ b/code/desktop.mdx @@ -1,6 +1,7 @@ --- title: "Code on desktop" description: "This PC and SSH hosts for Code sessions exist on the desktop app and the CLI. The browser stays Cloud-only — that is where the code runs, not a missing feature." +keywords: ["This PC", "SSH", "local", "app"] --- In the browser, a Code session runs in the Cloud. Install the [Cortex desktop app](/getting-started/desktop) or the [CLI](/cli) and two more hosts appear: **This PC**, which runs the workspace tools on your own machine, and **SSH**, which runs them over the shell of a host you register. diff --git a/code/github.mdx b/code/github.mdx index 057d093..bbbbfcb 100644 --- a/code/github.mdx +++ b/code/github.mdx @@ -1,6 +1,7 @@ --- title: "Connect GitHub" -description: "Cortex Code reaches repositories through Connect GitHub — on Code Home, in the repository picker, and under Settings → Integrations. No personal-access-token field." +description: "Cortex Code reaches repositories through Connect GitHub — on Code Home, in the repository picker, and under Settings → Integrations." +keywords: ["repository", "repos", "connection", "Reconnect"] --- Code needs a repository to work on. You give it one by connecting GitHub from the product; Cortex then lists the repositories the grant covers and starts sessions on them. The same connection adds GitHub tools to Chat. diff --git a/code/how-it-works.mdx b/code/how-it-works.mdx index 2f707bc..8c97c0c 100644 --- a/code/how-it-works.mdx +++ b/code/how-it-works.mdx @@ -1,6 +1,7 @@ --- title: "How Code works" -description: "Workspace tools execute inside the attached Cloud guest — never in the API process, never in Chat's Python sandbox. Modes withhold mutating tools; compaction fails closed." +description: "Workspace tools execute inside the attached guest — never in the API process or Chat sandbox. Modes withhold mutating tools; compaction fails closed." +keywords: ["tool loop", "context", "compaction"] --- A Code turn is a tool loop like Chat's, with two differences that define the product: the tools operate on a real repository inside an isolated guest, and the mode you choose decides which tools the model is offered at all. diff --git a/code/index.mdx b/code/index.mdx index 938ced1..9f5a4b5 100644 --- a/code/index.mdx +++ b/code/index.mdx @@ -1,7 +1,8 @@ --- title: "Cortex Code" -description: "A coding agent that works inside your repository on an isolated runtime. Sessions, Ask / Plan / Agent, GitHub, Cloud guests, desktop hosts, and pull-request review." +description: "A coding agent that works inside your repository on an isolated runtime — sessions, Ask / Plan / Agent, GitHub, Cloud guests, and review." image: "/images/banners/code.png" +keywords: ["coding agent", "repository", "developer"] --- Cortex Code is a coding agent, not a chat with a different icon. You point it at a repository, describe the change, and it works through the task inside an isolated runtime — reading, editing, running your build and tests — while you watch a timeline of what it did and decide how much it may do on its own. diff --git a/code/interactions.mdx b/code/interactions.mdx index f1ed3b2..62aacd2 100644 --- a/code/interactions.mdx +++ b/code/interactions.mdx @@ -1,6 +1,7 @@ --- title: "Ask, Plan, Agent" -description: "Choose how much a Code turn may change. Ask reads and explains, Plan proposes, Agent edits and runs. The choice rides on the turn; the session row holds the default." +description: "Choose how much a Code turn may change. Ask reads and explains, Plan proposes, Agent edits and runs. The choice rides on the turn." +keywords: ["Ask", "Plan", "Agent", "modes", "permissions"] --- Every Code turn runs in one of three modes. Pick it on the turn, or leave the session's default. A follow-up can pick a different mode — tighten to Ask to understand, widen to Agent to ship. diff --git a/code/quickstart.mdx b/code/quickstart.mdx index 4db82cc..e109efb 100644 --- a/code/quickstart.mdx +++ b/code/quickstart.mdx @@ -2,6 +2,7 @@ title: "Code quickstart" description: "Sign in, connect GitHub, start a Cloud session on a repository, and read the timeline — in the web app." image: "/images/banners/code.png" +keywords: ["tutorial", "first session", "getting started", "repository"] --- This quickstart uses the web app, where Code runs in the Cloud. For This PC or SSH, use the [desktop app](/code/desktop) or the [CLI](/cli/quickstart). diff --git a/code/security.mdx b/code/security.mdx index 9f62465..4fe509a 100644 --- a/code/security.mdx +++ b/code/security.mdx @@ -1,6 +1,7 @@ --- title: "Cortex Security" -description: "GitHub pull-request review without pasting a diff. The change stays in the repository; Cortex reads it there and posts a check run that stays failed when it fails." +description: "GitHub pull-request review without pasting a diff. Cortex reads the change in place and posts a check run that stays failed when it fails." +keywords: ["pull request review", "check run", "GitHub App"] --- Cortex Security reviews GitHub pull requests in place. Install its GitHub App from the product, and each pull request gets one review pass posted as a check run named **Cortex Security**. A review that could not run is stored as failed — never rewritten as an approval so a dashboard can look green. diff --git a/code/sessions.mdx b/code/sessions.mdx index b23039a..655bb0b 100644 --- a/code/sessions.mdx +++ b/code/sessions.mdx @@ -1,6 +1,8 @@ --- -title: "Sessions" +title: "Code sessions" +sidebarTitle: "Sessions" description: "A Code session is a timeline of the task you asked for and the work that followed — files read, edits, commands, plans — not a chat transcript." +keywords: ["timeline", "task", "transcript", "run"] --- A session is where Code does its work on one repository. It records every turn as a timeline: what you asked, which files the agent read, what it edited, which commands it ran, and the plan it wrote when you asked for one. You can stop a turn at any point and follow up with another. diff --git a/code/troubleshooting.mdx b/code/troubleshooting.mdx index 5d30ce7..d23917d 100644 --- a/code/troubleshooting.mdx +++ b/code/troubleshooting.mdx @@ -1,6 +1,7 @@ --- title: "Code troubleshooting" description: "Gates, Reconnect, Cloud availability, modes that will not edit, and what a failed Cortex Security review means." +keywords: ["errors", "problems", "fixes", "not working"] --- Code tells you what is wrong in plain words. This page maps those words to causes and fixes. For the terminal, see [CLI troubleshooting](/cli/troubleshooting); for the rest of the app, [Troubleshooting](/getting-started/troubleshooting). diff --git a/design/canvases.mdx b/design/canvases.mdx index 558f0b7..f7bd504 100644 --- a/design/canvases.mdx +++ b/design/canvases.mdx @@ -1,6 +1,8 @@ --- -title: "Canvases" +title: "Design canvases" +sidebarTitle: "Canvases" description: "Boards you host from Chat or Code — frames, notes, options, and the host binding. Default title Untitled canvas; deleting hides it from the list." +keywords: ["board", "frames", "notes", "options"] --- A Design canvas starts life as **Untitled canvas**, created the moment Chat or Code writes to it. It belongs to your account, remembers which conversation or session hosted it, and is edited in the [Design desktop app](/design/desktop). diff --git a/design/desktop.mdx b/design/desktop.mdx index 5993e85..ddc57f8 100644 --- a/design/desktop.mdx +++ b/design/desktop.mdx @@ -1,6 +1,7 @@ --- title: "Design desktop" description: "The working UI for Cortex Design is a separate desktop app. Chat and Code create canvases; you edit them here." +keywords: ["app", "download", "editor"] --- The Design working UI is a desktop app of its own. The web app creates and lists canvases when Chat or Code hosts one; editing frames, notes, options, and the Library drawer happens here. diff --git a/design/index.mdx b/design/index.mdx index cdcd379..3ea9315 100644 --- a/design/index.mdx +++ b/design/index.mdx @@ -2,6 +2,7 @@ title: "Cortex Design" description: "Persistable canvases hosted from Chat or Code and edited in the Design desktop app. Frames, notes, options, and a Library of components, pages, and tokens." image: "/images/banners/design.png" +keywords: ["canvas", "board", "UI design", "mockups"] --- Cortex Design is where visual work lives. A canvas is a board of frames, notes, and options that you host from a Chat conversation or a Code session and then open in the **Design desktop app** to work on. There is no Design mode in the web shell: the browser creates and lists canvases, the desktop app is where you edit them. diff --git a/design/library.mdx b/design/library.mdx index 17e2d9a..95fe4f5 100644 --- a/design/library.mdx +++ b/design/library.mdx @@ -1,6 +1,7 @@ --- title: "Design library" description: "Components, pages, and tokens you reuse across canvases. Thumbnails are uploaded, not rendered on this site. Not the same object as Chat's file Library." +keywords: ["components", "tokens", "assets", "design system"] --- The Design library holds the reusable pieces of your visual work: **components** you place on frames, **pages** you build from them, and **tokens** — colours, spacing, type — that keep everything consistent. It lives in the Library drawer of a canvas in the Design desktop app and belongs to your signed-in account. diff --git a/getting-started/accounts.mdx b/getting-started/accounts.mdx index 8220bcf..76b8b01 100644 --- a/getting-started/accounts.mdx +++ b/getting-started/accounts.mdx @@ -1,6 +1,7 @@ --- title: "Accounts and sign-in" description: "Chat works as a guest. Sign in once for projects, files, Code, Bot, and the CLI — the same account everywhere." +keywords: ["guest", "sign in", "membership", "plan"] --- You sign in from the product: choose **Sign in to save your work** in the sidebar footer, or the **Sign in** button on any public page at [cortex.foundation](https://cortex.foundation). This site describes what the account unlocks; it does not document the sign-in protocol. diff --git a/getting-started/desktop.mdx b/getting-started/desktop.mdx index 50af291..3e9d20e 100644 --- a/getting-started/desktop.mdx +++ b/getting-started/desktop.mdx @@ -1,6 +1,7 @@ --- title: "Desktop app (Chat + Code)" description: "The Cortex desktop app carries Chat and Code, and adds This PC and SSH hosts for Code sessions. The browser stays Cloud-only." +keywords: ["app", "This PC", "SSH", "local"] --- The Cortex desktop app is the same Chat and Code you know from the browser, with one addition that only a native app can offer: Code sessions can run on **This PC** or over **SSH** as well as in the Cloud. Download it from [releases.cortex.foundation](https://releases.cortex.foundation) and sign in when the app asks — same account as the web. diff --git a/getting-started/downloads.mdx b/getting-started/downloads.mdx index 0afd773..102da94 100644 --- a/getting-started/downloads.mdx +++ b/getting-started/downloads.mdx @@ -1,6 +1,7 @@ --- title: "Downloads" description: "Every way to run Cortex — the web app, the Cortex desktop app for Chat and Code, the Bot desktop app, the Design desktop app, and the CLI." +keywords: ["install", "apps", "desktop", "releases"] --- Cortex runs in the browser without installing anything. Download an app when you need a host the browser cannot offer: **This PC** and **SSH** for Code, a desktop home for your bots, or the Design working UI. diff --git a/getting-started/plans.mdx b/getting-started/plans.mdx index c3f3303..e6ae2ed 100644 --- a/getting-started/plans.mdx +++ b/getting-started/plans.mdx @@ -1,6 +1,7 @@ --- title: "Plans and quotas" description: "Guest, Free, Go, Plus, and Pro as listed in Settings → Plan & billing. Quotas fail closed and tell you when they reset." +keywords: ["pricing", "quotas", "limits", "billing", "Free", "Go", "Plus", "Pro"] --- Your plan, today's usage, and the reset timers live under **Settings → Plan & billing**. The product is the source of truth for numbers; this page explains what each plan is for and how limits behave. diff --git a/getting-started/quickstart.mdx b/getting-started/quickstart.mdx index 347c2e1..7ee2863 100644 --- a/getting-started/quickstart.mdx +++ b/getting-started/quickstart.mdx @@ -2,6 +2,7 @@ title: "Quickstart" description: "Open Cortex in a browser, get a first answer from Chat, then sign in for Code, Bot, and Design." image: "/images/banners/getting-started.png" +keywords: ["tutorial", "first steps", "getting started", "onboarding"] --- Cortex is one account across four products: **Chat**, **Code**, **Bot**, and **Design**, plus a **CLI** for the terminal. This page takes you from a browser tab to a useful first session in about five minutes. diff --git a/getting-started/settings.mdx b/getting-started/settings.mdx index bbb2e71..00ec2e8 100644 --- a/getting-started/settings.mdx +++ b/getting-started/settings.mdx @@ -1,6 +1,7 @@ --- title: "Settings" description: "Theme, language, answer font, keyboard shortcuts, default models, integrations, privacy, and billing — everything under Settings in the web app." +keywords: ["preferences", "theme", "language", "shortcuts", "privacy"] --- Open **Settings** from the sidebar footer, or go to `cortex.foundation/settings`. It is a panel with two sections: **Preferences** (General, Models, Integrations, Code) and **Account** (Data & privacy, Plan & billing). Guests can browse it; account controls unlock after you sign in. diff --git a/getting-started/troubleshooting.mdx b/getting-started/troubleshooting.mdx index 903d164..e8c3789 100644 --- a/getting-started/troubleshooting.mdx +++ b/getting-started/troubleshooting.mdx @@ -1,6 +1,7 @@ --- title: "Troubleshooting" description: "What the web app shows when something is off, what it means, and what to do — gates, quotas, reconnects, and product-facing errors." +keywords: ["errors", "problems", "fixes", "not working", "support"] --- Cortex prefers a clear message over a spinner. When something cannot run, the product says so in plain words and, where it can, tells you what to do next. This page collects those messages across Chat, Code, and Bot. The CLI has [its own page](/cli/troubleshooting). diff --git a/getting-started/what-is-cortex.mdx b/getting-started/what-is-cortex.mdx index 08b36fc..9eb80cf 100644 --- a/getting-started/what-is-cortex.mdx +++ b/getting-started/what-is-cortex.mdx @@ -2,6 +2,7 @@ title: "What is Cortex" description: "Four products and a CLI behind one account on cortex.foundation — Chat, Code, Bot, Design, and the Cortex CLI." image: "/images/banners/getting-started.png" +keywords: ["overview", "products", "comparison"] --- Cortex is built by the Cortex Foundation, a research-first lab. The products share one account, one domain, and one set of models. Use **Chat** for conversation and research, **Code** for a repository, **Bot** for an always-on teammate with its own computer, **Design** for persistable canvases, and the **CLI** to drive Code from a terminal. diff --git a/images/product/README.md b/images/product/README.md index 65bc079..206cfcf 100644 --- a/images/product/README.md +++ b/images/product/README.md @@ -15,7 +15,7 @@ Cream / ink / `#1F4945` stay on site chrome. The pixels are the app. | `chat-home-recents.png` | Chat home with Recents populated | `index` | | `chat-home-dark.png` | Chat home, Dark theme | `chat/index` | | `chat-home-fr.png` | Chat home, French interface | `getting-started/what-is-cortex` | -| `chat-session.png` | A thread with a one-paragraph reply | `chat/overview` | +| `chat-session.png` | A thread with a one-paragraph reply | `chat/index` | | `chat-composer-typed.png` | A multi-line prompt typed in the composer | `chat/quickstart` | | `chat-composer-menu.png` | The composer `+` menu: Attach file, Image, Check origin | `chat/images` | | `chat-model-picker.png` | Model chip open: models, Thinking, Deep Research | `chat/models` | @@ -54,10 +54,10 @@ Cream / ink / `#1F4945` stay on site chrome. The pixels are the app. | File | Surface | Used on | | --- | --- | --- | | `code-public.png` | `cortex.foundation/code` public page | `code/index` | -| `code-public-features.png` | Same page, feature grid | `code/overview` | +| `code-public-features.png` | Same page, feature grid | `code/index` | | `code-home.png` | Code guest gate (“This space is reserved”) | `code/quickstart` | | `bot-public.png` | `cortex.foundation/bot` public page | `bot/index` | -| `bot-public-features.png` | Same page, feature grid | `bot/overview` | +| `bot-public-features.png` | Same page, feature grid | `bot/index` | | `bot-home.png` | Bot guest gate | `bot/quickstart` | | `security-public.png` | `cortex.foundation/security` public page | `code/security` | | `security-public-features.png` | Same page, feature grid | `code/security` | diff --git a/index.mdx b/index.mdx index 31413a8..0bfdedb 100644 --- a/index.mdx +++ b/index.mdx @@ -2,6 +2,7 @@ title: "Guides for Chat, Code, Bot, Design, and the CLI" description: "Product docs for the Cortex app — how to chat, ship code, run bots, design canvases, and drive Code from a terminal. Platform API lives elsewhere." image: "/images/banners/getting-started.png" +keywords: ["Cortex docs", "documentation", "help", "guides"] --- docs.cortex.foundation diff --git a/platform.mdx b/platform.mdx index 5935b78..6c8eadf 100644 --- a/platform.mdx +++ b/platform.mdx @@ -1,6 +1,7 @@ --- title: "Platform API" description: "Coming soon. There is no public inference API to document yet — no endpoints, SDKs, or API keys." +keywords: ["inference API", "completions", "roadmap", "coming soon"] --- Cortex does not have a public inference API today. There is no completions diff --git a/problems/index.mdx b/problems/index.mdx index 6e0f794..6136bc7 100644 --- a/problems/index.mdx +++ b/problems/index.mdx @@ -1,6 +1,7 @@ --- title: "Problem catalog" description: "Every Cortex error code, its HTTP status, whether retrying helps, and the page that explains it." +keywords: ["error codes", "troubleshooting", "status codes"] --- Every Cortex failure carries a `code` and a `type`. The `type` is a URL that resolves to a page on this site, so an error message can point at its own explanation. diff --git a/security/overview.mdx b/security/overview.mdx index f14d824..7339731 100644 --- a/security/overview.mdx +++ b/security/overview.mdx @@ -2,6 +2,7 @@ title: "Security and privacy" description: "What Cortex stores, what it never stores, which cookies run, where your code executes, and how to export or delete your data." image: "/images/banners/security.png" +keywords: ["privacy", "data", "cookies", "storage", "export", "delete"] --- This page is about **how Cortex handles your data and where code runs**. It is not a status page, and it is not the [Cortex Security](/code/security) product that reviews pull requests. diff --git a/status.mdx b/status.mdx index a81a568..b38cc13 100644 --- a/status.mdx +++ b/status.mdx @@ -1,6 +1,7 @@ --- title: "System status" description: "Live availability for Cortex Web, API, Chat, Code, and Bot — incidents, maintenance, and history." +keywords: ["uptime", "outage", "incident", "maintenance", "availability"] ---