mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +00:00
c800b93804
Shareable `.scarftemplate` bundle format lets users package a project's dashboard, cross-agent AGENTS.md, optional per-agent instruction shims, optional namespaced skills, optional tagged cron jobs, and an optional memory appendix into a single zip that anyone can install with one click. Core: - Bundle format + manifest schema v1 (template.json with contents claim cross-checked against zip entries to prevent hidden files). - ProjectTemplateService inspects + validates + builds an install plan. - ProjectTemplateInstaller executes plans with transport-routed I/O so the v1 local-only flow extends cleanly to remote ServerContexts later. - ProjectTemplateExporter builds bundles from existing projects with user-selected skills + cron jobs. - ProjectTemplateUninstaller reverses installs using template.lock.json. Only lock-tracked files are removed; user-added files are preserved. UI: - Templates menu in Projects toolbar: Install from File, Install from URL, Export as Template. - Preview-and-confirm sheets for install, uninstall, and export with full diff of what will be written/removed before anything runs. - Right-click context menu on project list + dashboard header button for uninstall (only shown when template.lock.json exists). Deep link + file associations: - scarf:// URL scheme registered; onOpenURL in scarfApp.swift routes scarf://install?url=https://... and file:// URLs for .scarftemplate files to the install sheet. - Custom UTType com.scarf.template registered so Finder shows the file with a Scarf icon and double-click opens the install preview. - Cold-launch race fix: .task picks up any URL staged on the router before the onChange observer was installed. Safety: - Never writes to config.yaml, auth.json, sessions, or credentials. - Cron jobs ship paused with a [tmpl:<id>] name prefix. - Skills install to a namespaced ~/.hermes/skills/templates/<slug>/ dir so they never collide with user-authored skills. - Memory appendix is wrapped in scarf-template:<id>:begin/end markers for clean removal during uninstall. - Download cap: 50 MB for URL-fetched templates, enforced on the actual on-disk file size after download so chunked transfers can't bypass it. Tests: 22 tests in 7 suites cover manifest parsing, claim verification, URL routing (scarf:// + file://), end-to-end install and uninstall against a minimal bundle (projects registry is snapshotted + restored), user-added file preservation, and exporter round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
107 lines
8.4 KiB
Markdown
107 lines
8.4 KiB
Markdown
# Scarf — macOS GUI for the Hermes AI Agent
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
scarf/scarf/ Xcode project root (PBXFileSystemSynchronizedRootGroup — auto-discovers files)
|
|
scarf/ Main app target source
|
|
Core/Services/ HermesDataService, HermesFileService, HermesLogService, ACPClient, HermesFileWatcher
|
|
Core/Models/ Plain structs: HermesSession, HermesMessage, HermesConfig, etc.
|
|
Features/ MVVM-F feature modules (Dashboard, Sessions, Activity, Chat, Memory, Skills, Cron, Logs, Settings)
|
|
Navigation/ AppCoordinator, SidebarView
|
|
docs/ PRD, Architecture, Discovery notes
|
|
standards/ Copied development standards (read-only reference)
|
|
```
|
|
|
|
## Architecture Rules
|
|
|
|
- **MVVM-F**: Features never import sibling features. Cross-feature goes through services.
|
|
- **AppCoordinator**: Single `@Observable` coordinator for all navigation state, injected via `.environment()`.
|
|
- **No external dependencies**: System SQLite3, Foundation JSON, AttributedString markdown.
|
|
- **Read-only DB access**: Never write to `~/.hermes/state.db`. Only write to memory files and cron jobs.
|
|
- **Sandbox disabled**: App reads `~/.hermes/` directly.
|
|
- **Swift 6 concurrency**: `@MainActor` default. Services use `nonisolated` + async/await.
|
|
|
|
## Key Paths
|
|
|
|
- Hermes home: `~/.hermes/`
|
|
- SQLite DB: `~/.hermes/state.db` (WAL mode, read-only)
|
|
- Config: `~/.hermes/config.yaml`
|
|
- Memory: `~/.hermes/memories/MEMORY.md`, `~/.hermes/memories/USER.md`
|
|
- Sessions: `~/.hermes/sessions/session_*.json`
|
|
- Cron: `~/.hermes/cron/jobs.json`
|
|
- Logs: `~/.hermes/logs/errors.log`, `~/.hermes/logs/gateway.log`
|
|
- ACP: `hermes acp` subprocess (stdio JSON-RPC)
|
|
|
|
## Build
|
|
|
|
```bash
|
|
xcodebuild -project scarf/scarf.xcodeproj -scheme scarf -configuration Debug build
|
|
```
|
|
|
|
## Releases
|
|
|
|
Shipped via a single local script. **Never run manual `xcodebuild archive` / `notarytool` / `gh release create` steps — use the script so nothing is skipped or misordered.**
|
|
|
|
```bash
|
|
./scripts/release.sh <version> # full release: notarize → appcast → gh-pages → tag
|
|
./scripts/release.sh <version> --draft # draft: everything builds + notarizes, but appcast/tag are skipped
|
|
```
|
|
|
|
The script bumps version, archives Universal (arm64 + x86_64) + ARM64-only variants, signs with Developer ID, notarizes via `xcrun notarytool` (keychain profile `scarf-notary`), staples, EdDSA-signs the appcast entry with Sparkle's key, pushes the appcast to `gh-pages`, and creates a GitHub release with both zips attached. Draft mode stops after the release is uploaded so the current version stays "latest" until explicitly promoted.
|
|
|
|
**Release notes convention:** write them to `releases/v<version>/RELEASE_NOTES.md` BEFORE running the script — it's auto-included in the version-bump commit and used as the GitHub release body. If absent, a placeholder is used.
|
|
|
|
**Canonical prompts (any of these trigger the flow):**
|
|
- "Release v1.6.2" — full release
|
|
- "Release v1.6.2 as draft" — draft mode
|
|
- "Prepare v1.6.2 release notes from recent commits, then release" — generate notes first, then run
|
|
|
|
**Prerequisites (one-time, already set up on Alan's machine):** Developer ID Application cert in login Keychain (team `3Q6X2L86C4`), notarytool keychain profile `scarf-notary`, Sparkle EdDSA private key in Keychain item `https://sparkle-project.org`, `gh-pages` branch + GitHub Pages enabled. See the header of [scripts/release.sh](scripts/release.sh) and the Releases section in [README.md](README.md) for details.
|
|
|
|
## Wiki
|
|
|
|
Public documentation lives in the GitHub wiki at https://github.com/awizemann/scarf/wiki. The wiki is a separate git repo cloned to `.wiki-worktree/` in the repo root (gitignored, sibling to `.gh-pages-worktree/`). Internal dev notes stay in `scarf/docs/`; the wiki is for public-facing reference.
|
|
|
|
**Update the wiki when:**
|
|
- A new feature module is added under `scarf/scarf/scarf/Features/` → extend the relevant User Guide page.
|
|
- A new core service is added under `Core/Services/` → extend `Core-Services.md`.
|
|
- Architecture changes (AppCoordinator, transport, MVVM-F rule, sandbox) → `Architecture-Overview.md` + the specific sub-page.
|
|
- Hermes version bumps in this file → `Hermes-Version-Compatibility.md`.
|
|
- `scripts/release.sh` completes a full (non-draft) release → bump latest-version on `Home.md` + append to `Release-Notes-Index.md`.
|
|
- Keyboard shortcut or sidebar section changes → `Keyboard-Shortcuts.md` / `Sidebar-and-Navigation.md`.
|
|
|
|
**Skip for:** bug fixes with no user-observable change, pure refactors, typos, test-only changes, internal cleanups.
|
|
|
|
```bash
|
|
./scripts/wiki.sh pull # always first
|
|
# edit .wiki-worktree/*.md with normal tools
|
|
./scripts/wiki.sh commit "docs: describe X" # runs secret-scan
|
|
./scripts/wiki.sh push # runs secret-scan again, then push
|
|
```
|
|
|
|
**Never** commit API keys, tokens, `.env` files, private keys, or real hostnames/IPs to the wiki. The script's two-pass secret-scan blocks common token patterns and a user-maintained blocklist at `scripts/wiki-blocklist.txt` (gitignored). Do not bypass without explicit approval. Full workflow on the wiki itself at `.wiki-worktree/Wiki-Maintenance.md`.
|
|
|
|
## Hermes Version
|
|
|
|
Targets Hermes v0.9.0 (v2026.4.13). Log lines may carry an optional `[session_id]` tag between the level and logger name — `HermesLogService.parseLine` treats the session tag as an optional capture group, so older untagged lines still parse.
|
|
|
|
## Project Templates
|
|
|
|
Scarf ships a `.scarftemplate` format (v1 as of 2.2.0) for sharing pre-packaged projects across users and machines. A bundle is a zip containing:
|
|
|
|
- `template.json` — manifest (id, name, version, `contents` claim)
|
|
- `README.md` — shown in the install preview sheet
|
|
- `AGENTS.md` — required; the [Linux Foundation cross-agent instructions standard](https://agents.md/) — every template is agent-portable out of the box
|
|
- `dashboard.json` — copied to `<project>/.scarf/dashboard.json`
|
|
- `instructions/…` — optional per-agent shims (`CLAUDE.md`, `GEMINI.md`, `.cursorrules`, `.github/copilot-instructions.md`)
|
|
- `skills/<name>/…` — optional; installed to `~/.hermes/skills/templates/<slug>/` (namespaced so uninstall is `rm -rf` on one folder)
|
|
- `cron/jobs.json` — optional; registered via `hermes cron create` with a `[tmpl:<id>] …` name prefix and immediately paused
|
|
- `memory/append.md` — optional; appended to `~/.hermes/memories/MEMORY.md` between `<!-- scarf-template:<id>:begin/end -->` markers
|
|
|
|
Key services: [ProjectTemplateService.swift](scarf/scarf/Core/Services/ProjectTemplateService.swift) (inspect + validate + plan), [ProjectTemplateInstaller.swift](scarf/scarf/Core/Services/ProjectTemplateInstaller.swift) (execute a plan), [ProjectTemplateExporter.swift](scarf/scarf/Core/Services/ProjectTemplateExporter.swift) (build a bundle from a project), [ProjectTemplateUninstaller.swift](scarf/scarf/Core/Services/ProjectTemplateUninstaller.swift) (reverse an install using the lock file). UI in [Features/Templates/](scarf/scarf/Features/Templates/). The `scarf://install?url=<https URL>` deep link + `file://` URLs for `.scarftemplate` files are handled by [TemplateURLRouter.swift](scarf/scarf/Core/Services/TemplateURLRouter.swift) and `onOpenURL` in `scarfApp.swift`. A `<project>/.scarf/template.lock.json` uninstall manifest is written after every install and drives the uninstall flow.
|
|
|
|
**Uninstall semantics:** driven by the lock file. Only files listed in `lock.projectFiles` are removed from the project dir; user-added files (e.g. a `sites.txt` created on first run) are preserved. If every file in the dir was installed by the template, the dir is removed too; otherwise the dir stays with just the user's files. Skills namespace is always removed wholesale (it's isolated). Cron jobs are removed via `hermes cron remove <id>` after resolving each lock-recorded name. Memory block is stripped between the `begin`/`end` markers, leaving the rest of MEMORY.md intact. No "undo" — uninstall is destructive; to re-install, run the install flow again. Uninstall UI lives on the project-list context menu and the dashboard header (only shown when the selected project has a lock file).
|
|
|
|
**Never** let a template write to `config.yaml`, `auth.json`, sessions, or any credential path — the v1 installer refuses. If you extend the format, treat the preview sheet as load-bearing: the user's only trust boundary is that the sheet is honest about everything that's about to be written.
|