@madarco/agentbox 0.15.0 → 0.17.0
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/CHANGELOG.md +68 -0
- package/dist/{_cloud-attach-R6TRWG5L.js → _cloud-attach-SGNR6BXP.js} +5 -4
- package/dist/{chunk-BKU34KYY.js → chunk-3JXSW6PN.js} +42 -24
- package/dist/chunk-3JXSW6PN.js.map +1 -0
- package/dist/{chunk-RSKG7AFU.js → chunk-46NXCJ6Z.js} +45 -34
- package/dist/{chunk-RSKG7AFU.js.map → chunk-46NXCJ6Z.js.map} +1 -1
- package/dist/{chunk-43Q5GWP6.js → chunk-5AKAC27L.js} +7 -7
- package/dist/{chunk-XKH7NTT7.js → chunk-6QFPYU4Z.js} +248 -5
- package/dist/chunk-6QFPYU4Z.js.map +1 -0
- package/dist/{chunk-MLMFNN4T.js → chunk-HFQZJO73.js} +688 -197
- package/dist/chunk-HFQZJO73.js.map +1 -0
- package/dist/{chunk-E7CHS7ZR.js → chunk-KTDJ5JTE.js} +18 -6
- package/dist/chunk-KTDJ5JTE.js.map +1 -0
- package/dist/{chunk-72CJTXN6.js → chunk-RXPV3OIX.js} +80 -72
- package/dist/chunk-RXPV3OIX.js.map +1 -0
- package/dist/chunk-WJFZJZIM.js +24 -0
- package/dist/{chunk-MCOU6CZS.js → chunk-XWBFWUY2.js} +30 -22
- package/dist/chunk-XWBFWUY2.js.map +1 -0
- package/dist/{cloud-poller-SUNA6ZQC-2RG5WPRN.js → cloud-poller-SUNA6ZQC-7M5LJHHE.js} +2 -1
- package/dist/{dist-S4XR4ACV.js → dist-433ASGVG.js} +6 -5
- package/dist/{dist-S4XR4ACV.js.map → dist-433ASGVG.js.map} +1 -1
- package/dist/{dist-JZ3XO6EB.js → dist-BO2R55FX.js} +6 -5
- package/dist/{dist-JZ3XO6EB.js.map → dist-BO2R55FX.js.map} +1 -1
- package/dist/{dist-AGTIA7AD.js → dist-CNABE32V.js} +7 -6
- package/dist/{dist-AGTIA7AD.js.map → dist-CNABE32V.js.map} +1 -1
- package/dist/{dist-FIFEFKJ7.js → dist-TEKY3GFT.js} +6 -5
- package/dist/{dist-FIFEFKJ7.js.map → dist-TEKY3GFT.js.map} +1 -1
- package/dist/{dist-OGJGZETZ.js → dist-VAATGBAR.js} +6 -5
- package/dist/index.js +2426 -2094
- package/dist/index.js.map +1 -1
- package/dist/{prepared-state-MQHD3M5F-Q27AZU53.js → prepared-state-MQHD3M5F-OVABNV66.js} +3 -2
- package/dist/prepared-state-MQHD3M5F-OVABNV66.js.map +1 -0
- package/package.json +5 -4
- package/runtime/docker/Dockerfile.box +21 -2
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +82 -29
- package/runtime/docker/packages/ctl/dist/bin.cjs +10675 -9191
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +5 -2
- package/runtime/docker/packages/sandbox-docker/scripts/linear-shim +181 -0
- package/runtime/docker/packages/sandbox-docker/scripts/ntn-shim +95 -0
- package/runtime/e2b/agentbox-checkpoint-cleanup +5 -2
- package/runtime/e2b/agentbox-setup-skill.md +82 -29
- package/runtime/e2b/ctl.cjs +10675 -9191
- package/runtime/e2b/linear-shim +181 -0
- package/runtime/e2b/ntn-shim +95 -0
- package/runtime/e2b/scripts/build-template.sh +13 -7
- package/runtime/hetzner/agentbox-checkpoint-cleanup +5 -2
- package/runtime/hetzner/agentbox-setup-skill.md +82 -29
- package/runtime/hetzner/ctl.cjs +10675 -9191
- package/runtime/hetzner/linear-shim +181 -0
- package/runtime/hetzner/ntn-shim +95 -0
- package/runtime/hetzner/scripts/install-box.sh +19 -9
- package/runtime/relay/bin.cjs +3696 -2895
- package/runtime/vercel/agentbox-checkpoint-cleanup +5 -2
- package/runtime/vercel/agentbox-setup-skill.md +82 -29
- package/runtime/vercel/ctl.cjs +10675 -9191
- package/runtime/vercel/linear-shim +181 -0
- package/runtime/vercel/ntn-shim +95 -0
- package/runtime/vercel/scripts/provision.sh +13 -7
- package/share/agentbox-setup/SKILL.md +82 -29
- package/share/host-skills/agentbox-info/SKILL.md +1 -1
- package/dist/chunk-72CJTXN6.js.map +0 -1
- package/dist/chunk-BKU34KYY.js.map +0 -1
- package/dist/chunk-E7CHS7ZR.js.map +0 -1
- package/dist/chunk-MCOU6CZS.js.map +0 -1
- package/dist/chunk-MLMFNN4T.js.map +0 -1
- package/dist/chunk-XKH7NTT7.js.map +0 -1
- /package/dist/{_cloud-attach-R6TRWG5L.js.map → _cloud-attach-SGNR6BXP.js.map} +0 -0
- /package/dist/{chunk-43Q5GWP6.js.map → chunk-5AKAC27L.js.map} +0 -0
- /package/dist/{cloud-poller-SUNA6ZQC-2RG5WPRN.js.map → chunk-WJFZJZIM.js.map} +0 -0
- /package/dist/{dist-OGJGZETZ.js.map → cloud-poller-SUNA6ZQC-7M5LJHHE.js.map} +0 -0
- /package/dist/{prepared-state-MQHD3M5F-Q27AZU53.js.map → dist-VAATGBAR.js.map} +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# agentbox `linear` shim — translates a strict subset of `linear`
|
|
3
|
+
# (@schpet/linear-cli, v2) subcommands into `agentbox-ctl integration
|
|
4
|
+
# linear <op>` so the host's authenticated `linear` runs the operation and
|
|
5
|
+
# only the result crosses back into the box. The in-box agent never sees a
|
|
6
|
+
# Linear API token.
|
|
7
|
+
#
|
|
8
|
+
# Installed at /usr/local/bin/linear (real `linear` is not in the box).
|
|
9
|
+
#
|
|
10
|
+
# This shim ships only what documented agent flows need; anything outside
|
|
11
|
+
# the subset below is rejected with a clear error. Add ops deliberately —
|
|
12
|
+
# the relay is gated by `integrations.linear.enabled` and an explicit op
|
|
13
|
+
# allowlist in @agentbox/integrations.
|
|
14
|
+
#
|
|
15
|
+
# Three classes of upstream subcommand are EXPLICITLY rejected even though
|
|
16
|
+
# they exist on the host CLI, because proxying them would defeat the
|
|
17
|
+
# security model:
|
|
18
|
+
# - `auth token` PRINTS the raw API token to stdout — proxying it would
|
|
19
|
+
# hand the box the host's Linear credential. The only auth-family op
|
|
20
|
+
# we proxy is `auth whoami` (identity only), via `linear whoami`.
|
|
21
|
+
# - `auth login/logout/migrate/default` would mutate host auth state.
|
|
22
|
+
# - `issue delete` / `team delete` / `team create` are destructive and
|
|
23
|
+
# off-list (widen deliberately, as gated writes, only if needed).
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
# Path is a constant in production; the env override exists purely to let
|
|
28
|
+
# unit tests substitute a stub `agentbox-ctl` on PATH without rewriting the
|
|
29
|
+
# shim. Mirrors gh-shim / git-shim / ntn-shim.
|
|
30
|
+
CTL="${AGENTBOX_CTL_PATH:-/usr/local/bin/agentbox-ctl}"
|
|
31
|
+
|
|
32
|
+
die() {
|
|
33
|
+
printf 'agentbox linear shim: %s\n' "$*" >&2
|
|
34
|
+
exit 2
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
handle_auth() {
|
|
38
|
+
local sub="${1-}"; shift || true
|
|
39
|
+
case "$sub" in
|
|
40
|
+
whoami)
|
|
41
|
+
exec "$CTL" integration linear whoami -- "$@"
|
|
42
|
+
;;
|
|
43
|
+
token)
|
|
44
|
+
die "'auth token' leaks the raw API key — refused. Use 'linear whoami' for identity."
|
|
45
|
+
;;
|
|
46
|
+
login|logout|migrate|default)
|
|
47
|
+
die "'auth $sub' is not proxied (the host owns auth; run it on the host)."
|
|
48
|
+
;;
|
|
49
|
+
'')
|
|
50
|
+
die "missing subcommand for 'auth'. Supported: whoami"
|
|
51
|
+
;;
|
|
52
|
+
*)
|
|
53
|
+
die "unsupported 'auth $sub' (allowed: whoami)"
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
handle_issue_comment() {
|
|
59
|
+
local sub="${1-}"; shift || true
|
|
60
|
+
case "$sub" in
|
|
61
|
+
add)
|
|
62
|
+
exec "$CTL" integration linear issue.comment -- "$@"
|
|
63
|
+
;;
|
|
64
|
+
'')
|
|
65
|
+
die "missing subcommand for 'issue comment'. Supported: add"
|
|
66
|
+
;;
|
|
67
|
+
*)
|
|
68
|
+
die "unsupported 'issue comment $sub' (allowed: add)"
|
|
69
|
+
;;
|
|
70
|
+
esac
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
handle_issue() {
|
|
74
|
+
local sub="${1-}"; shift || true
|
|
75
|
+
case "$sub" in
|
|
76
|
+
list)
|
|
77
|
+
exec "$CTL" integration linear issue.list -- "$@"
|
|
78
|
+
;;
|
|
79
|
+
mine)
|
|
80
|
+
exec "$CTL" integration linear issue.mine -- "$@"
|
|
81
|
+
;;
|
|
82
|
+
view)
|
|
83
|
+
exec "$CTL" integration linear issue.view -- "$@"
|
|
84
|
+
;;
|
|
85
|
+
query)
|
|
86
|
+
exec "$CTL" integration linear issue.query -- "$@"
|
|
87
|
+
;;
|
|
88
|
+
create)
|
|
89
|
+
exec "$CTL" integration linear issue.create -- "$@"
|
|
90
|
+
;;
|
|
91
|
+
update)
|
|
92
|
+
exec "$CTL" integration linear issue.update -- "$@"
|
|
93
|
+
;;
|
|
94
|
+
comment)
|
|
95
|
+
handle_issue_comment "$@"
|
|
96
|
+
;;
|
|
97
|
+
delete)
|
|
98
|
+
die "'issue delete' is not proxied (destructive; off-list by default)."
|
|
99
|
+
;;
|
|
100
|
+
'')
|
|
101
|
+
die "missing subcommand for 'issue'. Supported: list, mine, view, query, create, update, comment add"
|
|
102
|
+
;;
|
|
103
|
+
*)
|
|
104
|
+
die "unsupported 'issue $sub' (allowed: list, mine, view, query, create, update, comment add)"
|
|
105
|
+
;;
|
|
106
|
+
esac
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
handle_team() {
|
|
110
|
+
local sub="${1-}"; shift || true
|
|
111
|
+
case "$sub" in
|
|
112
|
+
list)
|
|
113
|
+
exec "$CTL" integration linear team.list -- "$@"
|
|
114
|
+
;;
|
|
115
|
+
create|delete)
|
|
116
|
+
die "'team $sub' is not proxied (destructive; off-list by default)."
|
|
117
|
+
;;
|
|
118
|
+
'')
|
|
119
|
+
die "missing subcommand for 'team'. Supported: list"
|
|
120
|
+
;;
|
|
121
|
+
*)
|
|
122
|
+
die "unsupported 'team $sub' (allowed: list)"
|
|
123
|
+
;;
|
|
124
|
+
esac
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Top-level dispatch. `linear`'s real subcommands are
|
|
128
|
+
# `auth issue team project cycle milestone initiative label document api schema`;
|
|
129
|
+
# we expose only the read-safe ones plus a few gated writes (no destructive
|
|
130
|
+
# ops, no auth token).
|
|
131
|
+
if [ $# -eq 0 ]; then
|
|
132
|
+
die "no subcommand. Supported: whoami, auth whoami, issue {list,mine,view,query,create,update,comment add}, team list, api <query>, --version"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
case "$1" in
|
|
136
|
+
--version|-v)
|
|
137
|
+
# Tools that sniff "linear --version" succeed with our shim line. The
|
|
138
|
+
# real version lives host-side and is reported by the relay's
|
|
139
|
+
# readiness probe (`assertIntegrationReady`).
|
|
140
|
+
printf 'linear version 0.0.0 (agentbox-shim)\n'
|
|
141
|
+
;;
|
|
142
|
+
--help|-h)
|
|
143
|
+
printf 'agentbox linear shim — strict subset.\n' >&2
|
|
144
|
+
printf 'Supported: whoami, auth whoami, issue {list,mine,view,query,create,update,comment add}, team list, api <query>, --version\n' >&2
|
|
145
|
+
printf 'Anything else is rejected. Run host `linear --help` for full upstream docs.\n' >&2
|
|
146
|
+
;;
|
|
147
|
+
whoami)
|
|
148
|
+
shift
|
|
149
|
+
exec "$CTL" integration linear whoami -- "$@"
|
|
150
|
+
;;
|
|
151
|
+
auth)
|
|
152
|
+
shift
|
|
153
|
+
handle_auth "$@"
|
|
154
|
+
;;
|
|
155
|
+
issue)
|
|
156
|
+
shift
|
|
157
|
+
handle_issue "$@"
|
|
158
|
+
;;
|
|
159
|
+
team)
|
|
160
|
+
shift
|
|
161
|
+
handle_team "$@"
|
|
162
|
+
;;
|
|
163
|
+
api)
|
|
164
|
+
shift
|
|
165
|
+
# `linear api` accepts pre-positional flags (`--variable`,
|
|
166
|
+
# `--variables-json`, `--paginate`, `--silent`) before the GraphQL
|
|
167
|
+
# query, so we don't require the FIRST arg to be a non-flag — only
|
|
168
|
+
# that some arg is present. The relay's refuseGraphqlNonQuery
|
|
169
|
+
# enforces query-only by rejecting any positional whose first
|
|
170
|
+
# keyword is `mutation`/`subscription` (and any `--variable
|
|
171
|
+
# key=@<path>` host-file load), so we don't duplicate that check
|
|
172
|
+
# here. Writes go through the dedicated issue.* ops.
|
|
173
|
+
if [ $# -eq 0 ]; then
|
|
174
|
+
die "'api' requires a positional <query> (e.g. '{ teams { id } }')"
|
|
175
|
+
fi
|
|
176
|
+
exec "$CTL" integration linear api -- "$@"
|
|
177
|
+
;;
|
|
178
|
+
*)
|
|
179
|
+
die "'$1' is not proxied (supported: whoami, issue {list,mine,view,query,create,update,comment add}, team list, api <query>, --version)"
|
|
180
|
+
;;
|
|
181
|
+
esac
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# agentbox `ntn` shim — translates a strict subset of `ntn` (the official
|
|
3
|
+
# Notion CLI) subcommands into `agentbox-ctl integration notion <op>` so the
|
|
4
|
+
# host's authenticated `ntn` runs the operation and only the result crosses
|
|
5
|
+
# back into the box. The in-box agent never sees a Notion token.
|
|
6
|
+
#
|
|
7
|
+
# Installed at /usr/local/bin/ntn (real `ntn` is not in the box). The same
|
|
8
|
+
# shim is symlinked as /usr/local/bin/notion — the per-service surface name
|
|
9
|
+
# from docs/integrations_backlog.md — both invocations behave identically.
|
|
10
|
+
#
|
|
11
|
+
# This shim ships only what documented agent flows need; anything outside
|
|
12
|
+
# the subset below is rejected with a clear error. Add ops deliberately —
|
|
13
|
+
# the relay is gated by `integrations.notion.enabled` and an explicit op
|
|
14
|
+
# allowlist in @agentbox/integrations.
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
# Path is a constant in production; the env override exists purely to let
|
|
19
|
+
# unit tests substitute a stub `agentbox-ctl` on PATH without rewriting the
|
|
20
|
+
# shim. Mirrors gh-shim / git-shim.
|
|
21
|
+
CTL="${AGENTBOX_CTL_PATH:-/usr/local/bin/agentbox-ctl}"
|
|
22
|
+
|
|
23
|
+
die() {
|
|
24
|
+
printf 'agentbox notion shim: %s\n' "$*" >&2
|
|
25
|
+
exit 2
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
handle_pages() {
|
|
29
|
+
local op="${1-}"; shift || true
|
|
30
|
+
case "$op" in
|
|
31
|
+
create)
|
|
32
|
+
exec "$CTL" integration notion page.create -- "$@"
|
|
33
|
+
;;
|
|
34
|
+
update)
|
|
35
|
+
exec "$CTL" integration notion page.update -- "$@"
|
|
36
|
+
;;
|
|
37
|
+
'')
|
|
38
|
+
die "missing subcommand for 'pages'. Supported: create, update"
|
|
39
|
+
;;
|
|
40
|
+
*)
|
|
41
|
+
die "unsupported 'pages $op' (allowed: create, update)"
|
|
42
|
+
;;
|
|
43
|
+
esac
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Top-level dispatch. `ntn`'s real subcommands are
|
|
47
|
+
# `api datasources files pages login logout whoami workers`; we expose only
|
|
48
|
+
# the read-safe ones plus `pages {create,update}`.
|
|
49
|
+
if [ $# -eq 0 ]; then
|
|
50
|
+
die "no subcommand. Supported: whoami, api <endpoint>, pages {create,update}, --version"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
case "$1" in
|
|
54
|
+
--version|-v)
|
|
55
|
+
# Tools that sniff "ntn version" succeed with our shim line. The real
|
|
56
|
+
# version lives host-side and is reported by the relay's readiness probe
|
|
57
|
+
# (`assertIntegrationReady`).
|
|
58
|
+
printf 'ntn version 0.0.0 (agentbox-shim)\n'
|
|
59
|
+
;;
|
|
60
|
+
--help|-h)
|
|
61
|
+
printf 'agentbox notion shim — strict subset.\n' >&2
|
|
62
|
+
printf 'Supported: whoami, api <path> [inputs] [-d JSON], pages {create, update}, --version\n' >&2
|
|
63
|
+
printf 'api is read-only: GET to any endpoint; POST only to v1/search and\n' >&2
|
|
64
|
+
printf 'v1/{databases,data_sources}/{id}/query. Writes go through `pages`.\n' >&2
|
|
65
|
+
printf 'Anything else is rejected. Run host `ntn --help` for full upstream docs.\n' >&2
|
|
66
|
+
;;
|
|
67
|
+
whoami)
|
|
68
|
+
shift
|
|
69
|
+
exec "$CTL" integration notion whoami -- "$@"
|
|
70
|
+
;;
|
|
71
|
+
api)
|
|
72
|
+
shift
|
|
73
|
+
# Forward verbatim to mirror real `ntn api` (options may precede the path;
|
|
74
|
+
# `ls`/`help`/`--spec`/`--docs` and `-d <JSON>` bodies are all valid). The
|
|
75
|
+
# relay's refuseUnsafeApiCall is the security boundary: GET to any endpoint,
|
|
76
|
+
# POST only to read endpoints (v1/search, v1/databases/{id}/query,
|
|
77
|
+
# v1/data_sources/{id}/query); every other method/endpoint is refused.
|
|
78
|
+
# Writes go through the dedicated `pages create/update` ops.
|
|
79
|
+
exec "$CTL" integration notion api -- "$@"
|
|
80
|
+
;;
|
|
81
|
+
pages)
|
|
82
|
+
shift
|
|
83
|
+
handle_pages "$@"
|
|
84
|
+
;;
|
|
85
|
+
comment|comments)
|
|
86
|
+
# The T1 connector intentionally has no comment op — `ntn` exposes no
|
|
87
|
+
# top-level `comment` subcommand and Notion's REST POST /v1/comments
|
|
88
|
+
# takes a structured JSON body that doesn't trivially map from CLI
|
|
89
|
+
# flags. Tracked as a focused follow-up in docs/notion_backlog.md.
|
|
90
|
+
die "comment ops not supported yet (deferred from T2; see docs/notion_backlog.md)"
|
|
91
|
+
;;
|
|
92
|
+
*)
|
|
93
|
+
die "'$1' is not proxied (supported: whoami, api <endpoint>, pages {create,update}, --version)"
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
# /tmp/agentbox-open -- in-box xdg-open shim
|
|
23
23
|
# /tmp/agentbox-gh-shim -- in-box `gh` shim (routes to host gh)
|
|
24
24
|
# /tmp/agentbox-git-shim -- in-box `git` shim (routes via relay)
|
|
25
|
+
# /tmp/agentbox-ntn-shim -- in-box `ntn`/`notion` shim (routes to host ntn)
|
|
26
|
+
# /tmp/agentbox-linear-shim -- in-box `linear` shim (routes to host linear; rejects `auth token`)
|
|
25
27
|
# /tmp/agentbox-custom-CLAUDE.md -- /etc/claude-code/CLAUDE.md content
|
|
26
28
|
# /tmp/agentbox-managed-settings.json -- /etc/claude-code/managed-settings.json
|
|
27
29
|
# /tmp/agentbox-codex-hooks.json -- /usr/local/share/agentbox/codex-hooks.json
|
|
@@ -88,10 +90,10 @@ visudo -cf /etc/sudoers >/dev/null
|
|
|
88
90
|
done_ "vscode user + sudoers"
|
|
89
91
|
|
|
90
92
|
step "agentbox base dirs + /workspace ownership"
|
|
91
|
-
mkdir -p /workspace /run/agentbox /var/log/agentbox /etc/agentbox /etc/claude-code \
|
|
93
|
+
mkdir -p /workspace /run/agentbox /var/log/agentbox /var/lib/agentbox /etc/agentbox /etc/claude-code \
|
|
92
94
|
/usr/local/share/agentbox
|
|
93
95
|
chmod 755 /workspace
|
|
94
|
-
chown vscode:vscode /workspace /run/agentbox /var/log/agentbox
|
|
96
|
+
chown vscode:vscode /workspace /run/agentbox /var/log/agentbox /var/lib/agentbox
|
|
95
97
|
done_ "agentbox base dirs + /workspace ownership"
|
|
96
98
|
|
|
97
99
|
step "node setcap (bind <1024 without root)"
|
|
@@ -278,15 +280,19 @@ done_ "apt cleanup"
|
|
|
278
280
|
# login-shell shim above forces /usr/local/bin ahead of /usr/bin so these win.
|
|
279
281
|
# During the bake there is no relay, so they must not shadow the real binaries
|
|
280
282
|
# until provisioning is done. Installed from /tmp just before the trim step.
|
|
281
|
-
step "relay shims (gh + git)"
|
|
282
|
-
install -m 0755 /tmp/agentbox-gh-shim
|
|
283
|
-
install -m 0755 /tmp/agentbox-git-shim
|
|
284
|
-
|
|
283
|
+
step "relay shims (gh + git + ntn + linear)"
|
|
284
|
+
install -m 0755 /tmp/agentbox-gh-shim /usr/local/bin/gh
|
|
285
|
+
install -m 0755 /tmp/agentbox-git-shim /usr/local/bin/git
|
|
286
|
+
install -m 0755 /tmp/agentbox-ntn-shim /usr/local/bin/ntn
|
|
287
|
+
ln -sf /usr/local/bin/ntn /usr/local/bin/notion
|
|
288
|
+
install -m 0755 /tmp/agentbox-linear-shim /usr/local/bin/linear
|
|
289
|
+
done_ "relay shims (gh + git + ntn + linear)"
|
|
285
290
|
|
|
286
291
|
step "trim /tmp/agentbox-*"
|
|
287
292
|
rm -f /tmp/agentbox-ctl /tmp/agentbox-vnc-start \
|
|
288
293
|
/tmp/agentbox-checkpoint-cleanup /tmp/agentbox-open \
|
|
289
|
-
/tmp/agentbox-gh-shim /tmp/agentbox-git-shim \
|
|
294
|
+
/tmp/agentbox-gh-shim /tmp/agentbox-git-shim /tmp/agentbox-ntn-shim \
|
|
295
|
+
/tmp/agentbox-linear-shim \
|
|
290
296
|
/tmp/agentbox-custom-CLAUDE.md /tmp/agentbox-managed-settings.json \
|
|
291
297
|
/tmp/agentbox-codex-hooks.json /tmp/agentbox-setup-skill.md
|
|
292
298
|
mv /tmp/agentbox-build-template.sh /var/log/agentbox/build-template.sh 2>/dev/null || true
|
|
@@ -32,10 +32,13 @@ rm -rf /var/lib/apt/lists/* 2>/dev/null
|
|
|
32
32
|
find /tmp /var/tmp -mindepth 1 -maxdepth 1 ! -name 'claude-*' -exec rm -rf {} + 2>/dev/null
|
|
33
33
|
|
|
34
34
|
# Logs: truncate (don't delete) so the original file modes / ownerships stay
|
|
35
|
-
# intact for the next run. Targets common rotated archives too.
|
|
35
|
+
# intact for the next run. Targets common rotated archives too. Exclude the
|
|
36
|
+
# `state/` subdir — it holds the idempotent-task marker fallback (used when
|
|
37
|
+
# /var/lib/agentbox isn't writable) and must survive the checkpoint.
|
|
36
38
|
find /var/log -type f \( -name '*.log' -o -name '*.gz' -o -name '*.1' \) \
|
|
39
|
+
-not -path '/var/log/agentbox/state/*' -exec truncate -s0 {} + 2>/dev/null
|
|
40
|
+
find /var/log/agentbox -type f -not -path '/var/log/agentbox/state/*' \
|
|
37
41
|
-exec truncate -s0 {} + 2>/dev/null
|
|
38
|
-
find /var/log/agentbox -type f -exec truncate -s0 {} + 2>/dev/null
|
|
39
42
|
|
|
40
43
|
# Bash history (root + vscode). Re-assert vscode ownership: `: >` run as root
|
|
41
44
|
# (re)creates the file root-owned 0644 when it didn't exist, which the uid-1000
|
|
@@ -46,35 +46,56 @@ Look at `/workspace`:
|
|
|
46
46
|
- **Tasks** = one-shot. `pnpm install`, DB migrations, codegen, fixture loaders, install apt packages. Wire dependent services with `needs:` so they wait for the task to finish successfully.
|
|
47
47
|
- Names: must match `[A-Za-z0-9_-]+`. Task names and service names share a namespace — no collisions.
|
|
48
48
|
- No cycles in `needs:`.
|
|
49
|
-
- **Always generate a dependency-install task** and make it the root of the `needs:` graph (every service that needs deps gets `needs: [install, …]`). Future boxes start from a snapshot of the final filesystem so they won't need this, but updates or moving to a cloud provider might need to rebuild the container from scratch. The filesystem can be then later captured by `agentbox-ctl checkpoint --set-default`. The task must be **idempotent
|
|
49
|
+
- **Always generate a dependency-install task** and make it the root of the `needs:` graph (every service that needs deps gets `needs: [install, …]`). Future boxes start from a snapshot of the final filesystem so they won't need this, but updates or moving to a cloud provider might need to rebuild the container from scratch. The filesystem can be then later captured by `agentbox-ctl checkpoint --set-default`. The task must be **idempotent**: `agentbox-ctl` re-runs pending tasks on every box stop/start (the daemon dies with the container and is relaunched), so an unguarded install would reinstall on every start. The clean way is the **`run_once: true`** field — the supervisor stores a marker keyed by a hash of the command and skips warm boots automatically (the marker lives at `/var/lib/agentbox/tasks/<name>`, on the box rootfs, captured by checkpoints, never polluting `/workspace`). Editing the command re-runs it. Detect the package manager from the lockfile — never hardcode `pnpm`. See the worked example below.
|
|
50
50
|
- **Add a comment to the beginning** of the file to explain what you did and what issues you encountered, so that future run might use this information in case the project evolves and you need to update the agentbox.yaml file.
|
|
51
51
|
|
|
52
52
|
### Stateful services: data persistence & re-seeding (read this for databases)
|
|
53
53
|
|
|
54
|
+
**Declare a containerized dependency with the `image:` service form** — AgentBox
|
|
55
|
+
generates the `docker start`-or-`run` shell (no hand-written `docker run … || docker
|
|
56
|
+
start …`). The container runs in the box's dockerd; a published port is reachable
|
|
57
|
+
from other in-box services at `127.0.0.1:<host port>`:
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
services:
|
|
61
|
+
postgres:
|
|
62
|
+
image: # bare string (image: postgres:17-alpine) or a mapping:
|
|
63
|
+
name: postgres:17-alpine
|
|
64
|
+
ports: ["5432:5432"]
|
|
65
|
+
env:
|
|
66
|
+
POSTGRES_PASSWORD: postgres
|
|
67
|
+
POSTGRES_DB: app
|
|
68
|
+
args: "-c max_connections=200" # string or ["-c","max_connections=200"]
|
|
69
|
+
container_name: app_db # optional; default = service name
|
|
70
|
+
ready_when: { port: 5432 }
|
|
71
|
+
restart: always
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The container is reused by name across box stop/start. (Changing `image`/`env`
|
|
75
|
+
reuses the existing container as-is; `docker rm <container_name>` + `agentbox-ctl
|
|
76
|
+
reload` to apply.) Install the DB client the migrate/seed tasks need (e.g.
|
|
77
|
+
`postgresql-client`) in the `install` task and reach the DB over TCP — don't
|
|
78
|
+
`docker exec` the container (nested exec fails with a `setns` error in a box).
|
|
79
|
+
|
|
54
80
|
**A checkpoint does NOT capture docker-in-docker data.** `agentbox checkpoint` is a `docker commit` of the box's writable filesystem (the system + `/workspace`). The in-box `dockerd` keeps its storage in a *separate* per-box volume (`/var/lib/docker`), which is **not** part of that image — it's fresh on every new box and wiped on `agentbox destroy`. So a database or cache you run as a **docker container** (e.g. `docker run … postgres`) starts **empty on every new box** created from a checkpoint (every `agentbox claude` / `agentbox create`), even though `/workspace` and any marker files you wrote were restored. (A DB run as a **native process** with its data dir on the box filesystem — e.g. `postgres -D /var/lib/postgresql/data` — *is* captured by the checkpoint, since it lives in the writable layer.)
|
|
55
81
|
|
|
56
|
-
**Consequence for migrate/seed tasks of a containerized DB: do
|
|
82
|
+
**Consequence for migrate/seed tasks of a containerized DB: do NOT use `run_once: true` (the marker form).** A command-hash marker is correct for deps (they live in `/workspace`, which the checkpoint captures), but **wrong** for DB data living in a docker volume: the marker is restored from the checkpoint while the DB is empty, so a marker-guarded seed wrongly skips and the app boots against an empty database. Instead use the **`run_once: { check: <cmd> }`** form — the probe runs first and the seed runs unless the probe exits 0, and **no marker is written** (the DB is the source of truth). Gate on the actual data:
|
|
57
83
|
|
|
58
84
|
```yaml
|
|
59
85
|
seed:
|
|
60
|
-
# Re-seed when the DB is empty. The postgres data lives in the in-box
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
86
|
+
# Re-seed when the DB is empty. The postgres data lives in the in-box docker
|
|
87
|
+
# volume, which is NOT captured by `agentbox checkpoint` — so a box started
|
|
88
|
+
# from a checkpoint has the workspace warm but an empty DB. The marker form
|
|
89
|
+
# would be restored while the DB is blank and wrongly skip; the `check` probe
|
|
90
|
+
# gates on the data itself. Exit 0 = already seeded, skip. Fast no-op once
|
|
65
91
|
# the data is present.
|
|
66
|
-
command:
|
|
67
|
-
set -e
|
|
68
|
-
export PGPASSWORD=postgres
|
|
69
|
-
# Probe for existing data. If the table is missing the query errors,
|
|
70
|
-
# stderr is suppressed, stdout is empty, the grep fails — so we seed.
|
|
71
|
-
if psql -h 127.0.0.1 -p 5432 -U postgres -d app -tAc \
|
|
72
|
-
"SELECT EXISTS (SELECT 1 FROM users LIMIT 1)" 2>/dev/null | grep -q t; then
|
|
73
|
-
echo "data present — skip seed"
|
|
74
|
-
exit 0
|
|
75
|
-
fi
|
|
76
|
-
pnpm db:seed
|
|
92
|
+
command: pnpm db:seed
|
|
77
93
|
needs: [install, migrate]
|
|
94
|
+
run_once:
|
|
95
|
+
check: |
|
|
96
|
+
export PGPASSWORD=postgres
|
|
97
|
+
psql -h 127.0.0.1 -p 5432 -U postgres -d app -tAc \
|
|
98
|
+
"SELECT EXISTS (SELECT 1 FROM users LIMIT 1)" 2>/dev/null | grep -q t
|
|
78
99
|
```
|
|
79
100
|
|
|
80
101
|
**Lifecycle nuance (this is why the data check, not a marker, is right):**
|
|
@@ -148,22 +169,19 @@ tasks:
|
|
|
148
169
|
# Idempotent install. /workspace is the container's writable filesystem, so
|
|
149
170
|
# node_modules persists across pause/stop/start and is captured by
|
|
150
171
|
# `agentbox checkpoint`. The host's node_modules is macOS-native and is
|
|
151
|
-
# never copied in, so
|
|
152
|
-
# on every subsequent box start (
|
|
153
|
-
#
|
|
154
|
-
# manager.
|
|
172
|
+
# never copied in, so the first Linux install runs; `run_once: true` then
|
|
173
|
+
# skips it on every subsequent box start (the supervisor stores a marker
|
|
174
|
+
# keyed by a hash of the command). Adjust the lockfile detection to the
|
|
175
|
+
# project's package manager.
|
|
155
176
|
install:
|
|
156
177
|
command: |
|
|
157
178
|
set -e
|
|
158
|
-
|
|
159
|
-
[ -f "$MARKER" ] && { echo "deps installed (marker present) — skip"; exit 0; }
|
|
160
|
-
apt-get update && apt-get install -y postgresql-client
|
|
161
|
-
rm -rf node_modules
|
|
179
|
+
sudo apt-get update && sudo apt-get install -y postgresql-client
|
|
162
180
|
if [ -f pnpm-lock.yaml ]; then
|
|
163
181
|
corepack enable >/dev/null 2>&1 || true
|
|
164
182
|
pnpm install --frozen-lockfile || pnpm install
|
|
165
183
|
fi
|
|
166
|
-
|
|
184
|
+
run_once: true
|
|
167
185
|
|
|
168
186
|
migrate:
|
|
169
187
|
command: pnpm db:migrate
|
|
@@ -258,6 +276,41 @@ On Vercel: this actually STOPS the sandbox, so warn the user about it. Also the
|
|
|
258
276
|
|
|
259
277
|
- For Nextjs/Vite/Tasnstack projects, makes sure to forward also websocket for hot reload.
|
|
260
278
|
|
|
261
|
-
- Service like flask, nextjs, BETTER_AUTH_URL, NEXT_PUBLIC_APP_URL should use the
|
|
279
|
+
- Service like flask, nextjs, BETTER_AUTH_URL, NEXT_PUBLIC_APP_URL should use the `<boxname>.localhost` url for the local development so that on the host it will use the same url as the box. Render this automatically instead of hand-writing `sed` — see section 6c.
|
|
280
|
+
|
|
281
|
+
- The `install` task above uses `run_once: true`, so it is a no-op on warm boots. Do **not** wrap it in a manual marker check too. To force a one-off rebuild, run `agentbox-ctl run-task install --force` (which bypasses the run_once marker), or edit the command (a changed command invalidates the hash and re-runs).
|
|
282
|
+
|
|
283
|
+
## 11. Pin URLs / render config files (env, secrets)
|
|
284
|
+
|
|
285
|
+
Many apps hard-code a hostname (e.g. `optima.localhost`) or read a gitignored `.env`. Instead of long `sed` commands in a task, use the built-ins:
|
|
286
|
+
|
|
287
|
+
- **`agentbox-ctl render <src>`** — a declarative `sed` for files already in the workspace. `--env` substitutes `{{AGENTBOX_*}}` placeholders; `--rules <name>` applies a named rule-set from the top-level `replacements:` block; `--rule 'from=>to'` / `--rule-regex 'pat=>repl'` are inline. Write to `--out <path>` (or `--in-place`). The whitelist placeholders are `{{AGENTBOX_BOX_NAME}}`, `{{AGENTBOX_BOX_HOST}}` (= `<boxname>.localhost`), `{{AGENTBOX_BOX_ID}}`, `{{AGENTBOX_BOX_KIND}}`, `{{AGENTBOX_HOST_WORKSPACE}}`, `{{AGENTBOX_PROJECT_ROOT}}`.
|
|
288
|
+
|
|
289
|
+
Render a gitignored `.env` from a committed `env.example` on every boot, pinning the URLs to this box:
|
|
290
|
+
|
|
291
|
+
```yaml
|
|
292
|
+
replacements:
|
|
293
|
+
box-host:
|
|
294
|
+
- { from: 'optima\.localhost', to: '{{AGENTBOX_BOX_HOST}}', regex: true } # {{AGENTBOX_BOX_HOST}} = <box>.localhost
|
|
295
|
+
|
|
296
|
+
tasks:
|
|
297
|
+
env:
|
|
298
|
+
# The render is idempotent (the rules re-pin the same lines every boot), so
|
|
299
|
+
# no `run_once:` guard is needed — it self-corrects on a checkpoint-started
|
|
300
|
+
# box that carries a different box's host in .env.
|
|
301
|
+
command: agentbox-ctl render apps/saas/env.example --out apps/saas/.env --env --rules box-host
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Note: an `run_once: { check: <cmd> }` probe runs verbatim via `bash -c` with the box env — use shell vars like `$AGENTBOX_BOX_NAME`, NOT `{{…}}` placeholders (those are only expanded by `render`/carry, never by the supervisor).
|
|
305
|
+
|
|
306
|
+
**Generated secrets:** put `{{AGENTBOX_AUTO_SECRET}}` in the template for a value like `BETTER_AUTH_SECRET` instead of shelling out to `openssl rand`. Unnamed → a fresh 32-byte base64url secret each render (stable when you render the template→`.env` once). `{{AGENTBOX_AUTO_SECRET:better-auth}}` → generated once, persisted at `/var/lib/agentbox/secrets/<name>`, reused on every render (stable even if you render every boot). Example `env.example` line: `BETTER_AUTH_SECRET="{{AGENTBOX_AUTO_SECRET:better-auth}}"`.
|
|
307
|
+
|
|
308
|
+
- **`carry:` + `replaceEnvs`/`replace`/`rules`** — for a host-only file (e.g. a real `.env` with secrets that never lives in the repo), carry it in and render it host-side in one step (file entries only):
|
|
262
309
|
|
|
263
|
-
|
|
310
|
+
```yaml
|
|
311
|
+
carry:
|
|
312
|
+
- src: ~/secrets/optima.env
|
|
313
|
+
dest: /workspace/apps/saas/.env
|
|
314
|
+
replaceEnvs: true
|
|
315
|
+
rules: [box-host]
|
|
316
|
+
```
|