@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.
Files changed (377) hide show
  1. package/.bongos-core.json +404 -549
  2. package/.claude/skills/feedback/SKILL.md +2 -2
  3. package/bin/bongos.js +1 -3
  4. package/clients/bongos-client/README.md +1 -1
  5. package/clients/bongos-client/bongos-client.global.js +4 -60
  6. package/clients/bongos-client/index.cjs +4 -60
  7. package/clients/bongos-client/index.d.ts +5 -90
  8. package/clients/bongos-client/index.mjs +4 -60
  9. package/config/branding.neutral.json +1 -5
  10. package/config/modules.neutral.json +2 -3
  11. package/config/scheduled-routines.json +0 -2
  12. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
  13. package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
  14. package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
  15. package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
  16. package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
  17. package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
  18. package/docs/adr/README.md +1 -0
  19. package/docs/api/openapi.json +175 -1665
  20. package/docs/api-reference.md +9 -41
  21. package/docs/architecture.md +16 -1
  22. package/docs/branding-contract.md +0 -3
  23. package/docs/copy-inventory.md +454 -479
  24. package/docs/copy-registry.json +746 -978
  25. package/docs/file-map.md +5 -34
  26. package/docs/module-api-changelog.md +4 -0
  27. package/docs/modules-contract.md +13 -1
  28. package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
  29. package/docs/onboarding/diagrams/README.md +3 -3
  30. package/docs/onboarding/diagrams/assertions.json +2 -22
  31. package/docs/onboarding/primer.md +9 -28
  32. package/docs/page-inventory.json +4 -1
  33. package/docs/page-readings.json +1032 -1077
  34. package/docs/recipes/render-pilot.md +29 -10
  35. package/migrations/core_257_module_store_registry.sql +87 -0
  36. package/migrations/core_258_drop_dev_box_tables.sql +55 -0
  37. package/modules/agents/module.json +1 -0
  38. package/modules/autonomy/module.json +1 -0
  39. package/modules/builder-settings/module.json +1 -0
  40. package/modules/builder-settings/render-prefs.js +4 -5
  41. package/modules/copy-desk/docx.js +78 -5
  42. package/modules/copy-desk/module.json +1 -0
  43. package/modules/copy-desk/routes/copy-desk.js +10 -10
  44. package/modules/copy-desk/tests/copy_docx.mjs +107 -1
  45. package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
  46. package/modules/discord/module.json +1 -0
  47. package/modules/discord/ship-broadcast.js +3 -3
  48. package/modules/economy/credits.js +2 -2
  49. package/modules/economy/module.json +1 -0
  50. package/modules/economy/routes/credits.js +1 -2
  51. package/modules/government/catalog.js +13 -17
  52. package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
  53. package/modules/government/module.json +1 -0
  54. package/modules/government/protected-surfaces.json +3 -11
  55. package/modules/government/resolver.js +3 -46
  56. package/modules/government/routes/government.js +0 -2
  57. package/modules/government/session-scopes.js +43 -83
  58. package/modules/government/session-scopes.json +8 -18
  59. package/modules/grading/module.json +1 -0
  60. package/modules/hall-ui/module.json +1 -0
  61. package/modules/hall-ui/public/board-lib.js +2 -2
  62. package/modules/hall-ui/public/builders.js +29 -40
  63. package/modules/hall-ui/public/collab.html +1 -1
  64. package/modules/hall-ui/public/collab.js +1 -1
  65. package/modules/hall-ui/public/diagrams.html +3 -3
  66. package/modules/hall-ui/public/dom-utils.js +1 -1
  67. package/modules/hall-ui/public/gate.js +2 -2
  68. package/modules/hall-ui/public/gate.states.json +1 -1
  69. package/modules/hall-ui/public/hall-render.js +43 -79
  70. package/modules/hall-ui/public/index.html +5 -1
  71. package/modules/hall-ui/public/modules.js +1 -0
  72. package/modules/hall-ui/public/oversight.css +5 -5
  73. package/modules/hall-ui/public/palette.js +0 -11
  74. package/modules/hall-ui/public/primer.js +1 -1
  75. package/modules/hall-ui/public/sessions.html +1 -1
  76. package/modules/hall-ui/public/settings-sessions.js +1 -1
  77. package/modules/hall-ui/public/settings.css +0 -1
  78. package/modules/hall-ui/public/settings.html +4 -33
  79. package/modules/hall-ui/public/settings.js +3 -33
  80. package/modules/hall-ui/public/settings.states.json +2 -2
  81. package/modules/hall-ui/public/studio.html +1 -1
  82. package/modules/hall-ui/public/style.css +10 -13
  83. package/modules/hall-ui/public/task.html +4 -1
  84. package/modules/hall-ui/public/task.js +36 -1
  85. package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
  86. package/modules/hall-ui/public/tweak-editor.css +16 -0
  87. package/modules/hall-ui/public/tweak-editor.html +26 -3
  88. package/modules/hall-ui/public/tweak-editor.js +32 -1
  89. package/modules/hall-ui/public/work.js +1 -1
  90. package/modules/hall-ui/records/tweak-editor.md +1 -0
  91. package/modules/ideas/module.json +1 -0
  92. package/modules/ideas/projection.js +1 -1
  93. package/modules/lifecycle/db-claim-reads.js +1 -58
  94. package/modules/lifecycle/db-ship.js +4 -9
  95. package/modules/lifecycle/db.js +0 -4
  96. package/modules/lifecycle/github-push.js +12 -14
  97. package/modules/lifecycle/kickoff-checklist.js +0 -4
  98. package/modules/lifecycle/lifecycle.js +0 -8
  99. package/modules/lifecycle/module.json +1 -0
  100. package/modules/lifecycle/publish-reconciler.js +10 -10
  101. package/modules/lifecycle/routes/tasks.js +13 -15
  102. package/modules/lifecycle/ship-card.js +1 -1
  103. package/modules/lifecycle/task-visuals.js +2 -2
  104. package/modules/memory/module.json +1 -0
  105. package/modules/memory/routes/memory.js +2 -2
  106. package/modules/npm-release/module.json +1 -0
  107. package/modules/onboarding/module.json +1 -0
  108. package/modules/onboarding/onboarding-state.js +17 -36
  109. package/modules/onboarding/routes/access-requests.js +3 -3
  110. package/modules/platform-identity/module.json +1 -0
  111. package/modules/platform-identity/platform-identity.js +13 -5
  112. package/modules/platform-identity/routes/sso.js +5 -4
  113. package/modules/platform-identity/tests/platform-identity.mjs +17 -3
  114. package/modules/provisioning/capacity.js +1 -1
  115. package/modules/provisioning/module.json +1 -0
  116. package/modules/provisioning/provisioning.js +6 -5
  117. package/modules/provisioning/routes/provisioning.js +4 -4
  118. package/modules/provisioning/starter-bundles.js +5 -25
  119. package/modules/public-landing/module.json +1 -0
  120. package/modules/public-landing/public/assets/cosmos.css +1 -1
  121. package/modules/public-landing/public/contact.html +1 -1
  122. package/modules/public-landing/public/privacy.html +1 -1
  123. package/modules/public-landing/public/projects.html +101 -32
  124. package/modules/public-landing/public/projects.probes.json +2 -2
  125. package/modules/public-landing/public/projects.states.json +4 -3
  126. package/modules/public-landing/public/terms.html +1 -1
  127. package/modules/security/module.json +1 -0
  128. package/modules/sessions/db.js +1 -1
  129. package/modules/sessions/module.json +1 -0
  130. package/modules/specialities/module.json +1 -0
  131. package/modules/status-ui/module.json +1 -0
  132. package/modules/ui-design/module.json +1 -0
  133. package/package-lock.json +2 -2
  134. package/package.json +1 -1
  135. package/release-notes.json +84 -0
  136. package/scripts/gds/artifact-format.js +203 -0
  137. package/scripts/gds/artifact-staleness.js +2 -2
  138. package/scripts/gds/audit-authorship.sh +1 -13
  139. package/scripts/gds/backfill-task-rewards.js +5 -5
  140. package/scripts/gds/claim.js +0 -15
  141. package/scripts/gds/claude-materialize.js +2 -2
  142. package/scripts/gds/cli-lib.js +15 -15
  143. package/scripts/gds/codemod-rename-src-gds.js +1 -1
  144. package/scripts/gds/context-pack.js +5 -7
  145. package/scripts/gds/diagram-facts.js +12 -23
  146. package/scripts/gds/do-api.js +13 -80
  147. package/scripts/gds/doc-cli-guard.js +2 -0
  148. package/scripts/gds/doctor.js +4 -4
  149. package/scripts/gds/feedback-latest.js +139 -9
  150. package/scripts/gds/fitness-checks-identity.js +2 -7
  151. package/scripts/gds/fitness-checks-packaging.js +4 -4
  152. package/scripts/gds/fitness-checks-write-validation.js +4 -0
  153. package/scripts/gds/fitness-ratchets.js +5 -5
  154. package/scripts/gds/fitness.js +10 -10
  155. package/scripts/gds/gen-api-client.js +4 -4
  156. package/scripts/gds/gen-api-docs.js +1 -1
  157. package/scripts/gds/gen-diagrams.js +49 -71
  158. package/scripts/gds/gen-repo-map.js +13 -14
  159. package/scripts/gds/gen-session-index.js +8 -8
  160. package/scripts/gds/http-api-client.js +12 -19
  161. package/scripts/gds/init.js +1 -1
  162. package/scripts/gds/leak-scan-allowlist.js +1 -1
  163. package/scripts/gds/lint-infra-exec.js +1 -1
  164. package/scripts/gds/local-preview-lib.js +8 -39
  165. package/scripts/gds/local-preview.js +2 -15
  166. package/scripts/gds/migration-namespace.js +3 -3
  167. package/scripts/gds/module-artifact.js +212 -0
  168. package/scripts/gds/module.js +95 -6
  169. package/scripts/gds/package-core.js +5 -116
  170. package/scripts/gds/plain-cards.js +1 -1
  171. package/scripts/gds/provision-config.js +2 -2
  172. package/scripts/gds/provision-net.js +2 -3
  173. package/scripts/gds/provision.js +9 -9
  174. package/scripts/gds/publish-manifest.js +1 -2
  175. package/scripts/gds/push-path-brief.js +10 -10
  176. package/scripts/gds/regen-instance-docs.js +1 -1
  177. package/scripts/gds/rename-history-check.js +1 -1
  178. package/scripts/gds/run-unit-tests.js +2 -16
  179. package/scripts/gds/sandbox-stage.js +39 -341
  180. package/scripts/gds/session-digest-build.js +2 -2
  181. package/scripts/gds/ship-deploy-target.js +13 -13
  182. package/scripts/gds/ship-flow.js +7 -7
  183. package/scripts/gds/ship-land.js +9 -10
  184. package/scripts/gds/ship-merge.js +4 -4
  185. package/scripts/gds/ship-preflight-steps.js +4 -4
  186. package/scripts/gds/ship-regen.js +15 -49
  187. package/scripts/gds/ship.js +13 -33
  188. package/scripts/gds/skill-preflight.js +9 -8
  189. package/scripts/gds/start.js +0 -15
  190. package/scripts/hall-preview/README.md +5 -5
  191. package/scripts/hall-preview/server.js +6 -6
  192. package/scripts/render-diagrams.sh +1 -1
  193. package/src/bongos/api-errors.js +1 -1
  194. package/src/bongos/api-prefix.js +4 -4
  195. package/src/bongos/auth-github.js +2 -3
  196. package/src/bongos/auth.js +51 -126
  197. package/src/bongos/db-kernel.js +2 -3
  198. package/src/bongos/db.js +5 -28
  199. package/src/bongos/module-scope-map.js +14 -29
  200. package/src/bongos/module-store.js +141 -0
  201. package/src/bongos/platform-visibility-gate.js +1 -1
  202. package/src/bongos/route-rank-check.js +3 -16
  203. package/src/bongos/routes/auth.js +0 -116
  204. package/src/bongos/routes/builders.js +5 -15
  205. package/src/bongos/routes/instance.js +8 -14
  206. package/src/bongos/routes/me.js +7 -23
  207. package/src/bongos/routes/modules.js +83 -0
  208. package/src/bongos/routes/security.js +3 -3
  209. package/src/bongos/routes.js +7 -2
  210. package/src/bongos/serve-internal.js +5 -6
  211. package/src/bongos-downloads.js +1 -6
  212. package/src/branding.js +2 -2
  213. package/src/build-info.js +4 -6
  214. package/src/instance-config.js +2 -2
  215. package/src/module-api.js +13 -6
  216. package/src/module-loader/catalog.js +3 -0
  217. package/src/module-loader/loader.js +4 -4
  218. package/src/module-loader/manifest-schema.js +16 -8
  219. package/src/module-seams.js +2 -3
  220. package/src/modules.js +42 -14
  221. package/tests/agents_spend_guard.mjs +1 -1
  222. package/tests/api_alias_caller_ratchet.mjs +1 -1
  223. package/tests/api_alias_exceptions.mjs +1 -1
  224. package/tests/api_client.mjs +1 -16
  225. package/tests/api_path_404.mjs +1 -1
  226. package/tests/auth_page_gate.mjs +10 -10
  227. package/tests/backfill_task_rewards.mjs +1 -1
  228. package/tests/canonical_profile_url.mjs +1 -3
  229. package/tests/claim_action.mjs +4 -1
  230. package/tests/claim_from_task_and_home.mjs +127 -0
  231. package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
  232. package/tests/cli_sessions.mjs +1 -1
  233. package/tests/consumer_layout_boot.mjs +2 -0
  234. package/tests/context_pack.mjs +13 -13
  235. package/tests/copy_desk_page_docx.mjs +33 -2
  236. package/tests/copy_inventory.mjs +7 -7
  237. package/tests/core_upgrade_runner.mjs +6 -6
  238. package/tests/credit_grant.mjs +2 -2
  239. package/tests/criterion_suggest.mjs +1 -1
  240. package/tests/deploy_divergence_line.mjs +11 -11
  241. package/tests/design_tokens_sync.mjs +3 -2
  242. package/tests/diagram_facts_offset.mjs +1 -1
  243. package/tests/do_api.mjs +106 -0
  244. package/tests/feedback_latest.mjs +122 -0
  245. package/tests/fitness.mjs +44 -42
  246. package/tests/fitness_ratchets.mjs +2 -2
  247. package/tests/gate_approvals.mjs +0 -1
  248. package/tests/github_push_land_proof.mjs +1 -1
  249. package/tests/go_live.mjs +3 -3
  250. package/tests/goal_advisory.mjs +3 -3
  251. package/tests/goal_suggest.mjs +3 -3
  252. package/tests/government_abuse_matrix.mjs +21 -19
  253. package/tests/government_ownership_scope.mjs +33 -35
  254. package/tests/government_parity.mjs +2 -2
  255. package/tests/government_protected_surfaces.mjs +56 -37
  256. package/tests/government_require_permission.mjs +3 -3
  257. package/tests/government_seed.mjs +37 -4
  258. package/tests/government_session_scope.mjs +92 -366
  259. package/tests/hall_audit.mjs +1 -1
  260. package/tests/hall_error_envelope.mjs +4 -0
  261. package/tests/hall_landing_boot.mjs +4 -1
  262. package/tests/hall_palette.mjs +1 -16
  263. package/tests/hall_record_world.mjs +6 -0
  264. package/tests/hall_tweak_editor.mjs +52 -1
  265. package/tests/helpers.mjs +1 -1
  266. package/tests/host_topology_skips.mjs +3 -4
  267. package/tests/html_comment_nesting.mjs +137 -0
  268. package/tests/infra_exec_paths.mjs +9 -10
  269. package/tests/init.mjs +4 -5
  270. package/tests/instance_manifest.mjs +18 -25
  271. package/tests/kickoff_checklist.mjs +1 -1
  272. package/tests/lib_sh_resolution.mjs +0 -221
  273. package/tests/local_preview.mjs +0 -12
  274. package/tests/migration_namespace.mjs +6 -6
  275. package/tests/module-scope-map.mjs +9 -9
  276. package/tests/module_api.mjs +2 -2
  277. package/tests/module_catalog.mjs +1 -1
  278. package/tests/module_cli.mjs +20 -20
  279. package/tests/module_contributions.mjs +6 -6
  280. package/tests/module_loader.mjs +6 -6
  281. package/tests/module_manifest.mjs +26 -17
  282. package/tests/module_route_rank.mjs +2 -2
  283. package/tests/module_store_publish.mjs +307 -0
  284. package/tests/module_store_publish_route.mjs +121 -0
  285. package/tests/module_store_registry_migration.mjs +70 -0
  286. package/tests/modules.mjs +56 -26
  287. package/tests/modules_route_wiring.mjs +1 -1
  288. package/tests/onboarding_route_signals.mjs +21 -31
  289. package/tests/onboarding_state.mjs +35 -49
  290. package/tests/paste_token_session_store.mjs +1 -1
  291. package/tests/permission_path.mjs +0 -3
  292. package/tests/platform_boot.mjs +1 -2
  293. package/tests/platform_visibility_gate.mjs +1 -1
  294. package/tests/profile_route.mjs +1 -6
  295. package/tests/project_modules_ui.mjs +17 -17
  296. package/tests/projects_hub.mjs +5 -5
  297. package/tests/projects_hub_app_step.mjs +138 -0
  298. package/tests/projects_hub_module_picker.mjs +77 -76
  299. package/tests/projects_hub_pre_uat.mjs +4 -5
  300. package/tests/provision.mjs +7 -7
  301. package/tests/provisioning_capacity.mjs +1 -1
  302. package/tests/provisioning_recommendations.mjs +2 -1
  303. package/tests/provisioning_starter_bundles.mjs +0 -22
  304. package/tests/publish_branch_route.mjs +11 -19
  305. package/tests/publish_manifest.mjs +2 -3
  306. package/tests/publish_reconciler.mjs +8 -8
  307. package/tests/push_path_brief.mjs +10 -9
  308. package/tests/rank_tier_single_source.mjs +1 -13
  309. package/tests/regrade_eligibility.mjs +1 -1
  310. package/tests/rename_history_restraint.mjs +1 -1
  311. package/tests/repo_map.mjs +12 -13
  312. package/tests/runner_drift.mjs +3 -21
  313. package/tests/sandbox_stage.mjs +57 -390
  314. package/tests/seam_wiring_guard.mjs +11 -11
  315. package/tests/search_isolation.mjs +3 -3
  316. package/tests/session_records.mjs +1 -1
  317. package/tests/session_rename_fallback.mjs +1 -22
  318. package/tests/session_start_freshness.mjs +3 -3
  319. package/tests/session_token_hash_db.mjs +1 -1
  320. package/tests/ship_card.mjs +1 -1
  321. package/tests/ship_ci_deploy.mjs +17 -17
  322. package/tests/ship_error_shape.mjs +1 -1
  323. package/tests/ship_premerge.mjs +1 -1
  324. package/tests/ship_resume.mjs +1 -1
  325. package/tests/skill_preflight.mjs +2 -2
  326. package/tests/skip_is_not_pass.mjs +1 -1
  327. package/tests/task_visual_slots.mjs +31 -0
  328. package/tests/terms_acceptance.mjs +2 -2
  329. package/tests/upgrade.mjs +4 -4
  330. package/tests/watch_sealed_floor.mjs +0 -1
  331. package/tests/wizard_draft_resume.mjs +2 -2
  332. package/tests/wizard_intent_resume.mjs +21 -10
  333. package/tests/wizard_preselect_why.mjs +18 -18
  334. package/docs/onboarding/browser-terminal-guide.md +0 -73
  335. package/docs/recipes/managed-settings-remote-control.md +0 -416
  336. package/modules/dev-box/CLAUDE.md +0 -15
  337. package/modules/dev-box/box-access.js +0 -623
  338. package/modules/dev-box/box-credential.js +0 -94
  339. package/modules/dev-box/box-onboard.js +0 -486
  340. package/modules/dev-box/boxes.js +0 -1038
  341. package/modules/dev-box/db.js +0 -39
  342. package/modules/dev-box/module.json +0 -19
  343. package/modules/dev-box/routes/box.js +0 -1223
  344. package/scripts/gds/box-auth-check.js +0 -160
  345. package/scripts/gds/box-infra.js +0 -321
  346. package/scripts/gds/box-sync.js +0 -216
  347. package/scripts/gds/box.js +0 -1332
  348. package/scripts/gds/cf-tunnel.js +0 -558
  349. package/scripts/gds/smoke-box.sh +0 -150
  350. package/scripts/gds/tree-preflight.js +0 -241
  351. package/src/bongos/app-pair.js +0 -172
  352. package/tests/app_pair.mjs +0 -168
  353. package/tests/box_access.mjs +0 -578
  354. package/tests/box_auth_check.mjs +0 -114
  355. package/tests/box_code_staleness.mjs +0 -129
  356. package/tests/box_connect_e2e.mjs +0 -196
  357. package/tests/box_cost.mjs +0 -70
  358. package/tests/box_credential.mjs +0 -125
  359. package/tests/box_dns_repoint.mjs +0 -153
  360. package/tests/box_env.mjs +0 -64
  361. package/tests/box_host_keys.mjs +0 -126
  362. package/tests/box_infra_resolve.mjs +0 -246
  363. package/tests/box_onboard.mjs +0 -317
  364. package/tests/box_scope_session.mjs +0 -119
  365. package/tests/box_sweep_docs.mjs +0 -119
  366. package/tests/box_sync.mjs +0 -55
  367. package/tests/box_sync_scope_report.mjs +0 -126
  368. package/tests/box_task_scope.mjs +0 -169
  369. package/tests/box_task_scope_pause.mjs +0 -66
  370. package/tests/box_terminal_ssrf.mjs +0 -96
  371. package/tests/boxes.mjs +0 -1585
  372. package/tests/cf_ruleset_preflight.mjs +0 -142
  373. package/tests/cf_tunnel.mjs +0 -496
  374. package/tests/docs_scrubber_damage.mjs +0 -111
  375. package/tests/module_scope_active_claims.mjs +0 -94
  376. package/tests/ship_api_client_pathspec.mjs +0 -100
  377. 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._