@happier-dev/stack 0.1.0-preview.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. package/README.md +501 -0
  2. package/bin/hstack.mjs +348 -0
  3. package/docs/codex-mcp-resume.md +129 -0
  4. package/docs/edison.md +74 -0
  5. package/docs/forking-and-branding.md +189 -0
  6. package/docs/happy-development.md +22 -0
  7. package/docs/isolated-linux-vm.md +243 -0
  8. package/docs/menubar.md +244 -0
  9. package/docs/mobile-ios.md +322 -0
  10. package/docs/monorepo-migration.md +20 -0
  11. package/docs/paths-and-env.md +154 -0
  12. package/docs/remote-access.md +43 -0
  13. package/docs/server-flavors.md +147 -0
  14. package/docs/stacks.md +330 -0
  15. package/docs/tauri.md +60 -0
  16. package/docs/worktrees-and-forks.md +133 -0
  17. package/extras/swiftbar/auth-login.sh +29 -0
  18. package/extras/swiftbar/git-cache-refresh.sh +122 -0
  19. package/extras/swiftbar/hstack-term.sh +133 -0
  20. package/extras/swiftbar/hstack.5s.sh +296 -0
  21. package/extras/swiftbar/hstack.sh +35 -0
  22. package/extras/swiftbar/icons/happy-green.png +0 -0
  23. package/extras/swiftbar/icons/happy-orange.png +0 -0
  24. package/extras/swiftbar/icons/happy-red.png +0 -0
  25. package/extras/swiftbar/icons/logo-white.png +0 -0
  26. package/extras/swiftbar/install.sh +265 -0
  27. package/extras/swiftbar/lib/git.sh +629 -0
  28. package/extras/swiftbar/lib/icons.sh +92 -0
  29. package/extras/swiftbar/lib/render.sh +999 -0
  30. package/extras/swiftbar/lib/system.sh +244 -0
  31. package/extras/swiftbar/lib/utils.sh +717 -0
  32. package/extras/swiftbar/set-interval.sh +65 -0
  33. package/extras/swiftbar/set-server-flavor.sh +61 -0
  34. package/extras/swiftbar/wt-pr.sh +140 -0
  35. package/node_modules/@happier-dev/cli-common/README.md +6 -0
  36. package/node_modules/@happier-dev/cli-common/dist/index.d.ts +4 -0
  37. package/node_modules/@happier-dev/cli-common/dist/index.d.ts.map +1 -0
  38. package/node_modules/@happier-dev/cli-common/dist/index.js +4 -0
  39. package/node_modules/@happier-dev/cli-common/dist/index.js.map +1 -0
  40. package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts +18 -0
  41. package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts.map +1 -0
  42. package/node_modules/@happier-dev/cli-common/dist/links/index.js +25 -0
  43. package/node_modules/@happier-dev/cli-common/dist/links/index.js.map +1 -0
  44. package/node_modules/@happier-dev/cli-common/dist/links.d.ts +2 -0
  45. package/node_modules/@happier-dev/cli-common/dist/links.d.ts.map +1 -0
  46. package/node_modules/@happier-dev/cli-common/dist/links.js +2 -0
  47. package/node_modules/@happier-dev/cli-common/dist/links.js.map +1 -0
  48. package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts +67 -0
  49. package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts.map +1 -0
  50. package/node_modules/@happier-dev/cli-common/dist/update/index.js +259 -0
  51. package/node_modules/@happier-dev/cli-common/dist/update/index.js.map +1 -0
  52. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts +17 -0
  53. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts.map +1 -0
  54. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js +80 -0
  55. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js.map +1 -0
  56. package/node_modules/@happier-dev/cli-common/package.json +26 -0
  57. package/package.json +77 -0
  58. package/scripts/auth.mjs +1829 -0
  59. package/scripts/auth_copy_from_pglite_lock_in_use.integration.test.mjs +90 -0
  60. package/scripts/auth_copy_from_runCapture.integration.test.mjs +447 -0
  61. package/scripts/auth_help_cmd.test.mjs +28 -0
  62. package/scripts/auth_login_flow_in_tty.test.mjs +100 -0
  63. package/scripts/auth_login_force_default.test.mjs +66 -0
  64. package/scripts/auth_login_guided_server_no_expo.test.mjs +126 -0
  65. package/scripts/auth_login_method_override.test.mjs +67 -0
  66. package/scripts/auth_login_print_includes_configure_links.test.mjs +99 -0
  67. package/scripts/auth_status_server_validation.integration.test.mjs +140 -0
  68. package/scripts/build.mjs +266 -0
  69. package/scripts/bundleWorkspaceDeps.mjs +38 -0
  70. package/scripts/bundleWorkspaceDeps.test.mjs +77 -0
  71. package/scripts/ci.mjs +135 -0
  72. package/scripts/ci.test.mjs +50 -0
  73. package/scripts/cli-link.mjs +57 -0
  74. package/scripts/completion.mjs +395 -0
  75. package/scripts/contrib.mjs +333 -0
  76. package/scripts/daemon.mjs +1160 -0
  77. package/scripts/daemon.status_scope.test.mjs +51 -0
  78. package/scripts/daemon_cmd.mjs +26 -0
  79. package/scripts/daemon_dist_guard.test.mjs +171 -0
  80. package/scripts/daemon_invalid_auth_reseed_stack_name.integration.test.mjs +608 -0
  81. package/scripts/daemon_server_scoped_state.test.mjs +49 -0
  82. package/scripts/daemon_start_verification.integration.test.mjs +296 -0
  83. package/scripts/dev.mjs +545 -0
  84. package/scripts/doctor.mjs +340 -0
  85. package/scripts/doctor_cmd.test.mjs +22 -0
  86. package/scripts/doctor_ui_index_missing.test.mjs +37 -0
  87. package/scripts/eas.mjs +367 -0
  88. package/scripts/eas_platform_parsing.test.mjs +63 -0
  89. package/scripts/edison.mjs +1848 -0
  90. package/scripts/env.mjs +149 -0
  91. package/scripts/env_cmd.test.mjs +118 -0
  92. package/scripts/exit_cleanup_kills_detached_children_on_crash.integration.test.mjs +80 -0
  93. package/scripts/happier.mjs +82 -0
  94. package/scripts/import.mjs +1327 -0
  95. package/scripts/init.mjs +464 -0
  96. package/scripts/install.mjs +550 -0
  97. package/scripts/lint.mjs +177 -0
  98. package/scripts/menubar.mjs +202 -0
  99. package/scripts/migrate.mjs +318 -0
  100. package/scripts/mobile.mjs +353 -0
  101. package/scripts/mobile_dev_client.mjs +87 -0
  102. package/scripts/monorepo.mjs +2234 -0
  103. package/scripts/monorepo_port.apply.integration.test.mjs +680 -0
  104. package/scripts/monorepo_port.conflicts.integration.test.mjs +454 -0
  105. package/scripts/monorepo_port.validation.integration.test.mjs +486 -0
  106. package/scripts/orchestrated_stack_auth_flow.test.mjs +134 -0
  107. package/scripts/orchestrated_stack_auth_flow_resolve_port.test.mjs +98 -0
  108. package/scripts/orchestrated_stack_auth_flow_webapp_url.test.mjs +119 -0
  109. package/scripts/pack.mjs +257 -0
  110. package/scripts/pack.test.mjs +68 -0
  111. package/scripts/pglite_lock.integration.test.mjs +152 -0
  112. package/scripts/provision/linux-ubuntu-e2e.sh +132 -0
  113. package/scripts/provision/linux-ubuntu-review-pr.sh +66 -0
  114. package/scripts/provision/macos-lima-happy-vm.sh +192 -0
  115. package/scripts/provision/macos-lima-hstack-e2e.sh +100 -0
  116. package/scripts/release.mjs +53 -0
  117. package/scripts/release_binary_smoke.integration.test.mjs +159 -0
  118. package/scripts/review.mjs +1752 -0
  119. package/scripts/review_pr.mjs +435 -0
  120. package/scripts/run.mjs +561 -0
  121. package/scripts/run_script_with_stack_env.restart_port_reuse.test.mjs +30 -0
  122. package/scripts/self.mjs +465 -0
  123. package/scripts/self_host.mjs +9 -0
  124. package/scripts/self_host_binary_smoke.integration.test.mjs +94 -0
  125. package/scripts/self_host_runtime.mjs +883 -0
  126. package/scripts/self_host_runtime.test.mjs +82 -0
  127. package/scripts/self_host_systemd.real.integration.test.mjs +367 -0
  128. package/scripts/server_flavor.mjs +148 -0
  129. package/scripts/service.mjs +868 -0
  130. package/scripts/service_mode_help.test.mjs +27 -0
  131. package/scripts/setup.mjs +1324 -0
  132. package/scripts/setup_non_interactive_flag.test.mjs +60 -0
  133. package/scripts/setup_pr.mjs +605 -0
  134. package/scripts/setup_pr_orchestrated_auth_flow_util_import.test.mjs +117 -0
  135. package/scripts/stack/command_arguments.mjs +91 -0
  136. package/scripts/stack/copy_auth_from_stack.mjs +111 -0
  137. package/scripts/stack/delegated_script_commands.mjs +92 -0
  138. package/scripts/stack/help_text.mjs +110 -0
  139. package/scripts/stack/port_reservation.mjs +74 -0
  140. package/scripts/stack/repo_checkout_resolution.mjs +31 -0
  141. package/scripts/stack/run_script_with_stack_env.mjs +634 -0
  142. package/scripts/stack/stack_daemon_command.mjs +219 -0
  143. package/scripts/stack/stack_delegated_help.mjs +81 -0
  144. package/scripts/stack/stack_environment.mjs +151 -0
  145. package/scripts/stack/stack_environment.sanitization.test.mjs +75 -0
  146. package/scripts/stack/stack_happier_passthrough_command.mjs +63 -0
  147. package/scripts/stack/stack_info_snapshot.mjs +167 -0
  148. package/scripts/stack/stack_mobile_install_command.mjs +61 -0
  149. package/scripts/stack/stack_resume_command.mjs +76 -0
  150. package/scripts/stack/stack_stop_command.mjs +34 -0
  151. package/scripts/stack/stack_workspace_command.mjs +83 -0
  152. package/scripts/stack/transient_repo_overrides.mjs +29 -0
  153. package/scripts/stack.mjs +2388 -0
  154. package/scripts/stack_archive_cmd.integration.test.mjs +31 -0
  155. package/scripts/stack_audit_fix_light_env.test.mjs +129 -0
  156. package/scripts/stack_background_pinned_stack_json.test.mjs +81 -0
  157. package/scripts/stack_copy_auth_server_scoped.test.mjs +243 -0
  158. package/scripts/stack_daemon_cmd.integration.test.mjs +484 -0
  159. package/scripts/stack_eas_help.test.mjs +72 -0
  160. package/scripts/stack_editor_workspace_monorepo_root.test.mjs +102 -0
  161. package/scripts/stack_env_cmd.test.mjs +107 -0
  162. package/scripts/stack_guided_login_bundle_error_parse.test.mjs +20 -0
  163. package/scripts/stack_guided_login_inner_invocation.test.mjs +46 -0
  164. package/scripts/stack_happy_cmd.integration.test.mjs +263 -0
  165. package/scripts/stack_info_snapshot_running_status.test.mjs +186 -0
  166. package/scripts/stack_interactive_monorepo_group.test.mjs +128 -0
  167. package/scripts/stack_monorepo_defaults.test.mjs +31 -0
  168. package/scripts/stack_monorepo_repo_dev_token.test.mjs +32 -0
  169. package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +37 -0
  170. package/scripts/stack_new_name_normalize_cmd.test.mjs +38 -0
  171. package/scripts/stack_pr_name_normalize_cmd.test.mjs +84 -0
  172. package/scripts/stack_resume_cmd.integration.test.mjs +134 -0
  173. package/scripts/stack_server_flavors_defaults.test.mjs +64 -0
  174. package/scripts/stack_shorthand_cmd.integration.test.mjs +74 -0
  175. package/scripts/stack_stop_sweeps_legacy_infra_without_kind.integration.test.mjs +44 -0
  176. package/scripts/stack_stop_sweeps_when_runtime_missing.integration.test.mjs +42 -0
  177. package/scripts/stack_stop_sweeps_when_runtime_stale.integration.test.mjs +50 -0
  178. package/scripts/stack_wt_list.test.mjs +117 -0
  179. package/scripts/start_ui_required_default.test.mjs +63 -0
  180. package/scripts/stop.mjs +190 -0
  181. package/scripts/stopStackWithEnv_no_autosweep_when_runtime_missing.integration.test.mjs +95 -0
  182. package/scripts/swiftbar_git_monorepo_cmd.test.mjs +75 -0
  183. package/scripts/swiftbar_render_monorepo_wt_actions.integration.test.mjs +116 -0
  184. package/scripts/swiftbar_utils_cmd.test.mjs +92 -0
  185. package/scripts/swiftbar_wt_pr_backcompat.test.mjs +162 -0
  186. package/scripts/systemd_unit_info.test.mjs +24 -0
  187. package/scripts/tailscale.mjs +490 -0
  188. package/scripts/test_ci.mjs +36 -0
  189. package/scripts/test_cmd.mjs +274 -0
  190. package/scripts/test_cmd.test.mjs +133 -0
  191. package/scripts/test_integration.mjs +33 -0
  192. package/scripts/testkit/auth_testkit.mjs +121 -0
  193. package/scripts/testkit/doctor_testkit.mjs +68 -0
  194. package/scripts/testkit/monorepo_port_testkit.mjs +157 -0
  195. package/scripts/testkit/stack_archive_command_testkit.mjs +55 -0
  196. package/scripts/testkit/stack_new_monorepo_testkit.mjs +83 -0
  197. package/scripts/testkit/stack_script_command_testkit.mjs +27 -0
  198. package/scripts/testkit/stack_stop_sweeps_testkit.mjs +172 -0
  199. package/scripts/testkit/worktrees_monorepo_testkit.mjs +53 -0
  200. package/scripts/tools.mjs +70 -0
  201. package/scripts/tui.mjs +914 -0
  202. package/scripts/tui_stopStackForTuiExit_no_autosweep.integration.test.mjs +95 -0
  203. package/scripts/typecheck.mjs +178 -0
  204. package/scripts/ui_gateway.mjs +247 -0
  205. package/scripts/uninstall.mjs +179 -0
  206. package/scripts/utils/auth/credentials_paths.mjs +181 -0
  207. package/scripts/utils/auth/credentials_paths.test.mjs +187 -0
  208. package/scripts/utils/auth/daemon_gate.mjs +66 -0
  209. package/scripts/utils/auth/daemon_gate.test.mjs +116 -0
  210. package/scripts/utils/auth/decode_jwt_payload_unsafe.mjs +16 -0
  211. package/scripts/utils/auth/dev_key.mjs +163 -0
  212. package/scripts/utils/auth/files.mjs +56 -0
  213. package/scripts/utils/auth/guided_pr_auth.mjs +86 -0
  214. package/scripts/utils/auth/guided_stack_web_login.mjs +56 -0
  215. package/scripts/utils/auth/handy_master_secret.mjs +42 -0
  216. package/scripts/utils/auth/interactive_stack_auth.mjs +70 -0
  217. package/scripts/utils/auth/login_ux.mjs +105 -0
  218. package/scripts/utils/auth/orchestrated_stack_auth_flow.mjs +291 -0
  219. package/scripts/utils/auth/sources.mjs +28 -0
  220. package/scripts/utils/auth/stable_scope_id.mjs +91 -0
  221. package/scripts/utils/auth/stable_scope_id.test.mjs +51 -0
  222. package/scripts/utils/auth/stack_guided_login.mjs +438 -0
  223. package/scripts/utils/cli/arg_values.mjs +23 -0
  224. package/scripts/utils/cli/arg_values.test.mjs +43 -0
  225. package/scripts/utils/cli/args.mjs +17 -0
  226. package/scripts/utils/cli/cli.mjs +24 -0
  227. package/scripts/utils/cli/cli_registry.mjs +440 -0
  228. package/scripts/utils/cli/cwd_scope.mjs +158 -0
  229. package/scripts/utils/cli/cwd_scope.test.mjs +154 -0
  230. package/scripts/utils/cli/flags.mjs +17 -0
  231. package/scripts/utils/cli/log_forwarder.mjs +157 -0
  232. package/scripts/utils/cli/normalize.mjs +16 -0
  233. package/scripts/utils/cli/prereqs.mjs +103 -0
  234. package/scripts/utils/cli/prereqs.test.mjs +33 -0
  235. package/scripts/utils/cli/progress.mjs +141 -0
  236. package/scripts/utils/cli/smoke_help.mjs +44 -0
  237. package/scripts/utils/cli/verbosity.mjs +11 -0
  238. package/scripts/utils/cli/wizard.mjs +139 -0
  239. package/scripts/utils/cli/wizard_promptSelect.test.mjs +44 -0
  240. package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +132 -0
  241. package/scripts/utils/cli/wizard_worktree_slug.test.mjs +33 -0
  242. package/scripts/utils/crypto/tokens.mjs +14 -0
  243. package/scripts/utils/dev/daemon.mjs +232 -0
  244. package/scripts/utils/dev/daemon_watch_resilience.test.mjs +224 -0
  245. package/scripts/utils/dev/expo_dev.buildEnv.test.mjs +35 -0
  246. package/scripts/utils/dev/expo_dev.mjs +478 -0
  247. package/scripts/utils/dev/expo_dev.test.mjs +89 -0
  248. package/scripts/utils/dev/expo_dev_restart_port_reservation.test.mjs +120 -0
  249. package/scripts/utils/dev/expo_dev_verbose_logs.test.mjs +60 -0
  250. package/scripts/utils/dev/server.mjs +180 -0
  251. package/scripts/utils/dev_auth_key.mjs +7 -0
  252. package/scripts/utils/edison/git_roots.mjs +30 -0
  253. package/scripts/utils/edison/git_roots.test.mjs +49 -0
  254. package/scripts/utils/env/config.mjs +52 -0
  255. package/scripts/utils/env/dotenv.mjs +32 -0
  256. package/scripts/utils/env/dotenv.test.mjs +32 -0
  257. package/scripts/utils/env/env.mjs +130 -0
  258. package/scripts/utils/env/env_file.mjs +98 -0
  259. package/scripts/utils/env/env_file.test.mjs +49 -0
  260. package/scripts/utils/env/env_local.mjs +25 -0
  261. package/scripts/utils/env/load_env_file.mjs +34 -0
  262. package/scripts/utils/env/read.mjs +30 -0
  263. package/scripts/utils/env/sandbox.mjs +13 -0
  264. package/scripts/utils/env/scrub_env.mjs +69 -0
  265. package/scripts/utils/env/scrub_env.test.mjs +102 -0
  266. package/scripts/utils/env/values.mjs +13 -0
  267. package/scripts/utils/expo/command.mjs +65 -0
  268. package/scripts/utils/expo/expo.mjs +139 -0
  269. package/scripts/utils/expo/expo_state_running.test.mjs +48 -0
  270. package/scripts/utils/expo/metro_ports.mjs +101 -0
  271. package/scripts/utils/expo/metro_ports.test.mjs +35 -0
  272. package/scripts/utils/fs/atomic_dir_swap.mjs +55 -0
  273. package/scripts/utils/fs/atomic_dir_swap.test.mjs +54 -0
  274. package/scripts/utils/fs/file_has_content.mjs +10 -0
  275. package/scripts/utils/fs/fs.mjs +11 -0
  276. package/scripts/utils/fs/json.mjs +25 -0
  277. package/scripts/utils/fs/ops.mjs +29 -0
  278. package/scripts/utils/fs/package_json.mjs +8 -0
  279. package/scripts/utils/fs/tail.mjs +12 -0
  280. package/scripts/utils/git/dev_checkout.mjs +127 -0
  281. package/scripts/utils/git/dev_checkout.test.mjs +115 -0
  282. package/scripts/utils/git/git.mjs +67 -0
  283. package/scripts/utils/git/parse_name_status_z.mjs +21 -0
  284. package/scripts/utils/git/refs.mjs +26 -0
  285. package/scripts/utils/git/worktrees.mjs +323 -0
  286. package/scripts/utils/git/worktrees_monorepo.test.mjs +60 -0
  287. package/scripts/utils/git/worktrees_pathstyle.test.mjs +53 -0
  288. package/scripts/utils/llm/assist.mjs +260 -0
  289. package/scripts/utils/llm/codex_exec.mjs +61 -0
  290. package/scripts/utils/llm/codex_exec.test.mjs +46 -0
  291. package/scripts/utils/llm/hstack_runner.mjs +59 -0
  292. package/scripts/utils/llm/tools.mjs +56 -0
  293. package/scripts/utils/llm/tools.test.mjs +67 -0
  294. package/scripts/utils/menubar/swiftbar.mjs +121 -0
  295. package/scripts/utils/menubar/swiftbar.test.mjs +85 -0
  296. package/scripts/utils/mobile/config.mjs +35 -0
  297. package/scripts/utils/mobile/dev_client_links.mjs +59 -0
  298. package/scripts/utils/mobile/identifiers.mjs +46 -0
  299. package/scripts/utils/mobile/identifiers.test.mjs +41 -0
  300. package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
  301. package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +131 -0
  302. package/scripts/utils/net/bind_mode.mjs +39 -0
  303. package/scripts/utils/net/dns.mjs +10 -0
  304. package/scripts/utils/net/lan_ip.mjs +24 -0
  305. package/scripts/utils/net/ports.mjs +110 -0
  306. package/scripts/utils/net/tcp_forward.mjs +162 -0
  307. package/scripts/utils/net/url.mjs +30 -0
  308. package/scripts/utils/net/url.test.mjs +29 -0
  309. package/scripts/utils/paths/canonical_home.mjs +15 -0
  310. package/scripts/utils/paths/canonical_home.test.mjs +28 -0
  311. package/scripts/utils/paths/localhost_host.mjs +112 -0
  312. package/scripts/utils/paths/localhost_host.test.mjs +58 -0
  313. package/scripts/utils/paths/paths.mjs +302 -0
  314. package/scripts/utils/paths/paths_env_win32.test.mjs +36 -0
  315. package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
  316. package/scripts/utils/paths/paths_server_flavors.test.mjs +50 -0
  317. package/scripts/utils/paths/runtime.mjs +41 -0
  318. package/scripts/utils/pglite_lock.mjs +107 -0
  319. package/scripts/utils/proc/commands.mjs +33 -0
  320. package/scripts/utils/proc/exit_cleanup.mjs +57 -0
  321. package/scripts/utils/proc/happy_monorepo_deps.mjs +37 -0
  322. package/scripts/utils/proc/happy_monorepo_deps.test.mjs +89 -0
  323. package/scripts/utils/proc/ownership.mjs +217 -0
  324. package/scripts/utils/proc/ownership_killProcessGroupOwnedByStack.test.mjs +216 -0
  325. package/scripts/utils/proc/ownership_listPidsWithEnvNeedles.test.mjs +88 -0
  326. package/scripts/utils/proc/package_scripts.mjs +38 -0
  327. package/scripts/utils/proc/package_scripts.test.mjs +58 -0
  328. package/scripts/utils/proc/parallel.mjs +25 -0
  329. package/scripts/utils/proc/pids.mjs +11 -0
  330. package/scripts/utils/proc/pm.mjs +478 -0
  331. package/scripts/utils/proc/pm_spawn.integration.test.mjs +131 -0
  332. package/scripts/utils/proc/pm_stack_cache_env.test.mjs +313 -0
  333. package/scripts/utils/proc/proc.mjs +331 -0
  334. package/scripts/utils/proc/proc.test.mjs +85 -0
  335. package/scripts/utils/proc/terminate.mjs +69 -0
  336. package/scripts/utils/proc/terminate.test.mjs +54 -0
  337. package/scripts/utils/proc/watch.mjs +63 -0
  338. package/scripts/utils/review/augment_runner_integration.test.mjs +105 -0
  339. package/scripts/utils/review/base_ref.mjs +82 -0
  340. package/scripts/utils/review/base_ref.test.mjs +89 -0
  341. package/scripts/utils/review/chunks.mjs +55 -0
  342. package/scripts/utils/review/chunks.test.mjs +107 -0
  343. package/scripts/utils/review/detached_worktree.mjs +61 -0
  344. package/scripts/utils/review/detached_worktree.test.mjs +61 -0
  345. package/scripts/utils/review/findings.mjs +278 -0
  346. package/scripts/utils/review/findings.test.mjs +203 -0
  347. package/scripts/utils/review/head_slice.mjs +132 -0
  348. package/scripts/utils/review/head_slice.test.mjs +117 -0
  349. package/scripts/utils/review/instructions/deep.md +20 -0
  350. package/scripts/utils/review/prompts.mjs +279 -0
  351. package/scripts/utils/review/prompts.test.mjs +77 -0
  352. package/scripts/utils/review/run_reviewers_safe.mjs +12 -0
  353. package/scripts/utils/review/run_reviewers_safe.test.mjs +45 -0
  354. package/scripts/utils/review/runners/augment.mjs +91 -0
  355. package/scripts/utils/review/runners/augment.test.mjs +64 -0
  356. package/scripts/utils/review/runners/claude.mjs +92 -0
  357. package/scripts/utils/review/runners/claude.test.mjs +47 -0
  358. package/scripts/utils/review/runners/coderabbit.mjs +105 -0
  359. package/scripts/utils/review/runners/coderabbit.test.mjs +32 -0
  360. package/scripts/utils/review/runners/codex.mjs +129 -0
  361. package/scripts/utils/review/runners/codex.test.mjs +115 -0
  362. package/scripts/utils/review/slice_mode.mjs +20 -0
  363. package/scripts/utils/review/slice_mode.test.mjs +69 -0
  364. package/scripts/utils/review/sliced_runner.mjs +39 -0
  365. package/scripts/utils/review/sliced_runner.test.mjs +57 -0
  366. package/scripts/utils/review/slices.mjs +140 -0
  367. package/scripts/utils/review/slices.test.mjs +41 -0
  368. package/scripts/utils/review/targets.mjs +23 -0
  369. package/scripts/utils/review/targets.test.mjs +31 -0
  370. package/scripts/utils/review/tool_home_seed.mjs +106 -0
  371. package/scripts/utils/review/tool_home_seed.test.mjs +124 -0
  372. package/scripts/utils/review/uncommitted_ops.mjs +77 -0
  373. package/scripts/utils/review/uncommitted_ops.test.mjs +117 -0
  374. package/scripts/utils/sandbox/review_pr_sandbox.mjs +105 -0
  375. package/scripts/utils/server/apply_server_light_env_defaults.mjs +14 -0
  376. package/scripts/utils/server/flavor_scripts.mjs +138 -0
  377. package/scripts/utils/server/flavor_scripts.test.mjs +115 -0
  378. package/scripts/utils/server/infra/happy_server_infra.mjs +444 -0
  379. package/scripts/utils/server/mobile_api_url.mjs +60 -0
  380. package/scripts/utils/server/mobile_api_url.test.mjs +58 -0
  381. package/scripts/utils/server/port.mjs +55 -0
  382. package/scripts/utils/server/prisma_import.mjs +36 -0
  383. package/scripts/utils/server/prisma_import.test.mjs +78 -0
  384. package/scripts/utils/server/server.mjs +109 -0
  385. package/scripts/utils/server/ui_build_check.mjs +37 -0
  386. package/scripts/utils/server/ui_build_check.test.mjs +70 -0
  387. package/scripts/utils/server/ui_env.mjs +13 -0
  388. package/scripts/utils/server/ui_env.test.mjs +57 -0
  389. package/scripts/utils/server/urls.mjs +100 -0
  390. package/scripts/utils/server/validate.mjs +60 -0
  391. package/scripts/utils/server/validate.test.mjs +76 -0
  392. package/scripts/utils/service/autostart_darwin.mjs +198 -0
  393. package/scripts/utils/service/autostart_darwin.test.mjs +49 -0
  394. package/scripts/utils/service/autostart_darwin_keepalive.test.mjs +19 -0
  395. package/scripts/utils/stack/cli_identities.mjs +29 -0
  396. package/scripts/utils/stack/context.mjs +19 -0
  397. package/scripts/utils/stack/dirs.mjs +26 -0
  398. package/scripts/utils/stack/editor_workspace.mjs +126 -0
  399. package/scripts/utils/stack/interactive_stack_config.mjs +266 -0
  400. package/scripts/utils/stack/interactive_stack_config.port_validation.test.mjs +93 -0
  401. package/scripts/utils/stack/interactive_stack_config.remote_validation.test.mjs +122 -0
  402. package/scripts/utils/stack/interactive_stack_config.stack_name_validation.test.mjs +76 -0
  403. package/scripts/utils/stack/interactive_stack_config_testkit.mjs +18 -0
  404. package/scripts/utils/stack/names.mjs +27 -0
  405. package/scripts/utils/stack/names.test.mjs +26 -0
  406. package/scripts/utils/stack/pr_stack_name.mjs +16 -0
  407. package/scripts/utils/stack/runtime_state.mjs +88 -0
  408. package/scripts/utils/stack/stacks.mjs +40 -0
  409. package/scripts/utils/stack/startup.mjs +370 -0
  410. package/scripts/utils/stack/startup_server_light_dirs.test.mjs +119 -0
  411. package/scripts/utils/stack/startup_server_light_generate.test.mjs +20 -0
  412. package/scripts/utils/stack/startup_server_light_legacy.test.mjs +79 -0
  413. package/scripts/utils/stack/startup_server_light_testkit.mjs +106 -0
  414. package/scripts/utils/stack/stop.mjs +284 -0
  415. package/scripts/utils/stack_context.mjs +1 -0
  416. package/scripts/utils/stack_runtime_state.mjs +1 -0
  417. package/scripts/utils/stacks.mjs +1 -0
  418. package/scripts/utils/tailscale/ip.mjs +116 -0
  419. package/scripts/utils/tauri/stack_overrides.mjs +22 -0
  420. package/scripts/utils/test/collect_test_files.mjs +29 -0
  421. package/scripts/utils/time/get_today_ymd.mjs +7 -0
  422. package/scripts/utils/tui/cleanup.mjs +38 -0
  423. package/scripts/utils/ui/ansi.mjs +47 -0
  424. package/scripts/utils/ui/browser.mjs +31 -0
  425. package/scripts/utils/ui/browser.test.mjs +56 -0
  426. package/scripts/utils/ui/clipboard.mjs +38 -0
  427. package/scripts/utils/ui/layout.mjs +44 -0
  428. package/scripts/utils/ui/qr.mjs +17 -0
  429. package/scripts/utils/ui/terminal_launcher.mjs +129 -0
  430. package/scripts/utils/ui/text.mjs +16 -0
  431. package/scripts/utils/update/auto_update_notice.mjs +93 -0
  432. package/scripts/utils/validate.mjs +5 -0
  433. package/scripts/where.mjs +138 -0
  434. package/scripts/worktrees.mjs +2174 -0
  435. package/scripts/worktrees_archive_cmd.integration.test.mjs +228 -0
  436. package/scripts/worktrees_cursor_monorepo_root.test.mjs +23 -0
  437. package/scripts/worktrees_list_specs_no_recurse.test.mjs +32 -0
  438. package/scripts/worktrees_monorepo_testkit.test.mjs +29 -0
  439. package/scripts/worktrees_monorepo_use_group.test.mjs +41 -0
@@ -0,0 +1,163 @@
1
+ import { chmod, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+
5
+ import { getHappyStacksHomeDir } from '../paths/paths.mjs';
6
+
7
+ export function getDevAuthKeyPath(env = process.env) {
8
+ return join(getHappyStacksHomeDir(env), 'keys', 'dev-auth.json');
9
+ }
10
+
11
+ function base64UrlToBytes(s) {
12
+ try {
13
+ const raw = String(s ?? '').trim();
14
+ if (!raw) return null;
15
+ const b64 = raw.replace(/-/g, '+').replace(/_/g, '/');
16
+ const pad = b64.length % 4 === 0 ? '' : '='.repeat(4 - (b64.length % 4));
17
+ return new Uint8Array(Buffer.from(b64 + pad, 'base64'));
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ function bytesToBase64Url(bytes) {
24
+ const b64 = Buffer.from(bytes).toString('base64');
25
+ return b64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
26
+ }
27
+
28
+ // Base32 alphabet (RFC 4648)
29
+ const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
30
+
31
+ function bytesToBase32(bytes) {
32
+ let result = '';
33
+ let buffer = 0;
34
+ let bufferLength = 0;
35
+
36
+ for (const byte of bytes) {
37
+ buffer = (buffer << 8) | byte;
38
+ bufferLength += 8;
39
+ while (bufferLength >= 5) {
40
+ bufferLength -= 5;
41
+ result += BASE32_ALPHABET[(buffer >> bufferLength) & 0x1f];
42
+ }
43
+ }
44
+ if (bufferLength > 0) {
45
+ result += BASE32_ALPHABET[(buffer << (5 - bufferLength)) & 0x1f];
46
+ }
47
+ return result;
48
+ }
49
+
50
+ function base32ToBytes(base32) {
51
+ let normalized = String(base32 ?? '')
52
+ .toUpperCase()
53
+ .replace(/0/g, 'O')
54
+ .replace(/1/g, 'I')
55
+ .replace(/8/g, 'B')
56
+ .replace(/9/g, 'G');
57
+ const cleaned = normalized.replace(/[^A-Z2-7]/g, '');
58
+ if (!cleaned) throw new Error('no valid base32 characters');
59
+
60
+ const bytes = [];
61
+ let buffer = 0;
62
+ let bufferLength = 0;
63
+ for (const char of cleaned) {
64
+ const value = BASE32_ALPHABET.indexOf(char);
65
+ if (value === -1) throw new Error('invalid base32 character');
66
+ buffer = (buffer << 5) | value;
67
+ bufferLength += 5;
68
+ if (bufferLength >= 8) {
69
+ bufferLength -= 8;
70
+ bytes.push((buffer >> bufferLength) & 0xff);
71
+ }
72
+ }
73
+ return new Uint8Array(bytes);
74
+ }
75
+
76
+ export function normalizeDevAuthKeyInputToBytes(input) {
77
+ const raw = String(input ?? '').trim();
78
+ if (!raw) return null;
79
+
80
+ // Match Happy UI behavior:
81
+ // - backup format is base32 and is long (usually grouped with '-' / spaces)
82
+ // - base64url is short (~43 chars) and may contain '-' / '_' legitimately
83
+ //
84
+ // Key point: avoid mis-parsing backup base32 as base64.
85
+ if (raw.length > 50) {
86
+ try {
87
+ const b32 = base32ToBytes(raw);
88
+ return b32.length === 32 ? b32 : null;
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ const b64 = base64UrlToBytes(raw);
95
+ if (b64 && b64.length === 32) return b64;
96
+ try {
97
+ const b32 = base32ToBytes(raw);
98
+ return b32.length === 32 ? b32 : null;
99
+ } catch {
100
+ return null;
101
+ }
102
+ }
103
+
104
+ export function formatDevAuthKeyBackup(secretKeyBase64Url) {
105
+ const bytes = base64UrlToBytes(secretKeyBase64Url);
106
+ if (!bytes || bytes.length !== 32) throw new Error('invalid secret key (expected base64url 32 bytes)');
107
+ const base32 = bytesToBase32(bytes);
108
+ const groups = [];
109
+ for (let i = 0; i < base32.length; i += 5) groups.push(base32.slice(i, i + 5));
110
+ return groups.join('-');
111
+ }
112
+
113
+ export async function readDevAuthKey({ env = process.env } = {}) {
114
+ if ((env.HAPPIER_STACK_DEV_AUTH_SECRET_KEY ?? '').toString().trim()) {
115
+ const bytes = normalizeDevAuthKeyInputToBytes(env.HAPPIER_STACK_DEV_AUTH_SECRET_KEY);
116
+ if (!bytes) return { ok: false, error: 'invalid_env_key', source: 'env', secretKeyBase64Url: null, backup: null };
117
+ const base64url = bytesToBase64Url(bytes);
118
+ return { ok: true, source: 'env:HAPPIER_STACK_DEV_AUTH_SECRET_KEY', secretKeyBase64Url: base64url, backup: formatDevAuthKeyBackup(base64url) };
119
+ }
120
+
121
+ const path = getDevAuthKeyPath(env);
122
+ try {
123
+ if (!existsSync(path)) return { ok: true, source: `file:${path}`, secretKeyBase64Url: null, backup: null, path };
124
+ const raw = await readFile(path, 'utf-8');
125
+ const parsed = JSON.parse(raw);
126
+ const input = parsed?.secretKeyBase64Url ?? parsed?.secretKey ?? parsed?.key ?? null;
127
+ const bytes = normalizeDevAuthKeyInputToBytes(input);
128
+ if (!bytes) return { ok: false, error: 'invalid_file_key', source: `file:${path}`, secretKeyBase64Url: null, backup: null, path };
129
+ const base64url = bytesToBase64Url(bytes);
130
+ return { ok: true, source: `file:${path}`, secretKeyBase64Url: base64url, backup: formatDevAuthKeyBackup(base64url), path };
131
+ } catch (e) {
132
+ return { ok: false, error: 'failed_to_read', source: `file:${path}`, secretKeyBase64Url: null, backup: null, path, details: e instanceof Error ? e.message : String(e) };
133
+ }
134
+ }
135
+
136
+ export async function writeDevAuthKey({ env = process.env, input } = {}) {
137
+ const bytes = normalizeDevAuthKeyInputToBytes(input);
138
+ if (!bytes || bytes.length !== 32) {
139
+ throw new Error('invalid secret key (expected 32 bytes; accept base64url or backup format)');
140
+ }
141
+ const secretKeyBase64Url = bytesToBase64Url(bytes);
142
+ const path = getDevAuthKeyPath(env);
143
+ await mkdir(dirname(path), { recursive: true });
144
+ const payload = {
145
+ v: 1,
146
+ createdAt: new Date().toISOString(),
147
+ secretKeyBase64Url,
148
+ };
149
+ await writeFile(path, JSON.stringify(payload, null, 2) + '\n', { encoding: 'utf-8', mode: 0o600 });
150
+ await chmod(path, 0o600).catch(() => {});
151
+ return { ok: true, path, secretKeyBase64Url, backup: formatDevAuthKeyBackup(secretKeyBase64Url) };
152
+ }
153
+
154
+ export async function clearDevAuthKey({ env = process.env } = {}) {
155
+ const path = getDevAuthKeyPath(env);
156
+ try {
157
+ if (!existsSync(path)) return { ok: true, deleted: false, path };
158
+ await unlink(path);
159
+ return { ok: true, deleted: true, path };
160
+ } catch (e) {
161
+ return { ok: false, deleted: false, path, error: e instanceof Error ? e.message : String(e) };
162
+ }
163
+ }
@@ -0,0 +1,56 @@
1
+ import { chmod, copyFile, lstat, symlink, unlink, writeFile } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+
5
+ import { ensureDir } from '../fs/ops.mjs';
6
+
7
+ export async function removeFileOrSymlinkIfExists(path) {
8
+ try {
9
+ const st = await lstat(path);
10
+ if (st.isDirectory()) {
11
+ throw new Error(`[auth] refusing to remove directory path: ${path}`);
12
+ }
13
+ await unlink(path);
14
+ return true;
15
+ } catch (e) {
16
+ if (e && typeof e === 'object' && 'code' in e && e.code === 'ENOENT') return false;
17
+ throw e;
18
+ }
19
+ }
20
+
21
+ export async function writeSecretFileIfMissing({ path, secret, force = false }) {
22
+ if (!force && existsSync(path)) return false;
23
+ if (force && existsSync(path)) {
24
+ await removeFileOrSymlinkIfExists(path);
25
+ }
26
+ await ensureDir(dirname(path));
27
+ await writeFile(path, secret, { encoding: 'utf-8', mode: 0o600 });
28
+ return true;
29
+ }
30
+
31
+ export async function copyFileIfMissing({ from, to, mode, force = false }) {
32
+ if (!force && existsSync(to)) return false;
33
+ if (!existsSync(from)) return false;
34
+ await ensureDir(dirname(to));
35
+ // IMPORTANT: if `to` is a symlink and we "overwrite" it, do NOT write through it to the symlink target.
36
+ if (force && existsSync(to)) {
37
+ await removeFileOrSymlinkIfExists(to);
38
+ }
39
+ await copyFile(from, to);
40
+ if (mode) {
41
+ await chmod(to, mode).catch(() => {});
42
+ }
43
+ return true;
44
+ }
45
+
46
+ export async function linkFileIfMissing({ from, to, force = false }) {
47
+ if (!force && existsSync(to)) return false;
48
+ if (!existsSync(from)) return false;
49
+ await ensureDir(dirname(to));
50
+ if (force && existsSync(to)) {
51
+ await removeFileOrSymlinkIfExists(to);
52
+ }
53
+ await symlink(from, to);
54
+ return true;
55
+ }
56
+
@@ -0,0 +1,86 @@
1
+ import { isTty, promptSelect, withRl } from '../cli/wizard.mjs';
2
+ import { detectSeedableAuthSources } from './sources.mjs';
3
+ import { bold, cyan, dim, green } from '../ui/ansi.mjs';
4
+
5
+ /**
6
+ * Decide how a PR review stack should authenticate.
7
+ *
8
+ * This deliberately does NOT offer "legacy ~/.happy" sources:
9
+ * for production/remote Happy installs we cannot reliably seed local DB Account rows, so it leads to broken stacks.
10
+ */
11
+ export async function decidePrAuthPlan({
12
+ interactive = isTty(),
13
+ seedAuthFlag = null,
14
+ explicitFrom = '',
15
+ defaultLoginNow = true,
16
+ } = {}) {
17
+ if (seedAuthFlag === false) return { mode: 'login', loginNow: defaultLoginNow };
18
+ if (seedAuthFlag === true) {
19
+ // Caller must supply from; if not, pick best available.
20
+ const sources = detectSeedableAuthSources();
21
+ const from = explicitFrom || sources[0] || 'main';
22
+ return { mode: 'seed', from, link: true };
23
+ }
24
+ if (explicitFrom) {
25
+ return { mode: 'seed', from: explicitFrom, link: true };
26
+ }
27
+
28
+ const sources = detectSeedableAuthSources();
29
+ if (!interactive) {
30
+ // Non-interactive default: prefer seeding only if explicitly configured elsewhere.
31
+ // setup-pr will handle its own defaults.
32
+ return { mode: 'auto', sources };
33
+ }
34
+
35
+ // If there's nothing to reuse, don't ask a pointless question.
36
+ if (!sources.length) {
37
+ return { mode: 'login', loginNow: defaultLoginNow, reason: 'no_seed_sources' };
38
+ }
39
+
40
+ // Interactive prompt: keep it simple for reviewers.
41
+ const choice = await withRl(async (rl) => {
42
+ const opts = [];
43
+ if (sources.length) {
44
+ opts.push({
45
+ label: `reuse existing auth (${cyan(sources.join(' / '))})`,
46
+ value: 'seed',
47
+ });
48
+ }
49
+ opts.push({
50
+ label: defaultLoginNow ? `login now (${green('recommended')})` : 'login later',
51
+ value: 'login',
52
+ });
53
+ return await promptSelect(rl, {
54
+ title: `${bold('Authentication for this PR stack')}\n${dim('Choose whether to reuse existing credentials or do a fresh guided login.')}`,
55
+ options: opts,
56
+ defaultIndex: 0,
57
+ });
58
+ });
59
+
60
+ if (choice === 'seed' && sources.length) {
61
+ let from = sources[0];
62
+ if (sources.length > 1) {
63
+ from = await withRl(async (rl) => {
64
+ return await promptSelect(rl, {
65
+ title: `${bold('Which auth should we reuse?')}\n${dim('Pick the stack whose credentials should be shared into this PR stack.')}`,
66
+ options: sources.map((s) => ({ label: s, value: s })),
67
+ defaultIndex: 0,
68
+ });
69
+ });
70
+ }
71
+ const link = await withRl(async (rl) => {
72
+ return await promptSelect(rl, {
73
+ title: `${bold('Reuse mode')}\n${dim('Symlink stays up to date; copy is more isolated.')}`,
74
+ options: [
75
+ { label: `symlink (${green('recommended')}) — stays up to date`, value: true },
76
+ { label: 'copy — more isolated per stack', value: false },
77
+ ],
78
+ defaultIndex: 0,
79
+ });
80
+ });
81
+ return { mode: 'seed', from: String(from), link: Boolean(link) };
82
+ }
83
+
84
+ return { mode: 'login', loginNow: defaultLoginNow };
85
+ }
86
+
@@ -0,0 +1,56 @@
1
+ import { prompt, withRl } from '../cli/wizard.mjs';
2
+ import { openUrlInBrowser } from '../ui/browser.mjs';
3
+ import { preferStackLocalhostUrl } from '../paths/localhost_host.mjs';
4
+ import { bold, cyan, dim, green, yellow } from '../ui/ansi.mjs';
5
+
6
+ export async function guidedStackWebSignupThenLogin({ webappUrl, stackName }) {
7
+ const url = await preferStackLocalhostUrl(webappUrl, { stackName });
8
+
9
+ // eslint-disable-next-line no-console
10
+ console.log('');
11
+ // eslint-disable-next-line no-console
12
+ console.log(bold(`${cyan('Happier')} login`));
13
+
14
+ // Step 1/2
15
+ // eslint-disable-next-line no-console
16
+ console.log(dim('Step 1/2 — open the web app'));
17
+ // eslint-disable-next-line no-console
18
+ console.log(`We’ll open the Happier web app so you can ${bold('create an account')} (or ${bold('log in')}).`);
19
+ if (url) {
20
+ // eslint-disable-next-line no-console
21
+ console.log(`${dim('URL:')} ${cyan(url)}`);
22
+ }
23
+ // eslint-disable-next-line no-console
24
+ console.log('');
25
+ // eslint-disable-next-line no-console
26
+ console.log(`${bold('Press Enter')} to open it in your browser.`);
27
+ await withRl(async (rl) => {
28
+ await prompt(rl, '', { defaultValue: '' });
29
+ });
30
+ if (url) {
31
+ await openUrlInBrowser(url);
32
+ }
33
+ // eslint-disable-next-line no-console
34
+ console.log(`${green('✓')} Browser opened`);
35
+
36
+ // Step 2/2
37
+ // eslint-disable-next-line no-console
38
+ console.log('');
39
+ // eslint-disable-next-line no-console
40
+ console.log(dim('Step 2/2 — connect this terminal'));
41
+ // eslint-disable-next-line no-console
42
+ console.log(`Next, we’ll connect ${bold('this terminal')} to your Happier account.`);
43
+ // eslint-disable-next-line no-console
44
+ console.log(`When prompted, choose: ${bold('Web Browser')} ${dim('(press 2)')}.`);
45
+ // eslint-disable-next-line no-console
46
+ console.log('');
47
+ // eslint-disable-next-line no-console
48
+ console.log(`After you’ve created/logged in in the browser, ${bold('press Enter')} to continue.`);
49
+ // eslint-disable-next-line no-console
50
+ console.log(dim(`Tip: if the page is blank, wait for the first build to finish, then retry.`));
51
+ // eslint-disable-next-line no-console
52
+ console.log(dim(`Tip: you can always re-run later with: ${yellow(`hstack stack auth ${stackName || 'main'} login`)}`));
53
+ await withRl(async (rl) => {
54
+ await prompt(rl, '', { defaultValue: '' });
55
+ });
56
+ }
@@ -0,0 +1,42 @@
1
+ import { join } from 'node:path';
2
+
3
+ import { parseEnvToObject } from '../env/dotenv.mjs';
4
+ import { resolveStackEnvPath } from '../paths/paths.mjs';
5
+ import { getEnvValue } from '../env/values.mjs';
6
+ import { readTextIfExists } from '../fs/ops.mjs';
7
+ import { stackExistsSync } from '../stack/stacks.mjs';
8
+
9
+ export async function resolveHandyMasterSecretFromStack({
10
+ stackName,
11
+ requireStackExists = false,
12
+ } = {}) {
13
+ const name = String(stackName ?? '').trim() || 'main';
14
+
15
+ if (requireStackExists && !stackExistsSync(name)) {
16
+ throw new Error(`[auth] cannot copy auth: source stack "${name}" does not exist`);
17
+ }
18
+
19
+ const resolved = resolveStackEnvPath(name);
20
+ const sourceBaseDir = resolved.baseDir;
21
+ const sourceEnvPath = resolved.envPath;
22
+ const raw = await readTextIfExists(sourceEnvPath);
23
+ const env = raw ? parseEnvToObject(raw) : {};
24
+
25
+ const inline = getEnvValue(env, 'HANDY_MASTER_SECRET');
26
+ if (inline) {
27
+ return { secret: inline, source: `${sourceEnvPath} (HANDY_MASTER_SECRET)` };
28
+ }
29
+
30
+ const secretFile = getEnvValue(env, 'HAPPIER_STACK_HANDY_MASTER_SECRET_FILE');
31
+ if (secretFile) {
32
+ const secret = await readTextIfExists(secretFile);
33
+ if (secret) return { secret, source: secretFile };
34
+ }
35
+
36
+ const dataDir = getEnvValue(env, 'HAPPIER_SERVER_LIGHT_DATA_DIR') || join(sourceBaseDir, 'server-light');
37
+ const secretPath = join(dataDir, 'handy-master-secret.txt');
38
+ const secret = await readTextIfExists(secretPath);
39
+ if (secret) return { secret, source: secretPath };
40
+
41
+ return { secret: null, source: null };
42
+ }
@@ -0,0 +1,70 @@
1
+ import { isTty, promptSelect, withRl } from '../cli/wizard.mjs';
2
+ import { detectSeedableAuthSources } from './sources.mjs';
3
+ import { stackAuthCopyFrom } from './stack_guided_login.mjs';
4
+ import { runOrchestratedGuidedAuthFlow } from './orchestrated_stack_auth_flow.mjs';
5
+ import { bold, cyan, dim, green } from '../ui/ansi.mjs';
6
+ import { findAnyCredentialPathInCliHome } from './credentials_paths.mjs';
7
+
8
+ export function needsAuthSeed({ cliHomeDir, accountCount }) {
9
+ const hasAccessKey = Boolean(findAnyCredentialPathInCliHome({ cliHomeDir }));
10
+ const hasAccounts = typeof accountCount === 'number' ? accountCount > 0 : null;
11
+ return !hasAccessKey || hasAccounts === false;
12
+ }
13
+
14
+ export async function maybeRunInteractiveStackAuthSetup({
15
+ rootDir,
16
+ env = process.env,
17
+ stackName,
18
+ cliHomeDir,
19
+ accountCount,
20
+ isInteractive = isTty(),
21
+ autoSeedEnabled = false,
22
+ beforeLogin = null,
23
+ } = {}) {
24
+ if (!isInteractive) return { ok: true, skipped: true, reason: 'non_interactive' };
25
+ if (autoSeedEnabled) return { ok: true, skipped: true, reason: 'auto_seed_enabled' };
26
+ if (!needsAuthSeed({ cliHomeDir, accountCount })) return { ok: true, skipped: true, reason: 'already_initialized' };
27
+
28
+ const sources = detectSeedableAuthSources().filter((s) => s && s !== stackName);
29
+ const hasDevAuth = sources.includes('dev-auth');
30
+ const hasMain = sources.includes('main');
31
+
32
+ let choice = 'login';
33
+ if (hasDevAuth || hasMain) {
34
+ choice = await withRl(async (rl) => {
35
+ const opts = [];
36
+ if (hasDevAuth) {
37
+ opts.push({ label: `reuse ${cyan('dev-auth')} (${green('recommended')}) — no re-login`, value: 'dev-auth' });
38
+ }
39
+ if (hasMain) {
40
+ opts.push({ label: `reuse ${cyan('main')} — fast, but shares identity with main`, value: 'main' });
41
+ }
42
+ opts.push({ label: `login now — guided browser flow`, value: 'login' });
43
+ return await promptSelect(rl, {
44
+ title: `${bold('Authentication required')}\n${dim(
45
+ `Stack ${cyan(stackName)} needs auth before the daemon can register a machine.`
46
+ )}`,
47
+ options: opts,
48
+ defaultIndex: 0,
49
+ });
50
+ });
51
+ }
52
+
53
+ if (choice === 'login') {
54
+ if (beforeLogin && typeof beforeLogin === 'function') {
55
+ await beforeLogin();
56
+ }
57
+ await runOrchestratedGuidedAuthFlow({
58
+ rootDir,
59
+ stackName,
60
+ env,
61
+ verbosity: 0,
62
+ json: false,
63
+ });
64
+ return { ok: true, skipped: false, mode: 'login' };
65
+ }
66
+
67
+ const from = String(choice);
68
+ await stackAuthCopyFrom({ rootDir, stackName, fromStackName: from, env, link: true });
69
+ return { ok: true, skipped: false, mode: 'seed', from, link: true };
70
+ }
@@ -0,0 +1,105 @@
1
+ export function normalizeAuthLoginContext(raw) {
2
+ const v = String(raw ?? '')
3
+ .trim()
4
+ .toLowerCase();
5
+ if (v === 'selfhost' || v === 'self-host' || v === 'self_host') return 'selfhost';
6
+ if (v === 'dev' || v === 'developer' || v === 'development') return 'dev';
7
+ if (v === 'stack') return 'stack';
8
+ return 'generic';
9
+ }
10
+
11
+ import { bold, cyan, dim, green, yellow } from '../ui/ansi.mjs';
12
+ import { buildConfigureServerLinks } from '@happier-dev/cli-common/links';
13
+
14
+ export function printAuthLoginInstructions({
15
+ stackName,
16
+ context = 'generic',
17
+ webappUrl,
18
+ webappUrlSource,
19
+ internalServerUrl,
20
+ publicServerUrl,
21
+ rerunCmd,
22
+ }) {
23
+ const ctx = normalizeAuthLoginContext(context);
24
+ const subtitle =
25
+ ctx === 'selfhost'
26
+ ? 'Self-host'
27
+ : ctx === 'dev'
28
+ ? 'Dev'
29
+ : ctx === 'stack'
30
+ ? `Stack: ${stackName || 'unknown'}`
31
+ : '';
32
+
33
+ // eslint-disable-next-line no-console
34
+ console.log('');
35
+ // eslint-disable-next-line no-console
36
+ console.log(bold(`${cyan('Happier')} login`));
37
+ if (subtitle) {
38
+ // eslint-disable-next-line no-console
39
+ console.log(dim(subtitle));
40
+ }
41
+ // eslint-disable-next-line no-console
42
+ console.log('');
43
+ // eslint-disable-next-line no-console
44
+ console.log(bold('What will happen:'));
45
+ // eslint-disable-next-line no-console
46
+ console.log(`- ${cyan('browser')}: we’ll open the Happier web app`);
47
+ // eslint-disable-next-line no-console
48
+ console.log(`- ${cyan('account')}: you’ll sign in (or create an account)`);
49
+ // eslint-disable-next-line no-console
50
+ console.log(`- ${cyan('connect')}: you’ll approve this terminal/machine connection`);
51
+ // eslint-disable-next-line no-console
52
+ console.log(`- ${cyan('finish')}: the CLI will complete automatically`);
53
+
54
+ if (webappUrl) {
55
+ // eslint-disable-next-line no-console
56
+ console.log('');
57
+ // eslint-disable-next-line no-console
58
+ console.log(`${dim('Web app:')} ${cyan(webappUrl)}${webappUrlSource ? dim(` (${webappUrlSource})`) : ''}`);
59
+ }
60
+ if (internalServerUrl) {
61
+ // eslint-disable-next-line no-console
62
+ console.log(`${dim('Internal:')} ${internalServerUrl}`);
63
+ }
64
+ if (publicServerUrl) {
65
+ // eslint-disable-next-line no-console
66
+ console.log(`${dim('Public:')} ${publicServerUrl}`);
67
+ }
68
+
69
+ if (ctx === 'selfhost' && webappUrl && (publicServerUrl || internalServerUrl)) {
70
+ const serverUrl = publicServerUrl || internalServerUrl;
71
+ const links = buildConfigureServerLinks({ webappUrl, serverUrl });
72
+ // eslint-disable-next-line no-console
73
+ console.log('');
74
+ // eslint-disable-next-line no-console
75
+ console.log(bold('Step 0 — Configure your app/web (self-host only)'));
76
+ // eslint-disable-next-line no-console
77
+ console.log(dim('Open the link below, confirm, then sign in/create an account before approving the terminal connection.'));
78
+ // eslint-disable-next-line no-console
79
+ console.log('');
80
+ // eslint-disable-next-line no-console
81
+ console.log(`${dim('Web:')} ${cyan(links.webUrl)}`);
82
+ // eslint-disable-next-line no-console
83
+ console.log(`${dim('Mobile:')} ${cyan(links.mobileUrl)}`);
84
+ }
85
+
86
+ if (ctx === 'selfhost') {
87
+ // eslint-disable-next-line no-console
88
+ console.log('');
89
+ // eslint-disable-next-line no-console
90
+ console.log(dim('Why this matters: login lets the daemon register this machine and enables sync across devices.'));
91
+ }
92
+
93
+ // eslint-disable-next-line no-console
94
+ console.log('');
95
+ // eslint-disable-next-line no-console
96
+ console.log(bold('Tips:'));
97
+ // eslint-disable-next-line no-console
98
+ console.log(`- If the page does not load, make sure the stack is running and reachable.`);
99
+ // eslint-disable-next-line no-console
100
+ console.log(`- If you see a blank page, wait for the first build (Expo/Metro) to finish.`);
101
+ // eslint-disable-next-line no-console
102
+ console.log(`- Re-run anytime: ${yellow(rerunCmd || 'hstack auth login')}`);
103
+ // eslint-disable-next-line no-console
104
+ console.log(`${green('✓')} You can safely close the browser when it finishes.`);
105
+ }