codemaxxing 1.0.0 → 1.0.2
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 +18 -12
- package/dist/agent.d.ts +4 -0
- package/dist/agent.js +91 -16
- package/dist/commands/git.d.ts +2 -0
- package/dist/commands/git.js +50 -0
- package/dist/commands/ollama.d.ts +27 -0
- package/dist/commands/ollama.js +171 -0
- package/dist/commands/output.d.ts +2 -0
- package/dist/commands/output.js +18 -0
- package/dist/commands/registry.d.ts +2 -0
- package/dist/commands/registry.js +8 -0
- package/dist/commands/skills.d.ts +18 -0
- package/dist/commands/skills.js +121 -0
- package/dist/commands/types.d.ts +5 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/ui.d.ts +16 -0
- package/dist/commands/ui.js +79 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +13 -3
- package/dist/exec.js +4 -1
- package/dist/index.js +75 -401
- package/dist/tools/files.js +58 -3
- package/dist/utils/context.js +6 -0
- package/dist/utils/mcp.d.ts +7 -2
- package/dist/utils/mcp.js +34 -6
- package/package.json +8 -5
- package/src/agent.ts +0 -894
- package/src/auth-cli.ts +0 -287
- package/src/cli.ts +0 -37
- package/src/config.ts +0 -352
- package/src/exec.ts +0 -183
- package/src/index.tsx +0 -2647
- package/src/skills/registry.ts +0 -1436
- package/src/themes.ts +0 -335
- package/src/tools/files.ts +0 -374
- package/src/utils/auth.ts +0 -606
- package/src/utils/context.ts +0 -174
- package/src/utils/git.ts +0 -117
- package/src/utils/hardware.ts +0 -131
- package/src/utils/lint.ts +0 -116
- package/src/utils/mcp.ts +0 -307
- package/src/utils/models.ts +0 -218
- package/src/utils/ollama.ts +0 -352
- package/src/utils/repomap.ts +0 -220
- package/src/utils/sessions.ts +0 -254
- package/src/utils/skills.ts +0 -241
- package/tsconfig.json +0 -16
package/src/utils/ollama.ts
DELETED
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
import { execSync, spawn } from "child_process";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
|
|
5
|
-
/** Get known Ollama binary paths for Windows */
|
|
6
|
-
function getWindowsOllamaPaths(): string[] {
|
|
7
|
-
const paths: string[] = [];
|
|
8
|
-
const localAppData = process.env.LOCALAPPDATA || join(process.env.USERPROFILE || "", "AppData", "Local");
|
|
9
|
-
const programFiles = process.env.ProgramFiles || "C:\\Program Files";
|
|
10
|
-
paths.push(join(localAppData, "Programs", "Ollama", "ollama.exe"));
|
|
11
|
-
paths.push(join(programFiles, "Ollama", "ollama.exe"));
|
|
12
|
-
paths.push(join(localAppData, "Ollama", "ollama.exe"));
|
|
13
|
-
return paths;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Check if ollama binary exists on PATH, known install locations, or server is running */
|
|
17
|
-
export function isOllamaInstalled(): boolean {
|
|
18
|
-
// Check PATH first
|
|
19
|
-
try {
|
|
20
|
-
const cmd = process.platform === "win32" ? "where ollama" : "which ollama";
|
|
21
|
-
execSync(cmd, { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
22
|
-
return true;
|
|
23
|
-
} catch {}
|
|
24
|
-
|
|
25
|
-
// Check known install paths on Windows
|
|
26
|
-
if (process.platform === "win32") {
|
|
27
|
-
if (getWindowsOllamaPaths().some(p => existsSync(p))) return true;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Check if the server is responding (if server is running, Ollama is definitely installed)
|
|
31
|
-
try {
|
|
32
|
-
execSync("curl -s http://localhost:11434/api/tags", {
|
|
33
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
34
|
-
timeout: 3000,
|
|
35
|
-
});
|
|
36
|
-
return true;
|
|
37
|
-
} catch {}
|
|
38
|
-
|
|
39
|
-
// Check if ollama process is running (Windows)
|
|
40
|
-
if (process.platform === "win32") {
|
|
41
|
-
try {
|
|
42
|
-
const result = execSync("tasklist /fi \"imagename eq ollama app.exe\" /fo csv /nh", {
|
|
43
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
44
|
-
timeout: 3000,
|
|
45
|
-
});
|
|
46
|
-
if (result.toString().toLowerCase().includes("ollama")) return true;
|
|
47
|
-
} catch {}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Check if ollama server is responding */
|
|
54
|
-
export async function isOllamaRunning(): Promise<boolean> {
|
|
55
|
-
try {
|
|
56
|
-
const controller = new AbortController();
|
|
57
|
-
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
58
|
-
const res = await fetch("http://localhost:11434/api/tags", { signal: controller.signal });
|
|
59
|
-
clearTimeout(timeout);
|
|
60
|
-
return res.ok;
|
|
61
|
-
} catch {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Get the install command for the user's OS */
|
|
67
|
-
export function getOllamaInstallCommand(os: "macos" | "linux" | "windows"): string {
|
|
68
|
-
switch (os) {
|
|
69
|
-
case "macos": return "brew install ollama";
|
|
70
|
-
case "linux": return "curl -fsSL https://ollama.com/install.sh | sh";
|
|
71
|
-
case "windows": return "winget install Ollama.Ollama";
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Find the ollama binary path */
|
|
76
|
-
function findOllamaBinary(): string {
|
|
77
|
-
// Try PATH first
|
|
78
|
-
try {
|
|
79
|
-
const cmd = process.platform === "win32" ? "where ollama" : "which ollama";
|
|
80
|
-
return execSync(cmd, { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 }).toString().trim().split("\n")[0];
|
|
81
|
-
} catch {}
|
|
82
|
-
// Check known Windows paths
|
|
83
|
-
if (process.platform === "win32") {
|
|
84
|
-
for (const p of getWindowsOllamaPaths()) {
|
|
85
|
-
if (existsSync(p)) return p;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return "ollama"; // fallback, hope for the best
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Start ollama serve in background */
|
|
92
|
-
export function startOllama(): void {
|
|
93
|
-
const bin = findOllamaBinary();
|
|
94
|
-
const child = spawn(bin, ["serve"], {
|
|
95
|
-
detached: true,
|
|
96
|
-
stdio: "ignore",
|
|
97
|
-
});
|
|
98
|
-
child.unref();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export interface PullProgress {
|
|
102
|
-
status: string;
|
|
103
|
-
total?: number;
|
|
104
|
-
completed?: number;
|
|
105
|
-
percent: number;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Pull a model from Ollama registry via HTTP API.
|
|
110
|
-
* Falls back to CLI if API fails.
|
|
111
|
-
* Calls onProgress with download updates.
|
|
112
|
-
*/
|
|
113
|
-
export function pullModel(
|
|
114
|
-
modelId: string,
|
|
115
|
-
onProgress?: (progress: PullProgress) => void
|
|
116
|
-
): Promise<void> {
|
|
117
|
-
// Try HTTP API first (works even when CLI isn't on PATH)
|
|
118
|
-
return pullModelViaAPI(modelId, onProgress).catch(() => {
|
|
119
|
-
// Fallback to CLI
|
|
120
|
-
return pullModelViaCLI(modelId, onProgress);
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function pullModelViaAPI(
|
|
125
|
-
modelId: string,
|
|
126
|
-
onProgress?: (progress: PullProgress) => void
|
|
127
|
-
): Promise<void> {
|
|
128
|
-
return new Promise(async (resolve, reject) => {
|
|
129
|
-
try {
|
|
130
|
-
const res = await fetch("http://localhost:11434/api/pull", {
|
|
131
|
-
method: "POST",
|
|
132
|
-
headers: { "Content-Type": "application/json" },
|
|
133
|
-
body: JSON.stringify({ name: modelId, stream: true }),
|
|
134
|
-
});
|
|
135
|
-
if (!res.ok || !res.body) {
|
|
136
|
-
reject(new Error(`Ollama API returned ${res.status}`));
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
const reader = res.body.getReader();
|
|
140
|
-
const decoder = new TextDecoder();
|
|
141
|
-
let buffer = "";
|
|
142
|
-
while (true) {
|
|
143
|
-
const { done, value } = await reader.read();
|
|
144
|
-
if (done) break;
|
|
145
|
-
buffer += decoder.decode(value, { stream: true });
|
|
146
|
-
const lines = buffer.split("\n");
|
|
147
|
-
buffer = lines.pop() || "";
|
|
148
|
-
for (const line of lines) {
|
|
149
|
-
if (!line.trim()) continue;
|
|
150
|
-
try {
|
|
151
|
-
const data = JSON.parse(line);
|
|
152
|
-
if (data.status === "success") {
|
|
153
|
-
onProgress?.({ status: "success", percent: 100 });
|
|
154
|
-
resolve();
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
if (data.total && data.completed) {
|
|
158
|
-
const percent = Math.round((data.completed / data.total) * 100);
|
|
159
|
-
onProgress?.({ status: "downloading", total: data.total, completed: data.completed, percent });
|
|
160
|
-
} else {
|
|
161
|
-
onProgress?.({ status: data.status || "working...", percent: 0 });
|
|
162
|
-
}
|
|
163
|
-
} catch {}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
resolve();
|
|
167
|
-
} catch (err: any) {
|
|
168
|
-
reject(err);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function pullModelViaCLI(
|
|
174
|
-
modelId: string,
|
|
175
|
-
onProgress?: (progress: PullProgress) => void
|
|
176
|
-
): Promise<void> {
|
|
177
|
-
return new Promise((resolve, reject) => {
|
|
178
|
-
const bin = findOllamaBinary();
|
|
179
|
-
const child = spawn(bin, ["pull", modelId], {
|
|
180
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
let lastOutput = "";
|
|
184
|
-
|
|
185
|
-
const parseLine = (data: string) => {
|
|
186
|
-
lastOutput = data;
|
|
187
|
-
// Ollama pull output looks like:
|
|
188
|
-
// pulling manifest
|
|
189
|
-
// pulling abc123... 58% ▕██████████░░░░░░░░░░▏ 2.9 GB/5.0 GB
|
|
190
|
-
// verifying sha256 digest
|
|
191
|
-
// writing manifest
|
|
192
|
-
// success
|
|
193
|
-
|
|
194
|
-
// Try to parse percentage
|
|
195
|
-
const pctMatch = data.match(/(\d+)%/);
|
|
196
|
-
const sizeMatch = data.match(/([\d.]+)\s*GB\s*\/\s*([\d.]+)\s*GB/);
|
|
197
|
-
|
|
198
|
-
if (pctMatch) {
|
|
199
|
-
const percent = parseInt(pctMatch[1]);
|
|
200
|
-
let completed: number | undefined;
|
|
201
|
-
let total: number | undefined;
|
|
202
|
-
if (sizeMatch) {
|
|
203
|
-
completed = parseFloat(sizeMatch[1]) * 1024 * 1024 * 1024;
|
|
204
|
-
total = parseFloat(sizeMatch[2]) * 1024 * 1024 * 1024;
|
|
205
|
-
}
|
|
206
|
-
onProgress?.({ status: "downloading", total, completed, percent });
|
|
207
|
-
} else if (data.includes("pulling manifest")) {
|
|
208
|
-
onProgress?.({ status: "pulling manifest", percent: 0 });
|
|
209
|
-
} else if (data.includes("verifying")) {
|
|
210
|
-
onProgress?.({ status: "verifying", percent: 100 });
|
|
211
|
-
} else if (data.includes("writing manifest")) {
|
|
212
|
-
onProgress?.({ status: "writing manifest", percent: 100 });
|
|
213
|
-
} else if (data.includes("success")) {
|
|
214
|
-
onProgress?.({ status: "success", percent: 100 });
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
child.stdout?.on("data", (data: Buffer) => {
|
|
219
|
-
parseLine(data.toString().trim());
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
child.stderr?.on("data", (data: Buffer) => {
|
|
223
|
-
// Ollama writes progress to stderr
|
|
224
|
-
parseLine(data.toString().trim());
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
child.on("close", (code) => {
|
|
228
|
-
if (code === 0) {
|
|
229
|
-
resolve();
|
|
230
|
-
} else {
|
|
231
|
-
reject(new Error(`ollama pull failed (exit ${code}): ${lastOutput}`));
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
child.on("error", (err) => {
|
|
236
|
-
reject(new Error(`Failed to run ollama pull: ${err.message}`));
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export interface OllamaModelInfo {
|
|
242
|
-
name: string;
|
|
243
|
-
size: number; // bytes
|
|
244
|
-
modified_at: string;
|
|
245
|
-
digest: string;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/** List models installed in Ollama with detailed info */
|
|
249
|
-
export async function listInstalledModelsDetailed(): Promise<OllamaModelInfo[]> {
|
|
250
|
-
try {
|
|
251
|
-
const controller = new AbortController();
|
|
252
|
-
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
253
|
-
const res = await fetch("http://localhost:11434/api/tags", { signal: controller.signal });
|
|
254
|
-
clearTimeout(timeout);
|
|
255
|
-
if (res.ok) {
|
|
256
|
-
const data = (await res.json()) as { models?: Array<{ name: string; size: number; modified_at: string; digest: string }> };
|
|
257
|
-
return (data.models ?? []).map((m) => ({
|
|
258
|
-
name: m.name,
|
|
259
|
-
size: m.size,
|
|
260
|
-
modified_at: m.modified_at,
|
|
261
|
-
digest: m.digest,
|
|
262
|
-
}));
|
|
263
|
-
}
|
|
264
|
-
} catch { /* not running */ }
|
|
265
|
-
return [];
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** List models installed in Ollama */
|
|
269
|
-
export async function listInstalledModels(): Promise<string[]> {
|
|
270
|
-
const models = await listInstalledModelsDetailed();
|
|
271
|
-
return models.map((m) => m.name);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/** Stop all loaded models (frees VRAM) and kill the Ollama server process */
|
|
275
|
-
export async function stopOllama(): Promise<{ ok: boolean; message: string }> {
|
|
276
|
-
try {
|
|
277
|
-
// First unload all models from memory
|
|
278
|
-
try {
|
|
279
|
-
const bin = findOllamaBinary();
|
|
280
|
-
const { spawnSync } = require("child_process");
|
|
281
|
-
spawnSync(bin, ["stop"], { stdio: "pipe", timeout: 5000 });
|
|
282
|
-
} catch { /* may fail if no models loaded */ }
|
|
283
|
-
|
|
284
|
-
// Kill the server process
|
|
285
|
-
if (process.platform === "win32") {
|
|
286
|
-
execSync("taskkill /f /im ollama.exe", { stdio: ["pipe", "pipe", "pipe"], timeout: 5000 });
|
|
287
|
-
} else if (process.platform === "darwin") {
|
|
288
|
-
// Try launchctl first (Ollama app), then pkill
|
|
289
|
-
try {
|
|
290
|
-
execSync("launchctl stop com.ollama.ollama", { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
291
|
-
} catch {
|
|
292
|
-
try {
|
|
293
|
-
execSync("pkill ollama", { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
294
|
-
} catch { /* already stopped */ }
|
|
295
|
-
}
|
|
296
|
-
} else {
|
|
297
|
-
// Linux
|
|
298
|
-
try {
|
|
299
|
-
execSync("systemctl stop ollama", { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
300
|
-
} catch {
|
|
301
|
-
try {
|
|
302
|
-
execSync("pkill ollama", { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
303
|
-
} catch { /* already stopped */ }
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Verify it stopped
|
|
308
|
-
await new Promise(r => setTimeout(r, 500));
|
|
309
|
-
const stillRunning = await isOllamaRunning();
|
|
310
|
-
if (stillRunning) {
|
|
311
|
-
return { ok: false, message: "Ollama is still running. Try killing it manually." };
|
|
312
|
-
}
|
|
313
|
-
return { ok: true, message: "Ollama stopped." };
|
|
314
|
-
} catch (err: any) {
|
|
315
|
-
return { ok: false, message: `Failed to stop Ollama: ${err.message}` };
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/** Delete a model from disk */
|
|
320
|
-
export function deleteModel(modelId: string): { ok: boolean; message: string } {
|
|
321
|
-
try {
|
|
322
|
-
const bin = findOllamaBinary();
|
|
323
|
-
execSync(`"${bin}" rm ${modelId}`, { stdio: ["pipe", "pipe", "pipe"], timeout: 30000 });
|
|
324
|
-
return { ok: true, message: `Deleted ${modelId}` };
|
|
325
|
-
} catch (err: any) {
|
|
326
|
-
return { ok: false, message: `Failed to delete ${modelId}: ${err.stderr?.toString().trim() || err.message}` };
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/** Get GPU memory usage info (best-effort) */
|
|
331
|
-
export function getGPUMemoryUsage(): string | null {
|
|
332
|
-
try {
|
|
333
|
-
if (process.platform === "darwin") {
|
|
334
|
-
// Apple Silicon — check memory pressure
|
|
335
|
-
const raw = execSync("memory_pressure", { encoding: "utf-8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
|
|
336
|
-
const match = raw.match(/System-wide memory free percentage:\s*(\d+)%/);
|
|
337
|
-
if (match) {
|
|
338
|
-
return `${100 - parseInt(match[1])}% system memory in use`;
|
|
339
|
-
}
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
// NVIDIA GPU
|
|
343
|
-
const raw = execSync("nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader,nounits", {
|
|
344
|
-
encoding: "utf-8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"],
|
|
345
|
-
});
|
|
346
|
-
const parts = raw.trim().split(",").map(s => s.trim());
|
|
347
|
-
if (parts.length === 2) {
|
|
348
|
-
return `${parts[0]} MiB / ${parts[1]} MiB GPU memory`;
|
|
349
|
-
}
|
|
350
|
-
} catch { /* no GPU info available */ }
|
|
351
|
-
return null;
|
|
352
|
-
}
|
package/src/utils/repomap.ts
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, statSync } from "fs";
|
|
2
|
-
import { join, extname, relative } from "path";
|
|
3
|
-
|
|
4
|
-
const IGNORE_DIRS = new Set([
|
|
5
|
-
"node_modules", ".git", "dist", ".next", "__pycache__", ".pytest_cache",
|
|
6
|
-
"target", "build", "out", ".cache", ".parcel-cache", ".nuxt", ".svelte-kit",
|
|
7
|
-
"vendor", "venv", ".venv", "env", ".env", "coverage", ".nyc_output",
|
|
8
|
-
]);
|
|
9
|
-
|
|
10
|
-
const IGNORE_FILES = new Set([
|
|
11
|
-
".DS_Store", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
|
|
12
|
-
]);
|
|
13
|
-
|
|
14
|
-
const MAX_FILE_SIZE = 100 * 1024;
|
|
15
|
-
const MAX_MAP_SIZE = 15 * 1024;
|
|
16
|
-
|
|
17
|
-
// ── Per-line regex patterns (no /g flag!) ──
|
|
18
|
-
|
|
19
|
-
interface LangPatterns {
|
|
20
|
-
patterns: Array<{ kind: string; regex: RegExp; format: (m: RegExpMatchArray) => string }>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const LANGS: Record<string, LangPatterns> = {
|
|
24
|
-
javascript: {
|
|
25
|
-
patterns: [
|
|
26
|
-
{ kind: "fn", regex: /^(?:export\s+)?(?:export\s+default\s+)?(?:async\s+)?function\s+(\w+)\s*\(/, format: (m) => `function ${m[1]}(...)` },
|
|
27
|
-
{ kind: "arrow", regex: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(/, format: (m) => `const ${m[1]} = (...)` },
|
|
28
|
-
{ kind: "cls", regex: /^(?:export\s+)?(?:export\s+default\s+)?class\s+(\w+)/, format: (m) => `class ${m[1]}` },
|
|
29
|
-
],
|
|
30
|
-
},
|
|
31
|
-
typescript: {
|
|
32
|
-
patterns: [
|
|
33
|
-
{ kind: "fn", regex: /^(?:export\s+)?(?:export\s+default\s+)?(?:async\s+)?function\s+(\w+)/, format: (m) => `function ${m[1]}(...)` },
|
|
34
|
-
{ kind: "arrow", regex: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(/, format: (m) => `const ${m[1]} = (...)` },
|
|
35
|
-
{ kind: "cls", regex: /^(?:export\s+)?(?:export\s+default\s+)?class\s+(\w+)/, format: (m) => `class ${m[1]}` },
|
|
36
|
-
{ kind: "iface", regex: /^(?:export\s+)?interface\s+(\w+)/, format: (m) => `interface ${m[1]}` },
|
|
37
|
-
{ kind: "type", regex: /^(?:export\s+)?type\s+(\w+)\s*[=<]/, format: (m) => `type ${m[1]}` },
|
|
38
|
-
{ kind: "enum", regex: /^(?:export\s+)?enum\s+(\w+)/, format: (m) => `enum ${m[1]}` },
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
python: {
|
|
42
|
-
patterns: [
|
|
43
|
-
{ kind: "fn", regex: /^def\s+(\w+)\s*\(/, format: (m) => `def ${m[1]}(...)` },
|
|
44
|
-
{ kind: "method", regex: /^\s{2,}def\s+(\w+)\s*\(/, format: (m) => ` def ${m[1]}(...)` },
|
|
45
|
-
{ kind: "cls", regex: /^class\s+(\w+)/, format: (m) => `class ${m[1]}` },
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
go: {
|
|
49
|
-
patterns: [
|
|
50
|
-
{ kind: "fn", regex: /^func\s+(\w+)\s*\(/, format: (m) => `func ${m[1]}(...)` },
|
|
51
|
-
{ kind: "method", regex: /^func\s+\((\w+)\s+\*?(\w+)\)\s+(\w+)\s*\(/, format: (m) => `func (${m[2]}) ${m[3]}(...)` },
|
|
52
|
-
{ kind: "struct", regex: /^type\s+(\w+)\s+struct/, format: (m) => `type ${m[1]} struct` },
|
|
53
|
-
{ kind: "iface", regex: /^type\s+(\w+)\s+interface/, format: (m) => `type ${m[1]} interface` },
|
|
54
|
-
],
|
|
55
|
-
},
|
|
56
|
-
rust: {
|
|
57
|
-
patterns: [
|
|
58
|
-
{ kind: "fn", regex: /^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/, format: (m) => `fn ${m[1]}(...)` },
|
|
59
|
-
{ kind: "struct", regex: /^(?:pub\s+)?struct\s+(\w+)/, format: (m) => `struct ${m[1]}` },
|
|
60
|
-
{ kind: "enum", regex: /^(?:pub\s+)?enum\s+(\w+)/, format: (m) => `enum ${m[1]}` },
|
|
61
|
-
{ kind: "trait", regex: /^(?:pub\s+)?trait\s+(\w+)/, format: (m) => `trait ${m[1]}` },
|
|
62
|
-
{ kind: "impl", regex: /^impl(?:<[^>]*>)?\s+(?:(\w+)\s+for\s+)?(\w+)/, format: (m) => m[1] ? `impl ${m[1]} for ${m[2]}` : `impl ${m[2]}` },
|
|
63
|
-
{ kind: "mod", regex: /^(?:pub\s+)?mod\s+(\w+)/, format: (m) => `mod ${m[1]}` },
|
|
64
|
-
],
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Get language from extension
|
|
70
|
-
*/
|
|
71
|
-
function getLang(ext: string): string | null {
|
|
72
|
-
const map: Record<string, string> = {
|
|
73
|
-
".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
|
|
74
|
-
".ts": "typescript", ".tsx": "typescript",
|
|
75
|
-
".py": "python",
|
|
76
|
-
".go": "go",
|
|
77
|
-
".rs": "rust",
|
|
78
|
-
};
|
|
79
|
-
return map[ext.toLowerCase()] || null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Extract signatures from file content
|
|
84
|
-
*/
|
|
85
|
-
function extractSignatures(content: string, lang: string): string[] {
|
|
86
|
-
const sigs: string[] = [];
|
|
87
|
-
const langDef = LANGS[lang];
|
|
88
|
-
if (!langDef) return sigs;
|
|
89
|
-
|
|
90
|
-
const lines = content.split("\n");
|
|
91
|
-
const seen = new Set<string>();
|
|
92
|
-
|
|
93
|
-
for (const line of lines) {
|
|
94
|
-
for (const pattern of langDef.patterns) {
|
|
95
|
-
const match = line.match(pattern.regex);
|
|
96
|
-
if (match) {
|
|
97
|
-
const sig = pattern.format(match);
|
|
98
|
-
if (!seen.has(sig)) {
|
|
99
|
-
seen.add(sig);
|
|
100
|
-
sigs.push(sig);
|
|
101
|
-
}
|
|
102
|
-
break; // Only match one pattern per line
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return sigs;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Scan directory for supported files
|
|
112
|
-
*/
|
|
113
|
-
function getFiles(cwd: string): string[] {
|
|
114
|
-
const files: string[] = [];
|
|
115
|
-
|
|
116
|
-
function walk(dir: string, depth: number) {
|
|
117
|
-
if (depth > 5) return;
|
|
118
|
-
try {
|
|
119
|
-
for (const entry of readdirSync(dir)) {
|
|
120
|
-
if (IGNORE_FILES.has(entry) || entry.startsWith(".")) continue;
|
|
121
|
-
if (IGNORE_DIRS.has(entry)) continue;
|
|
122
|
-
const full = join(dir, entry);
|
|
123
|
-
|
|
124
|
-
const stat = statSync(full);
|
|
125
|
-
if (stat.isDirectory()) {
|
|
126
|
-
walk(full, depth + 1);
|
|
127
|
-
} else if (stat.isFile() && stat.size < MAX_FILE_SIZE) {
|
|
128
|
-
if (getLang(extname(entry))) files.push(full);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
} catch { /* skip */ }
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
walk(cwd, 0);
|
|
135
|
-
return files;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ── Cache ──
|
|
139
|
-
let cachedMap = "";
|
|
140
|
-
let cachedCwd = "";
|
|
141
|
-
let cachedTime = 0;
|
|
142
|
-
const CACHE_TTL = 60_000;
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Build the repo map — cached for 1 min
|
|
146
|
-
*/
|
|
147
|
-
export async function buildRepoMap(cwd: string): Promise<string> {
|
|
148
|
-
const now = Date.now();
|
|
149
|
-
if (cachedMap && cachedCwd === cwd && now - cachedTime < CACHE_TTL) {
|
|
150
|
-
return cachedMap;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const files = getFiles(cwd);
|
|
154
|
-
const lines: string[] = [];
|
|
155
|
-
|
|
156
|
-
for (const file of files) {
|
|
157
|
-
const ext = extname(file);
|
|
158
|
-
const lang = getLang(ext);
|
|
159
|
-
if (!lang) continue;
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
const content = readFileSync(file, "utf-8");
|
|
163
|
-
const sigs = extractSignatures(content, lang);
|
|
164
|
-
|
|
165
|
-
if (sigs.length > 0) {
|
|
166
|
-
const relPath = relative(cwd, file);
|
|
167
|
-
lines.push(`${relPath}:`);
|
|
168
|
-
for (const sig of sigs) {
|
|
169
|
-
lines.push(` ${sig}`);
|
|
170
|
-
}
|
|
171
|
-
lines.push("");
|
|
172
|
-
|
|
173
|
-
// Size guard
|
|
174
|
-
if (lines.join("\n").length > MAX_MAP_SIZE) {
|
|
175
|
-
lines.push(`... (truncated)`);
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
} catch { /* skip */ }
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const map = lines.length > 0 ? lines.join("\n") : "(no signatures found)";
|
|
183
|
-
|
|
184
|
-
cachedMap = map;
|
|
185
|
-
cachedCwd = cwd;
|
|
186
|
-
cachedTime = now;
|
|
187
|
-
|
|
188
|
-
return map;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Get cached map without rebuilding
|
|
193
|
-
*/
|
|
194
|
-
export function getCachedMap(): string {
|
|
195
|
-
return cachedMap;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Clear the cache
|
|
200
|
-
*/
|
|
201
|
-
export function clearMapCache(): void {
|
|
202
|
-
cachedMap = "";
|
|
203
|
-
cachedCwd = "";
|
|
204
|
-
cachedTime = 0;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Check if file is supported
|
|
209
|
-
*/
|
|
210
|
-
export function isSupportedFile(filePath: string): boolean {
|
|
211
|
-
return !!getLang(extname(filePath).toLowerCase());
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export function getSupportedExtensions(): string[] {
|
|
215
|
-
return [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rs"];
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export function getLanguageForExt(ext: string): string | null {
|
|
219
|
-
return getLang(ext);
|
|
220
|
-
}
|