@ridit/lens 0.3.7 → 0.3.9
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/dist/index.mjs +105368 -274002
- package/package.json +13 -19
- package/src/colors.ts +15 -15
- package/src/commands/chat.tsx +32 -23
- package/src/commands/provider.tsx +11 -238
- package/src/commands/repo.tsx +66 -120
- package/src/commands/timeline.tsx +11 -22
- package/src/components/ChatView.tsx +238 -0
- package/src/components/Message.tsx +46 -0
- package/src/components/ToolCall.tsx +67 -0
- package/src/components/chat/ChatView.tsx +550 -0
- package/src/components/chat/Message.tsx +152 -0
- package/src/components/chat/StatusBar.tsx +214 -0
- package/src/components/chat/TextArea.tsx +173 -176
- package/src/components/provider/ApiKeyStep.tsx +207 -199
- package/src/components/provider/ModelStep.tsx +90 -88
- package/src/components/provider/ProviderSetup.tsx +331 -0
- package/src/components/provider/ProviderTypeStep.tsx +53 -61
- package/src/components/repo/StepRow.tsx +68 -69
- package/src/components/timeline/TimelineView.tsx +840 -0
- package/src/components/toolcall-utils.ts +103 -0
- package/src/components/watch/RunView.tsx +497 -0
- package/src/hooks/useChatInput.ts +49 -0
- package/src/hooks/useCommandHandler.ts +117 -0
- package/src/index.tsx +386 -139
- package/src/utils/git.ts +149 -155
- package/src/utils/repo.ts +62 -69
- package/src/utils/thinking.tsx +64 -0
- package/src/utils/watch.ts +165 -307
- package/tests/message.test.ts +38 -0
- package/tests/toolcall-utils.test.ts +111 -0
- package/tsconfig.json +8 -24
- package/CLAUDE.md +0 -50
- package/LENS.md +0 -48
- package/LICENSE +0 -21
- package/README.md +0 -93
- package/addons/README.md +0 -55
- package/addons/clean-cache.js +0 -48
- package/addons/generate-readme.js +0 -67
- package/addons/git-stats.js +0 -29
- package/addons/run-tests.js +0 -127
- package/src/commands/commit.tsx +0 -668
- package/src/commands/review.tsx +0 -294
- package/src/commands/run.tsx +0 -56
- package/src/commands/task.tsx +0 -36
- package/src/components/chat/ChatMessage.tsx +0 -195
- package/src/components/chat/ChatOverlays.tsx +0 -399
- package/src/components/chat/ChatRunner.tsx +0 -517
- package/src/components/chat/hooks/useChat.ts +0 -631
- package/src/components/chat/hooks/useChatInput.ts +0 -79
- package/src/components/chat/hooks/useCommandHandlers.ts +0 -327
- package/src/components/provider/ProviderPicker.tsx +0 -76
- package/src/components/provider/RemoveProviderStep.tsx +0 -82
- package/src/components/repo/DiffViewer.tsx +0 -175
- package/src/components/repo/FileReviewer.tsx +0 -70
- package/src/components/repo/FileViewer.tsx +0 -60
- package/src/components/repo/IssueFixer.tsx +0 -666
- package/src/components/repo/LensFileMenu.tsx +0 -115
- package/src/components/repo/NoProviderPrompt.tsx +0 -28
- package/src/components/repo/PreviewRunner.tsx +0 -217
- package/src/components/repo/RepoAnalysis.tsx +0 -534
- package/src/components/task/TaskRunner.tsx +0 -396
- package/src/components/timeline/CommitDetail.tsx +0 -272
- package/src/components/timeline/CommitList.tsx +0 -162
- package/src/components/timeline/TimelineChat.tsx +0 -166
- package/src/components/timeline/TimelineRunner.tsx +0 -1285
- package/src/components/watch/RunRunner.tsx +0 -929
- package/src/prompts/fewshot.ts +0 -252
- package/src/prompts/index.ts +0 -2
- package/src/prompts/system.ts +0 -285
- package/src/tools/chart.ts +0 -202
- package/src/tools/convert-image.ts +0 -312
- package/src/tools/files.ts +0 -253
- package/src/tools/git.ts +0 -603
- package/src/tools/index.ts +0 -17
- package/src/tools/pdf.ts +0 -164
- package/src/tools/shell.ts +0 -96
- package/src/tools/view-image.ts +0 -335
- package/src/tools/web.ts +0 -212
- package/src/types/chat.ts +0 -86
- package/src/types/config.ts +0 -20
- package/src/types/repo.ts +0 -54
- package/src/utils/addons/loadAddons.ts +0 -34
- package/src/utils/ai.ts +0 -321
- package/src/utils/chat.ts +0 -326
- package/src/utils/chatHistory.ts +0 -121
- package/src/utils/config.ts +0 -61
- package/src/utils/files.ts +0 -105
- package/src/utils/intentClassifier.ts +0 -58
- package/src/utils/lensfile.ts +0 -142
- package/src/utils/llm.ts +0 -81
- package/src/utils/memory.ts +0 -209
- package/src/utils/preview.ts +0 -119
- package/src/utils/stats.ts +0 -174
- package/src/utils/tools/builtins.ts +0 -377
- package/src/utils/tools/registry.ts +0 -105
package/src/utils/chatHistory.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
existsSync,
|
|
3
|
-
mkdirSync,
|
|
4
|
-
readFileSync,
|
|
5
|
-
readdirSync,
|
|
6
|
-
writeFileSync,
|
|
7
|
-
unlinkSync,
|
|
8
|
-
} from "fs";
|
|
9
|
-
import path from "path";
|
|
10
|
-
import os from "os";
|
|
11
|
-
import type { Message } from "../types/chat";
|
|
12
|
-
|
|
13
|
-
const LENS_DIR = path.join(os.homedir(), ".lens");
|
|
14
|
-
const CHATS_DIR = path.join(LENS_DIR, "chats");
|
|
15
|
-
|
|
16
|
-
export type SavedChat = {
|
|
17
|
-
name: string;
|
|
18
|
-
repoPath: string;
|
|
19
|
-
messages: Message[];
|
|
20
|
-
savedAt: string;
|
|
21
|
-
userMessageCount: number;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
function ensureChatsDir(): void {
|
|
25
|
-
if (!existsSync(CHATS_DIR)) mkdirSync(CHATS_DIR, { recursive: true });
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function chatFilePath(name: string): string {
|
|
29
|
-
const safe = name.replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
|
|
30
|
-
return path.join(CHATS_DIR, `${safe}.json`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function saveChat(
|
|
34
|
-
name: string,
|
|
35
|
-
repoPath: string,
|
|
36
|
-
messages: Message[],
|
|
37
|
-
): void {
|
|
38
|
-
ensureChatsDir();
|
|
39
|
-
const data: SavedChat = {
|
|
40
|
-
name,
|
|
41
|
-
repoPath,
|
|
42
|
-
messages,
|
|
43
|
-
savedAt: new Date().toISOString(),
|
|
44
|
-
userMessageCount: messages.filter((m) => m.role === "user").length,
|
|
45
|
-
};
|
|
46
|
-
writeFileSync(chatFilePath(name), JSON.stringify(data, null, 2), "utf-8");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function loadChat(name: string): SavedChat | null {
|
|
50
|
-
const filePath = chatFilePath(name);
|
|
51
|
-
if (!existsSync(filePath)) return null;
|
|
52
|
-
try {
|
|
53
|
-
return JSON.parse(readFileSync(filePath, "utf-8")) as SavedChat;
|
|
54
|
-
} catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function listChats(repoPath?: string): SavedChat[] {
|
|
60
|
-
ensureChatsDir();
|
|
61
|
-
const files = readdirSync(CHATS_DIR).filter((f) => f.endsWith(".json"));
|
|
62
|
-
const chats: SavedChat[] = [];
|
|
63
|
-
for (const file of files) {
|
|
64
|
-
try {
|
|
65
|
-
const data = JSON.parse(
|
|
66
|
-
readFileSync(path.join(CHATS_DIR, file), "utf-8"),
|
|
67
|
-
) as SavedChat;
|
|
68
|
-
if (!repoPath || data.repoPath === repoPath) chats.push(data);
|
|
69
|
-
} catch {
|
|
70
|
-
// skip corrupt files
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return chats.sort(
|
|
74
|
-
(a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime(),
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function deleteChat(name: string): boolean {
|
|
79
|
-
const filePath = chatFilePath(name);
|
|
80
|
-
if (!existsSync(filePath)) return false;
|
|
81
|
-
try {
|
|
82
|
-
unlinkSync(filePath);
|
|
83
|
-
return true;
|
|
84
|
-
} catch {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function getChatNameSuggestions(messages: Message[]): string[] {
|
|
90
|
-
const userMsgs = messages
|
|
91
|
-
.filter((m) => m.role === "user")
|
|
92
|
-
.map((m) => m.content.toLowerCase().trim());
|
|
93
|
-
|
|
94
|
-
const date = new Date().toISOString().slice(0, 10);
|
|
95
|
-
|
|
96
|
-
if (userMsgs.length === 0) {
|
|
97
|
-
return [`chat-${date}`, `session-${date}`, `new-chat`];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const suggestions: string[] = [];
|
|
101
|
-
|
|
102
|
-
const toSlug = (s: string) =>
|
|
103
|
-
s
|
|
104
|
-
.replace(/[^a-z0-9\s]/g, "")
|
|
105
|
-
.split(/\s+/)
|
|
106
|
-
.filter(Boolean)
|
|
107
|
-
.slice(0, 4)
|
|
108
|
-
.join("-");
|
|
109
|
-
|
|
110
|
-
const firstSlug = toSlug(userMsgs[0]!);
|
|
111
|
-
if (firstSlug) suggestions.push(firstSlug);
|
|
112
|
-
|
|
113
|
-
if (userMsgs.length > 1) {
|
|
114
|
-
const lastSlug = toSlug(userMsgs[userMsgs.length - 1]!);
|
|
115
|
-
if (lastSlug && lastSlug !== firstSlug) suggestions.push(lastSlug);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
suggestions.push(`session-${date}`);
|
|
119
|
-
|
|
120
|
-
return suggestions.slice(0, 3);
|
|
121
|
-
}
|
package/src/utils/config.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import os from "os";
|
|
4
|
-
import type { Config, Provider } from "../types/config";
|
|
5
|
-
|
|
6
|
-
const CONFIG_DIR = path.join(os.homedir(), ".lens");
|
|
7
|
-
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
8
|
-
|
|
9
|
-
export function configExists(): boolean {
|
|
10
|
-
return existsSync(CONFIG_PATH);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function loadConfig(): Config {
|
|
14
|
-
if (!configExists()) return { providers: [] };
|
|
15
|
-
try {
|
|
16
|
-
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8")) as Config;
|
|
17
|
-
} catch {
|
|
18
|
-
return { providers: [] };
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function saveConfig(config: Config): void {
|
|
23
|
-
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function addProvider(provider: Provider): void {
|
|
28
|
-
const config = loadConfig();
|
|
29
|
-
const existing = config.providers.findIndex((p) => p.id === provider.id);
|
|
30
|
-
if (existing >= 0) {
|
|
31
|
-
config.providers[existing] = provider;
|
|
32
|
-
} else {
|
|
33
|
-
config.providers.push(provider);
|
|
34
|
-
}
|
|
35
|
-
if (!config.defaultProviderId) config.defaultProviderId = provider.id;
|
|
36
|
-
saveConfig(config);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function setDefaultProvider(id: string): void {
|
|
40
|
-
const config = loadConfig();
|
|
41
|
-
config.defaultProviderId = id;
|
|
42
|
-
saveConfig(config);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function getDefaultProvider(): Provider | undefined {
|
|
46
|
-
const config = loadConfig();
|
|
47
|
-
return config.providers.find((p) => p.id === config.defaultProviderId);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export const DEFAULT_MODELS: Record<string, string[]> = {
|
|
51
|
-
anthropic: [
|
|
52
|
-
"claude-sonnet-4-20250514",
|
|
53
|
-
"claude-opus-4-20250514",
|
|
54
|
-
"claude-haiku-4-5-20251001",
|
|
55
|
-
],
|
|
56
|
-
openai: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"],
|
|
57
|
-
ollama: ["llama3", "mistral", "codellama", "phi3"],
|
|
58
|
-
custom: [],
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export type CustomResult = { apiKey: string; baseUrl: string };
|
package/src/utils/files.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { exec } from "child_process";
|
|
4
|
-
import figures from "figures";
|
|
5
|
-
import type { FileTree, ImportantFile } from "../types/repo";
|
|
6
|
-
|
|
7
|
-
export const IMPORTANT_PATTERNS = [
|
|
8
|
-
/^\.gitignore$/,
|
|
9
|
-
/^\.env(\..+)?$/,
|
|
10
|
-
/^\.env\.example$/,
|
|
11
|
-
/^package\.json$/,
|
|
12
|
-
/^tsconfig(\..+)?\.json$/,
|
|
13
|
-
/^jsconfig\.json$/,
|
|
14
|
-
/^vite\.config\.(ts|js)$/,
|
|
15
|
-
/^webpack\.config\.(ts|js)$/,
|
|
16
|
-
/^rollup\.config\.(ts|js)$/,
|
|
17
|
-
/^babel\.config\.(ts|js|json)$/,
|
|
18
|
-
/^\.babelrc$/,
|
|
19
|
-
/^eslint\.config\.(ts|js|json)$/,
|
|
20
|
-
/^\.eslintrc(\..+)?$/,
|
|
21
|
-
/^\.prettierrc(\..+)?$/,
|
|
22
|
-
/^prettier\.config\.(ts|js)$/,
|
|
23
|
-
/^docker-compose(\..+)?\.yml$/,
|
|
24
|
-
/^Dockerfile(\..+)?$/,
|
|
25
|
-
/^README(\..+)?\.md$/,
|
|
26
|
-
/^LICENSE(\..+)?$/,
|
|
27
|
-
/^Makefile$/,
|
|
28
|
-
/^\.github\/workflows\/.+\.yml$/,
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
export function isImportantFile(filePath: string): boolean {
|
|
32
|
-
const fileName = path.basename(filePath);
|
|
33
|
-
return IMPORTANT_PATTERNS.some(
|
|
34
|
-
(pattern) => pattern.test(fileName) || pattern.test(filePath),
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function buildTree(files: string[]): FileTree[] {
|
|
39
|
-
const root: FileTree[] = [];
|
|
40
|
-
for (const file of files) {
|
|
41
|
-
const parts = file.split("/");
|
|
42
|
-
let current = root;
|
|
43
|
-
for (let i = 0; i < parts.length; i++) {
|
|
44
|
-
const part = parts[i] ?? "";
|
|
45
|
-
const isFile = i === parts.length - 1;
|
|
46
|
-
const existing = current.find((n) => n.name === part);
|
|
47
|
-
if (existing) {
|
|
48
|
-
if (!isFile && existing.children) current = existing.children;
|
|
49
|
-
} else {
|
|
50
|
-
const node: FileTree = isFile
|
|
51
|
-
? { name: part }
|
|
52
|
-
: { name: part, children: [] };
|
|
53
|
-
current.push(node);
|
|
54
|
-
if (!isFile && node.children) current = node.children;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return root;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function fetchFileTree(repoPath: string): Promise<string[]> {
|
|
62
|
-
return new Promise((resolve, reject) => {
|
|
63
|
-
exec("git ls-files", { cwd: repoPath }, (err, stdout) => {
|
|
64
|
-
if (err) return reject(err);
|
|
65
|
-
resolve(stdout.trim().split("\n").filter(Boolean));
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function readImportantFiles(
|
|
71
|
-
repoPath: string,
|
|
72
|
-
files: string[],
|
|
73
|
-
): ImportantFile[] {
|
|
74
|
-
if (files.length > 100) return [];
|
|
75
|
-
return files.filter(isImportantFile).flatMap((filePath) => {
|
|
76
|
-
try {
|
|
77
|
-
const content = readFileSync(path.join(repoPath, filePath), "utf-8");
|
|
78
|
-
return [{ path: filePath, content }];
|
|
79
|
-
} catch {
|
|
80
|
-
return [];
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const FILE_ICON: Record<string, string> = {
|
|
86
|
-
".gitignore": figures.pointer,
|
|
87
|
-
"package.json": figures.nodejs,
|
|
88
|
-
Dockerfile: figures.square,
|
|
89
|
-
Makefile: figures.play,
|
|
90
|
-
"README.md": figures.info,
|
|
91
|
-
LICENSE: figures.star,
|
|
92
|
-
".env": figures.warning,
|
|
93
|
-
".env.example": figures.warning,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
export function iconForFile(filePath: string): string {
|
|
97
|
-
const name = path.basename(filePath);
|
|
98
|
-
for (const [key, icon] of Object.entries(FILE_ICON)) {
|
|
99
|
-
if (name === key || name.startsWith(key)) return icon;
|
|
100
|
-
}
|
|
101
|
-
if (name.endsWith(".json")) return figures.arrowRight;
|
|
102
|
-
if (name.endsWith(".yml") || name.endsWith(".yaml")) return figures.bullet;
|
|
103
|
-
if (name.startsWith(".")) return figures.dot;
|
|
104
|
-
return figures.pointerSmall;
|
|
105
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Classifies user message intent to scope which tools the LLM is allowed to use.
|
|
3
|
-
*
|
|
4
|
-
* readonly → only read/search/fetch tools exposed (no write, delete, shell)
|
|
5
|
-
* mutating → all tools exposed
|
|
6
|
-
* any → all tools exposed (ambiguous / can't tell)
|
|
7
|
-
*/
|
|
8
|
-
export type Intent = "readonly" | "mutating" | "any";
|
|
9
|
-
|
|
10
|
-
const READONLY_PATTERNS: RegExp[] = [
|
|
11
|
-
// listing / exploring
|
|
12
|
-
/\b(list|ls|dir|show|display|print|dump)\b/i,
|
|
13
|
-
/\bwhat(('?s| is| are| does)\b| files| folder)/i,
|
|
14
|
-
/\b(folder|directory|file) (structure|tree|layout|contents?)\b/i,
|
|
15
|
-
/\bexplore\b/i,
|
|
16
|
-
|
|
17
|
-
// reading / explaining
|
|
18
|
-
/\b(read|open|view|look at|check out|inspect|peek)\b/i,
|
|
19
|
-
/\b(explain|describe|summarize|summarise|tell me about|walk me through)\b/i,
|
|
20
|
-
/\bhow does\b/i,
|
|
21
|
-
/\bwhat('?s| is) (in|inside|this|that|the)\b/i,
|
|
22
|
-
|
|
23
|
-
// searching
|
|
24
|
-
/\b(find|search|grep|locate|where is|where are)\b/i,
|
|
25
|
-
/\b(look for|scan|trace)\b/i,
|
|
26
|
-
|
|
27
|
-
// understanding
|
|
28
|
-
/\bunderstand\b/i,
|
|
29
|
-
/\bshow me (how|what|where|why)\b/i,
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
const MUTATING_PATTERNS: RegExp[] = [
|
|
33
|
-
// writing
|
|
34
|
-
/\b(write|create|make|generate|add|build|scaffold|init|initialize|setup|set up)\b/i,
|
|
35
|
-
/\b(new file|new folder|new component|new page|new route)\b/i,
|
|
36
|
-
|
|
37
|
-
// editing
|
|
38
|
-
/\b(edit|modify|update|change|refactor|rename|move|migrate)\b/i,
|
|
39
|
-
/\b(fix|patch|resolve|correct|debug|repair)\b/i,
|
|
40
|
-
/\b(implement|add .+ to|insert|inject|append|prepend)\b/i,
|
|
41
|
-
|
|
42
|
-
// deleting
|
|
43
|
-
/\b(delete|remove|drop|clean ?up|purge|wipe)\b/i,
|
|
44
|
-
|
|
45
|
-
// running
|
|
46
|
-
/\b(run|execute|install|deploy|build|test|start|launch|compile|lint|format)\b/i,
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
export function classifyIntent(userMessage: string): Intent {
|
|
50
|
-
const text = userMessage.trim();
|
|
51
|
-
|
|
52
|
-
const mutatingScore = MUTATING_PATTERNS.filter((p) => p.test(text)).length;
|
|
53
|
-
const readonlyScore = READONLY_PATTERNS.filter((p) => p.test(text)).length;
|
|
54
|
-
|
|
55
|
-
if (mutatingScore === 0 && readonlyScore > 0) return "readonly";
|
|
56
|
-
if (mutatingScore > 0) return "mutating";
|
|
57
|
-
return "any";
|
|
58
|
-
}
|
package/src/utils/lensfile.ts
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import type { AnalysisResult } from "../types/repo";
|
|
4
|
-
|
|
5
|
-
export const LENS_FILENAME = "LENS.md";
|
|
6
|
-
|
|
7
|
-
export type LensFile = {
|
|
8
|
-
overview: string;
|
|
9
|
-
importantFolders: string[];
|
|
10
|
-
tooling: Record<string, string>;
|
|
11
|
-
keyFiles: string[];
|
|
12
|
-
patterns: string[];
|
|
13
|
-
architecture: string;
|
|
14
|
-
suggestions: string[];
|
|
15
|
-
generatedAt: string;
|
|
16
|
-
lastUpdated: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export function lensFilePath(repoPath: string): string {
|
|
20
|
-
return path.join(repoPath, LENS_FILENAME);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function lensFileExists(repoPath: string): boolean {
|
|
24
|
-
return existsSync(lensFilePath(repoPath));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function renderLensFile(data: LensFile): string {
|
|
28
|
-
const toolingLines = Object.entries(data.tooling)
|
|
29
|
-
.map(([k, v]) => `- **${k}**: ${v}`)
|
|
30
|
-
.join("\n");
|
|
31
|
-
|
|
32
|
-
return `# Lens
|
|
33
|
-
> Generated: ${data.generatedAt}${data.lastUpdated !== data.generatedAt ? ` | Updated: ${data.lastUpdated}` : ""}
|
|
34
|
-
|
|
35
|
-
## Overview
|
|
36
|
-
${data.overview}
|
|
37
|
-
|
|
38
|
-
## Architecture
|
|
39
|
-
${data.architecture}
|
|
40
|
-
|
|
41
|
-
## Tooling & Conventions
|
|
42
|
-
${toolingLines || "- Not yet determined"}
|
|
43
|
-
|
|
44
|
-
## Important Folders
|
|
45
|
-
${data.importantFolders.map((f) => `- ${f}`).join("\n") || "- None"}
|
|
46
|
-
|
|
47
|
-
## Key Files
|
|
48
|
-
${data.keyFiles.map((f) => `- ${f}`).join("\n") || "- None"}
|
|
49
|
-
|
|
50
|
-
## Patterns & Idioms
|
|
51
|
-
${data.patterns.map((p) => `- ${p}`).join("\n") || "- None"}
|
|
52
|
-
|
|
53
|
-
## Suggestions
|
|
54
|
-
${data.suggestions.map((s) => `- ${s}`).join("\n") || "- None"}
|
|
55
|
-
|
|
56
|
-
<!--lens-json
|
|
57
|
-
${JSON.stringify(data)}
|
|
58
|
-
lens-json-->
|
|
59
|
-
`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function writeLensFile(repoPath: string, result: AnalysisResult): void {
|
|
63
|
-
const now = new Date().toISOString();
|
|
64
|
-
const data: LensFile = {
|
|
65
|
-
overview: result.overview,
|
|
66
|
-
importantFolders: result.importantFolders,
|
|
67
|
-
tooling: result.tooling ?? {},
|
|
68
|
-
keyFiles: result.keyFiles ?? [],
|
|
69
|
-
patterns: result.patterns ?? [],
|
|
70
|
-
architecture: result.architecture ?? "",
|
|
71
|
-
suggestions: result.suggestions,
|
|
72
|
-
generatedAt: now,
|
|
73
|
-
lastUpdated: now,
|
|
74
|
-
};
|
|
75
|
-
writeFileSync(lensFilePath(repoPath), renderLensFile(data), "utf-8");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function patchLensFile(
|
|
79
|
-
repoPath: string,
|
|
80
|
-
patch: Partial<AnalysisResult>,
|
|
81
|
-
): void {
|
|
82
|
-
const existing = readLensFile(repoPath);
|
|
83
|
-
const now = new Date().toISOString();
|
|
84
|
-
|
|
85
|
-
const base: LensFile = existing ?? {
|
|
86
|
-
overview: "",
|
|
87
|
-
importantFolders: [],
|
|
88
|
-
tooling: {},
|
|
89
|
-
keyFiles: [],
|
|
90
|
-
patterns: [],
|
|
91
|
-
architecture: "",
|
|
92
|
-
suggestions: [],
|
|
93
|
-
generatedAt: now,
|
|
94
|
-
lastUpdated: now,
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const merged: LensFile = {
|
|
98
|
-
...base,
|
|
99
|
-
lastUpdated: now,
|
|
100
|
-
overview: patch.overview ?? base.overview,
|
|
101
|
-
architecture: patch.architecture ?? base.architecture,
|
|
102
|
-
tooling: { ...base.tooling, ...(patch.tooling ?? {}) },
|
|
103
|
-
importantFolders: dedup([
|
|
104
|
-
...base.importantFolders,
|
|
105
|
-
...(patch.importantFolders ?? []),
|
|
106
|
-
]),
|
|
107
|
-
keyFiles: dedup([...base.keyFiles, ...(patch.keyFiles ?? [])]),
|
|
108
|
-
patterns: dedup([...base.patterns, ...(patch.patterns ?? [])]),
|
|
109
|
-
suggestions: dedup([...base.suggestions, ...(patch.suggestions ?? [])]),
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
writeFileSync(lensFilePath(repoPath), renderLensFile(merged), "utf-8");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function dedup(arr: string[]): string[] {
|
|
116
|
-
return [...new Map(arr.map((s) => [s.trim().toLowerCase(), s])).values()];
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function readLensFile(repoPath: string): LensFile | null {
|
|
120
|
-
const filePath = lensFilePath(repoPath);
|
|
121
|
-
if (!existsSync(filePath)) return null;
|
|
122
|
-
try {
|
|
123
|
-
const content = readFileSync(filePath, "utf-8");
|
|
124
|
-
const match = content.match(/<!--lens-json\n([\s\S]*?)\nlens-json-->/);
|
|
125
|
-
if (!match) return null;
|
|
126
|
-
return JSON.parse(match[1]!) as LensFile;
|
|
127
|
-
} catch {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function lensFileToAnalysisResult(lf: LensFile): AnalysisResult {
|
|
133
|
-
return {
|
|
134
|
-
overview: lf.overview,
|
|
135
|
-
importantFolders: lf.importantFolders,
|
|
136
|
-
tooling: lf.tooling,
|
|
137
|
-
keyFiles: lf.keyFiles,
|
|
138
|
-
patterns: lf.patterns,
|
|
139
|
-
architecture: lf.architecture,
|
|
140
|
-
suggestions: lf.suggestions,
|
|
141
|
-
};
|
|
142
|
-
}
|
package/src/utils/llm.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import type { Provider } from "../types/config";
|
|
2
|
-
|
|
3
|
-
type Message = { role: "user" | "assistant"; content: string };
|
|
4
|
-
|
|
5
|
-
export async function runPrompt(
|
|
6
|
-
provider: Provider,
|
|
7
|
-
prompt: string,
|
|
8
|
-
): Promise<string> {
|
|
9
|
-
if (provider.type === "anthropic") {
|
|
10
|
-
return runAnthropic(provider, prompt);
|
|
11
|
-
}
|
|
12
|
-
if (provider.type === "openai" || provider.type === "custom") {
|
|
13
|
-
return runOpenAICompat(provider, prompt);
|
|
14
|
-
}
|
|
15
|
-
if (provider.type === "ollama") {
|
|
16
|
-
return runOllama(provider, prompt);
|
|
17
|
-
}
|
|
18
|
-
throw new Error(`Unknown provider type: ${provider.type}`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function runAnthropic(
|
|
22
|
-
provider: Provider,
|
|
23
|
-
prompt: string,
|
|
24
|
-
): Promise<string> {
|
|
25
|
-
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
26
|
-
method: "POST",
|
|
27
|
-
headers: {
|
|
28
|
-
"Content-Type": "application/json",
|
|
29
|
-
"x-api-key": provider.apiKey ?? "",
|
|
30
|
-
"anthropic-version": "2023-06-01",
|
|
31
|
-
},
|
|
32
|
-
body: JSON.stringify({
|
|
33
|
-
model: provider.model,
|
|
34
|
-
max_tokens: 2000,
|
|
35
|
-
messages: [{ role: "user", content: prompt }],
|
|
36
|
-
}),
|
|
37
|
-
});
|
|
38
|
-
if (!res.ok) throw new Error(`Anthropic error: ${res.statusText}`);
|
|
39
|
-
const data = (await res.json()) as any;
|
|
40
|
-
return data.content
|
|
41
|
-
.filter((b: { type: string }) => b.type === "text")
|
|
42
|
-
.map((b: { text: string }) => b.text)
|
|
43
|
-
.join("");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function runOpenAICompat(
|
|
47
|
-
provider: Provider,
|
|
48
|
-
prompt: string,
|
|
49
|
-
): Promise<string> {
|
|
50
|
-
const baseUrl = provider.baseUrl ?? "https://api.openai.com/v1";
|
|
51
|
-
const res = await fetch(`${baseUrl}/chat/completions`, {
|
|
52
|
-
method: "POST",
|
|
53
|
-
headers: {
|
|
54
|
-
"Content-Type": "application/json",
|
|
55
|
-
Authorization: `Bearer ${provider.apiKey ?? ""}`,
|
|
56
|
-
},
|
|
57
|
-
body: JSON.stringify({
|
|
58
|
-
model: provider.model,
|
|
59
|
-
messages: [{ role: "user", content: prompt }],
|
|
60
|
-
}),
|
|
61
|
-
});
|
|
62
|
-
if (!res.ok) throw new Error(`OpenAI error: ${res.statusText}`);
|
|
63
|
-
const data = (await res.json()) as any;
|
|
64
|
-
return data.choices?.[0]?.message?.content ?? "";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function runOllama(provider: Provider, prompt: string): Promise<string> {
|
|
68
|
-
const baseUrl = provider.baseUrl ?? "http://localhost:11434";
|
|
69
|
-
const res = await fetch(`${baseUrl}/api/chat`, {
|
|
70
|
-
method: "POST",
|
|
71
|
-
headers: { "Content-Type": "application/json" },
|
|
72
|
-
body: JSON.stringify({
|
|
73
|
-
model: provider.model,
|
|
74
|
-
stream: false,
|
|
75
|
-
messages: [{ role: "user", content: prompt }],
|
|
76
|
-
}),
|
|
77
|
-
});
|
|
78
|
-
if (!res.ok) throw new Error(`Ollama error: ${res.statusText}`);
|
|
79
|
-
const data = (await res.json()) as any;
|
|
80
|
-
return data.message?.content ?? "";
|
|
81
|
-
}
|