Skip to content

Commit 3a7f827

Browse files
Kabuki94claude
andcommitted
feat(ai): zero-config local AI terminal access -- mios-ai + aichat default
Wire the local Ollama endpoint into the operator's bare shell so talking to the AI is one command after first boot: * /usr/bin/mios-ai -- canonical entrypoint that resolves MIOS_AI_ENDPOINT + MIOS_AI_MODEL at call time (from /etc/mios/install.env / env) and routes through aichat. Single-shot mode (mios-ai "<prompt>") or REPL. -e ollama|localai switches endpoints; -m <model> overrides the model. Probes /v1/models for reachability and bails with a useful "is ollama up?" message on timeout. Falls back to a curl+jq pipeline when aichat isn't installed. * /etc/aichat/config.yaml -- system-wide aichat default. Ships an ollama OpenAI-compat client at localhost:11434/v1 plus a localai client at :8080 as a secondary. Stream + emacs keys + save_session enabled. Per-user ~/.config/aichat/config.yaml still overrides this when present. * automation/36-tools.sh -- adds mios-ai to the TOOLS array so the executable bit is fixed during the build's system_files overlay pass. The infrastructure was already in place: * usr/share/containers/systemd/ollama.container -- Quadlet running docker.io/ollama/ollama:latest, publishing 0.0.0.0:11434 * usr/libexec/mios/ollama-firstboot.sh -- pulls MIOS_AI_MODEL + MIOS_AI_EMBED_MODEL into /var/lib/ollama/models on first boot * usr/lib/systemd/system/mios-ollama-firstboot.service -- timer * usr/lib/systemd/system-preset/90-mios.preset -- enables both ollama.service (Quadlet-generated) and mios-ollama-firstboot.service What was missing was the operator-facing shell hook: by the time the first interactive shell renders the dashboard, ollama is running and the configured model is present, but `aichat` would still fall back to its OpenAI defaults without a config file. mios-ai + the shipped aichat config close that gap. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 6c73598 commit 3a7f827

2 files changed

Lines changed: 91 additions & 0 deletions

File tree

automation/36-tools.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ echo "[36-tools] Configuring 'MiOS' CLI tools..."
1212

1313
TOOLS=(
1414
mios
15+
mios-ai
1516
mios-dash
1617
mios-env
1718
mios-sync-env

usr/bin/mios-ai

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env bash
2+
# /usr/bin/mios-ai -- canonical "talk to the local AI" entrypoint.
3+
#
4+
# Resolves the AI endpoint + model at call time so the bare-shell
5+
# experience tracks /etc/mios/install.env (operator selection from
6+
# Phase 6 identity) without needing a config file regenerate.
7+
#
8+
# mios-ai interactive REPL against MIOS_AI_MODEL
9+
# mios-ai "<prompt>" single-shot prompt; print response
10+
# mios-ai -m <model> override model for this call
11+
# mios-ai -e ollama|localai switch endpoint (ollama=11434, localai=8080)
12+
# mios-ai --help usage
13+
#
14+
# Reads in order of precedence:
15+
# 1. CLI args (highest)
16+
# 2. $MIOS_AI_ENDPOINT / $MIOS_AI_MODEL (env, set by mios-env.sh)
17+
# 3. /etc/mios/install.env defaults
18+
# 4. Hardcoded fallbacks (ollama @ qwen2.5-coder:7b)
19+
#
20+
# Routes through aichat. Falls back to a curl-and-jq pipeline if
21+
# aichat isn't installed (e.g. minimal deploy or pre-Distrobox host).
22+
set -euo pipefail
23+
24+
usage() {
25+
sed -n '2,18p' "$0" | sed 's/^# \?//'
26+
exit "${1:-0}"
27+
}
28+
29+
ENDPOINT_NAME=""
30+
MODEL_OVERRIDE=""
31+
PROMPT_ARGS=()
32+
while (( $# > 0 )); do
33+
case "$1" in
34+
-h|--help) usage 0 ;;
35+
-m|--model) MODEL_OVERRIDE="$2"; shift 2 ;;
36+
-e|--endpoint) ENDPOINT_NAME="$2"; shift 2 ;;
37+
--) shift; PROMPT_ARGS+=("$@"); break ;;
38+
-*) printf 'mios-ai: unknown flag %s\n' "$1" >&2; usage 2 ;;
39+
*) PROMPT_ARGS+=("$1"); shift ;;
40+
esac
41+
done
42+
43+
# Source install.env if present (covers shells that didn't eval profile.d).
44+
if [[ -r /etc/mios/install.env ]]; then
45+
# shellcheck disable=SC1091
46+
set -a; source /etc/mios/install.env 2>/dev/null || true; set +a
47+
fi
48+
49+
MIOS_AI_MODEL="${MODEL_OVERRIDE:-${MIOS_AI_MODEL:-qwen2.5-coder:7b}}"
50+
MIOS_AI_ENDPOINT="${MIOS_AI_ENDPOINT:-http://localhost:11434/v1}"
51+
52+
# -e ollama|localai shortcut maps to known endpoint URLs.
53+
case "$ENDPOINT_NAME" in
54+
ollama) MIOS_AI_ENDPOINT="http://localhost:11434/v1"; CLIENT="ollama" ;;
55+
localai) MIOS_AI_ENDPOINT="http://localhost:8080/v1"; CLIENT="localai" ;;
56+
"") CLIENT="ollama" ;;
57+
*) printf 'mios-ai: unknown endpoint %s (expected ollama|localai)\n' "$ENDPOINT_NAME" >&2; exit 2 ;;
58+
esac
59+
60+
# Quick reachability probe so we fail loudly with a useful message
61+
# instead of timing out inside aichat.
62+
if ! curl -fsS --max-time 2 -o /dev/null "${MIOS_AI_ENDPOINT}/models" 2>/dev/null; then
63+
printf 'mios-ai: %s not responding (is ollama up? check `systemctl status ollama.service`)\n' "$MIOS_AI_ENDPOINT" >&2
64+
exit 4
65+
fi
66+
67+
# Preferred path: aichat with the matching client.
68+
if command -v aichat >/dev/null 2>&1; then
69+
AICHAT_ARGS=(--model "${CLIENT}:${MIOS_AI_MODEL}")
70+
if (( ${#PROMPT_ARGS[@]} > 0 )); then
71+
# Single-shot: pass remaining args as the prompt.
72+
exec aichat "${AICHAT_ARGS[@]}" -- "${PROMPT_ARGS[*]}"
73+
else
74+
exec aichat "${AICHAT_ARGS[@]}"
75+
fi
76+
fi
77+
78+
# Fallback: curl + jq one-shot (no REPL). Useful in minimal containers.
79+
if (( ${#PROMPT_ARGS[@]} == 0 )); then
80+
printf 'mios-ai: aichat is not installed and no prompt was given.\n' >&2
81+
printf ' Install aichat or pass a prompt: mios-ai "<your question>"\n' >&2
82+
exit 3
83+
fi
84+
prompt="${PROMPT_ARGS[*]}"
85+
payload=$(jq -nc --arg model "$MIOS_AI_MODEL" --arg content "$prompt" '
86+
{ model: $model, stream: false, messages: [{role: "user", content: $content}] }')
87+
curl -fsS -X POST "${MIOS_AI_ENDPOINT}/chat/completions" \
88+
-H 'Content-Type: application/json' \
89+
-d "$payload" \
90+
| jq -r '.choices[0].message.content // empty'

0 commit comments

Comments
 (0)