@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,198 @@
1
+ import { homedir } from 'node:os';
2
+ import { dirname, join } from 'node:path';
3
+ import { mkdir, rename, writeFile } from 'node:fs/promises';
4
+ import { existsSync } from 'node:fs';
5
+
6
+ import { runCapture } from '../proc/proc.mjs';
7
+ import { getDefaultAutostartPaths } from '../paths/paths.mjs';
8
+ import { resolveInstalledCliRoot, resolveInstalledPath } from '../paths/runtime.mjs';
9
+ import { getCanonicalHomeDir } from '../env/config.mjs';
10
+
11
+ function plistPathForLabel(label) {
12
+ return join(homedir(), 'Library', 'LaunchAgents', `${label}.plist`);
13
+ }
14
+
15
+ function splitPath(p) {
16
+ return String(p ?? '')
17
+ .split(':')
18
+ .map((s) => s.trim())
19
+ .filter(Boolean);
20
+ }
21
+
22
+ export function buildLaunchdPath({ execPath = process.execPath, basePath = process.env.PATH } = {}) {
23
+ // launchd starts with a minimal environment; ensure common tool paths exist,
24
+ // and include the current Node binary directory so shell shims that exec `node`
25
+ // still work (e.g. nvm-managed installs).
26
+ const nodeDir = execPath ? dirname(execPath) : '';
27
+ const defaults = splitPath('/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin');
28
+ const fromNode = nodeDir ? [nodeDir] : [];
29
+ const fromEnv = splitPath(basePath);
30
+
31
+ const seen = new Set();
32
+ const out = [];
33
+ for (const part of [...fromNode, ...fromEnv, ...defaults]) {
34
+ if (seen.has(part)) continue;
35
+ seen.add(part);
36
+ out.push(part);
37
+ }
38
+ return out.join(':') || '/usr/bin:/bin:/usr/sbin:/sbin';
39
+ }
40
+
41
+ export function pickLaunchdProgramArgs({ rootDir, execPath = process.execPath } = {}) {
42
+ // Prefer the stable shim under the canonical home dir (used by selfhost installs).
43
+ // This keeps the LaunchAgent pointing at a stable path while allowing runtime updates.
44
+ const hstackShim = join(getCanonicalHomeDir(), 'bin', 'hstack');
45
+ if (existsSync(hstackShim)) {
46
+ return [hstackShim, 'start'];
47
+ }
48
+ // Fallback: call the Node entry directly (works in repo-only installs).
49
+ return [execPath, resolveInstalledPath(rootDir, 'bin/hstack.mjs'), 'start'];
50
+ }
51
+
52
+ function xmlEscape(s) {
53
+ return String(s ?? '')
54
+ .replaceAll('&', '&')
55
+ .replaceAll('<', '&lt;')
56
+ .replaceAll('>', '&gt;')
57
+ .replaceAll('"', '&quot;')
58
+ .replaceAll("'", '&apos;');
59
+ }
60
+
61
+ function plistXml({ label, programArgs, env = {}, stdoutPath, stderrPath, workingDirectory }) {
62
+ const envEntries = Object.entries(env ?? {}).filter(([k, v]) => String(k).trim() && String(v ?? '').trim());
63
+ const programArgsXml = programArgs.map((a) => ` <string>${xmlEscape(a)}</string>`).join('\n');
64
+ const envXml = envEntries
65
+ .map(([k, v]) => ` <key>${xmlEscape(k)}</key>\n <string>${xmlEscape(v)}</string>`)
66
+ .join('\n');
67
+ const workingDirXml = workingDirectory
68
+ ? `\n <key>WorkingDirectory</key>\n <string>${xmlEscape(workingDirectory)}</string>\n`
69
+ : '\n';
70
+
71
+ // IMPORTANT:
72
+ // We want launchd to restart the stack if it crashes, but not to spin in a tight loop when
73
+ // `hstack start` exits cleanly (e.g. it detects "already running").
74
+ //
75
+ // `KeepAlive` with `SuccessfulExit=false` means:
76
+ // - exit 0 => do not restart (success)
77
+ // - exit non-zero => restart (crash)
78
+ const keepAliveXml =
79
+ `\n <key>KeepAlive</key>\n` +
80
+ ` <dict>\n` +
81
+ ` <key>SuccessfulExit</key>\n` +
82
+ ` <false/>\n` +
83
+ ` </dict>\n`;
84
+
85
+ return `<?xml version="1.0" encoding="UTF-8"?>
86
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
87
+ <plist version="1.0">
88
+ <dict>
89
+ <key>Label</key>
90
+ <string>${xmlEscape(label)}</string>
91
+
92
+ <key>ProgramArguments</key>
93
+ <array>
94
+ ${programArgsXml}
95
+ </array>
96
+
97
+ <key>RunAtLoad</key>
98
+ <true/>
99
+ ${keepAliveXml}
100
+ ${workingDirXml} <key>StandardOutPath</key>
101
+ <string>${xmlEscape(stdoutPath)}</string>
102
+ <key>StandardErrorPath</key>
103
+ <string>${xmlEscape(stderrPath)}</string>
104
+
105
+ <key>EnvironmentVariables</key>
106
+ <dict>
107
+ ${envXml}
108
+ </dict>
109
+ </dict>
110
+ </plist>
111
+ `;
112
+ }
113
+
114
+ export function buildLaunchdPlistXml({ label, programArgs, env = {}, stdoutPath, stderrPath, workingDirectory }) {
115
+ return plistXml({ label, programArgs, env, stdoutPath, stderrPath, workingDirectory });
116
+ }
117
+
118
+ export async function ensureMacAutostartEnabled({ rootDir, label, env }) {
119
+ if (process.platform !== 'darwin') {
120
+ throw new Error('[local] macOS autostart is only supported on Darwin');
121
+ }
122
+ const l = String(label ?? '').trim();
123
+ if (!l) throw new Error('[local] missing launchd label');
124
+
125
+ const plistPath = plistPathForLabel(l);
126
+ const { stdoutPath, stderrPath } = getDefaultAutostartPaths();
127
+ await mkdir(dirname(plistPath), { recursive: true }).catch(() => {});
128
+ await mkdir(dirname(stdoutPath), { recursive: true }).catch(() => {});
129
+ await mkdir(dirname(stderrPath), { recursive: true }).catch(() => {});
130
+
131
+ const programArgs = pickLaunchdProgramArgs({ rootDir, execPath: process.execPath });
132
+ const mergedEnv = {
133
+ ...(env ?? {}),
134
+ // Ensure a reasonable PATH for subprocesses (git/docker/etc) in launchd’s minimal environment.
135
+ // Also ensure Node is on PATH for shell shims that exec `node` (common with nvm installs).
136
+ PATH: buildLaunchdPath({ execPath: process.execPath, basePath: process.env.PATH }),
137
+ };
138
+
139
+ const xml = buildLaunchdPlistXml({
140
+ label: l,
141
+ programArgs,
142
+ env: mergedEnv,
143
+ stdoutPath,
144
+ stderrPath,
145
+ workingDirectory: resolveInstalledCliRoot(rootDir),
146
+ });
147
+
148
+ const tmp = join(dirname(plistPath), `.tmp.${l}.${Date.now()}.plist`);
149
+ await writeFile(tmp, xml, 'utf-8');
150
+ await rename(tmp, plistPath);
151
+
152
+ // Best-effort load/enable; `scripts/service.mjs` has a more robust bootstrap fallback.
153
+ try {
154
+ await runCapture('launchctl', ['load', '-w', plistPath]);
155
+ } catch {
156
+ // ignore
157
+ }
158
+ }
159
+
160
+ export async function ensureMacAutostartDisabled({ label }) {
161
+ if (process.platform !== 'darwin') {
162
+ return;
163
+ }
164
+ const l = String(label ?? '').trim();
165
+ if (!l) return;
166
+ const plistPath = plistPathForLabel(l);
167
+
168
+ const uidRaw = Number(process.env.UID);
169
+ const uid = Number.isFinite(uidRaw) ? uidRaw : null;
170
+
171
+ try {
172
+ await runCapture('launchctl', ['unload', '-w', plistPath]);
173
+ } catch {
174
+ // ignore
175
+ }
176
+ try {
177
+ await runCapture('launchctl', ['unload', plistPath]);
178
+ } catch {
179
+ // ignore
180
+ }
181
+ if (uid != null) {
182
+ try {
183
+ await runCapture('launchctl', ['disable', `gui/${uid}/${l}`]);
184
+ } catch {
185
+ // ignore
186
+ }
187
+ try {
188
+ await runCapture('launchctl', ['bootout', `gui/${uid}`, plistPath]);
189
+ } catch {
190
+ // ignore
191
+ }
192
+ }
193
+ try {
194
+ await runCapture('launchctl', ['remove', l]);
195
+ } catch {
196
+ // ignore
197
+ }
198
+ }
@@ -0,0 +1,49 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+
7
+ import { buildLaunchdPath, pickLaunchdProgramArgs } from './autostart_darwin.mjs';
8
+
9
+ function setEnvKey(t, key, value) {
10
+ const previous = process.env[key];
11
+ if (value == null) delete process.env[key];
12
+ else process.env[key] = value;
13
+ t.after(() => {
14
+ if (previous == null) delete process.env[key];
15
+ else process.env[key] = previous;
16
+ });
17
+ }
18
+
19
+ test('buildLaunchdPath includes node dir and common tool paths', () => {
20
+ const execPath = '/Users/me/.nvm/versions/node/v22.14.0/bin/node';
21
+ const p = buildLaunchdPath({ execPath, basePath: '' });
22
+
23
+ assert.ok(p.includes('/Users/me/.nvm/versions/node/v22.14.0/bin'), 'includes node dir');
24
+ assert.ok(p.includes('/usr/bin'), 'includes /usr/bin');
25
+ assert.ok(p.includes('/bin'), 'includes /bin');
26
+ });
27
+
28
+ test('pickLaunchdProgramArgs uses stable hstack shim when present', async (t) => {
29
+ const dir = await mkdtemp(join(tmpdir(), 'happy-stacks-home-'));
30
+ t.after(async () => {
31
+ await rm(dir, { recursive: true, force: true });
32
+ });
33
+ const shim = join(dir, 'bin', 'hstack');
34
+ await mkdir(join(dir, 'bin'), { recursive: true });
35
+ await writeFile(shim, '#!/bin/sh\necho ok\n', { encoding: 'utf-8' });
36
+
37
+ setEnvKey(t, 'HAPPIER_STACK_CANONICAL_HOME_DIR', dir);
38
+ const args = pickLaunchdProgramArgs({ rootDir: '/fake/root' });
39
+ assert.deepEqual(args, [shim, 'start']);
40
+ });
41
+
42
+ test('pickLaunchdProgramArgs falls back to node + hstack.mjs when shim missing', (t) => {
43
+ setEnvKey(t, 'HAPPIER_STACK_CANONICAL_HOME_DIR', '/definitely-not-a-real-path');
44
+ const execPath = '/usr/local/bin/node';
45
+ const args = pickLaunchdProgramArgs({ rootDir: '/cli/root', execPath });
46
+ assert.equal(args[0], execPath);
47
+ assert.ok(String(args[1]).endsWith('/bin/hstack.mjs'));
48
+ assert.equal(args[2], 'start');
49
+ });
@@ -0,0 +1,19 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { buildLaunchdPlistXml } from './autostart_darwin.mjs';
5
+
6
+ test('buildLaunchdPlistXml uses KeepAlive SuccessfulExit=false (avoid launchd restart loops on clean exits)', () => {
7
+ const xml = buildLaunchdPlistXml({
8
+ label: 'dev.happier.stack',
9
+ programArgs: ['/bin/echo', 'start'],
10
+ env: { HAPPIER_STACK_ENV_FILE: '/tmp/env' },
11
+ stdoutPath: '/tmp/out.log',
12
+ stderrPath: '/tmp/err.log',
13
+ workingDirectory: '/tmp',
14
+ });
15
+
16
+ assert.match(xml, /<key>KeepAlive<\/key>\s*<dict>/m);
17
+ assert.match(xml, /<key>SuccessfulExit<\/key>\s*<false\/>/m);
18
+ });
19
+
@@ -0,0 +1,29 @@
1
+ import { dirname, join } from 'node:path';
2
+
3
+ const IDENTITY_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
4
+
5
+ export function parseCliIdentityOrThrow(raw) {
6
+ const v = (raw ?? '').toString().trim();
7
+ if (!v || v === 'default') return 'default';
8
+ if (!IDENTITY_RE.test(v)) {
9
+ throw new Error(
10
+ `[stack] invalid --identity=${JSON.stringify(v)}. ` +
11
+ `Expected: "default" or a short name matching ${IDENTITY_RE} (max 64 chars).`
12
+ );
13
+ }
14
+ return v;
15
+ }
16
+
17
+ export function resolveCliHomeDirForIdentity({ cliHomeDir, identity }) {
18
+ const id = parseCliIdentityOrThrow(identity);
19
+ if (id === 'default') return cliHomeDir;
20
+
21
+ // Keep identities adjacent to the stack's default cli home dir:
22
+ // <...>/<stack>/cli (default)
23
+ // <...>/<stack>/cli-identities/<id>
24
+ //
25
+ // If the stack overrides cliHomeDir to a custom path, we keep the same layout
26
+ // relative to that path's parent directory.
27
+ const baseDir = dirname(cliHomeDir);
28
+ return join(baseDir, 'cli-identities', id);
29
+ }
@@ -0,0 +1,19 @@
1
+ import { getStackName, resolveStackEnvPath } from '../paths/paths.mjs';
2
+ import { getStackRuntimeStatePath } from './runtime_state.mjs';
3
+
4
+ export function resolveStackContext({ env = process.env, autostart = null } = {}) {
5
+ const explicitStack = (env.HAPPIER_STACK_STACK ?? '').toString().trim();
6
+ const stackName = explicitStack || (autostart?.stackName ?? '') || getStackName(env);
7
+ const stackMode = Boolean(explicitStack);
8
+
9
+ const envPath =
10
+ (env.HAPPIER_STACK_ENV_FILE ?? '').toString().trim() || resolveStackEnvPath(stackName, env).envPath;
11
+
12
+ const runtimeStatePath =
13
+ (env.HAPPIER_STACK_RUNTIME_STATE_PATH ?? '').toString().trim() || getStackRuntimeStatePath(stackName);
14
+
15
+ const explicitEphemeral = (env.HAPPIER_STACK_EPHEMERAL_PORTS ?? '').toString().trim() === '1';
16
+ const ephemeral = explicitEphemeral || (stackMode && stackName !== 'main');
17
+
18
+ return { stackMode, stackName, envPath, runtimeStatePath, ephemeral };
19
+ }
@@ -0,0 +1,26 @@
1
+ import { join } from 'node:path';
2
+
3
+ import { expandHome } from '../paths/canonical_home.mjs';
4
+ import { getDefaultAutostartPaths } from '../paths/paths.mjs';
5
+
6
+ export function getCliHomeDirFromEnvOrDefault({ stackBaseDir, env }) {
7
+ const fromEnv = (env?.HAPPIER_STACK_CLI_HOME_DIR ?? '').trim();
8
+ return fromEnv || join(stackBaseDir, 'cli');
9
+ }
10
+
11
+ export function getServerLightDataDirFromEnvOrDefault({ stackBaseDir, env }) {
12
+ const fromEnv = (env?.HAPPIER_SERVER_LIGHT_DATA_DIR ?? '').trim();
13
+ return fromEnv || join(stackBaseDir, 'server-light');
14
+ }
15
+
16
+ export function resolveCliHomeDir(env = process.env) {
17
+ const fromExplicit = (env.HAPPIER_HOME_DIR ?? '').trim();
18
+ if (fromExplicit) {
19
+ return expandHome(fromExplicit);
20
+ }
21
+ const fromStacks = (env.HAPPIER_STACK_CLI_HOME_DIR ?? '').trim();
22
+ if (fromStacks) {
23
+ return expandHome(fromStacks);
24
+ }
25
+ return join(getDefaultAutostartPaths().baseDir, 'cli');
26
+ }
@@ -0,0 +1,126 @@
1
+ import { join, resolve } from 'node:path';
2
+ import { writeFile } from 'node:fs/promises';
3
+
4
+ import { expandHome } from '../paths/canonical_home.mjs';
5
+ import { getComponentDir, getRepoDir, resolveStackEnvPath } from '../paths/paths.mjs';
6
+ import { ensureDir } from '../fs/ops.mjs';
7
+ import { getEnvValueAny } from '../env/values.mjs';
8
+ import { readEnvObjectFromFile } from '../env/read.mjs';
9
+ import { resolveCommandPath } from '../proc/commands.mjs';
10
+ import { run, runCapture } from '../proc/proc.mjs';
11
+ import { getCliHomeDirFromEnvOrDefault } from './dirs.mjs';
12
+
13
+ export async function isCursorInstalled({ cwd, env } = {}) {
14
+ if (await resolveCommandPath('cursor', { cwd, env })) return true;
15
+ if (process.platform !== 'darwin') return false;
16
+ try {
17
+ await runCapture('open', ['-Ra', 'Cursor'], { cwd, env });
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ export async function openWorkspaceInEditor({ rootDir, editor, workspacePath }) {
25
+ if (editor === 'code') {
26
+ const codePath = await resolveCommandPath('code', { cwd: rootDir, env: process.env });
27
+ if (!codePath) {
28
+ throw new Error(
29
+ "[stack] VS Code CLI 'code' not found on PATH. In VS Code: Cmd+Shift+P → 'Shell Command: Install code command in PATH'."
30
+ );
31
+ }
32
+ await run(codePath, ['-n', workspacePath], { cwd: rootDir, env: process.env, stdio: 'inherit' });
33
+ return;
34
+ }
35
+
36
+ const cursorPath = await resolveCommandPath('cursor', { cwd: rootDir, env: process.env });
37
+ if (cursorPath) {
38
+ try {
39
+ await run(cursorPath, ['-n', workspacePath], { cwd: rootDir, env: process.env, stdio: 'inherit' });
40
+ } catch {
41
+ await run(cursorPath, [workspacePath], { cwd: rootDir, env: process.env, stdio: 'inherit' });
42
+ }
43
+ return;
44
+ }
45
+
46
+ if (process.platform === 'darwin') {
47
+ // Cursor installed but CLI missing is common on macOS.
48
+ await run('open', ['-na', 'Cursor', workspacePath], { cwd: rootDir, env: process.env, stdio: 'inherit' });
49
+ return;
50
+ }
51
+
52
+ throw new Error("[stack] Cursor CLI 'cursor' not found on PATH (and non-macOS fallback is unavailable).");
53
+ }
54
+
55
+ export async function writeStackCodeWorkspace({
56
+ rootDir,
57
+ stackName,
58
+ includeStackDir,
59
+ includeAllComponents,
60
+ includeCliHome,
61
+ }) {
62
+ const { baseDir, envPath } = resolveStackEnvPath(stackName);
63
+ const stackEnv = await readEnvObjectFromFile(envPath);
64
+
65
+ const serverComponent =
66
+ getEnvValueAny(stackEnv, ['HAPPIER_STACK_SERVER_COMPONENT']) || 'happier-server-light';
67
+
68
+ const folders = [];
69
+ if (includeStackDir) {
70
+ folders.push({ name: `stack:${stackName}`, path: baseDir });
71
+ }
72
+ if (includeCliHome) {
73
+ const cliHomeDir = getCliHomeDirFromEnvOrDefault({ stackBaseDir: baseDir, env: stackEnv });
74
+ folders.push({ name: `cli:${stackName}`, path: expandHome(cliHomeDir) });
75
+ }
76
+ // Repo-only model: stacks pin a single monorepo checkout/worktree via HAPPIER_STACK_REPO_DIR.
77
+ const repoRoot = getRepoDir(rootDir, stackEnv);
78
+ folders.push({ name: 'repo', path: repoRoot });
79
+
80
+ // Optional convenience: include service subfolders for easier IDE scoping.
81
+ if (includeAllComponents) {
82
+ const uiDir = getComponentDir(rootDir, 'happier-ui', stackEnv);
83
+ const cliDir = getComponentDir(rootDir, 'happier-cli', stackEnv);
84
+ const serverDir = getComponentDir(rootDir, serverComponent, stackEnv);
85
+ folders.push({ name: 'ui', path: uiDir });
86
+ folders.push({ name: 'cli', path: cliDir });
87
+ folders.push({ name: 'server', path: serverDir });
88
+ }
89
+
90
+ // Deduplicate by path (can happen if multiple components are pointed at the same dir).
91
+ const uniqFolders = folders.filter((f, i, arr) => arr.findIndex((x) => x.path === f.path) === i);
92
+
93
+ await ensureDir(baseDir);
94
+ const workspacePath = join(baseDir, `stack.${stackName}.code-workspace`);
95
+ const payload = {
96
+ folders: uniqFolders,
97
+ settings: {
98
+ 'search.exclude': {
99
+ '**/node_modules/**': true,
100
+ '**/.git/**': true,
101
+ '**/logs/**': true,
102
+ '**/cli/logs/**': true,
103
+ },
104
+ 'files.watcherExclude': {
105
+ '**/node_modules/**': true,
106
+ '**/.git/**': true,
107
+ '**/logs/**': true,
108
+ '**/cli/logs/**': true,
109
+ },
110
+ },
111
+ };
112
+ await writeFile(workspacePath, JSON.stringify(payload, null, 2) + '\n', 'utf-8');
113
+
114
+ return {
115
+ workspacePath,
116
+ baseDir,
117
+ envPath,
118
+ serverComponent,
119
+ folders: uniqFolders,
120
+ flags: {
121
+ includeStackDir: Boolean(includeStackDir),
122
+ includeCliHome: Boolean(includeCliHome),
123
+ includeAllComponents: Boolean(includeAllComponents),
124
+ },
125
+ };
126
+ }