@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,139 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { listWorktreeSpecs } from '../git/worktrees.mjs';
5
+ import { sanitizeSlugPart } from '../git/refs.mjs';
6
+ import { getDevRepoDir } from '../paths/paths.mjs';
7
+ import { bold, cyan, dim, green } from '../ui/ansi.mjs';
8
+ import { warn } from '../ui/layout.mjs';
9
+
10
+ export function isTty() {
11
+ const nonInteractive = (process.env.HAPPIER_STACK_NON_INTERACTIVE ?? '').toString().trim().toLowerCase();
12
+ if (nonInteractive === '1' || nonInteractive === 'true' || nonInteractive === 'yes' || nonInteractive === 'y') {
13
+ return false;
14
+ }
15
+ if (process.env.HAPPIER_STACK_TEST_TTY === '1') {
16
+ return true;
17
+ }
18
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
19
+ }
20
+
21
+ export async function withRl(fn) {
22
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
23
+ try {
24
+ return await fn(rl);
25
+ } finally {
26
+ rl.close();
27
+ }
28
+ }
29
+
30
+ export async function prompt(rl, question, { defaultValue = '' } = {}) {
31
+ const raw = (await rl.question(question)).trim();
32
+ return raw || defaultValue;
33
+ }
34
+
35
+ export async function promptSelect(rl, { title, options, defaultIndex = 0 }) {
36
+ if (!options.length) {
37
+ throw new Error('[wizard] no options to select from');
38
+ }
39
+ // eslint-disable-next-line no-console
40
+ console.log('');
41
+ // eslint-disable-next-line no-console
42
+ console.log(title);
43
+ for (let i = 0; i < options.length; i++) {
44
+ const isDefault = i === defaultIndex;
45
+ const suffix = isDefault ? ` ${dim('(default)')}` : '';
46
+ // eslint-disable-next-line no-console
47
+ console.log(` ${i + 1}) ${options[i].label}${suffix}`);
48
+ }
49
+ const answer = (await rl.question(`Pick [1-${options.length}] (default: ${defaultIndex + 1}): `)).trim();
50
+ const token = answer.match(/\d+/)?.[0] ?? '';
51
+ let n = defaultIndex + 1;
52
+ if (token) {
53
+ const parsed = Number(token);
54
+ if (Number.isFinite(parsed)) {
55
+ // Heuristic: in some nested-readline situations (or odd terminals), single-digit input can get duplicated
56
+ // (e.g. "2" becomes "22"). If that happens and all digits are identical, treat it as the intended single digit.
57
+ if (
58
+ token.length > 1 &&
59
+ token.split('').every((c) => c === token[0]) &&
60
+ Number(token[0]) >= 1 &&
61
+ Number(token[0]) <= options.length
62
+ ) {
63
+ n = Number(token[0]);
64
+ } else {
65
+ n = parsed;
66
+ }
67
+ }
68
+ }
69
+ const idx = Math.max(1, Math.min(options.length, Number.isFinite(n) ? n : defaultIndex + 1)) - 1;
70
+ return options[idx].value;
71
+ }
72
+
73
+ export async function promptWorktreeSource({ rl, rootDir, component, stackName, createRemote = 'upstream', env = process.env, deps = {} }) {
74
+ const promptFn = deps.prompt ?? prompt;
75
+ const promptSelectFn = deps.promptSelect ?? promptSelect;
76
+ const listWorktreeSpecsFn = deps.listWorktreeSpecs ?? listWorktreeSpecs;
77
+
78
+ const devDir = getDevRepoDir(rootDir, env);
79
+ const hasDev = Boolean(devDir && existsSync(join(devDir, '.git')));
80
+
81
+ const baseOptions = [{ label: `default (${dim('repo checkout')})`, value: 'default' }];
82
+ if (hasDev) {
83
+ baseOptions.push({ label: `dev (${dim('dev checkout')})`, value: 'dev' });
84
+ }
85
+ baseOptions.push({ label: `pick existing worktree`, value: 'pick' });
86
+ baseOptions.push({ label: `create new worktree (${cyan(createRemote)}; ${green('recommended for PRs')})`, value: 'create' });
87
+
88
+ const kind = await promptSelectFn(rl, { title: `Select ${cyan('repo')}:`, options: baseOptions, defaultIndex: 0 });
89
+
90
+ if (kind === 'default') {
91
+ return 'default';
92
+ }
93
+ if (kind === 'dev') {
94
+ return 'dev';
95
+ }
96
+ if (kind === 'pick') {
97
+ const specs = await listWorktreeSpecsFn({ rootDir, component });
98
+ const all = [
99
+ ...(hasDev ? [{ label: `dev (${dim('dev checkout')})`, value: 'dev' }] : []),
100
+ ...specs.map((s) => ({ label: s, value: s })),
101
+ ];
102
+ if (!all.length) {
103
+ // eslint-disable-next-line no-console
104
+ console.log(dim(`[wizard] no worktrees found (using default repo checkout)`));
105
+ return 'default';
106
+ }
107
+ const picked = await promptSelectFn(rl, {
108
+ title: `${bold(`Available ${cyan('repo')} worktrees`)}\n${dim('Tip: use `hstack wt new ... --use` to create more worktrees.')}`,
109
+ options: all,
110
+ defaultIndex: 0,
111
+ });
112
+ return picked;
113
+ }
114
+
115
+ // eslint-disable-next-line no-console
116
+ console.log('');
117
+ // eslint-disable-next-line no-console
118
+ console.log(bold(`Create a new ${cyan('repo')} worktree`));
119
+ // eslint-disable-next-line no-console
120
+ console.log(dim(`This will create a worktree under ${cyan('local/')}${dim('<owner>/...')} based on ${createRemote}.`));
121
+ while (true) {
122
+ // eslint-disable-next-line no-await-in-loop
123
+ const raw = await promptFn(rl, `New worktree slug (example: my-feature): `, { defaultValue: '' });
124
+ const trimmed = String(raw ?? '').trim();
125
+ if (!trimmed) return 'default';
126
+
127
+ const normalized = sanitizeSlugPart(trimmed);
128
+ if (!normalized) {
129
+ // eslint-disable-next-line no-console
130
+ console.log(warn('Invalid worktree slug. Use letters/numbers and separators like "-" (example: my-feature).'));
131
+ continue;
132
+ }
133
+ if (normalized !== trimmed) {
134
+ // eslint-disable-next-line no-console
135
+ console.log(dim(`Normalized slug to ${cyan(normalized)}.`));
136
+ }
137
+ return { create: true, slug: normalized, remote: createRemote };
138
+ }
139
+ }
@@ -0,0 +1,44 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { promptSelect } from './wizard.mjs';
5
+
6
+ function mkRl(answer) {
7
+ return { question: async () => String(answer ?? '') };
8
+ }
9
+
10
+ test('promptSelect marks the default option in the rendered list', async (t) => {
11
+ const lines = [];
12
+ t.mock.method(console, 'log', (...args) => {
13
+ lines.push(args.map(String).join(' '));
14
+ });
15
+ const picked = await promptSelect(mkRl(''), {
16
+ title: 'Pick a value:',
17
+ options: [
18
+ { label: 'One', value: 'one' },
19
+ { label: 'Two', value: 'two' },
20
+ { label: 'Three', value: 'three' },
21
+ ],
22
+ defaultIndex: 1,
23
+ });
24
+ assert.equal(picked, 'two');
25
+
26
+ const rendered = lines.join('\n');
27
+ assert.match(rendered, /\b2\)\s+Two\b.*\(\s*default\s*\)/i);
28
+ assert.doesNotMatch(rendered, /\b1\)\s+One\b.*\(\s*default\s*\)/i);
29
+ assert.doesNotMatch(rendered, /\b3\)\s+Three\b.*\(\s*default\s*\)/i);
30
+ });
31
+
32
+ test('promptSelect accepts duplicated single-digit input (e.g. \"22\")', async (t) => {
33
+ t.mock.method(console, 'log', () => {});
34
+ const picked = await promptSelect(mkRl('22'), {
35
+ title: 'Pick:',
36
+ options: [
37
+ { label: 'One', value: 'one' },
38
+ { label: 'Two', value: 'two' },
39
+ { label: 'Three', value: 'three' },
40
+ ],
41
+ defaultIndex: 0,
42
+ });
43
+ assert.equal(picked, 'two');
44
+ });
@@ -0,0 +1,132 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+
7
+ import { promptWorktreeSource } from './wizard.mjs';
8
+
9
+ async function withTempRoot(t) {
10
+ const root = await mkdtemp(join(tmpdir(), 'hstack-wizard-'));
11
+ t.after(async () => {
12
+ await rm(root, { recursive: true, force: true });
13
+ });
14
+ return root;
15
+ }
16
+
17
+ test('promptWorktreeSource does not list worktrees unless user selects "pick"', async () => {
18
+ let listed = 0;
19
+ const listWorktreeSpecs = async () => {
20
+ listed++;
21
+ return ['pr/123'];
22
+ };
23
+
24
+ const promptSelect = async () => 'default';
25
+ const prompt = async () => '';
26
+
27
+ const res = await promptWorktreeSource({
28
+ rl: {},
29
+ rootDir: '/tmp',
30
+ component: 'happier-ui',
31
+ stackName: 'exp1',
32
+ createRemote: 'upstream',
33
+ deps: { listWorktreeSpecs, promptSelect, prompt },
34
+ });
35
+
36
+ assert.equal(res, 'default');
37
+ assert.equal(listed, 0);
38
+ });
39
+
40
+ test('promptWorktreeSource lists worktrees when user selects "pick"', async () => {
41
+ let listed = 0;
42
+ const listWorktreeSpecs = async () => {
43
+ listed++;
44
+ return ['pr/123', 'pr/456'];
45
+ };
46
+
47
+ let selectCount = 0;
48
+ const promptSelect = async (_rl, { title }) => {
49
+ selectCount++;
50
+ if (selectCount === 1) {
51
+ assert.ok(title.startsWith('Select '));
52
+ return 'pick';
53
+ }
54
+ assert.ok(title.startsWith('Available '));
55
+ return 'pr/456';
56
+ };
57
+ const prompt = async () => '';
58
+
59
+ const res = await promptWorktreeSource({
60
+ rl: {},
61
+ rootDir: '/tmp',
62
+ component: 'happier-ui',
63
+ stackName: 'exp1',
64
+ createRemote: 'upstream',
65
+ deps: { listWorktreeSpecs, promptSelect, prompt },
66
+ });
67
+
68
+ assert.equal(res, 'pr/456');
69
+ assert.equal(listed, 1);
70
+ });
71
+
72
+ test('promptWorktreeSource falls back to default when pick has no available repos', async (t) => {
73
+ const workspaceDir = await withTempRoot(t);
74
+ let selectCount = 0;
75
+ const res = await promptWorktreeSource({
76
+ rl: {},
77
+ rootDir: '/tmp',
78
+ component: 'happier-ui',
79
+ stackName: 'exp1',
80
+ createRemote: 'upstream',
81
+ env: { ...process.env, HAPPIER_STACK_WORKSPACE_DIR: workspaceDir },
82
+ deps: {
83
+ listWorktreeSpecs: async () => [],
84
+ promptSelect: async (_rl, { title }) => {
85
+ selectCount += 1;
86
+ if (selectCount === 1) {
87
+ assert.ok(title.startsWith('Select '));
88
+ return 'pick';
89
+ }
90
+ throw new Error('unexpected second selection when no repos are available');
91
+ },
92
+ prompt: async () => '',
93
+ },
94
+ });
95
+
96
+ assert.equal(res, 'default');
97
+ assert.equal(selectCount, 1);
98
+ });
99
+
100
+ test('promptWorktreeSource offers dev when dev checkout exists (even with no category worktrees)', async (t) => {
101
+ const workspaceDir = await withTempRoot(t);
102
+ await mkdir(join(workspaceDir, 'dev'), { recursive: true });
103
+ const devGitFile = join(workspaceDir, 'dev', '.git');
104
+ await writeFile(devGitFile, 'gitdir: /tmp/fake', { encoding: 'utf8' });
105
+
106
+ const listWorktreeSpecs = async () => [];
107
+
108
+ let selectCount = 0;
109
+ const promptSelect = async (_rl, { title, options }) => {
110
+ selectCount++;
111
+ if (selectCount === 1) {
112
+ assert.ok(title.startsWith('Select '));
113
+ return 'pick';
114
+ }
115
+ assert.ok(title.startsWith('Available '));
116
+ assert.ok(options.some((o) => o.value === 'dev'));
117
+ return 'dev';
118
+ };
119
+ const prompt = async () => '';
120
+
121
+ const res = await promptWorktreeSource({
122
+ rl: {},
123
+ rootDir: '/tmp',
124
+ component: 'happier-ui',
125
+ stackName: 'exp1',
126
+ createRemote: 'upstream',
127
+ env: { ...process.env, HAPPIER_STACK_WORKSPACE_DIR: workspaceDir },
128
+ deps: { listWorktreeSpecs, promptSelect, prompt },
129
+ });
130
+
131
+ assert.equal(res, 'dev');
132
+ });
@@ -0,0 +1,33 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { promptWorktreeSource } from './wizard.mjs';
5
+
6
+ function mkRl() {
7
+ return { question: async () => '' };
8
+ }
9
+
10
+ test('promptWorktreeSource normalizes create worktree slug and reprompts when it sanitizes to empty', async (t) => {
11
+ t.mock.method(console, 'log', () => {});
12
+ let asks = 0;
13
+ const out = await promptWorktreeSource({
14
+ rl: mkRl(),
15
+ rootDir: '/tmp/hstack-root',
16
+ component: 'happier-ui',
17
+ stackName: 'exp-slug',
18
+ createRemote: 'upstream',
19
+ deps: {
20
+ promptSelect: async () => 'create',
21
+ listWorktreeSpecs: async () => [],
22
+ prompt: async (_rl, question, { defaultValue } = {}) => {
23
+ if (String(question).includes('New worktree slug')) {
24
+ asks += 1;
25
+ return asks === 1 ? '----' : 'My Feature';
26
+ }
27
+ return defaultValue ?? '';
28
+ },
29
+ },
30
+ });
31
+
32
+ assert.deepEqual(out, { create: true, slug: 'my-feature', remote: 'upstream' });
33
+ });
@@ -0,0 +1,14 @@
1
+ import { randomBytes } from 'node:crypto';
2
+
3
+ export function base64Url(buf) {
4
+ return Buffer.from(buf)
5
+ .toString('base64')
6
+ .replaceAll('+', '-')
7
+ .replaceAll('/', '_')
8
+ .replaceAll('=', '');
9
+ }
10
+
11
+ export function randomToken(lenBytes = 24) {
12
+ return base64Url(randomBytes(lenBytes));
13
+ }
14
+
@@ -0,0 +1,232 @@
1
+ import { join, resolve } from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+
4
+ import { ensureCliBuilt, ensureDepsInstalled } from '../proc/pm.mjs';
5
+ import { watchDebounced } from '../proc/watch.mjs';
6
+ import { getAccountCountForServerComponent, prepareDaemonAuthSeedIfNeeded } from '../stack/startup.mjs';
7
+ import { startLocalDaemonWithAuth } from '../../daemon.mjs';
8
+
9
+ export async function ensureDevCliReady(
10
+ { cliDir, buildCli, env = process.env },
11
+ { logger = console } = {}
12
+ ) {
13
+ await ensureDepsInstalled(cliDir, 'happier-cli', { env });
14
+ const distEntrypoint = join(cliDir, 'dist', 'index.mjs');
15
+
16
+ const keepExistingDistOnBuildFailure = (error) => {
17
+ if (!existsSync(distEntrypoint)) return null;
18
+ const msg = error instanceof Error ? error.stack || error.message : String(error);
19
+ logger.warn(
20
+ `[local] happier-cli build failed; keeping previous build output at ${distEntrypoint}.`
21
+ );
22
+ logger.warn(msg);
23
+ return { built: false, reason: 'build_failed_using_existing_dist' };
24
+ };
25
+
26
+ let res;
27
+ try {
28
+ res = await ensureCliBuilt(cliDir, { buildCli, env });
29
+ } catch (error) {
30
+ const fallback = keepExistingDistOnBuildFailure(error);
31
+ if (fallback) return fallback;
32
+ throw error;
33
+ }
34
+
35
+ // Fail closed: dev mode must never start the daemon without a usable happier-cli build output.
36
+ // Even if the user disabled CLI builds globally (or build mode is "never"), missing dist will
37
+ // cause an immediate MODULE_NOT_FOUND crash when spawning the daemon.
38
+ if (!existsSync(distEntrypoint)) {
39
+ // Last-chance recovery: force a build once.
40
+ try {
41
+ await ensureCliBuilt(cliDir, { buildCli: true, env });
42
+ } catch (error) {
43
+ const fallback = keepExistingDistOnBuildFailure(error);
44
+ if (fallback) return fallback;
45
+ throw error;
46
+ }
47
+ if (!existsSync(distEntrypoint)) {
48
+ throw new Error(
49
+ `[local] happier-cli build output is missing.\n` +
50
+ `Expected: ${distEntrypoint}\n` +
51
+ `Fix: run the component build directly and inspect its output:\n` +
52
+ ` cd "${cliDir}" && yarn build`
53
+ );
54
+ }
55
+ }
56
+
57
+ return res;
58
+ }
59
+
60
+ export async function prepareDaemonAuthSeed({
61
+ rootDir,
62
+ env,
63
+ stackName,
64
+ cliHomeDir,
65
+ startDaemon,
66
+ isInteractive,
67
+ serverComponentName,
68
+ serverDir,
69
+ serverEnv,
70
+ quiet = false,
71
+ }) {
72
+ if (!startDaemon) return { ok: true, skipped: true, reason: 'no_daemon' };
73
+ const acct = await getAccountCountForServerComponent({
74
+ serverComponentName,
75
+ serverDir,
76
+ env: serverEnv,
77
+ // This probe is used only for auth seeding heuristics (and should never block stack startup).
78
+ // For server-light (embedded PGlite), avoid doing anything that could fight for the single-connection DB.
79
+ bestEffort: true,
80
+ });
81
+ return await prepareDaemonAuthSeedIfNeeded({
82
+ rootDir,
83
+ env,
84
+ stackName,
85
+ cliHomeDir,
86
+ startDaemon,
87
+ isInteractive,
88
+ accountCount: typeof acct.accountCount === 'number' ? acct.accountCount : null,
89
+ quiet,
90
+ // IMPORTANT: run auth seeding under the same env used for server probes (includes DATABASE_URL).
91
+ authEnv: serverEnv,
92
+ });
93
+ }
94
+
95
+ export async function startDevDaemon({
96
+ startDaemon,
97
+ cliBin,
98
+ cliHomeDir,
99
+ internalServerUrl,
100
+ publicServerUrl,
101
+ restart,
102
+ isShuttingDown,
103
+ env = process.env,
104
+ stackName = null,
105
+ cliIdentity = 'default',
106
+ }, {
107
+ startLocalDaemonWithAuthImpl = startLocalDaemonWithAuth,
108
+ } = {}) {
109
+ if (!startDaemon) return;
110
+
111
+ await startLocalDaemonWithAuthImpl({
112
+ cliBin,
113
+ cliHomeDir,
114
+ internalServerUrl,
115
+ publicServerUrl,
116
+ isShuttingDown,
117
+ forceRestart: Boolean(restart),
118
+ env,
119
+ stackName,
120
+ cliIdentity,
121
+ });
122
+ }
123
+
124
+ export function watchHappyCliAndRestartDaemon({
125
+ enabled,
126
+ startDaemon,
127
+ buildCli,
128
+ cliDir,
129
+ cliBin,
130
+ cliHomeDir,
131
+ internalServerUrl,
132
+ publicServerUrl,
133
+ isShuttingDown,
134
+ env = process.env,
135
+ stackName = null,
136
+ cliIdentity = 'default',
137
+ }, {
138
+ watchDebouncedImpl = watchDebounced,
139
+ ensureCliBuiltImpl = ensureCliBuilt,
140
+ startLocalDaemonWithAuthImpl = startLocalDaemonWithAuth,
141
+ existsSyncImpl = existsSync,
142
+ logger = console,
143
+ } = {}) {
144
+ if (!enabled || !startDaemon) return null;
145
+
146
+ let inFlight = false;
147
+ let pending = false;
148
+
149
+ // IMPORTANT:
150
+ // Watch only source/config paths, not build outputs. Watching the whole repo can
151
+ // trigger rebuild loops because `yarn build` writes to `dist/` (and may touch other
152
+ // generated files), which then retriggers the watcher.
153
+ const watchPaths = [
154
+ join(cliDir, 'src'),
155
+ join(cliDir, 'bin'),
156
+ join(cliDir, 'codex'),
157
+ join(cliDir, 'package.json'),
158
+ join(cliDir, 'tsconfig.json'),
159
+ join(cliDir, 'tsconfig.build.json'),
160
+ join(cliDir, 'pkgroll.config.mjs'),
161
+ join(cliDir, 'yarn.lock'),
162
+ ].filter((p) => existsSyncImpl(p));
163
+
164
+ return watchDebouncedImpl({
165
+ paths: (watchPaths.length ? watchPaths : [cliDir]).map((p) => resolve(p)),
166
+ debounceMs: 500,
167
+ onChange: async () => {
168
+ if (isShuttingDown?.()) return;
169
+ if (inFlight) {
170
+ pending = true;
171
+ return;
172
+ }
173
+ inFlight = true;
174
+ try {
175
+ do {
176
+ pending = false;
177
+ if (isShuttingDown?.()) return;
178
+
179
+ logger.log('[local] watch: happier-cli changed → rebuilding + restarting daemon...');
180
+ try {
181
+ await ensureCliBuiltImpl(cliDir, { buildCli });
182
+ } catch (e) {
183
+ // IMPORTANT:
184
+ // - A rebuild can legitimately fail while an agent is mid-edit (e.g. TS errors).
185
+ // - In that case we must NOT restart the daemon (we'd just restart into a broken build),
186
+ // and we must NOT crash the parent dev process. Keep watching for the next change.
187
+ const msg = e instanceof Error ? e.stack || e.message : String(e);
188
+ logger.error('[local] watch: happier-cli rebuild failed; keeping daemon running (will retry on next change).');
189
+ logger.error(msg);
190
+ if (pending) continue;
191
+ break;
192
+ }
193
+
194
+ const distEntrypoint = join(cliDir, 'dist', 'index.mjs');
195
+ if (!existsSyncImpl(distEntrypoint)) {
196
+ logger.warn(
197
+ `[local] watch: happier-cli build did not produce ${distEntrypoint}; refusing to restart daemon to avoid downtime.`
198
+ );
199
+ if (pending) continue;
200
+ break;
201
+ }
202
+
203
+ try {
204
+ await startLocalDaemonWithAuthImpl({
205
+ cliBin,
206
+ cliHomeDir,
207
+ internalServerUrl,
208
+ publicServerUrl,
209
+ isShuttingDown,
210
+ forceRestart: true,
211
+ env,
212
+ stackName,
213
+ cliIdentity,
214
+ });
215
+ } catch (e) {
216
+ const msg = e instanceof Error ? e.stack || e.message : String(e);
217
+ logger.error('[local] watch: daemon restart failed; keeping dev runner alive (will retry on next change).');
218
+ logger.error(msg);
219
+ if (pending) continue;
220
+ break;
221
+ }
222
+ } while (pending);
223
+ } catch (e) {
224
+ const msg = e instanceof Error ? e.stack || e.message : String(e);
225
+ logger.error('[local] watch: unexpected watcher error (continuing):');
226
+ logger.error(msg);
227
+ } finally {
228
+ inFlight = false;
229
+ }
230
+ },
231
+ });
232
+ }