explainthisrepo 0.9.4 → 0.9.6
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/init.js +73 -18
- package/dist/providers/groq.d.ts +17 -0
- package/dist/providers/groq.js +70 -0
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/openrouter.d.ts +17 -0
- package/dist/providers/openrouter.js +72 -0
- package/dist/providers/registry.js +2 -0
- package/package.json +1 -1
package/dist/init.js
CHANGED
|
@@ -7,6 +7,8 @@ const PROVIDERS = {
|
|
|
7
7
|
"2": "openai",
|
|
8
8
|
"3": "ollama",
|
|
9
9
|
"4": "anthropic",
|
|
10
|
+
"5": "groq",
|
|
11
|
+
"6": "openrouter",
|
|
10
12
|
};
|
|
11
13
|
export async function runInit() {
|
|
12
14
|
const err = process.stderr;
|
|
@@ -18,7 +20,7 @@ export async function runInit() {
|
|
|
18
20
|
"[llm]",
|
|
19
21
|
`provider = "${provider}"`,
|
|
20
22
|
"",
|
|
21
|
-
`[providers.${provider}]
|
|
23
|
+
`[providers.${provider}]`,
|
|
22
24
|
];
|
|
23
25
|
for (const [k, v] of Object.entries(providerConfig)) {
|
|
24
26
|
lines.push(`${k} = "${v}"`);
|
|
@@ -43,7 +45,9 @@ async function promptProvider() {
|
|
|
43
45
|
err.write(" 1) Gemini\n");
|
|
44
46
|
err.write(" 2) OpenAI\n");
|
|
45
47
|
err.write(" 3) Ollama (local)\n");
|
|
46
|
-
err.write(" 4) Anthropic\n");
|
|
48
|
+
err.write(" 4) Anthropic (Claude)\n");
|
|
49
|
+
err.write(" 5) Groq\n");
|
|
50
|
+
err.write(" 6) OpenRouter\n");
|
|
47
51
|
const choice = (await prompt("> ")).trim();
|
|
48
52
|
const provider = PROVIDERS[choice];
|
|
49
53
|
if (!provider) {
|
|
@@ -54,36 +58,87 @@ async function promptProvider() {
|
|
|
54
58
|
async function promptProviderConfig(provider) {
|
|
55
59
|
if (provider === "gemini") {
|
|
56
60
|
const key = (await promptHidden("Gemini API key: ")).trim();
|
|
57
|
-
if (!key)
|
|
61
|
+
if (!key)
|
|
58
62
|
throw new Error("API key cannot be empty");
|
|
59
|
-
}
|
|
60
63
|
return { api_key: key };
|
|
61
64
|
}
|
|
62
65
|
if (provider === "openai") {
|
|
63
66
|
const key = (await promptHidden("OpenAI API key: ")).trim();
|
|
64
|
-
if (!key)
|
|
67
|
+
if (!key)
|
|
68
|
+
throw new Error("API key cannot be empty");
|
|
69
|
+
return { api_key: key };
|
|
70
|
+
}
|
|
71
|
+
if (provider === "anthropic") {
|
|
72
|
+
const key = (await promptHidden("Anthropic (Claude) API key: ")).trim();
|
|
73
|
+
if (!key)
|
|
65
74
|
throw new Error("API key cannot be empty");
|
|
66
|
-
}
|
|
67
75
|
return { api_key: key };
|
|
68
76
|
}
|
|
69
77
|
if (provider === "ollama") {
|
|
70
|
-
const model = (await prompt("Ollama model (e.g. llama3, glm-5:cloud): ")).trim();
|
|
71
|
-
if (!model)
|
|
78
|
+
const model = (await prompt("Ollama model (e.g. llama3, glm-5:cloud, gemma3:4b): ")).trim();
|
|
79
|
+
if (!model)
|
|
72
80
|
throw new Error("Model cannot be empty");
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
const host = (await prompt("Ollama host [http://localhost:11434]: ")).trim() ||
|
|
82
|
+
"http://localhost:11434";
|
|
83
|
+
return { model, host };
|
|
84
|
+
}
|
|
85
|
+
if (provider === "groq") {
|
|
86
|
+
const key = (await promptHidden("Groq API key: ")).trim();
|
|
87
|
+
if (!key)
|
|
88
|
+
throw new Error("API key cannot be empty");
|
|
89
|
+
const err = process.stderr;
|
|
90
|
+
err.write(chalk.bold("Select Groq model:\n"));
|
|
91
|
+
err.write(" 1) llama3-70b-8192\n");
|
|
92
|
+
err.write(" 2) mixtral-8x7b\n");
|
|
93
|
+
err.write(" 3) deepseek-r1-distill-llama-70b\n");
|
|
94
|
+
const choice = (await prompt("> ")).trim();
|
|
95
|
+
const modelMap = {
|
|
96
|
+
"1": "llama3-70b-8192",
|
|
97
|
+
"2": "mixtral-8x7b",
|
|
98
|
+
"3": "deepseek-r1-distill-llama-70b",
|
|
99
|
+
};
|
|
100
|
+
const model = modelMap[choice];
|
|
101
|
+
if (!model)
|
|
102
|
+
throw new Error("Invalid model selection");
|
|
76
103
|
return {
|
|
104
|
+
api_key: key,
|
|
77
105
|
model,
|
|
78
|
-
host
|
|
79
106
|
};
|
|
80
107
|
}
|
|
81
|
-
if (provider === "
|
|
82
|
-
const key = (await promptHidden("
|
|
83
|
-
if (!key)
|
|
108
|
+
if (provider === "openrouter") {
|
|
109
|
+
const key = (await promptHidden("OpenRouter API key: ")).trim();
|
|
110
|
+
if (!key)
|
|
84
111
|
throw new Error("API key cannot be empty");
|
|
112
|
+
const err = process.stderr;
|
|
113
|
+
err.write(chalk.bold("Select OpenRouter model:\n"));
|
|
114
|
+
err.write(" 1) openai/gpt-4o (balanced)\n");
|
|
115
|
+
err.write(" 2) anthropic/claude-3.5-sonnet (reasoning)\n");
|
|
116
|
+
err.write(" 3) meta-llama/llama-3-70b-instruct (open)\n");
|
|
117
|
+
err.write(" 4) deepseek/deepseek-chat (cheap/fast)\n");
|
|
118
|
+
err.write(" 5) Enter model manually\n");
|
|
119
|
+
const choice = (await prompt("> ")).trim();
|
|
120
|
+
const modelMap = {
|
|
121
|
+
"1": "openai/gpt-4o",
|
|
122
|
+
"2": "anthropic/claude-3.5-sonnet",
|
|
123
|
+
"3": "meta-llama/llama-3-70b-instruct",
|
|
124
|
+
"4": "deepseek/deepseek-chat",
|
|
125
|
+
};
|
|
126
|
+
let model;
|
|
127
|
+
if (choice === "5") {
|
|
128
|
+
model = (await prompt("Enter model (provider/model): ")).trim();
|
|
85
129
|
}
|
|
86
|
-
|
|
130
|
+
else {
|
|
131
|
+
model = modelMap[choice];
|
|
132
|
+
if (!model)
|
|
133
|
+
throw new Error("Invalid model selection");
|
|
134
|
+
}
|
|
135
|
+
if (!model || !model.trim()) {
|
|
136
|
+
throw new Error("Model cannot be empty");
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
api_key: key,
|
|
140
|
+
model,
|
|
141
|
+
};
|
|
87
142
|
}
|
|
88
143
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
89
144
|
}
|
|
@@ -91,7 +146,7 @@ function prompt(label) {
|
|
|
91
146
|
const rl = readline.createInterface({
|
|
92
147
|
input: process.stdin,
|
|
93
148
|
output: process.stderr,
|
|
94
|
-
terminal: true
|
|
149
|
+
terminal: true,
|
|
95
150
|
});
|
|
96
151
|
return new Promise((resolve) => {
|
|
97
152
|
rl.question(label, (answer) => {
|
|
@@ -107,7 +162,7 @@ function promptHidden(label) {
|
|
|
107
162
|
const rl = readline.createInterface({
|
|
108
163
|
input: process.stdin,
|
|
109
164
|
output: undefined,
|
|
110
|
-
terminal: true
|
|
165
|
+
terminal: true,
|
|
111
166
|
});
|
|
112
167
|
rl._writeToOutput = () => { };
|
|
113
168
|
rl.question("", (answer) => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { LLMProvider } from "./base.js";
|
|
2
|
+
type GroqConfig = {
|
|
3
|
+
api_key?: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare class GroqProvider implements LLMProvider {
|
|
7
|
+
name: string;
|
|
8
|
+
private apiKey;
|
|
9
|
+
private model;
|
|
10
|
+
private client;
|
|
11
|
+
constructor(config?: GroqConfig);
|
|
12
|
+
validateConfig(): void;
|
|
13
|
+
private getClient;
|
|
14
|
+
generate(prompt: string): Promise<string>;
|
|
15
|
+
doctor(): Promise<string[]>;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { LLMProviderError } from "./base.js";
|
|
2
|
+
export class GroqProvider {
|
|
3
|
+
name = "groq";
|
|
4
|
+
apiKey;
|
|
5
|
+
model;
|
|
6
|
+
client = null;
|
|
7
|
+
constructor(config = {}) {
|
|
8
|
+
this.apiKey = config.api_key ?? "";
|
|
9
|
+
this.model = config.model;
|
|
10
|
+
this.validateConfig();
|
|
11
|
+
}
|
|
12
|
+
validateConfig() {
|
|
13
|
+
if (!this.apiKey || !this.apiKey.trim()) {
|
|
14
|
+
throw new LLMProviderError("Groq provider requires an API key.\n" +
|
|
15
|
+
"Run `explainthisrepo init` or set providers.groq.api_key.");
|
|
16
|
+
}
|
|
17
|
+
if (!this.model || !String(this.model).trim()) {
|
|
18
|
+
throw new LLMProviderError("Groq provider requires a model.\n" +
|
|
19
|
+
"Set providers.groq.model (e.g. llama3-70b-8192, mixtral-8x7b, deepseek-r1-distill-llama-70b).");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
getClient() {
|
|
23
|
+
if (this.client) {
|
|
24
|
+
return this.client;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const Groq = require("groq");
|
|
28
|
+
this.client = new Groq({ apiKey: this.apiKey });
|
|
29
|
+
return this.client;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new LLMProviderError("Groq support is not installed.\n" +
|
|
33
|
+
"Install it with:\n" +
|
|
34
|
+
" npm install groq");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async generate(prompt) {
|
|
38
|
+
const client = this.getClient();
|
|
39
|
+
let response;
|
|
40
|
+
try {
|
|
41
|
+
response = await client.chat.completions.create({
|
|
42
|
+
model: this.model,
|
|
43
|
+
messages: [
|
|
44
|
+
{ role: "user", content: prompt }
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const message = err?.message ? String(err.message) : String(err);
|
|
50
|
+
throw new LLMProviderError(`Groq request failed: ${message}`);
|
|
51
|
+
}
|
|
52
|
+
let text = null;
|
|
53
|
+
try {
|
|
54
|
+
text = response?.choices?.[0]?.message?.content ?? null;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
text = null;
|
|
58
|
+
}
|
|
59
|
+
if (!text || !text.trim()) {
|
|
60
|
+
throw new LLMProviderError("Groq returned no text");
|
|
61
|
+
}
|
|
62
|
+
return text.trim();
|
|
63
|
+
}
|
|
64
|
+
async doctor() {
|
|
65
|
+
return [
|
|
66
|
+
`GROQ_API_KEY set: ${Boolean(this.apiKey)}`,
|
|
67
|
+
`model: ${this.model}`,
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LLMProvider, LLMProviderError } from "./base.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LLMProviderError } from "./base.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { LLMProvider } from "./base.js";
|
|
2
|
+
type OpenRouterConfig = {
|
|
3
|
+
api_key?: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare class OpenRouterProvider implements LLMProvider {
|
|
7
|
+
name: string;
|
|
8
|
+
private apiKey;
|
|
9
|
+
private model;
|
|
10
|
+
private client;
|
|
11
|
+
constructor(config?: OpenRouterConfig);
|
|
12
|
+
validateConfig(): void;
|
|
13
|
+
private getClient;
|
|
14
|
+
generate(prompt: string): Promise<string>;
|
|
15
|
+
doctor(): Promise<string[]>;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { LLMProviderError } from "./base.js";
|
|
2
|
+
const BASE_URL = "https://openrouter.ai/api/v1";
|
|
3
|
+
export class OpenRouterProvider {
|
|
4
|
+
name = "openrouter";
|
|
5
|
+
apiKey;
|
|
6
|
+
model;
|
|
7
|
+
client = null;
|
|
8
|
+
constructor(config = {}) {
|
|
9
|
+
this.apiKey = config.api_key ?? "";
|
|
10
|
+
this.model = config.model;
|
|
11
|
+
this.validateConfig();
|
|
12
|
+
}
|
|
13
|
+
validateConfig() {
|
|
14
|
+
if (!this.apiKey || !this.apiKey.trim()) {
|
|
15
|
+
throw new LLMProviderError("OpenRouter provider requires an API key.\n" +
|
|
16
|
+
"Run `explainthisrepo init` or set providers.openrouter.api_key.");
|
|
17
|
+
}
|
|
18
|
+
if (!this.model || !String(this.model).trim()) {
|
|
19
|
+
throw new LLMProviderError("OpenRouter provider requires a model.\n" +
|
|
20
|
+
"Set providers.openrouter.model (e.g. openai/gpt-4o, deepseek/deepseek-chat).");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
getClient() {
|
|
24
|
+
if (this.client) {
|
|
25
|
+
return this.client;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const { OpenAI } = require("openai");
|
|
29
|
+
this.client = new OpenAI({
|
|
30
|
+
apiKey: this.apiKey,
|
|
31
|
+
baseURL: BASE_URL,
|
|
32
|
+
});
|
|
33
|
+
return this.client;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new LLMProviderError("OpenRouter support is not installed.\n" +
|
|
37
|
+
"Install it with:\n" +
|
|
38
|
+
" npm install openai");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async generate(prompt) {
|
|
42
|
+
const client = this.getClient();
|
|
43
|
+
let response;
|
|
44
|
+
try {
|
|
45
|
+
response = await client.chat.completions.create({
|
|
46
|
+
model: this.model,
|
|
47
|
+
messages: [{ role: "user", content: prompt }],
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const message = err?.message ? String(err.message) : String(err);
|
|
52
|
+
throw new LLMProviderError(`OpenRouter request failed: ${message}`);
|
|
53
|
+
}
|
|
54
|
+
let text = null;
|
|
55
|
+
try {
|
|
56
|
+
text = response?.choices?.[0]?.message?.content ?? null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
text = null;
|
|
60
|
+
}
|
|
61
|
+
if (!text || !text.trim()) {
|
|
62
|
+
throw new LLMProviderError("OpenRouter returned no text");
|
|
63
|
+
}
|
|
64
|
+
return text.trim();
|
|
65
|
+
}
|
|
66
|
+
async doctor() {
|
|
67
|
+
return [
|
|
68
|
+
`OPENROUTER_API_KEY set: ${Boolean(this.apiKey)}`,
|
|
69
|
+
`model: ${this.model}`,
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -4,11 +4,13 @@ import { GeminiProvider } from "./gemini.js";
|
|
|
4
4
|
import { OpenAIProvider } from "./openai.js";
|
|
5
5
|
import { OllamaProvider } from "./ollama.js";
|
|
6
6
|
import { AnthropicProvider } from "./anthropic.js";
|
|
7
|
+
import { OpenRouterProvider } from "./openrouter.js";
|
|
7
8
|
const PROVIDER_REGISTRY = {
|
|
8
9
|
gemini: GeminiProvider,
|
|
9
10
|
openai: OpenAIProvider,
|
|
10
11
|
ollama: OllamaProvider,
|
|
11
12
|
anthropic: AnthropicProvider,
|
|
13
|
+
openrouter: OpenRouterProvider,
|
|
12
14
|
};
|
|
13
15
|
export function listProviders() {
|
|
14
16
|
return Object.keys(PROVIDER_REGISTRY);
|