marmot-logger 1.0.0
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 +172 -0
- package/bin/marmot.js +71 -0
- package/package.json +34 -0
- package/src/cli/disable.js +40 -0
- package/src/cli/enable.js +40 -0
- package/src/cli/init.js +85 -0
- package/src/cli/log.js +102 -0
- package/src/cli/login.js +120 -0
- package/src/cli/logs.js +93 -0
- package/src/cli/monitor.js +32 -0
- package/src/cli/status.js +72 -0
- package/src/cli/verify.js +81 -0
- package/src/core/config.js +190 -0
- package/src/core/gitignore.js +109 -0
- package/src/core/logger.js +120 -0
- package/src/core/signer.js +136 -0
- package/src/index.js +20 -0
- package/src/plugins/claude-hooks.js +134 -0
- package/src/plugins/file-monitor.js +233 -0
- package/src/plugins/git-hooks.js +177 -0
- package/src/plugins/index.js +7 -0
- package/src/plugins/makefile.js +68 -0
- package/src/plugins/terminal.js +136 -0
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Marmot
|
|
2
|
+
|
|
3
|
+
Activity monitoring tool for developer workflows. Tracks file changes, terminal commands, git operations, and Claude Code hooks.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g marmot-logger
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use with npx:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx marmot-logger init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Initialize marmot in your project
|
|
21
|
+
marmot init
|
|
22
|
+
|
|
23
|
+
# Enable plugins
|
|
24
|
+
marmot enable file-monitor # Track file changes
|
|
25
|
+
marmot enable terminal # Log terminal commands
|
|
26
|
+
marmot enable git-hooks # Log git operations
|
|
27
|
+
marmot enable claude-hooks # Log Claude Code activity
|
|
28
|
+
|
|
29
|
+
# Check status
|
|
30
|
+
marmot status
|
|
31
|
+
|
|
32
|
+
# View recent logs
|
|
33
|
+
marmot logs --last 20
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
### Environment Variables
|
|
39
|
+
|
|
40
|
+
- `MARMOT_URL` - Signing service URL (default: `https://logging.drazou.net`)
|
|
41
|
+
- `MARMOT_API_KEY` - API key for signing service (required)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
export MARMOT_API_KEY=your-api-key
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Project Configuration
|
|
48
|
+
|
|
49
|
+
Marmot creates `.marmotrc.json` in your project root:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"logDir": "./logs",
|
|
54
|
+
"snapshotDir": "./.marmot-snapshot",
|
|
55
|
+
"plugins": {
|
|
56
|
+
"file-monitor": {
|
|
57
|
+
"enabled": true,
|
|
58
|
+
"exclude": [".git", "node_modules", ".marmot-snapshot"]
|
|
59
|
+
},
|
|
60
|
+
"terminal": { "enabled": true },
|
|
61
|
+
"git-hooks": { "enabled": true },
|
|
62
|
+
"claude-hooks": { "enabled": true }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Plugins
|
|
68
|
+
|
|
69
|
+
### File Monitor
|
|
70
|
+
|
|
71
|
+
Detects file changes using git diff against a snapshot.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
marmot enable file-monitor
|
|
75
|
+
|
|
76
|
+
# Run manually or via cron
|
|
77
|
+
marmot monitor
|
|
78
|
+
|
|
79
|
+
# Add to crontab for every minute:
|
|
80
|
+
* * * * * cd /path/to/project && npx marmot monitor >> ./logs/cron.log 2>&1
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Terminal
|
|
84
|
+
|
|
85
|
+
Logs terminal commands executed in the project directory.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
marmot enable terminal
|
|
89
|
+
|
|
90
|
+
# Add to ~/.bashrc:
|
|
91
|
+
source "/path/to/project/.marmot/terminal-hook.sh"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Git Hooks
|
|
95
|
+
|
|
96
|
+
Logs git operations (commit, push, checkout, merge).
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
marmot enable git-hooks
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Hooks are automatically installed in `.git/hooks/`.
|
|
103
|
+
|
|
104
|
+
### Claude Hooks
|
|
105
|
+
|
|
106
|
+
Logs Claude Code IDE activity.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
marmot enable claude-hooks
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Hooks are configured in `.claude/settings.local.json`.
|
|
113
|
+
|
|
114
|
+
## CLI Commands
|
|
115
|
+
|
|
116
|
+
| Command | Description |
|
|
117
|
+
|---------|-------------|
|
|
118
|
+
| `marmot init` | Initialize marmot in current project |
|
|
119
|
+
| `marmot enable <plugin>` | Enable a plugin |
|
|
120
|
+
| `marmot disable <plugin>` | Disable a plugin |
|
|
121
|
+
| `marmot status` | Show status and enabled plugins |
|
|
122
|
+
| `marmot logs` | View recent log entries |
|
|
123
|
+
| `marmot logs --today` | View today's logs |
|
|
124
|
+
| `marmot logs --last N` | View last N entries |
|
|
125
|
+
| `marmot verify` | Verify log signatures |
|
|
126
|
+
| `marmot monitor` | Run file monitor once |
|
|
127
|
+
|
|
128
|
+
## Log Format
|
|
129
|
+
|
|
130
|
+
Logs are stored as JSON Lines in `./logs/file_events_YYYY-MM-DD.log`:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{"timestamp":"2025-12-22T14:30:00Z","uuid":"...","event":"modified","path":"/project/src/index.js","size":1234,"additions":10,"deletions":5,"signed":true}
|
|
134
|
+
{"timestamp":"2025-12-22T14:31:00Z","uuid":"...","event":"terminal","path":"/project","command":"npm test","size":0,"signed":true}
|
|
135
|
+
{"timestamp":"2025-12-22T14:32:00Z","uuid":"...","event":"git_commit","path":"abc1234: Fix bug","size":0,"signed":true}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Event Types
|
|
139
|
+
|
|
140
|
+
| Event | Description |
|
|
141
|
+
|-------|-------------|
|
|
142
|
+
| `created` | File created |
|
|
143
|
+
| `modified` | File modified (includes additions/deletions) |
|
|
144
|
+
| `deleted` | File deleted |
|
|
145
|
+
| `terminal` | Terminal command executed |
|
|
146
|
+
| `git_commit` | Git commit made |
|
|
147
|
+
| `git_push` | Git push executed |
|
|
148
|
+
| `git_checkout` | Git branch checkout |
|
|
149
|
+
| `git_merge` | Git merge completed |
|
|
150
|
+
| `make_command` | Make target executed |
|
|
151
|
+
| `claude_hook_*` | Claude Code hook events |
|
|
152
|
+
|
|
153
|
+
## API
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
const marmot = require('marmot-logger');
|
|
157
|
+
|
|
158
|
+
// Log an event programmatically
|
|
159
|
+
await marmot.log({
|
|
160
|
+
event: 'custom_event',
|
|
161
|
+
path: '/path/to/file',
|
|
162
|
+
size: 1234,
|
|
163
|
+
metadata: { custom: 'data' }
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Verify logs
|
|
167
|
+
const results = await marmot.verifyLogs('./logs/file_events_2025-12-22.log');
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
package/bin/marmot.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const pkg = require('../package.json');
|
|
6
|
+
|
|
7
|
+
const init = require('../src/cli/init');
|
|
8
|
+
const enable = require('../src/cli/enable');
|
|
9
|
+
const disable = require('../src/cli/disable');
|
|
10
|
+
const status = require('../src/cli/status');
|
|
11
|
+
const verify = require('../src/cli/verify');
|
|
12
|
+
const logs = require('../src/cli/logs');
|
|
13
|
+
const monitor = require('../src/cli/monitor');
|
|
14
|
+
const log = require('../src/cli/log');
|
|
15
|
+
const login = require('../src/cli/login');
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.name('marmot')
|
|
19
|
+
.description('Activity monitoring tool for developer workflows')
|
|
20
|
+
.version(pkg.version);
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command('init')
|
|
24
|
+
.description('Initialize marmot in current project')
|
|
25
|
+
.action(init);
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command('enable <plugin>')
|
|
29
|
+
.description('Enable a plugin (file-monitor, terminal, git-hooks, makefile, claude-hooks)')
|
|
30
|
+
.action(enable);
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command('disable <plugin>')
|
|
34
|
+
.description('Disable a plugin')
|
|
35
|
+
.action(disable);
|
|
36
|
+
|
|
37
|
+
program
|
|
38
|
+
.command('status')
|
|
39
|
+
.description('Show marmot status and enabled plugins')
|
|
40
|
+
.action(status);
|
|
41
|
+
|
|
42
|
+
program
|
|
43
|
+
.command('verify')
|
|
44
|
+
.description('Verify log signatures')
|
|
45
|
+
.option('-f, --file <path>', 'Log file to verify (default: today\'s log)')
|
|
46
|
+
.action(verify);
|
|
47
|
+
|
|
48
|
+
program
|
|
49
|
+
.command('logs')
|
|
50
|
+
.description('View recent log entries')
|
|
51
|
+
.option('-t, --today', 'Show today\'s logs')
|
|
52
|
+
.option('-l, --last <n>', 'Show last N entries', '10')
|
|
53
|
+
.action(logs);
|
|
54
|
+
|
|
55
|
+
program
|
|
56
|
+
.command('monitor')
|
|
57
|
+
.description('Run file monitor once (for cron/scripts)')
|
|
58
|
+
.action(monitor);
|
|
59
|
+
|
|
60
|
+
program
|
|
61
|
+
.command('log <event>')
|
|
62
|
+
.description('Log an event (used by hooks)')
|
|
63
|
+
.option('-d, --data <json>', 'JSON data for the event')
|
|
64
|
+
.action(log);
|
|
65
|
+
|
|
66
|
+
program
|
|
67
|
+
.command('login')
|
|
68
|
+
.description('Login with API key and save to .env')
|
|
69
|
+
.action(login);
|
|
70
|
+
|
|
71
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "marmot-logger",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Activity monitoring tool for developer workflows - tracks file changes, terminal commands, git operations, and Claude Code hooks",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"marmot": "./bin/marmot.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"monitoring",
|
|
14
|
+
"logging",
|
|
15
|
+
"developer-tools",
|
|
16
|
+
"activity-tracking",
|
|
17
|
+
"git-hooks",
|
|
18
|
+
"claude-code"
|
|
19
|
+
],
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"commander": "^12.1.0",
|
|
24
|
+
"chalk": "^4.1.2"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=16.0.0"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"bin/",
|
|
31
|
+
"src/",
|
|
32
|
+
"templates/"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const config = require('../core/config');
|
|
3
|
+
const plugins = require('../plugins');
|
|
4
|
+
|
|
5
|
+
const VALID_PLUGINS = ['file-monitor', 'terminal', 'git-hooks', 'makefile', 'claude-hooks'];
|
|
6
|
+
|
|
7
|
+
module.exports = async function disable(pluginName) {
|
|
8
|
+
if (!VALID_PLUGINS.includes(pluginName)) {
|
|
9
|
+
console.log(chalk.red(`Unknown plugin: ${pluginName}`));
|
|
10
|
+
console.log('Available plugins:', VALID_PLUGINS.join(', '));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const projectConfig = config.loadConfig();
|
|
15
|
+
if (!projectConfig) {
|
|
16
|
+
console.log(chalk.red('Marmot not initialized. Run: marmot init'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!config.isPluginEnabled(projectConfig, pluginName)) {
|
|
21
|
+
console.log(chalk.yellow(`Plugin '${pluginName}' is already disabled.`));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Run plugin-specific teardown
|
|
26
|
+
const plugin = plugins[pluginName];
|
|
27
|
+
if (plugin && plugin.disable) {
|
|
28
|
+
try {
|
|
29
|
+
await plugin.disable(projectConfig);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.log(chalk.red(`Teardown error: ${err.message}`));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Disable the plugin in config
|
|
36
|
+
config.disablePlugin(projectConfig, pluginName);
|
|
37
|
+
config.saveConfig(projectConfig);
|
|
38
|
+
|
|
39
|
+
console.log(chalk.green(`✓ Plugin '${pluginName}' disabled.`));
|
|
40
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const config = require('../core/config');
|
|
3
|
+
const plugins = require('../plugins');
|
|
4
|
+
|
|
5
|
+
const VALID_PLUGINS = ['file-monitor', 'terminal', 'git-hooks', 'makefile', 'claude-hooks'];
|
|
6
|
+
|
|
7
|
+
module.exports = async function enable(pluginName) {
|
|
8
|
+
if (!VALID_PLUGINS.includes(pluginName)) {
|
|
9
|
+
console.log(chalk.red(`Unknown plugin: ${pluginName}`));
|
|
10
|
+
console.log('Available plugins:', VALID_PLUGINS.join(', '));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const projectConfig = config.loadConfig();
|
|
15
|
+
if (!projectConfig) {
|
|
16
|
+
console.log(chalk.red('Marmot not initialized. Run: marmot init'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (config.isPluginEnabled(projectConfig, pluginName)) {
|
|
21
|
+
console.log(chalk.yellow(`Plugin '${pluginName}' is already enabled.`));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Enable the plugin in config
|
|
26
|
+
config.enablePlugin(projectConfig, pluginName);
|
|
27
|
+
config.saveConfig(projectConfig);
|
|
28
|
+
|
|
29
|
+
console.log(chalk.green(`✓ Plugin '${pluginName}' enabled.`));
|
|
30
|
+
|
|
31
|
+
// Run plugin-specific setup
|
|
32
|
+
const plugin = plugins[pluginName];
|
|
33
|
+
if (plugin && plugin.enable) {
|
|
34
|
+
try {
|
|
35
|
+
await plugin.enable(projectConfig);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.log(chalk.red(`Setup error: ${err.message}`));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
package/src/cli/init.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const config = require('../core/config');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
function addToGitignore(projectDir, entry) {
|
|
7
|
+
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
8
|
+
|
|
9
|
+
if (fs.existsSync(gitignorePath)) {
|
|
10
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
11
|
+
if (!content.includes(entry)) {
|
|
12
|
+
// Add entry with marmot section if not exists
|
|
13
|
+
let newContent = content;
|
|
14
|
+
if (!content.includes('# Marmot')) {
|
|
15
|
+
newContent = content.trimEnd() + '\n\n# Marmot\n' + entry + '\n';
|
|
16
|
+
} else {
|
|
17
|
+
// Add under existing Marmot section
|
|
18
|
+
newContent = content.replace('# Marmot\n', `# Marmot\n${entry}\n`);
|
|
19
|
+
}
|
|
20
|
+
fs.writeFileSync(gitignorePath, newContent);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
fs.writeFileSync(gitignorePath, `# Marmot\n${entry}\n`);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = async function init() {
|
|
31
|
+
const projectDir = process.cwd();
|
|
32
|
+
const existingConfig = config.loadConfig(projectDir);
|
|
33
|
+
|
|
34
|
+
if (existingConfig) {
|
|
35
|
+
console.log(chalk.yellow('Marmot is already initialized in this project.'));
|
|
36
|
+
console.log(`Configuration file: ${config.getConfigPath(projectDir)}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check for required environment variables
|
|
41
|
+
const signingUrl = config.getSigningUrl(projectDir);
|
|
42
|
+
const apiKey = config.getApiKey(projectDir);
|
|
43
|
+
|
|
44
|
+
if (!apiKey) {
|
|
45
|
+
console.log(chalk.yellow('Warning: MARMOT_API_KEY is not set.'));
|
|
46
|
+
console.log('Set it in your .env file or run:');
|
|
47
|
+
console.log(chalk.cyan(' marmot login'));
|
|
48
|
+
console.log('');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Create marmot directory in /tmp
|
|
52
|
+
const marmotDir = config.getMarmotDir(projectDir);
|
|
53
|
+
if (!fs.existsSync(marmotDir)) {
|
|
54
|
+
fs.mkdirSync(marmotDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Create default config
|
|
58
|
+
const newConfig = config.createDefaultConfig(projectDir);
|
|
59
|
+
|
|
60
|
+
// Create logs directory in project
|
|
61
|
+
const logDir = config.getLogDir(newConfig, projectDir);
|
|
62
|
+
if (!fs.existsSync(logDir)) {
|
|
63
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Add marmot-logs to project's .gitignore
|
|
67
|
+
const logDirName = path.basename(logDir);
|
|
68
|
+
addToGitignore(projectDir, logDirName + '/');
|
|
69
|
+
addToGitignore(projectDir, '.marmot/');
|
|
70
|
+
|
|
71
|
+
console.log(chalk.green('✓ Marmot initialized successfully!'));
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log('Marmot directory:', chalk.cyan(marmotDir));
|
|
74
|
+
console.log('Configuration file:', chalk.cyan(config.getConfigPath(projectDir)));
|
|
75
|
+
console.log('Logs directory:', chalk.cyan(logDir));
|
|
76
|
+
console.log('Signing URL:', chalk.cyan(signingUrl));
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log('Next steps:');
|
|
79
|
+
console.log(chalk.cyan(' marmot enable file-monitor') + ' - Track file changes');
|
|
80
|
+
console.log(chalk.cyan(' marmot enable terminal') + ' - Log terminal commands');
|
|
81
|
+
console.log(chalk.cyan(' marmot enable git-hooks') + ' - Log git operations');
|
|
82
|
+
console.log(chalk.cyan(' marmot enable claude-hooks') + ' - Log Claude Code activity');
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log('View status with:', chalk.cyan('marmot status'));
|
|
85
|
+
};
|
package/src/cli/log.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const config = require('../core/config');
|
|
2
|
+
const logger = require('../core/logger');
|
|
3
|
+
|
|
4
|
+
module.exports = async function log(event, options) {
|
|
5
|
+
const projectConfig = config.loadConfig();
|
|
6
|
+
|
|
7
|
+
if (!projectConfig) {
|
|
8
|
+
// Silent fail for hook usage - don't break the calling process
|
|
9
|
+
process.exit(0);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let data = {};
|
|
13
|
+
|
|
14
|
+
// Try to parse JSON data from option or stdin
|
|
15
|
+
if (options.data) {
|
|
16
|
+
try {
|
|
17
|
+
data = JSON.parse(options.data);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
// Ignore parse errors
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
// Try reading from stdin (for piped data from hooks)
|
|
23
|
+
try {
|
|
24
|
+
const stdin = require('fs').readFileSync(0, 'utf8');
|
|
25
|
+
if (stdin.trim()) {
|
|
26
|
+
data = JSON.parse(stdin);
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
// No stdin or parse error
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Extract details based on event type
|
|
34
|
+
let eventPath = data.path || '';
|
|
35
|
+
let size = data.size || 0;
|
|
36
|
+
const extra = {};
|
|
37
|
+
|
|
38
|
+
// Handle Claude hook events
|
|
39
|
+
if (event.startsWith('claude-')) {
|
|
40
|
+
const hookType = event.replace('claude-', '');
|
|
41
|
+
event = `claude_hook_${hookType}`;
|
|
42
|
+
|
|
43
|
+
// Extract relevant fields based on hook type
|
|
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)}`;
|
|
57
|
+
} else {
|
|
58
|
+
eventPath = toolName;
|
|
59
|
+
}
|
|
60
|
+
} else if (hookType === 'Stop' || hookType === 'SubagentStop') {
|
|
61
|
+
eventPath = data.stop_reason || 'completed';
|
|
62
|
+
} else if (hookType === 'UserPromptSubmit') {
|
|
63
|
+
const prompt = data.prompt || data.user_prompt || '';
|
|
64
|
+
eventPath = `prompt: ${prompt.substring(0, 200)}`;
|
|
65
|
+
} else if (hookType === 'SessionStart' || hookType === 'SessionEnd') {
|
|
66
|
+
const sessionId = data.session_id || 'unknown';
|
|
67
|
+
eventPath = `${hookType.toLowerCase()}: ${sessionId}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Handle git events
|
|
72
|
+
if (event.startsWith('git-')) {
|
|
73
|
+
const gitEvent = event.replace('git-', '');
|
|
74
|
+
event = `git_${gitEvent}`;
|
|
75
|
+
eventPath = data.path || data.details || '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle make events
|
|
79
|
+
if (event === 'make') {
|
|
80
|
+
event = 'make_command';
|
|
81
|
+
eventPath = data.target || data.path || '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Handle terminal events
|
|
85
|
+
if (event === 'terminal') {
|
|
86
|
+
extra.command = data.command || '';
|
|
87
|
+
eventPath = data.path || process.cwd();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Add any additional fields
|
|
91
|
+
if (data.additions !== undefined) extra.additions = data.additions;
|
|
92
|
+
if (data.deletions !== undefined) extra.deletions = data.deletions;
|
|
93
|
+
if (data.command && event !== 'terminal') extra.command = data.command;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
await logger.logEvent(event, eventPath, size, extra);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
// Silent fail for hook usage
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
process.exit(0);
|
|
102
|
+
};
|
package/src/cli/login.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
const config = require('../core/config');
|
|
6
|
+
const signer = require('../core/signer');
|
|
7
|
+
|
|
8
|
+
function prompt(question) {
|
|
9
|
+
const rl = readline.createInterface({
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stdout
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
rl.question(question, (answer) => {
|
|
16
|
+
rl.close();
|
|
17
|
+
resolve(answer.trim());
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function updateEnvFile(projectDir, key, value) {
|
|
23
|
+
const envPath = path.join(projectDir, '.env');
|
|
24
|
+
let content = '';
|
|
25
|
+
|
|
26
|
+
if (fs.existsSync(envPath)) {
|
|
27
|
+
content = fs.readFileSync(envPath, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check if key already exists
|
|
31
|
+
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
32
|
+
if (regex.test(content)) {
|
|
33
|
+
// Update existing key
|
|
34
|
+
content = content.replace(regex, `${key}=${value}`);
|
|
35
|
+
} else {
|
|
36
|
+
// Add new key
|
|
37
|
+
if (content && !content.endsWith('\n')) {
|
|
38
|
+
content += '\n';
|
|
39
|
+
}
|
|
40
|
+
// Add section comment if this is the first marmot key
|
|
41
|
+
if (!content.includes('# Marmot')) {
|
|
42
|
+
content += '\n# Marmot logging\n';
|
|
43
|
+
}
|
|
44
|
+
content += `${key}=${value}\n`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fs.writeFileSync(envPath, content);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = async function login() {
|
|
51
|
+
const projectDir = process.cwd();
|
|
52
|
+
const projectConfig = config.loadConfig(projectDir);
|
|
53
|
+
|
|
54
|
+
if (!projectConfig) {
|
|
55
|
+
console.log(chalk.red('Marmot not initialized. Run: marmot init'));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(chalk.bold('Marmot Login'));
|
|
60
|
+
console.log('─'.repeat(40));
|
|
61
|
+
console.log('');
|
|
62
|
+
|
|
63
|
+
// Check current status
|
|
64
|
+
const currentKey = config.getApiKey(projectDir);
|
|
65
|
+
if (currentKey) {
|
|
66
|
+
console.log(chalk.yellow('API key is already configured.'));
|
|
67
|
+
const overwrite = await prompt('Overwrite? (y/N): ');
|
|
68
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
69
|
+
console.log('Cancelled.');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log('');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Prompt for API key
|
|
76
|
+
const apiKey = await prompt('Enter your MARMOT_API_KEY: ');
|
|
77
|
+
|
|
78
|
+
if (!apiKey) {
|
|
79
|
+
console.log(chalk.red('No API key provided.'));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Test the API key
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log('Testing API key...');
|
|
86
|
+
|
|
87
|
+
// Temporarily set the key in environment for testing
|
|
88
|
+
process.env.MARMOT_API_KEY = apiKey;
|
|
89
|
+
signer.clearTokenCache();
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await signer.getToken(projectDir);
|
|
93
|
+
console.log(chalk.green('✓ API key is valid!'));
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.log(chalk.red(`✗ API key test failed: ${err.message}`));
|
|
96
|
+
console.log('');
|
|
97
|
+
console.log('The key was not saved. Please check your API key and try again.');
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Write to .env file
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log('Saving to .env...');
|
|
104
|
+
updateEnvFile(projectDir, 'MARMOT_API_KEY', apiKey);
|
|
105
|
+
|
|
106
|
+
// Also save MARMOT_URL if not already set
|
|
107
|
+
const currentUrl = config.getSigningUrl(projectDir);
|
|
108
|
+
if (currentUrl === 'https://logging.drazou.net') {
|
|
109
|
+
// Check if it's from default or actually in .env
|
|
110
|
+
const envFile = config.loadEnvFile ? config.loadEnvFile(projectDir) : {};
|
|
111
|
+
if (!envFile.MARMOT_URL && !process.env.MARMOT_URL) {
|
|
112
|
+
updateEnvFile(projectDir, 'MARMOT_URL', 'https://logging.drazou.net');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(chalk.green('✓ Credentials saved to .env'));
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log('You can now use marmot with signed logging.');
|
|
119
|
+
console.log('Run', chalk.cyan('marmot status'), 'to verify.');
|
|
120
|
+
};
|