mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +00:00
docs: phase 2 — fill all 26 stubs with real content
+95
-2
@@ -1,6 +1,99 @@
|
||||
# ACP Subprocess
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
ACP — Agent Client Protocol — is Hermes's chat protocol: JSON-RPC 2.0 over stdio. Scarf's Rich Chat surface speaks ACP end-to-end. There is no SQLite polling involved in chat; tokens, thoughts, tool calls, and permission prompts all stream live from the subprocess.
|
||||
|
||||
## Subprocess construction
|
||||
|
||||
[`ACPClient`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Services/ACPClient.swift) is an `actor` that owns the subprocess and exposes an `AsyncStream<ACPEvent>`.
|
||||
|
||||
The `Process` is created via `transport.makeProcess(executable: "hermes", args: ["acp"])`:
|
||||
|
||||
- **Local:** spawns `hermes acp` with `HermesFileService.enrichedEnvironment()` so MCP servers and shell tools find brew/nvm/asdf binaries on `PATH`. `TERM` is removed so terminal escapes don't pollute JSON-RPC.
|
||||
- **Remote:** the transport returns `/usr/bin/ssh -T <opts> host -- hermes acp`. `SSH_AUTH_SOCK` is inherited so the GUI-launched Scarf reaches the user's ssh-agent. `TERM` is removed.
|
||||
|
||||
`-T` (no PTY) is critical — without it stdin/stdout would be PTY-cooked and the JSON-RPC framing would break.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
| Phase | What happens |
|
||||
|---|---|
|
||||
| `start()` | Creates the event stream first, builds and configures the Process, attaches pipes, installs the termination handler, calls `proc.run()`, starts read loops for stdout/stderr, sends `initialize`, starts a 30-second keepalive ping (`{"jsonrpc":"2.0","method":"$/ping"}`). |
|
||||
| `newSession(cwd:)` / `loadSession(cwd:sessionId:)` / `resumeSession(cwd:sessionId:)` | Three modes: fresh, load existing, resume after disconnect. Each sends a `session/new`/`session/load`/`session/resume` RPC; updates `currentSessionId`. |
|
||||
| `sendPrompt(sessionId:text:)` | Sends `session/prompt` with the user text; returns `ACPPromptResult` with token usage and stop reason. **No timeout** — streaming may run for minutes. Tokens, thoughts, tool calls, and permission requests arrive as events on the stream while this awaits. |
|
||||
| `cancel(sessionId:)` | Sends `session/cancel` to interrupt an in-flight prompt. |
|
||||
| `respondToPermission(requestId:optionId:)` | Sends a JSON-RPC response to an incoming `session/request_permission` request. |
|
||||
| `stop()` | Cancels background tasks, finishes the event continuation, closes stdin (subprocess sees EOF), sends SIGINT, watchdogs to SIGTERM after 2 seconds, closes pipes. |
|
||||
|
||||
## Event stream
|
||||
|
||||
Consumers iterate `for await event in client.events`:
|
||||
|
||||
```swift
|
||||
enum ACPEvent: Sendable {
|
||||
case messageChunk(sessionId, text) // assistant token chunk
|
||||
case thoughtChunk(sessionId, text) // reasoning/thinking token chunk
|
||||
case toolCallStart(sessionId, call) // tool invocation began
|
||||
case toolCallUpdate(sessionId, update) // tool invocation finished/updated
|
||||
case permissionRequest(sessionId, requestId, request) // user approval needed
|
||||
case promptComplete(sessionId, response) // session/prompt resolved
|
||||
case availableCommands(sessionId, commands) // /commands the agent advertises
|
||||
case connectionLost(reason)
|
||||
case unknown(sessionId, type)
|
||||
}
|
||||
```
|
||||
|
||||
Events are extracted from incoming JSON-RPC notifications matching `method: "session/update"`. The `sessionUpdate` discriminator inside `params` selects the case (`agent_message_chunk`, `agent_thought_chunk`, `tool_call`, `tool_call_update`, etc.).
|
||||
|
||||
## Permission requests (bidirectional)
|
||||
|
||||
Most chat traffic is agent → client. Permission requests reverse direction — the agent sends an incoming JSON-RPC **request** with `method: "session/request_permission"`:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 42,
|
||||
"method": "session/request_permission",
|
||||
"params": {
|
||||
"sessionId": "...",
|
||||
"toolCall": { ... },
|
||||
"options": [
|
||||
{"optionId": "allow", "name": "Allow"},
|
||||
{"optionId": "deny", "name": "Deny"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The client emits `ACPEvent.permissionRequest`. The UI (Rich Chat) shows a sheet; once the user clicks an option, `respondToPermission(requestId: 42, optionId: "allow")` sends back:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 42,
|
||||
"result": {
|
||||
"outcome": {"kind": "allowed", "optionId": "allow"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Internals
|
||||
|
||||
- **Read loop** runs detached: `availableData` → buffer → split on `\n` → JSON-decode each line → `handleMessage`.
|
||||
- **Stderr loop** captures the subprocess's stderr into a 50-line ring buffer for attaching to user-visible errors.
|
||||
- **Pending requests dict** maps JSON-RPC `id` → `CheckedContinuation<AnyCodable?, Error>`. Responses resume the matching continuation; the read loop dispatches them by `id`.
|
||||
- **30-second control-message timeout** fires for `initialize`/`session/new`/etc. There is no timeout on `session/prompt` — that one streams for as long as the model takes.
|
||||
- **`safeWrite(fd:data:)`** handles partial writes and EPIPE; used for both prompt sends and keepalive pings.
|
||||
- **Disconnect cleanup** is single-pathed via `performDisconnectCleanup(reason)`. Three callers: stdout EOF (`handleReadLoopEnded`), process termination (`handleTermination`), write failure (`handleWriteFailed`). All three resume pending requests with `processTerminated` and finish the event continuation.
|
||||
|
||||
## Error hints
|
||||
|
||||
Raw error messages are noisy. `ACPErrorHint` (in the same file) pattern-matches across the error message and the stderr ring buffer to attach actionable hints:
|
||||
|
||||
| Pattern matched | Hint surfaced |
|
||||
|---|---|
|
||||
| "No credentials found" / `ANTHROPIC_API_KEY` | "Set `ANTHROPIC_API_KEY` in `~/.hermes/.env`" |
|
||||
| "No such file or directory" + binary name | "Binary not on `PATH`; check `~/.zprofile` exports" |
|
||||
| "Rate limit" / 429 | "AI provider rate-limited; try again later" |
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+146
-2
@@ -1,6 +1,150 @@
|
||||
# Adding a Feature Module
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
The MVVM-F recipe. Adding a feature touches 4 existing files and creates 2 new ones.
|
||||
|
||||
## Directory shape
|
||||
|
||||
```
|
||||
scarf/scarf/scarf/Features/MyFeature/
|
||||
Views/
|
||||
MyFeatureView.swift
|
||||
ViewModels/
|
||||
MyFeatureViewModel.swift
|
||||
```
|
||||
|
||||
Both subdirectories are conventional — even features that only have one view of each follow this shape so file discovery is consistent.
|
||||
|
||||
## Step 1: Create the ViewModel
|
||||
|
||||
```swift
|
||||
import Observation
|
||||
|
||||
@Observable
|
||||
final class MyFeatureViewModel {
|
||||
let context: ServerContext
|
||||
private let fileService: HermesFileService
|
||||
|
||||
init(context: ServerContext) {
|
||||
self.context = context
|
||||
self.fileService = HermesFileService(context: context)
|
||||
}
|
||||
|
||||
var items: [MyModel] = []
|
||||
var loadError: String?
|
||||
|
||||
func load() async {
|
||||
do {
|
||||
items = try await fetchItems()
|
||||
loadError = nil
|
||||
} catch {
|
||||
loadError = error.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Conventions:
|
||||
|
||||
- Always take `ServerContext` in `init` so the feature works against any window's bound server.
|
||||
- Construct any services inside `init`; don't hand them in.
|
||||
- Use `@Observable` (Swift macro), not `ObservableObject`.
|
||||
- Public state is `var` properties; mutate them on `MainActor`. Async work runs `nonisolated` and assigns the final value back on the main actor.
|
||||
|
||||
## Step 2: Create the View
|
||||
|
||||
```swift
|
||||
struct MyFeatureView: View {
|
||||
@State private var viewModel: MyFeatureViewModel
|
||||
@Environment(AppCoordinator.self) private var coordinator
|
||||
@Environment(HermesFileWatcher.self) private var fileWatcher
|
||||
|
||||
init(context: ServerContext) {
|
||||
_viewModel = State(initialValue: MyFeatureViewModel(context: context))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List(viewModel.items) { item in /* … */ }
|
||||
.navigationTitle("My Feature")
|
||||
.task { await viewModel.load() }
|
||||
.task(id: fileWatcher.lastChangeDate) {
|
||||
// Re-load when ~/.hermes/ changes
|
||||
await viewModel.load()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Conventions:
|
||||
|
||||
- View takes `ServerContext` in its `init`; it's the only initializer parameter.
|
||||
- `@State private var viewModel: MyFeatureViewModel` — `@State` is the right wrapper for `@Observable` classes inside views.
|
||||
- Read coordinator and watcher from `@Environment`.
|
||||
- Use `.task(id:)` for reactive reloads — make sure you include every dependency in the id, or changes to a missing one won't trigger reload.
|
||||
|
||||
## Step 3: Add the SidebarSection case
|
||||
|
||||
In [`Navigation/AppCoordinator.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Navigation/AppCoordinator.swift):
|
||||
|
||||
```swift
|
||||
enum SidebarSection: String, CaseIterable, Identifiable {
|
||||
// … existing cases …
|
||||
case myFeature = "My Feature"
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
// … existing icons …
|
||||
case .myFeature: return "star.fill" // pick an SF Symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4: Register in SidebarView
|
||||
|
||||
In [`Navigation/SidebarView.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Navigation/SidebarView.swift), add the case to the right `Section`:
|
||||
|
||||
```swift
|
||||
Section("Interact") {
|
||||
ForEach([SidebarSection.chat, .memory, .skills, .myFeature]) { section in
|
||||
Label(section.rawValue, systemImage: section.icon).tag(section)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pick the section thematically — Monitor for views, Interact for talking-to-Hermes, Configure for setup, Manage for operational.
|
||||
|
||||
## Step 5: Wire routing
|
||||
|
||||
In `ContentView.swift`'s `detailView` switch:
|
||||
|
||||
```swift
|
||||
switch coordinator.selectedSection {
|
||||
// … existing cases …
|
||||
case .myFeature: MyFeatureView(context: serverContext)
|
||||
}
|
||||
```
|
||||
|
||||
## Step 6: (If your feature uses a new service)
|
||||
|
||||
If you needed a new service to back this feature, add it under `Core/Services/` and inject any shared instance in `ContextBoundRoot` via `.environment(...)`. See [Adding a Service](Adding-a-Service).
|
||||
|
||||
## Cross-feature rules
|
||||
|
||||
The hard rules ([CLAUDE.md](https://github.com/awizemann/scarf/blob/main/CLAUDE.md)):
|
||||
|
||||
- **Features never import sibling features.** If `MyFeature` needs data another feature also uses, the data lives in a service, not in that other feature.
|
||||
- **Cross-feature navigation goes through `AppCoordinator`.** Set `coordinator.selectedSection = .otherFeature` and (if needed) `coordinator.selectedSessionId = ...`.
|
||||
|
||||
## Files touched
|
||||
|
||||
- ✏️ `Navigation/AppCoordinator.swift` — 1 enum case, 1 icon line.
|
||||
- ✏️ `Navigation/SidebarView.swift` — add to the right `Section`.
|
||||
- ✏️ `ContentView.swift` — 1 switch case.
|
||||
- ✏️ `scarfApp.swift` — only if you needed to inject a new shared service.
|
||||
- ✨ `Features/MyFeature/Views/MyFeatureView.swift` — new.
|
||||
- ✨ `Features/MyFeature/ViewModels/MyFeatureViewModel.swift` — new.
|
||||
|
||||
Total: ~5-10 lines across 4 existing files, plus 2 new files.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+108
-2
@@ -1,6 +1,112 @@
|
||||
# Adding a Service
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Services live under `scarf/scarf/scarf/Core/Services/`. They mediate between Hermes (filesystem, SQLite, subprocess) and feature ViewModels. The recipe is short.
|
||||
|
||||
## Pick the isolation
|
||||
|
||||
| Pattern | When | Examples in repo |
|
||||
|---|---|---|
|
||||
| `actor` | The service owns mutable state across calls (a subprocess, file handles, an open SQLite connection, a snapshot dedup table). | [`ACPClient`](Core-Services), [`HermesDataService`](Core-Services), [`HermesLogService`](Core-Services). |
|
||||
| `Sendable struct` | The service is stateless — every call re-reads through the transport. | [`HermesFileService`](Core-Services), [`HermesEnvService`](Core-Services), [`ModelCatalogService`](Core-Services). |
|
||||
| `@MainActor @Observable` | Rare — only when the service IS UI-observable state (Sparkle wrapper). | [`UpdaterService`](Core-Services). |
|
||||
|
||||
If you're not sure, start with `Sendable struct`. Promote to `actor` only when you find yourself wanting to cache mutable state across calls.
|
||||
|
||||
## Skeleton: stateless struct
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
struct MyHermesService: Sendable {
|
||||
let context: ServerContext
|
||||
private var transport: any ServerTransport { context.transport }
|
||||
|
||||
func loadSomething() async throws -> SomethingType {
|
||||
let data = try await transport.readFile(context.paths.somethingFile)
|
||||
return try JSONDecoder().decode(SomethingType.self, from: data)
|
||||
}
|
||||
|
||||
func saveSomething(_ value: SomethingType) async throws {
|
||||
let data = try JSONEncoder().encode(value)
|
||||
try await transport.writeFile(context.paths.somethingFile, data: data)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Skeleton: actor for stateful work
|
||||
|
||||
```swift
|
||||
actor MyStatefulService {
|
||||
let context: ServerContext
|
||||
private var cache: [String: Value] = [:]
|
||||
|
||||
init(context: ServerContext) {
|
||||
self.context = context
|
||||
}
|
||||
|
||||
func get(_ key: String) async throws -> Value {
|
||||
if let cached = cache[key] { return cached }
|
||||
let value = try await fetchFromHermes(key: key)
|
||||
cache[key] = value
|
||||
return value
|
||||
}
|
||||
|
||||
func invalidate() {
|
||||
cache.removeAll()
|
||||
}
|
||||
|
||||
private func fetchFromHermes(key: String) async throws -> Value {
|
||||
// Use context.transport for I/O.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Take `ServerContext` in `init`.** Never hardcode `ServerContext.local` — services must work against any window's bound server.
|
||||
- **Route I/O through `context.transport` or the `context.read*/write*/runHermes` helpers.** Never use `FileManager`, `Process`, or `NSWorkspace.open` directly for Hermes paths — those break on remote (and break the rule from the project's [feedback memory](Wiki-Maintenance)).
|
||||
- **Surface errors as `throws` or `Result`.** Don't swallow them; the UI knows what to do with them.
|
||||
- **Don't log to `print`** — use `os.Logger` (`logger.error()` for unexpected, `logger.warning()` for expected).
|
||||
- **Don't do synchronous file I/O on `@MainActor`.** Either dispatch via `Task.detached { }.value`, or expose async methods.
|
||||
|
||||
## Wiring the service into a feature
|
||||
|
||||
Two patterns:
|
||||
|
||||
**Per-ViewModel** (most common for stateless services):
|
||||
|
||||
```swift
|
||||
@Observable
|
||||
final class MyFeatureViewModel {
|
||||
private let service: MyHermesService
|
||||
init(context: ServerContext) {
|
||||
self.service = MyHermesService(context: context)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Shared via Environment** (for stateful services that multiple features want to share):
|
||||
|
||||
In `scarfApp.swift`'s `ContextBoundRoot`:
|
||||
|
||||
```swift
|
||||
@State private var fileWatcher: HermesFileWatcher
|
||||
|
||||
ContentView()
|
||||
.environment(fileWatcher)
|
||||
```
|
||||
|
||||
Then in any view:
|
||||
|
||||
```swift
|
||||
@Environment(HermesFileWatcher.self) private var fileWatcher
|
||||
```
|
||||
|
||||
Use Environment for things every window has exactly one of — file watcher, server registry, updater service. Use per-ViewModel construction for everything else.
|
||||
|
||||
## Tests
|
||||
|
||||
Service code that uses transport is testable with a mock transport. See [Testing](Testing) — what exists today is minimal, but the `ServerTransport` protocol is the obvious extension point for fakes.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+97
-2
@@ -1,6 +1,101 @@
|
||||
# Build and Run
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Scarf is a single Xcode target with one SPM dependency (Sparkle). No CocoaPods, no Carthage, no submodules.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **macOS 14.6+ (Sonoma)** on the dev machine.
|
||||
- **Xcode 16.0+**.
|
||||
- **Hermes** at `~/.hermes/` (so the local server window has something to point at — see [First Run](First-Run)).
|
||||
|
||||
## Open in Xcode
|
||||
|
||||
```bash
|
||||
git clone https://github.com/awizemann/scarf.git
|
||||
cd scarf
|
||||
open scarf/scarf.xcodeproj
|
||||
```
|
||||
|
||||
The project uses `PBXFileSystemSynchronizedRootGroup` — Xcode auto-discovers any new file you drop into the source tree. You don't need to manually add files to the target.
|
||||
|
||||
Build with ⌘B; run with ⌘R.
|
||||
|
||||
## Build from the command line
|
||||
|
||||
Debug build:
|
||||
|
||||
```bash
|
||||
xcodebuild -project scarf/scarf.xcodeproj -scheme scarf -configuration Debug build
|
||||
```
|
||||
|
||||
Universal release build (matches what the release script produces):
|
||||
|
||||
```bash
|
||||
xcodebuild -project scarf/scarf.xcodeproj -scheme scarf \
|
||||
-configuration Release \
|
||||
-arch arm64 -arch x86_64 ONLY_ACTIVE_ARCH=NO build
|
||||
```
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
scarf/ repo root
|
||||
CLAUDE.md project instructions for Claude Code
|
||||
CONTRIBUTING.md
|
||||
README.md
|
||||
releases/v<ver>/ per-version notes + appcast entry
|
||||
scripts/
|
||||
release.sh full release pipeline
|
||||
wiki.sh this wiki helper
|
||||
ExportOptions.plist
|
||||
scarf/ Xcode project root
|
||||
scarf.xcodeproj
|
||||
docs/ internal dev notes (PRD, Discovery, ARCH)
|
||||
standards/ read-only reference standards
|
||||
scarf/ APP TARGET — start here
|
||||
scarfApp.swift @main App
|
||||
ContentView.swift window root
|
||||
Core/
|
||||
Services/ 9 services
|
||||
Models/ 13+ models
|
||||
Transport/ 4 transport files
|
||||
Persistence/ ServerRegistry
|
||||
Utilities/ markdown helpers
|
||||
Features/ 25 feature modules
|
||||
Navigation/ AppCoordinator + SidebarView
|
||||
Assets.xcassets
|
||||
Info.plist
|
||||
scarf.entitlements
|
||||
scarfTests/
|
||||
scarfUITests/
|
||||
```
|
||||
|
||||
## Swift 6 concurrency
|
||||
|
||||
The project compiles with strict concurrency on. Two non-negotiables ([detail in CLAUDE.md](https://github.com/awizemann/scarf/blob/main/CLAUDE.md)):
|
||||
|
||||
- `@MainActor` is the default isolation. Services use `nonisolated` async methods and route results back to MainActor for UI updates.
|
||||
- All closures captured by `Task`, `Task.detached`, or `withCheckedThrowingContinuation` must be `@Sendable`. Don't capture non-Sendable types across concurrency boundaries.
|
||||
|
||||
## What to look at first
|
||||
|
||||
1. [`scarfApp.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/scarfApp.swift) — App entry point, multi-window setup, `ContextBoundRoot` injecting the `ServerContext` and friends.
|
||||
2. [`Navigation/AppCoordinator.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Navigation/AppCoordinator.swift) — single source of truth for navigation.
|
||||
3. [`Core/Models/ServerContext.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/ServerContext.swift) — the unified handle to a Hermes install.
|
||||
4. [`Core/Transport/`](https://github.com/awizemann/scarf/tree/main/scarf/scarf/Core/Transport) — local vs. SSH abstraction.
|
||||
5. Any feature module under [`Features/`](https://github.com/awizemann/scarf/tree/main/scarf/scarf/Features) for the MVVM-F shape.
|
||||
|
||||
## Running tests
|
||||
|
||||
```bash
|
||||
xcodebuild test -project scarf/scarf.xcodeproj -scheme scarf
|
||||
```
|
||||
|
||||
See [Testing](Testing) for what is and isn't tested.
|
||||
|
||||
## Releasing
|
||||
|
||||
Don't run `xcodebuild archive` / `notarytool` / `gh release create` by hand — use the release script. See [Release Process](Release-Process).
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+51
-2
@@ -1,6 +1,55 @@
|
||||
# Chat
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Two modes share the same sidebar item: **Rich Chat** (real-time ACP streaming with iMessage-style bubbles) and **Terminal** (the `hermes chat` CLI rendered with full ANSI color in an embedded terminal). Switch between them in the chat toolbar.
|
||||
|
||||
## Rich Chat
|
||||
|
||||
Streams tokens, thoughts, and tool calls live via the [ACP subprocess](ACP-Subprocess). No DB polling — when Hermes emits a message chunk, you see it immediately.
|
||||
|
||||
**What it renders:**
|
||||
|
||||
- iMessage-style bubbles, user vs. assistant.
|
||||
- Markdown content (links, lists, code blocks, tables).
|
||||
- **Reasoning / thinking** chunks in a collapsible block above the answer.
|
||||
- **Tool calls** as inline cards with the function name, argument summary, and (when complete) the result.
|
||||
- **Permission requests** as modal sheets — Hermes asks before executing risky tools; you choose Allow / Deny.
|
||||
- **`/compress`** — when Hermes advertises the command via `availableCommands`, a one-click focus sheet appears in the toolbar.
|
||||
|
||||
**Session lifecycle:**
|
||||
|
||||
- Each new conversation creates a session via `session/new` (cwd = your local working directory if you set one).
|
||||
- Resume an old conversation: pick from the session picker; Scarf calls `session/load` or `session/resume` depending on the state.
|
||||
- Auto-reconnect — if the subprocess dies, Scarf attempts a `session/resume` to pick up where it left off.
|
||||
|
||||
**Voice mode controls:** PTT (push-to-talk), TTS playback, STT transcription preferences live in **Settings → Voice**. The chat toolbar exposes the basic toggles.
|
||||
|
||||
## Terminal mode
|
||||
|
||||
The full `hermes chat` CLI rendered in an embedded SwiftTerm terminal:
|
||||
|
||||
- ANSI color and Rich formatting work as in your shell.
|
||||
- WhatsApp / Signal pairing flows render their QR codes here for scanning.
|
||||
- Useful when you need a feature only the CLI exposes (e.g. obscure flags, daemon management).
|
||||
|
||||
## Cancellation
|
||||
|
||||
The Stop button sends `session/cancel` over the JSON-RPC channel. The model stops generating; any in-flight tool call completes. You can immediately send another prompt without restarting the session.
|
||||
|
||||
## Multi-server chat
|
||||
|
||||
Each window is bound to one server, so chat in window A talks to local Hermes while window B talks to a remote one. Sessions don't cross windows — they live on the server they were created on.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **"Spinning forever, no response"** — check the [Logs](Gateway-Cron-Health-Logs) view for ACP errors. Common causes: missing `ANTHROPIC_API_KEY` (Scarf attaches a hint), rate limit, or `hermes` binary not on `$PATH`.
|
||||
- **Connection lost** — Rich Chat surfaces this banner; click Reconnect to call `session/resume`.
|
||||
- **Permission sheet doesn't appear** — make sure Hermes's `approval_mode` in `config.yaml` is set so it asks (not auto-approve).
|
||||
|
||||
## Related pages
|
||||
|
||||
- [ACP Subprocess](ACP-Subprocess) for the protocol internals.
|
||||
- [Memory & Skills](Memory-and-Skills) for editing what Hermes knows about you.
|
||||
- [Settings — Voice tab](Gateway-Cron-Health-Logs) for TTS/STT configuration (Settings is documented there).
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+56
-2
@@ -1,6 +1,60 @@
|
||||
# Contributing
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Thanks for your interest in contributing to Scarf. The canonical contributor guide lives in [`CONTRIBUTING.md`](https://github.com/awizemann/scarf/blob/main/CONTRIBUTING.md) in the repo. This page is a public-facing summary plus a few wiki-specific notes.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Fork and clone <https://github.com/awizemann/scarf>.
|
||||
2. Open `scarf/scarf.xcodeproj` in Xcode 16+.
|
||||
3. Build and run (requires macOS 14.6+ and Hermes installed at `~/.hermes/`).
|
||||
4. Read [Build & Run](Build-and-Run) for the codebase tour and [Architecture Overview](Architecture-Overview) for the layering.
|
||||
|
||||
## Code conventions
|
||||
|
||||
The full list is in [`CONTRIBUTING.md`](https://github.com/awizemann/scarf/blob/main/CONTRIBUTING.md). The non-negotiables:
|
||||
|
||||
- **MVVM-F.** Features never import sibling features. Cross-feature concerns go through services or `AppCoordinator`.
|
||||
- **No commented-out code, no TODOs, no deferred functionality** in PRs.
|
||||
- **Zero warnings** on build.
|
||||
- **Read-only DB access.** Never write to `~/.hermes/state.db`.
|
||||
- **Swift 6 strict concurrency.** `@MainActor` default isolation; `nonisolated` for service methods.
|
||||
- **Conventional commits.** `feat:`, `fix:`, `chore:`, `refactor:`, `docs:`, etc.
|
||||
|
||||
## What's good to work on
|
||||
|
||||
- Anything in the [Roadmap](Roadmap).
|
||||
- Test coverage — see [Testing](Testing) for the highest-value gaps.
|
||||
- Wiki content — every stub on the wiki is a pull request opportunity. See [Wiki Maintenance](Wiki-Maintenance) for the workflow.
|
||||
- Bug reports with reproducible steps.
|
||||
|
||||
## Pull request flow
|
||||
|
||||
1. Open an issue first describing the change. This avoids rework if the maintainer has constraints in mind.
|
||||
2. One feature or fix per PR — keeps reviews tight.
|
||||
3. Include a clear description of what changed and why.
|
||||
4. Ensure `xcodebuild -project scarf/scarf.xcodeproj -scheme scarf build` succeeds with zero warnings.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
Open an issue at <https://github.com/awizemann/scarf/issues> with:
|
||||
|
||||
- What you expected to happen.
|
||||
- What actually happened.
|
||||
- macOS version and Hermes version.
|
||||
- Steps to reproduce.
|
||||
- The relevant log snippet from `~/.hermes/logs/errors.log` if applicable (redact secrets first).
|
||||
|
||||
## Documentation contributions
|
||||
|
||||
Two paths:
|
||||
|
||||
- **Small wiki edits** (typos, clarifications) — click **Edit** on any wiki page in the GitHub UI. Editing requires push access to the main repo.
|
||||
- **Larger wiki changes** — clone the wiki directly (`git clone git@github.com:awizemann/scarf.wiki.git`) or open an issue describing what needs adding. See [Wiki Maintenance](Wiki-Maintenance) for the full workflow including the secret-scan policy.
|
||||
- **Internal dev docs** (PRD, Hermes API discovery, raw architecture notes) live in `scarf/docs/` in the main repo and follow the normal PR flow.
|
||||
|
||||
## Code of conduct
|
||||
|
||||
Be kind, be specific, assume good faith. Disagreements about technical direction are welcome; personal attacks aren't.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+27
-2
@@ -1,6 +1,31 @@
|
||||
# Core Services
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
The 9 services under [`scarf/scarf/scarf/Core/Services/`](https://github.com/awizemann/scarf/tree/main/scarf/scarf/Core/Services) are the data-and-side-effects layer between Hermes and the SwiftUI features. Each takes a `ServerContext` (local or SSH) so the same service code works against a local install or a remote one.
|
||||
|
||||
| Service | Isolation | Lines | Purpose |
|
||||
|---|---|---|---|
|
||||
| [`ACPClient`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Services/ACPClient.swift) | `actor` | ~605 | Spawns `hermes acp` subprocess; JSON-RPC over stdio; async event stream for chat. |
|
||||
| [`HermesDataService`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Services/HermesDataService.swift) | `actor` | ~658 | Read-only SQLite queries against `state.db`; pulls atomic snapshots for remote; dedupes concurrent snapshot calls via a nested `SnapshotCoordinator` actor. |
|
||||
| [`HermesEnvService`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Services/HermesEnvService.swift) | `Sendable struct` | ~217 | Non-destructive `~/.hermes/.env` I/O — preserves comments and blanks; `unset` comments out instead of deleting. |
|
||||
| [`HermesFileService`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Services/HermesFileService.swift) | `Sendable struct` | ~600 | Parses `config.yaml` into typed nested structs; resolves `hermes` binary; enriches `$PATH` for spawned tools (brew/nvm/asdf). |
|
||||
| [`HermesFileWatcher`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Services/HermesFileWatcher.swift) | `@Observable` | ~122 | Local: FSEvents via `DispatchSourceFileSystemObject`. Remote: mtime polling over the SSH ControlMaster. Updates `lastChangeDate`; views observe and refresh. |
|
||||
| [`HermesLogService`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Services/HermesLogService.swift) | `actor` | ~173 | Tails `agent.log` / `errors.log` / `gateway.log`. Local: `FileHandle`. Remote: `ssh host tail -F` with partial-line buffering. |
|
||||
| [`ModelCatalogService`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Services/ModelCatalogService.swift) | `Sendable struct` | ~210 | Reads `~/.hermes/models_dev_cache.json` (~1500 models across ~110 providers); helpers for cost and context-window display. |
|
||||
| [`ProjectDashboardService`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Services/ProjectDashboardService.swift) | `Sendable struct` | ~71 | Loads/saves the project registry and per-project `.scarf/dashboard.json`. |
|
||||
| [`UpdaterService`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Services/UpdaterService.swift) | `@MainActor @Observable` | ~41 | Thin Sparkle wrapper exposing the auto-check toggle, last-check date, and a "check now" trigger. |
|
||||
|
||||
## Patterns shared across the layer
|
||||
|
||||
- **`ServerContext` parameterizes all I/O.** Services receive the context at init; routing local vs. SSH happens through `context.transport`. See [Transport Layer](Transport-Layer).
|
||||
- **Stateful services are `actor`s.** ACPClient, HermesDataService, and HermesLogService own resources (subprocesses, file handles, SQLite connections) that need serialized access; they expose async APIs.
|
||||
- **Stateless services are `Sendable struct`s.** HermesEnvService, HermesFileService, ModelCatalogService, and ProjectDashboardService have no instance state worth coordinating; each call re-reads through the transport.
|
||||
- **Schema tolerance.** `HermesDataService` checks for v0.7+ columns (`reasoning_tokens`, `actual_cost_usd`, `cost_status`, `billing_provider`) and degrades gracefully on older databases.
|
||||
- **Snapshot dedup.** `SnapshotCoordinator` (nested in `HermesDataService`) ensures concurrent callers from Dashboard + Sessions + Activity await the same in-flight `sqlite3 .backup` rather than each spawning a fresh one.
|
||||
- **Error hints over raw stderr.** `ACPClient` keeps a 50-line stderr ring buffer and pattern-matches into `ACPErrorHint` for user-friendly messages (missing `ANTHROPIC_API_KEY`, binary not on `PATH`, rate-limited).
|
||||
|
||||
## Adding a service
|
||||
|
||||
See [Adding a Service](Adding-a-Service) for the recipe. Short version: take `ServerContext` in `init`, decide isolation (`actor` for stateful, `struct` for stateless), expose async public methods, route I/O through `context.transport`.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+41
-2
@@ -1,6 +1,45 @@
|
||||
# Dashboard
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
The Dashboard is the default landing view of every window. It answers four questions at a glance: **Is Hermes running?** **What's it cost me?** **What did it just do?** **Is anything broken?**
|
||||
|
||||
## Layout
|
||||
|
||||
A scrolling stack of cards, refreshed automatically when `~/.hermes/state.db`, `config.yaml`, or the logs change ([HermesFileWatcher](Core-Services) handles the reload).
|
||||
|
||||
| Card | Source | What it shows |
|
||||
|---|---|---|
|
||||
| **Hermes Process** | `pgrep` (local) or `ssh host pgrep` (remote) | Running / stopped, PID, uptime. Start/stop/restart buttons. |
|
||||
| **Token Usage** | `state.db` aggregations across all sessions | Input + output + cache + reasoning tokens; period selector (7/30/90 days / all). |
|
||||
| **Cost Tracking** | Per-session `actual_cost_usd` (v0.7+) or `estimated_cost_usd` | Total spend in the period, broken down by model when available. |
|
||||
| **Recent Sessions** | `sessions` table, ordered by `started_at DESC` | Last N sessions with title, source platform, message count, duration. Click to drill into [Sessions](Insights-and-Activity). |
|
||||
| **Active Platforms** | Parsed from `config.yaml` | Which messaging platforms are configured and their connection status. |
|
||||
| **Status** | `lastOpenError` from `HermesDataService` | Surfaced when the database can't be read (missing `sqlite3` on remote, permission denied, etc.). Links to [Health](Gateway-Cron-Health-Logs) for diagnostics. |
|
||||
|
||||
## Live refresh
|
||||
|
||||
Local windows watch `~/.hermes/` with FSEvents (`DispatchSourceFileSystemObject`). Remote windows poll mtimes every 3 seconds over the SSH ControlMaster. Either way, the Dashboard updates without needing a manual refresh — open it on a second monitor and watch sessions tick by as Hermes works.
|
||||
|
||||
## Status pill in the toolbar
|
||||
|
||||
Every Scarf window has a connection pill in the toolbar showing the bound server and its state:
|
||||
|
||||
- **Local** — green dot, "Local" label.
|
||||
- **Remote (healthy)** — green dot, server name.
|
||||
- **Remote (degraded)** — yellow dot, "Can't read Hermes state" — opens a diagnostics sheet that runs 14 checks in one SSH session (connectivity, `sqlite3` presence, read access on `config.yaml`/`state.db`, effective non-login `$PATH`).
|
||||
- **Remote (failed)** — red dot, error message.
|
||||
|
||||
## When something looks wrong
|
||||
|
||||
- **"Hermes not running"** — start Hermes from a terminal, or click the Start button.
|
||||
- **Empty cards** — `~/.hermes/state.db` is missing or unreadable. See [First Run](First-Run).
|
||||
- **Token/cost are zero but you've used Hermes** — the schema may predate v0.7. Update Hermes; Scarf detects the new columns automatically.
|
||||
- **Yellow "Can't read Hermes state" pill on remote** — open Manage Servers → Run Diagnostics. Each failed check explains why and how to fix.
|
||||
|
||||
## Related pages
|
||||
|
||||
- [Insights & Activity](Insights-and-Activity) for deeper analytics.
|
||||
- [Chat](Chat) for talking to the running Hermes.
|
||||
- [Servers & Remote](Servers-and-Remote) for adding remote hosts and the diagnostics flow.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+60
-2
@@ -1,6 +1,64 @@
|
||||
# Data Model
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Plain Codable structs under [`scarf/scarf/scarf/Core/Models/`](https://github.com/awizemann/scarf/tree/main/scarf/scarf/Core/Models). These are the in-memory representation of Hermes state — sessions, messages, config, cron jobs, MCP servers, project dashboards. None of them own I/O; services read from `~/.hermes/` (via [transport](Transport-Layer)) and decode into these types.
|
||||
|
||||
## Sessions and messages
|
||||
|
||||
| Type | Conformances | Notable fields | Notes |
|
||||
|---|---|---|---|
|
||||
| `HermesSession` | `Identifiable, Sendable` | `id, source, userId, model, title, parentSessionId, startedAt, endedAt, endReason, messageCount, toolCallCount, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens, estimatedCostUSD, actualCostUSD, costStatus, billingProvider` | `isSubagent` = `parentSessionId != nil`; `displayCostUSD` prefers `actualCostUSD`; `sourceIcon` calls `KnownPlatforms.icon(for:source)`. |
|
||||
| `HermesMessage` | `Identifiable, Sendable` | `id, sessionId, role, content, toolCallId, timestamp, tokenCount, finishReason, toolCalls, toolName, reasoning` | `isUser/isAssistant/isToolResult` helpers; `hasReasoning` for v0.7+ reasoning support. |
|
||||
| `HermesToolCall` (nested in `HermesMessage`) | custom Codable | `callId, functionName, arguments` | `toolKind` categorizes to `.read/.edit/.execute/.fetch/.browser/.other`; `argumentsSummary` extracts command/path/query/url or truncates to 120 chars. |
|
||||
|
||||
## Server context and paths
|
||||
|
||||
| Type | Purpose |
|
||||
|---|---|
|
||||
| [`ServerContext`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/ServerContext.swift) | The unifying handle. Holds `id, displayName, kind` (`.local` or `.ssh(SSHConfig)`); exposes `paths`, `makeTransport()`, and high-level helpers (`readText`, `writeText`, `runHermes`, `openInLocalEditor`). The `local` static is the well-known UUID `00000000-0000-0000-0000-000000000001`. |
|
||||
| `SSHConfig` (nested) | `host, user?, port?, identityFile?, remoteHome?, hermesBinaryHint?` |
|
||||
| `UserHomeCache` (nested) | Process-wide cache of remote `$HOME` per `ServerID`; probed once via `ssh host echo $HOME`, memoized. |
|
||||
| [`HermesPathSet`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/HermesPathSet.swift) | All `~/.hermes/*` paths as nonisolated computed properties. Parameterized by `home` and `isRemote`. Includes `hermesBinaryCandidates` (`~/.local/bin/hermes`, `/opt/homebrew/bin/hermes`, `/usr/local/bin/hermes`, `~/.hermes/bin/hermes`) and a `binaryHint` override for remote. |
|
||||
|
||||
See [Hermes Paths](Hermes-Paths) for the full path table.
|
||||
|
||||
## Config
|
||||
|
||||
[`HermesConfig`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/HermesConfig.swift) parses `config.yaml` into a typed tree. About 100+ properties grouped into nested setting structs:
|
||||
|
||||
- `display`, `terminal`, `browser`, `voice`, `auxiliary`, `security`, `humanDelay`, `compression`, `checkpoints`, `logging`, `delegation`
|
||||
- Per-platform: `discord, telegram, slack, matrix, mattermost, whatsapp, homeAssistant`
|
||||
- Top-level scalars: `model, provider, maxTurns, personality, terminalBackend, memoryEnabled, …`
|
||||
|
||||
`HermesConfig.empty` provides safe defaults for empty/missing files.
|
||||
|
||||
## Cron, MCP, plugins
|
||||
|
||||
| Type | Purpose |
|
||||
|---|---|
|
||||
| [`HermesCronJob`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/HermesCronJob.swift) | Job definition with `schedule, prompt, skills?, model?, enabled, state, deliver, nextRunAt?, lastRunAt?, lastError?, preRunScript?, deliveryFailures?, timeoutType?, timeoutSeconds?, silent?`. Nested `CronSchedule` and `CronJobsFile` container. |
|
||||
| [`HermesMCPServer`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/HermesMCPServer.swift) | MCP server config: `name, transport (.stdio/.http), command?, args, url?, auth?, env, headers, timeouts, enabled, toolsInclude/Exclude, resourcesEnabled, promptsEnabled, hasOAuthToken`. |
|
||||
| [`MCPServerPreset`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/MCPServerPreset.swift) | Curated presets gallery (Filesystem, GitHub, Postgres, Slack, Linear, Sentry, Puppeteer, Memory, Fetch). |
|
||||
| `HermesTool`, `HermesToolset`, `KnownPlatforms` | Tool/toolset definitions and a switch for platform icons (cli, telegram, discord, slack, whatsapp, signal, email, homeassistant, webhook, matrix, feishu, mattermost, imessage). |
|
||||
| `HermesSkill`, `HermesSkillCategory` | Installed skills with `id, name, category, path, files, requiredConfig`. |
|
||||
|
||||
## Project dashboards
|
||||
|
||||
The dashboard schema lives in [`ProjectDashboard.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/ProjectDashboard.swift):
|
||||
|
||||
- `ProjectRegistry` → `ProjectEntry` (name, path)
|
||||
- `ProjectDashboard` → `DashboardSection` → `DashboardWidget` (`.stat / .progress / .text / .table / .chart / .list / .webview`)
|
||||
- `WidgetValue` enum with `.string` or `.number` for stat boxes; custom Codable.
|
||||
- `ChartSeries`, `ChartDataPoint`, `ListItem` for non-scalar widgets.
|
||||
|
||||
The full schema is also documented in `scarf/docs/DASHBOARD_SCHEMA.md` in the main repo.
|
||||
|
||||
## ACP messages
|
||||
|
||||
[`ACPMessages.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/ACPMessages.swift) defines the JSON-RPC envelopes (`ACPRequest`, `ACPRawMessage`, `ACPError`) and the typed events (`ACPEvent.messageChunk / thoughtChunk / toolCallStart / toolCallUpdate / permissionRequest / promptComplete / availableCommands / connectionLost / unknown`). See [ACP Subprocess](ACP-Subprocess) for the protocol details.
|
||||
|
||||
## Constants
|
||||
|
||||
[`HermesConstants.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/HermesConstants.swift) holds query defaults — `sessionLimit = 100`, `messageSearchLimit = 50`, `toolCallLimit = 50`, `previewContentLength = 100`, `logLineLimit = 200` — plus SQLite C macro replacements and file-size unit constants for display formatting.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+36
-2
@@ -1,6 +1,40 @@
|
||||
# First Run
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
The first time you launch Scarf, it opens a single window bound to your local Hermes install at `~/.hermes/`. That window is automatic — there is no setup screen.
|
||||
|
||||
## What Scarf expects
|
||||
|
||||
For the local server window to work, Hermes must be installed at `~/.hermes/` with at least:
|
||||
|
||||
- `~/.hermes/state.db` — SQLite database (created automatically by Hermes on first run).
|
||||
- `~/.hermes/config.yaml` — runtime config.
|
||||
- The `hermes` CLI on your `$PATH` (Scarf checks `~/.local/bin/hermes`, `/opt/homebrew/bin/hermes`, `/usr/local/bin/hermes`, and `~/.hermes/bin/hermes` as fallbacks).
|
||||
|
||||
If `~/.hermes/` is missing or empty, the Dashboard will tell you so. Install Hermes first per the [Hermes README](https://github.com/hermes-ai/hermes-agent), then relaunch Scarf.
|
||||
|
||||
## What you'll see
|
||||
|
||||
The window opens to the **Dashboard** — system health, token usage, recent sessions. The sidebar on the left has four groups:
|
||||
|
||||
- **Monitor** — Dashboard, Insights, Sessions, Activity
|
||||
- **Interact** — Chat, Memory, Skills
|
||||
- **Configure** — Platforms, Personalities, Quick Commands, Credential Pools, Plugins, Webhooks, Profiles, Servers
|
||||
- **Manage** — Tools, MCP Servers, Gateway, Cron, Health, Logs, Settings
|
||||
|
||||
Click any item to open it. Selection lives in `AppCoordinator` — see [Sidebar & Navigation](Sidebar-and-Navigation) for the full list with icons.
|
||||
|
||||
## Adding a remote server
|
||||
|
||||
To open a window against a remote Hermes install, use **File → Open Server… → Add Server**. You'll need:
|
||||
|
||||
- Hostname or alias (resolved via your `~/.ssh/config`)
|
||||
- Optional user, port, identity file, remote home, and Hermes binary hint
|
||||
|
||||
See [Servers & Remote](Servers-and-Remote) for prerequisites on the remote host (`sqlite3`, `pgrep`, SSH key auth, readable `~/.hermes/`).
|
||||
|
||||
## Multiple windows
|
||||
|
||||
Each Scarf window is bound to exactly one server. Open as many windows as you want — they run with independent state and can be tiled side-by-side.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+92
-3
@@ -1,6 +1,95 @@
|
||||
# Gateway Cron Health Logs
|
||||
# Gateway / Cron / Health / Logs / Settings
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
The Manage section's operational tools, grouped because they're what you reach for when something needs attention.
|
||||
|
||||
## Gateway Control
|
||||
|
||||
Start, stop, and restart the messaging gateway. Live status:
|
||||
|
||||
- **PID, uptime, connected platforms.**
|
||||
- Per-platform connection state mirrors the dot you see throughout the app.
|
||||
- **Pairing management** — view approved users, approve new pairing requests, revoke existing approvals.
|
||||
|
||||
The gateway is what brings Hermes onto Telegram / Discord / Slack / etc. Stop it to take the agent offline without quitting Hermes itself.
|
||||
|
||||
## Cron Manager
|
||||
|
||||
View and edit Hermes scheduled jobs (`~/.hermes/cron/jobs.json`):
|
||||
|
||||
| Column | What it shows |
|
||||
|---|---|
|
||||
| Name | The job's display name. |
|
||||
| Schedule | Cron expression or run-at timestamp. |
|
||||
| State | `enabled` / `paused` / `failed` / `running` with an icon. |
|
||||
| Last run / next run | Timestamps. |
|
||||
| Delivery | Channel format like `discord:chat:thread`. |
|
||||
|
||||
**Operations** (new in 1.6 — full write support):
|
||||
|
||||
- Create a new job — prompt, optional skills, optional model override, schedule, delivery target.
|
||||
- Edit any field on an existing job.
|
||||
- Pause / resume.
|
||||
- Run-now (one-shot trigger outside the schedule).
|
||||
- Delete.
|
||||
- Pre-run scripts, delivery-failure tracking, timeout type / seconds, `[SILENT]` indicator for jobs that suppress output.
|
||||
|
||||
Edits go through [`ServerContext.writeText`](Architecture-Overview) — atomic, transport-aware.
|
||||
|
||||
## Health
|
||||
|
||||
Component-level status and diagnostics. Mirrors `hermes status` and `hermes doctor`:
|
||||
|
||||
- API key validation per provider.
|
||||
- Auth provider status.
|
||||
- External tools availability (browser, terminal, voice).
|
||||
- "Update available" indicator from [Sparkle](Updating).
|
||||
|
||||
**Two buttons:**
|
||||
|
||||
- **Run Dump** — captures `hermes dump` output inline.
|
||||
- **Share Debug Report** — uploads a structured report to Nous support, with a confirmation dialog before sending.
|
||||
|
||||
## Log Viewer
|
||||
|
||||
Real-time tail for the three main logs at `~/.hermes/logs/`:
|
||||
|
||||
- `agent.log` — the agent's loop.
|
||||
- `errors.log` — errors only.
|
||||
- `gateway.log` — messaging gateway.
|
||||
|
||||
**Filters:**
|
||||
|
||||
- **Level** — DEBUG / INFO / WARNING / ERROR / CRITICAL.
|
||||
- **Component** — Gateway / Agent / Tools / CLI / Cron.
|
||||
- **Session** — clickable session-ID pills filter the view to one session.
|
||||
- **Text search.**
|
||||
|
||||
Local windows tail with `FileHandle`. Remote windows run `ssh host tail -F` with partial-line buffering so you don't see half-arrived JSON. See [`HermesLogService`](Core-Services).
|
||||
|
||||
## Settings
|
||||
|
||||
Restructured in 1.6 into a 10-tab layout exposing ~60 previously hidden config fields:
|
||||
|
||||
| Tab | What lives here |
|
||||
|---|---|
|
||||
| **General** | Updates (Sparkle toggle + manual check), basic preferences. |
|
||||
| **Display** | Streaming, reasoning visibility, cost display, verbose mode. |
|
||||
| **Agent** | Model picker (backed by models.dev catalog — 111 providers), max turns, approval mode, reasoning effort. |
|
||||
| **Terminal** | Terminal backend, Docker / container settings, modal options. |
|
||||
| **Browser** | Browser backend selection. |
|
||||
| **Voice** | TTS / STT providers, PTT, silence threshold (default 200ms). |
|
||||
| **Memory** | `memory_enabled`, `memory_char_limit`, `user_char_limit`, `memory_provider`. |
|
||||
| **Aux Models** | All 8 auxiliary model tasks (vision, web extract, compression, delegation, etc.). |
|
||||
| **Security** | Tirith sandbox, command allowlist, website blocklist, redaction. |
|
||||
| **Advanced** | Logging level / rotation, checkpoints, human-delay simulation, compression thresholds. |
|
||||
|
||||
**Backup & Restore** lives at the bottom — wraps `hermes backup` (zips the current profile) and `hermes import` (unzips into the active profile). One-click via `context.runHermes`.
|
||||
|
||||
## Related pages
|
||||
|
||||
- [Core Services](Core-Services) — the services backing each of these views.
|
||||
- [Hermes Paths](Hermes-Paths) — where each operational file lives.
|
||||
- [Updating](Updating) — Sparkle, the appcast, and how the auto-update flow works.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
@@ -1,6 +1,49 @@
|
||||
# Hermes Version Compatibility
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Scarf reads Hermes's SQLite database directly and parses CLI output from `hermes status`, `hermes doctor`, `hermes tools`, `hermes sessions`, `hermes gateway`, and `hermes pairing`. It also speaks ACP (`hermes acp`) for chat. Compatibility comes from automatic schema detection plus tolerant parsing.
|
||||
|
||||
## Currently targeted
|
||||
|
||||
**Scarf 2.0.x targets Hermes v0.10.0** for the ACP `session/fork` / `session/list` / `session/resume` capabilities used by remote chat. The `CLAUDE.md` in the repo declares this as `Targets Hermes v0.9.0 (v2026.4.13)` for the broader feature set; v0.10.0 is the recommended minimum to get every chat capability working.
|
||||
|
||||
## Verified versions
|
||||
|
||||
| Hermes | Date | Status |
|
||||
|---|---|---|
|
||||
| v0.6.0 | 2026-03-30 | Verified |
|
||||
| v0.7.0 | 2026-04-03 | Verified |
|
||||
| v0.8.0 | 2026-04-08 | Verified |
|
||||
| v0.9.0 | 2026-04-13 | Verified |
|
||||
| v0.10.0 | 2026-04-18 | Verified (recommended for full 2.0 feature support) |
|
||||
|
||||
## How compatibility is maintained
|
||||
|
||||
**SQLite schema detection.** [`HermesDataService`](Core-Services) probes for v0.7+ columns (`reasoning_tokens`, `actual_cost_usd`, `cost_status`, `billing_provider`) at open time. When absent, the model fields stay nil and the views render placeholders. Older databases keep working; newer columns light up automatically once the database has them.
|
||||
|
||||
**Log line format.** Hermes log lines may carry an optional `[session_id]` tag between the level and the logger name. [`HermesLogService.parseLine`](Core-Services) treats the tag as an optional capture group, so older untagged lines still parse.
|
||||
|
||||
**ACP capability gates.** Rich Chat checks for `session/list` and `session/resume` support; if Hermes doesn't advertise them, the resume / picker UI degrades gracefully (you can still chat, just with reduced session navigation).
|
||||
|
||||
**CLI output parsing.** Where Scarf parses `hermes` CLI output (Health, Tools, Pairing), the parsers tolerate field reordering and unknown lines.
|
||||
|
||||
## What breaks across major Hermes versions
|
||||
|
||||
If Hermes ships a major release that changes:
|
||||
|
||||
- **Database schema** — columns dropped, table renames — Scarf may need a release. Expected to be infrequent and called out in Hermes release notes.
|
||||
- **ACP method names or message shapes** — chat would break. Scarf would ship an update with the new methods supported alongside the old.
|
||||
- **CLI output format** — Health / Tools / Pairing would degrade. Often Scarf can ship a parser update independently.
|
||||
|
||||
When in doubt, check the [Health](Gateway-Cron-Health-Logs) view for compatibility warnings (Scarf surfaces the Hermes version it sees and flags mismatches with what it was tested against).
|
||||
|
||||
## Reporting a compatibility issue
|
||||
|
||||
If a new Hermes release breaks something in Scarf, please file an issue including:
|
||||
|
||||
- Output of `hermes --version`.
|
||||
- Scarf version (Settings → General → About).
|
||||
- The specific view that's broken and what's wrong.
|
||||
- The relevant log snippet from `~/.hermes/logs/errors.log` (filter sensitive content first).
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+44
-3
@@ -1,6 +1,47 @@
|
||||
# Insights and Activity
|
||||
# Insights & Activity
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Three closely related sidebar items live here: **Insights** (analytics roll-up), **Sessions** (conversation browser), and **Activity** (per-tool execution feed). All three read from `~/.hermes/state.db` via [HermesDataService](Core-Services).
|
||||
|
||||
## Insights
|
||||
|
||||
Aggregated analytics with a time-period selector (7 / 30 / 90 days / all time):
|
||||
|
||||
| Section | Content |
|
||||
|---|---|
|
||||
| **Overview stats** | Total sessions, messages, tool calls, tokens, active time, average session duration. |
|
||||
| **Token breakdown** | Input + output + cache-read + cache-write + **reasoning tokens** (v0.7+). |
|
||||
| **Cost tracking** | Total spend, per-model breakdown, actual vs. estimated when both are available. |
|
||||
| **Model usage** | Sessions and tokens per model. |
|
||||
| **Platform breakdown** | CLI vs. Telegram vs. Discord vs. Slack, etc. — sessions and messages per source platform. |
|
||||
| **Top tools** | Bar chart of the most-called tools with counts and percentages. |
|
||||
| **Activity heatmap** | Sessions by day-of-week × hour-of-day grid; surfaces "when am I actually using this" patterns. |
|
||||
| **Notable sessions** | Longest, most messages, most tokens, most tool calls. |
|
||||
|
||||
All queries are aggregations over the same `sessions`, `messages`, `tool_calls` tables that drive the other views — no separate analytics pipeline.
|
||||
|
||||
## Sessions
|
||||
|
||||
The full conversation history browser:
|
||||
|
||||
- **List** — every session, ordered by start date DESC. Subagent sessions (those with a `parent_session_id`) are filtered from the main list and accessible by drilling into the parent.
|
||||
- **Detail panel** — full message stream: user → assistant → tool calls → tool results, with markdown rendering. **Reasoning blocks** (v0.7+) render in a collapsed section.
|
||||
- **Tool call inspector** — pretty-printed arguments, function name, result. Categorized by `toolKind` (read / edit / execute / fetch / browser / other).
|
||||
- **Search** — full-text via SQLite's `messages_fts` FTS5 virtual table. Limit defaults to 50 hits.
|
||||
- **Actions** — rename (`hermes sessions rename`), delete (`hermes sessions delete`), JSONL export (`hermes sessions export`).
|
||||
|
||||
Click a session in the Dashboard's "Recent" card to land here with that session pre-selected.
|
||||
|
||||
## Activity
|
||||
|
||||
The per-tool execution feed — what Hermes did, when, and with what arguments:
|
||||
|
||||
- **Filterable by kind and session.** "Show me every browser fetch in session X."
|
||||
- **Detail inspector** — pretty-printed arguments JSON, tool output (when available), `tool_call_id` for cross-referencing back to the Sessions message stream.
|
||||
- **Live refresh** — same `HermesFileWatcher`; the feed scrolls as Hermes works.
|
||||
|
||||
## Live data freshness
|
||||
|
||||
All three views observe the file watcher and re-query when `state.db` changes. Remote windows pull a fresh atomic snapshot via `sqlite3 .backup` (deduped by `SnapshotCoordinator` so Dashboard + Insights + Sessions don't each spawn parallel backups). See [Transport Layer](Transport-Layer) for the snapshot mechanics.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+51
-2
@@ -1,6 +1,55 @@
|
||||
# Keyboard Shortcuts
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Scarf exposes a focused set of shortcuts. The defining patterns are **window switching** (⌘1…⌘9), **standard dialog flow** (Return / Esc), and a handful of context-specific actions inside views.
|
||||
|
||||
## Window / server switching
|
||||
|
||||
| Keys | Action | Defined in |
|
||||
|---|---|---|
|
||||
| ⌘1 | Open the local server window | [`scarfApp.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/scarfApp.swift) |
|
||||
| ⌘2 … ⌘9 | Open remote server window 2 through 9 (in registry order) | [`scarfApp.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/scarfApp.swift) |
|
||||
| ⌘⇧S | Open the Manage Servers picker | [`scarfApp.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/scarfApp.swift) |
|
||||
| ⌘Q | Quit Scarf | macOS standard, declared in `scarfApp.swift` |
|
||||
|
||||
You can have a local window and up to 8 remote windows reachable by ⌘ + number. Beyond that, use Manage Servers (⌘⇧S).
|
||||
|
||||
## In-view shortcuts
|
||||
|
||||
| Keys | Where | Action |
|
||||
|---|---|---|
|
||||
| ⌃B | Chat | Toggle the sessions sidebar inside Chat |
|
||||
| ⌘S | Personalities (SOUL.md editor) | Save the current SOUL.md |
|
||||
|
||||
## Dialog defaults
|
||||
|
||||
These follow the macOS convention; Scarf is consistent across every dialog:
|
||||
|
||||
| Keys | Behavior |
|
||||
|---|---|
|
||||
| Return | Confirm — fires the `.defaultAction` button (Save, Add, Create, Run, etc.). |
|
||||
| Esc | Cancel — fires the `.cancelAction` button. |
|
||||
|
||||
Sheets that follow this pattern include: project create / rename, session rename, MCP server add, Add Server, Add Cron Job.
|
||||
|
||||
## Standard system shortcuts
|
||||
|
||||
These work in Scarf because they're macOS standards, not because Scarf binds them explicitly:
|
||||
|
||||
| Keys | Action |
|
||||
|---|---|
|
||||
| ⌘W | Close window |
|
||||
| ⌘M | Minimize window |
|
||||
| ⌘N | New window (opens a new local window) |
|
||||
| ⌘⌥N | New window without tabs (when tabbing is on) |
|
||||
| ⌘, | Open Settings (Settings is part of `scarfApp.swift`'s scene config) |
|
||||
| ⌘C / ⌘V | Copy / paste — works inside text fields, message bubbles, log lines |
|
||||
| ⌘F | Search inside the Sessions browser and Logs view |
|
||||
|
||||
## What there isn't
|
||||
|
||||
There's no command palette and no global shortcuts beyond ⌘ + number for windows. The sidebar is mouse / arrow-key navigated; there's no ⌘1 / ⌘2 within a window for switching sections.
|
||||
|
||||
If you'd like to see additional shortcuts, file an issue — keyboard accessibility is welcome contribution territory.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
@@ -1,6 +1,69 @@
|
||||
# MCP Servers Plugins Webhooks Tools
|
||||
# MCP Servers / Plugins / Webhooks / Tools
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Four sidebar items that all extend what Hermes can do. Grouped here because the workflows are similar.
|
||||
|
||||
## MCP Servers
|
||||
|
||||
Manage Model Context Protocol servers Hermes connects to. Two ways to add:
|
||||
|
||||
- **Curated presets** — Filesystem, GitHub, Postgres, Slack, Linear, Sentry, Notion, Stripe, Puppeteer, Memory, Fetch, and more. Picking a preset fills in the command, args, and the env keys it needs.
|
||||
- **Custom** — stdio (command + args) or HTTP (URL + optional bearer auth).
|
||||
|
||||
**Per-server detail view:**
|
||||
|
||||
- Enable / disable toggle.
|
||||
- Environment variable + header editor — written through [`HermesEnvService`](Core-Services) so existing comments and blanks are preserved.
|
||||
- Tool include / exclude filters (whitelist / blacklist what the server exposes).
|
||||
- Resources / prompts toggles.
|
||||
- Request and connect timeouts.
|
||||
- OAuth token detection and clearing.
|
||||
- **Test Connection** runs `hermes mcp test` and surfaces the discovered tool list inline.
|
||||
|
||||
A gateway-restart banner appears after config changes that require a reload.
|
||||
|
||||
MCP servers are stored in `config.yaml` under the `mcp_servers` key; the model is `HermesMCPServer`.
|
||||
|
||||
## Plugins
|
||||
|
||||
Hermes plugins are git-cloned into `~/.hermes/plugins/`. Scarf reads the directory directly for reliable state.
|
||||
|
||||
**Operations:**
|
||||
|
||||
- Install via Git URL or `owner/repo` shorthand.
|
||||
- Update (pulls latest).
|
||||
- Remove.
|
||||
- Enable / disable.
|
||||
|
||||
## Webhooks
|
||||
|
||||
Create, list, test-fire, and remove webhook subscriptions:
|
||||
|
||||
- Endpoint URL, event filter, optional secret.
|
||||
- **Test fire** sends a synthetic event so you can verify the receiver before going live.
|
||||
- Detects the "platform not enabled" state and links to the gateway setup.
|
||||
|
||||
## Tools
|
||||
|
||||
Enable / disable Hermes toolsets per platform.
|
||||
|
||||
- Each platform (Telegram, Discord, Slack, etc.) gets its own toolset list.
|
||||
- Connectivity-aware platform menu: green / orange / grey / red dots match the gateway's reported state.
|
||||
- Toggling calls `hermes tools enable/disable` via `context.runHermes`.
|
||||
|
||||
**Fixed in 1.6:** all 13 platforms now appear here (was previously stuck on CLI only).
|
||||
|
||||
## Credential Pools
|
||||
|
||||
(Same Configure section, related concept.) Per-provider credential rotation:
|
||||
|
||||
- API key + OAuth flow handling. The OAuth flow does URL extraction → browser open → code paste; `--type api-key` is correctly inferred for direct API keys.
|
||||
- API keys are never stored in UI state — only the last 4 chars are previewed.
|
||||
- Strategy picker: `fill_first` / `round_robin` / `least_used` / `random`.
|
||||
|
||||
## Related pages
|
||||
|
||||
- [Gateway / Cron / Health / Logs](Gateway-Cron-Health-Logs) — the gateway is what actually consumes platform / tool config.
|
||||
- [Hermes Paths](Hermes-Paths) — `~/.hermes/plugins/`, `config.yaml` `mcp_servers` key.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+34
-3
@@ -1,6 +1,37 @@
|
||||
# Memory and Skills
|
||||
# Memory & Skills
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Two adjacent sidebar items both deal with what Hermes knows. **Memory** is the per-conversation and per-user notes Hermes keeps about you. **Skills** are reusable capabilities you've installed.
|
||||
|
||||
## Memory
|
||||
|
||||
Live editor for Hermes's two memory files:
|
||||
|
||||
- `~/.hermes/memories/MEMORY.md` — project / topic memory.
|
||||
- `~/.hermes/memories/USER.md` — user memory (preferences, role, recurring context).
|
||||
|
||||
**What you get:**
|
||||
|
||||
- Side-by-side edit + render with markdown preview.
|
||||
- **Live refresh** — when Hermes (or you) updates the file from outside, the view reloads via `HermesFileWatcher`.
|
||||
- **Profile awareness** — if you have multiple Hermes profiles, the picker switches between their memory files.
|
||||
- **External provider awareness** — when `memory_provider` in `config.yaml` is set to a service like Honcho or Supermemory, the view tells you so and links to the provider's docs.
|
||||
|
||||
Edits are written through `ServerContext.writeText` — local: atomic temp + swap; remote: scp + remote `mv`. See [Transport Layer](Transport-Layer).
|
||||
|
||||
## Skills
|
||||
|
||||
Browse and manage Hermes skills:
|
||||
|
||||
- **Installed** — every skill under `~/.hermes/skills/`, grouped by category, with a file content viewer and required-config warnings (skill says it needs `OPENAI_API_KEY` in `.env`? It tells you).
|
||||
- **Hub** — search the registry catalog (official, skills.sh, well-known, GitHub, ClawHub, LobeHub). Install, check for updates, uninstall.
|
||||
|
||||
Operations are wrappers around the `hermes skills` CLI invoked via `context.runHermes(...)`, so they work identically against local and remote servers.
|
||||
|
||||
## Related pages
|
||||
|
||||
- [Hermes Paths](Hermes-Paths) for the underlying file layout.
|
||||
- [Personalities](Platforms-Personalities-QuickCommands) for `SOUL.md` editing — closely related to memory but tied to a personality, not a profile.
|
||||
- [Settings — Memory tab](Gateway-Cron-Health-Logs) for `memory_enabled`, `memory_char_limit`, `memory_provider`.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
@@ -1,6 +1,52 @@
|
||||
# Platforms Personalities QuickCommands
|
||||
# Platforms / Personalities / Quick Commands
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Three separate Configure-section items grouped together because they all shape how Hermes presents itself.
|
||||
|
||||
## Platforms
|
||||
|
||||
Native GUI setup for all 13 messaging platforms Hermes supports — no more hand-editing `.env` and `config.yaml`:
|
||||
|
||||
Telegram, Discord, Slack, WhatsApp, Signal, Email, Matrix, Mattermost, Feishu, iMessage, Home Assistant, Webhook, CLI.
|
||||
|
||||
**Per-platform forms:**
|
||||
|
||||
- Credentials → `~/.hermes/.env` via [`HermesEnvService`](Core-Services) (preserves comments, supports non-destructive unset by commenting-out).
|
||||
- Behavior toggles → `~/.hermes/config.yaml` via the typed config struct.
|
||||
- WhatsApp + Signal pairing use an inline SwiftTerm terminal for QR scan and `signal-cli` daemon management.
|
||||
|
||||
Connectivity dots next to each platform reflect the gateway's last reported status:
|
||||
|
||||
- **Green** — connected and healthy.
|
||||
- **Orange** — configured but offline.
|
||||
- **Grey** — not configured.
|
||||
- **Red** — error.
|
||||
|
||||
## Personalities
|
||||
|
||||
A personality is a `SOUL.md` file that shapes Hermes's voice, defaults, and internal rules. Personalities live under `~/.hermes/personalities/<name>/`.
|
||||
|
||||
**What you can do here:**
|
||||
|
||||
- List defined personalities.
|
||||
- Pick the active one — written to `personality:` in `config.yaml`.
|
||||
- Edit `SOUL.md` inline with markdown preview. ⌘S saves.
|
||||
- Create / rename / delete personalities.
|
||||
|
||||
Switching personality takes effect on the next agent turn — no restart needed.
|
||||
|
||||
## Quick Commands
|
||||
|
||||
Custom `/command_name` shell shortcuts. You define a name, a shell command (with optional arg substitution), and an optional description; the command becomes invocable from anywhere Hermes accepts commands.
|
||||
|
||||
**Safety:** the editor scans for dangerous patterns (`rm -rf`, `mkfs`, fork bombs, sudo, suspicious eval) and warns before saving. The check is heuristic — it's a guard against typos, not a sandbox.
|
||||
|
||||
Quick Commands live in `config.yaml` under the `quick_commands` key.
|
||||
|
||||
## Related pages
|
||||
|
||||
- [Memory & Skills](Memory-and-Skills) — memory is profile-scoped, personality is config-scoped.
|
||||
- [Gateway / Cron / Health / Logs](Gateway-Cron-Health-Logs) — the gateway reads platform configs to decide what to connect to.
|
||||
- [Hermes Paths](Hermes-Paths) — where `.env`, `config.yaml`, and `personalities/` live.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+44
-3
@@ -1,6 +1,47 @@
|
||||
# Projects and Profiles
|
||||
# Projects & Profiles
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Two distinct concepts in adjacent sidebar items. **Projects** are agent-generated dashboards for any directory. **Profiles** are isolated Hermes installations.
|
||||
|
||||
## Projects
|
||||
|
||||
A project is any directory you tell Scarf about — typically a code repo, but anything works. Each project gets a custom dashboard composed of widgets defined in `<project>/.scarf/dashboard.json`.
|
||||
|
||||
**Widget types** (from [`ProjectDashboard.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/ProjectDashboard.swift)):
|
||||
|
||||
| Type | Purpose |
|
||||
|---|---|
|
||||
| `stat` | Single metric: value + label + optional icon and color. |
|
||||
| `progress` | Progress bar with label. |
|
||||
| `text` | Markdown / plain text block. |
|
||||
| `table` | Columns + rows. |
|
||||
| `chart` | Line / bar / area / pie with `ChartSeries[]` of `ChartDataPoint{x, y}`. |
|
||||
| `list` | Bulleted list with optional status badges. |
|
||||
| `webview` | Embedded web view (URL + height). |
|
||||
|
||||
**The Hermes pattern:** ask your agent to build and maintain the dashboard for you. "Update `.scarf/dashboard.json` to show test pass rate, lines of code, and the open PR list." Scarf renders the result; the agent maintains it.
|
||||
|
||||
The full schema is documented in [`scarf/docs/DASHBOARD_SCHEMA.md`](https://github.com/awizemann/scarf/blob/main/scarf/docs/DASHBOARD_SCHEMA.md) in the main repo.
|
||||
|
||||
**Adding a project:** click + in the Projects sidebar, pick a directory. The project is registered in `~/.hermes/scarf/projects.json`; the dashboard JSON lives in `<project>/.scarf/dashboard.json` (which you should add to your project's `.gitignore` if it's user-specific).
|
||||
|
||||
## Profiles
|
||||
|
||||
A profile is an isolated Hermes installation — separate config, sessions, memory, skills, the lot. Useful for keeping work / personal context separate, or for testing a config change without disturbing your main instance.
|
||||
|
||||
**Operations** (all wrap `hermes profile ...` via `context.runHermes`):
|
||||
|
||||
- **Switch** — make a profile active. Scarf shows a "restart Scarf to fully apply" reminder because the active profile path is read at launch.
|
||||
- **Create / rename / delete** — straightforward.
|
||||
- **Export** — zips the profile directory; useful for backup or moving to a new machine.
|
||||
- **Import** — unzip into a new profile slot.
|
||||
|
||||
Profiles live under `~/.hermes/profiles/`. The currently active profile is whatever Hermes points its `~/.hermes/` symlink (or equivalent) at — Scarf reflects that.
|
||||
|
||||
## Related pages
|
||||
|
||||
- [Hermes Paths](Hermes-Paths) — `~/.hermes/profiles/` and the projects registry.
|
||||
- [Memory & Skills](Memory-and-Skills) — memory is profile-scoped.
|
||||
- [Settings](Gateway-Cron-Health-Logs) — exposes "Backup & Restore" buttons (`hermes backup` / `hermes import`) at the profile level.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+29
-2
@@ -1,6 +1,33 @@
|
||||
# Release Notes Index
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Every Scarf release in chronological order. The notes themselves live in `releases/v<version>/RELEASE_NOTES.md` in the main repo, attached to each GitHub Release as the release body.
|
||||
|
||||
| Version | Date | GitHub release | Notes file |
|
||||
|---|---|---|---|
|
||||
| **v2.0.1** | 2026-04-20 | [v2.0.1](https://github.com/awizemann/scarf/releases/tag/v2.0.1) | [`releases/v2.0.1/RELEASE_NOTES.md`](https://github.com/awizemann/scarf/blob/main/releases/v2.0.1/RELEASE_NOTES.md) |
|
||||
| **v2.0.0** | 2026-04 | [v2.0.0](https://github.com/awizemann/scarf/releases/tag/v2.0.0) | [`releases/v2.0.0/RELEASE_NOTES.md`](https://github.com/awizemann/scarf/blob/main/releases/v2.0.0/RELEASE_NOTES.md) |
|
||||
| **v1.6.2** | 2026-04 | [v1.6.2](https://github.com/awizemann/scarf/releases/tag/v1.6.2) | [`releases/v1.6.2/RELEASE_NOTES.md`](https://github.com/awizemann/scarf/blob/main/releases/v1.6.2/RELEASE_NOTES.md) |
|
||||
| **v1.6.1** | 2026-04 | [v1.6.1](https://github.com/awizemann/scarf/releases/tag/v1.6.1) | [`releases/v1.6.1/RELEASE_NOTES.md`](https://github.com/awizemann/scarf/blob/main/releases/v1.6.1/RELEASE_NOTES.md) |
|
||||
| v1.6.0 | 2026-03 | [v1.6.0](https://github.com/awizemann/scarf/releases/tag/v1.6.0) | (no notes file) |
|
||||
| v1.5.x | 2026-03 | [releases](https://github.com/awizemann/scarf/releases?q=v1.5) | (no notes files) |
|
||||
|
||||
## Highlights by major
|
||||
|
||||
**2.0** — Multi-server, remote Hermes over SSH, chat UX overhaul (no more flash, no scroll jumping, real error explanations), and a correctness pass that fixed remote WAL spam, stale-snapshot session resume, auto-resume of dead cron sessions, and 230+ Swift 6 concurrency warnings.
|
||||
|
||||
**1.6** — Configure section: native GUI for all 13 messaging platforms, Credential Pools with proper OAuth, Model Picker backed by models.dev's 111-provider catalog, 10-tab Settings exposing ~60 previously hidden config fields, plus Personalities, Quick Commands, Plugins, Webhooks, Profiles.
|
||||
|
||||
**1.5** — Initial public release series; foundation for everything above.
|
||||
|
||||
## After a release
|
||||
|
||||
When `scripts/release.sh <version>` completes a full (non-draft) release, this page should be updated:
|
||||
|
||||
1. Add a row at the top of the table.
|
||||
2. Bump the **Latest release** line on [Home](Home).
|
||||
3. Run `./scripts/wiki.sh commit "docs: index v<version> release"` then `push`.
|
||||
|
||||
This is one of the [wiki update triggers](Wiki-Maintenance) that future Claude Code sessions will follow automatically.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+40
-2
@@ -1,6 +1,44 @@
|
||||
# Roadmap
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
What's next for Scarf. Public, opinionated, subject to change. The internal version of this lives in [`scarf/docs/ROADMAP.md`](https://github.com/awizemann/scarf/blob/main/scarf/docs/ROADMAP.md) — the public wiki version is a distillation.
|
||||
|
||||
## Now (2.0.x)
|
||||
|
||||
- **Stability.** 2.0 was a big multi-server release. Expect 2.0.x patches focused on edge cases in remote diagnostics, snapshot recovery, ACP reconnection, and correctness.
|
||||
- **Wiki bootstrap.** This wiki is new — Phase 2 is filling it out, Phase 3 is keeping it in sync (ongoing).
|
||||
|
||||
## Near-term
|
||||
|
||||
These are the unblocked candidates for 2.1 / 2.2:
|
||||
|
||||
- **Test coverage.** A `MockTransport` unlocks unit-testing the service layer; protocol-oriented testing for `HermesEnvService`, `ACPClient` framing, `HermesPathSet`, `HermesConfig` decoding. See [Testing](Testing).
|
||||
- **Mermaid diagrams in the wiki.** Architecture pages get a lot of value from one good diagram; deferred to keep Phase 1 focused on text.
|
||||
- **Per-project FSEvents on remote.** Remote currently has one global mtime-poll loop ([HermesFileWatcher](Core-Services) line 84 has a TODO); per-project paths would reduce remote chattiness.
|
||||
- **More MCP presets.** The curated list grows as MCP ecosystem matures.
|
||||
|
||||
## Medium-term
|
||||
|
||||
- **Custom commands palette.** A ⌘K-style palette for quick actions across all sidebar sections.
|
||||
- **Better Insights.** Rolling heatmaps, drill-downs from any chart, exportable summaries.
|
||||
- **Voice mode polish.** Speaker selection, partial-results display, better handling of long-form dictation.
|
||||
- **In-app log filtering by structured fields.** Currently text-search; a typed query (level=error AND component=gateway AND session=...) would help.
|
||||
|
||||
## Long-term / speculative
|
||||
|
||||
- **Versioned docs.** GitHub wikis don't support `/v1.0/` paths natively; could mirror to GitHub Pages with a static-site generator (deferred — see [Wiki Maintenance](Wiki-Maintenance) "Out of scope").
|
||||
- **DocC → wiki bridge** for auto-generated API reference.
|
||||
- **Translated wiki pages** if there's demand.
|
||||
|
||||
## What we're NOT doing
|
||||
|
||||
- **A web version of Scarf.** The whole point is being a native macOS app close to the metal.
|
||||
- **Background sync / push notifications.** Scarf is a viewer; Hermes runs the agent.
|
||||
- **Bundled Hermes installer.** Hermes installation belongs in Hermes-land.
|
||||
- **Closed-source / paid tier.** MIT-licensed, free, will stay that way.
|
||||
|
||||
## Suggesting features
|
||||
|
||||
Open an issue at <https://github.com/awizemann/scarf/issues> with what you want and why. Star the repo if you'd use it (signal helps prioritization).
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+54
-3
@@ -1,6 +1,57 @@
|
||||
# Servers and Remote
|
||||
# Servers & Remote
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Scarf 2.0 is multi-server. Each window binds to one Hermes install — your local `~/.hermes/` (synthesized automatically) or any number of remote SSH hosts. Server state lives in `~/Library/Preferences/com.scarf.app.plist` via the `ServerRegistry`.
|
||||
|
||||
## Adding a remote server
|
||||
|
||||
**File → Open Server… → Add Server.** Fill in:
|
||||
|
||||
| Field | Required? | Notes |
|
||||
|---|---|---|
|
||||
| Hostname or alias | yes | Resolved via your `~/.ssh/config`. Use whatever you'd type after `ssh`. |
|
||||
| User | optional | Defaults to your local username if absent. |
|
||||
| Port | optional | Defaults to 22 (or whatever `~/.ssh/config` provides). |
|
||||
| Identity file | optional | Specific private key. Otherwise, ssh-agent's loaded keys are tried in order. |
|
||||
| Remote home | optional | Override `$HOME` if Hermes lives outside the SSH user's home. |
|
||||
| Hermes binary hint | optional | E.g. `/usr/local/bin/hermes` if not on the SSH user's `PATH`. |
|
||||
|
||||
**Test Connection** runs a fast probe before saving. If `state.db` isn't found at `~/.hermes/`, it tries `/var/lib/hermes/.hermes`, `/opt/hermes/.hermes`, `/home/hermes/.hermes`, and `/root/.hermes` (common systemd / Docker layouts) and offers a one-click fill if it finds any.
|
||||
|
||||
## Remote prerequisites
|
||||
|
||||
The remote host must have:
|
||||
|
||||
1. **SSH access** — key-based auth via your local ssh-agent. Scarf never prompts for passphrases; run `ssh-add` once in Terminal before connecting.
|
||||
2. **`sqlite3`** on the remote `$PATH` — needed for the atomic DB snapshots. Install with `apt install sqlite3` (Ubuntu/Debian), `yum install sqlite` (RHEL/Fedora), or `apk add sqlite` (Alpine).
|
||||
3. **`pgrep`** on the remote `$PATH` — used by the Dashboard's "is Hermes running" check. Standard on every distro; install `procps` if missing.
|
||||
4. **`~/.hermes/` readable by the SSH user.** When Hermes runs as a separate user (systemd service, Docker container), the SSH user needs read access to `config.yaml` and `state.db`. Either (a) SSH as the Hermes user, (b) `chmod` Hermes's home to be group-readable and add your SSH user to that group, or (c) set the **Hermes data directory** field when adding the server to point at the right location (e.g. `/var/lib/hermes/.hermes`).
|
||||
|
||||
## How remote works under the hood
|
||||
|
||||
- Every remote primitive goes through [`SSHTransport`](Transport-Layer), which multiplexes ssh / scp / sftp through one ControlMaster connection.
|
||||
- `state.db` is read from atomic `sqlite3 .backup` snapshots cached at `~/Library/Caches/scarf/snapshots/<server-id>/state.db`.
|
||||
- File watching uses 3-second mtime polling.
|
||||
- Chat uses `ssh -T host -- hermes acp` with JSON-RPC over the tunnel; see [ACP Subprocess](ACP-Subprocess).
|
||||
|
||||
## Diagnostics
|
||||
|
||||
If the connection pill is green but the Dashboard shows "Stopped", "unknown", or empty values, the SSH user can't read the Hermes state files.
|
||||
|
||||
**Manage Servers → 🩺 Run Diagnostics** (or click the yellow "Can't read Hermes state" pill in the toolbar) runs **fourteen** checks in one SSH session: connectivity, `sqlite3` presence, read access to `config.yaml` and `state.db`, the effective non-login `$PATH`, etc. Each failure explains itself with a remediation hint. **Copy Full Report** dumps the whole output for bug reports.
|
||||
|
||||
## Switching the active window
|
||||
|
||||
- **⌘1** — local server window.
|
||||
- **⌘2 … ⌘9** — your saved remote servers in order.
|
||||
- **⌘⇧S** — open the Manage Servers sheet to add / remove / test connections.
|
||||
|
||||
See [Keyboard Shortcuts](Keyboard-Shortcuts).
|
||||
|
||||
## Related pages
|
||||
|
||||
- [Transport Layer](Transport-Layer) for the SSH internals (ControlMaster, snapshot mechanics).
|
||||
- [ACP Subprocess](ACP-Subprocess) for chat over SSH.
|
||||
- [Hermes Paths](Hermes-Paths) for what each remote file is.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+84
-2
@@ -1,6 +1,88 @@
|
||||
# Sidebar and Navigation
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Navigation state lives in a single `@Observable` coordinator. The sidebar is a `List` bound to it; feature views observe it. There is no router, no NavigationStack stack tracking, no global state library — just one source of truth for "where am I?".
|
||||
|
||||
## AppCoordinator
|
||||
|
||||
[`AppCoordinator.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Navigation/AppCoordinator.swift) holds three pieces of state:
|
||||
|
||||
- `selectedSection: SidebarSection` — defaults to `.dashboard`.
|
||||
- `selectedSessionId: String?` — optional deep link into the Sessions browser.
|
||||
- `selectedProjectName: String?` — optional deep link into a project dashboard.
|
||||
|
||||
It is injected at the root of each window via `.environment(coordinator)` in `ContextBoundRoot` so any view can read it with `@Environment(AppCoordinator.self) private var coordinator`.
|
||||
|
||||
Each Scarf window has its **own** `AppCoordinator` — selection in one window doesn't bleed into another. The coordinator is paired with one [`ServerContext`](Architecture-Overview) for the lifetime of the window.
|
||||
|
||||
## SidebarSection
|
||||
|
||||
`SidebarSection` is the source of truth for every sidebar item. Each case has a `rawValue` (display name) and an `icon` (SF Symbol name). The 23 cases are grouped into four sidebar headers:
|
||||
|
||||
### Monitor (4)
|
||||
|
||||
| Section | Icon |
|
||||
|---|---|
|
||||
| Dashboard | `gauge.with.dots.needle.33percent` |
|
||||
| Insights | `chart.bar` |
|
||||
| Sessions | `bubble.left.and.bubble.right` |
|
||||
| Activity | `bolt.horizontal` |
|
||||
|
||||
### Interact (3)
|
||||
|
||||
| Section | Icon |
|
||||
|---|---|
|
||||
| Chat | `text.bubble` |
|
||||
| Memory | `brain` |
|
||||
| Skills | `lightbulb` |
|
||||
|
||||
### Configure (7)
|
||||
|
||||
| Section | Icon |
|
||||
|---|---|
|
||||
| Platforms | `dot.radiowaves.left.and.right` |
|
||||
| Personalities | `theatermasks` |
|
||||
| Quick Commands | `command.square` |
|
||||
| Credential Pools | `key.horizontal` |
|
||||
| Plugins | `app.badge.checkmark` |
|
||||
| Webhooks | `arrow.up.right.square` |
|
||||
| Profiles | `person.2.crop.square.stack` |
|
||||
|
||||
### Manage (8)
|
||||
|
||||
| Section | Icon |
|
||||
|---|---|
|
||||
| Projects | `square.grid.2x2` |
|
||||
| Tools | `wrench.and.screwdriver` |
|
||||
| MCP Servers | `puzzlepiece.extension` |
|
||||
| Gateway | `antenna.radiowaves.left.and.right` |
|
||||
| Cron | `clock.arrow.2.circlepath` |
|
||||
| Health | `stethoscope` |
|
||||
| Logs | `doc.text` |
|
||||
| Settings | `gearshape` |
|
||||
|
||||
## SidebarView
|
||||
|
||||
[`SidebarView.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Navigation/SidebarView.swift) is a `List` with hardcoded `Section` headers. Selection is two-way bound to `coordinator.selectedSection`:
|
||||
|
||||
```swift
|
||||
List(selection: $coordinator.selectedSection) {
|
||||
Section("Monitor") { … }
|
||||
Section("Interact") { … }
|
||||
Section("Configure") { … }
|
||||
Section("Manage") { … }
|
||||
}
|
||||
.listStyle(.sidebar)
|
||||
```
|
||||
|
||||
There is no search bar, no collapsible-section state to persist — every section is always expanded.
|
||||
|
||||
## Routing
|
||||
|
||||
`ContentView`'s `detailView` is a single `switch coordinator.selectedSection` over every `SidebarSection` case, returning the right view for the current selection. New features add one case here (see [Adding a Feature Module](Adding-a-Feature-Module)).
|
||||
|
||||
## Multi-window
|
||||
|
||||
Each window is bound to one `ServerContext` and one `AppCoordinator`. The window menu (and `⌘1…⌘9` keyboard shortcuts) opens additional windows for other servers — see [Keyboard Shortcuts](Keyboard-Shortcuts). Closing a window destroys its coordinator; reopening reads the section back from defaults.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+48
-2
@@ -1,6 +1,52 @@
|
||||
# Testing
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
**Honest disclosure:** Scarf's test coverage is minimal. The `scarfTests/` and `scarfUITests/` targets exist but contain placeholder tests only. The project has historically relied on dogfooding — the maintainer runs Scarf against their own daily Hermes install and against a remote dogfooding host.
|
||||
|
||||
This page documents what's in place and where contributions would help most.
|
||||
|
||||
## Frameworks
|
||||
|
||||
When tests are added, the standard is **Swift Testing** (`@Suite` / `@Test` macros), not XCTest. Per the project conventions ([CLAUDE.md](https://github.com/awizemann/scarf/blob/main/CLAUDE.md)):
|
||||
|
||||
- Use `@Suite` and `@Test` macros for all new tests.
|
||||
- Protocol-oriented services for testability — the `ServerTransport` protocol is the obvious mocking seam.
|
||||
- No timing-dependent tests: use polling with early exit, not `Task.sleep` + assertion.
|
||||
- Singleton state isolation: call cleanup methods + `await Task.yield()` before assertions.
|
||||
- No `print()` in production code — use `os.Logger`. `print()` is fine in `#Preview` and test helpers.
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
xcodebuild test -project scarf/scarf.xcodeproj -scheme scarf
|
||||
```
|
||||
|
||||
Or in Xcode: ⌘U.
|
||||
|
||||
## What would be high-value to add
|
||||
|
||||
If you're looking for a contribution, these are the gaps that would matter most:
|
||||
|
||||
1. **`ServerTransport` mock + `LocalTransport` smoke tests** — every service depends on transport, so a `MockTransport` unlocks unit-testing all of them.
|
||||
2. **`HermesEnvService` round-trip tests** — non-destructive .env editing has tricky comment / blank-line preservation; would benefit from regression coverage.
|
||||
3. **`ACPClient` JSON-RPC framing tests** — feed canned JSON-RPC byte streams and assert events emitted.
|
||||
4. **`HermesPathSet` path-resolution tests** — local vs. remote home, binary hint precedence.
|
||||
5. **`HermesConfig` decoding tests** — load representative `config.yaml` fixtures and check field mapping.
|
||||
6. **`SSHTransport` shell-quoting tests** — `shellQuote` and `remotePathArg` are correctness-critical and pure functions.
|
||||
|
||||
## Manual verification flows
|
||||
|
||||
For any change that touches behavior, here's the manual checklist the maintainer runs before tagging a release:
|
||||
|
||||
- Open a local window — Dashboard loads, Sessions browser populates, Memory editor opens.
|
||||
- Open a remote window — same Dashboard / Sessions / Memory, but against the dogfooding host.
|
||||
- Send a Rich Chat message — receives a streamed response, reasoning shows if the model emits it.
|
||||
- Edit and save a memory file — change appears in Hermes on next agent turn.
|
||||
- Run a Cron job — appears in the Cron view, has correct delivery channel.
|
||||
- Toggle a tool in Tools — `hermes tools enable/disable` runs and the dot color updates.
|
||||
|
||||
## Why so little automated coverage?
|
||||
|
||||
The app is a thin GUI over Hermes — most behavior depends on (a) the OS file system, (b) SQLite, (c) SSH, (d) a long-running subprocess speaking JSON-RPC. Mocking these well is non-trivial and historically the cost has been higher than the bug rate justified. The transport protocol now makes it cheaper; the gap is finally worth filling.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+120
-2
@@ -1,6 +1,124 @@
|
||||
# Transport Layer
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
The `ServerTransport` protocol unifies local and SSH I/O. Services consume `transport.readFile(path)`, `transport.runProcess(...)`, `transport.snapshotSQLite(path)`, etc., without caring whether the bytes come from disk or the wire. Two implementations live under [`scarf/scarf/scarf/Core/Transport/`](https://github.com/awizemann/scarf/tree/main/scarf/scarf/Core/Transport): `LocalTransport` and `SSHTransport`.
|
||||
|
||||
## Protocol surface
|
||||
|
||||
[`ServerTransport.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Transport/ServerTransport.swift) exposes:
|
||||
|
||||
**Identity**
|
||||
- `contextID: ServerID` — UUID; namespaces caches under `~/Library/Caches/scarf/snapshots/<id>/`.
|
||||
- `isRemote: Bool` — true for `SSHTransport`.
|
||||
|
||||
**File I/O**
|
||||
- `readFile(_ path) -> Data`
|
||||
- `writeFile(_ path, data:)` — atomic via temp + swap; preserves `0600` mode for `.env`/`auth.json`/`*-tokens.json`.
|
||||
- `fileExists(_ path) -> Bool`
|
||||
- `stat(_ path) -> FileStat?` — size, mtime, isDirectory.
|
||||
- `listDirectory(_ path) -> [String]`
|
||||
- `createDirectory(_ path)` — idempotent, creates intermediates.
|
||||
- `removeFile(_ path)` — idempotent.
|
||||
|
||||
**Processes**
|
||||
- `runProcess(executable, args, stdin, timeout) -> ProcessResult` — blocking; captures stdout/stderr; SIGTERM on timeout.
|
||||
- `makeProcess(executable, args) -> Process` — pre-configured but not yet started; caller owns lifecycle (used by `ACPClient`).
|
||||
|
||||
**SQLite snapshots**
|
||||
- `snapshotSQLite(remotePath) -> URL` — local: returns the path unchanged. Remote: `sqlite3 .backup` on the remote, scp the result down, return a local URL into the snapshot cache.
|
||||
|
||||
**Watching**
|
||||
- `watchPaths(_ paths) -> AsyncStream<WatchEvent>` — yields `.anyChanged` on any change. Local: FSEvents (`DispatchSourceFileSystemObject`). Remote: 3-second mtime polling.
|
||||
|
||||
## Errors
|
||||
|
||||
[`TransportErrors.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Transport/TransportErrors.swift) defines `TransportError`:
|
||||
|
||||
| Case | Cause |
|
||||
|---|---|
|
||||
| `hostUnreachable(host, stderr)` | DNS, connection refused, no route. |
|
||||
| `authenticationFailed(host, stderr)` | SSH key not loaded or rejected. |
|
||||
| `hostKeyMismatch(host, stderr)` | `~/.ssh/known_hosts` mismatch. |
|
||||
| `commandFailed(exitCode, stderr)` | Remote command exited non-zero. |
|
||||
| `fileIO(path, underlying)` | Local FS error. |
|
||||
| `timeout(seconds, partialStdout)` | Hit `timeout` parameter. |
|
||||
| `other(message)` | Catch-all. |
|
||||
|
||||
Stderr-pattern classification turns raw `ssh` errors into the right case so the UI can render actionable text.
|
||||
|
||||
## LocalTransport
|
||||
|
||||
[`LocalTransport.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Transport/LocalTransport.swift) — a thin wrapper around `FileManager`, `Process`, and `DispatchSourceFileSystemObject`.
|
||||
|
||||
- **Atomic writes:** writes to `<path>.scarf.tmp`, sets `0600` if the filename suggests a secret, then `replaceItemAt` (existing) or `moveItem` (new).
|
||||
- **Process timeout:** polls every 100ms until deadline; `terminate()` if exceeded.
|
||||
- **Watching:** opens each path with `O_EVTONLY`, creates a dispatch source for `.write/.extend/.rename`, yields `.anyChanged` on event.
|
||||
- **Snapshot:** no-op — returns the path unchanged.
|
||||
|
||||
## SSHTransport
|
||||
|
||||
[`SSHTransport.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Transport/SSHTransport.swift) — every primitive becomes an `ssh`/`scp`/`sftp` invocation, multiplexed over a single ControlMaster connection.
|
||||
|
||||
### ControlMaster pooling
|
||||
|
||||
Without ControlMaster, every remote call re-authenticates (500ms-2s each). With it, the first call sets up the master socket; subsequent calls reuse the same TCP+crypto session at ~5ms each.
|
||||
|
||||
The SSH option set is constructed by `sshArgs(extra:)`:
|
||||
|
||||
```
|
||||
-o ControlMaster=auto
|
||||
-o ControlPath=~/Library/Caches/scarf/ssh/%C
|
||||
-o ControlPersist=600 # keep alive 600s after last use
|
||||
-o ServerAliveInterval=30 # keepalive every 30s
|
||||
-o ServerAliveCountMax=3 # disconnect after 3 missed
|
||||
-o ConnectTimeout=10
|
||||
-o StrictHostKeyChecking=accept-new
|
||||
-o LogLevel=QUIET # binary-clean stdin/stdout for JSON-RPC
|
||||
-o BatchMode=yes # ssh-agent only; never prompt
|
||||
```
|
||||
|
||||
`%C` hashes `(host, user, port)` — multiple Scarf windows for the same host share one socket. `closeControlMaster()` issues `ssh -O exit` for clean shutdown.
|
||||
|
||||
### Path handling
|
||||
|
||||
Two helpers prevent shell-expansion breakage:
|
||||
|
||||
- `shellQuote(_:)` — wraps unsafe strings in single quotes, escaping embedded singles as `'\''`. Safe characters (alphanumerics + `@%+=:,./-_`) pass through unquoted.
|
||||
- `remotePathArg(_:)` — converts `~/...` to `$HOME/...` (because shells don't expand `~` inside quotes) and double-quotes so `$HOME` expands but spaces don't break.
|
||||
|
||||
### File I/O over SSH
|
||||
|
||||
- `readFile`: `ssh host -- sh -c 'cat <path>'`; classifies "No such file" into typed `fileIO`.
|
||||
- `writeFile`: scp to `<path>.scarf.tmp`, then remote `mv` — atomic; cleans the orphan on failure.
|
||||
- `stat`: tries GNU `stat -c "%s %Y %F"`, falls back to BSD `stat -f "%z %m %HT"`.
|
||||
- `listDirectory`: `ls -A <path>`. `createDirectory`: `mkdir -p`. `removeFile`: `rm -f`.
|
||||
|
||||
### Process execution
|
||||
|
||||
- `runProcess`: wraps `<exe> <args>` in `sh -c` so paths can use `$HOME`. Inherits `SSH_AUTH_SOCK` from the user's GUI environment so 1Password / Secretive agents work.
|
||||
- `makeProcess`: returns `/usr/bin/ssh -T <opts> host -- sh -c '<exe> <args>'`. The `-T` disables PTY allocation so stdin/stdout stay binary-clean for JSON-RPC.
|
||||
|
||||
### SQLite snapshot
|
||||
|
||||
The trickiest operation. The remote runs:
|
||||
|
||||
```
|
||||
sqlite3 "$HOME/.hermes/state.db" ".backup '/tmp/scarf-snapshot-XYZ.db'" && \
|
||||
sqlite3 '/tmp/scarf-snapshot-XYZ.db' "PRAGMA journal_mode=DELETE;"
|
||||
```
|
||||
|
||||
`.backup` is WAL-safe — it captures a consistent snapshot without blocking writers. The `PRAGMA journal_mode=DELETE` strips WAL mode so the snapshot is self-contained (no `-wal`/`-shm` sidecars). `scp` pulls it to `~/Library/Caches/scarf/snapshots/<id>/state.db`. The remote temp is removed.
|
||||
|
||||
### Remote watching
|
||||
|
||||
3-second polling: the remote runs a one-liner concatenating mtimes for the watched paths, hashed into a signature. When the signature changes, the stream yields `.anyChanged`. Transient connection drops are tolerated.
|
||||
|
||||
### Required tools on the remote
|
||||
|
||||
- `sqlite3` for the snapshot operation.
|
||||
- `pgrep` for the Dashboard's "is Hermes running" check.
|
||||
- `~/.hermes/` readable by the SSH user.
|
||||
|
||||
See [Servers & Remote](Servers-and-Remote) for setup and troubleshooting.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+34
-2
@@ -1,6 +1,38 @@
|
||||
# Uninstalling
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Scarf is a self-contained `.app` bundle with no installers, launch agents, or kernel extensions. Removing it is two steps; cleaning up its caches is one more.
|
||||
|
||||
## Quit and remove the app
|
||||
|
||||
1. Quit Scarf (⌘Q).
|
||||
2. Drag **Scarf.app** from `/Applications` to the Trash.
|
||||
|
||||
That's the minimum. Scarf is gone.
|
||||
|
||||
## Clean up Scarf's caches and prefs
|
||||
|
||||
If you want a complete uninstall, also remove:
|
||||
|
||||
```bash
|
||||
rm -rf ~/Library/Caches/scarf # remote SQLite snapshots, ssh ControlMaster sockets
|
||||
rm -f ~/Library/Preferences/com.scarf.app.plist # app preferences + server registry
|
||||
```
|
||||
|
||||
The cache holds atomic SQLite snapshots pulled from remote Hermes hosts and SSH ControlMaster sockets — both are safe to delete; Scarf rebuilds them on demand.
|
||||
|
||||
## What Scarf does NOT touch
|
||||
|
||||
Scarf reads Hermes's data; it does not own it. The following are **not** removed by uninstalling:
|
||||
|
||||
- `~/.hermes/` — your Hermes install, sessions, memory, config, etc.
|
||||
- `~/.ssh/` — SSH keys and config used to reach remote servers.
|
||||
- `~/.local/bin/hermes` (or wherever your `hermes` CLI lives).
|
||||
|
||||
To uninstall Hermes itself, follow the Hermes documentation — that's a separate process.
|
||||
|
||||
## What about the wiki?
|
||||
|
||||
Just for completeness: the GitHub wiki at <https://github.com/awizemann/scarf/wiki> is a separate git repo from the main one. Uninstalling the app has no effect on the wiki.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
+34
-2
@@ -1,6 +1,38 @@
|
||||
# Updating
|
||||
|
||||
> **TODO: document.** This page is a stub. See [Wiki Maintenance](Wiki-Maintenance).
|
||||
Scarf uses [Sparkle](https://sparkle-project.org/) to deliver automatic updates from a GitHub-Pages-hosted appcast at `https://awizemann.github.io/scarf/appcast.xml`. Each release is EdDSA-signed by the maintainer's private key; Scarf refuses any update whose signature doesn't verify against the embedded `SUPublicEDKey`.
|
||||
|
||||
## Automatic updates
|
||||
|
||||
By default, Scarf checks for updates on launch and every 24 hours after that. When a new version is available, Sparkle pops up its standard dialog with the release notes and asks whether to install.
|
||||
|
||||
To **disable automatic checks**, open **Settings → General → Updates**. You can re-enable later from the same place.
|
||||
|
||||
## Manual check
|
||||
|
||||
Two ways to force a check:
|
||||
|
||||
- **Settings → General → Updates → Check for Updates Now**
|
||||
- **Menu bar → Check for Updates**
|
||||
|
||||
## Beta / draft releases
|
||||
|
||||
Scarf does not ship beta channels — the appcast only carries the latest published release. Drafts pushed via `./scripts/release.sh <ver> --draft` are uploaded to GitHub but not added to the appcast, so they don't reach existing installs until promoted manually.
|
||||
|
||||
## Downgrading
|
||||
|
||||
There's no in-app downgrade. To run an older build:
|
||||
|
||||
1. Quit Scarf.
|
||||
2. Move the current `Scarf.app` to the Trash.
|
||||
3. Download the older zip from [Releases](https://github.com/awizemann/scarf/releases) and drag it to `/Applications`.
|
||||
4. Disable **automatic checks** in **Settings → General → Updates**, otherwise Sparkle will offer to update you back.
|
||||
|
||||
App preferences live in `~/Library/Preferences/com.scarf.app.plist` and are forward/backward compatible across versions in normal cases.
|
||||
|
||||
## Update fails to apply
|
||||
|
||||
If Sparkle reports a signature mismatch or download error, see the [Health](Gateway-Cron-Health-Logs) view for details. Re-trigger the check; if it persists, file an issue with the Sparkle log content.
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-20 — stub_
|
||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
||||
|
||||
Reference in New Issue
Block a user