Local dashboard for the attune-* documentation family
(attune-rag, attune-help, attune-author). Server-rendered Jinja2
UI ("Cowork dashboard") backed by a FastAPI sidecar — ships clean via
PyPI with no npm step required to run.
Sidebar nav with seven pages, each consuming the existing JSON API:
| Page | What it shows |
|---|---|
| Health | Cross-layer health (rag/help/author/gui versions) + corpus snapshot |
| Templates | Markdown templates with mtime staleness, tags, and a manual-pin toggle |
| Specs | Feature specs in specs/ with phase + status badges. + New spec bootstraps from TEMPLATE.md; + Design / + Tasks inline; status dropdown in Preview |
| Summaries | Inline-editable summaries.json with overwrite warning |
| Living Docs | Workspace editor, scan trigger, document health, review queue, RAG quality bars |
| Commands | Run any registered command from a card grid (RAG queries, regen, maintain, …) |
| Jobs | Job history with per-feature progress, last-output column, Cancel button, auto-refresh |
Click any spec or template to open the Preview / Edit panel — server-side
Markdown rendering plus a raw <textarea> for editing.
A first-class CodeMirror 6 editor for attune-help-style markdown templates.
Triggered by attune-author edit <path> or by navigating to
/editor?corpus=<id>&path=<rel>. Reads from / writes back to any registered
corpus on disk, with all retrieval/lint/refactor smarts coming from
attune-rag's editor toolkit.
Developer features:
| Feature | What it does |
|---|---|
| Schema-driven frontmatter form | Reads attune_rag.editor.load_schema() at request time and renders typed inputs (select / chip-input / textarea / text). Unknown frontmatter keys are preserved verbatim. Raw-YAML toggle round-trips byte-for-byte. |
| CodeMirror 6 + Lezer extension | Composes the standard @codemirror/lang-markdown with a custom Lezer extension for YAML frontmatter delimiters, ## Depth N markers, and [[alias]] refs (excluding fenced code, supporting \[[ escape). |
| Server-side lint | 300 ms debounced POST to /api/corpus/<id>/lint. Diagnostics paint as squiggles in the editor + a clickable strip at the bottom. Local fast-path skips the round-trip for YAML parse errors. |
| Tag + alias autocomplete | Context-aware completions inside tags: / aliases: frontmatter fields and [[…]] body refs. Per-prefix LRU cache invalidates on WS file-change events. |
| Per-hunk save modal | /api/corpus/<id>/template/diff returns stable hunk ids; the modal shows each hunk with a checkbox and runs a projected-state lint so a partial save can't write known-broken frontmatter. Atomic save via /template/save (409 on base_hash mismatch → conflict mode). |
| 3-way merge conflict mode | A WebSocket at /ws/corpus/<id>?path=<rel> pushes file_changed events from watchfiles. On conflict (or 409), a banner offers Reload / Keep / Resolve. Resolve uses node-diff3 for per-region accept-disk / accept-editor / keep-both. |
| Cross-corpus rename refactor | Right-click any tag/alias chip → "Rename …". /api/corpus/<id>/refactor/rename/preview returns a multi-file diff; apply is atomic across files (per-file tempfile + sequential rename + drift-detection rollback). 409 with owning_path on alias collisions. |
| Corpus switcher | Top-bar dropdown lists registered corpora (~/.attune/corpora.json). Search input materializes above 10 corpora. "+ Add corpus…" registers a new root via /api/corpus/register. Switching with unsaved edits prompts Save / Discard / Cancel. |
| Generated-corpus advisory | Persistent, non-dismissible banner when the active corpus has kind: "generated" — flags that edits will be overwritten on the next attune-author maintain. |
| Read-only on duplicate session | A second tab opening the same (corpus, path) receives a duplicate_session WS message and goes read-only with a banner — first tab keeps full control. |
| Keyboard shortcuts | ⌘/Ctrl-S opens the save modal. beforeunload warns on unsaved edits. |
| Pre-bundled, no Node at install | editor-frontend/ is Vite + TypeScript; make build-editor produces a hashed-filename bundle into sidecar/attune_gui/static/editor/ that's checked into the repo. PyPI consumers don't need npm. |
# In one shell — sidecar with auto-reload:
uv run attune-gui --port 8765 --reload
# In another — vitest watch (92 unit tests, ~2s):
cd editor-frontend && npm run test --watch
# Rebuild the bundle (deterministic; output committed):
make build-editor
# Run the Playwright e2e suite against a fresh sidecar
# (auto-spawned by playwright.config.ts):
cd editor-frontend && npm run e2eThe editor bundle is ~210 KB gzipped (budget: 600 KB). Schema and Lezer
grammar parse fixtures live in editor-frontend/src/grammar/; merge
correctness in three-way-merge.test.ts. The Playwright suite under
editor-frontend/e2e/ exercises the end-to-end flows (open→edit→save,
conflict resolve via WS-pushed file_changed, rename refactor
preview+apply, corpus switcher with unsaved-edits guard, save-modal
controls, keyboard shortcuts + advisories) — it spins up a real
sidecar with an isolated ATTUNE_CORPORA_REGISTRY so your dev
registry isn't touched.
| Method | Path | Purpose |
|---|---|---|
| GET | /editor?corpus=<id>&path=<rel> |
Editor shell (Jinja) |
| GET | /api/editor/template-schema |
Frontmatter JSON Schema |
| GET | /api/corpus |
List registered corpora + active id |
| POST | /api/corpus/active |
Switch active corpus |
| POST | /api/corpus/register |
Add a new corpus root |
| POST | /api/corpus/resolve |
Map an absolute path → (corpus_id, rel_path) |
| GET | /api/corpus/<id>/template?path=<rel> |
Read template + base hash + mtime |
| POST | /api/corpus/<id>/template/diff |
Compute unified-diff hunks vs disk |
| POST | /api/corpus/<id>/template/save |
Atomic save (base_hash 409-guarded) |
| POST | /api/corpus/<id>/lint |
Lint a template body |
| GET | /api/corpus/<id>/autocomplete?kind=tag|alias&prefix=… |
Autocomplete |
| POST | /api/corpus/<id>/refactor/rename/preview |
Multi-file rename plan |
| POST | /api/corpus/<id>/refactor/rename/apply |
Atomic rename across files |
| WS | /ws/corpus/<id>?path=<rel> |
file_changed + duplicate_session push |
Looking for AI dev workflows (code review, security audits, refactor planning, multi-agent orchestration)? Those live in
attune-ai— a separate product. attune-gui is deliberately scoped to the documentation lifecycle.
pip install attune-gui
attune-gui
# Or pick a specific port:
attune-gui --port 8765The sidecar binds to 127.0.0.1, prints SIDECAR_URL=…, and serves the
new dashboard at /. Use --open to auto-open your browser.
The sidecar loads KEY=value lines from the first .env it finds, in this order:
./.env(current working directory)<repo-root>/.env(the attune-gui checkout root)~/.attune-gui/.env~/.attune/.env
Existing real env values are preserved; empty/whitespace-only values are treated as unset and overwritten. Common keys:
ANTHROPIC_API_KEY=sk-ant-… # required for author.regen / author.maintain
ATTUNE_SPECS_ROOT=/path/to/your/repo/specs
ATTUNE_WORKSPACE=/path/to/your/project
A single typed config file holds the keys below. Each key has a matching environment variable that overrides the file at runtime — useful for CI or one-off runs. Precedence: env > file > default.
| Key | Env var | Purpose |
|---|---|---|
workspace |
ATTUNE_WORKSPACE |
Project the sidecar watches (Living Docs, templates) |
corpora_registry |
ATTUNE_CORPORA_REGISTRY |
Path to corpora registry (default: ~/.attune/corpora.json) |
specs_root |
ATTUNE_SPECS_ROOT |
Where the Specs page reads from (default: <workspace>/specs/, then walks up from cwd) |
Manage the file from the CLI — no hand-editing JSON:
attune-gui config list # show resolved values + source
attune-gui config get workspace # print one value
attune-gui config set workspace /path/to/project # persist a value
attune-gui config unset specs_root # remove a keyconfig set workspace validates that the path is a real directory; the
others trust you. Workspace can also be set via Living Docs →
Workspace in the UI — both surfaces write to the same file.
git clone https://github.com/Smart-AI-Memory/attune-gui
cd attune-gui
uv sync
uv run attune-gui --port 8765 --reloadTemplates auto-reload — edit anything under sidecar/attune_gui/templates/
and refresh the browser. Python code changes reload automatically with
--reload.
uv run pytest # 124 tests, ~2s
uv run ruff check . # lint┌──────────────────────────────────────┐
│ Cowork dashboard (Jinja2) / │
└──────────────────┬───────────────────┘
│ /api/*
┌──────────────────▼───────────────────┐
│ FastAPI sidecar — 127.0.0.1 │
│ ├─ routes/system, rag, help, … │
│ ├─ routes/cowork_health │
│ ├─ routes/cowork_specs │
│ ├─ routes/cowork_templates │
│ ├─ routes/cowork_files │
│ └─ routes/cowork_pages (HTML) │
└──────────────────┬───────────────────┘
│
┌──────────────────▼───────────────────┐
│ attune-rag · attune-help │
│ attune-author[ai] │
└──────────────────────────────────────┘
Two rendering surfaces, one rule:
- Template editor at
/editoris a Vite + TypeScript SPA (CodeMirror 6, WebSocket conflict mode, per-hunk diff). Source ineditor-frontend/, pre-bundled intosidecar/attune_gui/static/editor/and committed so PyPI consumers do not need Node. - Everything else (Health, Templates, Specs, Summaries, Living Docs, Commands, Jobs) is server-rendered Jinja2 with light vanilla-JS for inline actions and polling.
New UI defaults to Jinja. Reach for the SPA only when the surface needs editor-grade interactivity — rich text editing, real-time conflict resolution, multi-file refactor previews. Inline actions, polling, and form-driven dashboards stay server-rendered.
This is a single-user, local-only app. Not designed for multi-user deployment, not hardened against a motivated attacker on the same machine.
- Binds only to
127.0.0.1— not reachable from other machines - An
Originheader guard rejects browser requests from non-localhost origins - Mutating endpoints require the
X-Attune-Clientheader to match a per-process token (served from/api/session/token) - File API enforces a path-traversal guard against three named roots
(
templates,specs,summaries); writes outside those roots return 400
attune-rag— RAG pipelineattune-help— help runtimeattune-author— doc authoringattune-gui-plugin— Claude Code plugin that launches the dashboard inside Cowork's preview paneattune-ai— separate AI dev workflow product (not used by attune-gui)
Apache-2.0