marmot-logger 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -24
- package/package.json +1 -1
- package/src/cli/log.js +28 -18
- package/src/core/config.js +8 -3
- package/src/plugins/claude-hooks.js +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Marmot
|
|
2
2
|
|
|
3
|
-
Activity monitoring and logging tool for developer workflows. Creates tamper-evident audit trails by tracking file changes, terminal commands, git operations, and Claude Code hooks with optional cryptographic signing.
|
|
3
|
+
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.
|
|
4
4
|
|
|
5
5
|
## Project Structure
|
|
6
6
|
|
|
@@ -17,6 +17,7 @@ marmot/
|
|
|
17
17
|
│ │ ├── status.js # marmot status
|
|
18
18
|
│ │ ├── logs.js # marmot logs
|
|
19
19
|
│ │ ├── monitor.js # marmot monitor
|
|
20
|
+
│ │ ├── process-monitor.js # marmot process-monitor
|
|
20
21
|
│ │ ├── verify.js # marmot verify
|
|
21
22
|
│ │ ├── log.js # marmot log <event> (internal, used by hooks)
|
|
22
23
|
│ │ └── login.js # marmot login
|
|
@@ -31,6 +32,7 @@ marmot/
|
|
|
31
32
|
│ ├── terminal.js # Bash command logging via PROMPT_COMMAND
|
|
32
33
|
│ ├── git-hooks.js # Git hook integration
|
|
33
34
|
│ ├── claude-hooks.js # Claude Code IDE integration
|
|
35
|
+
│ ├── process-monitor.js # Process tracking via /proc filesystem
|
|
34
36
|
│ └── makefile.js # Makefile target logging (manual setup)
|
|
35
37
|
├── openapi.yaml # Signing service API specification
|
|
36
38
|
└── package.json
|
|
@@ -61,23 +63,24 @@ marmot --help
|
|
|
61
63
|
|
|
62
64
|
### Configuration Storage
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
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.
|
|
65
67
|
|
|
66
68
|
```
|
|
67
69
|
/tmp/marmot/<hash>/
|
|
68
|
-
├── .marmotrc.json
|
|
69
|
-
├──
|
|
70
|
-
└──
|
|
70
|
+
├── .marmotrc.json # Project config
|
|
71
|
+
├── logs/ # Log files (default location)
|
|
72
|
+
│ └── file_events_YYYY-MM-DD.log
|
|
73
|
+
├── snapshot/ # File monitor snapshot (rsync copy)
|
|
74
|
+
├── process-snapshot.json # Process monitor state
|
|
75
|
+
└── terminal-hook.sh # Generated bash hook script
|
|
71
76
|
```
|
|
72
77
|
|
|
73
|
-
Logs are written to `./marmot-logs/` in the project directory.
|
|
74
|
-
|
|
75
78
|
### Config Resolution Priority
|
|
76
79
|
|
|
77
80
|
1. Environment variables (`MARMOT_API_KEY`, `MARMOT_URL`)
|
|
78
81
|
2. `.env` file in project root
|
|
79
82
|
3. Cached values in `.marmotrc.json`
|
|
80
|
-
4. Defaults (`https://logging.drazou.net`,
|
|
83
|
+
4. Defaults (`https://logging.drazou.net`, logs in marmot temp dir)
|
|
81
84
|
|
|
82
85
|
### Core Modules
|
|
83
86
|
|
|
@@ -85,13 +88,14 @@ Logs are written to `./marmot-logs/` in the project directory.
|
|
|
85
88
|
- `loadConfig(projectDir)` / `saveConfig(config, projectDir)` - JSON persistence
|
|
86
89
|
- `getSigningUrl(projectDir)` / `getApiKey(projectDir)` - Multi-source resolution
|
|
87
90
|
- `getMarmotDir(projectDir)` - Returns `/tmp/marmot/<hash>`
|
|
91
|
+
- `getLogDir(config, projectDir)` - Returns log directory (default: `/tmp/marmot/<hash>/logs`)
|
|
88
92
|
- `enablePlugin(config, name)` / `disablePlugin(config, name)` - Toggle plugins
|
|
89
93
|
|
|
90
94
|
**[logger.js](src/core/logger.js)** - Activity logging
|
|
91
95
|
- `logEvent(eventType, path, size, extra, projectDir)` - Main logging API
|
|
92
96
|
- `readLogs(logFile, options)` - Parse JSON Lines log files
|
|
93
97
|
- `verifyLogs(logFile, projectDir)` - Verify signatures with backend
|
|
94
|
-
- Logs to
|
|
98
|
+
- Logs to `<marmotDir>/logs/file_events_YYYY-MM-DD.log` (JSON Lines format)
|
|
95
99
|
- Falls back to unsigned entries if signing service unavailable
|
|
96
100
|
|
|
97
101
|
**[signer.js](src/core/signer.js)** - Remote signing client
|
|
@@ -130,8 +134,18 @@ All plugins export `enable(projectConfig)` and `disable(projectConfig)` function
|
|
|
130
134
|
|
|
131
135
|
**[claude-hooks.js](src/plugins/claude-hooks.js)**
|
|
132
136
|
- Configures hooks in `.claude/settings.local.json`
|
|
133
|
-
- Events: `PreToolUse`, `PostToolUse`, `Stop`, `SubagentStop`, `UserPromptSubmit`, `SessionStart`, `SessionEnd`
|
|
137
|
+
- Events: `PreToolUse`, `PostToolUse`, `Stop`, `SubagentStop`, `UserPromptSubmit`, `Notification`, `PreCompact`, `SessionStart`, `SessionEnd`
|
|
134
138
|
- Logged as `claude_hook_<EventType>`
|
|
139
|
+
- Tool events include `tool_name`, `tool_input` (full parameters), and `description`
|
|
140
|
+
- `UserPromptSubmit` logs full untruncated prompt in `prompt` field
|
|
141
|
+
|
|
142
|
+
**[process-monitor.js](src/plugins/process-monitor.js)**
|
|
143
|
+
- Tracks processes launched from project directory via `/proc` filesystem
|
|
144
|
+
- Polls at configurable interval (default: 15 seconds)
|
|
145
|
+
- Cron job runs every minute, performs multiple polls within each minute
|
|
146
|
+
- Automatically filters out marmot's own processes
|
|
147
|
+
- Events: `process_started`, `process_ended`, `process_monitor_initialized`
|
|
148
|
+
- `process_ended` includes `duration` in seconds
|
|
135
149
|
|
|
136
150
|
**[makefile.js](src/plugins/makefile.js)**
|
|
137
151
|
- Non-intrusive: only prints manual setup instructions
|
|
@@ -142,12 +156,13 @@ All plugins export `enable(projectConfig)` and `disable(projectConfig)` function
|
|
|
142
156
|
|
|
143
157
|
| Command | Handler | Description |
|
|
144
158
|
|---------|---------|-------------|
|
|
145
|
-
| `marmot init` | [init.js](src/cli/init.js) | Create config
|
|
159
|
+
| `marmot init` | [init.js](src/cli/init.js) | Create config and log directory |
|
|
146
160
|
| `marmot enable <plugin>` | [enable.js](src/cli/enable.js) | Enable plugin and run setup |
|
|
147
161
|
| `marmot disable <plugin>` | [disable.js](src/cli/disable.js) | Disable plugin and cleanup |
|
|
148
162
|
| `marmot status` | [status.js](src/cli/status.js) | Show config, plugin status, signing health |
|
|
149
163
|
| `marmot logs [--today] [--last N]` | [logs.js](src/cli/logs.js) | Display log entries with color coding |
|
|
150
164
|
| `marmot monitor` | [monitor.js](src/cli/monitor.js) | Run file monitor once (for cron) |
|
|
165
|
+
| `marmot process-monitor` | [process-monitor.js](src/cli/process-monitor.js) | Run process monitor once (for cron) |
|
|
151
166
|
| `marmot verify [--file PATH]` | [verify.js](src/cli/verify.js) | Verify log signatures |
|
|
152
167
|
| `marmot log <event> [-d JSON]` | [log.js](src/cli/log.js) | Log event (used internally by hooks) |
|
|
153
168
|
| `marmot login` | [login.js](src/cli/login.js) | Set API key interactively |
|
|
@@ -160,6 +175,10 @@ JSON Lines format - one JSON object per line:
|
|
|
160
175
|
{"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}
|
|
161
176
|
{"timestamp":"2025-12-22T14:31:00Z","uuid":"...","event":"terminal","path":"/project","command":"npm test","size":0,"signed":true}
|
|
162
177
|
{"timestamp":"2025-12-22T14:32:00Z","uuid":"...","event":"git_commit","path":"abc1234: Fix bug","size":0,"signed":true}
|
|
178
|
+
{"timestamp":"2025-12-22T14:33:00Z","uuid":"...","event":"process_started","path":"/project","pid":12345,"cmdline":"node server.js","ppid":1234,"signed":true}
|
|
179
|
+
{"timestamp":"2025-12-22T14:35:00Z","uuid":"...","event":"process_ended","path":"/project","pid":12345,"cmdline":"node server.js","ppid":1234,"duration":120,"signed":true}
|
|
180
|
+
{"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}
|
|
181
|
+
{"timestamp":"2025-12-22T14:37:00Z","uuid":"...","event":"claude_hook_UserPromptSubmit","path":"/project","prompt":"Full user prompt text here...","signed":true}
|
|
163
182
|
```
|
|
164
183
|
|
|
165
184
|
Fields added by signing service: `timestamp`, `uuid`, `signed: true`
|
|
@@ -193,8 +212,8 @@ await marmot.log({
|
|
|
193
212
|
metadata: { custom: 'data' }
|
|
194
213
|
});
|
|
195
214
|
|
|
196
|
-
const entries = marmot.readLogs('
|
|
197
|
-
const results = await marmot.verifyLogs('
|
|
215
|
+
const entries = marmot.readLogs('/tmp/marmot/<hash>/logs/file_events_2025-12-22.log');
|
|
216
|
+
const results = await marmot.verifyLogs('/tmp/marmot/<hash>/logs/file_events_2025-12-22.log');
|
|
198
217
|
|
|
199
218
|
// Signing
|
|
200
219
|
const signed = await marmot.sign(entry);
|
|
@@ -213,7 +232,7 @@ const health = await marmot.healthCheck();
|
|
|
213
232
|
|
|
214
233
|
```json
|
|
215
234
|
{
|
|
216
|
-
"logDir":
|
|
235
|
+
"logDir": null,
|
|
217
236
|
"bearerToken": "broken-bearer",
|
|
218
237
|
"plugins": {
|
|
219
238
|
"file-monitor": { "enabled": false },
|
|
@@ -225,12 +244,18 @@ const health = await marmot.healthCheck();
|
|
|
225
244
|
"makefile": { "enabled": false },
|
|
226
245
|
"claude-hooks": {
|
|
227
246
|
"enabled": false,
|
|
228
|
-
"events": ["PreToolUse", "PostToolUse", "Stop", "SubagentStop", "UserPromptSubmit", "SessionStart", "SessionEnd"]
|
|
247
|
+
"events": ["PreToolUse", "PostToolUse", "Stop", "SubagentStop", "UserPromptSubmit", "Notification", "PreCompact", "SessionStart", "SessionEnd"]
|
|
248
|
+
},
|
|
249
|
+
"process-monitor": {
|
|
250
|
+
"enabled": false,
|
|
251
|
+
"intervalSeconds": 15
|
|
229
252
|
}
|
|
230
253
|
}
|
|
231
254
|
}
|
|
232
255
|
```
|
|
233
256
|
|
|
257
|
+
**Note:** When `logDir` is `null` (default), logs are stored in `/tmp/marmot/<hash>/logs/`. Set a custom path to override.
|
|
258
|
+
|
|
234
259
|
## Adding a New Plugin
|
|
235
260
|
|
|
236
261
|
1. Create `src/plugins/my-plugin.js`:
|
|
@@ -271,7 +296,9 @@ module.exports = {
|
|
|
271
296
|
}
|
|
272
297
|
```
|
|
273
298
|
|
|
274
|
-
4.
|
|
299
|
+
4. Add to `VALID_PLUGINS` in [src/cli/enable.js](src/cli/enable.js) and [src/cli/disable.js](src/cli/disable.js).
|
|
300
|
+
|
|
301
|
+
5. Update CLI help in [bin/marmot.js](bin/marmot.js) (enable command description).
|
|
275
302
|
|
|
276
303
|
## Adding a New CLI Command
|
|
277
304
|
|
|
@@ -317,13 +344,18 @@ program
|
|
|
317
344
|
| `git_checkout` | git-hooks | - |
|
|
318
345
|
| `git_merge` | git-hooks | - |
|
|
319
346
|
| `make_command` | makefile | - |
|
|
320
|
-
| `
|
|
321
|
-
| `
|
|
322
|
-
| `
|
|
323
|
-
| `
|
|
324
|
-
| `
|
|
325
|
-
| `
|
|
326
|
-
| `
|
|
347
|
+
| `process_monitor_initialized` | process-monitor | `processCount` |
|
|
348
|
+
| `process_started` | process-monitor | `pid`, `cmdline`, `ppid` |
|
|
349
|
+
| `process_ended` | process-monitor | `pid`, `cmdline`, `ppid`, `duration` |
|
|
350
|
+
| `claude_hook_PreToolUse` | claude-hooks | `tool_name`, `tool_input`, `description` |
|
|
351
|
+
| `claude_hook_PostToolUse` | claude-hooks | `tool_name`, `tool_input`, `description` |
|
|
352
|
+
| `claude_hook_Stop` | claude-hooks | `stop_reason` |
|
|
353
|
+
| `claude_hook_SubagentStop` | claude-hooks | `stop_reason` |
|
|
354
|
+
| `claude_hook_UserPromptSubmit` | claude-hooks | `prompt` (full untruncated) |
|
|
355
|
+
| `claude_hook_Notification` | claude-hooks | `message` |
|
|
356
|
+
| `claude_hook_PreCompact` | claude-hooks | - |
|
|
357
|
+
| `claude_hook_SessionStart` | claude-hooks | `session_id` |
|
|
358
|
+
| `claude_hook_SessionEnd` | claude-hooks | `session_id` |
|
|
327
359
|
|
|
328
360
|
## Data Flow
|
|
329
361
|
|
|
@@ -343,7 +375,7 @@ Unsigned entry Signed entry with uuid/timestamp
|
|
|
343
375
|
└───────────┬───────────────┘
|
|
344
376
|
│
|
|
345
377
|
▼
|
|
346
|
-
Append to
|
|
378
|
+
Append to /tmp/marmot/<hash>/logs/file_events_YYYY-MM-DD.log
|
|
347
379
|
```
|
|
348
380
|
|
|
349
381
|
## License
|
package/package.json
CHANGED
package/src/cli/log.js
CHANGED
|
@@ -42,29 +42,39 @@ module.exports = async function log(event, options) {
|
|
|
42
42
|
|
|
43
43
|
// Extract relevant fields based on hook type
|
|
44
44
|
if (hookType === 'PreToolUse' || hookType === 'PostToolUse') {
|
|
45
|
-
|
|
46
|
-
const toolName =
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
45
|
+
// Tool name is at top level, tool_input contains the parameters
|
|
46
|
+
const toolName = data.tool_name || 'unknown';
|
|
47
|
+
const toolInput = data.tool_input || {};
|
|
48
|
+
|
|
49
|
+
extra.tool_name = toolName;
|
|
50
|
+
extra.tool_input = toolInput;
|
|
51
|
+
|
|
52
|
+
// Extract description if available (Bash tool has this)
|
|
53
|
+
if (toolInput.description) {
|
|
54
|
+
extra.description = toolInput.description;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Use file_path as eventPath if available, otherwise use project dir
|
|
58
|
+
if (toolInput.file_path) {
|
|
59
|
+
eventPath = toolInput.file_path;
|
|
57
60
|
} else {
|
|
58
|
-
eventPath =
|
|
61
|
+
eventPath = process.cwd();
|
|
59
62
|
}
|
|
60
63
|
} else if (hookType === 'Stop' || hookType === 'SubagentStop') {
|
|
61
|
-
|
|
64
|
+
extra.stop_reason = data.stop_reason || 'completed';
|
|
65
|
+
eventPath = process.cwd();
|
|
62
66
|
} else if (hookType === 'UserPromptSubmit') {
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
// Store full untruncated prompt
|
|
68
|
+
extra.prompt = data.prompt || data.user_prompt || '';
|
|
69
|
+
eventPath = process.cwd();
|
|
65
70
|
} else if (hookType === 'SessionStart' || hookType === 'SessionEnd') {
|
|
66
|
-
|
|
67
|
-
eventPath =
|
|
71
|
+
extra.session_id = data.session_id || 'unknown';
|
|
72
|
+
eventPath = process.cwd();
|
|
73
|
+
} else if (hookType === 'Notification') {
|
|
74
|
+
extra.message = data.message || '';
|
|
75
|
+
eventPath = process.cwd();
|
|
76
|
+
} else if (hookType === 'PreCompact') {
|
|
77
|
+
eventPath = process.cwd();
|
|
68
78
|
}
|
|
69
79
|
}
|
|
70
80
|
|
package/src/core/config.js
CHANGED
|
@@ -6,7 +6,7 @@ const CONFIG_FILE = '.marmotrc.json';
|
|
|
6
6
|
const MARMOT_TMP_BASE = '/tmp/marmot';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_CONFIG = {
|
|
9
|
-
logDir:
|
|
9
|
+
logDir: null, // null means use marmot temp dir (default)
|
|
10
10
|
bearerToken: 'broken-bearer',
|
|
11
11
|
plugins: {
|
|
12
12
|
'file-monitor': {
|
|
@@ -24,7 +24,7 @@ const DEFAULT_CONFIG = {
|
|
|
24
24
|
},
|
|
25
25
|
'claude-hooks': {
|
|
26
26
|
enabled: false,
|
|
27
|
-
events: ['PreToolUse', 'PostToolUse', 'Stop', 'SubagentStop', 'UserPromptSubmit', 'SessionStart', 'SessionEnd']
|
|
27
|
+
events: ['PreToolUse', 'PostToolUse', 'Stop', 'SubagentStop', 'UserPromptSubmit', 'Notification', 'PreCompact', 'SessionStart', 'SessionEnd']
|
|
28
28
|
},
|
|
29
29
|
'process-monitor': {
|
|
30
30
|
enabled: false,
|
|
@@ -122,7 +122,12 @@ function getApiKey(projectDir = process.cwd()) {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
function getLogDir(config, projectDir = process.cwd()) {
|
|
125
|
-
|
|
125
|
+
if (config.logDir) {
|
|
126
|
+
// Custom log dir specified - resolve relative to project
|
|
127
|
+
return path.resolve(projectDir, config.logDir);
|
|
128
|
+
}
|
|
129
|
+
// Default: use marmot temp dir
|
|
130
|
+
return path.join(getMarmotDir(projectDir), 'logs');
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
function getSnapshotDir(config, projectDir = process.cwd()) {
|
|
@@ -50,7 +50,8 @@ async function enable(projectConfig) {
|
|
|
50
50
|
const projectDir = process.cwd();
|
|
51
51
|
const events = projectConfig.plugins?.['claude-hooks']?.events || [
|
|
52
52
|
'PreToolUse', 'PostToolUse', 'Stop', 'SubagentStop',
|
|
53
|
-
'UserPromptSubmit', '
|
|
53
|
+
'UserPromptSubmit', 'Notification', 'PreCompact',
|
|
54
|
+
'SessionStart', 'SessionEnd'
|
|
54
55
|
];
|
|
55
56
|
|
|
56
57
|
const settings = loadClaudeSettings(projectDir);
|