Activity monitoring tool for developer workflows - tracks file changes, terminal commands, git operations, and Claude Code hooks
npm install marmot-loggerActivity 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.
```
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
│ │ ├── disable.js # marmot disable
│ │ ├── 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
│ │ └── 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.json
- Runtime: Node.js >= 16.0.0
- Dependencies: commander (CLI), chalk (terminal colors)rsync
- External Tools: , git (for file-monitor plugin)
`bashClone and install
git clone
cd marmot
npm install
Architecture
$3
All marmot data is stored in
/tmp/marmot/ where is MD5 of the absolute project path. This keeps marmot data completely out of the project directory.`
/tmp/marmot//
├── .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 script
`$3
1. Environment variables (
MARMOT_API_KEY, MARMOT_URL)
2. .env file in project root
3. Cached values in .marmotrc.json
4. Defaults (https://logging.drazou.net, logs in marmot temp dir)$3
config.js - Configuration management
-
loadConfig(projectDir) / saveConfig(config, projectDir) - JSON persistence
- getSigningUrl(projectDir) / getApiKey(projectDir) - Multi-source resolution
- getMarmotDir(projectDir) - Returns /tmp/marmot/
- getLogDir(config, projectDir) - Returns log directory (default: /tmp/marmot/)
- enablePlugin(config, name) / disablePlugin(config, name) - Toggle pluginslogger.js - Activity logging
-
logEvent(eventType, path, size, extra, projectDir) - Main logging API
- readLogs(logFile, options) - Parse JSON Lines log files
- verifyLogs(logFile, projectDir) - Verify signatures with backend
- Logs to (JSON Lines format)
- Falls back to unsigned entries if signing service unavailablesigner.js - Remote signing client
-
getToken(projectDir) / refreshToken(projectDir) - Token management
- sign(entry, projectDir) - Sign entry with backend service
- verify(entry, projectDir) - Verify entry signature
- healthCheck(projectDir) - Service connectivity check
- 10-second request timeout, automatic token refresh on 401gitignore.js - Exclusion patterns
-
parseGitignore(projectDir) - Parse .gitignore file
- shouldExclude(filePath, projectDir) - Check if path matches exclusions
- buildRsyncExcludes(projectDir) - Generate rsync --exclude args
- Always excludes: .git, logs/$3
All plugins export
enable(projectConfig) and disable(projectConfig) functions.file-monitor.js
- Uses rsync to create snapshot, git diff to detect changes
- Installs cron job:
* cd
- Events: created, modified, deleted, monitor_initialized
- Modified events include additions and deletions line countsterminal.js
- Generates bash hook using
PROMPT_COMMAND
- Adds source line to ~/.bashrc
- De-duplicates consecutive identical commands
- Events: terminal with command fieldgit-hooks.js
- 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_mergeclaude-hooks.js
- Configures hooks in
.claude/settings.local.json
- Events: PreToolUse, PostToolUse, Stop, SubagentStop, UserPromptSubmit, Notification, PreCompact, SessionStart, SessionEnd
- Logged as claude_hook_
- Tool events include tool_name, tool_input (full parameters), and description
- UserPromptSubmit logs full untruncated prompt in prompt fieldprocess-monitor.js
- Tracks processes launched from project directory via
/proc filesystem
- 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_ended includes duration in secondsmakefile.js
- Non-intrusive: only prints manual setup instructions
- Users add logging commands to their Makefile
- Events:
make_commandvscode.js
- 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, or oldPath where applicable$3
| Command | Handler | Description |
|---------|---------|-------------|
|
marmot init | init.js | Create config and log directory |
| marmot enable | enable.js | Enable plugin and run setup |
| marmot disable | 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 | log.js | Log event (used internally by hooks) |
| marmot login | login.js | Set API key interactively |$3
JSON Lines format - one JSON object per line:
`json
{"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$3
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
`javascript
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//logs/file_events_2025-12-22.log');
const results = await marmot.verifyLogs('/tmp/marmot//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
`json
{
"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/. Set a custom path to override.Adding a New Plugin
1. Create
src/plugins/my-plugin.js:`javascript
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 };
`2. Register in src/plugins/index.js:
`javascript
module.exports = {
// ... existing plugins
'my-plugin': require('./my-plugin')
};
`3. Add default config in src/core/config.js
DEFAULT_CONFIG.plugins:`javascript
'my-plugin': {
enabled: false,
// plugin-specific options
}
`4. Add to
VALID_PLUGINS in src/cli/enable.js and src/cli/disable.js.5. Update CLI help in bin/marmot.js (enable command description).
Adding a New CLI Command
1. Create handler in
src/cli/my-command.js:`javascript
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
};
`2. Register in bin/marmot.js:
`javascript
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//logs/file_events_YYYY-MM-DD.log
``MIT