a13n
Build AI agents and run them on cloud.
A lightweight mac app that keeps watch over git worktrees while coding agents work.
Developer toolsSwift · ShellGitHub @CocoaBeansOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Mimamori (見守り, mee-mah-MOH-ree) is Japanese for watching over someone with care: the way you watch children at play, ready to step in.
A lightweight mac app that keeps watch over git worktrees while coding agents work. It sits beside Claude Code, Codex, Aider, Cursor or whatever you run, and answers two questions at a glance: what is it doing right now, and should I be worried.
Mimamori is read-only unless you turn on one of its two escape hatches: discarding a hunk or a file, or putting an idle agent to sleep. It never stages, commits, checks out, or takes the repository's index.lock, so it can watch any repository while another tool is busy in it.
Git clients are built for committing. When an agent is editing on your behalf, you want something closer to a monitor: it should refresh by itself, show you what changed in the last minute, keep showing you the whole session's work even after the agent commits, and point out the handful of changes that deserve a human look before anything ships. Mimamori is that monitor.
node_modules) are discarded without a refresh.open -a Mimamori <folder>) and Mimamori discovers its linked worktrees (for example the ones agent tools create under .claude/worktrees/) and watches them all. Repositories collapse in the sidebar; worktrees you do not care about can be hidden, and the filter button at the bottom narrows the list to worktrees with an agent at work. Stop watching a repository from any of its rows' context menu, the File menu (⌘⌫), or the Delete key.
.icns, an Android launcher icon, or a web app's touch icon or favicon), or else its initials on a muted colour taken from the icon or the name. The same mark heads its section in the sidebar and the menu bar and tags its agents' rows, so you can tell repositories apart at a glance. Pick another colour, or set the icon aside, from the repository's context menu..env and key material, secret-looking strings in added lines, CI and agent instruction files, .gitignore edits, mass deletions, deleted or skipped tests, conflict markers, lint suppressions, debug leftovers, permission changes, huge files, and edits outside a scope you define. Click the orange count to see every flagged file and why. In the diff, a flagged line takes its flag's colour (orange, or red for a secret or conflict marker) with a stripe down its edge and the reason on hover, and the strip beside a long diff marks where those lines are. Every rule can be switched off and thresholds are adjustable.git apply --check -R takes it out cleanly. Then it records a snapshot, even with automatic snapshots off, and writes that one file.refs/monitor/<worktree-id>/. Branches, the index and the working tree are untouched. Snapshots keep the Start marker alive across restarts and let you compare against any earlier moment.Follows subagents and background work. When Claude Code hands work to subagents, its agent stays in Working, "waiting on 2 subagents", and "finished a turn" waits for the last. Its card and banner list each subagent's kind, task, steps and latest step, and commands it left running in the background, with how each ended. See Subagents.
Puts idle agents to sleep, if you let it. Off until you turn it on in Settings › General › Sleeping agents. An agent waiting for your next prompt still holds hundreds of megabytes; the moon in its banner, or Put … to Sleep… in the worktree's context menu, stops it and keeps its session.
claude --resume <session> or codex resume <session>, leaving a shell there when the agent quits. Copy the command instead to resume it wherever you like; Mimamori notices the session is awake either way.Lives in the menu bar. A glance shows total changed and flagged files, and how many agents are waiting for you. Click it for a popover drawn like the desktop widget: each agent at work with a live timer, its task and latest steps, grouped under Needs you, Working, Ready and Ended as in the Agents window, then every repository and worktree with what changed. Click an agent or a worktree to open it in Mimamori. Fold the Agents or Worktrees section, or any of the agents' groups, away by its header, and pin the popover (the pin at its foot) to keep it open as a small window that floats above the others; it opens again at launch until you unpin it.
Reaches your iPhone. Pair an iPhone in Settings › iPhone and, on the same network, it shows your agents as the Agents window does, tells you when one needs you or finishes a turn, and lets you Allow or Deny Claude Code's permission requests, or answer Claude's questions, with Face ID before Allow or Answer. Off until you turn it on, and encrypted with a key of each phone's own. The iPhone app is built from source for now; see iPhone companion.
git binary. Homebrew or MacPorts git is preferred; the Xcode shim at /usr/bin/git works when the Command Line Tools or Xcode are installed, and Mimamori then runs the git it points to directly. A custom path can be set in Settings.The website, cocoabeans.github.io/mimamori, has the download and a short tour.
With Homebrew:
brew install --cask cocoabeans/tap/mimamori
Or download the .dmg from the latest release, open it, and drag Mimamori to Applications. It is signed with a Developer ID and notarized by Apple, and it keeps itself up to date: Mimamori › Check for Updates…, or once a day if you let it. The changelog lists what changed in each release.
The first time it opens, a welcome window says what Mimamori reads and writes, what hooks are, and exactly which file connecting Claude Code or Hermes changes, before anything is installed; Help › Welcome to Mimamori brings it back. Help › Mimamori Help (⌘?) is a short guide to the rest: reviewing changes, agents and hooks, the menu bar, snapshots, keyboard shortcuts and troubleshooting.
It takes about a minute:
git clone https://github.com/CocoaBeans/mimamori.git
cd mimamori
MIMAMORI_SIGN=adhoc Scripts/make-app.sh release
open build/Mimamori.app
A build of your own never checks for updates. Or Scripts/run.sh for a debug build that opens immediately. You can also run swift run Mimamori, or open Package.swift in Xcode and run the Mimamori scheme. Notifications only work from the app bundle.
MIMAMORI_SIGN=adhoc signs the app ad hoc, since the Xcode project is set up for the maintainer's team; the first launch may ask you to confirm in System Settings › Privacy & Security, or you can right-click the app and choose Open. An ad-hoc build's desktop widget cannot read Mimamori's data, which needs a team-signed App Group; CONTRIBUTING explains how to build with your own team.
Scripts/simulate-agent.sh
This creates a throwaway repository, prints its path, and then edits, adds, deletes and commits files every few seconds, including a few changes that should be flagged. Add the printed path to Mimamori and watch.
MimamoriCore (Sources/MimamoriCore) is a UI-free library: a typed git client, parsers for status --porcelain=v2, diff --raw, --numstat and unified patches, an FSEvents wrapper, the attention heuristics, an intraline diff, the activity differ, and the WorktreeInspector actor. The SwiftUI app in Sources/Mimamori is a thin shell over it.
Shadow index. Each worktree gets a private git index file in ~/Library/Application Support/Mimamori/index/. Every refresh runs git add -A against that file (with GIT_INDEX_FILE), which mirrors the working tree, honours ignore rules, and never touches the repository's own index. git diff --cached <baseline> against the shadow index then yields a complete, rename-aware diff that includes untracked files. Untracked files above a size limit are skipped and listed in the header.
No lock contention. Every git invocation runs with --no-optional-locks, so git status from Mimamori never writes the repository index and never races an agent for index.lock.
Discarding is the one write to a working tree, and only on your word. Each discard and Undo checks whether an agent in the worktree is mid-turn as it starts and again just before it writes, whatever the window showed, and is refused if one is. A hunk's patch is built from the two blobs its diff compared, not from the diff's text, so carriage returns, a missing final newline and text that is not UTF-8 come through intact, and the parsed hunk must match the blobs line for line. Mimamori splices the hunk's lines into the file itself rather than letting git apply write it, since git's line-ending and filter settings could rewrite the whole file; a file those settings change is refused instead. git apply --check -R against the working tree (never the index, with apply.whitespace and apply.ignoreWhitespace pinned) has the last word before the write. A whole file gets HEAD's blob as git would write it. Undo writes the earlier bytes back when the file is as the discard left it, and otherwise uses git apply to fit a hunk back where its surroundings still match.
Snapshots are commit-tree objects (parent: current HEAD) referenced from refs/monitor/<id>/snapshots/<unix-ms>, plus refs/monitor/<id>/start for the Start marker. They show up in git log --all, never in git branch, and are not pushed by a normal . Remove them from Settings, or by hand:
Without any setup, Mimamori detects agent processes by their working directory and shows them on the worktree they sit in. That tells you an agent is there and whether it is busy, nothing more.
Hooks tell Mimamori what the agent is doing. Settings › Agents › Hooks › Install Hooks adds entries to ~/.claude/settings.json for Claude Code's SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch, PermissionRequest, PermissionDenied, Notification, Stop, StopFailure, SubagentStop and SessionEnd events. Each entry runs:
curl -s -m 1 -H 'Expect:' -H 'Content-Type: application/json' \
--unix-socket "$HOME/Library/Application Support/Mimamori/mimamori.sock" \
--data-binary @- http://mimamori/event >/dev/null 2>&1 || true
A timestamped backup of the settings file is written first, hooks you already have are kept, and Uninstall Hooks removes only Mimamori's entries. When a newer Mimamori listens to events an older one did not install, it adds them at launch the same way (backup first).
A tool call ends in PostToolUse when it succeeds, PostToolUseFailure when it fails (a command exiting non-zero) or is interrupted, and only PostToolBatch when you deny it; a turn ends in Stop, or StopFailure after an API error.
Claude Code sends nothing between your approval of a command and the command finishing, so while a shell command waits on your permission Mimamori looks for the shell Claude Code starts for it (every 1.5 seconds, only while one waits). Once that is running, the agent shows as working instead of still needing you through a long build or test run. When Mimamori is not running the command fails silently in under a second, so agents are never slowed down.
Hooks only report what happens while Mimamori runs: a session sitting at its prompt when Mimamori starts sends nothing until you next use it, and one started before the hooks were installed never sends anything. For those, Mimamori reads the small file Claude Code keeps for each running session in ~/.claude/sessions/<pid>.json (read only) and shows whether it is working, ready or waiting on you, until the session's first hook event takes over. A turn already under way when Mimamori starts is timed from when that file says the session went busy, not from the first step Mimamori hears, so its length and summary are right; the steps before Mimamori started don't show. The same file ties a Claude Code process to its session, so a session that moves into another worktree (say, from the repository into one of its worktrees) shows there, process and all, instead of leaving a bare process behind in the folder it started in. Events are kept in memory only; nothing is written to disk and nothing leaves the machine.
Hermes Agent is supported the same way:
~/.hermes/agent-hooks/mimamori.sh and appends a delimited hooks: block to ~/.hermes/config.yaml (backup first). If your config already has a hooks: key, Mimamori copies the entries to the clipboard for you to paste instead.hermes --accept-hooks once in a terminal (or plain hermes and answer each prompt), then open a new session; Settings › Agents › Hooks shows how many of Mimamori's hooks are still waiting for approval.http://mimamori/event/hermes and are translated from Hermes's names (pre_tool_call, pre_approval_request, on_human_input_request, pre_verify, …).on_session_end at the end of every turn, not only when you quit, so Mimamori takes it as the turn ending: the session stays on its card, ready for your next prompt, for as long as its Hermes runs.execute_code only by its key (agent:main:local:dm:…), so Mimamori looks that key up in state.db (read only) to show the request on its session's card.~/.hermes/state.db (read only) and shows the session there from its first event; a file it changes in another worktree moves it there.Claude Code hands work to subagents (an Explore agent searching the code, a reviewer, a fetcher), often in the background. Its hooks say which subagent took each step and what each was asked, so Mimamori follows them:
.claude/worktrees/agent-…) stays on its parent's card rather than showing as an agent of its own there; its line names that worktree, and a click opens it, where its changes are to review.<task-notification>, which Mimamori reads as that task finishing, and shows as the turn's task in plain words.Two agents working in the same folder can undo each other's work. Mimamori keeps each session apart, by the session_id its hooks send (subagents share their parent's), and counts an agent process that no session accounts for once two scans have seen it and no other agent there started it. A session counts while it is live: it has not ended, and it reported in the last 10 minutes or its process still runs. A session that ended still accounts for the process it ran in, so that process isn't counted as another agent while it keeps running.
When a worktree has more than one live agent:
transcript_path in its hooks), read only.Any agent that can run a command when something happens can report to Mimamori the same way. Settings › Agents › Other Agents shows what to paste into Codex (~/.codex/hooks.json), Gemini CLI (~/.gemini/settings.json) or Cursor (~/.cursor/hooks.json), with a Copy button, and lists every agent that has reported since Mimamori started, so you can see a new setup work. Mimamori never edits those files; remove the entries yourself to stop. Every setup runs the same command, with the agent's name in the address:
curl -s -m 1 -H 'Expect:' -H 'Content-Type: application/json' \
--unix-socket "$HOME/Library/Application Support/Mimamori/mimamori.sock" \
--data-binary @- 'http://mimamori/event?agent=codex' >/dev/null 2>&1 || true
The hook's JSON goes in on standard input, as each agent writes it. Mimamori understands each agent's own names for the same moments and maps them onto one set:
| Moment | Claude Code, Codex | Gemini CLI | Cursor | Hermes | Plain name |
|---|---|---|---|---|---|
| Session opened | SessionStart | SessionStart | sessionStart | on_session_start | session_start |
| Prompt sent, turn starts | UserPromptSubmit | BeforeAgent | beforeSubmitPrompt | pre_llm_call | turn_start |
| About to use a tool | PreToolUse | BeforeTool | preToolUse | pre_tool_call | tool_start |
| Tool finished | PostToolUse | AfterTool | postToolUse | post_tool_call | tool_end |
| Tool failed | PostToolUseFailure | AfterTool with an error | postToolUseFailure | tool_failed | |
| Waiting for your permission | PermissionRequest | Notification (ToolPermission) | pre_approval_request | needs_permission | |
| Asked you something | Notification | on_human_input_request | question | ||
| Turn finished | Stop, Codex's Interrupt | AfterAgent | stop | pre_verify | turn_end |
| Turn failed | StopFailure | stop with status error | turn_failed | ||
| Session closed | SessionEnd | SessionEnd |
For an agent of your own, use the plain names with the fields Claude Code uses: {"event": "tool_start", "session_id": "…", "cwd": "/path/to/worktree", "tool_name": "Edit", "tool_input": {"file_path": "src/a.py"}}. file_path and command may also sit at the top level, and message carries a question's text. The address takes two optional parameters:
agent=<name> names the agent. Without it, Mimamori asks macOS which process is on the other end of the socket and walks up from there to the agent that ran the hook (the same detection as above, including names you added), then falls back to what the event's names suggest.event=<name> names the event, for an agent whose hook sends no JSON or names its events in a way Mimamori does not know. An empty body is fine with it: curl … --data-binary '' 'http://mimamori/event?agent=aider&event=turn_end'. Without a cwd in the payload, Mimamori uses the hook command's working directory.What Mimamori can show depends on what an agent sends: start and end of a turn give working and ready; tool events add each step and which edit changed which file; permission and question events make it count as needing you, with notifications. Without a session id, a worktree holds one session at a time.
When Claude Code asks permission for a tool call, Mimamori shows Allow and Deny in the agent's banner (and the toolbar pill's popover), on its card in the Agents window, and in a notification with the same buttons. Claude Code shows its own prompt at the same time and takes whichever answer comes first, so you can answer in either place. An allowed call goes ahead as if you allowed it in the prompt; a denied one tells Claude no, and it carries on without that step. An allow never gets past a deny rule in your Claude Code settings. When Claude asks you questions (its AskUserQuestion tool, one or several, each with options), the card shows them in place of Allow and Deny: choose an option for each, or several where it allows, or write your own answer, then Answer. Mimamori hands the answers back through the same hook, as answering in Claude Code's prompt would. To answer only in Claude Code's own prompt, Dismiss takes Allow and Deny away without answering; the agent shows as "needs permission in its own prompt" until it moves on.
In the Claude desktop app, Claude Code can wait for this hook before it shows its own prompt (in bypass permissions mode, for a call it asks about anyway), so until the hook answers, the app's own Allow and Deny do nothing. Mimamori holds a request from the desktop app for 20 seconds only, then leaves it to the app's prompt: its own buttons go, and the agent shows as "needs permission in its own prompt" until it moves on. Requests from Claude Code in a terminal are held for up to ten minutes, as both prompts work at once there.
This works through Mimamori's PermissionRequest hook. Unlike its other hooks, this one waits for an answer and prints it:
curl -s -m 595 -H 'Expect:' -H 'Content-Type: application/json' \
--unix-socket "$HOME/Library/Application Support/Mimamori/mimamori.sock" \
--data-binary @- http://mimamori/permission 2>/dev/null || true
Answer by itself if I don't (off by default, in the same pane) answers a request you haven't answered after a wait you choose (10 seconds to 5 minutes), with Allow or Deny. Each request shows a countdown ("Allowing in 23 s") with a Wait button that stops it for that request, and the notification says so too. It never allows a call that matches an ask rule in your Claude Code settings (~/.claude/settings.json, or the project's .claude/settings.json and settings.local.json): those always wait for you, and say which rule held them. Rules are matched as Claude Code writes them (Bash(git push *--force*), Bash(npm publish:*), a bare tool name), a compound command part by part; a rule for another tool with a specifier holds every call of that tool. A command is checked whole up to 64 KB; one longer than that can't be checked in full, so it isn't allowed by itself either. Every answer given in Mimamori, by you or by itself, goes in the activity feed.
Mimamori lets a request go with no decision as soon as the session moves on (you answered in the prompt, the step ran or was refused, the turn ended), and when you turn Answer permission requests in Mimamori off in Settings › Agents › Hooks. Claude Code sends no hook when you answer in its own prompt, so Mimamori reads the answer from what follows: a command you allowed shows up running within a second, and the agent shows as working on it; when you say no, Claude Code ends the waiting hook, and Mimamori takes its Allow and Deny away at once and shows the agent ready for your next prompt. With no decision, or Mimamori not running, the hook prints nothing and Claude Code's prompt decides. Hooks installed by an earlier Mimamori are updated at launch, with a backup of the settings file first; sessions already running keep the hooks they started with.
When an agent starts work in a git repository Mimamori doesn't watch, Settings › Agents › Hooks › New Repositories decides what happens:
Only git repositories are offered: never your home folder, temporary folders, or the dot folders agents keep in your home (~/.hermes, ~/.claude). A folder that isn't a repository yet is asked about again 30 seconds later, so a project the agent runs git init in still gets offered. A new worktree of a repository you already watch is picked up at once rather than offered.
An agent without hooks can add a repository it creates itself. Put this line in your CLAUDE.md or AGENTS.md; the same pane has a Copy button for it:
When you create a new git repository, run `open -g -a Mimamori "<path>"` so Mimamori watches it.
open -g adds the repository without bringing Mimamori to the front, and starts Mimamori if it isn't running. A folder that is no repository gets a notification instead of an alert, so nothing waits on screen.
When an agent doesn't show up, or shows up without saying what it is doing, open Settings › Agents › Hooks (or Help › Troubleshoot Agent Hooks…):
MIMAMORI_SUPPORT_DIR points somewhere deep. Every copy of Mimamori uses the same socket, so while two run at once (a build of your own and the one in Applications, say) one listens and the other says another copy has the socket. It takes over within a few seconds of that copy quitting./event) or sent something Mimamori could not read as an event. The row lists the JSON's top-level keys, never their values, so you can compare it with the setup in Other Agents.hermes --accept-hooks once.Mimamori also writes to the macOS log, under the subsystem com.kevinross.Mimamori in the categories hooks, agents, widgets and state:
log stream --predicate 'subsystem == "com.kevinross.Mimamori"' --level info
log show --last 1h --info --predicate 'subsystem == "com.kevinross.Mimamori"' shows the past hour. The log hides folders as <private>; Recent Events shows them.
To report a problem, click Copy Diagnostics (in the same pane, or Help › Copy Diagnostics) and paste it into the issue. It lists versions, the socket, which hooks are installed, which agents reported and the recent events with what became of each. It never includes prompts, commands or file contents, and folders under your home appear as ~, but it does name your worktrees' folders, so look it over before you post it.
Add the Agents widget from the desktop's Edit Widgets menu (or Notification Center) to see your agents without opening Mimamori, the one that needs you first:
The widget is an app extension running sandboxed in its own process. Mimamori writes what it shows to widgets.json in the App Group container ~/Library/Group Containers/555LXJXR4U.com.kevinross.Mimamori/ and asks WidgetKit to reload: at once when an agent starts or stops needing you, otherwise at most every 10 seconds, plus every 15 minutes so the widget can tell a quiet Mimamori from one that has quit. When Mimamori quits, or has written nothing for 45 minutes, the widget says Mimamori isn't running rather than show stale states. Timers ("Waiting 2:03") tick on their own between reloads.
Mimamori on your iPhone shows your agents while it's on the same network as your Mac, grouped and drawn as the Agents window draws them, with Details for each one's task, steps and files.
The iPhone app isn't on the App Store yet: build it from Mimamori.xcodeproj with the MimamoriCompanion scheme onto your iPhone (iOS 18 or later), signed with your own team. Then:
_mimamori._tcp) whenever both are on the network, and the Mac sends what changes, on the widgets' rules.Each paired iPhone has a key of its own, a random 32 bytes that only it and the Mac keep, each in its Keychain. Connections are TLS with that key (TLS 1.2 with a pre-shared key), so only a paired iPhone gets in, and nobody else on the network can read what is sent. Remove… in Settings › iPhone deletes a phone's key and cuts it off. Tasks and steps reach the iPhone only when Show tasks and steps in widgets is on; the command a permission request asks to run goes whatever that says, as it does in the status line, so you can see what you'd allow.
Tap an agent on the iPhone for its list of actions: the steps of the turn under way, newest first, then the turns before it under their prompts, each with its target, when it ran, how long it took and whether it failed or waits on you. The iPhone asks for them when the list opens; the Mac sends the leading session's last 200 steps, grouped under the turns the activity feed recorded, and again as they change.
The iPhone notifies you as the Mac does, when an agent needs you, finishes a turn or collides with another, working out what changed from one snapshot to the next; its gear chooses which. Those come while the app is open, or always with Stay connected in the background on: the app then keeps running in the background by looping one silent sample, mixed with your own audio, so it stays connected. App Review allows background audio only for audio you can hear, so that, and the audio background mode in Resources/MimamoriCompanion-Info.plist, are for builds of your own; an App Store build has to leave them out. On a stand beside your Mac, Keep the screen on stops the iPhone dimming and locking while the app is open, by default only while it's charging.
With Answer permission requests in Mimamori on (Settings › Agents), you can answer Claude Code's permission requests from the iPhone too. The agent's card shows the tool and the exact command or file, Allow and Deny, and the Mac's countdown if it answers by itself; the "needs you" notification has the same two actions. Allow runs a command on your Mac, so it asks for Face ID or the passcode first (on a notification, the iPhone must be unlocked); Deny is one tap. When Claude asks you questions (its AskUserQuestion tool), the card shows them with their options, as the Mac does, and Answer asks for Face ID or the passcode too, since an answer such as "Publish now" can set off as much as Allow; the notification for a question has no buttons and opens the card. A phone with an older Mimamori can't answer questions: the Mac turns down its Allow for one, and takes its Deny. The answer goes over the paired connection, and the Mac acts on it only if it comes from a paired iPhone and the request still waits. Which iPhone counts too: TLS shows only that a phone holds one of the paired keys, so on each connection the Mac sends a fresh random challenge, and the iPhone answers with an HMAC-SHA256 of it under its own pairing's key; until that proof holds, its answers are turned down, and the Mac doesn't take its name or, for an iPhone pairing now, finish its pairing, so one paired iPhone can't answer or pose under another's name. An answer to a request already settled, on the Mac or in Claude Code's own prompt, changes nothing, and the iPhone says so. The activity feed records it, as "Allowed on iPhone's name: Bash". If the iPhone can't reach the Mac, it says so rather than pretending the answer went. Only Claude Code hands Mimamori its prompts, so for other agents the iPhone just says they need you. The iPhone app is built from Sources/MimamoriCompanion in the same project; see the plan for what comes next.
Mimamori talks to nothing but your local git, and a Unix-domain socket that only your own processes can reach (the socket file is readable and writable by you alone). It has no telemetry. With the iPhone companion on, it also listens on your local network for the iPhones you paired, and only those, over TLS keyed by each one's own key. The one exception is updates, in release builds only: Sparkle fetches the list of releases (appcast.xml) from GitHub when you choose Mimamori › Check for Updates, or once a day if you said yes when it asked, the second time you opened Mimamori. Nothing about you or your Mac goes with it. A new version is checked against Mimamori's signing key before it is installed. Builds you make yourself never check. On disk it writes:
~/Library/Application Support/Mimamori/: settings, the list of watched repositories, review marks, the shadow index files, and activity.jsonl, the activity feed (one JSON entry per line, kept for the number of days you choose). With the iPhone companion, companion.json lists the paired iPhones.com.kevinross.Mimamori.companion).refs/monitor/… inside watched repositories, for snapshots (see above).~/.claude/settings.json, only when you connect Claude Code (Install Hooks, or Connect in the welcome window) or a newer Mimamori adds events to hooks you installed, with a backup beside it. For Hermes, ~/.hermes/agent-hooks/mimamori.sh and a marked block in ~/.hermes/config.yaml, likewise backed up. Mimamori keeps its newest five backups of each file; Settings › Agents › Hooks shows them.~/Library/Caches/com.kevinross.Mimamori/ until it is installed.macOS gives an app no word when it is deleted, so remove what Mimamori added first: Mimamori › Uninstall Mimamori… (also in Settings › Agents › Hooks) lists each thing, with a checkbox, then removes them and quits:
refs/monitor/ in the repositories you watch (your branches and commits are untouched);Agent sessions already running keep the hooks until they restart; the commands fail silently in the meantime. If you deleted the app first, remove Mimamori's entries (the commands mentioning mimamori.sock) from ~/.claude/settings.json or with an older copy's Settings › Agents › Hooks, then:
rm -rf ~/Library/Application\ Support/Mimamori ~/Library/Caches/com.kevinross.Mimamori
defaults delete com.kevinross.Mimamori
# in each repository Mimamori watched:
git for-each-ref --format='%(refname)' refs/monitor/ | xargs -n1 git update-ref -d
Open Mimamori.xcodeproj in Xcode to build, run and sign the app: the Mimamori app target over a MimamoriCore static framework, signed automatically with the project's team. Both targets use folder-synchronized groups, so a Swift file added under Sources/Mimamori or Sources/MimamoriCore is part of the build without editing the project.
Scripts/make-app.sh # build/Mimamori.app via xcodebuild, team-signed (debug|release)
MIMAMORI_SIGN=adhoc Scripts/make-app.sh # signed ad hoc, for a Mac without the team's certificate
Scripts/make-apps.sh # the Mac app as above, plus the iPhone companion installed on your iPhone; opens both
Scripts/make-apps.sh --list # the iPhones it can install on (--device <name or id> picks one of several)
swift test # parsers, rules, differ, plus integration tests against real git
swift build # the same app as a Swift package, for `swift run` and the debug hooks
make-apps.sh installs the companion on the one iPhone that's ready: Developer Mode on, unlocked, and on the cable or the same Wi-Fi. The first build registers the iPhone and the app's ID with the team, which an iPhone app needs; it can't be signed ad hoc. --no-mac, --no-phone and --no-launch leave out a part.
make-app.sh stamps the commit it builds from into the app (MimamoriCommit in Info.plist, passed to xcodebuild as MIMAMORI_COMMIT), with -dirty when tracked files have uncommitted changes. About Mimamori shows it under the version, linked to the commit, and Copy Diagnostics includes it. A build made straight from Xcode has none.
The icon, a paper lantern (chōchin) with 見 ("to watch", the first character of 見守り) painted on it, lighting the code behind it, where a diff's hunk shows a line taken out and two put in, is drawn from shapes by Scripts/make-icon.swift, which writes every size into Resources/AppIcon.icns. Small sizes keep fewer, bolder lines and leave out the character. The same script draws the iPhone companion's icon edge to edge, opaque, into its asset catalog, for iOS to round the corners. After changing the drawing, regenerate both and rebuild:
swift Scripts/make-icon.swift
Mimamori's own captures (MIMAMORI_SCREENSHOT, below) draw the window into an image, which flattens glass and vibrancy. For README images, Scripts/capture-window.sh captures a window as it looks on screen, shadow included, with ScreenCaptureKit. It builds Tools/WindowCapture into build/WindowCapture.app and runs it as an app of its own, so the Screen Recording permission goes to it alone, not to your terminal or editor. On the first run, allow WindowCapture in System Settings › Privacy & Security › Screen & System Audio Recording, then run the command again.
Scripts/capture-window.sh list
Scripts/capture-window.sh shot --app com.kevinross.Mimamori --out docs/shot.png
Scripts/capture-window.sh record --pid 1234 --out demo.mov --until /tmp/stop --seconds 60 --no-shadow
shot keeps the window at full Retina resolution with its shadow around it (--no-shadow leaves it out). record writes a video until --seconds pass or the --until file appears. While a window is recorded, macOS shows a "being shared" indicator in place of its traffic lights, so paint the buttons back over that corner when making a GIF, using the recording's own pixels (patching from a shot comes out a shade lighter). A recording keeps the window's starting size, so make the window big enough not to grow while it runs; the tool warns if it does.
Environment variables for driving the app headlessly, useful on machines without screen-recording permission. They exist only in debug builds (Xcode's Debug configuration, Scripts/make-app.sh debug, swift build and swift run): a release build, such as Scripts/make-app.sh makes by default, leaves DebugHooks.swift and every MIMAMORI_DEBUG_* switch out, so none of them can be set on an installed copy. MIMAMORI_SUPPORT_DIR is the exception and works in every build, for running a second copy against separate state.
| Variable | Effect |
|---|---|
MIMAMORI_SCREENSHOT=/path.png | Write the main window (and any popover) to PNG after launch |
MIMAMORI_SCREENSHOT_INTERVAL=5 | Rewrite it (and the dump) every 5 seconds |
MIMAMORI_DEBUG_DUMP=/path.txt | Write a text summary of repositories, sessions, changes and activity |
MIMAMORI_DEBUG_SELECT=src/app.py | Select that file in the selected worktree once it appears |
MIMAMORI_DEBUG_REVIEW=README.md,src/app.py | Mark those files in the selected worktree reviewed once they appear |
MIMAMORI_WINDOW=1000x640 | Resize the main window after launch (clamped to the content minimum) |
MIMAMORI_DEBUG_FLAGS=popover:sidebar,cycle | Comma-separated switches for scripted runs, listed below |
MIMAMORI_QUIET_AFTER=5 | Call a working agent quiet after 5 seconds without a tool call, instead of three minutes |
MIMAMORI_SCREENSHOT_WINDOW=Agents | Capture the window with that title instead of the main window |
MIMAMORI_SUPPORT_DIR=/tmp/mimamori-scratch | Keep state, shadow indexes and the hook socket in that folder, leaving your real setup alone |
The switches in MIMAMORI_DEBUG_FLAGS. Renders are written next to the dump, in light and dark, since live captures lose vibrant colours:
| Flag | Effect |
|---|---|
popover:sidebar | Open the attention popover |
cycle | Cycle the selection through all worktrees |
toggle-review | Press ⌘↩ once on the selected file |
agents-window | Open the Agents window |
summary | Open the session summary and write it next to the dump |
widgets | Render every widget size |
open-runs | Open every agent run in the activity feed |
menu-bar | Open the menu-bar popover and render it |
status-pill | Open the toolbar pill's popover and render it |
sidebar | Render the sidebar rows that have agents or changes |
diff-line:<n> | Scroll the selected file's diff to new-file line n |
note-line:<n> or note-line:<n>-<m> | Start a note on new-file lines n to m of the selected file, as their bubbles do |
discard:<n> or discard:file | Ask to discard hunk n, or the whole file, of the selected file, as their buttons do (with discarding on in Settings); add :confirm to go ahead 2 s later |
discard-undo | Press Undo once, 3 s after a discard |
sleep | Ask to put the selected worktree's agents to sleep; sleep:confirm goes ahead 2 s later |
wake | Wake the selected worktree's sleeping agents, 3 s after they sleep (opens a real terminal) |
about | Open the About window |
companion | Turn the iPhone companion on, whatever Settings › iPhone says |
companion:pair | With companion, make a pairing code as Pair iPhone… does, and write its link to <dump>.companion-pairing.txt, for a simulator to open (xcrun simctl openurl booted <link>). Use a scratch setup: the link holds the pairing's key |
Example: open --env MIMAMORI_SCREENSHOT=/tmp/mimamori.png build/Mimamori.app
Debug builds of the iPhone app take launch arguments too, for a simulator (xcrun simctl launch booted com.kevinross.Mimamori.Companion <arguments>):
| Argument | Effect |
|---|---|
-pairingLink <link> | Pair as opening that link does, such as the one companion:pair writes |
-debugAnswer allow | Answer each permission request that appears, as its card's Allow does, Face ID included (deny for Deny), after -debugAnswerDelay <seconds> (2 by default). In a simulator, Features › Face ID › Matching Face, or xcrun simctl spawn booted notifyutil -p com.apple.BiometricKit_Sim.pearl.match once enrolled, gets past Face ID |
-debugAnswerVia notification | With -debugAnswer, answer as a notification's action does instead, after letting go of the Mac as iOS suspending the app would, so it has to reconnect first |
-debugProof withhold | Send no proof of the pairing when the Mac challenges, so the Mac turns down this run's answers (other: a proof for another pairing id, turned down too) |
-notifyNeedsYou NO | Turn a notification switch off for this run (-notifyCollision NO, …), so a simulator doesn't ask to post notifications |
Scripts/release.sh archives Mimamori, exports it signed with Developer ID (the app and its widget extension, each with its entitlements and the hardened runtime), notarizes and staples it, then packs it into build/Mimamori-<version>.dmg, notarized and stapled in turn. It signs the DMG for Sparkle, writes build/appcast.xml offering it, and prints the DMG's SHA-256 for the Homebrew cask (Casks/mimamori.rb in CocoaBeans/homebrew-tap: set its version and sha256) and the gh release create command that publishes both.
MARKETING_VERSION, CURRENT_PROJECT_VERSION), which both Info.plists read. Scripts/set-version.sh 1.0.1 sets the next version and raises the build number by one; Sparkle compares build numbers, so every release needs a higher one. Run it with no version to see the current one.ASC_KEY_ID and ASC_ISSUER_ID (the key file is read from ~/.appstoreconnect/private_keys/).MimamoriReleaseBuild, which turns Sparkle on. The DMG is signed with Sparkle's EdDSA key, kept in the login keychain under the account mimamori (made once with Sparkle's generate_keys --account mimamori; its public half is SUPublicEDKey in Resources/Info.plist). Back that key up with generate_keys --account mimamori -x <file>: without it, no installed copy will accept another update. The tools come with the Sparkle package, in build/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin/.CHANGELOG.md keeps the short record of every release in the Keep a Changelog format. Pull requests add lines under Unreleased. Before a release, rename that section to the version and date, and start a new empty Unreleased above it. A release refuses to start without the version's section.docs/releases/<version>.md (or wherever RELEASE_NOTES points); a release refuses to start without them. The printed gh release create command puts them on the release page. Scripts/release-notes.sh renders them with GitHub's Markdown renderer, leaving out the section headed Install, and the appcast embeds the result. So the update window shows the notes alone, styled for light and dark, rather than the whole GitHub page. Its version history links to the releases page.https://github.com/<owner>/mimamori/releases/latest/download/appcast.xml (MIMAMORI_FEED_URL), so each GitHub release carries the DMG and appcast.xml, tagged ; the newest release's appcast is the one every copy sees.The roadmap lists what is planned. The larger pieces have plans of their own in docs/plans:
An idea not planned yet: a timeline scrubber across snapshots.
Issues and pull requests are welcome. CONTRIBUTING covers building with your own signing team, where code belongs, tests, and how to report a security problem privately.
git pushgit for-each-ref --format='%(refname)' refs/monitor | xargs -n1 git update-ref -d
Settings › Snapshots shows how much disk space the selected worktree's snapshots use on their own: the objects no branch, tag, other ref, worktree HEAD or index keeps (git rev-list --objects-edge-aggressive --disk-usage, git 2.38 or later). Deleting the snapshots frees that space the next time git gc prunes unreachable objects, which by default waits about two weeks.
Review marks record, per file, the blob you saw (from the shadow index). Comparing that against the current blob tells reviewed, unreviewed and changed-since-review apart, and git diff <reviewed blob> <current blob> gives the since-review diff. Marks live with the worktree's settings in state.json; blobs nothing else references are eventually pruned by git gc, after which only the full diff is available.
Review notes are kept beside the marks in state.json. Each quotes the lines it was written on and records their numbers on the new side, or the old side for removed lines. When the full diff loads, a note looks for its quoted lines among the diff's lines, taking the copy nearest its old place, and is outdated when they are gone.
Refresh pacing. File events are debounced (default 400 ms) with a maximum delay (default 2.5 s) so continuous churn still refreshes. Changes inside folders git ignores (.build, node_modules) are recognized once and skipped from then on, so a build does not set off refreshes. File events are reliable, so the quiet, staggered backstop poll runs only every 10 minutes per worktree, plus once for every worktree when the Mac wakes from sleep.
sessionEndon_session_end |
session_end |
permission:allow | Answer a waiting permission request once it has waited 5 s, as Allow does (permission:deny for Deny) |
hook-test | Send a test event through the hook command once, as Send Test Event does; the result and the recent events go in the dump, and Copy Diagnostics' report in <dump>.diagnostics.txt |
sidebar:show | Unfold the main window's sidebar at its usual width; the window remembers it folded across launches |
welcome | Open the welcome window |
help:<topic> | Open the Help window on that topic (getting-started, agents, shortcuts, … as in HelpTopics.swift); help alone opens it on the first |
appearance:light | Draw every window light, whatever the system's setting (appearance:dark for dark), for live captures of both |
resize:<w>/<w>/… | 8 seconds after launch, drag the window's width through these sizes in points, a frame at a time as a person would, holding 1.5 s at each. It takes the window MIMAMORI_SCREENSHOT_WINDOW names; with MIMAMORI_SCREENSHOT set, each width is captured just after the drag and once settled, as <screenshot>.resize-<n>a.png and <screenshot>.resize-<n>b.png |
hooks:raw | Write every hook request's body, as the agent sent it, to <dump>.hooks.jsonl, one line each with the time and the address it was posted to: for seeing exactly which events and fields an agent sends. The file holds commands, prompts and paths, so delete it when done |
toggle-review:repeat | Press ⌘↩ on the selected file every 1 to 3 seconds, as someone reviewing while an agent writes would, for chasing a hang with watchdog |
watchdog | Watch the main thread from a thread of its own: when it hasn't answered for a second, log the time to <dump>.watchdog.txt and record a 3-second sample of the app to <dump>.hang-<n>.txt, then log how long it took to answer. For hangs that come and go |
uninstall | Open the Uninstall Mimamori window |
uninstall:run | Run Uninstall with its default choices, keeping the app out of the Trash; its report goes in <dump>.uninstall.txt. Point it at a scratch setup (MIMAMORI_SUPPORT_DIR, CLAUDE_CONFIG_DIR, HERMES_HOME) |
settings:<pane> | Open Settings on that pane; with settings:agents, agents:hooks, agents:other, agents:widgets or agents:notifications opens that part of it |
v<version>RELEASE_DRY_RUN=1 Scripts/release.sh checks the archive, the entitlements, the DMG and the appcast with the everyday development signing, without notarizing. Add RELEASE_FEED_URL=http://127.0.0.1:8765/appcast.xml to build a copy that looks for updates there instead, with the DMG offered beside the feed: build one version, then a higher one, serve the second's DMG and appcast with python3 -m http.server 8765, and the first finds and installs it.