devlensio 0.4.3 → 0.6.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.
Files changed (50) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +212 -72
  3. package/dist/config/index.d.ts +14 -2
  4. package/dist/config/index.js +77 -2
  5. package/dist/config/providers/catalog.d.ts +27 -0
  6. package/dist/config/providers/catalog.js +130 -0
  7. package/dist/config/providers/file.d.ts +4 -2
  8. package/dist/config/providers/file.js +198 -32
  9. package/dist/config/providers/paths.d.ts +2 -0
  10. package/dist/config/providers/paths.js +4 -0
  11. package/dist/config/providers/providers.default.d.ts +3 -0
  12. package/dist/config/providers/providers.default.js +12 -0
  13. package/dist/config/providers/request.js +21 -9
  14. package/dist/config/types.d.ts +25 -2
  15. package/dist/config/types.js +22 -7
  16. package/dist/config/writer.d.ts +8 -2
  17. package/dist/config/writer.js +59 -12
  18. package/dist/filesystem/index.js +6 -0
  19. package/dist/filesystem/index.test.js +170 -0
  20. package/dist/filesystem/reactRouterRoutes.d.ts +2 -0
  21. package/dist/filesystem/reactRouterRoutes.js +243 -0
  22. package/dist/graph/edges/apiFetchEdges.js +1 -137
  23. package/dist/graph/edges/helpers/routeMatching.d.ts +8 -0
  24. package/dist/graph/edges/helpers/routeMatching.js +122 -0
  25. package/dist/graph/edges/navigationEdges.d.ts +5 -0
  26. package/dist/graph/edges/navigationEdges.js +339 -0
  27. package/dist/graph/edges/routeEdge.js +25 -0
  28. package/dist/graph/index.js +12 -1
  29. package/dist/graph/index.test.js +263 -8
  30. package/dist/index.d.ts +6 -1
  31. package/dist/index.js +5 -1
  32. package/dist/parser/extractors/objectMethods.js +2 -2
  33. package/dist/parser/index.test.js +0 -10
  34. package/dist/pipeline/index.js +26 -0
  35. package/dist/summarizer/providers/anthropic.d.ts +2 -1
  36. package/dist/summarizer/providers/anthropic.js +3 -2
  37. package/dist/summarizer/providers/index.js +19 -35
  38. package/dist/summarizer/providers/models.d.ts +11 -0
  39. package/dist/summarizer/providers/models.js +113 -0
  40. package/dist/summarizer/providers/openai.d.ts +2 -1
  41. package/dist/summarizer/providers/openai.js +2 -1
  42. package/dist/summarizer/providers/types.d.ts +1 -6
  43. package/dist/types.d.ts +3 -2
  44. package/package.json +1 -1
  45. package/dist/summarizer/providers/gemini.d.ts +0 -9
  46. package/dist/summarizer/providers/gemini.js +0 -79
  47. package/dist/summarizer/providers/ollama.d.ts +0 -9
  48. package/dist/summarizer/providers/ollama.js +0 -23
  49. package/dist/summarizer/providers/openRouter.d.ts +0 -9
  50. package/dist/summarizer/providers/openRouter.js +0 -19
@@ -0,0 +1,113 @@
1
+ const FETCH_TIMEOUT_MS = 10000;
2
+ /**
3
+ * Fetches the live model list from a provider's models endpoint.
4
+ * Returns deduped (case-insensitive), sorted model ID strings.
5
+ * Throws on auth/network errors with a clear message.
6
+ */
7
+ export async function listModels(opts) {
8
+ if (opts.protocol === "openai") {
9
+ return listOpenAIModels(opts.baseUrl, opts.apiKey);
10
+ }
11
+ return listAnthropicModels(opts.baseUrl, opts.apiKey);
12
+ }
13
+ // OpenAI-style lister
14
+ // GET {baseUrl}/models | Authorization: Bearer {apiKey}
15
+ // Tolerant of URLs with or without /v1 — tries as-is first, retries with /v1 if empty.
16
+ async function listOpenAIModels(baseUrl, apiKey) {
17
+ const headers = {
18
+ "Content-Type": "application/json",
19
+ };
20
+ if (apiKey) {
21
+ headers["Authorization"] = `Bearer ${apiKey}`;
22
+ }
23
+ async function tryFetch(url) {
24
+ const controller = new AbortController();
25
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
26
+ try {
27
+ return await fetch(url, { method: "GET", headers, signal: controller.signal });
28
+ }
29
+ finally {
30
+ clearTimeout(timer);
31
+ }
32
+ }
33
+ const cleanBase = baseUrl.replace(/\/+$/, "");
34
+ let res = await tryFetch(`${cleanBase}/models`);
35
+ // If URL has no /v1 segment and the request returned nothing useful, retry with /v1
36
+ if (!res.ok && !cleanBase.includes("/v1")) {
37
+ res = await tryFetch(`${cleanBase}/v1/models`);
38
+ }
39
+ if (res.status === 401) {
40
+ throw new Error("Invalid API key for this provider. Check your key in config.");
41
+ }
42
+ if (!res.ok) {
43
+ throw new Error(`Failed to fetch models: ${res.status} ${res.statusText}`);
44
+ }
45
+ const body = (await res.json());
46
+ return normalizeModels((body.data ?? []).map((m) => m.id));
47
+ }
48
+ // Anthropic-style lister
49
+ // GET {baseUrl}/v1/models | x-api-key + anthropic-version
50
+ // Paginated — loops via after_id until has_more === false
51
+ async function listAnthropicModels(baseUrl, apiKey) {
52
+ if (!apiKey) {
53
+ throw new Error("Anthropic-style provider requires an API key to list models.");
54
+ }
55
+ const headers = {
56
+ "x-api-key": apiKey,
57
+ "anthropic-version": "2023-06-01",
58
+ "Content-Type": "application/json",
59
+ };
60
+ const base = `${baseUrl.replace(/\/+$/, "")}/v1/models`;
61
+ const allIds = [];
62
+ let afterId;
63
+ let hasMore = true;
64
+ const controller = new AbortController();
65
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
66
+ try {
67
+ while (hasMore) {
68
+ const url = afterId
69
+ ? `${base}?after_id=${afterId}&limit=1000`
70
+ : `${base}?limit=1000`;
71
+ const res = await fetch(url, {
72
+ method: "GET",
73
+ headers,
74
+ signal: controller.signal,
75
+ });
76
+ if (res.status === 401) {
77
+ throw new Error("Invalid API key for this provider. Check your key in config.");
78
+ }
79
+ if (!res.ok) {
80
+ throw new Error(`Failed to fetch models: ${res.status} ${res.statusText}`);
81
+ }
82
+ const body = (await res.json());
83
+ allIds.push(...(body.data ?? []).map((m) => m.id));
84
+ hasMore = body.has_more ?? false;
85
+ afterId = body.last_id;
86
+ }
87
+ return normalizeModels(allIds);
88
+ }
89
+ catch (err) {
90
+ if (err.name === "AbortError") {
91
+ throw new Error(`Timed out fetching models from ${baseUrl} after ${FETCH_TIMEOUT_MS}ms`);
92
+ }
93
+ throw err;
94
+ }
95
+ finally {
96
+ clearTimeout(timer);
97
+ }
98
+ }
99
+ // Normalization
100
+ // Dedupe case-insensitively, sort ascending.
101
+ function normalizeModels(models) {
102
+ const seen = new Set();
103
+ const result = [];
104
+ for (const m of models) {
105
+ const key = m.toLowerCase();
106
+ if (!seen.has(key)) {
107
+ seen.add(key);
108
+ result.push(m);
109
+ }
110
+ }
111
+ result.sort((a, b) => a.localeCompare(b));
112
+ return result;
113
+ }
@@ -1,9 +1,10 @@
1
1
  import type { LLMClient, LLMRequest, NodeSummaryOutput } from "./types.js";
2
2
  export declare class OpenAIClient implements LLMClient {
3
3
  readonly provider: "openai";
4
+ readonly providerName?: string;
4
5
  readonly model: string;
5
6
  private client;
6
- constructor(apiKey: string, model: string, baseURL?: string);
7
+ constructor(apiKey: string, model: string, baseURL?: string, providerName?: string);
7
8
  summarize(request: LLMRequest): Promise<NodeSummaryOutput>;
8
9
  validateConnection(): Promise<void>;
9
10
  }
@@ -29,9 +29,10 @@ function parseResponse(raw) {
29
29
  // Also used as the base for OpenRouter and Ollama —
30
30
  // both expose OpenAI-compatible APIs, just with a different baseURL.
31
31
  export class OpenAIClient {
32
- constructor(apiKey, model, baseURL) {
32
+ constructor(apiKey, model, baseURL, providerName) {
33
33
  this.provider = "openai";
34
34
  this.model = model;
35
+ this.providerName = providerName;
35
36
  this.client = new OpenAI({ apiKey, ...(baseURL ? { baseURL } : {}) });
36
37
  }
37
38
  async summarize(request) {
@@ -20,13 +20,8 @@ export interface NodeSummaryOutput {
20
20
  }
21
21
  export interface LLMClient {
22
22
  readonly provider: LLMProvider;
23
+ readonly providerName?: string;
23
24
  readonly model: string;
24
25
  summarize(request: LLMRequest): Promise<NodeSummaryOutput>;
25
26
  validateConnection(): Promise<void>;
26
27
  }
27
- export type LLMClientFactory = (config: {
28
- provider: LLMProvider;
29
- model: string;
30
- apiKey?: string;
31
- baseUrl?: string;
32
- }) => LLMClient;
package/dist/types.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface ProjectFingerprint {
17
17
  databases: DatabaseLibrary[];
18
18
  rawDependencies: Record<string, string>;
19
19
  }
20
- export type RouteNodeType = "PAGE" | "LAYOUT" | "API_ROUTE" | "LOADING" | "ERROR" | "MIDDLEWARE" | "NOT_FOUND";
20
+ export type RouteNodeType = "PAGE" | "LAYOUT" | "API_ROUTE" | "LOADING" | "ERROR" | "MIDDLEWARE" | "NOT_FOUND" | "REACT_ROUTER_ROUTE";
21
21
  export interface RouteNode {
22
22
  type: RouteNodeType;
23
23
  nodeId?: string;
@@ -29,6 +29,7 @@ export interface RouteNode {
29
29
  layoutPath?: string;
30
30
  params?: string[];
31
31
  httpMethods?: string[];
32
+ rendersComponent?: string;
32
33
  }
33
34
  export interface BackendRouteNode {
34
35
  type: "BACKEND_ROUTE";
@@ -69,7 +70,7 @@ export interface CodeNode {
69
70
  score?: Number;
70
71
  metadata: Record<string, unknown>;
71
72
  }
72
- export type EdgeType = "CALLS" | "IMPORTS" | "READS_FROM" | "WRITES_TO" | "PROP_PASS" | "EMITS" | "LISTENS" | "WRAPPED_BY" | "GUARDS" | "HANDLES" | "TESTS" | "USES" | "NEXTJS_API_CALL";
73
+ export type EdgeType = "CALLS" | "IMPORTS" | "READS_FROM" | "WRITES_TO" | "PROP_PASS" | "EMITS" | "LISTENS" | "WRAPPED_BY" | "GUARDS" | "HANDLES" | "TESTS" | "USES" | "NEXTJS_API_CALL" | "NAVIGATES_TO";
73
74
  export interface CodeEdge {
74
75
  from: string;
75
76
  to: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devlensio",
3
- "version": "0.4.3",
3
+ "version": "0.6.0",
4
4
  "description": "Codebase intelligence engine for TypeScript/JavaScript/Reactjs/Nextjs repositories.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,9 +0,0 @@
1
- import type { LLMClient, LLMRequest, NodeSummaryOutput } from "./types.js";
2
- export declare class GeminiClient implements LLMClient {
3
- readonly provider: "gemini";
4
- readonly model: string;
5
- private ai;
6
- constructor(apiKey: string, model: string);
7
- summarize(request: LLMRequest): Promise<NodeSummaryOutput>;
8
- validateConnection(): Promise<void>;
9
- }
@@ -1,79 +0,0 @@
1
- import { GoogleGenAI } from "@google/genai";
2
- // ─── Response Parser ──────────────────────────────────────────────────────────
3
- //
4
- // Same XML format as all other providers — consistent across the board.
5
- const VALID_SEVERITIES = new Set(["none", "low", "medium", "high"]);
6
- function parseXmlTag(text, tag) {
7
- const match = text.match(new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`));
8
- return match ? match[1].trim() : "";
9
- }
10
- function parseResponse(raw) {
11
- const technicalSummary = parseXmlTag(raw, "technical");
12
- const businessSummary = parseXmlTag(raw, "business");
13
- const severityRaw = parseXmlTag(raw, "security_severity").toLowerCase();
14
- const securitySummary = parseXmlTag(raw, "security_summary");
15
- const severity = VALID_SEVERITIES.has(severityRaw)
16
- ? severityRaw
17
- : "none";
18
- return {
19
- technicalSummary: technicalSummary || raw.trim(),
20
- businessSummary: businessSummary || "",
21
- security: {
22
- severity,
23
- summary: severity === "none" ? "" : securitySummary,
24
- },
25
- tokensUsed: 0,
26
- };
27
- }
28
- export class GeminiClient {
29
- constructor(apiKey, model) {
30
- this.provider = "gemini";
31
- this.model = model;
32
- this.ai = new GoogleGenAI({ apiKey });
33
- }
34
- async summarize(request) {
35
- const systemMessage = request.messages.find(m => m.role === "system");
36
- const userMessages = request.messages.filter(m => m.role !== "system");
37
- const contents = userMessages.map(m => ({
38
- role: "user",
39
- parts: [{ text: m.content }],
40
- }));
41
- const response = await this.ai.models.generateContent({
42
- model: this.model,
43
- contents,
44
- config: {
45
- systemInstruction: systemMessage?.content,
46
- temperature: request.temperature ?? 0,
47
- maxOutputTokens: request.maxTokens ?? 2048,
48
- },
49
- });
50
- const raw = response.text ?? "";
51
- const result = parseResponse(raw);
52
- result.tokensUsed =
53
- (response.usageMetadata?.promptTokenCount ?? 0) +
54
- (response.usageMetadata?.candidatesTokenCount ?? 0);
55
- return result;
56
- }
57
- async validateConnection() {
58
- try {
59
- await this.ai.models.generateContent({
60
- model: this.model,
61
- contents: [{ role: "user", parts: [{ text: "hi" }] }],
62
- config: { maxOutputTokens: 10 },
63
- });
64
- }
65
- catch (err) {
66
- const status = err?.status ?? err?.statusCode ?? err?.code;
67
- const message = err?.message ?? "";
68
- if (status === 400 || message.includes("API_KEY_INVALID") || message.includes("invalid api key"))
69
- throw new Error(`Gemini API key is invalid or missing. Check your key in config.`);
70
- if (status === 403 || message.includes("PERMISSION_DENIED"))
71
- throw new Error(`Gemini API key does not have permission to use model "${this.model}".`);
72
- if (status === 404 || message.includes("models/") || message.includes("not found"))
73
- throw new Error(`Gemini model "${this.model}" not found. Check model name in config.`);
74
- if (status === 429 || message.includes("RESOURCE_EXHAUSTED"))
75
- throw new Error(`Gemini rate limit hit during connection check. Try again shortly.`);
76
- throw new Error(`Gemini connection failed: ${message || "unknown error"}`);
77
- }
78
- }
79
- }
@@ -1,9 +0,0 @@
1
- import type { LLMClient, LLMRequest, NodeSummaryOutput } from "./types.js";
2
- export declare class OllamaClient implements LLMClient {
3
- readonly provider: "ollama";
4
- readonly model: string;
5
- private inner;
6
- constructor(model: string, baseURL?: string);
7
- summarize(request: LLMRequest): Promise<NodeSummaryOutput>;
8
- validateConnection(): Promise<void>;
9
- }
@@ -1,23 +0,0 @@
1
- import { OpenAIClient } from "./openai.js";
2
- const OLLAMA_DEFAULT_BASE_URL = "http://localhost:11434";
3
- // ─── OllamaClient ─────────────────────────────────────────────────────────────
4
- //
5
- // Ollama exposes an OpenAI-compatible API at /v1 — wraps OpenAIClient.
6
- // No API key needed for local usage — passes a placeholder to satisfy the SDK.
7
- // baseURL is configurable for users running Ollama on a non-default port or host.
8
- // /v1 is always appended — user-provided baseURL should not include it.
9
- export class OllamaClient {
10
- constructor(model, baseURL) {
11
- this.provider = "ollama";
12
- this.model = model;
13
- const base = (baseURL ?? OLLAMA_DEFAULT_BASE_URL).replace(/\/+$/, "");
14
- this.inner = new OpenAIClient("ollama", // placeholder — Ollama doesn't validate the key
15
- model, `${base}/v1`);
16
- }
17
- summarize(request) {
18
- return this.inner.summarize(request);
19
- }
20
- validateConnection() {
21
- return this.inner.validateConnection();
22
- }
23
- }
@@ -1,9 +0,0 @@
1
- import type { LLMClient, LLMRequest, NodeSummaryOutput } from "./types.js";
2
- export declare class OpenRouterClient implements LLMClient {
3
- readonly provider: "openrouter";
4
- readonly model: string;
5
- private inner;
6
- constructor(apiKey: string, model: string);
7
- summarize(request: LLMRequest): Promise<NodeSummaryOutput>;
8
- validateConnection(): Promise<void>;
9
- }
@@ -1,19 +0,0 @@
1
- import { OpenAIClient } from "./openai.js";
2
- const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
3
- // ─── OpenRouterClient ─────────────────────────────────────────────────────────
4
- //
5
- // OpenRouter exposes an OpenAI-compatible API — no new logic needed.
6
- // Just wraps OpenAIClient with the correct baseURL and overrides provider. (OpenAI actually set the industry standard for the response of the LLMs through API.Everyone follows the OpenAI standard, Aside of course Anthropic, because its anthropic, who even go against pentagon.)
7
- export class OpenRouterClient {
8
- constructor(apiKey, model) {
9
- this.provider = "openrouter";
10
- this.model = model;
11
- this.inner = new OpenAIClient(apiKey, model, OPENROUTER_BASE_URL);
12
- }
13
- summarize(request) {
14
- return this.inner.summarize(request);
15
- }
16
- validateConnection() {
17
- return this.inner.validateConnection();
18
- }
19
- }