Skip to content

Smart-AI-Memory/attune-gui

Repository files navigation

attune-gui

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.

What it does

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.

Template editor (/editor)

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.

Editor frontend dev loop

# 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 e2e

The 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.

Endpoint summary

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.

Quickstart

pip install attune-gui
attune-gui
# Or pick a specific port:
attune-gui --port 8765

The sidecar binds to 127.0.0.1, prints SIDECAR_URL=…, and serves the new dashboard at /. Use --open to auto-open your browser.

Configuration

.env auto-loading

The sidecar loads KEY=value lines from the first .env it finds, in this order:

  1. ./.env (current working directory)
  2. <repo-root>/.env (the attune-gui checkout root)
  3. ~/.attune-gui/.env
  4. ~/.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

~/.attune-gui/config.json

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 key

config 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.

Development

git clone https://github.com/Smart-AI-Memory/attune-gui
cd attune-gui
uv sync
uv run attune-gui --port 8765 --reload

Templates auto-reload — edit anything under sidecar/attune_gui/templates/ and refresh the browser. Python code changes reload automatically with --reload.

Tests

uv run pytest                # 124 tests, ~2s
uv run ruff check .          # lint

Stacked pull requests

If PR B was opened on top of PR A's branch and A has since merged to main, B will show conflicts against main even though your own commits are clean. The conflicts are A's commits, now reachable from main via the merge, replaying against themselves.

Fix it by replaying only your commits onto main with git rebase --onto main <old-base-sha> <branch>. <old-base-sha> is the tip of A's branch at the moment you forked B — find it with git merge-base <branch> <A-branch> while A's branch still exists locally, or read it from the PR's "compare" base on github.com before the branch is deleted.

# B = feature/b, was stacked on feature/a (now merged + deleted)
git fetch origin
old_base=$(git merge-base feature/b origin/main~1)  # or paste the SHA
git rebase --onto origin/main "$old_base" feature/b

Then force-push with lease — never plain --force: git push --force-with-lease.

Architecture

┌──────────────────────────────────────┐
│  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]                   │
└──────────────────────────────────────┘

Frontend boundary

Two rendering surfaces, one rule:

  • Template editor at /editor is a Vite + TypeScript SPA (CodeMirror 6, WebSocket conflict mode, per-hunk diff). Source in editor-frontend/, pre-bundled into sidecar/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.

MCP integration

attune-gui ships an MCP server (attune-gui-mcp) that exposes a small, read-mostly tool surface for Claude Code and other MCP clients. Same data the dashboard renders — specs and living docs — but addressable programmatically. mcp>=0.9.0 is a core dependency, so pip install attune-gui is enough to get both the dashboard and the MCP server.

Tools

Tool What it returns
gui_list_specs All feature specs across configured roots (federated). Each tagged with project, root, phase, status.
gui_get_spec All phase-file contents (requirements.md / design.md / tasks.md) for one spec.
gui_get_spec_status The **Status**: value for one phase. Default: most-advanced phase.
gui_list_living_docs Doc registry — generated docs the workspace tracks, with status (current/stale/missing) and persona.
gui_get_living_doc One living-doc's file content. Path-traversal guarded.

Every tool returns a JSON envelope shaped {"success": bool, ...}. See docs/specs/mcp-server-scope/ for the scope decisions and tool schemas.

Wiring into Claude Code

Add to your project's .mcp.json (or ~/.claude/mcp.json for global):

{
  "mcpServers": {
    "attune-gui": {
      "command": "attune-gui-mcp"
    }
  }
}

The server boots on stdio and logs to <tmpdir>/attune-gui/attune-gui-mcp.log so the MCP transport stays clean. Restart Claude Code after editing the config; the five tools appear under the attune-gui server in /mcp.

Related work

attune-ai's ops-specs-features spec is the complement to this one: that work brings attune-gui's spec views into the attune-ai ops dashboard for humans; this MCP server exposes the same views to agents. Both serve the spec-driven workflow, just for different clients.

Security notes

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 Origin header guard rejects browser requests from non-localhost origins
  • Mutating endpoints require the X-Attune-Client header 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

Related packages

License

Apache-2.0

About

Unified local GUI driving attune-ai, attune-rag, attune-author via FastAPI sidecar + React UI

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors