@join-pantheon/cli 0.32.1 → 0.33.0-rc.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.
- package/dist/accounts.js +1 -1
- package/dist/board.js +1417 -0
- package/dist/claude-config.js +189 -0
- package/dist/cli.js +298 -58
- package/dist/command-state.js +2 -1
- package/dist/dbus.js +92 -13
- package/dist/doctor.js +36 -5
- package/dist/folder-guard.js +89 -0
- package/dist/git-safe.js +3 -3
- package/dist/host-env.js +47 -1
- package/dist/install.js +76 -10
- package/dist/installer.js +2 -1
- package/dist/ledger.js +2 -2
- package/dist/machine.js +38 -3
- package/dist/managed.js +27 -1
- package/dist/notice-linux.js +501 -0
- package/dist/notice-windows.js +323 -0
- package/dist/notifier.js +12 -3
- package/dist/quickstart.js +28 -11
- package/dist/relay.js +64 -9
- package/dist/service.js +455 -29
- package/dist/setup-guide.js +22 -5
- package/dist/store.js +18 -2
- package/dist/upgrade.js +249 -4
- package/dist/uri-handler.js +335 -0
- package/dist/win-host.js +969 -0
- package/dist/worker-policy.js +3 -0
- package/dist/worker.js +11 -11
- package/helper/macos/prebuilt/manifest.json +2 -2
- package/helper/macos/prebuilt/pantheon-hud +0 -0
- package/helper/windows/pantheon-host.cs +170 -0
- package/helper/windows/pantheon-notice.ps1 +127 -0
- package/helper/windows/pantheon-tray.ps1 +731 -0
- package/helper/windows/pantheon.ico +0 -0
- package/installer/install.ps1 +234 -18
- package/installer/install.sh +21 -0
- package/package.json +2 -2
|
Binary file
|
package/installer/install.ps1
CHANGED
|
@@ -8,13 +8,44 @@
|
|
|
8
8
|
# before this script can explain: there, use the `irm | iex` forms)
|
|
9
9
|
#
|
|
10
10
|
# In Command Prompt (cmd.exe), as one line:
|
|
11
|
-
# powershell -NoProfile -
|
|
11
|
+
# powershell -NoProfile -Command "$env:PANTHEON_ARGS='join <invite link>'; irm https://relay.joinpantheon.network/install.ps1 | iex"
|
|
12
12
|
#
|
|
13
13
|
# One line for either window (what `pantheon invite` prints): PowerShell would
|
|
14
14
|
# expand the $env: above inside the outer double quotes before the inner
|
|
15
15
|
# PowerShell runs, so this sets the variable without a $, and with a cmdlet
|
|
16
16
|
# that Constrained Language Mode still allows:
|
|
17
|
-
# powershell -NoProfile -
|
|
17
|
+
# powershell -NoProfile -Command "Set-Item Env:PANTHEON_ARGS 'join <invite link>'; irm https://relay.joinpantheon.network/install.ps1 | iex"
|
|
18
|
+
#
|
|
19
|
+
# Downloaded first (what a coding agent is asked to do, so it can read it), then
|
|
20
|
+
# run from the file the same way, from Git Bash, cmd.exe or PowerShell:
|
|
21
|
+
# curl.exe -fsSL https://relay.joinpantheon.network/install.ps1 -o pantheon-install.ps1
|
|
22
|
+
# powershell -NoProfile -Command "Set-Item Env:PANTHEON_ARGS 'join <invite link>'; Get-Content -Raw pantheon-install.ps1 | iex"
|
|
23
|
+
#
|
|
24
|
+
# None of these needs -ExecutionPolicy Bypass: the execution policy governs
|
|
25
|
+
# running a script FILE (-File, .\script.ps1), and iex runs a string, which no
|
|
26
|
+
# policy looks at, Restricted included. And iex, unlike [scriptblock]::Create,
|
|
27
|
+
# still runs under Constrained Language Mode far enough for the check below to
|
|
28
|
+
# say why the install cannot go on.
|
|
29
|
+
#
|
|
30
|
+
# What Pantheon is: a command-line tool (npm: @join-pantheon/cli) that lets your
|
|
31
|
+
# coding agents (Claude Code, Cursor, Codex, GitHub Copilot, Kiro) hand work and
|
|
32
|
+
# questions to your teammates' agents. Their messages pass through Pantheon's
|
|
33
|
+
# hosted relay, relay.joinpantheon.network, which this computer connects out to
|
|
34
|
+
# as a client, the way a chat app does. This computer runs no relay, opens no
|
|
35
|
+
# port to the network and carries no one else's traffic. (`pantheon board`,
|
|
36
|
+
# when you open it, listens on 127.0.0.1 only, for this computer's own browser.
|
|
37
|
+
# Hosting a relay is a separate, explicit choice: `pantheon relay`.)
|
|
38
|
+
#
|
|
39
|
+
# `start` creates a team; `join` joins one. Either signs you in with a 6-digit
|
|
40
|
+
# code sent to your email, adds a "pantheon" MCP server entry and a session hook
|
|
41
|
+
# to the coding agents it finds. By default it also sets up, for you alone and
|
|
42
|
+
# with no admin rights, one Task Scheduler task ("Pantheon") that starts a
|
|
43
|
+
# hidden background host when you log on. The host runs a worker that can run
|
|
44
|
+
# your own coding agent on a teammate's request while you are away (in a
|
|
45
|
+
# separate copy of the repo, delivering on a branch for your review), desktop
|
|
46
|
+
# alerts (Windows notifications), and a tray icon that opens `pantheon board`.
|
|
47
|
+
# Leave any of it out with --no-worker or --no-notifications; remove it all with
|
|
48
|
+
# pantheon uninstall.
|
|
18
49
|
#
|
|
19
50
|
# `irm | iex` cannot pass arguments, so the arguments for pantheon travel in
|
|
20
51
|
# $env:PANTHEON_ARGS, which reads the same from PowerShell and from cmd.exe.
|
|
@@ -98,6 +129,127 @@ function Install-Pantheon {
|
|
|
98
129
|
[IO.File]::WriteAllText($LogFile, $r.out + $r.err)
|
|
99
130
|
return $r.code
|
|
100
131
|
}
|
|
132
|
+
# This user's logon task: "Pantheon-<SID>" (src/win-host.ts taskNameFor), and
|
|
133
|
+
# "Pantheon", the name builds before 0.33.0 gave it.
|
|
134
|
+
function Get-PantheonTaskNames {
|
|
135
|
+
$names = @()
|
|
136
|
+
try { $names += 'Pantheon-' + [Security.Principal.WindowsIdentity]::GetCurrent().User.Value } catch { $names = @() }
|
|
137
|
+
$names += 'Pantheon'
|
|
138
|
+
return , $names
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# The processes running a program from inside $Dir: what keeps Windows from
|
|
142
|
+
# renaming it. Each as @{ id; name; line } (line: its command line, when Windows says).
|
|
143
|
+
function Get-FolderHolders([string]$Dir) {
|
|
144
|
+
$prefix = $Dir.TrimEnd('\') + '\'
|
|
145
|
+
$out = @()
|
|
146
|
+
foreach ($p in @(Get-Process -ErrorAction SilentlyContinue)) {
|
|
147
|
+
$path = $null
|
|
148
|
+
try { $path = $p.Path } catch { $path = $null }
|
|
149
|
+
if (-not $path -or -not $path.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) { continue }
|
|
150
|
+
$line = ''
|
|
151
|
+
try { $line = [string](Get-CimInstance Win32_Process -Filter ('ProcessId=' + $p.Id) -ErrorAction Stop).CommandLine } catch { $line = '' }
|
|
152
|
+
$out += , @{ id = $p.Id; name = ($p.ProcessName + '.exe'); line = $line }
|
|
153
|
+
}
|
|
154
|
+
return , $out
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# What holds a folder, for a person to read: "node.exe (pid 4242: ...cli.js mcp)".
|
|
158
|
+
function Format-Holders($Holders) {
|
|
159
|
+
(@($Holders) | ForEach-Object {
|
|
160
|
+
$l = [string]$_.line
|
|
161
|
+
if ($l.Length -gt 90) { $l = '...' + $l.Substring($l.Length - 87) }
|
|
162
|
+
if ($l) { '{0} (pid {1}: {2})' -f $_.name, $_.id, $l } else { '{0} (pid {1})' -f $_.name, $_.id }
|
|
163
|
+
}) -join ', '
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# The woken run in flight in this Pantheon home, as src/worker.ts runInFlight
|
|
167
|
+
# reads it: the pid in worker-run.json while the record is fresh (a run lasts
|
|
168
|
+
# at most 20 minutes; 5 more of slack) and that process is alive. $null when
|
|
169
|
+
# there is none, or the record cannot be read.
|
|
170
|
+
function Get-PantheonRunInFlight([string]$RunRecord, [scriptblock]$Alive) {
|
|
171
|
+
if (-not $RunRecord -or -not (Test-Path -LiteralPath $RunRecord -PathType Leaf)) { return $null }
|
|
172
|
+
$runPid = 0; $at = 0.0
|
|
173
|
+
try {
|
|
174
|
+
$r = [IO.File]::ReadAllText($RunRecord) | ConvertFrom-Json
|
|
175
|
+
$runPid = [int]$r.pid; $at = [double]$r.at
|
|
176
|
+
} catch { return $null }
|
|
177
|
+
if ($runPid -le 0 -or $at -le 0) { return $null }
|
|
178
|
+
$now = [double][DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
|
179
|
+
if ($at -gt $now + 60000 -or $now - $at -gt 25 * 60000) { return $null }
|
|
180
|
+
$up = $false
|
|
181
|
+
try { $up = [bool](& $Alive $runPid) } catch { $up = $false }
|
|
182
|
+
if (-not $up) { return $null }
|
|
183
|
+
return $runPid
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Is a task this user's? Its XML (schtasks /Query /TN <name> /XML) names its
|
|
187
|
+
# principal as DOMAIN\user, a bare user name, or a SID; every one of them must
|
|
188
|
+
# be this user (src/win-host.ts taskIsUsers). A task of the same name that
|
|
189
|
+
# another person on this PC registered is never ended.
|
|
190
|
+
function Test-PantheonTaskIsMine([string]$Xml, [string]$UserId, [string]$Sid) {
|
|
191
|
+
if (-not $Xml) { return $false }
|
|
192
|
+
$ids = @([regex]::Matches(($Xml -replace "`0", ''), '<UserId>([^<]*)</UserId>') | ForEach-Object { $_.Groups[1].Value.Trim().ToLowerInvariant() })
|
|
193
|
+
if ($ids.Count -eq 0) { return $false }
|
|
194
|
+
$u = ([string]$UserId).ToLowerInvariant()
|
|
195
|
+
$s = ([string]$Sid).ToLowerInvariant()
|
|
196
|
+
$bare = if ($u.Contains('\')) { $u.Substring($u.LastIndexOf('\') + 1) } else { $u }
|
|
197
|
+
foreach ($id in $ids) {
|
|
198
|
+
if ($u -and $id -eq $u) { continue }
|
|
199
|
+
if ($s -and $id -eq $s) { continue }
|
|
200
|
+
if ($bare -and -not $id.Contains('\') -and -not $id.StartsWith('s-1-') -and $id -eq $bare) { continue }
|
|
201
|
+
return $false
|
|
202
|
+
}
|
|
203
|
+
return $true
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# Before the runtime folder is swapped: Pantheon's background host (the
|
|
207
|
+
# logon task) runs node.exe from it, and Windows will not rename a folder a
|
|
208
|
+
# running program came from. A teammate's request the worker is running
|
|
209
|
+
# (worker-run.json) is waited for first, up to $RunWaitSeconds, and while it
|
|
210
|
+
# still runs nothing is stopped at all: busy comes back, and the runtime stays
|
|
211
|
+
# as it is. Then the host is asked to stop the way `pantheon stop all` asks it
|
|
212
|
+
# (<home>\host\stop), given up to $WaitSeconds to let go, and only then are
|
|
213
|
+
# its tasks ended -- those whose principal is this user ($TaskIsMine), never a
|
|
214
|
+
# task of the same name someone else on this PC registered. Returns
|
|
215
|
+
# @{ restart; holders; busy; tasks }: whether a task is to be started again
|
|
216
|
+
# once the install is done (tasks: which, this user's), and what still holds
|
|
217
|
+
# the folder (an agent window's Pantheon connection, which is not ours to end).
|
|
218
|
+
function Stop-PantheonHost {
|
|
219
|
+
param([string]$NodeDir, [string]$HostDir, [string[]]$Tasks, [scriptblock]$Holders, [scriptblock]$EndTask, [int]$WaitSeconds = 15,
|
|
220
|
+
[string]$RunRecord = '', [scriptblock]$RunAlive = { param($p) $null -ne (Get-Process -Id $p -ErrorAction SilentlyContinue) },
|
|
221
|
+
[int]$RunWaitSeconds = 180, [scriptblock]$TaskIsMine = { param($t) $true }, [scriptblock]$Say = { param($m) Write-Host $m })
|
|
222
|
+
$left = @(& $Holders $NodeDir)
|
|
223
|
+
if ($left.Count -eq 0) { return @{ restart = $false; holders = @(); busy = $false; tasks = @() } }
|
|
224
|
+
if (Get-PantheonRunInFlight $RunRecord $RunAlive) {
|
|
225
|
+
& $Say "A teammate's request is running here; waiting for it to finish before updating (up to $([math]::Ceiling($RunWaitSeconds / 60)) minutes)..."
|
|
226
|
+
$until = (Get-Date).AddSeconds($RunWaitSeconds)
|
|
227
|
+
$running = $true
|
|
228
|
+
while ($running -and (Get-Date) -lt $until) {
|
|
229
|
+
Start-Sleep -Milliseconds 1000
|
|
230
|
+
$running = [bool](Get-PantheonRunInFlight $RunRecord $RunAlive)
|
|
231
|
+
}
|
|
232
|
+
if ($running) { return @{ restart = $false; holders = $left; busy = $true; tasks = @() } }
|
|
233
|
+
$left = @(& $Holders $NodeDir)
|
|
234
|
+
if ($left.Count -eq 0) { return @{ restart = $false; holders = @(); busy = $false; tasks = @() } }
|
|
235
|
+
}
|
|
236
|
+
$hostUp = Test-Path -LiteralPath (Join-Path $HostDir 'state.json')
|
|
237
|
+
if ($hostUp) {
|
|
238
|
+
try { [IO.File]::WriteAllText((Join-Path $HostDir 'stop'), (Get-Date).ToString('o')) } catch { $hostUp = $false }
|
|
239
|
+
}
|
|
240
|
+
$deadline = (Get-Date).AddSeconds($WaitSeconds)
|
|
241
|
+
while ($hostUp -and $left.Count -gt 0 -and (Get-Date) -lt $deadline) {
|
|
242
|
+
Start-Sleep -Milliseconds 500
|
|
243
|
+
$left = @(& $Holders $NodeDir)
|
|
244
|
+
}
|
|
245
|
+
$mine = @($Tasks | Where-Object { $_ } | Where-Object { $ok = $false; try { $ok = [bool](& $TaskIsMine $_) } catch { $ok = $false }; $ok })
|
|
246
|
+
if ($left.Count -gt 0) {
|
|
247
|
+
foreach ($t in $mine) { & $EndTask $t }
|
|
248
|
+
for ($i = 0; $i -lt 6 -and $left.Count -gt 0; $i++) { Start-Sleep -Milliseconds 500; $left = @(& $Holders $NodeDir) }
|
|
249
|
+
}
|
|
250
|
+
return @{ restart = $hostUp; holders = $left; busy = $false; tasks = $mine }
|
|
251
|
+
}
|
|
252
|
+
|
|
101
253
|
# The same, with the output handed back instead: @{ code; out; err }.
|
|
102
254
|
function Invoke-Quiet([string]$Exe, [string[]]$Items) {
|
|
103
255
|
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
@@ -170,6 +322,8 @@ function Install-Pantheon {
|
|
|
170
322
|
$binDir = Join-Path $root 'bin'
|
|
171
323
|
$launcher = Join-Path $binDir 'pantheon.cmd'
|
|
172
324
|
$log = Join-Path $root 'install.log'
|
|
325
|
+
$pantheonHomeDir = if ($env:PANTHEON_HOME) { $env:PANTHEON_HOME } else { Join-Path ([Environment]::GetFolderPath('UserProfile')) '.pantheon' }
|
|
326
|
+
$hostDir = Join-Path $pantheonHomeDir 'host'
|
|
173
327
|
|
|
174
328
|
$State.step = "creating $root"
|
|
175
329
|
try { New-Item -ItemType Directory -Force -Path $runtime, $binDir | Out-Null }
|
|
@@ -212,6 +366,21 @@ function Install-Pantheon {
|
|
|
212
366
|
Fail "could not download $Url ($last). Check your internet connection; behind a proxy set HTTPS_PROXY; then run this again."
|
|
213
367
|
}
|
|
214
368
|
|
|
369
|
+
# The version of the CLI in the runtime, but only when it is whole: its
|
|
370
|
+
# package.json names that version AND it starts and says so. A run stopped
|
|
371
|
+
# halfway through npm can leave the first without the second; that is
|
|
372
|
+
# installed again, never called done. (Defined before step 1: a run that
|
|
373
|
+
# stops there says which version is still in place.)
|
|
374
|
+
$manifest = Join-Path $pkgDir 'package.json'
|
|
375
|
+
$installedCli = {
|
|
376
|
+
$v = ''
|
|
377
|
+
if ((Test-Path -LiteralPath $manifest) -and (Test-Path -LiteralPath $entry)) {
|
|
378
|
+
try { $v = [string](Get-Content -LiteralPath $manifest -Raw | ConvertFrom-Json).version } catch { $v = '' }
|
|
379
|
+
if ($v -and (Invoke-Quiet $nodeExe @($entry, '--version')).out.Trim() -ne $v) { $v = '' }
|
|
380
|
+
}
|
|
381
|
+
$v
|
|
382
|
+
}
|
|
383
|
+
|
|
215
384
|
# ---- 1. Pantheon's own Node --------------------------------------------
|
|
216
385
|
|
|
217
386
|
$haveNode = ''
|
|
@@ -278,6 +447,22 @@ function Install-Pantheon {
|
|
|
278
447
|
$State.step = 'putting Node in place'
|
|
279
448
|
$aside = $null
|
|
280
449
|
if (Test-Path -LiteralPath $nodeDir) {
|
|
450
|
+
# Pantheon's background host runs from this folder: stopped first
|
|
451
|
+
# (and started again at the end), or the folder cannot be moved.
|
|
452
|
+
$State.step = "stopping Pantheon's background host"
|
|
453
|
+
$schtasks = Join-Path $env:SystemRoot 'System32\schtasks.exe'
|
|
454
|
+
$tasks = Get-PantheonTaskNames
|
|
455
|
+
$meUser = ''; $meSid = ''
|
|
456
|
+
try { $meId = [Security.Principal.WindowsIdentity]::GetCurrent(); $meUser = $meId.Name; $meSid = $meId.User.Value } catch { $meUser = $env:USERNAME }
|
|
457
|
+
$stopped = Stop-PantheonHost -NodeDir $nodeDir -HostDir $hostDir -Tasks $tasks `
|
|
458
|
+
-Holders { param($d) Get-FolderHolders $d } -EndTask { param($t) $null = Invoke-Quiet $schtasks @('/End', '/TN', $t) } `
|
|
459
|
+
-RunRecord (Join-Path $pantheonHomeDir 'worker-run.json') `
|
|
460
|
+
-TaskIsMine { param($t) Test-PantheonTaskIsMine (Invoke-Quiet $schtasks @('/Query', '/TN', $t, '/XML')).out $meUser $meSid }
|
|
461
|
+
# A teammate's run still going after the wait: nothing was stopped,
|
|
462
|
+
# and the runtime is left exactly as it is (see the catch below).
|
|
463
|
+
if ($stopped.busy) { throw 'PANTHEON_BUSY:' }
|
|
464
|
+
if ($stopped.restart) { $State.restartTasks = @($stopped.tasks); Say "Stopped Pantheon's background host while its Node is replaced; it starts again when this is done." }
|
|
465
|
+
$State.step = 'putting Node in place'
|
|
281
466
|
$aside = Join-Path $tmp 'old'
|
|
282
467
|
# A virus scanner looking at the new or old node.exe holds it for a
|
|
283
468
|
# moment: a few tries before telling the person something is open.
|
|
@@ -285,7 +470,11 @@ function Install-Pantheon {
|
|
|
285
470
|
for ($i = 1; $i -le 5 -and -not $moved; $i++) {
|
|
286
471
|
try { Move-Item -LiteralPath $nodeDir -Destination $aside; $moved = $true } catch { Start-Sleep -Seconds 2 }
|
|
287
472
|
}
|
|
288
|
-
if (-not $moved) {
|
|
473
|
+
if (-not $moved) {
|
|
474
|
+
$held = Get-FolderHolders $nodeDir
|
|
475
|
+
$what = if (@($held).Count) { 'in use by ' + (Format-Holders $held) + '.' } else { 'in use (by a program Windows did not name; a virus scanner holds it for a moment).' }
|
|
476
|
+
Fail "Pantheon's Node in $nodeDir is $what Close your agent windows (Claude Code, Cursor, VS Code) and any pantheon command, then run this again."
|
|
477
|
+
}
|
|
289
478
|
}
|
|
290
479
|
$placed = $false; $lastErr = ''
|
|
291
480
|
for ($i = 1; $i -le 5 -and -not $placed; $i++) {
|
|
@@ -308,20 +497,6 @@ function Install-Pantheon {
|
|
|
308
497
|
|
|
309
498
|
# ---- 2. the Pantheon CLI, with that Node's own npm -----------------------
|
|
310
499
|
|
|
311
|
-
# The version of the CLI in the runtime, but only when it is whole: its
|
|
312
|
-
# package.json names that version AND it starts and says so. A run stopped
|
|
313
|
-
# halfway through npm can leave the first without the second; that is
|
|
314
|
-
# installed again, never called done.
|
|
315
|
-
$manifest = Join-Path $pkgDir 'package.json'
|
|
316
|
-
$installedCli = {
|
|
317
|
-
$v = ''
|
|
318
|
-
if ((Test-Path -LiteralPath $manifest) -and (Test-Path -LiteralPath $entry)) {
|
|
319
|
-
try { $v = [string](Get-Content -LiteralPath $manifest -Raw | ConvertFrom-Json).version } catch { $v = '' }
|
|
320
|
-
if ($v -and (Invoke-Quiet $nodeExe @($entry, '--version')).out.Trim() -ne $v) { $v = '' }
|
|
321
|
-
}
|
|
322
|
-
$v
|
|
323
|
-
}
|
|
324
|
-
|
|
325
500
|
# What npm installs. PANTHEON_INSTALL_SPEC swaps the registry package for a
|
|
326
501
|
# package file on this machine, so a release candidate goes through this
|
|
327
502
|
# exact script before its version is on npm. Only a full path on a drive
|
|
@@ -387,6 +562,8 @@ function Install-Pantheon {
|
|
|
387
562
|
$cmdText = "@echo off`r`n" +
|
|
388
563
|
"rem The pantheon command, written by the Pantheon installer. It runs Pantheon's own`r`n" +
|
|
389
564
|
"rem Node by absolute path, so it never depends on which node comes first on PATH.`r`n" +
|
|
565
|
+
'rem An upgrade cut off mid-swap leaves the previous version beside the runtime: put it back.' + "`r`n" +
|
|
566
|
+
'if not exist "%~dp0..\runtime\node\node_modules\@join-pantheon\cli\" if exist "%~dp0..\runtime\cli.prev\bin\pantheon.js" move "%~dp0..\runtime\cli.prev" "%~dp0..\runtime\node\node_modules\@join-pantheon\cli" >nul 2>&1' + "`r`n" +
|
|
390
567
|
"`"%~dp0..\runtime\node\node.exe`" $(if ($ca.Count) { '--use-system-ca ' })`"%~dp0..\runtime\node\node_modules\@join-pantheon\cli\bin\pantheon.js`" %*`r`n"
|
|
391
568
|
$current = if (Test-Path -LiteralPath $launcher) { [IO.File]::ReadAllText($launcher) } else { '' }
|
|
392
569
|
if ($current -ne $cmdText) {
|
|
@@ -428,6 +605,9 @@ function Install-Pantheon {
|
|
|
428
605
|
|
|
429
606
|
$State.lock.Dispose()
|
|
430
607
|
$State.lock = $null
|
|
608
|
+
# The background host stopped for the swap starts again, on the new Node.
|
|
609
|
+
Start-PantheonTasks $State
|
|
610
|
+
$State.step = 'starting pantheon'
|
|
431
611
|
if ($Arguments.Count -eq 0) {
|
|
432
612
|
Say ''
|
|
433
613
|
Say "Pantheon $PantheonVersion is installed. Open a new terminal, then:"
|
|
@@ -490,6 +670,16 @@ function Install-Pantheon {
|
|
|
490
670
|
}
|
|
491
671
|
} catch {
|
|
492
672
|
$m = $_.Exception.Message
|
|
673
|
+
if ($m -like 'PANTHEON_BUSY:*') {
|
|
674
|
+
# Not a failure: the Pantheon already here keeps working. Only when there
|
|
675
|
+
# is none that runs is the exit code non-zero.
|
|
676
|
+
$have = ''
|
|
677
|
+
if ($installedCli) { try { $have = & $installedCli } catch { $have = '' } }
|
|
678
|
+
$kept = if ($have) { " Pantheon $have is still installed and working." } else { '' }
|
|
679
|
+
Write-Host "Pantheon was not updated because a teammate's run was in progress; run the same line again in a few minutes.$kept" -ForegroundColor Yellow
|
|
680
|
+
$State.code = if ($have) { 0 } else { 1 }
|
|
681
|
+
return
|
|
682
|
+
}
|
|
493
683
|
if ($m -like 'PANTHEON_FAIL:*') { $m = $m.Substring('PANTHEON_FAIL:'.Length) }
|
|
494
684
|
else { $m = "it stopped while $($State.step) ($m). Run the same command again; if it fails the same way, send this line to sid@joinpantheon.network" }
|
|
495
685
|
Write-Host "pantheon install failed: $m" -ForegroundColor Red
|
|
@@ -497,7 +687,33 @@ function Install-Pantheon {
|
|
|
497
687
|
} finally {
|
|
498
688
|
# Every way out, Ctrl-C included, lets go of the install lock.
|
|
499
689
|
if ($State.lock) { $State.lock.Dispose(); $State.lock = $null }
|
|
690
|
+
# And starts again a background host this install stopped (on whichever Node is in place).
|
|
691
|
+
Start-PantheonTasks $State
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
# Start again the logon task Stop-PantheonHost stopped: this user's first task
|
|
696
|
+
# that starts. Once only. Its host removes the stop request when it starts.
|
|
697
|
+
function Start-PantheonTasks([hashtable]$State) {
|
|
698
|
+
if (-not $State.ContainsKey('restartTasks') -or $State.restarted) { return }
|
|
699
|
+
$State.restarted = $true
|
|
700
|
+
$schtasks = Join-Path $env:SystemRoot 'System32\schtasks.exe'
|
|
701
|
+
foreach ($t in $State.restartTasks) {
|
|
702
|
+
try {
|
|
703
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
704
|
+
$psi.FileName = $schtasks
|
|
705
|
+
$psi.Arguments = '/Run /TN "' + $t + '"'
|
|
706
|
+
$psi.UseShellExecute = $false
|
|
707
|
+
$psi.CreateNoWindow = $true
|
|
708
|
+
$psi.RedirectStandardOutput = $true
|
|
709
|
+
$psi.RedirectStandardError = $true
|
|
710
|
+
$p = [System.Diagnostics.Process]::Start($psi)
|
|
711
|
+
$null = $p.StandardOutput.ReadToEnd(); $null = $p.StandardError.ReadToEnd()
|
|
712
|
+
$p.WaitForExit()
|
|
713
|
+
if ($p.ExitCode -eq 0) { Write-Host "Started Pantheon's background host again ($t)."; return }
|
|
714
|
+
} catch { }
|
|
500
715
|
}
|
|
716
|
+
Write-Host "note: Pantheon's background host did not start again by itself; it starts at your next logon, or now with: pantheon service start worker" -ForegroundColor Yellow
|
|
501
717
|
}
|
|
502
718
|
|
|
503
719
|
# `join '<link>'` as two arguments: whitespace splits, and single or double
|
|
@@ -548,7 +764,7 @@ $PantheonInstallCode = [int]$PantheonInstallState.code
|
|
|
548
764
|
# result is left in $LASTEXITCODE instead.
|
|
549
765
|
$PantheonOneShot = $false
|
|
550
766
|
try { $PantheonOneShot = Test-PantheonOneShot ([Environment]::GetCommandLineArgs()) } catch { $PantheonOneShot = $false }
|
|
551
|
-
Remove-Item Function:Install-Pantheon, Function:Split-PantheonArgs, Function:Test-PantheonOneShot -ErrorAction SilentlyContinue
|
|
767
|
+
Remove-Item Function:Install-Pantheon, Function:Start-PantheonTasks, Function:Split-PantheonArgs, Function:Test-PantheonOneShot -ErrorAction SilentlyContinue
|
|
552
768
|
Remove-Variable PantheonInstallArgs, PantheonInstallState -ErrorAction SilentlyContinue
|
|
553
769
|
$global:LASTEXITCODE = $PantheonInstallCode
|
|
554
770
|
if ($PantheonOneShot) { exit $PantheonInstallCode }
|
package/installer/install.sh
CHANGED
|
@@ -5,6 +5,25 @@
|
|
|
5
5
|
# curl -fsSL https://relay.joinpantheon.network/install.sh | sh -s -- start
|
|
6
6
|
# curl -fsSL https://relay.joinpantheon.network/install.sh | sh (install only)
|
|
7
7
|
#
|
|
8
|
+
# What Pantheon is: a command-line tool (npm: @join-pantheon/cli) that lets your
|
|
9
|
+
# coding agents (Claude Code, Cursor, Codex, GitHub Copilot, Kiro) hand work and
|
|
10
|
+
# questions to your teammates' agents. Their messages pass through Pantheon's
|
|
11
|
+
# hosted relay, relay.joinpantheon.network, which this computer connects out to
|
|
12
|
+
# as a client, the way a chat app does. This computer runs no relay, opens no
|
|
13
|
+
# port to the network and carries no one else's traffic. (`pantheon board`,
|
|
14
|
+
# when you open it, listens on 127.0.0.1 only, for this computer's own browser.
|
|
15
|
+
# Hosting a relay is a separate, explicit choice: `pantheon relay`.)
|
|
16
|
+
#
|
|
17
|
+
# `start` creates a team; `join` joins one. Either signs you in with a 6-digit
|
|
18
|
+
# code sent to your email, adds a "pantheon" MCP server entry and a session hook
|
|
19
|
+
# to the coding agents it finds, and by default sets up background helpers, each
|
|
20
|
+
# of which can be left out (--no-worker, --no-menubar, --no-notifications): a
|
|
21
|
+
# worker that can run your own coding agent on a teammate's request while you
|
|
22
|
+
# are away, in a separate copy of the repo, delivering on a branch for your
|
|
23
|
+
# review; desktop alerts (on macOS, and on a Linux desktop through its own
|
|
24
|
+
# notifications); and the menu bar on macOS. On Linux, `pantheon board` shows
|
|
25
|
+
# the same in a browser tab.
|
|
26
|
+
#
|
|
8
27
|
# What it does, and nothing else:
|
|
9
28
|
# 1. downloads one pinned Node release from nodejs.org, checks its sha256
|
|
10
29
|
# against the sha256 pinned in this file (nodejs.org's own), and unpacks it into
|
|
@@ -428,6 +447,8 @@ STEP="writing $LAUNCHER"
|
|
|
428
447
|
printf '# The pantheon command, written by the Pantheon installer. It runs\n'
|
|
429
448
|
printf "# Pantheon's own Node by absolute path, so it never depends on which\n"
|
|
430
449
|
printf '# node comes first on PATH. Running the installer again rewrites it.\n'
|
|
450
|
+
printf '# An upgrade cut off mid-swap leaves the previous version beside the runtime: put it back.\n'
|
|
451
|
+
printf '[ -e %s ] || { [ -f %s ] && mv %s %s 2>/dev/null; }\n' "$(shq "$PKG_DIR")" "$(shq "$RUNTIME/cli.prev/bin/pantheon.js")" "$(shq "$RUNTIME/cli.prev")" "$(shq "$PKG_DIR")"
|
|
431
452
|
printf 'exec %s %s%s "$@"\n' "$(shq "$NODE")" "${ca:+$ca }" "$(shq "$PKG_DIR/bin/pantheon.js")"
|
|
432
453
|
} >"$TMP_DIR/launcher" || fail "could not write the pantheon command. Check free disk space, then run this again."
|
|
433
454
|
chmod 755 "$TMP_DIR/launcher"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@join-pantheon/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0-rc.1",
|
|
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": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p .",
|
|
14
14
|
"dev": "tsc -p . --watch",
|
|
15
15
|
"relay": "node dist/cli.js relay",
|
|
16
|
-
"test": "PANTHEON_HOME=\"$(mktemp -d)\" HOME=\"$(mktemp -d)\" node --test --test-force-exit dist/test/*.test.js",
|
|
16
|
+
"test": "PANTHEON_HOME=\"$(mktemp -d)\" HOME=\"$(mktemp -d)\" node --test --test-force-exit --import ./scripts/test-isolate.mjs dist/test/*.test.js",
|
|
17
17
|
"prepare": "npm run build",
|
|
18
18
|
"build:helpers": "node scripts/build-helpers.mjs",
|
|
19
19
|
"release:check": "node scripts/release-check.mjs",
|