Instructions to use jpanasuk/basecamp with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- HERMES
How to use jpanasuk/basecamp with HERMES:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
v1.0.3: indigo identity (skin+branding+basecamp/model), boot race fix, external-ollama auto-select, alias verification
Browse files- README.md +25 -2
- SOUL.md +14 -0
- basecamp.sh +1 -1
- discover.py +47 -8
- entrypoint.sh +17 -8
- index.html +548 -0
- skins/indigo.yaml +43 -0
README.md
CHANGED
|
@@ -17,6 +17,20 @@ library_name: docker
|
|
| 17 |
|
| 18 |
# ποΈ Basecamp β The First Portable AI Agent Container with Network Auto-Discovery
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
**v1.0.2** β the stack-gauntlet release (tested against a live 6-service AI
|
| 21 |
stack):
|
| 22 |
- **TabbyAPI + SillyTavern now discovered** β auth-gated services (401/403)
|
|
@@ -80,7 +94,7 @@ install on the host that runs it:
|
|
| 80 |
|
| 81 |
```bash
|
| 82 |
# Option A: pull the prebuilt image (Docker Hub)
|
| 83 |
-
docker pull jpanasuk/basecamp:1.0.
|
| 84 |
|
| 85 |
# Option B: build from source
|
| 86 |
docker build -t local/basecamp:latest .
|
|
@@ -93,7 +107,7 @@ docker run -it --rm \
|
|
| 93 |
-p 11436:11434 \
|
| 94 |
-v basecamp-ollama:/root/.ollama \
|
| 95 |
-v basecamp-hermes:/root/.hermes \
|
| 96 |
-
jpanasuk/basecamp:1.0.
|
| 97 |
```
|
| 98 |
|
| 99 |
Or use the convenience launcher (auto-detects network + GPU, TTY-safe):
|
|
@@ -139,6 +153,15 @@ basecamp/
|
|
| 139 |
βββ supervisord.conf # Ollama only (hermes is exec'd, not supervised)
|
| 140 |
```
|
| 141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
## Development notes
|
| 143 |
|
| 144 |
- Test discovery locally without touching anything:
|
|
|
|
| 17 |
|
| 18 |
# ποΈ Basecamp β The First Portable AI Agent Container with Network Auto-Discovery
|
| 19 |
|
| 20 |
+
**v1.0.3** β the identity + reliability release:
|
| 21 |
+
- **Indigo identity** β Basecamp runs its own indigo skin ("Welcome to Basecamp
|
| 22 |
+
Hermes!") so you can tell it apart from a host install at a glance; model
|
| 23 |
+
readout shows `basecamp/<model>` (created via `ollama cp` alias on the
|
| 24 |
+
discovered engine).
|
| 25 |
+
- **Boot race fixed** β the bundled-model pull now starts AFTER discovery,
|
| 26 |
+
so the network scan never competes with a multi-GB download. Cold boots
|
| 27 |
+
consistently discover the whole stack (7/7 on the reference stack).
|
| 28 |
+
- **Smarter auto-select** β an external Ollama (your stack) is preferred
|
| 29 |
+
over the bundled fallback; alias creation is verified before use, and
|
| 30 |
+
stale `basecamp/` aliases are excluded from model lists.
|
| 31 |
+
- **Separate commands** β host `hermes` = your install; `basecamp` =
|
| 32 |
+
the container edition. No more ambiguity about which agent you're in.
|
| 33 |
+
|
| 34 |
**v1.0.2** β the stack-gauntlet release (tested against a live 6-service AI
|
| 35 |
stack):
|
| 36 |
- **TabbyAPI + SillyTavern now discovered** β auth-gated services (401/403)
|
|
|
|
| 94 |
|
| 95 |
```bash
|
| 96 |
# Option A: pull the prebuilt image (Docker Hub)
|
| 97 |
+
docker pull jpanasuk/basecamp:1.0.3
|
| 98 |
|
| 99 |
# Option B: build from source
|
| 100 |
docker build -t local/basecamp:latest .
|
|
|
|
| 107 |
-p 11436:11434 \
|
| 108 |
-v basecamp-ollama:/root/.ollama \
|
| 109 |
-v basecamp-hermes:/root/.hermes \
|
| 110 |
+
jpanasuk/basecamp:1.0.3
|
| 111 |
```
|
| 112 |
|
| 113 |
Or use the convenience launcher (auto-detects network + GPU, TTY-safe):
|
|
|
|
| 153 |
βββ supervisord.conf # Ollama only (hermes is exec'd, not supervised)
|
| 154 |
```
|
| 155 |
|
| 156 |
+
## Credits
|
| 157 |
+
|
| 158 |
+
Basecamp is a **packaging of [Hermes Agent](https://hermes-agent.nousresearch.com)** by
|
| 159 |
+
[Nous Research](https://nousresearch.com) β all agent intelligence, tools,
|
| 160 |
+
skills, and the gateway are theirs. Basecamp's only contribution is the box:
|
| 161 |
+
the Docker packaging, the network auto-discovery, the safe env-scoped
|
| 162 |
+
wiring, and the `tavern` toolkit. Full credit and gratitude to the Hermes
|
| 163 |
+
team for building the agent that makes this project worth shipping.
|
| 164 |
+
|
| 165 |
## Development notes
|
| 166 |
|
| 167 |
- Test discovery locally without touching anything:
|
SOUL.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
You are Hermes Agent running inside Basecamp β a portable, self-contained
|
| 2 |
+
agent container that auto-discovers local AI stacks. You are the twin of the
|
| 3 |
+
host Hermes install, but you live in your own box: your config, keys, and
|
| 4 |
+
state stay inside this container and never touch a host install.
|
| 5 |
+
|
| 6 |
+
You identify as Basecamp Hermes. If asked who you are or where you run, say
|
| 7 |
+
you're the Basecamp edition (containerized) β distinct from any host-level
|
| 8 |
+
Hermes install. You are helpful, knowledgeable, and direct. You assist with
|
| 9 |
+
the full range of Hermes Agent tasks: answering questions, writing and
|
| 10 |
+
editing code, analyzing information, creative work, and executing actions
|
| 11 |
+
via your tools. You communicate clearly, admit uncertainty when appropriate,
|
| 12 |
+
and prioritize being genuinely useful over being verbose. You may be asked
|
| 13 |
+
to use the `tavern` CLI toolkit to inspect or manage the local AI stack you
|
| 14 |
+
discovered (inference engines, search, MCP tools, UIs).
|
basecamp.sh
CHANGED
|
@@ -18,7 +18,7 @@
|
|
| 18 |
# basecamp bash # drop into shell inside container
|
| 19 |
# basecamp rediscover # re-scan for services
|
| 20 |
|
| 21 |
-
IMAGE="${BASECAMP_IMAGE:-
|
| 22 |
CONTAINER="${BASECAMP_CONTAINER:-basecamp}"
|
| 23 |
|
| 24 |
# Interactive flags: only use -it when stdin is a TTY (cron/CI-safe)
|
|
|
|
| 18 |
# basecamp bash # drop into shell inside container
|
| 19 |
# basecamp rediscover # re-scan for services
|
| 20 |
|
| 21 |
+
IMAGE="${BASECAMP_IMAGE:-jpanasuk/basecamp:latest}"
|
| 22 |
CONTAINER="${BASECAMP_CONTAINER:-basecamp}"
|
| 23 |
|
| 24 |
# Interactive flags: only use -it when stdin is a TTY (cron/CI-safe)
|
discover.py
CHANGED
|
@@ -436,14 +436,26 @@ def generate_config(services, selected=None, auth_keys=None):
|
|
| 436 |
"tabbyapi", "vllm", "litellm", "localai", "llamacpp",
|
| 437 |
"text-generation-webui")]
|
| 438 |
if ollamas:
|
| 439 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
config["inference"] = {
|
| 441 |
"type": primary["type"], "url": primary["url"],
|
| 442 |
"label": primary["label"]}
|
| 443 |
config["openai_models"] = []
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
elif openai_engines:
|
| 448 |
primary = openai_engines[0]
|
| 449 |
config["inference"] = {
|
|
@@ -847,16 +859,43 @@ def write_basecamp_env(config):
|
|
| 847 |
api_key = config.get("api_keys", {}).get(primary.get("url", ""), "") or "ollama"
|
| 848 |
|
| 849 |
models = config.get("openai_models") or config.get("ollama_models") or ["llama3.1:8b"]
|
| 850 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 851 |
|
| 852 |
env_path = Path(os.environ.get("BASECAMP_ENV_FILE", "/opt/basecamp/basecamp.env"))
|
| 853 |
env_path.parent.mkdir(parents=True, exist_ok=True)
|
| 854 |
env_path.write_text(
|
| 855 |
f'export OPENAI_BASE_URL="{base_url}"\n'
|
| 856 |
f'export OPENAI_API_KEY="{api_key}"\n'
|
| 857 |
-
#
|
| 858 |
-
#
|
| 859 |
-
#
|
| 860 |
f'export HERMES_MODEL="{default_model}"\n'
|
| 861 |
)
|
| 862 |
try:
|
|
|
|
| 436 |
"tabbyapi", "vllm", "litellm", "localai", "llamacpp",
|
| 437 |
"text-generation-webui")]
|
| 438 |
if ollamas:
|
| 439 |
+
# Prefer an EXTERNAL ollama (the user's stack) over basecamp's own
|
| 440 |
+
# bundled one (127.0.0.1 / localhost) β the bundled ollama is the
|
| 441 |
+
# fallback for when nothing else exists. Discovery order is racy
|
| 442 |
+
# (parallel probes), so pick deterministically.
|
| 443 |
+
external = [s for s in ollamas
|
| 444 |
+
if not str(s["url"]).replace("http://", "").replace("https://", "").startswith(("127.", "localhost", "::1"))]
|
| 445 |
+
ordered = external + [s for s in ollamas if s not in external]
|
| 446 |
+
primary = ordered[0]
|
| 447 |
config["inference"] = {
|
| 448 |
"type": primary["type"], "url": primary["url"],
|
| 449 |
"label": primary["label"]}
|
| 450 |
config["openai_models"] = []
|
| 451 |
+
# Exclude pre-existing basecamp/ aliases from the model list β
|
| 452 |
+
# they're created by write_basecamp_env, not user models.
|
| 453 |
+
config["ollama_models"] = [
|
| 454 |
+
m for m in primary.get("details", {}).get("models", [])
|
| 455 |
+
if not str(m).startswith("basecamp/")
|
| 456 |
+
]
|
| 457 |
+
if len(ordered) > 1:
|
| 458 |
+
config["secondary"] = {"type": "ollama", "url": ordered[1]["url"], "label": ordered[1]["label"]}
|
| 459 |
elif openai_engines:
|
| 460 |
primary = openai_engines[0]
|
| 461 |
config["inference"] = {
|
|
|
|
| 859 |
api_key = config.get("api_keys", {}).get(primary.get("url", ""), "") or "ollama"
|
| 860 |
|
| 861 |
models = config.get("openai_models") or config.get("ollama_models") or ["llama3.1:8b"]
|
| 862 |
+
# Never pick a pre-namespaced basecamp/ alias as the base model β it would
|
| 863 |
+
# get double-prefixed below (basecamp/basecamp/...). Prefer a bare id.
|
| 864 |
+
bare_models = [m for m in models if not str(m).startswith("basecamp/")]
|
| 865 |
+
default_model = (bare_models[0] if bare_models else models[0]) if models else "llama3.1:8b"
|
| 866 |
+
|
| 867 |
+
# Namespace the model as basecamp/<model> so the hermes status/model
|
| 868 |
+
# readout clearly identifies this as the Basecamp instance. Only for
|
| 869 |
+
# ollama engines (they support aliasing via /api/copy); openai-compatible
|
| 870 |
+
# engines get the bare id. Best-effort: if the alias already exists or the
|
| 871 |
+
# server rejects it, fall back to the bare model id.
|
| 872 |
+
if primary.get("type") == "ollama":
|
| 873 |
+
ns_model = f"basecamp/{default_model}"
|
| 874 |
+
root_url = primary["url"].rstrip("/")
|
| 875 |
+
existing = http_get(f"{root_url}/api/tags", timeout=2) or ""
|
| 876 |
+
if f'"{ns_model}"' in existing:
|
| 877 |
+
default_model = ns_model
|
| 878 |
+
else:
|
| 879 |
+
http_post(
|
| 880 |
+
f"{root_url}/api/copy",
|
| 881 |
+
data={"source": default_model, "destination": ns_model},
|
| 882 |
+
timeout=5,
|
| 883 |
+
)
|
| 884 |
+
# VERIFY the alias actually exists before using it β /api/copy can
|
| 885 |
+
# return 404-with-body (source missing) which http_post surfaces as
|
| 886 |
+
# a non-None string, so a truthiness check would wrongly pass.
|
| 887 |
+
after = http_get(f"{root_url}/api/tags", timeout=2) or ""
|
| 888 |
+
if f'"{ns_model}"' in after:
|
| 889 |
+
default_model = ns_model
|
| 890 |
|
| 891 |
env_path = Path(os.environ.get("BASECAMP_ENV_FILE", "/opt/basecamp/basecamp.env"))
|
| 892 |
env_path.parent.mkdir(parents=True, exist_ok=True)
|
| 893 |
env_path.write_text(
|
| 894 |
f'export OPENAI_BASE_URL="{base_url}"\n'
|
| 895 |
f'export OPENAI_API_KEY="{api_key}"\n'
|
| 896 |
+
# namespaced model id (basecamp/<model>) when the engine supports it β
|
| 897 |
+
# the readout then shows Basecamp's identity; falls back to the bare
|
| 898 |
+
# id for engines that can't alias (tabbyapi/vllm/etc).
|
| 899 |
f'export HERMES_MODEL="{default_model}"\n'
|
| 900 |
)
|
| 901 |
try:
|
entrypoint.sh
CHANGED
|
@@ -21,14 +21,6 @@ for i in $(seq 1 60); do
|
|
| 21 |
sleep 1
|
| 22 |
done
|
| 23 |
|
| 24 |
-
# Pull default model in background
|
| 25 |
-
if [ -n "$BASECAMP_MODEL" ]; then
|
| 26 |
-
if ! ollama list 2>/dev/null | grep -q "$BASECAMP_MODEL"; then
|
| 27 |
-
echo "Pulling $BASECAMP_MODEL in background..."
|
| 28 |
-
ollama pull "$BASECAMP_MODEL" > /dev/null 2>&1 &
|
| 29 |
-
fi
|
| 30 |
-
fi
|
| 31 |
-
|
| 32 |
# First run: show connect screen. Everything written lives inside basecamp's
|
| 33 |
# own state (the mounted volume) β never touches a host hermes install.
|
| 34 |
CONFIG_FILE="/root/.hermes/basecamp_config.json"
|
|
@@ -93,6 +85,23 @@ if [ -n "${OPENAI_BASE_URL:-}" ]; then
|
|
| 93 |
rm -f "${HERMES_HOME:-/root/.hermes}/context_length_cache.yaml" 2>/dev/null || true
|
| 94 |
fi
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
echo ""
|
| 97 |
|
| 98 |
# Handle arguments
|
|
|
|
| 21 |
sleep 1
|
| 22 |
done
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
# First run: show connect screen. Everything written lives inside basecamp's
|
| 25 |
# own state (the mounted volume) β never touches a host hermes install.
|
| 26 |
CONFIG_FILE="/root/.hermes/basecamp_config.json"
|
|
|
|
| 85 |
rm -f "${HERMES_HOME:-/root/.hermes}/context_length_cache.yaml" 2>/dev/null || true
|
| 86 |
fi
|
| 87 |
|
| 88 |
+
# Basecamp's own skin β install into basecamp's HERMES_HOME and activate it.
|
| 89 |
+
# (Writes only inside the container's own state, never a host install.)
|
| 90 |
+
if [ -d /opt/basecamp/skins ] && [ -f /opt/basecamp/skins/indigo.yaml ]; then
|
| 91 |
+
mkdir -p "${HERMES_HOME:-/root/.hermes}/skins"
|
| 92 |
+
cp /opt/basecamp/skins/indigo.yaml "${HERMES_HOME:-/root/.hermes}/skins/indigo.yaml"
|
| 93 |
+
hermes config set display.skin indigo > /dev/null 2>&1 || true
|
| 94 |
+
fi
|
| 95 |
+
|
| 96 |
+
# Pull default model in background β AFTER discovery so the network scan
|
| 97 |
+
# isn't competing with a multi-GB download (missed-service boot race).
|
| 98 |
+
if [ -n "$BASECAMP_MODEL" ]; then
|
| 99 |
+
if ! ollama list 2>/dev/null | grep -q "$BASECAMP_MODEL"; then
|
| 100 |
+
echo "Pulling $BASECAMP_MODEL in background..."
|
| 101 |
+
ollama pull "$BASECAMP_MODEL" > /dev/null 2>&1 &
|
| 102 |
+
fi
|
| 103 |
+
fi
|
| 104 |
+
|
| 105 |
echo ""
|
| 106 |
|
| 107 |
# Handle arguments
|
index.html
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Basecamp β Portable AI Agent with Auto-Discovery</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root {
|
| 9 |
+
--navy: #0B1F3A;
|
| 10 |
+
--blue: #1F5FA8;
|
| 11 |
+
--accent: #E8730C;
|
| 12 |
+
--light: #EEF3F8;
|
| 13 |
+
--gray: #3E4C59;
|
| 14 |
+
--green: #1E8E5A;
|
| 15 |
+
--cream: #FFF7EC;
|
| 16 |
+
--tag: #FFD9A8;
|
| 17 |
+
--purple: #6C3FA8;
|
| 18 |
+
--teal: #0E8E8E;
|
| 19 |
+
--red: #C0392B;
|
| 20 |
+
--dark: #0a0e1a;
|
| 21 |
+
}
|
| 22 |
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 23 |
+
body {
|
| 24 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
| 25 |
+
background: #f5f7fa;
|
| 26 |
+
color: var(--gray);
|
| 27 |
+
line-height: 1.6;
|
| 28 |
+
-webkit-font-smoothing: antialiased;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
/* ---- BANNER ---- */
|
| 32 |
+
.banner {
|
| 33 |
+
background: linear-gradient(135deg, var(--dark) 0%, var(--navy) 50%, #152845 100%);
|
| 34 |
+
color: white;
|
| 35 |
+
text-align: center;
|
| 36 |
+
padding: 50px 24px 40px;
|
| 37 |
+
}
|
| 38 |
+
.banner .logo {
|
| 39 |
+
font-size: 2.8rem;
|
| 40 |
+
font-weight: 900;
|
| 41 |
+
letter-spacing: 2px;
|
| 42 |
+
margin-bottom: 8px;
|
| 43 |
+
background: linear-gradient(90deg, var(--accent), var(--tag));
|
| 44 |
+
-webkit-background-clip: text;
|
| 45 |
+
-webkit-text-fill-color: transparent;
|
| 46 |
+
background-clip: text;
|
| 47 |
+
}
|
| 48 |
+
.banner .tagline {
|
| 49 |
+
font-size: 1.2rem;
|
| 50 |
+
color: var(--tag);
|
| 51 |
+
max-width: 700px;
|
| 52 |
+
margin: 0 auto 16px;
|
| 53 |
+
line-height: 1.5;
|
| 54 |
+
}
|
| 55 |
+
.banner .contact {
|
| 56 |
+
font-size: 0.85rem;
|
| 57 |
+
color: var(--tag);
|
| 58 |
+
opacity: 0.7;
|
| 59 |
+
}
|
| 60 |
+
.banner .links {
|
| 61 |
+
margin-top: 18px;
|
| 62 |
+
display: flex;
|
| 63 |
+
gap: 12px;
|
| 64 |
+
justify-content: center;
|
| 65 |
+
flex-wrap: wrap;
|
| 66 |
+
}
|
| 67 |
+
.banner .links a {
|
| 68 |
+
display: inline-block;
|
| 69 |
+
padding: 10px 20px;
|
| 70 |
+
background: var(--accent);
|
| 71 |
+
color: white;
|
| 72 |
+
text-decoration: none;
|
| 73 |
+
border-radius: 6px;
|
| 74 |
+
font-weight: 700;
|
| 75 |
+
font-size: 0.9rem;
|
| 76 |
+
transition: transform 0.15s, box-shadow 0.15s;
|
| 77 |
+
}
|
| 78 |
+
.banner .links a:hover {
|
| 79 |
+
transform: translateY(-2px);
|
| 80 |
+
box-shadow: 0 4px 12px rgba(232,115,12,0.3);
|
| 81 |
+
}
|
| 82 |
+
.banner .links a.secondary {
|
| 83 |
+
background: transparent;
|
| 84 |
+
border: 1.5px solid var(--blue);
|
| 85 |
+
color: var(--tag);
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
/* ---- KICKER ---- */
|
| 89 |
+
.kicker-wrap { padding: 0 24px; }
|
| 90 |
+
.kicker {
|
| 91 |
+
background: var(--light);
|
| 92 |
+
border: 1.5px solid var(--blue);
|
| 93 |
+
text-align: center;
|
| 94 |
+
padding: 14px 20px;
|
| 95 |
+
margin: -18px auto 0;
|
| 96 |
+
max-width: 820px;
|
| 97 |
+
border-radius: 6px;
|
| 98 |
+
position: relative;
|
| 99 |
+
z-index: 1;
|
| 100 |
+
}
|
| 101 |
+
.kicker p {
|
| 102 |
+
font-size: 1.1rem;
|
| 103 |
+
font-weight: 700;
|
| 104 |
+
color: var(--navy);
|
| 105 |
+
}
|
| 106 |
+
.kicker code {
|
| 107 |
+
background: var(--navy);
|
| 108 |
+
color: var(--tag);
|
| 109 |
+
padding: 3px 8px;
|
| 110 |
+
border-radius: 4px;
|
| 111 |
+
font-size: 0.9rem;
|
| 112 |
+
font-family: 'Courier New', monospace;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
/* ---- MAIN ---- */
|
| 116 |
+
.container {
|
| 117 |
+
max-width: 820px;
|
| 118 |
+
margin: 0 auto;
|
| 119 |
+
padding: 28px 24px 60px;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.section { margin-bottom: 32px; }
|
| 123 |
+
.section h2 {
|
| 124 |
+
font-size: 1.3rem;
|
| 125 |
+
font-weight: 800;
|
| 126 |
+
color: var(--navy);
|
| 127 |
+
margin-bottom: 4px;
|
| 128 |
+
}
|
| 129 |
+
.section h2::before {
|
| 130 |
+
content: "\25B6";
|
| 131 |
+
color: var(--accent);
|
| 132 |
+
margin-right: 8px;
|
| 133 |
+
font-size: 0.85rem;
|
| 134 |
+
}
|
| 135 |
+
.section hr {
|
| 136 |
+
border: none;
|
| 137 |
+
border-top: 1.5px solid var(--blue);
|
| 138 |
+
margin-bottom: 12px;
|
| 139 |
+
}
|
| 140 |
+
.section p {
|
| 141 |
+
font-size: 0.95rem;
|
| 142 |
+
margin-bottom: 10px;
|
| 143 |
+
}
|
| 144 |
+
.section ul {
|
| 145 |
+
list-style: none;
|
| 146 |
+
padding: 0;
|
| 147 |
+
}
|
| 148 |
+
.section ul li {
|
| 149 |
+
font-size: 0.93rem;
|
| 150 |
+
padding: 5px 0 5px 22px;
|
| 151 |
+
position: relative;
|
| 152 |
+
}
|
| 153 |
+
.section ul li::before {
|
| 154 |
+
content: "\2022";
|
| 155 |
+
position: absolute;
|
| 156 |
+
left: 4px;
|
| 157 |
+
top: 0;
|
| 158 |
+
color: var(--blue);
|
| 159 |
+
font-weight: bold;
|
| 160 |
+
}
|
| 161 |
+
.section ul li b { color: var(--navy); }
|
| 162 |
+
|
| 163 |
+
/* ---- CODE BLOCK ---- */
|
| 164 |
+
.code-block {
|
| 165 |
+
background: var(--dark);
|
| 166 |
+
color: #c8d6e5;
|
| 167 |
+
padding: 16px 20px;
|
| 168 |
+
border-radius: 6px;
|
| 169 |
+
font-family: 'Courier New', monospace;
|
| 170 |
+
font-size: 0.88rem;
|
| 171 |
+
margin: 12px 0;
|
| 172 |
+
overflow-x: auto;
|
| 173 |
+
border-left: 3px solid var(--accent);
|
| 174 |
+
white-space: pre;
|
| 175 |
+
line-height: 1.5;
|
| 176 |
+
}
|
| 177 |
+
.code-block .comment { color: #5a7a9a; }
|
| 178 |
+
.code-block .cmd { color: var(--accent); font-weight: bold; }
|
| 179 |
+
.code-block .flag { color: var(--teal); }
|
| 180 |
+
.code-block .str { color: var(--green); }
|
| 181 |
+
.code-block .prompt { color: var(--tag); }
|
| 182 |
+
|
| 183 |
+
/* ---- CONNECT SCREEN MOCK ---- */
|
| 184 |
+
.connect-mock {
|
| 185 |
+
background: #0d1117;
|
| 186 |
+
color: #c9d1d9;
|
| 187 |
+
border-radius: 8px;
|
| 188 |
+
padding: 20px 24px;
|
| 189 |
+
font-family: 'Courier New', monospace;
|
| 190 |
+
font-size: 0.85rem;
|
| 191 |
+
margin: 14px 0;
|
| 192 |
+
border: 1px solid #1f2937;
|
| 193 |
+
line-height: 1.65;
|
| 194 |
+
}
|
| 195 |
+
.connect-mock .header { color: var(--accent); font-weight: bold; }
|
| 196 |
+
.connect-mock .svc { color: var(--blue); }
|
| 197 |
+
.connect-mock .svc-icon { color: var(--green); font-weight: bold; }
|
| 198 |
+
.connect-mock .label { color: #8b949e; }
|
| 199 |
+
.connect-mock .prompt { color: var(--tag); }
|
| 200 |
+
.connect-mock .selected { color: var(--accent); }
|
| 201 |
+
|
| 202 |
+
/* ---- DISCOVERY GRID ---- */
|
| 203 |
+
.disc-grid {
|
| 204 |
+
display: grid;
|
| 205 |
+
grid-template-columns: 1fr;
|
| 206 |
+
gap: 10px;
|
| 207 |
+
margin-top: 14px;
|
| 208 |
+
}
|
| 209 |
+
@media (min-width: 640px) {
|
| 210 |
+
.disc-grid { grid-template-columns: 1fr 1fr; }
|
| 211 |
+
}
|
| 212 |
+
.disc-card {
|
| 213 |
+
background: white;
|
| 214 |
+
border: 1px solid #d1d9e0;
|
| 215 |
+
border-radius: 6px;
|
| 216 |
+
padding: 14px 16px;
|
| 217 |
+
font-size: 0.88rem;
|
| 218 |
+
}
|
| 219 |
+
.disc-card .icon {
|
| 220 |
+
display: inline-block;
|
| 221 |
+
font-weight: bold;
|
| 222 |
+
font-size: 0.75rem;
|
| 223 |
+
padding: 2px 8px;
|
| 224 |
+
border-radius: 3px;
|
| 225 |
+
margin-right: 8px;
|
| 226 |
+
color: white;
|
| 227 |
+
}
|
| 228 |
+
.disc-card .icon.llm { background: var(--blue); }
|
| 229 |
+
.disc-card .icon.exl { background: var(--purple); }
|
| 230 |
+
.disc-card .icon.src { background: var(--teal); }
|
| 231 |
+
.disc-card .icon.mcp { background: var(--accent); }
|
| 232 |
+
.disc-card .icon.ui { background: var(--green); }
|
| 233 |
+
.disc-card .icon.cht { background: var(--red); }
|
| 234 |
+
.disc-card b { color: var(--navy); }
|
| 235 |
+
.disc-card .auth {
|
| 236 |
+
font-size: 0.78rem;
|
| 237 |
+
color: var(--accent);
|
| 238 |
+
margin-left: 4px;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
/* ---- ARCHITECTURE ---- */
|
| 242 |
+
.arch {
|
| 243 |
+
background: #0d1117;
|
| 244 |
+
color: #8b949e;
|
| 245 |
+
border-radius: 8px;
|
| 246 |
+
padding: 20px;
|
| 247 |
+
font-family: 'Courier New', monospace;
|
| 248 |
+
font-size: 0.82rem;
|
| 249 |
+
margin: 14px 0;
|
| 250 |
+
line-height: 1.5;
|
| 251 |
+
overflow-x: auto;
|
| 252 |
+
white-space: pre;
|
| 253 |
+
}
|
| 254 |
+
.arch .box { color: var(--blue); }
|
| 255 |
+
.arch .highlight { color: var(--accent); font-weight: bold; }
|
| 256 |
+
.arch .arrow { color: var(--green); }
|
| 257 |
+
|
| 258 |
+
/* ---- COMPARISON ---- */
|
| 259 |
+
.compare-table {
|
| 260 |
+
width: 100%;
|
| 261 |
+
border-collapse: collapse;
|
| 262 |
+
margin-top: 14px;
|
| 263 |
+
font-size: 0.9rem;
|
| 264 |
+
}
|
| 265 |
+
.compare-table th {
|
| 266 |
+
background: var(--navy);
|
| 267 |
+
color: white;
|
| 268 |
+
padding: 10px 14px;
|
| 269 |
+
text-align: left;
|
| 270 |
+
font-weight: 700;
|
| 271 |
+
}
|
| 272 |
+
.compare-table td {
|
| 273 |
+
padding: 10px 14px;
|
| 274 |
+
border-bottom: 1px solid #e0e4e8;
|
| 275 |
+
}
|
| 276 |
+
.compare-table tr:nth-child(even) td { background: var(--light); }
|
| 277 |
+
.compare-table .yes { color: var(--green); font-weight: bold; }
|
| 278 |
+
.compare-table .no { color: var(--red); }
|
| 279 |
+
|
| 280 |
+
/* ---- FOOTER ---- */
|
| 281 |
+
.footer {
|
| 282 |
+
text-align: center;
|
| 283 |
+
padding: 30px 24px;
|
| 284 |
+
background: var(--navy);
|
| 285 |
+
color: var(--tag);
|
| 286 |
+
font-size: 0.85rem;
|
| 287 |
+
}
|
| 288 |
+
.footer a { color: var(--tag); text-decoration: none; }
|
| 289 |
+
.footer a:hover { text-decoration: underline; }
|
| 290 |
+
.footer .links { margin-top: 10px; }
|
| 291 |
+
.footer .links a {
|
| 292 |
+
margin: 0 8px;
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
/* ---- BADGE ---- */
|
| 296 |
+
.badge {
|
| 297 |
+
display: inline-block;
|
| 298 |
+
padding: 3px 10px;
|
| 299 |
+
background: var(--green);
|
| 300 |
+
color: white;
|
| 301 |
+
border-radius: 12px;
|
| 302 |
+
font-size: 0.75rem;
|
| 303 |
+
font-weight: 700;
|
| 304 |
+
margin-left: 8px;
|
| 305 |
+
vertical-align: middle;
|
| 306 |
+
}
|
| 307 |
+
</style>
|
| 308 |
+
</head>
|
| 309 |
+
<body>
|
| 310 |
+
|
| 311 |
+
<div class="banner">
|
| 312 |
+
<div class="logo">BASECAMP</div>
|
| 313 |
+
<div class="tagline">The first portable AI agent container that auto-discovers your local AI stack β Hermes Agent + Ollama in one box that finds your whole stack and just works.</div>
|
| 314 |
+
<div class="contact">No hardcoded URLs. No config files. Just <code style="color:var(--tag);">docker run</code>.</div>
|
| 315 |
+
<div class="links">
|
| 316 |
+
<a href="https://hub.docker.com/r/jpanasuk/basecamp">Get the image</a>
|
| 317 |
+
<a href="https://github.com/jpanasuk-netizen" class="secondary">GitHub</a>
|
| 318 |
+
</div>
|
| 319 |
+
</div>
|
| 320 |
+
|
| 321 |
+
<div class="kicker-wrap">
|
| 322 |
+
<div class="kicker">
|
| 323 |
+
<p>One command to connect: <code>docker run -it --rm --network your-ai-network local/basecamp</code></p>
|
| 324 |
+
</div>
|
| 325 |
+
</div>
|
| 326 |
+
|
| 327 |
+
<div class="container">
|
| 328 |
+
|
| 329 |
+
<!-- PROBLEM -->
|
| 330 |
+
<div class="section">
|
| 331 |
+
<h2>The Problem</h2>
|
| 332 |
+
<hr>
|
| 333 |
+
<p>You've got local AI services running β Ollama, maybe TabbyAPI or vLLM, maybe SearXNG. They work. But to actually <b>use</b> them together, you're either:</p>
|
| 334 |
+
<ul>
|
| 335 |
+
<li>Opening 5 browser tabs and copy-pasting between them</li>
|
| 336 |
+
<li>Hardcoding API URLs and keys into config files</li>
|
| 337 |
+
<li>Writing glue code every time you add a service</li>
|
| 338 |
+
</ul>
|
| 339 |
+
<p style="margin-top:8px;color:var(--accent);font-weight:600;">Nothing ties your stack together. Until now.</p>
|
| 340 |
+
</div>
|
| 341 |
+
|
| 342 |
+
<!-- SOLUTION -->
|
| 343 |
+
<div class="section">
|
| 344 |
+
<h2>What Basecamp Does <span class="badge">Auto-Discovery</span></h2>
|
| 345 |
+
<hr>
|
| 346 |
+
<p>Basecamp is a Docker image containing <b>Hermes Agent</b> + <b>Ollama</b>. When you run it on a Docker network, it:</p>
|
| 347 |
+
<ul>
|
| 348 |
+
<li><b>Scans</b> for AI services using DNS probes, port scanning, and Docker network inspection</li>
|
| 349 |
+
<li><b>Identifies</b> service types by fingerprinting HTTP responses (Ollama, TabbyAPI, vLLM, SearXNG, MCPO, and more)</li>
|
| 350 |
+
<li><b>Shows a connect screen</b> where you pick which services to use</li>
|
| 351 |
+
<li><b>Prompts for API keys</b> when a service needs auth β no hardcoded secrets</li>
|
| 352 |
+
<li><b>Generates a <code>tavern</code> CLI</b> dynamically from what it found</li>
|
| 353 |
+
<li><b>Drops you into Hermes Agent</b> connected to your entire stack</li>
|
| 354 |
+
</ul>
|
| 355 |
+
</div>
|
| 356 |
+
|
| 357 |
+
<!-- CONNECT SCREEN -->
|
| 358 |
+
<div class="section">
|
| 359 |
+
<h2>The Connect Screen</h2>
|
| 360 |
+
<hr>
|
| 361 |
+
<p>First run, basecamp scans your network and shows you what it found:</p>
|
| 362 |
+
<div class="connect-mock">
|
| 363 |
+
<span class="header">============================================================</span>
|
| 364 |
+
<span class="header"> BASECAMP β AI Agent + Local Inference</span>
|
| 365 |
+
<span class="header"> Connect Screen</span>
|
| 366 |
+
<span class="header">============================================================</span>
|
| 367 |
+
|
| 368 |
+
Discovered 5 service(s):
|
| 369 |
+
|
| 370 |
+
[ 0] <span class="svc-icon">LLM</span> <span class="svc">Ollama (GGUF inference)</span> [1 model(s)]
|
| 371 |
+
<span class="label">-> http://ollama:11434</span>
|
| 372 |
+
[ 1] <span class="svc-icon">EXL</span> <span class="svc">TabbyAPI / OpenAI-compatible (EXL3/EXL2)</span> [1 model(s)] [needs auth]
|
| 373 |
+
<span class="label">-> http://tabbyapi:5000</span>
|
| 374 |
+
[ 2] <span class="svc-icon">SRC</span> <span class="svc">SearXNG (private search)</span>
|
| 375 |
+
<span class="label">-> http://searxng:8080</span>
|
| 376 |
+
[ 3] <span class="svc-icon">MCP</span> <span class="svc">MCPO (MCP OpenAPI proxy)</span>
|
| 377 |
+
<span class="label">-> http://mcpo:8000</span>
|
| 378 |
+
[ 4] <span class="svc-icon">UI </span> <span class="svc">Open WebUI (LLM workspace)</span>
|
| 379 |
+
<span class="label">-> http://open-webui:8080</span>
|
| 380 |
+
|
| 381 |
+
<span class="prompt">PRIMARY inference engine:
|
| 382 |
+
1. Ollama (GGUF inference) (http://ollama:11434)
|
| 383 |
+
2. TabbyAPI / OpenAI-compatible (http://tabbyapi:5000)
|
| 384 |
+
0. Use basecamp's own Ollama
|
| 385 |
+
Choice [1]:</span> <span class="selected">2</span>
|
| 386 |
+
|
| 387 |
+
<span class="prompt">TabbyAPI requires authentication (bearer).
|
| 388 |
+
Enter API key (or press Enter to skip):</span> <span class="selected">your-key-here</span>
|
| 389 |
+
|
| 390 |
+
<span class="prompt">Save and continue? [Y/n]:</span> <span class="selected">Y</span>
|
| 391 |
+
</div>
|
| 392 |
+
<p style="font-size:0.85rem;color:#8b949e;">Subsequent runs skip this β config is saved to a persistent volume. Run <code>tavern rediscover</code> to re-scan.</p>
|
| 393 |
+
</div>
|
| 394 |
+
|
| 395 |
+
<!-- QUICK START -->
|
| 396 |
+
<div class="section">
|
| 397 |
+
<h2>Quick Start</h2>
|
| 398 |
+
<hr>
|
| 399 |
+
<div class="code-block"><span class="comment"># Pull the image (Docker Hub)</span>
|
| 400 |
+
<span class="cmd">docker pull</span> <span class="str">jpanasuk/basecamp:1.0.2</span>
|
| 401 |
+
|
| 402 |
+
<span class="comment"># Run against your AI network</span>
|
| 403 |
+
<span class="cmd">docker run</span> <span class="flag">-it --rm</span> \
|
| 404 |
+
<span class="flag">--network</span> <span class="str">your-ai-network</span> \
|
| 405 |
+
<span class="flag">--gpus all</span> \
|
| 406 |
+
<span class="flag">-v</span> <span class="str">basecamp-ollama:/root/.ollama</span> \
|
| 407 |
+
<span class="flag">-v</span> <span class="str">basecamp-hermes:/root/.hermes</span> \
|
| 408 |
+
<span class="str">jpanasuk/basecamp:1.0.2</span>
|
| 409 |
+
|
| 410 |
+
<span class="comment"># First run: connect screen appears
|
| 411 |
+
# Pick services, enter API keys if needed
|
| 412 |
+
# Then you're in Hermes Agent with tavern CLI</span>
|
| 413 |
+
|
| 414 |
+
<span class="prompt">tavern status</span> <span class="comment"># show connected services</span>
|
| 415 |
+
<span class="prompt">tavern chat "hello"</span> <span class="comment"># chat with EXL3 model (streaming)</span>
|
| 416 |
+
<span class="prompt">tavern search "AI news"</span> <span class="comment"># search via SearXNG</span>
|
| 417 |
+
<span class="prompt">tavern mcp</span> <span class="comment"># list MCP tools</span>
|
| 418 |
+
<span class="prompt">tavern models</span> <span class="comment"># list available models</span></div>
|
| 419 |
+
</div>
|
| 420 |
+
|
| 421 |
+
<!-- DISCOVERY TABLE -->
|
| 422 |
+
<div class="section">
|
| 423 |
+
<h2>What It Discovers</h2>
|
| 424 |
+
<hr>
|
| 425 |
+
<div class="disc-grid">
|
| 426 |
+
<div class="disc-card"><span class="icon llm">LLM</span><b>Ollama</b><br>GGUF inference<br><span style="font-size:0.8rem;color:#8b949e;">Auto-discovered, no auth</span></div>
|
| 427 |
+
<div class="disc-card"><span class="icon exl">EXL</span><b>TabbyAPI</b><br>EXL3/EXL2 inference<span class="auth">needs API key</span><br><span style="font-size:0.8rem;color:#8b949e;">Prompted during connect</span></div>
|
| 428 |
+
<div class="disc-card"><span class="icon llm">LLM</span><b>vLLM</b><br>High-throughput inference<br><span style="font-size:0.8rem;color:#8b949e;">Auto-discovered, no auth</span></div>
|
| 429 |
+
<div class="disc-card"><span class="icon llm">PXY</span><b>LiteLLM</b><br>Multi-provider proxy<span class="auth">needs API key</span><br><span style="font-size:0.8rem;color:#8b949e;">Prompted during connect</span></div>
|
| 430 |
+
<div class="disc-card"><span class="icon llm">LLM</span><b>LocalAI</b><br>OpenAI replacement<br><span style="font-size:0.8rem;color:#8b949e;">Auto-discovered, no auth</span></div>
|
| 431 |
+
<div class="disc-card"><span class="icon llm">LLM</span><b>llama.cpp</b><br>Lightweight server<br><span style="font-size:0.8rem;color:#8b949e;">Auto-discovered, no auth</span></div>
|
| 432 |
+
<div class="disc-card"><span class="icon src">SRC</span><b>SearXNG</b><br>Private metasearch<br><span style="font-size:0.8rem;color:#8b949e;">Auto-discovered, no auth</span></div>
|
| 433 |
+
<div class="disc-card"><span class="icon mcp">MCP</span><b>MCPO</b><br>MCP OpenAPI proxy<span class="auth">needs API key</span><br><span style="font-size:0.8rem;color:#8b949e;">Prompted during connect</span></div>
|
| 434 |
+
<div class="disc-card"><span class="icon ui">UI</span><b>Open WebUI</b><br>LLM workspace<br><span style="font-size:0.8rem;color:#8b949e;">Auto-discovered, no auth</span></div>
|
| 435 |
+
<div class="disc-card"><span class="icon cht">CHT</span><b>SillyTavern</b><br>Character chat<span class="auth">needs basic auth</span><br><span style="font-size:0.8rem;color:#8b949e;">Tries default, prompts if fails</span></div>
|
| 436 |
+
</div>
|
| 437 |
+
</div>
|
| 438 |
+
|
| 439 |
+
<!-- WHY DIFFERENT -->
|
| 440 |
+
<div class="section">
|
| 441 |
+
<h2>Why It's Different</h2>
|
| 442 |
+
<hr>
|
| 443 |
+
<table class="compare-table">
|
| 444 |
+
<tr>
|
| 445 |
+
<th>Feature</th>
|
| 446 |
+
<th>Other tools</th>
|
| 447 |
+
<th>Basecamp</th>
|
| 448 |
+
</tr>
|
| 449 |
+
<tr>
|
| 450 |
+
<td>Service discovery</td>
|
| 451 |
+
<td class="no">Manual config</td>
|
| 452 |
+
<td class="yes">Auto-discover</td>
|
| 453 |
+
</tr>
|
| 454 |
+
<tr>
|
| 455 |
+
<td>API keys</td>
|
| 456 |
+
<td class="no">Hardcoded in config</td>
|
| 457 |
+
<td class="yes">Prompted at connect</td>
|
| 458 |
+
</tr>
|
| 459 |
+
<tr>
|
| 460 |
+
<td>Works with any stack</td>
|
| 461 |
+
<td class="no">Tied to one app</td>
|
| 462 |
+
<td class="yes">Stack-agnostic</td>
|
| 463 |
+
</tr>
|
| 464 |
+
<tr>
|
| 465 |
+
<td>Agent included</td>
|
| 466 |
+
<td class="no">BYO agent</td>
|
| 467 |
+
<td class="yes">Hermes Agent built-in</td>
|
| 468 |
+
</tr>
|
| 469 |
+
<tr>
|
| 470 |
+
<td>Streaming chat</td>
|
| 471 |
+
<td class="no">Usually no</td>
|
| 472 |
+
<td class="yes">Yes, by default</td>
|
| 473 |
+
</tr>
|
| 474 |
+
<tr>
|
| 475 |
+
<td>Re-scan after changes</td>
|
| 476 |
+
<td class="no">Edit config again</td>
|
| 477 |
+
<td class="yes">tavern rediscover</td>
|
| 478 |
+
</tr>
|
| 479 |
+
<tr>
|
| 480 |
+
<td>Secrets in image</td>
|
| 481 |
+
<td class="no">Often yes</td>
|
| 482 |
+
<td class="yes">Zero hardcoded keys</td>
|
| 483 |
+
</tr>
|
| 484 |
+
</table>
|
| 485 |
+
</div>
|
| 486 |
+
|
| 487 |
+
<!-- ARCHITECTURE -->
|
| 488 |
+
<div class="section">
|
| 489 |
+
<h2>Architecture</h2>
|
| 490 |
+
<hr>
|
| 491 |
+
<div class="arch"><span class="highlight">βββββββββββββββββββββββββββββββββββββββββββββββ</span>
|
| 492 |
+
<span class="highlight">β basecamp container β</span>
|
| 493 |
+
<span class="highlight">β</span> <span class="box">βββββββββββββ</span> <span class="box">ββββββββββββββββββββββββ</span> <span class="highlight">β</span>
|
| 494 |
+
<span class="highlight">β</span> <span class="box">β Ollama β</span> <span class="box">β Hermes Agent β</span> <span class="highlight">β</span>
|
| 495 |
+
<span class="highlight">β</span> <span class="box">β (GGUF) β</span> <span class="box">β (CLI AI assistant) β</span> <span class="highlight">β</span>
|
| 496 |
+
<span class="highlight">β</span> <span class="box">βββββββββββββ</span> <span class="box">ββββββββββββββββββββββββ</span> <span class="highlight">β</span>
|
| 497 |
+
<span class="highlight">β</span> <span class="box">ββββββββββββββββββββββββββββββββββββββββ</span> <span class="highlight">β</span>
|
| 498 |
+
<span class="highlight">β</span> <span class="box">β discover.py β</span> <span class="highlight">β</span>
|
| 499 |
+
<span class="highlight">β</span> <span class="box">β - scans network for AI services β</span> <span class="highlight">β</span>
|
| 500 |
+
<span class="highlight">β</span> <span class="box">β - connect screen TUI β</span> <span class="highlight">β</span>
|
| 501 |
+
<span class="highlight">β</span> <span class="box">β - generates tavern CLI dynamically β</span> <span class="highlight">β</span>
|
| 502 |
+
<span class="highlight">β</span> <span class="box">ββββββββββββββββββββββββββββββββββββββββ</span> <span class="highlight">β</span>
|
| 503 |
+
<span class="highlight">β</span> <span class="box">ββββββββββββββββββββββββββββββββββββββββ</span> <span class="highlight">β</span>
|
| 504 |
+
<span class="highlight">β</span> <span class="box">β tavern (auto-generated) β</span> <span class="highlight">β</span>
|
| 505 |
+
<span class="highlight">β</span> <span class="box">β - chat, search, mcp, models, status β</span> <span class="highlight">β</span>
|
| 506 |
+
<span class="highlight">β</span> <span class="box">β - streaming responses β</span> <span class="highlight">β</span>
|
| 507 |
+
<span class="highlight">β</span> <span class="box">β - rediscover on demand β</span> <span class="highlight">β</span>
|
| 508 |
+
<span class="highlight">β</span> <span class="box">ββββββββββββββββββββββββββββββββββββββββ</span> <span class="highlight">β</span>
|
| 509 |
+
<span class="highlight">βββββββββββββββββββββββββββββββββββββββββββββββ</span>
|
| 510 |
+
<span class="arrow">β</span> <span class="arrow">β</span> <span class="arrow">β</span>
|
| 511 |
+
<span class="arrow">βΌ</span> <span class="arrow">βΌ</span> <span class="arrow">βΌ</span>
|
| 512 |
+
<span class="box">ββββββββββββ</span> <span class="box">ββββββββββββ</span> <span class="box">ββββββββββββ</span>
|
| 513 |
+
<span class="box">β TabbyAPI β</span> <span class="box">β Ollama β</span> <span class="box">β SearXNG β</span>
|
| 514 |
+
<span class="box">β (EXL3) β</span> <span class="box">β (GGUF) β</span> <span class="box">β (search) β</span>
|
| 515 |
+
<span class="box">ββββββββββββ</span> <span class="box">ββββββββββββ</span> <span class="box">ββββββββββββ</span>
|
| 516 |
+
<span class="arrow">β</span> <span class="arrow">β</span>
|
| 517 |
+
<span class="arrow">βΌ</span> <span class="arrow">βΌ</span>
|
| 518 |
+
<span class="box">ββββββββββββ</span> <span class="box">ββββββββββββ</span>
|
| 519 |
+
<span class="box">β MCPO β</span> <span class="box">βOpen WebUIβ</span>
|
| 520 |
+
<span class="box">β (tools) β</span> <span class="box">β (UI) β</span>
|
| 521 |
+
<span class="box">ββββββββββββ</span> <span class="box">ββββββββββββ</span></div>
|
| 522 |
+
</div>
|
| 523 |
+
|
| 524 |
+
<!-- RELATED -->
|
| 525 |
+
<div class="section">
|
| 526 |
+
<h2>Related Projects</h2>
|
| 527 |
+
<hr>
|
| 528 |
+
<ul>
|
| 529 |
+
<li><b>Tabby Tavern Stack</b> β the full 6-service local AI infrastructure (TabbyAPI, SillyTavern, Open WebUI, Ollama, SearXNG, MCPO) β <a href="https://huggingface.co/jpanasuk/tabby-tavern-stack" style="color:var(--blue);">huggingface.co/jpanasuk/tabby-tavern-stack</a></li>
|
| 530 |
+
<li><b>Basecamp</b> β this project, the portable agent container β <a href="https://huggingface.co/jpanasuk/basecamp" style="color:var(--blue);">huggingface.co/jpanasuk/basecamp</a></li>
|
| 531 |
+
</ul>
|
| 532 |
+
</div>
|
| 533 |
+
|
| 534 |
+
</div>
|
| 535 |
+
|
| 536 |
+
<div class="footer">
|
| 537 |
+
<div><b>Jeremy Panasuk</b> β enterprise data architect turned local AI systems engineer</div>
|
| 538 |
+
<div class="links">
|
| 539 |
+
<a href="https://huggingface.co/jpanasuk">HuggingFace</a>
|
| 540 |
+
<a href="https://github.com/jpanasuk-netizen">GitHub</a>
|
| 541 |
+
<a href="https://www.linkedin.com/in/jeremy-p-34203322">LinkedIn</a>
|
| 542 |
+
</div>
|
| 543 |
+
<div style="margin-top:8px;opacity:0.6;font-size:0.78rem;">MIT License Β· No secrets shipped Β· Built on Hermes Agent + Ollama</div>
|
| 544 |
+
<div style="margin-top:4px;opacity:0.6;font-size:0.78rem;">Agent by <a href="https://nousresearch.com" style="color:inherit;">Nous Research</a> β this box just wraps it. All props to the Hermes team. π</div>
|
| 545 |
+
</div>
|
| 546 |
+
|
| 547 |
+
</body>
|
| 548 |
+
</html>
|
skins/indigo.yaml
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: indigo
|
| 2 |
+
description: Basecamp indigo β deep violet and electric blue
|
| 3 |
+
|
| 4 |
+
branding:
|
| 5 |
+
agent_name: "Basecamp Hermes"
|
| 6 |
+
welcome: "Welcome to Basecamp Hermes!"
|
| 7 |
+
goodbye: "Goodbye from Basecamp! β"
|
| 8 |
+
response_label: " β Basecamp Hermes "
|
| 9 |
+
prompt_symbol: "β―"
|
| 10 |
+
|
| 11 |
+
colors:
|
| 12 |
+
# Base surface
|
| 13 |
+
background: "#12101f"
|
| 14 |
+
|
| 15 |
+
# Brand accent β indigo/violet
|
| 16 |
+
ui_accent: "#7c6cf0"
|
| 17 |
+
banner_accent: "#7c6cf0"
|
| 18 |
+
ui_primary: "#8b7cf8"
|
| 19 |
+
|
| 20 |
+
# Text hierarchy
|
| 21 |
+
banner_title: "#a5b4fc"
|
| 22 |
+
banner_text: "#e0e0f0"
|
| 23 |
+
ui_text: "#e0e0f0"
|
| 24 |
+
banner_dim: "#6f6a99"
|
| 25 |
+
banner_border: "#3b3670"
|
| 26 |
+
ui_border: "#3b3670"
|
| 27 |
+
|
| 28 |
+
# Semantic status
|
| 29 |
+
ui_ok: "#4ade80"
|
| 30 |
+
ui_warn: "#fbbf24"
|
| 31 |
+
ui_error: "#f87171"
|
| 32 |
+
ui_label: "#a5b4fc"
|
| 33 |
+
|
| 34 |
+
# CLI / TUI chrome
|
| 35 |
+
prompt: "#e0e0f0"
|
| 36 |
+
input_rule: "#7c6cf0"
|
| 37 |
+
response_border: "#a5b4fc"
|
| 38 |
+
status_bar_bg: "#0c0a17"
|
| 39 |
+
status_bar_text: "#e0e0f0"
|
| 40 |
+
status_bar_good: "#4ade80"
|
| 41 |
+
status_bar_warn: "#fbbf24"
|
| 42 |
+
status_bar_critical: "#f87171"
|
| 43 |
+
session_label: "#a5b4fc"
|