返回 CodeWhale
REMOTE_SETUP_DESIGN.md
根目录 / docs / rfcs / REMOTE_SETUP_DESIGN.md
1 # `codewhale remote-setup` - Tailscale-first design
2
3 Status: **design / revision**. This RFC revises the earlier cloud-first
4 `remote-setup` plan. Keep the accurate implementation work already present:
5 `codewhale remote-setup` exists today as a generate-only bundle wizard for
6 cloud plus chat bridge deployments, and `--apply` is still not implemented.
7
8 ## Goal
9
10 Give users a guided, education-forward way to reach a local-first CodeWhale
11 runtime from another surface without accidentally publishing their agent.
12
13 Default posture:
14
15 1. **Local-first by default.**
16 2. **Tailnet-private when remote.**
17 3. **Public only when explicitly chosen.**
18
19 The wizard should ask:
20
21 > How do you want to reach CodeWhale?
22
23 and offer these paths, in this order:
24
25 1. This machine only (localhost)
26 2. Private devices with Tailscale (**Recommended**)
27 3. Telegram bot
28 4. Feishu/Lark bot
29 5. Weixin personal bridge
30 6. Public webhook / Funnel (**Advanced**)
31
32 The recommended remote answer is Tailscale Serve with the backend still bound
33 to `127.0.0.1`. Tailscale supplies device identity and encrypted transport.
34 Tailscale Funnel is public internet exposure and must stay advanced.
35
36 ## Current implementation checkpoint
37
38 ### `/setup` → Remote runtime step (#3409)
39
40 The setup wizard's `RemoteRuntime` card presents exactly four modes, each with a
41 status derived from observable state — never from intent:
42
43 | mode | status source |
44 |------|---------------|
45 | this machine only | always `ready`; nothing is exposed and nothing to configure |
46 | runtime API | presence (never value) of `CODEWHALE_RUNTIME_TOKEN` / `DEEPSEEK_RUNTIME_TOKEN` |
47 | phone on your network | `disabled` unless `CODEWHALE_RUNTIME_HOST` is set, because the shipped unit binds loopback |
48 | chat app | per-bridge `secret_keys` presence from `remote_setup::registry` |
49
50 Contract for that step:
51
52 - **Secret values are never read or rendered.** Only variable *names* and
53 set/unset are used, so a hostile token cannot reach the UI or the persisted
54 step result.
55 - **`R` renders a plan preview in memory** through `remote_setup::bundle::render_bundle`
56 with every secret replaced by `<redacted>`. It never calls `write_bundle`,
57 never runs a provisioning command, and creates no files.
58 - **Missing tokens/config are `NeedsAction`, never blocking.** Local-only is the
59 default and settles the step in one key, so a user who wants no remote access
60 is finished immediately. `/setup`'s report and `doctor` inherit the recorded
61 status verbatim.
62
63 Verified against the codebase:
64
65 - `codewhale app-server --http` is the canonical HTTP/SSE runtime API entrypoint.
66 It delegates to the mature `serve --http` implementation.
67 - `codewhale app-server --mobile` is real and serves the phone control page at
68 `/mobile`.
69 - `--host`, `--port`, `--workers`, `--auth-token`, `--insecure-no-auth`, and
70 repeatable `--cors-origin` exist on `app-server --http` / `--mobile`.
71 - `--mobile` without `--host` binds to `0.0.0.0` by design. Use
72 `--host 127.0.0.1` when putting Tailscale in front of the runtime.
73 - `/health` and `/v1/runtime/info` are public bootstrap/supervision endpoints.
74 `/v1/*` control routes require the runtime bearer token unless auth is
75 explicitly disabled on a trusted loopback bind.
76 - `codewhale doctor --json` exists as the machine-readable local diagnostic.
77 - `codewhale remote-setup` exists, but today it is generate-only. Its current
78 matrix is cloud target (`lighthouse`, `azure`, `digitalocean`) x bridge
79 (`feishu`, `telegram`) x provider registry. It does **not** yet model
80 localhost, Tailscale, Weixin, or Funnel as first-class choices.
81 - Telegram and Feishu bridge validators exist as `npm run validate:config`.
82 Weixin currently has `npm run check`, but no validate-config script.
83
84 Accuracy note for the Tailscale recommendation: the requested setup uses
85 `app-server --http`, but the current runtime serves `/mobile` only in mobile
86 mode. This RFC keeps the target command shape for the recommended loopback
87 runtime, and documents the verified current-binary variant when the mobile page
88 is required:
89
90 ```bash
91 # Runtime API only, verified:
92 codewhale app-server --http --host 127.0.0.1 --port 7878 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
93
94 # Runtime API plus /mobile, verified:
95 codewhale app-server --mobile --host 127.0.0.1 --port 7878 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
96 ```
97
98 ## Common runtime base
99
100 Every path starts from the same local runtime trust boundary.
101
102 ```bash
103 CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
104 export CODEWHALE_RUNTIME_TOKEN
105
106 codewhale app-server --http \
107 --host 127.0.0.1 \
108 --port 7878 \
109 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
110 ```
111
112 For the current binary, use `--mobile --host 127.0.0.1` instead of `--http` if
113 the path needs the built-in `/mobile` page.
114
115 Doctor-style local validation:
116
117 ```bash
118 codewhale doctor --json
119 curl -fsS http://127.0.0.1:7878/health
120 curl -fsS \
121 -H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
122 http://127.0.0.1:7878/v1/runtime/info
123 ```
124
125 Runtime mental model:
126
127 - Exposed by CodeWhale: only the address it binds. The recommended bind is
128 `127.0.0.1:7878`.
129 - Auth token: `CODEWHALE_RUNTIME_TOKEN`, passed as `Authorization: Bearer ...`
130 by clients and bridges. Legacy `DEEPSEEK_RUNTIME_TOKEN` remains a fallback.
131 - Provider secrets: stay in runtime configuration, not in bridge env files.
132 - Bridge secrets: stay in transport-specific env files.
133
134 ## Guided flow
135
136 ### 1. This machine only (localhost)
137
138 Use this when the TUI, SDK, browser, or local script runs on the same machine as
139 CodeWhale.
140
141 Setup:
142
143 ```bash
144 CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
145 export CODEWHALE_RUNTIME_TOKEN
146
147 codewhale app-server --http \
148 --host 127.0.0.1 \
149 --port 7878 \
150 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
151 ```
152
153 Env template:
154
155 ```env
156 CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
157 CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>
158 ```
159
160 Validation:
161
162 ```bash
163 codewhale doctor --json
164 curl -fsS http://127.0.0.1:7878/health
165 curl -fsS \
166 -H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
167 http://127.0.0.1:7878/v1/runtime/info
168 ```
169
170 Trust boundary:
171
172 - Exposed: loopback only.
173 - Not exposed: LAN, tailnet, or public internet.
174 - Token used: `CODEWHALE_RUNTIME_TOKEN` for control routes; local `/health` and
175 `/v1/runtime/info` are public bootstrap endpoints.
176
177 ### 2. Private devices with Tailscale (Recommended)
178
179 Use this to reach CodeWhale from your phone or laptop without opening a LAN or
180 public port. Tailscale authenticates devices in your tailnet; CodeWhale still
181 binds to localhost.
182
183 Target setup to feature in the wizard:
184
185 ```bash
186 CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
187 export CODEWHALE_RUNTIME_TOKEN
188
189 codewhale app-server --http \
190 --host 127.0.0.1 \
191 --port 7878 \
192 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
193
194 tailscale serve --bg --https=443 localhost:7878
195 ```
196
197 Then open the Tailscale Serve URL from a phone or laptop in the same tailnet.
198 For the current binary's mobile page, start CodeWhale with the verified mobile
199 variant:
200
201 ```bash
202 codewhale app-server --mobile \
203 --host 127.0.0.1 \
204 --port 7878 \
205 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
206 ```
207
208 Then open (put the token in the URL **fragment**, not a query param — the
209 `/mobile` page reads it from `location.hash`, and a fragment is never sent to
210 the Tailscale serving layer or to any proxy log):
211
212 ```text
213 https://<machine>.<tailnet>.ts.net/mobile#token=<CODEWHALE_RUNTIME_TOKEN>
214 ```
215
216 Env template:
217
218 ```env
219 CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
220 CODEWHALE_RUNTIME_TOKEN=<openssl-rand-hex-32>
221 TAILSCALE_SERVE_TARGET=localhost:7878
222 TAILSCALE_SERVE_URL=https://<machine>.<tailnet>.ts.net
223 ```
224
225 Validation:
226
227 ```bash
228 codewhale doctor --json
229 curl -fsS http://127.0.0.1:7878/health
230 curl -fsS https://<machine>.<tailnet>.ts.net/health
231 curl -fsS \
232 -H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
233 https://<machine>.<tailnet>.ts.net/v1/runtime/info
234 tailscale serve status
235 ```
236
237 Trust boundary:
238
239 - Exposed: an HTTPS endpoint reachable by devices authorized in your tailnet.
240 - Not exposed: the raw CodeWhale listener; it stays on `127.0.0.1`.
241 - Token used: Tailscale identity gates network reachability; CodeWhale still
242 uses `CODEWHALE_RUNTIME_TOKEN` for runtime control.
243 - Caveat: Tailscale Serve is private to the tailnet. Tailscale Funnel is public
244 internet exposure and belongs only in the advanced path below.
245
246 ### 3. Telegram bot
247
248 Use this when a Telegram DM should control a local CodeWhale runtime. The bridge
249 uses Telegram Bot API long polling, so it does not require a public webhook URL
250 or inbound port.
251
252 Setup:
253
254 ```bash
255 CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
256 export CODEWHALE_RUNTIME_TOKEN
257
258 codewhale app-server --http \
259 --host 127.0.0.1 \
260 --port 7878 \
261 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
262
263 cd integrations/telegram-bridge
264 npm install --omit=dev
265 cp .env.example .env
266 $EDITOR .env
267 npm run validate:config -- \
268 --env .env \
269 --workspace-root "$PWD/../.." \
270 --check-filesystem
271 npm start
272 ```
273
274 Env template:
275
276 ```env
277 TELEGRAM_BOT_TOKEN=replace-with-botfather-token
278
279 CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
280 CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>
281 CODEWHALE_WORKSPACE=/path/to/workspace
282 # Optional override; leave blank to inherit the runtime's configured provider/model.
283 CODEWHALE_MODEL=
284 CODEWHALE_MODE=agent
285 CODEWHALE_ALLOW_SHELL=true # grants shell execution from the bridge; set false for text-only chat
286 CODEWHALE_TRUST_MODE=false
287 CODEWHALE_AUTO_APPROVE=false
288
289 TELEGRAM_CHAT_ALLOWLIST=
290 TELEGRAM_ALLOW_UNLISTED=false
291 TELEGRAM_ALLOW_GROUPS=false
292 ```
293
294 First pairing:
295
296 ```bash
297 # Temporarily in .env:
298 TELEGRAM_ALLOW_UNLISTED=true
299 ```
300
301 DM the bot `/status`, copy the returned `chat_id` or `user_id` into
302 `TELEGRAM_CHAT_ALLOWLIST`, then set `TELEGRAM_ALLOW_UNLISTED=false` and restart
303 the bridge.
304
305 Validation:
306
307 ```bash
308 codewhale doctor --json
309 curl -fsS http://127.0.0.1:7878/health
310 npm run validate:config -- \
311 --env .env \
312 --workspace-root "$PWD/../.." \
313 --check-filesystem
314 ```
315
316 Trust boundary:
317
318 - Exposed: no inbound CodeWhale port. Telegram sees messages sent to the bot.
319 - Not exposed: CodeWhale remains on `127.0.0.1`; provider keys stay in the
320 runtime env, not the Telegram env.
321 - Tokens used: `TELEGRAM_BOT_TOKEN` for Telegram, `CODEWHALE_RUNTIME_TOKEN` for
322 bridge-to-runtime calls, and `TELEGRAM_CHAT_ALLOWLIST` for user/chat gating.
323 - Caveat: direct messages are the intended MVP control surface. Group control is
324 off unless `TELEGRAM_ALLOW_GROUPS=true`.
325
326 ### 4. Feishu/Lark bot
327
328 Use this when a Feishu or Lark chat should control the local runtime. The bridge
329 uses the Lark/Feishu long-connection SDK, so the first version does not need a
330 public webhook URL.
331
332 Setup:
333
334 ```bash
335 CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
336 export CODEWHALE_RUNTIME_TOKEN
337
338 codewhale app-server --http \
339 --host 127.0.0.1 \
340 --port 7878 \
341 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
342
343 cd integrations/feishu-bridge
344 npm install --omit=dev
345 cp .env.example .env
346 $EDITOR .env
347 npm run validate:config -- \
348 --env .env \
349 --workspace-root "$PWD/../.." \
350 --check-filesystem
351 npm start
352 ```
353
354 Env template:
355
356 ```env
357 FEISHU_APP_ID=cli_xxxxxxxxxxxxxxxx
358 FEISHU_APP_SECRET=replace-with-app-secret
359 FEISHU_DOMAIN=feishu # international Lark users: set to "lark"
360
361 CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
362 CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>
363 CODEWHALE_WORKSPACE=/path/to/workspace
364 # Optional override; leave blank to inherit the runtime's configured provider/model.
365 CODEWHALE_MODEL=
366 CODEWHALE_MODE=agent
367 CODEWHALE_ALLOW_SHELL=true # grants shell execution from the bridge; set false for text-only chat
368 CODEWHALE_TRUST_MODE=false
369 CODEWHALE_AUTO_APPROVE=false
370
371 CODEWHALE_CHAT_ALLOWLIST=
372 CODEWHALE_ALLOW_UNLISTED=false
373 FEISHU_ALLOW_GROUPS=false
374 ```
375
376 First pairing:
377
378 Temporarily set `CODEWHALE_ALLOW_UNLISTED=true`, message the app once, copy the
379 logged open id into `CODEWHALE_CHAT_ALLOWLIST`, then set
380 `CODEWHALE_ALLOW_UNLISTED=false` and restart the bridge.
381
382 Validation:
383
384 ```bash
385 codewhale doctor --json
386 curl -fsS http://127.0.0.1:7878/health
387 npm run validate:config -- \
388 --env .env \
389 --workspace-root "$PWD/../.." \
390 --check-filesystem
391 ```
392
393 Trust boundary:
394
395 - Exposed: no inbound CodeWhale port. Feishu/Lark sees messages sent to the app.
396 - Not exposed: CodeWhale remains on `127.0.0.1`; provider keys stay in runtime
397 config.
398 - Tokens used: `FEISHU_APP_ID` / `FEISHU_APP_SECRET` for the platform,
399 `CODEWHALE_RUNTIME_TOKEN` for bridge-to-runtime calls, and
400 `CODEWHALE_CHAT_ALLOWLIST` for chat gating.
401 - Caveat: group control is off unless explicitly enabled.
402
403 ### 5. Weixin personal bridge
404
405 Use this when a personal Weixin account should control the local runtime by QR
406 login. This is not a public account webhook. The bridge initiates long polling
407 and does not need a public port.
408
409 Setup:
410
411 ```bash
412 CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
413 export CODEWHALE_RUNTIME_TOKEN
414
415 codewhale app-server --http \
416 --host 127.0.0.1 \
417 --port 7878 \
418 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
419
420 cd integrations/weixin-bridge
421 npm install --omit=dev
422 cp .env.example .env
423 $EDITOR .env
424 npm run check
425 npm start
426 ```
427
428 Env template:
429
430 ```env
431 CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
432 CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>
433 CODEWHALE_WORKSPACE=/path/to/workspace
434 # Optional override; leave blank to inherit the runtime's configured provider/model.
435 CODEWHALE_MODEL=
436 CODEWHALE_MODE=agent
437 CODEWHALE_ALLOW_SHELL=true # grants shell execution from the bridge; set false for text-only chat
438 CODEWHALE_TRUST_MODE=false
439 CODEWHALE_AUTO_APPROVE=false
440
441 WEXIN_CHAT_ALLOWLIST=
442 WEXIN_ALLOW_UNLISTED=false
443 WEXIN_STATE_DIR=/var/lib/codewhale-weixin-bot-bridge
444 ```
445
446 First pairing:
447
448 Set `WEXIN_ALLOW_UNLISTED=true`, start the bridge, scan the QR code, send
449 `/status`, copy the returned `user_id` into `WEXIN_CHAT_ALLOWLIST`, then set
450 `WEXIN_ALLOW_UNLISTED=false` and restart the bridge.
451
452 Validation:
453
454 ```bash
455 codewhale doctor --json
456 curl -fsS http://127.0.0.1:7878/health
457 npm run check
458 ```
459
460 Trust boundary:
461
462 - Exposed: no inbound CodeWhale port. The personal Weixin session and the
463 bridge state directory become sensitive local state.
464 - Not exposed: CodeWhale remains on `127.0.0.1`; provider keys stay in runtime
465 config.
466 - Tokens used: the scanned Weixin login/session state for platform access,
467 `CODEWHALE_RUNTIME_TOKEN` for bridge-to-runtime calls, and
468 `WEXIN_CHAT_ALLOWLIST` for user gating.
469 - Caveat: this is a personal-account bridge. Treat the host and state directory
470 like a logged-in phone session.
471
472 ### 6. Public webhook / Funnel (Advanced)
473
474 Use this only when the user explicitly chooses public internet reachability,
475 understands that the URL can be reached outside the tailnet, and has a reason
476 that Tailscale Serve or long polling cannot satisfy.
477
478 Preferred advanced pattern:
479
480 ```bash
481 CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
482 export CODEWHALE_RUNTIME_TOKEN
483
484 codewhale app-server --mobile \
485 --host 127.0.0.1 \
486 --port 7878 \
487 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
488
489 tailscale funnel --bg --https=443 localhost:7878
490 ```
491
492 Env template:
493
494 ```env
495 CODEWHALE_RUNTIME_URL=https://<public-name>
496 CODEWHALE_RUNTIME_TOKEN=<openssl-rand-hex-32>
497 PUBLIC_EXPOSURE_ACK=true
498 ```
499
500 Validation:
501
502 ```bash
503 codewhale doctor --json
504 curl -fsS http://127.0.0.1:7878/health
505 curl -fsS https://<public-name>/health
506 curl -fsS \
507 -H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
508 https://<public-name>/v1/runtime/info
509 tailscale funnel status
510 ```
511
512 Trust boundary:
513
514 - Exposed: a public HTTPS endpoint, not just your tailnet.
515 - Not exposed by CodeWhale directly: the backend still binds to `127.0.0.1`,
516 but the fronting layer makes selected routes reachable from the internet.
517 - Token used: `CODEWHALE_RUNTIME_TOKEN` remains mandatory for control routes.
518 - Caveat: public does not mean safe. Do not use `--insecure-no-auth`, do not bind
519 CodeWhale to `0.0.0.0`, and do not call this the default.
520
521 ## Cloud/VPS posture
522
523 Cloud/VPS is a placement choice, not a trust model. The old RFC's cloud work is
524 still useful, but it should sit behind the same reachability choices:
525
526 - A VPS can run the runtime bound to `127.0.0.1`.
527 - Recommended remote access from personal devices is still Tailscale Serve.
528 - Bot bridges should use long polling / long connection where available, keeping
529 the runtime localhost-only on the host.
530 - SSH tunnels remain acceptable for ad hoc validation:
531
532 ```bash
533 ssh -L 7878:127.0.0.1:7878 <host>
534 ```
535
536 Public inbound listeners, public webhooks, and Tailscale Funnel are advanced
537 choices, not the default cloud path.
538
539 ## Prior art: Hermes Agent (reference only - do not copy)
540
541 Nous Research's Hermes Agent validates the table-driven part of this design.
542 Use it for ideas; keep CodeWhale's style: Rust core, local runtime, zero-dep
543 Node bridges where possible, and plain-text replies.
544
545 - `gateway/platform_registry.py` maps to our `BridgeSpec` / access-path
546 registry: one row per platform, with setup hints, required env, validation,
547 and adapter factory.
548 - `gateway/pairing.py` maps to our allowlist / first-pairing flow.
549
550 Telegram hardening carried forward from the original RFC:
551
552 | Edge case | In Hermes | In our Telegram bridge |
553 |---|---|---|
554 | 409 polling conflict | `_looks_like_polling_conflict` | done - poll loop backs off and warns |
555 | 429 `retry_after` | rate-limit handling | done - `telegramApi` honors `parameters.retry_after` |
556 | Forum General topic id handling | send/typing split | done - omit `message_thread_id` when id is 1 on send |
557 | Stale reply anchor after restart | retry without anchor | sidestepped - no `reply_to_message_id` |
558 | Network/connect timeout retry | network error detection | partial - generic poll-loop backoff |
559 | Text batching / progress edit | progress-edit tests | deferred - plain periodic chunks |
560 | MarkdownV2 escaping | escaping helpers | deferred - plain text |
561 | Webhook mode | webhook adapter | out of default scope - long polling first |
562
563 ## Design principle: table-driven, like `ProviderSpec`
564
565 The provider registry is the model to preserve: adding a provider is one row.
566 Apply the same idea to access paths, bridges, and cloud placements so the matrix
567 grows by data.
568
569 ```text
570 AccessPath x Placement x BridgeSpec + ProviderSpec
571 ---------- --------- ---------- ------------
572 localhost local none deepseek / openai / ...
573 tailscale local/vps none provider lives in runtime.env
574 telegram local/vps telegram bridge is pure transport
575 feishu local/vps feishu bridge is pure transport
576 weixin local/vps weixin bridge is pure transport
577 funnel local/vps optional explicit public exposure
578 ```
579
580 Clean separation:
581
582 - **Provider = runtime env.** The runtime resolves provider/model/API key from
583 `CODEWHALE_PROVIDER`, provider key vars, and the provider registry. Bridges do
584 not need provider keys.
585 - **Access path = reachability.** Localhost, Tailscale Serve, chat long polling,
586 and Funnel are separate choices with different trust boundaries.
587 - **Bridge = transport.** A chat bridge forwards allowed chat messages to
588 `http://127.0.0.1:7878` with `CODEWHALE_RUNTIME_TOKEN`.
589 - **Cloud = where it runs and where secrets live.** It is not permission to
590 open port 7878.
591
592 ## Proposed command surface
593
594 Current flags are verified for the generate-only cloud/bridge wizard:
595
596 | Flag | Current status |
597 |---|---|
598 | `--cloud <lighthouse|azure|digitalocean>` | verified |
599 | `--bridge <telegram|feishu>` | verified |
600 | `--provider <slug>` | verified, provider registry-backed |
601 | `--out <dir>` | verified |
602 | `--generate-only` | verified |
603 | `--apply` | verified flag, but not implemented |
604 | `--yes` | verified flag |
605 | `--non-interactive` | verified flag |
606
607 Proposed Tailscale-first revision:
608
609 | Flag | Meaning |
610 |---|---|
611 | `--access <localhost|tailscale|telegram|feishu|weixin|funnel>` | Skip the reachability prompt. |
612 | `--placement <local|vps|lighthouse|azure|digitalocean>` | Where the runtime runs; default local. |
613 | `--bridge <telegram|feishu|weixin>` | Optional when `--access` implies a bridge. |
614 | `--provider <slug>` | Provider slug; validated against the existing provider registry. |
615 | `--out <dir>` | Bundle output dir. |
616 | `--generate-only` | Emit commands/env/runbook, do not provision. Default. |
617 | `--apply` | Future cloud CLI provisioning, behind confirmation. Still not implemented. |
618 | `--yes` | Skip final confirmation gates where safe for CI/non-interactive use. |
619 | `--non-interactive` | Fail instead of prompting for missing required values. |
620
621 The first prompt should be the reachability question, not the cloud question.
622 Tailscale should be visually marked as recommended.
623
624 ## Generated bundle
625
626 The current bundle model stays useful. Extend it so the generated runbook is
627 access-path-first.
628
629 Files:
630
631 - `runtime.env` - provider and runtime config:
632
633 ```env
634 CODEWHALE_PROVIDER=openai
635 OPENAI_API_KEY=replace-with-provider-key
636 # Optional override; leave blank to inherit the runtime's configured provider/model.
637 CODEWHALE_MODEL=
638 CODEWHALE_RUNTIME_TOKEN=<random>
639 CODEWHALE_RUNTIME_PORT=7878
640 CODEWHALE_RUNTIME_WORKERS=2
641 RUST_LOG=info
642 ```
643
644 - `<bridge>.env` - transport only when a bridge is selected:
645
646 ```env
647 CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
648 CODEWHALE_RUNTIME_TOKEN=<same random token>
649 CODEWHALE_WORKSPACE=/opt/whalebro
650 # Optional override; leave blank to inherit the runtime's configured provider/model.
651 CODEWHALE_MODEL=
652 CODEWHALE_MODE=agent
653 CODEWHALE_ALLOW_SHELL=true # grants shell execution from the bridge; set false for text-only chat
654 CODEWHALE_TRUST_MODE=false
655 CODEWHALE_AUTO_APPROVE=false
656 ```
657
658 - `codewhale-runtime.service`
659 - optional `codewhale-<bridge>.service`
660 - optional cloud artifacts: `cloud-init.yaml`, `provision.sh`, `cnb.yml`, or
661 cloud-specific runbook steps
662 - `RUNBOOK.md` with:
663 - exact setup commands
664 - env template
665 - doctor-style validation
666 - first-pairing steps for bridges
667 - trust-boundary summary
668 - explicit "public exposure acknowledged" section for Funnel/webhook modes
669
670 ## Auto-provision
671
672 Preserve the original safety model:
673
674 - `--generate-only` is the default.
675 - `--apply` is explicit and is not implemented today.
676 - Every command is rendered before execution.
677 - Secrets are not passed through shell history or argv.
678 - Cloud CLIs are placement helpers, not permission to open runtime ports.
679
680 Existing cloud target design remains accurate:
681
682 - Tencent Lighthouse: native plus systemd, env-file secrets, CNB-oriented plan.
683 - Azure VM: Docker image plus Key Vault, managed identity at boot.
684 - DigitalOcean Droplet: native plus systemd, env-file secrets, `doctl` plan.
685
686 All cloud plans should bind CodeWhale to `127.0.0.1` and then layer one of the
687 reachability paths above.
688
689 ## Namespace migration: `DEEPSEEK_*` to `CODEWHALE_*`
690
691 Carry forward the convention already used in code: read `CODEWHALE_X` first,
692 fall back to `DEEPSEEK_X` where compatibility is needed.
693
694 Touch list from the original RFC remains valid:
695
696 1. Bridges: read `CODEWHALE_X ?? DEEPSEEK_X` for runtime URL/token, workspace,
697 model, mode, shell/trust/approval flags, allowlists, and timeouts. Templates
698 should emit `CODEWHALE_*`.
699 2. Deploy units: prefer `/etc/codewhale/*.env`; keep legacy path reads only for
700 compatibility where needed.
701 3. `.env.example` files and `config.example.toml`: lead with `CODEWHALE_*`,
702 document legacy aliases.
703 4. Drop DeepSeek-shaped defaults in bridge templates except where DeepSeek is
704 explicitly the chosen provider. Provider choice belongs in `runtime.env`.
705
706 ## Tests
707
708 Existing bundle tests should stay:
709
710 - Every cloud / bridge / provider triple renders.
711 - Runtime and bridge env files share the same `CODEWHALE_RUNTIME_TOKEN`.
712 - Env files lead with `CODEWHALE_*`.
713 - Generated runbooks are non-empty and list the provision plan.
714 - Provision plans are command data and are not executed in tests.
715
716 New tests for this revision:
717
718 - Every `AccessPath` row has setup commands, env template, validation commands,
719 and trust-boundary copy.
720 - Tailscale is the recommended remote path in prompt ordering.
721 - Funnel/webhook mode requires an explicit advanced/public acknowledgement.
722 - `/mobile` docs use `app-server --mobile --host 127.0.0.1` for current binary
723 behavior, or clearly mark any `--http` plus `/mobile` path as proposed.
724 - Weixin can be documented before it is in the `remote-setup` registry, but the
725 wizard must mark it proposed until a `BridgeSpec` row and validation story
726 exist.
727
728 ## Suggested sequencing
729
730 1. Revise the RFC and runbook copy to be Tailscale-first.
731 2. Add an access-path registry above the existing cloud/bridge/provider tables.
732 3. Add localhost and Tailscale generate-only bundles.
733 4. Add Weixin as a `BridgeSpec` row or explicitly hide it behind "proposed" in
734 the wizard until registry and validation support land.
735 5. Rework cloud bundles so placement is second and reachability is first.
736 6. Add Funnel/webhook only as an advanced path with explicit public-exposure
737 acknowledgement.
738 7. Implement `--apply` last, after generate-only output is reviewed.
739
740 ## Command verification ledger
741
742 Verified against CodeWhale code/docs in this worktree:
743
744 - `codewhale app-server --http --host 127.0.0.1 --port 7878 --auth-token TOKEN`
745 - `codewhale app-server --mobile --host 127.0.0.1 --port 7878 --auth-token TOKEN`
746 - `codewhale doctor --json`
747 - `curl /health` and authenticated `curl /v1/runtime/info`
748 - `npm run validate:config` for Telegram and Feishu bridges
749 - `npm run check` for the Weixin bridge
750 - Existing `remote-setup` generate-only flags listed above
751
752 Marked proposed or external:
753
754 - `codewhale remote-setup --access ...` and access-path registry
755 - first-class Tailscale, localhost, Weixin, and Funnel choices in the wizard
756 - `--apply` execution
757 - Tailscale CLI commands (`tailscale serve ...`, `tailscale funnel ...`) are
758 external Tailscale commands. They are the intended RFC examples, but they are
759 not CodeWhale CLI flags.
760
760 lines MARKDOWN