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
This commit is contained in:
Bohdan Triapitsyn
2026-03-15 03:30:35 +02:00
committed by GitHub
parent 1f8e875357
commit 75c90d955c
14 changed files with 549 additions and 0 deletions
+86
View File
@@ -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 @- <<JSON
{
"event_type": "docs_source_updated",
"client_payload": {
"source_repo": "${{ github.repository }}",
"source_ref": "$SOURCE_REF",
"archive_name": "${{ steps.archive.outputs.archive_name }}"
}
}
JSON
+2
View File
@@ -329,6 +329,8 @@ Independent project, not affiliated with the OpenCode team.
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
Docs source lives in [`packages/docs`](packages/docs/README.md).
## License
MIT
+1
View File
@@ -52,6 +52,7 @@
"vscode:build": "bun run --cwd packages/vscode build",
"vscode:package": "bun run --cwd packages/vscode package",
"vscode:type-check": "bun run --cwd packages/vscode type-check",
"docs:validate": "node scripts/docs/validate-docs.mjs",
"icons:sprite": "node scripts/generate-file-type-sprite.mjs",
"version:bump": "node scripts/bump-version.mjs",
"release:prepare": "bun run build && bun run type-check && bun run lint",
+57
View File
@@ -0,0 +1,57 @@
# Docs Authoring Guide
This package is docs content source-of-truth for OpenChamber.
## Add a new docs page
1. Create a new file in `packages/docs/content/docs/`.
- Example: `packages/docs/content/docs/remote-access.mdx`
2. Add frontmatter at top:
```mdx
---
title: Remote Access
description: Access OpenChamber from outside your local network.
---
```
3. Use route-safe naming:
- `foo.mdx` -> `/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).
+42
View File
@@ -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-<sha>.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.
+31
View File
@@ -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.
+25
View File
@@ -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.
+33
View File
@@ -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.
+22
View File
@@ -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
+30
View File
@@ -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)
@@ -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
+77
View File
@@ -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
+25
View File
@@ -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/" }
]
}
]
}
+88
View File
@@ -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)
})