Shareable `.scarftemplate` bundle format lets users package a project's
dashboard, cross-agent AGENTS.md, optional per-agent instruction shims,
optional namespaced skills, optional tagged cron jobs, and an optional
memory appendix into a single zip that anyone can install with one click.
Core:
- Bundle format + manifest schema v1 (template.json with contents claim
cross-checked against zip entries to prevent hidden files).
- ProjectTemplateService inspects + validates + builds an install plan.
- ProjectTemplateInstaller executes plans with transport-routed I/O so
the v1 local-only flow extends cleanly to remote ServerContexts later.
- ProjectTemplateExporter builds bundles from existing projects with
user-selected skills + cron jobs.
- ProjectTemplateUninstaller reverses installs using template.lock.json.
Only lock-tracked files are removed; user-added files are preserved.
UI:
- Templates menu in Projects toolbar: Install from File, Install from
URL, Export as Template.
- Preview-and-confirm sheets for install, uninstall, and export with
full diff of what will be written/removed before anything runs.
- Right-click context menu on project list + dashboard header button
for uninstall (only shown when template.lock.json exists).
Deep link + file associations:
- scarf:// URL scheme registered; onOpenURL in scarfApp.swift routes
scarf://install?url=https://... and file:// URLs for .scarftemplate
files to the install sheet.
- Custom UTType com.scarf.template registered so Finder shows the file
with a Scarf icon and double-click opens the install preview.
- Cold-launch race fix: .task picks up any URL staged on the router
before the onChange observer was installed.
Safety:
- Never writes to config.yaml, auth.json, sessions, or credentials.
- Cron jobs ship paused with a [tmpl:<id>] name prefix.
- Skills install to a namespaced ~/.hermes/skills/templates/<slug>/ dir
so they never collide with user-authored skills.
- Memory appendix is wrapped in scarf-template:<id>:begin/end markers
for clean removal during uninstall.
- Download cap: 50 MB for URL-fetched templates, enforced on the actual
on-disk file size after download so chunked transfers can't bypass it.
Tests: 22 tests in 7 suites cover manifest parsing, claim verification,
URL routing (scarf:// + file://), end-to-end install and uninstall
against a minimal bundle (projects registry is snapshotted + restored),
user-added file preservation, and exporter round-trip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-release prep so that when `./scripts/release.sh 2.1.0` runs on main,
the notes file is already in tree (script's `git add` is then a no-op,
bump commit contains only the pbxproj version change).
- README gains a 2.1 "What's New" section covering translations + the
chat slash-menu; 2.0 moves down to "Previously".
- Badge row gains a language list line.
- Full release notes at releases/v2.1.0/RELEASE_NOTES.md — covers the
three stacked i18n PRs (infra, audit burn-down, translations) and the
chat slash-menu work merged in parallel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three users reported on day-one of v2.0 that SSH connections showed a
green "Connected" pill but every data view read as empty / "not running"
/ "not configured". The common thread across Docker, homelab VM, and
Ubuntu VPS setups: file-access failures on the remote that Scarf
silently swallowed into nil/empty defaults.
Stop swallowing errors
- HermesFileService gains Result-returning variants for the four
dashboard-critical readers: loadConfigResult, loadGatewayStateResult,
hermesPIDResult, plus readFileResult / readFileDataResult as
primitives. Each logs os.Logger warnings on failure. Legacy nil-
returning signatures remain as thin forwarders.
- HermesDataService.open records lastOpenError with humanized hints
for the top three failure modes — sqlite3 not installed, permission
denied, file not found. Each maps to concrete remediation (`apt
install sqlite3`, "check file perms", "set Hermes data directory").
Dashboard surfaces the error
- DashboardViewModel collects errors from every loader into
lastReadError, only on remote contexts (local skips the banner).
- DashboardView renders an orange banner above the stats with the
specific error text, a copy-selectable detail, and a "Run
Diagnostics…" button.
New Remote Diagnostics sheet (stethoscope icon)
- RemoteDiagnosticsViewModel runs 14 checks in one SSH round-trip via
a pipe-delimited "KEY|STATUS|DETAIL" protocol. Covers: SSH
connectivity, remote user/$HOME, Hermes dir existence + readability,
config.yaml readability + actual read (distinct from just `test -e`
which can't detect permission issues), state.db readability, sqlite3
binary presence, sqlite3 open test, hermes binary on non-login AND
login PATH, pgrep availability.
- Each probe row shows a targeted hint on fail (e.g. "check perms on
~/.hermes", "apt install sqlite3", "move PATH export from .bashrc
to .zshenv"). A Copy Full Report button dumps plain-text output
for GitHub issues.
- Accessible from Manage Servers (stethoscope button per row) and
directly from the yellow pill.
Yellow "degraded" connection state
- ConnectionStatusViewModel.Status gains .degraded(reason:) between
.connected and .error. After tier-1 `true` passes, the probe runs
tier-2 `test -r $HOME/.hermes/config.yaml` in the same SSH round-
trip. On tier-2 fail, pill is orange with "Connected — can't read
Hermes state" tooltip.
- Clicking a degraded pill opens Remote Diagnostics directly. Exactly
the symptom in #19 is now one click from a specific answer.
Auto-suggest remoteHome for non-default installs
- TestConnectionProbe.TestResult.success gains suggestedRemoteHome:
String?. When state.db isn't found at the configured path, the
probe also checks /var/lib/hermes/.hermes, /opt/hermes/.hermes,
/home/hermes/.hermes, /root/.hermes — the common alternates for
systemd services, Docker containers, and single-user VPSes — and
surfaces the first hit as a "Use this" suggestion in Add Server.
- AddServerSheet relabels "Remote ~/.hermes override" to "Hermes data
directory" with an explanation of when you'd use it.
README
- New "Remote setup requirements" subsection lists the four concrete
prereqs (SSH, sqlite3, pgrep, read access to ~/.hermes).
- New "Troubleshooting remote connections" paragraph describes the
diagnostics sheet and remoteHome auto-suggest for the two most
common failure modes.
Releases
- releases/v2.0.1/RELEASE_NOTES.md for the GitHub release body.
- Ship via `./scripts/release.sh 2.0.1`.
Closes#19.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The multi-window / multi-server / remote-SSH work that landed in
00ca722 (feat: multi-window + remote SSH server support (Phases 0-4))
was feature-complete but accumulated rough edges during dogfooding
against a remote Mac mini. This commit finishes the 2.0 release:
correctness fixes on remote, a chat-view UX overhaul, and a Swift 6
complete-concurrency sweep across the service layer.
Correctness on remote
- Kill the WAL-error spam: snapshotSQLite now runs `PRAGMA
journal_mode=DELETE` on the remote temp DB before scp, so the
pulled file is self-contained. Open remote snapshots with
`file:...?immutable=1` URI as defense-in-depth, and drop the
pointless `PRAGMA journal_mode=WAL` from HermesDataService.open.
- loadSessionHistory and refreshMessages now force a fresh snapshot
via refresh(), so resuming a session on a remote shows messages
persisted since launch (previously stuck on the first snapshot).
- New SnapshotCoordinator actor dedupes concurrent snapshotSQLite
calls per ServerID — Dashboard + Sessions + Activity no longer
issue three parallel SSH backups for the same fetch.
- ACP cwd comes from the remote's $HOME (probed once, cached per
server in UserHomeCache), not the local Mac's NSHomeDirectory().
- Typing into a blank Chat always creates a new session. The old
auto-resume-most-recent fallback was picking up cron-spawned
sessions that Hermes had already GC'd, producing silent prompt
failures.
- handlePromptComplete surfaces non-success stopReasons ("refusal",
"error", "max_tokens") as a system message so failed prompts no
longer sit under a forever-spinning "Agent working…".
Chat UX
- Replace six racing onChange-driven scrollTo calls with
`.defaultScrollAnchor(.bottom)` alone. Manual proxy.scrollTo
against a LazyVStack that hadn't finished laying out was
overshooting into whitespace. Layout-pass-integrated anchor
behaves correctly at stream start and finish.
- Remove ContentUnavailableView swap in RichChatView — it tore down
the whole ScrollView hierarchy on first message. Empty state now
lives inside the scroll view.
- continueLastSession surfaces an acpError banner if open() fails,
instead of silently returning.
Lifecycle hygiene
- ServerRegistry.removeServer closes the server's SSH ControlMaster
(`ssh -O exit`), prunes its snapshot cache dir, and invalidates
UserHomeCache for that ID. App launch sweeps orphan snapshot dirs
whose UUIDs aren't in the registry anymore.
- NSWorkspace.activateFileViewerSelecting (backup-saved-to dialog)
gated on !context.isRemote; remote surfaces the remote path in the
saveMessage instead of silently no-op'ing on a nonexistent local
path.
Swift 6 concurrency — 230 warnings → 1
- Mark ServerContext, HermesPathSet, ServerTransport (protocol),
LocalTransport, SSHTransport, HermesFileService, and every value-
type accessor as `nonisolated`. Prevents AppKit-import-driven
MainActor inference from bleeding onto data-only types.
- Hand-written Codable conformances (vs. synthesized) for
ACPRequest, ACPRawMessage, ACPError, GatewayState, PlatformState,
HermesCronJob, CronSchedule, CronJobsFile, AuthFile, AuthEntry.
Synthesized inits were inferred @MainActor by Swift 6's default-
isolation rule; hand-written ones are explicitly nonisolated.
- Captured-var refactors in MCPServerEditorViewModel, PluginsView
Model, LocalTransport.watchPaths. Thread.sleep → Task.sleep in
TestConnectionProbe.
- Remaining warning is AnyCodable.value mutation in init(from:) —
Any-typed storage can't be strictly Sendable; acknowledged via
@unchecked Sendable.
ACP adapter upstream bug (not fixed here, but handled)
- Hermes's ACP adapter returns JSON-RPC success `{"result":{}}` for
session/load on a missing session, logging the warning only to
stderr. Scarf can't distinguish "loaded" from "silently missing"
at that layer; the stopReason=refusal surfacing above catches the
downstream symptom. Upstream issue worth filing.
Release docs
- releases/v2.0.0/RELEASE_NOTES.md with full user-facing breakdown.
- README.md "What's New" bumped to 2.0 with a multi-server section.
Compatibility table adds v0.10.0 as verified.
- GitHub repo description updated (via `gh repo edit`) to call out
multi-server + remote SSH.
35 files changed, +809/-350.
Co-Authored-By: Claude Opus 4.7 (1M context) <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>
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.
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>
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>
- 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>
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>
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>
Documents tested versions and the interfaces Scarf depends on
(SQLite schema v6, CLI output parsing).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added Tools Manager to features list
- Updated Sessions Browser with rename/delete/export
- Updated Skills Browser with file switcher
- Updated Dashboard with live refresh
- Updated Log Viewer with text search
- Added hermes tools and hermes sessions to data sources table
- Revised How It Works section to cover management actions
- Updated architecture tree with Tools feature
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hermes cost tracking returns $0.00 for models not in its static
pricing table (including claude-haiku-4-5). Token counts remain
displayed since those are always accurate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The activity feed loads historical tool calls from the database,
it does not stream live events.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Native SwiftUI app providing full visibility into the Hermes AI agent:
- Dashboard with system health, token usage, and cost tracking
- Sessions browser with conversation detail and FTS5 search
- Activity feed with tool call inspector (read/edit/execute/fetch/browser)
- Embedded terminal chat via SwiftTerm with full ANSI/Rich rendering
- Memory viewer/editor with live file-watching refresh
- Skills browser by category with file content viewer
- Cron job viewer with output display
- Real-time log tailing with level filtering
- Settings display with raw config and Finder path links
- Menu bar status icon with quick actions
Architecture: MVVM-Feature, zero dependencies beyond SwiftTerm,
read-only SQLite access, Swift 6 strict concurrency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>