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 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
- Config is stored in `/tmp/marmot/<hash>/` where `<hash>` is MD5 of the absolute project path. This keeps marmot data out of the project directory.
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 # Project config
69
- ├── snapshot/ # File monitor snapshot (rsync copy)
70
- └── terminal-hook.sh # Generated bash hook script
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`, `./marmot-logs/`)
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 `./marmot-logs/file_events_YYYY-MM-DD.log` (JSON Lines format)
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, log directory, update .gitignore |
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('./marmot-logs/file_events_2025-12-22.log');
197
- const results = await marmot.verifyLogs('./marmot-logs/file_events_2025-12-22.log');
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": "./marmot-logs",
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. Update CLI help in [bin/marmot.js](bin/marmot.js) (enable command description).
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
- | `claude_hook_PreToolUse` | claude-hooks | tool details in path |
321
- | `claude_hook_PostToolUse` | claude-hooks | tool details in path |
322
- | `claude_hook_Stop` | claude-hooks | stop reason in path |
323
- | `claude_hook_SubagentStop` | claude-hooks | stop reason in path |
324
- | `claude_hook_UserPromptSubmit` | claude-hooks | prompt preview in path |
325
- | `claude_hook_SessionStart` | claude-hooks | session ID in path |
326
- | `claude_hook_SessionEnd` | claude-hooks | session ID in path |
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 ./marmot-logs/file_events_YYYY-MM-DD.log
378
+ Append to /tmp/marmot/<hash>/logs/file_events_YYYY-MM-DD.log
347
379
  ```
348
380
 
349
381
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "marmot-logger",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Activity monitoring tool for developer workflows - tracks file changes, terminal commands, git operations, and Claude Code hooks",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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
- const toolInput = data.tool_input || data;
46
- const toolName = toolInput.tool_name || 'unknown';
47
- const command = toolInput.command || '';
48
- const filePath = toolInput.file_path || '';
49
- const description = toolInput.description || '';
50
-
51
- if (command) {
52
- eventPath = `${toolName}: ${command.substring(0, 200)}`;
53
- } else if (filePath) {
54
- eventPath = `${toolName}: ${filePath}`;
55
- } else if (description) {
56
- eventPath = `${toolName}: ${description.substring(0, 200)}`;
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 = toolName;
61
+ eventPath = process.cwd();
59
62
  }
60
63
  } else if (hookType === 'Stop' || hookType === 'SubagentStop') {
61
- eventPath = data.stop_reason || 'completed';
64
+ extra.stop_reason = data.stop_reason || 'completed';
65
+ eventPath = process.cwd();
62
66
  } else if (hookType === 'UserPromptSubmit') {
63
- const prompt = data.prompt || data.user_prompt || '';
64
- eventPath = `prompt: ${prompt.substring(0, 200)}`;
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
- const sessionId = data.session_id || 'unknown';
67
- eventPath = `${hookType.toLowerCase()}: ${sessionId}`;
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
 
@@ -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: './marmot-logs',
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
- return path.resolve(projectDir, config.logDir || './logs');
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', 'SessionStart', 'SessionEnd'
53
+ 'UserPromptSubmit', 'Notification', 'PreCompact',
54
+ 'SessionStart', 'SessionEnd'
54
55
  ];
55
56
 
56
57
  const settings = loadClaudeSettings(projectDir);