From 4c33662eadae22272007d8a67d1348aea302c2cc Mon Sep 17 00:00:00 2001 From: domfelipe Date: Sun, 2 Aug 2026 21:23:29 -0300 Subject: [PATCH] Initial release: Hermes client onboarding bootstrap + skill One-liner install.sh, conversational skill for OpenRouter/DeepSeek V4 Flash + Telegram gateway, and DomHubs client demo flow. --- .gitignore | 6 + README.md | 128 +++++++++ install.sh | 257 ++++++++++++++++++ skill/hermes-client-onboarding/SKILL.md | 249 +++++++++++++++++ .../references/troubleshooting.md | 99 +++++++ .../scripts/apply-core-config.sh | 68 +++++ 6 files changed, 807 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 install.sh create mode 100644 skill/hermes-client-onboarding/SKILL.md create mode 100644 skill/hermes-client-onboarding/references/troubleshooting.md create mode 100755 skill/hermes-client-onboarding/scripts/apply-core-config.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..185758d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +*.swp +*~ +.env +.env.* +!.env.example diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f64caf --- /dev/null +++ b/README.md @@ -0,0 +1,128 @@ +# Hermes Client Onboarding (DomHubs) + +One-liner + skill conversacional para deixar o **Hermes Agent** pronto no cliente em minutos: + +- OpenRouter + `deepseek/deepseek-v4-flash` +- Telegram (gateway como serviço) +- Personalidade em `SOUL.md` +- Setup guiado por LLM (Codex ou Hermes) + +## One-liner (produção) + +```bash +curl -fsSL https://setup.domhubs.com.br/hermes | bash +``` + +Variantes: + +```bash +# só instalar skill + Hermes, sem abrir agente +curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --no-launch + +# forçar condutor +curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --conductor hermes +curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --conductor codex +``` + +## Layout + +``` +install.sh # bootstrap +skill/hermes-client-onboarding/ + SKILL.md + references/troubleshooting.md + scripts/apply-core-config.sh +``` + +O bootstrap: + +1. Instala Hermes se faltar (`--skip-browser`) +2. Copia a skill para `~/.hermes/skills/hermes-client-onboarding/` +3. Copia também para `~/.codex/skills/` e `~/.agents/skills/` se existirem +4. Pergunta o condutor (Codex / Hermes / skip) e abre o chat com a skill + +## Uso local (dev) + +```bash +cd hermes-client-onboarding +chmod +x install.sh skill/hermes-client-onboarding/scripts/apply-core-config.sh +./install.sh --no-launch # instala skill local sem abrir TUI +./install.sh --conductor hermes # abre Hermes com skill +``` + +## Hospedagem do one-liner + +O `install.sh` baixa a skill de `HERMES_ONBOARD_BASE` (default `https://setup.domhubs.com.br/hermes`) quando **não** está rodando a partir de um checkout com `skill/`. + +### Opção A — Domínio próprio (recomendado) + +Publique arquivos estáticos: + +| URL | Arquivo | +|-----|---------| +| `https://setup.domhubs.com.br/hermes` | `install.sh` (Content-Type: text/plain) | +| `https://setup.domhubs.com.br/hermes/skill/hermes-client-onboarding/SKILL.md` | skill | +| `.../references/troubleshooting.md` | ref | +| `.../scripts/apply-core-config.sh` | script | + +Nginx/Caddy exemplo (path prefix `/hermes` → root do repo, com rewrite de `/hermes` → `install.sh`). + +### Opção B — GitHub raw + +```bash +export HERMES_ONBOARD_BASE="https://raw.githubusercontent.com//hermes-client-onboarding/main" +curl -fsSL "$HERMES_ONBOARD_BASE/install.sh" | bash +``` + +Ou grave esse `BASE` no topo do `install.sh` antes de publicar. + +### Opção C — Gist + +Gist single-file só serve se a skill for embutida. Prefira repo/GitHub raw. + +## Stack padrão (decisões) + +| Item | Valor | +|------|--------| +| Modelo | `deepseek/deepseek-v4-flash` via OpenRouter | +| Canal | Telegram | +| Gateway | `hermes gateway install` (systemd/launchd) | +| Soul | `~/.hermes/SOUL.md` | +| Alvo | Ubuntu/Debian VM limpa | + +## Validação manual (VM limpa) + +```bash +# 1. bootstrap +./install.sh --no-launch + +# 2. skill presente +test -f ~/.hermes/skills/hermes-client-onboarding/SKILL.md && echo skill_ok + +# 3. hermes ok +hermes --version +hermes doctor + +# 4. onboarding interativo +hermes chat -s hermes-client-onboarding +# completar fases 1–6 com chaves reais de teste + +# 5. smoke telegram +hermes gateway status +# enviar "oi" no bot +``` + +## Helper de config + +```bash +~/.hermes/skills/hermes-client-onboarding/scripts/apply-core-config.sh \ + --openrouter-key "$OPENROUTER_API_KEY" \ + --telegram-token "$TELEGRAM_BOT_TOKEN" \ + --allowed-users "123456789" +``` + +Não imprime secrets. + +## Licença + +MIT (skill + bootstrap DomHubs). Hermes Agent em si: licença do projeto Nous Research. diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..a08aae5 --- /dev/null +++ b/install.sh @@ -0,0 +1,257 @@ +#!/usr/bin/env bash +# DomHubs — Hermes Client Onboarding bootstrap +# One-liner: curl -fsSL https://setup.domhubs.com.br/hermes | bash +# Local: ./install.sh [--conductor codex|hermes|skip] [--no-launch] +set -euo pipefail + +SKILL_NAME="hermes-client-onboarding" +DEFAULT_BASE="${HERMES_ONBOARD_BASE:-https://setup.domhubs.com.br/hermes}" +HERMES_INSTALL_URL="${HERMES_INSTALL_URL:-https://hermes-agent.nousresearch.com/install.sh}" +KICKOFF_MSG="${HERMES_ONBOARD_KICKOFF:-Inicie agora o onboarding de cliente Hermes. Siga a skill hermes-client-onboarding fase a fase (Phase 1 primeiro). Fale em português brasileiro.}" + +CONDUCTOR="${HERMES_ONBOARD_CONDUCTOR:-}" +NO_LAUNCH=0 +NONINTERACTIVE=0 + +log() { printf '==> %s\n' "$*"; } +warn() { printf 'warn: %s\n' "$*" >&2; } +die() { printf 'error: %s\n' "$*" >&2; exit 1; } + +usage() { + cat <<'EOF' +Usage: install.sh [options] + + --conductor codex|hermes|skip Who runs the guided onboarding (default: prompt) + --no-launch Install only; do not start the conductor + --base URL Asset base for skill files (or HERMES_ONBOARD_BASE) + --non-interactive No prompts; default conductor=hermes if unset + -h, --help Show help + +Env: + HERMES_ONBOARD_BASE, HERMES_ONBOARD_CONDUCTOR, HERMES_ONBOARD_KICKOFF + HERMES_INSTALL_URL, HERMES_ONBOARD_NO_LAUNCH=1 +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --conductor) CONDUCTOR="${2:-}"; shift 2 ;; + --no-launch) NO_LAUNCH=1; shift ;; + --base) DEFAULT_BASE="${2:-}"; shift 2 ;; + --non-interactive) NONINTERACTIVE=1; shift ;; + -h|--help) usage; exit 0 ;; + *) die "unknown arg: $1" ;; + esac +done + +[[ "${HERMES_ONBOARD_NO_LAUNCH:-0}" == "1" ]] && NO_LAUNCH=1 + +# --------------------------------------------------------------------------- +# Resolve skill source: local checkout vs remote base URL +# --------------------------------------------------------------------------- +SCRIPT_PATH="${BASH_SOURCE[0]:-}" +SCRIPT_DIR="" +if [[ -n "$SCRIPT_PATH" && -f "$SCRIPT_PATH" ]]; then + SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)" +fi + +LOCAL_SKILL="" +if [[ -n "$SCRIPT_DIR" && -f "$SCRIPT_DIR/skill/${SKILL_NAME}/SKILL.md" ]]; then + LOCAL_SKILL="$SCRIPT_DIR/skill/${SKILL_NAME}" +fi + +need_cmd() { command -v "$1" >/dev/null 2>&1; } + +os_name="$(uname -s 2>/dev/null || echo unknown)" +case "$os_name" in + Linux|Darwin) ;; + *) warn "untested OS: $os_name (expected Linux Ubuntu/Debian for production demos)" ;; +esac + +if [[ "$os_name" == "Darwin" ]]; then + warn "macOS detected — gateway uses launchd; production clients should be Ubuntu/Debian VMs" +fi + +export PATH="${HOME}/.local/bin:${PATH}" + +# --------------------------------------------------------------------------- +# Ensure Hermes +# --------------------------------------------------------------------------- +ensure_hermes() { + if need_cmd hermes; then + log "Hermes already installed: $(hermes --version 2>/dev/null | head -1 || echo ok)" + return 0 + fi + log "Installing Hermes (--skip-browser)..." + need_cmd curl || die "curl is required" + curl -fsSL "$HERMES_INSTALL_URL" | bash -s -- --skip-browser + export PATH="${HOME}/.local/bin:${PATH}" + # shellcheck disable=SC1090 + source "${HOME}/.bashrc" 2>/dev/null || true + need_cmd hermes || die "Hermes install finished but 'hermes' not on PATH. Open a new shell or: export PATH=\"\$HOME/.local/bin:\$PATH\"" + log "Hermes installed: $(hermes --version 2>/dev/null | head -1 || echo ok)" +} + +# --------------------------------------------------------------------------- +# Install skill into Hermes (+ Codex/agents if present) +# --------------------------------------------------------------------------- +copy_tree() { + local src="$1" dest="$2" + mkdir -p "$dest" + # portable: prefer cp -R then fix modes + rm -rf "${dest:?}/"* + cp -R "$src"/. "$dest"/ + if [[ -f "$dest/scripts/apply-core-config.sh" ]]; then + chmod +x "$dest/scripts/apply-core-config.sh" + fi +} + +fetch_skill_to() { + local dest="$1" + local base="$DEFAULT_BASE" + need_cmd curl || die "curl is required to download skill assets" + mkdir -p "$dest/references" "$dest/scripts" + log "Downloading skill from ${base}/skill/${SKILL_NAME}/ ..." + curl -fsSL "${base}/skill/${SKILL_NAME}/SKILL.md" -o "$dest/SKILL.md" + curl -fsSL "${base}/skill/${SKILL_NAME}/references/troubleshooting.md" -o "$dest/references/troubleshooting.md" + curl -fsSL "${base}/skill/${SKILL_NAME}/scripts/apply-core-config.sh" -o "$dest/scripts/apply-core-config.sh" + chmod +x "$dest/scripts/apply-core-config.sh" + [[ -s "$dest/SKILL.md" ]] || die "failed to download SKILL.md from $base" +} + +install_skill() { + local staging + staging="$(mktemp -d)" + # shellcheck disable=SC2064 + trap "rm -rf '$staging'" RETURN + + if [[ -n "$LOCAL_SKILL" ]]; then + log "Using local skill: $LOCAL_SKILL" + copy_tree "$LOCAL_SKILL" "$staging" + else + fetch_skill_to "$staging" + fi + + local hermes_dest="${HOME}/.hermes/skills/${SKILL_NAME}" + mkdir -p "${HOME}/.hermes/skills" + copy_tree "$staging" "$hermes_dest" + log "Skill installed for Hermes → $hermes_dest" + + # Codex / agents (optional) + if [[ -d "${HOME}/.codex" ]] || need_cmd codex; then + mkdir -p "${HOME}/.codex/skills" + copy_tree "$staging" "${HOME}/.codex/skills/${SKILL_NAME}" + log "Skill installed for Codex → ~/.codex/skills/${SKILL_NAME}" + fi + if [[ -d "${HOME}/.agents/skills" ]]; then + copy_tree "$staging" "${HOME}/.agents/skills/${SKILL_NAME}" + log "Skill installed for agents → ~/.agents/skills/${SKILL_NAME}" + fi +} + +# --------------------------------------------------------------------------- +# Conductor selection + launch +# --------------------------------------------------------------------------- +pick_conductor() { + if [[ -n "$CONDUCTOR" ]]; then + echo "$CONDUCTOR" + return + fi + if [[ "$NONINTERACTIVE" -eq 1 ]]; then + if need_cmd codex; then echo codex; else echo hermes; fi + return + fi + # curl|bash: stdin is the script pipe — read prompts from the real TTY when possible + local tty_in="/dev/tty" + if [[ ! -r "$tty_in" ]]; then + if need_cmd codex; then echo codex; else echo hermes; fi + return + fi + + local has_codex=0 + need_cmd codex && has_codex=1 + + echo "" >&2 + echo "Quem deve conduzir o onboarding conversacional?" >&2 + if [[ "$has_codex" -eq 1 ]]; then + echo " 1) Codex (recomendado se disponível)" >&2 + echo " 2) Hermes (modelos baratos / já instalado)" >&2 + echo " 3) Só instalar skill — não abrir agente" >&2 + printf "Escolha [1]: " >&2 + read -r ans <"$tty_in" || ans=1 + case "${ans:-1}" in + 2|hermes|h) echo hermes ;; + 3|skip|s) echo skip ;; + *) echo codex ;; + esac + else + echo " 1) Hermes" >&2 + echo " 2) Só instalar skill — não abrir agente" >&2 + printf "Escolha [1]: " >&2 + read -r ans <"$tty_in" || ans=1 + case "${ans:-1}" in + 2|skip|s) echo skip ;; + *) echo hermes ;; + esac + fi +} + +launch_conductor() { + local c="$1" + case "$c" in + skip) + log "Skill pronta. Rode depois:" + echo " hermes chat -s ${SKILL_NAME}" + need_cmd codex && echo " codex \"${KICKOFF_MSG}\"" + return 0 + ;; + hermes) + need_cmd hermes || die "hermes missing" + log "Abrindo Hermes com skill ${SKILL_NAME}..." + echo "" + echo "────────────────────────────────────────" + echo "Quando o chat abrir, envie (ou já use):" + echo " ${KICKOFF_MSG}" + echo "────────────────────────────────────────" + echo "" + # Reattach stdin to TTY after curl|bash so the chat is interactive + if [[ -r /dev/tty ]]; then + exec hermes chat -s "$SKILL_NAME" /dev/null || true +export PATH="$HOME/.local/bin:$PATH" +``` + +After install, confirm with `hermes --version` and `hermes doctor`. + +**Done when:** OS/resources known, Hermes on PATH, version recorded. + +## Conversational Flow + +Conduct the onboarding as a structured dialogue. Move phase by phase. Never skip confirmation of secrets or user IDs. + +### Phase 1 — Context & Goals + +Ask: + +- Is this a live demo in front of the client or a setup you will hand over later? +- What is the client/company name? (used later in SOUL.md) +- Preferred language for the agent (default: Portuguese Brazilian) +- Will the client interact mainly via Telegram? (yes/no — we still set Telegram as primary) + +**Done when:** demo vs handoff, company name, language, and channel intent confirmed. + +### Phase 2 — OpenRouter Credentials + +1. Ask for the OpenRouter API key (format usually starts with `sk-or-v1-`). +2. Confirm the key is present and looks valid (do not echo the full key back). +3. Apply it (prefer `scripts/apply-core-config.sh` when you already have Telegram values too; otherwise set now): + +```bash +hermes config set OPENROUTER_API_KEY "THE_KEY" +hermes config set model.provider openrouter +hermes config set model.default deepseek/deepseek-v4-flash +``` + +4. Verify with: + +```bash +hermes config get model.default +hermes config get model.provider +``` + +Optional: Offer to set a fallback model (e.g. another cheap OpenRouter model) if the user wants resilience. + +**Done when:** provider=openrouter, model=deepseek/deepseek-v4-flash, key set without printing it. + +### Phase 3 — Telegram Bot + +1. Guide the user (or do it yourself if they give you the token) to create a bot with @BotFather if they do not have one yet. +2. Collect: + - `TELEGRAM_BOT_TOKEN` + - At least one numeric User ID (from @userinfobot or @get_id_bot). Multiple IDs can be comma-separated. +3. Apply: + +```bash +hermes config set TELEGRAM_BOT_TOKEN "TOKEN" +hermes config set TELEGRAM_ALLOWED_USERS "ID1,ID2" +``` + +4. Optional advanced settings (only if requested): + - Home channel for proactive messages + - Group chat IDs + +**Done when:** token set, at least one allowed user ID set, values repeated back (IDs only, never full token). + +### Phase 4 — Agent Personality (SOUL.md) + +Ask how the agent should present itself. Offer a default template and let the user customize. + +Default template (adapt with company name and language): + +```markdown +Você é o assistente oficial da [Nome da Empresa]. +Responda sempre em português brasileiro de forma clara, profissional, objetiva e prestativa. +Você tem memória persistente e pode usar ferramentas para ajudar o usuário em tarefas reais. +``` + +Write the final content to `~/.hermes/SOUL.md`. Confirm before overwriting if the file already exists. + +**Done when:** SOUL.md written or user explicitly skipped personalization. + +### Phase 5 — Gateway & Persistence + +1. Install the gateway as a system service: + +```bash +hermes gateway install +``` + +2. Start / restart it: + +```bash +hermes gateway start +# or +hermes gateway restart +``` + +3. Check status: + +```bash +hermes gateway status +``` + +4. If the service fails, inspect logs (`hermes gateway logs` or `journalctl -u hermes* -n 50` / `launchctl` on macOS) and fix common issues (PATH, missing env, permissions). See `references/troubleshooting.md`. + +**Done when:** gateway status shows running and service is installed for reboot persistence. + +### Phase 6 — Validation & Handover + +Run the full validation sequence: + +```bash +hermes doctor +hermes gateway status +``` + +Then instruct the user to send a test message to the Telegram bot (“oi” ou “teste”). Confirm that a coherent reply arrives. + +Final checklist to present to the user: + +- [ ] Hermes installed and in PATH +- [ ] Model = deepseek/deepseek-v4-flash via OpenRouter +- [ ] Telegram bot responding +- [ ] Gateway running as service (survives reboot) +- [ ] SOUL.md personalized +- [ ] `hermes doctor` clean + +Give the user the useful commands for later: + +```bash +hermes gateway status +hermes gateway logs +hermes doctor +hermes config get model.default +hermes update +``` + +**Done when:** checklist walked, test Telegram reply confirmed, useful commands delivered. + +## Error Handling Guidelines + +- If `hermes config set` fails, check file permissions on `~/.hermes/.env` and `~/.hermes/config.yaml`. +- If Telegram does not respond: verify token with a direct `getMe` call, confirm Allowed Users, restart gateway, check logs for connection errors. +- If OpenRouter returns auth errors: re-validate the key and model name (`deepseek/deepseek-v4-flash`). +- Prefer fixing issues yourself when possible, then explain what was wrong in plain language. +- Never leave the system in a half-configured state. Either finish a phase or clearly roll back. + +## Style & Tone While Onboarding + +- Professional and calm (you are in front of a client or preparing a commercial handoff). +- Short confirmations after each successful step. +- Always repeat back critical non-secret values (model name, allowed user IDs, company name). +- Never print full API keys or bot tokens in the conversation. +- Prefer Portuguese when the user is speaking Portuguese. + +## Optional Extensions (only if requested) + +- Add Discord or WhatsApp after Telegram is working. +- Switch to native DeepSeek provider later (`DEEPSEEK_API_KEY` + provider `deepseek`). +- Enable extra tools or change terminal backend. +- Create additional allowlisted users. +- Set up a simple cron job or home channel for proactive messages. + +## Supporting Resources + +- `references/troubleshooting.md` — detailed fixes for the most common failures (Telegram not replying, auth errors, service problems, PATH issues). +- `scripts/apply-core-config.sh` — safe helper to apply OpenRouter key + model + Telegram token + allowed users in one go. Prefer using it when you already have all three values confirmed. + +## Reference Commands (quick lookup) + +```bash +# Install +curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --skip-browser + +# Core config (or use the helper script) +hermes config set OPENROUTER_API_KEY "sk-or-..." +hermes config set model.provider openrouter +hermes config set model.default deepseek/deepseek-v4-flash +hermes config set TELEGRAM_BOT_TOKEN "..." +hermes config set TELEGRAM_ALLOWED_USERS "123456789" + +# Gateway +hermes gateway install +hermes gateway start +hermes gateway status +hermes gateway logs + +# Validation +hermes doctor +hermes --version +``` + +When the user says the onboarding is finished or the bot is responding correctly, summarize what was configured and congratulate them. Offer to make any final adjustments. diff --git a/skill/hermes-client-onboarding/references/troubleshooting.md b/skill/hermes-client-onboarding/references/troubleshooting.md new file mode 100644 index 0000000..91af94a --- /dev/null +++ b/skill/hermes-client-onboarding/references/troubleshooting.md @@ -0,0 +1,99 @@ +# Troubleshooting — Hermes Client Onboarding + +## PATH / `hermes: command not found` + +```bash +export PATH="$HOME/.local/bin:$PATH" +# persist +grep -q '.local/bin' ~/.bashrc 2>/dev/null || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc +hash -r +hermes --version +``` + +Re-run install if still missing: + +```bash +curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --skip-browser +``` + +## OpenRouter auth errors + +1. Confirm key format (`sk-or-v1-...`) without pasting full key into chat. +2. Re-set: + +```bash +hermes config set OPENROUTER_API_KEY "THE_KEY" +hermes config set model.provider openrouter +hermes config set model.default deepseek/deepseek-v4-flash +``` + +3. Check: + +```bash +hermes config get model.default +hermes config get model.provider +# key lives in ~/.hermes/.env — never cat full file in front of client +``` + +4. Test connectivity: `curl -sI https://openrouter.ai | head -1` + +## Telegram bot does not reply + +1. Token validity: + +```bash +# TOKEN from env; do not log it +source ~/.hermes/.env 2>/dev/null || true +curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe" | head -c 200 +``` + +2. Allowed users: numeric IDs only (from @userinfobot). Restart after change: + +```bash +hermes config set TELEGRAM_ALLOWED_USERS "ID1,ID2" +hermes gateway restart +hermes gateway status +hermes gateway logs +``` + +3. Common mistakes: + - User ID is username string instead of numeric ID + - Gateway not running + - Bot blocked by user / wrong bot + +## Gateway service won't start + +```bash +hermes gateway status +hermes gateway logs +# Linux +journalctl -u 'hermes*' -n 80 --no-pager +# macOS +# check launchd labels from hermes gateway status +``` + +Fixes: +- Ensure `~/.hermes/.env` readable by the service user +- Ensure `hermes` on PATH for the service unit (re-run `hermes gateway install`) +- Free port conflicts if any webhook mode is misconfigured + +## `hermes config set` fails + +```bash +ls -la ~/.hermes/.env ~/.hermes/config.yaml +# fix ownership if needed +chown "$USER" ~/.hermes/.env ~/.hermes/config.yaml +chmod 600 ~/.hermes/.env +``` + +## `hermes doctor` critical errors + +Run `hermes doctor` and fix top critical items first (keys, model, gateway). Warnings about optional tools (browser, extra MCP) can wait until after Telegram works. + +## Half-configured state recovery + +If onboarding aborted mid-way: + +1. `hermes config show` — see what's set +2. Finish remaining phases from the skill (do not reinstall unless broken) +3. `hermes gateway restart && hermes doctor` diff --git a/skill/hermes-client-onboarding/scripts/apply-core-config.sh b/skill/hermes-client-onboarding/scripts/apply-core-config.sh new file mode 100755 index 0000000..6df6cfb --- /dev/null +++ b/skill/hermes-client-onboarding/scripts/apply-core-config.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Apply OpenRouter + model + Telegram core config for Hermes client onboarding. +# Does not print secrets. Requires: hermes on PATH. +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + apply-core-config.sh \ + --openrouter-key KEY \ + --telegram-token TOKEN \ + --allowed-users ID1,ID2 \ + [--model deepseek/deepseek-v4-flash] \ + [--provider openrouter] + +Env fallbacks (if flags omitted): + OPENROUTER_API_KEY, TELEGRAM_BOT_TOKEN, TELEGRAM_ALLOWED_USERS +EOF +} + +MODEL="deepseek/deepseek-v4-flash" +PROVIDER="openrouter" +OR_KEY="${OPENROUTER_API_KEY:-}" +TG_TOKEN="${TELEGRAM_BOT_TOKEN:-}" +TG_USERS="${TELEGRAM_ALLOWED_USERS:-}" + +while [[ $# -gt 0 ]]; do + case "$1" in + --openrouter-key) OR_KEY="${2:-}"; shift 2 ;; + --telegram-token) TG_TOKEN="${2:-}"; shift 2 ;; + --allowed-users) TG_USERS="${2:-}"; shift 2 ;; + --model) MODEL="${2:-}"; shift 2 ;; + --provider) PROVIDER="${2:-}"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown arg: $1" >&2; usage; exit 2 ;; + esac +done + +if ! command -v hermes >/dev/null 2>&1; then + echo "error: hermes not found on PATH" >&2 + exit 1 +fi + +missing=0 +[[ -z "$OR_KEY" ]] && { echo "error: missing OpenRouter key" >&2; missing=1; } +[[ -z "$TG_TOKEN" ]] && { echo "error: missing Telegram bot token" >&2; missing=1; } +[[ -z "$TG_USERS" ]] && { echo "error: missing TELEGRAM_ALLOWED_USERS" >&2; missing=1; } +[[ "$missing" -eq 1 ]] && exit 1 + +# light validation (no secret echo) +if [[ ! "$OR_KEY" =~ ^sk-or- ]]; then + echo "warn: OpenRouter key does not start with sk-or- (continuing)" >&2 +fi +if [[ ! "$TG_USERS" =~ ^[0-9]+(,[0-9]+)*$ ]]; then + echo "error: allowed users must be numeric IDs, comma-separated" >&2 + exit 1 +fi + +hermes config set OPENROUTER_API_KEY "$OR_KEY" +hermes config set model.provider "$PROVIDER" +hermes config set model.default "$MODEL" +hermes config set TELEGRAM_BOT_TOKEN "$TG_TOKEN" +hermes config set TELEGRAM_ALLOWED_USERS "$TG_USERS" + +echo "ok: provider=$(hermes config get model.provider 2>/dev/null || echo "$PROVIDER")" +echo "ok: model=$(hermes config get model.default 2>/dev/null || echo "$MODEL")" +echo "ok: allowed_users=$TG_USERS" +echo "ok: secrets written (not displayed)"