Multi-agent activity audit. Logs every tool call from Claude Code, Gemini CLI, and Qwen Code to a SQLite database — separated by agent. Optionally audits Claude Desktop's MCP traffic via the same binary running as a long-running stdio proxy that writes to the same DB.
A set of /fluxmirror:* slash commands (installed by the Claude Code /
Qwen Code plugin and the Gemini CLI extension) turns the SQLite data
into daily, weekly, or per-agent reports.
Two binaries. Phase 1 + 2 shipped the fluxmirror binary — a
single statically-linked Rust program with a kubectl-style subcommand
surface (fluxmirror hook, fluxmirror proxy, fluxmirror init,
fluxmirror today, fluxmirror week, fluxmirror doctor, …) and zero
runtime dependencies (SQLite bundled). Phase 3 (in flight) adds a
second, opt-in binary — fluxmirror-studio, a local web dashboard
on 127.0.0.1:7090 that reads the same SQLite file in read-only mode.
You can run capture only, dashboard only, or both; the only thing they
share is the DB file. A small cross-shell wrapper layer
(wrappers/{shim.sh, shim.mjs, shim.cmd, router.sh}) auto-downloads
the per-arch capture binary on first invocation and execs it on every
call — first call is one-time ~1-2 s, every call after is ~30 ms.
The studio binary is installed and invoked directly by the user, never
through the wrapper layer.
| # | Output | Status | Notes |
|---|---|---|---|
| 1 | Nightly journal | ✅ shipped | /fluxmirror:today and fluxmirror today produce a one-page activity report in your chosen language. Also available as HTML via --html. |
| 2 | Usage tracker | 🟡 partial | Tool-call counts, per-agent and per-tool breakdowns shipped via /fluxmirror:agents and /fluxmirror:week. Estimated API cost lands in Phase 3 M6. |
| 3 | Weekly digest | ✅ shipped | /fluxmirror:week --html produces the shareable HTML card with summary, daily breakdown, highlights, and insights. |
| 4 | Local web dashboard | 🚧 in flight | Phase 3 ships the fluxmirror-studio binary — provenance per file, time-machine replay, auto-named work sessions, cost overlay. Opt-in install. |
| 5 | AI synthesis layer | 🚧 in flight | Phase 4 adds the fluxmirror-ai crate — provider abstraction (Anthropic + Ollama-stub), SQLite cache, daily USD budget cap, prompt registry, outbound redaction. Capture binary deps unchanged. |
| 6 | Anomaly alerts | 🗺 roadmap | No detection or gating ships in Phase 1 / 2 / 3. Events are captured but not analysed. Phase 4 lands heuristic anomaly stories on top of the AI layer. |
Legend: ✅ shipped · 🟡 partial (core function works; one named gap) · 🗺 roadmap (not yet implemented)
| Value | Status | Notes |
|---|---|---|
| Transparency | ✅ | Every tool call is logged to a queryable SQLite DB; CLI + HTML + (Phase 3) web dashboard surface it in plain language. |
| Control | 🗺 roadmap | No policy engine, no gating, no rate-limiting. Phase 4 introduces heuristic anomaly stories; FluxGate integration is Phase 5. |
| Memory | 🚧 in flight | Phase 3 adds provenance per file + time-machine replay + auto-named sessions, turning the raw history into something you actually browse. |
When you use multiple AI coding agents during a day, your activity is fragmented across each tool's local state. fluxmirror gives you a single queryable record per agent, with no cross-contamination — useful for daily journals, billing review, security audits, or just understanding how you actually work.
graph LR
CC[Claude Code] -->|hook| FB[fluxmirror hook]
GC[Gemini CLI] -->|hook| FB
QC[Qwen Code] -->|hook| FB
CD[Claude Desktop MCP] -->|stdio| FP[fluxmirror proxy]
FB --> AE[agent_events]
FP --> EV[events]
AE --> DB[(events.db)]
EV --> DB
DB --> CMD["/fluxmirror:* commands"]
CMD --> OUT[reports]
All four sources flow into a single SQLite database. The hook-driven
agents write to the agent_events table; fluxmirror proxy (the
stdio MCP relay used by Claude Desktop) writes to the events table.
The slash command surface queries both. See
docs/architecture.md for the layered model and
crate map; the four ADRs in docs/adr/ capture the design
decisions.
The agent label per row is determined automatically:
| CLI | agent_events.agent |
JSONL path |
|---|---|---|
| Claude Code | claude-code |
~/.claude/session-logs/ |
| Qwen Code | qwen-code |
~/.qwen/session-logs/ |
| Gemini CLI | gemini-cli |
~/.gemini/session-logs/ |
The Claude / Qwen distinction is detected at hook time via Qwen's
$QWEN_CODE_NO_RELAUNCH / $QWEN_PROJECT_DIR env signals.
Per-OS default DB path (created lazily on first write):
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/fluxmirror/events.db |
| Linux | ${XDG_DATA_HOME:-~/.local/share}/fluxmirror/events.db |
| Windows | %APPDATA%\fluxmirror\events.db |
The Rust binary itself has zero runtime dependencies — SQLite is statically linked. The only requirements are (1) an OS-provided shell runtime for the wrapper layer, and (2) at least one supported agent CLI to actually capture hooks from.
| What | Minimum version | Where it comes from |
|---|---|---|
| OS | macOS 11+ / Linux glibc 2.17+ / Windows 10+ (incl. WSL) | — |
| Shell runtime (one of) | bash + curl ≥ 7.50 or node ≥ 18 or cmd.exe + PowerShell ≥ 5.1 |
All preinstalled on every supported OS |
| Network access | One-time ~3 MB download on first hook fire | github.com/OpenFluxGate/fluxmirror/releases |
| Disk | ~3 MB binary + bounded SQLite DB | ~/Library/Application Support/fluxmirror/ (macOS), ${XDG_DATA_HOME}/fluxmirror/ (Linux), %APPDATA%\fluxmirror\ (Windows) |
| At least one agent CLI | Claude Code, Qwen Code, or Gemini CLI | See per-agent install below |
| Agent | Install | Hook event |
|---|---|---|
| Claude Code | https://claude.ai/code | PostToolUse |
| Qwen Code | npm install -g @qwen-code/qwen-code |
PostToolUse (reuses Claude plugin) |
| Gemini CLI | npm install -g @google/gemini-cli |
AfterTool |
| Claude Desktop (optional, MCP audit) | https://claude.ai/desktop | stdio MCP relay via fluxmirror proxy |
| What | Minimum version | Notes |
|---|---|---|
| Rust toolchain | stable, edition 2021 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
| C compiler | platform default | needed by rusqlite (bundled SQLite) — Xcode CLT on macOS, gcc on Linux, MSVC on Windows |
bash + node |
any | for bash -n wrappers/shim.sh and node --check wrappers/shim.mjs lint steps |
gh CLI |
optional | release tag pushes |
JVM, Gradle, Java, Python, jq — all removed in earlier phases. Neither end users nor developers need to install any of them.
Choose the agents you use.
/plugin marketplace add OpenFluxGate/fluxmirror
/plugin install fluxmirror@fluxmirrorDetails: plugins/fluxmirror/README.md.
Qwen accepts Claude marketplace plugins directly:
qwen extensions install OpenFluxGate/fluxmirror:fluxmirrorThe same plugin handles both. The hook auto-labels rows qwen-code
when running under Qwen.
Qwen's installer prompts
Do you want to continue? [Y/n]:and has no--yesflag. For non-interactive installs:echo y | qwen extensions install OpenFluxGate/fluxmirror:fluxmirror
gemini extensions install https://github.com/OpenFluxGate/fluxmirror \
--ref gemini-extension-pkg \
--consentAll three parts are required: the full https:// URL (the
owner/repo shorthand is not accepted), the --ref gemini-extension-pkg (the auto-published branch that contains
gemini-extension/* at the repo root so the installer finds the
manifest), and --consent (skips the interactive prompt). Details
and troubleshooting table:
gemini-extension/README.md.
Either build from source:
cargo install --path crates/fluxmirror-cliOr download the per-arch binary from the latest release:
| OS / arch | Asset |
|---|---|
| macOS Apple Silicon | fluxmirror-darwin-arm64 |
| macOS Intel | fluxmirror-darwin-x64 |
| Linux x86_64 | fluxmirror-linux-x64 |
| Linux ARM64 | fluxmirror-linux-arm64 |
| Windows x86_64 | fluxmirror-windows-x64.exe |
curl -L -o ~/fluxmirror \
https://github.com/OpenFluxGate/fluxmirror/releases/latest/download/fluxmirror-darwin-arm64
chmod +x ~/fluxmirrorFor Claude Desktop MCP audit, point claude_desktop_config.json at
~/fluxmirror proxy --server-name <name> --db <path> -- <real MCP server>. Snippet in
plugins/fluxmirror/README.md.
After installing any plugin / extension:
fluxmirror init # interactive (Tier A: language + timezone)
fluxmirror init --advanced # also asks retention / self-noise / per-agent
fluxmirror init --non-interactive --language=korean --timezone=Asia/SeoulThen trigger any tool call from Claude Code / Qwen Code / Gemini CLI.
The first successful insert writes a welcome.md next to the config.
Run fluxmirror doctor at any point to see a 5-component health
table.
Once data is flowing, use any of these inside any of the installed CLIs:
Reports
/fluxmirror:about explainer + auto-discovered command list
/fluxmirror:today today's report
/fluxmirror:yesterday yesterday
/fluxmirror:week last 7 days, daily breakdown
/fluxmirror:compare today vs yesterday side-by-side
/fluxmirror:agent <name> single-agent filtered report
(claude-code, gemini-cli, qwen-code)
/fluxmirror:agents per-agent 7-day totals + dominant tools
Configuration
/fluxmirror:init first-run interactive setup
(language, timezone, wrapper engine)
/fluxmirror:setup configure language and timezone
/fluxmirror:language set output language
/fluxmirror:timezone set timezone
/fluxmirror:config show / get / set / explain config
Health
/fluxmirror:doctor 5-component health table
Reports normalize tool names across both Claude PascalCase
(Edit/Write/Read/Bash) and Gemini / Qwen snake_case
(edit_file/write_file/read_file/run_shell_command), so a single
report covers all agents uniformly.
Layered, highest priority first:
CLI flags
> env vars
> project ./.fluxmirror.toml
> user config (~/.fluxmirror/config.json on macOS,
${XDG_CONFIG_HOME:-~/.config}/fluxmirror/config.json on Linux,
%APPDATA%\fluxmirror\config.json on Windows)
> inferred defaults
Useful environment variables:
| Variable | Effect |
|---|---|
FLUXMIRROR_DB |
Override DB path |
FLUXMIRROR_SKIP_SELF |
If 1, combined with FLUXMIRROR_SELF_REPO, skips events that look like fluxmirror querying its own DB from inside its own repo. Useful when self-developing fluxmirror so reports don't fill with self-noise. |
FLUXMIRROR_SELF_REPO |
Absolute path to the fluxmirror repo for the filter above. Anchored prefix match — adjacent dirs with similar names are not falsely filtered. |
Hook-side errors (e.g., DB locked) are appended to
~/.fluxmirror/hook-errors.log. The log auto-rotates when it exceeds
5 MiB (one backup .log.1 is kept), so disk usage stays bounded.
If you previously installed FluxMirror v0.5.x (the two-binary layout),
the v0.6 release publishes legacy fluxmirror-hook-<arch> and
fluxmirror-proxy-<arch> asset names as copies of the new single
binary, so existing wrappers continue to work. Run fluxmirror init
to migrate to the new wrapper layer (shim.sh / shim.mjs /
shim.cmd chosen automatically). The on-disk schema upgrades in
place: the v1 migration is purely additive (ALTER TABLE … ADD COLUMN) and runs automatically on the first connection.
If your installed plugin is older than v0.6.0, it will have cached a pre-subcommand binary under one of:
~/.claude/plugins/cache/fluxmirror/*/bin/
~/.qwen/extensions/fluxmirror/bin/
~/.gemini/extensions/fluxmirror/bin/
Evict that cache so the next hook fire downloads the v0.6+ binary:
rm -rf ~/.claude/plugins/cache/fluxmirror/*/bin
rm -rf ~/.qwen/extensions/fluxmirror/bin
rm -rf ~/.gemini/extensions/fluxmirror/binThe wrapper scripts always re-download on a missing cache, so the next agent tool call after the eviction will fetch the new binary transparently.
After installing on a new machine, confirm logs are isolated per agent at both the JSONL and SQLite layers:
./scripts/verify-isolation.shThe script runs five checks: JSONL file presence + counts per agent,
unique session IDs per JSONL file, cross-contamination across all 6
directional pairs, tool-name distribution per agent, and SQLite
agent_events isolation (no session_id shared across agents).
Expected: clean (0 session IDs cross over) for all checks.
For an at-a-glance per-agent row count:
fluxmirror sqlite --db "$(fluxmirror db-path)" \
"SELECT agent, COUNT(*) FROM agent_events GROUP BY agent"| Symptom | Resolution |
|---|---|
fluxmirror: command not found from a slash command |
Re-run fluxmirror init so the binary is on PATH for the shell that the slash command spawns. |
/fluxmirror:today reports "no events" |
Trigger any tool call in your agent first; confirm fluxmirror doctor shows database ok. A fresh install is also seeded with one demo row tagged agent='setup' so the very first report is non-empty. |
Extension "fluxmirror" already loaded |
A previous install left a *.backup.* directory next to the live one. Remove it: rm -rf ~/.gemini/extensions/fluxmirror.backup.* (or the matching ~/.qwen/extensions/ path). |
Qwen install completes but /fluxmirror:* commands never surface |
Confirm ~/.qwen/extensions/fluxmirror/qwen-extension.json exists. If it is missing, the package is from a pre-v0.6.0 build — re-install from the latest release. |
Gemini / Qwen show flat /today instead of /fluxmirror:today |
The /fluxmirror: namespace prefix comes from the commands/fluxmirror/ subdirectory. If your install has flat commands/*.{md,toml} files at the top level, the package shipped a flatten regression — re-install from the latest release. |
Gemini ran /fluxmirror:init non-interactively (skipped questions) |
The model raced past the question gate. Post-v0.6.0 init.toml puts the question block before any shell fence so the user must answer first; update the extension and retry. |
| Surface | Command |
|---|---|
| Claude Code | /plugin marketplace update fluxmirror then /reload-plugins (or enable auto-update under /plugin) |
| Qwen Code | qwen extensions update fluxmirror |
| Gemini CLI | gemini extensions update fluxmirror |
| Direct binary | fluxmirror upgrade (capture binary only) |
| Direct binary + studio | fluxmirror upgrade --with-studio |
fluxmirror upgrade polls the latest GitHub Release, downloads the
asset matching the current arch, verifies a .sha256 companion, and
atomically swaps the on-disk binary. Pass --with-studio to also
update fluxmirror-studio if it lives alongside fluxmirror or is on
your $PATH. If the binary path is owned by root (e.g. /usr/local/bin),
the command prints a sudo hint instead of escalating itself; SHA
mismatches abort the swap and leave the existing binary untouched.
- Redaction / sensitive-data masking: raw shell arguments and file paths land in the SQLite DB unfiltered; nothing is scrubbed before storage.
- Anomaly detection, policy gating, or rate limiting: events are captured but no analysis, alerting, or blocking layer exists. This is a Phase 3 goal.
- FluxGate integration: per-agent call rate control via FluxGate is planned but not started; FluxGate is not a runtime dependency today.
- Windows-native
cmd.exeshim hardening: only thebashandnodewrapper paths are exercised in CI;shim.cmdships but is not regression-tested. - Real
.fluxmirror.tomlparser: the TOML project-config layer exists at the precedence level but the parser is a stub. Use env vars (FLUXMIRROR_DB,FLUXMIRROR_LANGUAGE,FLUXMIRROR_TIMEZONE) orfluxmirror config setuntil Phase 2 turns this on. - Estimated API cost: only tool-call counts are tracked; no token-count or cost estimation is computed.
- Shareable HTML / image digest cards:
/fluxmirror:weekproduces a text report only; the visual card form is a Phase 2 M5 candidate, not yet implemented. - HTTP+SSE MCP transport:
fluxmirror proxyis stdio-only; the HTTP+SSE MCP transport is not audited.
fluxmirror/
├── CLAUDE.md project instructions (attribution policy at top)
├── README.md this file
├── LICENSE MIT
├── Cargo.toml workspace manifest
├── crates/
│ ├── fluxmirror-cli/ [[bin]] fluxmirror — clap dispatcher
│ ├── fluxmirror-core/ Event, normalize, Config, paths, tz
│ ├── fluxmirror-store/ EventStore trait + SqliteStore
│ └── fluxmirror-proxy/ stdio MCP relay (lib)
├── wrappers/
│ ├── shim.sh bash entry (macOS / Linux / WSL / Git-Bash)
│ ├── shim.mjs Node entry (PowerShell-only Windows etc.)
│ ├── shim.cmd cmd.exe entry (Node-less Windows)
│ └── router.sh tries shims in priority order (pre-init)
├── manifests/
│ └── source.yaml single source of truth for hooks.json
├── plugins/fluxmirror/ Claude Code plugin (also used by Qwen)
│ ├── .claude-plugin/plugin.json
│ ├── qwen-extension.json Qwen Code manifest (same dir is registered as a Qwen extension)
│ ├── hooks/ hooks.json (generated) + run-hook.sh wrapper
│ └── commands/fluxmirror/*.md /fluxmirror:* slash commands (Claude Code & Qwen Code)
├── gemini-extension/ Gemini CLI extension
│ ├── gemini-extension.json
│ ├── hooks/ hooks.json (generated) + run-hook.sh wrapper
│ └── commands/fluxmirror/*.toml /fluxmirror:* slash commands (Gemini CLI)
├── docs/
│ ├── architecture.md layered model + crate map + roadmap
│ └── adr/000{1..4}*.md design decisions
├── scripts/
│ ├── verify-isolation.sh JSONL + SQLite isolation verification
│ ├── test-rust-hook.sh hook regression suite (parity test)
│ ├── build-manifests.sh emit hooks.json from manifests/source.yaml (--check in CI)
│ └── bump-version.sh release helper (sync workspace + 4 plugin manifests + tag)
├── .github/workflows/
│ ├── test.yml CI on push/PR (3-OS matrix, manifest check, parity test)
│ ├── release.yml CI on tag: gemini-extension archive + branch
│ └── rust-release.yml CI on tag: per-arch single binary (5 targets)
├── .claude-plugin/
│ └── marketplace.json Claude marketplace listing
└── .omc/autopilot/{spec,plan,progress}.md durable autopilot state
cargo build --workspace --release # → target/release/fluxmirror (~2.2 MB)
cargo test --workspace # full workspace test suite
bash scripts/test-rust-hook.sh # black-box hook parity test
bash scripts/build-manifests.sh --check # CI guard: hooks.json must match source.yaml.github/workflows/test.yml runs the workspace test suite on
ubuntu-latest, macos-latest, and windows-latest on every push to
main and every pull request, plus the wrapper syntax checks
(bash -n / node --check), the manifest drift guard, the hook
parity suite, and a grep guard that blocks any slash command from
reintroducing the legacy interpreter / sqlite-CLI shell calls that
Phase 0 removed.
./scripts/bump-version.sh 0.6.0 # syncs workspace + 4 plugin manifests + commits + tags
git push origin main v0.6.0bump-version.sh updates the workspace Cargo.toml
[workspace.package].version, the four plugin manifests
(gemini-extension/gemini-extension.json,
plugins/fluxmirror/.claude-plugin/plugin.json,
plugins/fluxmirror/qwen-extension.json, and the nested
.plugins[].version in .claude-plugin/marketplace.json), and
creates the matching annotated tag. It refuses to run on a dirty
working tree, off main, or if the tag already exists. Pass
--dry-run to preview the diff without changing anything.
The tag push triggers three workflows in parallel:
release.yml— re-syncs versions defensively, packages the gemini-extension tarball (now includingwrappers/), publishes a GitHub release with the archive attached, and force-pushes thegemini-extension-pkgbranch withgemini-extension/*pluswrappers/at the root.rust-release.yml— matrix-builds the singlefluxmirrorbinary for five targets (linux x64/arm64, darwin x64/arm64, windows x64) and uploads each asfluxmirror-<arch>{,.exe}plus the legacyfluxmirror-hook-<arch>andfluxmirror-proxy-<arch>aliases (the same binary, copied) so existing wrappers keep working.test.yml— re-runs the workspace test matrix on the new tag commit.
To trigger a dry run of the matrix builds without tagging, use
GitHub's Run workflow button on rust-release.yml
(workflow_dispatch).
MIT