diff --git a/ACP-Subprocess.md b/ACP-Subprocess.md index e20090c..d2bc779 100644 --- a/ACP-Subprocess.md +++ b/ACP-Subprocess.md @@ -1,18 +1,33 @@ # 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`. +[`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`. 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. - **Remote:** the transport returns `/usr/bin/ssh -T host -- hermes acp`. `SSH_AUTH_SOCK` is inherited so the GUI-launched Scarf reaches the user's ssh-agent. `TERM` is removed. `-T` (no PTY) is critical — without it stdin/stdout would be PTY-cooked and the JSON-RPC framing would break. +## 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 | 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" | --- -_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)_ diff --git a/Adding-a-Feature-Module.md b/Adding-a-Feature-Module.md index ed223d6..71eb131 100644 --- a/Adding-a-Feature-Module.md +++ b/Adding-a-Feature-Module.md @@ -1,19 +1,33 @@ # 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/ Views/ MyFeatureView.swift 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. +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 ```swift @@ -101,17 +115,13 @@ enum SidebarSection: String, CaseIterable, Identifiable { ## 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 -Section("Interact") { - ForEach([SidebarSection.chat, .memory, .skills, .myFeature]) { section in - Label(section.rawValue, systemImage: section.icon).tag(section) - } -} +Section(title: "Interact", items: [.chat, .memory, .skills, .myFeature]), ``` -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 @@ -126,7 +136,16 @@ case .myFeature: MyFeatureView(context: serverContext) ## Step 6: (If your feature uses a new service) -If you needed a new service to back this feature, add it under `Core/Services/` and inject any shared instance in `ContextBoundRoot` via `.environment(...)`. See [Adding a Service](Adding-a-Service). +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 @@ -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. - **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/SidebarView.swift` — add to the right `Section`. +- ✏️ `Navigation/SidebarView.swift` — add to the right `Section` items array. - ✏️ `ContentView.swift` — 1 switch case. - ✏️ `scarfApp.swift` — only if you needed to inject a new shared service. - ✨ `Features/MyFeature/Views/MyFeatureView.swift` — new. -- ✨ `Features/MyFeature/ViewModels/MyFeatureViewModel.swift` — new. +- ✨ `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)_ diff --git a/Adding-a-Service.md b/Adding-a-Service.md index ffda026..3502368 100644 --- a/Adding-a-Service.md +++ b/Adding-a-Service.md @@ -1,6 +1,16 @@ # 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 @@ -64,9 +74,9 @@ actor MyStatefulService { ## Conventions - **Take `ServerContext` in `init`.** Never hardcode `ServerContext.local` — services must work against any window's bound server. -- **Route I/O through `context.transport` or the `context.read*/write*/runHermes` helpers.** Never use `FileManager`, `Process`, or `NSWorkspace.open` directly for Hermes paths — those break on remote (and break the rule from the project's [feedback memory](Wiki-Maintenance)). +- **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. -- **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. ## Wiring the service into a feature @@ -106,7 +116,7 @@ Use Environment for things every window has exactly one of — file watcher, ser ## 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)_ diff --git a/Build-and-Run.md b/Build-and-Run.md index c45385b..f6a2c09 100644 --- a/Build-and-Run.md +++ b/Build-and-Run.md @@ -1,12 +1,13 @@ # 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 - **macOS 14.6+ (Sonoma)** on the dev machine. - **Xcode 16.0+**. -- **Hermes** at `~/.hermes/` (so the local server window has something to point at — see [First Run](First-Run)). +- **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 @@ -22,6 +23,8 @@ Build with ⌘B; run with ⌘R. ## Build from the command line +### Mac scheme (`scarf`) + Debug build: ```bash @@ -36,6 +39,18 @@ xcodebuild -project scarf/scarf.xcodeproj -scheme scarf \ -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 ``` @@ -43,31 +58,53 @@ scarf/ repo root CLAUDE.md project instructions for Claude Code CONTRIBUTING.md README.md - releases/v/ 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/ per-version notes (RELEASE_NOTES.md, TESTFLIGHT_CHECKLIST.md, APP_STORE_METADATA.md) + templates/ Community .scarftemplate catalog source + tools/ build-catalog.py + tests scripts/ - release.sh full release pipeline - wiki.sh this wiki helper - ExportOptions.plist + release.sh Mac release pipeline (archive + notarize + appcast + tag) + wiki.sh GitHub wiki helper + catalog.sh .scarftemplate catalog publishing scarf/ Xcode project root - scarf.xcodeproj - docs/ internal dev notes (PRD, Discovery, ARCH) - standards/ read-only reference standards - scarf/ APP TARGET — start here - scarfApp.swift @main App - ContentView.swift window root + scarf.xcodeproj Two targets: scarf, scarf mobile + docs/ Internal dev notes + PRIVACY_POLICY.md + Packages/ Local SwiftPM packages + ScarfCore/ Shared models, services, view-models, transport, + ACP, parsing, security — Mac + iOS both link this + 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/ - Services/ 9 services - Models/ 13+ models - Transport/ 4 transport files - Persistence/ ServerRegistry - Utilities/ markdown helpers - Features/ 25 feature modules + Services/ Mac-only services (Sparkle wrapper, etc.) + Persistence/ ServerRegistry (plist) + Features/ Mac feature modules Navigation/ AppCoordinator + SidebarView - Assets.xcassets - Info.plist - scarf.entitlements - scarfTests/ - scarfUITests/ + Assets.xcassets Mac app icon (rust set), AccentColor + Info.plist + scarf.entitlements + Scarf iOS/ iOS TARGET — ScarfGo + App/ ScarfGoTabRoot, ScarfGoCoordinator, theme glue + 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 @@ -79,23 +116,32 @@ The project compiles with strict concurrency on. Two non-negotiables ([detail in ## What to look at first -1. [`scarfApp.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/scarfApp.swift) — App entry point, multi-window setup, `ContextBoundRoot` injecting the `ServerContext` and friends. -2. [`Navigation/AppCoordinator.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Navigation/AppCoordinator.swift) — single source of truth for navigation. -3. [`Core/Models/ServerContext.swift`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/ServerContext.swift) — the unified handle to a Hermes install. -4. [`Core/Transport/`](https://github.com/awizemann/scarf/tree/main/scarf/scarf/Core/Transport) — local vs. SSH abstraction. -5. Any feature module under [`Features/`](https://github.com/awizemann/scarf/tree/main/scarf/scarf/Features) for the MVVM-F shape. +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 Mac navigation. +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. [`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 [`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 +Mac target tests: + ```bash 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 -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 ` — 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/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)_ diff --git a/Contributing.md b/Contributing.md index 97fdf4a..f7add4d 100644 --- a/Contributing.md +++ b/Contributing.md @@ -5,9 +5,9 @@ Thanks for your interest in contributing to Scarf. The canonical contributor gui ## Quick start 1. Fork and clone . -2. Open `scarf/scarf.xcodeproj` in Xcode 16+. -3. Build and run (requires macOS 14.6+ and Hermes installed at `~/.hermes/`). -4. Read [Build & Run](Build-and-Run) for the codebase tour and [Architecture Overview](Architecture-Overview) for the layering. +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/` 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, [Architecture Overview](Architecture-Overview) for the layering, and [Design System](Design-System) for the rust palette + token usage. ## 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 -- Anything in the [Roadmap](Roadmap). -- Test coverage — see [Testing](Testing) for the highest-value gaps. +- Anything in the [Roadmap](Roadmap) or [ScarfGo Roadmap](ScarfGo-Roadmap). +- 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. - 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. 2. One feature or fix per PR — keeps reviews tight. 3. Include a clear description of what changed and why. -4. Ensure `xcodebuild -project scarf/scarf.xcodeproj -scheme scarf build` succeeds with zero warnings. +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 @@ -57,4 +63,4 @@ Two paths: 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)_ diff --git a/Dashboard.md b/Dashboard.md index e34e287..6f36fad 100644 --- a/Dashboard.md +++ b/Dashboard.md @@ -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. - **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 - [Insights & Activity](Insights-and-Activity) for deeper analytics. - [Chat](Chat) for talking to the running Hermes. - [Servers & Remote](Servers-and-Remote) for adding remote hosts and the diagnostics flow. +- [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)_ diff --git a/Data-Model.md b/Data-Model.md index e6d12b0..125da48 100644 --- a/Data-Model.md +++ b/Data-Model.md @@ -1,29 +1,31 @@ # 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 | Type | Conformances | Notable fields | Notes | |---|---|---|---| -| `HermesSession` | `Identifiable, Sendable` | `id, source, userId, model, title, parentSessionId, startedAt, endedAt, endReason, messageCount, toolCallCount, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens, estimatedCostUSD, actualCostUSD, costStatus, billingProvider` | `isSubagent` = `parentSessionId != nil`; `displayCostUSD` prefers `actualCostUSD`; `sourceIcon` calls `KnownPlatforms.icon(for:source)`. | -| `HermesMessage` | `Identifiable, Sendable` | `id, sessionId, role, content, toolCallId, timestamp, tokenCount, finishReason, toolCalls, toolName, reasoning` | `isUser/isAssistant/isToolResult` helpers; `hasReasoning` for v0.7+ reasoning support. | +| `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, 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. | ## Server context and paths | Type | Purpose | |---|---| -| [`ServerContext`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/ServerContext.swift) | The unifying handle. Holds `id, displayName, kind` (`.local` or `.ssh(SSHConfig)`); exposes `paths`, `makeTransport()`, and high-level helpers (`readText`, `writeText`, `runHermes`, `openInLocalEditor`). The `local` static is the well-known UUID `00000000-0000-0000-0000-000000000001`. | +| [`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?` | | `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. ## 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` - 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 | |---|---| -| [`HermesCronJob`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/HermesCronJob.swift) | Job definition with `schedule, prompt, skills?, model?, enabled, state, deliver, nextRunAt?, lastRunAt?, lastError?, preRunScript?, deliveryFailures?, timeoutType?, timeoutSeconds?, silent?`. Nested `CronSchedule` and `CronJobsFile` container. | -| [`HermesMCPServer`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/HermesMCPServer.swift) | MCP server config: `name, transport (.stdio/.http), command?, args, url?, auth?, env, headers, timeouts, enabled, toolsInclude/Exclude, resourcesEnabled, promptsEnabled, hasOAuthToken`. | -| [`MCPServerPreset`](https://github.com/awizemann/scarf/blob/main/scarf/scarf/Core/Models/MCPServerPreset.swift) | Curated presets gallery (Filesystem, GitHub, Postgres, Slack, Linear, Sentry, Puppeteer, Memory, Fetch). | +| [`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/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/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). | -| `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 -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) - `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 -[`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 -[`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)_ diff --git a/First-Run.md b/First-Run.md index 4317d39..6d82dc3 100644 --- a/First-Run.md +++ b/First-Run.md @@ -1,5 +1,7 @@ # 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. ## 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. +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)_ diff --git a/Gateway-Cron-Health-Logs.md b/Gateway-Cron-Health-Logs.md index 33cd040..311e99a 100644 --- a/Gateway-Cron-Health-Logs.md +++ b/Gateway-Cron-Health-Logs.md @@ -19,7 +19,7 @@ View and edit Hermes scheduled jobs (`~/.hermes/cron/jobs.json`): | Column | What it shows | |---|---| | Name | The job's display name. | -| Schedule | Cron expression or run-at timestamp. | +| 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. | | Last run / next run | Timestamps. | | 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. | | **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. | | **Browser** | Browser backend selection. | | **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.). | | **Security** | Tirith sandbox, command allowlist, website blocklist, redaction. | | **Advanced** | Logging level / rotation, checkpoints, human-delay simulation, compression thresholds. | **Backup & Restore** lives at the bottom — wraps `hermes backup` (zips the current profile) and `hermes import` (unzips into the active profile). One-click via `context.runHermes`. +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 - [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. --- -_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)_ diff --git a/Hermes-Paths.md b/Hermes-Paths.md index 64cd9e9..8567ed9 100644 --- a/Hermes-Paths.md +++ b/Hermes-Paths.md @@ -1,24 +1,46 @@ # 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 | |---|---|---| | `~/.hermes/` | Hermes home | read-only base | | `~/.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/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/USER.md` | Hermes's user memory | read + write | | `~/.hermes/sessions/session_*.json` | Session metadata files | read-only | | `~/.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/errors.log` | Error 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/personalities/` | Personalities + their `SOUL.md` | 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. | +| `/.scarf/dashboard.json` | Per-project dashboard JSON | Lives inside the project, not under `~/.hermes/`. | +| `/.scarf/template.lock.json` | `.scarftemplate` install manifest (when a project was installed from a template) | Drives clean uninstall. | +| `/.scarf/manifest.json` | Cached `template.json` for templates with a config schema | Drives the post-install Configuration sheet. | +| `/.scarf/config.json` | Non-secret configuration values | Secrets are `keychain://...` URIs; resolved at use time. | +| `/.scarf/slash-commands/.md` _(v2.5+)_ | Project-scoped slash commands | See [Slash Commands](Slash-Commands). | +| `/AGENTS.md` (between `` markers) | Auto-managed project context block | Idempotent, secret-safe. See [Projects & Profiles](Projects-and-Profiles). | ## ACP @@ -29,12 +51,25 @@ Chat does not go through the filesystem. It is a subprocess: `hermes acp` (local | Path | What lives here | |---|---| | `~/Library/Caches/scarf/snapshots//` | Atomic `state.db` snapshots pulled from remote servers via `sqlite3 .backup` | +| `~/Library/Application Support/com.scarf/skill-snapshots/.json` | Per-server skill snapshot for the v2.5 "What's New" pill | | `/tmp/scarf-ssh-/` | 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 | +## 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 → `/Library/Caches/scarf/snapshots//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:`, accessibility `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`. Never iCloud-synced. | +| Template config secrets | iOS Keychain — service `com.scarf.template.`, account `:` | + ## 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. --- -_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)_ diff --git a/Insights-and-Activity.md b/Insights-and-Activity.md index 0e58276..0af54dd 100644 --- a/Insights-and-Activity.md +++ b/Insights-and-Activity.md @@ -24,10 +24,12 @@ All queries are aggregations over the same `sessions`, `messages`, `tool_calls` The full conversation history browser: - **List** — every session, ordered by start date DESC. Subagent sessions (those with a `parent_session_id`) are filtered from the main list and accessible by drilling into the parent. -- **Detail panel** — full message stream: user → assistant → tool calls → tool results, with markdown rendering. **Reasoning blocks** (v0.7+) render in a collapsed section. +- **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). - **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. @@ -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. --- -_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)_ diff --git a/Installation.md b/Installation.md index 747c5d2..93ee25d 100644 --- a/Installation.md +++ b/Installation.md @@ -1,5 +1,7 @@ # 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 - **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. --- -_Last updated: 2026-04-20 — Scarf v2.0.1_ +_Last updated: 2026-04-25 — Scarf v2.5.0 (added ScarfGo cross-link)_ diff --git a/Keyboard-Shortcuts.md b/Keyboard-Shortcuts.md index 6f0a670..e601727 100644 --- a/Keyboard-Shortcuts.md +++ b/Keyboard-Shortcuts.md @@ -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 | | ⌘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 @@ -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. +## 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)_ diff --git a/Localization.md b/Localization.md index 5414135..01585a5 100644 --- a/Localization.md +++ b/Localization.md @@ -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. +> **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 | 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). --- -_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)_ diff --git a/MCP-Servers-Plugins-Webhooks-Tools.md b/MCP-Servers-Plugins-Webhooks-Tools.md index 47b37fb..7078a19 100644 --- a/MCP-Servers-Plugins-Webhooks-Tools.md +++ b/MCP-Servers-Plugins-Webhooks-Tools.md @@ -66,4 +66,4 @@ Enable / disable Hermes toolsets per platform. - [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)_ diff --git a/Platform-Differences.md b/Platform-Differences.md index 7b531b7..f7f7ebf 100644 --- a/Platform-Differences.md +++ b/Platform-Differences.md @@ -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. | | **Skills tree** | Yes | Yes (read-only) | iOS won't grow a skill editor — too cramped. | | **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 ` 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 ` 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 — 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. | @@ -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. | | **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. | -| **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) | — | | **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. | @@ -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: - **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?" - **Localization.** Translate the strings the Mac app already has; reuse the `.strings` files. - **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)._ diff --git a/Platforms-Personalities-QuickCommands.md b/Platforms-Personalities-QuickCommands.md index 477e923..acb5895 100644 --- a/Platforms-Personalities-QuickCommands.md +++ b/Platforms-Personalities-QuickCommands.md @@ -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. --- -_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)_ diff --git a/Privacy-Policy.md b/Privacy-Policy.md index b9c6a53..b5cbff8 100644 --- a/Privacy-Policy.md +++ b/Privacy-Policy.md @@ -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. - 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 @@ -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: - **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. diff --git a/Projects-and-Profiles.md b/Projects-and-Profiles.md index d4140c1..c5662e6 100644 --- a/Projects-and-Profiles.md +++ b/Projects-and-Profiles.md @@ -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 `/.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 `/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 @@ -41,10 +43,11 @@ Profiles live under `~/.hermes/profiles/`. The currently active profile is whate ## 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. - [Memory & Skills](Memory-and-Skills) — memory is profile-scoped. - [Settings](Gateway-Cron-Health-Logs) — exposes "Backup & Restore" buttons (`hermes backup` / `hermes import`) at the profile level. --- -_Last updated: 2026-04-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)_ diff --git a/Release-Process.md b/Release-Process.md index 3432291..9746f79 100644 --- a/Release-Process.md +++ b/Release-Process.md @@ -1,6 +1,8 @@ # 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/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 @@ -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). - 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)_ diff --git a/Roadmap.md b/Roadmap.md index e20af4c..54c2472 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -11,12 +11,12 @@ What's next for Scarf. Public, opinionated, subject to change. The internal vers ## 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 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 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. -- **iPad layout pass.** `.tabViewStyle(.sidebarAdaptable)` is wired; needs verification + a target-flag flip. +- ~~**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 `NotificationRouter.apnsEnabled = true`. Skeleton + lock-screen "Approve / Deny" action category are already in place. +- **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. - **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 @@ -35,10 +35,11 @@ What's next for Scarf. Public, opinionated, subject to change. The internal vers ## What we're NOT doing -- **A web version of Scarf.** The whole point is being a native macOS app close to the metal. -- **Background sync / push notifications.** Scarf is a viewer; Hermes runs the agent. +- **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.** 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. - **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 diff --git a/ScarfGo-Onboarding.md b/ScarfGo-Onboarding.md index f65b21e..25115f4 100644 --- a/ScarfGo-Onboarding.md +++ b/ScarfGo-Onboarding.md @@ -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. - **Port** — defaults to 22. Override if your host uses a non-standard SSH port. - **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**. @@ -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`). 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" @@ -156,4 +156,4 @@ Full policy: [Privacy Policy](Privacy-Policy). - [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)_ diff --git a/ScarfGo-Roadmap.md b/ScarfGo-Roadmap.md index 3e6d982..d88d7b1 100644 --- a/ScarfGo-Roadmap.md +++ b/ScarfGo-Roadmap.md @@ -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). - **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. -- **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) @@ -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. 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 ` 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 — 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 @@ -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 | | 3 | **No connecting feedback when entering Chat.** ChatController's existing `.connecting` state now drives a `.regularMaterial` overlay with "Connecting to …" + 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 | -| 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 | | 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 | @@ -123,7 +132,7 @@ The pass-1 session also surfaced the user-facing roadmap we delivered through M8 ## 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)_ diff --git a/ScarfGo.md b/ScarfGo.md index ffaf1fc..d6eed1a 100644 --- a/ScarfGo.md +++ b/ScarfGo.md @@ -41,7 +41,7 @@ Onboarding details: | **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. | | **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 `/.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 @@ -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 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 terminal mode.** Rich-chat (ACP) only. - **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)._ diff --git a/Servers-and-Remote.md b/Servers-and-Remote.md index e9cadcb..d082688 100644 --- a/Servers-and-Remote.md +++ b/Servers-and-Remote.md @@ -1,6 +1,8 @@ # 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 @@ -54,4 +56,4 @@ See [Keyboard Shortcuts](Keyboard-Shortcuts). - [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)_ diff --git a/Sidebar-and-Navigation.md b/Sidebar-and-Navigation.md index 8dc90fc..daadd97 100644 --- a/Sidebar-and-Navigation.md +++ b/Sidebar-and-Navigation.md @@ -16,7 +16,7 @@ Each Scarf window has its **own** `AppCoordinator` — selection in one window d ## 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) @@ -27,6 +27,14 @@ Each Scarf window has its **own** `AppCoordinator` — selection in one window d | Sessions | `bubble.left.and.bubble.right` | | 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) | Section | Icon | @@ -47,19 +55,20 @@ Each Scarf window has its **own** `AppCoordinator` — selection in one window d | Webhooks | `arrow.up.right.square` | | Profiles | `person.2.crop.square.stack` | -### Manage (8) +### Manage (7) | Section | Icon | |---|---| -| Projects | `square.grid.2x2` | | Tools | `wrench.and.screwdriver` | | MCP Servers | `puzzlepiece.extension` | -| Gateway | `antenna.radiowaves.left.and.right` | +| Messaging Gateway | `antenna.radiowaves.left.and.right` | | Cron | `clock.arrow.2.circlepath` | | Health | `stethoscope` | | Logs | `doc.text` | | 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.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 List(selection: $coordinator.selectedSection) { Section("Monitor") { … } + Section("Projects") { … } Section("Interact") { … } Section("Configure") { … } Section("Manage") { … } @@ -86,9 +96,11 @@ Each window is bound to one `ServerContext` and one `AppCoordinator`. The window ## 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. --- -_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)_ diff --git a/Testing.md b/Testing.md index 1d5282b..b5a421f 100644 --- a/Testing.md +++ b/Testing.md @@ -1,52 +1,88 @@ # 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. - -This page documents what's in place and where contributions would help most. +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. ## 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. -- Protocol-oriented services for testability — the `ServerTransport` protocol is the obvious mocking seam. +- Use `@Suite` and `@Test` macros. +- 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. - 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 +ScarfCore (the main coverage): + +```bash +swift test --package-path scarf/Packages/ScarfCore +``` + +Mac target: + ```bash 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. -2. **`HermesEnvService` round-trip tests** — non-destructive .env editing has tricky comment / blank-line preservation; would benefit from regression coverage. -3. **`ACPClient` JSON-RPC framing tests** — feed canned JSON-RPC byte streams and assert events emitted. -4. **`HermesPathSet` path-resolution tests** — local vs. remote home, binary hint precedence. -5. **`HermesConfig` decoding tests** — load representative `config.yaml` fixtures and check field mapping. -6. **`SSHTransport` shell-quoting tests** — `shellQuote` and `remotePathArg` are correctness-critical and pure functions. +## Mac target test inventory ([`scarf/scarfTests/`](https://github.com/awizemann/scarf/tree/main/scarf/scarfTests)) + +| Suite | Covers | +|---|---| +| `ToolGatewayTests` | Tool Gateway routing, `HERMES_OVERLAYS` mirror, Auxiliary tab behavior. | +| `ProjectAgentContextServiceTests` | AGENTS.md `` 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 -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 remote window — same Dashboard / Sessions / Memory, but against the dogfooding host. -- Send a Rich Chat message — receives a streamed response, reasoning shows if the model emits it. -- Edit and save a memory file — change appears in Hermes on next agent turn. -- Run a Cron job — appears in the Cron view, has correct delivery channel. +- Open a local Mac window — Dashboard loads, Sessions browser populates, Memory editor opens. +- Open a remote Mac window — same Dashboard / Sessions / Memory, against the dogfooding host (`Mardon` Mac mini). +- 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). +- Send a Rich Chat message — streamed response, reasoning shows if the model emits it. +- 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. +- 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)_ diff --git a/Transport-Layer.md b/Transport-Layer.md index 3bebae0..0bbc496 100644 --- a/Transport-Layer.md +++ b/Transport-Layer.md @@ -1,10 +1,16 @@ # 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 -[`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** - `contextID: ServerID` — UUID; namespaces caches under `~/Library/Caches/scarf/snapshots//`. @@ -31,7 +37,7 @@ The `ServerTransport` protocol unifies local and SSH I/O. Services consume `tran ## 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 | |---|---| @@ -47,7 +53,7 @@ Stderr-pattern classification turns raw `ssh` errors into the right case so the ## 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 `.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. @@ -56,7 +62,7 @@ Stderr-pattern classification turns raw `ssh` errors into the right case so the ## 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 @@ -122,5 +128,32 @@ sqlite3 '/tmp/scarf-snapshot-XYZ.db' "PRAGMA journal_mode=DELETE;" 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 (`.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:`). 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)_ diff --git a/Troubleshooting-Slow-Chat-Startup.md b/Troubleshooting-Slow-Chat-Startup.md index 63318e8..b3a126e 100644 --- a/Troubleshooting-Slow-Chat-Startup.md +++ b/Troubleshooting-Slow-Chat-Startup.md @@ -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`. - [ACP Subprocess](ACP-Subprocess) — how Scarf talks to `hermes acp`. - [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_ diff --git a/Uninstalling.md b/Uninstalling.md index dbc25f2..9390a67 100644 --- a/Uninstalling.md +++ b/Uninstalling.md @@ -1,6 +1,8 @@ # 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 @@ -36,4 +38,4 @@ To uninstall Hermes itself, follow the Hermes documentation — that's a separat Just for completeness: the GitHub wiki at is a separate git repo from the main one. Uninstalling the app has no effect on the wiki. --- -_Last updated: 2026-04-20 — Scarf v2.0.2_ +_Last updated: 2026-04-25 — Scarf v2.5.0 (added ScarfGo iOS uninstall note)_ diff --git a/Updating.md b/Updating.md index 4ca145d..f6a0f55 100644 --- a/Updating.md +++ b/Updating.md @@ -1,5 +1,7 @@ # 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`. ## 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. --- -_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)_ diff --git a/Wiki-Maintenance.md b/Wiki-Maintenance.md index 5aa7152..fc1a4c2 100644 --- a/Wiki-Maintenance.md +++ b/Wiki-Maintenance.md @@ -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`. - **Hermes Paths** mirrors the Key Paths block in `CLAUDE.md` — update both when paths change. - **Release notes** stay in `releases/v/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/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. ## 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. --- -_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)_