@join-pantheon/cli 0.31.2 → 0.32.0-rc.2
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 +4 -1
- package/dist/claude-confine.js +12 -2
- package/dist/cli.js +56 -17
- package/dist/doctor.js +64 -6
- package/dist/helpers-build.js +69 -1
- package/dist/install.js +25 -11
- package/dist/installer.js +204 -0
- package/dist/machine.js +15 -1
- package/dist/managed.js +238 -0
- package/dist/mcp-supervisor.js +2 -1
- package/dist/measure.js +2 -1
- package/dist/notifier.js +7 -17
- package/dist/notify.js +7 -1
- package/dist/relay.js +67 -6
- package/dist/service.js +14 -6
- package/dist/tray.js +5 -10
- package/dist/uninstall.js +194 -0
- package/dist/upgrade.js +44 -5
- package/dist/which.js +11 -3
- package/dist/worker-policy.js +2 -0
- package/dist/worker.js +30 -7
- package/helper/macos/prebuilt/manifest.json +29 -0
- package/helper/macos/prebuilt/pantheon-hud +0 -0
- package/helper/macos/prebuilt/pantheon-tray +0 -0
- package/installer/install.ps1 +554 -0
- package/installer/install.sh +612 -0
- package/package.json +8 -3
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Pantheon installer for macOS and Linux.
|
|
3
|
+
#
|
|
4
|
+
# curl -fsSL https://relay.joinpantheon.network/install.sh | sh -s -- join '<invite link>'
|
|
5
|
+
# curl -fsSL https://relay.joinpantheon.network/install.sh | sh -s -- start
|
|
6
|
+
# curl -fsSL https://relay.joinpantheon.network/install.sh | sh (install only)
|
|
7
|
+
#
|
|
8
|
+
# What it does, and nothing else:
|
|
9
|
+
# 1. downloads one pinned Node release from nodejs.org, checks its sha256
|
|
10
|
+
# against the sha256 pinned in this file (nodejs.org's own), and unpacks it into
|
|
11
|
+
# ~/.pantheon/runtime/node. That Node is Pantheon's own: a Node you already
|
|
12
|
+
# have is not touched, and nothing here depends on it.
|
|
13
|
+
# 2. installs the Pantheon CLI with THAT Node's npm, into that same folder.
|
|
14
|
+
# No sudo, no system npm, nothing written outside your home folder.
|
|
15
|
+
# 3. writes ~/.pantheon/bin/pantheon, which runs that Node by absolute path.
|
|
16
|
+
# 4. puts ~/.pantheon/bin first on PATH in your shell profiles, in a block
|
|
17
|
+
# marked "# >>> pantheon >>>" that you can delete.
|
|
18
|
+
# 5. runs `pantheon <the arguments you gave>`, reading answers from your
|
|
19
|
+
# terminal (stdin is the download, so it cannot be the keyboard).
|
|
20
|
+
#
|
|
21
|
+
# Running it again is safe: whatever is already right is left as it is. Two
|
|
22
|
+
# copies at once are safe too: the second waits for the first.
|
|
23
|
+
# The relay that serves this file fills in PANTHEON_VERSION below.
|
|
24
|
+
|
|
25
|
+
set -eu
|
|
26
|
+
|
|
27
|
+
PANTHEON_VERSION="__PANTHEON_VERSION__"
|
|
28
|
+
NODE_VERSION="22.23.3"
|
|
29
|
+
# The sha256 of every Node file this script may install, copied from nodejs.org's
|
|
30
|
+
# SHASUMS256.txt for NODE_VERSION. A download is checked against THESE, never
|
|
31
|
+
# against a sums file fetched beside it: a mirror (PANTHEON_NODE_MIRROR), or
|
|
32
|
+
# anything between here and nodejs.org, could serve a file and a sums file that
|
|
33
|
+
# agree with each other. Bumping NODE_VERSION means new pins: `node
|
|
34
|
+
# scripts/node-pins.mjs` prints them, and release:check refuses stale ones.
|
|
35
|
+
node_pin() {
|
|
36
|
+
case "$1" in
|
|
37
|
+
node-v22.23.3-darwin-arm64.tar.gz) echo 23b25245dcfb9af7262f8ff142e9e2e0af025368117329e7a7458a51e5922f53 ;;
|
|
38
|
+
node-v22.23.3-darwin-x64.tar.gz) echo 8a677b0219178efd6eb0e475457c4afb452b521a92f6e67845a73bd85727f2a8 ;;
|
|
39
|
+
node-v22.23.3-linux-arm64.tar.gz) echo 5ced2d48d1d7198739b7f86804de0171aefb6823b684b12341d3321afc3cb0b2 ;;
|
|
40
|
+
node-v22.23.3-linux-x64.tar.gz) echo 1084aa36196bba4c3a5e69a1ee388a6e4ff729dad09445fbcd434b28fe3c24af ;;
|
|
41
|
+
node-v22.23.3-linux-x64-musl.tar.gz) echo 633f49a4f388c5f9564d56d9c0f5ffa394346d92ca0076fe21cf776ece1988bc ;;
|
|
42
|
+
*) echo "" ;;
|
|
43
|
+
esac
|
|
44
|
+
}
|
|
45
|
+
# Another copy of nodejs.org/dist (a company mirror), if nodejs.org is blocked.
|
|
46
|
+
NODE_DIST="${PANTHEON_NODE_MIRROR:-https://nodejs.org/dist}"
|
|
47
|
+
SITE="https://relay.joinpantheon.network"
|
|
48
|
+
|
|
49
|
+
STEP="starting"
|
|
50
|
+
TMP_DIR=""
|
|
51
|
+
SAID=0
|
|
52
|
+
LOCK=""
|
|
53
|
+
LOCKED=0
|
|
54
|
+
|
|
55
|
+
# Every failure is one plain line: what failed, and what to do about it.
|
|
56
|
+
fail() {
|
|
57
|
+
printf 'pantheon install failed: %s\n' "$*" >&2
|
|
58
|
+
SAID=1
|
|
59
|
+
exit 1
|
|
60
|
+
}
|
|
61
|
+
say() { printf '%s\n' "$*"; }
|
|
62
|
+
note() { printf 'note: %s\n' "$*" >&2; }
|
|
63
|
+
|
|
64
|
+
# Let go of the install lock, but only if it is still this installer's: a lock
|
|
65
|
+
# taken over as stale while this one was stopped belongs to someone else now.
|
|
66
|
+
unlock() {
|
|
67
|
+
if [ "$LOCKED" -eq 1 ]; then
|
|
68
|
+
LOCKED=0
|
|
69
|
+
case "$(cat "$LOCK/pid" 2>/dev/null || true)" in
|
|
70
|
+
"$$ "* | "") rm -rf "$LOCK" ;;
|
|
71
|
+
esac
|
|
72
|
+
fi
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# shellcheck disable=SC2329 # run by the EXIT trap
|
|
76
|
+
cleanup() {
|
|
77
|
+
status=$?
|
|
78
|
+
if [ -n "$TMP_DIR" ]; then
|
|
79
|
+
# Stopped between moving the old runtime aside and putting the new one in
|
|
80
|
+
# place: the old one goes back, so the pantheon command keeps working.
|
|
81
|
+
if [ -d "$TMP_DIR/old-node" ] && [ ! -e "$NODE_DIR" ]; then mv "$TMP_DIR/old-node" "$NODE_DIR" 2>/dev/null || true; fi
|
|
82
|
+
rm -rf "$TMP_DIR"
|
|
83
|
+
fi
|
|
84
|
+
unlock
|
|
85
|
+
# set -e stopped on something no check above anticipated: still one line.
|
|
86
|
+
if [ "$status" -ne 0 ] && [ "$SAID" -eq 0 ]; then
|
|
87
|
+
printf 'pantheon install failed while %s (exit %s). Run the same command again; if it fails the same way, send this line to sid@joinpantheon.network\n' "$STEP" "$status" >&2
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
trap cleanup EXIT
|
|
91
|
+
# shellcheck disable=SC2329 # run by the INT/TERM/HUP trap
|
|
92
|
+
stopped() { SAID=1; printf 'pantheon install stopped.\n' >&2; exit 130; }
|
|
93
|
+
# Ctrl-C, a closed terminal, a kill: the EXIT trap above still runs, so the
|
|
94
|
+
# temporary folder and the lock go with it, and the next run starts clean.
|
|
95
|
+
trap stopped INT TERM HUP
|
|
96
|
+
|
|
97
|
+
case "$PANTHEON_VERSION" in
|
|
98
|
+
*__*) fail "this copy of install.sh has no version filled in. Use the one the relay serves: curl -fsSL $SITE/install.sh | sh" ;;
|
|
99
|
+
esac
|
|
100
|
+
|
|
101
|
+
# ---- what this machine is --------------------------------------------------
|
|
102
|
+
|
|
103
|
+
STEP="checking this machine"
|
|
104
|
+
[ -n "${HOME:-}" ] || fail "HOME is not set, so there is nowhere to install. Run this from your own login shell."
|
|
105
|
+
if [ "$(id -u)" = "0" ] && [ -n "${SUDO_USER:-}" ]; then
|
|
106
|
+
fail "do not run this with sudo: it needs no admin rights and installs into your home folder. Run it again without sudo."
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
for tool in uname tar gzip mktemp awk sed; do
|
|
110
|
+
command -v "$tool" >/dev/null 2>&1 || fail "'$tool' is missing on this machine. Install it with your package manager, then run this again."
|
|
111
|
+
done
|
|
112
|
+
command -v curl >/dev/null 2>&1 || fail "curl is missing. Install it (Debian/Ubuntu: sudo apt-get install -y curl ca-certificates; Fedora: sudo dnf install -y curl; Alpine: apk add curl), then run this again."
|
|
113
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
114
|
+
sha256() { sha256sum "$1" | awk '{print $1}'; }
|
|
115
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
116
|
+
sha256() { shasum -a 256 "$1" | awk '{print $1}'; }
|
|
117
|
+
else
|
|
118
|
+
fail "neither sha256sum nor shasum is available to check the download. Install coreutils (or perl's shasum), then run this again."
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
case "$(uname -s)" in
|
|
122
|
+
Darwin) node_os=darwin ;;
|
|
123
|
+
Linux) node_os=linux ;;
|
|
124
|
+
*) fail "this installer is for macOS and Linux. On Windows, in PowerShell: irm $SITE/install.ps1 | iex" ;;
|
|
125
|
+
esac
|
|
126
|
+
case "$(uname -m)" in
|
|
127
|
+
x86_64 | amd64) node_arch=x64 ;;
|
|
128
|
+
arm64 | aarch64) node_arch=arm64 ;;
|
|
129
|
+
*) fail "this processor ($(uname -m)) has no Node $NODE_VERSION build. Pantheon runs on x64 and arm64." ;;
|
|
130
|
+
esac
|
|
131
|
+
# A Terminal running under Rosetta reports x86_64 on an Apple-silicon Mac; the
|
|
132
|
+
# native build is the right one there.
|
|
133
|
+
if [ "$node_os" = darwin ] && [ "$node_arch" = x64 ] && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" = 1 ]; then
|
|
134
|
+
node_arch=arm64
|
|
135
|
+
fi
|
|
136
|
+
# Alpine and other musl systems cannot run the glibc build at all.
|
|
137
|
+
libc=""
|
|
138
|
+
if [ "$node_os" = linux ] && ls /lib/ld-musl-* >/dev/null 2>&1; then
|
|
139
|
+
[ "$node_arch" = x64 ] || fail "this is a musl Linux (Alpine) on arm64, which has no Node $NODE_VERSION build. Install Node 22.13+ yourself, then: npm install -g @join-pantheon/cli@$PANTHEON_VERSION"
|
|
140
|
+
libc="-musl"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# A container or a cloud workspace can install Pantheon, but what it installs
|
|
144
|
+
# goes away with the session, and the menu bar, alerts and the worker belong on
|
|
145
|
+
# the computer the person sits at. The same signals as src/host-env.ts.
|
|
146
|
+
container_evidence() {
|
|
147
|
+
[ "$node_os" = linux ] || return 0
|
|
148
|
+
for v in CODESPACES CLOUDENV_ENVIRONMENT_ID REMOTE_CONTAINERS REMOTE_CONTAINERS_IPC DEVCONTAINER GITPOD_WORKSPACE_ID CLOUD_SHELL REPL_ID KUBERNETES_SERVICE_HOST container; do
|
|
149
|
+
eval "val=\${$v:-}"
|
|
150
|
+
if [ -n "$val" ]; then printf '$%s is set; ' "$v"; fi
|
|
151
|
+
done
|
|
152
|
+
[ -e /.dockerenv ] && printf '/.dockerenv exists; '
|
|
153
|
+
[ -e /run/.containerenv ] && printf '/run/.containerenv exists; '
|
|
154
|
+
[ -e /var/run/secrets/kubernetes.io ] && printf 'a Kubernetes pod; '
|
|
155
|
+
wsl=0
|
|
156
|
+
{ [ -n "${WSL_DISTRO_NAME:-}" ] || grep -qi microsoft /proc/version 2>/dev/null; } && wsl=1
|
|
157
|
+
cg=$(cat /proc/1/cgroup 2>/dev/null || true)
|
|
158
|
+
case "$cg" in
|
|
159
|
+
*docker* | *containerd* | *kubepods* | *lxc* | *libpod* | *crio*) printf 'pid 1 is in a container cgroup; ' ;;
|
|
160
|
+
"0::/") [ "$wsl" = 1 ] || printf 'pid 1 is at the root of its own cgroup namespace; ' ;;
|
|
161
|
+
esac
|
|
162
|
+
comm=$(cat /proc/1/comm 2>/dev/null || true)
|
|
163
|
+
case "$comm" in
|
|
164
|
+
"" | systemd | init | openrc-init | runit | runit-init | launchd | dinit | s6-svscan | s6-linux-init | shepherd) ;;
|
|
165
|
+
*) printf 'pid 1 is "%s", not an init system; ' "$comm" ;;
|
|
166
|
+
esac
|
|
167
|
+
grep -qi gvisor /proc/version 2>/dev/null && printf 'the kernel is gVisor; '
|
|
168
|
+
root=$(awk '$5 == "/" { for (i = 6; i <= NF; i++) if ($i == "-") { print $(i+1) " " $(i+2); exit } }' /proc/1/mountinfo 2>/dev/null || true)
|
|
169
|
+
case "$root" in
|
|
170
|
+
"overlay "* | "overlayfs "* | "9p "* | "virtiofs "* | "aufs "*)
|
|
171
|
+
case "$root" in *composefs*) ;; *) [ -e /run/ostree-booted ] || printf 'the root filesystem is %s; ' "${root%% *}" ;; esac ;;
|
|
172
|
+
esac
|
|
173
|
+
return 0
|
|
174
|
+
}
|
|
175
|
+
evidence=$(container_evidence)
|
|
176
|
+
if [ -n "$evidence" ]; then
|
|
177
|
+
note "this looks like a container or cloud workspace, not the computer you work on (${evidence%; }). Pantheon will install, but it goes away with this session, and the menu bar, alerts and the worker need your own computer."
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# Both spellings, so curl, npm and Pantheon itself all see the same proxy.
|
|
181
|
+
for pair in "HTTPS_PROXY https_proxy" "HTTP_PROXY http_proxy" "NO_PROXY no_proxy"; do
|
|
182
|
+
up=${pair% *}; low=${pair#* }
|
|
183
|
+
eval "uv=\${$up:-}; lv=\${$low:-}"
|
|
184
|
+
if [ -n "$lv" ] && [ -z "$uv" ]; then eval "$up=\$lv; export $up"; fi
|
|
185
|
+
if [ -n "$uv" ] && [ -z "$lv" ]; then eval "$low=\$uv; export $low"; fi
|
|
186
|
+
done
|
|
187
|
+
|
|
188
|
+
ROOT="$HOME/.pantheon"
|
|
189
|
+
RUNTIME="$ROOT/runtime"
|
|
190
|
+
NODE_DIR="$RUNTIME/node"
|
|
191
|
+
NODE="$NODE_DIR/bin/node"
|
|
192
|
+
PKG_DIR="$NODE_DIR/lib/node_modules/@join-pantheon/cli"
|
|
193
|
+
BIN_DIR="$ROOT/bin"
|
|
194
|
+
LAUNCHER="$BIN_DIR/pantheon"
|
|
195
|
+
LOG="$ROOT/logs/install.log"
|
|
196
|
+
|
|
197
|
+
STEP="creating $ROOT"
|
|
198
|
+
# The first of Pantheon's folders (or the runtime's files) that belongs to
|
|
199
|
+
# someone else: what an earlier `sudo pantheon` or `sudo npm` leaves behind.
|
|
200
|
+
# Nothing when find is missing; the steps below then say what failed.
|
|
201
|
+
foreign() {
|
|
202
|
+
me=$(id -u)
|
|
203
|
+
for d in "$ROOT" "$RUNTIME" "$BIN_DIR" "$ROOT/logs"; do
|
|
204
|
+
if [ -e "$d" ]; then
|
|
205
|
+
f=$(find "$d" -maxdepth 1 ! -user "$me" 2>/dev/null | head -n 1)
|
|
206
|
+
if [ -n "$f" ]; then printf '%s' "$f"; return 0; fi
|
|
207
|
+
fi
|
|
208
|
+
done
|
|
209
|
+
if [ -d "$NODE_DIR" ]; then find "$NODE_DIR" ! -user "$me" 2>/dev/null | head -n 1; fi
|
|
210
|
+
}
|
|
211
|
+
not_ours() {
|
|
212
|
+
# shellcheck disable=SC2012 # the owner's name, which stat spells differently on macOS and Linux
|
|
213
|
+
fail "$1 belongs to $(ls -ld "$1" | awk '{print $3}'), not to you (left by an earlier install run with sudo). Give it back with: sudo chown -R $(id -un) \"$ROOT\" then run this again."
|
|
214
|
+
}
|
|
215
|
+
if ! mkdir -p "$RUNTIME" "$BIN_DIR" "$ROOT/logs" 2>/dev/null; then
|
|
216
|
+
f=$(foreign)
|
|
217
|
+
[ -n "$f" ] && not_ours "$f"
|
|
218
|
+
fail "could not create $RUNTIME. Check that your home folder is writable by you (ls -ld \"$HOME\"), then run this again."
|
|
219
|
+
fi
|
|
220
|
+
f=$(foreign)
|
|
221
|
+
[ -n "$f" ] && not_ours "$f"
|
|
222
|
+
|
|
223
|
+
# ---- one installer at a time ----------------------------------------------
|
|
224
|
+
|
|
225
|
+
# Two installers at once (a second terminal, a link pasted twice) would swap
|
|
226
|
+
# the runtime under each other. mkdir is atomic, so the lock is a folder; it
|
|
227
|
+
# holds the holder's pid and machine name. The second installer waits for the
|
|
228
|
+
# first, then finds everything in place. A lock whose holder is gone (killed,
|
|
229
|
+
# the machine lost power) is stale and taken over, through a second folder, so
|
|
230
|
+
# two installers that find the same stale lock cannot both take it.
|
|
231
|
+
LOCK="$RUNTIME/.install.lock"
|
|
232
|
+
STEP="waiting for another Pantheon install"
|
|
233
|
+
host=$(uname -n 2>/dev/null || echo unknown)
|
|
234
|
+
# Whether $1 is older than $2 minutes (false when it cannot tell).
|
|
235
|
+
older_than() { [ -n "$(find "$1" -maxdepth 0 -mmin +"$2" 2>/dev/null)" ]; }
|
|
236
|
+
lock_stale() {
|
|
237
|
+
holder=$(cat "$LOCK/pid" 2>/dev/null || true)
|
|
238
|
+
[ -d "$LOCK" ] || return 1
|
|
239
|
+
# An hour is far past any real install: a pid reused since, or a holder on
|
|
240
|
+
# another machine sharing this home folder, cannot keep the lock forever.
|
|
241
|
+
older_than "$LOCK" 60 && return 0
|
|
242
|
+
# Being taken right now (the pid not written yet), unless that was long ago.
|
|
243
|
+
[ -n "$holder" ] || { older_than "$LOCK" 1; return; }
|
|
244
|
+
hpid=${holder%% *}
|
|
245
|
+
case "$hpid" in "" | *[!0-9]*) return 0 ;; esac
|
|
246
|
+
[ "${holder#* }" = "$host" ] || return 1
|
|
247
|
+
! kill -0 "$hpid" 2>/dev/null
|
|
248
|
+
}
|
|
249
|
+
waited=0
|
|
250
|
+
until mkdir "$LOCK" 2>/dev/null; do
|
|
251
|
+
[ -d "$RUNTIME" ] || fail "could not create $RUNTIME. Check that your home folder is writable by you, then run this again."
|
|
252
|
+
if lock_stale; then
|
|
253
|
+
if mkdir "$LOCK.break" 2>/dev/null; then
|
|
254
|
+
# Checked again while holding the break folder: only a lock still stale
|
|
255
|
+
# now is removed, never one a live installer took a moment ago.
|
|
256
|
+
lock_stale && rm -rf "$LOCK"
|
|
257
|
+
rmdir "$LOCK.break" 2>/dev/null || true
|
|
258
|
+
elif older_than "$LOCK.break" 1; then
|
|
259
|
+
rm -rf "$LOCK.break"
|
|
260
|
+
else
|
|
261
|
+
sleep 1
|
|
262
|
+
fi
|
|
263
|
+
continue
|
|
264
|
+
fi
|
|
265
|
+
holder=$(cat "$LOCK/pid" 2>/dev/null || true)
|
|
266
|
+
if [ "$waited" -eq 0 ]; then say "Another Pantheon install is running${holder:+ (pid ${holder%% *})}; waiting for it to finish..."; fi
|
|
267
|
+
[ "$waited" -ge 600 ] && fail "another Pantheon install${holder:+ (pid ${holder%% *})} has been running for over 10 minutes. Let it finish or stop it, then run this again."
|
|
268
|
+
sleep 1
|
|
269
|
+
waited=$((waited + 1))
|
|
270
|
+
done
|
|
271
|
+
LOCKED=1
|
|
272
|
+
printf '%s %s\n' "$$" "$host" >"$LOCK/pid" 2>/dev/null || true
|
|
273
|
+
|
|
274
|
+
# What an installer stopped by kill -9 or a power cut left behind.
|
|
275
|
+
find "$RUNTIME" -maxdepth 1 -type d -name '.install.??????' -exec rm -rf {} + 2>/dev/null || true
|
|
276
|
+
|
|
277
|
+
STEP="creating $ROOT"
|
|
278
|
+
TMP_DIR=$(mktemp -d "$RUNTIME/.install.XXXXXX") || fail "could not make a temporary folder in $RUNTIME. Check free disk space, then run this again."
|
|
279
|
+
|
|
280
|
+
# Download $1 to $2. curl's own complaint goes into the one line, not above it.
|
|
281
|
+
fetch() {
|
|
282
|
+
if ! curl -fsSL --retry 3 --connect-timeout 20 -o "$2" "$1" 2>"$TMP_DIR/curl.err"; then
|
|
283
|
+
why=$(tr '\n' ' ' <"$TMP_DIR/curl.err" | sed 's/ *$//')
|
|
284
|
+
fail "could not download $1 (${why:-no answer}). Check your internet connection; behind a proxy set HTTPS_PROXY; then run this again."
|
|
285
|
+
fi
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# ---- 1. Pantheon's own Node -----------------------------------------------
|
|
289
|
+
|
|
290
|
+
have_node=""
|
|
291
|
+
if [ -x "$NODE" ]; then have_node=$("$NODE" --version 2>/dev/null || true); fi
|
|
292
|
+
if [ "$have_node" = "v$NODE_VERSION" ]; then
|
|
293
|
+
say "Node v$NODE_VERSION for Pantheon is already in $NODE_DIR."
|
|
294
|
+
else
|
|
295
|
+
STEP="downloading Node v$NODE_VERSION"
|
|
296
|
+
tarball="node-v$NODE_VERSION-$node_os-$node_arch$libc.tar.gz"
|
|
297
|
+
say "Downloading Node v$NODE_VERSION for Pantheon ($node_os-$node_arch$libc)..."
|
|
298
|
+
want=$(node_pin "$tarball")
|
|
299
|
+
[ -n "$want" ] || fail "this installer has no checksum for $tarball, so it cannot install Node here. Install Node 22.13+ yourself, then: npm install -g @join-pantheon/cli@$PANTHEON_VERSION"
|
|
300
|
+
fetch "$NODE_DIST/v$NODE_VERSION/$tarball" "$TMP_DIR/$tarball"
|
|
301
|
+
|
|
302
|
+
STEP="checking the Node download"
|
|
303
|
+
got=$(sha256 "$TMP_DIR/$tarball")
|
|
304
|
+
[ "$got" = "$want" ] || fail "the Node download does not match its published sha256 (expected $want, got $got), so it was not used: it arrived incomplete or was changed on the way. Run this again; if it repeats, try another network."
|
|
305
|
+
|
|
306
|
+
STEP="unpacking Node"
|
|
307
|
+
mkdir "$TMP_DIR/unpack" || fail "could not make a folder in $TMP_DIR. Check free disk space in $HOME, then run this again."
|
|
308
|
+
if ! tar -xzf "$TMP_DIR/$tarball" -C "$TMP_DIR/unpack" 2>"$TMP_DIR/tar.err"; then
|
|
309
|
+
why=$(head -n 1 "$TMP_DIR/tar.err" 2>/dev/null || true)
|
|
310
|
+
case "$why" in
|
|
311
|
+
*[Ss]pace* | *quota* | *"Cannot write"* | *"Write error"*) fail "could not unpack $tarball ($why). Free some disk space in $HOME, then run this again." ;;
|
|
312
|
+
*) fail "could not unpack $tarball (${why:-tar failed}). Free disk space in $HOME if it is full, then run this again." ;;
|
|
313
|
+
esac
|
|
314
|
+
fi
|
|
315
|
+
mv "$TMP_DIR/unpack/node-v$NODE_VERSION-$node_os-$node_arch$libc" "$TMP_DIR/node" || fail "the Node archive did not have the layout expected. Run this again; if it repeats, send this line to sid@joinpantheon.network"
|
|
316
|
+
if ! "$TMP_DIR/node/bin/node" --version >/dev/null 2>&1; then
|
|
317
|
+
# musl's Node build needs the C++ runtime, which a bare Alpine lacks.
|
|
318
|
+
[ -n "$libc" ] && fail "Node v$NODE_VERSION needs the C++ runtime on Alpine. Install it (apk add libstdc++), then run this again."
|
|
319
|
+
fail "Node v$NODE_VERSION does not run on this system (Linux needs glibc 2.28 or newer: Debian 10, Ubuntu 20.04, RHEL 8 or later). Install Node 22.13+ yourself, then: npm install -g @join-pantheon/cli@$PANTHEON_VERSION"
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
# The CLI from the old runtime moves into the new one with it, so the
|
|
323
|
+
# pantheon command keeps working until npm below has run.
|
|
324
|
+
if [ -d "$NODE_DIR/lib/node_modules/@join-pantheon" ]; then
|
|
325
|
+
mkdir -p "$TMP_DIR/node/lib/node_modules"
|
|
326
|
+
cp -R "$NODE_DIR/lib/node_modules/@join-pantheon" "$TMP_DIR/node/lib/node_modules/" 2>/dev/null || true
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
# Swapped by rename, never unpacked over: a half-written runtime is never
|
|
330
|
+
# the one a running service or a new terminal starts.
|
|
331
|
+
# Ctrl-C waits for the two renames: between them there is no runtime at all.
|
|
332
|
+
STEP="putting Node in place"
|
|
333
|
+
trap '' INT TERM HUP
|
|
334
|
+
if [ -e "$NODE_DIR" ]; then mv "$NODE_DIR" "$TMP_DIR/old-node" || fail "could not move the old $NODE_DIR aside. Quit anything using it (pantheon service stop worker), then run this again."; fi
|
|
335
|
+
if ! mv "$TMP_DIR/node" "$NODE_DIR"; then
|
|
336
|
+
[ -e "$TMP_DIR/old-node" ] && mv "$TMP_DIR/old-node" "$NODE_DIR"
|
|
337
|
+
fail "could not put the new Node in $NODE_DIR. Check free disk space and permissions, then run this again."
|
|
338
|
+
fi
|
|
339
|
+
trap stopped INT TERM HUP
|
|
340
|
+
say "Node v$NODE_VERSION is in $NODE_DIR."
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
# The system's certificate store, as well as Node's own: on a network that
|
|
344
|
+
# inspects secure traffic, the company's root is only there. npm needs it to
|
|
345
|
+
# install, and pantheon needs it to reach its relay, so the launcher below runs
|
|
346
|
+
# Node with it too (when this Node has the flag; the pinned one does).
|
|
347
|
+
ca=""
|
|
348
|
+
if "$NODE" --use-system-ca -e 0 >/dev/null 2>&1; then ca="--use-system-ca"; fi
|
|
349
|
+
|
|
350
|
+
# ---- 2. the Pantheon CLI, with that Node's own npm ------------------------
|
|
351
|
+
|
|
352
|
+
# The version of the CLI in the runtime, but only when it is whole: its
|
|
353
|
+
# package.json names that version AND it starts and says so. A run stopped
|
|
354
|
+
# halfway through npm can leave the first without the second; that is
|
|
355
|
+
# installed again, never called done.
|
|
356
|
+
installed_cli() {
|
|
357
|
+
if [ -f "$PKG_DIR/package.json" ] && [ -f "$PKG_DIR/bin/pantheon.js" ]; then
|
|
358
|
+
v=$("$NODE" -e 'try { process.stdout.write(require(process.argv[1]).version || "") } catch {}' "$PKG_DIR/package.json" 2>/dev/null || true)
|
|
359
|
+
if [ -n "$v" ] && [ "$("$NODE" "$PKG_DIR/bin/pantheon.js" --version 2>/dev/null </dev/null || true)" = "$v" ]; then printf '%s' "$v"; fi
|
|
360
|
+
fi
|
|
361
|
+
return 0
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
# What npm installs. PANTHEON_INSTALL_SPEC swaps the registry package for a
|
|
365
|
+
# package file on this machine, so a release candidate goes through this exact
|
|
366
|
+
# script (scripts/installer-tests does) before its version is on npm. Only a
|
|
367
|
+
# full path to a local .tgz is taken: no URL, git or registry spec, so it can
|
|
368
|
+
# never send an install anywhere npm would not have gone. Whoever can set it can
|
|
369
|
+
# already set npm_config_registry, NODE_OPTIONS or PATH for this shell, so it
|
|
370
|
+
# opens no new way in; and the file must hold PANTHEON_VERSION itself, so it
|
|
371
|
+
# cannot install a different release than the one this script names.
|
|
372
|
+
spec="@join-pantheon/cli@$PANTHEON_VERSION"
|
|
373
|
+
if [ -n "${PANTHEON_INSTALL_SPEC:-}" ]; then
|
|
374
|
+
case "$PANTHEON_INSTALL_SPEC" in
|
|
375
|
+
/*.tgz) [ -f "$PANTHEON_INSTALL_SPEC" ] || fail "PANTHEON_INSTALL_SPEC is $PANTHEON_INSTALL_SPEC, which is not a file. Unset it to install from npm." ;;
|
|
376
|
+
*) fail "PANTHEON_INSTALL_SPEC must be the full path of a package .tgz on this machine, not $PANTHEON_INSTALL_SPEC. Unset it to install from npm." ;;
|
|
377
|
+
esac
|
|
378
|
+
file_version=$(tar -xzOf "$PANTHEON_INSTALL_SPEC" package/package.json 2>/dev/null | "$NODE" -e 'let s = ""; process.stdin.on("data", (d) => { s += d; }).on("end", () => { try { process.stdout.write(JSON.parse(s).version || "") } catch {} })' 2>/dev/null || true)
|
|
379
|
+
[ "$file_version" = "$PANTHEON_VERSION" ] || fail "PANTHEON_INSTALL_SPEC holds Pantheon ${file_version:-of no version}, not $PANTHEON_VERSION. Use the package built for $PANTHEON_VERSION, or unset it to install from npm."
|
|
380
|
+
spec=$PANTHEON_INSTALL_SPEC
|
|
381
|
+
note "installing Pantheon $PANTHEON_VERSION from $spec, not from npm (PANTHEON_INSTALL_SPEC is set)."
|
|
382
|
+
fi
|
|
383
|
+
|
|
384
|
+
have_cli=$(installed_cli)
|
|
385
|
+
if [ "$have_cli" = "$PANTHEON_VERSION" ]; then
|
|
386
|
+
say "Pantheon $PANTHEON_VERSION is already installed."
|
|
387
|
+
else
|
|
388
|
+
STEP="installing Pantheon $PANTHEON_VERSION"
|
|
389
|
+
say "Installing Pantheon $PANTHEON_VERSION..."
|
|
390
|
+
# A registry that does not answer fails in about two minutes, not npm's six
|
|
391
|
+
# (--fetch-timeout/--fetch-retries): long enough for a slow network.
|
|
392
|
+
# npm runs as `node npm-cli.js`, so its `#!/usr/bin/env node` never picks up
|
|
393
|
+
# some other node; the prefix is named, so a prefix= in ~/.npmrc or
|
|
394
|
+
# NPM_CONFIG_PREFIX cannot send the package elsewhere; and the cache is
|
|
395
|
+
# Pantheon's own, so a ~/.npm left root-owned by an old `sudo npm` cannot
|
|
396
|
+
# fail it with EACCES. $ca (above) trusts the same certificates curl just
|
|
397
|
+
# did, which is what a TLS-inspecting company proxy needs.
|
|
398
|
+
if ! PATH="$NODE_DIR/bin:$PATH" "$NODE" ${ca:+"$ca"} "$NODE_DIR/lib/node_modules/npm/bin/npm-cli.js" install -g \
|
|
399
|
+
--prefix "$NODE_DIR" --cache "$RUNTIME/npm-cache" \
|
|
400
|
+
"$spec" --no-audit --no-fund --loglevel=error --fetch-timeout=60000 --fetch-retries=2 >"$LOG" 2>&1; then
|
|
401
|
+
# npm's first two error lines (`code E404`, `404 Not Found - GET ...`) say why.
|
|
402
|
+
why=$(grep -E '^npm (error|ERR!) ' "$LOG" | head -n 2 | sed -e 's/^npm error //' -e 's/^npm ERR! //' | tr '\n' ';' | sed 's/;$//; s/;/; /g' || true)
|
|
403
|
+
[ -n "$why" ] || why=$(grep -m 1 -i 'error' "$LOG" || true)
|
|
404
|
+
# A full disk can also leave the log itself empty, so free space is asked too.
|
|
405
|
+
free=$(df -Pk "$RUNTIME" 2>/dev/null | awk 'NR == 2 { print $4 }' || true)
|
|
406
|
+
if grep -q 'ENOSPC' "$LOG" 2>/dev/null || { [ -n "$free" ] && [ "$free" -lt 20480 ] 2>/dev/null; }; then
|
|
407
|
+
fail "npm could not install @join-pantheon/cli@$PANTHEON_VERSION because the disk is full (${why:-ENOSPC}). Free some disk space in $HOME, then run this again."
|
|
408
|
+
fi
|
|
409
|
+
fail "npm could not install @join-pantheon/cli@$PANTHEON_VERSION (${why:-see the log}). Behind a proxy, set HTTPS_PROXY; if your network inspects TLS, set NODE_EXTRA_CA_CERTS to your company's CA file. Full log: $LOG"
|
|
410
|
+
fi
|
|
411
|
+
[ -f "$PKG_DIR/bin/pantheon.js" ] || fail "npm finished but $PKG_DIR/bin/pantheon.js is not there. Full log: $LOG (send it to sid@joinpantheon.network)"
|
|
412
|
+
have_cli=$(installed_cli)
|
|
413
|
+
[ "$have_cli" = "$PANTHEON_VERSION" ] || fail "npm finished, but the Pantheon in $PKG_DIR does not start as $PANTHEON_VERSION${have_cli:+ (it is $have_cli)}. Run the same command again; if it fails the same way, send this line and $LOG to sid@joinpantheon.network"
|
|
414
|
+
say "Pantheon $PANTHEON_VERSION is installed."
|
|
415
|
+
fi
|
|
416
|
+
|
|
417
|
+
# ---- 3. the launcher -------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
# Whether two files hold the same bytes (false when either is missing, or cmp is).
|
|
420
|
+
same() { cmp -s "$1" "$2" 2>/dev/null; }
|
|
421
|
+
|
|
422
|
+
# A path as one single-quoted shell word.
|
|
423
|
+
shq() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"; }
|
|
424
|
+
|
|
425
|
+
STEP="writing $LAUNCHER"
|
|
426
|
+
{
|
|
427
|
+
printf '#!/bin/sh\n'
|
|
428
|
+
printf '# The pantheon command, written by the Pantheon installer. It runs\n'
|
|
429
|
+
printf "# Pantheon's own Node by absolute path, so it never depends on which\n"
|
|
430
|
+
printf '# node comes first on PATH. Running the installer again rewrites it.\n'
|
|
431
|
+
printf 'exec %s %s%s "$@"\n' "$(shq "$NODE")" "${ca:+$ca }" "$(shq "$PKG_DIR/bin/pantheon.js")"
|
|
432
|
+
} >"$TMP_DIR/launcher" || fail "could not write the pantheon command. Check free disk space, then run this again."
|
|
433
|
+
chmod 755 "$TMP_DIR/launcher"
|
|
434
|
+
if ! same "$TMP_DIR/launcher" "$LAUNCHER"; then
|
|
435
|
+
mv -f "$TMP_DIR/launcher" "$LAUNCHER" || fail "could not write $LAUNCHER. Check that $BIN_DIR is writable by you, then run this again."
|
|
436
|
+
fi
|
|
437
|
+
"$LAUNCHER" --version >/dev/null 2>&1 || fail "the installed pantheon does not start ($LAUNCHER --version failed). Run the same command again; if it fails the same way, send this line to sid@joinpantheon.network"
|
|
438
|
+
|
|
439
|
+
# ---- 4. PATH ---------------------------------------------------------------
|
|
440
|
+
|
|
441
|
+
STEP="adding $BIN_DIR to PATH"
|
|
442
|
+
BEGIN_MARK='# >>> pantheon >>>'
|
|
443
|
+
END_MARK='# <<< pantheon <<<'
|
|
444
|
+
# shellcheck disable=SC2016 # written literally: the profile expands it, not this script
|
|
445
|
+
posix_block() {
|
|
446
|
+
printf '%s\n' "$BEGIN_MARK"
|
|
447
|
+
printf '%s\n' '# The pantheon command, first on PATH. Written by the Pantheon installer; delete this block to undo.'
|
|
448
|
+
printf '%s\n' 'case "$PATH" in'
|
|
449
|
+
printf '%s\n' ' "$HOME/.pantheon/bin:"*) ;;'
|
|
450
|
+
printf '%s\n' ' *) export PATH="$HOME/.pantheon/bin:$PATH" ;;'
|
|
451
|
+
printf '%s\n' 'esac'
|
|
452
|
+
printf '%s\n' "$END_MARK"
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
# Replace the contents of profile $1 with $TMP_DIR/new. In place (cat >), so a
|
|
456
|
+
# dotfile that is a symlink stays one and keeps its owner and mode. Ctrl-C
|
|
457
|
+
# waits for it, and a write that fails halfway (a full disk) puts the old
|
|
458
|
+
# contents back: a profile is never left cut short.
|
|
459
|
+
write_over() {
|
|
460
|
+
trap '' INT TERM HUP
|
|
461
|
+
ok=0
|
|
462
|
+
if [ -e "$1" ]; then cp "$1" "$TMP_DIR/orig" 2>/dev/null || ok=1; fi
|
|
463
|
+
if [ "$ok" -eq 0 ] && ! cat "$TMP_DIR/new" >"$1" 2>/dev/null; then
|
|
464
|
+
ok=1
|
|
465
|
+
if [ -f "$TMP_DIR/orig" ]; then cat "$TMP_DIR/orig" >"$1" 2>/dev/null || true; fi
|
|
466
|
+
fi
|
|
467
|
+
rm -f "$TMP_DIR/orig"
|
|
468
|
+
trap stopped INT TERM HUP
|
|
469
|
+
return "$ok"
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
# Put the block in one profile, or bring an older block up to date in the
|
|
473
|
+
# same place. Every other line of the file is kept as it was.
|
|
474
|
+
added=""
|
|
475
|
+
add_block() {
|
|
476
|
+
f=$1
|
|
477
|
+
posix_block >"$TMP_DIR/block"
|
|
478
|
+
begins=0
|
|
479
|
+
ends=0
|
|
480
|
+
if [ -f "$f" ]; then
|
|
481
|
+
begins=$(grep -cxF "$BEGIN_MARK" "$f" 2>/dev/null || true)
|
|
482
|
+
ends=$(grep -cxF "$END_MARK" "$f" 2>/dev/null || true)
|
|
483
|
+
fi
|
|
484
|
+
# A marker without its pair (edited by hand, or cut short): rewriting from
|
|
485
|
+
# it could take the lines after it too, so the file is not touched.
|
|
486
|
+
if [ "${begins:-0}" -ne "${ends:-0}" ] || [ "${begins:-0}" -gt 1 ]; then
|
|
487
|
+
note "the pantheon block in $f has a marker line without its pair, so $f was left as it is; put $BIN_DIR at the front of PATH there yourself"
|
|
488
|
+
return 0
|
|
489
|
+
fi
|
|
490
|
+
if [ "${begins:-0}" -eq 1 ]; then
|
|
491
|
+
awk -v b="$BEGIN_MARK" -v e="$END_MARK" '$0 == b { keep = 1 } keep { print } $0 == e { keep = 0 }' "$f" >"$TMP_DIR/old"
|
|
492
|
+
same "$TMP_DIR/old" "$TMP_DIR/block" && return 0
|
|
493
|
+
awk -v b="$BEGIN_MARK" -v e="$END_MARK" -v nf="$TMP_DIR/block" '
|
|
494
|
+
$0 == b { while ((getline l < nf) > 0) print l; skip = 1; next }
|
|
495
|
+
$0 == e && skip { skip = 0; next }
|
|
496
|
+
!skip { print }' "$f" >"$TMP_DIR/new" || { note "could not update $f; put $BIN_DIR at the front of PATH there yourself"; return 0; }
|
|
497
|
+
else
|
|
498
|
+
{
|
|
499
|
+
if [ -s "$f" ]; then
|
|
500
|
+
cat "$f"
|
|
501
|
+
# A last line without its newline, then one blank line before ours.
|
|
502
|
+
if [ -n "$(tail -c 1 "$f")" ]; then printf '\n'; fi
|
|
503
|
+
printf '\n'
|
|
504
|
+
fi
|
|
505
|
+
cat "$TMP_DIR/block"
|
|
506
|
+
} >"$TMP_DIR/new" 2>/dev/null || { note "could not read $f; put $BIN_DIR at the front of PATH there yourself"; return 0; }
|
|
507
|
+
fi
|
|
508
|
+
write_over "$f" || { note "could not write $f; put $BIN_DIR at the front of PATH there yourself"; return 0; }
|
|
509
|
+
added="$added ${f#"$HOME"/}"
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
login_shell=$(basename "${SHELL:-sh}")
|
|
513
|
+
# ~/.profile: read by sh and dash logins, by bash when it has no .bash_profile,
|
|
514
|
+
# and by the `sh -lc` Pantheon itself uses to find programs.
|
|
515
|
+
add_block "$HOME/.profile"
|
|
516
|
+
[ -f "$HOME/.bash_profile" ] && add_block "$HOME/.bash_profile"
|
|
517
|
+
{ [ -f "$HOME/.bashrc" ] || [ "$login_shell" = bash ]; } && add_block "$HOME/.bashrc"
|
|
518
|
+
{ [ -f "$HOME/.zshrc" ] || [ "$login_shell" = zsh ]; } && add_block "$HOME/.zshrc"
|
|
519
|
+
[ -f "$HOME/.zprofile" ] && add_block "$HOME/.zprofile"
|
|
520
|
+
# fish reads its own conf.d folder; the file there is ours whole.
|
|
521
|
+
if command -v fish >/dev/null 2>&1 || [ -d "$HOME/.config/fish" ]; then
|
|
522
|
+
fish_conf="$HOME/.config/fish/conf.d/pantheon.fish"
|
|
523
|
+
# shellcheck disable=SC2016 # written literally: fish expands it, not this script
|
|
524
|
+
{
|
|
525
|
+
printf '%s\n' '# The pantheon command, first on PATH. Written by the Pantheon installer; delete this file to undo.'
|
|
526
|
+
printf '%s\n' 'set -l pantheon_bin $HOME/.pantheon/bin'
|
|
527
|
+
printf '%s\n' 'set -gx PATH $pantheon_bin (string match -v -- $pantheon_bin $PATH)'
|
|
528
|
+
} >"$TMP_DIR/fish"
|
|
529
|
+
if ! same "$TMP_DIR/fish" "$fish_conf"; then
|
|
530
|
+
if mkdir -p "$HOME/.config/fish/conf.d" 2>/dev/null && cat "$TMP_DIR/fish" >"$fish_conf" 2>/dev/null; then
|
|
531
|
+
added="$added ${fish_conf#"$HOME"/}"
|
|
532
|
+
else
|
|
533
|
+
note "could not write $fish_conf; add $BIN_DIR to the front of PATH in fish yourself"
|
|
534
|
+
fi
|
|
535
|
+
fi
|
|
536
|
+
fi
|
|
537
|
+
[ -n "$added" ] && say "Put $BIN_DIR first on PATH in:$added"
|
|
538
|
+
|
|
539
|
+
# An older pantheon (npm -g, Homebrew, a checkout) is left exactly as it is.
|
|
540
|
+
other=$(command -v pantheon 2>/dev/null || true)
|
|
541
|
+
if [ -n "$other" ] && [ "$other" != "$LAUNCHER" ]; then
|
|
542
|
+
note "another pantheon is installed at $other and was left as it is. New terminals run $LAUNCHER first."
|
|
543
|
+
fi
|
|
544
|
+
|
|
545
|
+
# ---- 5. hand over to pantheon ----------------------------------------------
|
|
546
|
+
|
|
547
|
+
rm -rf "$TMP_DIR"
|
|
548
|
+
TMP_DIR=""
|
|
549
|
+
unlock
|
|
550
|
+
trap - EXIT INT TERM HUP
|
|
551
|
+
|
|
552
|
+
if [ "$#" -eq 0 ]; then
|
|
553
|
+
say ""
|
|
554
|
+
say "Pantheon $PANTHEON_VERSION is installed. Open a new terminal (or run: export PATH=\"$BIN_DIR:\$PATH\"), then:"
|
|
555
|
+
say " pantheon start create a team"
|
|
556
|
+
say " pantheon join '<invite link>' join a teammate's team"
|
|
557
|
+
exit 0
|
|
558
|
+
fi
|
|
559
|
+
|
|
560
|
+
say ""
|
|
561
|
+
# Found first by anything pantheon starts during setup, before any new terminal.
|
|
562
|
+
PATH="$BIN_DIR:$PATH"
|
|
563
|
+
export PATH
|
|
564
|
+
# stdin is the curl pipe; sign-in and setup questions must come from the
|
|
565
|
+
# keyboard.
|
|
566
|
+
if (: </dev/tty) 2>/dev/null; then
|
|
567
|
+
exec "$LAUNCHER" "$@" </dev/tty
|
|
568
|
+
fi
|
|
569
|
+
if [ -t 0 ]; then exec "$LAUNCHER" "$@"; fi
|
|
570
|
+
|
|
571
|
+
# No terminal at all: CI, a pipe, some IDE task runners. pantheon still runs,
|
|
572
|
+
# because it never asks a question without one (every prompt checks
|
|
573
|
+
# process.stdin.isTTY first) and works on flags alone: a join to a self-hosted
|
|
574
|
+
# relay, or from a machine already signed in, needs no answers. stdin is
|
|
575
|
+
# /dev/null rather than the rest of the curl pipe, so nothing can wait on it.
|
|
576
|
+
#
|
|
577
|
+
# What it cannot do here is sign in. On the hosted relay (or one of a team's
|
|
578
|
+
# own that verifies email) join and start then stop at once, before anything
|
|
579
|
+
# is changed, with "sign in first" (or the relay's NOT_SIGNED_IN). When that is
|
|
580
|
+
# how it ended, and config.json still has no account, the last line says the
|
|
581
|
+
# one thing to do. Any other failure (a mistyped link, an expired invite) keeps
|
|
582
|
+
# pantheon's own words as the last line, since a terminal would not fix it.
|
|
583
|
+
code=0
|
|
584
|
+
said=$(mktemp 2>/dev/null || true)
|
|
585
|
+
if [ -n "$said" ]; then
|
|
586
|
+
# pantheon's stderr is shown as it comes and kept, to read its reason after.
|
|
587
|
+
{ { "$LAUNCHER" "$@" </dev/null 3>&- || echo "$?" >"$said.code"; } 2>&1 1>&3 | tee "$said" >&2; } 3>&1
|
|
588
|
+
if [ -f "$said.code" ]; then code=$(cat "$said.code"); fi
|
|
589
|
+
else
|
|
590
|
+
"$LAUNCHER" "$@" </dev/null || code=$?
|
|
591
|
+
fi
|
|
592
|
+
if [ "$code" -ne 0 ] && [ -n "$said" ] && grep -qE 'sign in first|NOT_SIGNED_IN' "$said" 2>/dev/null; then
|
|
593
|
+
case "$1" in
|
|
594
|
+
join | start)
|
|
595
|
+
cfg="${PANTHEON_HOME:-$HOME/.pantheon}/config.json"
|
|
596
|
+
if ! "$NODE" -e 'try { process.exit(JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).account ? 0 : 1) } catch { process.exit(1) }' "$cfg" 2>/dev/null; then
|
|
597
|
+
line="pantheon"
|
|
598
|
+
# Quoted only where a shell needs it, so it reads as the person typed it.
|
|
599
|
+
for a in "$@"; do
|
|
600
|
+
case "$a" in
|
|
601
|
+
"" | *[!A-Za-z0-9_./:=@%+,-]*) line="$line $(shq "$a")" ;;
|
|
602
|
+
*) line="$line $a" ;;
|
|
603
|
+
esac
|
|
604
|
+
done
|
|
605
|
+
if [ "$1" = join ]; then what="joining"; else what="creating your team"; fi
|
|
606
|
+
printf '\nPantheon is installed. To finish %s, open a terminal and run: %s\n' "$what" "$line" >&2
|
|
607
|
+
fi
|
|
608
|
+
;;
|
|
609
|
+
esac
|
|
610
|
+
fi
|
|
611
|
+
if [ -n "$said" ]; then rm -f "$said" "$said.code"; fi
|
|
612
|
+
exit "$code"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@join-pantheon/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0-rc.2",
|
|
4
4
|
"description": "Let your coding agent collaborate directly with your teammates' agents — ask, agree an interface, and deliver, without a human relaying messages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,10 +15,13 @@
|
|
|
15
15
|
"relay": "node dist/cli.js relay",
|
|
16
16
|
"test": "PANTHEON_HOME=\"$(mktemp -d)\" HOME=\"$(mktemp -d)\" node --test --test-force-exit dist/test/*.test.js",
|
|
17
17
|
"prepare": "npm run build",
|
|
18
|
-
"
|
|
18
|
+
"build:helpers": "node scripts/build-helpers.mjs",
|
|
19
|
+
"release:check": "node scripts/release-check.mjs",
|
|
20
|
+
"prepublishOnly": "npm run build && npm run build:helpers && npm test && npm run release:check",
|
|
19
21
|
"start": "node dist/cli.js relay",
|
|
20
22
|
"test:ci": "node scripts/test.mjs",
|
|
21
|
-
"smoke": "node scripts/smoke.mjs"
|
|
23
|
+
"smoke": "node scripts/smoke.mjs",
|
|
24
|
+
"test:installer": "node scripts/installer-tests/run.mjs"
|
|
22
25
|
},
|
|
23
26
|
"dependencies": {
|
|
24
27
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
@@ -35,6 +38,8 @@
|
|
|
35
38
|
"bin",
|
|
36
39
|
"dist",
|
|
37
40
|
"helper",
|
|
41
|
+
"installer",
|
|
42
|
+
"helper/macos/prebuilt/",
|
|
38
43
|
"!dist/test",
|
|
39
44
|
"!dist/**/*.map",
|
|
40
45
|
"README.md",
|