@ridit/lens 0.2.2 → 0.2.4

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/LENS.md CHANGED
@@ -1,25 +1,68 @@
1
- # Lens Analysis
2
- > Generated: 2026-03-11T17:49:46.564Z
1
+ # Lens - Codebase Intelligence Tool
3
2
 
4
- ## Overview
5
- This project is a CLI tool called 'lens' that analyzes a repository and provides insights. It uses React, Ink, and various other libraries to create a text-based interface. The tool has several commands, including 'repo' for analyzing a remote repository, 'init' for initializing the tool, and 'review' for reviewing a local codebase. The project is designed for developers who want to quickly understand a repository's structure and content.
3
+ Lens is a CLI tool that helps developers understand, navigate, and interact with codebases using AI-powered analysis.
6
4
 
7
- ## Important Folders
8
- - src/components: contains various components, including FileReviewer, FileViewer, and ProviderPicker. Each component is used to display specific information about the repository.
9
- - src/commands: contains the implementation of the 'repo', 'init', and 'review' commands. Each command has its own file and exports a function that handles the command's logic.
10
- - src/utils: contains utility functions for tasks such as cloning a repository, reading files, and analyzing code. These functions are used throughout the project to perform specific tasks.
5
+ ## Core Features
11
6
 
12
- ## Missing Configs
13
- - None detected
7
+ - **Repository Analysis**: Analyze both local and remote repositories
8
+ - **AI-Powered Insights**: Uses LLMs to understand code structure and content
9
+ - **Interactive Chat**: Converse with your codebase using natural language
10
+ - **Code Review**: Automated code reviews with specific suggestions
11
+ - **Timeline Exploration**: Explore commit history and code evolution
12
+ - **Task Automation**: Apply natural language changes to codebases
14
13
 
15
- ## Security Issues
16
- - None detected
14
+ ## Supported AI Providers
17
15
 
18
- ## Suggestions
19
- - In src/components/FileReviewer.tsx, consider adding a check to ensure that the 'files' prop is not empty before rendering the file list.
20
- - In src/commands/repo.tsx, consider adding error handling for cases where the repository URL is invalid or the repository cannot be cloned.
21
- - In src/utils/llm.ts, consider adding a timeout to the 'runPrompt' function to prevent it from running indefinitely if the model takes too long to respond.
16
+ - Anthropic
17
+ - Gemini (Google AI)
18
+ - OpenAI
19
+ - Ollama (local models)
20
+ - Custom endpoints
22
21
 
23
- <!--lens-json
24
- {"overview":"This project is a CLI tool called 'lens' that analyzes a repository and provides insights. It uses React, Ink, and various other libraries to create a text-based interface. The tool has several commands, including 'repo' for analyzing a remote repository, 'init' for initializing the tool, and 'review' for reviewing a local codebase. The project is designed for developers who want to quickly understand a repository's structure and content.","importantFolders":["src/components: contains various components, including FileReviewer, FileViewer, and ProviderPicker. Each component is used to display specific information about the repository.","src/commands: contains the implementation of the 'repo', 'init', and 'review' commands. Each command has its own file and exports a function that handles the command's logic.","src/utils: contains utility functions for tasks such as cloning a repository, reading files, and analyzing code. These functions are used throughout the project to perform specific tasks."],"missingConfigs":[],"securityIssues":[],"suggestions":["In src/components/FileReviewer.tsx, consider adding a check to ensure that the 'files' prop is not empty before rendering the file list.","In src/commands/repo.tsx, consider adding error handling for cases where the repository URL is invalid or the repository cannot be cloned.","In src/utils/llm.ts, consider adding a timeout to the 'runPrompt' function to prevent it from running indefinitely if the model takes too long to respond."],"generatedAt":"2026-03-11T17:49:46.564Z"}
25
- lens-json-->
22
+ ## Technical Architecture
23
+
24
+ - Built with React components rendered in terminal via Ink
25
+ - TypeScript throughout for type safety
26
+ - Bun as build tool and runtime
27
+ - Commander.js for CLI structure
28
+ - Modular command system with separate handlers
29
+
30
+ ## Commands
31
+
32
+ - `lens repo <url>` - Analyze a remote repository
33
+ - `lens review [path]` - Review a local codebase
34
+ - `lens task <text>` - Apply natural language changes
35
+ - `lens chat` - Interactive chat with codebase
36
+ - `lens timeline` - Explore commit history
37
+ - `lens provider` - Configure AI providers
38
+
39
+ ## Key Components
40
+
41
+ - **Smart File Selection**: AI determines which files are most important
42
+ - **Structured Analysis**: Provides overviews, folder insights, and suggestions
43
+ - **Security Scanning**: Identifies potential security issues
44
+ - **Multi-Model Support**: Flexible AI backend configuration
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ npm install -g @ridit/lens
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ```bash
55
+ # Analyze a GitHub repository
56
+ lens repo https://github.com/user/repo
57
+
58
+ # Review local codebase
59
+ lens review .
60
+
61
+ # Chat with your code
62
+ lens chat --path .
63
+
64
+ # Make changes with natural language
65
+ lens task "Add TypeScript types to this component" --path .
66
+ ```
67
+
68
+ Lens helps developers quickly understand complex codebases through AI-assisted analysis and natural language interaction.
package/dist/index.mjs CHANGED
@@ -35841,6 +35841,43 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
35841
35841
  }
35842
35842
  });
35843
35843
 
35844
+ // src/utils/tools/registry.ts
35845
+ class ToolRegistry {
35846
+ tools = new Map;
35847
+ register(tool) {
35848
+ if (this.tools.has(tool.name)) {
35849
+ console.warn(`[ToolRegistry] Overwriting existing tool: "${tool.name}"`);
35850
+ }
35851
+ this.tools.set(tool.name, tool);
35852
+ }
35853
+ unregister(name) {
35854
+ this.tools.delete(name);
35855
+ }
35856
+ get(name) {
35857
+ return this.tools.get(name);
35858
+ }
35859
+ all() {
35860
+ return Array.from(this.tools.values());
35861
+ }
35862
+ names() {
35863
+ return Array.from(this.tools.keys());
35864
+ }
35865
+ buildSystemPromptSection() {
35866
+ const lines = [`## TOOLS
35867
+ `];
35868
+ lines.push("You have exactly " + this.tools.size + ` tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.
35869
+ `);
35870
+ let i = 1;
35871
+ for (const tool of this.tools.values()) {
35872
+ lines.push(tool.systemPromptEntry(i++));
35873
+ }
35874
+ return lines.join(`
35875
+ `);
35876
+ }
35877
+ }
35878
+ var registry = new ToolRegistry;
35879
+ globalThis.__lens_registry = registry;
35880
+
35844
35881
  // node_modules/ink/build/render.js
35845
35882
  import { Stream } from "node:stream";
35846
35883
  import process13 from "node:process";
@@ -49805,42 +49842,6 @@ Got it — I'll always use bun for this project.`
49805
49842
  Done — removed that memory.`
49806
49843
  }
49807
49844
  ];
49808
- // src/utils/tools/registry.ts
49809
- class ToolRegistry {
49810
- tools = new Map;
49811
- register(tool) {
49812
- if (this.tools.has(tool.name)) {
49813
- console.warn(`[ToolRegistry] Overwriting existing tool: "${tool.name}"`);
49814
- }
49815
- this.tools.set(tool.name, tool);
49816
- }
49817
- unregister(name) {
49818
- this.tools.delete(name);
49819
- }
49820
- get(name) {
49821
- return this.tools.get(name);
49822
- }
49823
- all() {
49824
- return Array.from(this.tools.values());
49825
- }
49826
- names() {
49827
- return Array.from(this.tools.keys());
49828
- }
49829
- buildSystemPromptSection() {
49830
- const lines = [`## TOOLS
49831
- `];
49832
- lines.push("You have exactly " + this.tools.size + ` tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.
49833
- `);
49834
- let i = 1;
49835
- for (const tool of this.tools.values()) {
49836
- lines.push(tool.systemPromptEntry(i++));
49837
- }
49838
- return lines.join(`
49839
- `);
49840
- }
49841
- }
49842
- var registry = new ToolRegistry;
49843
-
49844
49845
  // src/utils/chat.ts
49845
49846
  function parseResponse(text) {
49846
49847
  const scanText = text.replace(/```[\s\S]*?```/g, (m) => " ".repeat(m.length));
@@ -52368,8 +52369,8 @@ function appendMemory(entry) {
52368
52369
  }
52369
52370
  function buildMemorySummary(repoPath) {
52370
52371
  const m = loadMemoryFile();
52371
- const relevant = m.entries.filter((e) => e.repoPath === repoPath).slice(-50);
52372
- const memories = m.memories.filter((mem) => mem.repoPath === repoPath);
52372
+ const relevant = m.entries.slice(-50);
52373
+ const memories = m.memories;
52373
52374
  const parts = [];
52374
52375
  if (memories.length > 0) {
52375
52376
  parts.push(`## MEMORIES ABOUT THIS REPO
@@ -52395,8 +52396,8 @@ ${lines.join(`
52395
52396
  }
52396
52397
  function clearRepoMemory(repoPath) {
52397
52398
  const m = loadMemoryFile();
52398
- m.entries = m.entries.filter((e) => e.repoPath !== repoPath);
52399
- m.memories = m.memories.filter((mem) => mem.repoPath !== repoPath);
52399
+ m.entries = m.entries = [];
52400
+ m.memories = m.memories = [];
52400
52401
  saveMemoryFile(m);
52401
52402
  }
52402
52403
  function generateId() {
@@ -52407,8 +52408,7 @@ function addMemory(content, repoPath) {
52407
52408
  const memory = {
52408
52409
  id: generateId(),
52409
52410
  content,
52410
- timestamp: new Date().toISOString(),
52411
- repoPath
52411
+ timestamp: new Date().toISOString()
52412
52412
  };
52413
52413
  m.memories.push(memory);
52414
52414
  saveMemoryFile(m);
@@ -52417,14 +52417,14 @@ function addMemory(content, repoPath) {
52417
52417
  function deleteMemory(id, repoPath) {
52418
52418
  const m = loadMemoryFile();
52419
52419
  const before2 = m.memories.length;
52420
- m.memories = m.memories.filter((mem) => !(mem.id === id && mem.repoPath === repoPath));
52420
+ m.memories = m.memories.filter((mem) => !(mem.id === id));
52421
52421
  if (m.memories.length === before2)
52422
52422
  return false;
52423
52423
  saveMemoryFile(m);
52424
52424
  return true;
52425
52425
  }
52426
52426
  function listMemories(repoPath) {
52427
- return loadMemoryFile().memories.filter((mem) => mem.repoPath === repoPath);
52427
+ return loadMemoryFile().memories;
52428
52428
  }
52429
52429
 
52430
52430
  // src/components/chat/ChatRunner.tsx
@@ -53678,9 +53678,32 @@ function registerBuiltins() {
53678
53678
  registry.register(changesTool);
53679
53679
  }
53680
53680
 
53681
+ // src/utils/addons/loadAddons.ts
53682
+ import path21 from "path";
53683
+ import os10 from "os";
53684
+ import { existsSync as existsSync16, readdirSync as readdirSync5 } from "fs";
53685
+ var ADDONS_DIR = path21.join(os10.homedir(), ".lens", "addons");
53686
+ async function loadAddons() {
53687
+ if (!existsSync16(ADDONS_DIR)) {
53688
+ return;
53689
+ }
53690
+ const files = readdirSync5(ADDONS_DIR).filter((f) => f.endsWith(".js") && !f.startsWith("_"));
53691
+ for (const file of files) {
53692
+ const fullPath = path21.join(ADDONS_DIR, file);
53693
+ try {
53694
+ await import(fullPath);
53695
+ console.log(`[addons] loaded: ${file}
53696
+ `);
53697
+ } catch (err) {
53698
+ console.error(`[addons] failed to load ${file}:`, err instanceof Error ? err.message : String(err));
53699
+ }
53700
+ }
53701
+ }
53702
+
53681
53703
  // src/index.tsx
53682
53704
  var jsx_dev_runtime25 = __toESM(require_jsx_dev_runtime(), 1);
53683
53705
  registerBuiltins();
53706
+ await loadAddons();
53684
53707
  var program2 = new Command;
53685
53708
  program2.command("repo <url>").description("Analyze a remote repository").action((url) => {
53686
53709
  render_default(/* @__PURE__ */ jsx_dev_runtime25.jsxDEV(RepoCommand, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ridit/lens",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Know Your Codebase.",
5
5
  "author": "Ridit Jangra <riditjangra09@gmail.com> (https://ridit.space)",
6
6
  "license": "MIT",
@@ -18,6 +18,7 @@
18
18
  "prepublishOnly": "npm run build"
19
19
  },
20
20
  "dependencies": {
21
+ "@ridit/lens-sdk": "0.1.4",
21
22
  "commander": "^14.0.3",
22
23
  "figures": "^6.1.0",
23
24
  "ink": "^6.8.0",
package/src/index.tsx CHANGED
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import "./utils/tools/registry";
2
3
  import { render } from "ink";
3
4
  import { Command } from "commander";
4
5
  import { RepoCommand } from "./commands/repo";
@@ -8,8 +9,10 @@ import { TaskCommand } from "./commands/task";
8
9
  import { ChatCommand } from "./commands/chat";
9
10
  import { TimelineCommand } from "./commands/timeline";
10
11
  import { registerBuiltins } from "./utils/tools/builtins";
12
+ import { loadAddons } from "./utils/addons/loadAddons";
11
13
 
12
14
  registerBuiltins();
15
+ await loadAddons();
13
16
 
14
17
  const program = new Command();
15
18
 
@@ -0,0 +1,29 @@
1
+ import path from "path";
2
+ import os from "os";
3
+ import { existsSync, readdirSync } from "fs";
4
+
5
+ const ADDONS_DIR = path.join(os.homedir(), ".lens", "addons");
6
+
7
+ export async function loadAddons(): Promise<void> {
8
+ if (!existsSync(ADDONS_DIR)) {
9
+ // Silently skip — no addons directory yet
10
+ return;
11
+ }
12
+
13
+ const files = readdirSync(ADDONS_DIR).filter(
14
+ (f) => f.endsWith(".js") && !f.startsWith("_"),
15
+ );
16
+
17
+ for (const file of files) {
18
+ const fullPath = path.join(ADDONS_DIR, file);
19
+ try {
20
+ await import(fullPath);
21
+ console.log(`[addons] loaded: ${file}\n`);
22
+ } catch (err) {
23
+ console.error(
24
+ `[addons] failed to load ${file}:`,
25
+ err instanceof Error ? err.message : String(err),
26
+ );
27
+ }
28
+ }
29
+ }
package/src/utils/chat.ts CHANGED
@@ -22,7 +22,7 @@ export { buildSystemPrompt, FEW_SHOT_MESSAGES } from "../prompts";
22
22
  import type { Message } from "../types/chat";
23
23
  import type { Provider } from "../types/config";
24
24
  import { FEW_SHOT_MESSAGES } from "../prompts";
25
- import { registry } from "./tools/registry";
25
+ import { registry } from "../utils/tools/registry";
26
26
  import type { FilePatch } from "../components/repo/DiffViewer";
27
27
 
28
28
  export type ParsedResponse =
@@ -15,14 +15,12 @@ export type MemoryEntry = {
15
15
  detail: string;
16
16
  summary: string;
17
17
  timestamp: string;
18
- repoPath: string;
19
18
  };
20
19
 
21
20
  export type Memory = {
22
21
  id: string;
23
22
  content: string;
24
23
  timestamp: string;
25
- repoPath: string;
26
24
  };
27
25
 
28
26
  export type MemoryFile = {
@@ -64,9 +62,9 @@ export function appendMemory(entry: Omit<MemoryEntry, "timestamp">): void {
64
62
 
65
63
  export function buildMemorySummary(repoPath: string): string {
66
64
  const m = loadMemoryFile();
67
- const relevant = m.entries.filter((e) => e.repoPath === repoPath).slice(-50);
65
+ const relevant = m.entries.slice(-50);
68
66
 
69
- const memories = m.memories.filter((mem) => mem.repoPath === repoPath);
67
+ const memories = m.memories;
70
68
 
71
69
  const parts: string[] = [];
72
70
 
@@ -92,13 +90,13 @@ export function buildMemorySummary(repoPath: string): string {
92
90
  }
93
91
 
94
92
  export function getRepoMemory(repoPath: string): MemoryEntry[] {
95
- return loadMemoryFile().entries.filter((e) => e.repoPath === repoPath);
93
+ return loadMemoryFile().entries;
96
94
  }
97
95
 
98
96
  export function clearRepoMemory(repoPath: string): void {
99
97
  const m = loadMemoryFile();
100
- m.entries = m.entries.filter((e) => e.repoPath !== repoPath);
101
- m.memories = m.memories.filter((mem) => mem.repoPath !== repoPath);
98
+ m.entries = m.entries = [];
99
+ m.memories = m.memories = [];
102
100
  saveMemoryFile(m);
103
101
  }
104
102
 
@@ -114,7 +112,6 @@ export function addMemory(content: string, repoPath: string): Memory {
114
112
  id: generateId(),
115
113
  content,
116
114
  timestamp: new Date().toISOString(),
117
- repoPath,
118
115
  };
119
116
  m.memories.push(memory);
120
117
  saveMemoryFile(m);
@@ -124,14 +121,12 @@ export function addMemory(content: string, repoPath: string): Memory {
124
121
  export function deleteMemory(id: string, repoPath: string): boolean {
125
122
  const m = loadMemoryFile();
126
123
  const before = m.memories.length;
127
- m.memories = m.memories.filter(
128
- (mem) => !(mem.id === id && mem.repoPath === repoPath),
129
- );
124
+ m.memories = m.memories.filter((mem) => !(mem.id === id));
130
125
  if (m.memories.length === before) return false;
131
126
  saveMemoryFile(m);
132
127
  return true;
133
128
  }
134
129
 
135
130
  export function listMemories(repoPath: string): Memory[] {
136
- return loadMemoryFile().memories.filter((mem) => mem.repoPath === repoPath);
131
+ return loadMemoryFile().memories;
137
132
  }
@@ -1,4 +1,4 @@
1
- import type { Tool, ToolContext, ToolResult } from "./registry";
1
+ import type { Tool, ToolContext } from "@ridit/lens-sdk";
2
2
  import {
3
3
  fetchUrl,
4
4
  searchWeb,
@@ -10,65 +10,7 @@
10
10
  // import { registry } from "lens/tools/registry";
11
11
  // registry.register({ name: "my-tool", ... });
12
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
- }
13
+ import type { Tool } from "@ridit/lens-sdk";
72
14
 
73
15
  // ── Registry ──────────────────────────────────────────────────────────────────
74
16
 
@@ -117,3 +59,5 @@ class ToolRegistry {
117
59
  }
118
60
 
119
61
  export const registry = new ToolRegistry();
62
+
63
+ (globalThis as any).__lens_registry = registry;
package/hello.py DELETED
@@ -1,51 +0,0 @@
1
- # hello.py
2
- # A simple script that reads a text file, makes an HTTP request,
3
- # and prints the result in a friendly way.
4
-
5
- import json
6
- import pathlib
7
- import sys
8
- from urllib.request import urlopen
9
-
10
- def read_local_file(path: str) -> str:
11
- """Read the contents of a file and return it as a string."""
12
- file_path = pathlib.Path(path)
13
- if not file_path.is_file():
14
- raise FileNotFoundError(f"❌ File not found: {path}")
15
- return file_path.read_text(encoding="utf-8")
16
-
17
- def fetch_json(url: str) -> dict:
18
- """Fetch JSON from a URL and decode it into a Python dict."""
19
- with urlopen(url) as response:
20
- if response.status != 200:
21
- raise RuntimeError(f"❌ HTTP {response.status} from {url}")
22
- data = response.read()
23
- return json.loads(data)
24
-
25
- def main():
26
- # 1️⃣ Read a local file (optional – you can comment this out)
27
- try:
28
- local_content = read_local_file("example.txt")
29
- print("📄 Contents of example.txt:")
30
- print(local_content)
31
- except FileNotFoundError:
32
- print("⚠️ example.txt not found – skipping that step.")
33
-
34
- # 2️⃣ Fetch some JSON data from a public API
35
- url = "https://api.github.com/repos/python/cpython"
36
- print(f"\n🌐 Fetching data from {url} …")
37
- repo_info = fetch_json(url)
38
-
39
- # 3️⃣ Extract a couple of useful fields and display them
40
- name = repo_info.get("name")
41
- stars = repo_info.get("stargazers_count")
42
- description = repo_info.get("description")
43
- print("\n🔎 Repository info:")
44
- print(f"• Name: {name}")
45
- print(f"• Stars: {stars:,}") # adds commas for readability
46
- print(f"• Description: {description}")
47
-
48
- return 0
49
-
50
- if __name__ == "__main__":
51
- sys.exit(main())