@agentshouse/kit 0.1.0-alpha.36 → 0.1.0-alpha.38
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 +38 -2277
- package/bin/connect-linux.sh +291 -78
- package/bin/connect-windows.ps1 +1 -1
- package/bootstrap.json +5 -5
- package/docs/configuration.md +104 -0
- package/docs/google.md +33 -0
- package/docs/linux.md +71 -0
- package/docs/macos.md +26 -0
- package/docs/telegram.md +29 -0
- package/docs/windows.md +36 -0
- package/package.json +1 -1
package/bin/connect-linux.sh
CHANGED
|
@@ -21,6 +21,15 @@ RUNTIME='Docker Engine'
|
|
|
21
21
|
OPENER=xdg-open
|
|
22
22
|
NETWORK=(--network host)
|
|
23
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
|
|
24
33
|
|
|
25
34
|
refuse() {
|
|
26
35
|
printf 'kit_bootstrap_refused: %s\n' "$1" >&2
|
|
@@ -32,13 +41,25 @@ bind_mount() {
|
|
|
32
41
|
printf 'type=bind,"src=%s",dst=%s' "$source" "$2"
|
|
33
42
|
}
|
|
34
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
|
+
|
|
35
48
|
login_kit() {
|
|
36
49
|
if [[ " $* " == *' --manual '* ]]; then
|
|
37
|
-
|
|
50
|
+
if ((NATIVE)); then
|
|
51
|
+
native_kit kit login "$@"
|
|
52
|
+
else
|
|
53
|
+
"${DOCKER_RUN[@]:0:2}" -i "${DOCKER_RUN[@]:2}" login "$@"
|
|
54
|
+
fi
|
|
38
55
|
return
|
|
39
56
|
fi
|
|
40
57
|
command -v "$OPENER" >/dev/null || refuse 'no host browser opener; rerun with --manual'
|
|
41
|
-
|
|
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
|
|
42
63
|
if [[ "$line" == 'Open '* ]]; then
|
|
43
64
|
printf 'Opening House Login in the host browser.\n'
|
|
44
65
|
"$OPENER" "${line#Open }" >/dev/null 2>&1 || refuse 'host browser did not open; rerun with --manual'
|
|
@@ -169,6 +190,204 @@ establish_docker_engine() {
|
|
|
169
190
|
docker_answers || refuse "Docker Engine does not answer: $(docker_cause)"
|
|
170
191
|
}
|
|
171
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
|
+
|
|
172
391
|
if [[ "${1:-}" == kit || "${1:-}" == house ]]; then
|
|
173
392
|
FORWARDING=1
|
|
174
393
|
FORWARDED="$1"
|
|
@@ -199,9 +418,20 @@ while (($#)); do
|
|
|
199
418
|
SKILLS=false
|
|
200
419
|
shift
|
|
201
420
|
;;
|
|
421
|
+
--linux)
|
|
422
|
+
NATIVE=1
|
|
423
|
+
shift
|
|
424
|
+
;;
|
|
202
425
|
*) refuse "unknown argument $1" ;;
|
|
203
426
|
esac
|
|
204
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
|
|
205
435
|
|
|
206
436
|
[[ "$IMAGE" =~ ^ghcr\.io/agentshouse/kit@sha256:[0-9a-f]{64}$ ]] || refuse 'this bootstrap has not been published with an immutable image digest'
|
|
207
437
|
case "$(uname -s)" in
|
|
@@ -225,21 +455,36 @@ case "$(uname -s)" in
|
|
|
225
455
|
;;
|
|
226
456
|
*) refuse "unsupported host $(uname -s); this bootstrap is for Linux and macOS" ;;
|
|
227
457
|
esac
|
|
458
|
+
((NATIVE == 0)) || check_native_host
|
|
228
459
|
[[ "$WORKSPACE" == /* ]] || refuse 'workspace root must be an absolute path'
|
|
229
|
-
if
|
|
230
|
-
|
|
231
|
-
DOCKER_SYSTEM=$(docker info --format '{{.OperatingSystem}}' 2>/dev/null) || refuse "Docker Desktop does not answer: $(docker_cause)"
|
|
232
|
-
[[ "$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"
|
|
460
|
+
if ((NATIVE && FORWARDING)); then
|
|
461
|
+
[[ -f "$NATIVE_UNIT" ]] || refuse 'House Kit is not installed; run this bootstrap with --linux first'
|
|
233
462
|
else
|
|
234
|
-
|
|
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"
|
|
235
476
|
fi
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
[[ "$
|
|
242
|
-
|
|
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
|
|
243
488
|
docker container inspect "$NAME" >/dev/null 2>&1 || refuse 'House Kit is not installed; run this bootstrap without arguments first'
|
|
244
489
|
WORKSPACE=$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Source}}{{end}}{{end}}' "$NAME")
|
|
245
490
|
fi
|
|
@@ -270,92 +515,60 @@ fi
|
|
|
270
515
|
[[ "$HOUSE" == https://* || "$HOUSE" == http://127.0.0.1:* || "$HOUSE" == http://localhost:* ]] || refuse 'House origin must use HTTPS or local loopback HTTP'
|
|
271
516
|
if ((FORWARDING == 0)); then
|
|
272
517
|
if [[ ! -f "$KIT_HOME/kit.json" ]]; then
|
|
273
|
-
printf '{"availability":"
|
|
518
|
+
printf '{"availability":"%s","execution_authority":"allow","migrators":[],"instances":[],"clients":[],"host":null,"skills":%s}\n' "$AVAILABILITY" "$SKILLS" > "$KIT_HOME/kit.json"
|
|
274
519
|
elif grep -Eq '"skills"[[:space:]]*:' "$KIT_HOME/kit.json"; then
|
|
275
520
|
sed -E -i.old "s/(\"skills\"[[:space:]]*:[[:space:]]*)(true|false)/\\1$SKILLS/" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
|
|
276
521
|
else
|
|
277
522
|
sed -E -i.old "1s/^([[:space:]]*)\\{/\\1{\"skills\":$SKILLS,/" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
|
|
278
523
|
fi
|
|
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
|
|
279
527
|
rm -f "$KIT_HOME/kit.json.old"
|
|
280
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'
|
|
281
530
|
fi
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
531
|
+
|
|
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")
|
|
286
542
|
fi
|
|
287
|
-
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")
|
|
288
543
|
|
|
289
|
-
if
|
|
290
|
-
|
|
291
|
-
((FORWARDING == 0)) || [[ "$INSTALLED_IMAGE" == "$IMAGE" ]] || refuse 'an installed Kit uses a different image; rerun this bootstrap without arguments to update it'
|
|
292
|
-
[[ "$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' "$NAME")" == unless-stopped ]] || refuse 'the installed Kit has a different lifecycle'
|
|
293
|
-
[[ "$(docker inspect --format '{{len .Mounts}}' "$NAME")" == 2 ]] || refuse 'the installed Kit has unexpected mounts'
|
|
294
|
-
[[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/kit-home"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$KIT_HOME" ]] || refuse 'the installed Kit uses another Kit home'
|
|
295
|
-
[[ "$(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'
|
|
296
|
-
[[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$WORKSPACE" ]] || refuse 'the installed Kit uses another workspace root'
|
|
297
|
-
[[ "$(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'
|
|
298
|
-
[[ "$(docker inspect --format '{{index .Config.Labels "agentshouse.house"}}' "$NAME")" == "$HOUSE" ]] || refuse 'the installed Kit uses another House origin'
|
|
544
|
+
if kit_installed; then
|
|
545
|
+
check_installation
|
|
299
546
|
[[ -f "$KIT_HOME/credential" && -f "$KIT_HOME/environment" && -f "$KIT_HOME/house" ]] || refuse 'the installed Kit has no enrolled authority'
|
|
300
547
|
if ((FORWARDING)); then
|
|
548
|
+
[[ "$(installed_image)" == "$IMAGE" ]] || refuse "an installed Kit uses a different image; rerun this bootstrap $RERUN to update it"
|
|
301
549
|
if [[ "$FORWARDED" == kit && "${FORWARD[0]:-}" == login ]]; then
|
|
302
550
|
login_kit ${FORWARD[@]+"${FORWARD[@]:1}"}
|
|
303
551
|
exit 0
|
|
304
552
|
fi
|
|
305
|
-
|
|
306
|
-
ENTRY=()
|
|
307
|
-
if [[ "$FORWARDED" == house ]]; then
|
|
308
|
-
HERE=$(pwd -P)
|
|
309
|
-
[[ "$HERE" == "$WORKSPACE" || "$HERE" == "$WORKSPACE"/* ]] || refuse "the current directory is outside the workspace $WORKSPACE"
|
|
310
|
-
PLACED=(--workdir "/agents/house${HERE#"$WORKSPACE"}")
|
|
311
|
-
ENTRY=(--entrypoint house)
|
|
312
|
-
fi
|
|
313
|
-
TERMINAL=()
|
|
314
|
-
if [[ -t 0 && -t 1 ]]; then TERMINAL=(-t); fi
|
|
315
|
-
if [[ -t 0 && ! -t 1 ]]; then
|
|
316
|
-
ECHOED=$(stty -g)
|
|
317
|
-
trap 'stty "$ECHOED"' EXIT
|
|
318
|
-
stty -echo
|
|
319
|
-
fi
|
|
320
|
-
if [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]; then
|
|
321
|
-
docker exec -i ${TERMINAL[@]+"${TERMINAL[@]}"} ${PLACED[@]+"${PLACED[@]}"} "$NAME" "$FORWARDED" ${FORWARD[@]+"${FORWARD[@]}"}
|
|
322
|
-
else
|
|
323
|
-
"${DOCKER_RUN[@]:0:2}" -i ${TERMINAL[@]+"${TERMINAL[@]}"} ${PLACED[@]+"${PLACED[@]}"} ${ENTRY[@]+"${ENTRY[@]}"} "${DOCKER_RUN[@]:2}" ${FORWARD[@]+"${FORWARD[@]}"}
|
|
324
|
-
fi
|
|
553
|
+
forward_kit
|
|
325
554
|
exit
|
|
326
555
|
fi
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
docker exec "$NAME" kit resident drain
|
|
331
|
-
fi
|
|
332
|
-
docker rm -f "$NAME" >/dev/null
|
|
333
|
-
rm -f "$KIT_HOME/state/update-drain"
|
|
334
|
-
start_resident
|
|
335
|
-
printf 'House Kit updated for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
|
|
336
|
-
exit 0
|
|
337
|
-
fi
|
|
338
|
-
"${DOCKER_RUN[@]}" credential check
|
|
339
|
-
if [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]; then
|
|
340
|
-
printf 'House Kit is already running for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
|
|
556
|
+
((NATIVE == 0)) || establish_native_packages
|
|
557
|
+
if [[ "$(installed_image)" != "$IMAGE" ]]; then
|
|
558
|
+
update_kit
|
|
341
559
|
else
|
|
342
|
-
|
|
343
|
-
printf 'House Kit restarted for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
|
|
560
|
+
resume_kit
|
|
344
561
|
fi
|
|
345
562
|
exit 0
|
|
346
563
|
fi
|
|
347
564
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if [[
|
|
351
|
-
|
|
565
|
+
if ((NATIVE)); then
|
|
566
|
+
establish_native_packages
|
|
567
|
+
if [[ "$(installed_image)" != "$IMAGE" ]]; then
|
|
568
|
+
stage_native_kit
|
|
569
|
+
place_native_kit
|
|
570
|
+
fi
|
|
352
571
|
else
|
|
353
|
-
|
|
354
|
-
if ((HOUSE_SELECTED)); then LOGIN+=(--house "$HOUSE"); fi
|
|
355
|
-
if ((MANUAL)); then LOGIN+=(--manual); fi
|
|
356
|
-
login_kit ${LOGIN[@]+"${LOGIN[@]}"}
|
|
572
|
+
docker pull --quiet --platform "linux/$ARCH" "$IMAGE" >/dev/null
|
|
357
573
|
fi
|
|
358
|
-
|
|
359
|
-
"${DOCKER_RUN[@]}" apply
|
|
360
|
-
start_resident
|
|
361
|
-
printf 'House Kit connected for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
|
|
574
|
+
connect_kit
|
package/bin/connect-windows.ps1
CHANGED
|
@@ -364,7 +364,7 @@ function Connect-Kit {
|
|
|
364
364
|
$entry = @('--entrypoint', 'house')
|
|
365
365
|
}
|
|
366
366
|
$terminal = @()
|
|
367
|
-
if (-not [Console]::IsInputRedirected
|
|
367
|
+
if (-not [Console]::IsInputRedirected) { $terminal = @('-t') }
|
|
368
368
|
if ((Read-Inspect '{{.State.Running}}') -ceq 'true') {
|
|
369
369
|
& docker exec -i @terminal @placed $Name $Forwarded @Forward
|
|
370
370
|
} else {
|
package/bootstrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.38",
|
|
4
4
|
"hosts": {
|
|
5
5
|
"linux": {
|
|
6
|
-
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
6
|
+
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/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.
|
|
9
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/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.
|
|
17
|
+
"command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/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.
|
|
20
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/connect-windows.ps1"
|
|
21
21
|
},
|
|
22
22
|
"workspace": {
|
|
23
23
|
"argument": "--workspace",
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
`kit.json` in Kit home is the one configuration file. Every section below is
|
|
4
|
+
required; a file that omits one is refused.
|
|
5
|
+
|
|
6
|
+
```json
|
|
7
|
+
{
|
|
8
|
+
"availability": "continuous",
|
|
9
|
+
"execution_authority": "allow",
|
|
10
|
+
"migrators": [],
|
|
11
|
+
"instances": [
|
|
12
|
+
{
|
|
13
|
+
"id": "acme",
|
|
14
|
+
"preset": "custom",
|
|
15
|
+
"services": [
|
|
16
|
+
{
|
|
17
|
+
"service": "notes",
|
|
18
|
+
"source_ref": "src_acme_notes",
|
|
19
|
+
"rooms": ["work-room"],
|
|
20
|
+
"backfill": { "days": 30 },
|
|
21
|
+
"connector": {
|
|
22
|
+
"command": ["/usr/local/bin/acme-export", "--ndjson"],
|
|
23
|
+
"timeout_ms": 120000,
|
|
24
|
+
"environment": { "ACME_REGION": "eu" },
|
|
25
|
+
"secrets": {
|
|
26
|
+
"ACME_TOKEN": { "environment": "ACME_TOKEN_SOURCE" },
|
|
27
|
+
"ACME_KEY": { "file": "/run/secrets/acme-key" }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"clients": [
|
|
35
|
+
{
|
|
36
|
+
"kind": "claude-code",
|
|
37
|
+
"settings": "/home/ada/.claude/settings.json"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"host": null,
|
|
41
|
+
"skills": true
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- `availability`: `"continuous"` for a computer that stays online,
|
|
46
|
+
`"intermittent"` for a laptop.
|
|
47
|
+
- `execution_authority`: `"allow"` or `"refuse"`, this computer's answer to
|
|
48
|
+
every permission request an agent makes.
|
|
49
|
+
- `instances`: your own sources, each with a `custom` connector (below).
|
|
50
|
+
- `clients`: agent clients outside Kit whose settings get House's session-start
|
|
51
|
+
hook. `claude-code` is the one supported kind; declare `[]` for none.
|
|
52
|
+
- `migrators`: `[]` unless you run a migrator.
|
|
53
|
+
- `host`: `null` unless this computer reports its capacity to House.
|
|
54
|
+
- `skills`: `true` accepts the Skill set, `false` declines it. The connect
|
|
55
|
+
command sets it.
|
|
56
|
+
|
|
57
|
+
Agents and agent CLIs are chosen in House, not here. Send your changes to
|
|
58
|
+
House with:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
kit apply
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Secrets
|
|
65
|
+
|
|
66
|
+
Never write a secret into `kit.json`. Under `secrets`, name an environment
|
|
67
|
+
variable (`{ "environment": "NAME" }`) or a file (`{ "file": "/path" }`); Kit
|
|
68
|
+
passes its value to the connector only.
|
|
69
|
+
|
|
70
|
+
## Custom connectors
|
|
71
|
+
|
|
72
|
+
A connector is your own command. Kit runs it without a shell, with only `PATH`,
|
|
73
|
+
`environment` and the resolved secrets, and never gives it a House credential.
|
|
74
|
+
It must finish within `timeout_ms` (at most 120000).
|
|
75
|
+
|
|
76
|
+
Kit writes one request line to its standard input:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{ "contract": "2026-08-22", "instance": "acme", "cursor": null, "limit": 100 }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The command prints NDJSON: `item` lines, then at most one `cursor` line.
|
|
83
|
+
`cursor` in the next request is the last one it printed.
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
{"type":"item","namespace":"acme/notes","native_object_id":"note-91","native_version":"7","observed_at":"2026-08-20T09:15:00.000Z","body":"the note","author":"ada","url":"https://acme.example/notes/91","attachments":[{"url":"https://acme.example/files/1","name":"plan.pdf"}]}
|
|
87
|
+
{"type":"cursor","cursor":"page-2"}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`namespace`, `native_object_id`, `observed_at` and `body` are required. An
|
|
91
|
+
attachment is a `url`, or a local `file` with its exact `path`, `bytes`,
|
|
92
|
+
`sha256` and optional `media_type`. A malformed line, a non-zero exit or a
|
|
93
|
+
timeout rejects the whole run and nothing is delivered.
|
|
94
|
+
|
|
95
|
+
`backfill` bounds the first capture: `{ "days": 30 }`, `{ "count": 500 }` or
|
|
96
|
+
`{ "all": true }`.
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
kit source capture acme # capture every service of an instance now
|
|
100
|
+
kit source capture acme notes # one service
|
|
101
|
+
kit source status acme # health, cursor, gaps and unsaved files
|
|
102
|
+
kit source retry-files acme # save files a capture could not save
|
|
103
|
+
kit source reset acme notes # start again from the backfill boundary
|
|
104
|
+
```
|
package/docs/google.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Google sources
|
|
2
|
+
|
|
3
|
+
You add Gmail, Calendar and Drive sources in the House App: an account name you
|
|
4
|
+
choose, the service, the Rooms it goes to, and how much history the first
|
|
5
|
+
capture takes. Nothing is configured on this computer.
|
|
6
|
+
|
|
7
|
+
Each service captures, read-only:
|
|
8
|
+
|
|
9
|
+
- Gmail: every message except Spam and Trash.
|
|
10
|
+
- Calendar: the account's primary calendar.
|
|
11
|
+
- Drive: the files the account owns.
|
|
12
|
+
|
|
13
|
+
## Signing in
|
|
14
|
+
|
|
15
|
+
Kit ships no Google OAuth client. Each account signs in with your own:
|
|
16
|
+
|
|
17
|
+
1. In your Google Cloud project, create an OAuth client of type **Desktop app**
|
|
18
|
+
and enable the API of each service you use.
|
|
19
|
+
2. When House asks, enter the client id and secret on its page.
|
|
20
|
+
3. Approve Google's consent. Google then sends the browser to a
|
|
21
|
+
`http://127.0.0.1:<port>/` address that does not load; copy that whole
|
|
22
|
+
address and paste it into the House page.
|
|
23
|
+
|
|
24
|
+
Kit then captures each signed-in source every 15 minutes.
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
kit source capture work # capture every source of the account now
|
|
28
|
+
kit source capture work gmail # one service
|
|
29
|
+
kit source status work
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If `kit source status` reports `api_disabled`, enable the API it names in your
|
|
33
|
+
Google Cloud project.
|