From 75c90d955cc120008201040fd7c38090be53ec3a Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sun, 15 Mar 2026 03:30:35 +0200 Subject: [PATCH] docs: refresh docs and add tunnel usage guide (#666) Add a new tunnels page with verified, up-to-date CLI examples Remove deprecated `--daemon` usage and clarify QR/password behavior Add docs validation tooling and docs-source workflow for packaging and sync --- .github/workflows/docs-source.yml | 86 ++++++++++++++++++ README.md | 2 + package.json | 1 + packages/docs/CONTRIBUTING.md | 57 ++++++++++++ packages/docs/DEPLOYMENT.md | 42 +++++++++ packages/docs/README.md | 31 +++++++ packages/docs/content/docs/index.mdx | 25 ++++++ packages/docs/content/docs/install.mdx | 33 +++++++ packages/docs/content/docs/quickstart.mdx | 22 +++++ packages/docs/content/docs/themes.mdx | 30 +++++++ .../docs/content/docs/troubleshooting.mdx | 30 +++++++ packages/docs/content/docs/tunnels.mdx | 77 ++++++++++++++++ packages/docs/sidebar.config.json | 25 ++++++ scripts/docs/validate-docs.mjs | 88 +++++++++++++++++++ 14 files changed, 549 insertions(+) create mode 100644 .github/workflows/docs-source.yml create mode 100644 packages/docs/CONTRIBUTING.md create mode 100644 packages/docs/DEPLOYMENT.md create mode 100644 packages/docs/README.md create mode 100644 packages/docs/content/docs/index.mdx create mode 100644 packages/docs/content/docs/install.mdx create mode 100644 packages/docs/content/docs/quickstart.mdx create mode 100644 packages/docs/content/docs/themes.mdx create mode 100644 packages/docs/content/docs/troubleshooting.mdx create mode 100644 packages/docs/content/docs/tunnels.mdx create mode 100644 packages/docs/sidebar.config.json create mode 100644 scripts/docs/validate-docs.mjs diff --git a/.github/workflows/docs-source.yml b/.github/workflows/docs-source.yml new file mode 100644 index 00000000..05feca6e --- /dev/null +++ b/.github/workflows/docs-source.yml @@ -0,0 +1,86 @@ +name: Docs Source + +on: + push: + branches: [main] + paths: + - "packages/docs/**" + - "scripts/docs/**" + - "package.json" + release: + types: [published] + workflow_dispatch: + inputs: + release_tag: + description: "Optional existing tag to upload docs source archive" + required: false + type: string + +permissions: + contents: write + +jobs: + validate-and-package: + runs-on: ubuntu-latest + outputs: + archive_name: ${{ steps.archive.outputs.archive_name }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup bun + uses: oven-sh/setup-bun@v2 + + - name: Validate docs source + run: bun run docs:validate + + - name: Build docs source archive + id: archive + run: | + mkdir -p artifacts + ARCHIVE_NAME="openchamber-docs-source-${GITHUB_SHA::8}.tar.gz" + tar -czf "artifacts/${ARCHIVE_NAME}" -C packages/docs . + echo "archive_name=${ARCHIVE_NAME}" >> "$GITHUB_OUTPUT" + + - name: Upload workflow artifact + uses: actions/upload-artifact@v4 + with: + name: docs-source + path: artifacts/${{ steps.archive.outputs.archive_name }} + retention-days: 14 + + - name: Upload archive to release tag + if: ${{ github.event_name == 'release' || github.event.inputs.release_tag != '' }} + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.release_tag }} + files: artifacts/${{ steps.archive.outputs.archive_name }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Trigger openchamber-website docs sync (optional) + if: ${{ github.event_name == 'release' || github.event_name == 'workflow_dispatch' }} + env: + WEBSITE_REPO: openchamber/openchamber-website + WEBSITE_TOKEN: ${{ secrets.OPENCHAMBER_WEBSITE_REPO_TOKEN }} + SOURCE_REF: ${{ github.event_name == 'release' && github.event.release.tag_name || github.ref_name }} + run: | + if [ -z "$WEBSITE_TOKEN" ]; then + echo "OPENCHAMBER_WEBSITE_REPO_TOKEN not set; skip dispatch." + exit 0 + fi + + curl -sS -X POST \ + -H "Authorization: Bearer $WEBSITE_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + https://api.github.com/repos/$WEBSITE_REPO/dispatches \ + -d @- < `/foo/` + - `folder/index.mdx` -> `/folder/` + - `folder/bar.mdx` -> `/folder/bar/` +4. Run validation: + + ```bash + bun run docs:validate + ``` + +## Add a new sidebar section + +Edit `packages/docs/sidebar.config.json`. + +Example: + +```json +{ + "label": "Advanced", + "items": [{ "label": "Remote Access", "link": "/remote-access/" }] +} +``` + +Rules: + +- use trailing slash in links (`/page/`) +- every sidebar link must map to an existing MDX file +- keep section labels short and task-oriented + +## Sync into openchamber-website + +`openchamber-website` renders/deploys docs via Starlight in `apps/docs`. + +After docs content updates here: + +1. copy `packages/docs/content/docs/*` -> `openchamber-website/apps/docs/src/content/docs/*` +2. map `packages/docs/sidebar.config.json` into `openchamber-website/apps/docs/astro.config.mjs` sidebar +3. run docs checks/build in website repo + +Automation support exists in `.github/workflows/docs-source.yml` (release/manual packaging of docs source artifact). diff --git a/packages/docs/DEPLOYMENT.md b/packages/docs/DEPLOYMENT.md new file mode 100644 index 00000000..ec5ed7b8 --- /dev/null +++ b/packages/docs/DEPLOYMENT.md @@ -0,0 +1,42 @@ +# Docs Source Deployment + +This repo publishes docs **source artifacts**. + +Rendering and hosting still happen in `openchamber-website` (`apps/docs`). + +## Workflow + +Use `.github/workflows/docs-source.yml`. + +Triggers: + +- push to `main` when docs source changes +- release published +- manual `workflow_dispatch` + +Outputs: + +- validates docs (`bun run docs:validate`) +- creates `openchamber-docs-source-.tar.gz` +- uploads archive as workflow artifact +- on release/manual with tag, uploads archive to release assets + +## Optional cross-repo sync trigger + +The workflow can trigger a `repository_dispatch` event in `openchamber-website`. + +Set secret in this repo: + +- `OPENCHAMBER_WEBSITE_REPO_TOKEN` (token with access to `openchamber/openchamber-website`) + +Event sent: + +- `event_type: docs_source_updated` + +Payload includes: + +- `source_repo` +- `source_ref` +- `archive_name` + +`openchamber-website` can listen for this event and pull docs source from release artifacts. diff --git a/packages/docs/README.md b/packages/docs/README.md new file mode 100644 index 00000000..42da9595 --- /dev/null +++ b/packages/docs/README.md @@ -0,0 +1,31 @@ +# OpenChamber Docs Source + +This package is the source-of-truth for OpenChamber public docs content. + +## Layout + +- `content/docs/*.mdx` - English docs pages +- `sidebar.config.json` - docs navigation structure for Starlight sidebar +- `CONTRIBUTING.md` - authoring guide for adding pages and sections +- `DEPLOYMENT.md` - release/manual packaging and sync trigger model + +## Local validation + +Run from repo root: + +```bash +bun run docs:validate +``` + +This validates: + +- frontmatter (`title`, `description`) exists for every MDX page +- sidebar links resolve to existing MDX routes + +## Deployment model + +This repo owns docs content. + +Website rendering/deployment happens in `openchamber-website` (`apps/docs`). + +Use `.github/workflows/docs-source.yml` to package docs source on release or manual trigger. diff --git a/packages/docs/content/docs/index.mdx b/packages/docs/content/docs/index.mdx new file mode 100644 index 00000000..020f9b77 --- /dev/null +++ b/packages/docs/content/docs/index.mdx @@ -0,0 +1,25 @@ +--- +title: OpenChamber Docs +description: Setup and operating guide for OpenChamber across web, desktop, and VS Code. +--- + +# OpenChamber Docs + +OpenChamber is the visual workspace around OpenCode. + +Use these docs to: + +- install the right surface for your workflow +- expose OpenChamber safely for remote use +- customize appearance and troubleshoot common issues + +## Read this first + +- [Install](/install/) +- [Quickstart](/quickstart/) +- [Tunnels](/tunnels/) +- [Troubleshooting](/troubleshooting/) + +## What OpenChamber is for + +OpenChamber is for the parts of AI coding that benefit from a control room: branching sessions, reviewing diffs, managing terminals, watching tool progress, running project actions, and keeping the full board visible while the agent works. diff --git a/packages/docs/content/docs/install.mdx b/packages/docs/content/docs/install.mdx new file mode 100644 index 00000000..c38706a8 --- /dev/null +++ b/packages/docs/content/docs/install.mdx @@ -0,0 +1,33 @@ +--- +title: Install +description: Install OpenChamber for desktop, web, or VS Code. +--- + +# Install + +OpenChamber has three main surfaces: + +- desktop app for macOS +- CLI-hosted web app with installable PWA +- VS Code extension + +## Prerequisite + +Install [OpenCode](https://opencode.ai) first. + +## Web + PWA + +```bash +curl -fsSL https://raw.githubusercontent.com/openchamber/openchamber/main/scripts/install.sh | bash +openchamber --ui-password be-creative-here +``` + +Then open the URL printed by the CLI (usually `http://localhost:3000`). + +## Desktop + +Download the latest desktop build from the GitHub releases page or the OpenChamber download page. + +## VS Code + +Install from the VS Code Marketplace and sign into your usual OpenCode workflow. diff --git a/packages/docs/content/docs/quickstart.mdx b/packages/docs/content/docs/quickstart.mdx new file mode 100644 index 00000000..38c761d4 --- /dev/null +++ b/packages/docs/content/docs/quickstart.mdx @@ -0,0 +1,22 @@ +--- +title: Quickstart +description: Start OpenChamber quickly and choose the right surface for the task. +--- + +# Quickstart + +## Fastest path + +1. Install OpenCode. +2. Install OpenChamber CLI. +3. Run `openchamber --ui-password be-creative-here`. +4. Open the web UI on your machine. +5. If needed, start a tunnel and scan the QR code from your phone. + +Use a strong UI password, especially if you plan to expose the instance remotely. + +## Which surface should I use? + +- use **desktop** for macOS-heavy daily driver workflows +- use **web** for remote access and mobile review +- use **VS Code** for editor-native sessions beside code diff --git a/packages/docs/content/docs/themes.mdx b/packages/docs/content/docs/themes.mdx new file mode 100644 index 00000000..633b06da --- /dev/null +++ b/packages/docs/content/docs/themes.mdx @@ -0,0 +1,30 @@ +--- +title: Themes +description: Customize OpenChamber with built-in and user-defined themes. +--- + +# Themes + +OpenChamber supports built-in themes and custom theme JSON files. + +## Add a custom theme + +1. Create the themes directory: + +```bash +mkdir -p ~/.config/openchamber/themes +``` + +2. Add your JSON file to that directory (for example `my-theme.json`). +3. Open OpenChamber, then go to **Settings -> Theme -> Reload themes**. +4. Pick your theme from the dropdown. + +## Theme location + +- macOS/Linux: `~/.config/openchamber/themes/` + +## Full JSON format reference + +Use the full format guide in the main repo docs: + +- [`docs/CUSTOM_THEMES.md`](https://github.com/openchamber/openchamber/blob/main/docs/CUSTOM_THEMES.md) diff --git a/packages/docs/content/docs/troubleshooting.mdx b/packages/docs/content/docs/troubleshooting.mdx new file mode 100644 index 00000000..7d013f14 --- /dev/null +++ b/packages/docs/content/docs/troubleshooting.mdx @@ -0,0 +1,30 @@ +--- +title: Troubleshooting +description: Common setup and runtime issues with quick fixes. +--- + +# Troubleshooting + +## OpenChamber command exits or fails to start + +- confirm Node.js `>=20` +- run `openchamber --version` +- reinstall latest CLI if needed + +## Web UI is not reachable + +- check server logs with `openchamber logs` +- verify active port (default `3000`) +- open `http://localhost:3000` directly first before testing tunnel links + +## Remote/tunnel link does not work + +- run `openchamber tunnel status --all` +- restart tunnel from the same instance/port +- regenerate connect link if previous token was already used + +## VS Code extension does not connect + +- confirm OpenChamber server is running +- verify extension is updated +- reload VS Code window and retry connection diff --git a/packages/docs/content/docs/tunnels.mdx b/packages/docs/content/docs/tunnels.mdx new file mode 100644 index 00000000..1206cdad --- /dev/null +++ b/packages/docs/content/docs/tunnels.mdx @@ -0,0 +1,77 @@ +--- +title: Tunnels +description: Expose OpenChamber safely for remote and mobile access. +--- + +# Tunnels + +Use `openchamber tunnel` to expose a running OpenChamber instance. + +## Quick start (Cloudflare quick mode) + +1. Start OpenChamber: + +```bash +openchamber +``` + +2. Start a tunnel: + +```bash +openchamber tunnel start --provider cloudflare --mode quick +``` + +3. Check status: + +```bash +openchamber tunnel status +``` + +By default, OpenChamber prints a QR code in interactive TTY sessions. Use `--qr` to force QR output, or `--no-qr` to disable it. + +## Managed modes + +### Managed remote + +Use a token + hostname managed by Cloudflare: + +```bash +openchamber tunnel start --provider cloudflare --mode managed-remote --token-file ~/.secrets/cf-token --hostname app.example.com +``` + +### Managed local + +Use a local `cloudflared` config: + +```bash +openchamber tunnel start --provider cloudflare --mode managed-local --config ~/.cloudflared/config.yml +``` + +## Profiles (managed-remote) + +Save a reusable profile: + +```bash +openchamber tunnel profile add --provider cloudflare --mode managed-remote --name prod-main --hostname app.example.com --token-file ~/.secrets/cf-token +``` + +Start using the saved profile: + +```bash +openchamber tunnel start --profile prod-main +``` + +## Useful commands + +```bash +openchamber tunnel providers +openchamber tunnel ready --provider cloudflare +openchamber tunnel doctor --provider cloudflare +openchamber tunnel stop --port 3000 +``` + +## Behavior notes + +- one active tunnel per OpenChamber instance (port) +- starting a new mode/provider on same instance replaces previous tunnel +- generating a new connect link revokes previous unused one diff --git a/packages/docs/sidebar.config.json b/packages/docs/sidebar.config.json new file mode 100644 index 00000000..2e3d8c46 --- /dev/null +++ b/packages/docs/sidebar.config.json @@ -0,0 +1,25 @@ +{ + "sections": [ + { + "label": "Start here", + "items": [ + { "label": "Overview", "link": "/" }, + { "label": "Install", "link": "/install/" }, + { "label": "Quickstart", "link": "/quickstart/" }, + { "label": "Tunnels", "link": "/tunnels/" } + ] + }, + { + "label": "Customize", + "items": [ + { "label": "Themes", "link": "/themes/" } + ] + }, + { + "label": "Help", + "items": [ + { "label": "Troubleshooting", "link": "/troubleshooting/" } + ] + } + ] +} diff --git a/scripts/docs/validate-docs.mjs b/scripts/docs/validate-docs.mjs new file mode 100644 index 00000000..ad658bb6 --- /dev/null +++ b/scripts/docs/validate-docs.mjs @@ -0,0 +1,88 @@ +import { readdir, readFile } from "node:fs/promises" +import path from "node:path" + +const repoRoot = path.resolve(import.meta.dirname, "..", "..") +const docsRoot = path.join(repoRoot, "packages", "docs") +const contentRoot = path.join(docsRoot, "content", "docs") +const sidebarPath = path.join(docsRoot, "sidebar.config.json") + +async function walk(dir) { + const entries = await readdir(dir, { withFileTypes: true }) + const files = await Promise.all( + entries.map(async (entry) => { + const target = path.join(dir, entry.name) + if (entry.isDirectory()) return walk(target) + return [target] + }), + ) + return files.flat() +} + +function toPosix(value) { + return value.split(path.sep).join("/") +} + +function routeFromFile(filePath) { + const relative = toPosix(path.relative(contentRoot, filePath)) + const withoutExt = relative.replace(/\.mdx$/, "") + + if (withoutExt === "index") return "/" + if (withoutExt.endsWith("/index")) { + return `/${withoutExt.slice(0, -"/index".length)}/` + } + + return `/${withoutExt}/` +} + +function hasFrontmatterKey(content, key) { + const hit = /^---\n([\s\S]*?)\n---\n/m.exec(content) + if (!hit) return false + return new RegExp(`^${key}:\\s*.+$`, "m").test(hit[1]) +} + +async function run() { + const filePaths = (await walk(contentRoot)).filter((p) => p.endsWith(".mdx")) + const routeSet = new Set() + const errors = [] + + for (const filePath of filePaths) { + const body = await readFile(filePath, "utf8") + const relative = toPosix(path.relative(repoRoot, filePath)) + const route = routeFromFile(filePath) + routeSet.add(route) + + if (!hasFrontmatterKey(body, "title")) { + errors.push(`${relative}: missing frontmatter key 'title'`) + } + if (!hasFrontmatterKey(body, "description")) { + errors.push(`${relative}: missing frontmatter key 'description'`) + } + } + + const sidebarRaw = await readFile(sidebarPath, "utf8") + const sidebar = JSON.parse(sidebarRaw) + const links = (sidebar.sections ?? []) + .flatMap((section) => section.items ?? []) + .map((item) => item.link) + + for (const link of links) { + if (!routeSet.has(link)) { + errors.push(`sidebar link has no page: ${link}`) + } + } + + if (errors.length > 0) { + console.error("Docs validation failed:") + for (const error of errors) { + console.error(`- ${error}`) + } + process.exit(1) + } + + console.log(`Docs validation passed: ${filePaths.length} pages, ${links.length} sidebar links.`) +} + +run().catch((error) => { + console.error(error) + process.exit(1) +})