feat(projects,cron): new project wizard + keychain env mirror + #75 fix

Three coordinated additions to the project surface:

1. New Project from Scratch wizard. Toolbar entry that scaffolds a
   Scarf-standard project skeleton (`<project>/.scarf/dashboard.json`
   placeholder + `AGENTS.md` marker block), registers it, opens an ACP
   chat session in the project's cwd, and auto-sends a kickoff prompt
   that activates the bundled `scarf-template-author` skill. The skill
   drives the substantive setup conversationally — widgets, optional
   config schema, optional cron, AGENTS.md content.

2. Keychain secrets mirror into ~/.hermes/.env. Cron jobs can now
   reference Keychain-backed config values via env vars named
   `SCARF_<UPPER_SLUG>_<UPPER_FIELDKEY>`. Hermes reloads .env per cron
   tick (cron/scheduler.py:897-903), so credential rotation is free.
   Source of truth stays in the Keychain — config.json keeps
   `keychain://` URIs unchanged. Mirror runs at install, post-install
   Configuration save, uninstall, "Remove from List", and on app
   launch (reconcileAll). Mode 0600 on `.env` enforced by
   LocalTransport's existing `.env` heuristic.

3. Configuration form layout recursion fix (issue #75). Per-stage
   frame sizes on `ConfigEditorSheet` triggered
   `_NSDetectedLayoutRecursion` for projects with manifest.json.
   Stabilized the outer frame at the editing stage's intrinsic size so
   transitions only swap content, never resize the container.

New services:
- `ProjectScaffolder` (Mac) — bare-shell project + AGENTS.md marker
- `SkillBootstrapService` (Mac) — copies bundled skills into ~/.hermes/skills/
- `KeychainEnvMirror` (Mac) — splice/unmirror/reconcileAll over ~/.hermes/.env
- `SecretsEnvBlock` (ScarfCore) — pure marker-block helpers

Bundled skill `scarf-template-author` v1.1.0 ships in
`Resources/BuiltinSkills.bundle/`; SkillBootstrapService copies it
into `~/.hermes/skills/scarf-template-author/` on launch (idempotent +
version-gated). The skill grew a "Using secrets in cron prompts"
section documenting the env-var convention.

Migration: launch reconciler auto-populates .env on first v2.8 launch.
Users with cron prompts authored against the old (broken) pattern need
to update them to use $SCARF_… references — see release notes.

Tests:
- SecretsEnvBlockTests: 24/24 (`swift test --filter SecretsEnvBlock`)
- KeychainEnvMirrorTests: 11/11 (`xcodebuild ... -only-testing:scarfTests/KeychainEnvMirror`)

The idempotent-mirror test caught a real bug: applyBlock's replace
path consumed the trailing newline from blockRange but didn't restore
it, breaking the no-op-when-unchanged contract that the launch
reconciler relies on. Fixed.

v2.8 RELEASE_NOTES.md committed but no release cut yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-05-05 11:44:23 +02:00
parent bd9bacb8b3
commit 4efd84c119
19 changed files with 2854 additions and 31 deletions
@@ -1,7 +1,7 @@
---
name: scarf-template-author
description: Scaffold a new Scarf project — dashboard, optional configuration schema, optional cron job, and AGENTS.md — from a short conversational interview with the user. Output is immediately usable locally and cleanly exportable as a .scarftemplate bundle.
version: 1.0.0
version: 1.1.0
author: Alan Wizemann
license: MIT
platforms: [macos]
@@ -319,11 +319,40 @@ Where `cron/jobs.json` is:
]
```
### Using secrets in cron prompts
`secret`-typed config fields land in the macOS Keychain at install time, with `keychain://` URIs in `<project>/.scarf/config.json` (never plaintext on disk). At install + on every config save, **Scarf mirrors the resolved values into `~/.hermes/.env`** under env var names like `SCARF_<UPPERCASE_SLUG>_<UPPERCASE_FIELDKEY>`. Hermes's cron scheduler reloads `~/.hermes/.env` fresh on every tick, so the values are reachable from any tool the agent invokes.
**The agent reads them via the terminal or code_exec tool — not from prompt-text substitution.** Hermes does not interpolate env vars into prompt bodies. Tool-invoked subprocesses (the only path through which env vars become visible) DO see them via shell-level expansion or `os.environ`. Cron prompts should reference secrets in tool invocations, not in inline text.
**Naming.** For a template with `slug = "site-status-checker"` and a secret field `api_token`, the env var is `SCARF_SITE_STATUS_CHECKER_API_TOKEN`. Both halves are upper-cased and any non-`[A-Z0-9_]` characters become `_`. Stable across releases — write your prompts using these names and they'll keep working when the user rotates the secret.
**Example cron prompt (with a secret):**
```json
{
"name": "Daily news digest",
"schedule": "0 9 * * *",
"prompt": "Use the terminal tool to fetch the RSS feed: `curl -sS -H \"Authorization: Bearer $SCARF_LOCAL_NEWS_API_TOKEN\" \"$SCARF_LOCAL_NEWS_RSS_URL\" -o {{PROJECT_DIR}}/.scarf/feed.xml`. Then summarise the top 5 items into {{PROJECT_DIR}}/.scarf/digest.md."
}
```
The agent runs `curl` via the terminal tool; the shell expands the env vars from the cron process's environment (which Hermes populated by loading `~/.hermes/.env`). For Python via the code_exec tool, use `os.environ['SCARF_LOCAL_NEWS_API_TOKEN']`.
**What NOT to do:**
-*"Read `keychain://...` from config.json and call the API with it."* Hermes treats the URI as opaque text — the API call sends `Authorization: Bearer keychain://...` and gets a 401.
-*"Use the API token from values.api_token in config.json."* Same issue — the value in config.json is the URI, not the secret.
- ❌ Inlining a secret into the prompt body and asking the agent to use it. Secrets shouldn't appear in prompts; that's why we route them through env vars.
**What about `~/.hermes/.env` rotation?** The user rotates a secret in Scarf's Configuration sheet → Scarf re-resolves from the Keychain → re-mirrors to `~/.hermes/.env` → next cron tick (Hermes reloads `.env` per tick) sees the new value. No cron-job edit needed.
### Gotchas
- **Hermes does not set a CWD when firing cron jobs.** Relative paths in the prompt resolve against wherever the Hermes process happens to be running, not the project. Always use `{{PROJECT_DIR}}` in the prompt — the installer substitutes the absolute path at install time. This is THE most common template-author mistake.
- **Cron jobs created by the installer start paused.** Their name is auto-prefixed with `[tmpl:<template-id>]`. The user enables them from Scarf's Cron sidebar when ready.
- **Registering a cron job for a user's local (non-exported) project:** run `hermes cron create --name "<descriptive name>" "<schedule>" "<prompt>"` directly, substituting the absolute `<project>` path for `{{PROJECT_DIR}}` yourself. Then `hermes cron pause <id>` so it doesn't run until the user opts in.
- **Hermes does not substitute env vars into prompt text.** `$VAR` references in the prompt body are passed through verbatim. Env vars only become visible when the agent invokes a tool (terminal, code_exec) that runs in a subprocess inheriting the cron process's environment — see the "Using secrets in cron prompts" section above.
### Schedule quick reference