diff --git a/docs/index.html b/docs/index.html index d033e3e..322962f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -37,6 +37,7 @@

Stay in the terminal.
Stay in command.

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.

Get Bodek Take a closer look
+
curl -fsSL https://bodek.21no.de/install.sh | sh

Go + Bubble Tea / MIT licensed / Powered by Odek

@@ -72,7 +73,7 @@

Stay in the terminal.
Stay in command.

ONE ENGINE. YOUR CHOICE OF INTERFACE.

Odek does the work.
Bodek keeps it in view.

Use your existing Odek provider configuration, sessions, and tools. The terminal connects to the same engine as the Web UI.

Meet the engine ↗
bodekterminal interface

WebSocket · live events & decisions

odek servetools · memory · sandbox · policy

03 / MAKE YOUR NEXT MOVE

One command.
Back in your flow.

Install Odek and Bodek, configure your provider,
then choose how you want to work.

- +

Start here. Build from here.

Starts Odek’s server and opens a fresh session in the current directory.

bodek

Pick up the thread.

Continue this directory’s last session instead of starting fresh.

bodek --resume
diff --git a/docs/install.sh b/docs/install.sh new file mode 100644 index 0000000..0843e3a --- /dev/null +++ b/docs/install.sh @@ -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 -> stdout + if command -v curl >/dev/null 2>&1; then curl -fsSL "$1" + else wget -qO- "$1"; fi +} +download() { # download + if command -v curl >/dev/null 2>&1; then curl -fsSL -o "$2" "$1" + else wget -qO "$2" "$1"; fi +} + +checksum() { # checksum + 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 -> "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 -> 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=$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 </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 > ~/.profile && source ~/.profile +EOF +fi + +cat <p{color:var(--muted);font-size:16px;margin:0}.tabs{display:flex;gap:4px;flex-wrap:wrap;border-bottom:1px solid var(--line);padding:0 16px}.tabs button{color:var(--muted);background:none;border:0;border-bottom:2px solid transparent;padding:18px 16px;cursor:pointer;font-size:14px}.tabs button[aria-selected=true]{color:var(--amber);border-color:var(--amber)}.tabs button:hover{color:var(--text)}.theme-gallery{border:1px solid #ffbb5230;border-radius:12px;overflow:hidden;background:#111216;box-shadow:0 30px 100px -60px #ffbb5250}.theme-swatch{display:inline-block;width:9px;height:9px;margin-right:8px;border-radius:50%;background:var(--amber)}.theme-swatch.ember-light{background:#f4ead5}.theme-swatch.high-contrast{background:#fff}.theme-swatch.classic{background:#bd8dfa}.terminal-bar{display:flex;justify-content:space-between;gap:16px;padding:14px 20px;font:12px/1.5 var(--mono);color:var(--muted);border-bottom:1px solid var(--line)}.theme-panel img{width:100%;height:auto;display:block}.theme-panel>a{display:block}.gallery-caption{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin:16px 0 0;color:var(--muted);font-size:12px}.gallery-caption p{margin:0 auto 0 0}.gallery-caption code{color:var(--amber)}.workflow{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:40px;border-bottom:1px solid var(--line);padding-bottom:64px;margin-top:60px}.workflow p{color:var(--muted);font-size:16px}.feature-key{display:block;min-height:32px;font-size:13px;color:var(--muted)}kbd{display:inline-block;min-width:26px;text-align:center;border:1px solid #ffffff28;border-bottom-width:3px;border-radius:5px;padding:3px 7px;font-size:12px;color:var(--text);background:#17191d}.why-section{display:grid;grid-template-columns:minmax(0,.85fr) minmax(0,1.2fr);gap:88px}.why-intro p{color:var(--muted);max-width:350px}.why-intro .eyebrow{color:var(--amber)}.why-intro h2{font-size:clamp(32px,4vw,46px)}.why-intro h2 span{color:var(--amber)}.why-list{list-style:none;padding:0;margin:0}.why-list li{display:grid;grid-template-columns:32px minmax(0,1fr);gap:20px;padding:34px 0;border-top:1px solid var(--line)}.why-list li:first-child{padding-top:0;border-top:0}.why-list h3{font-size:32px;line-height:1.12;margin:0 0 20px}.why-list .eyebrow{font-size:11px;color:var(--muted);margin-bottom:16px}.why-list p:not(.eyebrow){color:var(--muted)}.why-list a{font-size:14px;margin-right:15px}.number{font:13px var(--mono);color:var(--amber);padding-top:2px}.engine-section{display:grid;grid-template-columns:1.2fr 1fr;gap:80px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:56px 0}.engine-section p{color:var(--muted)}.engine-section .eyebrow{color:var(--amber)}.connection{align-self:center}.connection>div{border:1px solid var(--line);background:var(--panel);border-radius:8px;padding:20px 24px;display:flex;justify-content:space-between;align-items:center;gap:16px}.connection strong{font:18px var(--mono);color:var(--amber)}.connection span{font-size:12px;color:var(--muted)}.connection p{text-align:center;margin:14px 0;font-size:24px}.connection p span{vertical-align:middle;margin-left:8px}.install-links{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:32px 0}.install-links>span{display:block;width:100%;font-size:13px;color:var(--muted);margin-top:4px}.launch-examples{border:1px solid var(--line);border-radius:10px;background:var(--panel);overflow:hidden}.launch-examples [data-panel]{padding:28px}.launch-examples h3{margin:0 0 10px}.launch-examples p{color:var(--muted)}.command{display:flex;align-items:center;gap:20px;border-top:1px solid var(--line);padding-top:18px}.command pre{margin:0;overflow:auto;flex:1;min-width:0;font-size:15px;color:var(--amber)}.command button{background:#ffffff08;color:var(--text);border:1px solid var(--line);border-radius:5px;padding:7px 14px;cursor:pointer;font-size:13px}.hint{margin-top:20px}.closing{margin-top:90px;padding-top:48px;border-top:1px solid var(--line);display:flex;gap:30px;align-items:center;justify-content:space-between}.closing p{font-size:30px;line-height:1.15;letter-spacing:-.035em;margin:0}.closing p span{color:var(--amber)}[role=tabpanel]:focus-visible{outline:2px solid var(--amber);outline-offset:-3px}@media(max-width:900px){.why-section{gap:40px}.engine-section{gap:32px}.connection>div{align-items:start;flex-direction:column;gap:6px}.workflow{gap:24px}.section-head{align-items:start;flex-direction:column;gap:20px}}@media(max-width:650px){nav{font-size:12px}header .wrap{padding-top:14px;padding-bottom:14px}.hero-meta span{padding:0 2px}.tabs{padding:0 8px;gap:0}.tabs button{padding:14px 10px;font-size:12px}.terminal-bar{font-size:10px;padding:12px;flex-wrap:wrap}.workflow,.why-section,.engine-section{grid-template-columns:1fr}.workflow{gap:28px}.workflow h3{margin-top:14px}.why-list h3{font-size:30px}.why-intro p{max-width:none}.why-list li{gap:12px;grid-template-columns:24px minmax(0,1fr)}.engine-section{gap:30px}.connection>div{flex-direction:row;align-items:center}.closing{align-items:start;flex-direction:column;gap:24px}.launch-examples [data-panel]{padding:20px}.button{gap:20px}.command{gap:10px}}@media(prefers-reduced-motion:no-preference){.tabs button,.button{transition:background-color .16s,border-color .16s,color .16s}} +.skip{position:absolute;top:-100px;left:20px;padding:10px;background:var(--panel);z-index:5}.skip:focus{top:10px}.brand{color:var(--text)}.brand:hover{text-decoration:none}.nav-start{color:var(--amber)}.hero-meta span{color:#62636d;padding:0 10px}.hero{position:relative}.hero:after{content:'';position:absolute;pointer-events:none;inset:-80px -20px 0 25%;z-index:-1;background:radial-gradient(ellipse at top right,#ffbb5210,transparent 65%)}.showcase{margin-top:64px}.section-head{display:flex;justify-content:space-between;align-items:end;gap:30px;margin-bottom:30px}.section-head h2{margin-bottom:0}.section-head>p{color:var(--muted);font-size:16px;margin:0}.tabs{display:flex;gap:4px;flex-wrap:wrap;border-bottom:1px solid var(--line);padding:0 16px}.tabs button{color:var(--muted);background:none;border:0;border-bottom:2px solid transparent;padding:18px 16px;cursor:pointer;font-size:14px}.tabs button[aria-selected=true]{color:var(--amber);border-color:var(--amber)}.tabs button:hover{color:var(--text)}.theme-gallery{border:1px solid #ffbb5230;border-radius:12px;overflow:hidden;background:#111216;box-shadow:0 30px 100px -60px #ffbb5250}.theme-swatch{display:inline-block;width:9px;height:9px;margin-right:8px;border-radius:50%;background:var(--amber)}.theme-swatch.ember-light{background:#f4ead5}.theme-swatch.high-contrast{background:#fff}.theme-swatch.classic{background:#bd8dfa}.terminal-bar{display:flex;justify-content:space-between;gap:16px;padding:14px 20px;font:12px/1.5 var(--mono);color:var(--muted);border-bottom:1px solid var(--line)}.theme-panel img{width:100%;height:auto;display:block}.theme-panel>a{display:block}.gallery-caption{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin:16px 0 0;color:var(--muted);font-size:12px}.gallery-caption p{margin:0 auto 0 0}.gallery-caption code{color:var(--amber)}.workflow{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:40px;border-bottom:1px solid var(--line);padding-bottom:64px;margin-top:60px}.workflow p{color:var(--muted);font-size:16px}.feature-key{display:block;min-height:32px;font-size:13px;color:var(--muted)}kbd{display:inline-block;min-width:26px;text-align:center;border:1px solid #ffffff28;border-bottom-width:3px;border-radius:5px;padding:3px 7px;font-size:12px;color:var(--text);background:#17191d}.why-section{display:grid;grid-template-columns:minmax(0,.85fr) minmax(0,1.2fr);gap:88px}.why-intro p{color:var(--muted);max-width:350px}.why-intro .eyebrow{color:var(--amber)}.why-intro h2{font-size:clamp(32px,4vw,46px)}.why-intro h2 span{color:var(--amber)}.why-list{list-style:none;padding:0;margin:0}.why-list li{display:grid;grid-template-columns:32px minmax(0,1fr);gap:20px;padding:34px 0;border-top:1px solid var(--line)}.why-list li:first-child{padding-top:0;border-top:0}.why-list h3{font-size:32px;line-height:1.12;margin:0 0 20px}.why-list .eyebrow{font-size:11px;color:var(--muted);margin-bottom:16px}.why-list p:not(.eyebrow){color:var(--muted)}.why-list a{font-size:14px;margin-right:15px}.number{font:13px var(--mono);color:var(--amber);padding-top:2px}.engine-section{display:grid;grid-template-columns:1.2fr 1fr;gap:80px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:56px 0}.engine-section p{color:var(--muted)}.engine-section .eyebrow{color:var(--amber)}.connection{align-self:center}.connection>div{border:1px solid var(--line);background:var(--panel);border-radius:8px;padding:20px 24px;display:flex;justify-content:space-between;align-items:center;gap:16px}.connection strong{font:18px var(--mono);color:var(--amber)}.connection span{font-size:12px;color:var(--muted)}.connection p{text-align:center;margin:14px 0;font-size:24px}.connection p span{vertical-align:middle;margin-left:8px}.install-links{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:32px 0}.install-links>.command{flex:1 1 100%;min-width:0}.install-links>span{display:block;width:100%;font-size:13px;color:var(--muted);margin-top:4px}.launch-examples{border:1px solid var(--line);border-radius:10px;background:var(--panel);overflow:hidden}.launch-examples [data-panel]{padding:28px}.launch-examples h3{margin:0 0 10px}.launch-examples p{color:var(--muted)}.command{display:flex;align-items:center;gap:20px;border-top:1px solid var(--line);padding-top:18px}.command pre{margin:0;overflow:auto;flex:1;min-width:0;font-size:15px;color:var(--amber)}.command button{background:#ffffff08;color:var(--text);border:1px solid var(--line);border-radius:5px;padding:7px 14px;cursor:pointer;font-size:13px}.hint{margin-top:20px}.closing{margin-top:90px;padding-top:48px;border-top:1px solid var(--line);display:flex;gap:30px;align-items:center;justify-content:space-between}.closing p{font-size:30px;line-height:1.15;letter-spacing:-.035em;margin:0}.closing p span{color:var(--amber)}[role=tabpanel]:focus-visible{outline:2px solid var(--amber);outline-offset:-3px}@media(max-width:900px){.why-section{gap:40px}.engine-section{gap:32px}.connection>div{align-items:start;flex-direction:column;gap:6px}.workflow{gap:24px}.section-head{align-items:start;flex-direction:column;gap:20px}}@media(max-width:650px){nav{font-size:12px}header .wrap{padding-top:14px;padding-bottom:14px}.hero-meta span{padding:0 2px}.tabs{padding:0 8px;gap:0}.tabs button{padding:14px 10px;font-size:12px}.terminal-bar{font-size:10px;padding:12px;flex-wrap:wrap}.workflow,.why-section,.engine-section{grid-template-columns:1fr}.workflow{gap:28px}.workflow h3{margin-top:14px}.why-list h3{font-size:30px}.why-intro p{max-width:none}.why-list li{gap:12px;grid-template-columns:24px minmax(0,1fr)}.engine-section{gap:30px}.connection>div{flex-direction:row;align-items:center}.closing{align-items:start;flex-direction:column;gap:24px}.launch-examples [data-panel]{padding:20px}.button{gap:20px}.command{gap:10px}}@media(prefers-reduced-motion:no-preference){.tabs button,.button{transition:background-color .16s,border-color .16s,color .16s}}