@happier-dev/stack 0.1.0-preview.74.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 +138 -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 +74 -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,550 @@
1
+ import './utils/env/env.mjs';
2
+ import { parseArgs } from './utils/cli/args.mjs';
3
+ import { pathExists } from './utils/fs/fs.mjs';
4
+ import { run, runCapture } from './utils/proc/proc.mjs';
5
+ import { commandExists } from './utils/proc/commands.mjs';
6
+ import { getComponentDir, getComponentRepoDir, getRootDir, isHappyMonorepoRoot } from './utils/paths/paths.mjs';
7
+ import { getServerComponentName } from './utils/server/server.mjs';
8
+ import { ensureCliBuilt, ensureDepsInstalled, ensureHappyCliLocalNpmLinked } from './utils/proc/pm.mjs';
9
+ import { dirname, join } from 'node:path';
10
+ import { mkdir } from 'node:fs/promises';
11
+ import { installService, uninstallService } from './service.mjs';
12
+ import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
13
+ import { ensureEnvLocalUpdated } from './utils/env/env_local.mjs';
14
+ import { isTty, prompt, promptSelect, withRl } from './utils/cli/wizard.mjs';
15
+ import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
16
+ import { bold, cyan, dim, green } from './utils/ui/ansi.mjs';
17
+ import { getVerbosityLevel } from './utils/cli/verbosity.mjs';
18
+ import { createStepPrinter } from './utils/cli/progress.mjs';
19
+
20
+ /**
21
+ * Install/setup the local stack:
22
+ * - ensure the Happier monorepo exists (optionally clone if missing)
23
+ * - install dependencies where needed (yarn)
24
+ * - build happier-cli (optional) and install `happier`/`hstack` shims under `<homeDir>/bin`
25
+ * - build the web UI bundle (so `run` can serve it)
26
+ * - optional macOS autostart (LaunchAgent)
27
+ */
28
+
29
+ // Happier monorepo repo URL defaults.
30
+ // This should point at a repo that contains at least: apps/ui, apps/cli, apps/server.
31
+ //
32
+ // Stack is Happier-first. Contributors typically:
33
+ // - clone the canonical repo as `upstream`
34
+ // - use their fork as `origin`
35
+ const DEFAULT_MONOREPO_REPO_URL = 'https://github.com/happier-dev/happier.git';
36
+
37
+ const DEFAULT_FORK_REPOS = {
38
+ monorepo: DEFAULT_MONOREPO_REPO_URL,
39
+ };
40
+
41
+ const DEFAULT_UPSTREAM_REPOS = {
42
+ monorepo: DEFAULT_MONOREPO_REPO_URL,
43
+ };
44
+
45
+ function repoUrlsFromOwners({ forkOwner, upstreamOwner }) {
46
+ const fork = (name) => `https://github.com/${forkOwner}/${name}.git`;
47
+ const up = (name) => `https://github.com/${upstreamOwner}/${name}.git`;
48
+ return {
49
+ forks: {
50
+ monorepo: fork('happier'),
51
+ },
52
+ upstream: {
53
+ monorepo: up('happier'),
54
+ },
55
+ };
56
+ }
57
+
58
+ function resolveRepoSource({ flags }) {
59
+ if (flags.has('--forks')) {
60
+ return 'forks';
61
+ }
62
+ if (flags.has('--upstream')) {
63
+ return 'upstream';
64
+ }
65
+ const fromEnv = (process.env.HAPPIER_STACK_REPO_SOURCE ?? '').trim().toLowerCase();
66
+ if (fromEnv === 'fork' || fromEnv === 'forks') {
67
+ return 'forks';
68
+ }
69
+ if (fromEnv === 'upstream') {
70
+ return 'upstream';
71
+ }
72
+ // Default for external contributors.
73
+ return 'upstream';
74
+ }
75
+
76
+ function getRepoUrls({ repoSource }) {
77
+ const defaults = repoSource === 'upstream' ? DEFAULT_UPSTREAM_REPOS : DEFAULT_FORK_REPOS;
78
+ const monorepo =
79
+ process.env.HAPPIER_STACK_REPO_URL?.trim() ||
80
+ defaults.monorepo;
81
+
82
+ return { monorepo };
83
+ }
84
+
85
+ async function ensureGitBranchCheckedOut({ repoDir, branch, label }) {
86
+ if (!(await pathExists(join(repoDir, '.git')))) return;
87
+ const b = String(branch ?? '').trim();
88
+ if (!b) return;
89
+
90
+ try {
91
+ const head = (await runCapture('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoDir })).trim();
92
+ if (head && head === b) return;
93
+ } catch {
94
+ // ignore
95
+ }
96
+
97
+ // Ensure branch exists locally, otherwise fetch it from origin.
98
+ let hasLocal = true;
99
+ try {
100
+ await run('git', ['show-ref', '--verify', '--quiet', `refs/heads/${b}`], { cwd: repoDir });
101
+ } catch {
102
+ hasLocal = false;
103
+ }
104
+ if (!hasLocal) {
105
+ try {
106
+ await run('git', ['fetch', '--quiet', 'origin', b], { cwd: repoDir });
107
+ } catch {
108
+ throw new Error(
109
+ `[local] ${label}: expected branch "${b}" to exist in ${repoDir}.\n` +
110
+ `[local] Fix: ensure the remote has that branch (or pick a different --branch/base), then re-run bootstrap.`
111
+ );
112
+ }
113
+ }
114
+
115
+ try {
116
+ await run('git', ['checkout', '-q', b], { cwd: repoDir });
117
+ } catch {
118
+ // If remote-tracking branch exists but local doesn't, create it.
119
+ try {
120
+ await run('git', ['checkout', '-q', '-B', b, `origin/${b}`], { cwd: repoDir });
121
+ } catch {
122
+ throw new Error(
123
+ `[local] ${label}: failed to checkout branch "${b}" in ${repoDir}.\n` +
124
+ `[local] Fix: re-run with --force in worktree flows, or delete the checkout and re-run install/bootstrap.`
125
+ );
126
+ }
127
+ }
128
+ }
129
+
130
+ async function ensureComponentPresent({ dir, label, repoUrl, allowClone, quiet = false, runMaybeVerbose = null }) {
131
+ if (await pathExists(dir)) {
132
+ return;
133
+ }
134
+ if (!allowClone) {
135
+ throw new Error(`[local] missing ${label} at ${dir} (run with --clone or set HAPPIER_STACK_REPO_URL and re-run hstack bootstrap)`);
136
+ }
137
+ if (!repoUrl) {
138
+ throw new Error(
139
+ `[local] missing ${label} at ${dir} and no repo URL configured.\n` +
140
+ `Set HAPPIER_STACK_REPO_URL, or run: hstack bootstrap --interactive`
141
+ );
142
+ }
143
+ await mkdir(dirname(dir), { recursive: true });
144
+ if (!quiet) {
145
+ // eslint-disable-next-line no-console
146
+ console.log(`[local] cloning ${label} into ${dir}...`);
147
+ await run('git', ['clone', repoUrl, dir]);
148
+ return;
149
+ }
150
+
151
+ // Quiet-by-default: avoid spamming the terminal with git output.
152
+ // If it fails, we re-run with full logs so the user can see the root cause.
153
+ if (runMaybeVerbose) {
154
+ await runMaybeVerbose({ label: `clone ${label.toLowerCase()}`, cmd: 'git', args: ['clone', repoUrl, dir], cwd: dirname(dir) });
155
+ return;
156
+ }
157
+ await run('git', ['clone', repoUrl, dir], { stdio: 'ignore' });
158
+ }
159
+
160
+ async function ensureUpstreamRemote({ repoDir, upstreamUrl }) {
161
+ if (!(await pathExists(join(repoDir, '.git')))) {
162
+ return;
163
+ }
164
+ try {
165
+ // Use capture here to avoid printing scary errors like:
166
+ // "error: No such remote 'upstream'"
167
+ // when we're just probing for existence.
168
+ await runCapture('git', ['remote', 'get-url', 'upstream'], { cwd: repoDir });
169
+ // Upstream remote exists; best-effort update if different.
170
+ await runCapture('git', ['remote', 'set-url', 'upstream', upstreamUrl], { cwd: repoDir }).catch(() => {});
171
+ } catch {
172
+ await runCapture('git', ['remote', 'add', 'upstream', upstreamUrl], { cwd: repoDir });
173
+ }
174
+ }
175
+
176
+ async function interactiveWizard({ rootDir, defaults }) {
177
+ return await withRl(async (rl) => {
178
+ // Repo source is intentionally not prompted during bootstrap:
179
+ // - default: upstream (best for external contributors)
180
+ // - advanced: pass --forks (then we prompt for fork owner)
181
+ const repoSource = defaults.repoSource;
182
+
183
+ // eslint-disable-next-line no-console
184
+ console.log(
185
+ dim(
186
+ `Repo source: ${cyan(repoSource)}${
187
+ repoSource === 'upstream' ? ` ${green('(recommended)')}` : ''
188
+ }${repoSource === 'forks' ? ` ${dim('(advanced)')}` : ''}`
189
+ )
190
+ );
191
+ if (repoSource === 'upstream') {
192
+ // eslint-disable-next-line no-console
193
+ console.log(dim(`Tip: to use forks, re-run: ${cyan('hstack bootstrap --interactive --forks')}`));
194
+ }
195
+
196
+ let forkOwner = defaults.forkOwner;
197
+ let upstreamOwner = defaults.upstreamOwner;
198
+
199
+ if (repoSource === 'forks') {
200
+ // eslint-disable-next-line no-console
201
+ console.log(dim('Tip: choose this if you already have a fork of the Happier monorepo.'));
202
+ forkOwner = (
203
+ await prompt(rl, `GitHub fork owner (default: ${defaults.forkOwner}): `, { defaultValue: defaults.forkOwner })
204
+ ).trim() || defaults.forkOwner;
205
+ upstreamOwner = (
206
+ await prompt(rl, `GitHub upstream owner (default: ${defaults.upstreamOwner}): `, { defaultValue: defaults.upstreamOwner })
207
+ ).trim() || defaults.upstreamOwner;
208
+ }
209
+
210
+ const serverMode = await promptSelect(rl, {
211
+ title: `${bold('Server flavor')}\n${dim('Pick the backend this stack should run. You can switch later with `hstack srv use ...`.')}`,
212
+ options: [
213
+ { label: `${cyan('happier-server-light')} only (${green('recommended')})`, value: 'happier-server-light' },
214
+ { label: `${cyan('happier-server')} only — full server (Docker-managed infra)`, value: 'happier-server' },
215
+ ],
216
+ defaultIndex: defaults.serverComponentName === 'happier-server' ? 1 : 0,
217
+ });
218
+
219
+ // Setup/bootstrap is expected to be able to bring up a working workspace from scratch,
220
+ // so cloning missing repos is the default (and normally required for first-time users).
221
+ const allowClone = defaults.allowClone;
222
+
223
+ const supportsAutostart = process.platform === 'darwin' || process.platform === 'linux';
224
+ const enableAutostart = supportsAutostart
225
+ ? await promptSelect(rl, {
226
+ title: isSandboxed()
227
+ ? `${bold('Autostart')}\n${dim('Sandbox mode: this is global OS state; normally disabled in sandbox.')}`
228
+ : `${bold('Autostart')}\n${dim('Start Happier automatically at login?')}\n${dim(
229
+ process.platform === 'darwin' ? 'macOS: launchd LaunchAgent' : 'Linux: systemd --user service'
230
+ )}`,
231
+ options: [
232
+ { label: 'yes', value: true },
233
+ { label: 'no (default)', value: false },
234
+ ],
235
+ defaultIndex: defaults.enableAutostart ? 0 : 1,
236
+ })
237
+ : false;
238
+
239
+ const buildTauri = await promptSelect(rl, {
240
+ title: `${bold('Desktop app (optional)')}\n${dim('Build the Tauri desktop app as part of setup? (slow; requires extra toolchain)')}`,
241
+ options: [
242
+ { label: `yes ${dim('(slow)')} — build desktop app`, value: true },
243
+ { label: 'no (default)', value: false },
244
+ ],
245
+ defaultIndex: defaults.buildTauri ? 0 : 1,
246
+ });
247
+
248
+ // Keep bootstrap "just works" by default: ensure upstream remotes and mirror branches are configured.
249
+ const configureGit = true;
250
+
251
+ return {
252
+ repoSource,
253
+ forkOwner,
254
+ upstreamOwner,
255
+ serverComponentName: serverMode,
256
+ allowClone,
257
+ enableAutostart,
258
+ buildTauri,
259
+ configureGit,
260
+ };
261
+ });
262
+ }
263
+
264
+ async function main() {
265
+ const argv = process.argv.slice(2);
266
+ const { flags, kv } = parseArgs(argv);
267
+ const json = wantsJson(argv, { flags });
268
+ if (wantsHelp(argv, { flags })) {
269
+ printResult({
270
+ json,
271
+ data: {
272
+ flags: [
273
+ '--forks',
274
+ '--upstream',
275
+ '--clone',
276
+ '--no-clone',
277
+ '--autostart',
278
+ '--no-autostart',
279
+ '--server=...',
280
+ '--no-ui-build',
281
+ '--no-ui-deps',
282
+ '--no-cli-deps',
283
+ '--no-cli-build',
284
+ ],
285
+ json: true,
286
+ },
287
+ text: [
288
+ '[bootstrap] usage:',
289
+ ' hstack bootstrap [--forks|--upstream] [--server=happier-server|happier-server-light|both] [--json]',
290
+ ' hstack bootstrap --interactive',
291
+ ' hstack bootstrap --no-clone',
292
+ ].join('\n'),
293
+ });
294
+ return;
295
+ }
296
+ const rootDir = getRootDir(import.meta.url);
297
+
298
+ const interactive = flags.has('--interactive') && isTty();
299
+ const allowGlobal = sandboxAllowsGlobalSideEffects();
300
+ const sandboxed = isSandboxed();
301
+ const verbosity = getVerbosityLevel(process.env);
302
+ const quietUi = interactive && !json && verbosity === 0;
303
+ const steps = createStepPrinter({ enabled: quietUi });
304
+
305
+ async function runMaybeVerbose({ label, cmd, args, cwd }) {
306
+ if (!quietUi) {
307
+ await run(cmd, args, { cwd });
308
+ return;
309
+ }
310
+ steps.start(label);
311
+ try {
312
+ await run(cmd, args, { cwd, stdio: 'ignore' });
313
+ steps.stop('✓', label);
314
+ } catch (e) {
315
+ steps.stop('x', label);
316
+ // eslint-disable-next-line no-console
317
+ console.error(`[bootstrap] ${label} failed. Re-running with full logs...`);
318
+ await run(cmd, args, { cwd, stdio: 'inherit' });
319
+ throw e;
320
+ }
321
+ }
322
+
323
+ async function ensureDepsInstalledMaybeVerbose(dir, label) {
324
+ if (!quietUi) {
325
+ await ensureDepsInstalled(dir, label, { quiet: false, env: process.env });
326
+ return;
327
+ }
328
+ steps.start(`install deps: ${label}`);
329
+ try {
330
+ await ensureDepsInstalled(dir, label, { quiet: true, env: process.env });
331
+ steps.stop('✓', `install deps: ${label}`);
332
+ } catch (e) {
333
+ steps.stop('x', `install deps: ${label}`);
334
+ // eslint-disable-next-line no-console
335
+ console.error(`[bootstrap] dependency install failed for ${label}. Re-running with full logs...`);
336
+ await ensureDepsInstalled(dir, label, { quiet: false, env: process.env });
337
+ throw e;
338
+ }
339
+ }
340
+
341
+ async function ensureCliBuiltMaybeVerbose(cliDir, { buildCli }) {
342
+ if (!quietUi) {
343
+ await ensureCliBuilt(cliDir, { buildCli, quiet: false, env: process.env });
344
+ return;
345
+ }
346
+ steps.start('build happier-cli');
347
+ try {
348
+ await ensureCliBuilt(cliDir, { buildCli, quiet: true, env: process.env });
349
+ steps.stop('✓', 'build happier-cli');
350
+ } catch (e) {
351
+ steps.stop('x', 'build happier-cli');
352
+ // eslint-disable-next-line no-console
353
+ console.error('[bootstrap] happier-cli build failed. Re-running with full logs...');
354
+ await ensureCliBuilt(cliDir, { buildCli: true, quiet: false, env: process.env });
355
+ throw e;
356
+ }
357
+ }
358
+
359
+ // Defaults for wizard.
360
+ const defaultRepoSource = resolveRepoSource({ flags });
361
+ const defaults = {
362
+ repoSource: defaultRepoSource,
363
+ forkOwner: process.env.GITHUB_USER || process.env.USER || 'your-github-username',
364
+ upstreamOwner: 'happier-dev',
365
+ serverComponentName: getServerComponentName({ kv }),
366
+ allowClone: !flags.has('--no-clone') && ((process.env.HAPPIER_STACK_CLONE_MISSING ?? '1') !== '0' || flags.has('--clone')),
367
+ enableAutostart: (!sandboxed || allowGlobal) && (flags.has('--autostart') || (process.env.HAPPIER_STACK_AUTOSTART ?? '0') === '1'),
368
+ buildTauri: flags.has('--tauri') && !flags.has('--no-tauri'),
369
+ };
370
+
371
+ const wizard = interactive ? await interactiveWizard({ rootDir, defaults }) : null;
372
+ const repoSource = wizard?.repoSource ?? defaultRepoSource;
373
+
374
+ // Persist chosen repo source + URLs into the user config env file:
375
+ // - main stack env by default (recommended; consistent across install modes)
376
+ // - legacy fallback: <repo>/env.local when no home config exists yet
377
+ if (wizard) {
378
+ const owners = repoUrlsFromOwners({ forkOwner: wizard.forkOwner, upstreamOwner: wizard.upstreamOwner });
379
+ const chosen = repoSource === 'upstream' ? owners.upstream : owners.forks;
380
+ await ensureEnvLocalUpdated({
381
+ rootDir,
382
+ updates: [
383
+ { key: 'HAPPIER_STACK_REPO_SOURCE', value: repoSource },
384
+ { key: 'HAPPIER_STACK_REPO_URL', value: chosen.monorepo },
385
+ ],
386
+ });
387
+ }
388
+
389
+ const repos = getRepoUrls({ repoSource });
390
+
391
+ // Default: clone missing components (fresh checkouts "just work").
392
+ // Disable with --no-clone or HAPPIER_STACK_CLONE_MISSING=0.
393
+ const cloneMissingDefault = (process.env.HAPPIER_STACK_CLONE_MISSING ?? '1') !== '0';
394
+ const allowClone =
395
+ wizard?.allowClone ?? (!flags.has('--no-clone') && (flags.has('--clone') || cloneMissingDefault));
396
+ const enableAutostartRaw = wizard?.enableAutostart ?? (flags.has('--autostart') || (process.env.HAPPIER_STACK_AUTOSTART ?? '0') === '1');
397
+ const enableAutostart = sandboxed && !allowGlobal ? false : enableAutostartRaw;
398
+ const disableAutostart = flags.has('--no-autostart');
399
+
400
+ const serverComponentName = (wizard?.serverComponentName ?? getServerComponentName({ kv })).trim();
401
+ // Repo roots (clone locations)
402
+ //
403
+ // Happier-only: everything lives in a single monorepo checkout.
404
+ const uiRepoDir = getComponentRepoDir(rootDir, 'happier-ui');
405
+
406
+ // Ensure UI exists first (monorepo anchor).
407
+ await ensureComponentPresent({
408
+ dir: uiRepoDir,
409
+ label: 'UI',
410
+ repoUrl: repos.monorepo,
411
+ allowClone,
412
+ quiet: quietUi,
413
+ runMaybeVerbose,
414
+ });
415
+ // IMPORTANT: the workspace "main" checkout must always be on the stable branch,
416
+ // even if the GitHub default branch becomes "dev".
417
+ const stableBranch = (process.env.HAPPIER_STACK_STABLE_BRANCH ?? '').trim() || 'main';
418
+ await ensureGitBranchCheckedOut({ repoDir: uiRepoDir, branch: stableBranch, label: 'monorepo' });
419
+
420
+ // Package dirs (where we run installs/builds). Recompute after cloning UI.
421
+ const uiDir = getComponentDir(rootDir, 'happier-ui');
422
+ const cliDir = getComponentDir(rootDir, 'happier-cli');
423
+ const serverFullDir = getComponentDir(rootDir, 'happier-server');
424
+ const cliRepoDir = uiRepoDir;
425
+ const serverFullRepoDir = uiRepoDir;
426
+
427
+ if (!isHappyMonorepoRoot(uiRepoDir)) {
428
+ throw new Error(
429
+ `[bootstrap] expected a Happier monorepo checkout at ${uiRepoDir}, but it does not look like a supported layout.\n` +
430
+ `Fix: set HAPPIER_STACK_REPO_URL to a Happier monorepo repo (must contain apps/ui, apps/cli, apps/server).`
431
+ );
432
+ }
433
+ if (!(await pathExists(serverFullDir))) {
434
+ throw new Error(`[bootstrap] expected server package at ${serverFullDir} (missing).`);
435
+ }
436
+ if (!(await pathExists(cliDir))) {
437
+ throw new Error(`[bootstrap] expected cli package at ${cliDir} (missing).`);
438
+ }
439
+
440
+ const cliDirFinal = cliDir;
441
+ const uiDirFinal = uiDir;
442
+
443
+ // Install deps
444
+ const skipUiDeps = flags.has('--no-ui-deps') || (process.env.HAPPIER_STACK_INSTALL_NO_UI_DEPS ?? '').trim() === '1';
445
+ const skipCliDeps = flags.has('--no-cli-deps') || (process.env.HAPPIER_STACK_INSTALL_NO_CLI_DEPS ?? '').trim() === '1';
446
+ if (serverComponentName === 'both' || serverComponentName === 'happier-server-light' || serverComponentName === 'happier-server') {
447
+ await ensureDepsInstalledMaybeVerbose(serverFullDir, 'happier-server');
448
+ }
449
+ if (!skipUiDeps) {
450
+ await ensureDepsInstalledMaybeVerbose(uiDirFinal, 'happier-ui');
451
+ }
452
+ if (!skipCliDeps) {
453
+ await ensureDepsInstalledMaybeVerbose(cliDirFinal, 'happier-cli');
454
+ }
455
+
456
+ // CLI build + link
457
+ const skipCliBuild = flags.has('--no-cli-build') || (process.env.HAPPIER_STACK_INSTALL_NO_CLI_BUILD ?? '').trim() === '1';
458
+ if (!skipCliBuild) {
459
+ const buildCli = (process.env.HAPPIER_STACK_CLI_BUILD ?? '1') !== '0';
460
+ const npmLinkCli = (process.env.HAPPIER_STACK_NPM_LINK ?? '1') !== '0';
461
+ await ensureCliBuiltMaybeVerbose(cliDirFinal, { buildCli });
462
+ await ensureHappyCliLocalNpmLinked(rootDir, { npmLinkCli, quiet: quietUi });
463
+ }
464
+
465
+ // Build UI (so run works without expo dev server)
466
+ const skipUiBuild = flags.has('--no-ui-build') || (process.env.HAPPIER_STACK_INSTALL_NO_UI_BUILD ?? '').trim() === '1';
467
+ const buildArgs = [join(rootDir, 'scripts', 'build.mjs')];
468
+ // Tauri builds are opt-in (slow + requires additional toolchain).
469
+ const buildTauri = wizard?.buildTauri ?? (flags.has('--tauri') && !flags.has('--no-tauri'));
470
+ if (!skipUiBuild) {
471
+ if (buildTauri) {
472
+ buildArgs.push('--tauri');
473
+ } else if (flags.has('--no-tauri')) {
474
+ buildArgs.push('--no-tauri');
475
+ }
476
+ if (quietUi) {
477
+ await runMaybeVerbose({ label: 'build web UI bundle', cmd: process.execPath, args: buildArgs, cwd: rootDir });
478
+ } else {
479
+ await run(process.execPath, buildArgs, { cwd: rootDir });
480
+ }
481
+ }
482
+
483
+ // Optional autostart (macOS launchd / Linux systemd --user)
484
+ if (disableAutostart) {
485
+ await uninstallService();
486
+ } else if (enableAutostart) {
487
+ if (process.platform === 'linux') {
488
+ const hasSystemctl = await commandExists('systemctl');
489
+ if (!hasSystemctl) {
490
+ if (!json) {
491
+ // eslint-disable-next-line no-console
492
+ console.log('[bootstrap] autostart skipped: systemd user services not available (missing systemctl)');
493
+ }
494
+ } else {
495
+ await installService();
496
+ }
497
+ } else {
498
+ await installService();
499
+ }
500
+ }
501
+
502
+ // Optional git remote + mirror branch configuration
503
+ if (wizard?.configureGit) {
504
+ // Ensure upstream remote exists so `hstack wt sync-all` works consistently.
505
+ const upstreamRepos = getRepoUrls({ repoSource: 'upstream' });
506
+ await ensureUpstreamRemote({ repoDir: uiRepoDir, upstreamUrl: upstreamRepos.monorepo });
507
+
508
+ // Create/update mirror branches like upstream/main (best-effort).
509
+ try {
510
+ if (quietUi) {
511
+ await runMaybeVerbose({
512
+ label: 'update mirror branches (sync-all)',
513
+ cmd: process.execPath,
514
+ args: [join(rootDir, 'scripts', 'worktrees.mjs'), 'sync-all', '--json'],
515
+ cwd: rootDir,
516
+ });
517
+ } else {
518
+ await run(process.execPath, [join(rootDir, 'scripts', 'worktrees.mjs'), 'sync-all', '--json'], { cwd: rootDir });
519
+ }
520
+ } catch {
521
+ // ignore (still useful even if one component fails)
522
+ }
523
+ }
524
+
525
+ printResult({
526
+ json,
527
+ data: {
528
+ ok: true,
529
+ repoSource,
530
+ serverComponentName,
531
+ dirs: {
532
+ uiRepoDir,
533
+ uiDir: uiDirFinal,
534
+ cliRepoDir,
535
+ cliDir: cliDirFinal,
536
+ serverFullRepoDir,
537
+ serverFullDir,
538
+ },
539
+ cloned: allowClone,
540
+ autostart: enableAutostart ? 'enabled' : sandboxed && enableAutostartRaw && !allowGlobal ? 'skipped (sandbox)' : disableAutostart ? 'disabled' : 'unchanged',
541
+ interactive: Boolean(wizard),
542
+ },
543
+ text: '[local] setup complete',
544
+ });
545
+ }
546
+
547
+ main().catch((err) => {
548
+ console.error('[local] install failed:', err);
549
+ process.exit(1);
550
+ });