Skip to content
rheynardjanPublic

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

4 Commits

Folders and files

Repository files navigation

MARS

An interactive map of an agent operating system — Memory, Applications, Routines, Skills — rendered to a single self-contained HTML file.

No CDN. No build step. No runtime dependencies. Python in, one .html out, and it works from file:// with the network off.

The Solar System layout

python demo.py --open        # sample data, runs anywhere
python demo.py --live        # map your own Claude Code installation

Four layouts, one set of nodes

Layout Shows How it works
Galaxy Organic clusters Force simulation: repulsion, weighted springs, centre gravity
Orbit Groups side by side Domains on a circle, children fanned into arcs
Sector Grid Structured topology Domains claim hex territories, children pack into free cells
Solar System Hierarchy Concentric bands, first domain nearest the core

Galaxy runs the simulation. The other three compute target positions that the same integrator eases toward, so switching layouts moves the nodes already on screen rather than rebuilding the scene — the canvas never goes blank between modes.

The morph timing was measured rather than guessed. Error decays as (1-k)^n; at k=0.075 the layout is within 5% of its target after ~38 frames, about 640 ms. The first attempt used k=0.12, settled in 339 ms, and read as a snap rather than a move.

That the four layouts are genuinely different is verified rather than assumed: a radial histogram of drawn pixels separates the closest pair by 12× the frame-to-frame animation noise. Comparing raw canvas hashes would not prove it — the ambient particles guarantee any two frames differ.

Coverage is not optional

A map that shows nine of seventy connectors, silently, is stating something false about how much there is. So caps are drawn:

g.mark("applications", shown=9, total=70)   # renders "9/70" under the domain label
g.mark("routines", shown=2, total=2)        # renders no badge — nothing is hidden

A domain that hides nothing spends no pixels saying so. This is the one behaviour the test suite guards hardest, because the bug it prevents is invisible: the map looks perfectly reasonable while being wrong.

The same principle killed a subtler bug in the Claude Code adapter. Connectors were counted by each .mcp.json's parent directory — but in the versioned cache layout the parent is the version, so the map drew nodes labelled 0.1.10 and 0.0.4 and counted one plugin once per cached version. It had looked fine for weeks. Writing an honest denominator is what forced the question of what the numerator counted. test_connectors_are_not_version_strings now rejects any connector whose name parses as a version.

Interaction

Pointer — drag a node, drag empty space to pan, wheel to zoom, click to inspect, double-click empty space to fit.

Keyboard — the canvas is focusable and describes its own keys. Arrows walk to the nearest node inside a 60° cone; Tab roves in order but leaves the ends open so it can still escape the canvas; Enter opens details; Escape clears; + - 0 drive the camera. Every move is announced through a polite live region.

Search — matches keep their labels at any zoom and take a highlight ring; Enter flies the camera to the first hit.

Filters — one chip per domain. A hidden domain fades rather than vanishing, and becomes unpickable, untabbable and unsearchable. If it held the selection or the keyboard cursor, those are dropped instead of left pointing at something invisible.

Observatory — one button takes the whole component fullscreen, controls included; fullscreening the canvas alone would strand search, filters and pause and make the mode a dead end. The exit is the same button that entered and stays on screen throughout; Escape and F11 also work. The camera is saved on the way in and restored exactly on the way out. Where the Fullscreen API is refused — an iframe without allow="fullscreen" — it falls back to a fixed overlay rather than leaving the button inert.

Motion — prefers-reduced-motion drops the particles, the pulse rings and the morph easing. There is a pause control, and a hidden tab freezes the simulation on its own. Verified frozen: three consecutive frames hash identical.

API

from mars import Graph, render_page

g = Graph(hub="CORE")
g.domain("memory", "MEMORY", "#b88cff")
g.leaf("memory", "m:notes", "notes", "#b88cff", meta="182 entries")
g.mark("memory", 1, 12)

open("map.html", "w", encoding="utf-8").write(
    render_page(g.payload(), title="My OS", size=760)
)

render_map(graph) returns just the fragment (markup + inline script) if you are embedding into a page you already own; pair it with mars.CSS. Domains are whatever you pass — the hex anchors and ring radii are derived from how many there are, so the four MARS domains are a default, not a constraint.

Mapping a Claude Code installation

from mars.sources import claude_code
graph = claude_code()                    # defaults to ~/.claude

Scans skills, slash commands, per-project memory stores, and installed MCP connectors. Routines are passed in by the caller rather than discovered — scheduled work lives in the OS scheduler, not the installation, and inventing entries for it would be exactly the decoration this map refuses. With none supplied the domain honestly renders as none found.

prettify(name, rename=...) controls how encoded project folders are labelled.

Why hand-rolled

At a few dozen nodes the whole simulation is a few dozen lines of integrator. Pulling in D3 or Cytoscape would cost the offline guarantee that makes this useful as a local dashboard — and a CDN <script> on a page that lists your own systems is a trade worth refusing.

Tests

python -m pytest -q        # 37 tests

They cover the payload shape, the rendered markup, and the behaviours pinned inside the script: labels escaped before reaching innerHTML, reduced motion honoured, motion frozen when paused or hidden, filtered nodes unpickable, and the closed inspector hidden rather than parked over whatever is in the next column.

That last one is a real bug this suite now prevents. .mapwrap cannot use overflow:hidden, because the tooltip is meant to float above the top row of nodes — so the closed drawer sat opaque over the neighbouring column, still showing the last node inspected. Visibility does the hiding instead, delayed until the slide finishes.

Notes

Built alongside a local agent command centre. A note for anyone testing canvas work headlessly: in headless Chrome, getComputedStyle and getBoundingClientRect returned stale values and focus events never fired at all. The drawer's open and closed states had to be confirmed from screenshots, not from the DOM.

License

MIT.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages