@agentshouse/kit 0.1.0-alpha.4 → 0.1.0-alpha.41

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.
Files changed (150) hide show
  1. package/README.md +41 -1671
  2. package/bin/connect-linux.sh +501 -53
  3. package/bin/connect-windows.ps1 +440 -0
  4. package/bin/kit.js +200 -159
  5. package/bootstrap.json +28 -0
  6. package/docs/configuration.md +104 -0
  7. package/docs/google.md +33 -0
  8. package/docs/linux.md +77 -0
  9. package/docs/macos.md +26 -0
  10. package/docs/telegram.md +29 -0
  11. package/docs/windows.md +37 -0
  12. package/package.json +7 -2
  13. package/src/adapter-mcp.js +92 -12
  14. package/src/agents.js +63 -8
  15. package/src/binding.js +52 -0
  16. package/src/blocks.js +2 -26
  17. package/src/bridge.js +28 -14
  18. package/src/bytes.js +19 -8
  19. package/src/configuration.js +14 -211
  20. package/src/connector-login.js +3 -84
  21. package/src/contract.js +44 -12
  22. package/src/conversation-originals.js +1 -3
  23. package/src/delivery.js +4 -7
  24. package/src/directory.js +6 -26
  25. package/src/draft.js +4 -82
  26. package/src/git-main.js +138 -0
  27. package/src/git.js +62 -0
  28. package/src/google/calendar.js +39 -45
  29. package/src/google/drive.js +61 -186
  30. package/src/google/entries.js +349 -0
  31. package/src/google/gateway.js +3 -6
  32. package/src/google/gmail.js +33 -23
  33. package/src/google/oauth.js +106 -66
  34. package/src/google/source.js +21 -23
  35. package/src/host.js +6 -2
  36. package/src/house-main.js +65 -8
  37. package/src/house.js +446 -127
  38. package/src/how-we-work.md +25 -0
  39. package/src/inbound.js +2 -18
  40. package/src/kit-work-stream.js +120 -0
  41. package/src/launcher.js +46 -10
  42. package/src/local-files.js +3 -11
  43. package/src/login.js +27 -14
  44. package/src/mcp.js +18 -0
  45. package/src/mount.js +9 -18
  46. package/src/original.js +4 -4
  47. package/src/pacing.js +41 -0
  48. package/src/plan.js +37 -45
  49. package/src/repository.js +3 -61
  50. package/src/session-bin/git +2 -0
  51. package/src/setup.js +14 -15
  52. package/src/skill-set/ask-matt/PHASE-BOUNDARIES.md +55 -0
  53. package/src/skill-set/ask-matt/SKILL.md +91 -0
  54. package/src/skill-set/ask-matt/agents/openai.yaml +4 -0
  55. package/src/skill-set/code-review/LICENSE +21 -0
  56. package/src/skill-set/code-review/SKILL.md +89 -0
  57. package/src/skill-set/code-review/agents/openai.yaml +3 -0
  58. package/src/skill-set/codebase-design/DEEPENING.md +37 -0
  59. package/src/skill-set/codebase-design/DESIGN-IT-TWICE.md +44 -0
  60. package/src/skill-set/codebase-design/LICENSE +21 -0
  61. package/src/skill-set/codebase-design/SKILL.md +116 -0
  62. package/src/skill-set/codebase-design/agents/openai.yaml +3 -0
  63. package/src/skill-set/diagnosing-bugs/LICENSE +21 -0
  64. package/src/skill-set/diagnosing-bugs/SKILL.md +140 -0
  65. package/src/skill-set/diagnosing-bugs/agents/openai.yaml +3 -0
  66. package/src/skill-set/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
  67. package/src/skill-set/domain-modeling/ADR-FORMAT.md +47 -0
  68. package/src/skill-set/domain-modeling/CONTEXT-FORMAT.md +60 -0
  69. package/src/skill-set/domain-modeling/LICENSE +21 -0
  70. package/src/skill-set/domain-modeling/SKILL.md +76 -0
  71. package/src/skill-set/domain-modeling/agents/openai.yaml +3 -0
  72. package/src/skill-set/grill-me/LICENSE +21 -0
  73. package/src/skill-set/grill-me/SKILL.md +8 -0
  74. package/src/skill-set/grill-me/agents/openai.yaml +4 -0
  75. package/src/skill-set/grill-with-docs/LICENSE +21 -0
  76. package/src/skill-set/grill-with-docs/SKILL.md +8 -0
  77. package/src/skill-set/grill-with-docs/agents/openai.yaml +4 -0
  78. package/src/skill-set/grilling/LICENSE +21 -0
  79. package/src/{starter-set → skill-set}/grilling/SKILL.md +1 -1
  80. package/src/skill-set/handoff/LICENSE +21 -0
  81. package/src/skill-set/handoff/SKILL.md +17 -0
  82. package/src/skill-set/handoff/agents/openai.yaml +4 -0
  83. package/src/skill-set/implement/LICENSE +21 -0
  84. package/src/skill-set/implement/SKILL.md +16 -0
  85. package/src/skill-set/implement/agents/openai.yaml +4 -0
  86. package/src/skill-set/improve-codebase-architecture/HTML-REPORT.md +123 -0
  87. package/src/skill-set/improve-codebase-architecture/LICENSE +21 -0
  88. package/src/skill-set/improve-codebase-architecture/SKILL.md +72 -0
  89. package/src/skill-set/improve-codebase-architecture/agents/openai.yaml +4 -0
  90. package/src/skill-set/prototype/LICENSE +21 -0
  91. package/src/skill-set/prototype/LOGIC.md +67 -0
  92. package/src/skill-set/prototype/SKILL.md +28 -0
  93. package/src/skill-set/prototype/UI.md +112 -0
  94. package/src/skill-set/prototype/agents/openai.yaml +3 -0
  95. package/src/skill-set/research/LICENSE +21 -0
  96. package/src/skill-set/research/SKILL.md +14 -0
  97. package/src/skill-set/research/agents/openai.yaml +3 -0
  98. package/src/skill-set/resolving-merge-conflicts/LICENSE +21 -0
  99. package/src/skill-set/resolving-merge-conflicts/SKILL.md +16 -0
  100. package/src/skill-set/resolving-merge-conflicts/agents/openai.yaml +3 -0
  101. package/src/skill-set/tdd/LICENSE +21 -0
  102. package/src/skill-set/tdd/SKILL.md +40 -0
  103. package/src/skill-set/tdd/agents/openai.yaml +3 -0
  104. package/src/skill-set/tdd/mocking.md +59 -0
  105. package/src/skill-set/tdd/tests.md +77 -0
  106. package/src/skill-set/teach/GLOSSARY-FORMAT.md +35 -0
  107. package/src/skill-set/teach/LEARNING-RECORD-FORMAT.md +46 -0
  108. package/src/skill-set/teach/LICENSE +21 -0
  109. package/src/skill-set/teach/MISSION-FORMAT.md +31 -0
  110. package/src/skill-set/teach/RESOURCES-FORMAT.md +32 -0
  111. package/src/skill-set/teach/SKILL.md +141 -0
  112. package/src/skill-set/teach/agents/openai.yaml +4 -0
  113. package/src/skill-set/to-questionnaire/LICENSE +21 -0
  114. package/src/skill-set/to-questionnaire/SKILL.md +55 -0
  115. package/src/skill-set/to-questionnaire/agents/openai.yaml +4 -0
  116. package/src/skill-set/to-spec/LICENSE +21 -0
  117. package/src/skill-set/to-spec/SKILL.md +76 -0
  118. package/src/skill-set/to-spec/agents/openai.yaml +4 -0
  119. package/src/skill-set/to-tickets/LICENSE +21 -0
  120. package/src/skill-set/to-tickets/SKILL.md +106 -0
  121. package/src/skill-set/to-tickets/agents/openai.yaml +4 -0
  122. package/src/skill-set/wait-what/LICENSE +21 -0
  123. package/src/skill-set/wait-what/SKILL.md +8 -0
  124. package/src/skill-set/wait-what/agents/openai.yaml +4 -0
  125. package/src/skill-set/wayfinder/LICENSE +21 -0
  126. package/src/skill-set/wayfinder/SKILL.md +129 -0
  127. package/src/skill-set/wayfinder/agents/openai.yaml +4 -0
  128. package/src/skill-set/writing-for-agents/LICENSE +21 -0
  129. package/src/skill-set/writing-for-agents/SKILL-MECHANICS.md +22 -0
  130. package/src/skill-set/writing-for-agents/SKILL.md +83 -0
  131. package/src/skill-set/writing-for-agents/agents/openai.yaml +3 -0
  132. package/src/skill-set.js +85 -0
  133. package/src/source.js +93 -54
  134. package/src/state.js +19 -1
  135. package/src/sync-library.js +7 -11
  136. package/src/sync-state.js +12 -3
  137. package/src/sync.js +142 -43
  138. package/src/tasks.js +92 -0
  139. package/src/telegram-view.js +217 -0
  140. package/src/telegram.js +204 -69
  141. package/src/turn-frames.js +69 -0
  142. package/src/worker.js +738 -189
  143. package/src/working-copy.js +1274 -0
  144. package/src/workspace.js +299 -88
  145. package/src/google/declared.js +0 -146
  146. package/src/google/id.js +0 -1
  147. package/src/instance/google.js +0 -1
  148. package/src/starter-set.js +0 -17
  149. /package/src/{starter-set/grilling → skill-set/ask-matt}/LICENSE +0 -0
  150. /package/src/{starter-set → skill-set}/grilling/agents/openai.yaml +0 -0
@@ -6,14 +6,395 @@ NAME='house-kit'
6
6
  HOUSE='https://agents.house'
7
7
  WORKSPACE="$HOME/AgentsHouse"
8
8
  MANUAL=0
9
+ SKILLS=true
9
10
  WORKSPACE_SELECTED=0
10
11
  HOUSE_SELECTED=0
12
+ FORWARDING=0
13
+ FORWARDED=kit
14
+ FORWARD=()
15
+ UBUNTU_RELEASES=(jammy noble resolute)
16
+ DOCKER_PACKAGES=(docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin)
17
+ CONFLICTING_PACKAGES=(docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc)
18
+ DOCKER_APP=/Applications/Docker.app
19
+ DESKTOP_WAIT_SECONDS=300
20
+ RUNTIME='Docker Engine'
21
+ OPENER=xdg-open
22
+ NETWORK=(--network host)
23
+ PUBLISHED=()
24
+ AVAILABILITY=intermittent
25
+ NATIVE=0
26
+ NATIVE_RELEASE=26.04
27
+ NATIVE_ARCHITECTURE=amd64
28
+ NATIVE_PACKAGES=(git tmux)
29
+ NATIVE_PREFIX=/opt/house-kit
30
+ NATIVE_SERVICE=house-kit.service
31
+ NATIVE_UNIT=/etc/systemd/system/house-kit.service
32
+ NATIVE_WORKSPACE=/agents/house
11
33
 
12
34
  refuse() {
13
35
  printf 'kit_bootstrap_refused: %s\n' "$1" >&2
14
36
  exit 1
15
37
  }
16
38
 
39
+ bind_mount() {
40
+ local source=${1//\"/\"\"}
41
+ printf 'type=bind,"src=%s",dst=%s' "$source" "$2"
42
+ }
43
+
44
+ native_kit() {
45
+ PATH="$NATIVE_PREFIX/bin:$PATH" HOUSE_KIT_HOME="$KIT_HOME" "$NATIVE_PREFIX/bin/node" "$NATIVE_PREFIX/bin/$1" "${@:2}"
46
+ }
47
+
48
+ login_kit() {
49
+ if [[ " $* " == *' --manual '* ]]; then
50
+ if ((NATIVE)); then
51
+ native_kit kit login "$@"
52
+ else
53
+ "${DOCKER_RUN[@]:0:2}" -i "${DOCKER_RUN[@]:2}" login "$@"
54
+ fi
55
+ return
56
+ fi
57
+ command -v "$OPENER" >/dev/null || refuse 'no host browser opener; rerun with --manual'
58
+ if ((NATIVE)); then
59
+ native_kit kit login "$@" --host-browser
60
+ else
61
+ "${DOCKER_RUN[@]:0:2}" ${PUBLISHED[@]+"${PUBLISHED[@]}"} "${DOCKER_RUN[@]:2}" login "$@" --host-browser
62
+ fi | while IFS= read -r line; do
63
+ if [[ "$line" == 'Open '* ]]; then
64
+ printf 'Opening House Login in the host browser.\n'
65
+ "$OPENER" "${line#Open }" >/dev/null 2>&1 || refuse 'host browser did not open; rerun with --manual'
66
+ else
67
+ printf '%s\n' "$line"
68
+ fi
69
+ done
70
+ }
71
+
72
+ start_resident() {
73
+ local network=()
74
+ [[ "$HOUSE" == https://* ]] || network=(--network host)
75
+ docker run -d --name "$NAME" --restart unless-stopped ${network[@]+"${network[@]}"} --user "$(id -u):$(id -g)" --mount "$KIT_HOME_MOUNT" --mount "$WORKSPACE_MOUNT" --label "agentshouse.house=$HOUSE" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 "$IMAGE" resident >/dev/null
76
+ }
77
+
78
+ elevated() {
79
+ if ((EUID == 0)); then
80
+ "$@"
81
+ else
82
+ sudo "$@"
83
+ fi
84
+ }
85
+
86
+ docker_member() {
87
+ [[ " $(id -nG "$@") " == *' docker '* ]]
88
+ }
89
+
90
+ docker_answers() {
91
+ DOCKER_PLATFORM=$(docker info --format '{{.OSType}}/{{.Architecture}}' 2>/dev/null)
92
+ }
93
+
94
+ docker_cause() {
95
+ docker info 2>&1 >/dev/null | tail -n 1
96
+ }
97
+
98
+ unanswered() {
99
+ type -P docker >/dev/null || return 0
100
+ printf '; Docker Engine does not answer: %s' "$(docker_cause)"
101
+ }
102
+
103
+ install_docker_desktop() {
104
+ local mount
105
+ DOWNLOAD=$(mktemp -d) || refuse 'no temporary directory for the Docker Desktop download'
106
+ trap 'rm -rf "$DOWNLOAD"' EXIT
107
+ mount="$DOWNLOAD/volume"
108
+ printf 'Downloading Docker Desktop for %s from docker.com.\n' "$1"
109
+ curl -fsSL -o "$DOWNLOAD/Docker.dmg" "https://desktop.docker.com/mac/main/$ARCH/Docker.dmg" || refuse 'Docker Desktop could not be downloaded'
110
+ printf 'Installing Docker Desktop in /Applications; sudo may ask for your password.\n'
111
+ mkdir "$mount" || refuse 'the Docker Desktop disk image has no mount point'
112
+ elevated hdiutil attach -nobrowse -readonly -mountpoint "$mount" "$DOWNLOAD/Docker.dmg" >/dev/null || refuse 'the Docker Desktop disk image could not be opened'
113
+ if ! elevated "$mount/Docker.app/Contents/MacOS/install" "--user=$(id -un)"; then
114
+ elevated hdiutil detach "$mount" >/dev/null 2>&1 || true
115
+ refuse 'the Docker Desktop installer did not finish'
116
+ fi
117
+ elevated hdiutil detach "$mount" >/dev/null || refuse 'the Docker Desktop disk image could not be closed'
118
+ }
119
+
120
+ establish_docker_desktop() {
121
+ PATH="$PATH:$DOCKER_APP/Contents/Resources/bin"
122
+ docker_answers && return
123
+ if ((FORWARDING)); then
124
+ [[ -d "$DOCKER_APP" ]] || refuse 'House Kit is not installed; run this bootstrap without arguments first'
125
+ refuse "Docker Desktop does not answer: $(docker_cause); start Docker Desktop, then rerun this command"
126
+ fi
127
+ [[ -d "$DOCKER_APP" ]] || install_docker_desktop "$1"
128
+ printf 'Starting Docker Desktop; accept its terms or finish its setup if its window asks.\n'
129
+ open "$DOCKER_APP" || refuse 'Docker Desktop could not be opened'
130
+ local waited=0
131
+ until docker_answers; do
132
+ ((waited < DESKTOP_WAIT_SECONDS)) || refuse "Docker Desktop is not ready: $(docker_cause); finish what its window asks, then rerun this bootstrap"
133
+ sleep 2
134
+ waited=$((waited + 2))
135
+ done
136
+ }
137
+
138
+ installed_packages() {
139
+ { dpkg-query -W -f='${db:Status-Status} ${Package}\n' "$@" 2>/dev/null || true; } | awk '$1 == "installed" { print $2 }'
140
+ }
141
+
142
+ install_docker_engine() {
143
+ printf 'Installing Docker Engine from Docker'\''s apt repository for %s; sudo may ask for your password.\n' "$1"
144
+ elevated apt-get update -qq >/dev/null || refuse 'apt could not refresh its package lists'
145
+ elevated apt-get install -y -qq ca-certificates curl >/dev/null || refuse 'apt could not install ca-certificates and curl'
146
+ elevated install -m 0755 -d /etc/apt/keyrings || refuse '/etc/apt/keyrings could not be created'
147
+ elevated curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc || refuse 'the Docker apt repository key could not be downloaded'
148
+ elevated chmod a+r /etc/apt/keyrings/docker.asc || refuse 'the Docker apt repository key could not be made readable'
149
+ printf 'Types: deb\nURIs: https://download.docker.com/linux/ubuntu\nSuites: %s\nComponents: stable\nArchitectures: %s\nSigned-By: /etc/apt/keyrings/docker.asc\n' "$2" "$(dpkg --print-architecture)" \
150
+ | elevated tee /etc/apt/sources.list.d/docker.sources >/dev/null || refuse 'the Docker apt repository could not be configured'
151
+ elevated apt-get update -qq >/dev/null || refuse 'apt could not read the Docker apt repository'
152
+ elevated apt-get install -y -qq "${DOCKER_PACKAGES[@]}" >/dev/null || refuse 'apt could not install Docker Engine'
153
+ }
154
+
155
+ establish_docker_engine() {
156
+ docker_answers && return
157
+ if ((EUID != 0)) && ! docker_member && docker_member "$(id -un)"; then
158
+ refuse "$(id -un) joined the docker group after this session began; sign out and back in, then rerun this bootstrap"
159
+ fi
160
+ local distribution='' release='' described='this Linux distribution' conflicting present
161
+ if [[ -r /etc/os-release ]]; then
162
+ distribution=$(. /etc/os-release && printf '%s' "${ID:-}")
163
+ release=$(. /etc/os-release && printf '%s' "${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}")
164
+ described=$(. /etc/os-release && printf '%s' "${PRETTY_NAME:-$described}")
165
+ fi
166
+ if ((FORWARDING)); then
167
+ type -P docker >/dev/null || refuse 'House Kit is not installed; run this bootstrap without arguments first'
168
+ refuse "Docker Engine does not answer: $(docker_cause)"
169
+ fi
170
+ if [[ "$distribution" != ubuntu ]]; then
171
+ type -P docker >/dev/null || refuse "Docker Engine is not installed on $described; install a working Docker Engine, then rerun this bootstrap"
172
+ refuse "Docker Engine does not answer on $described: $(docker_cause)"
173
+ fi
174
+ [[ " ${UBUNTU_RELEASES[*]} " == *" $release "* ]] || refuse "this bootstrap installs Docker Engine only on Ubuntu 22.04, 24.04 and 26.04 LTS, not $described; install a working Docker Engine, then rerun this bootstrap$(unanswered)"
175
+ conflicting=$(installed_packages "${CONFLICTING_PACKAGES[@]}" | paste -sd ' ' -)
176
+ [[ -z "$conflicting" ]] || refuse "the installed packages $conflicting conflict with Docker Engine; remove them or make their Docker Engine work, then rerun this bootstrap$(unanswered)"
177
+ present=$(installed_packages "${DOCKER_PACKAGES[@]}" | wc -l)
178
+ ((present == ${#DOCKER_PACKAGES[@]})) || install_docker_engine "$described" "$release"
179
+ if ! systemctl is-active --quiet docker.service; then
180
+ printf 'Starting Docker Engine; sudo may ask for your password.\n'
181
+ elevated systemctl start docker.service || refuse 'Docker Engine could not be started'
182
+ fi
183
+ if ((EUID != 0)); then
184
+ if ! docker_member "$(id -un)"; then
185
+ printf 'Adding %s to the docker group; it applies from your next sign-in, so this run reaches Docker Engine through sudo.\n' "$(id -un)"
186
+ elevated usermod -aG docker "$(id -un)" || refuse "$(id -un) could not be added to the docker group"
187
+ fi
188
+ docker_member || docker() { sudo -u "$(id -un)" docker "$@"; }
189
+ fi
190
+ docker_answers || refuse "Docker Engine does not answer: $(docker_cause)"
191
+ }
192
+
193
+ check_native_host() {
194
+ local distribution='' release='' described=macOS
195
+ if [[ "$RUNTIME" == 'Docker Engine' ]]; then
196
+ described=Linux
197
+ if [[ -r /etc/os-release ]]; then
198
+ distribution=$(. /etc/os-release && printf '%s' "${ID:-}")
199
+ release=$(. /etc/os-release && printf '%s' "${VERSION_ID:-}")
200
+ described=$(. /etc/os-release && printf '%s' "${PRETTY_NAME:-$described}")
201
+ fi
202
+ fi
203
+ [[ "$distribution" == ubuntu && "$release" == "$NATIVE_RELEASE" && "$ARCH" == "$NATIVE_ARCHITECTURE" ]] ||
204
+ refuse "--linux runs only on Ubuntu $NATIVE_RELEASE LTS on $NATIVE_ARCHITECTURE, not $described on $ARCH"
205
+ }
206
+
207
+
208
+ establish_native_packages() {
209
+ local present
210
+ present=$(installed_packages "${NATIVE_PACKAGES[@]}" | wc -l)
211
+ ((present == ${#NATIVE_PACKAGES[@]})) && return
212
+ printf 'Installing git and tmux from Ubuntu'\''s apt repository; sudo may ask for your password.\n'
213
+ elevated apt-get update -qq >/dev/null || refuse 'apt could not refresh its package lists'
214
+ elevated apt-get install -y -qq "${NATIVE_PACKAGES[@]}" >/dev/null || refuse 'apt could not install git and tmux'
215
+ }
216
+
217
+ stage_native_kit() {
218
+ local copy
219
+ NATIVE_STAGE=$(mktemp -d) || refuse 'no temporary directory for the Kit package'
220
+ trap 'rm -rf "$NATIVE_STAGE"' EXIT
221
+ docker pull --quiet --platform "linux/$ARCH" "$IMAGE" >/dev/null
222
+ copy=$(docker create --platform "linux/$ARCH" "$IMAGE") || refuse 'the Kit image could not be opened'
223
+ mkdir "$NATIVE_STAGE/bin" "$NATIVE_STAGE/lib"
224
+ if ! docker cp --quiet "$copy:/usr/local/bin/node" "$NATIVE_STAGE/bin/node" ||
225
+ ! docker cp --quiet "$copy:/usr/local/lib/node_modules" "$NATIVE_STAGE/lib/node_modules"; then
226
+ docker rm "$copy" >/dev/null 2>&1 || true
227
+ refuse 'the Kit package could not be copied from its image'
228
+ fi
229
+ docker rm "$copy" >/dev/null
230
+ ln -s ../lib/node_modules/@agentshouse/kit/bin/kit.js "$NATIVE_STAGE/bin/kit"
231
+ ln -s ../lib/node_modules/@agentshouse/kit/src/session-bin/house "$NATIVE_STAGE/bin/house"
232
+ ln -s ../lib/node_modules/npm/bin/npm-cli.js "$NATIVE_STAGE/bin/npm"
233
+ ln -s ../lib/node_modules/npm/bin/npx-cli.js "$NATIVE_STAGE/bin/npx"
234
+ printf '%s\n' "$IMAGE" > "$NATIVE_STAGE/image"
235
+ chmod 0755 "$NATIVE_STAGE" "$NATIVE_STAGE/bin" "$NATIVE_STAGE/lib"
236
+ chmod 0644 "$NATIVE_STAGE/image"
237
+ }
238
+
239
+ place_native_kit() {
240
+ printf 'Installing House Kit in %s; sudo may ask for your password.\n' "$NATIVE_PREFIX"
241
+ elevated rm -rf "$NATIVE_PREFIX" && elevated mv "$NATIVE_STAGE" "$NATIVE_PREFIX" && elevated chown -R root:root "$NATIVE_PREFIX" ||
242
+ refuse "House Kit could not be installed in $NATIVE_PREFIX"
243
+ }
244
+
245
+ start_native_service() {
246
+ printf '[Unit]\nDescription=House Kit\nWants=network-online.target\nAfter=network-online.target docker.service\n\n[Service]\nUser=%s\nWorkingDirectory=%s\nEnvironment=PATH=%s/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nExecStart=%s/bin/node %s/bin/kit resident\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\n' \
247
+ "$(id -un)" "$NATIVE_WORKSPACE" "$NATIVE_PREFIX" "$NATIVE_PREFIX" "$NATIVE_PREFIX" | elevated tee "$NATIVE_UNIT" >/dev/null &&
248
+ elevated chmod 0644 "$NATIVE_UNIT" || refuse "$NATIVE_UNIT could not be written"
249
+ elevated systemctl daemon-reload || refuse 'systemd could not read the House Kit service'
250
+ elevated systemctl --quiet enable --now "$NATIVE_SERVICE" || refuse 'the House Kit service could not be started'
251
+ }
252
+
253
+ run_kit() {
254
+ if ((NATIVE)); then
255
+ native_kit kit "$@"
256
+ else
257
+ "${DOCKER_RUN[@]}" "$@"
258
+ fi
259
+ }
260
+
261
+ kit_running() {
262
+ if ((NATIVE)); then
263
+ systemctl is-active --quiet "$NATIVE_SERVICE"
264
+ else
265
+ [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]
266
+ fi
267
+ }
268
+
269
+ kit_installed() {
270
+ if ((NATIVE)); then
271
+ [[ -f "$NATIVE_UNIT" ]]
272
+ else
273
+ docker container inspect "$NAME" >/dev/null 2>&1
274
+ fi
275
+ }
276
+
277
+ installed_image() {
278
+ if ((NATIVE == 0)); then
279
+ docker inspect --format '{{.Config.Image}}' "$NAME"
280
+ elif [[ -f "$NATIVE_PREFIX/image" ]]; then
281
+ cat "$NATIVE_PREFIX/image"
282
+ fi
283
+ }
284
+
285
+ check_installation() {
286
+ if ((NATIVE)); then
287
+ grep -qx "User=$(id -un)" "$NATIVE_UNIT" || refuse 'House Kit runs natively on this host for another account'
288
+ return
289
+ fi
290
+ [[ "$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' "$NAME")" == unless-stopped ]] || refuse 'the installed Kit has a different lifecycle'
291
+ [[ "$(docker inspect --format '{{len .Mounts}}' "$NAME")" == 2 ]] || refuse 'the installed Kit has unexpected mounts'
292
+ [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/kit-home"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$KIT_HOME" ]] || refuse 'the installed Kit uses another Kit home'
293
+ [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/kit-home"}}{{.Type}}/{{.RW}}{{end}}{{end}}' "$NAME")" == bind/true ]] || refuse 'the installed Kit home is not a writable bind mount'
294
+ [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$WORKSPACE" ]] || refuse 'the installed Kit uses another workspace root'
295
+ [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Type}}/{{.RW}}{{end}}{{end}}' "$NAME")" == bind/true ]] || refuse 'the installed workspace is not a writable bind mount'
296
+ [[ "$(docker inspect --format '{{index .Config.Labels "agentshouse.house"}}' "$NAME")" == "$HOUSE" ]] || refuse 'the installed Kit uses another House origin'
297
+ }
298
+
299
+ forward_kit() {
300
+ if ((NATIVE)); then
301
+ native_kit "$FORWARDED" ${FORWARD[@]+"${FORWARD[@]}"}
302
+ return
303
+ fi
304
+ local placed=() entry=() terminal=() here
305
+ if [[ "$FORWARDED" == house ]]; then
306
+ here=$(pwd -P)
307
+ [[ "$here" == "$WORKSPACE" || "$here" == "$WORKSPACE"/* ]] || refuse "the current directory is outside the workspace $WORKSPACE"
308
+ placed=(--workdir "/agents/house${here#"$WORKSPACE"}")
309
+ entry=(--entrypoint house)
310
+ fi
311
+ if [[ -t 0 && -t 1 ]]; then terminal=(-t); fi
312
+ if [[ -t 0 && ! -t 1 ]]; then
313
+ ECHOED=$(stty -g)
314
+ trap 'stty "$ECHOED"' EXIT
315
+ stty -echo
316
+ fi
317
+ if kit_running; then
318
+ docker exec -i ${terminal[@]+"${terminal[@]}"} ${placed[@]+"${placed[@]}"} "$NAME" "$FORWARDED" ${FORWARD[@]+"${FORWARD[@]}"}
319
+ else
320
+ "${DOCKER_RUN[@]:0:2}" -i ${terminal[@]+"${terminal[@]}"} ${placed[@]+"${placed[@]}"} ${entry[@]+"${entry[@]}"} "${DOCKER_RUN[@]:2}" ${FORWARD[@]+"${FORWARD[@]}"}
321
+ fi
322
+ }
323
+
324
+ start_kit() {
325
+ if ((NATIVE)); then
326
+ start_native_service
327
+ else
328
+ start_resident
329
+ fi
330
+ }
331
+
332
+ update_kit() {
333
+ local stopped=0
334
+ if ((NATIVE)); then
335
+ stage_native_kit
336
+ else
337
+ docker pull --quiet --platform "linux/$ARCH" "$IMAGE" >/dev/null
338
+ fi
339
+ if ! kit_running; then
340
+ stopped=1
341
+ elif ((NATIVE)); then
342
+ native_kit kit resident drain
343
+ else
344
+ docker exec "$NAME" kit resident drain
345
+ fi
346
+ if ((NATIVE)); then
347
+ elevated systemctl stop "$NATIVE_SERVICE" || refuse 'the House Kit service could not be stopped'
348
+ place_native_kit
349
+ else
350
+ docker rm -f "$NAME" >/dev/null
351
+ fi
352
+ rm -f "$KIT_HOME/state/update-drain"
353
+ ((stopped == 0)) || run_kit credential check
354
+ start_kit
355
+ printf 'House Kit updated for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
356
+ }
357
+
358
+ resume_kit() {
359
+ run_kit credential check
360
+ if kit_running; then
361
+ printf 'House Kit is already running for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
362
+ return
363
+ fi
364
+ if ((NATIVE)); then
365
+ elevated systemctl start "$NATIVE_SERVICE" || refuse 'the House Kit service could not be started'
366
+ else
367
+ docker start "$NAME" >/dev/null
368
+ fi
369
+ printf 'House Kit restarted for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
370
+ }
371
+
372
+ connect_kit() {
373
+ if [[ -f "$KIT_HOME/credential" ]]; then
374
+ run_kit credential check
375
+ else
376
+ local login=()
377
+ if ((HOUSE_SELECTED)); then login+=(--house "$HOUSE"); fi
378
+ if ((MANUAL)); then login+=(--manual); fi
379
+ login_kit ${login[@]+"${login[@]}"}
380
+ fi
381
+ run_kit apply
382
+ start_kit
383
+ printf 'House Kit connected for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
384
+ }
385
+
386
+ if [[ "${1:-}" == --linux && ( "${2:-}" == kit || "${2:-}" == house ) ]]; then
387
+ NATIVE=1
388
+ shift
389
+ fi
390
+
391
+ if [[ "${1:-}" == kit || "${1:-}" == house ]]; then
392
+ FORWARDING=1
393
+ FORWARDED="$1"
394
+ FORWARD=("${@:2}")
395
+ set --
396
+ fi
397
+
17
398
  while (($#)); do
18
399
  case "$1" in
19
400
  --workspace|--house)
@@ -33,26 +414,80 @@ while (($#)); do
33
414
  MANUAL=1
34
415
  shift
35
416
  ;;
417
+ --no-skills)
418
+ SKILLS=false
419
+ shift
420
+ ;;
421
+ --linux)
422
+ NATIVE=1
423
+ shift
424
+ ;;
36
425
  *) refuse "unknown argument $1" ;;
37
426
  esac
38
427
  done
428
+ if ((NATIVE)); then
429
+ ((WORKSPACE_SELECTED == 0)) || refuse "--linux keeps its workspace at $NATIVE_WORKSPACE; --workspace is for a container"
430
+ WORKSPACE=$NATIVE_WORKSPACE
431
+ AVAILABILITY=continuous
432
+ elif [[ -e "$NATIVE_UNIT" ]]; then
433
+ refuse 'House Kit runs natively on this host; rerun this bootstrap with --linux'
434
+ fi
39
435
 
40
436
  [[ "$IMAGE" =~ ^ghcr\.io/agentshouse/kit@sha256:[0-9a-f]{64}$ ]] || refuse 'this bootstrap has not been published with an immutable image digest'
41
- [[ "$(uname -s)" == Linux ]] || refuse "unsupported host $(uname -s); this bootstrap is for Linux"
42
- case "$(uname -m)" in
43
- x86_64|amd64) ARCH=amd64 ;;
44
- aarch64|arm64) ARCH=arm64 ;;
45
- *) refuse "unsupported Linux architecture $(uname -m)" ;;
437
+ case "$(uname -s)" in
438
+ Linux)
439
+ case "$(uname -m)" in
440
+ x86_64|amd64) ARCH=amd64 ;;
441
+ aarch64|arm64) ARCH=arm64 ;;
442
+ *) refuse "unsupported Linux architecture $(uname -m)" ;;
443
+ esac
444
+ ;;
445
+ Darwin)
446
+ RUNTIME='Docker Desktop'
447
+ OPENER=open
448
+ if [[ "$(sysctl -n hw.optional.arm64 2>/dev/null)" == 1 ]]; then
449
+ ARCH=arm64
450
+ CHIP='Apple silicon'
451
+ else
452
+ ARCH=amd64
453
+ CHIP='an Intel chip'
454
+ fi
455
+ ;;
456
+ *) refuse "unsupported host $(uname -s); this bootstrap is for Linux and macOS" ;;
46
457
  esac
458
+ ((NATIVE == 0)) || check_native_host
47
459
  [[ "$WORKSPACE" == /* ]] || refuse 'workspace root must be an absolute path'
48
- command -v docker >/dev/null || refuse 'Docker Engine client is unavailable'
49
- DOCKER_PLATFORM=$(docker info --format '{{.OSType}}/{{.Architecture}}' 2>/dev/null) || refuse 'Docker Engine is unavailable'
50
- case "$DOCKER_PLATFORM" in
51
- linux/x86_64|linux/amd64) DOCKER_ARCH=amd64 ;;
52
- linux/aarch64|linux/arm64) DOCKER_ARCH=arm64 ;;
53
- *) refuse "unsupported Docker Engine platform $DOCKER_PLATFORM" ;;
54
- esac
55
- [[ "$DOCKER_ARCH" == "$ARCH" ]] || refuse "host linux/$ARCH does not match Docker Engine linux/$DOCKER_ARCH"
460
+ if ((NATIVE && FORWARDING)); then
461
+ [[ -f "$NATIVE_UNIT" ]] || refuse 'House Kit is not installed; run this bootstrap with --linux first'
462
+ else
463
+ if [[ "$RUNTIME" == 'Docker Desktop' ]]; then
464
+ establish_docker_desktop "$CHIP"
465
+ DOCKER_SYSTEM=$(docker info --format '{{.OperatingSystem}}' 2>/dev/null) || refuse "Docker Desktop does not answer: $(docker_cause)"
466
+ [[ "$DOCKER_SYSTEM" == 'Docker Desktop' ]] || refuse "Docker answers from $DOCKER_SYSTEM, not Docker Desktop; stop that engine or switch Docker to Docker Desktop, then rerun this bootstrap"
467
+ else
468
+ establish_docker_engine
469
+ fi
470
+ case "$DOCKER_PLATFORM" in
471
+ linux/x86_64|linux/amd64) DOCKER_ARCH=amd64 ;;
472
+ linux/aarch64|linux/arm64) DOCKER_ARCH=arm64 ;;
473
+ *) refuse "unsupported $RUNTIME platform $DOCKER_PLATFORM" ;;
474
+ esac
475
+ [[ "$DOCKER_ARCH" == "$ARCH" ]] || refuse "host linux/$ARCH does not match $RUNTIME linux/$DOCKER_ARCH"
476
+ fi
477
+ if ((NATIVE)); then
478
+ if ((FORWARDING == 0)) && docker container inspect "$NAME" >/dev/null 2>&1; then
479
+ refuse "House Kit runs in the container $NAME on this computer; remove that container, then rerun this bootstrap with --linux"
480
+ fi
481
+ [[ ! -L "${NATIVE_WORKSPACE%/*}" ]] || refuse "${NATIVE_WORKSPACE%/*} is a symbolic link"
482
+ if [[ ! -e "$NATIVE_WORKSPACE" && ! -L "$NATIVE_WORKSPACE" ]]; then
483
+ printf 'Creating %s for the Agents on this host; sudo may ask for your password.\n' "$NATIVE_WORKSPACE"
484
+ { [[ -d "${NATIVE_WORKSPACE%/*}" ]] || elevated install -d -m 0755 "${NATIVE_WORKSPACE%/*}"; } &&
485
+ elevated install -d -m 0700 -o "$(id -u)" -g "$(id -g)" "$NATIVE_WORKSPACE" || refuse "$NATIVE_WORKSPACE could not be created"
486
+ fi
487
+ elif ((FORWARDING)); then
488
+ docker container inspect "$NAME" >/dev/null 2>&1 || refuse 'House Kit is not installed; run this bootstrap without arguments first'
489
+ WORKSPACE=$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Source}}{{end}}{{end}}' "$NAME")
490
+ fi
56
491
 
57
492
  umask 077
58
493
  KIT_HOME="$HOME/.house-kit"
@@ -60,11 +495,13 @@ for path in "$KIT_HOME" "$WORKSPACE"; do
60
495
  [[ ! -L "$path" ]] || refuse "$path is a symbolic link"
61
496
  mkdir -p -m 700 "$path" || refuse "$path could not be created"
62
497
  [[ -d "$path" ]] || refuse "$path is not a directory"
63
- [[ "$(stat -c %u "$path")" == "$(id -u)" ]] || refuse "$path is not owned by this User"
498
+ [[ -O "$path" ]] || refuse "$path is not owned by this User"
64
499
  chmod 700 "$path" || refuse "$path could not be owner-protected"
65
500
  done
66
501
  KIT_HOME=$(cd "$KIT_HOME" && pwd -P)
67
502
  WORKSPACE=$(cd "$WORKSPACE" && pwd -P)
503
+ KIT_HOME_MOUNT=$(bind_mount "$KIT_HOME" /kit-home)
504
+ WORKSPACE_MOUNT=$(bind_mount "$WORKSPACE" /agents/house)
68
505
  if [[ -f "$KIT_HOME/credential" || -f "$KIT_HOME/environment" ]] && [[ ! -f "$KIT_HOME/house" ]]; then
69
506
  refuse 'stored House origin is missing for an enrolled Environment'
70
507
  fi
@@ -76,52 +513,63 @@ if [[ -f "$KIT_HOME/house" ]]; then
76
513
  HOUSE="$BOUND_HOUSE"
77
514
  fi
78
515
  [[ "$HOUSE" == https://* || "$HOUSE" == http://127.0.0.1:* || "$HOUSE" == http://localhost:* ]] || refuse 'House origin must use HTTPS or local loopback HTTP'
79
- DOCKER_RUN=(docker run --rm --network host --user "$(id -u):$(id -g)" --mount "type=bind,src=$KIT_HOME,dst=/kit-home" --mount "type=bind,src=$WORKSPACE,dst=/agents/house" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 --env "HOUSE_KIT_HOSTNAME=$(hostname)" "$IMAGE")
80
-
81
- if docker container inspect "$NAME" >/dev/null 2>&1; then
82
- [[ "$(docker inspect --format '{{.Config.Image}}' "$NAME")" == "$IMAGE" ]] || refuse 'an installed Kit uses a different image; update it explicitly'
83
- [[ "$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' "$NAME")" == unless-stopped ]] || refuse 'the installed Kit has a different lifecycle'
84
- [[ "$(docker inspect --format '{{len .Mounts}}' "$NAME")" == 2 ]] || refuse 'the installed Kit has unexpected mounts'
85
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/kit-home"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$KIT_HOME" ]] || refuse 'the installed Kit uses another Kit home'
86
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/kit-home"}}{{.Type}}/{{.RW}}{{end}}{{end}}' "$NAME")" == bind/true ]] || refuse 'the installed Kit home is not a writable bind mount'
87
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$WORKSPACE" ]] || refuse 'the installed Kit uses another workspace root'
88
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Type}}/{{.RW}}{{end}}{{end}}' "$NAME")" == bind/true ]] || refuse 'the installed workspace is not a writable bind mount'
89
- [[ "$(docker inspect --format '{{index .Config.Labels "agentshouse.house"}}' "$NAME")" == "$HOUSE" ]] || refuse 'the installed Kit uses another House origin'
90
- [[ -f "$KIT_HOME/credential" && -f "$KIT_HOME/environment" && -f "$KIT_HOME/house" ]] || refuse 'the installed Kit has no enrolled authority'
91
- "${DOCKER_RUN[@]}" credential check --house "$HOUSE"
92
- if [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]; then
93
- printf 'House Kit is already running for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
516
+ if ((FORWARDING == 0)); then
517
+ if [[ ! -f "$KIT_HOME/kit.json" ]]; then
518
+ printf '{"availability":"%s","execution_authority":"allow","migrators":[],"instances":[],"clients":[],"host":null,"skills":%s}\n' "$AVAILABILITY" "$SKILLS" > "$KIT_HOME/kit.json"
519
+ elif grep -Eq '"skills"[[:space:]]*:' "$KIT_HOME/kit.json"; then
520
+ sed -E -i.old "s/(\"skills\"[[:space:]]*:[[:space:]]*)(true|false)/\\1$SKILLS/" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
94
521
  else
95
- docker start "$NAME" >/dev/null
96
- printf 'House Kit restarted for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
522
+ sed -E -i.old "1s/^([[:space:]]*)\\{/\\1{\"skills\":$SKILLS,/" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
97
523
  fi
98
- exit 0
524
+ if ((NATIVE)); then
525
+ sed -E -i.old "s/(\"availability\"[[:space:]]*:[[:space:]]*)\"[^\"]*\"/\\1\"$AVAILABILITY\"/" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
526
+ fi
527
+ rm -f "$KIT_HOME/kit.json.old"
528
+ grep -Eq "\"skills\"[[:space:]]*:[[:space:]]*$SKILLS" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
529
+ ((NATIVE == 0)) || grep -Eq "\"availability\"[[:space:]]*:[[:space:]]*\"$AVAILABILITY\"" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
99
530
  fi
100
531
 
101
- docker pull --quiet --platform "linux/$ARCH" "$IMAGE" >/dev/null
102
-
103
- if [[ ! -f "$KIT_HOME/kit.json" ]]; then
104
- printf '{"availability":"intermittent","execution_authority":"allow","migrators":[],"instances":[],"clients":[],"host":null}\n' > "$KIT_HOME/kit.json"
532
+ if ((NATIVE)); then
533
+ RERUN='with --linux'
534
+ else
535
+ RERUN='without arguments'
536
+ if [[ "$RUNTIME" == 'Docker Desktop' && "$HOUSE" == https://* ]]; then
537
+ NETWORK=()
538
+ LOGIN_PORT=$((49152 + RANDOM % 16384))
539
+ PUBLISHED=(--publish "127.0.0.1:$LOGIN_PORT:$LOGIN_PORT" --env "HOUSE_KIT_LOGIN_PORT=$LOGIN_PORT")
540
+ fi
541
+ DOCKER_RUN=(docker run --rm ${NETWORK[@]+"${NETWORK[@]}"} --user "$(id -u):$(id -g)" --mount "$KIT_HOME_MOUNT" --mount "$WORKSPACE_MOUNT" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 --env "HOUSE_KIT_HOSTNAME=$(hostname)" "$IMAGE")
105
542
  fi
106
543
 
107
- if [[ -f "$KIT_HOME/credential" ]]; then
108
- "${DOCKER_RUN[@]}" credential check --house "$HOUSE"
109
- else
110
- if ((MANUAL)); then
111
- "${DOCKER_RUN[@]:0:2}" -i "${DOCKER_RUN[@]:2}" login --house "$HOUSE" --manual
544
+ if kit_installed; then
545
+ check_installation
546
+ [[ -f "$KIT_HOME/credential" && -f "$KIT_HOME/environment" && -f "$KIT_HOME/house" ]] || refuse 'the installed Kit has no enrolled authority'
547
+ if ((FORWARDING)); then
548
+ [[ "$(installed_image)" == "$IMAGE" ]] || refuse "an installed Kit uses a different image; rerun this bootstrap $RERUN to update it"
549
+ if [[ "$FORWARDED" == kit && "${FORWARD[0]:-}" == login ]]; then
550
+ login_kit ${FORWARD[@]+"${FORWARD[@]:1}"}
551
+ exit 0
552
+ fi
553
+ forward_kit
554
+ exit
555
+ fi
556
+ ((NATIVE == 0)) || establish_native_packages
557
+ if [[ "$(installed_image)" != "$IMAGE" ]]; then
558
+ update_kit
112
559
  else
113
- command -v xdg-open >/dev/null || refuse 'no host browser opener; rerun with --manual'
114
- "${DOCKER_RUN[@]}" login --house "$HOUSE" --host-browser | while IFS= read -r line; do
115
- if [[ "$line" == 'Open '* ]]; then
116
- printf 'Opening House Login in the host browser.\n'
117
- xdg-open "${line#Open }" >/dev/null 2>&1 || refuse 'host browser did not open; rerun with --manual'
118
- else
119
- printf '%s\n' "$line"
120
- fi
121
- done
560
+ resume_kit
122
561
  fi
562
+ ((NATIVE == 0)) || native_kit kit workspace update
563
+ exit 0
123
564
  fi
124
565
 
125
- "${DOCKER_RUN[@]}" apply --house "$HOUSE"
126
- docker run -d --name "$NAME" --restart unless-stopped --user "$(id -u):$(id -g)" --mount "type=bind,src=$KIT_HOME,dst=/kit-home" --mount "type=bind,src=$WORKSPACE,dst=/agents/house" --label "agentshouse.house=$HOUSE" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 "$IMAGE" resident --house "$HOUSE" >/dev/null
127
- printf 'House Kit connected for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
566
+ if ((NATIVE)); then
567
+ establish_native_packages
568
+ if [[ "$(installed_image)" != "$IMAGE" ]]; then
569
+ stage_native_kit
570
+ place_native_kit
571
+ fi
572
+ else
573
+ docker pull --quiet --platform "linux/$ARCH" "$IMAGE" >/dev/null
574
+ fi
575
+ connect_kit