@bongos/core 1.19.1073 → 1.19.1075
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +404 -549
- package/.claude/skills/feedback/SKILL.md +2 -2
- package/bin/bongos.js +1 -3
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -60
- package/clients/bongos-client/index.cjs +4 -60
- package/clients/bongos-client/index.d.ts +5 -90
- package/clients/bongos-client/index.mjs +4 -60
- package/config/branding.neutral.json +1 -5
- package/config/modules.neutral.json +2 -3
- package/config/scheduled-routines.json +0 -2
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
- package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
- package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
- package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
- package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
- package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +175 -1665
- package/docs/api-reference.md +9 -41
- package/docs/architecture.md +16 -1
- package/docs/branding-contract.md +0 -3
- package/docs/copy-inventory.md +454 -479
- package/docs/copy-registry.json +746 -978
- package/docs/file-map.md +5 -34
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +13 -1
- package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
- package/docs/onboarding/diagrams/README.md +3 -3
- package/docs/onboarding/diagrams/assertions.json +2 -22
- package/docs/onboarding/primer.md +9 -28
- package/docs/page-inventory.json +4 -1
- package/docs/page-readings.json +1032 -1077
- package/docs/recipes/render-pilot.md +29 -10
- package/migrations/core_257_module_store_registry.sql +87 -0
- package/migrations/core_258_drop_dev_box_tables.sql +55 -0
- package/modules/agents/module.json +1 -0
- package/modules/autonomy/module.json +1 -0
- package/modules/builder-settings/module.json +1 -0
- package/modules/builder-settings/render-prefs.js +4 -5
- package/modules/copy-desk/docx.js +78 -5
- package/modules/copy-desk/module.json +1 -0
- package/modules/copy-desk/routes/copy-desk.js +10 -10
- package/modules/copy-desk/tests/copy_docx.mjs +107 -1
- package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
- package/modules/discord/module.json +1 -0
- package/modules/discord/ship-broadcast.js +3 -3
- package/modules/economy/credits.js +2 -2
- package/modules/economy/module.json +1 -0
- package/modules/economy/routes/credits.js +1 -2
- package/modules/government/catalog.js +13 -17
- package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
- package/modules/government/module.json +1 -0
- package/modules/government/protected-surfaces.json +3 -11
- package/modules/government/resolver.js +3 -46
- package/modules/government/routes/government.js +0 -2
- package/modules/government/session-scopes.js +43 -83
- package/modules/government/session-scopes.json +8 -18
- package/modules/grading/module.json +1 -0
- package/modules/hall-ui/module.json +1 -0
- package/modules/hall-ui/public/board-lib.js +2 -2
- package/modules/hall-ui/public/builders.js +29 -40
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/collab.js +1 -1
- package/modules/hall-ui/public/diagrams.html +3 -3
- package/modules/hall-ui/public/dom-utils.js +1 -1
- package/modules/hall-ui/public/gate.js +2 -2
- package/modules/hall-ui/public/gate.states.json +1 -1
- package/modules/hall-ui/public/hall-render.js +43 -79
- package/modules/hall-ui/public/index.html +5 -1
- package/modules/hall-ui/public/modules.js +1 -0
- package/modules/hall-ui/public/oversight.css +5 -5
- package/modules/hall-ui/public/palette.js +0 -11
- package/modules/hall-ui/public/primer.js +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings-sessions.js +1 -1
- package/modules/hall-ui/public/settings.css +0 -1
- package/modules/hall-ui/public/settings.html +4 -33
- package/modules/hall-ui/public/settings.js +3 -33
- package/modules/hall-ui/public/settings.states.json +2 -2
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +10 -13
- package/modules/hall-ui/public/task.html +4 -1
- package/modules/hall-ui/public/task.js +36 -1
- package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
- package/modules/hall-ui/public/tweak-editor.css +16 -0
- package/modules/hall-ui/public/tweak-editor.html +26 -3
- package/modules/hall-ui/public/tweak-editor.js +32 -1
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/hall-ui/records/tweak-editor.md +1 -0
- package/modules/ideas/module.json +1 -0
- package/modules/ideas/projection.js +1 -1
- package/modules/lifecycle/db-claim-reads.js +1 -58
- package/modules/lifecycle/db-ship.js +4 -9
- package/modules/lifecycle/db.js +0 -4
- package/modules/lifecycle/github-push.js +12 -14
- package/modules/lifecycle/kickoff-checklist.js +0 -4
- package/modules/lifecycle/lifecycle.js +0 -8
- package/modules/lifecycle/module.json +1 -0
- package/modules/lifecycle/publish-reconciler.js +10 -10
- package/modules/lifecycle/routes/tasks.js +13 -15
- package/modules/lifecycle/ship-card.js +1 -1
- package/modules/lifecycle/task-visuals.js +2 -2
- package/modules/memory/module.json +1 -0
- package/modules/memory/routes/memory.js +2 -2
- package/modules/npm-release/module.json +1 -0
- package/modules/onboarding/module.json +1 -0
- package/modules/onboarding/onboarding-state.js +17 -36
- package/modules/onboarding/routes/access-requests.js +3 -3
- package/modules/platform-identity/module.json +1 -0
- package/modules/platform-identity/platform-identity.js +13 -5
- package/modules/platform-identity/routes/sso.js +5 -4
- package/modules/platform-identity/tests/platform-identity.mjs +17 -3
- package/modules/provisioning/capacity.js +1 -1
- package/modules/provisioning/module.json +1 -0
- package/modules/provisioning/provisioning.js +6 -5
- package/modules/provisioning/routes/provisioning.js +4 -4
- package/modules/provisioning/starter-bundles.js +5 -25
- package/modules/public-landing/module.json +1 -0
- package/modules/public-landing/public/assets/cosmos.css +1 -1
- package/modules/public-landing/public/contact.html +1 -1
- package/modules/public-landing/public/privacy.html +1 -1
- package/modules/public-landing/public/projects.html +101 -32
- package/modules/public-landing/public/projects.probes.json +2 -2
- package/modules/public-landing/public/projects.states.json +4 -3
- package/modules/public-landing/public/terms.html +1 -1
- package/modules/security/module.json +1 -0
- package/modules/sessions/db.js +1 -1
- package/modules/sessions/module.json +1 -0
- package/modules/specialities/module.json +1 -0
- package/modules/status-ui/module.json +1 -0
- package/modules/ui-design/module.json +1 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +84 -0
- package/scripts/gds/artifact-format.js +203 -0
- package/scripts/gds/artifact-staleness.js +2 -2
- package/scripts/gds/audit-authorship.sh +1 -13
- package/scripts/gds/backfill-task-rewards.js +5 -5
- package/scripts/gds/claim.js +0 -15
- package/scripts/gds/claude-materialize.js +2 -2
- package/scripts/gds/cli-lib.js +15 -15
- package/scripts/gds/codemod-rename-src-gds.js +1 -1
- package/scripts/gds/context-pack.js +5 -7
- package/scripts/gds/diagram-facts.js +12 -23
- package/scripts/gds/do-api.js +13 -80
- package/scripts/gds/doc-cli-guard.js +2 -0
- package/scripts/gds/doctor.js +4 -4
- package/scripts/gds/feedback-latest.js +139 -9
- package/scripts/gds/fitness-checks-identity.js +2 -7
- package/scripts/gds/fitness-checks-packaging.js +4 -4
- package/scripts/gds/fitness-checks-write-validation.js +4 -0
- package/scripts/gds/fitness-ratchets.js +5 -5
- package/scripts/gds/fitness.js +10 -10
- package/scripts/gds/gen-api-client.js +4 -4
- package/scripts/gds/gen-api-docs.js +1 -1
- package/scripts/gds/gen-diagrams.js +49 -71
- package/scripts/gds/gen-repo-map.js +13 -14
- package/scripts/gds/gen-session-index.js +8 -8
- package/scripts/gds/http-api-client.js +12 -19
- package/scripts/gds/init.js +1 -1
- package/scripts/gds/leak-scan-allowlist.js +1 -1
- package/scripts/gds/lint-infra-exec.js +1 -1
- package/scripts/gds/local-preview-lib.js +8 -39
- package/scripts/gds/local-preview.js +2 -15
- package/scripts/gds/migration-namespace.js +3 -3
- package/scripts/gds/module-artifact.js +212 -0
- package/scripts/gds/module.js +95 -6
- package/scripts/gds/package-core.js +5 -116
- package/scripts/gds/plain-cards.js +1 -1
- package/scripts/gds/provision-config.js +2 -2
- package/scripts/gds/provision-net.js +2 -3
- package/scripts/gds/provision.js +9 -9
- package/scripts/gds/publish-manifest.js +1 -2
- package/scripts/gds/push-path-brief.js +10 -10
- package/scripts/gds/regen-instance-docs.js +1 -1
- package/scripts/gds/rename-history-check.js +1 -1
- package/scripts/gds/run-unit-tests.js +2 -16
- package/scripts/gds/sandbox-stage.js +39 -341
- package/scripts/gds/session-digest-build.js +2 -2
- package/scripts/gds/ship-deploy-target.js +13 -13
- package/scripts/gds/ship-flow.js +7 -7
- package/scripts/gds/ship-land.js +9 -10
- package/scripts/gds/ship-merge.js +4 -4
- package/scripts/gds/ship-preflight-steps.js +4 -4
- package/scripts/gds/ship-regen.js +15 -49
- package/scripts/gds/ship.js +13 -33
- package/scripts/gds/skill-preflight.js +9 -8
- package/scripts/gds/start.js +0 -15
- package/scripts/hall-preview/README.md +5 -5
- package/scripts/hall-preview/server.js +6 -6
- package/scripts/render-diagrams.sh +1 -1
- package/src/bongos/api-errors.js +1 -1
- package/src/bongos/api-prefix.js +4 -4
- package/src/bongos/auth-github.js +2 -3
- package/src/bongos/auth.js +51 -126
- package/src/bongos/db-kernel.js +2 -3
- package/src/bongos/db.js +5 -28
- package/src/bongos/module-scope-map.js +14 -29
- package/src/bongos/module-store.js +141 -0
- package/src/bongos/platform-visibility-gate.js +1 -1
- package/src/bongos/route-rank-check.js +3 -16
- package/src/bongos/routes/auth.js +0 -116
- package/src/bongos/routes/builders.js +5 -15
- package/src/bongos/routes/instance.js +8 -14
- package/src/bongos/routes/me.js +7 -23
- package/src/bongos/routes/modules.js +83 -0
- package/src/bongos/routes/security.js +3 -3
- package/src/bongos/routes.js +7 -2
- package/src/bongos/serve-internal.js +5 -6
- package/src/bongos-downloads.js +1 -6
- package/src/branding.js +2 -2
- package/src/build-info.js +4 -6
- package/src/instance-config.js +2 -2
- package/src/module-api.js +13 -6
- package/src/module-loader/catalog.js +3 -0
- package/src/module-loader/loader.js +4 -4
- package/src/module-loader/manifest-schema.js +16 -8
- package/src/module-seams.js +2 -3
- package/src/modules.js +42 -14
- package/tests/agents_spend_guard.mjs +1 -1
- package/tests/api_alias_caller_ratchet.mjs +1 -1
- package/tests/api_alias_exceptions.mjs +1 -1
- package/tests/api_client.mjs +1 -16
- package/tests/api_path_404.mjs +1 -1
- package/tests/auth_page_gate.mjs +10 -10
- package/tests/backfill_task_rewards.mjs +1 -1
- package/tests/canonical_profile_url.mjs +1 -3
- package/tests/claim_action.mjs +4 -1
- package/tests/claim_from_task_and_home.mjs +127 -0
- package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
- package/tests/cli_sessions.mjs +1 -1
- package/tests/consumer_layout_boot.mjs +2 -0
- package/tests/context_pack.mjs +13 -13
- package/tests/copy_desk_page_docx.mjs +33 -2
- package/tests/copy_inventory.mjs +7 -7
- package/tests/core_upgrade_runner.mjs +6 -6
- package/tests/credit_grant.mjs +2 -2
- package/tests/criterion_suggest.mjs +1 -1
- package/tests/deploy_divergence_line.mjs +11 -11
- package/tests/design_tokens_sync.mjs +3 -2
- package/tests/diagram_facts_offset.mjs +1 -1
- package/tests/do_api.mjs +106 -0
- package/tests/feedback_latest.mjs +122 -0
- package/tests/fitness.mjs +44 -42
- package/tests/fitness_ratchets.mjs +2 -2
- package/tests/gate_approvals.mjs +0 -1
- package/tests/github_push_land_proof.mjs +1 -1
- package/tests/go_live.mjs +3 -3
- package/tests/goal_advisory.mjs +3 -3
- package/tests/goal_suggest.mjs +3 -3
- package/tests/government_abuse_matrix.mjs +21 -19
- package/tests/government_ownership_scope.mjs +33 -35
- package/tests/government_parity.mjs +2 -2
- package/tests/government_protected_surfaces.mjs +56 -37
- package/tests/government_require_permission.mjs +3 -3
- package/tests/government_seed.mjs +37 -4
- package/tests/government_session_scope.mjs +92 -366
- package/tests/hall_audit.mjs +1 -1
- package/tests/hall_error_envelope.mjs +4 -0
- package/tests/hall_landing_boot.mjs +4 -1
- package/tests/hall_palette.mjs +1 -16
- package/tests/hall_record_world.mjs +6 -0
- package/tests/hall_tweak_editor.mjs +52 -1
- package/tests/helpers.mjs +1 -1
- package/tests/host_topology_skips.mjs +3 -4
- package/tests/html_comment_nesting.mjs +137 -0
- package/tests/infra_exec_paths.mjs +9 -10
- package/tests/init.mjs +4 -5
- package/tests/instance_manifest.mjs +18 -25
- package/tests/kickoff_checklist.mjs +1 -1
- package/tests/lib_sh_resolution.mjs +0 -221
- package/tests/local_preview.mjs +0 -12
- package/tests/migration_namespace.mjs +6 -6
- package/tests/module-scope-map.mjs +9 -9
- package/tests/module_api.mjs +2 -2
- package/tests/module_catalog.mjs +1 -1
- package/tests/module_cli.mjs +20 -20
- package/tests/module_contributions.mjs +6 -6
- package/tests/module_loader.mjs +6 -6
- package/tests/module_manifest.mjs +26 -17
- package/tests/module_route_rank.mjs +2 -2
- package/tests/module_store_publish.mjs +307 -0
- package/tests/module_store_publish_route.mjs +121 -0
- package/tests/module_store_registry_migration.mjs +70 -0
- package/tests/modules.mjs +56 -26
- package/tests/modules_route_wiring.mjs +1 -1
- package/tests/onboarding_route_signals.mjs +21 -31
- package/tests/onboarding_state.mjs +35 -49
- package/tests/paste_token_session_store.mjs +1 -1
- package/tests/permission_path.mjs +0 -3
- package/tests/platform_boot.mjs +1 -2
- package/tests/platform_visibility_gate.mjs +1 -1
- package/tests/profile_route.mjs +1 -6
- package/tests/project_modules_ui.mjs +17 -17
- package/tests/projects_hub.mjs +5 -5
- package/tests/projects_hub_app_step.mjs +138 -0
- package/tests/projects_hub_module_picker.mjs +77 -76
- package/tests/projects_hub_pre_uat.mjs +4 -5
- package/tests/provision.mjs +7 -7
- package/tests/provisioning_capacity.mjs +1 -1
- package/tests/provisioning_recommendations.mjs +2 -1
- package/tests/provisioning_starter_bundles.mjs +0 -22
- package/tests/publish_branch_route.mjs +11 -19
- package/tests/publish_manifest.mjs +2 -3
- package/tests/publish_reconciler.mjs +8 -8
- package/tests/push_path_brief.mjs +10 -9
- package/tests/rank_tier_single_source.mjs +1 -13
- package/tests/regrade_eligibility.mjs +1 -1
- package/tests/rename_history_restraint.mjs +1 -1
- package/tests/repo_map.mjs +12 -13
- package/tests/runner_drift.mjs +3 -21
- package/tests/sandbox_stage.mjs +57 -390
- package/tests/seam_wiring_guard.mjs +11 -11
- package/tests/search_isolation.mjs +3 -3
- package/tests/session_records.mjs +1 -1
- package/tests/session_rename_fallback.mjs +1 -22
- package/tests/session_start_freshness.mjs +3 -3
- package/tests/session_token_hash_db.mjs +1 -1
- package/tests/ship_card.mjs +1 -1
- package/tests/ship_ci_deploy.mjs +17 -17
- package/tests/ship_error_shape.mjs +1 -1
- package/tests/ship_premerge.mjs +1 -1
- package/tests/ship_resume.mjs +1 -1
- package/tests/skill_preflight.mjs +2 -2
- package/tests/skip_is_not_pass.mjs +1 -1
- package/tests/task_visual_slots.mjs +31 -0
- package/tests/terms_acceptance.mjs +2 -2
- package/tests/upgrade.mjs +4 -4
- package/tests/watch_sealed_floor.mjs +0 -1
- package/tests/wizard_draft_resume.mjs +2 -2
- package/tests/wizard_intent_resume.mjs +21 -10
- package/tests/wizard_preselect_why.mjs +18 -18
- package/docs/onboarding/browser-terminal-guide.md +0 -73
- package/docs/recipes/managed-settings-remote-control.md +0 -416
- package/modules/dev-box/CLAUDE.md +0 -15
- package/modules/dev-box/box-access.js +0 -623
- package/modules/dev-box/box-credential.js +0 -94
- package/modules/dev-box/box-onboard.js +0 -486
- package/modules/dev-box/boxes.js +0 -1038
- package/modules/dev-box/db.js +0 -39
- package/modules/dev-box/module.json +0 -19
- package/modules/dev-box/routes/box.js +0 -1223
- package/scripts/gds/box-auth-check.js +0 -160
- package/scripts/gds/box-infra.js +0 -321
- package/scripts/gds/box-sync.js +0 -216
- package/scripts/gds/box.js +0 -1332
- package/scripts/gds/cf-tunnel.js +0 -558
- package/scripts/gds/smoke-box.sh +0 -150
- package/scripts/gds/tree-preflight.js +0 -241
- package/src/bongos/app-pair.js +0 -172
- package/tests/app_pair.mjs +0 -168
- package/tests/box_access.mjs +0 -578
- package/tests/box_auth_check.mjs +0 -114
- package/tests/box_code_staleness.mjs +0 -129
- package/tests/box_connect_e2e.mjs +0 -196
- package/tests/box_cost.mjs +0 -70
- package/tests/box_credential.mjs +0 -125
- package/tests/box_dns_repoint.mjs +0 -153
- package/tests/box_env.mjs +0 -64
- package/tests/box_host_keys.mjs +0 -126
- package/tests/box_infra_resolve.mjs +0 -246
- package/tests/box_onboard.mjs +0 -317
- package/tests/box_scope_session.mjs +0 -119
- package/tests/box_sweep_docs.mjs +0 -119
- package/tests/box_sync.mjs +0 -55
- package/tests/box_sync_scope_report.mjs +0 -126
- package/tests/box_task_scope.mjs +0 -169
- package/tests/box_task_scope_pause.mjs +0 -66
- package/tests/box_terminal_ssrf.mjs +0 -96
- package/tests/boxes.mjs +0 -1585
- package/tests/cf_ruleset_preflight.mjs +0 -142
- package/tests/cf_tunnel.mjs +0 -496
- package/tests/docs_scrubber_damage.mjs +0 -111
- package/tests/module_scope_active_claims.mjs +0 -94
- package/tests/ship_api_client_pathspec.mjs +0 -100
- package/tests/tree_preflight.mjs +0 -243
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# Building from a Chromebook, iPad, or phone (the browser terminal)
|
|
2
|
-
|
|
3
|
-
*Plain-language how-to for builders on a device that can't run the Claude Code desktop app.*
|
|
4
|
-
|
|
5
|
-
Most builders run Claude Code in the **desktop app** (Mac or Windows). But you don't need it. If all you have is a **Chromebook, iPad, or phone** — anything with a web browser — you can still build, because your dev box serves its own **terminal in a browser tab**, and from there you switch to driving the box from the Claude app. This is the **Remote Control** path.
|
|
6
|
-
|
|
7
|
-
You never install anything and you never touch SSH. It's two things: open a link, then sign in.
|
|
8
|
-
|
|
9
|
-
> **On a Mac or Windows laptop?** You don't need this page — use the desktop app instead ([claude.com/download](https://claude.com/download)); it's the smoother path. This guide is only for browser-only devices.
|
|
10
|
-
|
|
11
|
-
## What you'll do, at a glance
|
|
12
|
-
|
|
13
|
-
1. Ask for your box and its terminal link (**`/builder-box`**).
|
|
14
|
-
2. Open the link and sign in.
|
|
15
|
-
3. Start Claude in the terminal and sign in once.
|
|
16
|
-
4. Switch to **Remote Control** so you can work from the Claude app.
|
|
17
|
-
|
|
18
|
-
That's it. Steps 1–3 are a one-time launchpad; after that you live in the Claude app.
|
|
19
|
-
|
|
20
|
-
## Step 1 — Get your box and the terminal link
|
|
21
|
-
|
|
22
|
-
In a Claude session, run **`/builder-box`**. It's your box's control panel — it will:
|
|
23
|
-
|
|
24
|
-
- **wake or create your box** hands-off if it isn't already running (you never wait on an operator), and
|
|
25
|
-
- give you your **terminal link** and a **one-time password** once the box is up.
|
|
26
|
-
|
|
27
|
-
If the box was asleep, give it about a minute to come up, then run `/builder-box` again to read the link. (Under the hood `/builder-box` uses the box's own web terminal, published to the hall — you don't need any of that detail; just run the command.)
|
|
28
|
-
|
|
29
|
-
## Step 2 — Open the link and sign in
|
|
30
|
-
|
|
31
|
-
1. Open the **terminal link** in any browser tab.
|
|
32
|
-
2. A small sign-in box appears. Enter:
|
|
33
|
-
- **Username:** `otb`
|
|
34
|
-
- **Password:** the one-time password `/builder-box` gave you.
|
|
35
|
-
3. You're now looking at a terminal running on your box, already in your project folder.
|
|
36
|
-
|
|
37
|
-
## Step 3 — Start Claude in the terminal
|
|
38
|
-
|
|
39
|
-
In that terminal, type:
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
claude
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
and press Enter. The first time, it walks you through a normal browser sign-in (and may ask you to trust the folder once — say yes). When it finishes, you're in a real, interactive Claude Code session on your box. This first sign-in is the whole reason the browser terminal exists: a real person at a real browser can complete the login that a headless setup never could.
|
|
46
|
-
|
|
47
|
-
## Step 4 — Switch to Remote Control (the daily surface)
|
|
48
|
-
|
|
49
|
-
The terminal is a **launchpad, not where you'll live.** Once Claude is running in the terminal, run this **inside that session**:
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
/remote-control
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
It prints a **pairing link**. Open that link and you can now drive your box straight from the **Claude web or mobile app** — a much nicer surface than the terminal tab. From here on, that's where you work; the terminal was just where you logged in.
|
|
56
|
-
|
|
57
|
-
> ⚠️ **Use the `/remote-control` slash command from inside a running Claude session — not a standalone `claude rc` command in the shell.** The standalone version mis-reads folder trust and fails; starting Remote Control from within an already-signed-in session is the path that works.
|
|
58
|
-
|
|
59
|
-
## Good to know
|
|
60
|
-
|
|
61
|
-
- **Closing the terminal tab ends the session and closes the box.** That's expected — it's how the session cleans up. Your work that's committed/shipped is safe; the box itself just shuts down.
|
|
62
|
-
- **The box sleeps when idle and wakes on demand.** Next time, start again at Step 1 — `/builder-box` wakes it and hands you a fresh link.
|
|
63
|
-
- **You also get a live game preview.** If your box is on the stable-hostname setup, your terminal link looks like `https://term-<you>.example.com`; your private game preview (the **sandbox**) is the same address with `term-` swapped for `sandbox-` — `https://sandbox-<you>.example.com`. Run **`/builder-stage`** to push your current edits there and review a change in a browser before you ship it. (See "Your own dev box" in the [primer](primer.md).)
|
|
64
|
-
|
|
65
|
-
## If something isn't working
|
|
66
|
-
|
|
67
|
-
- **No link yet?** The box may still be waking. Wait ~1 minute after `/builder-box` says it's starting, then run `/builder-box` again.
|
|
68
|
-
- **Password rejected?** Re-run `/builder-box` to get a fresh link + password — the box republishes them when it restarts.
|
|
69
|
-
- **Brand-new to the project on a browser-only device?** Getting the very first box onto a pure browser-only device can need a one-time assist (there's a known first-boot gap for a device with no terminal at all). If `/builder-box` can't hand you a link, ask an Archon to help seed your box once; after that the browser flow works on its own.
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
*Deeper detail (the systemd units, the Cloudflare tunnel, operator setup, and end-to-end verification) lives in the operator recipe [`docs/recipes/managed-settings-remote-control.md`](../recipes/managed-settings-remote-control.md) (Part 2) and [ADR 0038](../adr/0038-chromebook-ttyd-cloudflare-tunnel.md) / [ADR 0040](../adr/0040-remote-control-default-browser-onramp.md). This page is the builder-facing summary of that flow.*
|
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
# Recipe - managed-settings SSH + browser-terminal on-ramps
|
|
2
|
-
|
|
3
|
-
> **Task [#599](https://example.com/builders#/task/599) · [ADR 0031](../adr/0031-cloud-dev-environments-for-builders.md) §1.** This is the operator guide for distributing a locked Claude Desktop SSH connection to builders and for serving the browser web-terminal on the box. It assumes a box is already provisioned and running; the lifecycle runbook that explained how to get one (`builder-box-lifecycle.md`) was retired with the rest of the published dev-box recipes (task 1004013, [ADR 0305](../adr/0305-a-recipe-lives-with-what-it-documents.md)). This recipe documents the same module and is queued to follow it.
|
|
4
|
-
|
|
5
|
-
> **Chromebook/browser path (ADR 0038 → [ADR 0040](../adr/0040-remote-control-default-browser-onramp.md), [#760](https://example.com/builders#/task/760) / [#824](https://example.com/builders#/task/824)):** the box serves its own web terminal - `ttyd` bound to localhost, exposed via an outbound-only Cloudflare Tunnel - and the builder runs a NORMAL interactive `claude` inside it from any browser. Because there is a real human at a real browser-backed terminal, `/login`, workspace-trust, and Trusted-Devices enrollment all succeed (the things that made headless `claude rc` unworkable). **ADR 0040 narrows that terminal to a one-time launchpad:** the builder logs in there once, runs the in-session **`/remote-control`** slash command, and then drives the box from the Claude web/mobile app — the terminal is no longer their daily surface. **The SSH path (Mac/Windows) below is unaffected and works.**
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## The two on-ramps
|
|
10
|
-
|
|
11
|
-
| Builder's device | Path | What this recipe wires |
|
|
12
|
-
|---|---|---|
|
|
13
|
-
| **Mac / Windows** | Claude Desktop → SSH → box | Pre-distributed `managed-settings.json` (locked `sshConfigs`) |
|
|
14
|
-
| **Chromebook / any browser** | Browser -> `ttyd` web terminal over an outbound Cloudflare Tunnel -> box (reached at `GET /box/terminal`) -> log in once, then `/remote-control` -> drive the box from the Claude web/mobile app (ADR 0040) | `box-terminal.service` (ttyd, localhost-only) + `cloudflared.service` (outbound tunnel) + `box-report-terminal` cron |
|
|
15
|
-
|
|
16
|
-
Both paths land the builder on the same box and the same `/workspace`. The choice is which GUI they prefer, not which machine they reach.
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Part 0 — the one-command flow ([#685](https://example.com/builders#/task/685), the default)
|
|
21
|
-
|
|
22
|
-
> **This is the path to use now.** Parts 1, 3 and 4 below document the underlying
|
|
23
|
-
> pieces (and the manual fallbacks); [#685](https://example.com/builders#/task/685) collapses the builder's side to a single
|
|
24
|
-
> command and the operator's side to a single command. Nobody has to `scp` a JSON
|
|
25
|
-
> file, paste a public key into `authorized_keys`, or `sudo cp` anything by hand.
|
|
26
|
-
|
|
27
|
-
**The split:**
|
|
28
|
-
|
|
29
|
-
| Who | Runs | What it does |
|
|
30
|
-
|---|---|---|
|
|
31
|
-
| **Builder** (own Mac/Windows) | `/builder-connect` — or, on a bare laptop, the hosted bootstrap (below) | GitHub sign-in → create+register their SSH key → install `managed-settings.json` |
|
|
32
|
-
| **Operator** (control plane) | `node scripts/gds/box.js onboard <login> --apply` | provision the box (bakes the builder's registered key) + print the welcome packet |
|
|
33
|
-
|
|
34
|
-
**Builder side** — guided in Claude Desktop (`/builder-connect`), or paste the hosted bootstrap on a bare laptop:
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
# macOS / Linux
|
|
38
|
-
curl -fsSL https://example.com/api/bongos/box/connect.sh | bash
|
|
39
|
-
# Windows (PowerShell)
|
|
40
|
-
irm https://example.com/api/bongos/box/connect.ps1 | iex
|
|
41
|
-
# Plain-text guide (no auth): https://example.com/api/bongos/box/connect
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
The bootstrap (`infra/box-connect.sh` / `.ps1`, served by `GET /box/connect.sh|.ps1`)
|
|
45
|
-
is self-contained — only `curl` + `ssh-keygen` (or PowerShell). It device-flow signs
|
|
46
|
-
the builder in, creates `~/.ssh/otb_builder` if absent, registers the **public** key
|
|
47
|
-
(`POST /box/ssh-key`), and downloads + installs `managed-settings.json`
|
|
48
|
-
(`GET /box/managed-settings`) at the OS path. The private key never leaves the machine;
|
|
49
|
-
the token is held only in memory for the two authenticated calls (passed to `curl` via a
|
|
50
|
-
0600 config file, never on the command line).
|
|
51
|
-
|
|
52
|
-
> **Operator note (staging):** the served script's `GDS_API_BASE` is pinned to
|
|
53
|
-
> `GDS_PUBLIC_ORIGIN` (default `https://example.com`) — the SAME env var the OAuth
|
|
54
|
-
> `redirect_uri` uses — and is **never** derived from a request header, so a spoofed
|
|
55
|
-
> `Host` can't redirect a victim's `curl … | bash`. On staging, set
|
|
56
|
-
> `GDS_PUBLIC_ORIGIN=https://staging.example.com` in the server env (you already do,
|
|
57
|
-
> for OAuth) and the connect scripts target staging automatically.
|
|
58
|
-
|
|
59
|
-
**Operator side:**
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
node scripts/gds/box.js onboard <login> # dry-run: shows the plan + key count
|
|
63
|
-
node scripts/gds/box.js onboard <login> --apply # provision + print the welcome packet
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
`onboard` refuses if the builder has **no registered SSH key** (the box would have no
|
|
67
|
-
way in) — the canonical order is **builder connects first, then operator onboards**.
|
|
68
|
-
Override with `--allow-no-keys` only if you will re-provision once they register a key.
|
|
69
|
-
|
|
70
|
-
**How the key reaches the box without an operator SSH round-trip:** `box.js` reads the
|
|
71
|
-
builder's registered keys from the DB and **bakes** them into the cloud-init managed
|
|
72
|
-
block at provision (so the first login works before the box has any Bongos token). From
|
|
73
|
-
then on, `infra/<redacted>.sh` (a 10-min cron on the box, wired by the
|
|
74
|
-
cloud-init) keeps `authorized_keys` in sync via `GET /box/authorized-keys` — which is
|
|
75
|
-
**gated on the builder's LIVE rank+status** exactly like source access ([#600](https://example.com/builders#/task/600)). A
|
|
76
|
-
demoted/deactivated builder gets a 403 and the cron empties the managed block — instant
|
|
77
|
-
SSH-login revocation — **without touching the operator's own key** (only the lines
|
|
78
|
-
between the `otb-managed` markers are rewritten). The droplet teardown follows via
|
|
79
|
-
`box.js reconcile`.
|
|
80
|
-
|
|
81
|
-
The rest of this recipe is the manual decomposition + fallbacks.
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## Part 1 — Claude Desktop SSH (Mac / Windows)
|
|
86
|
-
|
|
87
|
-
### How it works
|
|
88
|
-
|
|
89
|
-
The builder installs Claude Desktop (free; no paid plan needed for the app shell). You pre-distribute a `managed-settings.json` that contains the SSH connection for their box. When they open Claude Desktop → Code → Environment, they see exactly one entry: **"Example Dev Box"** — no configuration required on their end.
|
|
90
|
-
|
|
91
|
-
Claude Desktop SSHes to the box, auto-installs Claude Code on the host (on first connect), and starts a Code session directly in `/workspace`. The builder is ready to work.
|
|
92
|
-
|
|
93
|
-
### Step 1 — Provision the box
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
node scripts/gds/box.js provision <login> --apply
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
This creates the DigitalOcean droplet, runs the cloud-init, and registers the box. The builder's hostname is `box-<login>.dev.example.com`.
|
|
100
|
-
|
|
101
|
-
### Step 2 — Generate the managed-settings.json
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
bash infra/gen-managed-settings.sh <login> > /tmp/<login>-managed-settings.json
|
|
105
|
-
cat /tmp/<login>-managed-settings.json
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
The output is a small JSON file, e.g.:
|
|
109
|
-
|
|
110
|
-
```json
|
|
111
|
-
{
|
|
112
|
-
"sshConfigs": [
|
|
113
|
-
{
|
|
114
|
-
"id": "otb-alice",
|
|
115
|
-
"name": "Example Dev Box",
|
|
116
|
-
"sshHost": "root@box-alice.dev.example.com",
|
|
117
|
-
"sshIdentityFile": "~/.ssh/otb_builder",
|
|
118
|
-
"startDirectory": "/workspace"
|
|
119
|
-
}
|
|
120
|
-
],
|
|
121
|
-
"sshHostAllowlist": ["*.dev.example.com"]
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Step 3 — Give it to the builder
|
|
126
|
-
|
|
127
|
-
Send them the file and these instructions (copy-paste friendly):
|
|
128
|
-
|
|
129
|
-
> **Mac:** Open Terminal and run:
|
|
130
|
-
> ```bash
|
|
131
|
-
> sudo mkdir -p "/Library/Application Support/ClaudeCode"
|
|
132
|
-
> sudo cp ~/Downloads/<login>-managed-settings.json "/Library/Application Support/ClaudeCode/managed-settings.json"
|
|
133
|
-
> ```
|
|
134
|
-
> Then restart Claude Desktop. You'll see "Example Dev Box" in Code → Environment.
|
|
135
|
-
>
|
|
136
|
-
> **Windows:** Save the file to `C:\Program Files\ClaudeCode\managed-settings.json` (create the folder if it doesn't exist; needs Administrator). Restart Claude Desktop.
|
|
137
|
-
|
|
138
|
-
> ⚠️ **The directory is `ClaudeCode`, not `Claude`.** Claude Code reads managed settings from `/Library/Application Support/ClaudeCode/` (macOS) and `%ProgramFiles%\ClaudeCode\` (Windows). A file under `…/Claude/` or `%ProgramData%\Claude\` is silently ignored and the box never appears in the Environment picker (the [#808](https://example.com/builders#/task/808) onboarding bug). The legacy `%ProgramData%\ClaudeCode\` Windows path was dropped in v2.1.75.
|
|
139
|
-
|
|
140
|
-
### Step 4 — Builder's SSH key
|
|
141
|
-
|
|
142
|
-
The `managed-settings.json` points to `~/.ssh/otb_builder` as the identity file. The builder needs this key pair generated on their machine, and you need to add the **public key** to the box.
|
|
143
|
-
|
|
144
|
-
**Builder runs (once):**
|
|
145
|
-
```bash
|
|
146
|
-
ssh-keygen -t ed25519 -f ~/.ssh/otb_builder -C "otb-builder"
|
|
147
|
-
cat ~/.ssh/otb_builder.pub
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
**Operator adds the public key to the box:**
|
|
151
|
-
```bash
|
|
152
|
-
ssh root@box-<login>.dev.example.com \
|
|
153
|
-
"echo '<public key here>' >> ~/.ssh/authorized_keys"
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Or, if the builder's key is already registered as a DO SSH key, pass it via `BOX_SSH_KEY_IDS` at provision time so it's injected automatically.
|
|
157
|
-
|
|
158
|
-
### What `sshHostAllowlist` does
|
|
159
|
-
|
|
160
|
-
The `sshHostAllowlist: ["*.dev.example.com"]` entry prevents the builder from using Claude Desktop SSH to connect to arbitrary hosts. Their SSH picker only shows the pre-configured OTB box — they can't misconfigure or wander off it. This is a `managed-settings`-only field; user-level settings cannot override it.
|
|
161
|
-
|
|
162
|
-
---
|
|
163
|
-
|
|
164
|
-
## Part 2 - Browser on-ramp: terminal launchpad -> Remote Control (Chromebook / any browser) - ADR 0038 -> [ADR 0040](../adr/0040-remote-control-default-browser-onramp.md), [#760](https://example.com/builders#/task/760) / [#824](https://example.com/builders#/task/824)
|
|
165
|
-
|
|
166
|
-
### How it works
|
|
167
|
-
|
|
168
|
-
The box serves its own web terminal. Two systemd units do the work:
|
|
169
|
-
|
|
170
|
-
- **`box-terminal.service`** runs `ttyd` (a web-terminal daemon) bound to `127.0.0.1:7681`, basic-auth protected with a per-box generated password, dropping whoever connects into a shell in `/workspace`. It listens on localhost only - the box opens **no inbound port**.
|
|
171
|
-
- **`cloudflared.service`** (via `infra/cloudflared-run.sh`) runs a Cloudflare Tunnel that makes that localhost terminal reachable from a browser. The tunnel is **outbound-only**: the box dials out to Cloudflare, so nothing has to be opened in the firewall.
|
|
172
|
-
|
|
173
|
-
The builder opens the tunnel URL in any browser, authenticates at the basic-auth prompt, and then runs a **normal interactive `claude`** in the terminal. The first `claude` run prompts a normal browser `/login`; because there is a real human at a real browser-backed terminal, `/login`, workspace-trust, and Trusted-Devices enrollment all succeed - the exact steps that headless `claude rc` could never perform.
|
|
174
|
-
|
|
175
|
-
**The terminal is a one-time launchpad, not the daily surface (ADR 0040).** Once the builder is signed in to that interactive `claude` session, they run the in-session **`/remote-control`** slash command. It prints a pairing link; opening that link drives the box from the Claude web/mobile app. From there the builder lives in the Claude app — the ttyd terminal is just where they logged in and kicked RC off.
|
|
176
|
-
|
|
177
|
-
> ⚠️ **Use the in-session `/remote-control` slash command — NOT the standalone `claude rc` shell command.** The standalone command mis-checks workspace trust ([anthropics/claude-code#35817](https://github.com/anthropics/claude-code/issues/35817)): it reports `Workspace not trusted` even when the directory is trusted, and a plain shell can't accept the trust dialog. Starting RC from *inside* an already-running, already-trusted `claude` session is the working path (verified live 2026-06-07). Closing the terminal window ends the remote session and closes the box — a property of the session lifecycle, stated up front so builders aren't surprised.
|
|
178
|
-
|
|
179
|
-
> **Workspace trust is pre-seeded ([#824](https://example.com/builders#/task/824)).** The cloud-init writes `/root/.claude.json` with `<redacted>` for `/root` + `/workspace`, so the first `claude` launch usually skips the trust dialog entirely. This is **non-load-bearing** (ADR 0040): the seed is best-effort and only written when the file is absent (upstream pre-seeding is flaky, [#9113](https://github.com/anthropics/claude-code/issues/9113)). If it doesn't take, the builder just accepts trust once — the flow works either way.
|
|
180
|
-
|
|
181
|
-
The cloud-init wires both units automatically (and `infra/box-source-fetch.sh` re-installs them on its post-clone bring-up, so a box provisioned before this shipped picks them up on its next refresh). It copies `infra/box-terminal.service` and `infra/cloudflared.service` to `/etc/systemd/system/`, enables them with `systemctl enable --now`, and wires `infra/box-report-terminal.sh` as a 1-minute cron that publishes the terminal URL + credential **up** to Bongos (`POST /box/terminal`) so the builder's self-serve flow can read them back.
|
|
182
|
-
|
|
183
|
-
**Tunnel modes:**
|
|
184
|
-
|
|
185
|
-
- **Quick tunnel (default, zero operator secrets).** `cloudflared` opens a tokenless quick tunnel and gets a random `https://<id>.trycloudflare.com` hostname. Nothing to configure - works out of the box.
|
|
186
|
-
- **Named tunnel (production upgrade, stable hostname).** When `/etc/otb/box.env` carries `BOX_TUNNEL_TOKEN` + `BOX_TERMINAL_HOSTNAME`, `cloudflared` runs a named tunnel instead and the terminal is reachable at a stable `term-<login>.example.com` host. As of [#771](https://example.com/builders#/task/771) the control plane **creates and bakes those automatically** on `box.js provision` (and tears them down on `deprovision`) — see the [named-tunnel automation](#<redacted>) section below.
|
|
187
|
-
|
|
188
|
-
### Getting onto the terminal (builder self-serve)
|
|
189
|
-
|
|
190
|
-
No SSH and no pairing script - two API calls:
|
|
191
|
-
|
|
192
|
-
```bash
|
|
193
|
-
# 1. Get a box (provisions or wakes it; see Part 0 / #701):
|
|
194
|
-
POST /api/bongos/box/ensure
|
|
195
|
-
|
|
196
|
-
# 2. Poll until the box has reported its terminal up:
|
|
197
|
-
GET /api/bongos/box/terminal
|
|
198
|
-
# -> {"url": "https://<id>.trycloudflare.com", "credential": "<redacted>"}
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
Then:
|
|
202
|
-
|
|
203
|
-
1. Open the returned `url` in a browser.
|
|
204
|
-
2. At the browser's basic-auth prompt, enter username **`otb`** and the password (the `credential` value).
|
|
205
|
-
3. In the terminal, type `claude` and press enter. On first run it walks the normal browser `/login` (workspace trust is pre-seeded, [#824](https://example.com/builders#/task/824), so the trust dialog usually doesn't appear; accept once if it does). You're now in an interactive Claude Code session in `/workspace`.
|
|
206
|
-
4. Inside that session, run the **`/remote-control`** slash command. It prints a pairing link.
|
|
207
|
-
5. Open that link to drive the box from the Claude web/mobile app. The terminal was the launchpad — work from the Claude app from here. (Closing the terminal window ends the remote session and closes the box.)
|
|
208
|
-
|
|
209
|
-
### Service lifecycle
|
|
210
|
-
|
|
211
|
-
| State | What happens |
|
|
212
|
-
|---|---|
|
|
213
|
-
| Box first boots | `box-terminal.service` (ttyd) + `cloudflared.service` start; `box-report-terminal` cron publishes the URL + credential to Bongos within ~1 min |
|
|
214
|
-
| Builder connects (first time) | Opens the tunnel URL, basic-auths as `otb`, runs `claude` (one-time browser `/login`), then `/remote-control` to get a pairing link and drive the box from the Claude app (ADR 0040) |
|
|
215
|
-
| Builder works (each session) | Lives in the Claude web/mobile app via the RC link; the ttyd terminal is only the launchpad they logged in through |
|
|
216
|
-
| Session ends | Builder closes the browser tab; the terminal + tunnel stay up for the next visit |
|
|
217
|
-
| Box is parked (idle-suspend) | Both units stop naturally when the droplet is destroyed; they restart clean on wake and the cron re-publishes a fresh URL |
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
### Checking the services
|
|
221
|
-
|
|
222
|
-
```bash
|
|
223
|
-
# On the box:
|
|
224
|
-
sudo systemctl status box-terminal # the ttyd web terminal
|
|
225
|
-
sudo systemctl status cloudflared # the outbound tunnel
|
|
226
|
-
journalctl -u box-terminal -f # live tail
|
|
227
|
-
journalctl -u cloudflared -f # tunnel logs - quick-tunnel URL appears here
|
|
228
|
-
journalctl -u cloudflared -n 100 # last 100 lines
|
|
229
|
-
sudo systemctl restart box-terminal cloudflared
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
The quick-tunnel hostname is printed in the `cloudflared` journal; the same value is published to Bongos by the `box-report-terminal` cron (verify with `crontab -l | grep box-report-terminal`).
|
|
233
|
-
|
|
234
|
-
### Named-tunnel automation (control plane, [#771](https://example.com/builders#/task/771))
|
|
235
|
-
|
|
236
|
-
The named-tunnel upgrade is automated on the control plane (`scripts/gds/box.js` + `src/bongos/cf-tunnel.js`). It is **OPT-IN, default OFF** — leave it off and every box uses the zero-secret quick tunnel.
|
|
237
|
-
|
|
238
|
-
**Turning it on (operator, in the control-plane `box.env` — the same file that holds `DO_API_TOKEN`, NEVER the web tier):**
|
|
239
|
-
|
|
240
|
-
```bash
|
|
241
|
-
BOX_NAMED_TUNNEL=1
|
|
242
|
-
CLOUDFLARE_API_TOKEN=<token with Account:Argo Tunnel:Edit + Zone:DNS:Edit>
|
|
243
|
-
CLOUDFLARE_ACCOUNT_ID=<the Cloudflare account id>
|
|
244
|
-
# optional (defaults shown):
|
|
245
|
-
CLOUDFLARE_ZONE=example.com # the DNS zone the record lives in
|
|
246
|
-
BOX_TERMINAL_ZONE=example.com # hostname suffix → term-<login>.example.com
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
This reuses the **same `CLOUDFLARE_API_TOKEN`** as `box-dns-cloudflare.sh` (the [#725](https://example.com/builders#/task/725) `BOX_DNS_HOOK`) — just add the Tunnel scope to it. In the Cloudflare token UI the permission is **Account → Argo Tunnel → Edit** (the older label for the Cloudflare Tunnel / `cfd_tunnel` API; "Cloudflare Tunnel" on newer accounts). Keep the existing **Zone → DNS → Edit**.
|
|
250
|
-
|
|
251
|
-
> ⚠️ **TLS coverage — why the host is at the apex, not `dev.*` ([#771](https://example.com/builders#/task/771)).** The terminal rides a **proxied** tunnel (Cloudflare terminates HTTPS at its edge), so the hostname needs an edge certificate. Free **Universal SSL covers `example.com` and `*.example.com` (one label) but NOT a two-label host** like `term-x.dev.example.com` — a `dev.*` host fails the TLS handshake at the edge (verified live). So `BOX_TERMINAL_ZONE` defaults to the **apex** (`term-<login>.example.com`, one label, covered for free). Only override it to `dev.example.com` if you've bought an **Advanced Certificate** for `*.dev.example.com`. (The SSH A-record path stays on `dev.*` — it's `proxied=false`/direct-IP and needs no edge cert.)
|
|
252
|
-
|
|
253
|
-
**What the lifecycle does when it's on:**
|
|
254
|
-
|
|
255
|
-
| Op | Tunnel action |
|
|
256
|
-
|---|---|
|
|
257
|
-
| `provision` | create-or-reuse a per-builder named tunnel (`otb-term-<login>`), point its ingress at `127.0.0.1:7681`, route a proxied CNAME `term-<login>.example.com → <tunnel-id>.cfargotunnel.com`, fetch the connector token, and **bake** `BOX_TUNNEL_TOKEN` + `BOX_TERMINAL_HOSTNAME` into the box's `/etc/otb/box.env` via cloud-init. The tunnel is created **before** the droplet so its token is present at first boot. |
|
|
258
|
-
| `park` → `wake` | nothing — the tunnel + DNS are IP-independent and durable, and `box.env` rides the snapshot, so the stable hostname survives park/wake with no control-plane call. |
|
|
259
|
-
| `deprovision` (incl. `sweep-dormant` / `reconcile` clawback) | best-effort teardown: delete the CNAME and the tunnel (the droplet is already gone, so connections have dropped). Never blocks the deprovision. |
|
|
260
|
-
|
|
261
|
-
`box.js provision` (dry-run, the default) prints the tunnel plan without touching Cloudflare; pass `--apply` to create it for real. If `BOX_NAMED_TUNNEL=1` but the token/account id are missing, provision **fails loudly** rather than silently falling back.
|
|
262
|
-
|
|
263
|
-
**Per-box manual override (no automation):** you can still set the two values by hand on a single box and restart — `cloudflared-run.sh` detects the token and switches modes:
|
|
264
|
-
|
|
265
|
-
```bash
|
|
266
|
-
# In /etc/otb/box.env on the box:
|
|
267
|
-
BOX_TUNNEL_TOKEN=<redacted> named-tunnel token>
|
|
268
|
-
BOX_TERMINAL_HOSTNAME=term-<login>.example.com
|
|
269
|
-
sudo systemctl restart cloudflared
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
### Verifying the browser on-ramp end-to-end ([#771](https://example.com/builders#/task/771))
|
|
273
|
-
|
|
274
|
-
The build is unit- and smoke-covered, but the loop crosses real hardware + a human at a browser. Run this once per change to `ttyd`/`cloudflared`/the terminal routes. **Operator deps:** a box provision (small DO cost) and, for the named-tunnel leg, the CF token with Tunnel scope.
|
|
275
|
-
|
|
276
|
-
**Leg A — quick tunnel (zero secrets).**
|
|
277
|
-
|
|
278
|
-
1. Provision a box for a test builder: `node scripts/gds/box.js onboard <login> --apply` (builder must have a registered SSH key, or `--allow-no-keys`). Confirm cloud-init finished: SSH in and `cloud-init status --wait` → `done`; `systemctl is-active box-terminal cloudflared` → `active`.
|
|
279
|
-
2. Confirm the box published its terminal: `tail /tmp/box-report-terminal.log` should show `terminal access reported`. Then as the builder: `GET /api/bongos/box/terminal` (or `/builder-box`) → returns `{ url: https://<id>.trycloudflare.com, credential }`.
|
|
280
|
-
3. Open the `url` in a browser. Basic-auth as **`otb`** + the `credential`. You should land in a `/workspace` shell.
|
|
281
|
-
4. In that terminal run `claude`. Complete the browser `/login` (trust is pre-seeded, [#824](https://example.com/builders#/task/824) — accept once if still prompted) and Trusted-Devices enrollment. Confirm an interactive session starts. **This is the leg `claude rc` could never do** ([#745](https://example.com/builders#/task/745)).
|
|
282
|
-
5. Inside that session, run **`/remote-control`** (the slash command, NOT `claude rc`). Confirm it prints a pairing link; open the link and confirm the Claude web/mobile app drives the box (ADR 0040, verified live 2026-06-07).
|
|
283
|
-
6. Full Chromebook loop: from a browser-only device, `POST /box/ensure` → poll `GET /box/terminal` → open → `claude` → `/remote-control` → drive from the Claude app. No SSH, no Desktop.
|
|
284
|
-
|
|
285
|
-
**Leg B — named tunnel (stable hostname).**
|
|
286
|
-
|
|
287
|
-
1. With the CF token (Tunnel + DNS scope) in the control-plane `box.env`, set `BOX_NAMED_TUNNEL=1` + `CLOUDFLARE_ACCOUNT_ID`. Dry-run first: `node scripts/gds/box.js provision <login>` — confirm it prints the `term-<login>.example.com` plan and the CF calls without touching anything.
|
|
288
|
-
2. `node scripts/gds/box.js provision <login> --apply`. Confirm in the Cloudflare dashboard: a tunnel `otb-term-<login>` exists with one healthy connector, and a proxied CNAME `term-<login>.example.com → <id>.cfargotunnel.com`.
|
|
289
|
-
3. `GET /box/terminal` returns the stable `https://term-<login>.example.com`. Open it, basic-auth, `claude` — same as Leg A but on the stable host.
|
|
290
|
-
4. Teardown: `node scripts/gds/box.js deprovision <login> --apply`. Confirm the tunnel **and** the CNAME are gone from the Cloudflare dashboard.
|
|
291
|
-
|
|
292
|
-
### Gotchas surfaced by hardware verification ([#771](https://example.com/builders#/task/771))
|
|
293
|
-
|
|
294
|
-
- **Packaged ttyd collision (fixed).** The Ubuntu `ttyd` apt package ships its OWN auto-started `ttyd.service` that binds `127.0.0.1:7681` with **no `--credential`** and runs `login`. It both blocks our `box-terminal.service` (the unit crash-loops on `EADDRINUSE`) and — because the tunnel fronts whatever sits on 7681 — would expose an **unauthenticated** terminal. Cloud-init and the `box-source-fetch` post-clone bring-up now `systemctl mask ttyd` so only our basic-auth ttyd binds the port. On a box provisioned before this fix: `sudo systemctl disable --now ttyd && sudo systemctl mask ttyd && sudo systemctl restart box-terminal`.
|
|
295
|
-
- **IPv4-only Cloudflare calls (fixed).** The CF API token is IP-locked to the droplet's IPv4 (least privilege). The droplet also has IPv6 egress; `cf-tunnel.js` pins IPv4 (undici `Agent{connect:{family:4}}`) or every call 401s — mirroring `box-dns-cloudflare.sh`'s `curl -4`.
|
|
296
|
-
- **⚠ Browser-only bootstrap gap (OPEN — follow-up).** The `ttyd`/`cloudflared` systemd units live in the repo (`/workspace/infra/*.service`), so they only start **after** `box-source-fetch` clones — which needs the **builder's Bongos token on the box**. A pure Chromebook builder has no SSH and no terminal yet, so there is **no first-boot path to place that token** → the terminal never comes up for them. Today it only works if the token is seeded another way (SSH, or an operator). The fix (a follow-up) is to bring ttyd up at **first boot independent of the clone** — e.g. cloud-init writes the unit inline rather than copying it from the repo. Until then, the browser on-ramp is verified to work *given a token on the box*, not from a truly bare browser-only device.
|
|
297
|
-
|
|
298
|
-
---
|
|
299
|
-
|
|
300
|
-
## Part 3 — The box-heartbeat cron
|
|
301
|
-
|
|
302
|
-
The cloud-init also wires `infra/box-heartbeat.sh` as a 5-minute cron job. This pings `/api/bongos/box/heartbeat` while the box is in active use (interactive SSH session or CPU load > 0.2). Silence = the idle-suspend sweep ([#598](https://example.com/builders#/task/598)) is free to park the box.
|
|
303
|
-
|
|
304
|
-
Without this cron the idle sweep measures **uptime** instead of **activity** and may park a box someone is actively using. Always verify it's wired:
|
|
305
|
-
|
|
306
|
-
```bash
|
|
307
|
-
crontab -l | grep box-heartbeat
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
If missing (pre-[#599](https://example.com/builders#/task/599) box):
|
|
311
|
-
```bash
|
|
312
|
-
(crontab -l 2>/dev/null; echo "*/5 * * * * /workspace/infra/box-heartbeat.sh >> /tmp/box-heartbeat.log 2>&1") | crontab -
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
---
|
|
316
|
-
|
|
317
|
-
## Part 4 — Rank-gated source access ([#600](https://example.com/builders#/task/600), ADR 0031 §6 / §9.2)
|
|
318
|
-
|
|
319
|
-
The box holds **no baked credential**. Instead it fetches a rank-scoped clone spec
|
|
320
|
-
from Bongos at boot and on a 10-minute cron, via `infra/box-source-fetch.sh`
|
|
321
|
-
(carried onto the box as base64 inside the cloud-init `write_files`). The flow:
|
|
322
|
-
|
|
323
|
-
1. The box reads the builder's Bongos session token (it appears after they authenticate
|
|
324
|
-
Claude Code / run `/builder-reauth` — so the **first clone lands after the builder
|
|
325
|
-
connects**, not at boot).
|
|
326
|
-
2. It calls `GET /api/bongos/box/source-access`. Bongos checks the builder's **live**
|
|
327
|
-
rank + status (ADR 0016 — unforgeable from the box):
|
|
328
|
-
- **403 `SOURCE_ACCESS_REVOKED`** — builder demoted below the floor or deactivated.
|
|
329
|
-
The script cuts the credentialed remote (box-side clawback); `box.js reconcile`
|
|
330
|
-
deprovisions the droplet.
|
|
331
|
-
- **200 `configured:false`** — the server's source credential isn't set yet (see
|
|
332
|
-
env below); the box logs guidance and no-ops.
|
|
333
|
-
- **200 `configured:true`** — returns the scope-appropriate spec + a short-lived
|
|
334
|
-
credential. The box clones/refreshes `/workspace`, **never persisting the token
|
|
335
|
-
in `.git/config`** (clones via a one-shot authenticated URL, then resets the
|
|
336
|
-
remote to the token-less URL).
|
|
337
|
-
3. **Scope by rank** (`src/bongos/box-access.js`): a **Xenos → `starter`** sparse-checkout
|
|
338
|
-
of the safe surface (`.devcontainer`, `art`, `docs`, `modules/status-ui`,
|
|
339
|
-
`generative-ideas.md` — the §9.2 "first tasks on a scoped surface"); a **Metic+ →
|
|
340
|
-
`full`** clone. Graduation (`box.js reconcile`) rescopes a starter box to full; the
|
|
341
|
-
box re-clones on its next refresh.
|
|
342
|
-
|
|
343
|
-
**Operator: configure the source credential** (in the **prod env**, never the repo —
|
|
344
|
-
ADR 0022). The default provider (`src/bongos/box-credential.js`) is env-backed:
|
|
345
|
-
|
|
346
|
-
```bash
|
|
347
|
-
# In the prod environment (e.g. the droplet's systemd unit / .env):
|
|
348
|
-
BOX_SOURCE_REPO_URL=https://github.com/<org>/example.git
|
|
349
|
-
BOX_SOURCE_FULL_TOKEN=<redacted> PAT or installation token, contents:read>
|
|
350
|
-
# Optional — make 'starter' a HARD boundary (a physically separate repo that cannot
|
|
351
|
-
# read the crown jewels) instead of the default sparse-checkout defense-in-depth:
|
|
352
|
-
BOX_SOURCE_STARTER_REPO_URL=https://github.com/<org>/example-starter.git
|
|
353
|
-
BOX_SOURCE_STARTER_TOKEN=<token scoped to the starter repo>
|
|
354
|
-
# Optional — advisory token TTL (how often the box re-fetches + re-checks live rank):
|
|
355
|
-
BOX_SOURCE_TOKEN_TTL_SECONDS=3600
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
> **Honest limit (ADR 0031 §6).** Git cannot sub-path-scope a single private repo and
|
|
359
|
-
> sparse-checkout is client-side, so the default `starter` surface is
|
|
360
|
-
> **defense-in-depth + audit + revocation, not insider-proof DRM**. Set
|
|
361
|
-
> `BOX_SOURCE_STARTER_REPO_URL` for a hard boundary. Every grant/denial is audited to
|
|
362
|
-
> `box_events` (`source_grant` / `source_deny` / `source_revoke` / `source_rescope`).
|
|
363
|
-
|
|
364
|
-
**The clawback (clean offboarding).** Revoking rank or deactivating a builder cuts
|
|
365
|
-
source on two timelines: the **credential layer is instant** (their next
|
|
366
|
-
`/box/source-access` fetch 403s), and the **droplet teardown** follows when the
|
|
367
|
-
control plane runs reconcile:
|
|
368
|
-
|
|
369
|
-
```bash
|
|
370
|
-
node scripts/gds/box.js reconcile # dry-run: show what would change
|
|
371
|
-
node scripts/gds/box.js reconcile --apply # deprovision below-floor/inactive boxes,
|
|
372
|
-
# rescope graduated ones (run on a schedule)
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
Verify the source-fetch cron + check progress on the box:
|
|
376
|
-
|
|
377
|
-
```bash
|
|
378
|
-
crontab -l | grep box-source-fetch
|
|
379
|
-
tail /tmp/box-source-fetch.log
|
|
380
|
-
WORKSPACE=/workspace /usr/local/bin/box-source-fetch.sh # manual run
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
---
|
|
384
|
-
|
|
385
|
-
## Quick reference: full operator flow
|
|
386
|
-
|
|
387
|
-
```bash
|
|
388
|
-
# 0. One-time: set BOX_SOURCE_REPO_URL + BOX_SOURCE_FULL_TOKEN in the prod env (Part 4)
|
|
389
|
-
|
|
390
|
-
# 1. Provision the box
|
|
391
|
-
node scripts/gds/box.js provision alice --apply
|
|
392
|
-
|
|
393
|
-
# 2. Add the builder's SSH public key to the box
|
|
394
|
-
ssh root@box-alice.dev.example.com "echo '...' >> ~/.ssh/authorized_keys"
|
|
395
|
-
|
|
396
|
-
# 3. Generate and send managed-settings.json (Desktop / SSH path)
|
|
397
|
-
bash infra/gen-managed-settings.sh alice > /tmp/alice-managed-settings.json
|
|
398
|
-
# → email/DM to builder with placement instructions
|
|
399
|
-
|
|
400
|
-
# 4. For thin-device (Chromebook) path: the web terminal is ready automatically
|
|
401
|
-
# (box-terminal.service + cloudflared.service start at boot; box-report-terminal
|
|
402
|
-
# publishes the URL + credential to Bongos within ~1 min)
|
|
403
|
-
# Builder self-serves: POST /api/bongos/box/ensure, then poll GET /api/bongos/box/terminal
|
|
404
|
-
# -> open the url, basic-auth as user 'otb' with the returned credential, run: claude
|
|
405
|
-
# -> then /remote-control to drive the box from the Claude app (ADR 0040)
|
|
406
|
-
|
|
407
|
-
# 5. When the builder is done for the day, the idle sweep parks the box:
|
|
408
|
-
node scripts/gds/box.js sweep-idle --apply # control plane / prod cron
|
|
409
|
-
|
|
410
|
-
# 6. Source materializes automatically once the builder authenticates on the box
|
|
411
|
-
# (the box-source-fetch cron clones the rank-scoped surface). Nothing to do here.
|
|
412
|
-
|
|
413
|
-
# 7. Offboarding / demotion: the credential is cut on the builder's next fetch;
|
|
414
|
-
# reconcile then deprovisions the box (run on a schedule alongside the sweeps):
|
|
415
|
-
node scripts/gds/box.js reconcile --apply
|
|
416
|
-
```
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# @bongos/core — installed platform dependency (not your project's CLAUDE.md)
|
|
2
|
-
|
|
3
|
-
This file is a placeholder shipped inside the **@bongos/core** package. The core's own
|
|
4
|
-
internal agent notes are intentionally omitted from the published artifact so they cannot
|
|
5
|
-
load into your instance's context.
|
|
6
|
-
|
|
7
|
-
**It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
|
|
8
|
-
directory, and the live Bongos database are the source of truth for how work happens here
|
|
9
|
-
and who may do what.
|
|
10
|
-
|
|
11
|
-
You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
|
|
12
|
-
Nothing in here needs to be read to use the platform — run `bongos help` for commands.
|
|
13
|
-
|
|
14
|
-
_To work ON the platform core itself, use the standalone @bongos/core source repository,
|
|
15
|
-
not this vendored copy._
|