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