Adds the ability to manage multiple Hermes installations — local and
remote over SSH — from the same Scarf app, each in its own window.
Architecture:
- ServerContext value type carries per-server identity + paths through
every VM and service. ContentView routes serverContext into each
feature view's init; all 22 routed views thread it through to their
@State VMs.
- ServerTransport protocol with LocalTransport (FileManager/Process/
FSEvents) and SSHTransport (system ssh + scp + ControlMaster).
Services were ported from direct Foundation I/O to transport-routed
helpers so the same code runs against local or remote.
- WindowGroup(for: ServerID.self) gives each window its own
AppCoordinator + HermesFileWatcher + ChatViewModel. File menu has
Open Server commands with keyboard shortcuts (⌘1..⌘9). MenuBarExtra
fans out per-server with start/stop/restart controls.
- ServerRegistry persists connections to ~/Library/Application
Support/scarf/servers.json. Add Server sheet probes the remote with
ssh -v to capture the full handshake on failure.
- Connection-status pill in remote-window toolbars with silent reconnect
(3s retry on first failure, escalate to red after 2 consecutive),
known-hosts-mismatch + ssh-add hint cards with copy buttons.
Concurrency / UX hardening (the parts learned the hard way during
dogfooding — captured in the feedback memory):
- ServerContext exposes context.readText / readData / writeText /
fileExists / runHermes / openInLocalEditor as the canonical I/O
surface. Every VM uses these; never raw FileManager / Process() /
NSWorkspace.open with a Hermes path.
- SSHTransport.remotePathArg rewrites ~/foo to "$HOME/foo" so paths
expand correctly inside the sh -c command we build (POSIX shells
don't expand ~ inside any quotes).
- Heavy VM load() methods detach to a background task and commit
results back via MainActor.run, so synchronous ssh round-trips don't
beach-ball the UI. Applied to Dashboard, Memory, Settings,
MCPServers, Cron, Plugins, Personalities, QuickCommands, Skills,
Gateway, Health, CredentialPools.
- LoadingOverlay modifier shows a spinner over empty/stale section
content during background reloads.
- enrichedShellEnv (zsh -l -i probe, up to 8s) is now warmed at app
launch off-main so first MainActor caller doesn't block.
- Drop the file watcher's 5s heartbeat — FSEvents covers real changes
and the heartbeat was triggering wasted reloads across every
subscribing view.
Chat polish:
- ChatViewModel.hermesBinaryExists is a stored bool probed once at
init, not a sync transport call evaluated on every body re-render.
- MessageGroupView identifies assistant bubbles by array offset rather
than message.id, so the streaming → finalized id transition no
longer destroys + recreates the bubble.
- Static scroll anchor in RichChatMessageList prevents two onChange
handlers from racing on isWorking flips.
Branch state: feature complete, in active dogfooding. Plan + per-phase
status live at ~/.claude/plans/we-developed-an-application-harmonic-stroustrup.md;
the four hard-won transport/concurrency rules are saved in the
ServerContext-pattern feedback memory for future sessions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The script was creating the GitHub release before pushing main, which
caused gh to auto-create the v<VERSION> tag at the then-current origin
HEAD (one commit behind the bump, since main hadn't been pushed yet).
The subsequent `git push origin v<VERSION>` was then rejected as
non-fast-forward, leaving the remote tag pointing at the wrong commit.
Caught during v1.6.2. The remote tag for v1.6.2 was force-corrected to
12610fa (the bump commit); the release artifacts themselves were always
correct.
New order: push main → tag main locally → push tag → gh release create.
Gh will now find the tag already on origin and attach to the right
commit. Non-destructive: a retry-safe release can always be resumed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CLAUDE.md's release-notes convention says "write them to
releases/v<version>/RELEASE_NOTES.md BEFORE running the script" — but
the script's git-clean preflight rejected any working-tree state
including that exact file as untracked. Chicken-and-egg: you couldn't
follow the documented flow.
Preflight now whitelists releases/v<VERSION>/RELEASE_NOTES.md as the one
allowed untracked path. Everything else still fails the check.
Caught while running v1.6.2.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Stops the release script's git-clean preflight from tripping on the
zips + appcast-entry.xml that every release run produces under
releases/v<VERSION>/. GitHub Releases hosts the actual downloads; there's
no reason to commit ~30 MB of binaries per release into git history.
RELEASE_NOTES.md stays tracked — it's committed as part of the version
bump by the release script.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a Releases section so future Claude sessions (and teammates) don't
have to rediscover the release workflow. Documents:
- The single entry point: `./scripts/release.sh <ver> [--draft]`
- What the script does end-to-end
- The release notes convention (write them before running)
- A handful of canonical prompts the user can type
- Pointers to deeper prerequisite docs (README, script header)
Deliberately brief — detail lives in README and the personal auto-memory
at reference_release_process.md. CLAUDE.md's job here is just to make
the entry point discoverable on session start.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The orange "No AI provider credentials detected" banner was firing on the
Chat tab whenever no session was selected, even for users whose
credentials were configured and working. The banner only disappeared
when a session started — not because credentials were actually found,
but because the banner's `!hasActiveProcess` gate flipped to false once
ACP launched.
Root cause: `HermesFileService.hasAnyAICredential()` inspected only the
shell environment and `~/.hermes/.env`, while Hermes itself resolves
credentials from two additional places Scarf had never learned about:
- `~/.hermes/auth.json` — the Credential Pools file written by the
Configure → Credential Pools UI (the blessed v1.6 flow)
- `~/.hermes/config.yaml` — embedded `api_key:` under auxiliary.<task>
and delegation
The preflight now checks all four locations. For auth.json we parse the
JSON and look for any `credential_pool.<provider>[*].access_token` that
is non-empty. For config.yaml we line-scan for `api_key:` leaves with a
non-empty value, matching the defensive style of the existing .env
scanner (no YAML parser needed in a nonisolated function).
Also updated the banner subtitle to point users at Credential Pools
before .env, since the former is the blessed in-app flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each release now produces two distribution zips:
- Scarf-vX.X.X-Universal.zip (arm64 + x86_64, recommended)
- Scarf-vX.X.X-ARM64.zip (arm64 only, ~14% smaller)
Both are independently archived, exported with Developer ID, notarized,
and stapled via a new build_variant helper. The appcast still points at
the Universal zip since it works on all supported macs; ARM64 is an
alternative manual download for Apple Silicon users who want the smaller
file.
README updated to list both variants.
Prompted by the v1.6.1 release shipping only Universal; the ARM64 zip
for v1.6.1 was produced ad-hoc and uploaded to the existing release.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Drafts skip the appcast push and main tag, so a draft release won't
show up in users' Sparkle update feed and v1.6.0 stays "latest" until
explicitly promoted. The signed appcast entry is saved to the release
dir for later manual promotion.
Also adds release notes file convention: releases/v<VERSION>/RELEASE_NOTES.md
is auto-included in the version-bump commit and used as the GitHub
release body.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Xcode exports the bundle as scarf.app because PRODUCT_NAME = $TARGET_NAME
and the target is lowercase "scarf". Users expect Scarf.app in their
/Applications folder. Renaming the bundle wrapper preserves the
signature (codesign signs contents, not the wrapper directory name).
Caught during a build+sign+verify dry run before the first notarized
release.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A fresh-install user reported Scarf chat only worked while `hermes chat`
was also running in Terminal. ACP connected successfully but sending a
message errored. `~/.hermes/logs/errors.log` showed the real cause:
RuntimeError: No Anthropic credentials found. Set ANTHROPIC_TOKEN or
ANTHROPIC_API_KEY, run 'claude setup-token', or authenticate with
'claude /login'.
The terminal workaround masked the bug because the terminal-launched
`hermes` inherits the user's shell env (ANTHROPIC_* exports, Keychain
session) while a Finder/Dock-launched Scarf subprocess does not.
Scarf's previous PATH-only enrichment (commit b2a29ab) fixed binary
discovery but not credential propagation.
Five changes:
1. Propagate credential env vars from the login shell.
HermesFileService.enrichedEnvironment() now harvests a conservative
allowlist of AI-provider keys (ANTHROPIC_API_KEY/TOKEN/BASE_URL,
OPENAI_*, OPENROUTER_*, GEMINI/GOOGLE/GROQ/MISTRAL/XAI API keys,
CLAUDE_CODE_OAUTH_TOKEN) alongside PATH. Uses one `zsh` probe with
null-delimited `printf` so values with newlines survive, cached for
the process lifetime.
2. Two-attempt shell probe catches nvm/asdf/mise PATH.
Previous `zsh -l` missed `.zshrc`-exported PATH (nvm). New probe
first tries `zsh -l -i` (login + interactive, sources .zshrc) with
prompt frameworks defanged (TERM=dumb, empty PS1/PROMPT,
POWERLEVEL9K_INSTANT_PROMPT=off, STARSHIP_DISABLE=1,
ZSH_DISABLE_COMPFIX=true) and a 5s timeout; falls back to `zsh -l`
with 3s; finally to hardcoded defaults.
3. Resolve `hermes` binary across install locations.
HermesPaths.hermesBinary is now computed, walking pipx
(~/.local/bin), Apple Silicon brew (/opt/homebrew/bin), Intel brew
/ manual (/usr/local/bin), and ~/.hermes/bin. Returns the first
executable match or the pipx default for "Expected at …"
diagnostics. All 10+ callsites (ACPClient, scarfApp, Health /
Gateway / Tools / Sessions / QuickCommands / Personalities /
Settings / WhatsAppSetup / OAuthFlow / CredentialPools
ViewModels) auto-migrate with zero edits.
HermesFileService.hermesBinaryPath() shares the same candidate
list as the source of truth.
4. Surface the real failure in the chat UI.
ACPClient keeps a 50-line ring buffer of subprocess stderr
(previously only sent to os_log). New ACPErrorHint.classify pattern-
matches the common fresh-install failures — "No credentials found",
"No such file or directory: 'npx'", rate-limit — and returns a short
human hint. ChatView gains an errorBanner between toolbar and chat
area showing the hint + raw message + a "Show details" disclosure
with the stderr tail in a selectable monospaced view, plus a
clipboard-copy button.
5. Preflight credential check.
HermesFileService.hasAnyAICredential() scans the enriched env and
~/.hermes/.env for any known provider key. ChatViewModel exposes
`missingCredentials`; the banner becomes a pre-emptive warning
("No AI provider credentials detected — add ANTHROPIC_API_KEY to
~/.hermes/.env or your shell profile") before the user even hits
Send. HermesFileWatcher already watches ~/.hermes/.env, so edits
re-trigger preflight automatically.
Incidental cleanup: recordACPFailure(_:client:context:) folds the
per-site `logger.error` calls, removing three `_ = msg` suppressions.
Dead `enrichedPath` alias removed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds Sparkle 2 auto-updates and a local release script that produces
signed, notarized, stapled builds for GitHub distribution. App Store
submission was rejected because Scarf spawns the user-installed hermes
binary and reads ~/.hermes/ directly — both forbidden by App Sandbox —
so we commit to the GitHub-release path properly.
- Sparkle SPM dep wired into the app target (link-only; hardened-runtime
entitlement disable-library-validation lets Sparkle load at runtime).
- Tracked Info.plist with SUFeedURL, SUPublicEDKey, and daily check
interval; replaces the auto-generated plist so Sparkle keys live in
version control rather than pbxproj INFOPLIST_KEY_* noise.
- UpdaterService wraps SPUStandardUpdaterController and is injected via
.environment(). Menu bar, standard app menu (CommandGroup after
.appInfo), and a new Updates section in Settings → General each call
updater.checkForUpdates().
- scripts/release.sh runs the full pipeline: version bump → universal
archive → Developer ID export → notarytool submit (keychain profile
scarf-notary) → staple → appcast EdDSA sign → gh-pages push → gh
release → tag. scripts/ExportOptions.plist pins manual Developer ID
signing for team 3Q6X2L86C4.
- README: removes the right-click-Open workaround (notarized builds
don't need it), notes Sparkle, adds a Releases section describing
the pipeline and signing prerequisites.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reorganize the Features section to match the app's sidebar (Monitor, Interact,
Configure, Manage, Project Dashboards, System) so readers find features the
same way they find them in the app. Add a "What's New in 1.6" callout with
links to the release notes.
Binaries: ARM64 (15 MB) and Universal (19 MB). Both signed with the Apple
Development identity (Team 3Q6X2L86C4). Universal contains both arm64 and
x86_64 slices verified with lipo.
Major expansion of Scarf's Hermes platform coverage. Settings is now a 10-tab
layout exposing ~60 previously hidden config fields. A new "Configure" sidebar
section groups per-platform setup, personality management, quick commands,
credential pools, plugins, webhooks, and profile switching.
## Highlights
- **Platforms feature** — Native GUI setup for all 13 messaging platforms
(Telegram, Discord, Slack, WhatsApp, Signal, Email, Matrix, Mattermost,
Feishu, iMessage, Home Assistant, Webhook, CLI). Per-platform forms write
credentials to ~/.hermes/.env and behavior toggles to ~/.hermes/config.yaml.
WhatsApp and Signal use an inline SwiftTerm terminal for QR/link pairing.
- **Credential Pools** — Provider-aware add/remove with proper type handling.
OAuth flow uses Process + pipes to extract the authorization URL, open the
browser explicitly, and accept the code via a form field. Fixes the Anthropic
OAuth failure where the code had nowhere to be entered.
- **Model Picker** — Hierarchical provider -> model picker backed by
~/.hermes/models_dev_cache.json (111 providers, every major model). Used in
Settings -> General and Delegation. "Custom..." escape hatch for unlisted IDs.
- **Settings as tabs** — 10 tabs (General, Display, Agent, Terminal, Browser,
Voice, Memory, Aux Models, Security, Advanced). HermesConfig grew from 32 to
~90 fields via grouped sub-structs. All new fields round-trip through
`hermes config set`.
- **Extended existing features** — Cron (create/edit/pause/resume/run-now/
delete), Skills (Browse Hub + Updates tabs), Health (run `hermes dump` and
`hermes debug share` with confirmation dialog), Sessions (rename/delete/
export/export-all).
## Bug fixes
- Tools platform picker showed only CLI (was reading a nonexistent
`platform_toolsets:` YAML section). Now enumerates KnownPlatforms.all with
live connectivity dots from gateway_state.json.
- Credentials add with --api-key was triggering OAuth for providers like
Anthropic because --type was missing. Now always passes --type api-key.
- Remove-by-index used 0-based indexing; hermes CLI expects 1-based. Fixed.
- Various CLI parser fragility issues (plugins, profiles, skills hub, webhooks)
replaced with structured file reads or proper box-drawn table parsers.
## New core services
- HermesEnvService — reads/writes ~/.hermes/.env atomically, preserves
comments, commented-out keys get enabled in-place on save, values with
spaces/specials get quoted, unset commented out (non-destructive).
- ModelCatalogService — decodes the models.dev cache into typed providers and
models with context/cost/release-date metadata.
- OAuthFlowController — manages the OAuth Process subprocess: extracts the
auth URL via regex, opens the browser, pipes the code back via stdin,
detects success/failure markers in output.
## New sidebar structure
Monitor / Projects / Interact / **Configure (new)** / Manage
The Configure section gathers the setup-style features that used to require
the CLI: Platforms, Personalities, Quick Commands, Credential Pools, Plugins,
Webhooks, Profiles.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two related bugs surfaced when testing MCP servers that spawn npx, node,
python, etc. from Homebrew/nvm/asdf/mise installs.
1. MCP test reported success even when the connection failed.
`hermes mcp test <server>` exits 0 even when the inner connection
fails — it prints the error to stdout instead. Scarf trusted the
exit code and rendered a green checkmark while the output said
"✗ Connection failed: [Errno 2] No such file or directory: 'npx'".
Fix: also scan output for ✗, "Connection failed", "No such file or
directory", and "Error:" markers.
2. .app launches start with a minimal PATH that excludes Homebrew.
When Scarf is launched from Finder/Dock, ProcessInfo's PATH is
`/usr/bin:/bin:/usr/sbin:/sbin` — no /opt/homebrew/bin, no
/usr/local/bin, no nvm/asdf/mise shims. Hermes inherits this and
can't find npx/node/python when spawning MCP server subprocesses.
Fix: query the user's login shell PATH once via `/bin/zsh -lc 'echo
$PATH'`, cache it on HermesFileService, and inject it into both
`runHermesCLI` and the ACP subprocess. Falls back to a sane default
covering both Apple Silicon and Intel Homebrew if zsh query fails.
Bumps version to 1.5.8 (build 10). Includes signed Universal + ARM64
binaries.
Fixes a bug where adding a second MCP server caused the first to disappear
from the list view, and any args containing YAML reserved characters (e.g.
"@modelcontextprotocol/server-fetch") corrupted the config file.
Three root causes in HermesFileService MCP YAML patching:
1. extractMCPBlock extended through trailing comments to EOF when
mcp_servers was the last top-level key in config.yaml. Trailing
comments became part of the "block", so subsequent inserts landed
at end-of-file rather than inside the entry.
2. patchMCPServerField's entry boundary similarly absorbed trailing
blanks/comments, making the entry "own" everything until the next
sibling — or until EOF for the last entry.
3. yamlScalar did not quote values starting with YAML reserved
indicators (@ * & ? | > ! % , [ ] { } < ` ' "). Args like
"@modelcontextprotocol/server-fetch" were written bare, producing
invalid YAML that broke subsequent reads/writes.
Fix: trim trailing blanks/comments off both the block and the entry
in the locator/extractor; quote any scalar starting with a reserved
first character.
Bumps version to 1.5.7 (build 9). Includes signed Universal + ARM64
binaries.
Note: users with an already-corrupted ~/.hermes/config.yaml from the
1.5.6 bug should manually clean up their mcp_servers block (delete the
orphan args at end of file) before upgrading. New writes will be clean.
The first 1.5.6 zips contained `linker-signed` bundles with no Sealed
Resources, plus a stray nested scarf.app from a case-insensitive cp.
macOS Gatekeeper rejected the ARM64 download as "damaged"; the
Universal one ran only because the user had already trusted it.
Now both bundles are properly ad-hoc-signed (`Sealed Resources
version=2`) with the hardened runtime preserved. Sizes dropped
significantly (Universal 33MB→16MB, ARM64 27MB→13MB) because the
nested junk is gone.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Includes the MCP Servers management UI shipped in 219bca2:
- Add via curated presets (GitHub, Linear, Notion, Sentry, Stripe, …)
or fully custom (stdio command + args, or HTTP URL with bearer auth)
- Per-server detail view: enable/disable, env + headers editor,
tool include/exclude filters, resources/prompts toggles, request
and connect timeouts, OAuth token detection + clearing
- One-click "Test Connection" runs `hermes mcp test` and surfaces
the discovered tool list
- Gateway-restart banner after config changes that need a reload
README updated with the MCP Servers section, the new MCPServers/
feature module entry, and the `hermes mcp` + `mcp-tokens/` entries
in the Data Sources table.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ship Hermes v0.9.0 compatibility plus new features (log component
filter, session pill, Fast Mode, Backup/Restore, iMessage, /compress,
Discord threads). README lists both universal and ARM64 downloads.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Log parser: session-ID tag in v0.9.0 log format is now an optional
capture group; session pill renders inline and tap-filters the view.
- Logs: component filter (Gateway/Agent/Tools/CLI/Cron) and bounded
logger column with middle truncation.
- Gateway stop: uses `hermes gateway stop` CLI (v0.9.0's launchctl
bootout fix) with SIGTERM as fallback.
- HermesConfig: new keys for Fast Mode (service_tier), gateway notify
interval, force IPv4, context engine, interim assistant messages,
and Honcho eager init (camelCase per PR #6995).
- Settings: new Performance, Network, Advanced, and Backup & Restore
sections that call `hermes backup` / `hermes import` off the main
actor; robust zip-path extraction via regex.
- Platforms: iMessage (BlueBubbles) added to KnownPlatforms and
icon map.
- Cron: Discord thread delivery (`discord:chat:thread`) renders as
"Discord thread X in Y".
- Chat: `/compress <focus>` button appears when ACP advertises the
command; optional focus sheet sends through existing prompt path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add cumulative token tracking from ACP prompt results with fallback
display when DB has no data yet. Improve scroll-to-bottom reliability
with an external trigger for "Return to Active Session" and onAppear
auto-scroll. Show per-session cost in the dashboard session list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Synchronous Process.run()/waitUntilExit() calls on the main thread blocked
SwiftUI's render loop, causing toggle controls to appear as solid blue
rectangles instead of proper switches. All hermes subprocess and file I/O
calls are now async via Task.detached, toggle uses optimistic state update
for immediate visual feedback, and pipe file handles are properly closed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sessions were silently dying and losing chat history because:
- Pipe write errors (EPIPE) were completely undetected — broken pipe
writes via Task.detached { handle.write() } failed silently, leaving
the app unaware the subprocess had crashed
- Reconnection fell back to newSession() when loadSession() failed,
creating a blank session and permanently losing all conversation context
- No message reconciliation after reconnect — DB-persisted messages
were never re-fetched, so the UI stayed stale/incomplete
- Keepalive sent bare "\n" which caused json.loads("") parse errors
in the ACP library every 30 seconds, destabilizing the connection
- TERM=xterm-256color was set on a pipe-based subprocess, risking
terminal escape sequence pollution in the JSON-RPC stream
Fixes:
- Replace FileHandle.write() with POSIX Darwin.write() + SIGPIPE
suppression for immediate broken-pipe detection at all write sites
- Send valid JSON-RPC notification {"jsonrpc":"2.0","method":"$/ping"}
as keepalive instead of bare newlines
- Never fall back to newSession() during reconnection — try
resumeSession then loadSession, fail visibly if both fail
- Add reconcileWithDB() to merge DB-persisted messages with local
state after successful reconnection
- Finalize streaming messages immediately on disconnect so partial
content is preserved before reconnection begins
- Use SIGINT instead of SIGTERM for graceful Python subprocess shutdown
- Remove TERM env var from ACP subprocess environment
- Consolidate disconnect cleanup into single idempotent method
- Add isHandlingDisconnect guard against double-handling
- Increase reconnect attempts from 3 to 5 with capped backoff
- Add "Reconnect" button to toolbar error state
Also: bump version to 1.5.1, set deployment target to macOS 14.6
(Sonoma), and update README with rich chat/ACP features, process
controls, skill editing, and corrected system requirements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement a rich chat interface powered by the Hermes ACP (Agent
Communication Protocol) over JSON-RPC stdio pipes, with comprehensive
connection stability:
- ACPClient actor: manages hermes acp subprocess lifecycle, JSON-RPC
transport, event streaming via AsyncStream, and session management
- ACPMessages: full event parsing for message chunks, thought chunks,
tool calls, permission requests, and prompt completion
- RichChatViewModel: streaming message display with live updates,
tool result rendering, and message grouping
- ChatViewModel: ACP session orchestration, auto-start on first
message, and terminal mode fallback
Connection stability fixes:
- Non-blocking pipe writes via Task.detached to prevent actor deadlock
- Read loop cleanup (handleReadLoopEnded) finishes event stream and
fails pending requests on EOF instead of hanging silently
- 30s request timeouts on control messages via watchdog Task pattern
- Keepalive: writes \n to stdin every 30s to detect dead processes
via EPIPE before the next user action
- Health monitor: polls process.isRunning every 5s as belt-and-suspenders
- Auto-reconnect: retries up to 3 times with exponential backoff
(1s/2s/4s), restores session, only shows error after all retries fail
- connectionLost event displays system message in chat on failure
- Proper stderr pipe management: stored task reference, closed in stop()
- Idempotent cleanup across handleReadLoopEnded, handleTermination,
and handleConnectionDied via actor serialization and nil guards
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace inline AttributedString(markdown:) in RichMessageBubble with
the shared MarkdownContentView for consistent styled rendering of
headers, lists, blockquotes, and inline formatting in chat messages.
Code blocks continue to use CodeBlockView with its copy button.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add rich chat interface with iMessage-style message bubbles, terminal
toggle, session info bar, code block rendering with copy button, and
tool call cards. Supports both terminal and rich chat display modes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add tool result display to the Activity detail pane. When selecting a
tool call, the inspector now shows Arguments → Output → Assistant
Message, giving full visibility into what was requested, what came back,
and how the assistant interpreted it.
- Add fetchToolResult(callId:) query to HermesDataService
- Fetch tool result on entry selection in ActivityViewModel
- Display output in styled monospaced box in detail pane
- Render assistant message with MarkdownContentView
Closes#12
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The config.yaml uses YAML empty string literal (provider: '') which the
parser reads as the literal string '' rather than an empty string. Strip
surrounding quotes before checking so '' and "" are treated as empty.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a custom MarkdownContentView that renders markdown with visual
styling — large headers, styled code blocks with language labels,
bullet and numbered lists, blockquotes with colored borders, and
horizontal rules. YAML frontmatter in skill files is hidden.
Markdown rendering added to:
- Memory view (MEMORY.md, USER.md) with live preview in editor
- Skills view (.md files) with new edit/save capability
- Session messages (assistant responses)
- Dashboard text widgets
Other changes:
- Shared MarkdownRenderer utility for inline formatting
- Split-pane editors (raw markdown left, live preview right)
- saveSkillContent() in HermesFileService with path validation
- Line breaks preserved in non-markdown content (Key: Value format)
Closes#11
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add hermesPID() and stopHermes() to HermesFileService for process
signal management via SIGTERM
- Add process control bar to Health view with running status, PID
display, and Start/Stop/Restart buttons
- Add Start/Stop/Restart Hermes quick actions to menu bar
- Start launches gateway, stop sends SIGTERM, restart combines both
Closes#10
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce a new structured chat view as an alternative to the SwiftTerm
terminal. Users can switch between raw terminal and rich chat modes via a
segmented picker in the toolbar. The rich view polls state.db for messages
and renders them as conversation bubbles with markdown, code blocks,
expandable tool call cards, reasoning sections, and a live session info bar
showing tokens, cost, and model. The terminal process stays alive in both
modes — in rich mode it runs hidden while user input from the text field is
piped to its stdin.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Auto-detect v0.7.0 database schema with backward compat for older DBs
- Surface reasoning tokens, actual cost, and billing provider from sessions
- Display model reasoning/thinking content in session message bubbles
- Add cost tracking to Dashboard, Insights, and session detail views
- Fix FTS5 search crash on dotted terms (e.g., "config.yaml", "v0.7.0")
- Add missing platforms: Home Assistant, Webhook, Matrix
- Consolidate platform icon mapping into shared KnownPlatforms.icon(for:)
- Map execute_code tool to ToolKind.execute
- Add Settings UI for reasoning effort, approval mode, show cost
- Show memory provider warning when external provider (Honcho) is active
- Replace fragile manual HermesSession init with withTitle() helper
- De-duplicate formatTokens utility function
- Bump version to 1.4.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All builds were reporting version 1.0 because the Xcode project version
was never updated from its default. Fixes#5, fixes#7.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dashboards with a webview widget now show a tab bar: Dashboard tab
renders all normal widgets, Site tab displays the web content
full-canvas with even margins. Cleaner UX than the split layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New widget type that renders any URL (local dev servers, HTML reports)
directly in the dashboard via WKWebView. Sections with webviews
automatically split layout: grid widgets left, webview right.
Configurable height, non-persistent data store, navigation error logging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace try? with do/catch and [Scarf] error logging in all service-layer
JSON decoding, file writes, and directory creation
- Extract sqliteTransient constant replacing raw unsafeBitCast(-1, ...) pattern
- Add QueryDefaults and FileSizeUnit enums for all magic numbers
- Guard HOME env var with NSHomeDirectory() fallback instead of force-unwrap
- Add path traversal validation to loadSkillContent()
- Add SessionStats.empty and use it across all initialization sites
- Replace KnownPlatforms array indexing with named .cli constant
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces a new Projects section that renders custom dashboards from
JSON files in project directories. Supports 7 widget types (stat,
progress, text, table, chart, list) with live file-watching refresh.
Includes project registry, SwiftUI Charts integration, schema docs,
and comprehensive README documentation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Universal binary (arm64 + x86_64) available on Releases page.
Updated Building section to Install with download + build options.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Settings view now has editable form controls organized by section:
Model: editable model name field, provider dropdown picker
Display: personality picker (parsed from config), streaming/reasoning/verbose toggles
Terminal: backend picker (local/docker/singularity/modal/daytona/ssh), max turns stepper
Voice: auto TTS toggle, silence threshold stepper
Memory: enabled toggle, char limit steppers, nudge interval stepper
All changes write via `hermes config set key value` CLI with save
confirmation feedback. Open in Editor button launches the raw YAML
in the default text editor. Paths and raw config sections retained.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>