marmot-logger
v1.0.13
Published
Activity monitoring tool for developer workflows - tracks file changes, terminal commands, git operations, and Claude Code hooks
Downloads
1,218
Maintainers
Readme
Marmot
Activity monitoring and logging tool for developer workflows. Creates tamper-evident audit trails by tracking file changes, terminal commands, git operations, process execution, and Claude Code hooks with optional cryptographic signing.
Project Structure
marmot/
├── bin/
│ └── marmot.js # CLI entry point (Commander.js)
├── src/
│ ├── index.js # Public API exports
│ ├── cli/ # Command handlers
│ │ ├── init.js # marmot init
│ │ ├── enable.js # marmot enable <plugin>
│ │ ├── disable.js # marmot disable <plugin>
│ │ ├── status.js # marmot status
│ │ ├── logs.js # marmot logs
│ │ ├── monitor.js # marmot monitor
│ │ ├── process-monitor.js # marmot process-monitor
│ │ ├── verify.js # marmot verify
│ │ ├── log.js # marmot log <event> (internal, used by hooks)
│ │ └── login.js # marmot login
│ ├── core/
│ │ ├── config.js # Config management (load/save/paths)
│ │ ├── logger.js # Log entry persistence
│ │ ├── signer.js # Remote signing service client
│ │ └── gitignore.js # .gitignore pattern parsing
│ └── plugins/
│ ├── index.js # Plugin registry
│ ├── file-monitor.js # File change detection via rsync + git diff
│ ├── terminal.js # Bash command logging via PROMPT_COMMAND
│ ├── git-hooks.js # Git hook integration
│ ├── claude-hooks.js # Claude Code IDE integration
│ ├── process-monitor.js # Process tracking via /proc filesystem
│ ├── makefile.js # Makefile target logging (manual setup)
│ ├── vscode.js # VS Code extension installer
│ └── vscode-extension/ # Bundled VS Code extension
│ ├── package.json # Extension manifest
│ └── extension.js # Extension code
├── openapi.yaml # Signing service API specification
└── package.jsonTech Stack
- Runtime: Node.js >= 16.0.0
- Dependencies:
commander(CLI),chalk(terminal colors) - External Tools:
rsync,git(for file-monitor plugin)
Development Setup
# Clone and install
git clone <repo>
cd marmot
npm install
# Link for local development
npm link
# Now 'marmot' command is available globally
marmot --helpArchitecture
Configuration Storage
All marmot data is stored in /tmp/marmot/<hash>/ where <hash> is MD5 of the absolute project path. This keeps marmot data completely out of the project directory.
/tmp/marmot/<hash>/
├── .marmotrc.json # Project config
├── logs/ # Log files (default location)
│ └── file_events_YYYY-MM-DD.log
├── snapshot/ # File monitor snapshot (rsync copy)
├── process-snapshot.json # Process monitor state
└── terminal-hook.sh # Generated bash hook scriptConfig Resolution Priority
- Environment variables (
MARMOT_API_KEY,MARMOT_URL) .envfile in project root- Cached values in
.marmotrc.json - Defaults (
https://logging.drazou.net, logs in marmot temp dir)
Core Modules
config.js - Configuration management
loadConfig(projectDir)/saveConfig(config, projectDir)- JSON persistencegetSigningUrl(projectDir)/getApiKey(projectDir)- Multi-source resolutiongetMarmotDir(projectDir)- Returns/tmp/marmot/<hash>getLogDir(config, projectDir)- Returns log directory (default:/tmp/marmot/<hash>/logs)enablePlugin(config, name)/disablePlugin(config, name)- Toggle plugins
logger.js - Activity logging
logEvent(eventType, path, size, extra, projectDir)- Main logging APIreadLogs(logFile, options)- Parse JSON Lines log filesverifyLogs(logFile, projectDir)- Verify signatures with backend- Logs to
<marmotDir>/logs/file_events_YYYY-MM-DD.log(JSON Lines format) - Falls back to unsigned entries if signing service unavailable
signer.js - Remote signing client
getToken(projectDir)/refreshToken(projectDir)- Token managementsign(entry, projectDir)- Sign entry with backend serviceverify(entry, projectDir)- Verify entry signaturehealthCheck(projectDir)- Service connectivity check- 10-second request timeout, automatic token refresh on 401
gitignore.js - Exclusion patterns
parseGitignore(projectDir)- Parse .gitignore fileshouldExclude(filePath, projectDir)- Check if path matches exclusionsbuildRsyncExcludes(projectDir)- Generate rsync --exclude args- Always excludes:
.git,logs/
Plugin System
All plugins export enable(projectConfig) and disable(projectConfig) functions.
- Uses rsync to create snapshot, git diff to detect changes
- Installs cron job:
* * * * * cd <project> && marmot monitor - Events:
created,modified,deleted,monitor_initialized - Modified events include
additionsanddeletionsline counts
- Generates bash hook using
PROMPT_COMMAND - Adds source line to
~/.bashrc - De-duplicates consecutive identical commands
- Events:
terminalwithcommandfield
- Installs hooks in
.git/hooks/:post-commit,pre-push,post-checkout,post-merge - Can coexist with existing hooks (appends marmot commands)
- Events:
git_commit,git_push,git_checkout,git_merge
- Configures hooks in
.claude/settings.local.json - Events:
PreToolUse,PostToolUse,Stop,SubagentStop,UserPromptSubmit,Notification,PreCompact,SessionStart,SessionEnd - Logged as
claude_hook_<EventType> - Tool events include
tool_name,tool_input(full parameters), anddescription UserPromptSubmitlogs full untruncated prompt inpromptfield
- Tracks processes launched from project directory via
/procfilesystem - Polls at configurable interval (default: 15 seconds)
- Cron job runs every minute, performs multiple polls within each minute
- Automatically filters out marmot's own processes
- Events:
process_started,process_ended,process_monitor_initialized process_endedincludesdurationin seconds
- Non-intrusive: only prints manual setup instructions
- Users add logging commands to their Makefile
- Events:
make_command
- Installs VS Code extension to
~/.vscode/extensions/or~/.vscode-server/extensions/ - Tracks multiple Marmot-watched directories in shared config (
~/.config/marmot/vscode-projects.json) - Extension logs file events for any registered project
- Events:
vscode_file_opened,vscode_file_closed,vscode_file_saved,vscode_file_editor-changed,vscode_file_created,vscode_file_deleted,vscode_file_renamed - File events include
languageId,size, oroldPathwhere applicable
CLI Commands
| Command | Handler | Description |
|---------|---------|-------------|
| marmot init | init.js | Create config and log directory |
| marmot enable <plugin> | enable.js | Enable plugin and run setup |
| marmot disable <plugin> | disable.js | Disable plugin and cleanup |
| marmot status | status.js | Show config, plugin status, signing health |
| marmot logs [--today] [--last N] | logs.js | Display log entries with color coding |
| marmot monitor | monitor.js | Run file monitor once (for cron) |
| marmot process-monitor | process-monitor.js | Run process monitor once (for cron) |
| marmot verify [--file PATH] | verify.js | Verify log signatures |
| marmot log <event> [-d JSON] | log.js | Log event (used internally by hooks) |
| marmot login | login.js | Set API key interactively |
Log Format
JSON Lines format - one JSON object per line:
{"timestamp":"2025-12-22T14:30:00Z","uuid":"550e8400-e29b-41d4-a716-446655440000","event":"modified","path":"/project/src/index.js","size":1234,"additions":10,"deletions":5,"signed":true}
{"timestamp":"2025-12-22T14:31:00Z","uuid":"...","event":"terminal","path":"/project","command":"npm test","size":0,"signed":true}
{"timestamp":"2025-12-22T14:32:00Z","uuid":"...","event":"git_commit","path":"abc1234: Fix bug","size":0,"signed":true}
{"timestamp":"2025-12-22T14:33:00Z","uuid":"...","event":"process_started","path":"/project","pid":12345,"cmdline":"node server.js","ppid":1234,"signed":true}
{"timestamp":"2025-12-22T14:35:00Z","uuid":"...","event":"process_ended","path":"/project","pid":12345,"cmdline":"node server.js","ppid":1234,"duration":120,"signed":true}
{"timestamp":"2025-12-22T14:36:00Z","uuid":"...","event":"claude_hook_PreToolUse","path":"/project/src/file.js","tool_name":"Edit","tool_input":{"file_path":"/project/src/file.js","old_string":"...","new_string":"..."},"signed":true}
{"timestamp":"2025-12-22T14:37:00Z","uuid":"...","event":"claude_hook_UserPromptSubmit","path":"/project","prompt":"Full user prompt text here...","signed":true}Fields added by signing service: timestamp, uuid, signed: true
Signing Service API
See openapi.yaml for full specification. Endpoints:
| Endpoint | Method | Auth | Description |
|----------|--------|------|-------------|
| /token | POST | None | Exchange API key for bearer token |
| /sign | POST | Bearer | Sign a log entry, returns entry with uuid/timestamp |
| /verify | POST | Bearer | Verify a signed entry |
| /health | GET | None | Service health check |
Programmatic API
const marmot = require('marmot-logger');
// Configuration
const config = marmot.loadConfig();
marmot.saveConfig(config);
marmot.createDefaultConfig();
// Logging
await marmot.log({
event: 'custom_event',
path: '/path/to/file',
size: 1234,
metadata: { custom: 'data' }
});
const entries = marmot.readLogs('/tmp/marmot/<hash>/logs/file_events_2025-12-22.log');
const results = await marmot.verifyLogs('/tmp/marmot/<hash>/logs/file_events_2025-12-22.log');
// Signing
const signed = await marmot.sign(entry);
const result = await marmot.verify(entry);
const health = await marmot.healthCheck();Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| MARMOT_API_KEY | API key for signing service | (required for signing) |
| MARMOT_URL | Signing service URL | https://logging.drazou.net |
Default Config
{
"logDir": null,
"bearerToken": "broken-bearer",
"plugins": {
"file-monitor": { "enabled": false },
"terminal": { "enabled": false },
"git-hooks": {
"enabled": false,
"events": ["commit", "push", "checkout", "merge"]
},
"makefile": { "enabled": false },
"claude-hooks": {
"enabled": false,
"events": ["PreToolUse", "PostToolUse", "Stop", "SubagentStop", "UserPromptSubmit", "Notification", "PreCompact", "SessionStart", "SessionEnd"]
},
"process-monitor": {
"enabled": false,
"intervalSeconds": 15
},
"vscode": {
"enabled": false,
"events": ["opened", "closed", "saved", "editor-changed", "created", "deleted", "renamed"]
}
}
}Note: When logDir is null (default), logs are stored in /tmp/marmot/<hash>/logs/. Set a custom path to override.
Adding a New Plugin
- Create
src/plugins/my-plugin.js:
const chalk = require('chalk');
async function enable(projectConfig) {
const projectDir = process.cwd();
// Setup logic: install hooks, cron jobs, etc.
console.log(chalk.bold('My Plugin Setup:'));
console.log(' Plugin enabled successfully');
}
async function disable(projectConfig) {
// Cleanup logic: remove hooks, cron jobs, etc.
console.log(chalk.bold('My Plugin Disabled'));
}
module.exports = { enable, disable };- Register in src/plugins/index.js:
module.exports = {
// ... existing plugins
'my-plugin': require('./my-plugin')
};- Add default config in src/core/config.js
DEFAULT_CONFIG.plugins:
'my-plugin': {
enabled: false,
// plugin-specific options
}Add to
VALID_PLUGINSin src/cli/enable.js and src/cli/disable.js.Update CLI help in bin/marmot.js (enable command description).
Adding a New CLI Command
- Create handler in
src/cli/my-command.js:
const chalk = require('chalk');
const config = require('../core/config');
module.exports = async function myCommand(options) {
const projectConfig = config.loadConfig();
if (!projectConfig) {
console.log(chalk.red('Marmot not initialized. Run: marmot init'));
process.exit(1);
}
// Command logic
};- Register in bin/marmot.js:
const myCommand = require('../src/cli/my-command');
program
.command('my-command')
.description('Description of my command')
.option('-f, --flag', 'Option description')
.action(myCommand);Event Types Reference
| Event | Source | Extra Fields |
|-------|--------|--------------|
| created | file-monitor | - |
| modified | file-monitor | additions, deletions |
| deleted | file-monitor | - |
| monitor_initialized | file-monitor | - |
| terminal | terminal | command |
| git_commit | git-hooks | - |
| git_push | git-hooks | - |
| git_checkout | git-hooks | - |
| git_merge | git-hooks | - |
| make_command | makefile | - |
| process_monitor_initialized | process-monitor | processCount |
| process_started | process-monitor | pid, cmdline, ppid |
| process_ended | process-monitor | pid, cmdline, ppid, duration |
| claude_hook_PreToolUse | claude-hooks | tool_name, tool_input, description |
| claude_hook_PostToolUse | claude-hooks | tool_name, tool_input, description |
| claude_hook_Stop | claude-hooks | stop_reason |
| claude_hook_SubagentStop | claude-hooks | stop_reason |
| claude_hook_UserPromptSubmit | claude-hooks | prompt (full untruncated) |
| claude_hook_Notification | claude-hooks | message |
| claude_hook_PreCompact | claude-hooks | - |
| claude_hook_SessionStart | claude-hooks | session_id |
| claude_hook_SessionEnd | claude-hooks | session_id |
| vscode_file_opened | vscode | size, languageId |
| vscode_file_closed | vscode | - |
| vscode_file_saved | vscode | size |
| vscode_file_editor-changed | vscode | languageId |
| vscode_file_created | vscode | - |
| vscode_file_deleted | vscode | - |
| vscode_file_renamed | vscode | oldPath |
Data Flow
Plugin detects activity
│
▼
logger.logEvent(type, path, size, extra)
│
▼
signer.sign(entry) ──────► Signing Service
│ │
│ (fallback if unavailable) │
▼ ▼
Unsigned entry Signed entry with uuid/timestamp
│ │
└───────────┬───────────────┘
│
▼
Append to /tmp/marmot/<hash>/logs/file_events_YYYY-MM-DD.logLicense
MIT
