Files
scarf/CLAUDE.md
T
Alan Wizemann c800b93804 feat: project templates v1 (install + uninstall + export + URL handler)
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>
2026-04-23 00:35:46 +02:00

8.4 KiB

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

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.

./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 and the Releases section in 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.

./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 — 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 (inspect + validate + plan), ProjectTemplateInstaller.swift (execute a plan), ProjectTemplateExporter.swift (build a bundle from a project), ProjectTemplateUninstaller.swift (reverse an install using the lock file). UI in Features/Templates/. The scarf://install?url=<https URL> deep link + file:// URLs for .scarftemplate files are handled by 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.