entroly-wasm 0.9.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,108 @@
1
+ // Entroly Checkpoint Manager — JS port of checkpoint.py
2
+ // State persistence with gzip-compressed JSON.
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const zlib = require('zlib');
7
+ const crypto = require('crypto');
8
+
9
+ class CheckpointManager {
10
+ constructor(checkpointDir, autoInterval = 5) {
11
+ this.checkpointDir = checkpointDir;
12
+ this.autoInterval = autoInterval;
13
+ this._callsSinceCheckpoint = 0;
14
+ this._totalCheckpoints = 0;
15
+ fs.mkdirSync(checkpointDir, { recursive: true });
16
+ }
17
+
18
+ shouldAutoCheckpoint() {
19
+ this._callsSinceCheckpoint++;
20
+ return this._callsSinceCheckpoint >= this.autoInterval;
21
+ }
22
+
23
+ save(data) {
24
+ this._callsSinceCheckpoint = 0;
25
+ this._totalCheckpoints++;
26
+ const id = crypto.randomBytes(8).toString('hex');
27
+ const checkpoint = {
28
+ checkpoint_id: id,
29
+ timestamp: new Date().toISOString(),
30
+ ...data,
31
+ };
32
+ const json = JSON.stringify(checkpoint);
33
+ const compressed = zlib.gzipSync(json);
34
+ const filePath = path.join(this.checkpointDir, `checkpoint_${id}.json.gz`);
35
+ fs.writeFileSync(filePath, compressed);
36
+
37
+ // Keep only last 5 checkpoints
38
+ this._pruneOld(5);
39
+ return filePath;
40
+ }
41
+
42
+ loadLatest() {
43
+ try {
44
+ const files = fs.readdirSync(this.checkpointDir)
45
+ .filter(f => f.startsWith('checkpoint_') && f.endsWith('.json.gz'))
46
+ .sort()
47
+ .reverse();
48
+ if (!files.length) return null;
49
+ const filePath = path.join(this.checkpointDir, files[0]);
50
+ const compressed = fs.readFileSync(filePath);
51
+ const json = zlib.gunzipSync(compressed).toString();
52
+ return JSON.parse(json);
53
+ } catch { return null; }
54
+ }
55
+
56
+ _pruneOld(keep) {
57
+ try {
58
+ const files = fs.readdirSync(this.checkpointDir)
59
+ .filter(f => f.startsWith('checkpoint_') && f.endsWith('.json.gz'))
60
+ .sort();
61
+ while (files.length > keep) {
62
+ const old = files.shift();
63
+ try { fs.unlinkSync(path.join(this.checkpointDir, old)); } catch {}
64
+ }
65
+ } catch {}
66
+ }
67
+
68
+ stats() {
69
+ return {
70
+ checkpoint_dir: this.checkpointDir,
71
+ total_checkpoints: this._totalCheckpoints,
72
+ calls_since_last: this._callsSinceCheckpoint,
73
+ };
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Persist engine index to a gzip-compressed JSON file.
79
+ * @param {WasmEntrolyEngine} engine
80
+ * @param {string} indexPath - Path to index.json.gz
81
+ */
82
+ function persistIndex(engine, indexPath) {
83
+ try {
84
+ const state = engine.export_state();
85
+ const json = JSON.stringify(state);
86
+ const compressed = zlib.gzipSync(json);
87
+ fs.writeFileSync(indexPath, compressed);
88
+ return true;
89
+ } catch { return false; }
90
+ }
91
+
92
+ /**
93
+ * Load engine index from a gzip-compressed JSON file.
94
+ * @param {WasmEntrolyEngine} engine
95
+ * @param {string} indexPath
96
+ */
97
+ function loadIndex(engine, indexPath) {
98
+ try {
99
+ if (!fs.existsSync(indexPath)) return false;
100
+ const compressed = fs.readFileSync(indexPath);
101
+ const json = zlib.gunzipSync(compressed).toString();
102
+ const state = JSON.parse(json);
103
+ engine.import_state(JSON.stringify(state));
104
+ return true;
105
+ } catch { return false; }
106
+ }
107
+
108
+ module.exports = { CheckpointManager, persistIndex, loadIndex };
package/js/cli.js ADDED
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+ // Entroly CLI — JS port of cli.py
3
+ // Zero-friction onboarding for AI coding agents.
4
+ //
5
+ // Commands:
6
+ // entroly optimize Generate optimized context snapshot
7
+ // entroly serve Start MCP server with auto-indexing
8
+ // entroly health Analyze codebase health (grade A-F)
9
+ // entroly status Check if server is running
10
+ // entroly stats Show session statistics
11
+ // entroly init Auto-detect project + AI tool, generate MCP config
12
+ // entroly demo Before/after demo showing token savings
13
+ // entroly clean Clear cached state
14
+
15
+ const { WasmEntrolyEngine } = require('../pkg/entroly_wasm');
16
+ const { EntrolyConfig } = require('./config');
17
+ const { autoIndex } = require('./auto_index');
18
+ const { persistIndex, loadIndex } = require('./checkpoint');
19
+ const { EntrolyMCPServer } = require('./server');
20
+ const { runAutotune } = require('./autotune');
21
+ const path = require('path');
22
+ const fs = require('fs');
23
+ const os = require('os');
24
+
25
+ const VERSION = require('../package.json').version;
26
+
27
+ // ANSI colors
28
+ const C = {
29
+ BOLD: '\x1b[1m', GREEN: '\x1b[38;5;82m', CYAN: '\x1b[38;5;45m',
30
+ YELLOW: '\x1b[38;5;220m', RED: '\x1b[38;5;196m', GRAY: '\x1b[38;5;240m',
31
+ RESET: '\x1b[0m',
32
+ };
33
+
34
+ function banner() {
35
+ return `${C.CYAN}${C.BOLD} Entroly${C.RESET} v${VERSION} — information-theoretic context optimization`;
36
+ }
37
+
38
+ // ── Commands ──
39
+
40
+ function cmdServe() {
41
+ const server = new EntrolyMCPServer();
42
+ server.run();
43
+ }
44
+
45
+ function cmdOptimize(args) {
46
+ const config = new EntrolyConfig();
47
+ const engine = new WasmEntrolyEngine();
48
+ const indexPath = path.join(config.checkpointDir, 'index.json.gz');
49
+
50
+ // Load persistent index
51
+ if (loadIndex(engine, indexPath)) {
52
+ console.error(`${C.GREEN}✓${C.RESET} Loaded persistent index (${engine.fragment_count()} fragments)`);
53
+ }
54
+
55
+ // Auto-index if no fragments
56
+ if (engine.fragment_count() === 0) {
57
+ console.error(`${C.CYAN}⏳${C.RESET} Auto-indexing codebase...`);
58
+ const result = autoIndex(engine);
59
+ console.error(`${C.GREEN}✓${C.RESET} Indexed ${result.files_indexed} files (${result.total_tokens.toLocaleString()} tokens) in ${result.duration_s}s`);
60
+ persistIndex(engine, indexPath);
61
+ }
62
+
63
+ const budget = parseInt(args[0] || '128000', 10);
64
+ const query = args.slice(1).join(' ') || '';
65
+
66
+ if (!query) {
67
+ console.error(`${C.YELLOW || ''}! Missing task. Usage: entroly-wasm optimize <budget> "<task>"${C.RESET || ''}`);
68
+ console.error(` Example: entroly-wasm optimize 8000 "fix the auth bug"`);
69
+ process.exit(2);
70
+ }
71
+ if (engine.fragment_count() < 2) {
72
+ console.error(`${C.YELLOW || ''}! Only ${engine.fragment_count()} fragment indexed — cd into a real repo before optimizing.${C.RESET || ''}`);
73
+ process.exit(2);
74
+ }
75
+
76
+ engine.advance_turn();
77
+ const result = engine.optimize(budget, query);
78
+
79
+ // Persist after optimization
80
+ persistIndex(engine, indexPath);
81
+
82
+ console.log(JSON.stringify(result, null, 2));
83
+ }
84
+
85
+ function cmdHealth() {
86
+ const config = new EntrolyConfig();
87
+ const engine = new WasmEntrolyEngine();
88
+ const indexPath = path.join(config.checkpointDir, 'index.json.gz');
89
+
90
+ loadIndex(engine, indexPath);
91
+ if (engine.fragment_count() === 0) {
92
+ console.error(`${C.CYAN}⏳${C.RESET} Auto-indexing codebase...`);
93
+ autoIndex(engine);
94
+ persistIndex(engine, indexPath);
95
+ }
96
+
97
+ // Don't declare a grade on an empty scan — guards against false 'A' on blank dirs.
98
+ const n = engine.fragment_count();
99
+ if (n < 5) {
100
+ console.error(`${C.YELLOW || ''}! Only ${n} fragment${n===1?'':'s'} indexed — scan something with real code (cd into your repo, then retry).${C.RESET || ''}`);
101
+ process.exit(2);
102
+ }
103
+ const health = engine.analyze_health();
104
+ console.log(typeof health === 'string' ? health : JSON.stringify(health, null, 2));
105
+ }
106
+
107
+ function cmdStats() {
108
+ const config = new EntrolyConfig();
109
+ const engine = new WasmEntrolyEngine();
110
+ loadIndex(engine, path.join(config.checkpointDir, 'index.json.gz'));
111
+
112
+ const stats = engine.stats();
113
+ console.log(typeof stats === 'string' ? stats : JSON.stringify(stats, null, 2));
114
+ }
115
+
116
+ function cmdInit() {
117
+ console.log(banner());
118
+ console.log();
119
+
120
+ // Detect IDE
121
+ const cwd = process.cwd();
122
+ const cursorDir = path.join(cwd, '.cursor');
123
+ const vscodeDir = path.join(cwd, '.vscode');
124
+
125
+ if (fs.existsSync(cursorDir) || fs.existsSync(path.join(os.homedir(), '.cursor'))) {
126
+ console.log(`${C.GREEN}✓${C.RESET} Detected: ${C.BOLD}Cursor${C.RESET}`);
127
+ const mcpConfig = {
128
+ mcpServers: {
129
+ entroly: {
130
+ command: 'npx',
131
+ args: ['-y', 'entroly-wasm', '--serve'],
132
+ },
133
+ },
134
+ };
135
+ const configDir = fs.existsSync(cursorDir) ? cursorDir : path.join(os.homedir(), '.cursor');
136
+ const configPath = path.join(configDir, 'mcp.json');
137
+
138
+ // Merge with existing config; backup before overwrite so we never lose user data.
139
+ let existing = {};
140
+ let parseFailed = false;
141
+ if (fs.existsSync(configPath)) {
142
+ try { existing = JSON.parse(fs.readFileSync(configPath, 'utf-8')); }
143
+ catch { parseFailed = true; }
144
+ try { fs.copyFileSync(configPath, configPath + '.entroly-backup'); } catch {}
145
+ }
146
+ existing.mcpServers = { ...existing.mcpServers, ...mcpConfig.mcpServers };
147
+
148
+ fs.mkdirSync(configDir, { recursive: true });
149
+ fs.writeFileSync(configPath, JSON.stringify(existing, null, 2));
150
+ if (parseFailed) {
151
+ console.log(`${C.YELLOW || ''}! Existing config was unparseable; original kept at ${configPath}.entroly-backup${C.RESET}`);
152
+ }
153
+ console.log(`${C.GREEN}✓${C.RESET} Written MCP config to ${C.CYAN}${configPath}${C.RESET}`);
154
+ } else if (fs.existsSync(vscodeDir)) {
155
+ console.log(`${C.GREEN}✓${C.RESET} Detected: ${C.BOLD}VS Code${C.RESET}`);
156
+ console.log(` Add to your MCP settings:`);
157
+ console.log(` ${C.CYAN}"entroly": { "command": "npx", "args": ["-y", "entroly-wasm", "--serve"] }${C.RESET}`);
158
+ } else {
159
+ console.log(` No IDE detected. To use Entroly as an MCP server, run:`);
160
+ console.log(` ${C.CYAN}npx entroly-wasm --serve${C.RESET}`);
161
+ }
162
+
163
+ console.log();
164
+ console.log(` ${C.BOLD}Quick start:${C.RESET}`);
165
+ console.log(` ${C.CYAN}npx entroly-wasm optimize 8000 "fix the auth bug"${C.RESET}`);
166
+ console.log(` ${C.CYAN}npx entroly-wasm health${C.RESET}`);
167
+ console.log(` ${C.CYAN}npx entroly-wasm demo${C.RESET}`);
168
+ }
169
+
170
+ function cmdDemo() {
171
+ console.log(banner());
172
+ console.log();
173
+ console.log(` ${C.BOLD}Before/After Demo${C.RESET}`);
174
+ console.log();
175
+
176
+ const engine = new WasmEntrolyEngine();
177
+
178
+ // Create sample fragments
179
+ const samples = [
180
+ { content: 'function authenticate(user, pass) {\n const hash = bcrypt.hash(pass, 10);\n return db.users.findOne({ user, hash });\n}', source: 'file:auth.js', tokens: 45, pinned: true },
181
+ { content: 'export const API_KEY = process.env.API_KEY;\nexport const DB_URL = process.env.DATABASE_URL;\nexport const PORT = 3000;', source: 'file:config.js', tokens: 30, pinned: false },
182
+ { content: 'class PaymentProcessor {\n constructor(stripe) { this.stripe = stripe; }\n async charge(amount, currency, token) {\n return this.stripe.charges.create({ amount, currency, source: token });\n }\n}', source: 'file:payments.js', tokens: 55, pinned: false },
183
+ { content: 'import { test, expect } from "vitest";\ntest("auth works", () => {\n expect(authenticate("admin", "pass")).toBeTruthy();\n});', source: 'file:auth.test.js', tokens: 35, pinned: false },
184
+ { content: '# API Documentation\n\n## Endpoints\n\n### POST /api/auth\nBody: { username, password }\nResponse: { token, expiresIn }\n\n### POST /api/pay\nBody: { amount, currency, token }', source: 'file:README.md', tokens: 40, pinned: false },
185
+ ];
186
+
187
+ for (const s of samples) engine.ingest(s.content, s.source, s.tokens, s.pinned);
188
+ const totalTokens = samples.reduce((sum, s) => sum + s.tokens, 0);
189
+
190
+ console.log(` ${C.GRAY}Ingested ${samples.length} fragments (${totalTokens} tokens total)${C.RESET}`);
191
+ console.log();
192
+
193
+ // Without optimization (all fragments)
194
+ console.log(` ${C.RED}Without Entroly:${C.RESET} ${totalTokens} tokens → send everything`);
195
+ console.log(` ${C.GRAY} Cost: $${(totalTokens * 0.000015).toFixed(6)}/call${C.RESET}`);
196
+ console.log();
197
+
198
+ // With optimization
199
+ engine.advance_turn();
200
+ const result = engine.optimize(80, 'fix the authentication bug');
201
+ const optimized = result.total_tokens || 0;
202
+
203
+ console.log(` ${C.GREEN}With Entroly:${C.RESET} ${optimized} tokens → mathematically optimal subset`);
204
+ console.log(` ${C.GRAY} Cost: $${(optimized * 0.000015).toFixed(6)}/call${C.RESET}`);
205
+ console.log(` ${C.GREEN} Saved: ${totalTokens - optimized} tokens (${Math.round((1 - optimized / totalTokens) * 100)}% reduction)${C.RESET}`);
206
+ console.log();
207
+
208
+ // Show which fragments were selected
209
+ const selected = result.selected || [];
210
+ if (selected.length) {
211
+ console.log(` ${C.BOLD}Selected fragments:${C.RESET}`);
212
+ for (const frag of selected) {
213
+ if (frag.variant === 'full') {
214
+ console.log(` ${C.GREEN}✓${C.RESET} ${frag.source} (${frag.token_count} tokens, relevance=${frag.relevance})`);
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ function cmdGo() {
221
+ console.log(`\n${C.CYAN}${C.BOLD} Entroly Daemon${C.RESET} — self-evolving context engine\n`);
222
+ console.log(` ${C.GRAY}Auto-detecting IDE, indexing codebase, starting MCP server...${C.RESET}\n`);
223
+
224
+ // Step 1: Init (detect IDE + write config)
225
+ cmdInit();
226
+
227
+ // Step 2: Serve (start MCP daemon)
228
+ console.log(`\n ${C.GREEN}${C.BOLD}Starting MCP server...${C.RESET}\n`);
229
+ cmdServe();
230
+ }
231
+
232
+ function cmdGateways() {
233
+ console.log(banner());
234
+ console.log();
235
+ console.log(` ${C.BOLD}Chat Gateways${C.RESET} — stream evolution events to Telegram/Discord/Slack\n`);
236
+
237
+ // Delegate to the standalone entrypoint in gateways.js
238
+ const gwPath = path.join(__dirname, 'gateways.js');
239
+ require(gwPath);
240
+ }
241
+
242
+ function cmdClean() {
243
+ const config = new EntrolyConfig();
244
+ const dir = config.checkpointDir;
245
+ try {
246
+ fs.rmSync(dir, { recursive: true, force: true });
247
+ console.log(`${C.GREEN}✓${C.RESET} Cleaned: ${dir}`);
248
+ } catch (e) {
249
+ console.error(`${C.RED}✗${C.RESET} Failed to clean: ${e.message}`);
250
+ }
251
+ }
252
+
253
+ function cmdStatus() {
254
+ const config = new EntrolyConfig();
255
+ const indexPath = path.join(config.checkpointDir, 'index.json.gz');
256
+ const hasIndex = fs.existsSync(indexPath);
257
+ console.log(`${C.BOLD}Entroly Status${C.RESET}`);
258
+ console.log(` Version: ${VERSION}`);
259
+ console.log(` Engine: Wasm (standalone)`);
260
+ console.log(` Index: ${hasIndex ? C.GREEN + '✓ persistent index found' + C.RESET : C.YELLOW + '⚠ no index (run optimize first)' + C.RESET}`);
261
+ console.log(` Checkpoint: ${config.checkpointDir}`);
262
+ }
263
+
264
+ function cmdAutotune(args) {
265
+ const iterations = parseInt(args[0] || '100', 10);
266
+ const benchOnly = args.includes('--bench-only');
267
+ console.log(banner());
268
+ console.log();
269
+ console.log(` ${C.BOLD}Autotune${C.RESET} — autonomous self-tuning loop`);
270
+ console.log(` Iterations: ${iterations}${benchOnly ? ' (bench-only)' : ''}`);
271
+ console.log();
272
+ runAutotune(iterations, 5000, benchOnly);
273
+ }
274
+
275
+ function cmdHelp() {
276
+ console.log(banner());
277
+ console.log();
278
+ console.log(` ${C.BOLD}Usage:${C.RESET} entroly-wasm [command] [options]`);
279
+ console.log();
280
+ console.log(` ${C.BOLD}Commands:${C.RESET}`);
281
+ console.log(` ${C.CYAN}go${C.RESET} ${C.BOLD}One command: detect IDE → index → start daemon${C.RESET} (default)`);
282
+ console.log(` ${C.CYAN}serve${C.RESET} Start MCP server (stdio JSON-RPC)`);
283
+ console.log(` ${C.CYAN}optimize${C.RESET} Generate optimized context (args: [budget] [query...])`);
284
+ console.log(` ${C.CYAN}health${C.RESET} Analyze codebase health (grade A-F)`);
285
+ console.log(` ${C.CYAN}demo${C.RESET} Before/after demo showing token savings`);
286
+ console.log(` ${C.CYAN}gateways${C.RESET} Stream evolution events to Telegram/Discord/Slack`);
287
+ console.log(` ${C.CYAN}init${C.RESET} Auto-detect IDE and generate MCP config`);
288
+ console.log(` ${C.CYAN}stats${C.RESET} Show session statistics`);
289
+ console.log(` ${C.CYAN}status${C.RESET} Check environment status`);
290
+ console.log(` ${C.CYAN}autotune${C.RESET} Run autonomous self-tuning (args: [iterations] [--bench-only])`);
291
+ console.log(` ${C.CYAN}clean${C.RESET} Clear cached state`);
292
+ console.log();
293
+ console.log(` ${C.BOLD}Examples:${C.RESET}`);
294
+ console.log(` ${C.GRAY}npx entroly-wasm${C.RESET} ${C.GRAY}# auto-detect + start${C.RESET}`);
295
+ console.log(` ${C.GRAY}npx entroly-wasm optimize 8000 "fix the auth bug"${C.RESET}`);
296
+ console.log(` ${C.GRAY}npx entroly-wasm health${C.RESET}`);
297
+ console.log(` ${C.GRAY}npx entroly-wasm gateways${C.RESET}`);
298
+ }
299
+
300
+ // ── Main ──
301
+ const [,, cmd, ...args] = process.argv;
302
+
303
+ switch (cmd) {
304
+ case 'go': case undefined: cmdGo(); break;
305
+ case 'serve': case '--serve': case 'server': cmdServe(); break;
306
+ case 'optimize': case 'opt': cmdOptimize(args); break;
307
+ case 'health': cmdHealth(); break;
308
+ case 'stats': cmdStats(); break;
309
+ case 'init': cmdInit(); break;
310
+ case 'demo': cmdDemo(); break;
311
+ case 'gateways': case 'gateway': cmdGateways(); break;
312
+ case 'clean': cmdClean(); break;
313
+ case 'status': cmdStatus(); break;
314
+ case 'autotune': case 'tune': cmdAutotune(args); break;
315
+ case '--version': case '-v': console.log(VERSION); break;
316
+ case '--help': case '-h': cmdHelp(); break;
317
+ default:
318
+ console.error(`Unknown command: ${cmd}`);
319
+ cmdHelp();
320
+ process.exit(1);
321
+ }