Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<h1>Stay in the terminal.<br><span>Stay in command.</span></h1>
<p class="lede">Your agent is working. You’re in the loop. Follow the tools, inspect the changes, and make the call—with Odek’s power at your fingertips.</p>
<div class="hero-actions"><a class="button primary" href="#start">Get Bodek <span aria-hidden="true">↗</span></a><a class="button" href="#showcase">Take a closer look <span aria-hidden="true">↓</span></a></div>
<div class="command"><pre><code>curl -fsSL https://bodek.21no.de/install.sh | sh</code></pre><button type="button" data-copy hidden>Copy</button></div>
<p class="hero-meta">Go + Bubble Tea <span> / </span> MIT licensed <span> / </span> Powered by Odek</p>
</div>
<section class="showcase" id="showcase" aria-labelledby="showcase-h">
Expand Down Expand Up @@ -72,7 +73,7 @@ <h1>Stay in the terminal.<br><span>Stay in command.</span></h1>
</section>
<section class="engine-section" aria-labelledby="engine-h"><div><p class="eyebrow">ONE ENGINE. YOUR CHOICE OF INTERFACE.</p><h2 id="engine-h">Odek does the work.<br>Bodek keeps it in view.</h2><p>Use your existing Odek provider configuration, sessions, and tools. The terminal connects to the same engine as the Web UI.</p><a href="https://odek.21no.de">Meet the engine ↗</a></div><div class="connection" aria-label="Bodek connects over WebSocket to Odek, which runs tools and enforces policy"><div><strong>bodek</strong><span>terminal interface</span></div><p>↕ <span>WebSocket · live events &amp; decisions</span></p><div><strong>odek serve</strong><span>tools · memory · sandbox · policy</span></div></div></section>
<section class="start-section" id="start" aria-labelledby="start-h"><div class="section-head"><div><p class="eyebrow">03 / MAKE YOUR NEXT MOVE</p><h2 id="start-h">One command.<br>Back in your flow.</h2></div><p>Install Odek and Bodek, configure your provider,<br>then choose how you want to work.</p></div>
<div class="install-links"><a class="button primary" href="https://github.com/BackendStack21/odek/blob/main/GETTING_STARTED.md">Installation guide ↗</a><a class="button" href="https://github.com/BackendStack21/bodek/releases">Download Bodek ↗</a><span>Prebuilt Bodek binaries for macOS, Linux, and Windows.</span></div>
<div class="install-links"><div class="command"><pre><code>curl -fsSL https://bodek.21no.de/install.sh | sh</code></pre><button type="button" data-copy hidden>Copy</button></div><a class="button primary" href="https://github.com/BackendStack21/odek/blob/main/GETTING_STARTED.md">Installation guide ↗</a><a class="button" href="https://github.com/BackendStack21/bodek/releases">Download Bodek ↗</a><span>One-command installer for macOS &amp; Linux — it also sets up the Odek engine (needs a provider API key). Windows binaries are on the releases page.</span></div>
<div class="tabset launch-examples" data-tabset><div class="tabs" role="tablist" aria-label="Launch mode" hidden><button type="button" role="tab" id="tab-launch" aria-controls="panel-launch" aria-selected="true" tabindex="0">Launch locally</button><button type="button" role="tab" id="tab-resume" aria-controls="panel-resume" aria-selected="false" tabindex="-1">Resume work</button><button type="button" role="tab" id="tab-attach" aria-controls="panel-attach" aria-selected="false" tabindex="-1">Attach to Odek</button></div>
<div id="panel-launch" data-panel><h3>Start here. Build from here.</h3><p>Starts Odek’s server and opens a fresh session in the current directory.</p><div class="command"><pre><code>bodek</code></pre><button type="button" data-copy hidden>Copy</button></div></div>
<div id="panel-resume" data-panel><h3>Pick up the thread.</h3><p>Continue this directory’s last session instead of starting fresh.</p><div class="command"><pre><code>bodek --resume</code></pre><button type="button" data-copy hidden>Copy</button></div></div>
Expand Down
205 changes: 205 additions & 0 deletions docs/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#!/bin/sh
# bodek installer — downloads prebuilt release binaries from GitHub.
#
# Usage:
# curl -fsSL https://bodek.21no.de/install.sh | sh
# curl -fsSL https://bodek.21no.de/install.sh | sh -s -- --with-odek
# sh install.sh [--with-odek] # install odek without asking
#
# macOS and Linux. Windows users: grab a binary from the releases page.
#
# Installs bodek (and optionally odek) for the current platform into
# ~/.local/bin (or /usr/local/bin when writable). Verifies every download
# against the release checksums.txt when a sha256 tool is available.
set -eu

BODEK_REPO="BackendStack21/bodek"
ODEK_REPO="BackendStack21/odek"
GH_API="https://api.github.com/repos"
GH_DL="https://github.com"

say() { printf '==> %s\n' "$*"; }
warn() { printf 'warning: %s\n' "$*" >&2; }
die() { printf 'error: %s\n' "$*" >&2; exit 1; }

# ---------------------------------------------------------------- platform
OS=$(uname -s)
ARCH=$(uname -m)
case "$OS" in
Darwin) os=darwin ;;
Linux) os=linux ;;
*) die "unsupported OS '$OS' — this installer covers macOS and Linux." ;;
esac
case "$ARCH" in
x86_64|amd64) arch=amd64 ;;
aarch64|arm64) arch=arm64 ;;
*) die "unsupported architecture '$ARCH'." ;;
esac

command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 \
|| die "need curl or wget to download."

fetch() { # fetch <url> -> stdout
if command -v curl >/dev/null 2>&1; then curl -fsSL "$1"
else wget -qO- "$1"; fi
}
download() { # download <url> <dest>
if command -v curl >/dev/null 2>&1; then curl -fsSL -o "$2" "$1"
else wget -qO "$2" "$1"; fi
}

checksum() { # checksum <file> <expected-hex>
if command -v sha256sum >/dev/null 2>&1; then
actual=$(sha256sum "$1" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
actual=$(shasum -a 256 "$1" | awk '{print $1}')
else
warn "no sha256 tool found — skipping checksum verification."
return 0
fi
[ "$actual" = "$2" ] || die "checksum mismatch for $(basename "$1") — download corrupted or tampered. Aborting."
}

# ---------------------------------------------------------------- install dir
install_dir=""
pick_install_dir() {
for d in /usr/local/bin "$HOME/.local/bin"; do
if mkdir -p "$d" 2>/dev/null && [ -w "$d" ]; then install_dir=$d; return; fi
done
die "no writable install directory found (tried /usr/local/bin, ~/.local/bin)."
}

path_has_dir() { case ":$PATH:" in *":$1:"*) return 0 ;; *) return 1 ;; esac; }

# ---------------------------------------------------------------- releases
latest_tag() { # latest_tag <repo> -> "vX.Y.Z" (empty on failure)
# Resolve via the redirect of /releases/latest — no API rate limits.
url="$GH_DL/$1/releases/latest"
if command -v curl >/dev/null 2>&1; then
loc=$(curl -fsSI -o /dev/null -w '%{redirect_url}' "$url" 2>/dev/null || true)
else
loc=$(wget -qS --spider "$url" 2>&1 | sed -n 's/^ *Location: *//p' | tail -1)
fi
case "$loc" in
*/tag/*) printf '%s\n' "${loc##*/}" ;;
*) printf '' ;;
esac
}

checksum_line() { # checksum_line <repo> <tag> <asset> -> hex digest or ""
fetch "$GH_DL/$1/releases/download/$2/checksums.txt" 2>/dev/null \
| awk -v a="$3" '$2 == a {print $1; exit}'
}

TMP=$(mktemp -d) || die "cannot create temp dir."
trap 'rm -rf "$TMP"' EXIT INT TERM

install_asset() { # install_asset <repo> <tag> <asset-file> <binary-name>
repo=$1 tag=$2 asset=$3 bin=$4
say "Downloading $asset ($tag)"
download "$GH_DL/$repo/releases/download/$tag/$asset" "$TMP/$asset"
digest=$(checksum_line "$repo" "$tag" "$asset")
if [ -n "$digest" ]; then
checksum "$TMP/$asset" "$digest"
say "Checksum verified."
else
# Fail closed: a missing/unfetchable checksum line is suspicious, not a
# reason to install unverified code.
die "no checksum entry for $asset — refusing to install unverified."
fi
case "$asset" in
*.tar.gz) tar -xzf "$TMP/$asset" -C "$TMP" "$bin" 2>/dev/null \
|| tar -xzf "$TMP/$asset" -C "$TMP" ;;
*) cp "$TMP/$asset" "$TMP/$bin" && chmod +x "$TMP/$bin" ;;
esac
[ -f "$TMP/$bin" ] || die "binary '$bin' not found in archive."
mv -f "$TMP/$bin" "$install_dir/$bin"
chmod +x "$install_dir/$bin"
say "Installed $install_dir/$bin"
}

# ---------------------------------------------------------------- odek
install_odek() {
tag=$(latest_tag "$ODEK_REPO")
[ -n "$tag" ] || die "cannot resolve the latest odek release (network or GitHub problem — install curl if it is missing)."
install_asset "$ODEK_REPO" "$tag" "odek-$os-$arch" odek
cat <<EOF

Odek is installed. Next, set up your provider:

1. odek init # creates ~/.odek/config.json
2. Add your provider API key, e.g.:
"providers": { "openai": { "apiKey": "sk-..." } }
Full guide: https://github.com/BackendStack21/odek/blob/main/GETTING_STARTED.md
EOF
}

maybe_install_odek() {
if command -v odek >/dev/null 2>&1; then
say "Odek found: $(command -v odek) — skipping."
return
fi
printf 'Odek (the engine bodek drives) is not installed.\n'
if [ "${1:-}" = "--with-odek" ]; then
install_odek
return
fi
printf 'Install it now from prebuilt binaries? [y/N] '
# Never read the script's own stdin: under `curl | sh` that would swallow
# the rest of the script. Use the terminal, else default to No.
answer=""
if [ -t 0 ]; then
read -r answer
elif [ -t 1 ] && [ -r /dev/tty ]; then
read -r answer </dev/tty || answer=""
fi
case "$answer" in
y|Y|yes|YES) install_odek ;;
*) warn "skipping odek — bodek needs a running 'odek serve' to connect to.
To install it later, rerun with: sh install.sh --with-odek" ;;
esac
}

# ---------------------------------------------------------------- main
WITH_ODEK=""
for arg in "$@"; do
case "$arg" in
--with-odek) WITH_ODEK=1 ;;
-h|--help) if [ -f "$0" ]; then sed -n '2,8p' "$0"; else
echo 'usage: sh install.sh [--with-odek]'; fi
exit 0 ;;
*) die "unknown option '$arg' (supported: --with-odek)" ;;
esac
done

pick_install_dir

tag=$(latest_tag "$BODEK_REPO")
[ -n "$tag" ] || die "cannot resolve the latest bodek release (network or GitHub problem — install curl if it is missing)."
ver=$(printf '%s\n' "$tag" | sed 's/^v//')
install_asset "$BODEK_REPO" "$tag" "bodek_${ver}_${os}_${arch}.tar.gz" bodek

if [ -n "$WITH_ODEK" ]; then
maybe_install_odek --with-odek
else
maybe_install_odek
fi

if ! path_has_dir "$install_dir"; then
cat <<EOF

NOTE: $install_dir is not on your PATH. Add it:

echo 'export PATH="$install_dir:\$PATH"' >> ~/.profile && source ~/.profile
EOF
fi

cat <<EOF

Done. Start bodek in any project directory:

bodek

It will launch (or attach to) an 'odek serve' engine automatically.
Docs: https://github.com/BackendStack21/bodek
EOF
Loading