@happier-dev/stack 0.1.0-preview.5.1

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 (439) hide show
  1. package/README.md +501 -0
  2. package/bin/hstack.mjs +348 -0
  3. package/docs/codex-mcp-resume.md +129 -0
  4. package/docs/edison.md +74 -0
  5. package/docs/forking-and-branding.md +189 -0
  6. package/docs/happy-development.md +22 -0
  7. package/docs/isolated-linux-vm.md +243 -0
  8. package/docs/menubar.md +244 -0
  9. package/docs/mobile-ios.md +322 -0
  10. package/docs/monorepo-migration.md +20 -0
  11. package/docs/paths-and-env.md +154 -0
  12. package/docs/remote-access.md +43 -0
  13. package/docs/server-flavors.md +147 -0
  14. package/docs/stacks.md +330 -0
  15. package/docs/tauri.md +60 -0
  16. package/docs/worktrees-and-forks.md +133 -0
  17. package/extras/swiftbar/auth-login.sh +29 -0
  18. package/extras/swiftbar/git-cache-refresh.sh +122 -0
  19. package/extras/swiftbar/hstack-term.sh +133 -0
  20. package/extras/swiftbar/hstack.5s.sh +296 -0
  21. package/extras/swiftbar/hstack.sh +35 -0
  22. package/extras/swiftbar/icons/happy-green.png +0 -0
  23. package/extras/swiftbar/icons/happy-orange.png +0 -0
  24. package/extras/swiftbar/icons/happy-red.png +0 -0
  25. package/extras/swiftbar/icons/logo-white.png +0 -0
  26. package/extras/swiftbar/install.sh +265 -0
  27. package/extras/swiftbar/lib/git.sh +629 -0
  28. package/extras/swiftbar/lib/icons.sh +92 -0
  29. package/extras/swiftbar/lib/render.sh +999 -0
  30. package/extras/swiftbar/lib/system.sh +244 -0
  31. package/extras/swiftbar/lib/utils.sh +717 -0
  32. package/extras/swiftbar/set-interval.sh +65 -0
  33. package/extras/swiftbar/set-server-flavor.sh +61 -0
  34. package/extras/swiftbar/wt-pr.sh +140 -0
  35. package/node_modules/@happier-dev/cli-common/README.md +6 -0
  36. package/node_modules/@happier-dev/cli-common/dist/index.d.ts +4 -0
  37. package/node_modules/@happier-dev/cli-common/dist/index.d.ts.map +1 -0
  38. package/node_modules/@happier-dev/cli-common/dist/index.js +4 -0
  39. package/node_modules/@happier-dev/cli-common/dist/index.js.map +1 -0
  40. package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts +18 -0
  41. package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts.map +1 -0
  42. package/node_modules/@happier-dev/cli-common/dist/links/index.js +25 -0
  43. package/node_modules/@happier-dev/cli-common/dist/links/index.js.map +1 -0
  44. package/node_modules/@happier-dev/cli-common/dist/links.d.ts +2 -0
  45. package/node_modules/@happier-dev/cli-common/dist/links.d.ts.map +1 -0
  46. package/node_modules/@happier-dev/cli-common/dist/links.js +2 -0
  47. package/node_modules/@happier-dev/cli-common/dist/links.js.map +1 -0
  48. package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts +67 -0
  49. package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts.map +1 -0
  50. package/node_modules/@happier-dev/cli-common/dist/update/index.js +259 -0
  51. package/node_modules/@happier-dev/cli-common/dist/update/index.js.map +1 -0
  52. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts +17 -0
  53. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts.map +1 -0
  54. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js +80 -0
  55. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js.map +1 -0
  56. package/node_modules/@happier-dev/cli-common/package.json +26 -0
  57. package/package.json +77 -0
  58. package/scripts/auth.mjs +1829 -0
  59. package/scripts/auth_copy_from_pglite_lock_in_use.integration.test.mjs +90 -0
  60. package/scripts/auth_copy_from_runCapture.integration.test.mjs +447 -0
  61. package/scripts/auth_help_cmd.test.mjs +28 -0
  62. package/scripts/auth_login_flow_in_tty.test.mjs +100 -0
  63. package/scripts/auth_login_force_default.test.mjs +66 -0
  64. package/scripts/auth_login_guided_server_no_expo.test.mjs +126 -0
  65. package/scripts/auth_login_method_override.test.mjs +67 -0
  66. package/scripts/auth_login_print_includes_configure_links.test.mjs +99 -0
  67. package/scripts/auth_status_server_validation.integration.test.mjs +140 -0
  68. package/scripts/build.mjs +266 -0
  69. package/scripts/bundleWorkspaceDeps.mjs +38 -0
  70. package/scripts/bundleWorkspaceDeps.test.mjs +77 -0
  71. package/scripts/ci.mjs +135 -0
  72. package/scripts/ci.test.mjs +50 -0
  73. package/scripts/cli-link.mjs +57 -0
  74. package/scripts/completion.mjs +395 -0
  75. package/scripts/contrib.mjs +333 -0
  76. package/scripts/daemon.mjs +1160 -0
  77. package/scripts/daemon.status_scope.test.mjs +51 -0
  78. package/scripts/daemon_cmd.mjs +26 -0
  79. package/scripts/daemon_dist_guard.test.mjs +171 -0
  80. package/scripts/daemon_invalid_auth_reseed_stack_name.integration.test.mjs +608 -0
  81. package/scripts/daemon_server_scoped_state.test.mjs +49 -0
  82. package/scripts/daemon_start_verification.integration.test.mjs +296 -0
  83. package/scripts/dev.mjs +545 -0
  84. package/scripts/doctor.mjs +340 -0
  85. package/scripts/doctor_cmd.test.mjs +22 -0
  86. package/scripts/doctor_ui_index_missing.test.mjs +37 -0
  87. package/scripts/eas.mjs +367 -0
  88. package/scripts/eas_platform_parsing.test.mjs +63 -0
  89. package/scripts/edison.mjs +1848 -0
  90. package/scripts/env.mjs +149 -0
  91. package/scripts/env_cmd.test.mjs +118 -0
  92. package/scripts/exit_cleanup_kills_detached_children_on_crash.integration.test.mjs +80 -0
  93. package/scripts/happier.mjs +82 -0
  94. package/scripts/import.mjs +1327 -0
  95. package/scripts/init.mjs +464 -0
  96. package/scripts/install.mjs +550 -0
  97. package/scripts/lint.mjs +177 -0
  98. package/scripts/menubar.mjs +202 -0
  99. package/scripts/migrate.mjs +318 -0
  100. package/scripts/mobile.mjs +353 -0
  101. package/scripts/mobile_dev_client.mjs +87 -0
  102. package/scripts/monorepo.mjs +2234 -0
  103. package/scripts/monorepo_port.apply.integration.test.mjs +680 -0
  104. package/scripts/monorepo_port.conflicts.integration.test.mjs +454 -0
  105. package/scripts/monorepo_port.validation.integration.test.mjs +486 -0
  106. package/scripts/orchestrated_stack_auth_flow.test.mjs +134 -0
  107. package/scripts/orchestrated_stack_auth_flow_resolve_port.test.mjs +98 -0
  108. package/scripts/orchestrated_stack_auth_flow_webapp_url.test.mjs +119 -0
  109. package/scripts/pack.mjs +257 -0
  110. package/scripts/pack.test.mjs +68 -0
  111. package/scripts/pglite_lock.integration.test.mjs +152 -0
  112. package/scripts/provision/linux-ubuntu-e2e.sh +132 -0
  113. package/scripts/provision/linux-ubuntu-review-pr.sh +66 -0
  114. package/scripts/provision/macos-lima-happy-vm.sh +192 -0
  115. package/scripts/provision/macos-lima-hstack-e2e.sh +100 -0
  116. package/scripts/release.mjs +53 -0
  117. package/scripts/release_binary_smoke.integration.test.mjs +159 -0
  118. package/scripts/review.mjs +1752 -0
  119. package/scripts/review_pr.mjs +435 -0
  120. package/scripts/run.mjs +561 -0
  121. package/scripts/run_script_with_stack_env.restart_port_reuse.test.mjs +30 -0
  122. package/scripts/self.mjs +465 -0
  123. package/scripts/self_host.mjs +9 -0
  124. package/scripts/self_host_binary_smoke.integration.test.mjs +94 -0
  125. package/scripts/self_host_runtime.mjs +883 -0
  126. package/scripts/self_host_runtime.test.mjs +82 -0
  127. package/scripts/self_host_systemd.real.integration.test.mjs +367 -0
  128. package/scripts/server_flavor.mjs +148 -0
  129. package/scripts/service.mjs +868 -0
  130. package/scripts/service_mode_help.test.mjs +27 -0
  131. package/scripts/setup.mjs +1324 -0
  132. package/scripts/setup_non_interactive_flag.test.mjs +60 -0
  133. package/scripts/setup_pr.mjs +605 -0
  134. package/scripts/setup_pr_orchestrated_auth_flow_util_import.test.mjs +117 -0
  135. package/scripts/stack/command_arguments.mjs +91 -0
  136. package/scripts/stack/copy_auth_from_stack.mjs +111 -0
  137. package/scripts/stack/delegated_script_commands.mjs +92 -0
  138. package/scripts/stack/help_text.mjs +110 -0
  139. package/scripts/stack/port_reservation.mjs +74 -0
  140. package/scripts/stack/repo_checkout_resolution.mjs +31 -0
  141. package/scripts/stack/run_script_with_stack_env.mjs +634 -0
  142. package/scripts/stack/stack_daemon_command.mjs +219 -0
  143. package/scripts/stack/stack_delegated_help.mjs +81 -0
  144. package/scripts/stack/stack_environment.mjs +151 -0
  145. package/scripts/stack/stack_environment.sanitization.test.mjs +75 -0
  146. package/scripts/stack/stack_happier_passthrough_command.mjs +63 -0
  147. package/scripts/stack/stack_info_snapshot.mjs +167 -0
  148. package/scripts/stack/stack_mobile_install_command.mjs +61 -0
  149. package/scripts/stack/stack_resume_command.mjs +76 -0
  150. package/scripts/stack/stack_stop_command.mjs +34 -0
  151. package/scripts/stack/stack_workspace_command.mjs +83 -0
  152. package/scripts/stack/transient_repo_overrides.mjs +29 -0
  153. package/scripts/stack.mjs +2388 -0
  154. package/scripts/stack_archive_cmd.integration.test.mjs +31 -0
  155. package/scripts/stack_audit_fix_light_env.test.mjs +129 -0
  156. package/scripts/stack_background_pinned_stack_json.test.mjs +81 -0
  157. package/scripts/stack_copy_auth_server_scoped.test.mjs +243 -0
  158. package/scripts/stack_daemon_cmd.integration.test.mjs +484 -0
  159. package/scripts/stack_eas_help.test.mjs +72 -0
  160. package/scripts/stack_editor_workspace_monorepo_root.test.mjs +102 -0
  161. package/scripts/stack_env_cmd.test.mjs +107 -0
  162. package/scripts/stack_guided_login_bundle_error_parse.test.mjs +20 -0
  163. package/scripts/stack_guided_login_inner_invocation.test.mjs +46 -0
  164. package/scripts/stack_happy_cmd.integration.test.mjs +263 -0
  165. package/scripts/stack_info_snapshot_running_status.test.mjs +186 -0
  166. package/scripts/stack_interactive_monorepo_group.test.mjs +128 -0
  167. package/scripts/stack_monorepo_defaults.test.mjs +31 -0
  168. package/scripts/stack_monorepo_repo_dev_token.test.mjs +32 -0
  169. package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +37 -0
  170. package/scripts/stack_new_name_normalize_cmd.test.mjs +38 -0
  171. package/scripts/stack_pr_name_normalize_cmd.test.mjs +84 -0
  172. package/scripts/stack_resume_cmd.integration.test.mjs +134 -0
  173. package/scripts/stack_server_flavors_defaults.test.mjs +64 -0
  174. package/scripts/stack_shorthand_cmd.integration.test.mjs +74 -0
  175. package/scripts/stack_stop_sweeps_legacy_infra_without_kind.integration.test.mjs +44 -0
  176. package/scripts/stack_stop_sweeps_when_runtime_missing.integration.test.mjs +42 -0
  177. package/scripts/stack_stop_sweeps_when_runtime_stale.integration.test.mjs +50 -0
  178. package/scripts/stack_wt_list.test.mjs +117 -0
  179. package/scripts/start_ui_required_default.test.mjs +63 -0
  180. package/scripts/stop.mjs +190 -0
  181. package/scripts/stopStackWithEnv_no_autosweep_when_runtime_missing.integration.test.mjs +95 -0
  182. package/scripts/swiftbar_git_monorepo_cmd.test.mjs +75 -0
  183. package/scripts/swiftbar_render_monorepo_wt_actions.integration.test.mjs +116 -0
  184. package/scripts/swiftbar_utils_cmd.test.mjs +92 -0
  185. package/scripts/swiftbar_wt_pr_backcompat.test.mjs +162 -0
  186. package/scripts/systemd_unit_info.test.mjs +24 -0
  187. package/scripts/tailscale.mjs +490 -0
  188. package/scripts/test_ci.mjs +36 -0
  189. package/scripts/test_cmd.mjs +274 -0
  190. package/scripts/test_cmd.test.mjs +133 -0
  191. package/scripts/test_integration.mjs +33 -0
  192. package/scripts/testkit/auth_testkit.mjs +121 -0
  193. package/scripts/testkit/doctor_testkit.mjs +68 -0
  194. package/scripts/testkit/monorepo_port_testkit.mjs +157 -0
  195. package/scripts/testkit/stack_archive_command_testkit.mjs +55 -0
  196. package/scripts/testkit/stack_new_monorepo_testkit.mjs +83 -0
  197. package/scripts/testkit/stack_script_command_testkit.mjs +27 -0
  198. package/scripts/testkit/stack_stop_sweeps_testkit.mjs +172 -0
  199. package/scripts/testkit/worktrees_monorepo_testkit.mjs +53 -0
  200. package/scripts/tools.mjs +70 -0
  201. package/scripts/tui.mjs +914 -0
  202. package/scripts/tui_stopStackForTuiExit_no_autosweep.integration.test.mjs +95 -0
  203. package/scripts/typecheck.mjs +178 -0
  204. package/scripts/ui_gateway.mjs +247 -0
  205. package/scripts/uninstall.mjs +179 -0
  206. package/scripts/utils/auth/credentials_paths.mjs +181 -0
  207. package/scripts/utils/auth/credentials_paths.test.mjs +187 -0
  208. package/scripts/utils/auth/daemon_gate.mjs +66 -0
  209. package/scripts/utils/auth/daemon_gate.test.mjs +116 -0
  210. package/scripts/utils/auth/decode_jwt_payload_unsafe.mjs +16 -0
  211. package/scripts/utils/auth/dev_key.mjs +163 -0
  212. package/scripts/utils/auth/files.mjs +56 -0
  213. package/scripts/utils/auth/guided_pr_auth.mjs +86 -0
  214. package/scripts/utils/auth/guided_stack_web_login.mjs +56 -0
  215. package/scripts/utils/auth/handy_master_secret.mjs +42 -0
  216. package/scripts/utils/auth/interactive_stack_auth.mjs +70 -0
  217. package/scripts/utils/auth/login_ux.mjs +105 -0
  218. package/scripts/utils/auth/orchestrated_stack_auth_flow.mjs +291 -0
  219. package/scripts/utils/auth/sources.mjs +28 -0
  220. package/scripts/utils/auth/stable_scope_id.mjs +91 -0
  221. package/scripts/utils/auth/stable_scope_id.test.mjs +51 -0
  222. package/scripts/utils/auth/stack_guided_login.mjs +438 -0
  223. package/scripts/utils/cli/arg_values.mjs +23 -0
  224. package/scripts/utils/cli/arg_values.test.mjs +43 -0
  225. package/scripts/utils/cli/args.mjs +17 -0
  226. package/scripts/utils/cli/cli.mjs +24 -0
  227. package/scripts/utils/cli/cli_registry.mjs +440 -0
  228. package/scripts/utils/cli/cwd_scope.mjs +158 -0
  229. package/scripts/utils/cli/cwd_scope.test.mjs +154 -0
  230. package/scripts/utils/cli/flags.mjs +17 -0
  231. package/scripts/utils/cli/log_forwarder.mjs +157 -0
  232. package/scripts/utils/cli/normalize.mjs +16 -0
  233. package/scripts/utils/cli/prereqs.mjs +103 -0
  234. package/scripts/utils/cli/prereqs.test.mjs +33 -0
  235. package/scripts/utils/cli/progress.mjs +141 -0
  236. package/scripts/utils/cli/smoke_help.mjs +44 -0
  237. package/scripts/utils/cli/verbosity.mjs +11 -0
  238. package/scripts/utils/cli/wizard.mjs +139 -0
  239. package/scripts/utils/cli/wizard_promptSelect.test.mjs +44 -0
  240. package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +132 -0
  241. package/scripts/utils/cli/wizard_worktree_slug.test.mjs +33 -0
  242. package/scripts/utils/crypto/tokens.mjs +14 -0
  243. package/scripts/utils/dev/daemon.mjs +232 -0
  244. package/scripts/utils/dev/daemon_watch_resilience.test.mjs +224 -0
  245. package/scripts/utils/dev/expo_dev.buildEnv.test.mjs +35 -0
  246. package/scripts/utils/dev/expo_dev.mjs +478 -0
  247. package/scripts/utils/dev/expo_dev.test.mjs +89 -0
  248. package/scripts/utils/dev/expo_dev_restart_port_reservation.test.mjs +120 -0
  249. package/scripts/utils/dev/expo_dev_verbose_logs.test.mjs +60 -0
  250. package/scripts/utils/dev/server.mjs +180 -0
  251. package/scripts/utils/dev_auth_key.mjs +7 -0
  252. package/scripts/utils/edison/git_roots.mjs +30 -0
  253. package/scripts/utils/edison/git_roots.test.mjs +49 -0
  254. package/scripts/utils/env/config.mjs +52 -0
  255. package/scripts/utils/env/dotenv.mjs +32 -0
  256. package/scripts/utils/env/dotenv.test.mjs +32 -0
  257. package/scripts/utils/env/env.mjs +130 -0
  258. package/scripts/utils/env/env_file.mjs +98 -0
  259. package/scripts/utils/env/env_file.test.mjs +49 -0
  260. package/scripts/utils/env/env_local.mjs +25 -0
  261. package/scripts/utils/env/load_env_file.mjs +34 -0
  262. package/scripts/utils/env/read.mjs +30 -0
  263. package/scripts/utils/env/sandbox.mjs +13 -0
  264. package/scripts/utils/env/scrub_env.mjs +69 -0
  265. package/scripts/utils/env/scrub_env.test.mjs +102 -0
  266. package/scripts/utils/env/values.mjs +13 -0
  267. package/scripts/utils/expo/command.mjs +65 -0
  268. package/scripts/utils/expo/expo.mjs +139 -0
  269. package/scripts/utils/expo/expo_state_running.test.mjs +48 -0
  270. package/scripts/utils/expo/metro_ports.mjs +101 -0
  271. package/scripts/utils/expo/metro_ports.test.mjs +35 -0
  272. package/scripts/utils/fs/atomic_dir_swap.mjs +55 -0
  273. package/scripts/utils/fs/atomic_dir_swap.test.mjs +54 -0
  274. package/scripts/utils/fs/file_has_content.mjs +10 -0
  275. package/scripts/utils/fs/fs.mjs +11 -0
  276. package/scripts/utils/fs/json.mjs +25 -0
  277. package/scripts/utils/fs/ops.mjs +29 -0
  278. package/scripts/utils/fs/package_json.mjs +8 -0
  279. package/scripts/utils/fs/tail.mjs +12 -0
  280. package/scripts/utils/git/dev_checkout.mjs +127 -0
  281. package/scripts/utils/git/dev_checkout.test.mjs +115 -0
  282. package/scripts/utils/git/git.mjs +67 -0
  283. package/scripts/utils/git/parse_name_status_z.mjs +21 -0
  284. package/scripts/utils/git/refs.mjs +26 -0
  285. package/scripts/utils/git/worktrees.mjs +323 -0
  286. package/scripts/utils/git/worktrees_monorepo.test.mjs +60 -0
  287. package/scripts/utils/git/worktrees_pathstyle.test.mjs +53 -0
  288. package/scripts/utils/llm/assist.mjs +260 -0
  289. package/scripts/utils/llm/codex_exec.mjs +61 -0
  290. package/scripts/utils/llm/codex_exec.test.mjs +46 -0
  291. package/scripts/utils/llm/hstack_runner.mjs +59 -0
  292. package/scripts/utils/llm/tools.mjs +56 -0
  293. package/scripts/utils/llm/tools.test.mjs +67 -0
  294. package/scripts/utils/menubar/swiftbar.mjs +121 -0
  295. package/scripts/utils/menubar/swiftbar.test.mjs +85 -0
  296. package/scripts/utils/mobile/config.mjs +35 -0
  297. package/scripts/utils/mobile/dev_client_links.mjs +59 -0
  298. package/scripts/utils/mobile/identifiers.mjs +46 -0
  299. package/scripts/utils/mobile/identifiers.test.mjs +41 -0
  300. package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
  301. package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +131 -0
  302. package/scripts/utils/net/bind_mode.mjs +39 -0
  303. package/scripts/utils/net/dns.mjs +10 -0
  304. package/scripts/utils/net/lan_ip.mjs +24 -0
  305. package/scripts/utils/net/ports.mjs +110 -0
  306. package/scripts/utils/net/tcp_forward.mjs +162 -0
  307. package/scripts/utils/net/url.mjs +30 -0
  308. package/scripts/utils/net/url.test.mjs +29 -0
  309. package/scripts/utils/paths/canonical_home.mjs +15 -0
  310. package/scripts/utils/paths/canonical_home.test.mjs +28 -0
  311. package/scripts/utils/paths/localhost_host.mjs +112 -0
  312. package/scripts/utils/paths/localhost_host.test.mjs +58 -0
  313. package/scripts/utils/paths/paths.mjs +302 -0
  314. package/scripts/utils/paths/paths_env_win32.test.mjs +36 -0
  315. package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
  316. package/scripts/utils/paths/paths_server_flavors.test.mjs +50 -0
  317. package/scripts/utils/paths/runtime.mjs +41 -0
  318. package/scripts/utils/pglite_lock.mjs +107 -0
  319. package/scripts/utils/proc/commands.mjs +33 -0
  320. package/scripts/utils/proc/exit_cleanup.mjs +57 -0
  321. package/scripts/utils/proc/happy_monorepo_deps.mjs +37 -0
  322. package/scripts/utils/proc/happy_monorepo_deps.test.mjs +89 -0
  323. package/scripts/utils/proc/ownership.mjs +217 -0
  324. package/scripts/utils/proc/ownership_killProcessGroupOwnedByStack.test.mjs +216 -0
  325. package/scripts/utils/proc/ownership_listPidsWithEnvNeedles.test.mjs +88 -0
  326. package/scripts/utils/proc/package_scripts.mjs +38 -0
  327. package/scripts/utils/proc/package_scripts.test.mjs +58 -0
  328. package/scripts/utils/proc/parallel.mjs +25 -0
  329. package/scripts/utils/proc/pids.mjs +11 -0
  330. package/scripts/utils/proc/pm.mjs +478 -0
  331. package/scripts/utils/proc/pm_spawn.integration.test.mjs +131 -0
  332. package/scripts/utils/proc/pm_stack_cache_env.test.mjs +313 -0
  333. package/scripts/utils/proc/proc.mjs +331 -0
  334. package/scripts/utils/proc/proc.test.mjs +85 -0
  335. package/scripts/utils/proc/terminate.mjs +69 -0
  336. package/scripts/utils/proc/terminate.test.mjs +54 -0
  337. package/scripts/utils/proc/watch.mjs +63 -0
  338. package/scripts/utils/review/augment_runner_integration.test.mjs +105 -0
  339. package/scripts/utils/review/base_ref.mjs +82 -0
  340. package/scripts/utils/review/base_ref.test.mjs +89 -0
  341. package/scripts/utils/review/chunks.mjs +55 -0
  342. package/scripts/utils/review/chunks.test.mjs +107 -0
  343. package/scripts/utils/review/detached_worktree.mjs +61 -0
  344. package/scripts/utils/review/detached_worktree.test.mjs +61 -0
  345. package/scripts/utils/review/findings.mjs +278 -0
  346. package/scripts/utils/review/findings.test.mjs +203 -0
  347. package/scripts/utils/review/head_slice.mjs +132 -0
  348. package/scripts/utils/review/head_slice.test.mjs +117 -0
  349. package/scripts/utils/review/instructions/deep.md +20 -0
  350. package/scripts/utils/review/prompts.mjs +279 -0
  351. package/scripts/utils/review/prompts.test.mjs +77 -0
  352. package/scripts/utils/review/run_reviewers_safe.mjs +12 -0
  353. package/scripts/utils/review/run_reviewers_safe.test.mjs +45 -0
  354. package/scripts/utils/review/runners/augment.mjs +91 -0
  355. package/scripts/utils/review/runners/augment.test.mjs +64 -0
  356. package/scripts/utils/review/runners/claude.mjs +92 -0
  357. package/scripts/utils/review/runners/claude.test.mjs +47 -0
  358. package/scripts/utils/review/runners/coderabbit.mjs +105 -0
  359. package/scripts/utils/review/runners/coderabbit.test.mjs +32 -0
  360. package/scripts/utils/review/runners/codex.mjs +129 -0
  361. package/scripts/utils/review/runners/codex.test.mjs +115 -0
  362. package/scripts/utils/review/slice_mode.mjs +20 -0
  363. package/scripts/utils/review/slice_mode.test.mjs +69 -0
  364. package/scripts/utils/review/sliced_runner.mjs +39 -0
  365. package/scripts/utils/review/sliced_runner.test.mjs +57 -0
  366. package/scripts/utils/review/slices.mjs +140 -0
  367. package/scripts/utils/review/slices.test.mjs +41 -0
  368. package/scripts/utils/review/targets.mjs +23 -0
  369. package/scripts/utils/review/targets.test.mjs +31 -0
  370. package/scripts/utils/review/tool_home_seed.mjs +106 -0
  371. package/scripts/utils/review/tool_home_seed.test.mjs +124 -0
  372. package/scripts/utils/review/uncommitted_ops.mjs +77 -0
  373. package/scripts/utils/review/uncommitted_ops.test.mjs +117 -0
  374. package/scripts/utils/sandbox/review_pr_sandbox.mjs +105 -0
  375. package/scripts/utils/server/apply_server_light_env_defaults.mjs +14 -0
  376. package/scripts/utils/server/flavor_scripts.mjs +138 -0
  377. package/scripts/utils/server/flavor_scripts.test.mjs +115 -0
  378. package/scripts/utils/server/infra/happy_server_infra.mjs +444 -0
  379. package/scripts/utils/server/mobile_api_url.mjs +60 -0
  380. package/scripts/utils/server/mobile_api_url.test.mjs +58 -0
  381. package/scripts/utils/server/port.mjs +55 -0
  382. package/scripts/utils/server/prisma_import.mjs +36 -0
  383. package/scripts/utils/server/prisma_import.test.mjs +78 -0
  384. package/scripts/utils/server/server.mjs +109 -0
  385. package/scripts/utils/server/ui_build_check.mjs +37 -0
  386. package/scripts/utils/server/ui_build_check.test.mjs +70 -0
  387. package/scripts/utils/server/ui_env.mjs +13 -0
  388. package/scripts/utils/server/ui_env.test.mjs +57 -0
  389. package/scripts/utils/server/urls.mjs +100 -0
  390. package/scripts/utils/server/validate.mjs +60 -0
  391. package/scripts/utils/server/validate.test.mjs +76 -0
  392. package/scripts/utils/service/autostart_darwin.mjs +198 -0
  393. package/scripts/utils/service/autostart_darwin.test.mjs +49 -0
  394. package/scripts/utils/service/autostart_darwin_keepalive.test.mjs +19 -0
  395. package/scripts/utils/stack/cli_identities.mjs +29 -0
  396. package/scripts/utils/stack/context.mjs +19 -0
  397. package/scripts/utils/stack/dirs.mjs +26 -0
  398. package/scripts/utils/stack/editor_workspace.mjs +126 -0
  399. package/scripts/utils/stack/interactive_stack_config.mjs +266 -0
  400. package/scripts/utils/stack/interactive_stack_config.port_validation.test.mjs +93 -0
  401. package/scripts/utils/stack/interactive_stack_config.remote_validation.test.mjs +122 -0
  402. package/scripts/utils/stack/interactive_stack_config.stack_name_validation.test.mjs +76 -0
  403. package/scripts/utils/stack/interactive_stack_config_testkit.mjs +18 -0
  404. package/scripts/utils/stack/names.mjs +27 -0
  405. package/scripts/utils/stack/names.test.mjs +26 -0
  406. package/scripts/utils/stack/pr_stack_name.mjs +16 -0
  407. package/scripts/utils/stack/runtime_state.mjs +88 -0
  408. package/scripts/utils/stack/stacks.mjs +40 -0
  409. package/scripts/utils/stack/startup.mjs +370 -0
  410. package/scripts/utils/stack/startup_server_light_dirs.test.mjs +119 -0
  411. package/scripts/utils/stack/startup_server_light_generate.test.mjs +20 -0
  412. package/scripts/utils/stack/startup_server_light_legacy.test.mjs +79 -0
  413. package/scripts/utils/stack/startup_server_light_testkit.mjs +106 -0
  414. package/scripts/utils/stack/stop.mjs +284 -0
  415. package/scripts/utils/stack_context.mjs +1 -0
  416. package/scripts/utils/stack_runtime_state.mjs +1 -0
  417. package/scripts/utils/stacks.mjs +1 -0
  418. package/scripts/utils/tailscale/ip.mjs +116 -0
  419. package/scripts/utils/tauri/stack_overrides.mjs +22 -0
  420. package/scripts/utils/test/collect_test_files.mjs +29 -0
  421. package/scripts/utils/time/get_today_ymd.mjs +7 -0
  422. package/scripts/utils/tui/cleanup.mjs +38 -0
  423. package/scripts/utils/ui/ansi.mjs +47 -0
  424. package/scripts/utils/ui/browser.mjs +31 -0
  425. package/scripts/utils/ui/browser.test.mjs +56 -0
  426. package/scripts/utils/ui/clipboard.mjs +38 -0
  427. package/scripts/utils/ui/layout.mjs +44 -0
  428. package/scripts/utils/ui/qr.mjs +17 -0
  429. package/scripts/utils/ui/terminal_launcher.mjs +129 -0
  430. package/scripts/utils/ui/text.mjs +16 -0
  431. package/scripts/utils/update/auto_update_notice.mjs +93 -0
  432. package/scripts/utils/validate.mjs +5 -0
  433. package/scripts/where.mjs +138 -0
  434. package/scripts/worktrees.mjs +2174 -0
  435. package/scripts/worktrees_archive_cmd.integration.test.mjs +228 -0
  436. package/scripts/worktrees_cursor_monorepo_root.test.mjs +23 -0
  437. package/scripts/worktrees_list_specs_no_recurse.test.mjs +32 -0
  438. package/scripts/worktrees_monorepo_testkit.test.mjs +29 -0
  439. package/scripts/worktrees_monorepo_use_group.test.mjs +41 -0
@@ -0,0 +1,561 @@
1
+ import './utils/env/env.mjs';
2
+ import { parseArgs } from './utils/cli/args.mjs';
3
+ import { pathExists } from './utils/fs/fs.mjs';
4
+ import { killProcessTree, runCapture, spawnProc } from './utils/proc/proc.mjs';
5
+ import { getComponentDir, getDefaultAutostartPaths, getRootDir } from './utils/paths/paths.mjs';
6
+ import { killPortListeners } from './utils/net/ports.mjs';
7
+ import { getServerComponentName, isHappierServerRunning, waitForServerReady } from './utils/server/server.mjs';
8
+ import { ensureCliBuilt, ensureDepsInstalled, pmExecBin, pmSpawnScript, requireDir } from './utils/proc/pm.mjs';
9
+ import { join } from 'node:path';
10
+ import { setTimeout as delay } from 'node:timers/promises';
11
+ import { maybeResetTailscaleServe } from './tailscale.mjs';
12
+ import { isDaemonRunning, startLocalDaemonWithAuth, stopLocalDaemon } from './daemon.mjs';
13
+ import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
14
+ import { assertServerComponentDirMatches, assertServerPrismaProviderMatches } from './utils/server/validate.mjs';
15
+ import { resolveServerStartScript } from './utils/server/flavor_scripts.mjs';
16
+ import { applyHappyServerMigrations, ensureHappyServerManagedInfra } from './utils/server/infra/happy_server_infra.mjs';
17
+ import { applyServerLightEnvDefaults } from './utils/server/apply_server_light_env_defaults.mjs';
18
+ import { getAccountCountForServerComponent, prepareDaemonAuthSeedIfNeeded, resolveAutoCopyFromMainEnabled } from './utils/stack/startup.mjs';
19
+ import { recordStackRuntimeStart, recordStackRuntimeUpdate } from './utils/stack/runtime_state.mjs';
20
+ import { resolveStackContext } from './utils/stack/context.mjs';
21
+ import { getPublicServerUrlEnvOverride, resolveServerPortFromEnv, resolveServerUrls } from './utils/server/urls.mjs';
22
+ import { preferStackLocalhostUrl } from './utils/paths/localhost_host.mjs';
23
+ import { openUrlInBrowser } from './utils/ui/browser.mjs';
24
+ import { ensureDevExpoServer, resolveExpoTailscaleEnabled } from './utils/dev/expo_dev.mjs';
25
+ import { maybeRunInteractiveStackAuthSetup } from './utils/auth/interactive_stack_auth.mjs';
26
+ import { getInvokedCwd, inferComponentFromCwd } from './utils/cli/cwd_scope.mjs';
27
+ import { daemonStartGate, formatDaemonAuthRequiredError } from './utils/auth/daemon_gate.mjs';
28
+ import { resolveServerUiEnv } from './utils/server/ui_env.mjs';
29
+ import { applyBindModeToEnv, resolveBindModeFromArgs } from './utils/net/bind_mode.mjs';
30
+ import { cmd, sectionTitle } from './utils/ui/layout.mjs';
31
+ import { cyan, dim, green, yellow } from './utils/ui/ansi.mjs';
32
+ import { isSandboxed } from './utils/env/sandbox.mjs';
33
+ import { installExitCleanup } from './utils/proc/exit_cleanup.mjs';
34
+ import { expandHome } from './utils/paths/canonical_home.mjs';
35
+ import { validateUiServingConfig } from './utils/server/ui_build_check.mjs';
36
+
37
+ /**
38
+ * Run the local stack in "production-like" mode:
39
+ * - server (happier-server-light by default)
40
+ * - happier-cli daemon
41
+ * - optionally serve prebuilt UI (via server or gateway)
42
+ *
43
+ * Optional: Expo dev-client Metro for mobile reviewers (`--mobile`).
44
+ */
45
+
46
+ async function main() {
47
+ const argv = process.argv.slice(2);
48
+ const { flags, kv } = parseArgs(argv);
49
+ const json = wantsJson(argv, { flags });
50
+ if (wantsHelp(argv, { flags })) {
51
+ printResult({
52
+ json,
53
+ data: {
54
+ flags: [
55
+ '--server=happier-server|happier-server-light',
56
+ '--server-flavor=light|full',
57
+ '--no-ui',
58
+ '--no-daemon',
59
+ '--restart',
60
+ '--no-browser',
61
+ '--mobile',
62
+ '--expo-tailscale',
63
+ '--bind=loopback|lan',
64
+ '--loopback',
65
+ '--lan',
66
+ ],
67
+ json: true,
68
+ },
69
+ text: [
70
+ '[start] usage:',
71
+ ' hstack start [--server=happier-server|happier-server-light] [--server-flavor=light|full] [--restart] [--json]',
72
+ ' hstack start --mobile # also start Expo dev-client Metro for mobile',
73
+ ' hstack start --expo-tailscale # forward Expo to Tailscale interface for remote access',
74
+ ' hstack start --bind=loopback # prefer localhost-only URLs (not reachable from phones)',
75
+ ' note: --json prints the resolved config (dry-run) and exits.',
76
+ '',
77
+ 'note:',
78
+ ' If run from inside a repo checkout/worktree, that checkout is used for this run (without requiring `hstack wt use`).',
79
+ ].join('\n'),
80
+ });
81
+ return;
82
+ }
83
+
84
+ const rootDir = getRootDir(import.meta.url);
85
+
86
+ // Optional bind-mode override (affects Expo host/origins; best-effort sets HOST too).
87
+ const bindMode = resolveBindModeFromArgs({ flags, kv });
88
+ if (bindMode) {
89
+ applyBindModeToEnv(process.env, bindMode);
90
+ }
91
+
92
+ // Outside sandbox mode we allow a convenience: if you run `hstack start` from inside a repo checkout/worktree,
93
+ // we use that checkout even if you never ran `hstack wt use`.
94
+ //
95
+ // In sandbox mode this would break isolation by pointing at your "real" checkout, so we disable it.
96
+ if (!isSandboxed()) {
97
+ const inferred = inferComponentFromCwd({
98
+ rootDir,
99
+ invokedCwd: getInvokedCwd(process.env),
100
+ components: ['happier-ui', 'happier-cli', 'happier-server-light', 'happier-server'],
101
+ });
102
+ if (inferred) {
103
+ // Stack env should win. Only infer from CWD when the repo dir isn't already configured.
104
+ if (!(process.env.HAPPIER_STACK_REPO_DIR ?? '').toString().trim()) {
105
+ process.env.HAPPIER_STACK_REPO_DIR = inferred.repoDir;
106
+ }
107
+ }
108
+ }
109
+
110
+ const serverPort = resolveServerPortFromEnv({ defaultPort: 3005 });
111
+
112
+ // Internal URL used by local processes on this machine.
113
+ const internalServerUrl = `http://127.0.0.1:${serverPort}`;
114
+ // Public URL is what you might share/open (e.g. https://<machine>.<tailnet>.ts.net).
115
+ // We auto-prefer the Tailscale HTTPS URL when available, unless explicitly overridden.
116
+ const { defaultPublicUrl, envPublicUrl, publicServerUrl: publicServerUrlPreview } = getPublicServerUrlEnvOverride({ serverPort });
117
+ let publicServerUrl = publicServerUrlPreview;
118
+
119
+ // Convenience alias: allow `--server-flavor=light|full` for parity with `stack pr` and `tools setup-pr`.
120
+ // `--server=...` always wins when both are specified.
121
+ const serverFlavorFromArg = (kv.get('--server-flavor') ?? '').trim().toLowerCase();
122
+ if (!kv.get('--server') && serverFlavorFromArg) {
123
+ if (serverFlavorFromArg === 'light') kv.set('--server', 'happier-server-light');
124
+ else if (serverFlavorFromArg === 'full') kv.set('--server', 'happier-server');
125
+ else throw new Error(`[start] invalid --server-flavor=${serverFlavorFromArg} (expected: light|full)`);
126
+ }
127
+
128
+ const serverComponentName = getServerComponentName({ kv });
129
+ if (serverComponentName === 'both') {
130
+ throw new Error(`[local] --server=both is not supported for run (pick one: happier-server-light or happier-server)`);
131
+ }
132
+
133
+ const startDaemon = !flags.has('--no-daemon') && (process.env.HAPPIER_STACK_DAEMON ?? '1') !== '0';
134
+ const serveUiWanted = !flags.has('--no-ui') && (process.env.HAPPIER_STACK_SERVE_UI ?? '1') !== '0';
135
+ let serveUi = serveUiWanted;
136
+ // Capability semantics: if UI serving is enabled, default to "required" (fail closed)
137
+ // unless explicitly disabled.
138
+ const uiRequiredRaw = (process.env.HAPPIER_STACK_UI_REQUIRED ?? '').toString().trim();
139
+ const uiRequired = uiRequiredRaw ? uiRequiredRaw !== '0' : Boolean(serveUiWanted);
140
+ const startMobile = flags.has('--mobile') || flags.has('--with-mobile');
141
+ const expoTailscale = flags.has('--expo-tailscale') || resolveExpoTailscaleEnabled({ env: process.env });
142
+ const noBrowser = flags.has('--no-browser') || (process.env.HAPPIER_STACK_NO_BROWSER ?? '').toString().trim() === '1';
143
+ const uiPrefix = process.env.HAPPIER_STACK_UI_PREFIX?.trim() ? process.env.HAPPIER_STACK_UI_PREFIX.trim() : '/';
144
+ const autostart = getDefaultAutostartPaths();
145
+ const uiBuildDir = process.env.HAPPIER_STACK_UI_BUILD_DIR?.trim()
146
+ ? process.env.HAPPIER_STACK_UI_BUILD_DIR.trim()
147
+ : join(autostart.baseDir, 'ui');
148
+
149
+ const enableTailscaleServe = (process.env.HAPPIER_STACK_TAILSCALE_SERVE ?? '0') === '1';
150
+
151
+ const serverDir = getComponentDir(rootDir, serverComponentName);
152
+ const cliDir = getComponentDir(rootDir, 'happier-cli');
153
+ const uiDir = getComponentDir(rootDir, 'happier-ui');
154
+
155
+ const cliBin = join(cliDir, 'bin', 'happier.mjs');
156
+
157
+ const cliHomeDir = process.env.HAPPIER_STACK_CLI_HOME_DIR?.trim()
158
+ ? expandHome(process.env.HAPPIER_STACK_CLI_HOME_DIR.trim())
159
+ : join(autostart.baseDir, 'cli');
160
+ const restart = flags.has('--restart');
161
+
162
+ if (json) {
163
+ printResult({
164
+ json,
165
+ data: {
166
+ mode: 'start',
167
+ serverComponentName,
168
+ serverDir,
169
+ uiDir,
170
+ cliDir,
171
+ serverPort,
172
+ internalServerUrl,
173
+ publicServerUrl,
174
+ startDaemon,
175
+ serveUi,
176
+ uiRequired,
177
+ startMobile,
178
+ uiPrefix,
179
+ uiBuildDir,
180
+ cliHomeDir,
181
+ },
182
+ });
183
+ return;
184
+ }
185
+
186
+ const serverStartScript = resolveServerStartScript({ serverComponentName, serverDir });
187
+
188
+ assertServerComponentDirMatches({ rootDir, serverComponentName, serverDir });
189
+ assertServerPrismaProviderMatches({ serverComponentName, serverDir });
190
+
191
+ await requireDir(serverComponentName, serverDir);
192
+ await requireDir('happier-cli', cliDir);
193
+ if (startMobile) {
194
+ await requireDir('happier-ui', uiDir);
195
+ }
196
+
197
+ const uiBuildDirExists = await pathExists(uiBuildDir);
198
+ const uiIndexExists = serveUi && uiBuildDirExists ? await pathExists(join(uiBuildDir, 'index.html')) : false;
199
+ {
200
+ const validated = validateUiServingConfig({
201
+ serverComponentName,
202
+ serveUiWanted: serveUi,
203
+ uiRequired,
204
+ uiBuildDir,
205
+ uiBuildDirExists,
206
+ uiIndexExists,
207
+ });
208
+ serveUi = validated.serveUi;
209
+ if (!serveUi && validated.warning) {
210
+ // For happier-server, UI serving is optional; warn and continue.
211
+ if (serverComponentName !== 'happier-server-light') {
212
+ console.log(`${yellow('!')} ${validated.warning}`);
213
+ }
214
+ }
215
+ }
216
+
217
+ const children = [];
218
+ let shuttingDown = false;
219
+ installExitCleanup({ label: 'local', children });
220
+ const baseEnv = { ...process.env };
221
+ const stackCtx = resolveStackContext({ env: baseEnv, autostart });
222
+ const { stackMode, runtimeStatePath, stackName, envPath, ephemeral } = stackCtx;
223
+
224
+ // Ensure happier-cli is install+build ready before starting the daemon.
225
+ const buildCli = (baseEnv.HAPPIER_STACK_CLI_BUILD ?? '1').toString().trim() !== '0';
226
+ await ensureCliBuilt(cliDir, { buildCli });
227
+
228
+ // Ensure server deps exist before any Prisma/docker work.
229
+ await ensureDepsInstalled(serverDir, serverComponentName);
230
+ if (startMobile) {
231
+ await ensureDepsInstalled(uiDir, 'happier-ui');
232
+ }
233
+
234
+ // Public URL automation:
235
+ // - Only the main stack should ever auto-enable Tailscale Serve by default.
236
+ // - Non-main stacks default to localhost unless the user explicitly configured a public URL
237
+ // OR Tailscale Serve is already configured for this stack's internal URL (status matches).
238
+ const allowEnableTailscale =
239
+ !stackMode ||
240
+ stackName === 'main' ||
241
+ (baseEnv.HAPPIER_STACK_TAILSCALE_SERVE ?? '0').toString().trim() === '1';
242
+ const resolvedUrls = await resolveServerUrls({ env: baseEnv, serverPort, allowEnable: allowEnableTailscale });
243
+ if (stackMode && stackName !== 'main' && !resolvedUrls.envPublicUrl) {
244
+ const src = String(resolvedUrls.publicServerUrlSource ?? '');
245
+ const hasStackScopedTailscale = src.startsWith('tailscale-');
246
+ publicServerUrl = hasStackScopedTailscale ? resolvedUrls.publicServerUrl : resolvedUrls.defaultPublicUrl;
247
+ } else {
248
+ publicServerUrl = resolvedUrls.publicServerUrl;
249
+ }
250
+
251
+ const serverAlreadyRunning = await isHappierServerRunning(internalServerUrl);
252
+ const daemonAlreadyRunning = startDaemon ? isDaemonRunning(cliHomeDir) : false;
253
+ if (!restart && serverAlreadyRunning && (!startDaemon || daemonAlreadyRunning)) {
254
+ console.log(
255
+ `${green('✓')} start: already running ${dim('(')}` +
256
+ `${dim('server=')}${cyan(internalServerUrl)}` +
257
+ `${startDaemon ? ` ${dim('daemon=')}${daemonAlreadyRunning ? green('running') : dim('stopped')}` : ''}` +
258
+ `${dim(')')}`
259
+ );
260
+ return;
261
+ }
262
+
263
+ // Stack runtime state (stack-scoped commands only): record the runner PID + chosen ports so stop/restart never kills other stacks.
264
+ if (stackMode && runtimeStatePath) {
265
+ await recordStackRuntimeStart(runtimeStatePath, {
266
+ stackName,
267
+ script: 'run.mjs',
268
+ ephemeral,
269
+ ownerPid: process.pid,
270
+ ports: { server: serverPort },
271
+ }).catch(() => {});
272
+ }
273
+
274
+ // Server
275
+ // If a previous run left a server behind, free the port first (prevents false "ready" checks).
276
+ // NOTE: In stack mode we avoid killing arbitrary port listeners (fail-closed instead).
277
+ if ((!serverAlreadyRunning || restart) && !stackMode) {
278
+ await killPortListeners(serverPort, { label: 'server' });
279
+ }
280
+
281
+ const serverEnv = {
282
+ ...baseEnv,
283
+ PORT: String(serverPort),
284
+ // Used by server-light for generating public file URLs.
285
+ PUBLIC_URL: publicServerUrl,
286
+ // Avoid noisy failures if a previous run left the metrics port busy.
287
+ // You can override with METRICS_ENABLED=true if you want it.
288
+ METRICS_ENABLED: baseEnv.METRICS_ENABLED ?? 'false',
289
+ // Server-side enforcement: if UI serving is enabled (capability), require a valid bundle.
290
+ ...(serveUi ? { HAPPIER_SERVER_UI_REQUIRED: uiRequired ? '1' : '0' } : {}),
291
+ ...resolveServerUiEnv({ serveUi, uiBuildDir, uiPrefix, uiBuildDirExists: Boolean(serveUi && uiBuildDirExists && uiIndexExists) }),
292
+ };
293
+ let serverLightAccountCount = null;
294
+ let happierServerAccountCount = null;
295
+ if (serverComponentName === 'happier-server-light') {
296
+ applyServerLightEnvDefaults({ baseEnv, serverEnv, baseDir: autostart.baseDir });
297
+
298
+ // Reliability: ensure DB schema exists before daemon hits /v1/machines (health checks don't cover DB readiness).
299
+ // If the server is already running and we are not restarting, skip migrations/probes (pglite is single-connection).
300
+ const acct = await getAccountCountForServerComponent({
301
+ serverComponentName,
302
+ serverDir,
303
+ env: serverEnv,
304
+ bestEffort: Boolean(serverAlreadyRunning && !restart),
305
+ });
306
+ serverLightAccountCount = typeof acct.accountCount === 'number' ? acct.accountCount : null;
307
+ }
308
+ let effectiveInternalServerUrl = internalServerUrl;
309
+ if (serverComponentName === 'happier-server') {
310
+ const managed = (baseEnv.HAPPIER_STACK_MANAGED_INFRA ?? '1') !== '0';
311
+ if (managed) {
312
+ const envPath = baseEnv.HAPPIER_STACK_ENV_FILE ?? '';
313
+ const infra = await ensureHappyServerManagedInfra({
314
+ stackName: autostart.stackName,
315
+ baseDir: autostart.baseDir,
316
+ serverPort,
317
+ publicServerUrl,
318
+ envPath,
319
+ env: baseEnv,
320
+ });
321
+
322
+ // Backend runs on a separate port; gateway owns the public port.
323
+ const backendPortRaw = (baseEnv.HAPPIER_STACK_SERVER_BACKEND_PORT ?? '').trim();
324
+ const backendPort = backendPortRaw ? Number(backendPortRaw) : serverPort + 10;
325
+ const backendUrl = `http://127.0.0.1:${backendPort}`;
326
+ if (!stackMode) {
327
+ await killPortListeners(backendPort, { label: 'happier-server-backend' });
328
+ }
329
+
330
+ const backendEnv = { ...serverEnv, ...infra.env, PORT: String(backendPort) };
331
+ const autoMigrate = (baseEnv.HAPPIER_STACK_PRISMA_MIGRATE ?? '1') !== '0';
332
+ if (autoMigrate) {
333
+ await applyHappyServerMigrations({ serverDir, env: backendEnv });
334
+ }
335
+ // Account probe should use the *actual* DATABASE_URL/infra env (ephemeral stacks do not persist it in env files).
336
+ const acct = await getAccountCountForServerComponent({
337
+ serverComponentName,
338
+ serverDir,
339
+ env: backendEnv,
340
+ bestEffort: true,
341
+ });
342
+ happierServerAccountCount = typeof acct.accountCount === 'number' ? acct.accountCount : null;
343
+
344
+ const backend = await pmSpawnScript({ label: 'server', dir: serverDir, script: 'start', env: backendEnv });
345
+ children.push(backend);
346
+ if (stackMode && runtimeStatePath) {
347
+ await recordStackRuntimeUpdate(runtimeStatePath, {
348
+ ports: { server: serverPort, backend: backendPort },
349
+ processes: { happierServerBackendPid: backend.pid },
350
+ }).catch(() => {});
351
+ }
352
+ await waitForServerReady(backendUrl);
353
+
354
+ const gatewayArgs = [
355
+ join(rootDir, 'scripts', 'ui_gateway.mjs'),
356
+ `--port=${serverPort}`,
357
+ `--backend-url=${backendUrl}`,
358
+ `--minio-port=${infra.env.S3_PORT}`,
359
+ `--bucket=${infra.env.S3_BUCKET}`,
360
+ ];
361
+ if (serveUi && (await pathExists(uiBuildDir))) {
362
+ gatewayArgs.push(`--ui-dir=${uiBuildDir}`);
363
+ } else {
364
+ gatewayArgs.push('--no-ui');
365
+ }
366
+
367
+ const gateway = spawnProc('ui', process.execPath, gatewayArgs, { ...backendEnv, PORT: String(serverPort) }, { cwd: rootDir });
368
+ children.push(gateway);
369
+ if (stackMode && runtimeStatePath) {
370
+ await recordStackRuntimeUpdate(runtimeStatePath, { processes: { uiGatewayPid: gateway.pid } }).catch(() => {});
371
+ }
372
+ await waitForServerReady(internalServerUrl);
373
+ effectiveInternalServerUrl = internalServerUrl;
374
+
375
+ // Skip default server spawn below
376
+ }
377
+ }
378
+
379
+ // Default server start (happier-server-light, or happier-server without managed infra).
380
+ if (!(serverComponentName === 'happier-server' && (baseEnv.HAPPIER_STACK_MANAGED_INFRA ?? '1') !== '0')) {
381
+ if (!serverAlreadyRunning || restart) {
382
+ const server = await pmSpawnScript({ label: 'server', dir: serverDir, script: serverStartScript, env: serverEnv });
383
+ children.push(server);
384
+ if (stackMode && runtimeStatePath) {
385
+ await recordStackRuntimeUpdate(runtimeStatePath, { processes: { serverPid: server.pid } }).catch(() => {});
386
+ }
387
+ await waitForServerReady(internalServerUrl);
388
+ } else {
389
+ console.log(`${green('✓')} server: already running at ${cyan(internalServerUrl)}`);
390
+ }
391
+ }
392
+
393
+ if (enableTailscaleServe) {
394
+ try {
395
+ const status = await runCapture(process.execPath, [join(rootDir, 'scripts', 'tailscale.mjs'), 'status']);
396
+ const line = status.split('\n').find((l) => l.toLowerCase().includes('https://'))?.trim();
397
+ if (line) {
398
+ console.log(`${green('✓')} tailscale serve: ${cyan(line)}`);
399
+ } else {
400
+ console.log(`${green('✓')} tailscale serve enabled`);
401
+ }
402
+ } catch {
403
+ console.log(`${green('✓')} tailscale serve enabled`);
404
+ }
405
+ }
406
+
407
+ if (serveUi) {
408
+ const localUi = effectiveInternalServerUrl.replace(/\/+$/, '') + '/';
409
+ console.log('');
410
+ console.log(sectionTitle('Web UI'));
411
+ console.log(`${green('✓')} local: ${cyan(localUi)}`);
412
+ if (publicServerUrl && publicServerUrl !== effectiveInternalServerUrl && publicServerUrl !== localUi && publicServerUrl !== defaultPublicUrl) {
413
+ const pubUi = publicServerUrl.replace(/\/+$/, '') + '/';
414
+ console.log(`${green('✓')} public: ${cyan(pubUi)}`);
415
+ }
416
+ if (enableTailscaleServe) {
417
+ console.log(`${dim('Tip:')} use the HTTPS *.ts.net URL for remote access`);
418
+ }
419
+
420
+ console.log('');
421
+ console.log(sectionTitle('Terminal usage'));
422
+ console.log(dim(`To run ${cyan('happier')} against this stack (and have sessions appear in the UI), export:`));
423
+ console.log(cmd(`export HAPPIER_SERVER_URL="${effectiveInternalServerUrl}"`));
424
+ console.log(cmd(`export HAPPIER_HOME_DIR="${cliHomeDir}"`));
425
+ console.log(cmd(`export HAPPIER_WEBAPP_URL="${publicServerUrl}"`));
426
+
427
+ // Auto-open UI (interactive only) using the stack-scoped hostname when applicable.
428
+ const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
429
+ if (isInteractive && !noBrowser) {
430
+ const prefix = uiPrefix.startsWith('/') ? uiPrefix : `/${uiPrefix}`;
431
+ const openUrl = await preferStackLocalhostUrl(`http://localhost:${serverPort}${prefix}`, { stackName: autostart.stackName });
432
+ const res = await openUrlInBrowser(openUrl);
433
+ if (!res.ok) {
434
+ console.warn(`[local] ui: failed to open browser automatically (${res.error}).`);
435
+ }
436
+ }
437
+ }
438
+
439
+ // Daemon
440
+ if (startDaemon) {
441
+ const gate = daemonStartGate({ env: baseEnv, cliHomeDir, serverUrl: effectiveInternalServerUrl });
442
+ if (!gate.ok) {
443
+ const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
444
+ // In orchestrated auth flows, keep server/UI up and let the orchestrator start daemon post-auth.
445
+ if (gate.reason === 'auth_flow_missing_credentials') {
446
+ console.log('[local] auth flow: skipping daemon start until credentials exist');
447
+ } else if (!isInteractive) {
448
+ throw new Error(
449
+ formatDaemonAuthRequiredError({
450
+ stackName: autostart.stackName,
451
+ cliHomeDir,
452
+ serverUrl: effectiveInternalServerUrl,
453
+ })
454
+ );
455
+ }
456
+ } else {
457
+ const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
458
+ if (serverComponentName === 'happier-server' && happierServerAccountCount == null) {
459
+ const acct = await getAccountCountForServerComponent({
460
+ serverComponentName,
461
+ serverDir,
462
+ env: serverEnv,
463
+ bestEffort: true,
464
+ });
465
+ happierServerAccountCount = typeof acct.accountCount === 'number' ? acct.accountCount : null;
466
+ }
467
+ const accountCount =
468
+ serverComponentName === 'happier-server-light' ? serverLightAccountCount : happierServerAccountCount;
469
+ const autoSeedEnabled = resolveAutoCopyFromMainEnabled({ env: baseEnv, stackName: autostart.stackName, isInteractive });
470
+ await maybeRunInteractiveStackAuthSetup({
471
+ rootDir,
472
+ env: baseEnv,
473
+ stackName: autostart.stackName,
474
+ cliHomeDir,
475
+ accountCount,
476
+ isInteractive,
477
+ autoSeedEnabled,
478
+ });
479
+ await prepareDaemonAuthSeedIfNeeded({
480
+ rootDir,
481
+ env: baseEnv,
482
+ stackName: autostart.stackName,
483
+ cliHomeDir,
484
+ startDaemon,
485
+ isInteractive,
486
+ accountCount,
487
+ quiet: false,
488
+ });
489
+ await startLocalDaemonWithAuth({
490
+ cliBin,
491
+ cliHomeDir,
492
+ internalServerUrl: effectiveInternalServerUrl,
493
+ publicServerUrl,
494
+ isShuttingDown: () => shuttingDown,
495
+ forceRestart: restart,
496
+ env: baseEnv,
497
+ stackName: autostart.stackName,
498
+ });
499
+ }
500
+ }
501
+
502
+ // Optional: start Expo dev-client Metro for mobile reviewers.
503
+ if (startMobile) {
504
+ const expoRes = await ensureDevExpoServer({
505
+ startUi: false,
506
+ startMobile: true,
507
+ uiDir,
508
+ autostart,
509
+ baseEnv,
510
+ apiServerUrl: publicServerUrl,
511
+ restart,
512
+ stackMode,
513
+ runtimeStatePath,
514
+ stackName,
515
+ envPath,
516
+ children,
517
+ expoTailscale,
518
+ });
519
+ if (expoRes?.tailscale?.ok && expoRes.tailscale.tailscaleIp && expoRes.port) {
520
+ console.log(`[local] expo tailscale: http://${expoRes.tailscale.tailscaleIp}:${expoRes.port}`);
521
+ }
522
+ }
523
+
524
+ const shutdown = async () => {
525
+ if (shuttingDown) {
526
+ return;
527
+ }
528
+ shuttingDown = true;
529
+ console.log('\n[local] shutting down...');
530
+
531
+ if (startDaemon) {
532
+ await stopLocalDaemon({ cliBin, internalServerUrl: effectiveInternalServerUrl, cliHomeDir });
533
+ }
534
+
535
+ for (const child of children) {
536
+ if (child.exitCode == null) {
537
+ killProcessTree(child, 'SIGINT');
538
+ }
539
+ }
540
+
541
+ await delay(1500);
542
+ for (const child of children) {
543
+ if (child.exitCode == null) {
544
+ killProcessTree(child, 'SIGKILL');
545
+ }
546
+ }
547
+
548
+ await maybeResetTailscaleServe();
549
+ };
550
+
551
+ process.on('SIGINT', () => shutdown().then(() => process.exit(0)));
552
+ process.on('SIGTERM', () => shutdown().then(() => process.exit(0)));
553
+
554
+ // Keep running
555
+ await new Promise(() => {});
556
+ }
557
+
558
+ main().catch((err) => {
559
+ console.error('[local] failed:', err);
560
+ process.exit(1);
561
+ });
@@ -0,0 +1,30 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import {
5
+ hasRecordedRuntimePortsForRestart,
6
+ shouldReuseRuntimePortsOnRestart,
7
+ } from './stack/run_script_with_stack_env.mjs';
8
+
9
+ test('hasRecordedRuntimePortsForRestart requires a positive server port', () => {
10
+ assert.equal(hasRecordedRuntimePortsForRestart(null), false);
11
+ assert.equal(hasRecordedRuntimePortsForRestart({ ports: {} }), false);
12
+ assert.equal(hasRecordedRuntimePortsForRestart({ ports: { server: '0' } }), false);
13
+ assert.equal(hasRecordedRuntimePortsForRestart({ ports: { server: '3010' } }), true);
14
+ });
15
+
16
+ test('shouldReuseRuntimePortsOnRestart reuses runtime ports on stale-owner restarts', () => {
17
+ const runtimeState = { ownerPid: 999_999_999, ports: { server: 3010 } };
18
+ assert.equal(
19
+ shouldReuseRuntimePortsOnRestart({ wantsRestart: true, runtimeState, wasRunning: false }),
20
+ true
21
+ );
22
+ });
23
+
24
+ test('shouldReuseRuntimePortsOnRestart stays false when restart was not requested', () => {
25
+ const runtimeState = { ports: { server: 3010 } };
26
+ assert.equal(
27
+ shouldReuseRuntimePortsOnRestart({ wantsRestart: false, runtimeState, wasRunning: true }),
28
+ false
29
+ );
30
+ });