From ad50c6f2a38a17961b5294b1b4a7ecf75965346d Mon Sep 17 00:00:00 2001 From: EngineerProjects Date: Mon, 22 Jun 2026 18:21:57 +0200 Subject: [PATCH 1/4] feat(release): version command, checksums, install script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cmd/cli: add var version (set by ldflags at build time, default 'dev') - cmd/cli: add 'seshat version' / '--version' / '-v' subcommand - release.yml: split 'release' job → 'build' for clarity; add SHA256SUMS.txt step - scripts/install.sh: curl-based installer — detects OS/arch, downloads correct binary from GitHub Releases, verifies SHA256, installs to ~/.local/bin (override with INSTALL_DIR=); warns if not in PATH --- .github/workflows/release.yml | 13 ++++-- cmd/cli/main.go | 3 ++ cmd/cli/root.go | 5 ++ scripts/install.sh | 88 +++++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100755 scripts/install.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca6ae81..e179913 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,8 +9,8 @@ permissions: contents: write jobs: - release: - name: Build and publish binaries + build: + name: Build ${{ matrix.suffix }} runs-on: ubuntu-latest strategy: matrix: @@ -73,7 +73,7 @@ jobs: publish: name: Publish GitHub Release - needs: release + needs: build runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 @@ -82,8 +82,13 @@ jobs: path: dist/ merge-multiple: true + - name: Generate checksums + working-directory: dist + run: sha256sum * > SHA256SUMS.txt + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: dist/** + files: | + dist/** generate_release_notes: true diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 839e3d0..5aa2440 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -9,6 +9,9 @@ import ( "github.com/EngineerProjects/seshat/pkg/runtimepath" ) +// version is set at build time via -ldflags "-X main.version=v1.2.3". +var version = "dev" + func main() { // Pin the CLI runtime root to the platform config dir (seshat-cli), // isolated from the seshat-product backend (seshat). SESHAT_RUNTIME_ROOT takes precedence. diff --git a/cmd/cli/root.go b/cmd/cli/root.go index 980c16f..905a717 100644 --- a/cmd/cli/root.go +++ b/cmd/cli/root.go @@ -25,6 +25,9 @@ func execute(ctx context.Context, args []string, stdin io.Reader, stdout, stderr return runMemory(args[1:], stdout, stderr) case "login": return runLogin(ctx, args[1:], stdout, stderr) + case "version", "--version", "-v": + fmt.Fprintln(stdout, version) + return nil case "help", "-h", "--help": printUsage(stdout) return nil @@ -63,4 +66,6 @@ func printUsage(out io.Writer) { fmt.Fprintln(out, " seshat login [--provider openai|anthropic] [--client-id ID]") fmt.Fprintln(out, " Authenticate via browser using your ChatGPT subscription.") fmt.Fprintln(out, " Runs a device-code flow — no API key required.") + fmt.Fprintln(out, "") + fmt.Fprintln(out, " seshat version Print the current version.") } diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..ecd00b1 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO="EngineerProjects/seshat" +BINARY="seshat" +INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" + +# ── Detect OS / arch ────────────────────────────────────────────────────────── + +OS="$(uname -s)" +ARCH="$(uname -m)" + +case "$OS" in + Linux) os="linux" ;; + Darwin) os="darwin" ;; + *) + echo "Unsupported OS: $OS" >&2 + echo "Download a binary manually from: https://github.com/$REPO/releases" >&2 + exit 1 + ;; +esac + +case "$ARCH" in + x86_64|amd64) arch="amd64" ;; + arm64|aarch64) arch="arm64" ;; + *) + echo "Unsupported architecture: $ARCH" >&2 + exit 1 + ;; +esac + +SUFFIX="${os}-${arch}" + +# ── Resolve version ─────────────────────────────────────────────────────────── + +if [ -z "${VERSION:-}" ]; then + echo "Fetching latest release..." + VERSION="$(curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" \ + | grep '"tag_name"' | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')" +fi + +if [ -z "$VERSION" ]; then + echo "Could not determine latest version. Set VERSION= explicitly." >&2 + exit 1 +fi + +echo "Installing seshat $VERSION ($SUFFIX)..." + +# ── Download ────────────────────────────────────────────────────────────────── + +BASE_URL="https://github.com/$REPO/releases/download/$VERSION" +BIN_NAME="${BINARY}-${SUFFIX}" +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +curl -fsSL "$BASE_URL/$BIN_NAME" -o "$TMP_DIR/$BINARY" +curl -fsSL "$BASE_URL/SHA256SUMS.txt" -o "$TMP_DIR/SHA256SUMS.txt" + +# ── Verify checksum ─────────────────────────────────────────────────────────── + +(cd "$TMP_DIR" && grep "$BIN_NAME" SHA256SUMS.txt | sha256sum --check --status) || { + echo "Checksum verification failed." >&2 + exit 1 +} + +# ── Install ─────────────────────────────────────────────────────────────────── + +mkdir -p "$INSTALL_DIR" +chmod +x "$TMP_DIR/$BINARY" +mv "$TMP_DIR/$BINARY" "$INSTALL_DIR/$BINARY" + +echo "" +echo "seshat $VERSION installed to $INSTALL_DIR/seshat" + +# Warn if INSTALL_DIR is not in PATH +case ":$PATH:" in + *":$INSTALL_DIR:"*) ;; + *) + echo "" + echo " Note: $INSTALL_DIR is not in your PATH." + echo " Add this to your shell profile (~/.bashrc, ~/.zshrc, ...):" + echo "" + echo " export PATH=\"\$HOME/.local/bin:\$PATH\"" + echo "" + ;; +esac + +echo "Run: seshat version" From 3bb137b9e0ca3a387b2305ccbc36ee656708d24c Mon Sep 17 00:00:00 2001 From: EngineerProjects Date: Mon, 22 Jun 2026 18:37:02 +0200 Subject: [PATCH 2/4] feat(install): full batteries-included end-user installer scripts/install.sh now covers the complete user setup: - Detect OS/arch, download binary, verify SHA256 - Install to ~/.local/bin (or $INSTALL_DIR) - Detect bash/zsh/fish and add PATH export to the right profile - Install uv if not present (via official installer) - Create Python venv + install docling-serve in $RUNTIME_ROOT/.venv - DB and runtime paths are created automatically on first 'seshat' run - NO_PYTHON=1 skips the uv/docling step (minimal install) - DOCLING_EXTRAS=gpu / PYTHON_VERSION=3.12 for customisation Developer path (no installer needed): go install github.com/EngineerProjects/seshat/cmd/cli@latest go get github.com/EngineerProjects/seshat@latest --- scripts/install.sh | 240 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 196 insertions(+), 44 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index ecd00b1..4dca734 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,88 +1,240 @@ #!/usr/bin/env bash +# install.sh — Seshat end-user installer +# +# Usage: +# curl -fsSL https://raw.githubusercontent.com/EngineerProjects/seshat/main/scripts/install.sh | bash +# +# Options (env vars): +# VERSION=v0.1.0 Install a specific version (default: latest) +# INSTALL_DIR=... Binary destination (default: ~/.local/bin) +# NO_PYTHON=1 Skip uv + docling-serve setup +# DOCLING_EXTRAS=gpu Install docling-serve[gpu] instead of the base package +# PYTHON_VERSION=3.12 Python version for the docling venv (default: 3.11) +# +# Developer / SDK usage (no installer needed): +# go install github.com/EngineerProjects/seshat/cmd/cli@latest +# go get github.com/EngineerProjects/seshat@latest # in your go.mod + set -euo pipefail REPO="EngineerProjects/seshat" BINARY="seshat" INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" +NO_PYTHON="${NO_PYTHON:-0}" +PYTHON_VERSION="${PYTHON_VERSION:-3.11}" +DOCLING_EXTRAS="${DOCLING_EXTRAS:-}" + +# ── Colors ──────────────────────────────────────────────────────────────────── +if [ -t 1 ]; then + RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' + BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m' +else + RED=''; GREEN=''; YELLOW=''; BLUE=''; BOLD=''; NC='' +fi + +info() { echo -e "${BLUE}[seshat]${NC} $*"; } +success() { echo -e "${GREEN}[seshat]${NC} $*"; } +warn() { echo -e "${YELLOW}[seshat]${NC} $*"; } +error() { echo -e "${RED}[seshat]${NC} $*" >&2; } +step() { echo -e "\n${BOLD}▸ $*${NC}"; } + +# ── Runtime root (mirrors Go DefaultConfigDir logic) ────────────────────────── +_runtime_root() { + if [ -n "${SESHAT_RUNTIME_ROOT:-}" ]; then echo "$SESHAT_RUNTIME_ROOT"; return; fi + if [ -n "${XDG_CONFIG_HOME:-}" ]; then echo "$XDG_CONFIG_HOME/seshat-cli"; return; fi + echo "$HOME/.config/seshat-cli" +} +RUNTIME_ROOT="$(_runtime_root)" # ── Detect OS / arch ────────────────────────────────────────────────────────── +step "Detecting platform" OS="$(uname -s)" ARCH="$(uname -m)" case "$OS" in - Linux) os="linux" ;; - Darwin) os="darwin" ;; - *) - echo "Unsupported OS: $OS" >&2 - echo "Download a binary manually from: https://github.com/$REPO/releases" >&2 - exit 1 - ;; + Linux) os="linux" ;; + Darwin) os="darwin" ;; + *) + error "Unsupported OS: $OS" + error "Download a binary manually: https://github.com/$REPO/releases" + exit 1 + ;; esac case "$ARCH" in - x86_64|amd64) arch="amd64" ;; - arm64|aarch64) arch="arm64" ;; - *) - echo "Unsupported architecture: $ARCH" >&2 - exit 1 - ;; + x86_64|amd64) arch="amd64" ;; + arm64|aarch64) arch="arm64" ;; + *) + error "Unsupported architecture: $ARCH" + exit 1 + ;; esac SUFFIX="${os}-${arch}" +info "Platform: $SUFFIX" # ── Resolve version ─────────────────────────────────────────────────────────── +step "Resolving version" if [ -z "${VERSION:-}" ]; then - echo "Fetching latest release..." - VERSION="$(curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" \ - | grep '"tag_name"' | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')" -fi - -if [ -z "$VERSION" ]; then - echo "Could not determine latest version. Set VERSION= explicitly." >&2 - exit 1 + info "Fetching latest release..." + _dl() { command -v curl &>/dev/null && curl -fsSL "$1" || wget -qO- "$1"; } + VERSION="$(_dl "https://api.github.com/repos/$REPO/releases/latest" \ + | grep '"tag_name"' | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')" fi -echo "Installing seshat $VERSION ($SUFFIX)..." +[ -z "$VERSION" ] && { error "Could not resolve version. Set VERSION=vX.Y.Z explicitly."; exit 1; } +info "Version: $VERSION" -# ── Download ────────────────────────────────────────────────────────────────── +# ── Download binary ─────────────────────────────────────────────────────────── +step "Downloading seshat $VERSION" BASE_URL="https://github.com/$REPO/releases/download/$VERSION" -BIN_NAME="${BINARY}-${SUFFIX}" +BIN_ASSET="${BINARY}-${SUFFIX}" TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT -curl -fsSL "$BASE_URL/$BIN_NAME" -o "$TMP_DIR/$BINARY" -curl -fsSL "$BASE_URL/SHA256SUMS.txt" -o "$TMP_DIR/SHA256SUMS.txt" +_dl() { command -v curl &>/dev/null && curl -fsSL "$1" -o "$2" || wget -qO "$2" "$1"; } + +info "Binary: $BIN_ASSET" +_dl "$BASE_URL/$BIN_ASSET" "$TMP_DIR/$BINARY" +_dl "$BASE_URL/SHA256SUMS.txt" "$TMP_DIR/SHA256SUMS.txt" # ── Verify checksum ─────────────────────────────────────────────────────────── +step "Verifying checksum" -(cd "$TMP_DIR" && grep "$BIN_NAME" SHA256SUMS.txt | sha256sum --check --status) || { - echo "Checksum verification failed." >&2 - exit 1 -} +(cd "$TMP_DIR" && grep "$BIN_ASSET" SHA256SUMS.txt | sha256sum --check --status) \ + || { error "Checksum verification failed — aborting."; exit 1; } +success "Checksum OK" -# ── Install ─────────────────────────────────────────────────────────────────── +# ── Install binary ──────────────────────────────────────────────────────────── +step "Installing binary" mkdir -p "$INSTALL_DIR" chmod +x "$TMP_DIR/$BINARY" mv "$TMP_DIR/$BINARY" "$INSTALL_DIR/$BINARY" +success "Installed: $INSTALL_DIR/$BINARY" + +# ── Add to PATH in shell profile ────────────────────────────────────────────── +step "Configuring PATH" + +_add_to_path() { + local profile="$1" + local line='export PATH="$HOME/.local/bin:$PATH"' + if [ -f "$profile" ] && grep -qF '.local/bin' "$profile"; then + info "$profile already exports ~/.local/bin — skipping" + else + echo "" >> "$profile" + echo "# Added by seshat installer" >> "$profile" + echo "$line" >> "$profile" + success "Added PATH export to $profile" + fi +} + +case ":$PATH:" in + *":$INSTALL_DIR:"*) + success "$INSTALL_DIR already in PATH" + RELOAD_NEEDED=0 + ;; + *) + warn "$INSTALL_DIR not in PATH — adding to shell profile" + RELOAD_NEEDED=1 + + # Fish shell + if [ -n "${FISH_VERSION:-}" ] || echo "${SHELL:-}" | grep -q fish; then + FISH_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/fish" + mkdir -p "$FISH_DIR" + FISH_CONF="$FISH_DIR/config.fish" + if grep -qF '.local/bin' "$FISH_CONF" 2>/dev/null; then + info "$FISH_CONF already exports ~/.local/bin — skipping" + else + echo "" >> "$FISH_CONF" + echo "# Added by seshat installer" >> "$FISH_CONF" + echo 'fish_add_path $HOME/.local/bin' >> "$FISH_CONF" + success "Added PATH to $FISH_CONF" + fi + else + # Bash + [ -f "$HOME/.bashrc" ] && _add_to_path "$HOME/.bashrc" + # Zsh + [ -f "$HOME/.zshrc" ] && _add_to_path "$HOME/.zshrc" + # Fallback: .profile + if [ ! -f "$HOME/.bashrc" ] && [ ! -f "$HOME/.zshrc" ]; then + _add_to_path "$HOME/.profile" + fi + fi + ;; +esac +# ── Python / docling setup ──────────────────────────────────────────────────── +if [ "$NO_PYTHON" = "1" ]; then + warn "Skipping Python setup (NO_PYTHON=1)" +else + step "Setting up Python environment (uv + docling-serve)" + info "Runtime root: $RUNTIME_ROOT" + + # Install uv if missing + if command -v uv &>/dev/null; then + success "uv already installed: $(uv --version)" + else + info "Installing uv..." + if command -v curl &>/dev/null; then + curl -LsSf https://astral.sh/uv/install.sh | sh + else + wget -qO- https://astral.sh/uv/install.sh | sh + fi + export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH" + command -v uv &>/dev/null || { + error "uv installed but not found on PATH — reopen your terminal and re-run." + exit 1 + } + success "uv installed: $(uv --version)" + fi + + # Create venv + VENV_DIR="$RUNTIME_ROOT/.venv" + mkdir -p "$RUNTIME_ROOT" + + if [ -d "$VENV_DIR" ]; then + info "Python venv already exists — skipping creation" + else + info "Creating Python $PYTHON_VERSION venv at $VENV_DIR ..." + uv venv "$VENV_DIR" --python "$PYTHON_VERSION" --seed + success "Venv created" + fi + + # Install docling-serve + PACKAGE="docling-serve" + [ -n "$DOCLING_EXTRAS" ] && PACKAGE="docling-serve[$DOCLING_EXTRAS]" + info "Installing $PACKAGE ..." + uv pip install --python "$VENV_DIR/bin/python" "$PACKAGE" + + [ -x "$VENV_DIR/bin/docling-serve" ] \ + || { error "docling-serve not found after install — check the output above."; exit 1; } + success "docling-serve ready" +fi + +# ── Done ────────────────────────────────────────────────────────────────────── +echo "" +echo -e "${GREEN}${BOLD}✓ Seshat $VERSION installed successfully${NC}" +echo "" +echo " Binary: $INSTALL_DIR/seshat" +echo " Runtime: $RUNTIME_ROOT (DB + sessions created on first run)" +if [ "$NO_PYTHON" != "1" ]; then +echo " Docling venv: $RUNTIME_ROOT/.venv" +fi echo "" -echo "seshat $VERSION installed to $INSTALL_DIR/seshat" -# Warn if INSTALL_DIR is not in PATH -case ":$PATH:" in - *":$INSTALL_DIR:"*) ;; - *) +if [ "${RELOAD_NEEDED:-0}" = "1" ]; then + echo -e "${YELLOW} Reload your shell to activate PATH:${NC}" + echo " source ~/.bashrc # or ~/.zshrc / open a new terminal" echo "" - echo " Note: $INSTALL_DIR is not in your PATH." - echo " Add this to your shell profile (~/.bashrc, ~/.zshrc, ...):" - echo "" - echo " export PATH=\"\$HOME/.local/bin:\$PATH\"" - echo "" - ;; -esac +fi -echo "Run: seshat version" +echo " Get started:" +echo " seshat config # configure your AI provider + API key" +echo " seshat chat # start chatting" +echo "" +echo " Docs: https://github.com/$REPO" +echo "" From eeba1050785ed13b2703115d3f003bcf8d754d20 Mon Sep 17 00:00:00 2001 From: EngineerProjects Date: Mon, 22 Jun 2026 18:44:22 +0200 Subject: [PATCH 3/4] feat(cli): add 'seshat setup' command for uv + docling install - cmd/cli/setup.go: new 'seshat setup' subcommand - 'seshat setup' installs uv (if missing) + creates venv + installs docling-serve - 'seshat setup --check' reports what is installed without changing anything - 'seshat setup --python' custom Python version (default 3.11) - 'seshat setup --extras' pip extras e.g. gpu - root.go: dispatch 'setup', add it to printUsage - scripts/install.sh: replace inline uv/docling logic with '$seshat setup' so the setup logic lives in one place and both install paths are consistent: curl | bash -> binary install + seshat setup (automatic) go install ... -> binary only, then: seshat setup (on demand) --- cmd/cli/root.go | 6 ++ cmd/cli/setup.go | 164 +++++++++++++++++++++++++++++++++++++++++++++ scripts/install.sh | 59 ++++------------ 3 files changed, 184 insertions(+), 45 deletions(-) create mode 100644 cmd/cli/setup.go diff --git a/cmd/cli/root.go b/cmd/cli/root.go index 905a717..8af7ca0 100644 --- a/cmd/cli/root.go +++ b/cmd/cli/root.go @@ -25,6 +25,8 @@ func execute(ctx context.Context, args []string, stdin io.Reader, stdout, stderr return runMemory(args[1:], stdout, stderr) case "login": return runLogin(ctx, args[1:], stdout, stderr) + case "setup": + return runSetup(args[1:], stdout, stderr) case "version", "--version", "-v": fmt.Fprintln(stdout, version) return nil @@ -67,5 +69,9 @@ func printUsage(out io.Writer) { fmt.Fprintln(out, " Authenticate via browser using your ChatGPT subscription.") fmt.Fprintln(out, " Runs a device-code flow — no API key required.") fmt.Fprintln(out, "") + fmt.Fprintln(out, " seshat setup [--check] [--python VERSION] [--extras EXTRAS]") + fmt.Fprintln(out, " Install uv + docling-serve for document processing.") + fmt.Fprintln(out, " --check show status without installing.") + fmt.Fprintln(out, "") fmt.Fprintln(out, " seshat version Print the current version.") } diff --git a/cmd/cli/setup.go b/cmd/cli/setup.go new file mode 100644 index 0000000..31b6f86 --- /dev/null +++ b/cmd/cli/setup.go @@ -0,0 +1,164 @@ +package main + +import ( + "flag" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/EngineerProjects/seshat/pkg/runtimepath" +) + +func runSetup(args []string, stdout, stderr io.Writer) error { + fs := flag.NewFlagSet("setup", flag.ContinueOnError) + fs.SetOutput(stderr) + checkOnly := fs.Bool("check", false, "report what is installed without installing anything") + pythonVer := fs.String("python", "3.11", "Python version for the docling venv") + extras := fs.String("extras", "", "docling-serve pip extras (e.g. gpu)") + if err := fs.Parse(args); err != nil { + return err + } + + root := os.Getenv(runtimepath.EnvRuntimeRoot) + if root == "" { + root = runtimepath.DefaultConfigDir("seshat-cli") + } + venv := filepath.Join(root, ".venv") + + if *checkOnly { + return setupCheck(stdout, root, venv) + } + return setupInstall(stdout, stderr, root, venv, *pythonVer, *extras) +} + +// ── Check ───────────────────────────────────────────────────────────────────── + +func setupCheck(out io.Writer, root, venv string) error { + fmt.Fprintln(out, "Seshat setup status") + fmt.Fprintln(out, "-------------------") + fmt.Fprintf(out, "Runtime root : %s\n", root) + + uvPath, uvErr := exec.LookPath("uv") + if uvErr == nil { + uvVer, _ := cmdOutput("uv", "--version") + fmt.Fprintf(out, "uv : %s (%s)\n", strings.TrimSpace(uvVer), uvPath) + } else { + fmt.Fprintln(out, "uv : not found") + } + + doclingBin := filepath.Join(venv, "bin", "docling-serve") + if _, err := os.Stat(doclingBin); err == nil { + fmt.Fprintf(out, "docling-serve: %s\n", doclingBin) + } else { + fmt.Fprintln(out, "docling-serve: not installed") + } + + if _, err := os.Stat(root); err == nil { + fmt.Fprintf(out, "runtime dir : OK (%s)\n", root) + } else { + fmt.Fprintf(out, "runtime dir : not yet created (created on first run)\n") + } + return nil +} + +// ── Install ─────────────────────────────────────────────────────────────────── + +func setupInstall(stdout, stderr io.Writer, root, venv, pythonVer, extras string) error { + logf := func(msg string, a ...any) { fmt.Fprintf(stdout, "[seshat] "+msg+"\n", a...) } + fail := func(msg string, a ...any) error { return fmt.Errorf(msg, a...) } + + // 1. Install uv if missing + if _, err := exec.LookPath("uv"); err != nil { + logf("uv not found — installing via official installer...") + if err := installUV(stdout, stderr); err != nil { + return fail("uv installation failed: %w", err) + } + // uv installer places the binary in ~/.local/bin or ~/.cargo/bin + extra := os.Getenv("HOME") + "/.local/bin" + string(os.PathListSeparator) + + os.Getenv("HOME") + "/.cargo/bin" + os.Setenv("PATH", extra+string(os.PathListSeparator)+os.Getenv("PATH")) + + if _, err := exec.LookPath("uv"); err != nil { + return fail("uv installed but not found on PATH — reopen your terminal and retry") + } + } + uvVer, _ := cmdOutput("uv", "--version") + logf("uv: %s", strings.TrimSpace(uvVer)) + + // 2. Create runtime root + if err := os.MkdirAll(root, 0o750); err != nil { + return fail("could not create runtime root %s: %w", root, err) + } + logf("Runtime root: %s", root) + + // 3. Create venv + if _, err := os.Stat(venv); os.IsNotExist(err) { + logf("Creating Python %s venv at %s ...", pythonVer, venv) + if err := runCmd(stdout, stderr, "uv", "venv", venv, "--python", pythonVer, "--seed"); err != nil { + return fail("venv creation failed: %w", err) + } + } else { + logf("Venv already exists at %s", venv) + } + + // 4. Install docling-serve + pkg := "docling-serve" + if extras != "" { + pkg = "docling-serve[" + extras + "]" + } + logf("Installing %s ...", pkg) + pythonBin := filepath.Join(venv, "bin", "python") + if err := runCmd(stdout, stderr, "uv", "pip", "install", "--python", pythonBin, pkg); err != nil { + return fail("docling-serve installation failed: %w", err) + } + + // 5. Verify + doclingBin := filepath.Join(venv, "bin", "docling-serve") + if _, err := os.Stat(doclingBin); err != nil { + return fail("docling-serve not found at %s after install", doclingBin) + } + + logf("Setup complete.") + fmt.Fprintln(stdout, "") + fmt.Fprintf(stdout, " Runtime: %s\n", root) + fmt.Fprintf(stdout, " Venv: %s\n", venv) + fmt.Fprintf(stdout, " Docling: %s\n", doclingBin) + fmt.Fprintln(stdout, "") + fmt.Fprintln(stdout, " seshat auto-starts docling on launch when the venv is present.") + fmt.Fprintln(stdout, " Run: seshat chat") + fmt.Fprintln(stdout, "") + return nil +} + +// installUV downloads and runs the official uv installer. +func installUV(stdout, stderr io.Writer) error { + // Try curl first, then wget. + var cmd *exec.Cmd + if _, err := exec.LookPath("curl"); err == nil { + cmd = exec.Command("sh", "-c", "curl -LsSf https://astral.sh/uv/install.sh | sh") + } else if _, err := exec.LookPath("wget"); err == nil { + cmd = exec.Command("sh", "-c", "wget -qO- https://astral.sh/uv/install.sh | sh") + } else { + return fmt.Errorf("neither curl nor wget found; install uv manually: https://docs.astral.sh/uv/getting-started/installation/") + } + cmd.Stdout = stdout + cmd.Stderr = stderr + return cmd.Run() +} + +// runCmd runs a command streaming output to stdout/stderr. +func runCmd(stdout, stderr io.Writer, name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdout = stdout + cmd.Stderr = stderr + return cmd.Run() +} + +// cmdOutput runs a command and returns its combined output. +func cmdOutput(name string, args ...string) (string, error) { + out, err := exec.Command(name, args...).CombinedOutput() + return string(out), err +} diff --git a/scripts/install.sh b/scripts/install.sh index 4dca734..9b230b8 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -167,63 +167,32 @@ case ":$PATH:" in ;; esac -# ── Python / docling setup ──────────────────────────────────────────────────── +# ── Python / docling setup via seshat setup ────────────────────────────────── +# Use the freshly installed binary so the logic lives in one place. +SESHAT_BIN="$INSTALL_DIR/seshat" +export PATH="$INSTALL_DIR:$HOME/.local/bin:$HOME/.cargo/bin:$PATH" + if [ "$NO_PYTHON" = "1" ]; then warn "Skipping Python setup (NO_PYTHON=1)" + warn "Run 'seshat setup' later to enable document processing." else step "Setting up Python environment (uv + docling-serve)" - info "Runtime root: $RUNTIME_ROOT" - - # Install uv if missing - if command -v uv &>/dev/null; then - success "uv already installed: $(uv --version)" - else - info "Installing uv..." - if command -v curl &>/dev/null; then - curl -LsSf https://astral.sh/uv/install.sh | sh - else - wget -qO- https://astral.sh/uv/install.sh | sh - fi - export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH" - command -v uv &>/dev/null || { - error "uv installed but not found on PATH — reopen your terminal and re-run." - exit 1 - } - success "uv installed: $(uv --version)" - fi - # Create venv - VENV_DIR="$RUNTIME_ROOT/.venv" - mkdir -p "$RUNTIME_ROOT" + SETUP_ARGS="" + [ -n "$PYTHON_VERSION" ] && SETUP_ARGS="$SETUP_ARGS --python $PYTHON_VERSION" + [ -n "$DOCLING_EXTRAS" ] && SETUP_ARGS="$SETUP_ARGS --extras $DOCLING_EXTRAS" - if [ -d "$VENV_DIR" ]; then - info "Python venv already exists — skipping creation" - else - info "Creating Python $PYTHON_VERSION venv at $VENV_DIR ..." - uv venv "$VENV_DIR" --python "$PYTHON_VERSION" --seed - success "Venv created" - fi - - # Install docling-serve - PACKAGE="docling-serve" - [ -n "$DOCLING_EXTRAS" ] && PACKAGE="docling-serve[$DOCLING_EXTRAS]" - info "Installing $PACKAGE ..." - uv pip install --python "$VENV_DIR/bin/python" "$PACKAGE" - - [ -x "$VENV_DIR/bin/docling-serve" ] \ - || { error "docling-serve not found after install — check the output above."; exit 1; } - success "docling-serve ready" + # shellcheck disable=SC2086 + SESHAT_RUNTIME_ROOT="$RUNTIME_ROOT" "$SESHAT_BIN" setup $SETUP_ARGS fi # ── Done ────────────────────────────────────────────────────────────────────── echo "" echo -e "${GREEN}${BOLD}✓ Seshat $VERSION installed successfully${NC}" echo "" -echo " Binary: $INSTALL_DIR/seshat" -echo " Runtime: $RUNTIME_ROOT (DB + sessions created on first run)" -if [ "$NO_PYTHON" != "1" ]; then -echo " Docling venv: $RUNTIME_ROOT/.venv" -fi +echo " Binary: $INSTALL_DIR/seshat" +echo " Runtime: $RUNTIME_ROOT" +echo " (DB + sessions are created automatically on first run)" echo "" if [ "${RELOAD_NEEDED:-0}" = "1" ]; then From 70d833f749b894a4579f19a3794f7ef666b17f06 Mon Sep 17 00:00:00 2001 From: EngineerProjects Date: Mon, 22 Jun 2026 18:50:45 +0200 Subject: [PATCH 4/4] =?UTF-8?q?docs(readme):=20update=20installation=20?= =?UTF-8?q?=E2=80=94=20curl=20installer,=20go=20install,=20seshat=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace 'coming soon' binaries section with actual install instructions - Lead Quick Start with curl installer (end-user path) - CLI section: show both curl and go install paths side by side - Add seshat setup / setup --check to CLI command reference - Development section: clarify build-from-source vs runtime setup - Add seshat setup section explaining --check / --python / --extras flags - Add make clean / clean-runtime / clean-all to daily commands --- README.md | 124 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index b704a69..31289b9 100644 --- a/README.md +++ b/README.md @@ -106,9 +106,40 @@ seshat is the **headless runtime**: pure Go, no UI, no users, no billing. It is The engine is intentionally kept minimal and fast. If you need something from the SDK that is not exposed yet, open an issue and we will prioritize it. -### 📦 Prebuilt binaries (coming soon) +### 📦 Installation -You will soon be able to download a single binary to use the CLI or embed the SDK in your application without building from source. Watch this repo for releases. +**End users — one command, fully configured:** + +```bash +curl -fsSL https://raw.githubusercontent.com/EngineerProjects/seshat/main/scripts/install.sh | bash +``` + +Downloads the right binary for your platform, adds it to your PATH, installs `uv` and `docling-serve` for document processing, and leaves the runtime directory (`~/.config/seshat-cli/`) ready. The DB and sessions are created on first run. + +Options: +```bash +NO_PYTHON=1 bash <(curl -fsSL ...) # skip uv + docling (minimal install) +VERSION=v0.1.0 bash <(curl -fsSL ...) # pin a specific version +``` + +**Developers — Go toolchain:** + +```bash +# Install the CLI binary +go install github.com/EngineerProjects/seshat/cmd/cli@latest + +# Then set up document processing if needed +seshat setup + +# Or check what is already installed +seshat setup --check +``` + +**SDK — embed in your Go application:** + +```bash +go get github.com/EngineerProjects/seshat@latest +``` --- @@ -118,20 +149,16 @@ You will soon be able to download a single binary to use the CLI or embed the SD An AI agent in your terminal. Multi-provider, local-first, skills-aware. -**Build from source** +**Install** ```bash -git clone https://github.com/EngineerProjects/seshat -cd seshat -make build # produces bin/seshat and bin/seshat-grpc -``` +# End users — full setup in one command: +curl -fsSL https://raw.githubusercontent.com/EngineerProjects/seshat/main/scripts/install.sh | bash -Add `bin/` to your PATH, or copy `bin/seshat` to `/usr/local/bin`: - -```bash -export PATH="$PATH:$(pwd)/bin" -# or -sudo cp bin/seshat /usr/local/bin/seshat +# Developers — binary only via Go toolchain: +go install github.com/EngineerProjects/seshat/cmd/cli@latest +seshat setup # install uv + docling-serve afterwards if needed +seshat setup --check # check what is already configured ``` **Configure a provider** @@ -139,20 +166,20 @@ sudo cp bin/seshat /usr/local/bin/seshat ```bash seshat config --provider anthropic --api-key sk-ant-... seshat config --model anthropic:claude-sonnet-4-20250514 - seshat config --print ``` **Run** ```bash -seshat chat # interactive TUI session -seshat chat --resume # resume a specific session -seshat chat --continue # resume the most recent session -seshat run "list all TODO comments in this codebase" # one-shot task -seshat sessions list # browse past sessions -seshat sessions list --status active # active sessions only -seshat help # full command reference +seshat chat # interactive TUI session +seshat chat --resume # resume a specific session +seshat chat --continue # resume the most recent session +seshat run "list all TODO comments in this codebase" # one-shot task +seshat sessions list # browse past sessions +seshat setup --check # show uv / docling status +seshat version # print installed version +seshat help # full command reference ``` Sessions are persisted locally in SQLite. Skills are loaded from `.seshat/skills/` in your project. The full tool set is available: file edits, sandboxed bash, web search, browser, MCP servers, sub-agents. @@ -309,32 +336,28 @@ Full model listings and capabilities: [`docs/providers.md`](./docs/providers.md) ## 🚀 Quick Start ```bash -# 1. Clone and build -git clone https://github.com/EngineerProjects/seshat -cd seshat -make build # produces bin/seshat and bin/seshat-grpc -export PATH="$PATH:$(pwd)/bin" +# 1. Install +curl -fsSL https://raw.githubusercontent.com/EngineerProjects/seshat/main/scripts/install.sh | bash + +# Reload your shell (or open a new terminal) if prompted, then: -# 2. Set your API key and model +# 2. Configure your provider seshat config --provider anthropic --api-key sk-ant-... seshat config --model anthropic:claude-sonnet-4-20250514 # 3. Start chatting -seshat chat # new session +seshat chat # new session (opens TUI) seshat chat --continue # resume last session seshat chat --resume # resume a specific session - -# One-shot task in the current directory -seshat run "list all TODO comments in this codebase" - -# Start the gRPC server (port 50051) -ANTHROPIC_API_KEY=sk-ant-... ./bin/seshat-grpc +seshat run "list all TODO comments in this codebase" # one-shot task ``` > **No API key?** Use Ollama for free local inference: > `seshat config --provider ollama --model ollama:llama3.2` > (requires [Ollama](https://ollama.com) running locally) +> **Developers building from source:** see the [Development](#️-development) section below. + --- ## ⚡ Skills @@ -396,30 +419,45 @@ Full architecture diagrams (Mermaid): [`docs/vision/diagrams.md`](./docs/vision/ ## 🛠️ Development -### First-time setup +### First-time setup (build from source) ```bash # Linux / macOS — installs all dependencies, builds, wires git hooks +git clone https://github.com/EngineerProjects/seshat +cd seshat make setup # Windows (PowerShell — make is not available by default on Windows) powershell -ExecutionPolicy Bypass -File scripts\setup.ps1 ``` -`make setup` handles everything: Go version check, ripgrep, uv, Python venv with docling-serve, and the final build. +`make setup` handles everything: Go version check, ripgrep, uv, Python venv with docling-serve, and the final build. Binaries land in `bin/`. ### Daily commands ```bash -make build # build CLI and gRPC binaries to bin/ -make test # run all tests -make test-race # run tests with race detector -make lint # golangci-lint -make fmt # gofmt -make hooks # (re-)install git pre-commit hooks -make install-deps # install ripgrep only (included in make setup) +make build # build CLI and gRPC binaries to bin/ +make test # run all tests +make test-race # run tests with race detector +make lint # golangci-lint +make fmt # gofmt +make hooks # (re-)install git pre-commit hooks make install-python # install/update the Python venv + docling-serve only make start-docling # start docling-serve manually (auto-started by seshat chat) +make clean # remove bin/ +make clean-runtime # erase runtime data (~/.config/seshat-cli) +make clean-all # both +``` + +### seshat setup (runtime, not source) + +When the CLI is installed via `curl | bash` or `go install`, use the built-in setup command to manage the Python environment: + +```bash +seshat setup # install uv + docling-serve +seshat setup --check # show status without installing +seshat setup --python 3.12 # use a specific Python version +seshat setup --extras gpu # GPU-accelerated docling ``` ### Runtime data directory