File size: 8,433 Bytes
fd1a3f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e725fbe
 
 
 
 
 
 
 
 
 
fd1a3f5
 
e725fbe
 
 
 
fd1a3f5
 
 
 
 
 
 
 
 
 
fe9a48a
 
fd1a3f5
 
 
fe9a48a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd1a3f5
94e4c89
 
 
 
 
 
 
 
6093073
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b69294
 
 
 
 
 
 
94e4c89
 
 
 
 
 
 
 
 
a8fb550
 
 
6093073
 
 
 
a8fb550
 
 
fd1a3f5
6093073
 
 
 
 
 
 
 
 
 
 
fd1a3f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b69294
 
 
 
fd1a3f5
 
 
 
 
 
2b69294
fd1a3f5
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
#!/bin/bash
set -e

echo "================================================"
echo "  BASECAMP β€” AI Agent + Local Inference"
echo "  Portable. Auto-discovering. Stack-agnostic."
echo "  (Isolated: config lives only in basecamp state)"
echo "================================================"
echo ""

# Start Ollama via supervisor in background
supervisord -c /etc/supervisor/conf.d/supervisord.conf &

# Wait for Ollama to be ready
echo "Starting local Ollama..."
for i in $(seq 1 60); do
    if curl -s http://127.0.0.1:11434/api/tags > /dev/null 2>&1; then
        echo "Local Ollama is up."
        break
    fi
    sleep 1
done

# First run: show connect screen. Everything written lives inside basecamp's
# own state (the mounted volume) β€” never touches a host hermes install.
CONFIG_FILE="/root/.hermes/basecamp_config.json"
ENV_FILE="/opt/basecamp/basecamp.env"

# FIX 2026-08-10: ALWAYS re-scan, even when a config exists. The old logic
# skipped discovery when config was found ("Config found. Regenerating
# tavern toolkit..."), so newly-added stack services stayed invisible until
# the user manually ran `tavern rediscover`. The box must discover fresh
# on every boot β€” that's the whole point of auto-discovery.
echo ""
echo "=== Scanning for AI services ==="
echo ""
if python3 /opt/basecamp/discover.py serve; then
    :
else
    echo ""
    echo "Discovery failed. Falling back to basecamp's own Ollama."
    python3 /opt/basecamp/discover.py env || true
    # Install the secret-free static tavern so stack commands still work
    cp /opt/basecamp/tavern.sh /usr/local/bin/tavern 2>/dev/null || true
    chmod +x /usr/local/bin/tavern 2>/dev/null || true
fi

# Apply Hermes runtime config β€” process-scoped env vars, no config.yaml/.env writes
if [ -f "$ENV_FILE" ]; then
    # shellcheck disable=SC1090
    . "$ENV_FILE"
fi

# Best-effort: if Hermes has no model configured yet, set it via Hermes' own
# Best-effort: if Hermes has no model configured yet, set it via Hermes' own
# config CLI (schema-safe). This only ever writes inside basecamp's own HERMES_HOME.
if [ -n "${HERMES_MODEL:-}" ] && ! hermes config get model > /dev/null 2>&1; then
    hermes config set model "$HERMES_MODEL" > /dev/null 2>&1 || true
fi
# CRITICAL (v0.20+): the stock config.yaml ships a default model+base_url
# (openrouter), so the guard above never fires and env-only wiring
# (OPENAI_BASE_URL / OPENAI_API_KEY) is IGNORED for non-authoritative hosts β€”
# hermes's #28660 security gate blocks env keys for LAN/custom endpoints and
# sends the "no-key-required" sentinel instead (auth 401). The sanctioned path
# is a NAMED custom provider with an inline api_key. Configure it in
# basecamp's OWN HERMES_HOME (never a host install).
if [ -n "${OPENAI_BASE_URL:-}" ]; then
    hermes config set providers.basecamp.api "$OPENAI_BASE_URL" > /dev/null 2>&1 || true
    hermes config set providers.basecamp.default_model "$HERMES_MODEL" > /dev/null 2>&1 || true
    if [ -n "${OPENAI_API_KEY:-}" ]; then
        hermes config set providers.basecamp.api_key "$OPENAI_API_KEY" > /dev/null 2>&1 || true
    fi
    hermes config set providers.basecamp.context_length 131072 > /dev/null 2>&1 || true
    hermes config set model.provider basecamp > /dev/null 2>&1 || true
    hermes config set model.default "$HERMES_MODEL" > /dev/null 2>&1 || true
    # Drop the stock openrouter base_url so model.base_url doesn't lie about
    # where traffic actually goes (the named provider's api is authoritative).
    hermes config set model.base_url "" > /dev/null 2>&1 || true
    # Local engines (ollama/tabbyapi/vllm) generally don't support hermes's
    # reasoning-effort params β€” sending them yields "does not support thinking"
    # HTTP 400. Turn reasoning off for the basecamp provider.
    hermes config set agent.reasoning_effort off > /dev/null 2>&1 || true
    # Clear stale context probes so the live endpoint is re-queried.
    rm -f "${HERMES_HOME:-/root/.hermes}/context_length_cache.yaml" 2>/dev/null || true
fi

# Basecamp's own skin β€” install into basecamp's HERMES_HOME and activate it.
# (Writes only inside the container's own state, never a host install.)
if [ -d /opt/basecamp/skins ] && [ -f /opt/basecamp/skins/indigo.yaml ]; then
    mkdir -p "${HERMES_HOME:-/root/.hermes}/skins"
    cp /opt/basecamp/skins/indigo.yaml "${HERMES_HOME:-/root/.hermes}/skins/indigo.yaml"
    hermes config set display.skin indigo > /dev/null 2>&1 || true
fi

# ── The tavern MCP server β€” exposes the connectivity toolkit as REAL
# Hermes tools (mcp_tavern_status, mcp_tavern_self_check, mcp_tavern_wire,
# mcp_tavern_rediscover, mcp_tavern_models, mcp_tavern_chat). The agent
# sees them in its tool list and CALLS them β€” it can't invent commands
# that don't exist. Writes only inside basecamp's own HERMES_HOME.
# Injected DIRECTLY into config.yaml with yq (idempotent) β€” the
# `hermes config set`/`hermes mcp add` paths get clobbered by hermes's
# first-run config regeneration, but a yq write to the file survives.
# Runs again just before exec hermes (below) so the injection happens
# AFTER hermes's first-run config write, on every boot.
inject_tavern_mcp() {
    if [ -f /opt/basecamp/tavern_mcp.py ]; then
        CFG="${HERMES_HOME:-/root/.hermes}/config.yaml"
        if [ -f "$CFG" ]; then
            yq -i '.mcp_servers.tavern.command = "python3" |
                   .mcp_servers.tavern.args = ["/opt/basecamp/tavern_mcp.py"] |
                   .mcp_servers.tavern.timeout = 120' "$CFG" 2>/dev/null || true
        fi
    fi
}
inject_tavern_mcp

# Basecamp's own identity β€” install the SOUL.md that makes this Hermes
# KNOW it must load the basecamp-stack skill for any stack question.
# (Writes only inside the container's own state, never a host install.)
if [ -f /opt/basecamp/SOUL.md ]; then
    cp /opt/basecamp/SOUL.md "${HERMES_HOME:-/root/.hermes}/SOUL.md"
fi

# Pull default model in background β€” AFTER discovery so the network scan
# isn't competing with a multi-GB download (missed-service boot race).
if [ -n "$BASECAMP_MODEL" ]; then
    if ! ollama list 2>/dev/null | grep -q "$BASECAMP_MODEL"; then
        echo "Pulling $BASECAMP_MODEL in background..."
        ollama pull "$BASECAMP_MODEL" > /dev/null 2>&1 &
    fi
fi

# Stack wiring audit β€” show every boot what's connected and what needs a fix.
# (Read-only: audits and prints fixes; never edits other containers' configs.)
if [ -x /opt/basecamp/discover.py ]; then
    echo ""
    echo "════════════════════════════════════════════"
    echo "  βœ“ Connected & wired β€” auditing your stack"
    echo "════════════════════════════════════════════"
    python3 /opt/basecamp/discover.py wire 2>/dev/null || true
fi

echo ""
echo "════════════════════════════════════════════"
echo "  πŸš€ Starting Basecamp Hermes..."
echo "  (this is the agent you'll talk to β€” loading"
echo "   its tools, skills, and memory. ~10-30s)"
echo "════════════════════════════════════════════"
echo ""

# Re-inject the tavern MCP server β€” config.yaml definitely exists now
# (hermes's first-run write happened above), so the connectivity toolkit
# is registered on EVERY boot, first boot included.
inject_tavern_mcp

# Handle arguments
if [ $# -gt 0 ]; then
    case "$1" in
        tavern)
            shift
            exec tavern "$@"
            ;;
        ollama)
            shift
            exec ollama "$@"
            ;;
        bash|shell)
            shift
            exec bash "$@"
            ;;
        rediscover)
            rm -f "$CONFIG_FILE"
            exec python3 /opt/basecamp/discover.py serve
            ;;
        *)
            # Force-load the stack skill so hermes always has the discovered
            # services + fix recipes in context (description matching alone
            # isn't reliable with 67 skills competing).
            exec hermes -s basecamp-stack "$@"
            ;;
    esac
else
    echo "Type 'tavern help' for stack commands."
    echo "Type 'tavern rediscover' to re-scan for services."
    echo ""
    exec hermes -s basecamp-stack
fi