feat(site): marketing landing page for Mac + ScarfGo

Replace the gh-pages root placeholder with a real landing page that
sells both apps. Sources live at site/landing/ and publish through a
new scripts/site.sh that mirrors scripts/catalog.sh and scripts/wiki.sh
(check / build / preview / serve / publish, two-pass secret-scan, only
touches root files + assets/ on gh-pages so appcast.xml and templates/
stay disjoint).

Page is rust-palette tokens mapped from ScarfDesign, semantic HTML,
SEO + AEO infra (OpenGraph, Twitter cards, JSON-LD SoftwareApplication
+ MobileApplication + FAQPage, llms.txt, sitemap, manifest), 12-entry
FAQ, light/dark via prefers-color-scheme + manual toggle that swaps
both site chrome and screenshot variants. tools/og-image.html renders
the 1200x630 OG / 1200x600 Twitter cards via headless Chromium.

Real captures from the live Mac app (9 surfaces x light + dark) +
existing ScarfGo screenshots round out the imagery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-04-30 14:41:37 +02:00
parent cca99d4e13
commit 4140983866
41 changed files with 2203 additions and 0 deletions
+278
View File
@@ -0,0 +1,278 @@
#!/usr/bin/env bash
#
# Scarf landing-site helper — builds the marketing landing page from
# site/landing/ and (on `publish`) commits + pushes to gh-pages.
#
# Usage:
# ./scripts/site.sh check # validate that all required files exist
# ./scripts/site.sh build # render to .gh-pages-worktree/ root (with token substitution)
# ./scripts/site.sh preview [PORT] # build + serve on localhost:PORT (default 8000) + open browser
# ./scripts/site.sh serve [PORT] # serve .gh-pages-worktree/ without rebuilding (default 8000)
# ./scripts/site.sh publish # check + build + secret-scan + commit + push gh-pages (root files only)
# ./scripts/site.sh --help # this help
#
# Path discipline. This script ONLY touches root-level landing files plus the
# top-level assets/ directory on gh-pages. It NEVER touches:
# - appcast.xml (owned by scripts/release.sh)
# - templates/ (owned by scripts/catalog.sh)
# All three publishers stay on disjoint paths.
#
# Bootstrap (one-time): a .gh-pages-worktree/ clone of the gh-pages branch.
# scripts/release.sh creates it on first use. If missing:
# git worktree add .gh-pages-worktree gh-pages
#
# Token substitution. index.html and sitemap.xml.tmpl are run through a
# minimal {{TOKEN}} replacement at build time:
# {{VERSION}} — current Scarf version (read from appcast.xml on
# gh-pages, or "unreleased" if not found)
# {{LASTMOD}} — today's date in YYYY-MM-DD
# {{TEMPLATE_URLS}} — <url> entries for every template in
# templates/catalog.json (only used in sitemap.xml.tmpl)
set -euo pipefail
# ---------- config ----------
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
GHPAGES_DIR="$REPO_ROOT/.gh-pages-worktree"
SRC_DIR="$REPO_ROOT/site/landing"
PY="${PYTHON:-python3}"
# Files we OWN on gh-pages root. Anything else stays untouched.
OWNED_ROOT_FILES=(
index.html
styles.css
app.js
llms.txt
robots.txt
sitemap.xml
manifest.webmanifest
favicon.ico
apple-touch-icon.png
)
# ---------- helpers (same shape as scripts/catalog.sh / wiki.sh) ----------
log() { printf '\033[1;34m==> %s\033[0m\n' "$*"; }
warn() { printf '\033[1;33m[WARN] %s\033[0m\n' "$*" >&2; }
die() { printf '\033[1;31m[ERR] %s\033[0m\n' "$*" >&2; exit 1; }
need_src() {
[[ -d "$SRC_DIR" ]] || die "missing $SRC_DIR"
for f in index.html styles.css app.js llms.txt robots.txt sitemap.xml.tmpl manifest.webmanifest favicon.ico apple-touch-icon.png; do
[[ -e "$SRC_DIR/$f" ]] || die "missing required source file: $SRC_DIR/$f"
done
[[ -d "$SRC_DIR/assets" ]] || die "missing $SRC_DIR/assets/"
}
need_ghpages() {
[[ -e "$GHPAGES_DIR/.git" ]] || die "no gh-pages worktree at $GHPAGES_DIR
Run: git worktree add .gh-pages-worktree gh-pages"
}
# ---------- token resolvers ----------
# Pull current version from appcast.xml on gh-pages (preferred — reflects
# what's actually shipped). Fall back to "unreleased".
resolve_version() {
if [[ -f "$GHPAGES_DIR/appcast.xml" ]]; then
APPCAST="$GHPAGES_DIR/appcast.xml" "$PY" -c '
import os, re
src = open(os.environ["APPCAST"], "r", encoding="utf-8").read()
# Sparkle uses <sparkle:shortVersionString>X.Y.Z</sparkle:shortVersionString>.
# Take the first match (newest entry — appcast is reverse-chronological).
m = re.search(r"<sparkle:shortVersionString>([^<]+)</sparkle:shortVersionString>", src)
print(m.group(1) if m else "unreleased")
'
else
echo "unreleased"
fi
}
# Render <url> entries for each template in catalog.json. The catalog lives
# at templates/catalog.json on gh-pages (built by scripts/catalog.sh).
resolve_template_urls() {
local catalog="$GHPAGES_DIR/templates/catalog.json"
if [[ ! -f "$catalog" ]]; then
return 0
fi
"$PY" - <<'PY' "$catalog"
import json, sys, datetime
catalog = json.load(open(sys.argv[1], 'r', encoding='utf-8'))
today = datetime.date.today().isoformat()
out = []
for tpl in catalog.get("templates", []):
slug = tpl.get("slug") or tpl.get("id") or ""
if not slug:
continue
out.append(
f' <url>\n'
f' <loc>https://awizemann.github.io/scarf/templates/{slug}/</loc>\n'
f' <lastmod>{today}</lastmod>\n'
f' <changefreq>monthly</changefreq>\n'
f' <priority>0.6</priority>\n'
f' </url>'
)
print("\n".join(out))
PY
}
# Apply {{TOKEN}} substitution: substitute_tokens VERSION LASTMOD TEMPLATE_URLS SRC_FILE DEST_FILE
substitute_tokens() {
local version="$1"
local lastmod="$2"
local template_urls="$3"
local src_file="$4"
local dest_file="$5"
VERSION="$version" LASTMOD="$lastmod" TEMPLATE_URLS="$template_urls" \
SRC="$src_file" DEST="$dest_file" \
"$PY" -c '
import os
src_path = os.environ["SRC"]
dest_path = os.environ["DEST"]
with open(src_path, "r", encoding="utf-8") as fh:
text = fh.read()
text = text.replace("{{VERSION}}", os.environ["VERSION"])
text = text.replace("{{LASTMOD}}", os.environ["LASTMOD"])
text = text.replace("{{TEMPLATE_URLS}}", os.environ["TEMPLATE_URLS"])
with open(dest_path, "w", encoding="utf-8") as fh:
fh.write(text)
'
}
# ---------- secret-scan (mirrors scripts/wiki.sh + catalog.sh) ----------
hard_regex='(sk-[A-Za-z0-9_-]{20,}|ghp_[A-Za-z0-9]{30,}|ghs_[A-Za-z0-9]{30,}|ghu_[A-Za-z0-9]{30,}|gho_[A-Za-z0-9]{30,}|ghr_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|-----BEGIN [A-Z ]*PRIVATE KEY-----|BEGIN OPENSSH PRIVATE KEY)'
scan_hard_source() {
# Pre-build pass: scan source files (text only — image content is on the
# author to review visually). Catches accidentally-pasted credentials.
local hits
hits="$(grep -rInE --exclude-dir=.git --include='*.html' --include='*.css' --include='*.js' --include='*.txt' --include='*.xml' --include='*.json' --include='*.tmpl' --include='*.webmanifest' "$hard_regex" "$SRC_DIR" 2>/dev/null || true)"
if [[ -n "$hits" ]]; then
printf '%s\n' "$hits" >&2
die "hard-pattern secret match in source — refusing to build."
fi
}
scan_hard_rendered() {
# Post-build pass: scan the gh-pages tree we're about to publish, but
# only the files we own (so we don't false-flag on appcast.xml or
# templates/ which other scripts manage).
local hits=""
for f in "${OWNED_ROOT_FILES[@]}"; do
[[ -f "$GHPAGES_DIR/$f" ]] || continue
case "$f" in
*.png|*.ico|*.jpg|*.jpeg|*.webp) continue ;;
esac
local h
h="$(grep -InE "$hard_regex" "$GHPAGES_DIR/$f" 2>/dev/null || true)"
[[ -n "$h" ]] && hits="$hits$h"$'\n'
done
if [[ -d "$GHPAGES_DIR/assets" ]]; then
local h
h="$(grep -rInE --include='*.html' --include='*.css' --include='*.js' --include='*.txt' --include='*.xml' --include='*.json' --include='*.tmpl' "$hard_regex" "$GHPAGES_DIR/assets" 2>/dev/null || true)"
[[ -n "$h" ]] && hits="$hits$h"$'\n'
fi
if [[ -n "$hits" ]]; then
printf '%s\n' "$hits" >&2
die "hard-pattern secret match in rendered site — refusing to publish."
fi
}
# ---------- commands ----------
cmd_check() {
need_src
scan_hard_source
log "Source files OK ($(ls -1 "$SRC_DIR" | wc -l | tr -d ' ') entries; assets/: $(find "$SRC_DIR/assets" -type f | wc -l | tr -d ' ') files)"
}
cmd_build() {
need_src
need_ghpages
scan_hard_source
local version lastmod template_urls
version="$(resolve_version)"
lastmod="$(date -u +%Y-%m-%d)"
template_urls="$(resolve_template_urls)"
log "Building (version=$version, lastmod=$lastmod)"
# Static copies (no substitution needed)
for f in styles.css app.js llms.txt robots.txt manifest.webmanifest favicon.ico apple-touch-icon.png; do
cp "$SRC_DIR/$f" "$GHPAGES_DIR/$f"
done
# Token-substituted: index.html
substitute_tokens "$version" "$lastmod" "$template_urls" \
"$SRC_DIR/index.html" "$GHPAGES_DIR/index.html"
# Token-substituted: sitemap.xml (rendered from .tmpl)
substitute_tokens "$version" "$lastmod" "$template_urls" \
"$SRC_DIR/sitemap.xml.tmpl" "$GHPAGES_DIR/sitemap.xml"
# Sync assets/ — mirror the source tree
rm -rf "$GHPAGES_DIR/assets"
cp -R "$SRC_DIR/assets" "$GHPAGES_DIR/assets"
log "Built into $GHPAGES_DIR/"
}
cmd_preview() {
cmd_build
local port="${1:-8000}"
log "Built. Open http://localhost:$port/ in your browser."
log "Press Ctrl-C to stop the server."
cmd_serve "$port"
}
cmd_serve() {
need_ghpages
local port="${1:-8000}"
log "Serving $GHPAGES_DIR on http://localhost:$port/"
log "Open: http://localhost:$port/"
(cd "$GHPAGES_DIR" && "$PY" -m http.server "$port")
}
cmd_publish() {
need_src
need_ghpages
log "Validating source"
scan_hard_source
log "Building"
cmd_build
log "Secret-scanning rendered site"
scan_hard_rendered
log "Staging + committing gh-pages"
(cd "$GHPAGES_DIR" && git add "${OWNED_ROOT_FILES[@]}" assets/)
if (cd "$GHPAGES_DIR" && git diff --cached --quiet); then
log "No changes to publish."
return 0
fi
local msg
msg="site: rebuild landing page at $(date -u +%Y-%m-%dT%H:%M:%SZ)"
(cd "$GHPAGES_DIR" && git commit -m "$msg")
log "Pushing gh-pages"
(cd "$GHPAGES_DIR" && git push origin gh-pages)
log "Published."
}
cmd_help() {
sed -n '1,32p' "$0" | sed -n '/^# Usage/,/^#$/p'
}
# ---------- dispatch ----------
sub="${1:-help}"
shift || true
case "$sub" in
check) cmd_check "$@" ;;
build) cmd_build "$@" ;;
preview) cmd_preview "$@" ;;
serve) cmd_serve "$@" ;;
publish) cmd_publish "$@" ;;
help|--help|-h) cmd_help ;;
*) die "unknown command: $sub (try --help)" ;;
esac
+106
View File
@@ -0,0 +1,106 @@
// Scarf landing page — minimal client behavior.
// No dependencies. Runs after defer-parse.
(function () {
const root = document.documentElement;
const STORAGE_KEY = 'scarf-theme';
function applyTheme(theme) {
if (theme === 'light' || theme === 'dark') {
root.setAttribute('data-theme', theme);
} else {
root.removeAttribute('data-theme');
}
applyImageTheme();
}
// Resolve the *effective* theme — explicit data-theme wins, otherwise
// fall back to the OS preference.
function resolveTheme() {
const explicit = root.getAttribute('data-theme');
if (explicit === 'light' || explicit === 'dark') return explicit;
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
// Swap every <img data-dark-src="..."> between its light and dark variants.
// Also rewrites the parent <picture>'s <source srcset> so the picture
// algorithm doesn't override us on resize/layout passes.
function applyImageTheme() {
const theme = resolveTheme();
document.querySelectorAll('img[data-dark-src]').forEach((img) => {
if (!img.dataset.lightSrc) {
img.dataset.lightSrc = img.getAttribute('src');
}
const target = theme === 'dark' ? img.dataset.darkSrc : img.dataset.lightSrc;
if (img.getAttribute('src') !== target) img.setAttribute('src', target);
const picture = img.parentElement;
if (picture && picture.tagName === 'PICTURE') {
picture.querySelectorAll('source').forEach((s) => {
if (s.getAttribute('srcset') !== target) s.setAttribute('srcset', target);
});
}
});
}
// Hydrate stored preference (if any) — runs after DOMContentLoaded since
// the <script> is deferred. There's a brief moment of media-query default
// before hydrate; that's acceptable here (no FOUC because the media query
// already gets the right colors and the first images render at light by
// default — JS swaps within a frame on dark-mode systems).
let stored = null;
try {
stored = localStorage.getItem(STORAGE_KEY);
if (stored === 'light' || stored === 'dark') applyTheme(stored);
else applyImageTheme(); // initial pass even if no stored preference
} catch (_) {
applyImageTheme();
}
const toggle = document.querySelector('[data-theme-toggle]');
if (toggle) {
toggle.addEventListener('click', () => {
const current = root.getAttribute('data-theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
let next;
if (current === 'light') next = 'dark';
else if (current === 'dark') next = null;
else next = prefersDark ? 'light' : 'dark';
applyTheme(next);
try {
if (next) localStorage.setItem(STORAGE_KEY, next);
else localStorage.removeItem(STORAGE_KEY);
} catch (_) { /* ignore */ }
});
}
// Re-apply on system preference change so users who haven't set an
// explicit override still get matching screenshots.
if (window.matchMedia) {
const mql = window.matchMedia('(prefers-color-scheme: dark)');
const onChange = () => {
if (!root.hasAttribute('data-theme')) applyImageTheme();
};
if (mql.addEventListener) mql.addEventListener('change', onChange);
else if (mql.addListener) mql.addListener(onChange);
}
// Auto-collapse sticky header on scroll-down, restore on scroll-up.
const header = document.querySelector('.site-header');
if (header) {
let lastY = window.scrollY;
let ticking = false;
window.addEventListener('scroll', () => {
if (ticking) return;
window.requestAnimationFrame(() => {
const y = window.scrollY;
if (y > 80 && y > lastY) header.style.transform = 'translateY(-100%)';
else header.style.transform = '';
lastY = y;
ticking = false;
});
ticking = true;
}, { passive: true });
header.style.transition = 'transform 0.25s ease';
}
})();
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

+684
View File
@@ -0,0 +1,684 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Scarf — Native Mac &amp; iOS app for your Hermes AI agent</title>
<meta name="description" content="Scarf is the native macOS and iOS GUI for the Hermes AI agent — sessions, projects, memory, skills, cron, multi-server SSH. Free and open source.">
<link rel="canonical" href="https://awizemann.github.io/scarf/">
<!-- Open Graph -->
<meta property="og:type" content="website">
<meta property="og:site_name" content="Scarf">
<meta property="og:title" content="Scarf — Native Mac &amp; iOS app for your Hermes AI agent">
<meta property="og:description" content="Native macOS and iOS GUI for the Hermes AI agent. Sessions, projects, memory, skills, cron, multi-server SSH. Free and open source.">
<meta property="og:url" content="https://awizemann.github.io/scarf/">
<meta property="og:image" content="https://awizemann.github.io/scarf/assets/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Scarf — native Mac & iOS app for the Hermes AI agent">
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Scarf — Native Mac &amp; iOS app for your Hermes AI agent">
<meta name="twitter:description" content="Native macOS and iOS GUI for the Hermes AI agent. Sessions, projects, memory, skills, cron, multi-server SSH.">
<meta name="twitter:image" content="https://awizemann.github.io/scarf/assets/twitter-card.png">
<!-- Theme + favicons -->
<meta name="theme-color" content="#C2563D" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1A1818" media="(prefers-color-scheme: dark)">
<link rel="icon" href="favicon.ico" sizes="any">
<link rel="icon" href="assets/icon-192.png" type="image/png" sizes="192x192">
<link rel="icon" href="assets/icon-512.png" type="image/png" sizes="512x512">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<link rel="manifest" href="manifest.webmanifest">
<!-- AI crawler hint -->
<link rel="alternate" type="text/plain" title="LLM-friendly summary" href="llms.txt">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="site-header" role="banner">
<a class="brand" href="./" aria-label="Scarf home">
<img src="assets/scarf-icon-512.png" width="32" height="32" alt="" decoding="async">
<span class="brand-name">Scarf</span>
</a>
<nav class="site-nav" aria-label="Primary">
<a href="#features">Features</a>
<a href="#ios">iPhone</a>
<a href="templates/">Templates</a>
<a href="#download">Download</a>
<a href="https://github.com/awizemann/scarf" rel="noopener">GitHub</a>
</nav>
<button type="button" class="theme-toggle" aria-label="Toggle dark mode" data-theme-toggle>
<svg class="theme-icon icon-sun" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path></svg>
<svg class="theme-icon icon-moon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
</button>
</header>
<main id="main">
<!-- Hero -->
<section class="hero" id="hero" aria-labelledby="hero-title">
<div class="hero-copy">
<h1 id="hero-title">Native Mac &amp; iOS app<br>for your Hermes AI agent.</h1>
<p class="hero-lede">
Scarf is a native macOS and iOS GUI for the
<a href="https://github.com/hermes-ai/hermes-agent" rel="noopener">Hermes AI agent</a>.
See every session, project, skill, memory file, and cron job — locally on your Mac
and remotely from your iPhone over SSH.
</p>
<div class="cta-row">
<a class="btn btn-primary" href="https://github.com/awizemann/scarf/releases/latest" rel="noopener">
<span>Download for Mac</span>
<span class="btn-meta">macOS 14.6+ · Apple Silicon &amp; Intel</span>
</a>
<a class="btn btn-secondary" href="https://testflight.apple.com/join/qCrRpcTz" rel="noopener">
<span>Get on iPhone</span>
<span class="btn-meta">TestFlight · iOS 18+</span>
</a>
</div>
<p class="hero-prereq">
Requires <a href="https://github.com/hermes-ai/hermes-agent" rel="noopener">Hermes</a> installed at <code>~/.hermes/</code>.
</p>
</div>
<div class="hero-visual" aria-hidden="false">
<picture class="hero-mac">
<source media="(prefers-color-scheme: dark)" srcset="assets/screenshots/mac-hero-dark.png">
<img data-dark-src="assets/screenshots/mac-hero-dark.png" src="assets/screenshots/mac-hero.png"
alt="Scarf on macOS — chat view streaming a response with a tool-call card"
width="1600" height="1000"
fetchpriority="high" decoding="async">
</picture>
<picture class="hero-iphone">
<img src="assets/screenshots/ios-chat.png"
alt="ScarfGo on iPhone — chat with the Hermes agent over SSH"
width="430" height="932"
decoding="async">
</picture>
</div>
</section>
<!-- Trust strip -->
<aside class="trust-strip" aria-label="At a glance">
<ul>
<li>Native Swift 6</li>
<li>No Electron</li>
<li>MIT licensed</li>
<li>Sparkle auto-updates</li>
<li>macOS 14.6+ &nbsp;·&nbsp; iOS 18+</li>
</ul>
</aside>
<!-- What Scarf does (AEO load-bearing paragraph) -->
<section class="what" id="what" aria-labelledby="what-title">
<h2 id="what-title">What Scarf does</h2>
<p>
Scarf is a native macOS and iOS GUI for the Hermes AI agent. It surfaces every part of a
running Hermes installation — chat sessions, project workspaces, memory files, installed
skills, MCP servers, cron jobs, messaging gateways, logs, and configuration — through a
sidebar-driven app on Mac and a tab-based companion on iPhone. Scarf reads from
<code>~/.hermes/state.db</code> directly (read-only), streams agent replies in real time
over the Agent Client Protocol, and connects to remote Hermes installations through your
existing SSH config. There is no telemetry, no login, and no separate cloud service.
</p>
</section>
<!-- Mac feature blocks -->
<section class="features" id="features" aria-labelledby="features-title">
<h2 id="features-title" class="section-heading">Built for the way Hermes actually works</h2>
<article class="feature feature-flip">
<div class="feature-text">
<h3>Live agent sessions</h3>
<p>
Real-time streaming chat over the Agent Client Protocol. Tool calls render inline with
collapsible argument and output panes. Tool-permission requests surface as interactive
dialogs you can approve, deny, or stage. Reasoning and thinking blocks display when the
model emits them. Resume any prior session from the sidebar, or jump back into a
conversation that disconnected — Scarf reconnects to the same session id.
</p>
</div>
<div class="feature-visual">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/screenshots/mac-chat-dark.png">
<img data-dark-src="assets/screenshots/mac-chat-dark.png" src="assets/screenshots/mac-chat.png"
alt="Mac app — chat view with a tool-call card and a streaming response"
width="1600" height="1000" loading="lazy" decoding="async">
</picture>
</div>
</article>
<article class="feature">
<div class="feature-text">
<h3>Project workspaces</h3>
<p>
Per-project chat with the agent's working directory pinned, custom dashboards rendered
from a JSON spec the agent itself can author, project-scoped slash commands defined as
Markdown files, and a Scarf-managed <code>AGENTS.md</code> block that gives Hermes
project context before every session boots. Templates package these into a single
<code>.scarftemplate</code> bundle you can share or install with one click.
</p>
</div>
<div class="feature-visual">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/screenshots/mac-projects-dark.png">
<img data-dark-src="assets/screenshots/mac-projects-dark.png" src="assets/screenshots/mac-projects.png"
alt="Mac app — project dashboard with stat boxes and a chart widget"
width="1600" height="1000" loading="lazy" decoding="async">
</picture>
</div>
</article>
<article class="feature feature-flip">
<div class="feature-text">
<h3>Multi-server over SSH</h3>
<p>
One window per Hermes server. Local <code>~/.hermes/</code> is synthesized
automatically; remote servers connect through your existing
<code>~/.ssh/config</code>, ssh-agent, ProxyJump, and ControlMaster. File I/O routes
through scp/sftp; the SQLite database is served from atomic snapshots; chat tunnels as
<code>ssh -T host hermes acp</code> with JSON-RPC end-to-end. There is no companion
service in the middle.
</p>
</div>
<div class="feature-visual">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/screenshots/mac-sessions-dark.png">
<img data-dark-src="assets/screenshots/mac-sessions-dark.png" src="assets/screenshots/mac-sessions.png"
alt="Mac app — sessions browser filtered to a remote server"
width="1600" height="1000" loading="lazy" decoding="async">
</picture>
</div>
</article>
<article class="feature">
<div class="feature-text">
<h3>Memory, skills, MCP</h3>
<p>
Edit <code>MEMORY.md</code> and <code>USER.md</code> with live file-watcher refresh and
external-provider awareness. Browse the Skills Hub across six registries, install,
update, and inspect <code>SKILL.md</code> frontmatter. Configure MCP servers from a
curated preset list (GitHub, Linear, Notion, Sentry, Stripe) or fully custom; test the
connection in-app and watch the discovered tool list populate.
</p>
</div>
<div class="feature-visual">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/screenshots/mac-mcp-dark.png">
<img data-dark-src="assets/screenshots/mac-mcp-dark.png" src="assets/screenshots/mac-mcp.png"
alt="Mac app — MCP servers configuration view"
width="1600" height="1000" loading="lazy" decoding="async">
</picture>
</div>
</article>
<article class="feature feature-flip">
<div class="feature-text">
<h3>Cron &amp; messaging gateways</h3>
<p>
Create, edit, pause, resume, run-now, and delete cron jobs without touching the CLI —
with human-readable schedules ("Every weekday at 9:00 AM") and the underlying
expression a tap away. Native GUI for thirteen messaging platforms (Telegram, Discord,
Slack, WhatsApp, Signal, iMessage, Email, Matrix, Mattermost, Feishu, Home Assistant,
Webhook, CLI) with per-platform credential forms and connectivity dots.
</p>
</div>
<div class="feature-visual">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/screenshots/mac-cron-dark.png">
<img data-dark-src="assets/screenshots/mac-cron-dark.png" src="assets/screenshots/mac-cron.png"
alt="Mac app — cron manager with paused and active jobs"
width="1600" height="1000" loading="lazy" decoding="async">
</picture>
</div>
</article>
<article class="feature">
<div class="feature-text">
<h3>Insights &amp; health</h3>
<p>
Token usage and cost broken down by day, model, and platform; reasoning tokens
tracked separately. Activity heatmaps and notable sessions across 7, 30, 90 days, or
all time. Component-level health checks, on-demand diagnostics, and a one-click debug
report uploader for Hermes support — with a confirmation dialog before anything
leaves the machine.
</p>
</div>
<div class="feature-visual">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/screenshots/mac-dashboard-dark.png">
<img data-dark-src="assets/screenshots/mac-dashboard-dark.png" src="assets/screenshots/mac-dashboard.png"
alt="Mac app — dashboard with token usage, recent sessions, and health"
width="1600" height="1000" loading="lazy" decoding="async">
</picture>
</div>
</article>
</section>
<!-- iOS / ScarfGo -->
<section class="ios" id="ios" aria-labelledby="ios-title">
<div class="ios-copy">
<p class="eyebrow">ScarfGo for iPhone</p>
<h2 id="ios-title">Your Hermes agent, on your phone.</h2>
<p>
ScarfGo is the native iOS companion to Scarf. It speaks SSH directly to your Hermes
host using <a href="https://github.com/orlandos-nl/Citadel" rel="noopener">Citadel</a> — a
pure-Swift SSH stack, no companion service, no developer-controlled relay. Generate an
Ed25519 keypair on the device, paste the public half into <code>authorized_keys</code>,
and you have full project-aware chat, session resume, memory editing, cron browsing,
and skill management on iOS 18+.
</p>
<ul class="ios-points">
<li>Pure-Swift SSH — keys live in the iOS Keychain and never leave the device.</li>
<li>Multi-server: connect to as many Hermes hosts as you have SSH access to.</li>
<li>Project-scoped chat writes the same Scarf-managed <code>AGENTS.md</code> block as Mac.</li>
<li>Same session attribution, so "which project does this conversation belong to?" matches across devices.</li>
</ul>
<a class="btn btn-primary" href="https://testflight.apple.com/join/qCrRpcTz" rel="noopener">
<span>Join the public TestFlight</span>
<span class="btn-meta">iOS 18+</span>
</a>
</div>
<div class="ios-gallery" role="list" aria-label="ScarfGo screenshots">
<div class="phone-frame" role="listitem">
<img src="assets/screenshots/ios-servers.png" alt="ScarfGo — server picker with two configured Hermes hosts" loading="lazy" decoding="async" width="430" height="932">
</div>
<div class="phone-frame" role="listitem">
<img src="assets/screenshots/ios-chat.png" alt="ScarfGo — chat with the Hermes agent" loading="lazy" decoding="async" width="430" height="932">
</div>
<div class="phone-frame" role="listitem">
<img src="assets/screenshots/ios-project-dashboard.png" alt="ScarfGo — project dashboard" loading="lazy" decoding="async" width="430" height="932">
</div>
<div class="phone-frame" role="listitem">
<img src="assets/screenshots/ios-skills.png" alt="ScarfGo — skills browser" loading="lazy" decoding="async" width="430" height="932">
</div>
<div class="phone-frame" role="listitem">
<img src="assets/screenshots/ios-system.png" alt="ScarfGo — system tab" loading="lazy" decoding="async" width="430" height="932">
</div>
</div>
</section>
<!-- Why native -->
<section class="why" id="why" aria-labelledby="why-title">
<h2 id="why-title" class="section-heading">Why a native app</h2>
<div class="why-grid">
<article class="why-card">
<h3>Native, not Electron</h3>
<p>
One Mach-O binary. SwiftUI on macOS 14.6+ and iOS 18+. Kilobytes of memory and
negligible energy use compared to a bundled Chromium. Drag-and-drop, sharing, the
menu bar, Spotlight, and accessibility all behave the way a Mac or iPhone user
expects.
</p>
</article>
<article class="why-card">
<h3>Read-only safe</h3>
<p>
Scarf opens <code>~/.hermes/state.db</code> in read-only WAL mode. The app cannot
corrupt your Hermes data even if it crashes mid-write — because it never writes.
Memory files and cron jobs are the only mutable surfaces, both with explicit
confirmations.
</p>
</article>
<article class="why-card">
<h3>Open and inspectable</h3>
<p>
<a href="https://github.com/awizemann/scarf" rel="noopener">MIT licensed</a>, pure
Swift, zero external runtime dependencies. Build it yourself with
<code>xcodebuild</code> in two minutes. Sparkle auto-updates ship signed,
notarized, EdDSA-verified zips — never silent over-the-wire mutations.
</p>
</article>
</div>
</section>
<!-- Templates teaser -->
<section class="templates" id="templates" aria-labelledby="templates-title">
<h2 id="templates-title">Project templates</h2>
<p>
A <code>.scarftemplate</code> bundle packages a project's dashboard, skills, cron jobs,
memory blocks, slash commands, and configuration schema into one shareable file.
Browse the public catalog and install with a single click.
</p>
<a class="btn btn-secondary" href="templates/">Browse the template catalog →</a>
</section>
<!-- Download -->
<section class="download" id="download" aria-labelledby="download-title">
<h2 id="download-title" class="section-heading">Download</h2>
<div class="download-grid">
<article class="download-card">
<h3>Scarf for Mac</h3>
<p class="download-meta">macOS 14.6 (Sonoma) or later · Apple Silicon &amp; Intel · Universal binary</p>
<ul class="download-points">
<li>Notarized, code-signed Developer ID build</li>
<li>Sparkle auto-updates with EdDSA signature verification</li>
<li>Free and open source under the MIT license</li>
</ul>
<a class="btn btn-primary" href="https://github.com/awizemann/scarf/releases/latest" rel="noopener">
<span>Get the latest release</span>
<span class="btn-meta">GitHub Releases · .zip</span>
</a>
</article>
<article class="download-card">
<h3>ScarfGo for iPhone</h3>
<p class="download-meta">iOS 18.0 or later · iPhone · public TestFlight</p>
<ul class="download-points">
<li>Pure-Swift SSH — no companion service required</li>
<li>Multi-server support, identical to Mac</li>
<li>Free and open source under the MIT license</li>
</ul>
<a class="btn btn-primary" href="https://testflight.apple.com/join/qCrRpcTz" rel="noopener">
<span>Join the TestFlight</span>
<span class="btn-meta">Apple TestFlight</span>
</a>
</article>
</div>
<p class="download-prereq">
Both apps require <a href="https://github.com/hermes-ai/hermes-agent" rel="noopener">Hermes</a>
installed at <code>~/.hermes/</code> on each host you want to manage. Scarf does not include
Hermes itself — see the
<a href="https://github.com/hermes-ai/hermes-agent#installation" rel="noopener">Hermes installation
guide</a> first.
</p>
</section>
<!-- FAQ -->
<section class="faq" id="faq" aria-labelledby="faq-title">
<h2 id="faq-title" class="section-heading">Frequently asked questions</h2>
<div class="faq-list">
<details>
<summary>What is Scarf?</summary>
<div>
<p>Scarf is a native macOS and iOS GUI for the <a href="https://github.com/hermes-ai/hermes-agent" rel="noopener">Hermes AI agent</a>. It surfaces Hermes's sessions, projects, memory, skills, MCP servers, cron jobs, messaging gateways, logs, and configuration through a sidebar-driven Mac app and a tab-based iPhone companion called ScarfGo.</p>
</div>
</details>
<details>
<summary>Do I need Hermes installed first?</summary>
<div>
<p>Yes. Scarf is a client for an existing Hermes installation. It expects to find Hermes's data directory at <code>~/.hermes/</code> on each host you connect to (local or remote). Install Hermes first by following the <a href="https://github.com/hermes-ai/hermes-agent#installation" rel="noopener">Hermes installation guide</a>.</p>
</div>
</details>
<details>
<summary>Does Scarf work without internet?</summary>
<div>
<p>Yes for the local Hermes case — Scarf reads files and the SQLite database directly from <code>~/.hermes/</code> with no network involvement. Internet is only required when Hermes itself reaches out to model providers or MCP servers, when you connect to a remote Hermes host over SSH, or when checking for Sparkle updates.</p>
</div>
</details>
<details>
<summary>Is Scarf open source?</summary>
<div>
<p>Yes. Both Scarf and ScarfGo are <a href="https://github.com/awizemann/scarf/blob/main/LICENSE" rel="noopener">MIT licensed</a> and built from the same open repository at <a href="https://github.com/awizemann/scarf" rel="noopener">github.com/awizemann/scarf</a>. There are no closed-source components and no telemetry.</p>
</div>
</details>
<details>
<summary>What macOS and iOS versions are supported?</summary>
<div>
<p>Scarf for Mac requires macOS 14.6 Sonoma or later, on Apple Silicon or Intel. ScarfGo for iPhone requires iOS 18.0 or later. Both are universal builds; there is no separate Apple Silicon download.</p>
</div>
</details>
<details>
<summary>How does ScarfGo connect to my Mac?</summary>
<div>
<p>ScarfGo speaks SSH directly to your Hermes host using a pure-Swift SSH stack (Citadel). On first launch it generates an Ed25519 keypair on the device — the private key lives in the iOS Keychain (with <code>kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly</code>) and is excluded from iCloud sync. Paste the public key into the host's <code>~/.ssh/authorized_keys</code>, and ScarfGo can run <code>hermes acp</code> over the SSH session for chat and read the SQLite database for everything else. There is no companion service or developer-controlled relay.</p>
</div>
</details>
<details>
<summary>What data does Scarf collect?</summary>
<div>
<p>None. Scarf has no telemetry, no analytics, no crash reporter, and no account system. The only outbound network connections are to GitHub Releases (when you check for updates via Sparkle), to remote Hermes hosts you explicitly add, and to Hermes's own model providers and MCP servers — all initiated by Hermes, not Scarf.</p>
</div>
</details>
<details>
<summary>Where are my conversations stored?</summary>
<div>
<p>In Hermes's own data directory — <code>~/.hermes/state.db</code> for session history and <code>~/.hermes/sessions/session_*.json</code> for full transcripts. Scarf reads these files but never writes to <code>state.db</code>. ScarfGo reads them through SSH-initiated SQLite snapshots and never caches them locally on the device.</p>
</div>
</details>
<details>
<summary>How do updates work?</summary>
<div>
<p>Scarf for Mac uses <a href="https://sparkle-project.org/" rel="noopener">Sparkle</a> for in-app updates — signed and notarized zips with EdDSA signature verification. The appcast lives at <a href="appcast.xml">awizemann.github.io/scarf/appcast.xml</a>. ScarfGo updates through TestFlight in the usual way until it ships on the App Store.</p>
</div>
</details>
<details>
<summary>Can I use Scarf with a remote or headless Hermes server?</summary>
<div>
<p>Yes — that is one of the main use cases. Add the host through <strong>File → Open Server… → Add Server</strong> on Mac, or tap <strong>Add Server</strong> on the ScarfGo Servers tab. Scarf uses the system SSH config (Mac) or a device-generated key (iOS), so anything reachable through your normal terminal SSH workflow works without extra setup. The remote host needs <code>sqlite3</code> and <code>pgrep</code> on its <code>$PATH</code> and the SSH user needs read access to <code>~/.hermes/</code>.</p>
</div>
</details>
<details>
<summary>What's the difference between Scarf and using Hermes from the terminal?</summary>
<div>
<p>Scarf is strictly additive — it visualizes data Hermes already produces. The terminal CLI (<code>hermes chat</code>, <code>hermes cron</code>, <code>hermes mcp</code>, etc.) remains the source of truth for everything. Scarf gives you live streaming chat with rich tool-call rendering, multi-server windows, project workspaces with custom dashboards, and a one-pane view of skills, MCP servers, cron jobs, and gateways without memorizing CLI subcommands.</p>
</div>
</details>
<details>
<summary>Is there a Windows or Linux version?</summary>
<div>
<p>No. Scarf is built on SwiftUI and AppKit and ships only for Apple platforms. There are no current plans for Windows or Linux ports — the
<a href="https://github.com/hermes-ai/hermes-agent" rel="noopener">Hermes CLI</a> itself works on those platforms, and Scarf can connect to a remote Linux Hermes host from a Mac.</p>
</div>
</details>
</div>
</section>
</main>
<footer class="site-footer" role="contentinfo">
<div class="footer-inner">
<div class="footer-brand">
<img src="assets/scarf-icon-512.png" width="40" height="40" alt="" decoding="async">
<p>Scarf is made by <a href="https://github.com/awizemann" rel="noopener">Alan Wizemann</a>. MIT licensed.</p>
</div>
<nav class="footer-nav" aria-label="Footer">
<div>
<h4>Project</h4>
<ul>
<li><a href="https://github.com/awizemann/scarf" rel="noopener">GitHub</a></li>
<li><a href="https://github.com/awizemann/scarf/wiki" rel="noopener">Wiki</a></li>
<li><a href="https://github.com/awizemann/scarf/releases" rel="noopener">Releases</a></li>
<li><a href="https://github.com/awizemann/scarf/blob/main/LICENSE" rel="noopener">License (MIT)</a></li>
</ul>
</div>
<div>
<h4>Community</h4>
<ul>
<li><a href="templates/">Template catalog</a></li>
<li><a href="https://github.com/awizemann/scarf/discussions" rel="noopener">Discussions</a></li>
<li><a href="https://github.com/awizemann/scarf/issues" rel="noopener">Report a bug</a></li>
<li><a href="https://www.buymeacoffee.com/awizemann" rel="noopener">Buy me a coffee</a></li>
</ul>
</div>
<div>
<h4>Technical</h4>
<ul>
<li><a href="appcast.xml">Sparkle appcast</a></li>
<li><a href="llms.txt">llms.txt</a></li>
<li><a href="https://github.com/hermes-ai/hermes-agent" rel="noopener">Hermes agent</a></li>
<li><a href="https://testflight.apple.com/join/qCrRpcTz" rel="noopener">TestFlight (iOS)</a></li>
</ul>
</div>
</nav>
</div>
</footer>
<!-- Structured data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "SoftwareApplication",
"name": "Scarf",
"alternateName": "Scarf for Mac",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "macOS 14.6 or later",
"description": "Native macOS GUI for the Hermes AI agent. Sessions, projects, memory, skills, MCP servers, cron jobs, messaging gateways, multi-server SSH.",
"url": "https://awizemann.github.io/scarf/",
"downloadUrl": "https://github.com/awizemann/scarf/releases/latest",
"softwareVersion": "{{VERSION}}",
"license": "https://opensource.org/licenses/MIT",
"image": "https://awizemann.github.io/scarf/assets/og-image.png",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"author": {
"@type": "Person",
"name": "Alan Wizemann",
"url": "https://github.com/awizemann"
}
},
{
"@type": "MobileApplication",
"name": "ScarfGo",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "iOS 18.0 or later",
"description": "Native iPhone companion to Scarf. Multi-server Hermes management over SSH, project-aware chat, memory editor, cron browser, skills browser.",
"url": "https://awizemann.github.io/scarf/#ios",
"downloadUrl": "https://testflight.apple.com/join/qCrRpcTz",
"license": "https://opensource.org/licenses/MIT",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"author": {
"@type": "Person",
"name": "Alan Wizemann",
"url": "https://github.com/awizemann"
}
},
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is Scarf?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Scarf is a native macOS and iOS GUI for the Hermes AI agent. It surfaces Hermes's sessions, projects, memory, skills, MCP servers, cron jobs, messaging gateways, logs, and configuration through a sidebar-driven Mac app and a tab-based iPhone companion called ScarfGo."
}
},
{
"@type": "Question",
"name": "Do I need Hermes installed first?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Scarf is a client for an existing Hermes installation. It expects to find Hermes's data directory at ~/.hermes/ on each host you connect to. Install Hermes first by following the Hermes installation guide."
}
},
{
"@type": "Question",
"name": "Does Scarf work without internet?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes for the local Hermes case — Scarf reads files and the SQLite database directly from ~/.hermes/ with no network involvement. Internet is only required when Hermes itself reaches out to model providers or MCP servers, when connecting to a remote Hermes host over SSH, or when checking for Sparkle updates."
}
},
{
"@type": "Question",
"name": "Is Scarf open source?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Both Scarf and ScarfGo are MIT licensed and built from the same open repository at github.com/awizemann/scarf. There are no closed-source components and no telemetry."
}
},
{
"@type": "Question",
"name": "What macOS and iOS versions are supported?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Scarf for Mac requires macOS 14.6 Sonoma or later, on Apple Silicon or Intel. ScarfGo for iPhone requires iOS 18.0 or later. Both are universal builds."
}
},
{
"@type": "Question",
"name": "How does ScarfGo connect to my Mac?",
"acceptedAnswer": {
"@type": "Answer",
"text": "ScarfGo speaks SSH directly to your Hermes host using Citadel, a pure-Swift SSH stack. On first launch it generates an Ed25519 keypair on the device — the private key lives in the iOS Keychain and never leaves the phone. Paste the public key into the host's authorized_keys and ScarfGo can run hermes acp over the SSH session for chat and read the SQLite database for everything else. There is no companion service or relay."
}
},
{
"@type": "Question",
"name": "What data does Scarf collect?",
"acceptedAnswer": {
"@type": "Answer",
"text": "None. Scarf has no telemetry, no analytics, no crash reporter, and no account system. The only outbound network connections are to GitHub Releases (Sparkle update checks), remote Hermes hosts you explicitly add, and Hermes's own model providers and MCP servers — all initiated by Hermes, not Scarf."
}
},
{
"@type": "Question",
"name": "Where are my conversations stored?",
"acceptedAnswer": {
"@type": "Answer",
"text": "In Hermes's own data directory — ~/.hermes/state.db for session history and ~/.hermes/sessions/session_*.json for full transcripts. Scarf reads these files but never writes to state.db. ScarfGo reads them through SSH-initiated SQLite snapshots and never caches them locally on the device."
}
},
{
"@type": "Question",
"name": "How do updates work?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Scarf for Mac uses Sparkle for in-app updates — signed and notarized zips with EdDSA signature verification. The appcast lives at awizemann.github.io/scarf/appcast.xml. ScarfGo updates through TestFlight in the usual way until it ships on the App Store."
}
},
{
"@type": "Question",
"name": "Can I use Scarf with a remote or headless Hermes server?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Add the host through File → Open Server… → Add Server on Mac, or Add Server on ScarfGo. Scarf uses the system SSH config (Mac) or a device-generated key (iOS), so anything reachable through normal terminal SSH works without extra setup. The remote host needs sqlite3 and pgrep on its PATH and the SSH user needs read access to ~/.hermes/."
}
},
{
"@type": "Question",
"name": "What's the difference between Scarf and using Hermes from the terminal?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Scarf is strictly additive — it visualizes data Hermes already produces. The terminal CLI remains the source of truth. Scarf gives you live streaming chat with rich tool-call rendering, multi-server windows, project workspaces with custom dashboards, and a one-pane view of skills, MCP servers, cron jobs, and gateways without memorizing CLI subcommands."
}
},
{
"@type": "Question",
"name": "Is there a Windows or Linux version?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. Scarf is built on SwiftUI and AppKit and ships only for Apple platforms. The Hermes CLI itself works on those platforms, and Scarf can connect to a remote Linux Hermes host from a Mac."
}
}
]
}
]
}
</script>
<script src="app.js" defer></script>
</body>
</html>
+65
View File
@@ -0,0 +1,65 @@
# Scarf
> Scarf is a native macOS and iOS GUI for the Hermes AI agent. It surfaces every part of a running Hermes installation — chat sessions, project workspaces, memory files, installed skills, MCP servers, cron jobs, messaging gateways, logs, and configuration — through a sidebar-driven Mac app and a tab-based iPhone companion called ScarfGo.
Both apps are free, MIT licensed, and built from a single open repository. Scarf reads from `~/.hermes/state.db` directly (read-only) and streams agent replies in real time over the Agent Client Protocol. It connects to remote Hermes installations using the host's existing SSH config — no companion service, no telemetry, no account.
## Quick facts
- **Platforms:** macOS 14.6+ Sonoma (Apple Silicon and Intel), iOS 18+
- **License:** MIT
- **Repository:** https://github.com/awizemann/scarf
- **Author:** Alan Wizemann
- **Hermes prerequisite:** https://github.com/hermes-ai/hermes-agent installed at `~/.hermes/`
- **Mac download:** https://github.com/awizemann/scarf/releases/latest
- **iOS download:** https://testflight.apple.com/join/qCrRpcTz (public TestFlight)
- **Auto-updates (Mac):** Sparkle, with EdDSA signature verification
- **Telemetry:** none
## Documentation
- [README](https://github.com/awizemann/scarf/blob/main/README.md): project overview, full feature list, build instructions
- [Wiki](https://github.com/awizemann/scarf/wiki): user guide, architecture, design system reference
- [Wiki — ScarfGo](https://github.com/awizemann/scarf/wiki/ScarfGo): iOS companion details
- [Wiki — ScarfGo Onboarding](https://github.com/awizemann/scarf/wiki/ScarfGo-Onboarding): SSH key setup walkthrough
- [Wiki — Platform Differences](https://github.com/awizemann/scarf/wiki/Platform-Differences): what is and isn't shared between Mac and iOS
- [Releases](https://github.com/awizemann/scarf/releases): release notes for every version
- [License](https://github.com/awizemann/scarf/blob/main/LICENSE): MIT
## Feature surfaces
Mac (sidebar sections):
- **Monitor:** Dashboard, Insights, Sessions Browser, Activity Feed
- **Interact:** Live Chat (ACP streaming + Terminal mode), Memory Viewer/Editor, Skills Browser
- **Configure:** Platforms, Personalities, Quick Commands, Credential Pools, Plugins, Webhooks, Profiles
- **Manage:** Tools, MCP Servers, Gateway Control, Cron Manager, Health, Log Viewer, Settings
- **Project Dashboards:** custom JSON-defined dashboards rendered by Scarf, populated by the agent
- **System:** Hermes process control, menu bar status
iOS (ScarfGo, tabs):
- Servers (multi-host management with pure-Swift SSH)
- Dashboard (stats + recent sessions per server)
- Chat (full ACP, project-scoped)
- Sessions (resume, attribute to projects)
- Memory editor (read/write `MEMORY.md`, `USER.md`)
- Cron (list view, human-readable schedules)
- Skills browser (categories + prereq banners)
- Settings (read-only `config.yaml`)
## Differentiators
- Native SwiftUI, not Electron — single Mach-O binary, kilobytes of memory, full system integration
- Read-only access to `state.db` — Scarf cannot corrupt Hermes data because it never writes
- Multi-server: one window per Hermes host on Mac, multi-server on iOS, all over standard SSH
- Project-scoped chat with Scarf-managed `AGENTS.md` block injected before session boot
- Portable `.scarftemplate` bundles for sharing project setups (dashboards, skills, cron jobs, slash commands)
- Live ACP streaming with rich tool-call rendering, permission dialogs, voice control
- 13 messaging platforms managed in one native UI (Telegram, Discord, Slack, WhatsApp, Signal, iMessage, Email, Matrix, Mattermost, Feishu, Home Assistant, Webhook, CLI)
- Open and inspectable — pure Swift, MIT, no external runtime dependencies
## Optional
- [Templates Catalog](https://awizemann.github.io/scarf/templates/): community-contributed `.scarftemplate` bundles, one-click install
- [Sparkle Appcast](https://awizemann.github.io/scarf/appcast.xml): the auto-update feed (RSS/XML)
+24
View File
@@ -0,0 +1,24 @@
{
"name": "Scarf",
"short_name": "Scarf",
"description": "Native macOS and iOS GUI for the Hermes AI agent.",
"start_url": "./",
"scope": "./",
"display": "browser",
"background_color": "#FAF7F2",
"theme_color": "#C2563D",
"icons": [
{
"src": "assets/icon-192.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "any"
},
{
"src": "assets/icon-512.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "any"
}
]
}
+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://awizemann.github.io/scarf/sitemap.xml
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://awizemann.github.io/scarf/</loc>
<lastmod>{{LASTMOD}}</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://awizemann.github.io/scarf/templates/</loc>
<lastmod>{{LASTMOD}}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
{{TEMPLATE_URLS}}
</urlset>
+859
View File
@@ -0,0 +1,859 @@
/* Scarf landing page.
* Vanilla CSS, no framework. Tokens map to ScarfDesign (rust palette).
*
* Layers:
* 1. Tokens (CSS custom properties)
* 2. Reset + base
* 3. Layout primitives
* 4. Header / footer
* 5. Sections (hero, trust strip, what, features, ios, why, templates, download, faq)
* 6. Components (buttons, cards, phone frame)
* 7. Responsive
*/
/* ---------- 1. Tokens ---------- */
:root {
/* Colors — light mode (mirrors ScarfBrand.xcassets light variants) */
--accent: #C2563D;
--accent-hover: #A8482F;
--accent-active: #8E3B26;
--accent-tint: rgba(194, 86, 61, 0.12);
--fg: #1A1818;
--fg-muted: #5C5552;
--fg-faint: #8B8480;
--bg: #FAF7F2;
--bg-card: #FFFFFF;
--bg-tertiary: #F2EDE5;
--border: #E5DED2;
--border-strong: #C9BFAE;
--success: #4F8B5F;
--danger: #B5453A;
--warning: #C9821C;
--info: #4A6F8E;
/* Spacing — ScarfSpace s1..s10 */
--s1: 4px;
--s2: 8px;
--s3: 12px;
--s4: 16px;
--s5: 20px;
--s6: 24px;
--s7: 32px;
--s8: 40px;
--s9: 56px;
--s10: 80px;
/* Radius — ScarfRadius */
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
--r-xl: 20px;
--r-xxl: 28px;
--r-pill: 999px;
/* Typography */
--sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Segoe UI", Roboto, sans-serif;
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "JetBrains Mono", monospace;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(26, 24, 24, 0.04), 0 2px 6px rgba(26, 24, 24, 0.04);
--shadow-md: 0 2px 4px rgba(26, 24, 24, 0.06), 0 8px 20px rgba(26, 24, 24, 0.06);
--shadow-lg: 0 4px 8px rgba(26, 24, 24, 0.08), 0 16px 40px rgba(26, 24, 24, 0.10);
--shadow-xl: 0 8px 16px rgba(26, 24, 24, 0.10), 0 32px 80px rgba(26, 24, 24, 0.14);
/* Layout */
--max-w: 1180px;
--max-w-prose: 720px;
}
@media (prefers-color-scheme: dark) {
:root {
--accent: #E89580;
--accent-hover: #ECA593;
--accent-active: #F0B5A6;
--accent-tint: rgba(232, 149, 128, 0.16);
--fg: #EDEBEB;
--fg-muted: #ADA8A4;
--fg-faint: #807A75;
--bg: #141211;
--bg-card: #1F1C1A;
--bg-tertiary: #2A2622;
--border: #2F2A26;
--border-strong: #4A413A;
--success: #6FA37E;
--danger: #D27468;
--warning: #DDA653;
--info: #7397B5;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 6px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 4px rgba(0, 0, 0, 0.4), 0 8px 20px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.45), 0 16px 40px rgba(0, 0, 0, 0.5);
--shadow-xl: 0 8px 16px rgba(0, 0, 0, 0.5), 0 32px 80px rgba(0, 0, 0, 0.6);
}
}
/* Manual theme override (set by app.js — wins over media query) */
:root[data-theme="light"] {
color-scheme: light;
--accent: #C2563D;
--accent-hover: #A8482F;
--accent-active: #8E3B26;
--accent-tint: rgba(194, 86, 61, 0.12);
--fg: #1A1818;
--fg-muted: #5C5552;
--fg-faint: #8B8480;
--bg: #FAF7F2;
--bg-card: #FFFFFF;
--bg-tertiary: #F2EDE5;
--border: #E5DED2;
--border-strong: #C9BFAE;
--success: #4F8B5F;
--danger: #B5453A;
--warning: #C9821C;
--info: #4A6F8E;
--shadow-sm: 0 1px 2px rgba(26, 24, 24, 0.04), 0 2px 6px rgba(26, 24, 24, 0.04);
--shadow-md: 0 2px 4px rgba(26, 24, 24, 0.06), 0 8px 20px rgba(26, 24, 24, 0.06);
--shadow-lg: 0 4px 8px rgba(26, 24, 24, 0.08), 0 16px 40px rgba(26, 24, 24, 0.10);
--shadow-xl: 0 8px 16px rgba(26, 24, 24, 0.10), 0 32px 80px rgba(26, 24, 24, 0.14);
}
:root[data-theme="dark"] {
color-scheme: dark;
--accent: #E89580;
--accent-hover: #ECA593;
--accent-active: #F0B5A6;
--accent-tint: rgba(232, 149, 128, 0.16);
--fg: #EDEBEB;
--fg-muted: #ADA8A4;
--fg-faint: #807A75;
--bg: #141211;
--bg-card: #1F1C1A;
--bg-tertiary: #2A2622;
--border: #2F2A26;
--border-strong: #4A413A;
--success: #6FA37E;
--danger: #D27468;
--warning: #DDA653;
--info: #7397B5;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 6px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 4px rgba(0, 0, 0, 0.4), 0 8px 20px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.45), 0 16px 40px rgba(0, 0, 0, 0.5);
--shadow-xl: 0 8px 16px rgba(0, 0, 0, 0.5), 0 32px 80px rgba(0, 0, 0, 0.6);
}
/* ---------- 2. Reset + base ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
body {
font-family: var(--sans);
font-size: 17px;
line-height: 1.55;
color: var(--fg);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-feature-settings: "kern", "liga", "calt";
text-rendering: optimizeLegibility;
}
img, picture { max-width: 100%; height: auto; display: block; }
a {
color: var(--accent);
text-decoration: none;
transition: color 0.15s ease;
}
a:hover { color: var(--accent-hover); text-decoration: underline; }
a:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
border-radius: 3px;
}
code {
font-family: var(--mono);
font-size: 0.92em;
background: var(--accent-tint);
color: var(--accent);
padding: 1px 6px;
border-radius: var(--r-sm);
word-break: break-word;
}
h1, h2, h3, h4 {
font-family: var(--display);
line-height: 1.18;
letter-spacing: -0.02em;
font-weight: 700;
}
h1 { font-size: clamp(40px, 6vw, 68px); letter-spacing: -0.03em; }
h2 { font-size: clamp(28px, 3.5vw, 40px); letter-spacing: -0.025em; }
h3 { font-size: clamp(22px, 2.4vw, 28px); }
h4 { font-size: 14px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--fg-muted); }
p { color: var(--fg); }
/* Skip link for keyboard users */
.skip-link {
position: absolute;
left: -10000px;
top: auto;
}
.skip-link:focus {
position: fixed;
left: var(--s4);
top: var(--s4);
z-index: 1000;
padding: var(--s3) var(--s5);
background: var(--bg-card);
border: 2px solid var(--accent);
border-radius: var(--r-md);
}
/* ---------- 3. Layout primitives ---------- */
main { display: block; }
section {
padding: var(--s10) var(--s5);
}
@media (min-width: 768px) {
section { padding-inline: var(--s7); }
}
.section-heading {
text-align: center;
margin-bottom: var(--s8);
max-width: var(--max-w-prose);
margin-inline: auto;
}
/* ---------- 4. Header ---------- */
.site-header {
position: sticky;
top: 0;
z-index: 50;
display: flex;
align-items: center;
gap: var(--s5);
padding: var(--s3) var(--s5);
background: color-mix(in srgb, var(--bg) 88%, transparent);
backdrop-filter: saturate(160%) blur(12px);
-webkit-backdrop-filter: saturate(160%) blur(12px);
border-bottom: 1px solid var(--border);
}
@media (min-width: 768px) {
.site-header { padding: var(--s3) var(--s7); }
}
.brand {
display: flex;
align-items: center;
gap: var(--s2);
font-weight: 600;
font-size: 17px;
color: var(--fg);
letter-spacing: -0.01em;
flex-shrink: 0;
}
.brand:hover { color: var(--fg); text-decoration: none; }
.brand img { width: 32px; height: 32px; border-radius: var(--r-sm); }
.brand-name { display: none; }
@media (min-width: 540px) { .brand-name { display: inline; } }
.site-nav {
display: flex;
align-items: center;
gap: var(--s5);
margin-left: auto;
font-size: 15px;
flex-wrap: wrap;
justify-content: flex-end;
}
.site-nav a {
color: var(--fg-muted);
font-weight: 500;
}
.site-nav a:hover { color: var(--fg); text-decoration: none; }
@media (max-width: 600px) {
.site-nav { gap: var(--s4); font-size: 14px; }
.site-nav a:nth-child(3) { display: none; } /* hide Templates on tiny widths */
}
.theme-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: var(--r-md);
border: 1px solid var(--border);
background: var(--bg-card);
color: var(--fg-muted);
cursor: pointer;
flex-shrink: 0;
transition: color 0.15s ease, border-color 0.15s ease;
}
.theme-toggle:hover { color: var(--fg); border-color: var(--border-strong); }
.theme-toggle .theme-icon { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-sun,
:root:not([data-theme]) .theme-toggle .icon-sun { display: block; }
:root[data-theme="light"] .theme-toggle .icon-moon { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .theme-toggle .icon-sun { display: none; }
:root:not([data-theme]) .theme-toggle .icon-moon { display: block; }
}
@media (prefers-color-scheme: light) {
:root:not([data-theme]) .theme-toggle .icon-moon { display: none; }
:root:not([data-theme]) .theme-toggle .icon-sun { display: block; }
}
/* ---------- 5. Hero ---------- */
.hero {
position: relative;
display: grid;
grid-template-columns: 1fr;
gap: var(--s8);
align-items: center;
max-width: var(--max-w);
margin: 0 auto;
padding-block: var(--s8) var(--s10);
}
@media (min-width: 1024px) {
.hero { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: var(--s10); }
}
.hero-copy { max-width: 620px; }
.hero h1 {
font-size: clamp(40px, 5.5vw, 68px);
margin-bottom: var(--s5);
}
.hero-lede {
font-size: clamp(18px, 1.6vw, 22px);
line-height: 1.5;
color: var(--fg-muted);
max-width: 56ch;
margin-bottom: var(--s7);
}
.hero-lede a { color: var(--fg); text-decoration: underline; text-decoration-color: var(--accent); text-underline-offset: 3px; text-decoration-thickness: 2px; }
.hero-lede a:hover { color: var(--accent); }
.hero-prereq {
margin-top: var(--s5);
font-size: 14px;
color: var(--fg-faint);
}
.cta-row {
display: flex;
flex-wrap: wrap;
gap: var(--s3);
}
/* Hero visual — Mac window with overlapping iPhone */
.hero-visual {
position: relative;
isolation: isolate;
margin: 0 auto;
width: 100%;
max-width: 720px;
aspect-ratio: 16 / 11;
}
.hero-mac {
position: absolute;
inset: 0 8% 12% 0;
border-radius: var(--r-lg);
overflow: hidden;
box-shadow: var(--shadow-xl);
background: var(--bg-card);
border: 1px solid var(--border);
}
.hero-mac img { width: 100%; height: 100%; object-fit: cover; object-position: top left; }
.hero-iphone {
position: absolute;
right: 0;
bottom: -4%;
width: 22%;
aspect-ratio: 9 / 19.5;
border-radius: 18px;
overflow: hidden;
box-shadow: var(--shadow-xl), 0 0 0 6px var(--bg-card);
background: var(--bg-card);
border: 1px solid var(--border-strong);
transform: rotate(2deg);
}
.hero-iphone img { width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 1023px) {
.hero-visual { max-width: 600px; aspect-ratio: 16 / 11; }
.hero-iphone { width: 24%; bottom: -2%; }
}
@media (max-width: 540px) {
.hero-visual { aspect-ratio: 4 / 3.5; }
.hero-iphone { width: 28%; right: 2%; }
}
/* ---------- Trust strip ---------- */
.trust-strip {
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
background: var(--bg-tertiary);
padding-block: var(--s5);
}
.trust-strip ul {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: var(--s3) var(--s7);
list-style: none;
padding: 0 var(--s5);
max-width: var(--max-w);
margin: 0 auto;
font-size: 14px;
font-weight: 500;
color: var(--fg-muted);
letter-spacing: 0.02em;
}
/* ---------- 'What' section ---------- */
.what {
max-width: var(--max-w-prose);
margin: 0 auto;
text-align: left;
}
.what h2 {
margin-bottom: var(--s5);
}
.what p {
font-size: clamp(17px, 1.5vw, 20px);
line-height: 1.6;
color: var(--fg);
}
/* ---------- Features ---------- */
.features {
max-width: var(--max-w);
margin: 0 auto;
padding-block: var(--s10);
}
.features .section-heading { margin-bottom: var(--s10); }
.feature {
display: grid;
grid-template-columns: 1fr;
gap: var(--s7);
align-items: center;
margin-bottom: var(--s10);
}
.feature:last-child { margin-bottom: 0; }
@media (min-width: 900px) {
.feature { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--s9); }
.feature.feature-flip { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
.feature.feature-flip .feature-text { order: 2; }
.feature.feature-flip .feature-visual { order: 1; }
}
.feature-text h3 { margin-bottom: var(--s4); }
.feature-text p { color: var(--fg-muted); font-size: 17px; line-height: 1.6; max-width: 52ch; }
.feature-text p code { font-size: 0.88em; }
.feature-visual {
position: relative;
border-radius: var(--r-lg);
overflow: hidden;
box-shadow: var(--shadow-lg);
background: var(--bg-card);
border: 1px solid var(--border);
aspect-ratio: 16 / 10;
}
.feature-visual img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: top left;
}
/* ---------- iOS / ScarfGo ---------- */
.ios {
background: var(--bg-tertiary);
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
padding-block: var(--s10);
}
.ios > * { max-width: var(--max-w); margin-inline: auto; }
.ios {
display: grid;
grid-template-columns: 1fr;
gap: var(--s8);
align-items: center;
}
@media (min-width: 1024px) {
.ios {
grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
gap: var(--s9);
}
}
.ios-copy { padding-inline: var(--s5); }
.eyebrow {
display: inline-block;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--accent);
background: var(--accent-tint);
padding: 4px 10px;
border-radius: var(--r-pill);
margin-bottom: var(--s4);
}
.ios-copy h2 { margin-bottom: var(--s5); }
.ios-copy > p { color: var(--fg-muted); font-size: 17px; line-height: 1.6; margin-bottom: var(--s5); }
.ios-points {
list-style: none;
padding: 0;
margin: 0 0 var(--s7) 0;
display: grid;
gap: var(--s3);
}
.ios-points li {
position: relative;
padding-left: var(--s6);
color: var(--fg);
font-size: 16px;
}
.ios-points li::before {
content: "";
position: absolute;
left: 0;
top: 10px;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent);
}
.ios-gallery {
display: flex;
gap: var(--s3);
overflow-x: auto;
scroll-snap-type: x mandatory;
padding: var(--s5) var(--s5) var(--s7);
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
}
@media (min-width: 1024px) {
.ios-gallery { padding-block: var(--s5); padding-inline: 0 var(--s5); }
}
.phone-frame {
flex: 0 0 200px;
scroll-snap-align: start;
position: relative;
background: #1A1818;
border-radius: 28px;
padding: 8px;
box-shadow: var(--shadow-md);
}
.phone-frame::before {
/* Dynamic Island stand-in */
content: "";
position: absolute;
top: 14px;
left: 50%;
transform: translateX(-50%);
width: 64px;
height: 18px;
border-radius: 12px;
background: #0a0a0a;
z-index: 1;
}
.phone-frame img {
width: 100%;
height: auto;
border-radius: 22px;
display: block;
background: var(--bg-card);
}
@media (min-width: 600px) {
.phone-frame { flex-basis: 220px; }
}
/* ---------- Why native ---------- */
.why {
max-width: var(--max-w);
margin: 0 auto;
padding-block: var(--s10);
}
.why-grid {
display: grid;
gap: var(--s5);
grid-template-columns: 1fr;
}
@media (min-width: 720px) { .why-grid { grid-template-columns: repeat(3, 1fr); } }
.why-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--r-lg);
padding: var(--s7);
box-shadow: var(--shadow-sm);
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.why-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
border-color: var(--border-strong);
}
.why-card h3 {
font-size: 22px;
margin-bottom: var(--s4);
color: var(--accent);
}
.why-card p { color: var(--fg-muted); font-size: 16px; line-height: 1.55; }
/* ---------- Templates teaser ---------- */
.templates {
max-width: var(--max-w-prose);
margin: 0 auto;
text-align: center;
padding-block: var(--s9);
}
.templates h2 { margin-bottom: var(--s4); }
.templates p { color: var(--fg-muted); margin-bottom: var(--s6); font-size: 17px; }
/* ---------- Download ---------- */
.download {
max-width: var(--max-w);
margin: 0 auto;
padding-block: var(--s10);
}
.download-grid {
display: grid;
grid-template-columns: 1fr;
gap: var(--s5);
}
@media (min-width: 720px) { .download-grid { grid-template-columns: 1fr 1fr; } }
.download-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--r-xl);
padding: var(--s8);
box-shadow: var(--shadow-sm);
display: flex;
flex-direction: column;
}
.download-card h3 { font-size: 26px; margin-bottom: var(--s2); }
.download-meta {
font-size: 14px;
color: var(--fg-muted);
margin-bottom: var(--s5);
}
.download-points {
list-style: none;
padding: 0;
margin: 0 0 var(--s7);
display: grid;
gap: var(--s2);
font-size: 15px;
color: var(--fg-muted);
}
.download-points li::before {
content: "✓ ";
color: var(--success);
font-weight: 700;
}
.download-card .btn { margin-top: auto; }
.download-prereq {
margin-top: var(--s7);
text-align: center;
color: var(--fg-muted);
font-size: 15px;
max-width: var(--max-w-prose);
margin-inline: auto;
}
/* ---------- FAQ ---------- */
.faq {
max-width: 820px;
margin: 0 auto;
padding-block: var(--s10);
}
.faq-list {
display: grid;
gap: var(--s2);
}
.faq details {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--r-md);
padding: var(--s5) var(--s6);
transition: border-color 0.15s ease;
}
.faq details[open] {
border-color: var(--border-strong);
box-shadow: var(--shadow-sm);
}
.faq summary {
cursor: pointer;
font-weight: 600;
font-size: 17px;
color: var(--fg);
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--s4);
padding: var(--s2) 0;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
content: "+";
font-size: 24px;
font-weight: 300;
color: var(--fg-faint);
transition: transform 0.2s ease, color 0.15s ease;
flex-shrink: 0;
line-height: 1;
}
.faq details[open] summary::after { transform: rotate(45deg); color: var(--accent); }
.faq summary:hover { color: var(--accent); }
.faq details > div {
padding-top: var(--s4);
color: var(--fg-muted);
font-size: 16px;
line-height: 1.6;
}
.faq details > div p { color: inherit; }
/* ---------- 6. Components ---------- */
.btn {
display: inline-flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: var(--s4) var(--s6);
border-radius: var(--r-md);
font-weight: 600;
font-size: 16px;
text-decoration: none;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
line-height: 1.2;
border: 1px solid transparent;
min-height: 48px;
}
.btn:hover { text-decoration: none; transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn-meta {
display: block;
font-size: 12px;
font-weight: 500;
margin-top: 2px;
opacity: 0.8;
}
.btn-primary {
background: var(--accent);
color: #FFFFFF;
}
.btn-primary:hover { background: var(--accent-hover); color: #FFFFFF; }
.btn-primary:active { background: var(--accent-active); }
.btn-secondary {
background: var(--bg-card);
color: var(--fg);
border-color: var(--border-strong);
}
.btn-secondary:hover { color: var(--fg); border-color: var(--accent); background: var(--bg-card); }
/* ---------- Footer ---------- */
.site-footer {
background: var(--bg-tertiary);
border-top: 1px solid var(--border);
padding: var(--s9) var(--s5) var(--s8);
margin-top: var(--s8);
}
.footer-inner {
max-width: var(--max-w);
margin: 0 auto;
display: grid;
grid-template-columns: 1fr;
gap: var(--s8);
}
@media (min-width: 720px) {
.footer-inner { grid-template-columns: 1fr 2fr; gap: var(--s9); }
}
.footer-brand { display: flex; align-items: flex-start; gap: var(--s4); }
.footer-brand img { width: 40px; height: 40px; border-radius: var(--r-sm); flex-shrink: 0; }
.footer-brand p { font-size: 15px; color: var(--fg-muted); }
.footer-brand a { color: var(--fg); }
.footer-nav {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: var(--s7);
}
.footer-nav h4 { margin-bottom: var(--s3); color: var(--fg); font-size: 13px; }
.footer-nav ul { list-style: none; padding: 0; display: grid; gap: var(--s2); }
.footer-nav a { color: var(--fg-muted); font-size: 14px; }
.footer-nav a:hover { color: var(--accent); }
/* ---------- 7. Responsive tweaks ---------- */
@media (max-width: 540px) {
section { padding-block: var(--s9); }
.hero { padding-block: var(--s7) var(--s9); }
.features { padding-block: var(--s9); }
.feature { gap: var(--s5); margin-bottom: var(--s9); }
.ios { padding-block: var(--s9); }
.why { padding-block: var(--s9); }
.download { padding-block: var(--s9); }
.faq { padding-block: var(--s9); }
.btn { width: 100%; }
.cta-row { flex-direction: column; align-items: stretch; }
.download-card { padding: var(--s6); }
.why-card { padding: var(--s6); }
}
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Scarf — OG image source</title>
<!--
Render to PNG via headless Chromium:
google-chrome --headless --no-sandbox --hide-scrollbars \
--window-size=1200,630 --screenshot=og-image.png \
--default-background-color=00000000 \
file://$PWD/og-image.html
Then 1200x600 for Twitter:
google-chrome --headless --no-sandbox --hide-scrollbars \
--window-size=1200,600 --screenshot=twitter-card.png \
--default-background-color=00000000 \
file://$PWD/og-image.html
Move outputs to site/landing/assets/og-image.png and twitter-card.png.
Note: change `body { width: 1200px; height: 630px; }` to 600 for Twitter.
-->
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px;
height: 630px;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, sans-serif;
background:
radial-gradient(ellipse at top right, rgba(232, 149, 128, 0.45) 0%, transparent 55%),
radial-gradient(ellipse at bottom left, rgba(168, 72, 47, 0.35) 0%, transparent 60%),
linear-gradient(135deg, #C2563D 0%, #8E3B26 100%);
color: #FAF7F2;
overflow: hidden;
position: relative;
}
/* Subtle texture — paper-like grain via repeating gradient */
body::before {
content: "";
position: absolute;
inset: 0;
background-image:
repeating-linear-gradient(45deg,
rgba(255, 255, 255, 0.015) 0,
rgba(255, 255, 255, 0.015) 1px,
transparent 1px,
transparent 4px);
pointer-events: none;
}
.frame {
position: relative;
height: 100%;
padding: 56px 72px;
display: flex;
flex-direction: column;
gap: 36px;
}
.top {
display: flex;
align-items: center;
gap: 20px;
}
.logo {
width: 72px;
height: 72px;
border-radius: 16px;
background: #FAF7F2;
display: flex;
align-items: center;
justify-content: center;
font-family: Georgia, serif;
font-size: 48px;
font-weight: 600;
font-style: italic;
color: #C2563D;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.22);
}
.wordmark {
font-size: 44px;
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1;
}
.pill {
margin-left: auto;
font-size: 15px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 8px 16px;
border-radius: 999px;
background: rgba(250, 247, 242, 0.18);
border: 1px solid rgba(250, 247, 242, 0.32);
}
.headline {
font-size: 68px;
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1.05;
max-width: 980px;
}
.headline em {
font-style: normal;
color: #FAF7F2;
text-decoration: underline;
text-decoration-color: rgba(250, 247, 242, 0.55);
text-underline-offset: 6px;
text-decoration-thickness: 5px;
}
.bottom {
margin-top: auto;
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 24px;
}
.tagline {
font-size: 20px;
font-weight: 500;
color: rgba(250, 247, 242, 0.85);
max-width: 720px;
line-height: 1.4;
}
.url {
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
font-size: 16px;
color: rgba(250, 247, 242, 0.72);
letter-spacing: 0.02em;
flex-shrink: 0;
}
</style>
</head>
<body>
<div class="frame">
<div class="top">
<div class="logo">S</div>
<div class="wordmark">Scarf</div>
<div class="pill">macOS · iOS</div>
</div>
<div class="headline">
Native Mac &amp; iOS app<br>
for your <em>Hermes AI agent</em>.
</div>
<div class="bottom">
<div class="tagline">
Sessions, projects, memory, skills, cron.<br>
Multi-server SSH. Free and open source.
</div>
<div class="url">awizemann.github.io/scarf</div>
</div>
</div>
</body>
</html>