@ridit/lens 0.2.0 → 0.2.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.
@@ -0,0 +1,119 @@
1
+ // ── Tool Plugin System ────────────────────────────────────────────────────────
2
+ //
3
+ // To create a new tool:
4
+ //
5
+ // 1. Implement the Tool interface
6
+ // 2. Call registry.register(myTool) before the app starts
7
+ //
8
+ // External addon example:
9
+ //
10
+ // import { registry } from "lens/tools/registry";
11
+ // registry.register({ name: "my-tool", ... });
12
+
13
+ export interface ToolContext {
14
+ repoPath: string;
15
+ /** All messages in the current conversation so far */
16
+ messages: unknown[];
17
+ }
18
+
19
+ export type ToolResult =
20
+ | { kind: "text"; value: string }
21
+ | { kind: "error"; value: string };
22
+
23
+ export interface Tool<TInput = string> {
24
+ /**
25
+ * Tag name used in XML: <name>...</name>
26
+ * Must be lowercase, hyphens allowed. Must be unique.
27
+ */
28
+ name: string;
29
+
30
+ /**
31
+ * Short description shown in system prompt and /help.
32
+ */
33
+ description: string;
34
+
35
+ /**
36
+ * System prompt snippet explaining how to invoke this tool.
37
+ * Return the full ### N. name — description block.
38
+ */
39
+ systemPromptEntry(index: number): string;
40
+
41
+ /**
42
+ * Parse the raw inner text of the XML tag into a typed input.
43
+ * Throw or return null to signal a parse failure (tool will be skipped).
44
+ */
45
+ parseInput(body: string): TInput | null;
46
+
47
+ /**
48
+ * Execute the tool. May be async.
49
+ * Return a ToolResult — the value is fed back to the model as the tool result.
50
+ */
51
+ execute(input: TInput, ctx: ToolContext): Promise<ToolResult> | ToolResult;
52
+
53
+ /**
54
+ * Whether this tool is safe to auto-approve (read-only, no side effects).
55
+ * Defaults to false.
56
+ */
57
+ safe?: boolean;
58
+
59
+ /**
60
+ * Optional: permission prompt label shown to the user before execution.
61
+ * e.g. "run", "read", "write", "delete"
62
+ * Defaults to the tool name.
63
+ */
64
+ permissionLabel?: string;
65
+
66
+ /**
67
+ * Optional: summarise the input for display in the chat history.
68
+ * Defaults to showing the raw input string.
69
+ */
70
+ summariseInput?(input: TInput): string;
71
+ }
72
+
73
+ // ── Registry ──────────────────────────────────────────────────────────────────
74
+
75
+ class ToolRegistry {
76
+ private tools = new Map<string, Tool<unknown>>();
77
+
78
+ register<T>(tool: Tool<T>): void {
79
+ if (this.tools.has(tool.name)) {
80
+ console.warn(`[ToolRegistry] Overwriting existing tool: "${tool.name}"`);
81
+ }
82
+ this.tools.set(tool.name, tool as Tool<unknown>);
83
+ }
84
+
85
+ unregister(name: string): void {
86
+ this.tools.delete(name);
87
+ }
88
+
89
+ get(name: string): Tool<unknown> | undefined {
90
+ return this.tools.get(name);
91
+ }
92
+
93
+ all(): Tool<unknown>[] {
94
+ return Array.from(this.tools.values());
95
+ }
96
+
97
+ names(): string[] {
98
+ return Array.from(this.tools.keys());
99
+ }
100
+
101
+ /**
102
+ * Build the TOOLS section of the system prompt from all registered tools.
103
+ */
104
+ buildSystemPromptSection(): string {
105
+ const lines: string[] = ["## TOOLS\n"];
106
+ lines.push(
107
+ "You have exactly " +
108
+ this.tools.size +
109
+ " tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.\n",
110
+ );
111
+ let i = 1;
112
+ for (const tool of this.tools.values()) {
113
+ lines.push(tool.systemPromptEntry(i++));
114
+ }
115
+ return lines.join("\n");
116
+ }
117
+ }
118
+
119
+ export const registry = new ToolRegistry();
@@ -1,86 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
- import path from "path";
3
- import os from "os";
4
-
5
- export type HistoryEntryKind =
6
- | "file-written"
7
- | "file-read"
8
- | "url-fetched"
9
- | "shell-run"
10
- | "code-applied"
11
- | "code-skipped";
12
-
13
- export type HistoryEntry = {
14
- kind: HistoryEntryKind;
15
- detail: string;
16
- summary: string;
17
- timestamp: string;
18
- repoPath: string;
19
- };
20
-
21
- export type HistoryFile = {
22
- entries: HistoryEntry[];
23
- };
24
-
25
- const LENS_DIR = path.join(os.homedir(), ".lens");
26
- const HISTORY_PATH = path.join(LENS_DIR, "history.json");
27
-
28
- function loadHistory(): HistoryFile {
29
- if (!existsSync(HISTORY_PATH)) return { entries: [] };
30
- try {
31
- return JSON.parse(readFileSync(HISTORY_PATH, "utf-8")) as HistoryFile;
32
- } catch {
33
- return { entries: [] };
34
- }
35
- }
36
-
37
- function saveHistory(h: HistoryFile): void {
38
- if (!existsSync(LENS_DIR)) mkdirSync(LENS_DIR, { recursive: true });
39
- writeFileSync(HISTORY_PATH, JSON.stringify(h, null, 2), "utf-8");
40
- }
41
-
42
- export function appendHistory(entry: Omit<HistoryEntry, "timestamp">): void {
43
- const h = loadHistory();
44
- h.entries.push({ ...entry, timestamp: new Date().toISOString() });
45
-
46
- if (h.entries.length > 500) h.entries = h.entries.slice(-500);
47
- saveHistory(h);
48
- }
49
-
50
- /**
51
- * Returns a compact summary string to inject into the system prompt.
52
- * Only includes entries for the current repo, most recent 50.
53
- */
54
- export function buildHistorySummary(repoPath: string): string {
55
- const h = loadHistory();
56
- const relevant = h.entries.filter((e) => e.repoPath === repoPath).slice(-50);
57
-
58
- if (relevant.length === 0) return "";
59
-
60
- const lines = relevant.map((e) => {
61
- const ts = new Date(e.timestamp).toLocaleString();
62
- return `[${ts}] ${e.kind}: ${e.detail} — ${e.summary}`;
63
- });
64
-
65
- return `## WHAT YOU HAVE ALREADY DONE IN THIS REPO
66
-
67
- The following actions have already been completed. Do NOT repeat them unless the user explicitly asks you to redo something:
68
-
69
- ${lines.join("\n")}`;
70
- }
71
-
72
- /**
73
- * Returns all entries for a repo, for display purposes.
74
- */
75
- export function getRepoHistory(repoPath: string): HistoryEntry[] {
76
- return loadHistory().entries.filter((e) => e.repoPath === repoPath);
77
- }
78
-
79
- /**
80
- * Clears all history for a repo.
81
- */
82
- export function clearRepoHistory(repoPath: string): void {
83
- const h = loadHistory();
84
- h.entries = h.entries.filter((e) => e.repoPath !== repoPath);
85
- saveHistory(h);
86
- }