@join-pantheon/cli 0.33.1 → 0.33.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 +6 -5
- package/dist/accounts.js +10 -0
- package/dist/agents.js +11 -0
- package/dist/board.js +55 -9
- package/dist/claude-confine.js +1 -1
- package/dist/cli.js +556 -89
- package/dist/client.js +8 -4
- package/dist/config.js +18 -4
- package/dist/continued.js +5 -0
- package/dist/desktop-app.js +103 -0
- package/dist/doctor.js +84 -32
- package/dist/folder-guard.js +12 -0
- package/dist/folder-snapshot.js +432 -0
- package/dist/install.js +15 -8
- package/dist/machine.js +84 -4
- package/dist/managed.js +2 -2
- package/dist/mcp.js +5 -3
- package/dist/network.js +156 -0
- package/dist/notice-linux.js +6 -5
- package/dist/notice-windows.js +2 -2
- package/dist/notice.js +13 -0
- package/dist/notifier.js +29 -2
- package/dist/notify.js +11 -0
- package/dist/onboard-defaults.js +37 -1
- package/dist/pins.js +7 -1
- package/dist/plans.js +23 -0
- package/dist/protocol.js +5 -0
- package/dist/quickstart.js +17 -11
- package/dist/refusal.js +4 -4
- package/dist/relay.js +332 -22
- package/dist/repo-find.js +6 -3
- package/dist/roster-file.js +50 -5
- package/dist/tray.js +12 -1
- package/dist/uninstall.js +38 -3
- package/dist/uri-handler.js +3 -2
- package/dist/verify-act.js +17 -0
- package/dist/worker-fleet.js +140 -5
- package/dist/worker.js +201 -63
- package/installer/install.ps1 +251 -15
- package/installer/install.sh +147 -18
- package/package.json +1 -1
- package/helper/macos/prebuilt/manifest.json +0 -29
- package/helper/macos/prebuilt/pantheon-hud +0 -0
- package/helper/macos/prebuilt/pantheon-tray +0 -0
package/installer/install.ps1
CHANGED
|
@@ -190,6 +190,168 @@ function Install-Pantheon {
|
|
|
190
190
|
return $mine
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
# An app package's folder name as a person knows the app: Claude_pzs8sxrjxfjjc
|
|
194
|
+
# is the Claude desktop app; Publisher.Some.App_8wekyb3d8bbwe is Some.App.
|
|
195
|
+
function Get-PantheonAppName([string]$Package) {
|
|
196
|
+
if ($Package -match '^Claude_') { return 'the Claude desktop app' }
|
|
197
|
+
if ($Package) { return (($Package -replace '_[A-Za-z0-9]+$', '') -replace '^[A-Za-z0-9]+\.', '') }
|
|
198
|
+
return ''
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
# Copies of Pantheon that only an app could see: an installer before 0.33.1
|
|
202
|
+
# run from inside an app package (the Claude desktop app's agent) put
|
|
203
|
+
# Pantheon in %LOCALAPPDATA%\Pantheon, which Windows kept in that app's
|
|
204
|
+
# private AppData, %LOCALAPPDATA%\Packages\<package>\LocalCache\Local\Pantheon.
|
|
205
|
+
# Nothing outside the app ever ran it; it only takes space (npm's cache) and
|
|
206
|
+
# can confuse the app's agent. Pantheon's only by what the installer puts
|
|
207
|
+
# there (the launcher, the CLI, the install lock), never a folder another app
|
|
208
|
+
# happens to call Pantheon; and never through a link (junction or symlink) at
|
|
209
|
+
# any step of the path, which would lead somewhere else. Each as @{ path; package }.
|
|
210
|
+
function Find-PantheonAppCopies([string]$Local) {
|
|
211
|
+
$out = @()
|
|
212
|
+
if (-not $Local) { return , $out }
|
|
213
|
+
$pk = Join-Path $Local 'Packages'
|
|
214
|
+
if (-not (Test-Path -LiteralPath $pk -PathType Container)) { return , $out }
|
|
215
|
+
foreach ($f in @(Get-ChildItem -LiteralPath $pk -Directory -Force -ErrorAction SilentlyContinue)) {
|
|
216
|
+
$local2 = [IO.Path]::Combine($f.FullName, 'LocalCache', 'Local')
|
|
217
|
+
if (-not (Test-Path -LiteralPath $local2 -PathType Container)) { continue }
|
|
218
|
+
# The copy, and what a removal cut off halfway left beside it (named below, so ours by name).
|
|
219
|
+
$dirs = @()
|
|
220
|
+
$dir = Join-Path $local2 'Pantheon'
|
|
221
|
+
if (Test-Path -LiteralPath $dir -PathType Container) {
|
|
222
|
+
$ours = (Test-Path -LiteralPath ([IO.Path]::Combine($dir, 'bin', 'pantheon.cmd'))) -or
|
|
223
|
+
(Test-Path -LiteralPath ([IO.Path]::Combine($dir, 'runtime', 'node', 'node_modules', '@join-pantheon'))) -or
|
|
224
|
+
(Test-Path -LiteralPath ([IO.Path]::Combine($dir, 'runtime', 'install.lock')))
|
|
225
|
+
if ($ours) { $dirs += $dir }
|
|
226
|
+
}
|
|
227
|
+
# One suffix or more: a removal cut off twice by a build before 0.33.2 renamed it again.
|
|
228
|
+
$dirs += @(Get-ChildItem -LiteralPath $local2 -Directory -Force -ErrorAction SilentlyContinue |
|
|
229
|
+
Where-Object { $_.Name -match '^Pantheon(\.remove-[0-9a-f]{6})+$' } | ForEach-Object { $_.FullName })
|
|
230
|
+
foreach ($d in $dirs) {
|
|
231
|
+
$linked = $false
|
|
232
|
+
# Every step from the Packages folder down: a link at Packages itself, or
|
|
233
|
+
# at the package's folder, leads somewhere else just the same.
|
|
234
|
+
foreach ($step in @($pk, $f.FullName, [IO.Path]::Combine($f.FullName, 'LocalCache'), $local2, $d)) {
|
|
235
|
+
$item = $null
|
|
236
|
+
try { $item = Get-Item -LiteralPath $step -Force -ErrorAction Stop } catch { $item = $null }
|
|
237
|
+
if (-not $item -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) { $linked = $true; break }
|
|
238
|
+
}
|
|
239
|
+
if (-not $linked) { $out += , @{ path = $d; package = $f.Name } }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return , $out
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# A full path in the \\?\ form, which Windows takes past 260 characters.
|
|
246
|
+
function ConvertTo-PantheonLongPath([string]$Path) {
|
|
247
|
+
if ($Path.StartsWith('\\?\')) { return $Path }
|
|
248
|
+
if ($Path.StartsWith('\\')) { return '\\?\UNC\' + $Path.Substring(2) }
|
|
249
|
+
return '\\?\' + $Path
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# rd /s /q by the \\?\ form of the path. Windows PowerShell 5.1's Remove-Item
|
|
253
|
+
# stops at 260 characters, and npm's cache inside such a copy goes deeper
|
|
254
|
+
# (removing one by hand took exactly this). The path is in double quotes,
|
|
255
|
+
# where cmd.exe reads & and ^ as they are. '' when the folder went; otherwise
|
|
256
|
+
# what rd said.
|
|
257
|
+
function Remove-PantheonTree([string]$Path) {
|
|
258
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
259
|
+
$psi.FileName = Join-Path $env:SystemRoot 'System32\cmd.exe'
|
|
260
|
+
$psi.Arguments = '/d /c rd /s /q "' + (ConvertTo-PantheonLongPath $Path) + '"'
|
|
261
|
+
$psi.UseShellExecute = $false
|
|
262
|
+
$psi.CreateNoWindow = $true
|
|
263
|
+
$psi.RedirectStandardInput = $true
|
|
264
|
+
$psi.RedirectStandardOutput = $true
|
|
265
|
+
$psi.RedirectStandardError = $true
|
|
266
|
+
$p = [System.Diagnostics.Process]::Start($psi)
|
|
267
|
+
$p.StandardInput.Close()
|
|
268
|
+
$out = $p.StandardOutput.ReadToEndAsync()
|
|
269
|
+
$err = $p.StandardError.ReadToEndAsync()
|
|
270
|
+
$p.WaitForExit()
|
|
271
|
+
if (-not (Test-Path -LiteralPath $Path)) { return '' }
|
|
272
|
+
$said = (($err.Result + ' ' + $out.Result) -replace '\s+', ' ').Trim()
|
|
273
|
+
if ($said) { return $said }
|
|
274
|
+
return "rd exited $($p.ExitCode)"
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Each copy nothing runs from is removed. First it is renamed beside itself
|
|
278
|
+
# ($Rename): Windows refuses to rename a folder a running program came from,
|
|
279
|
+
# however deep, which tells "in use" even where an app's private copy shows a
|
|
280
|
+
# running program under another path; and a copy cut off halfway is then no
|
|
281
|
+
# longer one anything starts. Then rd ($Remove: Remove-PantheonTree). A copy
|
|
282
|
+
# still in use is left as it is, with what runs from it ($Holders:
|
|
283
|
+
# Get-FolderHolders) when Windows says. Returns @{ removed = @(@{ path; package });
|
|
284
|
+
# held = @(@{ path; package; holders }); failed = @(@{ path; package; why }) }.
|
|
285
|
+
function Remove-PantheonAppCopies([object[]]$Copies, [scriptblock]$Holders, [scriptblock]$Remove,
|
|
286
|
+
[scriptblock]$Rename = { param($from, $to) Move-Item -LiteralPath $from -Destination $to -ErrorAction Stop }, [int]$Tries = 3) {
|
|
287
|
+
$removed = @(); $held = @(); $failed = @()
|
|
288
|
+
foreach ($c in @($Copies | Where-Object { $_ })) {
|
|
289
|
+
$h = @()
|
|
290
|
+
try { $h = @(& $Holders $c.path | ForEach-Object { $_ }) } catch { $h = @() }
|
|
291
|
+
# A copy already renamed aside (a removal cut off before) is renamed to a
|
|
292
|
+
# fresh name of the same form, never given a second suffix the next run
|
|
293
|
+
# would not know as Pantheon's.
|
|
294
|
+
$aside = ($c.path -replace '(\.remove-[0-9a-f]{6})+$', '') + '.remove-' + [Guid]::NewGuid().ToString('N').Substring(0, 6)
|
|
295
|
+
$moved = $false
|
|
296
|
+
if ($h.Count -eq 0) {
|
|
297
|
+
for ($i = 1; $i -le $Tries -and -not $moved; $i++) {
|
|
298
|
+
try { & $Rename $c.path $aside; $moved = $true } catch { if ($i -lt $Tries) { Start-Sleep -Milliseconds 700 } }
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (-not $moved) { $held += , @{ path = $c.path; package = $c.package; holders = $h }; continue }
|
|
302
|
+
$why = ''
|
|
303
|
+
try { $why = [string](& $Remove $aside) } catch { $why = $_.Exception.Message }
|
|
304
|
+
if (Test-Path -LiteralPath $aside) {
|
|
305
|
+
if (-not $why) { $why = 'some of it is still there' }
|
|
306
|
+
$failed += , @{ path = $aside; package = $c.package; why = $why }
|
|
307
|
+
} else { $removed += , @{ path = $c.path; package = $c.package } }
|
|
308
|
+
}
|
|
309
|
+
return @{ removed = $removed; held = $held; failed = $failed }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
# What a shell is, from its program's name: the syntax its next command
|
|
313
|
+
# is said in. '' when it cannot be told.
|
|
314
|
+
function Get-PantheonShellKind([string]$Name) {
|
|
315
|
+
$n = ([string]$Name).ToLowerInvariant() -replace '\.exe$', ''
|
|
316
|
+
if ($n -eq 'cmd') { return 'cmd' }
|
|
317
|
+
if ($n -in @('powershell', 'pwsh', 'powershell_ise')) { return 'powershell' }
|
|
318
|
+
if ($n -in @('bash', 'sh', 'zsh', 'dash', 'fish', 'mintty', 'git-bash')) { return 'bash' }
|
|
319
|
+
return ''
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
# The program that started this PowerShell (cmd.exe, when the installer
|
|
323
|
+
# was run as powershell -Command "..." from Command Prompt). '' when
|
|
324
|
+
# Windows will not say.
|
|
325
|
+
function Get-PantheonParentName {
|
|
326
|
+
try {
|
|
327
|
+
$me = Get-CimInstance Win32_Process -Filter ('ProcessId=' + $PID) -ErrorAction Stop
|
|
328
|
+
$up = Get-CimInstance Win32_Process -Filter ('ProcessId=' + [int]$me.ParentProcessId) -ErrorAction Stop
|
|
329
|
+
return [string]$up.Name
|
|
330
|
+
} catch { return '' }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# A pantheon command line as typed in $Shell: by name ($Launcher '') where
|
|
334
|
+
# pantheon is already on that window's PATH, otherwise by the launcher's
|
|
335
|
+
# full path in that shell's own syntax. Arguments are quoted only where
|
|
336
|
+
# that shell needs it, so the line reads as typed. A bash on Windows (Git
|
|
337
|
+
# Bash) runs a .cmd only by its full name, and reads C:/ paths, so it gets
|
|
338
|
+
# one of those.
|
|
339
|
+
function Format-PantheonCommand([string]$Launcher, [string[]]$Arguments, [string]$Shell) {
|
|
340
|
+
$plain = '^[A-Za-z0-9_./:=@+,-]+$'
|
|
341
|
+
$items = @($Arguments | Where-Object { $null -ne $_ })
|
|
342
|
+
if ($Shell -eq 'cmd') {
|
|
343
|
+
$head = if ($Launcher) { '"' + $Launcher + '"' } else { 'pantheon' }
|
|
344
|
+
$rest = @($items | ForEach-Object { if ($_ -match $plain) { $_ } else { '"' + $_ + '"' } })
|
|
345
|
+
} elseif ($Shell -eq 'bash') {
|
|
346
|
+
$head = if ($Launcher) { "'" + (($Launcher -replace '\\', '/') -replace "'", "'\''") + "'" } else { 'pantheon' }
|
|
347
|
+
$rest = @($items | ForEach-Object { if ($_ -match $plain) { $_ } else { "'" + ($_ -replace "'", "'\''") + "'" } })
|
|
348
|
+
} else {
|
|
349
|
+
$head = if ($Launcher) { "& '" + ($Launcher -replace "'", "''") + "'" } else { 'pantheon' }
|
|
350
|
+
$rest = @($items | ForEach-Object { if ($_ -match $plain) { $_ } else { "'" + ($_ -replace "'", "''") + "'" } })
|
|
351
|
+
}
|
|
352
|
+
return ((@($head) + @($rest)) -join ' ')
|
|
353
|
+
}
|
|
354
|
+
|
|
193
355
|
# The user PATH, set from OUTSIDE this app's package. Inside one, Windows 10
|
|
194
356
|
# keeps HKCU changes private to the app (Windows 11 lets this key through),
|
|
195
357
|
# so a terminal opened from the Start menu would not find pantheon. Task
|
|
@@ -390,7 +552,7 @@ function Install-Pantheon {
|
|
|
390
552
|
param([string]$NodeDir, [string]$HostDir, [string[]]$Tasks, [scriptblock]$Holders, [scriptblock]$EndTask, [int]$WaitSeconds = 15,
|
|
391
553
|
[string]$RunRecord = '', [scriptblock]$RunAlive = { param($p) $null -ne (Get-Process -Id $p -ErrorAction SilentlyContinue) },
|
|
392
554
|
[int]$RunWaitSeconds = 180, [scriptblock]$TaskIsMine = { param($t) $true }, [scriptblock]$Say = { param($m) Write-Host $m })
|
|
393
|
-
$left = @(& $Holders $NodeDir)
|
|
555
|
+
$left = @(& $Holders $NodeDir | ForEach-Object { $_ })
|
|
394
556
|
if ($left.Count -eq 0) { return @{ restart = $false; holders = @(); busy = $false; tasks = @() } }
|
|
395
557
|
if (Get-PantheonRunInFlight $RunRecord $RunAlive) {
|
|
396
558
|
& $Say "A teammate's request is running here; waiting for it to finish before updating (up to $([math]::Ceiling($RunWaitSeconds / 60)) minutes)..."
|
|
@@ -401,7 +563,7 @@ function Install-Pantheon {
|
|
|
401
563
|
$running = [bool](Get-PantheonRunInFlight $RunRecord $RunAlive)
|
|
402
564
|
}
|
|
403
565
|
if ($running) { return @{ restart = $false; holders = $left; busy = $true; tasks = @() } }
|
|
404
|
-
$left = @(& $Holders $NodeDir)
|
|
566
|
+
$left = @(& $Holders $NodeDir | ForEach-Object { $_ })
|
|
405
567
|
if ($left.Count -eq 0) { return @{ restart = $false; holders = @(); busy = $false; tasks = @() } }
|
|
406
568
|
}
|
|
407
569
|
$hostUp = Test-Path -LiteralPath (Join-Path $HostDir 'state.json')
|
|
@@ -411,12 +573,12 @@ function Install-Pantheon {
|
|
|
411
573
|
$deadline = (Get-Date).AddSeconds($WaitSeconds)
|
|
412
574
|
while ($hostUp -and $left.Count -gt 0 -and (Get-Date) -lt $deadline) {
|
|
413
575
|
Start-Sleep -Milliseconds 500
|
|
414
|
-
$left = @(& $Holders $NodeDir)
|
|
576
|
+
$left = @(& $Holders $NodeDir | ForEach-Object { $_ })
|
|
415
577
|
}
|
|
416
578
|
$mine = @($Tasks | Where-Object { $_ } | Where-Object { $ok = $false; try { $ok = [bool](& $TaskIsMine $_) } catch { $ok = $false }; $ok })
|
|
417
579
|
if ($left.Count -gt 0) {
|
|
418
580
|
foreach ($t in $mine) { & $EndTask $t }
|
|
419
|
-
for ($i = 0; $i -lt 6 -and $left.Count -gt 0; $i++) { Start-Sleep -Milliseconds 500; $left = @(& $Holders $NodeDir) }
|
|
581
|
+
for ($i = 0; $i -lt 6 -and $left.Count -gt 0; $i++) { Start-Sleep -Milliseconds 500; $left = @(& $Holders $NodeDir | ForEach-Object { $_ }) }
|
|
420
582
|
}
|
|
421
583
|
return @{ restart = $hostUp; holders = $left; busy = $false; tasks = $mine }
|
|
422
584
|
}
|
|
@@ -489,11 +651,13 @@ function Install-Pantheon {
|
|
|
489
651
|
$signedIn = $false
|
|
490
652
|
try { $signedIn = [bool]((Get-Content -LiteralPath (Join-Path $pantheonHome 'config.json') -Raw | ConvertFrom-Json).account) } catch { $signedIn = $false }
|
|
491
653
|
if (-not $signedIn) {
|
|
492
|
-
#
|
|
493
|
-
|
|
654
|
+
# By the launcher's full path, as typed in PowerShell: whatever runs it
|
|
655
|
+
# next (an agent's shell, a window opened before this install) may not
|
|
656
|
+
# have the new PATH yet.
|
|
657
|
+
$show = Format-PantheonCommand $launcher $Arguments 'powershell'
|
|
494
658
|
$what = if ($Arguments[0] -eq 'join') { 'joining' } else { 'creating your team' }
|
|
495
659
|
Say ''
|
|
496
|
-
Say "Pantheon is installed. To finish $what, open PowerShell and run:
|
|
660
|
+
Say "Pantheon is installed. To finish $what, open PowerShell and run: $show"
|
|
497
661
|
}
|
|
498
662
|
}
|
|
499
663
|
} finally {
|
|
@@ -549,7 +713,7 @@ function Install-Pantheon {
|
|
|
549
713
|
if (-not $profileDir) { Fail 'USERPROFILE is not set, so there is nowhere to install. Run this from your own Windows account.' }
|
|
550
714
|
# Inside an app package (the Claude desktop app): said once, and acted on below.
|
|
551
715
|
$appPackage = Get-PantheonAppPackage $localAppData
|
|
552
|
-
$appName =
|
|
716
|
+
$appName = Get-PantheonAppName $appPackage
|
|
553
717
|
$where = Select-PantheonRoot $profileDir $localAppData $appPackage
|
|
554
718
|
$root = $where.root
|
|
555
719
|
# A release before 0.33.1 does not know ~\.pantheon (it would not find itself to
|
|
@@ -573,6 +737,38 @@ function Install-Pantheon {
|
|
|
573
737
|
$binDir = Join-Path $root 'bin'
|
|
574
738
|
$launcher = Join-Path $binDir 'pantheon.cmd'
|
|
575
739
|
$log = Join-Path $root 'install.log'
|
|
740
|
+
|
|
741
|
+
# The window the person ran this from, and how pantheon is typed there
|
|
742
|
+
# once this is done. An open PowerShell window running irm | iex is this
|
|
743
|
+
# very process, whose PATH is set below: pantheon by name. Run as
|
|
744
|
+
# powershell -Command "..." from another window (Command Prompt, a
|
|
745
|
+
# PowerShell window, Git Bash, an agent's shell), that window keeps the PATH
|
|
746
|
+
# it started with, so pantheon is "not recognized" there: by name only if
|
|
747
|
+
# pantheon already meant this launcher in it (asked now, before this
|
|
748
|
+
# script changes PATH), otherwise by its full path in that window's syntax.
|
|
749
|
+
$oneShot = $false
|
|
750
|
+
try { $oneShot = Test-PantheonOneShot ([Environment]::GetCommandLineArgs()) -OrFile } catch { $oneShot = $false }
|
|
751
|
+
$shellKind = 'here'
|
|
752
|
+
$byPath = ''
|
|
753
|
+
if ($oneShot) {
|
|
754
|
+
$shellKind = Get-PantheonShellKind (Get-PantheonParentName)
|
|
755
|
+
$known = $null
|
|
756
|
+
try { $known = Get-Command pantheon -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 } catch { $known = $null }
|
|
757
|
+
if ($shellKind -eq 'bash' -or -not ($known -and $known.Source -ieq $launcher)) { $byPath = $launcher }
|
|
758
|
+
}
|
|
759
|
+
# The next command for that window. Where the shell cannot be told, in
|
|
760
|
+
# PowerShell's syntax, and the note below gives Command Prompt's.
|
|
761
|
+
$nextFor = {
|
|
762
|
+
param([string[]]$Items)
|
|
763
|
+
if ($shellKind -eq 'here') { return Format-PantheonCommand '' $Items 'powershell' }
|
|
764
|
+
if ($shellKind) { return Format-PantheonCommand $byPath $Items $shellKind }
|
|
765
|
+
return Format-PantheonCommand $byPath $Items 'powershell'
|
|
766
|
+
}
|
|
767
|
+
$byPathNote = {
|
|
768
|
+
if (-not $byPath) { return }
|
|
769
|
+
$cmdToo = if ($shellKind) { '' } else { " (in Command Prompt: $(Format-PantheonCommand $byPath @() 'cmd'))" }
|
|
770
|
+
Say "(This window was open before Pantheon was on your PATH, so that is its full path$cmdToo; a new window finds it as pantheon.)"
|
|
771
|
+
}
|
|
576
772
|
$pantheonHomeDir = if ($env:PANTHEON_HOME) { $env:PANTHEON_HOME } else { Join-Path ([Environment]::GetFolderPath('UserProfile')) '.pantheon' }
|
|
577
773
|
$hostDir = Join-Path $pantheonHomeDir 'host'
|
|
578
774
|
|
|
@@ -867,6 +1063,28 @@ function Install-Pantheon {
|
|
|
867
1063
|
Where-Object { $_.Source -and -not $_.Source.StartsWith($root + '\', [StringComparison]::OrdinalIgnoreCase) } | Select-Object -First 1
|
|
868
1064
|
if ($other) { Note "another pantheon is installed at $($other.Source) and was left as it is. New terminals run $launcher first, unless that one is on the system PATH." }
|
|
869
1065
|
|
|
1066
|
+
# ---- old copies that only an app could see -------------------------------
|
|
1067
|
+
|
|
1068
|
+
# Only from outside an app package: inside one, its private copy is what
|
|
1069
|
+
# this very app may be running, and Select-PantheonRoot already passes it over.
|
|
1070
|
+
if (-not $appPackage) {
|
|
1071
|
+
$State.step = 'removing old copies of Pantheon that only an app could see'
|
|
1072
|
+
$copies = Find-PantheonAppCopies $localAppData
|
|
1073
|
+
if ($copies.Count) {
|
|
1074
|
+
$gone = Remove-PantheonAppCopies $copies { param($d) Get-FolderHolders $d } { param($d) Remove-PantheonTree $d }
|
|
1075
|
+
foreach ($c in $gone.removed) { Say "Removed an old copy of Pantheon that only $(Get-PantheonAppName $c.package) could see: $($c.path)" }
|
|
1076
|
+
foreach ($c in $gone.held) {
|
|
1077
|
+
$n = Get-PantheonAppName $c.package
|
|
1078
|
+
$what = if (@($c.holders).Count) { Format-Holders $c.holders } else { "a program (Windows did not say which; most likely $n)" }
|
|
1079
|
+
Note "an old copy of Pantheon that only $n could see is in $($c.path), and $what runs from it. Quit $n, then run this installer again to remove it."
|
|
1080
|
+
}
|
|
1081
|
+
foreach ($c in $gone.failed) {
|
|
1082
|
+
$n = Get-PantheonAppName $c.package
|
|
1083
|
+
Note "an old copy of Pantheon that only $n could see was only partly removed: $($c.path) ($($c.why)). Quit $n, then run this installer again to finish."
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
870
1088
|
# ---- 5. hand over to pantheon ------------------------------------------
|
|
871
1089
|
|
|
872
1090
|
$State.lock.Dispose()
|
|
@@ -919,17 +1137,26 @@ function Install-Pantheon {
|
|
|
919
1137
|
Say ''
|
|
920
1138
|
if ($member) {
|
|
921
1139
|
Say "Pantheon $PantheonVersion is installed, and you are still signed in as $member -- nothing to rejoin."
|
|
922
|
-
Say
|
|
1140
|
+
Say "Restart any agent window that was open and carry on. To check everything, in this window: $(& $nextFor @('doctor'))"
|
|
923
1141
|
} else {
|
|
924
|
-
Say "Pantheon $PantheonVersion is installed.
|
|
925
|
-
|
|
926
|
-
|
|
1142
|
+
Say "Pantheon $PantheonVersion is installed. Next, in this window:"
|
|
1143
|
+
$startLine = & $nextFor @('start')
|
|
1144
|
+
$joinLine = & $nextFor @('join', '<invite link>')
|
|
1145
|
+
# The start line padded to the join line's width, so the descriptions line up.
|
|
1146
|
+
Say (' ' + $startLine.PadRight($joinLine.Length) + ' create a team')
|
|
1147
|
+
Say (' ' + $joinLine + " join a teammate's team")
|
|
927
1148
|
}
|
|
1149
|
+
& $byPathNote
|
|
928
1150
|
$State.code = 0
|
|
929
1151
|
return
|
|
930
1152
|
}
|
|
931
1153
|
Say ''
|
|
932
1154
|
Invoke-PantheonHandoff $nodeExe $entry $ca $Arguments
|
|
1155
|
+
if ($State.code -eq 0) {
|
|
1156
|
+
Say ''
|
|
1157
|
+
Say "Next, in this window: $(& $nextFor @('doctor')) (checks that everything works)"
|
|
1158
|
+
& $byPathNote
|
|
1159
|
+
}
|
|
933
1160
|
} catch {
|
|
934
1161
|
$m = $_.Exception.Message
|
|
935
1162
|
if ($m -like 'PANTHEON_BUSY:*') {
|
|
@@ -947,6 +1174,11 @@ function Install-Pantheon {
|
|
|
947
1174
|
try {
|
|
948
1175
|
Say ''
|
|
949
1176
|
Invoke-PantheonHandoff $nodeExe $entry $caNow $Arguments
|
|
1177
|
+
if ($State.code -eq 0 -and $nextFor) {
|
|
1178
|
+
Say ''
|
|
1179
|
+
Say "Next, in this window: $(& $nextFor @('doctor')) (checks that everything works)"
|
|
1180
|
+
& $byPathNote
|
|
1181
|
+
}
|
|
950
1182
|
} catch {
|
|
951
1183
|
Write-Host "pantheon install failed: it stopped while starting pantheon ($($_.Exception.Message)). Open a new terminal and run your pantheon command again." -ForegroundColor Red
|
|
952
1184
|
$State.code = 1
|
|
@@ -1016,15 +1248,19 @@ function Split-PantheonArgs([string]$Line) {
|
|
|
1016
1248
|
# (`powershell -Command "... | iex"` from cmd.exe), where the exit code is the
|
|
1017
1249
|
# result, rather than an open window, which `exit` would close. PowerShell
|
|
1018
1250
|
# takes any prefix of a parameter's name, after -, -- or /; -NoExit anywhere
|
|
1019
|
-
# keeps the window open, so then it is never one-shot.
|
|
1020
|
-
|
|
1251
|
+
# keeps the window open, so then it is never one-shot. -OrFile also counts a
|
|
1252
|
+
# script run with -File (or -f): the window it was started from keeps its own
|
|
1253
|
+
# PATH, so the next command names the launcher by path there. Only for that
|
|
1254
|
+
# line: the script may be someone's own that runs this installer and goes on,
|
|
1255
|
+
# which `exit` would end.
|
|
1256
|
+
function Test-PantheonOneShot([string[]]$CommandLine, [switch]$OrFile) {
|
|
1021
1257
|
$flags = @($CommandLine | Select-Object -Skip 1 | Where-Object { $_ -match '^(--?|/)[A-Za-z]+$' } |
|
|
1022
1258
|
ForEach-Object { ($_ -replace '^(--?|/)', '').ToLowerInvariant() })
|
|
1023
1259
|
$prefixOf = { param($f, $name, $min) $f.Length -ge $min -and $name.StartsWith($f) }
|
|
1024
1260
|
if ($flags | Where-Object { & $prefixOf $_ 'noexit' 3 }) { return $false }
|
|
1025
1261
|
foreach ($f in $flags) {
|
|
1026
1262
|
if ((& $prefixOf $f 'command' 1) -or (& $prefixOf $f 'commandwithargs' 8) -or $f -eq 'cwa' -or
|
|
1027
|
-
(& $prefixOf $f 'encodedcommand' 1) -or $f -eq 'ec') { return $true }
|
|
1263
|
+
(& $prefixOf $f 'encodedcommand' 1) -or $f -eq 'ec' -or ($OrFile -and (& $prefixOf $f 'file' 1))) { return $true }
|
|
1028
1264
|
}
|
|
1029
1265
|
return $false
|
|
1030
1266
|
}
|
package/installer/install.sh
CHANGED
|
@@ -541,7 +541,81 @@ add_block() {
|
|
|
541
541
|
added="$added ${f#"$HOME"/}"
|
|
542
542
|
}
|
|
543
543
|
|
|
544
|
-
|
|
544
|
+
# The shell this account logs in with: what a new terminal starts. $SHELL is
|
|
545
|
+
# only the shell of whatever ran this installer, and an app or an agent passes
|
|
546
|
+
# its own (/bin/sh) or none: on 6 Oct a new Mac (no ~/.zshrc yet) installed
|
|
547
|
+
# from an agent got no zsh block, and a new Terminal said "pantheon: command
|
|
548
|
+
# not found". So the account's own record first: macOS's directory (dscl),
|
|
549
|
+
# Linux's passwd (getent, else /etc/passwd); $SHELL only when neither says.
|
|
550
|
+
account_shell() {
|
|
551
|
+
me=$(id -un 2>/dev/null || printf '%s' "${USER:-}")
|
|
552
|
+
s=""
|
|
553
|
+
if [ -n "$me" ]; then
|
|
554
|
+
if [ "$node_os" = darwin ]; then
|
|
555
|
+
s=$(dscl . -read "/Users/$me" UserShell 2>/dev/null | sed -n 's/^UserShell:[[:space:]]*//p' | head -n 1 || true)
|
|
556
|
+
else
|
|
557
|
+
s=$(getent passwd "$me" 2>/dev/null | cut -d: -f7 || true)
|
|
558
|
+
[ -n "$s" ] || s=$(awk -F: -v u="$me" '$1 == u { print $7; exit }' /etc/passwd 2>/dev/null || true)
|
|
559
|
+
fi
|
|
560
|
+
fi
|
|
561
|
+
[ -n "$s" ] || s=${SHELL:-}
|
|
562
|
+
basename "${s:-sh}"
|
|
563
|
+
}
|
|
564
|
+
login_shell=$(account_shell)
|
|
565
|
+
# zsh reads .zshrc and .zprofile from $ZDOTDIR when that is set (usually in
|
|
566
|
+
# ~/.zshenv), not from HOME: asked of zsh itself, which reads ~/.zshenv as
|
|
567
|
+
# every new zsh does. After a marker, so whatever .zshenv prints is not taken.
|
|
568
|
+
# A .zshenv can wait for input (from the terminal, past a stdin of /dev/null)
|
|
569
|
+
# or block outright, so zsh is asked in the background, into a file (a pipe a
|
|
570
|
+
# leftover child kept open would wait with it), and killed after 5 s; then the
|
|
571
|
+
# answer is this environment's $ZDOTDIR, else HOME. POSIX only: macOS has no
|
|
572
|
+
# `timeout`. The watchdog's own output goes nowhere, so nothing waits on it.
|
|
573
|
+
zdot=$HOME
|
|
574
|
+
z=${ZDOTDIR:-}
|
|
575
|
+
if command -v zsh >/dev/null 2>&1; then
|
|
576
|
+
: >"$TMP_DIR/zdotdir"
|
|
577
|
+
zsh -c 'printf "\n__pantheon_zdotdir__%s\n" "${ZDOTDIR:-}"' </dev/null >"$TMP_DIR/zdotdir" 2>/dev/null &
|
|
578
|
+
zsh_pid=$!
|
|
579
|
+
(sleep 5; : >"$TMP_DIR/zsh-slow"; kill -9 "$zsh_pid") </dev/null >/dev/null 2>&1 &
|
|
580
|
+
watch_pid=$!
|
|
581
|
+
wait "$zsh_pid" 2>/dev/null || true
|
|
582
|
+
kill "$watch_pid" 2>/dev/null || true
|
|
583
|
+
if grep -q '^__pantheon_zdotdir__' "$TMP_DIR/zdotdir" 2>/dev/null; then
|
|
584
|
+
z=$(sed -n 's/^__pantheon_zdotdir__//p' "$TMP_DIR/zdotdir" | tail -n 1)
|
|
585
|
+
elif [ -f "$TMP_DIR/zsh-slow" ]; then
|
|
586
|
+
note "zsh did not start within 5 seconds (something in ~/.zshenv waits), so its folder was taken from \$ZDOTDIR here, or the home folder"
|
|
587
|
+
fi
|
|
588
|
+
fi
|
|
589
|
+
# A folder's real path (cd ... && pwd -P); one not made yet is its nearest existing
|
|
590
|
+
# parent's real path and the rest, which must then hold no . or .. to be judged.
|
|
591
|
+
real_dir() {
|
|
592
|
+
rd=$1
|
|
593
|
+
rest=""
|
|
594
|
+
while [ -n "$rd" ] && [ "$rd" != / ] && [ ! -d "$rd" ]; do
|
|
595
|
+
rest="/$(basename "$rd")$rest"
|
|
596
|
+
rd=$(dirname "$rd")
|
|
597
|
+
done
|
|
598
|
+
case "$rest" in */. | */./* | */.. | */../*) return 1 ;; esac
|
|
599
|
+
rd=$(cd "$rd" 2>/dev/null && pwd -P) || return 1
|
|
600
|
+
printf '%s%s' "${rd%/}" "$rest"
|
|
601
|
+
}
|
|
602
|
+
# Only a folder inside HOME, by its real path: $HOME/../bob, or a link in HOME to
|
|
603
|
+
# a folder elsewhere, is outside, and nothing is written outside the home folder.
|
|
604
|
+
# Named under HOME as given, so the list of files changed reads as the person knows it.
|
|
605
|
+
z=${z%/}
|
|
606
|
+
if [ -n "$z" ] && [ "$z" != "$HOME" ]; then
|
|
607
|
+
zr=$(real_dir "$z" || true)
|
|
608
|
+
hr=$(real_dir "$HOME" || true)
|
|
609
|
+
[ -n "$hr" ] || hr=$HOME
|
|
610
|
+
case "$zr" in
|
|
611
|
+
"$hr"/?*) zdot="$HOME${zr#"$hr"}" ;;
|
|
612
|
+
*)
|
|
613
|
+
if [ "$zr" != "$hr" ]; then
|
|
614
|
+
note "zsh reads its startup files from $z (ZDOTDIR), outside your home folder, so nothing was written there; to have pantheon in new zsh terminals, put $BIN_DIR at the front of PATH in $z/.zshrc yourself"
|
|
615
|
+
fi
|
|
616
|
+
;;
|
|
617
|
+
esac
|
|
618
|
+
fi
|
|
545
619
|
# ~/.profile: read by sh and dash logins, by bash when it has no .bash_profile or .bash_login,
|
|
546
620
|
# and by the `sh -lc` Pantheon itself uses to find programs.
|
|
547
621
|
add_block "$HOME/.profile"
|
|
@@ -549,11 +623,23 @@ add_block "$HOME/.profile"
|
|
|
549
623
|
# bash logins read the first of .bash_profile, .bash_login and .profile: with
|
|
550
624
|
# no .bash_profile, an existing .bash_login is the one read, not .profile.
|
|
551
625
|
[ -f "$HOME/.bash_login" ] && add_block "$HOME/.bash_login"
|
|
626
|
+
# An interactive bash that is not a login (most Linux terminals) reads only .bashrc.
|
|
552
627
|
{ [ -f "$HOME/.bashrc" ] || [ "$login_shell" = bash ]; } && add_block "$HOME/.bashrc"
|
|
553
|
-
|
|
554
|
-
|
|
628
|
+
# zsh, login or not, reads .zshrc. A Mac's terminals start zsh unless the
|
|
629
|
+
# person chose otherwise, and a new Mac has no .zshrc: there it is always
|
|
630
|
+
# written (made when missing), whatever this installer was run from.
|
|
631
|
+
if [ -f "$zdot/.zshrc" ] || [ "$login_shell" = zsh ] || [ "$node_os" = darwin ]; then
|
|
632
|
+
mkdir -p "$zdot" 2>/dev/null || true
|
|
633
|
+
add_block "$zdot/.zshrc"
|
|
634
|
+
fi
|
|
635
|
+
[ -f "$zdot/.zprofile" ] && add_block "$zdot/.zprofile"
|
|
636
|
+
# With ZDOTDIR elsewhere, a block already in HOME's own zsh files is kept current.
|
|
637
|
+
if [ "$zdot" != "$HOME" ]; then
|
|
638
|
+
[ -f "$HOME/.zshrc" ] && add_block "$HOME/.zshrc"
|
|
639
|
+
[ -f "$HOME/.zprofile" ] && add_block "$HOME/.zprofile"
|
|
640
|
+
fi
|
|
555
641
|
# fish reads its own conf.d folder; the file there is ours whole.
|
|
556
|
-
if command -v fish >/dev/null 2>&1 || [ -d "$HOME/.config/fish" ]; then
|
|
642
|
+
if command -v fish >/dev/null 2>&1 || [ -d "$HOME/.config/fish" ] || [ "$login_shell" = fish ]; then
|
|
557
643
|
fish_conf="$HOME/.config/fish/conf.d/pantheon.fish"
|
|
558
644
|
# shellcheck disable=SC2016 # written literally: fish expands it, not this script
|
|
559
645
|
{
|
|
@@ -577,6 +663,28 @@ if [ -n "$other" ] && [ "$other" != "$LAUNCHER" ]; then
|
|
|
577
663
|
note "another pantheon is installed at $other and was left as it is. New terminals run $LAUNCHER first."
|
|
578
664
|
fi
|
|
579
665
|
|
|
666
|
+
# The next command, for the terminal this was run from. That terminal read its
|
|
667
|
+
# PATH before the profiles above changed, so unless pantheon already meant this
|
|
668
|
+
# launcher there, the line names it by its full path; a new terminal finds it
|
|
669
|
+
# by name. Quoted only where a shell needs it, so it reads as typed.
|
|
670
|
+
shown() {
|
|
671
|
+
case "$1" in
|
|
672
|
+
"" | *[!A-Za-z0-9_./:=@%+,-]*) shq "$1" ;;
|
|
673
|
+
*) printf '%s' "$1" ;;
|
|
674
|
+
esac
|
|
675
|
+
}
|
|
676
|
+
if [ "$other" = "$LAUNCHER" ]; then PANTHEON_HERE="pantheon"; else PANTHEON_HERE=$(shown "$LAUNCHER"); fi
|
|
677
|
+
next_line() {
|
|
678
|
+
line="$PANTHEON_HERE"
|
|
679
|
+
for a in "$@"; do line="$line $(shown "$a")"; done
|
|
680
|
+
printf '%s' "$line"
|
|
681
|
+
}
|
|
682
|
+
by_path_note() {
|
|
683
|
+
if [ "$PANTHEON_HERE" != "pantheon" ]; then
|
|
684
|
+
say "(This terminal was open before Pantheon was on your PATH, so that is its full path; a new terminal finds it as pantheon.)"
|
|
685
|
+
fi
|
|
686
|
+
}
|
|
687
|
+
|
|
580
688
|
# ---- 5. hand over to pantheon ----------------------------------------------
|
|
581
689
|
|
|
582
690
|
rm -rf "$TMP_DIR"
|
|
@@ -586,9 +694,14 @@ trap - EXIT INT TERM HUP
|
|
|
586
694
|
|
|
587
695
|
if [ "$#" -eq 0 ]; then
|
|
588
696
|
say ""
|
|
589
|
-
say "Pantheon $PANTHEON_VERSION is installed.
|
|
590
|
-
|
|
591
|
-
|
|
697
|
+
say "Pantheon $PANTHEON_VERSION is installed. Next, in this terminal:"
|
|
698
|
+
start_line=$(next_line start)
|
|
699
|
+
join_line="$PANTHEON_HERE join '<invite link>'"
|
|
700
|
+
# The start line padded to the join line's width, so the descriptions line up.
|
|
701
|
+
while [ "${#start_line}" -lt "${#join_line}" ]; do start_line="$start_line "; done
|
|
702
|
+
say " $start_line create a team"
|
|
703
|
+
say " $join_line join a teammate's team"
|
|
704
|
+
by_path_note
|
|
592
705
|
exit 0
|
|
593
706
|
fi
|
|
594
707
|
|
|
@@ -596,12 +709,32 @@ say ""
|
|
|
596
709
|
# Found first by anything pantheon starts during setup, before any new terminal.
|
|
597
710
|
PATH="$BIN_DIR:$PATH"
|
|
598
711
|
export PATH
|
|
712
|
+
# Once pantheon has done what was asked, the last line is the next command for
|
|
713
|
+
# this terminal. Not exec, so there is a last line; Ctrl-C still reaches
|
|
714
|
+
# pantheon (a trap is reset in a child, where an ignored signal would not be)
|
|
715
|
+
# and this shell waits for it rather than dying first.
|
|
716
|
+
finish() {
|
|
717
|
+
if [ "$1" -eq 0 ]; then
|
|
718
|
+
say ""
|
|
719
|
+
say "Next, in this terminal: $(next_line doctor) (checks that everything works)"
|
|
720
|
+
by_path_note
|
|
721
|
+
fi
|
|
722
|
+
exit "$1"
|
|
723
|
+
}
|
|
599
724
|
# stdin is the curl pipe; sign-in and setup questions must come from the
|
|
600
725
|
# keyboard.
|
|
601
726
|
if (: </dev/tty) 2>/dev/null; then
|
|
602
|
-
|
|
727
|
+
trap ':' INT
|
|
728
|
+
code=0
|
|
729
|
+
"$LAUNCHER" "$@" </dev/tty || code=$?
|
|
730
|
+
finish "$code"
|
|
731
|
+
fi
|
|
732
|
+
if [ -t 0 ]; then
|
|
733
|
+
trap ':' INT
|
|
734
|
+
code=0
|
|
735
|
+
"$LAUNCHER" "$@" || code=$?
|
|
736
|
+
finish "$code"
|
|
603
737
|
fi
|
|
604
|
-
if [ -t 0 ]; then exec "$LAUNCHER" "$@"; fi
|
|
605
738
|
|
|
606
739
|
# No terminal at all: CI, a pipe, some IDE task runners. pantheon still runs,
|
|
607
740
|
# because it never asks a question without one (every prompt checks
|
|
@@ -629,14 +762,10 @@ if [ "$code" -ne 0 ] && [ -n "$said" ] && grep -qE 'sign in first|NOT_SIGNED_IN'
|
|
|
629
762
|
join | start)
|
|
630
763
|
cfg="${PANTHEON_HOME:-$HOME/.pantheon}/config.json"
|
|
631
764
|
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
|
|
632
|
-
|
|
633
|
-
#
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
"" | *[!A-Za-z0-9_./:=@%+,-]*) line="$line $(shq "$a")" ;;
|
|
637
|
-
*) line="$line $a" ;;
|
|
638
|
-
esac
|
|
639
|
-
done
|
|
765
|
+
# By its full path: whatever runs it next (an agent's shell, a terminal
|
|
766
|
+
# opened before this install) may not have the new PATH yet.
|
|
767
|
+
line=$(shown "$LAUNCHER")
|
|
768
|
+
for a in "$@"; do line="$line $(shown "$a")"; done
|
|
640
769
|
if [ "$1" = join ]; then what="joining"; else what="creating your team"; fi
|
|
641
770
|
printf '\nPantheon is installed. To finish %s, open a terminal and run: %s\n' "$what" "$line" >&2
|
|
642
771
|
fi
|
|
@@ -644,4 +773,4 @@ if [ "$code" -ne 0 ] && [ -n "$said" ] && grep -qE 'sign in first|NOT_SIGNED_IN'
|
|
|
644
773
|
esac
|
|
645
774
|
fi
|
|
646
775
|
if [ -n "$said" ]; then rm -f "$said" "$said.code"; fi
|
|
647
|
-
|
|
776
|
+
finish "$code"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@join-pantheon/cli",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.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": {
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"format": 1,
|
|
3
|
-
"built_at": "2026-10-05T17:23:08.044Z",
|
|
4
|
-
"swiftc": "Apple Swift version 6.3.3 (swiftlang-6.3.3.1.3 clang-2100.1.1.101)",
|
|
5
|
-
"helpers": {
|
|
6
|
-
"pantheon-tray": {
|
|
7
|
-
"source": "menubar.swift",
|
|
8
|
-
"source_sha256": "f079629099a81f9b679ef4133d897ceb09025f675099c5f941951604df3531fa",
|
|
9
|
-
"sha256": "cc081120555f528c83b38951663d37e8394e3a3e119a19b0bb3b2a737f4d617f",
|
|
10
|
-
"archs": [
|
|
11
|
-
"arm64",
|
|
12
|
-
"x86_64"
|
|
13
|
-
],
|
|
14
|
-
"target": "macos11",
|
|
15
|
-
"version": "2"
|
|
16
|
-
},
|
|
17
|
-
"pantheon-hud": {
|
|
18
|
-
"source": "main.swift",
|
|
19
|
-
"source_sha256": "bcf55a19b658d1f09a9d117c76030393ab08a2569062dd7413f884ca05f3a0eb",
|
|
20
|
-
"sha256": "191f3f18e8212e11ad4438bdbef87a5cc533f5ae2fae208801db01c9a265193b",
|
|
21
|
-
"archs": [
|
|
22
|
-
"arm64",
|
|
23
|
-
"x86_64"
|
|
24
|
-
],
|
|
25
|
-
"target": "macos11",
|
|
26
|
-
"version": "4"
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
Binary file
|
|
Binary file
|