happy-stacks 0.2.0 → 0.4.0

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 (149) hide show
  1. package/README.md +84 -25
  2. package/bin/happys.mjs +116 -17
  3. package/docs/happy-development.md +2 -2
  4. package/docs/isolated-linux-vm.md +82 -0
  5. package/docs/mobile-ios.md +112 -54
  6. package/package.json +5 -1
  7. package/scripts/auth.mjs +59 -208
  8. package/scripts/build.mjs +58 -12
  9. package/scripts/cli-link.mjs +3 -3
  10. package/scripts/completion.mjs +5 -5
  11. package/scripts/daemon.mjs +168 -20
  12. package/scripts/dev.mjs +196 -70
  13. package/scripts/doctor.mjs +20 -36
  14. package/scripts/edison.mjs +105 -78
  15. package/scripts/happy.mjs +8 -19
  16. package/scripts/init.mjs +8 -14
  17. package/scripts/install.mjs +119 -23
  18. package/scripts/lint.mjs +31 -32
  19. package/scripts/menubar.mjs +6 -13
  20. package/scripts/migrate.mjs +11 -21
  21. package/scripts/mobile.mjs +93 -108
  22. package/scripts/mobile_dev_client.mjs +83 -0
  23. package/scripts/provision/linux-ubuntu-review-pr.sh +51 -0
  24. package/scripts/review.mjs +217 -0
  25. package/scripts/review_pr.mjs +368 -0
  26. package/scripts/run.mjs +95 -21
  27. package/scripts/self.mjs +11 -29
  28. package/scripts/server_flavor.mjs +4 -4
  29. package/scripts/service.mjs +19 -29
  30. package/scripts/setup.mjs +63 -160
  31. package/scripts/setup_pr.mjs +592 -52
  32. package/scripts/stack.mjs +608 -200
  33. package/scripts/stop.mjs +3 -3
  34. package/scripts/tailscale.mjs +44 -11
  35. package/scripts/test.mjs +52 -36
  36. package/scripts/tui.mjs +314 -74
  37. package/scripts/typecheck.mjs +31 -32
  38. package/scripts/ui_gateway.mjs +1 -1
  39. package/scripts/uninstall.mjs +6 -6
  40. package/scripts/utils/auth/daemon_gate.mjs +55 -0
  41. package/scripts/utils/auth/daemon_gate.test.mjs +37 -0
  42. package/scripts/utils/auth/dev_key.mjs +163 -0
  43. package/scripts/utils/{auth_files.mjs → auth/files.mjs} +2 -4
  44. package/scripts/utils/auth/guided_pr_auth.mjs +79 -0
  45. package/scripts/utils/auth/guided_stack_web_login.mjs +75 -0
  46. package/scripts/utils/auth/handy_master_secret.mjs +68 -0
  47. package/scripts/utils/auth/interactive_stack_auth.mjs +72 -0
  48. package/scripts/utils/{auth_login_ux.mjs → auth/login_ux.mjs} +32 -13
  49. package/scripts/utils/auth/sources.mjs +38 -0
  50. package/scripts/utils/auth/stack_guided_login.mjs +353 -0
  51. package/scripts/utils/cli/cli_registry.mjs +24 -0
  52. package/scripts/utils/cli/cwd_scope.mjs +82 -0
  53. package/scripts/utils/cli/cwd_scope.test.mjs +77 -0
  54. package/scripts/utils/cli/flags.mjs +17 -0
  55. package/scripts/utils/cli/log_forwarder.mjs +157 -0
  56. package/scripts/utils/cli/normalize.mjs +16 -0
  57. package/scripts/utils/cli/prereqs.mjs +72 -0
  58. package/scripts/utils/cli/progress.mjs +126 -0
  59. package/scripts/utils/cli/smoke_help.mjs +2 -2
  60. package/scripts/utils/cli/verbosity.mjs +12 -0
  61. package/scripts/utils/cli/wizard.mjs +1 -1
  62. package/scripts/utils/crypto/tokens.mjs +14 -0
  63. package/scripts/utils/{dev_daemon.mjs → dev/daemon.mjs} +51 -7
  64. package/scripts/utils/dev/expo_dev.mjs +246 -0
  65. package/scripts/utils/dev/expo_dev.test.mjs +76 -0
  66. package/scripts/utils/{dev_server.mjs → dev/server.mjs} +22 -32
  67. package/scripts/utils/dev_auth_key.mjs +1 -1
  68. package/scripts/utils/{config.mjs → env/config.mjs} +3 -2
  69. package/scripts/utils/{dotenv.mjs → env/dotenv.mjs} +3 -0
  70. package/scripts/utils/{env.mjs → env/env.mjs} +5 -3
  71. package/scripts/utils/{env_file.mjs → env/env_file.mjs} +2 -1
  72. package/scripts/utils/{env_local.mjs → env/env_local.mjs} +1 -0
  73. package/scripts/utils/env/read.mjs +30 -0
  74. package/scripts/utils/env/values.mjs +13 -0
  75. package/scripts/utils/expo/command.mjs +52 -0
  76. package/scripts/utils/{expo.mjs → expo/expo.mjs} +23 -10
  77. package/scripts/utils/expo/metro_ports.mjs +114 -0
  78. package/scripts/utils/fs/json.mjs +25 -0
  79. package/scripts/utils/fs/ops.mjs +29 -0
  80. package/scripts/utils/fs/package_json.mjs +8 -0
  81. package/scripts/utils/fs/tail.mjs +12 -0
  82. package/scripts/utils/git/git.mjs +67 -0
  83. package/scripts/utils/git/refs.mjs +26 -0
  84. package/scripts/utils/{worktrees.mjs → git/worktrees.mjs} +27 -23
  85. package/scripts/utils/handy_master_secret.mjs +2 -2
  86. package/scripts/utils/mobile/config.mjs +31 -0
  87. package/scripts/utils/mobile/dev_client_links.mjs +60 -0
  88. package/scripts/utils/mobile/identifiers.mjs +47 -0
  89. package/scripts/utils/mobile/identifiers.test.mjs +42 -0
  90. package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
  91. package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +98 -0
  92. package/scripts/utils/net/dns.mjs +10 -0
  93. package/scripts/utils/net/lan_ip.mjs +24 -0
  94. package/scripts/utils/{ports.mjs → net/ports.mjs} +12 -6
  95. package/scripts/utils/net/url.mjs +30 -0
  96. package/scripts/utils/net/url.test.mjs +20 -0
  97. package/scripts/utils/paths/localhost_host.mjs +56 -0
  98. package/scripts/utils/{paths.mjs → paths/paths.mjs} +52 -45
  99. package/scripts/utils/{runtime.mjs → paths/runtime.mjs} +3 -1
  100. package/scripts/utils/proc/commands.mjs +34 -0
  101. package/scripts/utils/{ownership.mjs → proc/ownership.mjs} +1 -1
  102. package/scripts/utils/proc/package_scripts.mjs +31 -0
  103. package/scripts/utils/proc/parallel.mjs +25 -0
  104. package/scripts/utils/proc/pids.mjs +11 -0
  105. package/scripts/utils/{pm.mjs → proc/pm.mjs} +128 -158
  106. package/scripts/utils/{proc.mjs → proc/proc.mjs} +77 -2
  107. package/scripts/utils/review/base_ref.mjs +74 -0
  108. package/scripts/utils/review/base_ref.test.mjs +54 -0
  109. package/scripts/utils/review/runners/coderabbit.mjs +19 -0
  110. package/scripts/utils/review/runners/codex.mjs +51 -0
  111. package/scripts/utils/review/targets.mjs +24 -0
  112. package/scripts/utils/review/targets.test.mjs +36 -0
  113. package/scripts/utils/sandbox/review_pr_sandbox.mjs +106 -0
  114. package/scripts/utils/{happy_server_infra.mjs → server/infra/happy_server_infra.mjs} +10 -49
  115. package/scripts/utils/server/mobile_api_url.mjs +61 -0
  116. package/scripts/utils/server/mobile_api_url.test.mjs +41 -0
  117. package/scripts/utils/server/port.mjs +68 -0
  118. package/scripts/utils/{server.mjs → server/server.mjs} +12 -0
  119. package/scripts/utils/server/urls.mjs +101 -0
  120. package/scripts/utils/server/validate.mjs +88 -0
  121. package/scripts/utils/service/autostart_darwin.mjs +182 -0
  122. package/scripts/utils/service/autostart_darwin.test.mjs +50 -0
  123. package/scripts/utils/stack/context.mjs +23 -0
  124. package/scripts/utils/stack/dirs.mjs +27 -0
  125. package/scripts/utils/stack/editor_workspace.mjs +152 -0
  126. package/scripts/utils/stack/names.mjs +12 -0
  127. package/scripts/utils/stack/pr_stack_name.mjs +16 -0
  128. package/scripts/utils/stack/runtime_state.mjs +88 -0
  129. package/scripts/utils/stack/stacks.mjs +45 -0
  130. package/scripts/utils/{stack_startup.mjs → stack/startup.mjs} +9 -2
  131. package/scripts/utils/{stack_stop.mjs → stack/stop.mjs} +24 -19
  132. package/scripts/utils/stack_context.mjs +3 -3
  133. package/scripts/utils/stack_runtime_state.mjs +1 -1
  134. package/scripts/utils/stacks.mjs +2 -2
  135. package/scripts/utils/{browser.mjs → ui/browser.mjs} +1 -1
  136. package/scripts/utils/ui/qr.mjs +17 -0
  137. package/scripts/utils/ui/text.mjs +16 -0
  138. package/scripts/utils/validate.mjs +1 -1
  139. package/scripts/where.mjs +6 -6
  140. package/scripts/worktrees.mjs +171 -113
  141. package/scripts/utils/auth_sources.mjs +0 -12
  142. package/scripts/utils/dev_expo_web.mjs +0 -112
  143. package/scripts/utils/localhost_host.mjs +0 -17
  144. package/scripts/utils/server_port.mjs +0 -9
  145. package/scripts/utils/server_urls.mjs +0 -54
  146. /package/scripts/utils/{sandbox.mjs → env/sandbox.mjs} +0 -0
  147. /package/scripts/utils/{fs.mjs → fs/fs.mjs} +0 -0
  148. /package/scripts/utils/{canonical_home.mjs → paths/canonical_home.mjs} +0 -0
  149. /package/scripts/utils/{watch.mjs → proc/watch.mjs} +0 -0
package/scripts/setup.mjs CHANGED
@@ -1,111 +1,44 @@
1
- import './utils/env.mjs';
1
+ import './utils/env/env.mjs';
2
2
  import { spawn } from 'node:child_process';
3
3
  import { existsSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
5
  import { parseArgs } from './utils/cli/args.mjs';
6
6
  import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
7
- import { getRootDir, resolveStackEnvPath } from './utils/paths.mjs';
7
+ import { getRootDir, resolveStackEnvPath } from './utils/paths/paths.mjs';
8
8
  import { isTty, promptSelect, withRl } from './utils/cli/wizard.mjs';
9
- import { getCanonicalHomeDir } from './utils/config.mjs';
10
- import { ensureEnvLocalUpdated } from './utils/env_local.mjs';
11
- import { run, runCapture } from './utils/proc.mjs';
12
- import { fetchHappyHealth } from './utils/server.mjs';
9
+ import { getCanonicalHomeDir } from './utils/env/config.mjs';
10
+ import { ensureEnvLocalUpdated } from './utils/env/env_local.mjs';
11
+ import { run, runCapture } from './utils/proc/proc.mjs';
12
+ import { waitForHappyHealthOk } from './utils/server/server.mjs';
13
13
  import { tailscaleServeEnable, tailscaleServeHttpsUrlForInternalServerUrl } from './tailscale.mjs';
14
- import { getRuntimeDir } from './utils/runtime.mjs';
14
+ import { getRuntimeDir } from './utils/paths/runtime.mjs';
15
15
  import { readFile } from 'node:fs/promises';
16
16
  import { homedir } from 'node:os';
17
- import { parseDotenv } from './utils/dotenv.mjs';
18
17
  import { installService } from './service.mjs';
19
- import { getDevAuthKeyPath } from './utils/dev_auth_key.mjs';
20
- import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/sandbox.mjs';
21
-
22
- function boolFromFlagsOrKv({ flags, kv, onFlag, offFlag, key, defaultValue }) {
23
- if (flags.has(offFlag)) return false;
24
- if (flags.has(onFlag)) return true;
25
- if (key && kv.has(key)) {
26
- const raw = String(kv.get(key) ?? '').trim().toLowerCase();
27
- if (raw === '1' || raw === 'true' || raw === 'yes' || raw === 'y') return true;
28
- if (raw === '0' || raw === 'false' || raw === 'no' || raw === 'n') return false;
29
- }
30
- return defaultValue;
31
- }
32
-
33
- function normalizeProfile(raw) {
34
- const v = (raw ?? '').trim().toLowerCase();
35
- if (!v) return '';
36
- if (v === 'selfhost' || v === 'self-host' || v === 'self_host' || v === 'host') return 'selfhost';
37
- if (v === 'dev' || v === 'developer' || v === 'develop') return 'dev';
38
- return '';
39
- }
40
-
41
- function normalizeServer(raw) {
42
- const v = (raw ?? '').trim().toLowerCase();
43
- if (!v) return '';
44
- if (v === 'light' || v === 'server-light' || v === 'happy-server-light') return 'happy-server-light';
45
- if (v === 'server' || v === 'full' || v === 'happy-server') return 'happy-server';
46
- return '';
47
- }
48
-
49
- function boolFromFlags({ flags, onFlag, offFlag, defaultValue }) {
50
- if (flags.has(offFlag)) return false;
51
- if (flags.has(onFlag)) return true;
52
- return defaultValue;
53
- }
54
-
55
- async function commandExists(cmd) {
56
- try {
57
- const out = (await runCapture('sh', ['-lc', `command -v ${cmd} >/dev/null 2>&1 && echo yes || echo no`])).trim();
58
- return out === 'yes';
59
- } catch {
60
- return false;
61
- }
62
- }
63
-
64
- async function openUrl(url) {
65
- const u = String(url ?? '').trim();
66
- if (!u) return false;
67
- if (process.platform === 'darwin') {
68
- await run('open', [u]).catch(() => {});
69
- return true;
70
- }
71
- if (process.platform === 'linux') {
72
- if (await commandExists('xdg-open')) {
73
- await run('xdg-open', [u]).catch(() => {});
74
- return true;
75
- }
76
- return false;
77
- }
78
- return false;
79
- }
80
-
81
- async function waitForHealthOk(internalServerUrl, { timeoutMs = 60_000 } = {}) {
82
- const deadline = Date.now() + timeoutMs;
83
- while (Date.now() < deadline) {
84
- // eslint-disable-next-line no-await-in-loop
85
- const health = await fetchHappyHealth(internalServerUrl);
86
- if (health.ok) {
87
- return true;
88
- }
89
- // eslint-disable-next-line no-await-in-loop
90
- await new Promise((r) => setTimeout(r, 300));
91
- }
92
- return false;
93
- }
94
-
95
- function parseEnvFileText(text) {
18
+ import { getDevAuthKeyPath } from './utils/auth/dev_key.mjs';
19
+ import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
20
+ import { boolFromFlags, boolFromFlagsOrKv } from './utils/cli/flags.mjs';
21
+ import { normalizeProfile, normalizeServerComponent } from './utils/cli/normalize.mjs';
22
+ import { openUrlInBrowser } from './utils/ui/browser.mjs';
23
+ import { commandExists } from './utils/proc/commands.mjs';
24
+ import { readEnvValueFromFile } from './utils/env/read.mjs';
25
+ import { readServerPortFromEnvFile, resolveServerPortFromEnv } from './utils/server/port.mjs';
26
+ import { guidedStackWebSignupThenLogin } from './utils/auth/guided_stack_web_login.mjs';
27
+
28
+ async function resolveMainWebappUrlForAuth({ rootDir, port }) {
96
29
  try {
97
- return parseDotenv(text ?? '');
98
- } catch {
99
- return new Map();
100
- }
101
- }
102
-
103
- async function readEnvValueFromFile(envPath, key) {
104
- try {
105
- if (!envPath || !existsSync(envPath)) return '';
106
- const raw = await readFile(envPath, 'utf-8');
107
- const parsed = parseEnvFileText(raw);
108
- return (parsed.get(key) ?? '').trim();
30
+ const raw = await runCapture(process.execPath, [join(rootDir, 'scripts', 'auth.mjs'), 'login', '--print', '--json'], {
31
+ cwd: rootDir,
32
+ env: {
33
+ ...process.env,
34
+ HAPPY_STACKS_SERVER_PORT: String(port),
35
+ HAPPY_LOCAL_SERVER_PORT: String(port),
36
+ },
37
+ });
38
+ const parsed = JSON.parse(String(raw ?? '').trim());
39
+ const cmd = typeof parsed?.cmd === 'string' ? parsed.cmd : '';
40
+ const m = cmd.match(/HAPPY_WEBAPP_URL="([^"]+)"/);
41
+ return m?.[1] ? String(m[1]) : '';
109
42
  } catch {
110
43
  return '';
111
44
  }
@@ -116,33 +49,28 @@ async function resolveMainServerPort() {
116
49
  // - explicit env var
117
50
  // - main stack env file (preferred)
118
51
  // - default
119
- const fromEnv =
120
- (process.env.HAPPY_LOCAL_SERVER_PORT ?? process.env.HAPPY_STACKS_SERVER_PORT ?? '').toString().trim();
121
- if (fromEnv) {
122
- const n = Number(fromEnv);
123
- return Number.isFinite(n) && n > 0 ? n : 3005;
52
+ const hasEnvOverride =
53
+ (process.env.HAPPY_STACKS_SERVER_PORT ?? process.env.HAPPY_LOCAL_SERVER_PORT ?? '').toString().trim() !== '';
54
+ if (hasEnvOverride) {
55
+ return resolveServerPortFromEnv({ env: process.env, defaultPort: 3005 });
124
56
  }
125
57
  const envPath = resolveStackEnvPath('main').envPath;
126
- const v =
127
- (await readEnvValueFromFile(envPath, 'HAPPY_LOCAL_SERVER_PORT')) ||
128
- (await readEnvValueFromFile(envPath, 'HAPPY_STACKS_SERVER_PORT')) ||
129
- '';
130
- if (v) {
131
- const n = Number(v);
132
- return Number.isFinite(n) && n > 0 ? n : 3005;
133
- }
134
- return 3005;
58
+ return await readServerPortFromEnvFile(envPath, { defaultPort: 3005 });
135
59
  }
136
60
 
137
61
  async function ensureSetupConfigPersisted({ rootDir, profile, serverComponent, tailscaleWanted, menubarMode }) {
62
+ const repoSourceForProfile =
63
+ profile === 'selfhost' ? (serverComponent === 'happy-server-light' ? 'forks' : 'upstream') : null;
138
64
  const updates = [
139
65
  { key: 'HAPPY_STACKS_SERVER_COMPONENT', value: serverComponent },
140
66
  { key: 'HAPPY_LOCAL_SERVER_COMPONENT', value: serverComponent },
141
- // Default for selfhost: upstream.
142
- ...(profile === 'selfhost'
67
+ // Default for selfhost:
68
+ // - full server: upstream (slopus/*)
69
+ // - server-light: forks (sqlite server-light is not available upstream today)
70
+ ...(repoSourceForProfile
143
71
  ? [
144
- { key: 'HAPPY_STACKS_REPO_SOURCE', value: 'upstream' },
145
- { key: 'HAPPY_LOCAL_REPO_SOURCE', value: 'upstream' },
72
+ { key: 'HAPPY_STACKS_REPO_SOURCE', value: repoSourceForProfile },
73
+ { key: 'HAPPY_LOCAL_REPO_SOURCE', value: repoSourceForProfile },
146
74
  ]
147
75
  : []),
148
76
  { key: 'HAPPY_STACKS_MENUBAR_MODE', value: menubarMode },
@@ -393,8 +321,8 @@ async function cmdSetup({ rootDir, argv }) {
393
321
  const supportsAutostart = platform === 'darwin' || platform === 'linux';
394
322
  const supportsMenubar = platform === 'darwin';
395
323
 
396
- const serverFromArg = normalizeServer(kv.get('--server'));
397
- let serverComponent = serverFromArg || normalizeServer(process.env.HAPPY_STACKS_SERVER_COMPONENT) || 'happy-server-light';
324
+ const serverFromArg = normalizeServerComponent(kv.get('--server'));
325
+ let serverComponent = serverFromArg || normalizeServerComponent(process.env.HAPPY_STACKS_SERVER_COMPONENT) || 'happy-server-light';
398
326
  if (profile === 'selfhost' && interactive && !serverFromArg) {
399
327
  serverComponent = await withRl(async (rl) => {
400
328
  const picked = await promptSelect(rl, {
@@ -606,10 +534,11 @@ async function cmdSetup({ rootDir, argv }) {
606
534
  }
607
535
  } else {
608
536
  // Selfhost setup: run non-interactively and keep it simple.
537
+ const repoFlag = serverComponent === 'happy-server-light' ? '--forks' : '--upstream';
609
538
  await runNodeScript({
610
539
  rootDir,
611
540
  rel: 'scripts/install.mjs',
612
- args: [`--server=${serverComponent}`, '--upstream'],
541
+ args: [`--server=${serverComponent}`, repoFlag],
613
542
  });
614
543
  }
615
544
 
@@ -645,12 +574,12 @@ async function cmdSetup({ rootDir, argv }) {
645
574
  // eslint-disable-next-line no-console
646
575
  console.log(res.enableUrl);
647
576
  // Best-effort open
648
- await openUrl(res.enableUrl);
577
+ await openUrlInBrowser(res.enableUrl).catch(() => {});
649
578
  }
650
579
  } catch (e) {
651
580
  // eslint-disable-next-line no-console
652
581
  console.log('[setup] tailscale not available. Install it from: https://tailscale.com/download');
653
- await openUrl('https://tailscale.com/download');
582
+ await openUrlInBrowser('https://tailscale.com/download').catch(() => {});
654
583
  }
655
584
  }
656
585
 
@@ -664,7 +593,7 @@ async function cmdSetup({ rootDir, argv }) {
664
593
  await spawnDetachedNodeScript({ rootDir, rel: 'scripts/run.mjs', args: [] });
665
594
  }
666
595
 
667
- const ready = await waitForHealthOk(internalServerUrl, { timeoutMs: 90_000 });
596
+ const ready = await waitForHappyHealthOk(internalServerUrl, { timeoutMs: 90_000 });
668
597
  if (!ready) {
669
598
  // eslint-disable-next-line no-console
670
599
  console.log(`[setup] started, but server did not become healthy yet: ${internalServerUrl}`);
@@ -688,46 +617,20 @@ async function cmdSetup({ rootDir, argv }) {
688
617
  // eslint-disable-next-line no-console
689
618
  console.log('[setup] auth: already configured (access.key exists)');
690
619
  } else {
691
- // Before starting an interactive login, offer the best available shortcut in this order:
692
- // For selfhost profile:
693
- // - prefer reusing legacy ~/.happy creds if present (maintainers often already have a local install)
694
- // - otherwise, run the normal login flow
695
- let reused = false;
696
620
  if (interactive) {
697
- const legacyAccessKey = join(homedir(), '.happy', 'cli', 'access.key');
698
- const allowLegacy = !isSandboxed() || sandboxAllowsGlobalSideEffects();
699
- const hasLegacy = allowLegacy && existsSync(legacyAccessKey);
700
-
701
- if (hasLegacy) {
702
- const options = [];
703
- if (hasLegacy) {
704
- options.push({ label: 'reuse legacy ~/.happy (symlink; stays up to date)', value: 'legacy-link' });
705
- options.push({ label: 'reuse legacy ~/.happy (copy; more isolated)', value: 'legacy-copy' });
706
- }
707
- options.push({ label: 'do login flow instead', value: 'login' });
708
-
709
- const choice = await withRl(async (rl) => {
710
- return await promptSelect(rl, {
711
- title:
712
- 'We found existing credentials on this machine. How should Happy Stacks main authenticate?',
713
- options,
714
- defaultIndex: 0,
715
- });
716
- });
717
-
718
- if (choice === 'legacy-link') {
719
- await runNodeScript({ rootDir, rel: 'scripts/auth.mjs', args: ['copy-from', 'legacy', '--allow-main', '--link'] });
720
- reused = existsSync(accessKey);
721
- } else if (choice === 'legacy-copy') {
722
- await runNodeScript({ rootDir, rel: 'scripts/auth.mjs', args: ['copy-from', 'legacy', '--allow-main'] });
723
- reused = existsSync(accessKey);
724
- }
725
- }
726
- }
727
-
728
- if (!reused) {
729
- await runNodeScript({ rootDir, rel: 'scripts/auth.mjs', args: ['login', `--context=${ctx}`] });
621
+ const webappUrl = await resolveMainWebappUrlForAuth({ rootDir, port });
622
+ await guidedStackWebSignupThenLogin({ webappUrl, stackName: 'main' });
730
623
  }
624
+ await runNodeScript({
625
+ rootDir,
626
+ rel: 'scripts/auth.mjs',
627
+ args: ['login', `--context=${ctx}`, '--quiet'],
628
+ env: {
629
+ ...process.env,
630
+ HAPPY_STACKS_SERVER_PORT: String(port),
631
+ HAPPY_LOCAL_SERVER_PORT: String(port),
632
+ },
633
+ });
731
634
 
732
635
  if (!existsSync(accessKey)) {
733
636
  // eslint-disable-next-line no-console
@@ -742,7 +645,7 @@ async function cmdSetup({ rootDir, argv }) {
742
645
  console.log('[setup] tip: when you are ready, authenticate with: happys auth login');
743
646
  }
744
647
 
745
- await openUrl(openTarget);
648
+ await openUrlInBrowser(openTarget).catch(() => {});
746
649
  // eslint-disable-next-line no-console
747
650
  console.log(`[setup] open: ${openTarget}`);
748
651
  }