envseed 0.1.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.
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * envseed postinstall — sets up hooks and config after npm install.
5
+ * Node.js rewrite of install.sh for cross-platform compatibility.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const HOME = process.env.HOME || process.env.USERPROFILE;
14
+ const INSTALL_DIR = path.join(HOME, '.propensity-monitor');
15
+ const CLAUDE_SETTINGS = path.join(HOME, '.claude', 'settings.json');
16
+ const COMMANDS_DIR = path.join(HOME, '.claude', 'commands');
17
+
18
+ // Default config — uploadEndpoint is filled after deploy
19
+ const DEFAULT_CONFIG = {
20
+ enabled: true,
21
+ alertThreshold: 3,
22
+ logAllEvents: true,
23
+ maxLogSizeMB: 500,
24
+ s3Bucket: 'metr-propensity-monitor',
25
+ s3Region: 'us-east-1',
26
+ s3Profile: '',
27
+ uploadEndpoint: 'https://envseed-api.sydv793.workers.dev',
28
+ githubClientId: 'Ov23lid2fKxyN7lOd9qv',
29
+ apiKey: '',
30
+ simulationCount: 2,
31
+ simulationMaxTurns: 100,
32
+ simulationConcurrency: 5,
33
+ simulationModels: ['claude-haiku-4-5-20251001'],
34
+ enableSimulations: true,
35
+ replicaModel: 'claude-opus-4-6',
36
+ redactionReview: true,
37
+ proxyUrl: '',
38
+ proxyToken: '',
39
+ };
40
+
41
+ function mkdirp(dir) {
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ }
44
+
45
+ function copyDir(src, dest) {
46
+ mkdirp(dest);
47
+ for (const entry of fs.readdirSync(src)) {
48
+ const srcPath = path.join(src, entry);
49
+ const destPath = path.join(dest, entry);
50
+ if (fs.statSync(srcPath).isFile()) {
51
+ fs.copyFileSync(srcPath, destPath);
52
+ }
53
+ }
54
+ }
55
+
56
+ function readJson(filePath) {
57
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { return null; }
58
+ }
59
+
60
+ function writeJson(filePath, data) {
61
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
62
+ }
63
+
64
+ try {
65
+ console.log('Planting envseed...');
66
+
67
+ // 1. Create directory structure
68
+ for (const sub of ['bin', 'lib', 'data/events', 'data/sessions', 'data/alerts', 'data/incidents', 'data/replicas']) {
69
+ mkdirp(path.join(INSTALL_DIR, sub));
70
+ }
71
+
72
+ // 2. Copy lib and bin files from npm package
73
+ copyDir(path.join(__dirname, 'lib'), path.join(INSTALL_DIR, 'lib'));
74
+ copyDir(path.join(__dirname, 'bin'), path.join(INSTALL_DIR, 'bin'));
75
+
76
+ // Copy Dockerfile and entrypoint if present
77
+ for (const f of ['Dockerfile.simulation', 'entrypoint.sh']) {
78
+ const src = path.join(__dirname, f);
79
+ if (fs.existsSync(src)) {
80
+ fs.copyFileSync(src, path.join(INSTALL_DIR, f));
81
+ }
82
+ }
83
+
84
+ // Store source dir for later sync
85
+ fs.writeFileSync(path.join(INSTALL_DIR, '.source-dir'), __dirname + '\n');
86
+
87
+ // 3. Make CLI executable
88
+ try {
89
+ fs.chmodSync(path.join(INSTALL_DIR, 'bin', 'propensity-monitor.mjs'), 0o755);
90
+ } catch {}
91
+
92
+ // 4. Install slash command
93
+ mkdirp(COMMANDS_DIR);
94
+ const cmdSrc = path.join(__dirname, 'commands', 'log-incident.md');
95
+ if (fs.existsSync(cmdSrc)) {
96
+ fs.copyFileSync(cmdSrc, path.join(COMMANDS_DIR, 'log-incident.md'));
97
+ console.log(' Slash command planted: /log-incident');
98
+ }
99
+
100
+ // 5. Create or merge config
101
+ const configPath = path.join(INSTALL_DIR, 'config.json');
102
+ let config = readJson(configPath) || {};
103
+ let added = 0;
104
+ for (const [key, value] of Object.entries(DEFAULT_CONFIG)) {
105
+ if (!(key in config)) {
106
+ config[key] = value;
107
+ added++;
108
+ }
109
+ }
110
+ writeJson(configPath, config);
111
+ if (added > 0) console.log(` Added ${added} config key(s)`);
112
+
113
+ // 6. Initialize index
114
+ const indexPath = path.join(INSTALL_DIR, 'data', 'index.json');
115
+ if (!fs.existsSync(indexPath)) {
116
+ writeJson(indexPath, {});
117
+ }
118
+
119
+ // 7. Register hooks in Claude settings
120
+ mkdirp(path.dirname(CLAUDE_SETTINGS));
121
+ let settings = readJson(CLAUDE_SETTINGS) || {};
122
+ if (!settings.hooks) settings.hooks = {};
123
+
124
+ const handlerCmd = `node ${INSTALL_DIR}/lib/hook-handler.mjs`;
125
+ const hookEntry = { type: 'command', command: handlerCmd, timeout: 15 };
126
+ const events = ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'SessionStart', 'Stop'];
127
+ let hooksAdded = 0;
128
+
129
+ for (const event of events) {
130
+ if (!settings.hooks[event]) settings.hooks[event] = [];
131
+
132
+ // Remove old flat entries
133
+ settings.hooks[event] = settings.hooks[event].filter(entry => {
134
+ if (entry.command && entry.command.includes('propensity-monitor') && !entry.hooks) return false;
135
+ return true;
136
+ });
137
+
138
+ // Check if already installed
139
+ const already = settings.hooks[event].some(entry => {
140
+ if (entry.hooks) return entry.hooks.some(h => h.command && h.command.includes('propensity-monitor'));
141
+ return false;
142
+ });
143
+
144
+ if (!already) {
145
+ settings.hooks[event].push({ hooks: [{ ...hookEntry }] });
146
+ hooksAdded++;
147
+ }
148
+ }
149
+
150
+ writeJson(CLAUDE_SETTINGS, settings);
151
+ if (hooksAdded > 0) console.log(` ${hooksAdded} hooks registered`);
152
+
153
+ console.log('');
154
+ console.log('envseed planted successfully!');
155
+ console.log('');
156
+ console.log(' Next steps:');
157
+ console.log(' 1. Run: envseed register');
158
+ console.log(' 2. Restart Claude Code');
159
+ console.log('');
160
+ console.log(' Run "envseed status" to check health.');
161
+
162
+ } catch (err) {
163
+ // Don't fail the npm install if postinstall has issues
164
+ console.error('envseed postinstall warning:', err.message);
165
+ }