make-laten 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 +132 -0
- package/dist/cli/index.js +1302 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +1279 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/index.js +2036 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1946 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,1279 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/cli/commands/read.ts
|
|
7
|
+
import fs from "fs/promises";
|
|
8
|
+
|
|
9
|
+
// src/compress/confidence.ts
|
|
10
|
+
function calculateConfidence(original, compressed) {
|
|
11
|
+
if (!original.length) return 0;
|
|
12
|
+
let score = 1;
|
|
13
|
+
const ratio = compressed.length / original.length;
|
|
14
|
+
if (ratio < 0.01) score -= 0.5;
|
|
15
|
+
else if (ratio < 0.05) score -= 0.3;
|
|
16
|
+
if (codeBlocksModified(original, compressed)) score -= 0.4;
|
|
17
|
+
if (isKnownPattern(original)) score += 0.1;
|
|
18
|
+
return Math.max(0, Math.min(1, score));
|
|
19
|
+
}
|
|
20
|
+
function codeBlocksModified(original, compressed) {
|
|
21
|
+
const codeBlockRegex = /```[\s\S]*?```/g;
|
|
22
|
+
const originalBlocks = original.match(codeBlockRegex) || [];
|
|
23
|
+
const compressedBlocks = compressed.match(codeBlockRegex) || [];
|
|
24
|
+
if (originalBlocks.length !== compressedBlocks.length) return true;
|
|
25
|
+
for (let i = 0; i < originalBlocks.length; i++) {
|
|
26
|
+
if (originalBlocks[i] !== compressedBlocks[i]) return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
function isKnownPattern(content) {
|
|
31
|
+
const patterns = [
|
|
32
|
+
/export\s+(default\s+)?(function|class|const|let|var)/,
|
|
33
|
+
/import\s+.*from\s+['"]/
|
|
34
|
+
];
|
|
35
|
+
return patterns.some((p) => p.test(content));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/compress/file-read.ts
|
|
39
|
+
var FileReadCompressor = class {
|
|
40
|
+
async compress(input) {
|
|
41
|
+
const { content, filePath, language } = input;
|
|
42
|
+
const signatures = this.extractSignatures(content, language);
|
|
43
|
+
const exports = this.extractExports(content);
|
|
44
|
+
const lines = [
|
|
45
|
+
`// ${filePath} (${content.split("\n").length} lines)`,
|
|
46
|
+
"",
|
|
47
|
+
...signatures,
|
|
48
|
+
"",
|
|
49
|
+
"// Exports:",
|
|
50
|
+
...exports
|
|
51
|
+
];
|
|
52
|
+
const compressed = lines.join("\n");
|
|
53
|
+
return {
|
|
54
|
+
content: compressed,
|
|
55
|
+
original: content,
|
|
56
|
+
confidence: calculateConfidence(content, compressed),
|
|
57
|
+
metadata: {
|
|
58
|
+
strategy: "signatures",
|
|
59
|
+
originalLines: content.split("\n").length,
|
|
60
|
+
compressedLines: lines.length,
|
|
61
|
+
savings: 1 - compressed.length / content.length
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async decompress(compressed) {
|
|
66
|
+
return compressed.original;
|
|
67
|
+
}
|
|
68
|
+
extractSignatures(content, language) {
|
|
69
|
+
const signatures = [];
|
|
70
|
+
const lines = content.split("\n");
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
const trimmed = line.trim();
|
|
73
|
+
if (/^(export\s+)?(async\s+)?function\s+\w+/.test(trimmed)) {
|
|
74
|
+
signatures.push(trimmed.replace(/\{[\s\S]*$/, "{ ... }"));
|
|
75
|
+
}
|
|
76
|
+
if (/^(export\s+)?class\s+\w+/.test(trimmed)) {
|
|
77
|
+
signatures.push(trimmed);
|
|
78
|
+
}
|
|
79
|
+
if (/^(export\s+)?(type|interface)\s+\w+/.test(trimmed)) {
|
|
80
|
+
signatures.push(trimmed);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return signatures;
|
|
84
|
+
}
|
|
85
|
+
extractExports(content) {
|
|
86
|
+
const exports = [];
|
|
87
|
+
const exportRegex = /export\s+(default\s+)?(function|class|const|let|var|type|interface)\s+(\w+)/g;
|
|
88
|
+
let match;
|
|
89
|
+
while ((match = exportRegex.exec(content)) !== null) {
|
|
90
|
+
exports.push(` - ${match[3]} (${match[2]})`);
|
|
91
|
+
}
|
|
92
|
+
return exports;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/cli/commands/read.ts
|
|
97
|
+
function detectLanguage(filePath) {
|
|
98
|
+
const ext = filePath.split(".").pop()?.toLowerCase() || "";
|
|
99
|
+
const langMap = {
|
|
100
|
+
ts: "typescript",
|
|
101
|
+
tsx: "typescript",
|
|
102
|
+
js: "javascript",
|
|
103
|
+
jsx: "javascript",
|
|
104
|
+
py: "python",
|
|
105
|
+
rs: "rust",
|
|
106
|
+
go: "go",
|
|
107
|
+
java: "java",
|
|
108
|
+
rb: "ruby",
|
|
109
|
+
json: "json",
|
|
110
|
+
yaml: "yaml",
|
|
111
|
+
yml: "yaml",
|
|
112
|
+
md: "markdown"
|
|
113
|
+
};
|
|
114
|
+
return langMap[ext] || "text";
|
|
115
|
+
}
|
|
116
|
+
async function readCommand(filePath) {
|
|
117
|
+
try {
|
|
118
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
119
|
+
const compressor = new FileReadCompressor();
|
|
120
|
+
const result = await compressor.compress({
|
|
121
|
+
content,
|
|
122
|
+
filePath,
|
|
123
|
+
language: detectLanguage(filePath)
|
|
124
|
+
});
|
|
125
|
+
console.log(JSON.stringify({
|
|
126
|
+
file: filePath,
|
|
127
|
+
compressed: result.content,
|
|
128
|
+
confidence: result.confidence,
|
|
129
|
+
metadata: result.metadata
|
|
130
|
+
}, null, 2));
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(`Error reading ${filePath}:`, error);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/cli/commands/grep.ts
|
|
138
|
+
import { exec } from "child_process";
|
|
139
|
+
import { promisify } from "util";
|
|
140
|
+
|
|
141
|
+
// src/compress/grep.ts
|
|
142
|
+
var GrepCompressor = class {
|
|
143
|
+
async compress(input) {
|
|
144
|
+
const { results } = input;
|
|
145
|
+
const grouped = this.groupByFile(results);
|
|
146
|
+
const lines = [];
|
|
147
|
+
for (const [file, matches] of grouped) {
|
|
148
|
+
lines.push(`${file} (${matches.length} matches)`);
|
|
149
|
+
const unique = this.deduplicate(matches);
|
|
150
|
+
for (const match of unique) {
|
|
151
|
+
lines.push(` L${match.line}: ${match.content}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const compressed = lines.join("\n");
|
|
155
|
+
const original = results.map((r) => `${r.file}:${r.line}: ${r.content}`).join("\n");
|
|
156
|
+
return {
|
|
157
|
+
content: compressed,
|
|
158
|
+
original,
|
|
159
|
+
confidence: calculateConfidence(original, compressed),
|
|
160
|
+
metadata: {
|
|
161
|
+
strategy: "grouped",
|
|
162
|
+
totalMatches: results.length,
|
|
163
|
+
uniqueFiles: grouped.size,
|
|
164
|
+
savings: 1 - compressed.length / original.length
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async decompress(compressed) {
|
|
169
|
+
return compressed.original;
|
|
170
|
+
}
|
|
171
|
+
groupByFile(results) {
|
|
172
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
173
|
+
for (const result of results) {
|
|
174
|
+
const existing = grouped.get(result.file) || [];
|
|
175
|
+
existing.push(result);
|
|
176
|
+
grouped.set(result.file, existing);
|
|
177
|
+
}
|
|
178
|
+
return grouped;
|
|
179
|
+
}
|
|
180
|
+
deduplicate(matches) {
|
|
181
|
+
const seen = /* @__PURE__ */ new Set();
|
|
182
|
+
return matches.filter((match) => {
|
|
183
|
+
const key = match.content.trim();
|
|
184
|
+
if (seen.has(key)) return false;
|
|
185
|
+
seen.add(key);
|
|
186
|
+
return true;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// src/cli/commands/grep.ts
|
|
192
|
+
var execAsync = promisify(exec);
|
|
193
|
+
function parseGrepOutput(output) {
|
|
194
|
+
const matches = [];
|
|
195
|
+
const lines = output.split("\n").filter(Boolean);
|
|
196
|
+
for (const line of lines) {
|
|
197
|
+
const parts = line.split(":");
|
|
198
|
+
if (parts.length >= 3) {
|
|
199
|
+
matches.push({
|
|
200
|
+
file: parts[0],
|
|
201
|
+
line: parseInt(parts[1], 10) || 0,
|
|
202
|
+
content: parts.slice(2).join(":").trim()
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return matches;
|
|
207
|
+
}
|
|
208
|
+
async function grepCommand(pattern, directory, options) {
|
|
209
|
+
try {
|
|
210
|
+
let cmd = `grep -rn "${pattern}" ${directory}`;
|
|
211
|
+
if (options.ignore) {
|
|
212
|
+
cmd += ` --include="*.${options.ignore}"`;
|
|
213
|
+
}
|
|
214
|
+
const { stdout } = await execAsync(cmd, { maxBuffer: 10 * 1024 * 1024 });
|
|
215
|
+
const matches = parseGrepOutput(stdout);
|
|
216
|
+
if (matches.length === 0) {
|
|
217
|
+
console.log(JSON.stringify({ pattern, directory, matches: [], total: 0 }));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const compressor = new GrepCompressor();
|
|
221
|
+
const result = await compressor.compress({
|
|
222
|
+
results: matches,
|
|
223
|
+
pattern,
|
|
224
|
+
directory
|
|
225
|
+
});
|
|
226
|
+
console.log(JSON.stringify({
|
|
227
|
+
pattern,
|
|
228
|
+
directory,
|
|
229
|
+
compressed: result.content,
|
|
230
|
+
confidence: result.confidence,
|
|
231
|
+
total: matches.length,
|
|
232
|
+
metadata: result.metadata
|
|
233
|
+
}, null, 2));
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (error.code === 1) {
|
|
236
|
+
console.log(JSON.stringify({ pattern, directory, matches: [], total: 0 }));
|
|
237
|
+
} else {
|
|
238
|
+
console.error(`Error grep:`, error.message);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/cli/commands/git.ts
|
|
245
|
+
import { exec as exec2 } from "child_process";
|
|
246
|
+
import { promisify as promisify2 } from "util";
|
|
247
|
+
|
|
248
|
+
// src/compress/git-diff.ts
|
|
249
|
+
var GitDiffCompressor = class {
|
|
250
|
+
async compress(input) {
|
|
251
|
+
const { diff } = input;
|
|
252
|
+
const hunks = this.parseDiff(diff);
|
|
253
|
+
const condensed = hunks.map((hunk) => ({
|
|
254
|
+
file: hunk.file,
|
|
255
|
+
changes: this.condenseHunk(hunk),
|
|
256
|
+
stats: { additions: hunk.additions, deletions: hunk.deletions }
|
|
257
|
+
}));
|
|
258
|
+
const lines = [];
|
|
259
|
+
for (const hunk of condensed) {
|
|
260
|
+
if (hunk.changes.length > 0) {
|
|
261
|
+
lines.push(`${hunk.file} (+${hunk.stats.additions} -${hunk.stats.deletions})`);
|
|
262
|
+
lines.push(...hunk.changes);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const compressed = lines.join("\n");
|
|
266
|
+
return {
|
|
267
|
+
content: compressed,
|
|
268
|
+
original: diff,
|
|
269
|
+
confidence: calculateConfidence(diff, compressed),
|
|
270
|
+
metadata: {
|
|
271
|
+
strategy: "condensed",
|
|
272
|
+
filesChanged: condensed.length,
|
|
273
|
+
savings: 1 - compressed.length / diff.length
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
async decompress(compressed) {
|
|
278
|
+
return compressed.original;
|
|
279
|
+
}
|
|
280
|
+
parseDiff(diff) {
|
|
281
|
+
const hunks = [];
|
|
282
|
+
const lines = diff.split("\n");
|
|
283
|
+
let currentHunk = null;
|
|
284
|
+
for (const line of lines) {
|
|
285
|
+
if (line.startsWith("diff --git")) {
|
|
286
|
+
if (currentHunk) hunks.push(currentHunk);
|
|
287
|
+
const fileMatch = line.match(/b\/(.+)/);
|
|
288
|
+
currentHunk = {
|
|
289
|
+
file: fileMatch?.[1] || "unknown",
|
|
290
|
+
lines: [],
|
|
291
|
+
additions: 0,
|
|
292
|
+
deletions: 0
|
|
293
|
+
};
|
|
294
|
+
} else if (currentHunk) {
|
|
295
|
+
if (line.startsWith("+")) {
|
|
296
|
+
currentHunk.additions++;
|
|
297
|
+
currentHunk.lines.push({ type: "addition", content: line.slice(1) });
|
|
298
|
+
} else if (line.startsWith("-")) {
|
|
299
|
+
currentHunk.deletions++;
|
|
300
|
+
currentHunk.lines.push({ type: "deletion", content: line.slice(1) });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (currentHunk) hunks.push(currentHunk);
|
|
305
|
+
return hunks;
|
|
306
|
+
}
|
|
307
|
+
condenseHunk(hunk) {
|
|
308
|
+
return hunk.lines.filter((l) => l.type === "addition" || l.type === "deletion").map((l) => `${l.type === "addition" ? "+" : "-"} ${l.content}`);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// src/cli/commands/git.ts
|
|
313
|
+
var execAsync2 = promisify2(exec2);
|
|
314
|
+
async function gitDiffCommand(options) {
|
|
315
|
+
try {
|
|
316
|
+
const flag = options.staged ? "--staged" : "";
|
|
317
|
+
const { stdout } = await execAsync2(`git diff ${flag}`, { maxBuffer: 10 * 1024 * 1024 });
|
|
318
|
+
if (!stdout.trim()) {
|
|
319
|
+
console.log(JSON.stringify({ diff: "", changes: 0 }));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const compressor = new GitDiffCompressor();
|
|
323
|
+
const result = await compressor.compress({ diff: stdout });
|
|
324
|
+
console.log(JSON.stringify({
|
|
325
|
+
compressed: result.content,
|
|
326
|
+
confidence: result.confidence,
|
|
327
|
+
metadata: result.metadata
|
|
328
|
+
}, null, 2));
|
|
329
|
+
} catch (error) {
|
|
330
|
+
console.error(`Error git diff:`, error.message);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function gitStatusCommand() {
|
|
335
|
+
try {
|
|
336
|
+
const { stdout } = await execAsync2("git status --porcelain");
|
|
337
|
+
const lines = stdout.split("\n").filter(Boolean);
|
|
338
|
+
const files = lines.map((line) => ({
|
|
339
|
+
status: line[0],
|
|
340
|
+
path: line.slice(3)
|
|
341
|
+
}));
|
|
342
|
+
console.log(JSON.stringify({
|
|
343
|
+
total: files.length,
|
|
344
|
+
files
|
|
345
|
+
}, null, 2));
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error(`Error git status:`, error.message);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// src/graph/database.ts
|
|
353
|
+
import Database from "better-sqlite3";
|
|
354
|
+
var SCHEMA = `
|
|
355
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
356
|
+
id TEXT PRIMARY KEY,
|
|
357
|
+
type TEXT NOT NULL,
|
|
358
|
+
content TEXT NOT NULL,
|
|
359
|
+
original TEXT,
|
|
360
|
+
embedding BLOB,
|
|
361
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
362
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
363
|
+
accessed_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
364
|
+
access_count INTEGER DEFAULT 0
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
368
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
369
|
+
source TEXT NOT NULL,
|
|
370
|
+
target TEXT NOT NULL,
|
|
371
|
+
type TEXT NOT NULL,
|
|
372
|
+
weight REAL DEFAULT 1.0,
|
|
373
|
+
metadata TEXT,
|
|
374
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
375
|
+
FOREIGN KEY (source) REFERENCES nodes(id),
|
|
376
|
+
FOREIGN KEY (target) REFERENCES nodes(id)
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);
|
|
380
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_created ON nodes(created_at);
|
|
381
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_accessed ON nodes(accessed_at);
|
|
382
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source);
|
|
383
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target);
|
|
384
|
+
CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);
|
|
385
|
+
`;
|
|
386
|
+
async function createDatabase(dbPath) {
|
|
387
|
+
const db = new Database(dbPath);
|
|
388
|
+
db.pragma("journal_mode = WAL");
|
|
389
|
+
db.pragma("foreign_keys = ON");
|
|
390
|
+
db.exec(SCHEMA);
|
|
391
|
+
return db;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/cache/l1-session.ts
|
|
395
|
+
var SessionCache = class {
|
|
396
|
+
store = /* @__PURE__ */ new Map();
|
|
397
|
+
hits = 0;
|
|
398
|
+
misses = 0;
|
|
399
|
+
get(key) {
|
|
400
|
+
const entry = this.store.get(key);
|
|
401
|
+
if (entry) {
|
|
402
|
+
this.hits++;
|
|
403
|
+
return entry;
|
|
404
|
+
}
|
|
405
|
+
this.misses++;
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
set(key, value) {
|
|
409
|
+
this.store.set(key, value);
|
|
410
|
+
}
|
|
411
|
+
clear() {
|
|
412
|
+
this.store.clear();
|
|
413
|
+
this.hits = 0;
|
|
414
|
+
this.misses = 0;
|
|
415
|
+
}
|
|
416
|
+
stats() {
|
|
417
|
+
const total = this.hits + this.misses;
|
|
418
|
+
return {
|
|
419
|
+
hits: this.hits,
|
|
420
|
+
misses: this.misses,
|
|
421
|
+
size: this.store.size,
|
|
422
|
+
hitRate: total > 0 ? this.hits / total : 0
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// src/cache/l2-cross.ts
|
|
428
|
+
var CrossSessionCache = class {
|
|
429
|
+
constructor(db) {
|
|
430
|
+
this.db = db;
|
|
431
|
+
}
|
|
432
|
+
db;
|
|
433
|
+
async get(key) {
|
|
434
|
+
const row = this.db.prepare(`
|
|
435
|
+
SELECT content, metadata, created_at
|
|
436
|
+
FROM nodes
|
|
437
|
+
WHERE id = ? AND type = 'cache'
|
|
438
|
+
`).get(key);
|
|
439
|
+
if (!row) return null;
|
|
440
|
+
const metadata = JSON.parse(row.metadata);
|
|
441
|
+
if (metadata.ttl) {
|
|
442
|
+
const age = Date.now() / 1e3 - row.created_at;
|
|
443
|
+
if (age > metadata.ttl) {
|
|
444
|
+
this.db.prepare("DELETE FROM nodes WHERE id = ?").run(key);
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
this.db.prepare(`
|
|
449
|
+
UPDATE nodes SET accessed_at = unixepoch(), access_count = access_count + 1
|
|
450
|
+
WHERE id = ?
|
|
451
|
+
`).run(key);
|
|
452
|
+
return {
|
|
453
|
+
content: row.content,
|
|
454
|
+
metadata: JSON.parse(row.metadata)
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
async set(key, value, options) {
|
|
458
|
+
this.db.prepare(`
|
|
459
|
+
INSERT INTO nodes (id, type, content, metadata, created_at, accessed_at, access_count)
|
|
460
|
+
VALUES (?, 'cache', ?, ?, unixepoch(), unixepoch(), 1)
|
|
461
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
462
|
+
content = excluded.content,
|
|
463
|
+
metadata = excluded.metadata,
|
|
464
|
+
accessed_at = unixepoch(),
|
|
465
|
+
access_count = access_count + 1
|
|
466
|
+
`).run(key, value.content, JSON.stringify({ ...value.metadata, ttl: options?.ttl }));
|
|
467
|
+
}
|
|
468
|
+
async delete(key) {
|
|
469
|
+
this.db.prepare("DELETE FROM nodes WHERE id = ?").run(key);
|
|
470
|
+
}
|
|
471
|
+
async clear() {
|
|
472
|
+
this.db.prepare("DELETE FROM nodes WHERE type = 'cache'").run();
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// src/cache/index.ts
|
|
477
|
+
function makeLatenCache(db) {
|
|
478
|
+
const l1 = new SessionCache();
|
|
479
|
+
const l2 = new CrossSessionCache(db);
|
|
480
|
+
return {
|
|
481
|
+
l1,
|
|
482
|
+
l2,
|
|
483
|
+
async get(key) {
|
|
484
|
+
const l1Result = l1.get(key);
|
|
485
|
+
if (l1Result) return l1Result;
|
|
486
|
+
const l2Result = await l2.get(key);
|
|
487
|
+
if (l2Result) {
|
|
488
|
+
l1.set(key, l2Result);
|
|
489
|
+
return l2Result;
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
},
|
|
493
|
+
async set(key, value, options) {
|
|
494
|
+
l1.set(key, value);
|
|
495
|
+
await l2.set(key, value, options);
|
|
496
|
+
},
|
|
497
|
+
async invalidate(pattern) {
|
|
498
|
+
l1.clear();
|
|
499
|
+
await l2.clear();
|
|
500
|
+
},
|
|
501
|
+
stats() {
|
|
502
|
+
return {
|
|
503
|
+
l1: l1.stats(),
|
|
504
|
+
l2: { size: 0 }
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/cli/commands/cache.ts
|
|
511
|
+
async function cacheStatsCommand() {
|
|
512
|
+
try {
|
|
513
|
+
const dbPath = process.env.MAKE_LATEN_DB || ".make-laten.db";
|
|
514
|
+
const db = await createDatabase(dbPath);
|
|
515
|
+
const cache = makeLatenCache(db);
|
|
516
|
+
const stats = cache.stats();
|
|
517
|
+
console.log(JSON.stringify({
|
|
518
|
+
l1: {
|
|
519
|
+
hits: stats.l1.hits,
|
|
520
|
+
misses: stats.l1.misses,
|
|
521
|
+
size: stats.l1.size,
|
|
522
|
+
hitRate: `${(stats.l1.hitRate * 100).toFixed(1)}%`
|
|
523
|
+
},
|
|
524
|
+
l2: {
|
|
525
|
+
size: stats.l2.size
|
|
526
|
+
}
|
|
527
|
+
}, null, 2));
|
|
528
|
+
db.close();
|
|
529
|
+
} catch (error) {
|
|
530
|
+
console.error(`Error cache stats:`, error.message);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async function cacheClearCommand() {
|
|
535
|
+
try {
|
|
536
|
+
const dbPath = process.env.MAKE_LATEN_DB || ".make-laten.db";
|
|
537
|
+
const db = await createDatabase(dbPath);
|
|
538
|
+
const cache = makeLatenCache(db);
|
|
539
|
+
await cache.invalidate("*");
|
|
540
|
+
console.log(JSON.stringify({ cleared: true }));
|
|
541
|
+
db.close();
|
|
542
|
+
} catch (error) {
|
|
543
|
+
console.error(`Error cache clear:`, error.message);
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/web/backends/duckduckgo.ts
|
|
549
|
+
var DuckDuckGoBackend = class {
|
|
550
|
+
name = "duckduckgo";
|
|
551
|
+
isAvailable() {
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
async search(query, options) {
|
|
555
|
+
const maxResults = options?.maxResults || 5;
|
|
556
|
+
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
557
|
+
const response = await fetch(url, {
|
|
558
|
+
headers: {
|
|
559
|
+
"User-Agent": "Mozilla/5.0 (compatible; make-laten/0.1.0)"
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
const html = await response.text();
|
|
563
|
+
return this.parseResults(html, maxResults);
|
|
564
|
+
}
|
|
565
|
+
parseResults(html, maxResults) {
|
|
566
|
+
const results = [];
|
|
567
|
+
const resultRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
568
|
+
const snippetRegex = /<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
569
|
+
let match;
|
|
570
|
+
const urls = [];
|
|
571
|
+
const titles = [];
|
|
572
|
+
while ((match = resultRegex.exec(html)) !== null && urls.length < maxResults) {
|
|
573
|
+
const rawUrl = match[1];
|
|
574
|
+
const urlMatch = rawUrl.match(/uddg=([^&]+)/);
|
|
575
|
+
const url = urlMatch ? decodeURIComponent(urlMatch[1]) : rawUrl;
|
|
576
|
+
const title = this.stripTags(match[2]).trim();
|
|
577
|
+
if (url && title) {
|
|
578
|
+
urls.push(url);
|
|
579
|
+
titles.push(title);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const snippets = [];
|
|
583
|
+
while ((match = snippetRegex.exec(html)) !== null && snippets.length < maxResults) {
|
|
584
|
+
snippets.push(this.stripTags(match[1]).trim());
|
|
585
|
+
}
|
|
586
|
+
for (let i = 0; i < urls.length; i++) {
|
|
587
|
+
results.push({
|
|
588
|
+
title: titles[i],
|
|
589
|
+
url: urls[i],
|
|
590
|
+
snippet: snippets[i] || "",
|
|
591
|
+
score: 1 - i * 0.1
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
return results;
|
|
595
|
+
}
|
|
596
|
+
stripTags(html) {
|
|
597
|
+
return html.replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'").replace(/"/g, '"');
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
// src/web/backends/index.ts
|
|
602
|
+
function selectBackend(preferred) {
|
|
603
|
+
const backends = [
|
|
604
|
+
new DuckDuckGoBackend()
|
|
605
|
+
];
|
|
606
|
+
if (preferred) {
|
|
607
|
+
const found = backends.find((b) => b.name === preferred);
|
|
608
|
+
if (found) return found;
|
|
609
|
+
}
|
|
610
|
+
return backends.find((b) => b.isAvailable()) || backends[0];
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/web/extractor.ts
|
|
614
|
+
function stripTags(html) {
|
|
615
|
+
return html.replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
616
|
+
}
|
|
617
|
+
function extractTitle(html) {
|
|
618
|
+
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
619
|
+
if (titleMatch) return stripTags(titleMatch[1]).trim();
|
|
620
|
+
const h1Match = html.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
|
|
621
|
+
if (h1Match) return stripTags(h1Match[1]).trim();
|
|
622
|
+
return "Untitled";
|
|
623
|
+
}
|
|
624
|
+
function extractSections(html) {
|
|
625
|
+
const sections = [];
|
|
626
|
+
const headingRegex = /<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi;
|
|
627
|
+
let match;
|
|
628
|
+
while ((match = headingRegex.exec(html)) !== null) {
|
|
629
|
+
const level = parseInt(match[1]);
|
|
630
|
+
const heading = stripTags(match[2]).trim();
|
|
631
|
+
const startPos = match.index + match[0].length;
|
|
632
|
+
const nextHeading = html.substring(startPos).match(/<h[1-6]/i);
|
|
633
|
+
const endPos = nextHeading ? startPos + nextHeading.index : html.length;
|
|
634
|
+
const content = stripTags(html.substring(startPos, endPos)).trim();
|
|
635
|
+
if (!content) continue;
|
|
636
|
+
const importance = level <= 2 ? "primary" : level <= 4 ? "secondary" : "tertiary";
|
|
637
|
+
sections.push({ heading, level, content, importance });
|
|
638
|
+
}
|
|
639
|
+
return sections;
|
|
640
|
+
}
|
|
641
|
+
function extractCodeExamples(html) {
|
|
642
|
+
const examples = [];
|
|
643
|
+
const codeRegex = /<(?:pre|code)[^>]*>[\s\S]*?<(?:code|\/pre)[^>]*>/gi;
|
|
644
|
+
const blockRegex = /<pre[^>]*><code[^>]*class="[^"]*language-(\w+)[^"]*"[^>]*>([\s\S]*?)<\/code><\/pre>/gi;
|
|
645
|
+
let match;
|
|
646
|
+
while ((match = blockRegex.exec(html)) !== null) {
|
|
647
|
+
const language = match[1];
|
|
648
|
+
const code = stripTags(match[2]).trim();
|
|
649
|
+
if (code.length > 10) {
|
|
650
|
+
examples.push({ language, code });
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const inlineRegex = /<code[^>]*class="[^"]*language-(\w+)[^"]*"[^>]*>([\s\S]*?)<\/code>/gi;
|
|
654
|
+
while ((match = inlineRegex.exec(html)) !== null) {
|
|
655
|
+
const language = match[1];
|
|
656
|
+
const code = stripTags(match[2]).trim();
|
|
657
|
+
if (code.length > 20 && !examples.some((e) => e.code === code)) {
|
|
658
|
+
examples.push({ language, code });
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return examples.slice(0, 10);
|
|
662
|
+
}
|
|
663
|
+
function extractKeyPoints(sections) {
|
|
664
|
+
const keyPoints = [];
|
|
665
|
+
for (const section of sections.filter((s) => s.importance === "primary")) {
|
|
666
|
+
const sentences = section.content.split(/[.!?\n]+/).filter((s) => s.trim().length > 10);
|
|
667
|
+
if (sentences.length > 0) {
|
|
668
|
+
keyPoints.push(sentences[0].trim());
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return keyPoints.slice(0, 5);
|
|
672
|
+
}
|
|
673
|
+
function classifyPurpose(html, sections) {
|
|
674
|
+
const text = (html + " " + sections.map((s) => s.heading).join(" ")).toLowerCase();
|
|
675
|
+
if (text.includes("api reference") || text.includes("endpoint") || text.includes("parameters")) return "reference";
|
|
676
|
+
if (text.includes("tutorial") || text.includes("step by step") || text.includes("getting started")) return "tutorial";
|
|
677
|
+
if (text.includes("blog") || text.includes("published") || text.includes("author")) return "blog";
|
|
678
|
+
if (text.includes("install") || text.includes("quickstart") || text.includes("overview")) return "documentation";
|
|
679
|
+
return "documentation";
|
|
680
|
+
}
|
|
681
|
+
function extractSemantic(html, url) {
|
|
682
|
+
const title = extractTitle(html);
|
|
683
|
+
const sections = extractSections(html);
|
|
684
|
+
const codeExamples = extractCodeExamples(html);
|
|
685
|
+
const keyPoints = extractKeyPoints(sections);
|
|
686
|
+
const purpose = classifyPurpose(html, sections);
|
|
687
|
+
return {
|
|
688
|
+
type: "webpage",
|
|
689
|
+
url,
|
|
690
|
+
title,
|
|
691
|
+
purpose,
|
|
692
|
+
sections,
|
|
693
|
+
keyPoints,
|
|
694
|
+
codeExamples,
|
|
695
|
+
metadata: {
|
|
696
|
+
language: html.match(/lang="(\w+)"/)?.[1] || "en",
|
|
697
|
+
author: html.match(/<meta[^>]*name="author"[^>]*content="([^"]+)"/i)?.[1],
|
|
698
|
+
tags: html.match(/<meta[^>]*name="keywords"[^>]*content="([^"]+)"/i)?.[1]?.split(",").map((t) => t.trim())
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// src/web/compressor.ts
|
|
704
|
+
function compressWebContent(semantic, options = {}) {
|
|
705
|
+
const { maxTokens = 1e3, includeCode = true, includeSections = "all" } = options;
|
|
706
|
+
const lines = [];
|
|
707
|
+
lines.push(`# ${semantic.title}`);
|
|
708
|
+
lines.push(`URL: ${semantic.url}`);
|
|
709
|
+
lines.push(`Purpose: ${semantic.purpose}`);
|
|
710
|
+
lines.push("");
|
|
711
|
+
if (semantic.keyPoints.length > 0) {
|
|
712
|
+
lines.push("## Key Points");
|
|
713
|
+
for (const point of semantic.keyPoints) {
|
|
714
|
+
lines.push(`- ${point}`);
|
|
715
|
+
}
|
|
716
|
+
lines.push("");
|
|
717
|
+
}
|
|
718
|
+
const filteredSections = filterSections(semantic.sections, includeSections);
|
|
719
|
+
for (const section of filteredSections) {
|
|
720
|
+
const content = truncateContent(section.content, 500);
|
|
721
|
+
lines.push(`## ${section.heading}`);
|
|
722
|
+
lines.push(content);
|
|
723
|
+
lines.push("");
|
|
724
|
+
}
|
|
725
|
+
if (includeCode && semantic.codeExamples.length > 0) {
|
|
726
|
+
const topExamples = semantic.codeExamples.slice(0, 3);
|
|
727
|
+
lines.push("## Code Examples");
|
|
728
|
+
for (const example of topExamples) {
|
|
729
|
+
lines.push(`\`\`\`${example.language}`);
|
|
730
|
+
lines.push(truncateContent(example.code, 300));
|
|
731
|
+
lines.push("```");
|
|
732
|
+
lines.push("");
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
let result = lines.join("\n");
|
|
736
|
+
const estimatedTokens = estimateTokens(result);
|
|
737
|
+
if (estimatedTokens > maxTokens) {
|
|
738
|
+
result = truncateByTokens(result, maxTokens);
|
|
739
|
+
}
|
|
740
|
+
return result;
|
|
741
|
+
}
|
|
742
|
+
function filterSections(sections, mode) {
|
|
743
|
+
if (mode === "none") return [];
|
|
744
|
+
if (mode === "primary") return sections.filter((s) => s.importance === "primary");
|
|
745
|
+
return sections;
|
|
746
|
+
}
|
|
747
|
+
function truncateContent(content, maxLength) {
|
|
748
|
+
if (content.length <= maxLength) return content;
|
|
749
|
+
return content.slice(0, maxLength) + "...";
|
|
750
|
+
}
|
|
751
|
+
function estimateTokens(text) {
|
|
752
|
+
return Math.ceil(text.length / 4);
|
|
753
|
+
}
|
|
754
|
+
function truncateByTokens(text, maxTokens) {
|
|
755
|
+
const maxChars = maxTokens * 4;
|
|
756
|
+
if (text.length <= maxChars) return text;
|
|
757
|
+
const truncated = text.slice(0, maxChars);
|
|
758
|
+
const lastNewline = truncated.lastIndexOf("\n");
|
|
759
|
+
return truncated.slice(0, lastNewline > 0 ? lastNewline : maxChars) + "\n\n... (truncated)";
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/web/router.ts
|
|
763
|
+
var WebRouter = class {
|
|
764
|
+
fetchCache = /* @__PURE__ */ new Map();
|
|
765
|
+
searchCache = /* @__PURE__ */ new Map();
|
|
766
|
+
cacheTtl;
|
|
767
|
+
constructor(options) {
|
|
768
|
+
this.cacheTtl = options?.cacheTtlMs || 60 * 60 * 1e3;
|
|
769
|
+
}
|
|
770
|
+
async search(query, options) {
|
|
771
|
+
const cacheKey = `search:${query}:${JSON.stringify(options || {})}`;
|
|
772
|
+
const cached = this.searchCache.get(cacheKey);
|
|
773
|
+
if (cached && this.isFresh(cacheKey)) {
|
|
774
|
+
return cached;
|
|
775
|
+
}
|
|
776
|
+
const backend = selectBackend(options?.backend);
|
|
777
|
+
const results = await backend.search(query, options);
|
|
778
|
+
this.searchCache.set(cacheKey, results);
|
|
779
|
+
this.storeFreshness(cacheKey);
|
|
780
|
+
return results;
|
|
781
|
+
}
|
|
782
|
+
async fetch(url, options) {
|
|
783
|
+
const cacheKey = `fetch:${url}:${options?.format || "text"}`;
|
|
784
|
+
if (options?.cache !== false) {
|
|
785
|
+
const cached = this.fetchCache.get(cacheKey);
|
|
786
|
+
if (cached && this.isFresh(cacheKey)) {
|
|
787
|
+
return cached;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
const startTime = Date.now();
|
|
791
|
+
const response = await fetch(url);
|
|
792
|
+
const html = await response.text();
|
|
793
|
+
const originalSize = html.length;
|
|
794
|
+
let content;
|
|
795
|
+
let semantic = void 0;
|
|
796
|
+
if (options?.extract !== false) {
|
|
797
|
+
semantic = extractSemantic(html, url);
|
|
798
|
+
content = compressWebContent(semantic, {
|
|
799
|
+
includeCode: true,
|
|
800
|
+
includeSections: options?.format === "html" ? "all" : "primary"
|
|
801
|
+
});
|
|
802
|
+
} else {
|
|
803
|
+
content = html;
|
|
804
|
+
}
|
|
805
|
+
const compressedSize = content.length;
|
|
806
|
+
const result = {
|
|
807
|
+
url,
|
|
808
|
+
title: semantic?.title || extractSimpleTitle(html),
|
|
809
|
+
content,
|
|
810
|
+
semantic,
|
|
811
|
+
metadata: {
|
|
812
|
+
fetchTime: Date.now() - startTime,
|
|
813
|
+
originalSize,
|
|
814
|
+
compressedSize,
|
|
815
|
+
savings: 1 - compressedSize / originalSize
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
if (options?.cache !== false) {
|
|
819
|
+
this.fetchCache.set(cacheKey, result);
|
|
820
|
+
this.storeFreshness(cacheKey);
|
|
821
|
+
}
|
|
822
|
+
return result;
|
|
823
|
+
}
|
|
824
|
+
async extract(url) {
|
|
825
|
+
return this.fetch(url, { extract: true, compress: true });
|
|
826
|
+
}
|
|
827
|
+
async summarize(content, options) {
|
|
828
|
+
const lines = content.split("\n");
|
|
829
|
+
const summaryLines = [];
|
|
830
|
+
let tokenCount = 0;
|
|
831
|
+
const maxTokens = options?.maxTokens || 200;
|
|
832
|
+
for (const line of lines) {
|
|
833
|
+
const lineTokens = Math.ceil(line.length / 4);
|
|
834
|
+
if (tokenCount + lineTokens > maxTokens) break;
|
|
835
|
+
if (line.startsWith("#") || line.startsWith("-") || line.startsWith("*")) {
|
|
836
|
+
summaryLines.push(line);
|
|
837
|
+
tokenCount += lineTokens;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (summaryLines.length === 0 && lines.length > 0) {
|
|
841
|
+
for (const line of lines.slice(0, 5)) {
|
|
842
|
+
const lineTokens = Math.ceil(line.length / 4);
|
|
843
|
+
if (tokenCount + lineTokens > maxTokens) break;
|
|
844
|
+
summaryLines.push(line);
|
|
845
|
+
tokenCount += lineTokens;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return summaryLines.join("\n");
|
|
849
|
+
}
|
|
850
|
+
clearCache() {
|
|
851
|
+
this.fetchCache.clear();
|
|
852
|
+
this.searchCache.clear();
|
|
853
|
+
}
|
|
854
|
+
getCacheStats() {
|
|
855
|
+
return {
|
|
856
|
+
fetchSize: this.fetchCache.size,
|
|
857
|
+
searchSize: this.searchCache.size
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
isFresh(key) {
|
|
861
|
+
const timestamp = this.freshness.get(key);
|
|
862
|
+
if (!timestamp) return false;
|
|
863
|
+
return Date.now() - timestamp < this.cacheTtl;
|
|
864
|
+
}
|
|
865
|
+
storeFreshness(key) {
|
|
866
|
+
this.freshness.set(key, Date.now());
|
|
867
|
+
}
|
|
868
|
+
freshness = /* @__PURE__ */ new Map();
|
|
869
|
+
};
|
|
870
|
+
function extractSimpleTitle(html) {
|
|
871
|
+
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
872
|
+
return titleMatch ? titleMatch[1].replace(/<[^>]+>/g, "").trim() : "Untitled";
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// src/cli/commands/web.ts
|
|
876
|
+
async function searchCommand(query, options) {
|
|
877
|
+
try {
|
|
878
|
+
const router = new WebRouter();
|
|
879
|
+
const results = await router.search(query, {
|
|
880
|
+
maxResults: options.max || 5,
|
|
881
|
+
backend: options.backend
|
|
882
|
+
});
|
|
883
|
+
console.log(JSON.stringify({
|
|
884
|
+
query,
|
|
885
|
+
total: results.length,
|
|
886
|
+
results: results.map((r) => ({
|
|
887
|
+
title: r.title,
|
|
888
|
+
url: r.url,
|
|
889
|
+
snippet: r.snippet,
|
|
890
|
+
score: r.score
|
|
891
|
+
}))
|
|
892
|
+
}, null, 2));
|
|
893
|
+
} catch (error) {
|
|
894
|
+
console.error(`Error search:`, error.message);
|
|
895
|
+
process.exit(1);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
async function fetchCommand(url, options) {
|
|
899
|
+
try {
|
|
900
|
+
const router = new WebRouter();
|
|
901
|
+
const result = await router.fetch(url, {
|
|
902
|
+
compress: options.compress !== false,
|
|
903
|
+
extract: options.extract !== false,
|
|
904
|
+
cache: true
|
|
905
|
+
});
|
|
906
|
+
console.log(JSON.stringify({
|
|
907
|
+
url: result.url,
|
|
908
|
+
title: result.title,
|
|
909
|
+
content: result.content,
|
|
910
|
+
metadata: result.metadata
|
|
911
|
+
}, null, 2));
|
|
912
|
+
} catch (error) {
|
|
913
|
+
console.error(`Error fetch:`, error.message);
|
|
914
|
+
process.exit(1);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// src/adapter/detector.ts
|
|
919
|
+
import { exec as exec3 } from "child_process";
|
|
920
|
+
import { promisify as promisify3 } from "util";
|
|
921
|
+
import fs2 from "fs/promises";
|
|
922
|
+
import path from "path";
|
|
923
|
+
|
|
924
|
+
// src/adapter/types.ts
|
|
925
|
+
var AGENT_CONFIGS = {
|
|
926
|
+
"claude-code": { type: "hook" },
|
|
927
|
+
"codex": { type: "hook" },
|
|
928
|
+
"gemini-cli": { type: "hook" },
|
|
929
|
+
"cursor": { type: "rules", rulesFile: ".cursorrules" },
|
|
930
|
+
"windsurf": { type: "rules", rulesFile: ".windsurfrules" },
|
|
931
|
+
"cline": { type: "rules", rulesFile: ".clinerules" },
|
|
932
|
+
"copilot": { type: "rules", rulesFile: ".github/copilot-instructions.md" }
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
// src/adapter/detector.ts
|
|
936
|
+
var execAsync3 = promisify3(exec3);
|
|
937
|
+
var AgentDetector = class {
|
|
938
|
+
async detectAgents() {
|
|
939
|
+
const agents = [];
|
|
940
|
+
for (const [name, config] of Object.entries(AGENT_CONFIGS)) {
|
|
941
|
+
const detected = await this.detectAgent(name, config.type);
|
|
942
|
+
if (detected) {
|
|
943
|
+
agents.push(detected);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return agents;
|
|
947
|
+
}
|
|
948
|
+
async detectAgent(name, type) {
|
|
949
|
+
const commands = {
|
|
950
|
+
"claude-code": "claude --version",
|
|
951
|
+
"codex": "codex --version",
|
|
952
|
+
"gemini-cli": "gemini --version"
|
|
953
|
+
};
|
|
954
|
+
if (commands[name]) {
|
|
955
|
+
try {
|
|
956
|
+
const { stdout } = await execAsync3(commands[name], { timeout: 5e3 });
|
|
957
|
+
const version = stdout.trim().split("\n")[0];
|
|
958
|
+
const agentPath = await this.getAgentPath(name);
|
|
959
|
+
return { name, type, path: agentPath, version, detected: true };
|
|
960
|
+
} catch {
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (type === "rules") {
|
|
965
|
+
const rulesPath = AGENT_CONFIGS[name]?.rulesFile;
|
|
966
|
+
if (rulesPath) {
|
|
967
|
+
try {
|
|
968
|
+
await fs2.access(path.join(process.cwd(), rulesPath));
|
|
969
|
+
return { name, type, path: process.cwd(), version: null, detected: true };
|
|
970
|
+
} catch {
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
async getAgentPath(name) {
|
|
978
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
979
|
+
const paths = {
|
|
980
|
+
"claude-code": path.join(home, ".claude"),
|
|
981
|
+
"codex": path.join(home, ".codex"),
|
|
982
|
+
"gemini-cli": path.join(home, ".gemini")
|
|
983
|
+
};
|
|
984
|
+
return paths[name] || path.join(home, `.${name}`);
|
|
985
|
+
}
|
|
986
|
+
async hasCommand(cmd) {
|
|
987
|
+
try {
|
|
988
|
+
await execAsync3(`which ${cmd}`, { timeout: 3e3 });
|
|
989
|
+
return true;
|
|
990
|
+
} catch {
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
// src/adapter/hook.ts
|
|
997
|
+
import fs3 from "fs/promises";
|
|
998
|
+
import path2 from "path";
|
|
999
|
+
var HookAdapter = class {
|
|
1000
|
+
name;
|
|
1001
|
+
version = "1.0.0";
|
|
1002
|
+
type = "hook";
|
|
1003
|
+
hooks = {};
|
|
1004
|
+
constructor(name) {
|
|
1005
|
+
this.name = name;
|
|
1006
|
+
}
|
|
1007
|
+
format(input) {
|
|
1008
|
+
const { content, context } = input;
|
|
1009
|
+
const parts = [];
|
|
1010
|
+
if (context?.filePath) {
|
|
1011
|
+
parts.push(`**File:** \`${context.filePath}\``);
|
|
1012
|
+
}
|
|
1013
|
+
parts.push(content);
|
|
1014
|
+
return {
|
|
1015
|
+
output: parts.join("\n\n"),
|
|
1016
|
+
format: this.name,
|
|
1017
|
+
metadata: { filePath: context?.filePath, timestamp: Date.now() }
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
parse(output) {
|
|
1021
|
+
return { content: output, parsed: true };
|
|
1022
|
+
}
|
|
1023
|
+
setHooks(hooks) {
|
|
1024
|
+
this.hooks = hooks;
|
|
1025
|
+
}
|
|
1026
|
+
async intercept(toolCall) {
|
|
1027
|
+
if (this.hooks.preToolUse) {
|
|
1028
|
+
return this.hooks.preToolUse(toolCall);
|
|
1029
|
+
}
|
|
1030
|
+
return toolCall;
|
|
1031
|
+
}
|
|
1032
|
+
async postProcess(result) {
|
|
1033
|
+
if (this.hooks.postToolUse) {
|
|
1034
|
+
return this.hooks.postToolUse(result);
|
|
1035
|
+
}
|
|
1036
|
+
return result;
|
|
1037
|
+
}
|
|
1038
|
+
async install(agentPath) {
|
|
1039
|
+
const hooksDir = path2.join(agentPath, "hooks");
|
|
1040
|
+
await fs3.mkdir(hooksDir, { recursive: true });
|
|
1041
|
+
const preToolHook = `#!/usr/bin/env node
|
|
1042
|
+
const { readFileSync } = require('fs');
|
|
1043
|
+
const input = JSON.parse(readFileSync('/dev/stdin', 'utf-8'));
|
|
1044
|
+
|
|
1045
|
+
if (input.tool === 'Read' || input.tool === 'Bash') {
|
|
1046
|
+
process.env.MAKE_LATEN_INTERCEPT = '1';
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
process.stdout.write(JSON.stringify(input));
|
|
1050
|
+
`;
|
|
1051
|
+
const postToolHook = `#!/usr/bin/env node
|
|
1052
|
+
const { readFileSync } = require('fs');
|
|
1053
|
+
const input = JSON.parse(readFileSync('/dev/stdin', 'utf-8'));
|
|
1054
|
+
|
|
1055
|
+
if (input.output && input.output.length > 10000) {
|
|
1056
|
+
input.output = input.output.slice(0, 10000) + '\\n... (truncated by make-laten)';
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
process.stdout.write(JSON.stringify(input));
|
|
1060
|
+
`;
|
|
1061
|
+
await fs3.writeFile(path2.join(hooksDir, "pre-tool-use.js"), preToolHook);
|
|
1062
|
+
await fs3.writeFile(path2.join(hooksDir, "post-tool-use.js"), postToolHook);
|
|
1063
|
+
}
|
|
1064
|
+
async uninstall(agentPath) {
|
|
1065
|
+
const hooksDir = path2.join(agentPath, "hooks");
|
|
1066
|
+
try {
|
|
1067
|
+
await fs3.rm(path2.join(hooksDir, "pre-tool-use.js"), { force: true });
|
|
1068
|
+
await fs3.rm(path2.join(hooksDir, "post-tool-use.js"), { force: true });
|
|
1069
|
+
} catch {
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async isInstalled(agentPath) {
|
|
1073
|
+
try {
|
|
1074
|
+
await fs3.access(path2.join(agentPath, "hooks", "pre-tool-use.js"));
|
|
1075
|
+
return true;
|
|
1076
|
+
} catch {
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
// src/adapter/rules.ts
|
|
1083
|
+
import fs4 from "fs/promises";
|
|
1084
|
+
import path3 from "path";
|
|
1085
|
+
var RULES_TEMPLATE = `
|
|
1086
|
+
# make-laten efficiency rules
|
|
1087
|
+
|
|
1088
|
+
## Token Optimization
|
|
1089
|
+
- When reading files, prefer using \`make-laten read <file>\` for compressed output
|
|
1090
|
+
- When searching code, prefer using \`make-laten grep <pattern>\` for grouped results
|
|
1091
|
+
- When checking git status, prefer using \`make-laten git diff\` for condensed output
|
|
1092
|
+
|
|
1093
|
+
## Caching
|
|
1094
|
+
- make-laten automatically caches frequently accessed files
|
|
1095
|
+
- Use \`make-laten cache stats\` to see cache performance
|
|
1096
|
+
- Use \`make-laten cache clear\` to reset cache if needed
|
|
1097
|
+
|
|
1098
|
+
## Web Search
|
|
1099
|
+
- Use \`make-laten search <query>\` for optimized web search
|
|
1100
|
+
- Use \`make-laten fetch <url>\` for compressed web fetch
|
|
1101
|
+
`;
|
|
1102
|
+
var RulesAdapter = class {
|
|
1103
|
+
name;
|
|
1104
|
+
version = "1.0.0";
|
|
1105
|
+
type = "rules";
|
|
1106
|
+
constructor(agentName) {
|
|
1107
|
+
this.name = agentName;
|
|
1108
|
+
}
|
|
1109
|
+
format(input) {
|
|
1110
|
+
return {
|
|
1111
|
+
output: input.content,
|
|
1112
|
+
format: "markdown",
|
|
1113
|
+
metadata: { agent: this.name, timestamp: Date.now() }
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
parse(output) {
|
|
1117
|
+
return { content: output };
|
|
1118
|
+
}
|
|
1119
|
+
async install(agentPath) {
|
|
1120
|
+
const config = AGENT_CONFIGS[this.name];
|
|
1121
|
+
if (!config?.rulesFile) {
|
|
1122
|
+
throw new Error(`No rules file configured for ${this.name}`);
|
|
1123
|
+
}
|
|
1124
|
+
const rulesPath = path3.join(agentPath, config.rulesFile);
|
|
1125
|
+
const existing = await fs4.readFile(rulesPath, "utf-8").catch(() => "");
|
|
1126
|
+
if (existing.includes("make-laten")) {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
await fs4.mkdir(path3.dirname(rulesPath), { recursive: true });
|
|
1130
|
+
await fs4.writeFile(rulesPath, existing + "\n\n" + RULES_TEMPLATE);
|
|
1131
|
+
}
|
|
1132
|
+
async uninstall(agentPath) {
|
|
1133
|
+
const config = AGENT_CONFIGS[this.name];
|
|
1134
|
+
if (!config?.rulesFile) return;
|
|
1135
|
+
const rulesPath = path3.join(agentPath, config.rulesFile);
|
|
1136
|
+
try {
|
|
1137
|
+
let content = await fs4.readFile(rulesPath, "utf-8");
|
|
1138
|
+
const marker = "# make-laten efficiency rules";
|
|
1139
|
+
const idx = content.indexOf(marker);
|
|
1140
|
+
if (idx !== -1) {
|
|
1141
|
+
const endIdx = content.indexOf("\n#", idx + marker.length);
|
|
1142
|
+
content = content.slice(0, idx).trim() + "\n" + (endIdx !== -1 ? content.slice(endIdx) : "");
|
|
1143
|
+
await fs4.writeFile(rulesPath, content.trim() + "\n");
|
|
1144
|
+
}
|
|
1145
|
+
} catch {
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
async isInstalled(agentPath) {
|
|
1149
|
+
const config = AGENT_CONFIGS[this.name];
|
|
1150
|
+
if (!config?.rulesFile) return false;
|
|
1151
|
+
try {
|
|
1152
|
+
const content = await fs4.readFile(path3.join(agentPath, config.rulesFile), "utf-8");
|
|
1153
|
+
return content.includes("make-laten");
|
|
1154
|
+
} catch {
|
|
1155
|
+
return false;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
|
|
1160
|
+
// src/adapter/installer.ts
|
|
1161
|
+
var Installer = class {
|
|
1162
|
+
detector = new AgentDetector();
|
|
1163
|
+
async install() {
|
|
1164
|
+
const agents = await this.detector.detectAgents();
|
|
1165
|
+
const installed = [];
|
|
1166
|
+
const skipped = [];
|
|
1167
|
+
for (const agent of agents) {
|
|
1168
|
+
try {
|
|
1169
|
+
const adapter = this.createAdapter(agent);
|
|
1170
|
+
if (adapter.install) {
|
|
1171
|
+
await adapter.install(agent.path);
|
|
1172
|
+
installed.push(agent.name);
|
|
1173
|
+
} else {
|
|
1174
|
+
skipped.push(agent.name);
|
|
1175
|
+
}
|
|
1176
|
+
} catch {
|
|
1177
|
+
skipped.push(agent.name);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return { installed, skipped };
|
|
1181
|
+
}
|
|
1182
|
+
async uninstall() {
|
|
1183
|
+
const agents = await this.detector.detectAgents();
|
|
1184
|
+
const removed = [];
|
|
1185
|
+
for (const agent of agents) {
|
|
1186
|
+
try {
|
|
1187
|
+
const adapter = this.createAdapter(agent);
|
|
1188
|
+
if (adapter.uninstall) {
|
|
1189
|
+
await adapter.uninstall(agent.path);
|
|
1190
|
+
removed.push(agent.name);
|
|
1191
|
+
}
|
|
1192
|
+
} catch {
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return { removed };
|
|
1196
|
+
}
|
|
1197
|
+
async status() {
|
|
1198
|
+
const agents = await this.detector.detectAgents();
|
|
1199
|
+
for (const agent of agents) {
|
|
1200
|
+
const adapter = this.createAdapter(agent);
|
|
1201
|
+
if (adapter.isInstalled) {
|
|
1202
|
+
agent.detected = await adapter.isInstalled(agent.path);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
return agents;
|
|
1206
|
+
}
|
|
1207
|
+
createAdapter(agent) {
|
|
1208
|
+
const config = AGENT_CONFIGS[agent.name];
|
|
1209
|
+
if (config?.type === "rules") {
|
|
1210
|
+
return new RulesAdapter(agent.name);
|
|
1211
|
+
}
|
|
1212
|
+
return new HookAdapter(agent.name);
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
// src/cli/commands/install.ts
|
|
1217
|
+
async function installCommand(options) {
|
|
1218
|
+
const installer = new Installer();
|
|
1219
|
+
if (options.status) {
|
|
1220
|
+
console.log("Detecting installed agents...\n");
|
|
1221
|
+
const agents = await installer.status();
|
|
1222
|
+
if (agents.length === 0) {
|
|
1223
|
+
console.log("No supported agents detected.");
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
for (const agent of agents) {
|
|
1227
|
+
const status = agent.detected ? "\u2713 installed" : "\u25CB detected";
|
|
1228
|
+
console.log(` ${status} ${agent.name} (${agent.type})${agent.version ? ` v${agent.version}` : ""}`);
|
|
1229
|
+
}
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (options.uninstall) {
|
|
1233
|
+
console.log("Uninstalling make-laten adapters...\n");
|
|
1234
|
+
const { removed } = await installer.uninstall();
|
|
1235
|
+
if (removed.length === 0) {
|
|
1236
|
+
console.log("Nothing to uninstall.");
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
for (const name of removed) {
|
|
1240
|
+
console.log(` \u2713 removed from ${name}`);
|
|
1241
|
+
}
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
console.log("Installing make-laten adapters...\n");
|
|
1245
|
+
const { installed, skipped } = await installer.install();
|
|
1246
|
+
if (installed.length === 0 && skipped.length === 0) {
|
|
1247
|
+
console.log("No supported agents detected.");
|
|
1248
|
+
console.log("make-laten CLI works standalone \u2014 use commands directly:");
|
|
1249
|
+
console.log(" make-laten read <file>");
|
|
1250
|
+
console.log(" make-laten grep <pattern>");
|
|
1251
|
+
console.log(" make-laten git diff");
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
for (const name of installed) {
|
|
1255
|
+
console.log(` \u2713 ${name} configured`);
|
|
1256
|
+
}
|
|
1257
|
+
for (const name of skipped) {
|
|
1258
|
+
console.log(` \u25CB ${name} skipped (already configured or unavailable)`);
|
|
1259
|
+
}
|
|
1260
|
+
console.log(`
|
|
1261
|
+
Done! Configured for ${installed.length} agent(s).`);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// src/cli/index.ts
|
|
1265
|
+
var program = new Command();
|
|
1266
|
+
program.name("make-laten").description("Universal efficiency skill for AI coding agents").version("0.1.0");
|
|
1267
|
+
program.command("read").description("Compressed file read").argument("<file>", "File path").action(readCommand);
|
|
1268
|
+
program.command("grep").description("Compressed grep with file grouping").argument("<pattern>", "Search pattern").argument("[directory]", "Directory to search", ".").option("-i, --ignore <ext>", "File extension to ignore").action(grepCommand);
|
|
1269
|
+
var gitCmd = program.command("git").description("Git operations");
|
|
1270
|
+
gitCmd.command("diff").description("Compressed git diff").option("-s, --staged", "Show staged changes").action(gitDiffCommand);
|
|
1271
|
+
gitCmd.command("status").description("Git status summary").action(gitStatusCommand);
|
|
1272
|
+
var cacheCmd = program.command("cache").description("Cache management");
|
|
1273
|
+
cacheCmd.command("stats").description("Show cache statistics").action(cacheStatsCommand);
|
|
1274
|
+
cacheCmd.command("clear").description("Clear cache").action(cacheClearCommand);
|
|
1275
|
+
program.command("search").description("Search the web").argument("<query>", "Search query").option("-b, --backend <backend>", "Search backend").option("-m, --max <n>", "Max results", "5").action(searchCommand);
|
|
1276
|
+
program.command("fetch").description("Fetch and compress web content").argument("<url>", "URL to fetch").option("--no-compress", "Disable compression").option("--no-extract", "Disable semantic extraction").action(fetchCommand);
|
|
1277
|
+
program.command("install").description("Install make-laten adapters for detected agents").option("-u, --uninstall", "Remove adapters").option("-s, --status", "Show installation status").action(installCommand);
|
|
1278
|
+
program.parse();
|
|
1279
|
+
//# sourceMappingURL=index.mjs.map
|