@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,464 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, writeFile, readFile, unlink } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { spawnSync } from 'node:child_process';
7
+ import { ensureCanonicalHomeEnvUpdated, ensureHomeEnvUpdated } from './utils/env/config.mjs';
8
+ import { loadEnvFile } from './utils/env/load_env_file.mjs';
9
+ import { expandHome } from './utils/paths/canonical_home.mjs';
10
+ import { readJsonIfExists } from './utils/fs/json.mjs';
11
+ import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
12
+ import { banner, bullets, cmd, kv, sectionTitle } from './utils/ui/layout.mjs';
13
+ import { cyan, dim, green, yellow } from './utils/ui/ansi.mjs';
14
+
15
+ function getCliRootDir() {
16
+ return dirname(dirname(fileURLToPath(import.meta.url)));
17
+ }
18
+
19
+ function parseArgValue(argv, key) {
20
+ const long = `--${key}=`;
21
+ const hit = argv.find((a) => a.startsWith(long));
22
+ if (hit) return hit.slice(long.length);
23
+ const idx = argv.indexOf(`--${key}`);
24
+ if (idx >= 0 && argv[idx + 1]) return argv[idx + 1];
25
+ return null;
26
+ }
27
+
28
+ function firstNonEmpty(...values) {
29
+ for (const v of values) {
30
+ const s = (v ?? '').trim();
31
+ if (s) return s;
32
+ }
33
+ return '';
34
+ }
35
+
36
+ function isWorkspaceBootstrapped(workspaceDir) {
37
+ // Heuristic: if the Happier monorepo checkout exists under the workspace, consider bootstrap "already done"
38
+ // and avoid re-running the interactive bootstrap wizard from `hstack init`.
39
+ //
40
+ // Users can always re-run bootstrap explicitly:
41
+ // hstack bootstrap --interactive
42
+ const looksLikeMonorepo = (dir) => {
43
+ try {
44
+ return (
45
+ existsSync(join(dir, 'apps', 'ui', 'package.json')) &&
46
+ existsSync(join(dir, 'apps', 'cli', 'package.json')) &&
47
+ existsSync(join(dir, 'apps', 'server', 'package.json'))
48
+ );
49
+ } catch {
50
+ return false;
51
+ }
52
+ };
53
+
54
+ try {
55
+ const candidates = [
56
+ // New default layout (Option C):
57
+ join(workspaceDir, 'main'),
58
+ // Legacy fallback while refactors are in-flight:
59
+ join(workspaceDir, 'happier'),
60
+ ];
61
+ return candidates.some(looksLikeMonorepo);
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ async function writeExecutable(path, contents) {
68
+ await writeFile(path, contents, { mode: 0o755 });
69
+ }
70
+
71
+ function escapeForDoubleQuotes(s) {
72
+ return String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
73
+ }
74
+
75
+ async function ensurePathInstalled({ homeDir }) {
76
+ const shell = (process.env.SHELL ?? '').toLowerCase();
77
+ const isDarwin = process.platform === 'darwin';
78
+
79
+ const zshrc = join(homedir(), '.zshrc');
80
+ const bashrc = join(homedir(), '.bashrc');
81
+ const bashProfile = join(homedir(), '.bash_profile');
82
+ const fishDir = join(homedir(), '.config', 'fish', 'conf.d');
83
+ const fishConf = join(fishDir, 'hstack.fish');
84
+
85
+ const markerStart = '# >>> hstack >>>';
86
+ const markerEnd = '# <<< hstack <<<';
87
+
88
+ const lineSh = `export PATH="${escapeForDoubleQuotes(join(homeDir, 'bin'))}:$PATH"`;
89
+ const blockSh = `\n${markerStart}\n${lineSh}\n${markerEnd}\n`;
90
+
91
+ const lineFish = `set -gx PATH "${escapeForDoubleQuotes(join(homeDir, 'bin'))}" $PATH`;
92
+ const blockFish = `\n${markerStart}\n${lineFish}\n${markerEnd}\n`;
93
+
94
+ const writeIfMissing = async (path, block) => {
95
+ let existing = '';
96
+ try {
97
+ existing = await readFile(path, 'utf-8');
98
+ } catch {
99
+ existing = '';
100
+ }
101
+ if (existing.includes(markerStart) || existing.includes(lineSh) || existing.includes(lineFish)) {
102
+ return { updated: false, path };
103
+ }
104
+ await writeFile(path, existing.replace(/\s*$/, '') + block, 'utf-8');
105
+ return { updated: true, path };
106
+ };
107
+
108
+ if (shell.includes('fish')) {
109
+ await mkdir(fishDir, { recursive: true });
110
+ return await writeIfMissing(fishConf, blockFish);
111
+ }
112
+
113
+ if (shell.includes('bash')) {
114
+ // macOS interactive bash typically sources ~/.bash_profile; linux usually uses ~/.bashrc.
115
+ const target = isDarwin ? bashProfile : bashrc;
116
+ return await writeIfMissing(target, blockSh);
117
+ }
118
+
119
+ // Default to zsh on modern macOS; also fine for linux users.
120
+ return await writeIfMissing(zshrc, blockSh);
121
+ }
122
+
123
+ async function main() {
124
+ const rawArgv = process.argv.slice(2);
125
+ const sep = rawArgv.indexOf('--');
126
+ const argv = sep >= 0 ? rawArgv.slice(0, sep) : rawArgv;
127
+ const bootstrapArgsRaw = sep >= 0 ? rawArgv.slice(sep + 1) : [];
128
+ const bootstrapArgs = bootstrapArgsRaw[0] === '--' ? bootstrapArgsRaw.slice(1) : bootstrapArgsRaw;
129
+ if (argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
130
+ console.log(
131
+ [
132
+ '',
133
+ banner('init', { subtitle: 'Initialize ~/.happier-stack (runtime + shims).' }),
134
+ '',
135
+ sectionTitle('usage:'),
136
+ ` ${cyan('hstack init')} [--canonical-home-dir=/path] [--home-dir=/path] [--workspace-dir=/path] [--runtime-dir=/path] [--storage-dir=/path] [--cli-root-dir=/path] [--tailscale-bin=/path] [--tailscale-cmd-timeout-ms=MS] [--tailscale-enable-timeout-ms=MS] [--tailscale-enable-timeout-ms-auto=MS] [--tailscale-reset-timeout-ms=MS] [--install-path] [--no-runtime] [--force-runtime] [--no-bootstrap] [--] [bootstrap args...]`,
137
+ '',
138
+ sectionTitle('what it does:'),
139
+ bullets([
140
+ `${cyan('home')} — stores runtime, shims, caches (default: ${cyan('~/.happier-stack')})`,
141
+ `${cyan('workspace')} — where component checkouts live (default: ${cyan('~/.happier-stack/workspace')})`,
142
+ `${cyan('runtime')} — stable install used by services/SwiftBar (default: ${cyan('~/.happier-stack/runtime')})`,
143
+ `${cyan('shims')} — installs ${cyan('hstack')} / ${cyan('happier')} under ${cyan('~/.happier-stack/bin')}`,
144
+ ]),
145
+ '',
146
+ sectionTitle('notes:'),
147
+ bullets([
148
+ `Writes ${cyan('~/.happier-stack/.env')} as a stable pointer file (helps launchd/SwiftBar find the install).`,
149
+ `Runtime install is skipped if the same version is already installed (use ${cyan('--force-runtime')} to reinstall).`,
150
+ `Set ${cyan('HAPPIER_STACK_INIT_NO_RUNTIME=1')} to persist skipping runtime installs on this machine.`,
151
+ `Optional: ${cyan('--install-path')} adds shims to your shell PATH (idempotent).`,
152
+ `By default, runs ${cyan('hstack bootstrap --interactive')} at the end (TTY only) if components are missing.`,
153
+ ]),
154
+ '',
155
+ ].join('\n')
156
+ );
157
+ return;
158
+ }
159
+
160
+ const cliRootDir = getCliRootDir();
161
+
162
+ // Important: `hstack init` must be idempotent and must not "forget" custom dirs from a prior install.
163
+ //
164
+ // Other scripts load this pointer via `scripts/utils/env.mjs`, but `init.mjs` is often run before
165
+ // anything else (or directly from a repo checkout). So we load it here too.
166
+ const canonicalHomeDirRaw = parseArgValue(argv, 'canonical-home-dir');
167
+ const canonicalHomeDir = expandHome(
168
+ firstNonEmpty(canonicalHomeDirRaw, process.env.HAPPIER_STACK_CANONICAL_HOME_DIR, join(homedir(), '.happier-stack'))
169
+ );
170
+ process.env.HAPPIER_STACK_CANONICAL_HOME_DIR = canonicalHomeDir;
171
+
172
+ const canonicalEnvPath = join(canonicalHomeDir, '.env');
173
+ if (existsSync(canonicalEnvPath)) {
174
+ await loadEnvFile(canonicalEnvPath, { override: false });
175
+ await loadEnvFile(canonicalEnvPath, { override: true, overridePrefix: 'HAPPIER_STACK_' });
176
+ }
177
+
178
+ const homeDirRaw = parseArgValue(argv, 'home-dir');
179
+ const homeDir = expandHome(firstNonEmpty(homeDirRaw, process.env.HAPPIER_STACK_HOME_DIR, join(homedir(), '.happier-stack')));
180
+ process.env.HAPPIER_STACK_HOME_DIR = homeDir;
181
+
182
+ const workspaceDirRaw = parseArgValue(argv, 'workspace-dir');
183
+ const workspaceDirExpanded = expandHome(firstNonEmpty(
184
+ workspaceDirRaw,
185
+ process.env.HAPPIER_STACK_WORKSPACE_DIR,
186
+ join(homeDir, 'workspace'),
187
+ ));
188
+ // If the user passes a relative --workspace-dir, interpret it as relative to the home dir
189
+ // (not the current cwd). This keeps setup predictable, especially when invoked via `npx`.
190
+ const workspaceDir = workspaceDirExpanded.startsWith('/') ? workspaceDirExpanded : resolve(homeDir, workspaceDirExpanded);
191
+ process.env.HAPPIER_STACK_WORKSPACE_DIR = workspaceDir;
192
+
193
+ const runtimeDirRaw = parseArgValue(argv, 'runtime-dir');
194
+ const runtimeDir = expandHome(firstNonEmpty(
195
+ runtimeDirRaw,
196
+ process.env.HAPPIER_STACK_RUNTIME_DIR,
197
+ join(homeDir, 'runtime'),
198
+ ));
199
+ process.env.HAPPIER_STACK_RUNTIME_DIR = runtimeDir;
200
+
201
+ const storageDirRaw = parseArgValue(argv, 'storage-dir');
202
+ const storageDirOverride = expandHome((storageDirRaw ?? '').trim());
203
+ if (storageDirOverride) {
204
+ // In sandbox mode, storage dir MUST be isolated and must override any pre-existing env.
205
+ process.env.HAPPIER_STACK_STORAGE_DIR = isSandboxed() ? storageDirOverride : (process.env.HAPPIER_STACK_STORAGE_DIR ?? storageDirOverride);
206
+ }
207
+
208
+ const cliRootDirRaw = parseArgValue(argv, 'cli-root-dir');
209
+ const cliRootDirOverride = expandHome((cliRootDirRaw ?? '').trim());
210
+ if (cliRootDirOverride) {
211
+ process.env.HAPPIER_STACK_CLI_ROOT_DIR = process.env.HAPPIER_STACK_CLI_ROOT_DIR ?? cliRootDirOverride;
212
+ }
213
+
214
+ const tailscaleBinRaw = parseArgValue(argv, 'tailscale-bin');
215
+ const tailscaleBinOverride = expandHome((tailscaleBinRaw ?? '').trim());
216
+ if (tailscaleBinOverride) {
217
+ process.env.HAPPIER_STACK_TAILSCALE_BIN = process.env.HAPPIER_STACK_TAILSCALE_BIN ?? tailscaleBinOverride;
218
+ }
219
+
220
+ const tailscaleCmdTimeoutMsRaw = parseArgValue(argv, 'tailscale-cmd-timeout-ms');
221
+ const tailscaleCmdTimeoutMsOverride = (tailscaleCmdTimeoutMsRaw ?? '').trim();
222
+ if (tailscaleCmdTimeoutMsOverride) {
223
+ process.env.HAPPIER_STACK_TAILSCALE_CMD_TIMEOUT_MS =
224
+ process.env.HAPPIER_STACK_TAILSCALE_CMD_TIMEOUT_MS ?? tailscaleCmdTimeoutMsOverride;
225
+ }
226
+
227
+ const tailscaleEnableTimeoutMsRaw = parseArgValue(argv, 'tailscale-enable-timeout-ms');
228
+ const tailscaleEnableTimeoutMsOverride = (tailscaleEnableTimeoutMsRaw ?? '').trim();
229
+ if (tailscaleEnableTimeoutMsOverride) {
230
+ process.env.HAPPIER_STACK_TAILSCALE_ENABLE_TIMEOUT_MS =
231
+ process.env.HAPPIER_STACK_TAILSCALE_ENABLE_TIMEOUT_MS ?? tailscaleEnableTimeoutMsOverride;
232
+ }
233
+
234
+ const tailscaleEnableTimeoutMsAutoRaw = parseArgValue(argv, 'tailscale-enable-timeout-ms-auto');
235
+ const tailscaleEnableTimeoutMsAutoOverride = (tailscaleEnableTimeoutMsAutoRaw ?? '').trim();
236
+ if (tailscaleEnableTimeoutMsAutoOverride) {
237
+ process.env.HAPPIER_STACK_TAILSCALE_ENABLE_TIMEOUT_MS_AUTO =
238
+ process.env.HAPPIER_STACK_TAILSCALE_ENABLE_TIMEOUT_MS_AUTO ?? tailscaleEnableTimeoutMsAutoOverride;
239
+ }
240
+
241
+ const tailscaleResetTimeoutMsRaw = parseArgValue(argv, 'tailscale-reset-timeout-ms');
242
+ const tailscaleResetTimeoutMsOverride = (tailscaleResetTimeoutMsRaw ?? '').trim();
243
+ if (tailscaleResetTimeoutMsOverride) {
244
+ process.env.HAPPIER_STACK_TAILSCALE_RESET_TIMEOUT_MS =
245
+ process.env.HAPPIER_STACK_TAILSCALE_RESET_TIMEOUT_MS ?? tailscaleResetTimeoutMsOverride;
246
+ }
247
+
248
+ const nodePath = process.execPath;
249
+
250
+ await mkdir(homeDir, { recursive: true });
251
+ await mkdir(canonicalHomeDir, { recursive: true });
252
+ await mkdir(workspaceDir, { recursive: true });
253
+ await mkdir(join(workspaceDir, 'components'), { recursive: true });
254
+ await mkdir(runtimeDir, { recursive: true });
255
+ await mkdir(join(homeDir, 'bin'), { recursive: true });
256
+
257
+ const pointerUpdates = [
258
+ { key: 'HAPPIER_STACK_HOME_DIR', value: homeDir },
259
+ { key: 'HAPPIER_STACK_WORKSPACE_DIR', value: workspaceDir },
260
+ { key: 'HAPPIER_STACK_RUNTIME_DIR', value: runtimeDir },
261
+ { key: 'HAPPIER_STACK_NODE', value: nodePath },
262
+ ];
263
+ if (storageDirOverride) {
264
+ pointerUpdates.push({ key: 'HAPPIER_STACK_STORAGE_DIR', value: storageDirOverride });
265
+ }
266
+ if (cliRootDirOverride) {
267
+ pointerUpdates.push({ key: 'HAPPIER_STACK_CLI_ROOT_DIR', value: cliRootDirOverride });
268
+ }
269
+
270
+ // Write the "real" home env (used by runtime + scripts), AND a stable pointer at ~/.happier-stack/.env.
271
+ // The pointer file allows launchd/SwiftBar/minimal shells to discover the actual install location
272
+ // even when no env vars are exported.
273
+ await ensureHomeEnvUpdated({ updates: pointerUpdates });
274
+ await ensureCanonicalHomeEnvUpdated({ updates: pointerUpdates });
275
+
276
+ const initNoRuntimeRaw = (process.env.HAPPIER_STACK_INIT_NO_RUNTIME ?? '').trim();
277
+ const initNoRuntime = initNoRuntimeRaw === '1' || initNoRuntimeRaw.toLowerCase() === 'true' || initNoRuntimeRaw.toLowerCase() === 'yes';
278
+ const forceRuntime = argv.includes('--force-runtime');
279
+ const skipRuntime = argv.includes('--no-runtime') || (initNoRuntime && !forceRuntime);
280
+ const installRuntime = !skipRuntime;
281
+ if (installRuntime) {
282
+ const cliPkg = await readJsonIfExists(join(cliRootDir, 'package.json'));
283
+ const cliVersion = String(cliPkg?.version ?? '').trim() || 'latest';
284
+ const spec = cliVersion === '0.0.0' ? '@happier-dev/stack@latest' : `@happier-dev/stack@${cliVersion}`;
285
+
286
+ const runtimePkgPath = join(runtimeDir, 'node_modules', '@happier-dev', 'stack', 'package.json');
287
+ const runtimePkg = await readJsonIfExists(runtimePkgPath);
288
+ const runtimeVersion = String(runtimePkg?.version ?? '').trim();
289
+ const sameVersionInstalled = Boolean(cliVersion && cliVersion !== '0.0.0' && runtimeVersion && runtimeVersion === cliVersion);
290
+
291
+ if (!forceRuntime && sameVersionInstalled) {
292
+ console.log(`${green('✓')} runtime already installed ${dim('(')}${cyan(runtimeDir)}${dim(')')} ${dim('@happier-dev/stack@')}${cyan(runtimeVersion)}`);
293
+ } else {
294
+ console.log(`${yellow('!')} installing runtime into ${cyan(runtimeDir)} ${dim('(')}${cyan(spec)}${dim(')')}...`);
295
+ let res = spawnSync('npm', ['install', '--no-audit', '--no-fund', '--silent', '--prefix', runtimeDir, spec], { stdio: 'inherit' });
296
+ if (res.status !== 0) {
297
+ // Pre-publish developer experience: if the package isn't on npm yet (E404),
298
+ // fall back to installing the local checkout into the runtime prefix.
299
+ console.log(`${yellow('!')} runtime install failed; attempting local install from ${cyan(cliRootDir)}...`);
300
+ res = spawnSync('npm', ['install', '--no-audit', '--no-fund', '--silent', '--prefix', runtimeDir, cliRootDir], { stdio: 'inherit' });
301
+ if (res.status !== 0) {
302
+ process.exit(res.status ?? 1);
303
+ }
304
+ }
305
+ }
306
+ }
307
+
308
+ const hstackShimPath = join(homeDir, 'bin', 'hstack');
309
+ const happierShimPath = join(homeDir, 'bin', 'happier');
310
+ const legacyHappyShimPath = join(homeDir, 'bin', 'happy');
311
+ const shim = [
312
+ '#!/bin/bash',
313
+ 'set -euo pipefail',
314
+ `CANONICAL_ENV="${canonicalEnvPath}"`,
315
+ '',
316
+ '# Best-effort: if env vars are not exported (common under launchd/SwiftBar),',
317
+ '# read the stable pointer file at CANONICAL_ENV to discover the real dirs.',
318
+ 'if [[ -f "$CANONICAL_ENV" ]]; then',
319
+ ' if [[ -z "${HAPPIER_STACK_HOME_DIR:-}" ]]; then',
320
+ ' HAPPIER_STACK_HOME_DIR="$(grep -E \'^HAPPIER_STACK_HOME_DIR=\' "$CANONICAL_ENV" | head -n 1 | sed \'s/^HAPPIER_STACK_HOME_DIR=//\')" || true',
321
+ ' export HAPPIER_STACK_HOME_DIR',
322
+ ' fi',
323
+ ' if [[ -z "${HAPPIER_STACK_WORKSPACE_DIR:-}" ]]; then',
324
+ ' HAPPIER_STACK_WORKSPACE_DIR="$(grep -E \'^HAPPIER_STACK_WORKSPACE_DIR=\' "$CANONICAL_ENV" | head -n 1 | sed \'s/^HAPPIER_STACK_WORKSPACE_DIR=//\')" || true',
325
+ ' export HAPPIER_STACK_WORKSPACE_DIR',
326
+ ' fi',
327
+ ' if [[ -z "${HAPPIER_STACK_RUNTIME_DIR:-}" ]]; then',
328
+ ' HAPPIER_STACK_RUNTIME_DIR="$(grep -E \'^HAPPIER_STACK_RUNTIME_DIR=\' "$CANONICAL_ENV" | head -n 1 | sed \'s/^HAPPIER_STACK_RUNTIME_DIR=//\')" || true',
329
+ ' export HAPPIER_STACK_RUNTIME_DIR',
330
+ ' fi',
331
+ ' if [[ -z "${HAPPIER_STACK_NODE:-}" ]]; then',
332
+ ' HAPPIER_STACK_NODE="$(grep -E \'^HAPPIER_STACK_NODE=\' "$CANONICAL_ENV" | head -n 1 | sed \'s/^HAPPIER_STACK_NODE=//\')" || true',
333
+ ' export HAPPIER_STACK_NODE',
334
+ ' fi',
335
+ ' if [[ -z "${HAPPIER_STACK_CLI_ROOT_DIR:-}" ]]; then',
336
+ ' HAPPIER_STACK_CLI_ROOT_DIR="$(grep -E \'^HAPPIER_STACK_CLI_ROOT_DIR=\' "$CANONICAL_ENV" | head -n 1 | sed \'s/^HAPPIER_STACK_CLI_ROOT_DIR=//\')" || true',
337
+ ' export HAPPIER_STACK_CLI_ROOT_DIR',
338
+ ' fi',
339
+ 'fi',
340
+ '',
341
+ `HOME_DIR="\${HAPPIER_STACK_HOME_DIR:-${canonicalHomeDir}}"`,
342
+ 'ENV_FILE="$HOME_DIR/.env"',
343
+ 'WORKDIR="${HAPPIER_STACK_WORKSPACE_DIR:-$HOME_DIR/workspace}"',
344
+ 'if [[ -d "$WORKDIR" ]]; then',
345
+ ' cd "$WORKDIR"',
346
+ 'else',
347
+ ' cd "$HOME"',
348
+ 'fi',
349
+ 'NODE_BIN="${HAPPIER_STACK_NODE:-}"',
350
+ 'if [[ -z "$NODE_BIN" && -f "$ENV_FILE" ]]; then',
351
+ ' NODE_BIN="$(grep -E \'^HAPPIER_STACK_NODE=\' "$ENV_FILE" | head -n 1 | sed \'s/^HAPPIER_STACK_NODE=//\')"',
352
+ 'fi',
353
+ 'if [[ -z "$NODE_BIN" ]]; then',
354
+ ' NODE_BIN="$(command -v node 2>/dev/null || true)"',
355
+ 'fi',
356
+ 'CLI_ROOT_DIR="${HAPPIER_STACK_CLI_ROOT_DIR:-}"',
357
+ 'if [[ -z "$CLI_ROOT_DIR" && -f "$ENV_FILE" ]]; then',
358
+ ' CLI_ROOT_DIR="$(grep -E \'^HAPPIER_STACK_CLI_ROOT_DIR=\' "$ENV_FILE" | head -n 1 | sed \'s/^HAPPIER_STACK_CLI_ROOT_DIR=//\')" || true',
359
+ 'fi',
360
+ 'if [[ -n "$CLI_ROOT_DIR" ]]; then',
361
+ ' CLI_ENTRY="$CLI_ROOT_DIR/bin/hstack.mjs"',
362
+ ' if [[ -f "$CLI_ENTRY" ]]; then',
363
+ ' exec "$NODE_BIN" "$CLI_ENTRY" "$@"',
364
+ ' fi',
365
+ 'fi',
366
+ 'RUNTIME_DIR="${HAPPIER_STACK_RUNTIME_DIR:-$HOME_DIR/runtime}"',
367
+ 'ENTRY="$RUNTIME_DIR/node_modules/@happier-dev/stack/bin/hstack.mjs"',
368
+ 'if [[ -f "$ENTRY" ]]; then',
369
+ ' exec "$NODE_BIN" "$ENTRY" "$@"',
370
+ 'fi',
371
+ 'echo "[hstack] missing runtime install; run: hstack init --force-runtime" >&2',
372
+ 'exit 127',
373
+ '',
374
+ ].join('\n');
375
+
376
+ await writeExecutable(hstackShimPath, shim);
377
+
378
+ // Convenience shim for the Happier CLI (avoid clashing with Happy stacks' `happy`).
379
+ await writeExecutable(
380
+ happierShimPath,
381
+ `#!/bin/bash\nset -euo pipefail\nexec "${hstackShimPath}" happier "$@"\n`
382
+ );
383
+
384
+ // Remove legacy `happy` shim if it exists (it conflicts with Happy stacks installs).
385
+ await unlink(legacyHappyShimPath).catch(() => {});
386
+
387
+ let didInstallPath = false;
388
+ if (argv.includes('--install-path')) {
389
+ if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
390
+ console.log(`${yellow('!')} sandbox mode: skipping --install-path (would modify your shell config)`);
391
+ console.log(`${dim('Tip:')} set ${cyan('HAPPIER_STACK_SANDBOX_ALLOW_GLOBAL=1')} if you really want to test PATH modifications`);
392
+ } else {
393
+ const res = await ensurePathInstalled({ homeDir });
394
+ didInstallPath = true;
395
+ if (res.updated) {
396
+ console.log(`${green('✓')} added ${cyan(join(homeDir, 'bin'))} to PATH via ${cyan(res.path)}`);
397
+ } else {
398
+ console.log(`${green('✓')} PATH already configured in ${cyan(res.path)}`);
399
+ }
400
+ }
401
+ }
402
+
403
+ const invokedBySetup = (process.env.HAPPIER_STACK_SETUP_CHILD ?? '').trim() === '1';
404
+
405
+ console.log('');
406
+ console.log(`${green('✓')} init complete`);
407
+ console.log(bullets([kv('home:', cyan(homeDir)), kv('workspace:', cyan(workspaceDir)), kv('shims:', cyan(join(homeDir, 'bin')))]));
408
+ console.log('');
409
+
410
+ if (!argv.includes('--install-path') || !didInstallPath) {
411
+ console.log(sectionTitle('PATH'));
412
+ console.log(dim('To use `hstack` / `happier` from any terminal, add shims to PATH:'));
413
+ console.log(cmd(`export PATH="${join(homeDir, 'bin')}:$PATH"`));
414
+ console.log(dim(`(or re-run: ${cmd('hstack init --install-path')})`));
415
+ console.log('');
416
+ } else {
417
+ console.log(dim('Note: restart your terminal (or source your shell config) to pick up PATH changes.'));
418
+ console.log('');
419
+ }
420
+
421
+ const wantBootstrap = !argv.includes('--no-bootstrap');
422
+ const isTty = process.stdout.isTTY && process.stdin.isTTY;
423
+ const alreadyBootstrapped = isWorkspaceBootstrapped(workspaceDir);
424
+ const bootstrapExplicit = bootstrapArgs.length > 0;
425
+ const shouldBootstrap = wantBootstrap && (bootstrapExplicit || !alreadyBootstrapped);
426
+
427
+ if (shouldBootstrap) {
428
+ const nextArgs = [...bootstrapArgs];
429
+ // Only auto-enable the interactive wizard when init is driving bootstrap with no explicit args.
430
+ // If users pass args after `--`, we assume they know what they want and avoid injecting prompts.
431
+ if (!bootstrapExplicit && isTty && !nextArgs.includes('--interactive') && !nextArgs.includes('-i')) {
432
+ nextArgs.unshift('--interactive');
433
+ }
434
+ console.log(`${yellow('!')} running bootstrap...`);
435
+ const res = spawnSync(process.execPath, [join(cliRootDir, 'scripts', 'install.mjs'), ...nextArgs], {
436
+ stdio: 'inherit',
437
+ env: process.env,
438
+ cwd: cliRootDir,
439
+ });
440
+ if (res.status !== 0) {
441
+ process.exit(res.status ?? 1);
442
+ }
443
+ return;
444
+ }
445
+
446
+ if (wantBootstrap && alreadyBootstrapped && !bootstrapExplicit) {
447
+ console.log(`${green('✓')} bootstrap already set up; skipping`);
448
+ console.log(`${dim('Tip: for guided onboarding run:')} ${cmd('hstack setup')}`);
449
+ console.log('');
450
+ }
451
+
452
+ // When `hstack setup` drives init, avoid printing confusing “next steps”.
453
+ if (invokedBySetup) {
454
+ return;
455
+ }
456
+
457
+ console.log(sectionTitle('Next steps'));
458
+ console.log(bullets([cmd(`export PATH="${join(homeDir, 'bin')}:$PATH"`), cmd('hstack setup')]));
459
+ }
460
+
461
+ main().catch((err) => {
462
+ console.error('[init] failed:', err);
463
+ process.exit(1);
464
+ });