Files
wehub-resource-sync d68f003000
CI / Change detection (push) Has been cancelled
CI / Version drift (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Workflow RLM cache (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / npm wrapper smoke (push) Has been cancelled
CI / Mobile runtime smoke (push) Has been cancelled
CI / Workflow lint (push) Has been cancelled
CI / Documentation (push) Has been cancelled
Web Frontend / Lint & Type Check (push) Failing after 1s
Auto-close harvested PRs / close (push) Failing after 1s
cargo-deny / cargo-deny (bans licenses sources) (push) Failing after 1s
Security audit / cargo-audit (push) Failing after 1s
Sync to CNB / sync (push) Failing after 1s
cargo-deny / cargo-deny (advisories) (push) Failing after 3s
Web Frontend / Deploy to Cloudflare (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:08:23 +08:00

24 KiB

codewhale remote-setup - Tailscale-first design

Status: design / revision. This RFC revises the earlier cloud-first remote-setup plan. Keep the accurate implementation work already present: codewhale remote-setup exists today as a generate-only bundle wizard for cloud plus chat bridge deployments, and --apply is still not implemented.

Goal

Give users a guided, education-forward way to reach a local-first CodeWhale runtime from another surface without accidentally publishing their agent.

Default posture:

  1. Local-first by default.
  2. Tailnet-private when remote.
  3. Public only when explicitly chosen.

The wizard should ask:

How do you want to reach CodeWhale?

and offer these paths, in this order:

  1. This machine only (localhost)
  2. Private devices with Tailscale (Recommended)
  3. Telegram bot
  4. Feishu/Lark bot
  5. Weixin personal bridge
  6. Public webhook / Funnel (Advanced)

The recommended remote answer is Tailscale Serve with the backend still bound to 127.0.0.1. Tailscale supplies device identity and encrypted transport. Tailscale Funnel is public internet exposure and must stay advanced.

Current implementation checkpoint

Verified against the codebase:

  • codewhale app-server --http is the canonical HTTP/SSE runtime API entrypoint. It delegates to the mature serve --http implementation.
  • codewhale app-server --mobile is real and serves the phone control page at /mobile.
  • --host, --port, --workers, --auth-token, --insecure-no-auth, and repeatable --cors-origin exist on app-server --http / --mobile.
  • --mobile without --host binds to 0.0.0.0 by design. Use --host 127.0.0.1 when putting Tailscale in front of the runtime.
  • /health and /v1/runtime/info are public bootstrap/supervision endpoints. /v1/* control routes require the runtime bearer token unless auth is explicitly disabled on a trusted loopback bind.
  • codewhale doctor --json exists as the machine-readable local diagnostic.
  • codewhale remote-setup exists, but today it is generate-only. Its current matrix is cloud target (lighthouse, azure, digitalocean) x bridge (feishu, telegram) x provider registry. It does not yet model localhost, Tailscale, Weixin, or Funnel as first-class choices.
  • Telegram and Feishu bridge validators exist as npm run validate:config. Weixin currently has npm run check, but no validate-config script.

Accuracy note for the Tailscale recommendation: the requested setup uses app-server --http, but the current runtime serves /mobile only in mobile mode. This RFC keeps the target command shape for the recommended loopback runtime, and documents the verified current-binary variant when the mobile page is required:

# Runtime API only, verified:
codewhale app-server --http --host 127.0.0.1 --port 7878 --auth-token "$CODEWHALE_RUNTIME_TOKEN"

# Runtime API plus /mobile, verified:
codewhale app-server --mobile --host 127.0.0.1 --port 7878 --auth-token "$CODEWHALE_RUNTIME_TOKEN"

Common runtime base

Every path starts from the same local runtime trust boundary.

CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
export CODEWHALE_RUNTIME_TOKEN

codewhale app-server --http \
  --host 127.0.0.1 \
  --port 7878 \
  --auth-token "$CODEWHALE_RUNTIME_TOKEN"

For the current binary, use --mobile --host 127.0.0.1 instead of --http if the path needs the built-in /mobile page.

Doctor-style local validation:

codewhale doctor --json
curl -fsS http://127.0.0.1:7878/health
curl -fsS \
  -H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
  http://127.0.0.1:7878/v1/runtime/info

Runtime mental model:

  • Exposed by CodeWhale: only the address it binds. The recommended bind is 127.0.0.1:7878.
  • Auth token: CODEWHALE_RUNTIME_TOKEN, passed as Authorization: Bearer ... by clients and bridges. Legacy DEEPSEEK_RUNTIME_TOKEN remains a fallback.
  • Provider secrets: stay in runtime configuration, not in bridge env files.
  • Bridge secrets: stay in transport-specific env files.

Guided flow

1. This machine only (localhost)

Use this when the TUI, SDK, browser, or local script runs on the same machine as CodeWhale.

Setup:

CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
export CODEWHALE_RUNTIME_TOKEN

codewhale app-server --http \
  --host 127.0.0.1 \
  --port 7878 \
  --auth-token "$CODEWHALE_RUNTIME_TOKEN"

Env template:

CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>

Validation:

codewhale doctor --json
curl -fsS http://127.0.0.1:7878/health
curl -fsS \
  -H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
  http://127.0.0.1:7878/v1/runtime/info

Trust boundary:

  • Exposed: loopback only.
  • Not exposed: LAN, tailnet, or public internet.
  • Token used: CODEWHALE_RUNTIME_TOKEN for control routes; local /health and /v1/runtime/info are public bootstrap endpoints.

Use this to reach CodeWhale from your phone or laptop without opening a LAN or public port. Tailscale authenticates devices in your tailnet; CodeWhale still binds to localhost.

Target setup to feature in the wizard:

CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
export CODEWHALE_RUNTIME_TOKEN

codewhale app-server --http \
  --host 127.0.0.1 \
  --port 7878 \
  --auth-token "$CODEWHALE_RUNTIME_TOKEN"

tailscale serve --bg --https=443 localhost:7878

Then open the Tailscale Serve URL from a phone or laptop in the same tailnet. For the current binary's mobile page, start CodeWhale with the verified mobile variant:

codewhale app-server --mobile \
  --host 127.0.0.1 \
  --port 7878 \
  --auth-token "$CODEWHALE_RUNTIME_TOKEN"

Then open (put the token in the URL fragment, not a query param — the /mobile page reads it from location.hash, and a fragment is never sent to the Tailscale serving layer or to any proxy log):

https://<machine>.<tailnet>.ts.net/mobile#token=<CODEWHALE_RUNTIME_TOKEN>

Env template:

CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
CODEWHALE_RUNTIME_TOKEN=<openssl-rand-hex-32>
TAILSCALE_SERVE_TARGET=localhost:7878
TAILSCALE_SERVE_URL=https://<machine>.<tailnet>.ts.net

Validation:

codewhale doctor --json
curl -fsS http://127.0.0.1:7878/health
curl -fsS https://<machine>.<tailnet>.ts.net/health
curl -fsS \
  -H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
  https://<machine>.<tailnet>.ts.net/v1/runtime/info
tailscale serve status

Trust boundary:

  • Exposed: an HTTPS endpoint reachable by devices authorized in your tailnet.
  • Not exposed: the raw CodeWhale listener; it stays on 127.0.0.1.
  • Token used: Tailscale identity gates network reachability; CodeWhale still uses CODEWHALE_RUNTIME_TOKEN for runtime control.
  • Caveat: Tailscale Serve is private to the tailnet. Tailscale Funnel is public internet exposure and belongs only in the advanced path below.

3. Telegram bot

Use this when a Telegram DM should control a local CodeWhale runtime. The bridge uses Telegram Bot API long polling, so it does not require a public webhook URL or inbound port.

Setup:

CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
export CODEWHALE_RUNTIME_TOKEN

codewhale app-server --http \
  --host 127.0.0.1 \
  --port 7878 \
  --auth-token "$CODEWHALE_RUNTIME_TOKEN"

cd integrations/telegram-bridge
npm install --omit=dev
cp .env.example .env
$EDITOR .env
npm run validate:config -- \
  --env .env \
  --workspace-root "$PWD/../.." \
  --check-filesystem
npm start

Env template:

TELEGRAM_BOT_TOKEN=replace-with-botfather-token

CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>
CODEWHALE_WORKSPACE=/path/to/workspace
# Optional override; leave blank to inherit the runtime's configured provider/model.
CODEWHALE_MODEL=
CODEWHALE_MODE=agent
CODEWHALE_ALLOW_SHELL=true     # grants shell execution from the bridge; set false for text-only chat
CODEWHALE_TRUST_MODE=false
CODEWHALE_AUTO_APPROVE=false

TELEGRAM_CHAT_ALLOWLIST=
TELEGRAM_ALLOW_UNLISTED=false
TELEGRAM_ALLOW_GROUPS=false

First pairing:

# Temporarily in .env:
TELEGRAM_ALLOW_UNLISTED=true

DM the bot /status, copy the returned chat_id or user_id into TELEGRAM_CHAT_ALLOWLIST, then set TELEGRAM_ALLOW_UNLISTED=false and restart the bridge.

Validation:

codewhale doctor --json
curl -fsS http://127.0.0.1:7878/health
npm run validate:config -- \
  --env .env \
  --workspace-root "$PWD/../.." \
  --check-filesystem

Trust boundary:

  • Exposed: no inbound CodeWhale port. Telegram sees messages sent to the bot.
  • Not exposed: CodeWhale remains on 127.0.0.1; provider keys stay in the runtime env, not the Telegram env.
  • Tokens used: TELEGRAM_BOT_TOKEN for Telegram, CODEWHALE_RUNTIME_TOKEN for bridge-to-runtime calls, and TELEGRAM_CHAT_ALLOWLIST for user/chat gating.
  • Caveat: direct messages are the intended MVP control surface. Group control is off unless TELEGRAM_ALLOW_GROUPS=true.

4. Feishu/Lark bot

Use this when a Feishu or Lark chat should control the local runtime. The bridge uses the Lark/Feishu long-connection SDK, so the first version does not need a public webhook URL.

Setup:

CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
export CODEWHALE_RUNTIME_TOKEN

codewhale app-server --http \
  --host 127.0.0.1 \
  --port 7878 \
  --auth-token "$CODEWHALE_RUNTIME_TOKEN"

cd integrations/feishu-bridge
npm install --omit=dev
cp .env.example .env
$EDITOR .env
npm run validate:config -- \
  --env .env \
  --workspace-root "$PWD/../.." \
  --check-filesystem
npm start

Env template:

FEISHU_APP_ID=cli_xxxxxxxxxxxxxxxx
FEISHU_APP_SECRET=replace-with-app-secret
FEISHU_DOMAIN=feishu               # international Lark users: set to "lark"

CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>
CODEWHALE_WORKSPACE=/path/to/workspace
# Optional override; leave blank to inherit the runtime's configured provider/model.
CODEWHALE_MODEL=
CODEWHALE_MODE=agent
CODEWHALE_ALLOW_SHELL=true     # grants shell execution from the bridge; set false for text-only chat
CODEWHALE_TRUST_MODE=false
CODEWHALE_AUTO_APPROVE=false

CODEWHALE_CHAT_ALLOWLIST=
CODEWHALE_ALLOW_UNLISTED=false
FEISHU_ALLOW_GROUPS=false

First pairing:

Temporarily set CODEWHALE_ALLOW_UNLISTED=true, message the app once, copy the logged open id into CODEWHALE_CHAT_ALLOWLIST, then set CODEWHALE_ALLOW_UNLISTED=false and restart the bridge.

Validation:

codewhale doctor --json
curl -fsS http://127.0.0.1:7878/health
npm run validate:config -- \
  --env .env \
  --workspace-root "$PWD/../.." \
  --check-filesystem

Trust boundary:

  • Exposed: no inbound CodeWhale port. Feishu/Lark sees messages sent to the app.
  • Not exposed: CodeWhale remains on 127.0.0.1; provider keys stay in runtime config.
  • Tokens used: FEISHU_APP_ID / FEISHU_APP_SECRET for the platform, CODEWHALE_RUNTIME_TOKEN for bridge-to-runtime calls, and CODEWHALE_CHAT_ALLOWLIST for chat gating.
  • Caveat: group control is off unless explicitly enabled.

5. Weixin personal bridge

Use this when a personal Weixin account should control the local runtime by QR login. This is not a public account webhook. The bridge initiates long polling and does not need a public port.

Setup:

CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
export CODEWHALE_RUNTIME_TOKEN

codewhale app-server --http \
  --host 127.0.0.1 \
  --port 7878 \
  --auth-token "$CODEWHALE_RUNTIME_TOKEN"

cd integrations/weixin-bridge
npm install --omit=dev
cp .env.example .env
$EDITOR .env
npm run check
npm start

Env template:

CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>
CODEWHALE_WORKSPACE=/path/to/workspace
# Optional override; leave blank to inherit the runtime's configured provider/model.
CODEWHALE_MODEL=
CODEWHALE_MODE=agent
CODEWHALE_ALLOW_SHELL=true     # grants shell execution from the bridge; set false for text-only chat
CODEWHALE_TRUST_MODE=false
CODEWHALE_AUTO_APPROVE=false

WEXIN_CHAT_ALLOWLIST=
WEXIN_ALLOW_UNLISTED=false
WEXIN_STATE_DIR=/var/lib/codewhale-weixin-bot-bridge

First pairing:

Set WEXIN_ALLOW_UNLISTED=true, start the bridge, scan the QR code, send /status, copy the returned user_id into WEXIN_CHAT_ALLOWLIST, then set WEXIN_ALLOW_UNLISTED=false and restart the bridge.

Validation:

codewhale doctor --json
curl -fsS http://127.0.0.1:7878/health
npm run check

Trust boundary:

  • Exposed: no inbound CodeWhale port. The personal Weixin session and the bridge state directory become sensitive local state.
  • Not exposed: CodeWhale remains on 127.0.0.1; provider keys stay in runtime config.
  • Tokens used: the scanned Weixin login/session state for platform access, CODEWHALE_RUNTIME_TOKEN for bridge-to-runtime calls, and WEXIN_CHAT_ALLOWLIST for user gating.
  • Caveat: this is a personal-account bridge. Treat the host and state directory like a logged-in phone session.

6. Public webhook / Funnel (Advanced)

Use this only when the user explicitly chooses public internet reachability, understands that the URL can be reached outside the tailnet, and has a reason that Tailscale Serve or long polling cannot satisfy.

Preferred advanced pattern:

CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
export CODEWHALE_RUNTIME_TOKEN

codewhale app-server --mobile \
  --host 127.0.0.1 \
  --port 7878 \
  --auth-token "$CODEWHALE_RUNTIME_TOKEN"

tailscale funnel --bg --https=443 localhost:7878

Env template:

CODEWHALE_RUNTIME_URL=https://<public-name>
CODEWHALE_RUNTIME_TOKEN=<openssl-rand-hex-32>
PUBLIC_EXPOSURE_ACK=true

Validation:

codewhale doctor --json
curl -fsS http://127.0.0.1:7878/health
curl -fsS https://<public-name>/health
curl -fsS \
  -H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
  https://<public-name>/v1/runtime/info
tailscale funnel status

Trust boundary:

  • Exposed: a public HTTPS endpoint, not just your tailnet.
  • Not exposed by CodeWhale directly: the backend still binds to 127.0.0.1, but the fronting layer makes selected routes reachable from the internet.
  • Token used: CODEWHALE_RUNTIME_TOKEN remains mandatory for control routes.
  • Caveat: public does not mean safe. Do not use --insecure-no-auth, do not bind CodeWhale to 0.0.0.0, and do not call this the default.

Cloud/VPS posture

Cloud/VPS is a placement choice, not a trust model. The old RFC's cloud work is still useful, but it should sit behind the same reachability choices:

  • A VPS can run the runtime bound to 127.0.0.1.
  • Recommended remote access from personal devices is still Tailscale Serve.
  • Bot bridges should use long polling / long connection where available, keeping the runtime localhost-only on the host.
  • SSH tunnels remain acceptable for ad hoc validation:
ssh -L 7878:127.0.0.1:7878 <host>

Public inbound listeners, public webhooks, and Tailscale Funnel are advanced choices, not the default cloud path.

Prior art: Hermes Agent (reference only - do not copy)

Nous Research's Hermes Agent validates the table-driven part of this design. Use it for ideas; keep CodeWhale's style: Rust core, local runtime, zero-dep Node bridges where possible, and plain-text replies.

  • gateway/platform_registry.py maps to our BridgeSpec / access-path registry: one row per platform, with setup hints, required env, validation, and adapter factory.
  • gateway/pairing.py maps to our allowlist / first-pairing flow.

Telegram hardening carried forward from the original RFC:

Edge case In Hermes In our Telegram bridge
409 polling conflict _looks_like_polling_conflict done - poll loop backs off and warns
429 retry_after rate-limit handling done - telegramApi honors parameters.retry_after
Forum General topic id handling send/typing split done - omit message_thread_id when id is 1 on send
Stale reply anchor after restart retry without anchor sidestepped - no reply_to_message_id
Network/connect timeout retry network error detection partial - generic poll-loop backoff
Text batching / progress edit progress-edit tests deferred - plain periodic chunks
MarkdownV2 escaping escaping helpers deferred - plain text
Webhook mode webhook adapter out of default scope - long polling first

Design principle: table-driven, like ProviderSpec

The provider registry is the model to preserve: adding a provider is one row. Apply the same idea to access paths, bridges, and cloud placements so the matrix grows by data.

AccessPath x Placement x BridgeSpec + ProviderSpec
----------   ---------   ----------   ------------
localhost    local       none         deepseek / openai / ...
tailscale    local/vps   none         provider lives in runtime.env
telegram     local/vps   telegram     bridge is pure transport
feishu       local/vps   feishu       bridge is pure transport
weixin       local/vps   weixin       bridge is pure transport
funnel       local/vps   optional     explicit public exposure

Clean separation:

  • Provider = runtime env. The runtime resolves provider/model/API key from CODEWHALE_PROVIDER, provider key vars, and the provider registry. Bridges do not need provider keys.
  • Access path = reachability. Localhost, Tailscale Serve, chat long polling, and Funnel are separate choices with different trust boundaries.
  • Bridge = transport. A chat bridge forwards allowed chat messages to http://127.0.0.1:7878 with CODEWHALE_RUNTIME_TOKEN.
  • Cloud = where it runs and where secrets live. It is not permission to open port 7878.

Proposed command surface

Current flags are verified for the generate-only cloud/bridge wizard:

Flag Current status
`--cloud <lighthouse azure
`--bridge <telegram feishu>`
--provider <slug> verified, provider registry-backed
--out <dir> verified
--generate-only verified
--apply verified flag, but not implemented
--yes verified flag
--non-interactive verified flag

Proposed Tailscale-first revision:

Flag Meaning
`--access <localhost tailscale
`--placement <local vps
`--bridge <telegram feishu
--provider <slug> Provider slug; validated against the existing provider registry.
--out <dir> Bundle output dir.
--generate-only Emit commands/env/runbook, do not provision. Default.
--apply Future cloud CLI provisioning, behind confirmation. Still not implemented.
--yes Skip final confirmation gates where safe for CI/non-interactive use.
--non-interactive Fail instead of prompting for missing required values.

The first prompt should be the reachability question, not the cloud question. Tailscale should be visually marked as recommended.

Generated bundle

The current bundle model stays useful. Extend it so the generated runbook is access-path-first.

Files:

  • runtime.env - provider and runtime config:

    CODEWHALE_PROVIDER=openai
    OPENAI_API_KEY=replace-with-provider-key
    # Optional override; leave blank to inherit the runtime's configured provider/model.
    

CODEWHALE_MODEL= CODEWHALE_RUNTIME_TOKEN= CODEWHALE_RUNTIME_PORT=7878 CODEWHALE_RUNTIME_WORKERS=2 RUST_LOG=info


- `<bridge>.env` - transport only when a bridge is selected:

```env
CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
CODEWHALE_RUNTIME_TOKEN=<same random token>
CODEWHALE_WORKSPACE=/opt/whalebro
# Optional override; leave blank to inherit the runtime's configured provider/model.
CODEWHALE_MODEL=
CODEWHALE_MODE=agent
CODEWHALE_ALLOW_SHELL=true     # grants shell execution from the bridge; set false for text-only chat
CODEWHALE_TRUST_MODE=false
CODEWHALE_AUTO_APPROVE=false
  • codewhale-runtime.service
  • optional codewhale-<bridge>.service
  • optional cloud artifacts: cloud-init.yaml, provision.sh, cnb.yml, or cloud-specific runbook steps
  • RUNBOOK.md with:
    • exact setup commands
    • env template
    • doctor-style validation
    • first-pairing steps for bridges
    • trust-boundary summary
    • explicit "public exposure acknowledged" section for Funnel/webhook modes

Auto-provision

Preserve the original safety model:

  • --generate-only is the default.
  • --apply is explicit and is not implemented today.
  • Every command is rendered before execution.
  • Secrets are not passed through shell history or argv.
  • Cloud CLIs are placement helpers, not permission to open runtime ports.

Existing cloud target design remains accurate:

  • Tencent Lighthouse: native plus systemd, env-file secrets, CNB-oriented plan.
  • Azure VM: Docker image plus Key Vault, managed identity at boot.
  • DigitalOcean Droplet: native plus systemd, env-file secrets, doctl plan.

All cloud plans should bind CodeWhale to 127.0.0.1 and then layer one of the reachability paths above.

Namespace migration: DEEPSEEK_* to CODEWHALE_*

Carry forward the convention already used in code: read CODEWHALE_X first, fall back to DEEPSEEK_X where compatibility is needed.

Touch list from the original RFC remains valid:

  1. Bridges: read CODEWHALE_X ?? DEEPSEEK_X for runtime URL/token, workspace, model, mode, shell/trust/approval flags, allowlists, and timeouts. Templates should emit CODEWHALE_*.
  2. Deploy units: prefer /etc/codewhale/*.env; keep legacy path reads only for compatibility where needed.
  3. .env.example files and config.example.toml: lead with CODEWHALE_*, document legacy aliases.
  4. Drop DeepSeek-shaped defaults in bridge templates except where DeepSeek is explicitly the chosen provider. Provider choice belongs in runtime.env.

Tests

Existing bundle tests should stay:

  • Every cloud / bridge / provider triple renders.
  • Runtime and bridge env files share the same CODEWHALE_RUNTIME_TOKEN.
  • Env files lead with CODEWHALE_*.
  • Generated runbooks are non-empty and list the provision plan.
  • Provision plans are command data and are not executed in tests.

New tests for this revision:

  • Every AccessPath row has setup commands, env template, validation commands, and trust-boundary copy.
  • Tailscale is the recommended remote path in prompt ordering.
  • Funnel/webhook mode requires an explicit advanced/public acknowledgement.
  • /mobile docs use app-server --mobile --host 127.0.0.1 for current binary behavior, or clearly mark any --http plus /mobile path as proposed.
  • Weixin can be documented before it is in the remote-setup registry, but the wizard must mark it proposed until a BridgeSpec row and validation story exist.

Suggested sequencing

  1. Revise the RFC and runbook copy to be Tailscale-first.
  2. Add an access-path registry above the existing cloud/bridge/provider tables.
  3. Add localhost and Tailscale generate-only bundles.
  4. Add Weixin as a BridgeSpec row or explicitly hide it behind "proposed" in the wizard until registry and validation support land.
  5. Rework cloud bundles so placement is second and reachability is first.
  6. Add Funnel/webhook only as an advanced path with explicit public-exposure acknowledgement.
  7. Implement --apply last, after generate-only output is reviewed.

Command verification ledger

Verified against CodeWhale code/docs in this worktree:

  • codewhale app-server --http --host 127.0.0.1 --port 7878 --auth-token TOKEN
  • codewhale app-server --mobile --host 127.0.0.1 --port 7878 --auth-token TOKEN
  • codewhale doctor --json
  • curl /health and authenticated curl /v1/runtime/info
  • npm run validate:config for Telegram and Feishu bridges
  • npm run check for the Weixin bridge
  • Existing remote-setup generate-only flags listed above

Marked proposed or external:

  • codewhale remote-setup --access ... and access-path registry
  • first-class Tailscale, localhost, Weixin, and Funnel choices in the wizard
  • --apply execution
  • Tailscale CLI commands (tailscale serve ..., tailscale funnel ...) are external Tailscale commands. They are the intended RFC examples, but they are not CodeWhale CLI flags.