@oaklandzoo/ostup 0.7.0 → 0.9.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.
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # ostup
2
2
 
3
3
  Scaffold a new client project with one command. Get a live GitHub repo
4
- and live Vercel deploy URL in under five minutes.
4
+ and live Vercel deploy URL in under ten minutes, even from a stock
5
+ computer.
5
6
 
6
7
  ## What this does
7
8
 
@@ -17,60 +18,79 @@ When you run this tool, it will:
17
18
  prior materials (research, reference repos, screenshots, brand
18
19
  assets) you want the agent to have on hand
19
20
 
20
- ## What you need before running it
21
+ ## Quick Start
21
22
 
22
- ### One-time machine setup (you only do this once)
23
+ Pick the path that matches your computer.
23
24
 
24
- 1. Install Node.js 20 or higher. Download from https://nodejs.org
25
+ ### Mac (beginner stock computer with nothing installed)
25
26
 
26
- 2. Install GitHub CLI. In Terminal:
27
- ```
28
- brew install gh
29
- ```
30
- Then log in:
31
- ```
32
- gh auth login
33
- ```
34
- Choose: GitHub.com, then HTTPS, then "Login with a web browser."
35
- Follow the prompts.
27
+ Open Terminal and paste:
36
28
 
37
- 3. Install Vercel CLI. In Terminal:
38
- ```
39
- npm install -g vercel
40
- ```
41
- Then log in:
42
- ```
43
- vercel login
44
- ```
45
- Choose your email login method.
29
+ ```
30
+ /bin/bash -c "$(curl -fsSL https://ostup-install.vercel.app/install.sh)"
31
+ ```
32
+
33
+ That script installs Homebrew and Node (if missing), then runs `ostup init`,
34
+ which installs Git, GitHub CLI, and Vercel CLI and walks you through signing
35
+ in. Total time: about 5-10 minutes the first time, mostly download waits.
46
36
 
47
- ### Accounts you need
37
+ To pre-set flags (advanced):
48
38
 
49
- - GitHub account: free at https://github.com/signup
50
- - Vercel account: free at https://vercel.com/signup (sign in with GitHub)
39
+ ```
40
+ /bin/bash -c "$(curl -fsSL https://ostup-install.vercel.app/install.sh)" -- --yes --profile=default
41
+ ```
51
42
 
52
- If you do not have these accounts, the tool will pause and walk you
53
- through creating them when you reach that step.
43
+ ### Windows (beginner stock computer with nothing installed)
44
+
45
+ Open PowerShell and paste:
46
+
47
+ ```
48
+ irm https://ostup-install.vercel.app/install.ps1 | iex
49
+ ```
54
50
 
55
- ## Install
51
+ That script installs Node via WinGet (if missing), then runs `ostup init`,
52
+ which installs Git, GitHub CLI, and Vercel CLI and walks you through signing
53
+ in. Total time: about 5-10 minutes the first time.
56
54
 
57
- The fastest path:
55
+ To pre-set flags (advanced):
58
56
 
59
57
  ```
60
- npx @oaklandzoo/ostup init
58
+ irm https://ostup-install.vercel.app/install.ps1 -OutFile install.ps1
59
+ ./install.ps1 --yes --profile=default
61
60
  ```
62
61
 
63
- That is the whole install. `npx` downloads ostup on first run, nothing
64
- to install permanently.
62
+ ### Developer (you already have Node 20+ and npm)
63
+
64
+ ```
65
+ npx @oaklandzoo/ostup@latest init
66
+ ```
67
+
68
+ ostup will detect any missing tools (`git`, `gh`, `vercel`) and offer to
69
+ install them. Pass `--skip-bootstrap` to bypass detection or `--no-install`
70
+ to see what would be installed without making changes.
65
71
 
66
72
  If you'd rather install it globally so the command is just `ostup`:
67
73
 
68
74
  ```
69
75
  npm install -g @oaklandzoo/ostup
70
- ostup --version # should print 0.1.0
71
76
  ostup init
72
77
  ```
73
78
 
79
+ ## What ostup needs (handled for you by the beginner installer)
80
+
81
+ The beginner installer scripts above handle this list automatically.
82
+ This is what they're installing under the hood:
83
+
84
+ - Node.js 20+
85
+ - Git
86
+ - GitHub CLI (`gh`)
87
+ - Vercel CLI (`vercel`)
88
+ - A free GitHub account (https://github.com/signup) — sign in during the flow
89
+ - A free Vercel account (https://vercel.com/signup) — sign in during the flow
90
+
91
+ If you do not have GitHub or Vercel accounts yet, the tool pauses and
92
+ walks you through creating them when you reach those steps.
93
+
74
94
  ### Alternate: install from source
75
95
 
76
96
  If you cloned https://github.com/DubsFan/goodshin and want to run from
@@ -168,16 +188,17 @@ you do not have to repeat them every session.
168
188
 
169
189
  | Problem | Fix |
170
190
  |---|---|
171
- | "node: command not found" | Install Node 20+ from https://nodejs.org |
172
- | "gh: command not found" | Run `brew install gh` |
173
- | "vercel: command not found" | Run `npm install -g vercel` |
174
- | "gh auth required" | Run `gh auth login` |
175
- | "vercel auth required" | Run `vercel login` |
191
+ | "command not found" for node/gh/vercel | Run the beginner installer for your OS (top of this README). Or run `ostup bootstrap` if you already have Node. |
192
+ | "gh auth required" | Re-run `ostup init` and pick "Sign in to GitHub in your browser". Or run `gh auth login`. |
193
+ | "vercel auth required" | Re-run `ostup init` and pick "Sign in to Vercel in your browser". Or run `vercel login`. |
176
194
  | "Project name invalid" | Use only lowercase letters, numbers, and hyphens |
177
195
  | "Repo already exists" | Pick a different project name |
178
196
  | Vercel deploy hangs more than 5 minutes | Cancel with Ctrl-C, run again |
179
197
  | Deploy URL returns 401 | The tool tried to auto-disable Vercel deployment protection but could not. Open Vercel dashboard, find your project, Settings, Deployment Protection, Disable. |
180
198
  | "ingest path not found" | The path you gave does not exist. Re-run and provide a valid absolute or relative path. |
199
+ | Beginner installer failed | The bash and PowerShell installers log to `~/.ostup/logs/install-*.log`. The ostup CLI logs to `~/.ostup/logs/init-*.log`. Open the most recent log for the failing command's stderr. |
200
+ | "BOOTSTRAP_FAILED: Homebrew missing" | You ran `npx ostup init` on a Mac without Homebrew. Run the Mac beginner installer at the top of this README instead. |
201
+ | "BOOTSTRAP_FAILED: WinGet missing" | Update App Installer from the Microsoft Store: `ms-windows-store://pdp/?productid=9NBLGGH4NNS1`. |
181
202
 
182
203
  ## Advanced: API tokens instead of interactive login
183
204
 
package/bin/cli.mjs CHANGED
@@ -5,12 +5,13 @@ import { dirname, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { loadDotEnv } from '../src/env-loader.mjs';
7
7
  import { setDryRun } from '../src/exec.mjs';
8
+ import { startRun, endRun, disableTracer, traceError } from '../src/tracer.mjs';
8
9
 
9
10
  loadDotEnv();
10
11
 
11
12
  const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
12
13
 
13
- const SUBCOMMANDS = new Set(['init', 'update', 'brief', 'export-pro']);
14
+ const SUBCOMMANDS = new Set(['init', 'update', 'brief', 'export-pro', 'doctor', 'bootstrap']);
14
15
 
15
16
  async function readPkg() {
16
17
  const raw = await readFile(resolve(PKG_ROOT, 'package.json'), 'utf8');
@@ -44,6 +45,9 @@ function parseArgs(argv) {
44
45
  else if (a === '--output') flags.output = argv[++i];
45
46
  else if (a.startsWith('--output=')) flags.output = a.slice('--output='.length);
46
47
  else if (a === '--white-label') flags.whiteLabel = true;
48
+ else if (a === '--no-log') flags.noLog = true;
49
+ else if (a === '--skip-bootstrap') flags.skipBootstrap = true;
50
+ else if (a === '--no-install') flags.noInstall = true;
47
51
  else if (a.startsWith('-')) {
48
52
  process.stderr.write(`unknown flag: ${a}\n`);
49
53
  process.exit(1);
@@ -59,13 +63,21 @@ function printHelp() {
59
63
  [
60
64
  'ostup: scaffold a new repo with the Ostup Agent Kit plus GitHub and Vercel.',
61
65
  '',
66
+ 'First time on this computer? Use the beginner installer for your OS:',
67
+ ' Mac: /bin/bash -c "$(curl -fsSL https://ostup-install.vercel.app/install.sh)"',
68
+ ' Windows: irm https://ostup-install.vercel.app/install.ps1 | iex',
69
+ '',
70
+ 'Already have Node 20+ and npm? Use this CLI directly.',
71
+ '',
62
72
  'Usage:',
63
73
  ' ostup <command> [flags]',
64
74
  '',
65
75
  'Commands:',
66
76
  ' init Scaffold a new project (interactive or with --yes).',
77
+ ' bootstrap Install missing tools (git, gh, vercel) on this machine.',
67
78
  ' brief Run the 10-question operator intake; write docs/brief.md + brief.json.',
68
79
  ' export-pro Bundle brief + brand + content + initial PRD into a ZIP for client handoff.',
80
+ ' doctor Self-diagnosis: tool detection + auth + permissions + disk + Chrome. Read-only.',
69
81
  ' update Refresh bundled templates from the pinned source.',
70
82
  '',
71
83
  'Flags for `ostup init`:',
@@ -79,6 +91,12 @@ function printHelp() {
79
91
  ' --white-label Strip OSTUP / Goodshin attribution from generated docs (Studio tier).',
80
92
  ' --kit-only Drop the markdown kit into a target dir, no GitHub or Vercel.',
81
93
  ' --config <path> Read .ostup-config.yml from this path (kit-only mode).',
94
+ ' --skip-bootstrap Skip the in-CLI tool detection / install step (advanced).',
95
+ ' --no-install Detect missing tools, print the install plan, exit without installing.',
96
+ '',
97
+ 'Flags for `ostup bootstrap`:',
98
+ ' --yes, -y Auto-accept the install prompt and choose browser auth where possible.',
99
+ ' --no-install Print the install plan, exit without installing.',
82
100
  '',
83
101
  'Flags for `ostup brief`:',
84
102
  ' --brief <path> Load brief.json from <path> instead of running the intake.',
@@ -93,6 +111,7 @@ function printHelp() {
93
111
  'Global flags:',
94
112
  ' --version, -v Print version and exit.',
95
113
  ' --help, -h Print this help and exit.',
114
+ ' --no-log Disable auto-logging to ~/.ostup/logs/. (default: write a log per run)',
96
115
  '',
97
116
  ].join('\n')
98
117
  );
@@ -171,6 +190,36 @@ if (subcommand === 'export-pro') {
171
190
  }
172
191
  }
173
192
 
193
+ if (subcommand === 'doctor') {
194
+ const { runDoctor, printDoctorReport } = await import('../src/doctor.mjs');
195
+ try {
196
+ const result = await runDoctor();
197
+ process.stdout.write(printDoctorReport(result));
198
+ process.exit(result.failCount > 0 ? 1 : 0);
199
+ } catch (err) {
200
+ process.stderr.write(`doctor failed: ${err.message}\n`);
201
+ process.exit(2);
202
+ }
203
+ }
204
+
205
+ if (subcommand === 'bootstrap') {
206
+ const { runBootstrapStandalone } = await import('../src/bootstrap.mjs');
207
+ try {
208
+ await runBootstrapStandalone({ flags });
209
+ process.exit(0);
210
+ } catch (err) {
211
+ process.stderr.write(`${err.message}\n`);
212
+ const userErrors = new Set([
213
+ 'NO_TTY_BOOTSTRAP',
214
+ 'BOOTSTRAP_DECLINED',
215
+ 'BOOTSTRAP_FAILED',
216
+ 'BOOTSTRAP_VERIFY_FAILED',
217
+ 'UNSUPPORTED_OS_INSTALL',
218
+ ]);
219
+ process.exit(userErrors.has(err.code) ? 1 : 2);
220
+ }
221
+ }
222
+
174
223
  // subcommand === 'init'
175
224
  if (flags.kitOnly) {
176
225
  const { scaffold } = await import('../src/scaffold.mjs');
@@ -185,12 +234,20 @@ if (flags.kitOnly) {
185
234
  }
186
235
  }
187
236
 
237
+ if (flags.noLog) disableTracer();
238
+ const logPath = await startRun({ label: 'init' });
239
+ if (logPath) process.stdout.write(`Logging this run to ${logPath}\n`);
240
+
188
241
  const { runMvp } = await import('../src/mvp-flow.mjs');
189
242
  try {
190
243
  await runMvp({ flags });
244
+ await endRun({ ok: true });
191
245
  process.exit(0);
192
246
  } catch (err) {
247
+ await traceError('init', err);
248
+ await endRun({ ok: false });
193
249
  process.stderr.write(`${err.message}\n`);
250
+ if (logPath) process.stderr.write(`Full run log: ${logPath}\n`);
194
251
  const userErrors = new Set([
195
252
  'NO_TTY',
196
253
  'USER_ABORT',
@@ -200,6 +257,11 @@ try {
200
257
  'INGEST_PATH_NOT_FOUND',
201
258
  'BRIEF_NOT_FOUND',
202
259
  'BRIEF_INVALID',
260
+ 'NO_TTY_BOOTSTRAP',
261
+ 'BOOTSTRAP_DECLINED',
262
+ 'BOOTSTRAP_FAILED',
263
+ 'BOOTSTRAP_VERIFY_FAILED',
264
+ 'UNSUPPORTED_OS_INSTALL',
203
265
  ]);
204
266
  process.exit(userErrors.has(err.code) ? 1 : 2);
205
267
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oaklandzoo/ostup",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Scaffolds a new repo with the Ostup Agent Kit pre-installed: slash commands, doc templates, and a clean working state.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,6 +44,7 @@
44
44
  "files": [
45
45
  "bin/",
46
46
  "src/",
47
+ "scripts/",
47
48
  "templates/",
48
49
  "LICENSE",
49
50
  "README.md"
@@ -0,0 +1,115 @@
1
+ # ostup beginner installer for Windows.
2
+ #
3
+ # Stage 1: get this PC to "can run Node 20+". That means installing Node via WinGet
4
+ # (if missing or below v20). Nothing else. Git, GitHub CLI, Vercel CLI, auth, scaffold,
5
+ # and deploy all happen in stage 2 — the ostup CLI's in-CLI bootstrap.
6
+ #
7
+ # Usage:
8
+ # irm https://ostup-install.vercel.app/install.ps1 | iex
9
+ #
10
+ # Forward flags to `ostup init` (download then run):
11
+ # irm https://ostup-install.vercel.app/install.ps1 -OutFile install.ps1
12
+ # ./install.ps1 --yes --profile=default
13
+
14
+ #Requires -Version 5.1
15
+ $ErrorActionPreference = 'Stop'
16
+
17
+ # --- logging -----------------------------------------------------------------
18
+ $LogDir = Join-Path $env:USERPROFILE '.ostup\logs'
19
+ New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
20
+ $ts = (Get-Date).ToUniversalTime().ToString('yyyyMMddTHHmmssZ')
21
+ $LogFile = Join-Path $LogDir "install-$ts.log"
22
+ try { Start-Transcript -Path $LogFile -ErrorAction Stop | Out-Null } catch {}
23
+
24
+ # --- detect ------------------------------------------------------------------
25
+ $hasWinget = $null -ne (Get-Command winget -ErrorAction SilentlyContinue)
26
+ $hasNodeOk = $false
27
+ $nodeVerDetected = ''
28
+ if (Get-Command node -ErrorAction SilentlyContinue) {
29
+ try {
30
+ $nodeVerDetected = (& node --version).Trim()
31
+ if ($nodeVerDetected -match '^v(\d+)') {
32
+ $nodeMajor = [int]$Matches[1]
33
+ if ($nodeMajor -ge 20) { $hasNodeOk = $true }
34
+ }
35
+ } catch {}
36
+ }
37
+
38
+ # --- WinGet gate -------------------------------------------------------------
39
+ if (-not $hasWinget) {
40
+ Write-Host ''
41
+ Write-Host 'WinGet (Microsoft App Installer) is required to install Node automatically.'
42
+ Write-Host 'On Windows 11, WinGet is built in. On Windows 10, you may need to update App Installer:'
43
+ Write-Host ' ms-windows-store://pdp/?productid=9NBLGGH4NNS1'
44
+ Write-Host 'Open that URL in your browser, install or update App Installer, then re-run.'
45
+ try { Stop-Transcript | Out-Null } catch {}
46
+ exit 1
47
+ }
48
+
49
+ $needNode = -not $hasNodeOk
50
+
51
+ # --- explainer + consent (only when something is missing) -------------------
52
+ if ($needNode) {
53
+ Write-Host ''
54
+ Write-Host "You're about to set up your PC to run modern web tools."
55
+ Write-Host ''
56
+ if ($nodeVerDetected) {
57
+ Write-Host " 1. Node.js - The engine that runs the website setup tool (ostup)."
58
+ Write-Host " Your current Node $nodeVerDetected is too old; we need v20+."
59
+ } else {
60
+ Write-Host " 1. Node.js - The engine that runs the website setup tool (ostup)."
61
+ }
62
+ Write-Host ''
63
+ Write-Host 'Node will install via WinGet (Microsoft package manager). Windows may show a prompt'
64
+ Write-Host 'asking you to allow the install.'
65
+ Write-Host ''
66
+ Write-Host 'After this, ostup will continue and install Git, GitHub CLI, and Vercel CLI,'
67
+ Write-Host 'then walk you through signing in.'
68
+ Write-Host ''
69
+ Write-Host 'Total time: about 3-5 minutes for this step. Requires an internet connection.'
70
+ Write-Host ''
71
+
72
+ if ([Environment]::UserInteractive) {
73
+ $yn = Read-Host 'Continue? [Y/n]'
74
+ if ($yn -and $yn -notmatch '^(y|yes)$') {
75
+ Write-Host 'Cancelled. Re-run when ready.'
76
+ try { Stop-Transcript | Out-Null } catch {}
77
+ exit 1
78
+ }
79
+ } else {
80
+ Write-Host '(non-interactive session; proceeding)'
81
+ }
82
+
83
+ Write-Host ''
84
+ Write-Host 'Installing Node via WinGet...'
85
+ & winget install -e --id OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements
86
+ if ($LASTEXITCODE -ne 0) {
87
+ Write-Host ''
88
+ Write-Host "ERROR: WinGet returned exit code $LASTEXITCODE."
89
+ Write-Host "Log: $LogFile"
90
+ try { Stop-Transcript | Out-Null } catch {}
91
+ exit 1
92
+ }
93
+
94
+ # Patch PATH for this PowerShell session so npx works without restarting.
95
+ $env:Path = "C:\Program Files\nodejs;$env:Path"
96
+
97
+ if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
98
+ Write-Host 'ERROR: Node install completed but node is not on PATH in this session.'
99
+ Write-Host 'Open a new PowerShell window and re-run, or check the log:'
100
+ Write-Host " $LogFile"
101
+ try { Stop-Transcript | Out-Null } catch {}
102
+ exit 1
103
+ }
104
+ $postVer = (& node --version).Trim()
105
+ Write-Host "Node $postVer installed."
106
+ }
107
+
108
+ # --- hand off to the ostup CLI ----------------------------------------------
109
+ Write-Host ''
110
+ Write-Host 'Stage 1 complete. Continuing with ostup...'
111
+ Write-Host ''
112
+
113
+ try { Stop-Transcript | Out-Null } catch {}
114
+ & npx '@oaklandzoo/ostup@latest' init @args
115
+ exit $LASTEXITCODE
@@ -0,0 +1,144 @@
1
+ #!/bin/bash
2
+ # ostup beginner installer for macOS.
3
+ #
4
+ # Stage 1: get this Mac to "can run Node 20+". That means installing Homebrew (if missing)
5
+ # and Node (if missing or below v20). Nothing else. Git, GitHub CLI, Vercel CLI, auth,
6
+ # scaffold, and deploy all happen in stage 2 — the ostup CLI's in-CLI bootstrap.
7
+ #
8
+ # Usage:
9
+ # /bin/bash -c "$(curl -fsSL https://ostup-install.vercel.app/install.sh)"
10
+ #
11
+ # Forward flags to `ostup init`:
12
+ # /bin/bash -c "$(curl -fsSL ...install.sh)" -- --yes --profile=default
13
+
14
+ set -euo pipefail
15
+
16
+ # --- logging -----------------------------------------------------------------
17
+ LOG_DIR="${HOME}/.ostup/logs"
18
+ mkdir -p "$LOG_DIR"
19
+ LOG_FILE="${LOG_DIR}/install-$(date -u +%Y%m%dT%H%M%SZ).log"
20
+ exec > >(tee -a "$LOG_FILE") 2>&1
21
+
22
+ # --- platform check ----------------------------------------------------------
23
+ UNAME_S="$(uname -s 2>/dev/null || echo unknown)"
24
+ if [ "$UNAME_S" != "Darwin" ]; then
25
+ cat <<EOF
26
+ This installer is for macOS only.
27
+
28
+ On Windows, open PowerShell and run:
29
+ irm https://ostup-install.vercel.app/install.ps1 | iex
30
+
31
+ On Linux, install Node 20+ from https://nodejs.org or your package manager, then run:
32
+ npx @oaklandzoo/ostup@latest init
33
+ EOF
34
+ exit 1
35
+ fi
36
+
37
+ # --- detect ------------------------------------------------------------------
38
+ HAS_BREW=0
39
+ HAS_NODE_OK=0
40
+ NODE_VER_DETECTED=""
41
+
42
+ if command -v brew >/dev/null 2>&1; then
43
+ HAS_BREW=1
44
+ fi
45
+
46
+ if command -v node >/dev/null 2>&1; then
47
+ NODE_VER_DETECTED="$(node --version 2>/dev/null || echo '')"
48
+ NODE_MAJOR="$(echo "$NODE_VER_DETECTED" | sed -E 's/^v([0-9]+).*/\1/')"
49
+ if [ -n "$NODE_MAJOR" ] && [ "$NODE_MAJOR" -ge 20 ] 2>/dev/null; then
50
+ HAS_NODE_OK=1
51
+ fi
52
+ fi
53
+
54
+ # --- explainer + consent (only when something is missing) --------------------
55
+ NEED_BREW=$(( 1 - HAS_BREW ))
56
+ NEED_NODE=$(( 1 - HAS_NODE_OK ))
57
+
58
+ if [ "$NEED_BREW" -eq 1 ] || [ "$NEED_NODE" -eq 1 ]; then
59
+ echo ""
60
+ echo "You're about to set up your Mac to run modern web tools."
61
+ echo ""
62
+ i=1
63
+ if [ "$NEED_BREW" -eq 1 ]; then
64
+ echo " $i. Homebrew - Apple's package manager. Used to install Node."
65
+ i=$((i + 1))
66
+ fi
67
+ if [ "$NEED_NODE" -eq 1 ]; then
68
+ if [ -n "$NODE_VER_DETECTED" ]; then
69
+ echo " $i. Node.js - The engine that runs the website setup tool (ostup)."
70
+ echo " Your current Node $NODE_VER_DETECTED is too old; we need v20+."
71
+ else
72
+ echo " $i. Node.js - The engine that runs the website setup tool (ostup)."
73
+ fi
74
+ fi
75
+ echo ""
76
+ if [ "$NEED_BREW" -eq 1 ]; then
77
+ echo "You'll be asked for your Mac login password once (for Homebrew). It goes"
78
+ echo "straight to your Mac, not to ostup - we never see or store it."
79
+ echo ""
80
+ fi
81
+ echo "After this, ostup will continue and install GitHub CLI and Vercel CLI,"
82
+ echo "then walk you through signing in."
83
+ echo ""
84
+ echo "Total time: about 3-5 minutes for this step, depending on your internet."
85
+ echo "Requires an internet connection."
86
+ echo ""
87
+ if [ -t 0 ]; then
88
+ read -r -p "Continue? [Y/n] " yn
89
+ case "$yn" in
90
+ ""|y|Y|yes|YES) ;;
91
+ *)
92
+ echo "Cancelled. Re-run the command when ready."
93
+ exit 1
94
+ ;;
95
+ esac
96
+ else
97
+ echo "(no terminal detected; proceeding non-interactively)"
98
+ fi
99
+ fi
100
+
101
+ # --- install Homebrew if missing --------------------------------------------
102
+ if [ "$HAS_BREW" -eq 0 ]; then
103
+ echo ""
104
+ echo "[1/2] Installing Homebrew..."
105
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
106
+ # Add brew to current shell's PATH.
107
+ if [ -x /opt/homebrew/bin/brew ]; then
108
+ eval "$(/opt/homebrew/bin/brew shellenv)"
109
+ elif [ -x /usr/local/bin/brew ]; then
110
+ eval "$(/usr/local/bin/brew shellenv)"
111
+ else
112
+ echo "ERROR: Homebrew installation appears to have failed (brew not found at /opt/homebrew/bin or /usr/local/bin)."
113
+ echo "Log: $LOG_FILE"
114
+ exit 1
115
+ fi
116
+ echo "Homebrew installed."
117
+ fi
118
+
119
+ # --- install Node if missing or too old --------------------------------------
120
+ if [ "$HAS_NODE_OK" -eq 0 ]; then
121
+ echo ""
122
+ echo "[2/2] Installing Node via Homebrew..."
123
+ brew install node
124
+ # Confirm node is now on PATH and at ≥20.
125
+ if ! command -v node >/dev/null 2>&1; then
126
+ echo "ERROR: Node install completed but 'node' is not on PATH. Open a new Terminal window and re-run."
127
+ echo "Log: $LOG_FILE"
128
+ exit 1
129
+ fi
130
+ POST_VER="$(node --version)"
131
+ POST_MAJOR="$(echo "$POST_VER" | sed -E 's/^v([0-9]+).*/\1/')"
132
+ if [ "$POST_MAJOR" -lt 20 ] 2>/dev/null; then
133
+ echo "ERROR: Node $POST_VER installed but version is still below 20. Run: brew upgrade node"
134
+ echo "Log: $LOG_FILE"
135
+ exit 1
136
+ fi
137
+ echo "Node $POST_VER installed."
138
+ fi
139
+
140
+ # --- hand off to the ostup CLI ----------------------------------------------
141
+ echo ""
142
+ echo "Stage 1 complete. Continuing with ostup..."
143
+ echo ""
144
+ exec npx @oaklandzoo/ostup@latest init "$@"