@agentshouse/kit 0.1.0-alpha.40 → 0.1.0-alpha.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,20 +13,20 @@ The Skill set Kit installs is Matt Pocock's
13
13
  Linux (`amd64`, `arm64`) or macOS (Apple silicon, Intel):
14
14
 
15
15
  ```sh
16
- bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-linux.sh)
16
+ bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.42/connect-linux.sh)
17
17
  ```
18
18
 
19
19
  Windows 11 (`amd64`), in PowerShell:
20
20
 
21
21
  ```powershell
22
- & ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-windows.ps1)))
22
+ & ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.42/connect-windows.ps1)))
23
23
  ```
24
24
 
25
- The command sets up Docker if it is missing, creates `~/.house-kit` (Kit home)
26
- and `~/AgentsHouse` (your workspace), signs this computer in to House in your
27
- browser, and starts Kit in a container that comes back after a restart. Running
28
- it again is safe: it continues where it stopped. Running a newer release's
29
- command is the update.
25
+ The command sets up Docker if it is missing, creates `~/.house-kit` (Kit home;
26
+ on Windows the Docker volume `house-kit-home`) and `~/AgentsHouse` (your
27
+ workspace), signs this computer in to House in your browser, and starts Kit in
28
+ a container that comes back after a restart. Running it again is safe: it
29
+ continues where it stopped. Running a newer release's command is the update.
30
30
 
31
31
  Options:
32
32
 
@@ -3,6 +3,8 @@ $ProgressPreference = 'SilentlyContinue'
3
3
 
4
4
  $Image = 'ghcr.io/agentshouse/kit@sha256:__IMAGE_DIGEST__'
5
5
  $Name = 'house-kit'
6
+ $KitHome = 'house-kit-home'
7
+ $KitHomeMount = "type=volume,src=$KitHome,dst=/kit-home"
6
8
  $BootstrapArguments = @($args | ForEach-Object { [string]$_ })
7
9
  $MinimumBuild = 22631
8
10
  $MinimumWsl = [version]'2.1.5'
@@ -180,8 +182,25 @@ function Protect-Directory([string] $Path) {
180
182
  $item.FullName.TrimEnd('\')
181
183
  }
182
184
 
183
- function Write-Text([string] $Path, [string] $Text) {
184
- [IO.File]::WriteAllText($Path, $Text, (New-Object Text.UTF8Encoding($false)))
185
+ function Invoke-KitHome([string] $Failure, [string] $Script) {
186
+ $ErrorActionPreference = 'Continue'
187
+ $output = @($input | & docker run -i --rm --network none --mount $KitHomeMount --entrypoint sh $Image -c $Script 2>&1 | ForEach-Object { "$_" })
188
+ if ($LASTEXITCODE -ne 0) { Stop-Bootstrap "${Failure}: $(@($output | Where-Object { $_ })[-1])" }
189
+ $output
190
+ }
191
+
192
+ function Read-KitHome {
193
+ $files = @{}
194
+ $listed = Invoke-KitHome 'the Kit home could not be read' 'cd /kit-home && for name in environment house kit.json; do if [ -f $name ]; then echo $name $(base64 -w 0 $name); fi; done && if [ -f credential ]; then echo credential; fi'
195
+ foreach ($line in @($listed | Where-Object { $_ -match '^(credential|environment|house|kit\.json)( [A-Za-z0-9+/=]+)?$' })) {
196
+ $file, $encoded = $line -split ' ', 2
197
+ $files[$file] = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String("$encoded"))
198
+ }
199
+ $files
200
+ }
201
+
202
+ function Write-KitHome([string] $File, [string] $Text) {
203
+ [void]([Convert]::ToBase64String((New-Object Text.UTF8Encoding($false)).GetBytes($Text)) | Invoke-KitHome "$File could not be updated" "umask 077 && base64 -di > /kit-home/$File")
185
204
  }
186
205
 
187
206
  function Read-Inspect([string] $Format) {
@@ -284,49 +303,43 @@ function Connect-Kit {
284
303
  if ($docker.Platform -ne 'linux/x86_64' -and $docker.Platform -ne 'linux/amd64') { Stop-Bootstrap "host linux/amd64 does not match Docker Desktop $($docker.Platform)" }
285
304
  if ($Forwarding) {
286
305
  if (-not (Test-Container)) { Stop-Bootstrap 'House Kit is not installed; run this bootstrap without arguments first' }
306
+ if ((Read-Inspect '{{.Config.Image}}') -cne $Image) { Stop-Bootstrap 'an installed Kit uses a different image; rerun this bootstrap without arguments to update it' }
287
307
  $Workspace = Read-Source '/agents/house'
288
308
  if ($Workspace -notmatch '^[A-Za-z]:\\' -or -not (Test-Path -LiteralPath $Workspace -PathType Container)) {
289
309
  Stop-Bootstrap "the installed Kit's workspace root $Workspace is not a directory on this computer; rerun this bootstrap without arguments"
290
310
  }
311
+ } else {
312
+ Invoke-Docker volume create $KitHome | Out-Null
313
+ if ((Invoke-Native docker @('image', 'inspect', '--format', '{{.Id}}', $Image)).Status -ne 0) { Invoke-Docker pull --quiet --platform linux/amd64 $Image | Out-Null }
291
314
  }
292
315
  if (-not $Workspace) { $Workspace = Join-Path $env:USERPROFILE 'AgentsHouse' }
293
316
 
294
317
  $User = [Security.Principal.WindowsIdentity]::GetCurrent().User
295
- $kitHome = Protect-Directory (Join-Path $env:USERPROFILE '.house-kit')
296
318
  $Workspace = Protect-Directory $Workspace
297
- $KitHomeMount = "type=bind,src=$kitHome,dst=/kit-home"
298
319
  $WorkspaceMount = "type=bind,src=$Workspace,dst=/agents/house"
299
- $credential = Join-Path $kitHome 'credential'
300
- $environment = Join-Path $kitHome 'environment'
301
- $bound = Join-Path $kitHome 'house'
302
- $configuration = Join-Path $kitHome 'kit.json'
303
- if (((Test-Path -LiteralPath $credential) -or (Test-Path -LiteralPath $environment)) -and -not (Test-Path -LiteralPath $bound)) {
320
+ $stored = Read-KitHome
321
+ if (($stored.ContainsKey('credential') -or $stored.ContainsKey('environment')) -and -not $stored.ContainsKey('house')) {
304
322
  Stop-Bootstrap 'stored House origin is missing for an enrolled Environment'
305
323
  }
306
- if (Test-Path -LiteralPath $bound) {
307
- $boundHouse = @([IO.File]::ReadAllLines($bound))
308
- if ($boundHouse.Count -eq 0) { Stop-Bootstrap 'stored House origin is unreadable' }
309
- if ($HouseSelected -and $House -cne $boundHouse[0]) { Stop-Bootstrap 'this Environment is bound to another House origin' }
310
- $House = $boundHouse[0]
324
+ if ($stored.ContainsKey('house')) {
325
+ $boundHouse = @($stored['house'] -split '\r?\n')[0]
326
+ if (-not $boundHouse) { Stop-Bootstrap 'stored House origin is unreadable' }
327
+ if ($HouseSelected -and $House -cne $boundHouse) { Stop-Bootstrap 'this Environment is bound to another House origin' }
328
+ $House = $boundHouse
311
329
  }
312
330
  if ($House -notmatch '^(https://|http://127\.0\.0\.1:|http://localhost:)') { Stop-Bootstrap 'House origin must use HTTPS or local loopback HTTP' }
313
331
  if (-not $Forwarding) {
314
- try {
315
- if (-not (Test-Path -LiteralPath $configuration)) {
316
- Write-Text $configuration "{`"availability`":`"intermittent`",`"execution_authority`":`"allow`",`"migrators`":[],`"instances`":[],`"clients`":[],`"host`":null,`"skills`":$Skills}`n"
332
+ $text = "{`"availability`":`"intermittent`",`"execution_authority`":`"allow`",`"migrators`":[],`"instances`":[],`"clients`":[],`"host`":null,`"skills`":$Skills}`n"
333
+ if ($stored.ContainsKey('kit.json')) {
334
+ $text = $stored['kit.json']
335
+ if ($text -match '"skills"\s*:') {
336
+ $text = $text -replace '("skills"\s*:\s*)(true|false)', "`${1}$Skills"
317
337
  } else {
318
- $text = [IO.File]::ReadAllText($configuration)
319
- if ($text -match '"skills"\s*:') {
320
- $text = $text -replace '("skills"\s*:\s*)(true|false)', "`${1}$Skills"
321
- } else {
322
- $text = $text -replace '^(\s*)\{', "`${1}{`"skills`":$Skills,"
323
- }
324
- Write-Text $configuration $text
338
+ $text = $text -replace '^(\s*)\{', "`${1}{`"skills`":$Skills,"
325
339
  }
326
- } catch {
327
- Stop-Bootstrap 'kit.json could not be updated'
328
340
  }
329
- if ([IO.File]::ReadAllText($configuration) -notmatch "`"skills`"\s*:\s*$Skills") { Stop-Bootstrap 'kit.json could not be updated' }
341
+ if ($text -notmatch "`"skills`"\s*:\s*$Skills") { Stop-Bootstrap 'kit.json could not be updated' }
342
+ Write-KitHome 'kit.json' $text
330
343
  }
331
344
  $Network = @('--network', 'host')
332
345
  $Published = @()
@@ -339,15 +352,14 @@ function Connect-Kit {
339
352
 
340
353
  if (Test-Container) {
341
354
  $installed = Read-Inspect '{{.Config.Image}}'
342
- if ($Forwarding -and $installed -cne $Image) { Stop-Bootstrap 'an installed Kit uses a different image; rerun this bootstrap without arguments to update it' }
343
355
  if ((Read-Inspect '{{.HostConfig.RestartPolicy.Name}}') -cne 'unless-stopped') { Stop-Bootstrap 'the installed Kit has a different lifecycle' }
344
356
  if ((Read-Inspect '{{len .Mounts}}') -cne '2') { Stop-Bootstrap 'the installed Kit has unexpected mounts' }
345
- if ((Read-Source '/kit-home') -ine $kitHome) { Stop-Bootstrap 'the installed Kit uses another Kit home' }
346
- if ((Read-Inspect '{{range .Mounts}}{{if eq .Destination `/kit-home`}}{{.Type}}/{{.RW}}{{end}}{{end}}') -cne 'bind/true') { Stop-Bootstrap 'the installed Kit home is not a writable bind mount' }
357
+ if ((Read-Inspect '{{range .Mounts}}{{if eq .Destination `/kit-home`}}{{.Name}}{{end}}{{end}}') -cne $KitHome) { Stop-Bootstrap 'the installed Kit uses another Kit home' }
358
+ if ((Read-Inspect '{{range .Mounts}}{{if eq .Destination `/kit-home`}}{{.Type}}/{{.RW}}{{end}}{{end}}') -cne 'volume/true') { Stop-Bootstrap 'the installed Kit home is not a writable volume' }
347
359
  if ((Read-Source '/agents/house') -ine $Workspace) { Stop-Bootstrap 'the installed Kit uses another workspace root' }
348
360
  if ((Read-Inspect '{{range .Mounts}}{{if eq .Destination `/agents/house`}}{{.Type}}/{{.RW}}{{end}}{{end}}') -cne 'bind/true') { Stop-Bootstrap 'the installed workspace is not a writable bind mount' }
349
361
  if ((Read-Inspect '{{index .Config.Labels `agentshouse.house`}}') -cne $House) { Stop-Bootstrap 'the installed Kit uses another House origin' }
350
- if (-not ((Test-Path -LiteralPath $credential) -and (Test-Path -LiteralPath $environment) -and (Test-Path -LiteralPath $bound))) { Stop-Bootstrap 'the installed Kit has no enrolled authority' }
362
+ if (-not ($stored.ContainsKey('credential') -and $stored.ContainsKey('environment') -and $stored.ContainsKey('house'))) { Stop-Bootstrap 'the installed Kit has no enrolled authority' }
351
363
  if ($Forwarding) {
352
364
  if ($Forwarded -ceq 'kit' -and $Forward.Count -gt 0 -and $Forward[0] -ceq 'login') {
353
365
  Invoke-Login @($Forward | Select-Object -Skip 1)
@@ -373,26 +385,24 @@ function Connect-Kit {
373
385
  Exit-Bootstrap $LASTEXITCODE
374
386
  }
375
387
  if ($installed -cne $Image) {
376
- Invoke-Docker pull --quiet --platform linux/amd64 $Image | Out-Null
377
388
  if ((Read-Inspect '{{.State.Running}}') -ceq 'true') { Invoke-Docker exec $Name kit resident drain }
378
389
  Invoke-Docker rm -f $Name | Out-Null
379
- Remove-Item -LiteralPath (Join-Path $kitHome 'state\update-drain') -Force -ErrorAction SilentlyContinue
390
+ [void](Invoke-KitHome 'the Kit home could not be updated' 'rm -f /kit-home/state/update-drain')
380
391
  Start-Resident
381
- Write-Host "House Kit updated for Environment $([IO.File]::ReadAllText($environment).Trim())."
392
+ Write-Host "House Kit updated for Environment $($stored['environment'].Trim())."
382
393
  return
383
394
  }
384
395
  Invoke-Docker run --rm @Network @Common credential check
385
396
  if ((Read-Inspect '{{.State.Running}}') -ceq 'true') {
386
- Write-Host "House Kit is already running for Environment $([IO.File]::ReadAllText($environment).Trim())."
397
+ Write-Host "House Kit is already running for Environment $($stored['environment'].Trim())."
387
398
  } else {
388
399
  Invoke-Docker start $Name | Out-Null
389
- Write-Host "House Kit restarted for Environment $([IO.File]::ReadAllText($environment).Trim())."
400
+ Write-Host "House Kit restarted for Environment $($stored['environment'].Trim())."
390
401
  }
391
402
  return
392
403
  }
393
404
 
394
- Invoke-Docker pull --quiet --platform linux/amd64 $Image | Out-Null
395
- if (Test-Path -LiteralPath $credential) {
405
+ if ($stored.ContainsKey('credential')) {
396
406
  Invoke-Docker run --rm @Network @Common credential check
397
407
  } else {
398
408
  $login = @()
@@ -402,7 +412,7 @@ function Connect-Kit {
402
412
  }
403
413
  Invoke-Docker run --rm @Network @Common apply
404
414
  Start-Resident
405
- Write-Host "House Kit connected for Environment $([IO.File]::ReadAllText($environment).Trim())."
415
+ Write-Host "House Kit connected for Environment $((Read-KitHome)['environment'].Trim())."
406
416
  }
407
417
 
408
418
  $consoleEncoding = [Console]::OutputEncoding
package/bootstrap.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.1.0-alpha.40",
3
+ "version": "0.1.0-alpha.42",
4
4
  "hosts": {
5
5
  "linux": {
6
- "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-linux.sh)",
6
+ "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.42/connect-linux.sh)",
7
7
  "asset": {
8
8
  "name": "connect-linux.sh",
9
- "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-linux.sh"
9
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.42/connect-linux.sh"
10
10
  },
11
11
  "workspace": {
12
12
  "argument": "--workspace",
@@ -14,10 +14,10 @@
14
14
  }
15
15
  },
16
16
  "windows": {
17
- "command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-windows.ps1)))",
17
+ "command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.42/connect-windows.ps1)))",
18
18
  "asset": {
19
19
  "name": "connect-windows.ps1",
20
- "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.40/connect-windows.ps1"
20
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.42/connect-windows.ps1"
21
21
  },
22
22
  "workspace": {
23
23
  "argument": "--workspace",
package/docs/windows.md CHANGED
@@ -19,12 +19,13 @@ on `amd64`. Windows Server, Windows 10 and Windows on ARM are refused.
19
19
 
20
20
  ## What it creates
21
21
 
22
- - `%USERPROFILE%\.house-kit`: Kit home.
22
+ - The Docker volume `house-kit-home`: Kit home, with your credential and
23
+ `kit.json`, mounted in the container as `/kit-home`. It lives on Docker
24
+ Desktop's own WSL 2 disk, not on your Windows drive. Docker Desktop's
25
+ Volumes view shows its files; double-click `kit.json` there to edit it.
23
26
  - `%USERPROFILE%\AgentsHouse`: your workspace. Pass
24
- `--workspace 'D:\Agents'` to use another directory.
25
-
26
- Both are restricted to your Windows account. A path containing a comma is
27
- refused.
27
+ `--workspace 'D:\Agents'` to use another directory. It is restricted to your
28
+ Windows account, and a path containing a comma is refused.
28
29
 
29
30
  ## Notes
30
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/kit",
3
- "version": "0.1.0-alpha.40",
3
+ "version": "0.1.0-alpha.42",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
package/src/workspace.js CHANGED
@@ -401,7 +401,7 @@ async function placeReplacement(host, settings, identity, held, running) {
401
401
  }
402
402
 
403
403
  async function rollBack(host, identity, running, cause) {
404
- const unrestored = await restorePrevious(host, identity);
404
+ const unrestored = await restorePrevious(host, identity, true);
405
405
  if (unrestored !== null) return `${cause}, and ${unrestored}`;
406
406
  if (!running) return cause;
407
407
  const restarted = await docker(host, ["start", identity]);
@@ -409,10 +409,16 @@ async function rollBack(host, identity, running, cause) {
409
409
  return `${cause}, and the workspace did not start again: ${restarted.stderr.trim()}`;
410
410
  }
411
411
 
412
- async function restorePrevious(host, identity) {
412
+ async function restorePrevious(host, identity, rollingBack = false) {
413
413
  const previous = previousName(identity);
414
414
  if ((await workspacePresent(host, previous)) === null) return null;
415
- if ((await workspacePresent(host, identity)) !== null) {
415
+ const current = await workspacePresent(host, identity);
416
+ if (current === "running" && !rollingBack) {
417
+ const removed = await docker(host, ["rm", "--force", previous]);
418
+ if (removed.status !== 0) return `the previous workspace was not removed: ${removed.stderr.trim()}`;
419
+ return null;
420
+ }
421
+ if (current !== null) {
416
422
  const discarded = await docker(host, ["rm", "--force", identity]);
417
423
  if (discarded.status !== 0) {
418
424
  return `the replacement was not removed: ${discarded.stderr.trim()}`;