Give your AI assistant a persistent memory of everything you do on your computer.
LLMs are powerful but stateless — every conversation starts from zero. QubTrace runs silently in the background, recording your developer activity, and exposes it to any AI agent via MCP. Your AI now knows what you were working on, what commands you ran, what files you changed, what sites you visited — without you having to explain anything.
Ask Claude: "What was I working on yesterday?"
"Why did I change that file last week?"
"Show me every command I ran before this bug appeared"
Everything stays on your machine. No cloud, no accounts, no telemetry.
Shell / Git / VS Code / Browser / SSH
↓
QubTrace daemon ← secrets stripped before writing
↓
SQLite (~/.qubtrace/) ← local, private
↓
MCP server (stdio)
↓
Claude / Cursor / any LLM
| OS | Status |
|---|---|
| macOS (Apple Silicon & Intel) | ✅ Supported |
| Linux (x86-64, ARM64) | ✅ Supported |
| Windows | 🔜 Coming soon |
Two moving parts:
- Daemon — lightweight background service that receives events from collectors, runs them through processors (sanitizer, embeddings), and writes to SQLite.
- MCP server — started on demand by your LLM client via
qubtrace mcp. Reads from SQLite and exposes structured tools to the model.
git clone https://github.com/qubcore/qubtrace
cd qubtrace
go build -o /tmp/qubtrace ./cmd/qubtrace && /tmp/qubtrace installqubtrace install does three things in one step:
- Copies the binary to
~/.local/bin/qubtrace - Installs and starts the background daemon (launchd on macOS, systemd on Linux)
- Enables the shell collector for your current shell
Make sure ~/.local/bin is on your PATH:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && source ~/.zshrcOpen a new terminal — recording starts immediately.
Claude Code — add to ~/.claude.json:
{
"mcpServers": {
"qubtrace": {
"command": "qubtrace",
"args": ["mcp"]
}
}
}Cursor — Settings → MCP → add server, same command/args.
Any MCP-compatible client — use {"type":"stdio","command":"qubtrace","args":["mcp"]}.
Restart your client after editing the config. The MCP server is started on demand (stdio) — it does not need to be running beforehand.
Each collector is independent. Enable only what makes sense for your workflow.
| Collector | What it captures | Enable |
|---|---|---|
| shell | Every command, exit code, duration, working directory | qubtrace enable shell |
| git | Every git operation — branch, hash, message, remote | qubtrace enable git |
| vscode | File saves, creates, deletes, renames | qubtrace enable vscode |
| ssh | Command and session output of every SSH connection | qubtrace enable ssh |
| chrome | Page navigations and clicks in Chromium-based browsers | qubtrace enable chrome |
qubtrace enable <name> # install dependencies + activate
qubtrace disable <name> # deactivate (keeps dependencies)
qubtrace uninstall <name> # deactivate + remove dependenciesenable is idempotent — safe to run multiple times.
Chrome's security model requires loading the extension manually once:
qubtrace enable chromeThe command writes the extension to disk and prints the exact steps:
- Open
chrome://extensions - Enable Developer Mode (toggle, top-right)
- Click Load unpacked → select the folder printed by the command
- Done — navigations and clicks are recorded from now on
Works with Chrome, Brave, and Edge.
By default the Chrome collector records all sites. To restrict it to specific domains:
qubtrace collector set chrome allowed_domains "github.com,notion.so,linear.app"Subdomains are included automatically (docs.github.com matches github.com). Set to empty string to record all sites again:
qubtrace collector set chrome allowed_domains ""The domain filter applies both in the extension and server-side — events from blocked domains are dropped before they reach the pipeline.
The Sanitizer processor runs on every event before it is written to SQLite. It strips:
| Rule | Example |
|---|---|
| URL credentials | https://user:token@host → https://user:[REDACTED]@host |
| Environment secrets | API_KEY=abc123 → API_KEY=[REDACTED] |
| Password flags | --password=secret → --password=[REDACTED] |
| JWT tokens | eyJhbGci… → [REDACTED_JWT] |
| AWS keys | AKIA… → [REDACTED_AWS_KEY] |
| Auth headers | Authorization: Bearer … → Authorization: Bearer [REDACTED] |
| GitHub tokens | ghp_… → [REDACTED_GH_TOKEN] |
The sanitizer is enabled by default. Toggle individual rules via CLI or YAML — changes take effect immediately without a daemon restart:
# toggle a built-in rule
qubtrace processors set sanitizer rules.jwt_tokens false
# add a custom regex pattern
qubtrace processors custom add "MY_CORP_SECRET=[^\s]+"
qubtrace processors custom list
qubtrace processors custom remove 1~/.qubtrace/processors/sanitizer.yml (direct YAML editing also works):
rules:
url_credentials: true
env_secrets: true
password_flags: true
jwt_tokens: true
aws_keys: true
auth_headers: true
github_tokens: true
custom:
- pattern: "MY_INTERNAL_SECRET=[^\s]+"
description: "Internal secret pattern"
enabled: true# Install & service
qubtrace install # full one-step install
qubtrace service install # register daemon with OS init system
qubtrace service start
qubtrace service stop
qubtrace service status
qubtrace service uninstall
# Pipeline overview
qubtrace status # all collectors, processors, and sinks at a glance
# Collectors
qubtrace collectors # list all with enabled/deps status
qubtrace enable <name> # install deps + activate
qubtrace disable <name> # deactivate (keeps deps)
qubtrace uninstall <name> # deactivate + remove deps
# Collector config
qubtrace collector config <name> # show a collector's current config as YAML
qubtrace collector set chrome allowed_domains "github.com,stackoverflow.com"
# filter Chrome to specific domains (empty = all)
# Processors
qubtrace processors # list all with enabled status
qubtrace processors enable <name> # enable a processor
qubtrace processors disable <name> # disable a processor
qubtrace processors config <name> # show current config as YAML
qubtrace processors set <name> <key> <value> # set a top-level config value
qubtrace processors set <name> <key>.<subkey> <value> # set a nested config value
# examples:
qubtrace processors set sanitizer rules.jwt_tokens false
qubtrace processors set sanitizer rules.aws_keys true
qubtrace processors set embeddings model nomic-embed-text
# Custom sanitizer patterns (take effect immediately, no restart needed)
qubtrace processors custom list
qubtrace processors custom add "MY_CORP_SECRET=[^\s]+"
qubtrace processors custom remove 1
# Sinks
qubtrace sinks # list all sinks
# Events
qubtrace events # last 20 events (table)
qubtrace events --limit 100
qubtrace events --source terminal.zsh
qubtrace events --session <id>
qubtrace export # JSON export, ready for LLMs
qubtrace export --source git --limit 0 # all git events
# MCP
qubtrace mcp # start MCP server (stdio)
# Semantic search (optional)
qubtrace embeddings setup # install Ollama + pull model + enable
qubtrace embeddings status # show stats and Ollama health
# Maintenance
qubtrace cleanup # apply retention policy now (also runs daily in daemon)Once connected, your LLM can call:
| Tool | What it does |
|---|---|
events |
Query activity by source, time range, session, or keyword |
session |
Full output of a recorded shell or SSH session |
summary |
Activity breakdown by tool for any time window |
semantic_search |
Find events by meaning via vector similarity (requires embeddings) |
Example questions you can ask your AI:
- "What was I working on before I got interrupted?"
- "List all git commits I made this week"
- "What command did I run to fix that Docker error last month?"
- "Which sites was I visiting while debugging that networking issue?"
- "Find anything related to the payment integration" (semantic search)
Enable vector embeddings so your AI can search by meaning, not just keywords:
qubtrace embeddings setup
# installs Ollama, pulls nomic-embed-text, enables the embeddings processor
qubtrace service stop && qubtrace service start
# restart the daemon to begin generating embeddings for new eventsCheck status at any time:
qubtrace embeddings status
# embeddings enabled: true
# ollama url: http://localhost:11434
# model: nomic-embed-text
# ollama running: true
# events total: 1482
# events embedded: 1480 (99%)By default the daemon automatically deletes old data on startup and once every 24 hours:
| Policy | Default |
|---|---|
| Events older than N days | 90 days |
| DB size cap (oldest first) | 2 GB |
| Session recordings older than N days | 30 days |
Run it manually at any time:
qubtrace cleanup
# retention policy: max_age=90 days max_db=2.0 GB sessions=30 days
# events deleted: 1204
# sessions deleted: 38
# db size: 847.2 MB → 312.0 MBCustomize in ~/.qubtrace/retention.yml:
max_age_days: 90 # delete events older than this
max_db_gb: 2.0 # trim oldest events if DB exceeds this
sessions_max_age_days: 30 # delete session recordings older than thisSet any value to 0 to disable that check (e.g. max_db_gb: 0 to skip the size cap).
~/.qubtrace/
├── qubtrace.db # SQLite: events + embeddings
├── cmd.log # ring buffer: shell/git hooks write here, daemon reads+truncates
├── retention.yml # cleanup policy (optional, defaults above apply if absent)
├── sessions/ # full PTY recordings (secrets stripped)
├── collectors/
│ ├── shell.yml # .yml = enabled, .disabled = disabled
│ ├── git.yml
│ ├── chrome.yml
│ └── ...
└── processors/
├── sanitizer.yml # redaction rules + custom patterns
└── embeddings.yml # Ollama URL + model
Config files are plain YAML. The daemon watches both directories with fsnotify and applies changes without a restart — collector enable/disable and sanitizer custom patterns take effect immediately.
When the Chrome collector is enabled it opens an HTTP server at 127.0.0.1:7335 that also accepts events from any local process:
# requires: qubtrace enable chrome
curl -s -X POST http://127.0.0.1:7335/v1/events \
-H 'Content-Type: application/json' \
-d '{
"source": "mytool.action",
"command": "deployed v1.2.3 to staging",
"cwd": "/my/project",
"exit_code": 0
}'- Create a package under
collectors/that implementscollectors/types.Collector:
type Collector interface {
Manifest() Manifest // name, description, sources
Run(ctx context.Context, emit func(Event)) error // blocking; call emit() for each event
InstallDependencies() error // write files, install hooks, etc.
UninstallDependencies() error
Enable() error // Base.Enable() renames .disabled → .yml
Disable() error // Base.Disable() renames .yml → .disabled
UpdateConfig(fields map[string]any) error
IsEnabled() bool
IsDepsInstalled() bool
}-
Call
types.InitDisabled("myname", defaultConfig)inNew()to create the initial config file. -
Register it in
collectors/registry.go:
var registry = []types.Collector{
shell.New(),
git.New(),
// ...
mypkg.New(), // add here
}The daemon picks it up automatically — no other changes needed.
- Create a package under
processor/that implementsprocessor/types.Processor:
type Processor interface {
Manifest() Manifest
Process(ctx context.Context, e event.Event) (event.Event, error) // transform or enrich
Enable() error
Disable() error
IsEnabled() bool
InstallDependencies() error
UninstallDependencies() error
}- Optionally implement
types.ConfigReloaderso the daemon hot-reloads your config when the YAML changes:
func (p *MyProcessor) ReloadConfig() {
types.ReadConfig("myprocessor", p)
p.rebuildInternalState()
}- Register it in
processor/registry.go.
- Implement
sink/types.Sink:
type Sink interface {
Manifest() Manifest
Run(ctx context.Context, in <-chan event.Event) error // drain the channel
}- Register it in
sink/registry.go. The daemon starts all registered sinks automatically — no config file required.
qubtrace service stop && qubtrace service uninstall
qubtrace uninstall shell git vscode ssh chrome
rm -rf ~/.qubtrace ~/QubTrace
rm ~/.local/bin/qubtrace