Web page for ConnectBot, the first open source SSH client on Android.
-
Clone the repository:
git clone https://github.com/connectbot/connectbot.github.io.git cd connectbot.github.io -
Install dependencies:
pnpm install
-
Start the development server:
pnpm dev
The site will be available at http://localhost:3000
To open the dev server through a LAN address, set
NEXT_ALLOWED_DEV_ORIGINS=192.168.1.10in.env.local, using your server's IP or hostname, then restartpnpm dev. Separate multiple hosts with commas.
The site is built with Next.js and Nextra, a documentation framework. Content is written in MDX (Markdown + JSX) in the src/content/ directory.
Project structure:
src/content/- MDX content filessrc/content/_meta.ts- Page configuration (titles, navigation)src/app/- Next.js App Router structuresrc/components/- React componentspublic/- Static assets
Git hooks: This project uses Lefthook for Git hooks:
- Pre-commit: Runs ESLint auto-fix and TypeScript type checking
- Commit-msg: Validates conventional commit format
Build the site:
pnpm buildThis generates a static export in the ./out directory.
Lint your code:
pnpm lint # Check for errors
pnpm lint:fix # Auto-fix errorsType check:
pnpm run check:typesCheck for unused dependencies:
pnpm run check:depsAnalyze bundle size:
pnpm run analyzeThe capture pipeline uses real Android screens, recorded actions, chapter links,
yellow tap cues, and optional narration. Defaults are English, Android API 36,
and both light and dark themes. Generated assets live in public/guides/;
Android captures and speech caches live in ignored local/guide-build/.
Install uv, the Android SDK command-line tools, platform-tools, emulator, and
ffprobe. Set ANDROID_SDK_ROOT to your SDK directory. Python dependencies and
an FFmpeg encoder are installed automatically by the guide commands.
export ANDROID_SDK_ROOT=/path/to/android-sdk
sdkmanager 'system-images;android-36;google_apis;x86_64'
pnpm check:guidesThe release APK and emulator profiles are configured in
scripts/guides/config.json. UI labels come from the matching ConnectBot resource
tag, using ~/git/connectbot when available. Use --app-repo for another checkout.
Create scripts/guides/scenarios/my_feature.py. Each guide has its own file:
add_host.py, special_keys.py, and terminal_appearance.py are working examples.
Export SEED_LOCAL and a run(c) function, then register the module under the
guide's ID in scripts/guides/scenarios/__init__.py.
"""Capture the my-feature walkthrough."""
SEED_LOCAL = False # True creates a local demo terminal before recording.
def run(c):
with c.step("open-menu"):
c.click("button_more_options", description=True)
with c.step("open-settings"):
c.click("list_menu_settings")c.step() declares a chapter and captures its starting screenshot and settled
ending. Use stable step IDs in the same order as the prose. c.click() locates
translated Android string resources and records the tap and target bounds;
c.click_action() handles toolbar text or icons. c.tap(node) also records a
cue. Use c.node() to wait for the expected result, c.fill() to enter text,
c.back() to navigate, and c.center_setting() to reveal a Settings row.
Prefer resource keys and accessibility labels over fixed screen coordinates.
Raw c.d.click() calls bypass the tap recording helper.
The shared device setup, screenshot/recording helpers, and cleanup remain in
scripts/guides/capture.py. SEED_LOCAL = True calls its existing
seed_local() helper; add any other scenario-specific preparation deliberately
before recording rather than presenting it as a user instruction.
Add a guide object to src/guides/en.json. Its ID must match the scenario registry
key, and its step IDs and order must match the c.step() calls:
{
"id": "my-feature",
"title": "Open ConnectBot settings",
"description": "Find the app settings from the Host List.",
"steps": [
{
"id": "open-menu",
"title": "Open the menu",
"text": "From the Host List, tap the three-dot menu."
},
{
"id": "open-settings",
"title": "Open Settings",
"text": "Tap Settings."
}
]
}text supplies the written instruction and default speech. An optional spoken
field overrides speech for pronunciation, such as spelling out a server address.
Write instructions for the user; avoid describing the capture or narration process.
Add the same guide and step IDs to every existing translation file
(src/guides/es.json and src/guides/ar.json), with translated titles, descriptions,
and prose. A new language needs a complete src/guides/<language>.json containing
all guides with the same IDs and step order as English.
Create src/content/guides/my-feature.mdx:
---
title: Open ConnectBot settings
---
import { GuideContent } from '@/components/GuideContent';
# Open ConnectBot settings
<GuideContent id="my-feature" />Add the page to src/content/guides/_meta.ts and link it from the guide index.
Run pnpm check:guides --languages en,es,ar to validate prose and encoders.
The player follows the site's language and theme automatically. Until a matching
recording is published, the page shows only its written instructions.
Capture first without rendering, so you can inspect the screenshots and recording:
pnpm capture:guides --guide my-feature --languages en --api-levels 36 --themes light,dark --capture-onlyReview local/guide-build/captures/my-feature/en/api-36/{light,dark}/:
raw.mp4, step PNGs, capture.json, and any failure diagnostics. The tool uses
its own disposable emulator and validates that captured steps match the prose.
For a connected phone with ConnectBot already installed:
adb devices -l
pnpm capture:guides --guide my-feature --serial DEVICE_SERIAL --resource-tag MATCHING_TAG_OR_COMMIT --languages en --themes light,dark --capture-onlyPhone capture uses a temporary Android user and restores the original user after
cleanup. Keep it unlocked and approve debugging prompts. Its actual API level
replaces --api-levels; use that API level when rendering. See
phone setup and capture details.
Put speech settings in the ignored root .env.guides.local. Choose one provider.
For an OpenAI-compatible endpoint, use its exact speech URL:
TTS_PROVIDER=openai
TTS_SPEECH_URL=http://your-server:1234/v1/audio/speech
TTS_MODEL=your-speech-model
TTS_VOICE=your-voice
TTS_API_KEY=your-api-key/audio/speech URLs also work. Omit the key for an unauthenticated service.
For ElevenLabs, use these settings instead:
TTS_PROVIDER=elevenlabs
TTS_MODEL=eleven_v3
TTS_VOICE=your-eligible-voice-id
TTS_API_KEY=your-api-keyKeep keys out of git and never use a NEXT_PUBLIC_ variable for them. Generate
speech from the step prose (or spoken override):
pnpm narrate:guides --guide my-feature --languages enSpeech clips are cached and reused across themes and API levels. To add languages,
review their prose first, then run --languages en,es,ar. Only changed clips incur
new speech requests. Per-language voice settings and provider details are in
the speech configuration documentation.
Render the existing captures using the cached narration:
pnpm render:guides --guide my-feature --languages en --api-levels 36 --themes light,dark --narration required
pnpm devOpen /guides/my-feature/. Check both site themes, tap cue timing, chapters,
subtitles, narration, and the screenshot tour. Rendering creates VP9/WebM and
H.264/MP4 videos, screenshots, captions, chapters, and manifests, then updates
public/guides/index.json. Use --narration off to render without speech.
For a language/API matrix, install each SDK image and configure each API profile before capture. Use the same filters for capture and render:
pnpm capture:guides --guide my-feature --languages en,es,ar --api-levels 29,36 --themes light,dark --capture-only
pnpm narrate:guides --guide my-feature --languages en,es,ar
pnpm render:guides --guide my-feature --languages en,es,ar --api-levels 29,36 --themes light,dark --narration requiredAfter editing only prose, rerun narration and rendering; existing captures are reused. After changing app screens or scripted interactions, recapture before rendering. Verify the pipeline and site:
pnpm test:guides
pnpm lint
pnpm build
pnpm test:guides:browser --project chromium --project firefox --workers 1Browser tests require installed Playwright browsers; see validation setup. Publish the generated index and every asset it references together with the guide's code and prose. Speech keys, raw captures, and synthesis caches remain local.
To keep only the current published build for each guide/language/API/theme:
pnpm prune:guides --dry-run # Preview obsolete build directories.
pnpm prune:guides # Delete them, keeping all indexed assets.
pnpm prune:guides --guide special-keys # Limit cleanup to one guide.Run pruning after rendering finishes, before building or publishing the site. It
keeps every build referenced by public/guides/index.json, including both themes,
and leaves raw captures and paid speech caches intact. No additional package
or Android/speech setup is required.
The site is automatically deployed to GitHub Pages when changes are pushed to the develop branch. The CI workflow:
- Runs linting and type checking
- Builds the static site
- Deploys to GitHub Pages
You can preview the production build locally by serving the ./out directory:
npx serve ./out