diff --git a/ACP-Subprocess.md b/ACP-Subprocess.md index 3593fc6..e20090c 100644 --- a/ACP-Subprocess.md +++ b/ACP-Subprocess.md @@ -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`. + +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 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`. 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_ diff --git a/Adding-a-Feature-Module.md b/Adding-a-Feature-Module.md index 8d2bbaa..ed223d6 100644 --- a/Adding-a-Feature-Module.md +++ b/Adding-a-Feature-Module.md @@ -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_ diff --git a/Adding-a-Service.md b/Adding-a-Service.md index 4b7171c..ffda026 100644 --- a/Adding-a-Service.md +++ b/Adding-a-Service.md @@ -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_ diff --git a/Build-and-Run.md b/Build-and-Run.md index b48e938..c45385b 100644 --- a/Build-and-Run.md +++ b/Build-and-Run.md @@ -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/ 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_ diff --git a/Chat.md b/Chat.md index dfe825d..2454ae6 100644 --- a/Chat.md +++ b/Chat.md @@ -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_ diff --git a/Contributing.md b/Contributing.md index 521a1bb..97fdf4a 100644 --- a/Contributing.md +++ b/Contributing.md @@ -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 . +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 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_ diff --git a/Core-Services.md b/Core-Services.md index 68939c1..282b96a 100644 --- a/Core-Services.md +++ b/Core-Services.md @@ -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_ diff --git a/Dashboard.md b/Dashboard.md index 8b0e1a8..e34e287 100644 --- a/Dashboard.md +++ b/Dashboard.md @@ -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_ diff --git a/Data-Model.md b/Data-Model.md index 69353e5..e6d12b0 100644 --- a/Data-Model.md +++ b/Data-Model.md @@ -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_ diff --git a/First-Run.md b/First-Run.md index 158ed6d..4317d39 100644 --- a/First-Run.md +++ b/First-Run.md @@ -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_ diff --git a/Gateway-Cron-Health-Logs.md b/Gateway-Cron-Health-Logs.md index 50ee00b..a9641aa 100644 --- a/Gateway-Cron-Health-Logs.md +++ b/Gateway-Cron-Health-Logs.md @@ -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_ diff --git a/Hermes-Version-Compatibility.md b/Hermes-Version-Compatibility.md index e52b070..d0bb48f 100644 --- a/Hermes-Version-Compatibility.md +++ b/Hermes-Version-Compatibility.md @@ -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_ diff --git a/Insights-and-Activity.md b/Insights-and-Activity.md index e17ba7c..0e58276 100644 --- a/Insights-and-Activity.md +++ b/Insights-and-Activity.md @@ -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_ diff --git a/Keyboard-Shortcuts.md b/Keyboard-Shortcuts.md index 4296abe..6f0a670 100644 --- a/Keyboard-Shortcuts.md +++ b/Keyboard-Shortcuts.md @@ -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_ diff --git a/MCP-Servers-Plugins-Webhooks-Tools.md b/MCP-Servers-Plugins-Webhooks-Tools.md index 8e2b8ce..47b37fb 100644 --- a/MCP-Servers-Plugins-Webhooks-Tools.md +++ b/MCP-Servers-Plugins-Webhooks-Tools.md @@ -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_ diff --git a/Memory-and-Skills.md b/Memory-and-Skills.md index d11624d..18089a3 100644 --- a/Memory-and-Skills.md +++ b/Memory-and-Skills.md @@ -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_ diff --git a/Platforms-Personalities-QuickCommands.md b/Platforms-Personalities-QuickCommands.md index f80996f..477e923 100644 --- a/Platforms-Personalities-QuickCommands.md +++ b/Platforms-Personalities-QuickCommands.md @@ -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//`. + +**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_ diff --git a/Projects-and-Profiles.md b/Projects-and-Profiles.md index 6f4d487..453f106 100644 --- a/Projects-and-Profiles.md +++ b/Projects-and-Profiles.md @@ -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 `/.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 `/.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_ diff --git a/Release-Notes-Index.md b/Release-Notes-Index.md index ad132e1..b15087f 100644 --- a/Release-Notes-Index.md +++ b/Release-Notes-Index.md @@ -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/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 ` 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 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_ diff --git a/Roadmap.md b/Roadmap.md index 5f7b0f4..9b23084 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -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 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_ diff --git a/Servers-and-Remote.md b/Servers-and-Remote.md index d71d8f4..e9cadcb 100644 --- a/Servers-and-Remote.md +++ b/Servers-and-Remote.md @@ -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//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_ diff --git a/Sidebar-and-Navigation.md b/Sidebar-and-Navigation.md index 1d37df7..aa20bee 100644 --- a/Sidebar-and-Navigation.md +++ b/Sidebar-and-Navigation.md @@ -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_ diff --git a/Testing.md b/Testing.md index 7eb9d7b..1d5282b 100644 --- a/Testing.md +++ b/Testing.md @@ -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_ diff --git a/Transport-Layer.md b/Transport-Layer.md index 510b2c9..b693cb7 100644 --- a/Transport-Layer.md +++ b/Transport-Layer.md @@ -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//`. +- `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` — 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 `.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 '`; classifies "No such file" into typed `fileIO`. +- `writeFile`: scp to `.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 `. `createDirectory`: `mkdir -p`. `removeFile`: `rm -f`. + +### Process execution + +- `runProcess`: wraps ` ` 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 host -- sh -c ' '`. 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//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_ diff --git a/Uninstalling.md b/Uninstalling.md index 8043c5d..9904ef0 100644 --- a/Uninstalling.md +++ b/Uninstalling.md @@ -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 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_ diff --git a/Updating.md b/Updating.md index 9fd78c9..4ca145d 100644 --- a/Updating.md +++ b/Updating.md @@ -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 --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_