mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
docs(wiki-audit): full v2.5 cross-section sync against active codebase
+20
-5
@@ -1,18 +1,33 @@
|
|||||||
# ACP Subprocess
|
# ACP Subprocess
|
||||||
|
|
||||||
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.
|
ACP — Agent Client Protocol — is Hermes's chat protocol: JSON-RPC 2.0 over stdio (or its bidirectional equivalent). 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 channel.
|
||||||
|
|
||||||
## Subprocess construction
|
## ACPClient + ACPChannel
|
||||||
|
|
||||||
[`ACPClient`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Services/ACPClient.swift) is an `actor` that owns the subprocess and exposes an `AsyncStream<ACPEvent>`.
|
[`ACPClient`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/ACP/ACPClient.swift) is an `actor` that owns whatever-it-is that ferries JSON-RPC bytes back and forth, and exposes an `AsyncStream<ACPEvent>`. The "whatever-it-is" is abstracted behind the [`ACPChannel`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/ACP/ACPChannel.swift) protocol so Mac and iOS share the client without `#if os(...)`:
|
||||||
|
|
||||||
The `Process` is created via `transport.makeProcess(executable: "hermes", args: ["acp"])`:
|
| Channel | Where it lives | What it wraps |
|
||||||
|
|---|---|---|
|
||||||
|
| [`ProcessACPChannel`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/ACP/ProcessACPChannel.swift) | ScarfCore | A Foundation `Process` running `hermes acp` (local) or `ssh -T host -- hermes acp` (Mac remote). |
|
||||||
|
| [`SSHExecACPChannel`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfIOS/Sources/ScarfIOS/SSHExecACPChannel.swift) | ScarfIOS | A Citadel SSH exec channel (no Foundation `Process` available on iOS) — bidirectional stdin/stdout streams over the SSH transport. |
|
||||||
|
|
||||||
|
ACPClient consumes whichever channel the host provides at init; from there the protocol handling is identical.
|
||||||
|
|
||||||
|
## Process channel construction (Mac)
|
||||||
|
|
||||||
|
`ProcessACPChannel` 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.
|
- **Local:** spawns `hermes acp` with `HermesFileService.enrichedEnvironment()` so MCP servers and shell tools find brew/nvm/asdf binaries on `PATH`. `TERM` is removed so terminal escapes don't pollute JSON-RPC.
|
||||||
- **Remote:** the transport returns `/usr/bin/ssh -T <opts> host -- hermes acp`. `SSH_AUTH_SOCK` is inherited so the GUI-launched Scarf reaches the user's ssh-agent. `TERM` is removed.
|
- **Remote:** the transport returns `/usr/bin/ssh -T <opts> host -- hermes acp`. `SSH_AUTH_SOCK` is inherited so the GUI-launched Scarf reaches the user's ssh-agent. `TERM` is removed.
|
||||||
|
|
||||||
`-T` (no PTY) is critical — without it stdin/stdout would be PTY-cooked and the JSON-RPC framing would break.
|
`-T` (no PTY) is critical — without it stdin/stdout would be PTY-cooked and the JSON-RPC framing would break.
|
||||||
|
|
||||||
|
## SSH exec channel construction (iOS)
|
||||||
|
|
||||||
|
`SSHExecACPChannel` opens a Citadel exec channel against the user's configured Hermes host, runs `hermes acp` over it, and surfaces the channel's `inbound` / `outbound` byte streams as the same stdin/stdout abstraction `ACPClient` consumes from `Process`. There's no PTY allocation — Citadel's exec is binary-clean by default.
|
||||||
|
|
||||||
|
Because iOS's PATH is stripped on non-interactive SSH (Citadel doesn't source rc files — see [Transport Layer § CitadelServerTransport](Transport-Layer)), the channel inlines `PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"` in front of `hermes acp` exactly the way `runProcess` does. Same fix, same reason.
|
||||||
|
|
||||||
## Lifecycle
|
## Lifecycle
|
||||||
|
|
||||||
| Phase | What happens |
|
| Phase | What happens |
|
||||||
@@ -96,4 +111,4 @@ Raw error messages are noisy. `ACPErrorHint` (in the same file) pattern-matches
|
|||||||
| "Rate limit" / 429 | "AI provider rate-limited; try again later" |
|
| "Rate limit" / 429 | "AI provider rate-limited; try again later" |
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (ScarfCore extraction + ACPChannel abstraction + iOS SSHExecACPChannel section)_
|
||||||
|
|||||||
+43
-16
@@ -1,19 +1,33 @@
|
|||||||
# Adding a Feature Module
|
# Adding a Feature Module
|
||||||
|
|
||||||
The MVVM-F recipe. Adding a feature touches 4 existing files and creates 2 new ones.
|
The MVVM-F recipe. Adding a Mac feature touches 4 existing files and creates 2 new ones; iOS additions are simpler since each tab is its own module.
|
||||||
|
|
||||||
## Directory shape
|
> **Should the ViewModel live in ScarfCore or the Mac/iOS target?** Per [ScarfCore Package](ScarfCore-Package): if the VM does I/O against Hermes (transport reads, parsing, attribution lookups, formatting), it belongs in [`Packages/ScarfCore/Sources/ScarfCore/ViewModels/`](https://github.com/awizemann/scarf/tree/main/scarf/Packages/ScarfCore/Sources/ScarfCore/ViewModels) so iOS can reuse it. If the VM owns Mac- or iOS-specific UI state (toolbar items, keyboard shortcut bindings), keep it in the target.
|
||||||
|
|
||||||
|
## Directory shape (Mac)
|
||||||
|
|
||||||
```
|
```
|
||||||
scarf/scarf/scarf/Features/MyFeature/
|
scarf/scarf/scarf/Features/MyFeature/
|
||||||
Views/
|
Views/
|
||||||
MyFeatureView.swift
|
MyFeatureView.swift
|
||||||
ViewModels/
|
ViewModels/
|
||||||
MyFeatureViewModel.swift
|
MyFeatureViewModel.swift ← target-local, only if Mac-specific UI state
|
||||||
```
|
```
|
||||||
|
|
||||||
Both subdirectories are conventional — even features that only have one view of each follow this shape so file discovery is consistent.
|
Both subdirectories are conventional — even features that only have one view of each follow this shape so file discovery is consistent.
|
||||||
|
|
||||||
|
If the ViewModel is shared, the Mac feature module only contains the View(s) and reaches into `ScarfCore.MyFeatureViewModel`.
|
||||||
|
|
||||||
|
## Directory shape (iOS)
|
||||||
|
|
||||||
|
```
|
||||||
|
scarf/Scarf iOS/MyFeature/
|
||||||
|
MyFeatureView.swift
|
||||||
|
MyFeatureView+Components.swift (optional split for compositional sub-views)
|
||||||
|
```
|
||||||
|
|
||||||
|
iOS feature modules are flatter — no Views/ViewModels/ subdirectories. Each tab in [`ScarfGoTabRoot`](https://github.com/awizemann/scarf/blob/main/scarf/Scarf%20iOS/App/ScarfGoTabRoot.swift) wires the feature view directly. Adding a *primary* tab is rare in v2.5; secondary screens just push onto the parent tab's `NavigationStack`.
|
||||||
|
|
||||||
## Step 1: Create the ViewModel
|
## Step 1: Create the ViewModel
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
@@ -101,17 +115,13 @@ enum SidebarSection: String, CaseIterable, Identifiable {
|
|||||||
|
|
||||||
## Step 4: Register in SidebarView
|
## 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`:
|
In [`Navigation/SidebarView.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Navigation/SidebarView.swift), add the case to the right `Section` array. The current shape uses a `[Section]` declaration; add your case to the matching items list:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Section("Interact") {
|
Section(title: "Interact", items: [.chat, .memory, .skills, .myFeature]),
|
||||||
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.
|
Pick the section thematically — Monitor for views, Projects for project-scoped surfaces, Interact for talking-to-Hermes, Configure for setup, Manage for operational. See [Sidebar & Navigation](Sidebar-and-Navigation) for the canonical 5-group structure (22 cases as of v2.5).
|
||||||
|
|
||||||
## Step 5: Wire routing
|
## Step 5: Wire routing
|
||||||
|
|
||||||
@@ -126,7 +136,16 @@ case .myFeature: MyFeatureView(context: serverContext)
|
|||||||
|
|
||||||
## Step 6: (If your feature uses a new service)
|
## 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).
|
If you needed a new service to back this feature, decide between [`ScarfCore`](ScarfCore-Package) (shared with iOS) and Mac-only `Core/Services/`, then inject any shared instance in `ContextBoundRoot` via `.environment(...)`. See [Adding a Service](Adding-a-Service).
|
||||||
|
|
||||||
|
## Step 7: (If the feature should also be on iOS)
|
||||||
|
|
||||||
|
Mac and iOS share data + view-models via ScarfCore but have separate views. To bring `MyFeature` to ScarfGo:
|
||||||
|
|
||||||
|
1. Add `Scarf iOS/MyFeature/MyFeatureView.swift` consuming the same ScarfCore ViewModel.
|
||||||
|
2. Add a row, sub-tab, or tab to the appropriate parent in [`ScarfGoTabRoot.swift`](https://github.com/awizemann/scarf/blob/main/scarf/Scarf%20iOS/App/ScarfGoTabRoot.swift). Most additions push onto an existing tab's `NavigationStack` — not a new tab. New tabs in v2.5+ require Coordinator + product-design review (5-tab cap on iPhone today).
|
||||||
|
3. Apply ScarfDesign tokens — see [Design System](Design-System). Heads-up: iOS uses semantic Dynamic Type tokens (`.font(.body)` etc.) for body copy and `ScarfFont` only for chrome/badges/intentional fixed-size; Mac uses `ScarfFont` everywhere.
|
||||||
|
4. Re-test against the iOS simulator. Verify multi-server switching doesn't leak feature state.
|
||||||
|
|
||||||
## Cross-feature rules
|
## Cross-feature rules
|
||||||
|
|
||||||
@@ -135,16 +154,24 @@ The hard rules ([CLAUDE.md](https://github.com/awizemann/scarf/blob/main/CLAUDE.
|
|||||||
- **Features never import sibling features.** If `MyFeature` needs data another feature also uses, the data lives in a service, not in that other feature.
|
- **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 = ...`.
|
- **Cross-feature navigation goes through `AppCoordinator`.** Set `coordinator.selectedSection = .otherFeature` and (if needed) `coordinator.selectedSessionId = ...`.
|
||||||
|
|
||||||
## Files touched
|
## Files touched (Mac)
|
||||||
|
|
||||||
- ✏️ `Navigation/AppCoordinator.swift` — 1 enum case, 1 icon line.
|
- ✏️ `Navigation/AppCoordinator.swift` — 1 enum case, 1 icon line.
|
||||||
- ✏️ `Navigation/SidebarView.swift` — add to the right `Section`.
|
- ✏️ `Navigation/SidebarView.swift` — add to the right `Section` items array.
|
||||||
- ✏️ `ContentView.swift` — 1 switch case.
|
- ✏️ `ContentView.swift` — 1 switch case.
|
||||||
- ✏️ `scarfApp.swift` — only if you needed to inject a new shared service.
|
- ✏️ `scarfApp.swift` — only if you needed to inject a new shared service.
|
||||||
- ✨ `Features/MyFeature/Views/MyFeatureView.swift` — new.
|
- ✨ `Features/MyFeature/Views/MyFeatureView.swift` — new.
|
||||||
- ✨ `Features/MyFeature/ViewModels/MyFeatureViewModel.swift` — new.
|
- ✨ `Features/MyFeature/ViewModels/MyFeatureViewModel.swift` OR `Packages/ScarfCore/.../ViewModels/MyFeatureViewModel.swift` — new (location depends on whether iOS will share it).
|
||||||
|
|
||||||
Total: ~5-10 lines across 4 existing files, plus 2 new files.
|
Total: ~5–10 lines across 4 existing files, plus 1–2 new files.
|
||||||
|
|
||||||
|
## Files touched (iOS, optional)
|
||||||
|
|
||||||
|
- ✏️ `Scarf iOS/App/ScarfGoTabRoot.swift` — only if adding a primary tab.
|
||||||
|
- ✏️ Whichever existing tab pushes the new view onto its `NavigationStack`.
|
||||||
|
- ✨ `Scarf iOS/MyFeature/MyFeatureView.swift` — new.
|
||||||
|
|
||||||
|
iOS feature additions usually skip the AppCoordinator step — the iOS coordinator (`ScarfGoCoordinator`) handles cross-tab signalling, not per-view dispatch.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (added ScarfCore VM placement guidance + iOS step + 5-group sidebar reference)_
|
||||||
|
|||||||
+15
-5
@@ -1,6 +1,16 @@
|
|||||||
# Adding a Service
|
# Adding a Service
|
||||||
|
|
||||||
Services live under `scarf/scarf/scarf/Core/Services/`. They mediate between Hermes (filesystem, SQLite, subprocess) and feature ViewModels. The recipe is short.
|
Services mediate between Hermes (filesystem, SQLite, subprocess) and feature ViewModels. As of v2.5 there are **two homes** for a service depending on whether iOS needs it:
|
||||||
|
|
||||||
|
| Home | When to use | Path |
|
||||||
|
|---|---|---|
|
||||||
|
| **`ScarfCore` package** | The service does pure I/O against Hermes (transport reads, parsing, attribution, formatting) — **default choice**, since iOS likely needs it too. | [`scarf/Packages/ScarfCore/Sources/ScarfCore/Services/`](https://github.com/awizemann/scarf/tree/main/scarf/Packages/ScarfCore/Sources/ScarfCore/Services) |
|
||||||
|
| **Mac target** (`scarf/Core/Services/`) | The service depends on AppKit, Sparkle, NSWorkspace, AppleScript, or anything else iOS can't link. | [`scarf/scarf/scarf/Core/Services/`](https://github.com/awizemann/scarf/tree/main/scarf/scarf/scarf/Core/Services) |
|
||||||
|
| **`ScarfIOS` package** | iOS-only — needs Citadel, iOS Keychain, UIKit. | [`scarf/Packages/ScarfIOS/Sources/ScarfIOS/`](https://github.com/awizemann/scarf/tree/main/scarf/Packages/ScarfIOS/Sources/ScarfIOS) |
|
||||||
|
|
||||||
|
When in doubt, **start in ScarfCore.** Promote to a target only when you hit a framework restriction. See [ScarfCore Package](ScarfCore-Package) for the package boundary rationale.
|
||||||
|
|
||||||
|
The recipe below applies regardless of home.
|
||||||
|
|
||||||
## Pick the isolation
|
## Pick the isolation
|
||||||
|
|
||||||
@@ -64,9 +74,9 @@ actor MyStatefulService {
|
|||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
- **Take `ServerContext` in `init`.** Never hardcode `ServerContext.local` — services must work against any window's bound server.
|
- **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)).
|
- **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)). On iOS specifically, `Process` doesn't exist at all — services in ScarfCore must stay platform-agnostic.
|
||||||
- **Surface errors as `throws` or `Result`.** Don't swallow them; the UI knows what to do with them.
|
- **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 log to `print`** — use `os.Logger` (`logger.error()` for unexpected, `logger.warning()` for expected). v2.5's logger conversion sweep removed the last `print("[Scarf] …")` calls; new code should follow.
|
||||||
- **Don't do synchronous file I/O on `@MainActor`.** Either dispatch via `Task.detached { }.value`, or expose async methods.
|
- **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
|
## Wiring the service into a feature
|
||||||
@@ -106,7 +116,7 @@ Use Environment for things every window has exactly one of — file watcher, ser
|
|||||||
|
|
||||||
## Tests
|
## 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.
|
Service code that uses transport is testable with a mock transport. ScarfCore ships a `MockTransport` and the test suite exercises the contract — see [Testing](Testing). Add tests in `scarf/Packages/ScarfCore/Tests/ScarfCoreTests/` for ScarfCore services; in `scarf/scarfTests/` for Mac-only ones.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (ScarfCore / Mac / ScarfIOS placement matrix; logger sweep note)_
|
||||||
|
|||||||
+77
-31
@@ -1,12 +1,13 @@
|
|||||||
# Build and Run
|
# Build and Run
|
||||||
|
|
||||||
Scarf is a single Xcode target with one SPM dependency (Sparkle). No CocoaPods, no Carthage, no submodules.
|
Scarf is one Xcode project with **two app targets**: `scarf` (the macOS app) and `scarf mobile` (ScarfGo, iOS). Both targets share three local SwiftPM packages (`ScarfCore`, `ScarfIOS`, `ScarfDesign`) and pull in Sparkle (Mac auto-update) + Citadel (iOS pure-Swift SSH) as remote dependencies. No CocoaPods, no Carthage, no submodules.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- **macOS 14.6+ (Sonoma)** on the dev machine.
|
- **macOS 14.6+ (Sonoma)** on the dev machine.
|
||||||
- **Xcode 16.0+**.
|
- **Xcode 16.0+**.
|
||||||
- **Hermes** at `~/.hermes/` (so the local server window has something to point at — see [First Run](First-Run)).
|
- **Hermes** at `~/.hermes/` (so the local Mac window has something to point at — see [First Run](First-Run)).
|
||||||
|
- For iOS: **Xcode iOS 18.0 simulator** (or a real iPhone running iOS 18+) and a Hermes-running host you can SSH to from the simulator.
|
||||||
|
|
||||||
## Open in Xcode
|
## Open in Xcode
|
||||||
|
|
||||||
@@ -22,6 +23,8 @@ Build with ⌘B; run with ⌘R.
|
|||||||
|
|
||||||
## Build from the command line
|
## Build from the command line
|
||||||
|
|
||||||
|
### Mac scheme (`scarf`)
|
||||||
|
|
||||||
Debug build:
|
Debug build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -36,6 +39,18 @@ xcodebuild -project scarf/scarf.xcodeproj -scheme scarf \
|
|||||||
-arch arm64 -arch x86_64 ONLY_ACTIVE_ARCH=NO build
|
-arch arm64 -arch x86_64 ONLY_ACTIVE_ARCH=NO build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### iOS scheme (`scarf mobile`)
|
||||||
|
|
||||||
|
Debug build for the simulator (no physical device needed):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xcodebuild -project scarf/scarf.xcodeproj -scheme "scarf mobile" \
|
||||||
|
-configuration Debug \
|
||||||
|
-destination "generic/platform=iOS Simulator" build
|
||||||
|
```
|
||||||
|
|
||||||
|
The iOS target is iPhone-only as of v2.5: `TARGETED_DEVICE_FAMILY = 1`, `SUPPORTS_MACCATALYST = NO`, `SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO`.
|
||||||
|
|
||||||
## Project layout
|
## Project layout
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -43,31 +58,53 @@ scarf/ repo root
|
|||||||
CLAUDE.md project instructions for Claude Code
|
CLAUDE.md project instructions for Claude Code
|
||||||
CONTRIBUTING.md
|
CONTRIBUTING.md
|
||||||
README.md
|
README.md
|
||||||
releases/v<ver>/ per-version notes + appcast entry
|
icon-v2.5.png README hero icon
|
||||||
|
assets/screenshots/ ScarfGo screenshot gallery (in README)
|
||||||
|
design/ Reference UI kit (JSX mockups + token bundle source)
|
||||||
|
releases/v<ver>/ per-version notes (RELEASE_NOTES.md, TESTFLIGHT_CHECKLIST.md, APP_STORE_METADATA.md)
|
||||||
|
templates/ Community .scarftemplate catalog source
|
||||||
|
tools/ build-catalog.py + tests
|
||||||
scripts/
|
scripts/
|
||||||
release.sh full release pipeline
|
release.sh Mac release pipeline (archive + notarize + appcast + tag)
|
||||||
wiki.sh this wiki helper
|
wiki.sh GitHub wiki helper
|
||||||
ExportOptions.plist
|
catalog.sh .scarftemplate catalog publishing
|
||||||
scarf/ Xcode project root
|
scarf/ Xcode project root
|
||||||
scarf.xcodeproj
|
scarf.xcodeproj Two targets: scarf, scarf mobile
|
||||||
docs/ internal dev notes (PRD, Discovery, ARCH)
|
docs/ Internal dev notes + PRIVACY_POLICY.md
|
||||||
standards/ read-only reference standards
|
Packages/ Local SwiftPM packages
|
||||||
scarf/ APP TARGET — start here
|
ScarfCore/ Shared models, services, view-models, transport,
|
||||||
scarfApp.swift @main App
|
ACP, parsing, security — Mac + iOS both link this
|
||||||
ContentView.swift window root
|
ScarfIOS/ iOS-only: CitadelServerTransport, KeychainSSHKeyStore,
|
||||||
|
SSHExecACPChannel, CitadelSSHService
|
||||||
|
ScarfDesign/ Rust palette + tokens + components — Mac + iOS
|
||||||
|
scarf/ MAC TARGET — start here
|
||||||
|
scarfApp.swift @main App, multi-window, ServerLiveStatus polling
|
||||||
|
ContentView.swift window root, detail view switch
|
||||||
Core/
|
Core/
|
||||||
Services/ 9 services
|
Services/ Mac-only services (Sparkle wrapper, etc.)
|
||||||
Models/ 13+ models
|
Persistence/ ServerRegistry (plist)
|
||||||
Transport/ 4 transport files
|
Features/ Mac feature modules
|
||||||
Persistence/ ServerRegistry
|
|
||||||
Utilities/ markdown helpers
|
|
||||||
Features/ 25 feature modules
|
|
||||||
Navigation/ AppCoordinator + SidebarView
|
Navigation/ AppCoordinator + SidebarView
|
||||||
Assets.xcassets
|
Assets.xcassets Mac app icon (rust set), AccentColor
|
||||||
Info.plist
|
Info.plist + scarf.entitlements
|
||||||
scarf.entitlements
|
Scarf iOS/ iOS TARGET — ScarfGo
|
||||||
scarfTests/
|
App/ ScarfGoTabRoot, ScarfGoCoordinator, theme glue
|
||||||
scarfUITests/
|
Onboarding/ SSH key generation + connection test
|
||||||
|
Servers/ Multi-server list + Forget flow
|
||||||
|
Dashboard/ Stats grid + recent sessions + Switch server button
|
||||||
|
Chat/ Single-column chat + composer + project picker
|
||||||
|
Projects/ Project detail tabs (Dashboard / Site / Sessions)
|
||||||
|
Skills/ Hub / Installed / Updates sub-tabs
|
||||||
|
Memory/ MEMORY.md + USER.md editor
|
||||||
|
Cron/ Read-only list with human-readable schedules
|
||||||
|
Settings/ Read view + Quick Edits sheet (7 keys)
|
||||||
|
Components/ ScarfDesign-wrapped iOS controls
|
||||||
|
Notifications/ APNs skeleton (gated apnsEnabled = false)
|
||||||
|
Assets.xcassets iOS rust app icon set + accent color
|
||||||
|
scarfTests/ Mac target unit tests (Swift Testing)
|
||||||
|
scarfUITests/ Mac UI tests
|
||||||
|
Scarf iOSTests/ iOS unit test placeholder
|
||||||
|
Scarf iOSUITests/ iOS UI test placeholder
|
||||||
```
|
```
|
||||||
|
|
||||||
## Swift 6 concurrency
|
## Swift 6 concurrency
|
||||||
@@ -79,23 +116,32 @@ The project compiles with strict concurrency on. Two non-negotiables ([detail in
|
|||||||
|
|
||||||
## What to look at first
|
## 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.
|
1. [`scarfApp.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/scarfApp.swift) — Mac 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.
|
2. [`Navigation/AppCoordinator.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Navigation/AppCoordinator.swift) — single source of truth for Mac 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.
|
3. [`Models/ServerContext.swift`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/Models/ServerContext.swift) — the unified handle to a Hermes install (used by both targets).
|
||||||
4. [`Core/Transport/`](https://github.com/awizemann/scarf/tree/main/scarf/scarf/Core/Transport) — local vs. SSH abstraction.
|
4. [`Transport/`](https://github.com/awizemann/scarf/tree/main/scarf/Packages/ScarfCore/Sources/ScarfCore/Transport) (LocalTransport + SSHTransport) and [`ScarfIOS/CitadelServerTransport.swift`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfIOS/Sources/ScarfIOS/CitadelServerTransport.swift) — the three transport implementations.
|
||||||
5. Any feature module under [`Features/`](https://github.com/awizemann/scarf/tree/main/scarf/scarf/Features) for the MVVM-F shape.
|
5. Any feature module under [`scarf/Features/`](https://github.com/awizemann/scarf/tree/main/scarf/scarf/Features) for the Mac MVVM-F shape, or under [`Scarf iOS/`](https://github.com/awizemann/scarf/tree/main/scarf/Scarf%20iOS) for the iOS equivalents.
|
||||||
|
6. [`ScarfGoTabRoot.swift`](https://github.com/awizemann/scarf/blob/main/scarf/Scarf%20iOS/App/ScarfGoTabRoot.swift) — iOS root, 5-tab navigation, ServerContext wiring.
|
||||||
|
|
||||||
## Running tests
|
## Running tests
|
||||||
|
|
||||||
|
Mac target tests:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
xcodebuild test -project scarf/scarf.xcodeproj -scheme scarf
|
xcodebuild test -project scarf/scarf.xcodeproj -scheme scarf
|
||||||
```
|
```
|
||||||
|
|
||||||
See [Testing](Testing) for what is and isn't tested.
|
ScarfCore SwiftPM tests (the bulk of v2.5 coverage — 14 test suites):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
swift test --package-path scarf/Packages/ScarfCore
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Testing](Testing) for the full inventory.
|
||||||
|
|
||||||
## Releasing
|
## Releasing
|
||||||
|
|
||||||
Don't run `xcodebuild archive` / `notarytool` / `gh release create` by hand — use the release script. See [Release Process](Release-Process).
|
The Mac release runs through `./scripts/release.sh <version>` — don't invoke `xcodebuild archive` / `notarytool` / `gh release create` by hand. The iOS release is a separate Xcode Archive + App Store Connect upload (see [`releases/v<ver>/TESTFLIGHT_CHECKLIST.md`](https://github.com/awizemann/scarf/blob/main/releases/v2.5.0/TESTFLIGHT_CHECKLIST.md) and [`APP_STORE_METADATA.md`](https://github.com/awizemann/scarf/blob/main/releases/v2.5.0/APP_STORE_METADATA.md)). See [Release Process](Release-Process).
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (two targets, ScarfCore + ScarfIOS + ScarfDesign packages, iPhone-only iOS)_
|
||||||
|
|||||||
+13
-7
@@ -5,9 +5,9 @@ Thanks for your interest in contributing to Scarf. The canonical contributor gui
|
|||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
1. Fork and clone <https://github.com/awizemann/scarf>.
|
1. Fork and clone <https://github.com/awizemann/scarf>.
|
||||||
2. Open `scarf/scarf.xcodeproj` in Xcode 16+.
|
2. Open `scarf/scarf.xcodeproj` in Xcode 16+. Two app targets: `scarf` (macOS) and `scarf mobile` (ScarfGo, iOS). Both share the local SwiftPM packages [ScarfCore](ScarfCore-Package), `ScarfIOS`, and [ScarfDesign](Design-System).
|
||||||
3. Build and run (requires macOS 14.6+ and Hermes installed at `~/.hermes/`).
|
3. Build and run (requires macOS 14.6+ and Hermes installed at `~/.hermes/` for the local Mac window; iOS sim or device + a SSH-reachable Hermes host for ScarfGo).
|
||||||
4. Read [Build & Run](Build-and-Run) for the codebase tour and [Architecture Overview](Architecture-Overview) for the layering.
|
4. Read [Build & Run](Build-and-Run) for the codebase tour, [Architecture Overview](Architecture-Overview) for the layering, and [Design System](Design-System) for the rust palette + token usage.
|
||||||
|
|
||||||
## Code conventions
|
## Code conventions
|
||||||
|
|
||||||
@@ -22,8 +22,11 @@ The full list is in [`CONTRIBUTING.md`](https://github.com/awizemann/scarf/blob/
|
|||||||
|
|
||||||
## What's good to work on
|
## What's good to work on
|
||||||
|
|
||||||
- Anything in the [Roadmap](Roadmap).
|
- Anything in the [Roadmap](Roadmap) or [ScarfGo Roadmap](ScarfGo-Roadmap).
|
||||||
- Test coverage — see [Testing](Testing) for the highest-value gaps.
|
- iOS-specific gaps — Cron editor, Settings full-YAML editor, Insights / Activity views, iPad layout polish, push notifications wiring once Hermes ships a sender. See [Platform Differences](Platform-Differences).
|
||||||
|
- Test coverage — see [Testing](Testing) for the highest-value remaining gaps (UI tests, log streaming).
|
||||||
|
- iOS localization — strings are extracted; translations welcome. See [Localization](Localization).
|
||||||
|
- Templates for the public catalog — see [Project Templates](Project-Templates) and the catalog at [awizemann.github.io/scarf/templates/](https://awizemann.github.io/scarf/templates/).
|
||||||
- Wiki content — every stub on the wiki is a pull request opportunity. See [Wiki Maintenance](Wiki-Maintenance) for the workflow.
|
- 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.
|
- Bug reports with reproducible steps.
|
||||||
|
|
||||||
@@ -32,7 +35,10 @@ The full list is in [`CONTRIBUTING.md`](https://github.com/awizemann/scarf/blob/
|
|||||||
1. Open an issue first describing the change. This avoids rework if the maintainer has constraints in mind.
|
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.
|
2. One feature or fix per PR — keeps reviews tight.
|
||||||
3. Include a clear description of what changed and why.
|
3. Include a clear description of what changed and why.
|
||||||
4. Ensure `xcodebuild -project scarf/scarf.xcodeproj -scheme scarf build` succeeds with zero warnings.
|
4. Ensure both schemes build clean:
|
||||||
|
- `xcodebuild -project scarf/scarf.xcodeproj -scheme scarf -configuration Debug build`
|
||||||
|
- `xcodebuild -project scarf/scarf.xcodeproj -scheme "scarf mobile" -configuration Debug -destination "generic/platform=iOS Simulator" build`
|
||||||
|
5. Run the ScarfCore test suite if you touched anything in `Packages/ScarfCore`: `swift test --package-path scarf/Packages/ScarfCore`.
|
||||||
|
|
||||||
## Reporting issues
|
## Reporting issues
|
||||||
|
|
||||||
@@ -57,4 +63,4 @@ Two paths:
|
|||||||
Be kind, be specific, assume good faith. Disagreements about technical direction are welcome; personal attacks aren't.
|
Be kind, be specific, assume good faith. Disagreements about technical direction are welcome; personal attacks aren't.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (two targets, ScarfCore tests, iOS contribution areas)_
|
||||||
|
|||||||
+8
-1
@@ -35,11 +35,18 @@ Every Scarf window has a connection pill in the toolbar showing the bound server
|
|||||||
- **Token/cost are zero but you've used Hermes** — the schema may predate v0.7. Update Hermes; Scarf detects the new columns automatically.
|
- **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.
|
- **Yellow "Can't read Hermes state" pill on remote** — open Manage Servers → Run Diagnostics. Each failed check explains why and how to fix.
|
||||||
|
|
||||||
|
## ScarfGo Dashboard (iOS, v2.5+)
|
||||||
|
|
||||||
|
ScarfGo's Dashboard is a slimmer take: a stats grid (Sessions / Messages / Tool Calls / Tokens) over a recent-sessions card, plus a Sessions sub-tab with a project filter Menu. Swipe the row to resume a session. The Hermes Process / Gateway / Active Platforms cards are Mac-only — they read state Hermes doesn't yet expose remotely in a phone-friendly way. See [Platform Differences](Platform-Differences) for the full Mac vs iOS feature matrix.
|
||||||
|
|
||||||
|
A **Switch server** button in the iOS Dashboard's top-right corner (added v2.5) returns you to the Servers list without first navigating to the System tab — handy when you have multiple Hermes hosts configured.
|
||||||
|
|
||||||
## Related pages
|
## Related pages
|
||||||
|
|
||||||
- [Insights & Activity](Insights-and-Activity) for deeper analytics.
|
- [Insights & Activity](Insights-and-Activity) for deeper analytics.
|
||||||
- [Chat](Chat) for talking to the running Hermes.
|
- [Chat](Chat) for talking to the running Hermes.
|
||||||
- [Servers & Remote](Servers-and-Remote) for adding remote hosts and the diagnostics flow.
|
- [Servers & Remote](Servers-and-Remote) for adding remote hosts and the diagnostics flow.
|
||||||
|
- [ScarfGo](ScarfGo) for the iOS Dashboard tour.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (added iOS Dashboard cross-reference + Switch server button)_
|
||||||
|
|||||||
+16
-14
@@ -1,29 +1,31 @@
|
|||||||
# Data Model
|
# Data Model
|
||||||
|
|
||||||
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.
|
Plain Codable structs under [`scarf/Packages/ScarfCore/Sources/ScarfCore/Models/`](https://github.com/awizemann/scarf/tree/main/scarf/Packages/ScarfCore/Sources/ScarfCore/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.
|
||||||
|
|
||||||
|
In v2.5 every model moved out of the Mac target into the shared [ScarfCore](ScarfCore-Package) SwiftPM package, so iOS reuses them byte-for-byte. The path references below all point at the package locations.
|
||||||
|
|
||||||
## Sessions and messages
|
## Sessions and messages
|
||||||
|
|
||||||
| Type | Conformances | Notable fields | Notes |
|
| 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)`. |
|
| `HermesSession` | `Identifiable, Sendable` | `id, source, userId, model, title, parentSessionId, startedAt, endedAt, endReason, messageCount, toolCallCount, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens, estimatedCostUSD, actualCostUSD, costStatus, billingProvider, apiCallCount` _(v0.11+)_ | `isSubagent` = `parentSessionId != nil`; `displayCostUSD` prefers `actualCostUSD`; `sourceIcon` calls `KnownPlatforms.icon(for:source)`. `apiCallCount` (v0.11+) counts per-turn API round-trips, distinct from `toolCallCount`. |
|
||||||
| `HermesMessage` | `Identifiable, Sendable` | `id, sessionId, role, content, toolCallId, timestamp, tokenCount, finishReason, toolCalls, toolName, reasoning` | `isUser/isAssistant/isToolResult` helpers; `hasReasoning` for v0.7+ reasoning support. |
|
| `HermesMessage` | `Identifiable, Sendable` | `id, sessionId, role, content, toolCallId, timestamp, tokenCount, finishReason, toolCalls, toolName, reasoning, reasoningContent` _(v0.11+)_ | `isUser/isAssistant/isToolResult` helpers; `hasReasoning` for v0.7+ reasoning support. `preferredReasoning` returns `reasoningContent` when both columns are populated (v0.11 path) and falls back to legacy `reasoning`. |
|
||||||
| `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. |
|
| `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
|
## Server context and paths
|
||||||
|
|
||||||
| Type | Purpose |
|
| 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`. |
|
| [`ServerContext`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/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?` |
|
| `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. |
|
| `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. |
|
| [`HermesPathSet`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/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.
|
See [Hermes Paths](Hermes-Paths) for the full path table.
|
||||||
|
|
||||||
## Config
|
## 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:
|
[`HermesConfig`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/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`
|
- `display`, `terminal`, `browser`, `voice`, `auxiliary`, `security`, `humanDelay`, `compression`, `checkpoints`, `logging`, `delegation`
|
||||||
- Per-platform: `discord, telegram, slack, matrix, mattermost, whatsapp, homeAssistant`
|
- Per-platform: `discord, telegram, slack, matrix, mattermost, whatsapp, homeAssistant`
|
||||||
@@ -35,15 +37,15 @@ See [Hermes Paths](Hermes-Paths) for the full path table.
|
|||||||
|
|
||||||
| Type | Purpose |
|
| 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. |
|
| [`HermesCronJob`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/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`. |
|
| [`HermesMCPServer`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/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). |
|
| [`MCPServerPreset`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/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). |
|
| `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`. |
|
| `HermesSkill`, `HermesSkillCategory` | Installed skills with `id, name, category, path, files, requiredConfig`, plus v0.11 frontmatter fields `allowedTools?`, `relatedSkills?`, `dependencies?` (parsed from SKILL.md YAML by `SkillsScanner`). Old skills without these fields stay nil and the chip rows hide themselves in the UI. |
|
||||||
|
|
||||||
## Project dashboards
|
## Project dashboards
|
||||||
|
|
||||||
The dashboard schema lives in [`ProjectDashboard.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/ProjectDashboard.swift):
|
The dashboard schema lives in [`ProjectDashboard.swift`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/Models/ProjectDashboard.swift):
|
||||||
|
|
||||||
- `ProjectRegistry` → `ProjectEntry` (name, path)
|
- `ProjectRegistry` → `ProjectEntry` (name, path)
|
||||||
- `ProjectDashboard` → `DashboardSection` → `DashboardWidget` (`.stat / .progress / .text / .table / .chart / .list / .webview`)
|
- `ProjectDashboard` → `DashboardSection` → `DashboardWidget` (`.stat / .progress / .text / .table / .chart / .list / .webview`)
|
||||||
@@ -54,11 +56,11 @@ The full schema is also documented in `scarf/docs/DASHBOARD_SCHEMA.md` in the ma
|
|||||||
|
|
||||||
## ACP messages
|
## 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.
|
[`ACPMessages.swift`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/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
|
## 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.
|
[`HermesConstants.swift`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/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 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (ScarfCore extraction + v0.11 fields + SKILL.md frontmatter)_
|
||||||
|
|||||||
+5
-1
@@ -1,5 +1,7 @@
|
|||||||
# First Run
|
# First Run
|
||||||
|
|
||||||
|
> **Looking for ScarfGo's first run?** The iOS app has a multi-step onboarding flow (host details → SSH key generation → paste public key → connection test). See [ScarfGo Onboarding](ScarfGo-Onboarding) for the walkthrough. This page covers the macOS app.
|
||||||
|
|
||||||
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.
|
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
|
## What Scarf expects
|
||||||
@@ -36,5 +38,7 @@ See [Servers & Remote](Servers-and-Remote) for prerequisites on the remote host
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
ScarfGo on iOS uses a single-window 5-tab layout instead — see [Sidebar & Navigation](Sidebar-and-Navigation) for the cross-platform navigation comparison.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (cross-linked ScarfGo onboarding + iOS nav)_
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ View and edit Hermes scheduled jobs (`~/.hermes/cron/jobs.json`):
|
|||||||
| Column | What it shows |
|
| Column | What it shows |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Name | The job's display name. |
|
| Name | The job's display name. |
|
||||||
| Schedule | Cron expression or run-at timestamp. |
|
| Schedule | **Human-readable phrase** _(v2.5+)_ — "Every 6 hours", "Weekdays at 09:00", "@hourly", etc. — falling back to the raw cron expression for anything the formatter doesn't recognize. Backed by [`CronScheduleFormatter`](Core-Services); ScarfGo renders the same text. |
|
||||||
| State | `enabled` / `paused` / `failed` / `running` with an icon. |
|
| State | `enabled` / `paused` / `failed` / `running` with an icon. |
|
||||||
| Last run / next run | Timestamps. |
|
| Last run / next run | Timestamps. |
|
||||||
| Delivery | Channel format like `discord:chat:thread`. |
|
| Delivery | Channel format like `discord:chat:thread`. |
|
||||||
@@ -84,17 +84,19 @@ Restructured in 1.6 into a 10-tab layout exposing ~60 previously hidden config f
|
|||||||
|---|---|
|
|---|---|
|
||||||
| **General** | Updates (Sparkle toggle + manual check), basic preferences. |
|
| **General** | Updates (Sparkle toggle + manual check), basic preferences. |
|
||||||
| **Display** | Streaming, reasoning visibility, cost display, verbose mode. |
|
| **Display** | Streaming, reasoning visibility, cost display, verbose mode. |
|
||||||
| **Agent** | Model picker (backed by models.dev catalog — 111 providers), max turns, approval mode, reasoning effort. |
|
| **Agent** | Model picker (backed by models.dev catalog — 111 providers + 6 overlay-only providers from `HERMES_OVERLAYS`), max turns, approval mode, reasoning effort. |
|
||||||
| **Terminal** | Terminal backend, Docker / container settings, modal options. |
|
| **Terminal** | Terminal backend, Docker / container settings, modal options. |
|
||||||
| **Browser** | Browser backend selection. |
|
| **Browser** | Browser backend selection. |
|
||||||
| **Voice** | TTS / STT providers, PTT, silence threshold (default 200ms). |
|
| **Voice** | TTS / STT providers, PTT, silence threshold (default 200ms). |
|
||||||
| **Memory** | `memory_enabled`, `memory_char_limit`, `user_char_limit`, `memory_provider`. |
|
| **Memory** | `memory_enabled`, `memory_char_limit`, `user_char_limit`, `memory_provider`. **Reset memory** _(v2.5+)_ — a toolbar button on Memory views (Mac + iOS) runs `hermes memory reset --yes` with a destructive-confirmation dialog. |
|
||||||
| **Aux Models** | All 8 auxiliary model tasks (vision, web extract, compression, delegation, etc.). |
|
| **Aux Models** | All 8 auxiliary model tasks (vision, web extract, compression, delegation, etc.). |
|
||||||
| **Security** | Tirith sandbox, command allowlist, website blocklist, redaction. |
|
| **Security** | Tirith sandbox, command allowlist, website blocklist, redaction. |
|
||||||
| **Advanced** | Logging level / rotation, checkpoints, human-delay simulation, compression thresholds. |
|
| **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`.
|
**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`.
|
||||||
|
|
||||||
|
ScarfGo's Settings tab is **read view + Quick Edits** — see [ScarfGo](ScarfGo) and [Platform Differences](Platform-Differences). The 7 quick-edit keys (`model.default` / `provider`, `agent.approval_mode` / `max_turns`, `display.streaming` / `show_cost` / `show_reasoning`) shell out to `hermes config set`. Other keys remain read-only on iOS.
|
||||||
|
|
||||||
## Related pages
|
## Related pages
|
||||||
|
|
||||||
- [Core Services](Core-Services) — the services backing each of these views.
|
- [Core Services](Core-Services) — the services backing each of these views.
|
||||||
@@ -102,4 +104,4 @@ Restructured in 1.6 into a 10-tab layout exposing ~60 previously hidden config f
|
|||||||
- [Updating](Updating) — Sparkle, the appcast, and how the auto-update flow works.
|
- [Updating](Updating) — Sparkle, the appcast, and how the auto-update flow works.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-24 — Scarf v2.3 (Web Dashboard launcher in Health; ships in v2.4)_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (human-readable cron schedules, hermes memory reset, ScarfGo Quick Edits cross-link)_
|
||||||
|
|||||||
+39
-4
@@ -1,24 +1,46 @@
|
|||||||
# Hermes Paths
|
# Hermes Paths
|
||||||
|
|
||||||
Canonical layout of `~/.hermes/`. Scarf reads these paths through `HermesPathSet` (`Core/Models/HermesPathSet.swift`). Updates here should also be mirrored to the **Key Paths** block in `CLAUDE.md` in the main repo.
|
Canonical layout of `~/.hermes/`. Scarf reads these paths through [`HermesPathSet`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/Models/HermesPathSet.swift) in the ScarfCore package — same path resolution on Mac and iOS. Updates here should mirror to the **Key Paths** block in `scarf/CLAUDE.md`.
|
||||||
|
|
||||||
|
## Hermes-owned paths
|
||||||
|
|
||||||
| Path | What lives here | Scarf access |
|
| Path | What lives here | Scarf access |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `~/.hermes/` | Hermes home | read-only base |
|
| `~/.hermes/` | Hermes home | read-only base |
|
||||||
| `~/.hermes/state.db` | SQLite (WAL mode) — sessions, messages, activity, costs | **read-only**, never written |
|
| `~/.hermes/state.db` | SQLite (WAL mode) — sessions, messages, activity, costs | **read-only**, never written |
|
||||||
| `~/.hermes/config.yaml` | Hermes runtime config (platforms, models, LLM settings, ~60 fields) | read + write (via Settings) |
|
| `~/.hermes/config.yaml` | Hermes runtime config (platforms, models, LLM settings, ~60 fields) | read + write (Mac Settings; iOS Quick Edits via `hermes config set`) |
|
||||||
| `~/.hermes/.env` | Secrets and env vars referenced by `config.yaml` | read + write (preserves comments + blanks) |
|
| `~/.hermes/.env` | Secrets and env vars referenced by `config.yaml` | read + write (preserves comments + blanks) |
|
||||||
|
| `~/.hermes/auth.json` | Auth tokens (Nous Portal subscription, Spotify OAuth, other provider tokens) | read-only — Hermes owns the write path |
|
||||||
|
| `~/.hermes/SOUL.md` | Top-level personality | read + write |
|
||||||
| `~/.hermes/memories/MEMORY.md` | Hermes's project/topic memory | read + write |
|
| `~/.hermes/memories/MEMORY.md` | Hermes's project/topic memory | read + write |
|
||||||
| `~/.hermes/memories/USER.md` | Hermes's user memory | read + write |
|
| `~/.hermes/memories/USER.md` | Hermes's user memory | read + write |
|
||||||
| `~/.hermes/sessions/session_*.json` | Session metadata files | read-only |
|
| `~/.hermes/sessions/session_*.json` | Session metadata files | read-only |
|
||||||
| `~/.hermes/cron/jobs.json` | Scheduled job definitions | read + write |
|
| `~/.hermes/cron/jobs.json` | Scheduled job definitions | read + write |
|
||||||
|
| `~/.hermes/cron/output/` | Cron job output captures | read-only |
|
||||||
| `~/.hermes/logs/agent.log` | Agent log (per-session tagged) | read-only, tail |
|
| `~/.hermes/logs/agent.log` | Agent log (per-session tagged) | read-only, tail |
|
||||||
| `~/.hermes/logs/errors.log` | Error log | read-only, tail |
|
| `~/.hermes/logs/errors.log` | Error log | read-only, tail |
|
||||||
| `~/.hermes/logs/gateway.log` | Messaging-gateway log | read-only, tail |
|
| `~/.hermes/logs/gateway.log` | Messaging-gateway log | read-only, tail |
|
||||||
| `~/.hermes/skills/` | Installed skills | read + write (install/update/uninstall) |
|
| `~/.hermes/gateway_state.json` | Gateway live state (PID, connected platforms) | read-only |
|
||||||
|
| `~/.hermes/skills/` | Installed skills (with v0.11 SKILL.md frontmatter) | read + write (install/update/uninstall) |
|
||||||
| `~/.hermes/plugins/` | Installed plugins (cloned from Git URLs) | read + write |
|
| `~/.hermes/plugins/` | Installed plugins (cloned from Git URLs) | read + write |
|
||||||
| `~/.hermes/personalities/` | Personalities + their `SOUL.md` | read + write |
|
| `~/.hermes/personalities/` | Personalities + their `SOUL.md` | read + write |
|
||||||
| `~/.hermes/profiles/` | Isolated Hermes instances | read + write |
|
| `~/.hermes/profiles/` | Isolated Hermes instances | read + write |
|
||||||
|
| `~/.hermes/mcp-tokens/*.json` | Per-server MCP OAuth tokens | read (detect) + delete (clear) |
|
||||||
|
|
||||||
|
## Scarf-owned paths under `~/.hermes/scarf/`
|
||||||
|
|
||||||
|
These are written by Scarf, never by Hermes. Both clients (Mac + iOS) read and write the same files so attribution and project context survive across platforms.
|
||||||
|
|
||||||
|
| Path | What lives here | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `~/.hermes/scarf/projects.json` | Project registry — every directory you've registered as a Scarf project | Mac authors via Projects sidebar; iOS reads via SFTP. |
|
||||||
|
| `~/.hermes/scarf/session_project_map.json` | Attribution sidecar — maps Hermes session IDs to project paths | Written when project-scoped chat starts. Drives the project-filter UI on both Mac global Sessions and ScarfGo Dashboard. |
|
||||||
|
| `<project>/.scarf/dashboard.json` | Per-project dashboard JSON | Lives inside the project, not under `~/.hermes/`. |
|
||||||
|
| `<project>/.scarf/template.lock.json` | `.scarftemplate` install manifest (when a project was installed from a template) | Drives clean uninstall. |
|
||||||
|
| `<project>/.scarf/manifest.json` | Cached `template.json` for templates with a config schema | Drives the post-install Configuration sheet. |
|
||||||
|
| `<project>/.scarf/config.json` | Non-secret configuration values | Secrets are `keychain://...` URIs; resolved at use time. |
|
||||||
|
| `<project>/.scarf/slash-commands/<name>.md` _(v2.5+)_ | Project-scoped slash commands | See [Slash Commands](Slash-Commands). |
|
||||||
|
| `<project>/AGENTS.md` (between `<!-- scarf-project:begin -->` markers) | Auto-managed project context block | Idempotent, secret-safe. See [Projects & Profiles](Projects-and-Profiles). |
|
||||||
|
|
||||||
## ACP
|
## ACP
|
||||||
|
|
||||||
@@ -29,12 +51,25 @@ Chat does not go through the filesystem. It is a subprocess: `hermes acp` (local
|
|||||||
| Path | What lives here |
|
| Path | What lives here |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `~/Library/Caches/scarf/snapshots/<server-id>/` | Atomic `state.db` snapshots pulled from remote servers via `sqlite3 .backup` |
|
| `~/Library/Caches/scarf/snapshots/<server-id>/` | Atomic `state.db` snapshots pulled from remote servers via `sqlite3 .backup` |
|
||||||
|
| `~/Library/Application Support/com.scarf/skill-snapshots/<serverID>.json` | Per-server skill snapshot for the v2.5 "What's New" pill |
|
||||||
| `/tmp/scarf-ssh-<uid>/` | SSH ControlMaster sockets (per-host `%C` hash). Mode 0700; per-uid suffix isolates between local users. Lives under `/tmp` to stay within macOS' 104-byte Unix domain socket path limit. |
|
| `/tmp/scarf-ssh-<uid>/` | SSH ControlMaster sockets (per-host `%C` hash). Mode 0700; per-uid suffix isolates between local users. Lives under `/tmp` to stay within macOS' 104-byte Unix domain socket path limit. |
|
||||||
| `~/Library/Preferences/com.scarf.app.plist` | App preferences + the server registry |
|
| `~/Library/Preferences/com.scarf.app.plist` | App preferences + the server registry |
|
||||||
|
|
||||||
|
## iOS-side (ScarfGo) state
|
||||||
|
|
||||||
|
ScarfGo can't write to `~/Library/Caches/scarf/...` — it lives in its own iOS app sandbox. Equivalent paths:
|
||||||
|
|
||||||
|
| What | Where |
|
||||||
|
|---|---|
|
||||||
|
| SQLite snapshots | iOS Caches dir → `<sandbox>/Library/Caches/scarf/snapshots/<server-id>/state.db` |
|
||||||
|
| Skill snapshots ("What's New" pill) | `UserDefaults` (the iOS sandbox doesn't have a clean Application Support equivalent for tiny per-server JSON) |
|
||||||
|
| Server registry (multi-server) | `UserDefaults` key `com.scarf.ios.servers.v2` (auto-migrated from legacy `ScarfGo.servers.v1`) |
|
||||||
|
| SSH private keys | iOS Keychain — service `com.scarf.ssh-key`, account `server-key:<UUID>`, accessibility `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`. Never iCloud-synced. |
|
||||||
|
| Template config secrets | iOS Keychain — service `com.scarf.template.<slug>`, account `<fieldKey>:<project-path-hash>` |
|
||||||
|
|
||||||
## Log line format
|
## Log line format
|
||||||
|
|
||||||
Hermes log lines may carry an optional `[session_id]` tag between the level and the logger name. `HermesLogService.parseLine` treats the session tag as an optional capture group, so older untagged lines still parse correctly.
|
Hermes log lines may carry an optional `[session_id]` tag between the level and the logger name. `HermesLogService.parseLine` treats the session tag as an optional capture group, so older untagged lines still parse correctly.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.2_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (Scarf-owned paths section, slash-commands/template lock-files, iOS sandbox + Keychain layout)_
|
||||||
|
|||||||
+5
-3
@@ -24,10 +24,12 @@ All queries are aggregations over the same `sessions`, `messages`, `tool_calls`
|
|||||||
The full conversation history browser:
|
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.
|
- **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.
|
- **Project filter** _(v2.5+)_ — Menu above the list picks **All projects / Unattributed / one entry per registered project**. Each row carries a tinted folder chip when the session is attributed to a project. The filter and the badges share the same `SessionAttributionService` ScarfGo's Sessions tab uses, so cross-platform parity is by construction. See [Projects & Profiles](Projects-and-Profiles).
|
||||||
|
- **Detail panel** — full message stream: user → assistant → tool calls → tool results, with markdown rendering. **Reasoning blocks** (v0.7+) render in a collapsed section. v0.11+ `messages.reasoning_content` (when present) is preferred over the legacy `reasoning` blob.
|
||||||
|
- **API call counter** _(v2.5+)_ — each row carries a network-icon chip showing `sessions.api_call_count` (v0.11+). Distinct from `tool_call_count`; counts per-turn API round-trips.
|
||||||
- **Tool call inspector** — pretty-printed arguments, function name, result. Categorized by `toolKind` (read / edit / execute / fetch / browser / other).
|
- **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.
|
- **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`).
|
- **Actions** — rename (`hermes sessions rename`), delete (`hermes sessions delete`), JSONL export (`hermes sessions export`). Right-click any row in the v2.5 chat sessions sidebar exposes the same Rename / Delete actions inline.
|
||||||
|
|
||||||
Click a session in the Dashboard's "Recent" card to land here with that session pre-selected.
|
Click a session in the Dashboard's "Recent" card to land here with that session pre-selected.
|
||||||
|
|
||||||
@@ -44,4 +46,4 @@ The per-tool execution feed — what Hermes did, when, and with what arguments:
|
|||||||
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.
|
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 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (project filter + badges, API call chip, reasoning_content preference)_
|
||||||
|
|||||||
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
|
> **Looking for the iPhone?** This page covers the macOS desktop app. ScarfGo, the iOS companion, ships via TestFlight — see [ScarfGo](ScarfGo) and [ScarfGo Onboarding](ScarfGo-Onboarding) for that flow.
|
||||||
|
|
||||||
## System requirements
|
## System requirements
|
||||||
|
|
||||||
- **macOS 14.6+ (Sonoma)** or newer.
|
- **macOS 14.6+ (Sonoma)** or newer.
|
||||||
@@ -43,4 +45,4 @@ Scarf uses [Sparkle](https://sparkle-project.org/) for automatic updates from a
|
|||||||
- [Uninstalling](Uninstalling) — removing the app and its files.
|
- [Uninstalling](Uninstalling) — removing the app and its files.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (added ScarfGo cross-link)_
|
||||||
|
|||||||
+8
-1
@@ -19,6 +19,7 @@ You can have a local window and up to 8 remote windows reachable by ⌘ + number
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| ⌃B | Chat | Toggle the sessions sidebar inside Chat |
|
| ⌃B | Chat | Toggle the sessions sidebar inside Chat |
|
||||||
| ⌘S | Personalities (SOUL.md editor) | Save the current SOUL.md |
|
| ⌘S | Personalities (SOUL.md editor) | Save the current SOUL.md |
|
||||||
|
| **1**…**9** _(v2.5+)_ | Chat permission sheet | Approve / deny by option number — Hermes's request-permission sheet binds 1–9 to the option buttons. Visible "1. " / "2. " prefixes hint the binding. Power users approve / deny without reaching for the mouse. |
|
||||||
|
|
||||||
## Dialog defaults
|
## Dialog defaults
|
||||||
|
|
||||||
@@ -51,5 +52,11 @@ There's no command palette and no global shortcuts beyond ⌘ + number for windo
|
|||||||
|
|
||||||
If you'd like to see additional shortcuts, file an issue — keyboard accessibility is welcome contribution territory.
|
If you'd like to see additional shortcuts, file an issue — keyboard accessibility is welcome contribution territory.
|
||||||
|
|
||||||
|
## ScarfGo (iOS)
|
||||||
|
|
||||||
|
iPhone has no Mac keyboard, so the binding tables above don't apply. The numbered permission-sheet hints (1. / 2. / …) still render on iOS but as hierarchy cues — the row tap target is the action.
|
||||||
|
|
||||||
|
If you connect a Bluetooth keyboard or an iPad with a Magic Keyboard, you get system iOS shortcuts (⌘C copy, ⌘W close sheet, ⌘N new chat in some flows). No app-specific bindings yet — on the [ScarfGo Roadmap](ScarfGo-Roadmap).
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (numbered approval shortcuts + iOS note)_
|
||||||
|
|||||||
+3
-1
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
Scarf 2.1 added full UI translations on top of English. Seven languages ship in the box and more can be contributed via a plain GitHub PR — no translation-management tool, no account to create.
|
Scarf 2.1 added full UI translations on top of English. Seven languages ship in the box and more can be contributed via a plain GitHub PR — no translation-management tool, no account to create.
|
||||||
|
|
||||||
|
> **ScarfGo (iOS) is English-only in v2.5.** The iOS strings are extracted but no translations are contributed yet. Localizing the iOS app is on the [ScarfGo Roadmap](ScarfGo-Roadmap) — most of the strings already exist in `Localizable.xcstrings`, so contributing iOS translations would lean on the same workflow described below.
|
||||||
|
|
||||||
## Supported languages
|
## Supported languages
|
||||||
|
|
||||||
| Locale | Name | Status |
|
| Locale | Name | Status |
|
||||||
@@ -70,4 +72,4 @@ Per-locale JSON under `tools/translations/` is the canonical source of truth for
|
|||||||
Deeper dev-facing notes on which SwiftUI patterns silently bypass localization (and how to avoid them when adding new UI) are in [`scarf/docs/I18N.md`](https://github.com/awizemann/scarf/blob/main/scarf/docs/I18N.md).
|
Deeper dev-facing notes on which SwiftUI patterns silently bypass localization (and how to avoid them when adding new UI) are in [`scarf/docs/I18N.md`](https://github.com/awizemann/scarf/blob/main/scarf/docs/I18N.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-21 — Scarf v2.1.0_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (added iOS English-only note + ScarfGo Roadmap link)_
|
||||||
|
|||||||
@@ -66,4 +66,4 @@ Enable / disable Hermes toolsets per platform.
|
|||||||
- [Hermes Paths](Hermes-Paths) — `~/.hermes/plugins/`, `config.yaml` `mcp_servers` key.
|
- [Hermes Paths](Hermes-Paths) — `~/.hermes/plugins/`, `config.yaml` `mcp_servers` key.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (no MCP/Plugins/Webhooks/Tools changes since v2.0.1; refresh footer)_
|
||||||
|
|||||||
+4
-4
@@ -17,7 +17,7 @@ Both clients talk to the same Hermes host with the same paths and the same data.
|
|||||||
| **Cron editor (write)** | Yes | No | Mac builds a richer editor sheet; iOS read-only in v1. Coming. |
|
| **Cron editor (write)** | Yes | No | Mac builds a richer editor sheet; iOS read-only in v1. Coming. |
|
||||||
| **Skills tree** | Yes | Yes (read-only) | iOS won't grow a skill editor — too cramped. |
|
| **Skills tree** | Yes | Yes (read-only) | iOS won't grow a skill editor — too cramped. |
|
||||||
| **Settings (read)** | Yes (full YAML) | Yes (full YAML) | — |
|
| **Settings (read)** | Yes (full YAML) | Yes (full YAML) | — |
|
||||||
| **Settings (write)** | Yes (full YAML editor) | **No** | iOS deferred. The plan was a curated `hermes config set <key> <value>` shell-out for 7 keys; that landed in the codebase but isn't surfaced in v1 because changing settings remotely without local validation is too easy to break. Mac stays the canonical editor. |
|
| **Settings (write)** | Yes (full YAML editor) | **Quick Edits (7 keys)** | iOS exposes a curated `hermes config set <key> <value>` shell-out for `model.default`, `model.provider`, `agent.approval_mode`, `agent.max_turns`, `display.show_cost`, `display.show_reasoning`, `display.streaming`. Other keys stay read-only — Mac is the canonical full-YAML editor. |
|
||||||
| **Slash commands — author** _(v2.5)_ | Yes (per-project tab + live preview) | **No** | Multi-line markdown editing on a phone keyboard is its own UX problem; iOS gets a read-only browser instead. |
|
| **Slash commands — author** _(v2.5)_ | Yes (per-project tab + live preview) | **No** | Multi-line markdown editing on a phone keyboard is its own UX problem; iOS gets a read-only browser instead. |
|
||||||
| **Slash commands — invoke / browse** _(v2.5)_ | Yes (slash menu) | Yes (read-only browser sheet) | — |
|
| **Slash commands — invoke / browse** _(v2.5)_ | Yes (slash menu) | Yes (read-only browser sheet) | — |
|
||||||
| **Templates — install** | Yes | No | Templates are a content-creation surface; iOS won't get a UI for it in v1. Use Mac. |
|
| **Templates — install** | Yes | No | Templates are a content-creation surface; iOS won't get a UI for it in v1. Use Mac. |
|
||||||
@@ -29,7 +29,7 @@ Both clients talk to the same Hermes host with the same paths and the same data.
|
|||||||
| **Tool Gateway (Nous Portal)** | Yes (provider picker + Auxiliary tab + Health surface) | Provider exists in catalog | iOS Settings is read-only, so tool-gateway routing toggles aren't editable. Use Mac. |
|
| **Tool Gateway (Nous Portal)** | Yes (provider picker + Auxiliary tab + Health surface) | Provider exists in catalog | iOS Settings is read-only, so tool-gateway routing toggles aren't editable. Use Mac. |
|
||||||
| **Insights / activity charts** | Yes | No | Charts are richer on bigger screens; deferred. |
|
| **Insights / activity charts** | Yes | No | Charts are richer on bigger screens; deferred. |
|
||||||
| **Terminal (SwiftTerm)** | Mac doesn't have it either | No | Hermes shell mode is CLI-only; neither client wraps it. |
|
| **Terminal (SwiftTerm)** | Mac doesn't have it either | No | Hermes shell mode is CLI-only; neither client wraps it. |
|
||||||
| **Localization** | 7 languages (de, es, fr, it, ja, pt-BR, zh-Hans) | English only | iOS strings are extracted but no translations contributed in v1. |
|
| **Localization** | 7 languages (en, de, es, fr, ja, pt-BR, zh-Hans — verified in `Localizable.xcstrings`) | English only | iOS strings are extracted but no translations contributed in v1. |
|
||||||
| **Multi-server** | Yes (one window per server) | Yes (sidebar-adaptable Tab root) | — |
|
| **Multi-server** | Yes (one window per server) | Yes (sidebar-adaptable Tab root) | — |
|
||||||
| **Push notifications** | n/a (Mac uses local notifications + the menu bar) | Skeleton present, gated `apnsEnabled = false` | Push sender doesn't exist on Hermes side yet. Capability disabled in target. Flips on simultaneously when both lights are green. |
|
| **Push notifications** | n/a (Mac uses local notifications + the menu bar) | Skeleton present, gated `apnsEnabled = false` | Push sender doesn't exist on Hermes side yet. Capability disabled in target. Flips on simultaneously when both lights are green. |
|
||||||
| **Sparkle auto-update** | Yes | n/a | iOS uses TestFlight / App Store. |
|
| **Sparkle auto-update** | Yes | n/a | iOS uses TestFlight / App Store. |
|
||||||
@@ -63,7 +63,7 @@ Those belong on Mac, where you have a keyboard, real screen, and the full Hermes
|
|||||||
These are gaps without a fundamental reason; they just need engineering time:
|
These are gaps without a fundamental reason; they just need engineering time:
|
||||||
|
|
||||||
- **Cron editor.** Add / remove jobs from the phone. The data model is shared; only the editor sheet is missing.
|
- **Cron editor.** Add / remove jobs from the phone. The data model is shared; only the editor sheet is missing.
|
||||||
- **Scoped Settings editor.** A whitelisted "Quick edits" sheet for the 7 keys most users actually change (`model.default`, `model.provider`, `agent.approval_mode`, `agent.max_turns`, `display.show_cost`, `display.show_reasoning`, `display.streaming`).
|
- ~~**Scoped Settings editor.**~~ ✅ Shipped in v2.5 — the **Quick Edits** sheet covers the 7 keys most users actually change (`model.default`, `model.provider`, `agent.approval_mode`, `agent.max_turns`, `display.show_cost`, `display.show_reasoning`, `display.streaming`). Listed here for history; arbitrary-key editing remains future work.
|
||||||
- **Health summary card.** A reduced version of the Mac Health view — just enough to answer "is the gateway running, is the DB reachable, is the agent crashy?"
|
- **Health summary card.** A reduced version of the Mac Health view — just enough to answer "is the gateway running, is the DB reachable, is the agent crashy?"
|
||||||
- **Localization.** Translate the strings the Mac app already has; reuse the `.strings` files.
|
- **Localization.** Translate the strings the Mac app already has; reuse the `.strings` files.
|
||||||
- **iPad layout pass.** Probably one afternoon's verification.
|
- **iPad layout pass.** Probably one afternoon's verification.
|
||||||
@@ -80,4 +80,4 @@ These are gaps without a fundamental reason; they just need engineering time:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
_Last updated: 2026-04-25 — v2.5._
|
_Last updated: 2026-04-25 — v2.5 (audit pass: Settings Quick Edits ship; locale list corrected)._
|
||||||
|
|||||||
@@ -49,4 +49,4 @@ Quick Commands live in `config.yaml` under the `quick_commands` key.
|
|||||||
- [Hermes Paths](Hermes-Paths) — where `.env`, `config.yaml`, and `personalities/` live.
|
- [Hermes Paths](Hermes-Paths) — where `.env`, `config.yaml`, and `personalities/` live.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (no behavior changes since v2.0.1; refresh footer)_
|
||||||
|
|||||||
+3
-2
@@ -58,7 +58,8 @@ If you join the ScarfGo beta via TestFlight, Apple shares anonymized crash repor
|
|||||||
|
|
||||||
- iOS Keychain storage uses `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` so credentials are unreachable while the device is locked and never synced to iCloud.
|
- iOS Keychain storage uses `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` so credentials are unreachable while the device is locked and never synced to iCloud.
|
||||||
- SSH connections use the same protocol stack as `ssh(1)` — strict host-key verification on first connect, key-based auth (no passwords are sent over the wire), and Citadel's pure-Swift implementation on iOS.
|
- SSH connections use the same protocol stack as `ssh(1)` — strict host-key verification on first connect, key-based auth (no passwords are sent over the wire), and Citadel's pure-Swift implementation on iOS.
|
||||||
- The macOS app is sandboxed where possible and notarized via Apple's standard Developer ID flow.
|
- The macOS app is **notarized via Apple's standard Developer ID flow** (signed + stapled by `xcrun notarytool` on every release). It is **not App-Sandboxed** — Scarf needs direct read access to `~/.hermes/` and the ability to spawn the `hermes` CLI, both of which the App Sandbox forbids. This is why Scarf is distributed via GitHub Releases + Sparkle rather than the Mac App Store.
|
||||||
|
- ScarfGo on iOS runs inside the standard iOS app sandbox — no special entitlements beyond Keychain access for the SSH key.
|
||||||
|
|
||||||
## Children's privacy
|
## Children's privacy
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ Neither app is directed at children under 13 and we do not knowingly collect any
|
|||||||
Because we don't collect any data on developer-controlled servers, there is nothing for you to opt out of, request deletion of, or export. To remove all app-stored data from your device:
|
Because we don't collect any data on developer-controlled servers, there is nothing for you to opt out of, request deletion of, or export. To remove all app-stored data from your device:
|
||||||
|
|
||||||
- **ScarfGo**: delete the app. iOS purges the Keychain group + app container.
|
- **ScarfGo**: delete the app. iOS purges the Keychain group + app container.
|
||||||
- **Scarf**: delete the app and the `~/Library/Containers/com.scarf` directory (the app is sandboxed; this is the only on-disk data).
|
- **Scarf**: delete `Scarf.app` from `/Applications`, then optionally remove `~/Library/Caches/scarf/` (remote SQLite snapshots), `~/Library/Preferences/com.scarf.app.plist` (server registry + preferences), and `~/Library/Application Support/com.scarf/` (skill snapshots). See [Uninstalling](Uninstalling) for the full cleanup.
|
||||||
|
|
||||||
Your Hermes host's data (`~/.hermes/`) stays untouched — that's yours to manage.
|
Your Hermes host's data (`~/.hermes/`) stays untouched — that's yours to manage.
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -24,7 +24,9 @@ The full schema is documented in [`scarf/docs/DASHBOARD_SCHEMA.md`](https://gith
|
|||||||
|
|
||||||
**Adding a project:** click + in the Projects sidebar, pick a directory. The project is registered in `~/.hermes/scarf/projects.json`; the dashboard JSON lives in `<project>/.scarf/dashboard.json` (which you should add to your project's `.gitignore` if it's user-specific).
|
**Adding a project:** click + in the Projects sidebar, pick a directory. The project is registered in `~/.hermes/scarf/projects.json`; the dashboard JSON lives in `<project>/.scarf/dashboard.json` (which you should add to your project's `.gitignore` if it's user-specific).
|
||||||
|
|
||||||
**Sharing a project:** as of v2.2.0, projects can be packaged into `.scarftemplate` bundles and shared with anyone — see [Project Templates](Project-Templates). Export turns a live project into a redistributable bundle; install unpacks one and sets up the dashboard, skills, cron jobs, and configuration schema in a single preview-and-confirm step. The public catalog lives at [awizemann.github.io/scarf/templates/](https://awizemann.github.io/scarf/templates/).
|
**Per-project tabs** _(v2.3+, v2.5)_: clicking a project row reveals a tabbed detail view — **Dashboard**, **Sessions**, **Site** (when the dashboard has a webview widget), and **Slash Commands** (v2.5). The Sessions tab lists chats attributed to the project; **New Chat** spawns `hermes acp` with the project's directory as the session cwd and writes a Scarf-managed block into `<project>/AGENTS.md` so the agent boots with project context. Attribution survives across Mac and ScarfGo via the shared `SessionAttributionService`. See [Slash Commands](Slash-Commands) for the per-project authoring tab.
|
||||||
|
|
||||||
|
**Sharing a project:** as of v2.2.0, projects can be packaged into `.scarftemplate` bundles and shared with anyone — see [Project Templates](Project-Templates). Export turns a live project into a redistributable bundle; install unpacks one and sets up the dashboard, skills, cron jobs, configuration schema, and (in v2.5+) project-scoped slash commands in a single preview-and-confirm step. The public catalog lives at [awizemann.github.io/scarf/templates/](https://awizemann.github.io/scarf/templates/).
|
||||||
|
|
||||||
## Profiles
|
## Profiles
|
||||||
|
|
||||||
@@ -41,10 +43,11 @@ Profiles live under `~/.hermes/profiles/`. The currently active profile is whate
|
|||||||
|
|
||||||
## Related pages
|
## Related pages
|
||||||
|
|
||||||
- [Project Templates](Project-Templates) — `.scarftemplate` bundles, the install / export / author flows, the public catalog.
|
- [Project Templates](Project-Templates) — `.scarftemplate` bundles (schemaVersion 3 in v2.5), the install / export / author flows, the public catalog.
|
||||||
|
- [Slash Commands](Slash-Commands) — project-scoped slash commands authored in the per-project Slash Commands tab.
|
||||||
- [Hermes Paths](Hermes-Paths) — `~/.hermes/profiles/` and the projects registry.
|
- [Hermes Paths](Hermes-Paths) — `~/.hermes/profiles/` and the projects registry.
|
||||||
- [Memory & Skills](Memory-and-Skills) — memory is profile-scoped.
|
- [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.
|
- [Settings](Gateway-Cron-Health-Logs) — exposes "Backup & Restore" buttons (`hermes backup` / `hermes import`) at the profile level.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-23 — Scarf v2.2.0_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (per-project Sessions / Site / Slash Commands tabs + AGENTS.md context handoff)_
|
||||||
|
|||||||
+17
-2
@@ -1,6 +1,8 @@
|
|||||||
# Release Process
|
# Release Process
|
||||||
|
|
||||||
Scarf releases are produced by a single local script: [`scripts/release.sh`](https://github.com/awizemann/scarf/blob/main/scripts/release.sh) in the main repo. **The script is the source of truth** — this page is a public-facing summary; do not duplicate prerequisites or step-by-step internals here.
|
> **Two release tracks as of v2.5.** The Mac app ships through GitHub Releases + Sparkle (this page). ScarfGo (iOS) ships through TestFlight / App Store Connect — see [`releases/v<version>/TESTFLIGHT_CHECKLIST.md`](https://github.com/awizemann/scarf/blob/main/releases/v2.5.0/TESTFLIGHT_CHECKLIST.md) and [`APP_STORE_METADATA.md`](https://github.com/awizemann/scarf/blob/main/releases/v2.5.0/APP_STORE_METADATA.md). The two tracks are independent and don't share a single command — they share the version number by convention.
|
||||||
|
|
||||||
|
Mac releases are produced by a single local script: [`scripts/release.sh`](https://github.com/awizemann/scarf/blob/main/scripts/release.sh) in the main repo. **The script is the source of truth** — this page is a public-facing summary; do not duplicate prerequisites or step-by-step internals here.
|
||||||
|
|
||||||
## Modes
|
## Modes
|
||||||
|
|
||||||
@@ -41,6 +43,19 @@ Releases are EdDSA-signed by Sparkle. The private key lives in the user's macOS
|
|||||||
|
|
||||||
- Bump the **Latest release** line on [Home](Home).
|
- Bump the **Latest release** line on [Home](Home).
|
||||||
- Append the new version to [Release Notes Index](Release-Notes-Index).
|
- Append the new version to [Release Notes Index](Release-Notes-Index).
|
||||||
|
- If the version includes ScarfGo changes: separately archive + upload via Xcode Organizer per [`TESTFLIGHT_CHECKLIST.md`](https://github.com/awizemann/scarf/blob/main/releases/v2.5.0/TESTFLIGHT_CHECKLIST.md). The Mac release script doesn't touch iOS.
|
||||||
|
|
||||||
|
## iOS release flow (separate from `release.sh`)
|
||||||
|
|
||||||
|
ScarfGo ships through:
|
||||||
|
|
||||||
|
1. **Xcode → Product → Archive** for the `scarf mobile` scheme (Any iOS Device destination).
|
||||||
|
2. **Organizer → Distribute App → App Store Connect → Upload** — automatic re-sign.
|
||||||
|
3. App Store Connect processes the binary (~5–15 min). Once ready, add it to a TestFlight group + submit for **Beta App Review** (24–48h queue).
|
||||||
|
4. After Beta Review approval, the public TestFlight URL ([testflight.apple.com/join/qCrRpcTz](https://testflight.apple.com/join/qCrRpcTz)) accepts new joiners. Until then it shows "not accepting new testers."
|
||||||
|
5. Public App Store submission is a separate review (24–72h) using the same processed build — see [`APP_STORE_METADATA.md`](https://github.com/awizemann/scarf/blob/main/releases/v2.5.0/APP_STORE_METADATA.md) for the description, keywords, support URL, and privacy URL fields.
|
||||||
|
|
||||||
|
The iOS `MARKETING_VERSION` should match the Mac `MARKETING_VERSION` for the same release; the iOS `CURRENT_PROJECT_VERSION` (build number) increments independently per Apple's monotonic-build-number rule. There's no automation for iOS bumping yet — manual edit in the Xcode target before archiving.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (added iOS release flow + cross-link to TestFlight + App Store metadata files)_
|
||||||
|
|||||||
+7
-6
@@ -11,12 +11,12 @@ What's next for Scarf. Public, opinionated, subject to change. The internal vers
|
|||||||
## Near-term (2.6 candidates)
|
## Near-term (2.6 candidates)
|
||||||
|
|
||||||
- **iOS cron editor.** Add / remove / toggle cron jobs from the phone. Data model is already shared; just needs the editor sheet.
|
- **iOS cron editor.** Add / remove / toggle cron jobs from the phone. Data model is already shared; just needs the editor sheet.
|
||||||
- **iOS scoped Settings editor.** The codebase already has the `hermes config set` shell-out wired for 7 keys; needs UX work + the curated whitelist before exposing.
|
- ~~**iOS scoped Settings editor.**~~ ✅ Shipped in v2.5 — Quick Edits sheet covers 7 commonly-changed keys via `hermes config set`. Arbitrary-key editing is the v2.6+ stretch.
|
||||||
- **iOS push notifications, lit up.** Three things need to happen together: enable the Push Notifications capability in the Xcode target, ship a Hermes-side push sender, flip `apnsEnabled = true`. Skeleton + lock-screen action category are already in place.
|
- **iOS push notifications, lit up.** Three things need to happen together: enable the Push Notifications capability in the Xcode target, ship a Hermes-side push sender, flip `NotificationRouter.apnsEnabled = true`. Skeleton + lock-screen "Approve / Deny" action category are already in place.
|
||||||
- **iPad layout pass.** `.tabViewStyle(.sidebarAdaptable)` is wired; needs verification + a target-flag flip.
|
- **iPad layout pass.** v2.5 ships iPhone-only (`TARGETED_DEVICE_FAMILY = 1`, Catalyst + Designed-for-iPad disabled). `.tabViewStyle(.sidebarAdaptable)` is wired in the view layer; flipping the target flag and verifying is the bulk of the work.
|
||||||
- **More MCP presets.** The curated list grows as MCP ecosystem matures.
|
- **More MCP presets.** The curated list grows as MCP ecosystem matures.
|
||||||
- **Mermaid diagrams in the wiki.** Architecture pages get a lot of value from one good diagram.
|
- **Mermaid diagrams in the wiki.** Architecture pages get a lot of value from one good diagram.
|
||||||
- **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.
|
- **Per-project FSEvents on remote.** Remote currently has one global mtime-poll loop ([HermesFileWatcher](Core-Services) has a TODO); per-project paths would reduce remote chattiness.
|
||||||
|
|
||||||
## Medium-term
|
## Medium-term
|
||||||
|
|
||||||
@@ -35,10 +35,11 @@ What's next for Scarf. Public, opinionated, subject to change. The internal vers
|
|||||||
|
|
||||||
## What we're NOT doing
|
## What we're NOT doing
|
||||||
|
|
||||||
- **A web version of Scarf.** The whole point is being a native macOS app close to the metal.
|
- **A web version of Scarf.** The whole point is being native — macOS app on the desktop, iPhone app on mobile, both close to the metal.
|
||||||
- **Background sync / push notifications.** Scarf is a viewer; Hermes runs the agent.
|
- **Background sync.** Scarf is a viewer; Hermes runs the agent. Pull happens when you open a tab, not in the background. (Push notifications, when Hermes ships a sender, are an *event* surface — they alert; they don't sync.)
|
||||||
- **Bundled Hermes installer.** Hermes installation belongs in Hermes-land.
|
- **Bundled Hermes installer.** Hermes installation belongs in Hermes-land.
|
||||||
- **Closed-source / paid tier.** MIT-licensed, free, will stay that way.
|
- **Closed-source / paid tier.** MIT-licensed, free, will stay that way.
|
||||||
|
- **Local Hermes runtime on iOS.** Hermes is Python; iOS doesn't sandbox Python runtimes practically. ScarfGo will always be a thin client over SSH.
|
||||||
|
|
||||||
## Suggesting features
|
## Suggesting features
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -31,7 +31,7 @@ Tap **Add Server**. Fill in:
|
|||||||
- **User** — the SSH user. Often the same login you `ssh user@host` with from your terminal.
|
- **User** — the SSH user. Often the same login you `ssh user@host` with from your terminal.
|
||||||
- **Port** — defaults to 22. Override if your host uses a non-standard SSH port.
|
- **Port** — defaults to 22. Override if your host uses a non-standard SSH port.
|
||||||
- **Nickname (optional)** — display name in ScarfGo. Defaults to `user@host`.
|
- **Nickname (optional)** — display name in ScarfGo. Defaults to `user@host`.
|
||||||
- **Hermes binary hint (optional)** — leave empty unless you know `hermes` is at an unusual path. ScarfGo prepends `~/.local/bin`, `/opt/homebrew/bin`, and `/usr/local/bin` to PATH automatically — those four cover ~95% of pipx + Homebrew installs.
|
- **Hermes binary hint (optional)** — leave empty unless you know `hermes` is at an unusual path. ScarfGo prepends `~/.local/bin`, `/opt/homebrew/bin`, and `/usr/local/bin` to PATH automatically — those three cover ~95% of pipx + Homebrew installs. If your `hermes` lives elsewhere (custom virtualenv, system-managed install dir like `~/.hermes/bin`), set the hint to its absolute path.
|
||||||
|
|
||||||
Tap **Next**.
|
Tap **Next**.
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ Citadel's raw exec channel doesn't source the user's shell rc files (`.bashrc`,
|
|||||||
2. In ScarfGo: **Servers → tap the server → Edit → Hermes binary hint** → paste the absolute path (e.g. `/opt/scarf-tools/bin/hermes`).
|
2. In ScarfGo: **Servers → tap the server → Edit → Hermes binary hint** → paste the absolute path (e.g. `/opt/scarf-tools/bin/hermes`).
|
||||||
3. Re-test the connection.
|
3. Re-test the connection.
|
||||||
|
|
||||||
The `~/.hermes/bin/hermes` location is also auto-probed (some self-install layouts put it there), so set the hint only if your install is somewhere none of the four candidates cover.
|
The inline PATH covers `~/.local/bin`, `/opt/homebrew/bin`, and `/usr/local/bin`. Anything else — including `~/.hermes/bin` self-install layouts — needs the binary-hint override.
|
||||||
|
|
||||||
### "Connection refused" / "Connection timed out"
|
### "Connection refused" / "Connection timed out"
|
||||||
|
|
||||||
@@ -156,4 +156,4 @@ Full policy: [Privacy Policy](Privacy-Policy).
|
|||||||
- [Support](Support) — bug reports, feature requests, security disclosures.
|
- [Support](Support) — bug reports, feature requests, security disclosures.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-25 — Scarf v2.5.0_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (audit pass: corrected PATH-prefix candidate count + `~/.hermes/bin` claim)_
|
||||||
|
|||||||
+15
-6
@@ -11,7 +11,7 @@ ScarfGo is the on-the-go iPhone companion to [Scarf](Home). Its scope is deliber
|
|||||||
- **Transport:** pure-Swift SSH via [Citadel](https://github.com/orlandos-nl/Citadel) 0.12.x — no OpenSSH client subprocess (iOS sandbox).
|
- **Transport:** pure-Swift SSH via [Citadel](https://github.com/orlandos-nl/Citadel) 0.12.x — no OpenSSH client subprocess (iOS sandbox).
|
||||||
- **Shared core:** `ScarfCore` SPM package — Models / Transport / Services / ViewModels portable across macOS and iOS, unit-tested on Linux in CI.
|
- **Shared core:** `ScarfCore` SPM package — Models / Transport / Services / ViewModels portable across macOS and iOS, unit-tested on Linux in CI.
|
||||||
- **iOS-only code:** `ScarfIOS` package (Citadel glue, Keychain key storage) + `Scarf iOS/` SwiftUI views.
|
- **iOS-only code:** `ScarfIOS` package (Citadel glue, Keychain key storage) + `Scarf iOS/` SwiftUI views.
|
||||||
- **Target:** iPhone, iOS 18+. iPad / macCatalyst deferred.
|
- **Target:** iPhone-only, iOS 18+. v2.5 ships with `TARGETED_DEVICE_FAMILY = 1`, `SUPPORTS_MACCATALYST = NO`, `SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO`, `SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO`. iPad and Catalyst flags are explicitly OFF until layout polish lands (M10+).
|
||||||
|
|
||||||
## What's shipped today (M6)
|
## What's shipped today (M6)
|
||||||
|
|
||||||
@@ -62,9 +62,18 @@ Features that only make sense on mobile, in priority order:
|
|||||||
5. **Lock-screen quick-approve.** Notification action button for "Approve" / "Deny" on pending permissions so the agent keeps running while you're away from the app.
|
5. **Lock-screen quick-approve.** Notification action button for "Approve" / "Deny" on pending permissions so the agent keeps running while you're away from the app.
|
||||||
6. **Scoped Settings editor.** Not a generic YAML round-trip editor — a curated set of high-value fields (model / provider / approval mode / max turns / display toggles) that save via `hermes config set <key> <value>` over SSH exec. Hermes owns the YAML round-trip; Scarf just picks values.
|
6. **Scoped Settings editor.** Not a generic YAML round-trip editor — a curated set of high-value fields (model / provider / approval mode / max turns / display toggles) that save via `hermes config set <key> <value>` over SSH exec. Hermes owns the YAML round-trip; Scarf just picks values.
|
||||||
|
|
||||||
### M10 — TestFlight
|
### M10 — TestFlight ✅ Shipped (v2.5)
|
||||||
|
|
||||||
App Store Connect + provisioning profile + internal TestFlight group of 1 (Alan). Public TestFlight after 2–3 real sessions on a real iPhone without crashes.
|
App Store Connect + Apple Distribution cert + Apple Developer Program enrollment + privacy policy live at `awizemann.github.io/scarf/privacy/`. Public TestFlight live at <https://testflight.apple.com/join/qCrRpcTz> — accepts new joiners after Apple's Beta Review approves the first build (24–48h queue). See [TESTFLIGHT_CHECKLIST.md](https://github.com/awizemann/scarf/blob/main/releases/v2.5.0/TESTFLIGHT_CHECKLIST.md) for the submission flow + [APP_STORE_METADATA.md](https://github.com/awizemann/scarf/blob/main/releases/v2.5.0/APP_STORE_METADATA.md) for the public App Store metadata bundle (description, keywords, support URL, etc.) staged for the eventual public release.
|
||||||
|
|
||||||
|
### M11+ — Post-TestFlight feedback loop
|
||||||
|
|
||||||
|
- Iterate on TestFlight feedback over v2.5.x patches.
|
||||||
|
- iPad layout polish (flip device family flag + verify).
|
||||||
|
- Cron editor on iOS — adds the editor sheet that's missing in v2.5.
|
||||||
|
- iOS localization — translate the strings the Mac already has.
|
||||||
|
- Push notifications — flip the capability + deploy Hermes-side push sender.
|
||||||
|
- Deeper Insights / Activity views, scaled to phone screen sizes.
|
||||||
|
|
||||||
## Known Issues
|
## Known Issues
|
||||||
|
|
||||||
@@ -78,7 +87,7 @@ All tracked from the 2026-04-24 pass-1 smoke test. This list is the truth about
|
|||||||
| 2 | **Non-retryable provider errors → perpetual spinner.** ACP error triplet (`acpError`, `acpErrorHint`, `acpErrorDetails`) promoted to ScarfCore so Mac + ScarfGo share state; ChatView renders an inline banner with Copy Details / Expand. `handlePromptComplete` now calls `recordPromptStopFailureUsingProvider(stopReason:)` on non-`end_turn` stops with the stderr tail appended. | Cross-platform | ✅ Fixed |
|
| 2 | **Non-retryable provider errors → perpetual spinner.** ACP error triplet (`acpError`, `acpErrorHint`, `acpErrorDetails`) promoted to ScarfCore so Mac + ScarfGo share state; ChatView renders an inline banner with Copy Details / Expand. `handlePromptComplete` now calls `recordPromptStopFailureUsingProvider(stopReason:)` on non-`end_turn` stops with the stderr tail appended. | Cross-platform | ✅ Fixed |
|
||||||
| 3 | **No connecting feedback when entering Chat.** ChatController's existing `.connecting` state now drives a `.regularMaterial` overlay with "Connecting to <nickname>…" + ProgressView. | ScarfGo | ✅ Fixed |
|
| 3 | **No connecting feedback when entering Chat.** ChatController's existing `.connecting` state now drives a `.regularMaterial` overlay with "Connecting to <nickname>…" + ProgressView. | ScarfGo | ✅ Fixed |
|
||||||
| 4 | **`isAgentWorking` doesn't clear after primary response.** Split into computed `isGenerating` (agent still producing text) + `isPostProcessing` (agent done producing; ACP `promptComplete` not yet fired). Prominent spinner drops as soon as the reply is visible; subtle "Finishing up…" pill covers auxiliary post-work. Applied cross-platform. | Cross-platform | ✅ Fixed |
|
| 4 | **`isAgentWorking` doesn't clear after primary response.** Split into computed `isGenerating` (agent still producing text) + `isPostProcessing` (agent done producing; ACP `promptComplete` not yet fired). Prominent spinner drops as soon as the reply is visible; subtle "Finishing up…" pill covers auxiliary post-work. Applied cross-platform. | Cross-platform | ✅ Fixed |
|
||||||
| 5 | **ACP command missing PATH prefix.** SSH exec runs a non-interactive shell whose PATH is `/usr/bin:/bin:/usr/sbin:/sbin`. Fixed by prepending common install locations (`~/.local/bin`, `/opt/homebrew/bin`, `/usr/local/bin`, `~/.hermes/bin`) to PATH inline in the exec command. Mirrors `HermesPathSet.hermesBinaryCandidates`. | ScarfGo | ✅ Fixed |
|
| 5 | **ACP command missing PATH prefix.** SSH exec runs a non-interactive shell whose PATH is `/usr/bin:/bin:/usr/sbin:/sbin`. Fixed by prepending the three most common pipx + Homebrew install locations (`~/.local/bin`, `/opt/homebrew/bin`, `/usr/local/bin`) to PATH inline on every Citadel `runProcess` and `SSHExecACPChannel` invocation. Self-install layouts at `~/.hermes/bin` need the per-server **Hermes binary hint** override. | ScarfGo | ✅ Fixed |
|
||||||
| 6 | **SFTP `~` tilde not expanded.** Per-connection cached `resolveHome()` on `ConnectionHolder` + `resolveSFTPPath()` helper applied to every SFTP entry point (`readFile` / `writeFile` / `fileExists` / `stat` / `listDirectory` / `createDirectory` / `removeFile`). | ScarfGo | ✅ Fixed |
|
| 6 | **SFTP `~` tilde not expanded.** Per-connection cached `resolveHome()` on `ConnectionHolder` + `resolveSFTPPath()` helper applied to every SFTP entry point (`readFile` / `writeFile` / `fileExists` / `stat` / `listDirectory` / `createDirectory` / `removeFile`). | ScarfGo | ✅ Fixed |
|
||||||
| 7 | **No loading state on Memory editor.** Switched to throwing read (#8) so `lastError` populates on real failures instead of silently showing "empty" — the existing error banner now renders. | ScarfGo | ✅ Fixed |
|
| 7 | **No loading state on Memory editor.** Switched to throwing read (#8) so `lastError` populates on real failures instead of silently showing "empty" — the existing error banner now renders. | ScarfGo | ✅ Fixed |
|
||||||
| 8 | **`ServerContext.readText` swallows errors.** New `readTextThrowing(_:)` distinguishes "file absent" from "transport error"; old nil-returning `readText` stays as a `try?` shim for callers that really don't care. Memory editor uses the throwing variant. | Cross-platform | ✅ Fixed |
|
| 8 | **`ServerContext.readText` swallows errors.** New `readTextThrowing(_:)` distinguishes "file absent" from "transport error"; old nil-returning `readText` stays as a `try?` shim for callers that really don't care. Memory editor uses the throwing variant. | Cross-platform | ✅ Fixed |
|
||||||
@@ -123,7 +132,7 @@ The pass-1 session also surfaced the user-facing roadmap we delivered through M8
|
|||||||
|
|
||||||
## For contributors
|
## For contributors
|
||||||
|
|
||||||
See [Architecture Overview](Architecture-Overview) for how ScarfCore + ScarfIOS fit together. The `scarf-mobile-development` branch is the working branch; the M7/M8/M9 changes above live there pending a pass-2 smoke test before merging to `main`.
|
See [Architecture Overview](Architecture-Overview) for how ScarfCore + ScarfIOS fit together, [ScarfCore Package](ScarfCore-Package) for the package boundaries, and [Transport Layer](Transport-Layer) for the Citadel transport details. M6 → M10 are all merged to `main` and shipped as part of Scarf v2.5.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-24 — post M7/M8/M9 implementation, pass-2 pending_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (M10 TestFlight shipped; iPhone-only target settings; PATH-prefix correction)_
|
||||||
|
|||||||
+3
-3
@@ -41,7 +41,7 @@ Onboarding details:
|
|||||||
| **Memory** | Read + edit `MEMORY.md` and `USER.md`. The "Saved" pill survives keyboard dismissal; Revert undoes unsaved edits. |
|
| **Memory** | Read + edit `MEMORY.md` and `USER.md`. The "Saved" pill survives keyboard dismissal; Revert undoes unsaved edits. |
|
||||||
| **Cron** | List view of `~/.hermes/cron/jobs.json` with **human-readable schedules** ("Every 6 hours", "Weekdays at 09:00") and a relative next-run ("in 4 hours"). Read-only in v1 — editing comes later. |
|
| **Cron** | List view of `~/.hermes/cron/jobs.json` with **human-readable schedules** ("Every 6 hours", "Weekdays at 09:00") and a relative next-run ("in 4 hours"). Read-only in v1 — editing comes later. |
|
||||||
| **Skills** | Browse the skills tree from `~/.hermes/skills/`. Read-only. |
|
| **Skills** | Browse the skills tree from `~/.hermes/skills/`. Read-only. |
|
||||||
| **Settings** | Read-only view of `config.yaml`. An in-app editor is planned but not in v1 — see [Platform Differences](Platform-Differences). |
|
| **Settings** | Read view of full `config.yaml` plus a **Quick Edits** section that flips 7 commonly-changed keys (`model.default`, `model.provider`, `agent.approval_mode`, `agent.max_turns`, `display.show_cost`, `display.show_reasoning`, `display.streaming`) via `hermes config set` on the remote. Other keys remain read-only — edit from the Mac app or a remote shell. |
|
||||||
| **Slash commands** _(v2.5)_ | Read-only browser of project-scoped slash commands shipped via `<project>/.scarf/slash-commands/`. Tap a row to see the expanded prompt with a sample-argument field. Authoring is Mac-only in v1. See [Slash Commands](Slash-Commands). |
|
| **Slash commands** _(v2.5)_ | Read-only browser of project-scoped slash commands shipped via `<project>/.scarf/slash-commands/`. Tap a row to see the expanded prompt with a sample-argument field. Authoring is Mac-only in v1. See [Slash Commands](Slash-Commands). |
|
||||||
|
|
||||||
## Project-scoped chat
|
## Project-scoped chat
|
||||||
@@ -61,7 +61,7 @@ If the SFTP write fails (permissions, disk full, network drop), ScarfGo surfaces
|
|||||||
|
|
||||||
- **No local mode.** ScarfGo only operates against an SSH-reachable Hermes host. There's no local Hermes runtime on iOS.
|
- **No local mode.** ScarfGo only operates against an SSH-reachable Hermes host. There's no local Hermes runtime on iOS.
|
||||||
- **No push notifications yet.** The skeleton (UNNotificationCenter delegate, "Approve / Deny" action category) ships in the binary but is gated behind an internal feature flag because: (a) the Push Notifications capability is not yet enabled in the Xcode target, (b) Hermes doesn't yet have a push sender. When both land, push lights up on the iOS side without an app update — well, with one update to flip the flag. Watch this page.
|
- **No push notifications yet.** The skeleton (UNNotificationCenter delegate, "Approve / Deny" action category) ships in the binary but is gated behind an internal feature flag because: (a) the Push Notifications capability is not yet enabled in the Xcode target, (b) Hermes doesn't yet have a push sender. When both land, push lights up on the iOS side without an app update — well, with one update to flip the flag. Watch this page.
|
||||||
- **No in-app config editor.** Settings is read-only in v1. Use the Mac app or a remote shell to change values.
|
- **Limited config editor.** Settings on iOS surfaces a 7-key **Quick Edits** sheet that shells out to `hermes config set`; the rest of `config.yaml` stays read-only. Editing arbitrary keys still belongs on the Mac app or a remote shell.
|
||||||
- **No template install UI.** `.scarftemplate` install + uninstall is Mac-only in v1.
|
- **No template install UI.** `.scarftemplate` install + uninstall is Mac-only in v1.
|
||||||
- **No terminal mode.** Rich-chat (ACP) only.
|
- **No terminal mode.** Rich-chat (ACP) only.
|
||||||
- **English only.** The Mac app ships in 7 languages; ScarfGo is English-only for v1.
|
- **English only.** The Mac app ships in 7 languages; ScarfGo is English-only for v1.
|
||||||
@@ -113,4 +113,4 @@ A: [ScarfGo Roadmap](ScarfGo-Roadmap) tracks shipped milestones (M6 / M7 / M8 /
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
_Last updated: 2026-04-25 — v2.5 public TestFlight._
|
_Last updated: 2026-04-25 — v2.5 public TestFlight (corrected Settings — Quick Edits ship in v1)._
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,8 @@
|
|||||||
# Servers & Remote
|
# Servers & Remote
|
||||||
|
|
||||||
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 server on iOS?** ScarfGo's Servers list works the same idea but with on-device key generation. See [ScarfGo Onboarding](ScarfGo-Onboarding) for the iPhone walkthrough. The rest of this page is the macOS Mac-app flow.
|
||||||
|
|
||||||
|
Scarf 2.0 is multi-server. Each Mac 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`. ScarfGo (iOS) uses a single-window TabView; switching servers from its Servers list rebuilds the tab root against the new context.
|
||||||
|
|
||||||
## Adding a remote server
|
## Adding a remote server
|
||||||
|
|
||||||
@@ -54,4 +56,4 @@ See [Keyboard Shortcuts](Keyboard-Shortcuts).
|
|||||||
- [Hermes Paths](Hermes-Paths) for what each remote file is.
|
- [Hermes Paths](Hermes-Paths) for what each remote file is.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (added ScarfGo cross-references)_
|
||||||
|
|||||||
+18
-6
@@ -16,7 +16,7 @@ Each Scarf window has its **own** `AppCoordinator` — selection in one window d
|
|||||||
|
|
||||||
## SidebarSection
|
## 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:
|
`SidebarSection` ([`AppCoordinator.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Navigation/AppCoordinator.swift)) is the source of truth for every sidebar item. Each case has a `rawValue` (display name) and an `icon` (SF Symbol name). **22 cases** grouped into **5 sidebar headers** (the order is hardcoded in `SidebarView.swift`):
|
||||||
|
|
||||||
### Monitor (4)
|
### Monitor (4)
|
||||||
|
|
||||||
@@ -27,6 +27,14 @@ Each Scarf window has its **own** `AppCoordinator` — selection in one window d
|
|||||||
| Sessions | `bubble.left.and.bubble.right` |
|
| Sessions | `bubble.left.and.bubble.right` |
|
||||||
| Activity | `bolt.horizontal` |
|
| Activity | `bolt.horizontal` |
|
||||||
|
|
||||||
|
### Projects (1)
|
||||||
|
|
||||||
|
| Section | Icon |
|
||||||
|
|---|---|
|
||||||
|
| Projects | `square.grid.2x2` |
|
||||||
|
|
||||||
|
Projects has its own header — not a Manage sub-item — because per-project work (Dashboard / Sessions / Site / Slash Commands tabs, template install) is a top-level workflow surface in v2.5.
|
||||||
|
|
||||||
### Interact (3)
|
### Interact (3)
|
||||||
|
|
||||||
| Section | Icon |
|
| Section | Icon |
|
||||||
@@ -47,19 +55,20 @@ Each Scarf window has its **own** `AppCoordinator` — selection in one window d
|
|||||||
| Webhooks | `arrow.up.right.square` |
|
| Webhooks | `arrow.up.right.square` |
|
||||||
| Profiles | `person.2.crop.square.stack` |
|
| Profiles | `person.2.crop.square.stack` |
|
||||||
|
|
||||||
### Manage (8)
|
### Manage (7)
|
||||||
|
|
||||||
| Section | Icon |
|
| Section | Icon |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Projects | `square.grid.2x2` |
|
|
||||||
| Tools | `wrench.and.screwdriver` |
|
| Tools | `wrench.and.screwdriver` |
|
||||||
| MCP Servers | `puzzlepiece.extension` |
|
| MCP Servers | `puzzlepiece.extension` |
|
||||||
| Gateway | `antenna.radiowaves.left.and.right` |
|
| Messaging Gateway | `antenna.radiowaves.left.and.right` |
|
||||||
| Cron | `clock.arrow.2.circlepath` |
|
| Cron | `clock.arrow.2.circlepath` |
|
||||||
| Health | `stethoscope` |
|
| Health | `stethoscope` |
|
||||||
| Logs | `doc.text` |
|
| Logs | `doc.text` |
|
||||||
| Settings | `gearshape` |
|
| Settings | `gearshape` |
|
||||||
|
|
||||||
|
The Gateway item's `displayName` is **"Messaging Gateway"** — disambiguates from the v2.3 Tool Gateway (Nous Portal subscription routing) which is a Health-tab surface, not its own sidebar item. The enum case is still `.gateway` and the persisted state file path (`~/.hermes/gateway_state.json`) is unchanged.
|
||||||
|
|
||||||
## SidebarView
|
## 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`:
|
[`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`:
|
||||||
@@ -67,6 +76,7 @@ Each Scarf window has its **own** `AppCoordinator` — selection in one window d
|
|||||||
```swift
|
```swift
|
||||||
List(selection: $coordinator.selectedSection) {
|
List(selection: $coordinator.selectedSection) {
|
||||||
Section("Monitor") { … }
|
Section("Monitor") { … }
|
||||||
|
Section("Projects") { … }
|
||||||
Section("Interact") { … }
|
Section("Interact") { … }
|
||||||
Section("Configure") { … }
|
Section("Configure") { … }
|
||||||
Section("Manage") { … }
|
Section("Manage") { … }
|
||||||
@@ -86,9 +96,11 @@ Each window is bound to one `ServerContext` and one `AppCoordinator`. The window
|
|||||||
|
|
||||||
## ScarfGo (iOS) navigation
|
## ScarfGo (iOS) navigation
|
||||||
|
|
||||||
ScarfGo uses a different model — a 5-tab `TabView` rather than a sidebar. The tabs (Dashboard | Projects | Chat | Skills | System) are wrapped in their own `NavigationStack`s so push navigation (Cron editor, Memory detail, Project detail, Settings) stays scoped to the tab. Cross-tab signalling (Dashboard row → Chat tab resume, Project Detail → in-project chat handoff, notification deep-link → Chat) flows through `ScarfGoCoordinator`. `.tabViewStyle(.sidebarAdaptable)` automatically switches to a sidebar layout on iPad / Mac Catalyst — no separate code path.
|
ScarfGo uses a different model — a 5-tab `TabView` rather than a sidebar. The tabs (Dashboard | Projects | Chat | Skills | System) are wrapped in their own `NavigationStack`s so push navigation (Cron editor, Memory detail, Project detail, Settings) stays scoped to the tab. Cross-tab signalling (Dashboard row → Chat tab resume, Project Detail → in-project chat handoff, notification deep-link → Chat) flows through `ScarfGoCoordinator`.
|
||||||
|
|
||||||
|
`.tabViewStyle(.sidebarAdaptable)` is wired so the system can switch to a sidebar layout on larger devices — but as of v2.5 the iOS target ships **iPhone-only** (`TARGETED_DEVICE_FAMILY = 1`, `SUPPORTS_MACCATALYST = NO`, `SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO`). iPad polish is on the [ScarfGo Roadmap](ScarfGo-Roadmap) but not in scope for v2.5.
|
||||||
|
|
||||||
The Mac sidebar's "System" / advanced sections collapse into the iOS **System** tab (server identity, Memory link, Cron link, Settings link, Disconnect / Forget). See [Platform Differences](Platform-Differences) for the full Mac↔iOS feature matrix.
|
The Mac sidebar's "System" / advanced sections collapse into the iOS **System** tab (server identity, Memory link, Cron link, Settings link, Disconnect / Forget). See [Platform Differences](Platform-Differences) for the full Mac↔iOS feature matrix.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-25 — Scarf v2.5.0 (ScarfGo 5-tab nav added)_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (audit pass: 5 sidebar groups not 4, 22 cases not 23, Projects own group, Messaging Gateway display name, iPhone-only iOS target)_
|
||||||
|
|||||||
+61
-25
@@ -1,52 +1,88 @@
|
|||||||
# Testing
|
# Testing
|
||||||
|
|
||||||
**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.
|
The bulk of Scarf's automated coverage lives in the **ScarfCore** SwiftPM package — 14 test suites covering the iOS-port milestones (M0–M9) plus dedicated parsers, services, and view-models. The Mac target has 10 additional test suites for Mac-specific surfaces (Tool Gateway, ProjectsViewModel, NousAuthFlow, template config, etc.). UI tests are placeholder-only on both targets; dogfooding fills the gap.
|
||||||
|
|
||||||
This page documents what's in place and where contributions would help most.
|
|
||||||
|
|
||||||
## Frameworks
|
## 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)):
|
**Swift Testing** (`@Suite` / `@Test` macros) for all new tests, not XCTest. Per [CLAUDE.md](https://github.com/awizemann/scarf/blob/main/CLAUDE.md):
|
||||||
|
|
||||||
- Use `@Suite` and `@Test` macros for all new tests.
|
- Use `@Suite` and `@Test` macros.
|
||||||
- Protocol-oriented services for testability — the `ServerTransport` protocol is the obvious mocking seam.
|
- Protocol-oriented services for testability — `ServerTransport` is the mocking seam, with `MockTransport` already in the ScarfCore test target.
|
||||||
- No timing-dependent tests: use polling with early exit, not `Task.sleep` + assertion.
|
- No timing-dependent tests: use polling with early exit, not `Task.sleep` + assertion.
|
||||||
- Singleton state isolation: call cleanup methods + `await Task.yield()` before assertions.
|
- 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.
|
- Cross-suite state contention (e.g. `ServerContext.sshTransportFactory`) goes into a single `.serialized` suite — see `M3TransportTests.swift`. v2.5's test-reliability pass consolidated every factory-touching test there to fix flakes.
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
|
ScarfCore (the main coverage):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
swift test --package-path scarf/Packages/ScarfCore
|
||||||
|
```
|
||||||
|
|
||||||
|
Mac target:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
xcodebuild test -project scarf/scarf.xcodeproj -scheme scarf
|
xcodebuild test -project scarf/scarf.xcodeproj -scheme scarf
|
||||||
```
|
```
|
||||||
|
|
||||||
Or in Xcode: ⌘U.
|
Or in Xcode: ⌘U with the matching scheme selected.
|
||||||
|
|
||||||
## What would be high-value to add
|
## ScarfCore test inventory ([`scarf/Packages/ScarfCore/Tests/ScarfCoreTests/`](https://github.com/awizemann/scarf/tree/main/scarf/Packages/ScarfCore/Tests/ScarfCoreTests))
|
||||||
|
|
||||||
If you're looking for a contribution, these are the gaps that would matter most:
|
| Suite | Covers |
|
||||||
|
|---|---|
|
||||||
|
| `ScarfCoreSmokeTests` | Module imports, public surface visibility. |
|
||||||
|
| `M0bTransportTests` | `LocalTransport` + `SSHTransport` protocol contract, atomic-write semantics, error classification. |
|
||||||
|
| `M0cServicesTests` | `HermesFileService`, `HermesEnvService`, `HermesDataService` (against fixture DBs), `ProjectDashboardService`. |
|
||||||
|
| `M0dViewModelsTests` | `IOSDashboardViewModel`, `IOSSettingsViewModel`, etc. — view-model state machines without UI. |
|
||||||
|
| `M1ACPTests` | `ACPClient` JSON-RPC framing, event extraction, error-hint pattern matching. |
|
||||||
|
| `M2OnboardingTests` | iOS onboarding state machine, key generation, paste-import flow. |
|
||||||
|
| `M3TransportTests` | `.serialized` suite for cross-suite state contention; transport factory swaps. |
|
||||||
|
| `M4ACPIOSTests` | `SSHExecACPChannel` over a Citadel test fixture. |
|
||||||
|
| `M5FeatureVMTests` | iOS feature view-models — Memory, Cron, Skills. |
|
||||||
|
| `M6ConfigCronTests` | YAML config parsing, cron job round-trip, `HermesConfig` field mapping. |
|
||||||
|
| `M9SlashCommandTests` | Project-scoped slash command parsing, `{{argument}}` substitution, default fallback, AGENTS.md block extension. |
|
||||||
|
| `SkillsHubParserTests` | `hermes skills browse`/`search` Rich-table parser — every column shape, continuation rows, header skip. |
|
||||||
|
| `SkillFrontmatterParserTests` | SKILL.md YAML frontmatter parser (`allowed_tools`, `related_skills`, `dependencies`). |
|
||||||
|
| `CronScheduleFormatterTests` | Cron string → English translator across every recognized shape. |
|
||||||
|
|
||||||
1. **`ServerTransport` mock + `LocalTransport` smoke tests** — every service depends on transport, so a `MockTransport` unlocks unit-testing all of them.
|
## Mac target test inventory ([`scarf/scarfTests/`](https://github.com/awizemann/scarf/tree/main/scarf/scarfTests))
|
||||||
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.
|
| Suite | Covers |
|
||||||
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.
|
| `ToolGatewayTests` | Tool Gateway routing, `HERMES_OVERLAYS` mirror, Auxiliary tab behavior. |
|
||||||
6. **`SSHTransport` shell-quoting tests** — `shellQuote` and `remotePathArg` are correctness-critical and pure functions.
|
| `ProjectAgentContextServiceTests` | AGENTS.md `<!-- scarf-project -->` block — idempotency, secret-safe field-name surfacing, preserve-around-block invariant. |
|
||||||
|
| `SessionAttributionServiceTests` | Sidecar JSON read/write, attribution lookup, refresh cadence. |
|
||||||
|
| `NousAuthFlowTests` | Device-code flow parser, subscription-required detection, billing URL extraction. |
|
||||||
|
| `ProjectsViewModelTests` | Mac project sidebar — folder grouping, archive, ⌘1–9 jumps, search. |
|
||||||
|
| `ProjectRegistryMigrationTests` | v2.2 → v2.3 registry migration. |
|
||||||
|
| `CredentialPoolsGatingTests` | Per-provider strategy + last-4 preview. |
|
||||||
|
| `TemplateConfigTests` | Schema validation (string/text/number/bool/enum/list/secret). |
|
||||||
|
| `ProjectTemplateTests` | `.scarftemplate` install, lock-file accounting, uninstall preserve-user-files invariant. |
|
||||||
|
| `scarfTests` | Misc. legacy. |
|
||||||
|
|
||||||
|
163 tests across 12 suites pre-2.5; v2.5 brought that to **179 tests across 13 suites** (per the release notes), with the slash-command test additions and the cross-suite race fixes.
|
||||||
|
|
||||||
## Manual verification flows
|
## Manual verification flows
|
||||||
|
|
||||||
For any change that touches behavior, here's the manual checklist the maintainer runs before tagging a release:
|
For changes that touch UI or remote-host behavior, the maintainer runs:
|
||||||
|
|
||||||
- Open a local window — Dashboard loads, Sessions browser populates, Memory editor opens.
|
- Open a local Mac window — Dashboard loads, Sessions browser populates, Memory editor opens.
|
||||||
- Open a remote window — same Dashboard / Sessions / Memory, but against the dogfooding host.
|
- Open a remote Mac window — same Dashboard / Sessions / Memory, against the dogfooding host (`Mardon` Mac mini).
|
||||||
- Send a Rich Chat message — receives a streamed response, reasoning shows if the model emits it.
|
- Open ScarfGo against the same host — Dashboard / Chat / Skills / System all populate; Browse Hub returns results (regression check on the Citadel `executeCommandStream` v2.5 fix).
|
||||||
- Edit and save a memory file — change appears in Hermes on next agent turn.
|
- Send a Rich Chat message — streamed response, reasoning shows if the model emits it.
|
||||||
- Run a Cron job — appears in the Cron view, has correct delivery channel.
|
- Edit and save a memory file — change appears in Hermes on next agent turn (Mac + iOS).
|
||||||
|
- Trigger `hermes memory reset` from the Memory toolbar — destructive confirm, reset runs, view refreshes.
|
||||||
|
- Run a Cron job — appears in Cron view, schedule renders as human-readable phrase.
|
||||||
- Toggle a tool in Tools — `hermes tools enable/disable` runs and the dot color updates.
|
- Toggle a tool in Tools — `hermes tools enable/disable` runs and the dot color updates.
|
||||||
|
- iOS-specific: forget a server, re-onboard, verify Keychain wipe + re-creation; Settings → Quick Edits flips a value via `hermes config set`.
|
||||||
|
|
||||||
## Why so little automated coverage?
|
## What's still under-covered
|
||||||
|
|
||||||
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.
|
- **UI tests.** `scarfUITests/` and `Scarf iOSUITests/` are placeholder-only. Worth contributing if you have UI-test experience.
|
||||||
|
- **`HermesLogService.streamLines` remote tail.** Hard to mock SSH stream behavior cleanly; relies on the dogfooding flow today.
|
||||||
|
- **Sparkle update flow.** The auto-update path is exercised manually before each release (see [Release Process](Release-Process)).
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (179 tests, 13 suites; full inventory)_
|
||||||
|
|||||||
+39
-6
@@ -1,10 +1,16 @@
|
|||||||
# Transport Layer
|
# Transport Layer
|
||||||
|
|
||||||
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`.
|
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. Three implementations exist as of v2.5:
|
||||||
|
|
||||||
|
- **`LocalTransport`** — direct `FileManager` + `Process` against the local disk (`scarf/Packages/ScarfCore/Sources/ScarfCore/Transport/LocalTransport.swift`).
|
||||||
|
- **`SSHTransport`** — OpenSSH-driven, multiplexed via ControlMaster. **Mac only**; iOS doesn't ship the `/usr/bin/ssh` binary (`scarf/Packages/ScarfCore/Sources/ScarfCore/Transport/SSHTransport.swift`).
|
||||||
|
- **`CitadelServerTransport`** — pure-Swift SSH via Citadel + NIO. **iOS only**, used by ScarfGo for every remote primitive (`scarf/Packages/ScarfIOS/Sources/ScarfIOS/CitadelServerTransport.swift`).
|
||||||
|
|
||||||
|
All three implement the same protocol, so services in [ScarfCore](ScarfCore-Package) can consume any of them without `#if os(...)` shims.
|
||||||
|
|
||||||
## Protocol surface
|
## Protocol surface
|
||||||
|
|
||||||
[`ServerTransport.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Transport/ServerTransport.swift) exposes:
|
[`ServerTransport.swift`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/Transport/ServerTransport.swift) exposes:
|
||||||
|
|
||||||
**Identity**
|
**Identity**
|
||||||
- `contextID: ServerID` — UUID; namespaces caches under `~/Library/Caches/scarf/snapshots/<id>/`.
|
- `contextID: ServerID` — UUID; namespaces caches under `~/Library/Caches/scarf/snapshots/<id>/`.
|
||||||
@@ -31,7 +37,7 @@ The `ServerTransport` protocol unifies local and SSH I/O. Services consume `tran
|
|||||||
|
|
||||||
## Errors
|
## Errors
|
||||||
|
|
||||||
[`TransportErrors.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Transport/TransportErrors.swift) defines `TransportError`:
|
[`TransportErrors.swift`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/Transport/TransportErrors.swift) defines `TransportError`:
|
||||||
|
|
||||||
| Case | Cause |
|
| Case | Cause |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -47,7 +53,7 @@ Stderr-pattern classification turns raw `ssh` errors into the right case so the
|
|||||||
|
|
||||||
## LocalTransport
|
## LocalTransport
|
||||||
|
|
||||||
[`LocalTransport.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Transport/LocalTransport.swift) — a thin wrapper around `FileManager`, `Process`, and `DispatchSourceFileSystemObject`.
|
[`LocalTransport.swift`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/Transport/LocalTransport.swift) — a thin wrapper around `FileManager`, `Process`, and `DispatchSourceFileSystemObject`.
|
||||||
|
|
||||||
- **Atomic writes:** writes to `<path>.scarf.tmp`, sets `0600` if the filename suggests a secret, then `replaceItemAt` (existing) or `moveItem` (new).
|
- **Atomic writes:** writes to `<path>.scarf.tmp`, sets `0600` if the filename suggests a secret, then `replaceItemAt` (existing) or `moveItem` (new).
|
||||||
- **Process timeout:** polls every 100ms until deadline; `terminate()` if exceeded.
|
- **Process timeout:** polls every 100ms until deadline; `terminate()` if exceeded.
|
||||||
@@ -56,7 +62,7 @@ Stderr-pattern classification turns raw `ssh` errors into the right case so the
|
|||||||
|
|
||||||
## SSHTransport
|
## 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.
|
[`SSHTransport.swift`](https://github.com/awizemann/scarf/blob/main/scarf/Packages/ScarfCore/Sources/ScarfCore/Transport/SSHTransport.swift) — every primitive becomes an `ssh`/`scp`/`sftp` invocation, multiplexed over a single ControlMaster connection.
|
||||||
|
|
||||||
### ControlMaster pooling
|
### ControlMaster pooling
|
||||||
|
|
||||||
@@ -122,5 +128,32 @@ sqlite3 '/tmp/scarf-snapshot-XYZ.db' "PRAGMA journal_mode=DELETE;"
|
|||||||
|
|
||||||
See [Servers & Remote](Servers-and-Remote) for setup and troubleshooting.
|
See [Servers & Remote](Servers-and-Remote) for setup and troubleshooting.
|
||||||
|
|
||||||
|
## CitadelServerTransport (iOS, v2.5+)
|
||||||
|
|
||||||
|
The iOS app can't shell out to `/usr/bin/ssh` — there's no such binary in the iOS sandbox. Instead, ScarfGo drives [Citadel](https://github.com/orlandos-nl/Citadel), a pure-Swift SSH/SFTP/exec implementation built on SwiftNIO. `CitadelServerTransport` wraps it behind the same `ServerTransport` protocol so all of ScarfCore consumes one shape.
|
||||||
|
|
||||||
|
### What's shared with the Mac transports
|
||||||
|
|
||||||
|
- Same `readFile` / `writeFile` / `stat` / `listDirectory` / `runProcess` / `snapshotSQLite` / `watchPaths` API.
|
||||||
|
- Same `TransportError` classification (host unreachable, auth failed, command failed, etc.).
|
||||||
|
- Same atomic-write convention (`<path>.scarf.tmp` → SFTP `rename`).
|
||||||
|
- Same SQLite snapshot mechanics — `sqlite3 .backup` on the remote, SFTP-pull the snapshot, `PRAGMA journal_mode=DELETE` to strip WAL.
|
||||||
|
|
||||||
|
### What's iOS-specific
|
||||||
|
|
||||||
|
- **Pure-Swift exec channel.** Citadel's exec channel does the SSH wire protocol (RFC 4254) directly; there is no shelled-out `ssh -T host -- cmd`. One long-lived `SSHClient` per host, kept warm by `CitadelConnectionHolder`.
|
||||||
|
- **Pure-Swift SFTP.** All `readFile` / `writeFile` / `stat` / `listDirectory` go over SFTP via Citadel's `SFTPClient`. Path resolution rewrites `~/...` to the probed `$HOME` (SFTP doesn't expand tildes per RFC 4254).
|
||||||
|
- **Inline PATH prefix on every `runProcess`.** Citadel's raw exec channel doesn't source the user's shell rc files, so non-interactive sessions land with `PATH=/usr/bin:/bin`. v2.5 inlines `PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"` on every command so pipx-installed `hermes` resolves and any subprocess hermes spawns can find git/curl/python. Mac's OpenSSH sshd handles this transparently via login-shell init; Citadel does not.
|
||||||
|
- **Output preservation on non-zero exit.** Citadel's high-level `executeCommand` API throws `CommandFailed` and discards captured stdout when the remote exits non-zero. v2.5 drives `executeCommandStream` directly — drains stdout + stderr regardless of outcome, recovers the actual exit code from the `CommandFailed` catch. This was the bug behind "Skills Browse failed" on iOS while Mac worked.
|
||||||
|
- **Keychain-backed SSH key.** Each configured server holds its own Ed25519 keypair in the iOS Keychain (`com.scarf.ssh-key` service, `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`, account `server-key:<UUID>`). Mac uses the system ssh-agent + `~/.ssh/config`; iOS keys never leave the device.
|
||||||
|
- **Watch via mtime polling.** Same 3-second cadence as `SSHTransport` — Citadel doesn't have an equivalent of inotify-over-SSH.
|
||||||
|
- **No streamed exec yet.** `streamLines` is a stub on iOS; log tailing in ScarfGo uses periodic refreshes instead. Future work — Citadel exposes the raw exec channel, just hasn't been wired up.
|
||||||
|
|
||||||
|
### Connection holder + reuse
|
||||||
|
|
||||||
|
Citadel's `SSHClient.connect(...)` handshake costs ~500ms on a warm network. ScarfGo keeps a long-lived per-server `CitadelConnectionHolder` so subsequent calls reuse the same TCP+crypto session — same idea as Mac ControlMaster, different mechanism. The holder is cached per-`ServerID` so two configured remotes don't contend on a single channel pool.
|
||||||
|
|
||||||
|
See [ScarfGo Onboarding](ScarfGo-Onboarding) for user-side setup and [ScarfCore Package](ScarfCore-Package) for why `KeychainSSHKeyStore` lives in `ScarfIOS` and not `ScarfCore`.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.2_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (ScarfCore path refresh + CitadelServerTransport section)_
|
||||||
|
|||||||
@@ -80,3 +80,7 @@ If something else is still slow, it's likely Python startup + a working but heav
|
|||||||
- [MCP, Plugins, Webhooks, Tools](MCP-Servers-Plugins-Webhooks-Tools) — the in-app editor for `mcp_servers`.
|
- [MCP, Plugins, Webhooks, Tools](MCP-Servers-Plugins-Webhooks-Tools) — the in-app editor for `mcp_servers`.
|
||||||
- [ACP Subprocess](ACP-Subprocess) — how Scarf talks to `hermes acp`.
|
- [ACP Subprocess](ACP-Subprocess) — how Scarf talks to `hermes acp`.
|
||||||
- [Servers & Remote](Servers-and-Remote) — connectivity issues that aren't MCP-related.
|
- [Servers & Remote](Servers-and-Remote) — connectivity issues that aren't MCP-related.
|
||||||
|
- [ScarfGo Onboarding](ScarfGo-Onboarding) — iOS-specific PATH workarounds (Citadel inlines `$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin` on every command in v2.5+ so pipx-installed tools resolve, but `hermes` sub-tools may still be missing if their PATH isn't covered).
|
||||||
|
|
||||||
|
---
|
||||||
|
_Last updated: 2026-04-25 — Scarf v2.5.0_
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,8 @@
|
|||||||
# Uninstalling
|
# Uninstalling
|
||||||
|
|
||||||
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.
|
> **Removing ScarfGo from your iPhone?** Standard iOS app delete — long-press the icon → Remove App → Delete App. iOS purges the Keychain group (your SSH keys) and app container along with the binary, so nothing lingers. The Hermes host's `~/.ssh/authorized_keys` line you added during onboarding stays — clean it up manually if you want.
|
||||||
|
|
||||||
|
This page covers the macOS app. 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
|
## Quit and remove the app
|
||||||
|
|
||||||
@@ -36,4 +38,4 @@ To uninstall Hermes itself, follow the Hermes documentation — that's a separat
|
|||||||
Just for completeness: the GitHub wiki at <https://github.com/awizemann/scarf/wiki> is a separate git repo from the main one. Uninstalling the app has no effect on the wiki.
|
Just for completeness: the GitHub wiki at <https://github.com/awizemann/scarf/wiki> is a separate git repo from the main one. Uninstalling the app has no effect on the wiki.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.2_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (added ScarfGo iOS uninstall note)_
|
||||||
|
|||||||
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
# Updating
|
# Updating
|
||||||
|
|
||||||
|
> **ScarfGo updates differently.** The iOS app updates through TestFlight (and, in future, the App Store) — Apple's standard update channel. The Sparkle flow described below is **macOS only**.
|
||||||
|
|
||||||
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`.
|
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
|
## Automatic updates
|
||||||
@@ -35,4 +37,4 @@ App preferences live in `~/Library/Preferences/com.scarf.app.plist` and are forw
|
|||||||
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.
|
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 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (clarified macOS-only Sparkle vs ScarfGo TestFlight)_
|
||||||
|
|||||||
+3
-1
@@ -82,6 +82,8 @@ _Last updated: YYYY-MM-DD — stub_
|
|||||||
- **Release Process** on the wiki is a pointer; the canonical instructions live in `CLAUDE.md` and the header of `scripts/release.sh`.
|
- **Release Process** on the wiki is a pointer; the canonical instructions live in `CLAUDE.md` and the header of `scripts/release.sh`.
|
||||||
- **Hermes Paths** mirrors the Key Paths block in `CLAUDE.md` — update both when paths change.
|
- **Hermes Paths** mirrors the Key Paths block in `CLAUDE.md` — update both when paths change.
|
||||||
- **Release notes** stay in `releases/v<ver>/RELEASE_NOTES.md` on `main`. The wiki's [Release Notes Index](Release-Notes-Index) only links out.
|
- **Release notes** stay in `releases/v<ver>/RELEASE_NOTES.md` on `main`. The wiki's [Release Notes Index](Release-Notes-Index) only links out.
|
||||||
|
- **TestFlight + App Store metadata** stay in `releases/v<ver>/TESTFLIGHT_CHECKLIST.md` and `APP_STORE_METADATA.md` on `main`. The wiki's [ScarfGo](ScarfGo) page links to the live TestFlight URL but doesn't duplicate Apple-side metadata.
|
||||||
|
- **Privacy Policy** has two copies on purpose: the canonical one at `awizemann.github.io/scarf/privacy/` (linked from the iOS Info.plist + App Store Connect), plus a wiki mirror at [Privacy Policy](Privacy-Policy) for in-wiki readability. The wiki copy is updated alongside major releases.
|
||||||
- **Internal dev notes** (PRD, Hermes API discovery, raw architecture) live in `scarf/docs/` in the main repo. The wiki carries the public-relevant parts in distilled form, not full duplicates.
|
- **Internal dev notes** (PRD, Hermes API discovery, raw architecture) live in `scarf/docs/` in the main repo. The wiki carries the public-relevant parts in distilled form, not full duplicates.
|
||||||
|
|
||||||
## For external contributors
|
## For external contributors
|
||||||
@@ -92,4 +94,4 @@ The wiki is **not forked** when someone forks the main repo — it is a separate
|
|||||||
- **Larger changes:** clone `git@github.com:awizemann/scarf.wiki.git` directly, or open an issue describing the proposed change and we'll work it in.
|
- **Larger changes:** clone `git@github.com:awizemann/scarf.wiki.git` directly, or open an issue describing the proposed change and we'll work it in.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last updated: 2026-04-20 — Scarf v2.0.1_
|
_Last updated: 2026-04-25 — Scarf v2.5.0 (added TestFlight + App Store metadata + privacy-mirror source-of-truth rules)_
|
||||||
|
|||||||
Reference in New Issue
Block a user