Repository navigation
Expand file tree
/
Copy pathinstall.sh
More file actions
254 lines (235 loc) · 12.5 KB
/
Copy pathinstall.sh
File metadata and controls
254 lines (235 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
#!/bin/sh
# AgentEye collector installer.
#
# curl -fsSL https://raw.githubusercontent.com/FailproofAI/agenteye-collector/main/install.sh | sh
#
# Downloads the right binary for your OS/arch from the public GitHub Releases,
# verifies its SHA-256 checksum, installs it on your PATH, and — if you pass a URL
# and key — installs it as a background service. Tokenless.
#
# Session capture is off until you turn it on per source: --codex-enabled,
# --claude-enabled, --openclaw-enabled, --hermes-enabled. Combine as many as you run.
#
# Running OpenClaw out of more than one state dir? Add --openclaw-extra-path per
# extra install, e.g. --openclaw-extra-path profile2=/home/u/.openclaw-profile2
#
# Keeping Claude Code somewhere other than ~/.claude? Name the transcript folder,
# e.g. --claude-projects-path /home/u/.config/claude/projects
#
# Configure via flags or env vars:
# curl -fsSL <url> | sh -s -- --key sk-… --codex-enabled --claude-enabled
# curl -fsSL <url> | sh -s -- --key sk-… --hermes-enabled --hermes-extra-path prod=/srv/hermes
# curl -fsSL <url> | AGENTEYE_URL=… AGENTEYE_KEY=… sh
set -eu
REPO="FailproofAI/agenteye-collector"
BIN="agenteye-collector"
# Defined before the argument loop below, which calls err() on an unknown flag.
err() { echo "error: $*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }
dl() { # dl <url> <out>
# Name the URL on failure. Both downloaders are quiet by design (-s / -q), so an
# unhandled 404 would otherwise abort under `set -e` with no output at all — the
# user sees "downloading …" and then nothing.
if have curl; then curl -fsSL "$1" -o "$2" || err "download failed: $1"
elif have wget; then wget -qO "$2" "$1" || err "download failed: $1"
else err "need curl or wget to download"; fi
}
URL="${AGENTEYE_URL:-}"
KEY="${AGENTEYE_KEY:-}"
VERSION="${AGENTEYE_COLLECTOR_VERSION:-}"
CHANNEL=stable
CODEX=""
CLAUDE=""
OPENCLAW=""
HERMES=""
CLAUDE_OPTS=""
OPENCLAW_OPTS=""
HERMES_OPTS=""
SYSTEM=""
while [ $# -gt 0 ]; do
case "$1" in
--url) URL="$2"; shift 2 ;;
--url=*) URL="${1#*=}"; shift ;;
--key) KEY="$2"; shift 2 ;;
--key=*) KEY="${1#*=}"; shift ;;
--version) VERSION="$2"; shift 2 ;;
--version=*) VERSION="${1#*=}"; shift ;;
--beta) CHANNEL=beta; shift ;;
# One branch per capture source. These are a hand-kept mirror of the binary's
# own flags — the collector growing a source does NOT update this file, so add
# a branch here in the same change, and mirror it into the README's flag list.
--codex-enabled | --codex) CODEX="--codex-enabled"; shift ;;
--claude-enabled | --claude) CLAUDE="--claude-enabled"; shift ;;
--openclaw-enabled | --openclaw) OPENCLAW="--openclaw-enabled"; shift ;;
--hermes-enabled | --hermes) HERMES="--hermes-enabled"; shift ;;
# Claude Code's transcript folder. Only needed when it is not under ~/.claude:
# the service starts from a near-empty environment, so a CLAUDE_CONFIG_DIR that
# only exists in the user's shell profile is invisible to it and capture would
# come up pointed at a directory that does not exist.
--claude-projects-path) CLAUDE_OPTS="$CLAUDE_OPTS --claude-projects-path $2"; shift 2 ;;
--claude-projects-path=*) CLAUDE_OPTS="$CLAUDE_OPTS --claude-projects-path ${1#*=}"; shift ;;
# Hermes folders. The primary one only needs naming when Hermes does not live in
# ~/.hermes; extra ones are additive and repeatable, and each may be given as
# label=path when the folder name would not make a good label on its own.
--hermes-default-path) HERMES_OPTS="$HERMES_OPTS --hermes-default-path $2"; shift 2 ;;
--hermes-default-path=*) HERMES_OPTS="$HERMES_OPTS --hermes-default-path ${1#*=}"; shift ;;
--hermes-path) HERMES_OPTS="$HERMES_OPTS --hermes-default-path $2"; shift 2 ;;
--hermes-path=*) HERMES_OPTS="$HERMES_OPTS --hermes-default-path ${1#*=}"; shift ;;
--hermes-extra-path) HERMES_OPTS="$HERMES_OPTS --hermes-extra-path $2"; shift 2 ;;
--hermes-extra-path=*) HERMES_OPTS="$HERMES_OPTS --hermes-extra-path ${1#*=}"; shift ;;
# OpenClaw tuning. Only needed when OpenClaw does not write where it usually does
# (its runtime log lives in /tmp, which container deployments do not mount out).
--openclaw-log-dir) OPENCLAW_OPTS="$OPENCLAW_OPTS --openclaw-log-dir $2"; shift 2 ;;
--openclaw-log-dir=*) OPENCLAW_OPTS="$OPENCLAW_OPTS --openclaw-log-dir ${1#*=}"; shift ;;
# Installer flag names mirror the binary's, so there is one name per concept to learn.
# `--openclaw-state-dir` predates that and stays as an alias.
--openclaw-default-path) OPENCLAW_OPTS="$OPENCLAW_OPTS --openclaw-default-path $2"; shift 2 ;;
--openclaw-default-path=*) OPENCLAW_OPTS="$OPENCLAW_OPTS --openclaw-default-path ${1#*=}"; shift ;;
--openclaw-state-dir) OPENCLAW_OPTS="$OPENCLAW_OPTS --openclaw-default-path $2"; shift 2 ;;
--openclaw-state-dir=*) OPENCLAW_OPTS="$OPENCLAW_OPTS --openclaw-default-path ${1#*=}"; shift ;;
# Additional state dirs, for a host running more than one OpenClaw install. Repeatable;
# each value is `path` or `label=path`. Without this branch the flag would be
# documented everywhere and undeliverable here, which is how #462 shipped.
--openclaw-extra-path) OPENCLAW_OPTS="$OPENCLAW_OPTS --openclaw-extra-path $2"; shift 2 ;;
--openclaw-extra-path=*) OPENCLAW_OPTS="$OPENCLAW_OPTS --openclaw-extra-path ${1#*=}"; shift ;;
--no-openclaw-gateway-log) OPENCLAW_OPTS="$OPENCLAW_OPTS --openclaw-gateway-log-enabled false"; shift ;;
--openclaw-single-agent) OPENCLAW_OPTS="$OPENCLAW_OPTS --openclaw-agent-id-from-path false"; shift ;;
--system) SYSTEM="--system"; shift ;;
# Unknown flags are FATAL, not a warning. This branch used to warn and continue,
# which made the missing --openclaw-enabled / --hermes-enabled branches above
# invisible: the README documented both, passing either printed one line into a
# `curl | sh` pipeline nobody reads, the service installed with the source OFF,
# and the installer still exited 0. Failing closed turns the next missed flag
# into a loud error instead of silent non-capture.
*) err "unknown option '$1'. Supported: --url --key --version --beta --codex-enabled --claude-enabled --openclaw-enabled --hermes-enabled --claude-projects-path --openclaw-default-path --openclaw-extra-path --openclaw-state-dir --openclaw-log-dir --no-openclaw-gateway-log --openclaw-single-agent --system" ;;
esac
done
# --- detect os/arch (must match the release asset names) ---
os=$(uname -s)
arch=$(uname -m)
case "$os" in
Linux) os=linux ;;
Darwin) os=darwin ;;
*) err "unsupported OS '$os'. On Windows, download the .exe from https://github.com/$REPO/releases and run it under Task Scheduler." ;;
esac
case "$arch" in
x86_64 | amd64) arch=x86_64 ;;
arm64 | aarch64) arch=arm64 ;;
*) err "unsupported architecture '$arch'" ;;
esac
asset="${BIN}-${os}-${arch}"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT INT TERM
# --- resolve version (channel, or pinned) ---
#
# Read the channel pointers from versions.json rather than the GitHub API's
# releases/latest. That endpoint EXCLUDES pre-releases, so on a repo publishing only
# betas it 404s and this installer would die with no usable version. It is also rate
# limited to 60 requests/hour per IP, which a fleet behind one NAT would exhaust.
# raw.githubusercontent.com has neither problem (it does cache for a few minutes).
#
# Extract one channel's tag: strip all whitespace so the JSON is one line, isolate the
# channel's object, then pull its tag. A null channel has no {...} and yields "".
manifest_tag() { # manifest_tag <file> <channel>
tr -d '\n\r\t ' < "$1" \
| sed -n "s/.*\"$2\":{\([^}]*\)}.*/\1/p" \
| sed -n 's/.*"tag":"\([^"]*\)".*/\1/p'
}
if [ -n "$VERSION" ]; then
case "$VERSION" in v*) tag="$VERSION" ;; *) tag="v$VERSION" ;; esac
else
dl "https://raw.githubusercontent.com/$REPO/main/versions.json" "$tmp/versions.json"
tag=$(manifest_tag "$tmp/versions.json" "$CHANNEL")
# Nothing promoted to stable yet is an expected state, not a broken install: fall back
# to beta so `curl | sh` works, but say so, and point at the matching update command —
# otherwise the user lands on a pre-release whose `update` (stable by default) would
# then report nothing to do, forever.
if [ -z "$tag" ] && [ "$CHANNEL" = stable ]; then
tag=$(manifest_tag "$tmp/versions.json" beta)
if [ -n "$tag" ]; then
CHANNEL=beta
echo "note: no stable release published yet — installing the current beta ($tag)."
echo " keep it current with: agenteye-collector update --beta"
fi
fi
[ -n "$tag" ] || err "could not resolve a $CHANNEL release from versions.json"
fi
base="https://github.com/$REPO/releases/download/$tag"
# --- download + verify ---
echo "downloading $asset ($tag) …"
dl "$base/$asset" "$tmp/$asset"
dl "$base/SHA256SUMS" "$tmp/SHA256SUMS"
echo "verifying checksum …"
# Match the asset name exactly. A substring match would let a shorter name pick up a
# longer one's hash (e.g. `…-windows-x86_64` matching `…-windows-x86_64.exe`).
# The `*` prefix is sha256sum's binary-mode marker.
want=$(awk -v a="$asset" '{ n = $2; sub(/^\*/, "", n); if (n == a) { print $1; exit } }' "$tmp/SHA256SUMS")
[ -n "$want" ] || err "no checksum for $asset in SHA256SUMS"
if have sha256sum; then got=$(sha256sum "$tmp/$asset" | awk '{print $1}')
elif have shasum; then got=$(shasum -a 256 "$tmp/$asset" | awk '{print $1}')
else err "need sha256sum or shasum to verify the download"; fi
[ "$want" = "$got" ] || err "checksum mismatch for $asset (expected $want, got $got)"
# --- install onto PATH ---
chmod +x "$tmp/$asset"
# Install into a user-writable dir on PATH. Used both when we never had privileges and as
# the fallback when a sudo install fails, so the two paths can't drift.
install_user_local() {
dest="$HOME/.local/bin"
mkdir -p "$dest"
mv "$tmp/$asset" "$dest/$BIN"
case ":$PATH:" in
*":$dest:"*) : ;;
*) echo "note: add $dest to your PATH (e.g. export PATH=\"$dest:\$PATH\")" ;;
esac
}
dest=/usr/local/bin
# Only elevate with sudo when it can actually succeed: passwordless sudo, or a
# controlling terminal to prompt on. In a non-interactive `curl | sh` (CI, no tty)
# fall back to a user-writable dir instead of failing on a sudo password prompt.
if [ -w "$dest" ]; then
mv "$tmp/$asset" "$dest/$BIN"
elif have sudo && { : </dev/tty; } 2>/dev/null; then
# A controlling terminal exists, so sudo can prompt for a password — but the prompt can
# still fail (wrong password, requiretty, sudoers disallows), and `set -e` would abort
# the whole install on that. Fall back to the user-writable dir instead of leaving the
# user with nothing installed.
echo "installing to $dest (sudo) …"
if ! sudo mv "$tmp/$asset" "$dest/$BIN"; then
echo "note: sudo install failed; falling back to a user-writable dir."
install_user_local
fi
else
install_user_local
fi
echo "installed $BIN $tag to $dest/$BIN"
# --- optionally configure + start the service ---
# The URL defaults to the hosted ingest endpoint, so a key alone is enough to start
# shipping events; pass --url only to point at a different (e.g. self-hosted) server.
if [ -n "$KEY" ]; then
echo "setting up the background service …"
url_arg=""
[ -n "$URL" ] && url_arg="--url $URL"
# word-split is intentional: each of these is either empty or one whole flag
# ("--url <url>", "--codex-enabled", …), so an empty one must vanish entirely
# rather than become an empty argument.
# shellcheck disable=SC2086
"$dest/$BIN" service install $url_arg --key "$KEY" $CODEX $CLAUDE $CLAUDE_OPTS $OPENCLAW $OPENCLAW_OPTS $HERMES $HERMES_OPTS $SYSTEM
if [ -z "$CODEX$CLAUDE$OPENCLAW$HERMES" ]; then
echo "note: no session capture enabled — the service will ship spooled events only."
echo " re-run with --codex-enabled, --claude-enabled, --openclaw-enabled and/or --hermes-enabled to capture sessions."
fi
echo "done — verify with: $BIN health"
else
cat <<EOF
Installed. To start capturing your local coding-agent sessions, set up the service with
your key, enabling each agent you run:
$BIN service install --key <YOUR_EVENTS_ADD_KEY> --codex-enabled --claude-enabled --openclaw-enabled --hermes-enabled
That is one command — drop the flags for agents you do not run. Re-running it replaces the
previous configuration rather than adding to it.
Events go to https://app.befailproof.ai/v1/events by default, which needs only an API
key. Pass --url to send them elsewhere — your own server, or
https://server.befailproof.ai/events, the ingest host, which supports mutual TLS.
Then verify with: $BIN health
Update later with: $BIN update
EOF
fi