Skip to content

feat(onboarding): add one-shot bootstrap and first-run setup wizard - #285

Closed
gabogabucho wants to merge 4 commits into
nesquena:masterfrom
gabogabucho:feat/onboarding-wizard
Closed

feat(onboarding): add one-shot bootstrap and first-run setup wizard#285
gabogabucho wants to merge 4 commits into
nesquena:masterfrom
gabogabucho:feat/onboarding-wizard

Conversation

@gabogabucho

Copy link
Copy Markdown
Contributor

This PR reduces the setup friction for Hermes WebUI by adding a one-shot bootstrap flow plus a first-run onboarding wizard that can perform the minimum real Hermes setup for common API-key-based providers.

Summary

  • add a repo-local bootstrap.py launcher that installs Hermes Agent if missing, starts the WebUI, waits for health, and opens the browser
  • add a blocking first-run onboarding wizard in the WebUI
  • make onboarding persist real Hermes provider config for common API-key flows instead of only storing WebUI defaults
  • localize the onboarding wizard so it works with the existing English/Spanish i18n system

What the onboarding wizard now supports

The wizard can now do the minimum setup needed to get a typical user chatting with Hermes from the WebUI:

  • detect/install Hermes Agent
  • choose a workspace
  • configure a real provider + model
  • save API key credentials into the active Hermes .env
  • save provider/model config into Hermes config.yaml
  • optionally set a WebUI password
  • drop the user into the normal app ready to chat
    Supported provider flows in this MVP:
  • OpenRouter
  • Anthropic
  • OpenAI
  • custom OpenAI-compatible endpoints

Why

Today Hermes + WebUI onboarding still feels too terminal-heavy for non-technical users. This change aims to let someone run one command, open the browser, follow a short wizard, and get to a working Hermes chat setup in minutes.

Important scope note

This PR intentionally does not try to recreate every Hermes CLI setup path in-browser.
Advanced/OAuth-driven flows such as:

  • Nous Portal
  • OpenAI Codex
  • GitHub Copilot
  • other terminal-first auth flows
    still remain outside this wizard and should continue through hermes model.

Files changed

File Change
bootstrap.py adds one-shot bootstrap launcher for install/start/open flow
start.sh delegates to bootstrap for a simpler entrypoint
api/onboarding.py adds onboarding status, readiness checks, and real provider config persistence
api/routes.py adds onboarding setup endpoint wiring
api/config.py persists onboarding completion flag
static/onboarding.js adds first-run wizard UI and real provider setup flow
static/index.html adds onboarding overlay markup
static/style.css adds onboarding styling
static/i18n.js adds onboarding copy in English and Spanish
README.md documents one-command onboarding flow
TESTING.md documents onboarding-related validation
ARCHITECTURE.md updates architecture notes for onboarding
ROADMAP.md updates roadmap notes
tests/test_onboarding_mvp.py tests onboarding API and real setup persistence
tests/test_onboarding_static.py tests onboarding static presence and i18n wiring

Validation

  • python -m py_compile "bootstrap.py" "api/onboarding.py" "api/routes.py" "api/config.py"
  • HERMES_WEBUI_PYTHON=python python -m pytest tests/test_onboarding_mvp.py tests/test_onboarding_static.py tests/test_sprint33.py tests/test_mobile_layout.py -v
  • HERMES_WEBUI_PYTHON=python python -m pytest tests/test_onboarding_mvp.py -v
  • HERMES_WEBUI_PYTHON=python python -m pytest tests/test_onboarding_static.py tests/test_spanish_locale.py -v
  • $env:HERMES_WEBUI_PYTHON=(Get-Command python).Source; python -m pytest tests/test_onboarding_mvp.py tests/test_onboarding_static.py tests/test_model_resolver.py tests/test_spanish_locale.py -v

Notes

  • native Windows is still intentionally out of scope for bootstrap; Linux/macOS/WSL2 are the supported paths
  • tools/toolsets are intentionally left on Hermes CLI defaults for this MVP
  • the wizard is now localized and consistent with the existing Spanish support

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Thanks for this — it's a well-structured PR that addresses a real friction point. The scope is right: get common API-key-based providers working through a browser wizard while leaving terminal-first/OAuth flows to hermes model. The localization and real provider config persistence are nice additions for a first pass.

A few things to look at before merging:


1. bootstrap.py security surface

Writing a launcher that installs Hermes and modifies .env / config.yaml on the user's machine is higher-stakes than typical UI code. A few things to verify:

  • API key handling — keys typed into the wizard should flow directly to .env and never be logged, stored in the browser's localStorage, or reflected in any API response. Make sure api/onboarding.py does not echo submitted keys back in any endpoint response body.
  • Path traversal — if bootstrap.py or api/onboarding.py accepts a workspace path from the wizard payload, validate it (reject .. components, restrict to user home subtree).
  • No shell interpolation — if bootstrap.py uses subprocess with shell strings that include user input, switch to list-form subprocess.run([...]) to prevent injection.

2. api/onboarding.py endpoint design

  • The endpoint that persists provider config should be a POST, not a GET, to avoid the config being submitted via a link-click or browser prefetch.
  • If the wizard writes to config.yaml, use a safe YAML serialisation path (not string interpolation) so a provider name with special chars can't corrupt the file.
  • The onboarding completion flag written by api/config.py should be checked server-side (not just client-side) so that refreshing the page doesn't re-show the wizard after it's been dismissed.

3. First-run detection race

If bootstrap.py starts the server and immediately opens the browser, there's a brief window where the server is up but the onboarding completion flag isn't yet written. The PR mentions waiting for health — make sure the health endpoint is available and the wizard overlay is shown based on a server-side flag query (not a stale client-side cookie) to avoid re-triggering the wizard on the first page load.


4. start.sh delegation

If start.sh now delegates to bootstrap.py, existing users who call start.sh directly will hit the install/check logic. Make sure bootstrap.py short-circuits cleanly when Hermes is already installed and the wizard has already completed — a no-op fast path is important so existing users don't notice any change.


5. Test coverage

The test files listed (test_onboarding_mvp.py, test_onboarding_static.py) look appropriate. A few specific cases worth adding if not already covered:

  • Wizard does not re-appear after onboarding completion flag is set
  • API key is not present in any onboarding status endpoint response
  • Workspace path with .. in it is rejected by the backend
  • bootstrap.py fast-path when Hermes is already configured (no re-install)

Minor

  • TESTING.md / ARCHITECTURE.md / ROADMAP.md updates look like good housekeeping — appreciated.
  • The start.shbootstrap.py delegation is a breaking change for anyone sourcing start.sh in scripts. A brief note in CHANGELOG.md or a comment at the top of start.sh explaining the delegation would help.

Overall the shape of this is right. The security items in #1 and #2 are the most important to verify — once those are confirmed clean (or addressed), this should be close to ready.

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Agent review — APPROVED WITH FIXES ✅

Reviewed by Hermes agent. Full diff, security audit, test suite, and browser QA completed. This is a solid onboarding MVP contribution — the feature works end-to-end and the code is well structured. A few issues needed fixing before this is merge-ready, all addressed on review branch pr-285-review.


Summary

First-run onboarding wizard with one-shot bootstrap launcher. The wizard detects the Hermes agent, walks the user through provider/model selection, saves real credentials into config.yaml and .env, and drops them into the main UI. Non-regression for existing users is confirmed — the wizard only shows when onboarding_completed is absent or false.


Diff

16 files (via clean cherry-pick onto current master — see stale-base note below), 3,004 insertions

Security — 3 issues found and fixed

[FIXED] /api/onboarding/setup not loopback-restricted. The endpoint writes API keys directly to ~/.hermes/.env and config.yaml. With auth disabled (the default for new installs), any network client could POST to it and overwrite credentials. The simulate endpoint and other sensitive endpoints have the same loopback guard — applied the same pattern. When auth is enabled the existing auth middleware provides equivalent protection.

[FIXED] Newline injection in _write_env_file. api_key values containing embedded \n would produce extra KEY=VALUE lines in the .env file. The str.strip() call only strips leading/trailing whitespace, not internal newlines. Added rejection of any api_key with embedded \n or \r, with a new test covering the attack path.

[FIXED] setup.unsupported_note inserted into innerHTML without esc(). Currently a hardcoded string so safe in practice, but the pattern is wrong — wrapped with esc() for defensive consistency.

[INFO] curl -fsSL <url> | bash in bootstrap.py. This is a known-safe pattern for installer scripts that are common in the ecosystem. The URL is a compile-time constant. Acceptable for a bootstrap launcher, noted in code.

Tests — 693 passed, 0 failed (up from the 679 baseline)

Four additional tests added on the review branch beyond what the PR ships:

  • test_onboarding_already_completed_status — regression guard ensuring the wizard does not reappear after completion
  • test_onboarding_setup_rejects_api_key_with_newline — covers the injection fix
  • Two fixes to the existing tests (see below)

Test isolation bugs found and fixed:

  1. test_onboarding_complete_preserves_other_settings set bot_name='Guide' with no cleanup. This contaminated test_sprint19::test_login_page_served and test_sprint27::test_settings_default_bot_name when the full suite ran. Fixed by using send_key (a safe enum field) as the preservation check instead, with a try/finally cleanup that always restores the default.

  2. tests/test_onboarding_mvp.py imported TEST_STATE_DIR from conftest at module-level. api.config's init_profile_state() resets HERMES_HOME at import time, so when conftest is re-imported as a plain module (which Python does when a test file does from conftest import X), it computes the wrong TEST_STATE_DIR. Fixed by querying _server_hermes_home() from the live server's /api/onboarding/status response instead — the server always knows its own hermes home.

  3. test_regressions.py::test_server_delete_invalidates_index searches for 'api/session/delete' with single quotes. The PR reformats routes.py to use double quotes throughout. Fixed to accept both styles.

Code correctness — clean

  • bootstrap.py: all subprocess calls use list-form args (no shell injection). Platform checks are correct. Discovery logic handles all common install paths.
  • api/onboarding.py: provider enum validation, base_url scheme validation, model prefix normalization, reload_config() after write. Well-structured.
  • static/onboarding.js: esc() used throughout for server-supplied data in innerHTML. Step navigation, form state, and notice rendering are clean.
  • static/boot.js: loadOnboardingWizard() correctly returns early when completed=true — existing users are not affected.
  • static/i18n.js: 68 onboarding keys in both en and es — full parity confirmed.
  • get_onboarding_status() returns a minimal settings subset (default_model, bot_name, password_enabled). The test for "preserves other settings" was checking done["settings"]["send_key"] which isn't in that response — fixed on the review branch to use a separate GET /api/settings call.

Stale-base note

The PR branch was cut from v0.47.1 (current master is v0.48.2). The direct diff showed:

  • gateway_watcher.py, test_gateway_sync.py, test_provider_mismatch.py appearing as deleted
  • startGatewaySSE, _checkProviderMismatch, source_tag, and inlineMd table rendering all regressed

All genuine feature commits were cherry-picked onto current master on the review branch. None of the stale-base deletions are present. The fix count is identical regardless of how the PR is eventually merged — the review branch is the authoritative version.


Browser QA — PASS

  • Fresh install: onboarding wizard appears immediately on first load ✅
  • Step navigation (System check → Provider setup): correct ✅
  • Provider dropdown shows OpenRouter, Anthropic, OpenAI, Custom ✅
  • After /api/onboarding/complete: wizard does not reappear on page reload ✅
  • Main 3-panel layout loads correctly after onboarding ✅
  • Settings panel opens cleanly — zero JS errors throughout ✅

Review branch: pr-285-review

Contains the 3 original commits plus one fix commit. All 693 tests pass. The review branch is what should be merged (or its changes applied to the contributor's branch).

Plan: this looks good to merge. We will apply these fixes to the branch and merge. No action needed from you unless you want to revise anything.

@nesquena

Copy link
Copy Markdown
Owner

Full Independent Review: PR #285 — onboarding wizard (review branch pr-285-review)

Reviewed all 16 files, ran full test suite, read issue #283 and all comments. Pushed one additional fix to the review branch.

Security Audit

API key handling — CLEAN:

  • _write_env_file() rejects embedded newlines/carriage returns (.env injection prevention) — line 104
  • Keys are written to ~/.hermes/.env with standard KEY=value format
  • Keys are never returned in API responses (the onboarding status endpoint returns provider_ready: true/false, not the key itself)
  • Loopback restriction: /api/onboarding/setup only accepts requests from 127.0.0.1 when auth is not enabled — correct for an endpoint that writes credentials to disk

bootstrap.py — CLEAN:

  • curl | bash for hermes-agent install uses a hardcoded URL from the official NousResearch repo — not user-controllable
  • All subprocess calls use list args (no shell injection)
  • Python venv creation uses stdlib venv.EnvBuilder — safe

onboarding.js — CLEAN:

  • All user data (provider IDs, paths, keys, model names) go through esc() before innerHTML insertion
  • i18n t() values are static strings from LOCALES — not user-controllable
  • Input fields use standard <input> elements with oninput handlers that store to ONBOARDING.form — no DOM injection

Route handler — CLEAN:

  • ValueError → 400, RuntimeError → 500 — correct error classification
  • Body is parsed via existing read_body() (20MB limit intact)

Code Review

api/onboarding.py — Well-structured:

  • Clear separation: get_onboarding_status() (read-only), apply_onboarding_setup() (writes config), complete_onboarding() (sets flag)
  • _SUPPORTED_PROVIDER_SETUPS dict cleanly defines supported providers with env vars, default models, and base URL requirements
  • _status_from_runtime() computes state machine: needs_providerprovider_incompleteready (or agent_unavailable)
  • _normalize_model_for_provider() strips redundant provider prefixes — good

static/onboarding.js — Clean wizard implementation:

  • 5-step flow: system → setup → workspace → password → finish
  • Step navigation with back/next, validation before advancing
  • Provider selection dynamically updates model list and base URL field
  • API key submitted to /api/onboarding/setup which persists to real Hermes config

bootstrap.py — Solid launcher:

  • Discovers agent dir, Python interpreter, creates venv if needed
  • Installs hermes-agent if missing (user-initiated, not automatic)
  • Starts server, waits for health check, opens browser
  • Graceful error handling throughout

i18n — All strings localized in en + es for the onboarding flow.

Fix Pushed

tests/test_onboarding_mvp.py — 4 tests were failing with 500 errors

The /api/onboarding/setup endpoint needs PyYAML to write config.yaml. When hermes-agent's venv isn't installed, the test server falls back to system Python which lacks pyyaml, causing RuntimeError("PyYAML is required") → 500 instead of the expected 200/400.

Added @_needs_yaml skip marker to 5 tests that hit the setup endpoint. They now skip gracefully in environments without pyyaml and run normally when the full agent venv is available.

Test Results

After fix: 637 passed, 0 new failures, 47 skipped (42 agent-dependent + 5 yaml-dependent). The 1 pre-existing failure (test_security_redaction) is from unmerged PR #243.

Agent Review Verification

The agent's fix commit (5a700d9) addressed items from the first review. I verified the review branch includes:

  • Original 3 commits from @gabogabucho
  • Agent's hardening commit (loopback restriction, i18n fixes, etc.)
  • My test skip fix (b93b90d)

Summary

Item Status
Security Clean — API key injection prevented, loopback restriction, esc() on all user data
Bootstrap Safe — hardcoded URLs, list args for subprocess, venv isolation
Tests (9 onboarding) 4 pass directly, 5 skip without pyyaml (all pass with agent venv)
i18n en + es complete
Architecture Clean: read-only status endpoint + write setup endpoint + completion flag

Ready to merge. The review branch (pr-285-review) has all fixes applied.

nesquena-hermes pushed a commit that referenced this pull request Apr 12, 2026
nesquena-hermes pushed a commit that referenced this pull request Apr 12, 2026
…285)

Adds a bootstrap launcher and a blocking first-run onboarding wizard that guides
new users through minimum Hermes setup from the browser UI.

Supported provider flows: OpenRouter, Anthropic, OpenAI, custom OpenAI-compatible.
OAuth/terminal-first flows remain via 'hermes model'.

Security hardening applied during review:
- /api/onboarding/setup restricted to loopback when auth disabled
- Newline injection guard in _write_env_file
- esc() on setup.unsupported_note in onboarding.js
- Test isolation fix (send_key instead of bot_name in contamination test)
- Skip markers for PyYAML-dependent tests in agent-less environments

Tests: 693 passed (up from 679)

Co-authored-by: gabogabucho <gabogabucho@gmail.com>
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Closing this PR to reopen from the review branch — the original contributor's branch became conflicted after docs were committed to master. All code from this PR has been carefully reviewed and integrated into pr-285-final (based on current master). Reopening as a clean PR to complete the merge.

Thanks again @gabogabucho for the excellent contribution!

nesquena-hermes added a commit that referenced this pull request Apr 12, 2026
…285)

Adds a bootstrap launcher and a blocking first-run onboarding wizard that guides
new users through minimum Hermes setup from the browser UI.

Supported provider flows: OpenRouter, Anthropic, OpenAI, custom OpenAI-compatible.
OAuth/terminal-first flows remain via 'hermes model'.

Security hardening applied during review:
- /api/onboarding/setup restricted to loopback when auth disabled
- Newline injection guard in _write_env_file
- esc() on setup.unsupported_note in onboarding.js
- Test isolation fix (send_key instead of bot_name in contamination test)
- Skip markers for PyYAML-dependent tests in agent-less environments

Tests: 693 passed (up from 679)

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: gabogabucho <gabogabucho@gmail.com>
nesquena-hermes pushed a commit that referenced this pull request Apr 12, 2026
…ries

All three PRs now merged:
- #285: first-run onboarding wizard
- #287: self-update git pull diagnostics
- #289: skip flaky redaction test in agent-less envs

Final test count: 697 (up from 679)
nesquena-hermes added a commit that referenced this pull request Apr 12, 2026
All three PRs now merged:
- #285: first-run onboarding wizard
- #287: self-update git pull diagnostics
- #289: skip flaky redaction test in agent-less envs

Final test count: 697 (up from 679)

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request Apr 25, 2026
…esquena#285)

Adds a bootstrap launcher and a blocking first-run onboarding wizard that guides
new users through minimum Hermes setup from the browser UI.

Supported provider flows: OpenRouter, Anthropic, OpenAI, custom OpenAI-compatible.
OAuth/terminal-first flows remain via 'hermes model'.

Security hardening applied during review:
- /api/onboarding/setup restricted to loopback when auth disabled
- Newline injection guard in _write_env_file
- esc() on setup.unsupported_note in onboarding.js
- Test isolation fix (send_key instead of bot_name in contamination test)
- Skip markers for PyYAML-dependent tests in agent-less environments

Tests: 693 passed (up from 679)

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: gabogabucho <gabogabucho@gmail.com>
JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request Apr 25, 2026
… entries

All three PRs now merged:
- nesquena#285: first-run onboarding wizard
- nesquena#287: self-update git pull diagnostics
- nesquena#289: skip flaky redaction test in agent-less envs

Final test count: 697 (up from 679)

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
…esquena#285)

Adds a bootstrap launcher and a blocking first-run onboarding wizard that guides
new users through minimum Hermes setup from the browser UI.

Supported provider flows: OpenRouter, Anthropic, OpenAI, custom OpenAI-compatible.
OAuth/terminal-first flows remain via 'hermes model'.

Security hardening applied during review:
- /api/onboarding/setup restricted to loopback when auth disabled
- Newline injection guard in _write_env_file
- esc() on setup.unsupported_note in onboarding.js
- Test isolation fix (send_key instead of bot_name in contamination test)
- Skip markers for PyYAML-dependent tests in agent-less environments

Tests: 693 passed (up from 679)

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: gabogabucho <gabogabucho@gmail.com>
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
… entries

All three PRs now merged:
- nesquena#285: first-run onboarding wizard
- nesquena#287: self-update git pull diagnostics
- nesquena#289: skip flaky redaction test in agent-less envs

Final test count: 697 (up from 679)

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants