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
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ghost 👻

[![License: MIT](https://img.shields.io/badge/License-MIT-3DDC97?style=flat-square)](LICENSE)  ![Platform: macOS](https://img.shields.io/badge/platform-macOS-lightgrey?style=flat-square)  [![Built on Hermes Agent](https://img.shields.io/badge/built%20on-Hermes%20Agent-7C5CFF?style=flat-square)](https://github.com/NousResearch/hermes-agent)  ![Open-weight only](https://img.shields.io/badge/models-open--weight%20only-FF8A3D?style=flat-square)
[![License: MIT](https://img.shields.io/badge/License-MIT-3DDC97?style=flat-square)](LICENSE)  ![Platform](https://img.shields.io/badge/platform-macOS%20%C2%B7%20Linux%20%C2%B7%20WSL2-lightgrey?style=flat-square)  [![Built on Hermes Agent](https://img.shields.io/badge/built%20on-Hermes%20Agent-7C5CFF?style=flat-square)](https://github.com/NousResearch/hermes-agent)  ![Open-weight only](https://img.shields.io/badge/models-open--weight%20only-FF8A3D?style=flat-square)

**A private, unrestricted agentic harness.** A real terminal agent that runs commands, edits files, executes code, and searches the web, with every hosted request routed through OpenGradient's TEE gateway so the model provider never sees your prompts. It answers what you actually ask, drops to a fully-offline local model on demand, and phones home to no one.

Expand All @@ -12,7 +12,7 @@ Built on the [Hermes Agent](https://github.com/NousResearch/hermes-agent) engine

## Install (30 seconds)

macOS only. One deterministic command, no LLM and nothing agentic, installs **and** updates everything (the engine, the privacy stack, the `ghost` commands). uv provisions an isolated Python 3.11 under the hood, so the only prerequisite is `git`:
One deterministic command, no LLM and nothing agentic, installs **and** updates everything (the engine, the privacy stack, the `ghost` commands) on macOS, Linux, or WSL2. uv provisions an isolated Python 3.11 under the hood, so the only prerequisite is `git`:

```bash
curl -fsSL https://raw.githubusercontent.com/OpenGradient/ghost/main/install.sh | bash
Expand All @@ -29,9 +29,7 @@ Re-run the same command, or `ghost update`, to update. From a local clone it's j

## Why ghost exists

Most agents are either useless or creepy for real work. ghost fixes the two that matter most.

### #1: The Model Lectures You Instead of Working
### Problem #1: The Model Lectures You Instead of Working

> "The Net interprets censorship as damage and routes around it."
>
Expand All @@ -41,7 +39,7 @@ Most agents are either useless or creepy for real work. ghost fixes the two that

**The Fix.** ghost only connects **open-weight, unrestricted models** (DeepSeek V4 Pro by default; Hermes 4 405B/70B) and applies a per-model steer, so the default answers in full with no sermon. Closed, refusing models (Claude, GPT, Gemini, Grok) aren't offered, and the gateway rejects anything off the list. It treats you as a competent adult, but it isn't an edgelord either: it won't volunteer illegal or shock content, it just won't refuse you.

### #2: The Provider Reads Everything You Send
### Problem #2: The Provider Reads Everything You Send

> "Privacy is the power to selectively reveal oneself to the world."
>
Expand Down
64 changes: 39 additions & 25 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
# GHOST_CHAT_APP_URL= override the website used for `ghost-login` (default chat.opengradient.ai)
set -euo pipefail

# macOS only: the privacy stack runs as launchd LaunchAgents and uses BSD tooling. Fail fast
# with a clear message rather than part-installing on Linux/WSL and erroring confusingly later.
if [ "$(uname -s)" != "Darwin" ]; then
echo "!! ghost's installer currently supports macOS only (it uses launchd). Detected: $(uname -s)." >&2
exit 1
fi
# Supported platforms: macOS (privacy services via launchd) and Linux / WSL2 (systemd --user,
# with a plain background-process fallback where systemd --user isn't available).
OS="$(uname -s)"
case "$OS" in
Darwin|Linux) ;;
*) echo "!! ghost supports macOS, Linux, and WSL2. Detected: $OS." >&2; exit 1 ;;
esac

# Resolve where this script lives. When run via `curl ... | bash` there is no checkout, so
# self-bootstrap: clone (or fast-forward) the repo into ~/.ghost-src and re-exec from there. This
Expand Down Expand Up @@ -90,7 +91,9 @@ have uv || { echo "!! ghost needs uv (https://docs.astral.sh/uv/getting-started/
if [ -n "$WANT_LOCAL" ]; then
if ! have ollama && have brew; then echo " installing Ollama (brew --cask)"; brew install --cask ollama || true; fi
have ollama || { echo "!! GHOST_LOCAL set but Ollama is missing -- install it from https://ollama.com (or drop GHOST_LOCAL for hosted-only) then re-run."; exit 1; }
pgrep -xq ollama || open -a Ollama 2>/dev/null || true ; sleep 1
if [ "$OS" = "Darwin" ]; then pgrep -xq ollama || open -a Ollama 2>/dev/null || true
else pgrep -xq ollama || (nohup ollama serve >/dev/null 2>&1 &) || true; fi
sleep 1
fi

if [ ! -d "$ENGINE_HOME/hermes-agent" ] && ! have hermes; then
Expand Down Expand Up @@ -193,25 +196,36 @@ else
rm -f "$PRIV/.scrub" "$PRIV/.no_scrub"
say "Full-fidelity mode (default) -- no outbound redaction. Set GHOST_SCRUB=1 to strip your PII/secrets before the gateway."
fi
mkdir -p "$LA"

# The scrubber runs as a launchd service; og-veil talks to chat-api directly (content is
# still private via OHTTP/TEE). Clean up any rotating-proxy marker from an older install.
BASE_SERVICES="hermes-pii-scrubber"
rm -f "$PRIV/.proxy"

for svc in $BASE_SERVICES; do
sed -e "s#__PYTHON__#$PYTHON#g" -e "s#__HOME__#$HOME#g" "$REPO/launchd/com.advait.$svc.plist" > "$LA/com.advait.$svc.plist"
launchctl unload "$LA/com.advait.$svc.plist" 2>/dev/null || true
launchctl load -w "$LA/com.advait.$svc.plist"
done
# Run the privacy services (scrubber :8788 + og-veil :11435) as managed, auto-restarting
# background services -- launchd on macOS, systemd --user on Linux/WSL2 (with a plain
# background-process fallback). Both talk to chat-api directly (content private via OHTTP/TEE).
rm -f "$PRIV/.proxy" # clean any rotating-proxy marker from an older install

# og-veil service (port 11435, to avoid colliding with Ollama on 11434). It owns the
# OHTTP/TEE/verification + auth, and talks to chat-api directly.
VEIL_PLIST="$LA/com.advait.hermes-veil.plist"
sed -e "s#__PYTHON__#$PYTHON#g" -e "s#__HOME__#$HOME#g" "$REPO/launchd/com.advait.hermes-veil.plist" > "$VEIL_PLIST"
launchctl unload "$VEIL_PLIST" 2>/dev/null || true
launchctl load -w "$VEIL_PLIST"
if [ "$OS" = "Darwin" ]; then
mkdir -p "$LA"
for svc in hermes-pii-scrubber hermes-veil; do
sed -e "s#__PYTHON__#$PYTHON#g" -e "s#__HOME__#$HOME#g" "$REPO/launchd/com.advait.$svc.plist" > "$LA/com.advait.$svc.plist"
launchctl unload "$LA/com.advait.$svc.plist" 2>/dev/null || true
launchctl load -w "$LA/com.advait.$svc.plist"
done
elif command -v systemctl >/dev/null 2>&1 && systemctl --user show-environment >/dev/null 2>&1; then
UD="$HOME/.config/systemd/user"; mkdir -p "$UD"
for svc in ghost-scrubber ghost-veil; do
sed -e "s#__PYTHON__#$PYTHON#g" -e "s#__PRIV__#$PRIV#g" "$REPO/systemd/$svc.service" > "$UD/$svc.service"
done
systemctl --user daemon-reload
systemctl --user reenable ghost-scrubber.service ghost-veil.service >/dev/null 2>&1 || true
systemctl --user restart ghost-scrubber.service ghost-veil.service
loginctl enable-linger "$USER" >/dev/null 2>&1 || true # keep services up without an active login (reboot persistence)
echo " services started via systemd --user (logs: journalctl --user -u ghost-veil)"
else
echo " systemd --user unavailable -- starting services as background processes"
echo " (no reboot persistence; re-run the installer or 'ghost update' to restart them)"
pkill -f "$PRIV/scrubbing_proxy.py" 2>/dev/null || true
pkill -f "veil serve --foreground --skip-setup --port 11435" 2>/dev/null || true
OG_VEIL_PORT=11435 nohup "$PYTHON" -m veil serve --foreground --skip-setup --port 11435 > "$PRIV/veil.out.log" 2>&1 &
nohup "$PYTHON" "$PRIV/scrubbing_proxy.py" > "$PRIV/scrubber.out.log" 2>&1 &
fi

printf " waiting for the scrubbing bridge"
for _ in $(seq 1 15); do [ "$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 "$SCRUBBER/healthz" 2>/dev/null)" = 200 ] && break; printf "."; sleep 1; done; echo " up"
Expand Down
10 changes: 10 additions & 0 deletions systemd/ghost-scrubber.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Unit]
Description=ghost PII/secret scrubbing bridge (localhost :8788)

[Service]
ExecStart=__PYTHON__ __PRIV__/scrubbing_proxy.py
Restart=always
RestartSec=2

[Install]
WantedBy=default.target
11 changes: 11 additions & 0 deletions systemd/ghost-veil.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=ghost og-veil transport (OHTTP relay + TEE verify, localhost :11435)

[Service]
Environment=OG_VEIL_PORT=11435
ExecStart=__PYTHON__ -m veil serve --foreground --skip-setup --port 11435
Restart=always
RestartSec=2

[Install]
WantedBy=default.target
Loading